feat(mcp): cut over to the SDK envelope and declare the two missing conformance fields - #9681
Merged
Merged
Conversation
…onformance fields
The SDK envelope has served all production traffic since 2026-08-06 12:15 UTC,
with parity verified live across 12 protocol cases and 95/98 zero-argument
tools. The flag has done its job. Keeping it means keeping a second envelope
nobody exercises, which is how the untested path becomes the one that breaks,
so mcpSdkEnvelopeEnabled and its branch are removed.
The malformed-input branch stays, and is not a rollback. The SDK's transport
answers 400 -32700 Parse error for the whole request, which is wrong three
ways: -32700 is reserved for JSON that did not parse (-32600 is Invalid
Request), a JSON-RPC error belongs inside a 200, and for a mixed batch the SDK
drops the valid members outright. That last one is data loss, so malformed
input is answered by dispatchMessage directly and always will be.
Two MCP 2025-11-25 fields were never emitted:
Tool.execution.taskSupport declared "forbidden" on every tool -- this
server registers no task store, and absent the
declaration a client learns that by attempting a
task-augmented call and failing.
Implementation.websiteUrl both on serverInfo, both what a client renders in
Implementation.icons a server picker. Icons point at real published
assets on our own origin.
Icons are server-level only. The spec allows them per tool, but all 224 would
carry the identical set -- roughly 45 KB added to a tools/list every client
holds in context, for nothing the handshake did not already say. Same
reasoning that refused tools/list pagination in #9648, and asserted so the
decision is explicit rather than an omission someone later "fixes".
Adding websiteUrl turned up three copies of the site origin (src/feeds.ts,
src/subnet-news.ts, and the icons wanted a fourth); collapsed into SITE_ORIGIN
in src/contracts.ts beside PRIMARY_DOMAIN.
The parity harness becomes a contract test: with one implementation left there
is nothing to diff against, so every expectation is now the literal response a
caller receives. That is the stronger form regardless -- a comparison passes
happily when both sides regress together, which initialize was caught doing
during the migration.
Closes #9680
Closes #9647
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
metagraphed-registry-sync-api | 27b79d5 | Aug 06 2026, 12:39 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
metagraphed-data-api | 27b79d5 | Aug 06 2026, 12:38 PM |
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three things on the MCP surface, all found while wiring #9677.
1. Cut over to the SDK envelope — no flag, no second path
The SDK envelope has served all production traffic since 2026-08-06 12:15 UTC, with parity verified live across 12 protocol cases and 95/98 zero-argument tools. The flag has done its job; keeping it means keeping a second envelope nobody exercises, which is how the untested path becomes the one that breaks.
mcpSdkEnvelopeEnabledand its branch are gone. Every well-formed request is served by the SDK.The malformed-input branch stays, permanently — it is not a rollback. The SDK's transport answers
400 -32700 Parse errorfor the whole request, which is wrong three ways:-32600Invalid Request-32700Parse error — reserved for JSON that didn't parse200— a JSON-RPC error is a successful HTTP exchange400, which also starts firing the #9639 refusal eventThe third is data loss, so malformed input is answered by
dispatchMessagedirectly and always will be.2.
Tool.execution.taskSupportwas never emittedMCP 2025-11-25 defines it as
optional/required/forbidden. This server registers no task store, so a task-augmented call cannot be honoured — and absent the declaration a client discovers that by attempting one and failing. Declared"forbidden"on every tool, emitted once atlistToolDefinitionsso the next tool added carries it without anyone remembering.3.
Implementation.websiteUrlandImplementation.iconswere never emittedBoth are
serverInfofields a client renders in a server picker. Now declared, pointing at real published assets (favicon.svg,android-chrome-192x192.png,android-chrome-512x512.png) on our own origin — verified reachable, and asserted same-origin so a third-party host can't creep in.Server-level only, deliberately. The spec allows
iconsper tool, but all 224 would carry the identical set — roughly 45 KB added to atools/listevery client holds in context, for nothing the handshake didn't already say. Same reasoning that refusedtools/listpagination in #9648. Asserted, so it reads as a decision rather than an omission someone later "fixes".Adding
websiteUrlturned up three copies of the site origin (src/feeds.ts,src/subnet-news.ts, and the icons wanted a fourth). Collapsed intoSITE_ORIGINinsrc/contracts.tsbesidePRIMARY_DOMAIN.The parity harness becomes a contract test
With one implementation left there is nothing to diff against, so
tests/mcp-sdk-parity.test.ts→tests/mcp-envelope-contract.test.ts, and every expectation is now the literal response a caller receives. That's the stronger form regardless: a comparison passes happily when both sides regress together — whichinitializewas caught doing during the migration.The malformed-input cases still exercise the non-SDK branch. That's not legacy coverage; the branch is permanent.
Verification
A pre-existing allowlist in
tests/mcp-server.test.tscaughtexecutionas an unpublished key on first run — working exactly as designed; added there with the reason.Closes #9680
Closes #9647