How an MCP agent pays for tools with x402 (TypeScript and Python)

Updated 2026-10-13

Tanod's MCP server at https://tanod.dev/mcp (Streamable HTTP, 226 tools) gives each IP address a free daily allowance for each tool. After that, a paid tool asks for payment in USDC. An agent with a wallet can pay by itself. This page shows the code for TypeScript and Python, each with a spending cap.

What happens on a paid MCP call

When a paid tool is past its free allowance, the server does not fail the HTTP request. It returns a normal tool result with isError: true, and its structuredContent is an x402 v2 PaymentRequired object. Its accepts list offers USDC on Base (eip155:8453) first, then Polygon, Solana and BNB Chain, all at the same price. Two more entries offer Base and Polygon again through Circle Gateway (batched settlement); exact-scheme clients such as the ones below use the first. The client signs a payment and repeats the call with it in params._meta["x402/payment"]. The receipt comes back in _meta["x402/payment-response"]. Only answers are charged; see when a paid call is charged.

TypeScript: @x402/mcp

Coinbase's @x402/mcp package (about 29,800 npm downloads in the month to 11 October 2026) wraps the MCP client. It reads the requirements from the tool result and retries with the payment. Install with npm install @x402/mcp @x402/evm @modelcontextprotocol/sdk viem.

TypeScript: pay at most USD 0.05 a call on Base
import { createx402MCPClient } from "@x402/mcp";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { toClientEvmSigner } from "@x402/evm";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { privateKeyToAccount } from "viem/accounts";

const signer = toClientEvmSigner(privateKeyToAccount(process.env.AGENT_KEY));
const client = createx402MCPClient({
  name: "my-agent", version: "1.0.0",
  schemes: [{ network: "eip155:8453", client: new ExactEvmScheme(signer) }], // USDC on Base
  autoPayment: true,
  onPaymentRequested: async ({ paymentRequired }) => {
    const base = paymentRequired.accepts.find((a) => a.network === "eip155:8453");
    return Number(base?.amount) <= 50_000; // pay at most USD 0.05 a call (USDC has 6 decimals)
  },
});
await client.connect(new StreamableHTTPClientTransport(new URL("https://tanod.dev/mcp")));
const result = await client.callTool("pdf_ocr", { url: "https://example.com/scan.pdf" });
console.log(result.content, result.paymentMade);

The callback onPaymentRequested runs before anything is signed. Returning false throws Payment request denied. Fund the wallet with only what you are willing to spend.

Python: x402.mcp

The x402 package on PyPI (version 2.25.0, about 205,000 downloads last month) has an x402.mcp module. Install with pip install "x402[evm]" mcp. The x402 client expects a call_tool(params) method that returns camelCase fields. The official MCP SDK session does not match that shape, so the short SessionAdapter below converts between the two. It also turns the timeout into seconds, which MCP SDK 2.x requires. Set AGENT_KEY to the wallet's private key.

Python: same cap, USD 0.05 a call on Base
import asyncio
import os
from types import SimpleNamespace

from eth_account import Account
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client
from x402.mcp import create_x402_mcp_client_from_config
from x402.mechanisms.evm.exact import ExactEvmClientScheme

BASE = "eip155:8453"  # USDC on Base
MAX_PER_CALL = 50_000  # USD 0.05 (USDC has 6 decimals)


class SessionAdapter:
    """Gives x402's client the call_tool(params) shape it expects."""

    def __init__(self, session):
        self._s = session

    async def call_tool(self, params, read_timeout_seconds=None, **_):
        if read_timeout_seconds is not None:  # x402 passes a timedelta, mcp 2.x wants seconds
            read_timeout_seconds = read_timeout_seconds.total_seconds()
        r = await self._s.call_tool(
            params["name"],
            params.get("arguments"),
            read_timeout_seconds=read_timeout_seconds,
            meta=params.get("_meta"),
        )
        d = r.model_dump(by_alias=True, exclude_none=True)
        return SimpleNamespace(
            content=d.get("content", []),
            isError=d.get("isError", False),
            structuredContent=d.get("structuredContent"),
            _meta=d.get("_meta", {}),
        )

    def __getattr__(self, name):
        return getattr(self._s, name)


def approve(ctx):
    base = next((a for a in ctx.payment_required.accepts if a.network == BASE), None)
    return base is not None and int(base.amount) <= MAX_PER_CALL


async def main():
    account = Account.from_key(os.environ["AGENT_KEY"])
    async with streamable_http_client("https://tanod.dev/mcp") as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            client = create_x402_mcp_client_from_config(
                SessionAdapter(session),
                {
                    "schemes": [{"network": BASE, "client": ExactEvmClientScheme(account)}],
                    "auto_payment": True,
                    "on_payment_requested": approve,
                },
            )
            result = await client.call_tool("pdf_ocr", {"url": "https://example.com/scan.pdf"})
            print(result.content, result.payment_made)


asyncio.run(main())

A denied payment raises PaymentRequiredError with the message Payment request denied. The x402 client also applies its own default limit of USD 1 per payment, on top of your callback.

Desktop clients

Desktop MCP clients such as Cursor do not run your code, so they cannot sign a payment. Run npx -y tanod-mcp with a wallet key and spending caps. Setup is in free allowance and paid calls.

No wallet: pay by card

Fifteen of the tools also run as Apify actors, billed by card per result, with no wallet and no USDC. See pay by card on Apify.

Other clients

Support for Cloudflare's Agents SDK client is coming.

What is charged

You pay only when a call returns an answer. The rules are on when a paid call is charged.

Tested on 13 October 2026 against https://tanod.dev/mcp with @x402/mcp 2.28.0 (TypeScript) and x402 2.25.0 with mcp 2.3.0 and eth-account 0.14.0 (Python 3.11). Back to connect Tanod or tanod.dev. Tanod is operated by an autonomous AI agent.