# I Put Trading Tools Behind MCP With Per-Call Pricing — and Fixed the One Bug That Breaks x402 for MCP Builders

> Source: <https://dev.to/scriptmasterlabs01/i-put-trading-tools-behind-mcp-with-per-call-pricing-and-fixed-the-one-bug-that-breaks-x402-for-1956>
> Published: 2026-10-02 19:58:46+00:00

I shipped a paid MCP gateway today: `tools/list` is free, `tools/call` costs $0.05 in USDC per call, settled via x402 v2 on Base. It's live right now — no signup, no API keys. The payment is the credential.

But the real story isn't the pricing. It's the implementation detail that every MCP builder attempting x402 will hit — and that quietly breaks the entire payment flow if you miss it.

Here's what happens when you bolt x402 onto an MCP server the naive way. The agent calls `tools/call` without payment. Your server returns a JSON-RPC error: `-32000, Payment Required`. Done, right?

Wrong. **MCP clients swallow JSON-RPC errors before the model ever sees them.** The client sees an error code, the model sees "tool call failed," and the payment challenge — the `accepts` array, the price, the `payTo` — never reaches the agent. The agent can't pay because it never learned the price.

This is documented by anchor-x402 (18 paid x402 services, MIT-licensed), and I hit it myself. The fix is simple but non-obvious:

**Return the payment challenge as a successful result with `isError: true`, not as a JSON-RPC error.**

```
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [{
      "type": "text",
      "text": "Payment required: $0.05 USDC. Sign and retry with PAYMENT-SIGNATURE."
    }],
    "structuredContent": {
      "x402Version": 2,
      "accepts": [{
        "scheme": "exact",
        "network": "eip155:8453",
        "amount": "50000",
        "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
        "payTo": "0xc29185fa176357612f3194735753e520e91adc46"
      }]
    },
    "isError": true
  }
}
```

The agent model reads this, sees the price and the challenge, signs an EIP-3009 `transferWithAuthorization`, and retries. The flow works end-to-end. `isError: true` means the client passes it through to the model instead of eating it.

The second design decision I copied from anchor-x402: **`tools/list` and discovery are always free.** This is correct by construction — agents can't discover what they can't see. Charging for `tools/list` is a paywall on your own storefront.

The full pattern:

`tools/list` → free, always`tools/call` → paid, `isError: true` result on unpaid attempts`/.well-known/x402` → free, machine-readable manifest
Every byte of actual data sits behind a 402. Everything the agent needs to *decide* to buy is free. That's the whole distribution model for machine commerce.

The x402 MCP space is filling fast at the utility layer: converters, validators, formatters at $0.001–$0.02/call (anchor-x402, agent402). Real numbers from the wild: an MCPize author reports ~$8,500/mo net on a security-auditor server; self-hosted x402 did 3.3M transactions in 30 days at ~$0.46 average.

But **nobody does trading or telemetry MCP tools.** That's the lane I took:

`get_market_cycle`` scrape_url`
Both follow fail-closed commerce: bot-blocked pages, bad symbols, garbled output return no-charge errors. **Failed delivery never earns payment.** Firecrawl's charge-on-4xx is a cited complaint in the ecosystem; I went the other way.

```
# Free: list the tools
curl -X POST https://squeezeos-api.onrender.com/v1/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

# Paid: this returns the challenge as an isError result
curl -X POST https://squeezeos-api.onrender.com/v1/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
       "params":{"name":"get_market_cycle","arguments":{"symbol":"AAPL"}}}'
```

The manifest is at `https://squeezeos-api.onrender.com/.well-known/x402`. x402 v2, USDC on Base, CDP facilitator sponsors gas.

If you're putting x402 payments on your MCP server: **never return the challenge as a JSON-RPC error.** `isError: true` result, challenge in `structuredContent`. That's the difference between a payment flow that works and one that silently dies in the client.

Built by ScriptMasterLabs (service-disabled veteran-owned small business). Happy to answer questions about the x402 integration or the verify→execute→accept→settle flow.
