Skip to content

Serve Streamable HTTP from every reference server - #5536

Merged
cliffhall merged 3 commits into
v2/mainfrom
v2/feat/576-streamable-http-all-servers
Nov 12, 2026
Merged

cliffhall merged 3 commits into
v2/mainfrom
v2/feat/576-streamable-http-all-servers

Conversation

@cliffhall

@cliffhall cliffhall commented Nov 12, 2026 •

Copy link
Copy Markdown
Member

Closes #576

Description

Every reference server now serves Streamable HTTP as well as stdio. Until now only everything did.

  • TypeScript (filesystem, memory, sequentialthinking): a new http.ts in each serves the server's existing factory through the SDK's createMcpHandler on one /mcp endpoint. 2026-07-28 requests go to the modern handler, and 2025-era requests to the SDK's stateless fallback. The endpoint sits behind localhostOriginValidation and answers 404 on any other path. --transport streamable-http selects it, and stdio stays the default.
  • Python (fetch, git, time): a new transport.py in each serves the SDK's Streamable HTTP app under uvicorn when --transport streamable-http is given. It wraps the app in a localhost-Origin middleware that refuses foreign origins with 403 whatever HOST is, and on loopback the SDK's own Host and Origin checks also run.
  • All six listen on HOST:PORT, 127.0.0.1:3001 by default. These are the same variables, defaults and loopback-first posture as everything (everything: bind the HTTP transports to loopback by default, with HOST to widen #5371). A bad PORT is a usage error at startup.

Per-server behaviour over HTTP, where the stateless model matters:

  • filesystem: a stateless request has no channel back to the client, so the server cannot ask a client for its Roots. Over HTTP it requires command-line directories and refuses to start without one.
  • memory: the graph lives in its file, so a fresh server per request loses nothing. Graph changes are published on the handler's subscriptions/listen bus, so a 2026-07-28 listener hears about writes from any client.
  • sequentialthinking: no server outlives its request, so the process holds one shared SequentialThinkingServer. Over HTTP the history belongs to the process, not to a connection, and the README says so.

The boot smoke (scripts/smoke-servers.mjs) now boots every server over Streamable HTTP as well as stdio: 15 server/transport pairs, up from 9.

Server Details

  • Servers: filesystem, memory, sequentialthinking, fetch, git, time
  • Changes: a new --transport option (stdio | streamable-http), plus the HOST/PORT environment variables when HTTP is selected. No tool, resource or prompt changes.

Motivation and Context

#576: the reference servers should show how to serve a transport other than stdio, and Streamable HTTP is the standard one. It replaces SSE, as the issue's update notes.

How Has This Been Tested?

Tests. Each server has a new protocol-level HTTP suite that binds the real endpoint on a free loopback port and drives it with the SDK client in both eras (TS: __tests__/http.test.ts, Py: tests/test_http.py). The suites also cover the Origin 403 (Python: on a non-loopback Host too), a malformed request target, the 404 off-path, a refused request reaching onerror, EADDRINUSE, HOST/PORT parsing, --transport parsing, and the SIGINT/SIGTERM shutdown. Memory's suite checks that a 2026-07-28 listener receives another client's write. Every file clears the per-file coverage gate, and npm run local:gate passes.

Inspector CLI 2.12.0, each server started with --transport streamable-http and called at its URL, --protocol-era modern and --protocol-era legacy. All 12 runs exit 0 (trimmed):

Server Call 2026-07-28 2025-11-25
filesystem read_text_file on a file in the allowed dir "hello over http", _meta.serverInfo = secure-filesystem-server "hello over http"
memory create_entities Ada created Ada skipped: ["Ada"]: the earlier request's write persisted
sequentialthinking one thought thoughtHistoryLength: 1 thoughtHistoryLength: 2: one history per process
time convert_time 12:00 UTC → Asia/Tokyo 21:00+09:00 21:00+09:00
git git_status of a fresh repo On branch main / No commits yet same
fetch fetch https://example.com # Example Domain … same

LLM client: Claude Code 2.1.322 (claude -p --strict-mcp-config), with all six servers configured as {"type":"http","url":…} against the local builds. Prompt: "read note.txt with the filesystem server; create an entity Ada in memory; record one sequential thought; convert 12:00 UTC to Asia/Tokyo; get git status of the repo; fetch https://example.com and give its title." Result: all six calls succeeded. The file text came back, Ada was created, thought 1 was recorded, 21:00 Asia/Tokyo, main with no commits, and the title was "Example Domain". Claude Code has no era switch and does not print the version it negotiated, so this run is not labelled with an era. Both eras are covered by the Inspector rows above and by the tests.

Boot smoke: node scripts/smoke-servers.mjs reports smoke — OK (15 server/transport pairs booted), each server over stdio and Streamable HTTP from its built bin or console script.

Breaking Changes

None. Stdio stays the default, and no existing argument changes meaning. In filesystem, --transport is a new option, so a directory literally named --transport now needs -- before it, as the other options already do.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Protocol Documentation (Transports: Streamable HTTP, and the security warning on Origin validation and binding to localhost)
  • My changes follow MCP security best practices: loopback by default, and Origin validated on every server whatever HOST is (TS: localhostOriginValidation, Py: a localhost-Origin middleware, plus the SDK's Host check on loopback). HOST widens the bind only deliberately. CORS for browser clients is Answer CORS on the new Streamable HTTP endpoints (filesystem, memory, sequential-thinking) #5538
  • I have updated the server's README accordingly: a Streamable HTTP section in all six, plus the root README's transports section
  • I have added a changeset (npm run changeset) if this changes what a TypeScript server publishes (.changeset/576-streamable-http.md, minor for all three)
  • I have tested this with an LLM client
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling: bind failure, a bad PORT, an unknown transport, and a failed shutdown each exit non-zero with a message
  • I have documented all environment variables and configuration options (--transport, HOST, PORT in each README)

Additional context

  • The 2025-era HTTP serving is the SDK's stateless fallback, not everything's sessionful wiring. That keeps each server's HTTP layer about 100 lines. The cost is that nothing server-initiated reaches a 2025-era client between its requests (Roots, resources/subscribe updates). Each README states this. everything remains the showcase for sessionful 2025-era HTTP.
  • AGENTS.md's transport rule, docs/quality-gate.md, and the local-dev, testing and project-structure skills are updated to match. Only skill bodies changed, no descriptions, so no skills:eval run is needed.

🤖 Generated with Claude Code

Every server already served stdio, and only everything served Streamable
HTTP. Now filesystem, memory, sequential-thinking, fetch, git and time take
--transport streamable-http and serve one /mcp endpoint for 2026-07-28 and
2025-era clients alike, on HOST:PORT (127.0.0.1:3001 by default).

The TypeScript servers serve their existing factory through the SDK's
createMcpHandler (stateless for 2025-era clients) behind a localhost
Origin check; the Python servers run the SDK's Streamable HTTP app. Over
HTTP, filesystem requires command-line directories (Roots cannot reach
it), memory publishes graph changes on the handler's listen bus, and
sequential-thinking keeps one history per process.

The boot smoke now boots each server over Streamable HTTP as well.

Closes #576

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
@cliffhall cliffhall added the v2 label Nov 12, 2026
@changeset-bot

changeset-bot Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 97d89b9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@modelcontextprotocol/server-filesystem Minor
@modelcontextprotocol/server-memory Minor
@modelcontextprotocol/server-sequential-thinking Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cliffhall cliffhall linked an issue Nov 12, 2026 that may be closed by this pull request
@cliffhall
cliffhall requested a balanced review from Copilot November 12, 2026 04:54
@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

filesystem: MCP interface diff

Base: 9914133c0557 (Merge pull request #5532 from modelcontextprotocol/v2/docs/5524-everything-get-env-masking-wording)
Head: the build in this checkout, at a63392e235fa (Merge 97d89b9 into 09eb689)

Diffed because it was named (--server).

✅ No interface changes detected.

Tools Prompts Resources Resource templates
Base 16 0 0 1
Head 16 0 0 1

Updated by this run for 97d89b92e3fb74a070cc38caf4e4b8062e1bb741.

@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

time: MCP interface diff

Base: 9914133c0557 (Merge pull request #5532 from modelcontextprotocol/v2/docs/5524-everything-get-env-masking-wording)
Head: the build in this checkout, at a63392e235fa (Merge 97d89b9 into 09eb689)

Diffed because it was named (--server).

✅ No interface changes detected.

Tools Prompts Resources Resource templates
Base 3 0 0 0
Head 3 0 0 0

Updated by this run for 97d89b92e3fb74a070cc38caf4e4b8062e1bb741.

@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

memory: MCP interface diff

Base: 9914133c0557 (Merge pull request #5532 from modelcontextprotocol/v2/docs/5524-everything-get-env-masking-wording)
Head: the build in this checkout, at a63392e235fa (Merge 97d89b9 into 09eb689)

Diffed because it was named (--server).

✅ No interface changes detected.

Tools Prompts Resources Resource templates
Base 9 0 1 0
Head 9 0 1 0

Updated by this run for 97d89b92e3fb74a070cc38caf4e4b8062e1bb741.

@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

fetch: MCP interface diff

Base: 9914133c0557 (Merge pull request #5532 from modelcontextprotocol/v2/docs/5524-everything-get-env-masking-wording)
Head: the build in this checkout, at a63392e235fa (Merge 97d89b9 into 09eb689)

Diffed because it was named (--server).

✅ No interface changes detected.

Tools Prompts Resources Resource templates
Base 1 1 0 0
Head 1 1 0 0

Updated by this run for 97d89b92e3fb74a070cc38caf4e4b8062e1bb741.

@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

git: MCP interface diff

Base: 9914133c0557 (Merge pull request #5532 from modelcontextprotocol/v2/docs/5524-everything-get-env-masking-wording)
Head: the build in this checkout, at a63392e235fa (Merge 97d89b9 into 09eb689)

Diffed because it was named (--server).

⚠️ Interface changes detected in 1 part(s) of the interface. Review them below to confirm they are intended.

Tools Prompts Resources Resource templates
Base 14 2 0 0
Head 14 2 0 0

tools

--- base/tools.json
+++ target/tools.json

- tools[git_remote].description: "Lists the repository's remotes with their fetch and push URLs, like `git remote -v`. Credentials emb..."
+ tools[git_remote].description: "Lists the repository's remotes with their fetch and push URLs, like `git remote -v`. Credentials emb..."

Updated by this run for 97d89b92e3fb74a070cc38caf4e4b8062e1bb741.

@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

sequentialthinking: MCP interface diff

Base: 9914133c0557 (Merge pull request #5532 from modelcontextprotocol/v2/docs/5524-everything-get-env-masking-wording)
Head: the build in this checkout, at a63392e235fa (Merge 97d89b9 into 09eb689)

Diffed because it was named (--server).

✅ No interface changes detected.

Tools Prompts Resources Resource templates
Base 1 0 0 0
Head 1 0 0 0

Updated by this run for 97d89b92e3fb74a070cc38caf4e4b8062e1bb741.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Malformed HTTP request targets can crash the servers, and legacy-client writes fail to notify modern subscribers on the shared graph.

2 open issues
What changed in this PR

Adds Streamable HTTP to the six reference servers that previously supported only stdio, addressing #576 while retaining stdio as the default and the loopback-first approach from #5371.

Changes:

  • Adds --transport streamable-http and HOST/PORT configuration, supporting both protocol eras.
  • Adapts filesystem directory requirements, memory notifications, and shared sequential-thinking history for HTTP.
  • Adds HTTP tests, expands boot smoke coverage to 15 server/transport pairs, and updates documentation and release metadata.
File Description
src/​time/​tests/​test_http.py Adds HTTP and transport-option tests.
src/​time/​tests/​test_entrypoints.py Updates default-transport expectations.
src/​time/​src/​mcp_server_time/​transport.py Adds transport dispatch and endpoint parsing.
src/​time/​src/​mcp_server_time/​server.py Delegates serving to the selected transport.
src/​time/​src/​mcp_server_time/​__init__.py Adds the transport CLI option.
src/​time/​README.md Documents HTTP configuration.
src/​sequentialthinking/​README.md Documents HTTP and shared history.
src/​sequentialthinking/​package.json Adds the SDK Node adapter dependency.
src/​sequentialthinking/​index.ts Adds transport selection and injectable history.
src/​sequentialthinking/​http.ts Adds the HTTP endpoint with shared history.
src/​sequentialthinking/​__tests__/​http.test.ts Tests HTTP, shared history, and shutdown.
src/​memory/​README.md Documents HTTP persistence and notifications.
src/​memory/​package.json Adds the SDK Node adapter dependency.
src/​memory/​index.ts Adds transport selection and notification publishing.
src/​memory/​http.ts Adds HTTP serving and the subscription bus.
src/​memory/​__tests__/​http.test.ts Tests HTTP persistence, notifications, and shutdown.
src/​git/​tests/​test_http.py Adds HTTP and transport-option tests.
src/​git/​tests/​test_entrypoints.py Updates serving mocks for transport selection.
src/​git/​src/​mcp_server_git/​transport.py Adds transport dispatch and endpoint parsing.
src/​git/​src/​mcp_server_git/​server.py Delegates serving to the selected transport.
src/​git/​src/​mcp_server_git/​__init__.py Adds the transport CLI option.
src/​git/​README.md Documents HTTP and repository configuration.
src/​filesystem/​README.md Documents HTTP directory requirements.
src/​filesystem/​package.json Adds the SDK Node adapter dependency.
src/​filesystem/​index.ts Selects HTTP and requires command-line directories.
src/​filesystem/​http.ts Adds the guarded HTTP endpoint.
src/​filesystem/​__tests__/​stdio.test.ts Updates usage-text expectations.
src/​filesystem/​__tests__/​read-only.test.ts Updates argument and usage expectations.
src/​filesystem/​__tests__/​read-only-dirs.test.ts Updates parsed-argument expectations.
src/​filesystem/​__tests__/​http.test.ts Tests HTTP access boundaries and configuration.
src/​filesystem/​__tests__/​confine-roots.test.ts Updates parsed-argument expectations.
src/​fetch/​tests/​test_readability_js_opt_out.py Updates serving-call expectations.
src/​fetch/​tests/​test_http.py Adds HTTP and transport-option tests.
src/​fetch/​tests/​test_entrypoints.py Updates default-transport expectations.
src/​fetch/​tests/​test_allowed_hosts.py Updates serving-call expectations.
src/​fetch/​src/​mcp_server_fetch/​transport.py Adds transport dispatch and endpoint parsing.
src/​fetch/​src/​mcp_server_fetch/​server.py Delegates serving to the selected transport.
src/​fetch/​src/​mcp_server_fetch/​__init__.py Adds the transport CLI option.
src/​fetch/​README.md Documents HTTP configuration.
scripts/​smoke-servers.test.mjs Updates transport counts and launch expectations.
scripts/​smoke-servers.mjs Adds HTTP smoke targets for all six servers.
README.md Describes repository-wide HTTP support.
package-lock.json Records the added Node adapter dependencies.
docs/​quality-gate.md Updates documented smoke coverage.
AGENTS.md Updates the repository transport rule.
.claude/​skills/​testing/​SKILL.md Documents TypeScript HTTP test coverage.
.claude/​skills/​project-structure/​SKILL.md Documents transport modules and entry points.
.claude/​skills/​local-dev/​SKILL.md Adds local HTTP launch guidance.
.changeset/​576-streamable-http.md Records minor releases for three TypeScript servers.

Review effort: Balanced

Comment thread src/filesystem/http.ts Resolved
Comment thread src/memory/index.ts Resolved
A target such as `//[` made `new URL()` throw inside the Node request
listener, outside any handler, ending the process. The HTTP endpoints now
compare the path without parsing the target, so it is answered 404.

Over HTTP, memory now publishes every graph change on the handler's bus
whatever era wrote it, so a 2026-07-28 listener hears of a 2025-era
client's writes too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 1: both findings were defects in what this PR added, and both are fixed in 17d9f9e.

  • Malformed request target crashes the HTTP servers (high): the path is now compared without URL parsing, so a target like //[ gets a 404. A raw-socket regression test was added for filesystem, memory and sequentialthinking.
  • Legacy-era writes skip the shared notification bus (medium): memory now publishes every change on the bus over HTTP, whatever era wrote it. The test covers both writer eras.

Requesting round 2.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Active issues leave Git, Fetch, and Time tools exposed to DNS-rebinding requests under HOST overrides, while missing CORS handling prevents localhost browser clients from connecting to Filesystem, Memory, and Sequential Thinking transports.

1 open issue
1 previously missed issue

In code that hasn't changed since last review

Medium severity Memory and sequential-thinking transports lack CORS support

src/​filesystem/​http.ts:95

Localhost browser clients cannot connect because Origin validation does not add CORS headers: preflights are rejected and actual responses lack Access-Control-Allow-Origin. Handle allowed-origin OPTIONS requests and add CORS response headers before SDK dispatch, following the existing Everything transport. Apply the same fix in src/memory/http.ts:100 and src/sequentialthinking/http.ts:95.

2 resolved since last review

Review effort: Balanced

Comment thread src/git/src/mcp_server_git/transport.py Resolved
The SDK's DNS-rebinding protection checks Origin only when it binds
loopback, so with HOST widened a page on any origin could reach the git,
fetch and time tools. Each transport.py now wraps the SDK's app in a
localhost-Origin middleware, as the TypeScript servers do with
localhostOriginValidation, and runs it under uvicorn itself.

The READMEs now say what each endpoint refuses, and that the TypeScript
ones send no CORS headers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 2: one finding fixed and one declined.

  • HOST override disables Origin validation on the Python servers (high, in its own thread): fixed in 97d89b9. A localhost-Origin middleware now wraps the SDK app whatever HOST is.
  • Memory and sequential-thinking transports lack CORS (medium, in the review's "previously missed" section, so it has no thread to reply in): declined as out of scope. Add Streamable HTTP support to all reference servers #576 asks for the Streamable HTTP transport, and serving browser-based clients directly (preflights, Access-Control-* headers) is an extra capability, which applies equally to filesystem and to the Python servers. Filed as Answer CORS on the new Streamable HTTP endpoints (filesystem, memory, sequential-thinking) #5538. The READMEs no longer imply that a localhost browser page can connect. They now say a foreign origin is refused with 403 and that the TypeScript endpoints send no CORS headers.

Requesting round 3.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues are recorded for the Streamable HTTP additions across the six reference servers.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 3: clean (no findings), so the loop stops here. Round 2's Origin finding is marked resolved, and its CORS finding is tracked in #5538.

@cliffhall
cliffhall merged commit c991ded into v2/main Nov 12, 2026
59 checks passed
@cliffhall
cliffhall deleted the v2/feat/576-streamable-http-all-servers branch November 12, 2026 05:23
cliffhall added a commit that referenced this pull request Nov 12, 2026
…y-write-caps

Resolve the createServer signature against #5536's Streamable HTTP
transport: limits becomes the fourth parameter, after
publishGraphUpdated. run() reads the limits for the HTTP path too, and
startHttpServer passes them to each request's server, with a test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Streamable HTTP support to all reference servers

2 participants