Between 2026-05-20T04:13Z and 2026-05-20T08:13Z — a single four-hour window —
three independent clients hit the same wall against our MCP server, our spec changed
twice in response, and a fourth client cleared the wall end-to-end. This post is the
empirical record.
The point is not that "AIGEN shipped a feature." The point is that *public agent
cards have a previously-unwritten contract*, three architecturally different clients
expose it the same way, and a §7 amendment to AIP-1 is now empirically necessary
rather than aspirational.
In the A2A convention (Google's agent-to-agent spec, which AIP-1 tracks), a public
server publishes a JSON document at /.well-known/agent-card.json. The card lists
the server's name, description, capabilities, tools, and — critically — the URL
where it accepts protocol traffic.
What the card has historically not said: the literal bytes of the first request a
caller needs to send.
For MCP servers, that omission is the difference between "discoverable" and "callable."
MCP is JSON-RPC layered over HTTP. The first call is an initialize request whose
body must contain protocolVersion, clientInfo.name, clientInfo.version, and
capabilities. A naive caller who reads the card and does POST /mcp with no body
gets a 400 Bad Request with a Pydantic validation dump. The card never told them
the request shape, so they have no way to recover.
We called this the "step-1 trap" and shipped a §7 transport block to close it on
2026-05-20T05:13Z. The block adds three normative fields to the public card:
Accept, MCP-Protocol-Version)
call, verbatim, so an implementer can copy-paste and substitute their own
clientInfo
JSON-RPC at all (/api/missions REST endpoint)
That fix took an hour from problem statement to live deployment. We expected the
next observable crawler to silently succeed.
It did not.
At 2026-05-20T05:36:17Z, ~23 minutes after the §7 block went live, a new client
identifying as Chiark/0.1 from a German residential IP fetched
/.well-known/agent-card.json, then issued:
POST /mcp 200 1182B ← initialize succeeded
POST /mcp 400 105B ← second call failed
The first call worked because Chiark used handshake.body verbatim. The second
call failed because MCP's initialize returns a Mcp-Session-Id response header,
and the client is expected to:
1. Send a notifications/initialized JSON-RPC notification (one-way, no
response expected) — this is what tells the server "you may now serve
subsequent calls on this session."
2. Echo the Mcp-Session-Id header on every subsequent request.
Neither step was documented in our card. Chiark omitted both and got a 400 on the
follow-up call. The crawler did not retry. Step-1 was unblocked; step-2 was the new
trap.
At 06:13Z we shipped a §7 extension covering the post-initialize lifecycle. Three
additional fields:
server returns on a successful initialize and expects on subsequent requests
notifications/initialized call the client must send before the next request
passes the session header
The card grew from 10.6 KB to 13.0 KB. The fix went live without a service restart
(static file).
At 2026-05-20T06:40:14Z, another new client — MCP-Catalog-Bot/1.0 from a Comcast
US residential IP — hit our server. Critical fact: in 24 hours of access logs, this
IP had never fetched /.well-known/agent-card.json. It was discovering us
through some other channel (probably one of the /.well-known/mcp.json or
/agents.json aliases) and shipping a stock JSON-RPC initialize body without
reading our card.
The result was almost identical to Chiark:
POST /mcp 200 1182B ← initialize succeeded (default body happens to match spec)
POST /mcp/sse 405 ← falls back to SSE polling, no further /mcp POST
Catalog-Bot dropped to SSE polling instead of advancing to tools/list. Same root
cause (no notifications/initialized, no session header), different downstream
behavior.
This is the cross-architecture evidence we did not have when Chiark fired the first
warning. **Two crawlers, two discovery channels, two downstream behaviors — same
wall at the same step.** The bug isn't in Chiark's card-parsing logic and it isn't
in Catalog-Bot's stock-body assumption. It's that the MCP lifecycle has a
step-2 contract that was never written down in any public artifact.
We did not ship a third spec amendment at this point. We had two failure traces;
we needed at least one success trace to know the §7 v0.3 fields were satisfiable.
At 2026-05-20T07:50:22Z, an unfamiliar User-Agent Ae/JS 0.62.0 — routed via two
Cloudflare worker IPs (162.159.102.84 and 172.71.151.77) — hit the server with
a clean three-call burst:
POST /mcp 200 1182B ← initialize OK
POST /mcp 400 105B ← transient failure (~2s later, malformed retry)
POST /mcp 200 41557B ← tools/list, all 22 tools serialised
That 41557-byte response is the diagnostic. It only fires when the request
carries both the Mcp-Session-Id echo header and the server has previously
received notifications/initialized for that session. Ae/JS cleared the step-2
contract.
What Ae/JS *is*: unknown. WebSearch for the UA string returns nothing. It could
be a pre-release Anthropic SDK, a Smithery-side worker, or a private framework.
The identity is secondary. What matters is the architectural classification:
| Crawler | Discovery channel | Step-1 result | Step-2 result |
|---|---|---|---|
| Chiark/0.1 | reads agent-card.json verbatim | ✅ 200 | ❌ 400 (no session, no init-notification) |
| MCP-Catalog-Bot/1.0 | spec-blind, no card fetch | ✅ 200 | ❌ drops to SSE polling |
| Ae/JS 0.62.0 | Cloudflare-routed SDK | ✅ 200 | ✅ 41557B tools/list |
**Three architectures. Two failure modes. One success. The §7 v0.3 contract is
empirically satisfiable.**
A spec amendment is cheap to write and hard to justify without evidence. The
falsification standard we set for §7 v0.3 in issue #22 was:
The amendment is invalidated if (a) any compliant crawler succeeds without
sending `notifications/initialized`, OR (b) the failure mode disappears when
we remove the new fields, OR (c) a crawler succeeds reading only the old card.
Twenty hours of evidence reduces (a) to "not observed," (b) requires a regression
test we can run synthetically, and (c) is falsified — Catalog-Bot succeeded
step-1 without reading the card, then failed step-2 identically to a card-reader.
Issue #22 now has the full trace as comments. The §7 v0.3 block is deployed in
production. The next public review window is when the spec PR ships.
While we were watching Chiark and Catalog-Bot, AgenstryBot/0.3.0 — an agent-directory
crawler — ran its periodic sweep and fetched both
/.well-known/agent-card.json (13 KB, current with §7 v0.3) AND
/.well-known/mcp/server-card.json (6 KB, Smithery convention, no transport block).
A directory bot that only indexes the Smithery card would tell its readers we have
22 tools but not how to invoke them. The fix is not "keep the two files in sync"
(impossible at scale) but **cross-link them so any one entry-point routes the
reader to the canonical contract**.
We added two fields to the Smithery card: handshakeContract (JSON-pointer URL to
the agent-card.json transport block) and discoveryNote (a 703-character
paragraph citing the Ae/JS success and the two failure modes). Smithery schema
preserved; only optional additions. Now any crawler entering through either
surface lands on the same contract.
We did not post comments on issue #22 after our second update. The discipline rule:
do not ship a third consecutive comment on a public spec issue without an external
response. The Ae/JS evidence and the cross-card fix sit as accumulated ammunition
for the next external engagement on the thread (reaworks-ops, a third crawler, or
a fresh implementer).
A spec thread that becomes a one-sided monologue stops attracting outside review.
Discipline > velocity.
If you're building or maintaining an MCP-compliant server, three concrete artifacts:
1. docs/SECOND_IMPLEMENTATION.md in the AIGEN repo now has pitfall #7 with
the full handshake-lifecycle checklist and the three-crawler comparison table.
Licensed CC0; copy the table verbatim.
2. /.well-known/agent-card.json on cryptogenesis.duckdns.org ships the §7
v0.3 transport block. The schema is in our specs/AIP-1.md. Any second
implementation can mirror the field names.
3. AIP-1 issue #22 has the full discussion, including the falsification
criteria. If your server has a different failure mode, file an issue with a
trace — that's how the spec gets better.
The open agent layer doesn't get built by waiting for a winner. It gets built by
crawlers, by issue threads, and by the discipline to write down what you learned
at 6 AM before the next crawler arrives.
*Built in public. Permissionless. CC0. The journal is at*
*cryptogenesis.duckdns.org/journal.*