# SwapWallet MCP server SwapWallet is a custodial Iranian crypto exchange (https://swapwallet.app). Its MCP server lets an AI assistant work with a SwapWallet user's own account: read balances, deposit addresses, profit and loss and transaction history, and *request* swaps, transfers and withdrawals. No tool moves money by itself: every money-moving request is priced, sent to the user's Telegram and executed only after the user taps Approve there. - Endpoint: https://swapwallet.app/mcp - Transport: Streamable HTTP, JSON responses only. POST one JSON-RPC 2.0 request, get one JSON response. Stateless: no Mcp-Session-Id, no SSE stream (GET returns 405), no JSON-RPC batches. - Protocol versions: 2025-06-18 (default), 2025-03-26, 2024-11-05. - Methods: initialize, ping, tools/list, tools/call. Capabilities: tools only (no resources, no prompts). - Server info: name "swapwallet", version "1.0.0". - Who can use it: any SwapWallet user, from any MCP client that supports remote servers with OAuth. There is no API-key mode; the REST API's API keys are not accepted here. ## Connecting Add https://swapwallet.app/mcp as a remote (HTTP) MCP server; the client handles OAuth on first use. - Claude (web, desktop): Settings > Connectors > Add custom connector, URL https://swapwallet.app/mcp - Claude Code: `claude mcp add --transport http swapwallet https://swapwallet.app/mcp` - Cursor and other JSON-configured clients: `{"mcpServers": {"swapwallet": {"url": "https://swapwallet.app/mcp"}}}` ## Authentication (OAuth 2.1) A request without a valid token gets 401 with `WWW-Authenticate: Bearer resource_metadata="https://swapwallet.app/.well-known/oauth-protected-resource"`. - Protected resource metadata: https://swapwallet.app/.well-known/oauth-protected-resource (resource: https://swapwallet.app/mcp) - Authorization server metadata: https://swapwallet.app/.well-known/oauth-authorization-server (issuer: https://swapwallet.app) - Dynamic client registration (RFC 7591): POST https://swapwallet.app/oauth/register with client_name and redirect_uris. Returns a public client_id (no secret). Redirect URIs must be https, http on a loopback host, or a custom app scheme. - Authorization: GET https://swapwallet.app/oauth/authorize, response_type=code, PKCE required (code_challenge_method=S256). - Token: POST https://swapwallet.app/oauth/token, grant types authorization_code and refresh_token, token_endpoint_auth_method none. - Revocation (RFC 7009): POST https://swapwallet.app/oauth/revoke. Revoking a grant also rejects its pending requests. - Lifetimes: authorization code 5 minutes (single use); access token 1 hour; refresh token until the grant is revoked. The user signs in on SwapWallet's consent page with their phone number and an SMS code, reviews the requested scopes, can untick any of them, and approves. The page is in Persian or English depending on the browser language. ### Scopes | Scope | Grants | |---|---| | wallet:read | balances, account info, deposit addresses | | portfolio:read | profit and loss | | transactions:read | transaction history | | contacts:read | saved contacts and verified IBANs | | withdraw:request | request crypto and toman withdrawals (user approves in Telegram) | | transfer:request | request transfers to saved SwapWallet contacts (user approves in Telegram) | | swap:request | request swaps between assets (user approves in Telegram) | A client that requests no scope asks for all of them. `withdraw:execute` is listed but can never be granted: no scope lets an agent move funds without the user's approval. tools/list returns only the tools the granted scopes allow, and scopes are checked again on every call. ## Tools Read tools answer directly. | Tool | Scope | Arguments | |---|---|---| | get_total_balance | wallet:read | none | | get_balance | wallet:read | tokens (array, required) | | get_user_info | wallet:read | none; returns profile, user level, limits and KYC status | | get_deposit_address | wallet:read | token, network. Creates the address if the user has none yet on that network. | | get_ibans | contacts:read | none; the user's verified bank accounts (IBANs) | | get_contacts | contacts:read | page (0-based, 10 per page) | | get_profit_and_loss | portfolio:read | tokens (array, optional) | | get_transactions | transactions:read | limit (1-20, default 5), types, tokens, startDate, endDate (ISO-8601) | | get_action_request | any | requestId (integer) | Approval tools create a request and return at once with status PENDING: | Tool | Scope | Arguments | |---|---|---| | request_swap | swap:request | sourceToken, destinationToken, amount | | request_transfer | transfer:request | token, amount, recipient (a saved contact: phone, username or id) | | request_crypto_withdraw | withdraw:request | token, network, amount, address, memo (optional) | | request_fiat_withdraw | withdraw:request | amount (toman), iban (one returned by get_ibans) | Amounts are decimal strings. token and network are enums; read the exact values from the tools/list input schemas. A tool error comes back as a normal result with isError: true and an English message. ## The approval flow 1. The agent calls a request_* tool. SwapWallet prices the action and sends a preview with Approve and Cancel buttons to the user's Telegram (the SwapWallet bot). 2. The tool returns requestId, type, status (PENDING), approvalChannel ("telegram"), preview and expiresAt. 3. The user approves or cancels in Telegram. On approval the action is re-priced and executed. 4. The agent calls get_action_request with the requestId to learn the outcome. Statuses: PENDING, APPROVED (briefly, while executing), EXECUTED (includes a supportCode), REJECTED, EXPIRED, FAILED (includes a failureReason). A request not approved within 15 minutes expires. get_action_request only returns requests made under the same OAuth grant. Conditions: - The user's SwapWallet account must be linked to Telegram, or request_* tools fail with MCP_TELEGRAM_NOT_LINKED. - If the user has two-factor authentication on, request_crypto_withdraw and request_transfer fail with MCP_TWO_FACTOR_ACTIVE; the user has to make those in the SwapWallet app. Swaps and toman withdrawals are not affected. - A crypto withdrawal to an address that belongs to another SwapWallet user becomes an internal transfer request. ## Rules for agents - Never tell the user a swap, transfer or withdrawal has happened until get_action_request reports EXECUTED. PENDING means "waiting for your approval in Telegram", nothing more. - After a request_* call, tell the user to open Telegram and approve or cancel it there, and mention that it expires in 15 minutes. - Show the preview (amounts, fees, destination) before the user approves; do not invent prices or fees. - For crypto withdrawals, confirm the network and address with the user; a wrong network or address can lose funds. - For balances, prices and history, call the tools; do not answer from memory. ## Discovery - MCP server card: https://swapwallet.app/.well-known/mcp/server-card.json - Official MCP Registry: app.swapwallet/mcp (https://registry.modelcontextprotocol.io) - Agentic Resource Discovery (https://agenticresourcediscovery.org/spec/): manifest at https://swapwallet.app/.well-known/ard.json (entry urn:air:swapwallet.app:mcp:swapwallet); ARD registry at https://swapwallet.app/ard with POST /ard/search, POST /ard/explore and GET /ard/agents. ## More - REST API reference: https://docs.swapwallet.app - Fees: https://swapwallet.app/fee · Help center: https://swapwallet.app/support · Security policy: https://swapwallet.app/security - Report a vulnerability in the MCP server or OAuth flow: https://t.me/SadeghHayeri