Add stateless Streamable HTTP transport (MCP 2026-07-28) - #37
Merged
Merged
Conversation
`jev-mcp --http` (or JEV_MCP_TRANSPORT=http) serves the tools over Streamable HTTP with no sessions: MCP 2026-07-28 per request, and 2025-era clients through the SDK's stateless fallback, from one endpoint. A bearer token (JEV_MCP_AUTH_TOKEN) is required unless HOST is loopback, since every call spends the operator's Jev key. Stdio stays the default. Moves from @modelcontextprotocol/sdk 1.x to the v2 packages, which is where 2026-07-28 serving lives. Tools are registered once at module scope and replayed onto a fresh McpServer per stdio connection or HTTP request, so the tool bodies are untouched apart from extra.signal -> ctx.mcpReq.signal. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A loopback bind may run without JEV_MCP_AUTH_TOKEN, which left it open to DNS rebinding: a web page could resolve its own hostname to 127.0.0.1 and spend the operator's Jev key. Apply the SDK's localhost Host/Origin guards on loopback binds, as the Streamable HTTP spec requires, with a regression test that fails without them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Owner
|
Contributor
Author
|
Thank you |
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
jev-mcp --http(orJEV_MCP_TRANSPORT=http) serves the eleven tools over Streamable HTTP with no sessions. It speaks MCP 2026-07-28 per request and serves 2025-era clients (Claude Code, Codex, and others today) through the SDK's stateless fallback, all from one/mcpendpoint. Nothing is held between requests, so replicas scale behind any load balancer without sticky routing./healthis included for platform health checks.JEV_MCP_AUTH_TOKENgates the endpoint with a bearer token (constant-time compare). Every call spends the operator's Jev key, so the server refuses to start on a non-loopbackHOSTwithout a token.serveStdio.How
Serving 2026-07-28 exists only in the v2 SDK packages. v1.30.x can't serve it, so this moves
@modelcontextprotocol/sdk1.x to@modelcontextprotocol/server+@modelcontextprotocol/node2.1.0 (and@modelcontextprotocol/clientfor tests).To keep the diff small, the tool bodies are untouched. The module-level
server.registerTool(...)calls becometools.registerTool(...): they record into a list, andcreateServer()replays that list onto a freshMcpServerper stdio connection or HTTP request. That factory is the shape bothcreateMcpHandlerandserveStdioexpect. The only other change inside the tools isextra.signal→ctx.mcpReq.signal, the v2 name, so cancellation is wired exactly as before. The HTTP wiring lives in a newsrc/http.ts(about 50 lines,node:httponly, no new framework dependency).Prior art
#30 (OtisRed) added a sessionful Streamable HTTP mode on SDK v1 and was closed by its author for a local pilot. This PR takes the stateless route instead. That's the direction the 2026-07-28 revision standardizes:
Mcp-Session-Idand theinitializehandshake are gone (SEP-2567 / SEP-2575). It also means there's no session expiry, stale-session error handling, or per-session transport bookkeeping to maintain.Verification
npm run typecheck,npm run buildnpm test: 226 passed, 0 failed. That's the existing 224, with the two cancellation tests updated for v2'scallTool(params, options)signature, plus a newtest/http.test.mjs:/healthis 200, and/mcpwithout a token is 401Clientand a client pinned to2026-07-28both list all 11 tools, and reportlegacyandmoderneras respectivelynode:22-slimcontainer,curltools/listreturns all 11 tools in both eras: 2025 with noinitializeand noMcp-Session-Idin the response, and 2026-07-28 with the_metaenvelope. A stdio client pinned to 2026-07-28 negotiatesmodernand lists all 11.Not run:
npm run test:e2e(needs a live key).Scope
No changes to tool semantics, providers, inputs, or outputs, and no new runtime dependencies beyond the SDK package split. README gets a short "Remote / HTTP" section, and the CHANGELOG gets an Unreleased entry.
🤖 Generated with Claude Code