Skip to content

feat(ai): let McpServer.layerHttp terminate sessions on DELETE - #8773

Merged
tim-smart merged 8 commits into
Effect-TS:mainfrom
juliusmarminge:t3code/mcp-http-delete-session
Oct 5, 2026
Merged

tim-smart merged 8 commits into
Effect-TS:mainfrom
juliusmarminge:t3code/mcp-http-delete-session

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Problem

The MCP Streamable HTTP spec says clients that no longer need a session SHOULD send DELETE with Mcp-Session-Id to end it, and servers MAY answer 405 to refuse. McpServer.layerHttp always answers 405, so a server has no way to accept termination. Its HTTP session state (bySessionId in the stateful runtime) then stays around for the process lifetime.

We run one MCP endpoint for many short-lived agent sessions in T3 Code, and have been carrying a patch that adds DELETE.

Change

New option allowSessionTermination on layerHttp. When it is set:

  • DELETE with a known Mcp-Session-Id removes the session and returns 204.
  • An unknown id returns 404; a missing header returns 400.
  • Later requests with a terminated id get 404, which tells the client to re-initialize, as the spec requires.

Without the option nothing changes: DELETE returns 405 with Allow: POST, and the existing conformance scenario ("declines client session termination without invalidating the session") still passes. The Origin check applies to DELETE the same way it does to POST.

Internally, StatefulRuntime and ServerRuntime gain a terminateSession(sessionId): boolean.

Testing

  • Focused tests in McpServer/McpServer.test.ts cover termination and stale-id rejection, missing/unknown ids, and Origin rejection without session invalidation. DELETE requests use the shared HTTP harness.
  • nix develop -c pnpm test --run packages/effect/test/ai/McpServer: 8 files passed, 929 tests passed, 42 skipped. Existing default-405 conformance coverage passes unchanged.
  • The three DELETE tests fail against the pre-change implementation (405 instead of 204/400).
  • nix develop -c pnpm check and lint/format validation pass.

Closes EFF-1706

With allowSessionTermination, a DELETE carrying an Mcp-Session-Id ends that
HTTP session (204, 404 for an unknown session, 400 without the header), and
later requests with the id get 404 so the client re-initializes. Without it,
DELETE keeps returning 405, which the spec allows.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c13617d

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

@juliusmarminge
juliusmarminge requested a deployment to fork October 5, 2026 18:12 — with GitHub Actions Abandoned
@effect-janitor effect-janitor Bot added the 4.0 label Oct 5, 2026
juliusmarminge added a commit to pingdotgg/t3code that referenced this pull request Oct 5, 2026
RequestHooks and the ConnectionHooks onPing/onPong/onPingTimeout callbacks
have had no callers since the client connection rewrite (#2978). The
patch now carries only MCP session DELETE (upstream: Effect-TS/effect#8773),
the getSetCookie guard (Effect-TS/effect#8772) and the missed-pong
tolerance (Effect-TS/effect#8774).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@lloydrichards

Copy link
Copy Markdown
Contributor

Just had a quick peruse of this and two thoughts came to mind:

  • Session ID isn't authenticate so there is a chance for session hijacking. Might need to check how the this could be mitigated.
  • The issue would be protocol versions since in the v2026 protocols there are no longer any session id and the 2026 transport specification specifies using 405. So would need a protocol guard

Maybe HttpRouter.middleware could authenticate the request before DELETE reaches the handler. But checking that the caller owns the session would require recording the authenticated user when the session is created. Is there an existing way for applications to do that, or would we need a hook to associate sessions with their owners?

…quests

- Route every MCP endpoint method from layerMcpProtocolHttp
- Keep DELETE at 405 when no stateful protocol is configured
- Check MCP-Protocol-Version on DELETE with the same session rules as POST
- Interrupt in-flight requests of a terminated session and answer them with 404
- Advertise DELETE in Allow when termination is enabled
@github-actions

github-actions Bot commented Oct 5, 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.90 KB 38.90 KB 0.00 KB (0.00%)
basic.ts 7.04 KB 7.04 KB 0.00 KB (0.00%)
batching.ts 10.13 KB 10.13 KB 0.00 KB (0.00%)
brand.ts 6.72 KB 6.72 KB 0.00 KB (0.00%)
cache.ts 10.88 KB 10.88 KB 0.00 KB (0.00%)
config.ts 22.15 KB 22.15 KB 0.00 KB (0.00%)
differ.ts 21.29 KB 21.29 KB 0.00 KB (0.00%)
http-client.ts 22.44 KB 22.44 KB 0.00 KB (0.00%)
http-router.ts 33.74 KB 33.74 KB 0.00 KB (0.00%)
logger.ts 11.08 KB 11.08 KB 0.00 KB (0.00%)
metric.ts 9.02 KB 9.02 KB 0.00 KB (0.00%)
optic.ts 6.95 KB 6.95 KB 0.00 KB (0.00%)
pubsub.ts 15.16 KB 15.16 KB 0.00 KB (0.00%)
queue.ts 12.08 KB 12.08 KB 0.00 KB (0.00%)
schedule.ts 11.18 KB 11.18 KB 0.00 KB (0.00%)
schema-bigdecimal.ts 13.73 KB 13.73 KB 0.00 KB (0.00%)
schema-binary.ts 39.95 KB 39.95 KB 0.00 KB (0.00%)
schema-class.ts 21.03 KB 21.03 KB 0.00 KB (0.00%)
schema-fromJsonSchemaDocument.ts 32.06 KB 32.06 KB 0.00 KB (0.00%)
schema-representation-roundtrip.ts 27.24 KB 27.24 KB 0.00 KB (0.00%)
schema-string-transformation.ts 14.46 KB 14.46 KB 0.00 KB (0.00%)
schema-string.ts 11.99 KB 11.99 KB 0.00 KB (0.00%)
schema-template-literal.ts 16.05 KB 16.05 KB 0.00 KB (0.00%)
schema-toArbitrary.ts 38.44 KB 38.44 KB 0.00 KB (0.00%)
schema-toCodeDocument.ts 25.26 KB 25.26 KB 0.00 KB (0.00%)
schema-toCodecJson.ts 20.21 KB 20.21 KB 0.00 KB (0.00%)
schema-toEquivalence.ts 20.41 KB 20.41 KB 0.00 KB (0.00%)
schema-toFormatter.ts 20.54 KB 20.54 KB 0.00 KB (0.00%)
schema-toJsonSchemaDocument.ts 25.00 KB 25.00 KB 0.00 KB (0.00%)
schema-toRepresentation.ts 20.50 KB 20.50 KB 0.00 KB (0.00%)
schema.ts 20.25 KB 20.25 KB 0.00 KB (0.00%)
stm.ts 13.08 KB 13.08 KB 0.00 KB (0.00%)
stream.ts 9.98 KB 9.98 KB 0.00 KB (0.00%)

@tim-smart
tim-smart merged commit 1f74b62 into Effect-TS:main Oct 5, 2026
8 of 10 checks passed
@effect-bot effect-bot mentioned this pull request Oct 5, 2026

This branch is waiting to be deployed

1 waiting deployment
fork — c13617d4 Waiting Oct 5, 2026 by tim-smart via approval-gate #17386
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.

3 participants