Skip to main content
Glama

x402 MCP tool demo — a real 402, a real rejection, zero funds at risk

A minimal demo of the x402 payment protocol protecting an MCP-style tool endpoint, run against the real x402==2.16.0 Python package and its default public facilitator (https://x402.org/facilitator) on Base Sepolia testnet. Nothing here is hand-written protocol JSON — every response in captured_responses/ came out of the library, unedited.

Companion to: How to Charge for an MCP Tool: A Working x402 Endpoint in Under an Hour.

What's here

File

What it does

server.py

FastAPI endpoint (POST /mcp/tools/vendor_audit) gated by x402ResourceServer + the EVM exact scheme, priced at $0.01 USDC on Base Sepolia

gen_wallets.py

Generates two throwaway EOAs — a receiving address and a paying address. Zero funds, testnet only, never reuse these keys.

client_probe.py

Acts as the paying agent: gets the real 402, signs a real payment payload with the zero-balance account, and shows the facilitator's real rejection

captured_responses/

The two response bodies this produced, verbatim

Related MCP server: 402-mcp

Run it

pip install -r requirements.txt
python gen_wallets.py          # writes wallets.json (gitignored) -- two fresh, empty keys
python server.py               # starts on 127.0.0.1:8402
python client_probe.py         # in a second terminal

What you'll see

Round 1 — no payment. A 402 with a payment-required header (base64 JSON): scheme, network, asset contract, amount in the token's smallest unit, and the address to pay.

Round 2 — a real signed payment, zero balance. The client builds and signs an actual EIP-3009 authorization with the throwaway key, attaches it as X-PAYMENT, and the facilitator checks it against the real Base Sepolia chain state. It comes back:

{ "error": "invalid_exact_evm_insufficient_balance" }

That string is the facilitator's real error code, not a guess — it is what you get when the protocol works exactly as designed and the payer simply doesn't have the money. Fund the agent_address with Base Sepolia testnet USDC (any faucet) to see round 2 return 200 with a payment-response settlement receipt instead.

Why the manual round trip in client_probe.py

x402 2.16.0 ships PaymentRoundTripper, but it's a callback (handle_response(...)) meant to be wired into a custom httpx/requests transport — it is not a drop-in transport by itself, despite what the docstring implies. client_probe.py calls the same three primitives it calls internally (get_payment_required_responsecreate_payment_payloadencode_payment_signature_header) directly, which is fewer moving parts for a demo and produces identical wire output.

Safety notes

  • gen_wallets.py creates keys with eth_account.Account.create() — cryptographically random, never funded, never touching mainnet in this demo.

  • wallets.json is gitignored. wallets.example.json shows the shape with a well-known burn address so the repo is runnable from a clean clone without secrets in git history.

  • The facilitator URL, network, and asset contract are the library's own testnet defaults — nothing here talks to Base mainnet or moves real value.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Thinkflow-ro/x402-mcp-demo'

If you have feedback or need assistance with the MCP directory API, please join our Discord server