WalletConnect MCP HTTP API v0.6.0

The same tools the MCP server offers, as JSON routes for agents and backends that do not speak MCP. Machine-readable description: /api/v1/openapi.json.

Getting a token

OAuth 2.1 with PKCE on this origin. A token is bound to the resource https://mcp.walletconnect.com/api; an MCP token does not work here, nor the other way round. Send it as Authorization: Bearer …. One thing no integration can skip: a person opens the consent page in a browser and approves in their wallet. There is no API key.

1. Discovery

A call without a token answers 401 and names the resource metadata, which names the authorization server, which lists its endpoints.

GET https://mcp.walletconnect.com/api/v1
→ 401  WWW-Authenticate: Bearer realm="OAuth", resource_metadata="https://mcp.walletconnect.com/.well-known/oauth-protected-resource/api"

GET https://mcp.walletconnect.com/.well-known/oauth-protected-resource/api
→ { "resource": "https://mcp.walletconnect.com/api", "authorization_servers": ["https://mcp.walletconnect.com"] }

GET https://mcp.walletconnect.com/.well-known/oauth-authorization-server
→ { "authorization_endpoint": "https://mcp.walletconnect.com/authorize", "token_endpoint": "https://mcp.walletconnect.com/token",
    "registration_endpoint": "https://mcp.walletconnect.com/register", "code_challenge_methods_supported": ["S256"], … }

2. A client identity

POST https://mcp.walletconnect.com/register
{ "client_name": "my-agent", "redirect_uris": ["http://localhost:9999/cb"],
  "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "token_endpoint_auth_method": "none" }
→ { "client_id": "…", … }

3. The authorization request

Open this in the user's browser. code_challenge is the base64url SHA-256 of a random code_verifier you keep. resource picks the audience; a request that omits it gets the API.

GET https://mcp.walletconnect.com/authorize?response_type=code&client_id=…&redirect_uri=…&state=…
    &code_challenge=…&code_challenge_method=S256
    &scope=wallet:read wallet:write
    &resource=https://mcp.walletconnect.com/api

The consent page is where the wallet comes in. The browser gets a signed cookie that is the user's identity, a new one if it has none. The page shows a WalletConnect QR; the user scans it with their wallet, and that session attaches to the user. Allow sends the browser back to your callback with code and state. A browser that already paired a wallet for an earlier app skips the QR, so one wallet serves every app the user authorizes. The user may also skip the wallet; see step 6.

A client that creates the pairing itself, a wallet for one, adds connect=skip to the request. The consent page then shows the decision alone and starts no pairing; Allow returns to your callback at once, and you pair through POST /api/v1/wallet/pairings with the token (step 6).

4. The code exchange

POST https://mcp.walletconnect.com/token
grant_type=authorization_code&code=…&redirect_uri=…&client_id=…&code_verifier=…&resource=https://mcp.walletconnect.com/api
(a confidential client adds client_secret, or uses HTTP basic auth)

→ { "access_token": "…", "token_type": "bearer", "expires_in": 3600, "refresh_token": "…",
    "scope": "wallet:read wallet:write", "resource": "https://mcp.walletconnect.com/api" }

5. Lifetimes and refresh

An access token lives an hour. POST /token with grant_type=refresh_token, the refresh token and the same resource returns a new pair; the refresh token rotates, and the previous one stays valid until its replacement is first used, so a lost response can be retried. A grant that keeps refreshing never expires; one left idle for thirty days does. Revoke at the token endpoint with token=… (RFC 7009).

6. If the wallet is not connected yet

GET /api/v1/wallet says so. POST /api/v1/wallet/pairings starts a pairing and returns the wc: URI, a QR as a PNG under images, a hosted connect page and deep links for common wallets; show one of them to the user, then GET /api/v1/wallet?wait_seconds=55 in a loop until connected is true. The pairing URI is a bearer secret: anyone who scans it pairs their wallet to this user. A wallet that is itself the client compares the session topics in GET /api/v1/wallet with its own, and passes new_session: true when none is its own.

Quickstart

examples/quickstart.mjs in the repository does all of the above from Node with no dependencies, then reads the portfolio and, with --send, makes a transfer through prepare, confirm and poll. scripts/api-flow.sh is the same flow in curl.

How a write works

POST /api/v1/actions/transfers or /actions/swaps prepares an action and sends nothing: the result decodes every step, carries the raw transaction bytes and plan.hash. Compare it with what the user asked, then POST /api/v1/actions/{id}/confirm with the hash and expect, your own restatement (the keys are in plan.expectKeys). The server sends the first step to the user's wallet only if both match. Then GET /api/v1/actions/{id}?wait_seconds=55 until the status is confirmed, rejected, expired, failed or cancelled. Each call waits server-side for a change, so poll in a loop without sleeping.

Shapes

Every success is { text, data, images? }: one line for a human or an agent, the tool's data, and images when the tool produced one (the pairing QR). GET and DELETE take arguments from the query string, POST from a JSON body; a path parameter is the tool argument of the same name. Errors are { error: { code, message, issues? } }: 400 invalid_request, 401 from the OAuth layer, 404 not_found, 409 conflict (no wallet connected, an open action in the way, an expired or mismatched plan), 422 blocked (caps, unlisted tokens or chains, bad amounts or addresses), 500 internal.

Routes

meta · Server info, the tool list and the generic call

wallet · Pairing and session

portfolio · Balances

actions · Prepare, confirm, poll and cancel

swaps · Quotes