Skip to content

Commit 2386223

Browse files
committed
Match the exact MCP restart envelope before retrying
1 parent 3a12f69 commit 2386223

1 file changed

Lines changed: 67 additions & 5 deletions

File tree

‎e2e/cloud/mcp-session-cap-eviction.test.ts‎

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
// the whole boot (see that file for the value and its headroom story), so
2020
// this test can cross it with a bounded number of real sessions instead of
2121
// registering the production default of 32.
22-
import { expect } from "@effect/vitest";
23-
import { Effect, Schedule } from "effect";
22+
import { expect, it } from "@effect/vitest";
23+
import { Effect, Option, Schedule, Schema } from "effect";
2424

2525
import { scenario } from "../src/scenario";
2626
import { Mcp, Target, Telemetry } from "../src/services";
@@ -52,6 +52,68 @@ const postJson = (mcpUrl: string, bearer: string, body: unknown, sessionId?: str
5252
body: JSON.stringify(body),
5353
});
5454

55+
const decodeRestartEnvelope = Schema.decodeUnknownOption(
56+
Schema.fromJsonString(
57+
Schema.Struct({
58+
jsonrpc: Schema.Literal("2.0"),
59+
id: Schema.Null,
60+
error: Schema.Struct({
61+
code: Schema.Literal(-32001),
62+
message: Schema.Literal("MCP session is restarting, please retry"),
63+
}),
64+
}),
65+
),
66+
);
67+
68+
const isRestartResponse = (status: number, body: string): boolean =>
69+
status === 503 && Option.isSome(decodeRestartEnvelope(body));
70+
71+
it.each([
72+
[
73+
503,
74+
{
75+
jsonrpc: "2.0",
76+
id: null,
77+
error: { code: -32001, message: "MCP session is restarting, please retry" },
78+
},
79+
true,
80+
],
81+
[
82+
404,
83+
{
84+
jsonrpc: "2.0",
85+
id: null,
86+
error: { code: -32001, message: "MCP session is restarting, please retry" },
87+
},
88+
false,
89+
],
90+
[
91+
503,
92+
{
93+
jsonrpc: "2.0",
94+
id: null,
95+
error: { code: -32603, message: "MCP session is restarting, please retry" },
96+
},
97+
false,
98+
],
99+
[
100+
503,
101+
{
102+
jsonrpc: "2.0",
103+
id: null,
104+
error: { code: -32001, message: "MCP session is restarting unexpectedly" },
105+
},
106+
false,
107+
],
108+
[503, { error: "MCP session is restarting, please retry" }, false],
109+
] as const)("only retries the documented restart envelope (%s, %j)", (status, body, retry) => {
110+
expect(isRestartResponse(status, JSON.stringify(body))).toBe(retry);
111+
});
112+
113+
it("does not retry malformed restart responses", () => {
114+
expect(isRestartResponse(503, "MCP session is restarting, please retry")).toBe(false);
115+
});
116+
55117
/**
56118
* Opens one fresh MCP session under an already-minted bearer. `initialize`
57119
* without an existing `mcp-session-id` always mints a new session, the same
@@ -80,7 +142,7 @@ const openSession = async (
80142
// advertised delay. Treat it as transient here instead of failing the
81143
// scenario on a retryable platform blip.
82144
const RESTART_ATTEMPTS = 8;
83-
const RESTART_DELAY_MS = 250;
145+
const RESTART_DELAY_MS = 2_000; // The host advertises Retry-After: 2.
84146
let minted: { readonly response: Response; readonly sessionId: string } | undefined;
85147
for (let attempt = 0; attempt < RESTART_ATTEMPTS; attempt += 1) {
86148
const response = await postJson(mcpUrl, bearer, {
@@ -98,8 +160,8 @@ const openSession = async (
98160
minted = { response, sessionId: candidate };
99161
break;
100162
}
101-
const body = await response.text().catch(() => "");
102-
const isRestart = response.status === 503 && body.includes("MCP session is restarting");
163+
const body = await response.text();
164+
const isRestart = isRestartResponse(response.status, body);
103165
if (!isRestart) break;
104166
if (attempt === RESTART_ATTEMPTS - 1) break;
105167
await new Promise((resolve) => setTimeout(resolve, RESTART_DELAY_MS));

0 commit comments

Comments
 (0)