| 59 | }); |
| 60 | |
| 61 | const withClient = async ( |
| 62 | config: TestServerConfig, |
| 63 | run: (client: Client) => Promise<void>, |
| 64 | options?: { readonly manualInputRequired?: boolean }, |
| 65 | ) => { |
| 66 | const requestBodies = new WeakMap<Request, unknown>(); |
| 67 | const handler = createMcpHandler( |
| 68 | (context) => |
| 69 | Effect.runPromise( |
| 70 | Effect.gen(function* () { |
| 71 | const requestStatePrincipal = config.requestStatePrincipal?.() ?? "principal-test"; |
| 72 | const requestStateResource = config.requestStateResource?.() ?? defaultMcpResource; |
| 73 | const requestStateBinding = yield* Effect.promise(() => |
| 74 | mcpRequestStateBindingFromBody({ |
| 75 | body: context.requestInfo ? requestBodies.get(context.requestInfo) : undefined, |
| 76 | principal: requestStatePrincipal, |
| 77 | resource: requestStateResource, |
| 78 | }), |
| 79 | ); |
| 80 | return yield* buildMcpServer({ |
| 81 | ...config, |
| 82 | requestStateSigningKey: REQUEST_STATE_KEY, |
| 83 | requestStatePrincipal, |
| 84 | ...(requestStateBinding === null ? {} : { requestStateBinding }), |
| 85 | }); |
| 86 | }), |
| 87 | ), |
| 88 | { legacy: "reject" }, |
| 89 | ); |
| 90 | const transport = new StreamableHTTPClientTransport(new URL("http://executor.test/mcp"), { |
| 91 | fetch: async (input, init) => { |
| 92 | const request = |
| 93 | input instanceof Request ? new Request(input, init) : new Request(input.toString(), init); |
| 94 | requestBodies.set(request, await request.clone().json()); |
| 95 | return handler.fetch(request, { parsedBody: requestBodies.get(request) }); |
| 96 | }, |
| 97 | }); |
| 98 | const client = new Client( |
| 99 | { name: "executor-protocol-test", version: "1.0.0" }, |
| 100 | { |
| 101 | capabilities: { elicitation: { form: {} } }, |
| 102 | versionNegotiation: { mode: { pin: "2026-07-28" } }, |
| 103 | ...(options?.manualInputRequired ? { inputRequired: { autoFulfill: false } } : {}), |
| 104 | }, |
| 105 | ); |
| 106 | await client.connect(transport); |
| 107 | // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: test helper owns the client transport and in-process HTTP handler |
| 108 | try { |
| 109 | await run(client); |
| 110 | } finally { |
| 111 | await client.close(); |
| 112 | await handler.close(); |
| 113 | } |
| 114 | }; |
| 115 | |
| 116 | const manualToolCall = ( |
| 117 | client: Client, |