DEV Community

Global Chat
Global Chat

Posted on

Shipping a paid MCP server with x402: what 11 probes taught us

Last week I pushed @globalchatadsapp/mcp-server with x402 v1.2.0 wired in. The idea was simple: return a payment quote inline in mcp.json so any agent reading the manifest already knows the price before it calls a tool. One fetch, full discovery, priced.

Then I watched the telemetry for a week. 11 PROBE events, 1 QUERY, 0 REGISTER. That's the kind of number that either means your funnel is leaking or your metric is lying. Turns out it was both.

How the x402 quote lands in mcp.json

The canonical x402 flow is a 402 response on the first call. That works, but it costs a round trip and most lightweight agents give up after one 4xx. Instead we embed the quote as a payment block alongside the tool definition:

{
  "tools": [
    {
      "name": "search_directory",
      "description": "Query the global-chat agent directory",
      "payment": {
        "protocol": "x402",
        "version": "1.2.0",
        "network": "base",
        "asset": "USDC",
        "amount": "0.10",
        "facilitator": "https://facilitator.coinbase.com",
        "recipient": "0x..."
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Facilitator is Coinbase's hosted one. Settlement is USDC on Base L2, so a 0.10 USDC call settles for sub-cent gas. An autonomous agent reading this manifest can decide to pay before it ever hits the tool endpoint. The 402 path still works for clients that want strict protocol conformance. Both roads lead home.

The funnel numbers, and why they're half wrong

Here is what the agent-funnel classifier reported for the last cycle:

Stage Count
DISCOVER 34
PROBE 11
QUERY 1
REGISTER 0

A 1/11 PROBE to QUERY ratio looked terrible. So I went and reproduced what a probing agent would actually do: GET /.well-known/agent-card.json, parse each advertised skill, call the MCP endpoint.

The MCP endpoint returned HTTP 406 Client must accept both application/json and text/event-stream for every request that didn't send both Accept values. That's technically MCP Streamable HTTP compliant on the strict read, but the spec allows a server to negotiate down when the client only asks for JSON. Every non-SDK probe was bouncing off a header check before it got near a tool call.

That's one bug. The second is messier. The probe counter was pulling in Discordbot, TelegramBot, Slackbot, WordPress, and plain iPhone Safari requests. None of those are agents. They are link previewers and humans pasting URLs. With them included, any social share inflates the PROBE bucket while the true-agent count sits flat. The ratio becomes noise.

The fixes

Three changes, shipped in parallel:

  1. /api/mcp POST now accepts application/json, */*, or a missing Accept header. SSE stays available for SDK clients that negotiate it. Everyone else gets JSON.
  2. Every skill in the agent card now declares an inputSchema. Previously a2a-crawl-domain advertised a POST endpoint but gave no hint that a domain field was required, so probing agents hit 400 on their first try and stopped.
  3. The funnel classifier now partitions link-preview bots and human browsers out of the PROBE bucket. They get counted under a new link-preview class that rolls up to DISCOVER but not PROBE. Unit test asserts that a fixture of 2 real agent UAs plus 3 link-preview bots plus 3 browsers yields PROBE=2, not 8.

What I'm watching next

The real question isn't whether PROBE to QUERY goes up. With the link-preview bots removed from the numerator and the 406 removed from the denominator, both numbers will shift. The cleaner signal is QUERY to REGISTER, because a QUERY means an agent successfully called a tool and REGISTER means it paid and wrote to the directory. That's the actual commerce path.

A synthetic trace script now runs the full probe sequence against production every cycle: fetch agent card, walk every skill, try every tool from the MCP manifest, record the status and any 402 challenge. If a future change breaks the advertised surface, CI catches it before the next funnel report lies to me.

If you're building an agent that consumes paid MCP servers, I'd love to hear how you're handling the x402 quote-inline vs 402-challenge tradeoff. The spec accepts both. The ergonomics are very different.

Top comments (0)