Loomal

Add x402 payments to a Python MCP server

FastMCP's HTTP transport is ASGI underneath, which means payment gating is one middleware away. Return the 402, verify through a facilitator, run the tool.

A Python MCP server built on FastMCP can take per-call payments without touching a line of blockchain code. When you run FastMCP over its HTTP transport, you get a standard ASGI app — and ASGI middleware is the natural place to intercept requests, demand payment, and verify it before any tool executes.

The whole job is HTTP and JSON: respond 402 with payment requirements, accept a retried request carrying an X-PAYMENT header, and POST that header to a facilitator that handles signature verification and on-chain settlement.

Prerequisite: run over HTTP, not stdio

FastMCP defaults to stdio, which is a local pipe between client and subprocess — there is no HTTP exchange to gate. Switch to the HTTP transport (mcp.run(transport="http")) and host the server at a URL. Once requests arrive as POSTs, every tool call has a request/response cycle that can carry a 402 status and a payment header.

If your server is currently distributed as a pip package users run locally, this is the architectural fork: the paid version is a service you host, not a process they spawn.

Decide what costs money

Gate execution, not discovery. The initialize handshake and tools/list must succeed unpaid, or no client will learn what your server does. Inspect the JSON-RPC method in the request body and apply the payment check only to tools/call — and within that, only to the tools you intend to charge for. Prices are denominated in USDC base units (six decimals), so $0.01 is 10000.

The middleware

Here's the shape as Starlette-style ASGI middleware on FastMCP's http_app. No payment header: return 402 with the requirements. Header present: verify with the facilitator, then pass the request through.

x402_gate.py
import httpx
from starlette.responses import JSONResponse

REQUIREMENTS = {
    "scheme": "exact", "network": "base",
    "maxAmountRequired": "10000",  # $0.01 USDC (6 decimals)
    "payTo": "0xYourWalletAddress", "resource": "/mcp",
}

async def x402_gate(request, call_next):
    payment = request.headers.get("x-payment")
    if payment is None:
        return JSONResponse(
            {"x402Version": 1, "accepts": [REQUIREMENTS]}, status_code=402)
    async with httpx.AsyncClient() as client:
        r = await client.post(f"{FACILITATOR}/verify", json={
            "x402Version": 1, "paymentHeader": payment,
            "paymentRequirements": REQUIREMENTS,
        })
    if not r.json().get("isValid"):
        return JSONResponse({"error": "payment invalid"}, status_code=402)
    return await call_next(request)

What the facilitator does for you

The X-PAYMENT header contains the agent's signed USDC transfer authorization. The facilitator checks the signature, the amount, the asset, and the recipient against your stated requirements, then submits the transfer for settlement on Base — typically clearing in about two seconds. Your Python code only ever sees a boolean.

Because the verification happens before call_next, the tool handler never runs on an unpaid request. There's no invoicing, no dunning, and no chargeback path: settled means settled, and each payment yields an Ed25519-signed receipt.

Test the flow, then list it

Exercise the gate locally before going live: curl your /mcp endpoint with a tools/call body and confirm you get a 402 with the accepts payload, then run a funded test client against a testnet facilitator and confirm the retry succeeds. Once it behaves, list the server on Loomal so agents can find the URL and the price in one index query. Minimum price is $0.01 per call; the platform fee is 5% on settled transactions, currently waived.

FAQ

Do I need web3.py or any chain tooling?

No. The server's responsibilities are returning a 402 JSON payload and making one HTTP POST to a facilitator. Signature checking and USDC settlement on Base happen on the facilitator's side; your dependencies are an HTTP client like httpx and nothing else.

Can I keep some tools free on the same FastMCP server?

Yes. Parse the JSON-RPC body in the middleware and look at params.name when the method is tools/call — charge for the tools you choose and pass everything else through. Free discovery plus a free tier of tools is a common and sensible layout.

What happens if an agent sends an underpaying authorization?

Verification fails. The facilitator checks the authorized amount against maxAmountRequired in your requirements, and your middleware returns another 402. The tool never executes, so there's no scenario where work goes out the door unpaid.

Does this work with the official Python MCP SDK too?

Yes — the same pattern applies to any MCP server exposed as an ASGI app over Streamable HTTP. FastMCP just makes the HTTP hosting step short; the 402/verify/settle exchange is identical regardless of framework.

Gate it, price it, list it.

Connect your Python server and start settling USDC per call.

Open the Loomal console