Skip to content

feat: official SDK transport for /api/mcp — one server definition, every transport - #97

Open
adamstankiewicz wants to merge 4 commits into
docs/landing-and-docsfrom
feat/mcp-sdk-transport
Open

feat: official SDK transport for /api/mcp — one server definition, every transport#97
adamstankiewicz wants to merge 4 commits into
docs/landing-and-docsfrom
feat/mcp-sdk-transport

Conversation

@adamstankiewicz

Copy link
Copy Markdown
Owner

What & why

Closes #92, in-stack. The tool surface moves to src/lib/mcp/server.ts — one definition consumed by the deployed route, stdio, and the local HTTP server. The route becomes an 80-line Web-standard Streamable HTTP envelope over WebStandardStreamableHTTPServerTransport (stateless per request); 640 lines of hand-rolled JSON-RPC delete, and mcp/app.mjs — whose tool list had already drifted (14 kinds, no find_activity/build_pathway) — deletes with it. Both historical hand-rolling reasons are dead: the SDK ships a fetch-native transport, and _meta.ui passes through (per-call view URIs via ResourceTemplate).

Also wires pnpm mcp:inspect (official inspector) and preserves the audience-not-grade tool surface where this rewrite crossed it.

Type of change

  • New/changed seam implementation (transport)

Breaking or contract changes

Wire-visible but protocol-legal: responses may arrive as SSE and the Accept header is enforced per spec (the SDK is stricter than our hand-rolled leniency — real hosts and the inspector already comply); unknown tools return a tool-result error rather than a protocol error. Tool names, schemas, and MCP Apps metadata are unchanged (inspector-verified).

How it was verified

  • pnpm test — 61 green, including the protocol-envelope suite rewritten as a spec-correct client
  • rm -rf .next && npx tsc --noEmit, pnpm build, pnpm mcp:build (emits the shared server-core bundle), lint
  • Live inspector v2 against the SDK route: initialize, tools/list (_meta.ui present, audience surface intact), and a real find_activity call — semantic ranking, ESS2.C verified, 11 listings
  • CLI server constructs from the shared bundle (mcp/core.mjs smoke)

Authorship

  • I understand every line of this diff
  • Agent-assisted or agent-authored: authored by Claude working interactively with @adamstankiewicz; adopted the parallel audience commit rather than clobbering it
  • package.json change is one devex script (mcp:inspect), no dependencies

🤖 Generated with Claude Code

@vercel

vercel Bot commented Aug 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
interactive-learning-experiences Ready Ready Preview Aug 31, 2026 12:39am

Request Review

@adamstankiewicz
adamstankiewicz force-pushed the feat/mcp-sdk-transport branch 2 times, most recently from 06828a2 to 7d30304 Compare August 30, 2026 20:11
@adamstankiewicz

Copy link
Copy Markdown
Owner Author

Addendum to the description: this branch also picked up a spec-correctness fix authored in a parallel session — a GET with Accept: text/event-stream now gets the Streamable HTTP spec's 405 + Allow (this stateless transport never initiates messages, so there is no stream to offer; a definitive no stops SDK reconnect loops where a 200 of unparseable JSON invites them). The human info GET is unchanged. Tests included.

adamstankiewicz and others added 4 commits August 30, 2026 20:38
The tool surface moves to src/lib/mcp/server.ts — one definition consumed
by every transport. The route shrinks from 640 hand-rolled JSON-RPC lines
to an 80-line Web-standard Streamable HTTP envelope
(WebStandardStreamableHTTPServerTransport, stateless per request — the
serverless shape). Both historical reasons for hand-rolling are gone: the
SDK ships a fetch-native transport, and _meta.ui passes through
registerTool/registerResource (per-call view URIs ride a ResourceTemplate).

The CLI transports unify on the same definition via an esbuild bundle
(mcp/dist/server-core.mjs, emitted by mcp:build): server.mjs and
server-http.mjs consume core.mjs; app.mjs — whose tool list had already
drifted stale — deletes. pnpm mcp:inspect wires the official inspector.

The protocol tests became protocol-correct clients in the process (the
SDK enforces the Accept header and answers over SSE where our hand-rolled
version was lenient), and adopted the audience-not-grade surface where it
crossed this rewrite.

Verified: 61 tests green; live inspector v2 run against the SDK route —
initialize, tools/list with _meta.ui, and a real find_activity call
(semantic ranking, ESS2.C verified, 11 listings) end-to-end.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The tool surface iterated in a parallel session: the free-text field is
audienceHint on all three tools, keeping the bare word reserved for the
scheme-scoped, graph-verified audience on emitted manifests. The contract
test now pins both halves — the hint exists under its suffixed name, and
no tool exposes a bare audience input to collide with the verified one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Streamable HTTP lets clients open a server-to-client stream with GET;
this transport is stateless and never initiates messages, so there is
no stream to offer, and the spec's answer is 405 with Allow. A client
told no definitively stops asking, where a 200 of unparseable JSON
invites a reconnect loop. The Accept header separates that client from
a human checking the URL, who still gets the useful info response.
Authored in a parallel session; committed with its tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same rewrite as the hand-rolled route below: the live loop keeps the
activate → model → practice → check arc as its spine, with evidence
setting pacing inside it. One tool surface, one pedagogy, both
transports say it identically.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate /api/mcp to the SDK's webStandardStreamableHttp; unify the three server transports

1 participant