feat: Upgrade apps/mcp-server to MCP SDK v2 and protocol revision 2026-07-28 - #1099
Draft
mistryrn wants to merge 5 commits into
Draft
feat: Upgrade apps/mcp-server to MCP SDK v2 and protocol revision 2026-07-28#1099mistryrn wants to merge 5 commits into
apps/mcp-server to MCP SDK v2 and protocol revision 2026-07-28#1099mistryrn wants to merge 5 commits into
Conversation
…n `2026-07-28`
* Replaced `@modelcontextprotocol/sdk@1` with `@modelcontextprotocol/{server,node}@2` and dropped Express, serving the web-standard handler on plain `node:http` through `toNodeHandler`
* Served protocol revision `2026-07-28` only, with `legacy: 'reject'`, rather than falling back to stateless 2025-era serving
* Capped request bodies at the `100kb` `express.json()` used to apply, since serving on `node:http` removes that parser and the SDK replaces neither it nor the limit
* Added `MCP_ALLOWED_HOSTS` and `MCP_ALLOWED_ORIGINS`, and made a routable bind with no Host allowlist fail at startup rather than warn as the SDK does
* Disabled confirm-before-execute in `execute_query`, which the next commit rebuilds on the multi-round-trip flow
* Pinned the integration-test client to `2026-07-28`, since `@modelcontextprotocol/client@2` negotiates the 2025 era by default and the suite would otherwise have covered the wrong wire era silently
* Replaced the `ping` liveness probe with `server/discover` and loosened a prompt-argument assertion, both of which this revision changed out from under the suite
* Replaced `execute_query`'s push-style `elicitInput()` call with an `input_required` return, so confirmation survives revision `2026-07-28` removing the server-to-client request channel * Refused clients that do not declare `elicitation` rather than executing unconfirmed * Restored the two confirmation tests unchanged * Added unit coverage for the confirmation states the integration suite cannot reach, because a well-behaved client never sends them * Consolidated the test clients onto one helper, dropping `withModernNegotiation` now that `connectMcpClient` can configure a client before it connects
* Sealed a digest of the built query, its variables and its endpoint into `requestState`, so an agent cannot show one query for confirmation and re-enter with another * Refused an answer carrying no `requestState` exactly like a mismatched one, since nothing forces a client to echo it and comparing only when present would leave the binding opt-out at the caller's discretion * Refused rather than re-asked on both failures, which would otherwise hand a caller an unlimited retry loop against the confirmation gate * Installed `codec.verify` as `ServerOptions.requestState.verify`, so a forged, expired or wrongly bound value is refused at the seam and never reaches the tool * Built the codec once per process and passed it through `McpServerDeps` rather than inside the per-request server factory, which would mint and verify the two rounds of one confirmation under different keys * Added `MCP_REQUEST_STATE_SECRET`, falling back to a per-process key and a startup warning, so a single replica needs no configuration and an operator running several is told why confirmations fail * Corrected `SERVER_INSTRUCTIONS`, which still told the model that a client without elicitation gets no prompt, describing the branch the previous commit replaced with a refusal
…2026-07-28` added * Published freshness hints on all six cacheable results, so the SDK default stops telling every client to cache nothing * Read `serverInfo.version` from the package manifest, now that the revision stamps it onto every result rather than onto a handshake that no longer exists * Pinned `server/discover`'s instructions and capabilities, `tools/list` ordering, and every cache hint as it reaches the wire * Recorded Arranger introspection caching as tech debt, since confirmation becoming two requests doubled the round trips a confirmed query costs and no commit here owns it, and removed two entries the SDK v2 migration had already obsoleted
* Stated the served revision and the modern-negotiation a consumer must opt into in `apps/mcp-server/README.md`, which still described the v1 SDK and named neither, and noted that `execute_query` refuses a client that cannot elicit * Recorded every operator-facing change of the upgrade in `CHANGELOG.md`, headlined by 2025-era clients no longer being served at all * Set `protocolEra: "modern"` in `mcp-inspector.json`, without which the Inspector negotiates 2025-era and this endpoint refuses it * Added `mcp-sdk-v2-changes.md` for a developer who needs what changed and why but not the alternatives rejected, covering the single-revision endpoint, the loss of sessions, `node:http` replacing Express, confirmation becoming two requests, the `requestState` binding, and the cache hints
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
Upgrades
apps/mcp-serverto MCP SDK v2 and protocol revision2026-07-28, which removes protocol sessions, theinitializehandshake, and server-initiated requests, so the transport and the confirm-before-execute flow both had to be rebuilt rather than ported. Breaking for consumers: the endpoint serves2026-07-28only, and every SDK client negotiates the 2025 era by default, so a host must opt into modern negotiation explicitly.Please refer to
.dev/docs/mcp-sdk-v2-changes.mdfor a short summary of the key changes in MCP v2 and how they impacted our MCP Server.Issues
Description of Changes
MCP Server
@modelcontextprotocol/{server,node}@2and protocol revision2026-07-28execute_query's confirmation: the SDK's legacy shim reads capabilities declared atinitialize, which per-request serving never seesnode:http, keeping the SDK's Host and Origin guards and re-adding the100kbbody capexpress.json()used to provideexecute_queryreturns aninput_requiredresult and the client re-invokes the tool with the answer, since servers can no longer initiate requestselicitation, rather than executing unconfirmed, which was the last route to running a query nobody approvedrequestState, which travels through the client and returns as untrusted inputttlMsandcacheScopeon the six cacheable results, and readserverInfo.versionfrom the package manifest now that the revision stamps it onto every resulthttp/app.tsandutils/inMemoryEventStore.ts, both of which existed only to manage sessions the revision removedIntegration Tests
2026-07-28, which no SDK client negotiates by default, so the suite would otherwise have covered the wrong wire era silentlypingliveness probe withserver/discoverDocumentation
.dev/docs/mcp-sdk-v2-changes.md, a short read on what changed and why, andmcp-sdk-v2-upgrade-plan.md, the implementation record behind each decisionapps/mcp-server/README.mdand the rootCHANGELOG.md, and set"protocolEra": "modern"inmcp-inspector.json, without which the MCP Inspector negotiates 2025-era and is refusedSpecial Instructions
Before running these changes, you will need to install the latest dependencies and rebuild your local modules:
Any MCP client used to test this branch must pin protocol revision
2026-07-28; the endpoint refuses anything else.New Environment Variables
MCP_ALLOWED_HOSTS: hostnames clients use to reach the server, matched againstHostfor DNS rebinding protection. Required wheneverMCP_HOSTis not loopback, which the0.0.0.0default is not, or the server exits at startup.MCP_ALLOWED_ORIGINS: browser origins allowed to call the server.MCP_MAX_BODY_BYTES: largest request body accepted, default102_400.MCP_REQUEST_STATE_SECRET: signs query confirmations. Optional at a single replica, required across several.For full details refer to the environment variable table in
apps/mcp-server/README.mdandapps/mcp-server/.env.schema.Readiness Checklist
.env.schemafile and documented in the README