Skip to content

Commit 12d7fc1

Browse files
test(e2e): hosting-entry-http arm-posture fix; widen method-405 probe set
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.
1 parent 2b63ce6 commit 12d7fc1

3 files changed

Lines changed: 9 additions & 7 deletions

File tree

test/e2e/requirements.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2381,9 +2381,9 @@ export const REQUIREMENTS: Record<string, Requirement> = {
23812381
'typescript:hosting:entry:method-405': {
23822382
source: 'sdk',
23832383
behavior:
2384-
'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.',
2384+
'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.',
23852385
transports: ['entryStateless', 'entryModern'],
2386-
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.'
2386+
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.'
23872387
},
23882388
'typescript:hosting:entry:parse-error-400': {
23892389
source: 'https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#sending-messages-to-the-server',

test/e2e/scenarios/hosting-entry-http.test.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@ function echoFactory(_ctx?: McpRequestContext): McpServer {
3232

3333
verifies('typescript:hosting:entry:method-405', async ({ transport }: TestArgs) => {
3434
const client = new Client({ name: 'method-405-client', version: '1.0.0' });
35-
await using wired = await wire(transport, echoFactory, client, { entry: { legacy: 'stateless' } });
35+
// No `entry` override: the arm posture (`stateless` on entryStateless,
36+
// `reject` on entryModern) is the configuration under test.
37+
await using wired = await wire(transport, echoFactory, client);
3638

37-
for (const method of ['PUT', 'PATCH']) {
39+
for (const method of ['GET', 'DELETE', 'PUT', 'PATCH']) {
3840
const response = await wired.fetch!(wired.url!, { method });
3941
expect(response.status).toBe(405);
4042
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)
4648

4749
verifies('typescript:hosting:entry:parse-error-400', async ({ transport }: TestArgs) => {
4850
const client = new Client({ name: 'parse-error-client', version: '1.0.0' });
49-
await using wired = await wire(transport, echoFactory, client, { entry: { legacy: 'stateless' } });
51+
await using wired = await wire(transport, echoFactory, client);
5052

5153
const response = await wired.fetch!(wired.url!, {
5254
method: 'POST',
@@ -138,7 +140,7 @@ verifies('typescript:hosting:entry:legacy-protocol-version-default', async ({ tr
138140

139141
verifies('typescript:hosting:entry:no-session-id', async ({ transport }: TestArgs) => {
140142
const client = new Client({ name: 'no-session-id-client', version: '1.0.0' });
141-
await using wired = await wire(transport, echoFactory, client, { entry: { legacy: 'stateless' } });
143+
await using wired = await wire(transport, echoFactory, client);
142144

143145
// A typed round trip through the wired client (so both the connect-time
144146
// negotiation and a follow-up request are recorded), then assert no

test/e2e/scenarios/hosting-entry.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ verifies('typescript:hosting:entry:strict-rejects-legacy', async ({ transport }:
123123

124124
verifies('typescript:hosting:entry:notification-202', async ({ transport }: TestArgs) => {
125125
const client = new Client({ name: 'notify-client', version: '1.0.0' });
126-
await using wired = await wire(transport, greetFactory, client, { entry: { legacy: 'stateless' } });
126+
await using wired = await wire(transport, greetFactory, client);
127127

128128
// 2025 leg: an envelope-less notification rides the legacy stateless slot.
129129
// 2026 leg: the notification carries the per-request envelope and a method

0 commit comments

Comments
 (0)