Skip to content

feat: add session-lifecycle conformance scenario for streamable HTTP - #322

Merged
pcarleton merged 3 commits into
modelcontextprotocol:mainfrom
pugsatoshi:feat/79-session-lifecycle-scenario
Jul 31, 2026
Merged

feat: add session-lifecycle conformance scenario for streamable HTTP#322
pcarleton merged 3 commits into
modelcontextprotocol:mainfrom
pugsatoshi:feat/79-session-lifecycle-scenario

Conversation

@pugsatoshi

Copy link
Copy Markdown
Contributor

This pull request adds a new server-session-lifecycle conformance scenario that verifies the two server-side guarantees the Streamable HTTP transport spec places on session termination: (a) the server accepts (or 405s) an HTTP DELETE bearing the issued Mcp-Session-Id, and (b) subsequent requests bearing the terminated session ID get HTTP 404 Not Found.
It also corrects a related spec deviation in the example everything-server.ts, where stale/unknown session IDs were returning HTTP 400 instead of 404.

Motivation and Context

Refs #79.

Issue #79 asked for two things. The first (the conformance harness should clean up its sessions after a run) is handled by PR 316. This PR addresses the second half — there should probably be a conformance test for session lifecycle! — by adding a dedicated scenario that exercises DELETE + 404-on-terminated-session end-to-end.
While developing the scenario, I noticed that the example server (examples/servers/typescript/everything-server.ts) returned HTTP 400 for requests bearing a stale or unknown Mcp-Session-Id, which violates the spec's "MUST 404" requirement.
Without that fix, the new scenario would have failed in all-scenarios.test.ts, so the two changes are bundled in the same PR.

How Has This Been Tested?

  • npm run check — typecheck + lint pass.
  • npm run test — 219/219 tests pass. The new session-lifecycle.test.ts covers four cases: stateless server (INFO + SKIPPED), happy path (DELETE 200 → POST 404), DELETE-not-supported (405 → both SKIPPED), and the regression case (server returns 200 for a terminated session — Check B FAILURE).
  • Manual smoke against the patched examples/servers/typescript/everything-server.ts:

$ PORT=3011 npx tsx examples/servers/typescript/everything-server.ts &
$ npx tsx src/index.ts server --url http://localhost:3011/mcp --scenario server-session-lifecycle
Running client scenario 'server-session-lifecycle' against server: http://localhost:3011/mcp
Checks:
2026-05-25T02:25:16.795Z [server-session-delete-accepted ] SUCCESS Server accepts HTTP DELETE on the issued session ID with a 2xx response
2026-05-25T02:25:16.798Z [server-session-terminated-returns-404] SUCCESS Server returns HTTP 404 for requests bearing a
terminated session ID

Test Results:
Passed: 2/2, 0 failed, 0 warnings

The server logs the expected Session initialized with ID: ... / Received session termination request for session ... / Session ... closed sequence.

Breaking Changes

Downstream SDK conformance suites may surface failures on the new server-session-delete-accepted or server-session-terminated-returns-404 checks if their server returns 400 (or some other status) for a stale/unknown Mcp-Session-Id.
The scenario is intentionally lenient compared to the spec (servers MAY refuse DELETE outright with 405, in which case both checks are SKIPPED), so the only way to fail is the specific “DELETE accepted, but subsequent POST does not return 404” misbehavior.
SDKs that hit this can add the check IDs to their expected-failures baseline as a temporary measure until they implement the spec.

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 Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

This commits a `server-session-lifecycle` scenario that verifies the server honors the two RFC 2119 statements the Streamable HTTP transport spec places on session termination:

1. After receiving an HTTP DELETE bearing the issued Mcp-Session-Id, the server accepts it (2xx) or signals that explicit termination is not supported (405).
2. Subsequent requests bearing the terminated session ID MUST get HTTP 404 Not Found.

The scenario inlines a raw `fetch` initialize/terminate/probe flow so the DELETE is the test action (not background cleanup).
Stateless servers that never issue a session ID are reported as INFO, and the lifecycle checks are SKIPPED.
Servers that return 405 on DELETE skip both checks without flagging a failure — the spec allows servers to refuse explicit termination.

Refs modelcontextprotocol#79

Signed-off-by: Satoshi Ito <satoshi.ito.tf@hitachi.com>
@pkg-pr-new

pkg-pr-new Bot commented May 28, 2026

Copy link
Copy Markdown

Open in StackBlitz

npx https://pkg.pr.new/@modelcontextprotocol/conformance@322

commit: b146faf

- Adapt run() to the RunContext signature introduced in modelcontextprotocol#318.
- Send the protocol version the server negotiated in the initialize
  result on follow-up requests instead of hardcoding 2025-11-25,
  falling back to the requested spec version (the sse-multiple-streams
  pattern from modelcontextprotocol#415/modelcontextprotocol#416). Parsing the initialize body is bounded by a
  5s timeout, and every fetch shares one AbortController aborted when
  the scenario ends, so a server answering with a never-ending SSE
  stream can neither hang the run nor leak a held-open connection.
- Fail the scenario when initialize itself fails; previously a broken
  server (401/500) was reported as stateless and passed.
- Check the initialized notification response and fail when it is
  rejected, emitting SKIPPED placeholders for the lifecycle checks so
  per-check aggregation keeps its rows.
- Report unexpected DELETE statuses as WARNING instead of FAILURE (the
  spec never pins a success status for session termination).
- Treat 404 on DELETE as an already-terminated session only when the
  follow-up request confirms the session is gone; a 404 from a server
  whose session still responds (likely no DELETE route) is a WARNING
  with the 404 check skipped, rather than a misattributed failure.
- Exclude the scenario from the draft spec version via removedIn:
  sessions were removed entirely by SEP-2575.
@pcarleton

Copy link
Copy Markdown
Member

Thanks @pugsatoshi. Solid scenario design and worth landing; main moved a lot under this PR, so rather than a change-request round trip I pushed the rebase and review fixes onto your branch:

  1. Merge with current main, plus the run(ctx: RunContext) signature from feat: version-aware Connection abstraction for server scenarios #318.
  2. Follow-up requests now carry the protocol version the server negotiated in the initialize result (parsed from JSON or SSE, bounded at 5s), falling back to the requested spec version, matching the fix: use negotiated protocol version in server-sse-multiple-streams raw POSTs #415/fix: use negotiated protocol version in server-sse-polling raw requests #416 convention. Previously a compliant server on a 2025-03-26/06-18 run would 400 the hardcoded 2025-11-25 follow-ups and false-fail.
  3. False-pass guards: a failed initialize (401/500/dead endpoint) is now a FAILURE instead of a green "stateless" INFO, and the initialized notification response is checked.
  4. DELETE status handling: the spec never pins a success status for session termination (filed Spec gap: no defined success response for session-termination DELETE (only 405 is specified) #429 to chase that upstream), so unexpected statuses are WARNING rather than FAILURE, and 404 counts as already-terminated only when the follow-up probe confirms the session is gone.
  5. removedIn: draft — sessions were removed entirely by SEP-2575.

Verified: 494/494 unit tests, scenario green end-to-end at 2025-03-26/06-18/11-25 against the everything-server, and all five SDK everything-servers (typescript, python, go, rust, csharp) pass 3/3.

@pcarleton pcarleton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed, fixes applied on-branch, SDK matrix green (ts/py/go/rust/csharp).

@pcarleton
pcarleton merged commit 3531a64 into modelcontextprotocol:main Jul 31, 2026
4 checks passed
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.

2 participants