From 5e5327b4d75385b87b189af84a6f8335b7585654 Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Fri, 19 Jun 2026 01:31:20 +0000 Subject: [PATCH 1/2] chore: design-proposal round marker (#19 SEP-2106, #39 client caching) No SDK changes. Design proposals written to the project docs/ tree (outside this worktree): - docs/2026-06-19-sep2106-design.md - docs/2026-06-19-client-caching-design.md Report lines appended to logs/agent-reports/37-overnight.md. From 3c4a8297460652ecc5161c636b3ac3141dd27fb5 Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Fri, 19 Jun 2026 02:57:44 +0000 Subject: [PATCH 2/2] test(e2e): hosting-entry-http arm-posture fix; widen method-405 probe set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit review-3 on #33: O1 MEDIUM — method-405 / parse-error-400 / no-session-id passed { entry: { legacy: 'stateless' } } to wire(), which spreads after the arm posture and so overrode entryModern's legacy:'reject' with 'stateless'. The entryModern cells were config-duplicates of entryStateless. Dropped the override entirely: the arm posture ('stateless' on entryStateless, 'reject' on entryModern) is exactly what each cell should test, so no override is needed. O2 LOW — method-405 now probes GET, DELETE, PUT, PATCH (was PUT, PATCH only). Both legs answer 405 / -32000 / 'Method not allowed.' for every non-POST method. Note reworded ('POSTed through' -> 'sent through'); behavior text updated to name the four methods now covered. --- test/e2e/requirements.ts | 4 ++-- test/e2e/scenarios/hosting-entry-http.test.ts | 10 ++++++---- test/e2e/scenarios/hosting-entry.test.ts | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/test/e2e/requirements.ts b/test/e2e/requirements.ts index 0959555c70..e2f3cc8a14 100644 --- a/test/e2e/requirements.ts +++ b/test/e2e/requirements.ts @@ -2381,9 +2381,9 @@ export const REQUIREMENTS: Record = { 'typescript:hosting:entry:method-405': { source: 'sdk', behavior: - '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.', + 'A non-POST HTTP method (GET, DELETE, 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.', transports: ['entryStateless', 'entryModern'], - 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.' + note: 'Runs on the createMcpHandler entry arms; each non-POST method is sent 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.' }, 'typescript:hosting:entry:parse-error-400': { source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#sending-messages-to-the-server', diff --git a/test/e2e/scenarios/hosting-entry-http.test.ts b/test/e2e/scenarios/hosting-entry-http.test.ts index 8048846335..8712da381a 100644 --- a/test/e2e/scenarios/hosting-entry-http.test.ts +++ b/test/e2e/scenarios/hosting-entry-http.test.ts @@ -32,9 +32,11 @@ function echoFactory(_ctx?: McpRequestContext): McpServer { verifies('typescript:hosting:entry:method-405', async ({ transport }: TestArgs) => { const client = new Client({ name: 'method-405-client', version: '1.0.0' }); - await using wired = await wire(transport, echoFactory, client, { entry: { legacy: 'stateless' } }); + // No `entry` override: the arm posture (`stateless` on entryStateless, + // `reject` on entryModern) is the configuration under test. + await using wired = await wire(transport, echoFactory, client); - for (const method of ['PUT', 'PATCH']) { + for (const method of ['GET', 'DELETE', 'PUT', 'PATCH']) { const response = await wired.fetch!(wired.url!, { method }); expect(response.status).toBe(405); const body = (await response.json()) as { jsonrpc: string; error: { code: number; message: string } }; @@ -46,7 +48,7 @@ verifies('typescript:hosting:entry:method-405', async ({ transport }: TestArgs) verifies('typescript:hosting:entry:parse-error-400', async ({ transport }: TestArgs) => { const client = new Client({ name: 'parse-error-client', version: '1.0.0' }); - await using wired = await wire(transport, echoFactory, client, { entry: { legacy: 'stateless' } }); + await using wired = await wire(transport, echoFactory, client); const response = await wired.fetch!(wired.url!, { method: 'POST', @@ -138,7 +140,7 @@ verifies('typescript:hosting:entry:legacy-protocol-version-default', async ({ tr verifies('typescript:hosting:entry:no-session-id', async ({ transport }: TestArgs) => { const client = new Client({ name: 'no-session-id-client', version: '1.0.0' }); - await using wired = await wire(transport, echoFactory, client, { entry: { legacy: 'stateless' } }); + await using wired = await wire(transport, echoFactory, client); // A typed round trip through the wired client (so both the connect-time // negotiation and a follow-up request are recorded), then assert no diff --git a/test/e2e/scenarios/hosting-entry.test.ts b/test/e2e/scenarios/hosting-entry.test.ts index 12e82aad5e..2ca08fd388 100644 --- a/test/e2e/scenarios/hosting-entry.test.ts +++ b/test/e2e/scenarios/hosting-entry.test.ts @@ -123,7 +123,7 @@ verifies('typescript:hosting:entry:strict-rejects-legacy', async ({ transport }: verifies('typescript:hosting:entry:notification-202', async ({ transport }: TestArgs) => { const client = new Client({ name: 'notify-client', version: '1.0.0' }); - await using wired = await wire(transport, greetFactory, client, { entry: { legacy: 'stateless' } }); + await using wired = await wire(transport, greetFactory, client); // 2025 leg: an envelope-less notification rides the legacy stateless slot. // 2026 leg: the notification carries the per-request envelope and a method