History

Revert the outbound MCP SDK v2 migration (#1697)

* Revert the outbound MCP SDK v2 migration * Drop the v2 MCP packages from the lockfile

Rhys Sullivan committed 2 months ago

Commit a5800a11 parent 54cde5e91 files · +5838 −10032Browse files

Showing the changes to e2e/cloud (1 of 91 files). Show all

Unified · Split

e2e/cloud/mcp-modern-protocol.test.ts+0−235
@@ -1,235 +0,0 @@
1−import { randomBytes, randomUUID } from "node:crypto";
2−
3−import { expect } from "@effect/vitest";
4−import { Effect } from "effect";
5−import { composePluginApi } from "@executor-js/api/server";
6−import { isInputRequiredResult } from "@modelcontextprotocol/client";
7−
8−import { scenario } from "../src/scenario";
9−import { Api, Mcp, Target } from "../src/services";
10−import {
11− callModernToolWithInputRequired,
12− connectModernMcpClient,
13− MODERN_MCP_PROTOCOL_VERSION,
14− modernToolText,
15− readModernMcpAuthChallenge,
16−} from "../src/surfaces/modern-mcp";
17−import type { Identity } from "../src/target";
18−
19−const coreApi = composePluginApi([] as const);
20−const LEGACY_PROTOCOL_VERSION = "2025-03-26";
21−const JSON_AND_SSE = "application/json, text/event-stream";
22−
23−const emailOf = (identity: Identity): string => identity.credentials?.email ?? identity.label;
24−
25−const legacyInitialize = {
26− jsonrpc: "2.0" as const,
27− id: 1,
28− method: "initialize",
29− params: {
30− protocolVersion: LEGACY_PROTOCOL_VERSION,
31− capabilities: {},
32− clientInfo: { name: "executor-modern-e2e-control", version: "1.0.0" },
33− },
34−};
35−
36−const postLegacy = (
37− url: string,
38− body: unknown,
39− options?: { readonly bearer?: string; readonly sessionId?: string },
40−): Promise<Response> =>
41− fetch(url, {
42− method: "POST",
43− headers: {
44− accept: JSON_AND_SSE,
45− "content-type": "application/json",
46− "mcp-protocol-version": LEGACY_PROTOCOL_VERSION,
47− ...(options?.bearer ? { authorization: `Bearer ${options.bearer}` } : {}),
48− ...(options?.sessionId ? { "mcp-session-id": options.sessionId } : {}),
49− },
50− body: JSON.stringify(body),
51− });
52−
53−scenario(
54− "MCP modern protocol · a pinned 2026 client discovers, lists, and executes while legacy isolation stays intact",
55− {},
56− Effect.scoped(
57− Effect.gen(function* () {
58− const target = yield* Target;
59− const mcp = yield* Mcp;
60− const identity = yield* target.newIdentity();
61− const bearer = yield* mcp.mintBearer(emailOf(identity));
62− const client = yield* connectModernMcpClient({
63− url: target.mcpUrl,
64− bearer,
65− mode: { pin: MODERN_MCP_PROTOCOL_VERSION },
66− });
67−
68− expect(client.getProtocolEra(), "the pinned client selected the modern era").toBe("modern");
69− expect(client.getNegotiatedProtocolVersion(), "the exact revision was selected").toBe(
70− MODERN_MCP_PROTOCOL_VERSION,
71− );
72− expect(
73− client.getDiscoverResult()?.supportedVersions,
74− "server/discover advertises the pinned revision",
75− ).toContain(MODERN_MCP_PROTOCOL_VERSION);
76−
77− const tools = yield* Effect.promise(() => client.listTools());
78− expect(
79− tools.tools.map((tool) => tool.name),
80− "the modern catalog advertises Executor's execute tool",
81− ).toContain("execute");
82−
83− const result = yield* Effect.promise(() =>
84− client.callTool({ name: "execute", arguments: { code: "return 6 * 7;" } }),
85− );
86− expect(result.isError, "the modern execute call completes successfully").not.toBe(true);
87− expect(modernToolText(result), "the sandbox result crosses the modern wire").toBe("42");
88−
89− const foreignSession = yield* Effect.promise(() =>
90− postLegacy(
91− target.mcpUrl,
92− { jsonrpc: "2.0", id: 2, method: "tools/list", params: {} },
93− { bearer, sessionId: `foreign-${randomUUID()}` },
94− ),
95− );
96− expect(
97− foreignSession.status,
98− "a foreign legacy session id remains a clean not-found response",
99− ).toBe(404);
100− const foreignBody = (yield* Effect.promise(() => foreignSession.json())) as {
101− readonly error?: { readonly code?: number; readonly message?: string };
102− };
103− expect(foreignBody.error?.code, "the legacy rejection remains a JSON-RPC error").toBe(-32001);
104− expect(
105− foreignBody.error?.message,
106− "the unknown legacy session stays a clean not-found error",
107− ).toBe("Session not found");
108−
109− const [modernChallenge, legacyChallenge] = yield* Effect.all([
110− readModernMcpAuthChallenge(target.mcpUrl),
111− Effect.promise(() => postLegacy(target.mcpUrl, legacyInitialize)),
112− ]);
113− expect(modernChallenge.status, "the unauthenticated modern probe is challenged").toBe(401);
114− expect(legacyChallenge.status, "the unauthenticated legacy initialize is challenged").toBe(
115− 401,
116− );
117− expect(
118− modernChallenge.wwwAuthenticate,
119− "modern and legacy entry paths publish the same Bearer challenge",
120− ).toBe(legacyChallenge.headers.get("www-authenticate"));
121− }),
122− ),
123−);
124−
125−scenario(
126− "MCP modern protocol · a default v2 auto client probes Executor and selects modern",
127− {},
128− Effect.scoped(
129− Effect.gen(function* () {
130− const target = yield* Target;
131− const mcp = yield* Mcp;
132− const identity = yield* target.newIdentity();
133− const bearer = yield* mcp.mintBearer(emailOf(identity));
134− const client = yield* connectModernMcpClient({
135− url: target.mcpUrl,
136− bearer,
137− mode: "auto",
138− });
139−
140− expect(client.getProtocolEra(), "auto negotiation selected the modern path").toBe("modern");
141− expect(client.getNegotiatedProtocolVersion(), "auto selected the current revision").toBe(
142− MODERN_MCP_PROTOCOL_VERSION,
143− );
144− expect(
145− client.getDiscoverResult()?.supportedVersions,
146− "the probe result records the server's modern offer",
147− ).toContain(MODERN_MCP_PROTOCOL_VERSION);
148− expect(
149− (yield* Effect.promise(() => client.listTools())).tools.map((tool) => tool.name),
150− "the selected path is usable",
151− ).toContain("execute");
152− }),
153− ),
154−);
155−
156−scenario(
157− "MCP modern protocol · native input_required resumes an approval-gated execution",
158− {},
159− Effect.scoped(
160− Effect.gen(function* () {
161− const target = yield* Target;
162− const { client: makeApiClient } = yield* Api;
163− const mcp = yield* Mcp;
164− const identity = yield* target.newIdentity();
165− const api = yield* makeApiClient(coreApi, identity);
166− const bearer = yield* mcp.mintBearer(emailOf(identity));
167− const pattern = `modern-native-${randomBytes(4).toString("hex")}.*`;
168− const code = `
169−const result = await tools.executor.coreTools.policies.create({
170− owner: "user",
171− pattern: ${JSON.stringify(pattern)},
172− action: "block",
173−});
174−return JSON.stringify(result);
175−`;
176−
177− const cleanup = api.policies.list().pipe(
178− Effect.flatMap((policies) =>
179− Effect.forEach(
180− policies.filter((policy) => policy.pattern === pattern),
181− (policy) =>
182− api.policies
183− .remove({ params: { policyId: policy.id }, payload: { owner: "user" } })
184− .pipe(Effect.ignore),
185− ),
186− ),
187− Effect.ignore,
188− );
189−
190− yield* Effect.gen(function* () {
191− const nativeUrl = new URL(target.mcpUrl);
192− nativeUrl.searchParams.set("elicitation_mode", "native");
193− const client = yield* connectModernMcpClient({
194− url: nativeUrl.toString(),
195− bearer,
196− mode: { pin: MODERN_MCP_PROTOCOL_VERSION },
197− manualInputRequired: true,
198− });
199−
200− const first = yield* callModernToolWithInputRequired(client, {
201− name: "execute",
202− arguments: { code },
203− });
204− expect(isInputRequiredResult(first), "the gated action requests native input").toBe(true);
205− if (!isInputRequiredResult(first)) return;
206− expect(
207− first.inputRequests?.elicitation,
208− "the pause carries an elicitation request",
209− ).toMatchObject({ method: "elicitation/create" });
210− expect(first.requestState, "the continuation state is opaque and present").toEqual(
211− expect.any(String),
212− );
213−
214− const completed = yield* callModernToolWithInputRequired(client, {
215− name: "execute",
216− arguments: { code },
217− inputResponses: { elicitation: { action: "accept", content: {} } },
218− requestState: first.requestState,
219− });
220− expect(isInputRequiredResult(completed), "the accepted round completes").toBe(false);
221− if (isInputRequiredResult(completed)) return;
222− expect(completed.isError, "the resumed execution succeeds").not.toBe(true);
223− expect(
224− modernToolText(completed),
225− "the tool result returns after the second round",
226− ).toContain('"ok":true');
227−
228− expect(
229− (yield* api.policies.list()).map((policy) => policy.pattern),
230− "the approved side effect ran",
231− ).toContain(pattern);
232− }).pipe(Effect.ensuring(cleanup));
233− }),
234− ),
235−);