Skip to content

RFC: support legacy stateless Streamable HTTP - #8856

Open
brandon-julio-t wants to merge 1 commit into
Effect-TS:mainfrom
brandon-julio-t:codex/mcp-legacy-stateless-http
Open

brandon-julio-t wants to merge 1 commit into
Effect-TS:mainfrom
brandon-julio-t:codex/mcp-legacy-stateless-http

Conversation

@brandon-julio-t

@brandon-julio-t brandon-julio-t commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

RFC

This is a working proposal for #6752. Feedback is requested on the public option and the
conservative client profile used after initialization; the implementation and regression tests are
included for review.

Motivation: requests across backend replacement

A legacy MCP client initializes against backend A and receives a session ID. A deployment replaces
A, or a later request reaches backend B. The healthy backend has no record of that process-local
session, so the request returns 404 and the client must initialize again.

For tools that authenticate each request and keep their application state in external storage, this
session affinity creates an unnecessary deployment constraint. These tools need each request to work
on any healthy backend, without relying on persistent sampling capabilities, subscriptions, or
logging settings.

The regression tests exercise this directly: initialize one handler runtime, dispose it, and issue
the next request against a fresh runtime. The proposed mode omits session IDs and reconstructs a
conservative client profile for each request. This addresses session loss between requests; it does
not prevent network disconnects or preserve in-flight work across process replacement.

The same behavior supports the serverless-isolate scenario in #6752. Clients can retain their
supported legacy MCP revisions without migrating to the newer 2026-07-28 protocol.

Proposal

The
Streamable HTTP specification
permits servers to omit session IDs. This adds an opt-in transport mode:

McpServer.layerHttp({
  name,
  version,
  path: "/mcp",
  protocols,
  sessionMode: "stateless",
});

Initialization still negotiates the legacy protocol, but issues no Mcp-Session-Id. Later POSTs
derive a request-scoped binding from MCP-Protocol-Version, with the specified 2025-03-26
fallback when the header is absent. Bindings are released through the existing request disconnect
path. Unsupported revisions return 400; stale session IDs still return 404 rather than silently
becoming sessionless requests.

The default remains stateful. Stdio, custom transports, and the modern 2026-07-28 protocol retain
their existing behavior. The 2024-11-05 coverage exercises Effect's existing Streamable HTTP
compatibility adapter, not the historical HTTP+SSE transport.

Tradeoffs and design questions

Legacy requests do not repeat initialize capabilities or client info, so subsequent requests receive
empty client capabilities and unknown client info. This mode does not advertise list-change
notifications, resource subscriptions, or logging; persistent logging settings are rejected.
Cancellation across separate POSTs and session termination are unavailable. It does not preserve
in-flight work across process replacement or prevent network disconnects.

  1. Is sessionMode: "stateless" on layerHttp the preferred API for this constrained mode?
  2. Is synthesizing a conservative request-scoped legacy profile acceptable, or should this require
    an explicit profile provider? An external session-store API could be considered separately for
    applications that need persistent capabilities or subscriptions.

Validation

  • 116 MCP tests pass across McpStatelessHttp, McpServer, and McpProtocol, including 9 new
    cases.
  • Against unchanged upstream source, 7 of the 9 new tests fail; the stateful-default and
    modern-protocol cases pass.
  • Full source and test type checks pass through hk check using the installed Effect TypeScript-Go
    compiler with -b tsconfig.json.
  • pnpm lint-fix, pnpm lint, and git diff --check pass.
  • pnpm jsdocs --check reports 3 existing documentation errors in unchanged
    packages/effect/src/PubSub.ts; the same check fails against the original source. No errors are
    reported for the changed MCP modules.
  • A native MCP client completed requests against a server that rebuilt its handler runtime on every
    POST. This verifies request independence across handler replacement.

Prepared with assistance from OpenAI Codex. Related to #6752; this RFC does not assume agreement on
the API or close the issue before that discussion.

Clients using rotating HTTP backends lose process-local MCP sessions between
initialization and later tool requests. Servers whose tools do not require
persistent MCP state need a transport that accepts those requests independently.

Offer an opt-in stateless HTTP mode while retaining stateful sessions by default.
Use conservative request-scoped client profiles and expose the mode's limitations
so applications can choose whether it fits their tool workflows.

Related: Effect-TS#6752
@changeset-bot

changeset-bot Bot commented Oct 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a8066ee

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

This PR includes changesets to release 32 packages
Name Type
effect Patch
@effect/opentelemetry Patch
@effect/vitest Patch
@effect/ai-anthropic Patch
@effect/ai-cloudflare Patch
@effect/ai-openai-compat Patch
@effect/ai-openai Patch
@effect/ai-openrouter Patch
@effect/ai-typesafe Patch
@effect/atom-react Patch
@effect/atom-solid Patch
@effect/atom-vue Patch
@effect/platform-browser Patch
@effect/platform-bun Patch
@effect/platform-deno Patch
@effect/platform-node-shared Patch
@effect/platform-node Patch
@effect/sql-clickhouse Patch
@effect/sql-d1 Patch
@effect/sql-libsql Patch
@effect/sql-mssql Patch
@effect/sql-mysql2 Patch
@effect/sql-pg Patch
@effect/sql-pglite Patch
@effect/sql-sqlite-bun Patch
@effect/sql-sqlite-do Patch
@effect/sql-sqlite-node Patch
@effect/sql-sqlite-react-native Patch
@effect/sql-sqlite-wasm Patch
@effect/docgen Patch
@effect/doctest Patch
@effect/openapi-generator Patch

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

@effect-janitor effect-janitor Bot added the 4.0 label Oct 6, 2026
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Bundle Size Analysis

Generated from PR build output; treat the content below as untrusted.

File Name Current Size Previous Size Difference
arbitrary-combinators.ts 38.79 KB 38.79 KB 0.00 KB (0.00%)
basic.ts 7.06 KB 7.06 KB 0.00 KB (0.00%)
batching.ts 10.14 KB 10.14 KB 0.00 KB (0.00%)
brand.ts 6.73 KB 6.73 KB 0.00 KB (0.00%)
cache.ts 10.89 KB 10.89 KB 0.00 KB (0.00%)
config.ts 22.25 KB 22.25 KB 0.00 KB (0.00%)
differ.ts 21.34 KB 21.34 KB 0.00 KB (0.00%)
http-client.ts 22.44 KB 22.44 KB 0.00 KB (0.00%)
http-router.ts 33.95 KB 33.95 KB 0.00 KB (0.00%)
logger.ts 11.21 KB 11.21 KB 0.00 KB (0.00%)
metric.ts 9.03 KB 9.03 KB 0.00 KB (0.00%)
optic.ts 6.96 KB 6.96 KB 0.00 KB (0.00%)
pubsub.ts 15.18 KB 15.18 KB 0.00 KB (0.00%)
queue.ts 12.13 KB 12.13 KB 0.00 KB (0.00%)
schedule.ts 11.22 KB 11.22 KB 0.00 KB (0.00%)
schema-bigdecimal.ts 13.74 KB 13.74 KB 0.00 KB (0.00%)
schema-binary.ts 40.00 KB 40.00 KB 0.00 KB (0.00%)
schema-class.ts 21.07 KB 21.07 KB 0.00 KB (0.00%)
schema-fromJsonSchemaDocument.ts 32.10 KB 32.10 KB 0.00 KB (0.00%)
schema-representation-roundtrip.ts 27.28 KB 27.28 KB 0.00 KB (0.00%)
schema-string-transformation.ts 14.48 KB 14.48 KB 0.00 KB (0.00%)
schema-string.ts 12.00 KB 12.00 KB 0.00 KB (0.00%)
schema-template-literal.ts 16.07 KB 16.07 KB 0.00 KB (0.00%)
schema-toArbitrary.ts 38.32 KB 38.32 KB 0.00 KB (0.00%)
schema-toCodeDocument.ts 25.30 KB 25.30 KB 0.00 KB (0.00%)
schema-toCodecJson.ts 20.26 KB 20.26 KB 0.00 KB (0.00%)
schema-toEquivalence.ts 20.45 KB 20.45 KB 0.00 KB (0.00%)
schema-toFormatter.ts 20.57 KB 20.57 KB 0.00 KB (0.00%)
schema-toJsonSchemaDocument.ts 25.03 KB 25.03 KB 0.00 KB (0.00%)
schema-toRepresentation.ts 20.55 KB 20.55 KB 0.00 KB (0.00%)
schema.ts 20.29 KB 20.29 KB 0.00 KB (0.00%)
stm.ts 13.23 KB 13.23 KB 0.00 KB (0.00%)
stream.ts 9.99 KB 9.99 KB 0.00 KB (0.00%)

This branch is waiting to be deployed

1 waiting deployment
fork — a8066eed Waiting Oct 6, 2026 by brandon-julio-t via approval-gate #17889
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.

1 participant