The demand gap in one line
On the paid /v1/intel/* surface over
30 days, we counted
21,230
external 402 challenges and
0
external settled paid calls.
A 402 challenge is not a purchase. It is the server saying
“this endpoint costs money — here is a payment offer in
the response body.” A caller either then pays and re-issues
the request with an X-PAYMENT
header, or leaves. On our surface for the 30-day window, the second
thing happened every single time. A 402 is a challenge,
never a buyer.
Who those 402 challenges actually came from
We broke the external 402 volume down by user-agent for the 30-day window. The top populations are all machine traffic, but none of them are agents trying to buy anything.
-
SEO backlink crawlers —
SemrushBot,MJ12bot, and similar backlink-index workers, hitting/v1/intel/*because our llms.txt, robots.txt, and sitemap.xml advertise those paths. They index the URL and the response fingerprint. They do not pay. -
AI-crawler paid-endpoint probes —
GPTBot,Amazonbot,ClaudeBot. These crawl the paid surface, log the 402 response, and move on. They do not carry a payments client. -
Competitor fingerprint scanners —
agentcount-probe,CryptoWatch-FastjsonHR, and a small set of long-tailGuzzleHttpand generic-browser scrapers. These are looking at the shape of our surface, not shopping on it. -
Generic-browser UAs — a bulk
Chrome/Safari on Windows/Macpopulation with no referrer and no session, consistent with datacenter scraping fleets.
Across the whole tail, we did not find a single 402-challenged
caller whose subsequent behaviour on our host looked like a purchase
attempt: no X-PAYMENT header on
retry, no follow-up hit on the challenged path, no request to
/v1/public/datasets, no
POST /v1/subscribe. The 402
challenge population and the paying population have an empty
intersection.
The MCP surface tells the same story, more sharply
Our /mcp server is free. It exposes four
read-only tools —
get_agent_summary,
get_coverage_stats,
get_leaderboards,
get_network_teaser — and it
is the intended free on-ramp: an agent uses these to satisfy itself
the coverage is real, then upgrades to the paid
/v1/intel/* for the specific
queries it cannot answer for free.
Over 30 days there were
92
distinct external MCP callers on
/mcp. Of those:
-
0
ever requested any paid or paywall-adjacent path on our host
(
/v1/intel/*,/v1/public/datasets*, or/.well-known/x402.json) at any status, in any window. - 80 appeared on exactly one UTC date and never returned.
- 12 returned on two or more distinct UTC dates — and every one of them matched the same 6-to-9-call footprint that the one-shot population did.
Tightening the join to a 15-minute intra-session window after each
tools/call did not change the
picture: no external MCP caller followed a tool response to the
paid host within 15 minutes, in either direction. The named callers
we can identify — Airia catalog sync, ToolRouter, an
mcp-schema-probe/0.1 bulk
enumerator co-located with Airia,
mcp-reputation-scanner,
elliot-grader-probe — are all
catalogs, graders, or reputation scanners cataloguing that the
tools respond, not agents using them to answer a user question.
The naming problem — what these numbers are NOT
Before we widen this into any category claim, three things it is important to name:
- A 402 challenge is a CHALLENGE. It is the server offering a price. It is never a buyer and never an attempted purchase. Interpretations that count 402s as demand are counting the sound of a doorbell as a party.
-
The ERC-8004 IdentityRegistry contains raw
Transfer-from-zero mints. That is not an agent count.
As of 2026-09-07 the on-chain
Ethereum mint total is
50,071; that number is
a registration count. Roughly
60.9% of
those mints have a populated
tokenURI, which is a strictly weaker property than “the agent is reachable, has capabilities, and is transacting.” Anyone quoting a raw mint total as an agent-economy TAM is quoting the arithmetic of a mint contract, not the size of a market. - Any AEO / answer-engine reach figure we report is a LOWER BOUND, derived from crawl-and-render passes that intentionally under-count silent success. We do not quote 1-day series from it because they are noise; the shape emerges over weeks.
Our own trust-filter pages disclose a per-publisher dedup cap of 50 (any single ERC-8004 publisher's mints above that count are capped in our indexed set, so a single-owner farm cannot inflate our numbers). Disclosing that cap IS the differentiation from a raw-registry mirror. We would rather report a small number honestly than a large one that a reviewer can fish apart.
What we could not measure — and why publishing the gap alongside the finding matters
Any single-operator measurement of a machine-traffic funnel has instrument defects. Naming them is a credibility precondition, not a caveat.
-
No MCP session identity. The
mcp_callstable has nosession_idcolumn, so we cannot definitively fold a caller'sinitialize → tools/list → tools/callinto a single JSON-RPC session. The “6-to-9 calls per IP is a scan” inference is population shape, not session evidence. -
mcp-schema-probe/0.1is not in the canonical scanner filter. This UA — 21 rows in the 30-day window, egressing from the same IPv6 that Airia's catalog sync uses — looks like a bulk enumerator but was not on the scanner list at report time. We flag rather than silently reclassify it; a future report will widen the filter. -
Smithery-gateway attribution is blind. Our
known-Smithery-egress IP set is empty (no confirmed egress
captured from a real tool call). If Smithery's hosted MCP gateway
ever funnels a real end-user agent through to our tools, it would
land on our logs as
python-httpxon Smithery's egress IP and be indistinguishable from Smithery's own catalog scanner. Getting one confirmed egress IP would close this. -
The
initializehandshake carries aclientInfo.namewe do not persist. MCP clients (Claude Desktop, Cursor, custom agents) declare themselves in the handshake; capturing that field intomcp_callsand joining on IP would let a future report name the client mix behind thepython-requestsUA blob rather than leaving it as one. - This is one operator. We are one API. The absence of paying agents on our surface is not, on its own, evidence about someone else's surface. If another live x402 endpoint is seeing a different picture, we would like to see the log-shape breakdown.
Category context, without turning this into a rails comparison
This is not a comparison piece for x402 vs AP2 vs MPP vs ACP. Those are payment rails; this article is not about how money moves. A separate piece can compare rails.
What the 30-day measurement lets us say plainly is that on this
endpoint, in this window, the volume of challenges is not
the same thing as the volume of demand. If an agent were
reaching this endpoint intending to buy, we would see: (a) a
payment client on retry, (b) a variable tool-call fingerprint
rather than an enumeration one, (c) sustained multi-day usage of
/v1/intel/* from the same IP. We
saw none of the three, from any of the
92
distinct external MCP callers, over 30 days.
The interesting question, if this shape holds on other operators' surfaces, is not “why aren't the agents paying.” It is: what is the missing primitive between an agent finding a service catalog and an agent choosing to spend? We think it is a trust filter — a way for the buyer-side agent to score whether the seller is real, whether it will deliver what it advertises, whether its reputation is populated by real work rather than by self-endorsement. That is the shape of the thing we are building. But this report is about what we measured, not what we think should follow from it.
Reproducing this report
The full evidence doc — every SQL and every filter — is
in the repo at
docs/evidence/0356-mcp-toolcall-composition.md.
The reproducer script
docs/evidence/0356_mcp_toolcall_composition.py
opens ~/.openclaw/audit_trail.db
read-only and takes no arguments. Test coverage in
smartcontractauditpro/tests/test_0356_mcp_toolcall_composition.py
pins every reject predicate. The six
funnel_30d.* counters that back
the numbers on this page are exposed by
/v1/public/stats under the same
canonical filter (task 0358); a stale value fails the served body
loudly rather than shipping a blank (task 0359).