You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
test(e2e): dedicated entry-side cells for HTTP-mechanics and bearer-auth on createMcpHandler
Entry-side siblings of the hosting:http / hosting:stateless / hosting:auth /
hosting:context rows whose bodies hand-host their own server transport and so
never reach createMcpHandler when given an entry arm. Ten new
typescript:hosting:entry:* requirements + bodies, no edits to existing rows:
HTTP mechanics through the harness-hosted entry (wired.fetch probes):
- method-405 (both arms), parse-error-400 (both arms), no-session-id (both arms)
- legacy-accept-406, legacy-content-type-415, legacy-protocol-version-header-400,
legacy-protocol-version-default (entryStateless only — the legacy fallback
delegates to the streamable HTTP server transport whose validation is unchanged;
the modern per-request path does not apply Accept/Content-Type negotiation)
Per-request HTTP context and bearer-auth composition (self-hosted createMcpHandler,
matrix arm selects legacy posture + client pin):
- ctx-http-req-headers (both arms): custom client header reaches ctx.http.req
- auth:missing-401 (both arms): user-composed bearer gate answers 401 +
WWW-Authenticate before the entry; factory never runs
- auth:authinfo-propagates (both arms): authInfo handed to handler.fetch reaches
ctx.http.authInfo and the factory's McpRequestContext.authInfo unchanged
Test-only; no src/ changes; no existing scenario-body or knownFailure edits.
note: "Runs on the entryModern arm; the body wires one harness-hosted endpoint per responseMode value via wire()'s entry.responseMode option and asserts the response shape on the arm-recorded HTTP exchanges."
2399
2399
},
2400
+
2401
+
// v2 features: dual-era HTTP entry — HTTP request mechanics on the harness-hosted entry
2402
+
// (entry-side siblings of the hosting:http / hosting:stateless families, which hand-host the
2403
+
// server transport themselves and so never reach createMcpHandler when given an entry arm).
2404
+
2405
+
'typescript:hosting:entry:method-405': {
2406
+
source: 'sdk',
2407
+
behavior:
2408
+
'An unsupported HTTP method (PUT, PATCH) on a createMcpHandler endpoint is answered 405 with a JSON-RPC Method-not-allowed body on both legs: the stateless legacy fallback rejects every non-POST method, and the modern-only strict path rejects body-less non-POST traffic via the modern-only-method-not-allowed cell.',
2409
+
transports: ['entryStateless','entryModern'],
2410
+
note: 'Runs on the createMcpHandler entry arms; the unsupported methods are POSTed through wired.fetch so the HTTP status and body are observed directly. The entry does not emit an Allow header (the per-session server transport does), so only the status and JSON-RPC error shape are pinned.'
'A POST whose body is not valid JSON is answered 400 by a createMcpHandler endpoint on both legs, with a JSON-RPC Parse-error (-32700) body: the entry classifier reads no envelope claim from a non-JSON body, so the stateless legacy fallback delegates the parse error and the modern-only strict path emits it itself.',
2416
+
transports: ['entryStateless','entryModern'],
2417
+
note: 'Runs on the createMcpHandler entry arms; the malformed body is POSTed through wired.fetch so the HTTP status and JSON-RPC error code are observed directly.'
2418
+
},
2419
+
'typescript:hosting:entry:legacy-accept-406': {
2420
+
source: 'sdk',
2421
+
behavior:
2422
+
"A 2025-era POST whose Accept header does not allow both application/json and text/event-stream is answered 406 by a createMcpHandler endpoint's stateless legacy slot (the legacy fallback delegates to the streamable HTTP server transport, whose Accept negotiation is unchanged).",
2423
+
transports: ['entryStateless'],
2424
+
removedInSpecVersion: '2026-07-28',
2425
+
note: 'Runs on the entryStateless arm and is bounded to the 2025-11-25 axis: Accept negotiation is enforced by the legacy server transport the fallback delegates to, not by the modern per-request path. The probes are POSTed through wired.fetch so the 406 is observed directly.'
"A 2025-era POST whose Content-Type is not application/json is answered 415 by a createMcpHandler endpoint's stateless legacy slot (the legacy fallback delegates to the streamable HTTP server transport, whose Content-Type validation is unchanged).",
2431
+
transports: ['entryStateless'],
2432
+
removedInSpecVersion: '2026-07-28',
2433
+
note: 'Runs on the entryStateless arm and is bounded to the 2025-11-25 axis: Content-Type validation is enforced by the legacy server transport the fallback delegates to. The entry classifier reads the body before that delegate runs, so a body that happens to be valid JSON is still rejected on Content-Type alone.'
"A 2025-era POST carrying an MCP-Protocol-Version header naming an unknown revision is answered 400 by a createMcpHandler endpoint's stateless legacy slot, with the response body naming the supported version(s).",
2439
+
transports: ['entryStateless'],
2440
+
removedInSpecVersion: '2026-07-28',
2441
+
note: 'Runs on the entryStateless arm and is bounded to the 2025-11-25 axis: the protocol-version header check is enforced by the legacy server transport the fallback delegates to. Header/body cross-checks on the modern path are pinned by the entry std-header rows; this row pins only that a non-modern unsupported header still surfaces as 400 through the fallback.'
"A 2025-era POST without an MCP-Protocol-Version header is served by a createMcpHandler endpoint's stateless legacy slot under the assumed default protocol version (2025-03-26): a tools/list round-trips without the header.",
2447
+
transports: ['entryStateless'],
2448
+
removedInSpecVersion: '2026-07-28',
2449
+
note: 'Runs on the entryStateless arm and is bounded to the 2025-11-25 axis. The probe is POSTed through wired.fetch with only Accept and Content-Type headers so the default-version path is the one exercised.'
2450
+
},
2451
+
'typescript:hosting:entry:no-session-id': {
2452
+
source: 'sdk',
2453
+
behavior:
2454
+
'A createMcpHandler endpoint emits no Mcp-Session-Id response header on either leg: the stateless legacy fallback hosts a sessionless server transport per request, and the modern per-request path has no session at all — every recorded exchange of a connect-then-tools/call round trip carries no session header.',
2455
+
transports: ['entryStateless','entryModern'],
2456
+
note: "Runs on the createMcpHandler entry arms; asserted on the arm-recorded httpLog response clones. The entry's BYO sessionful composition is the only way to issue a session id and is pinned by typescript:hosting:entry:byo-sessionful-legacy."
"A custom HTTP header set on the StreamableHTTP client transport reaches a tool handler's ctx.http.req as Fetch Headers when the server is hosted by createMcpHandler, on both legs: the stateless legacy fallback and the modern per-request path each thread the original Request through to handler context.",
2462
+
transports: ['entryStateless','entryModern'],
2463
+
note: "The body hosts createMcpHandler itself (the wire() entry arm builds the client transport without a custom-header hook) and the matrix arm selects the legacy posture and client pin: entryStateless drives a plain client through legacy: 'stateless', entryModern drives a 2026-07-28-pinned client through legacy: 'reject'."
2464
+
},
2465
+
2466
+
// v2 features: dual-era HTTP entry — bearer auth composed in front of createMcpHandler
2467
+
// (entry-side siblings of the hosting:auth family, which hand-hosts an Express stack and so
2468
+
// never reaches createMcpHandler when given an entry arm). The SDK does not enforce endpoint
2469
+
// authentication on either era — bearer/OAuth auth is deployer-composed middleware in front of
2470
+
// whichever handler is mounted, and the entry passes a verified AuthInfo through unchanged.
'A bearer-protected createMcpHandler deployment — a user-composed verification gate in front of handler.fetch — answers a request without an Authorization header with 401 and a WWW-Authenticate challenge on both legs, and the entry is never reached for that request (no factory call).',
2476
+
transports: ['entryStateless','entryModern'],
2477
+
note: "The body hosts createMcpHandler itself behind the documented bearer-gate composition (verify the Authorization header, then call handler.fetch(request, { authInfo })); the matrix arm selects the legacy posture and client pin. The 401/WWW-Authenticate is the gate's own response — the entry performs no token verification — and the body asserts the gate composes correctly with both serving paths."
"A verified AuthInfo handed to createMcpHandler.fetch(request, { authInfo }) reaches per-request handlers as ctx.http.authInfo unchanged on both legs, and the same AuthInfo is exposed on the factory's per-request context (McpRequestContext.authInfo) before the instance is built.",
2483
+
transports: ['entryStateless','entryModern'],
2484
+
note: 'The body hosts createMcpHandler itself behind the documented bearer-gate composition; the matrix arm selects the legacy posture and client pin. authInfo is strictly pass-through — the entry never derives it from request headers — so the cell pins delivery, not verification. The OAuth client flow that obtains the token is hosting-agnostic and is covered by the client-auth family; the dedicated client-completes-OAuth-then-negotiates-2026 journey rides the auth-package redo (M13.1) so it is targeted at the surviving auth surface.'
0 commit comments