import { describe, expect, it } from "@effect/vitest";import { Cause, Effect, Exit, Layer, Option, Predicate, Schema, Tracer } from "effect";import { HttpClient, HttpClientRequest, HttpClientResponse, HttpServerResponse,} from "effect/unstable/http"; import { AuthTemplateSlug, ConnectionName, IntegrationSlug, OAuthClientSlug, ToolAddress, createExecutor,} from "@executor-js/sdk";import { makeTestConfig, memoryCredentialsPlugin, scopesFromAuthorizeUrl, serveOAuthTestServer, serveTestHttpApp,} from "@executor-js/sdk/testing"; import { createMcpConnector } from "./connection";import { mcpPlugin, userFacingProbeMessage } from "./plugin";import { McpInvocationError } from "./errors";import { extractManifestFromListToolsResult, deriveMcpNamespace, joinToolPath } from "./manifest";import { makeAnnotationsMcpServer, serveMcpServer } from "../testing"; // removed: the v1 addSource / scopes / secrets / credential-binding / usages /// sources.configure / multi-scope shadowing suites. v2 has no scope stack, no// secrets table, and no credential bindings — an MCP server is registered as an// integration (`addServer`) and a connection IS the credential (created via// `connections.create` / `oauth.start`). Owner isolation is covered by// owner-isolation.test.ts; the end-to-end auth/header path is covered by// elicitation.test.ts + owner-isolation.test.ts. const TEMPLATE = AuthTemplateSlug.make("none"); const JsonRpcId = Schema.Union([Schema.String, Schema.Number, Schema.Null]);const JsonRpcRequest = Schema.Struct({ id: Schema.optional(JsonRpcId), method: Schema.String,});type JsonRpcRequest = typeof JsonRpcRequest.Type; const decodeJsonRpcRequest = Schema.decodeUnknownOption(Schema.fromJsonString(JsonRpcRequest)); const jsonRpcResult = (request: JsonRpcRequest, result: unknown) => HttpServerResponse.jsonUnsafe({ jsonrpc: "2.0", id: request.id ?? null, result, }); // The call-tool fixtures share one JSON-RPC scaffold (handshake, tool listing,// unknown-method rejection); only the `tools/call` response varies. Each// scenario supplies that branch via a `CallToolResponder`.type CallToolResponder = (rpc: JsonRpcRequest) => ReturnType<typeof HttpServerResponse.text>; const callToolFixtureResponse = (rpc: JsonRpcRequest, callTool: CallToolResponder) => { if (rpc.method === "initialize") { return jsonRpcResult(rpc, { protocolVersion: "2025-06-18", capabilities: { tools: {} }, serverInfo: { name: "call-tool-fixture", version: "1.0.0" }, }); } if (rpc.method === "notifications/initialized") { return HttpServerResponse.text("", { status: 202 }); } if (rpc.method === "tools/list") { return jsonRpcResult(rpc, { tools: [ { name: "explode", description: "Returns a failure from tools/call", inputSchema: { type: "object", properties: {} }, }, ], }); } if (rpc.method === "tools/call") { return callTool(rpc); } return HttpServerResponse.text("Unexpected JSON-RPC method", { status: 400 });}; const serveCallToolServer = (callTool: CallToolResponder) => serveTestHttpApp((request) => Effect.gen(function* () { if (request.method === "GET") { return HttpServerResponse.text("SSE disabled", { status: 405 }); } const body = yield* request.text.pipe(Effect.orDie); return Option.match(decodeJsonRpcRequest(body), { onNone: () => HttpServerResponse.text("Invalid JSON-RPC fixture request", { status: 400 }), onSome: (rpc) => callToolFixtureResponse(rpc, callTool), }); }), ); const rejectedOAuthDiscoveryLayer = (endpoint: string) => { const issuer = new URL(endpoint).origin; const requests: string[] = []; const layer = Layer.succeed(HttpClient.HttpClient)( HttpClient.make((request: HttpClientRequest.HttpClientRequest) => { requests.push(request.url); const url = new URL(request.url); const response = request.url === endpoint ? new Response("", { status: 401 }) : url.pathname === "/.well-known/oauth-protected-resource/mcp" ? Response.json({ resource: endpoint, authorization_servers: [issuer] }) : url.pathname === "/.well-known/oauth-authorization-server" ? Response.json({ issuer, authorization_endpoint: `${issuer}/authorize`, token_endpoint: `${issuer}/token`, registration_endpoint: `${issuer}/register`, response_types_supported: ["code"], code_challenge_methods_supported: ["S256"], }) : url.pathname === "/register" ? Response.json( { client_id: "replacement-client", redirect_uris: ["http://localhost/oauth/callback"], grant_types: ["authorization_code", "refresh_token"], response_types: ["code"], token_endpoint_auth_method: "none", }, { status: 201 }, ) : new Response("unexpected request", { status: 500 }); return Effect.succeed(HttpClientResponse.fromWeb(request, response)); }), ); return { layer, requests };}; const clientRpcOf = (request: HttpClientRequest.HttpClientRequest): JsonRpcRequest | undefined => Predicate.isTagged(request.body, "Uint8Array") ? Option.getOrUndefined(decodeJsonRpcRequest(new TextDecoder().decode(request.body.body))) : undefined; /** Streamable-http fixture for the post-handshake auth wall: the handshake * methods succeed and `tools/list` answers 401 for the first * `revokedListResponses` calls (Infinity = the bearer stays revoked). The * OAuth discovery + DCR endpoints are served so an SDK fallback that DID see * the 401 could register — the ledger proves it never gets there. Entries are * `pathname` or `pathname#jsonRpcMethod`. */const listRejectionFixtureLayer = (endpoint: string, revokedListResponses: number) => { const issuer = new URL(endpoint).origin; const requests: string[] = []; let remaining = revokedListResponses; const jsonRpc = (rpc: JsonRpcRequest, result: unknown) => Response.json({ jsonrpc: "2.0", id: rpc.id ?? null, result }); const layer = Layer.succeed(HttpClient.HttpClient)( HttpClient.make((request: HttpClientRequest.HttpClientRequest) => { const url = new URL(request.url); const rpc = request.url === endpoint ? clientRpcOf(request) : undefined; requests.push(rpc === undefined ? url.pathname : `${url.pathname}#${rpc.method}`); const respond = (response: Response) => Effect.succeed(HttpClientResponse.fromWeb(request, response)); if (request.url === endpoint) { if (rpc === undefined) return respond(new Response("SSE disabled", { status: 405 })); if (rpc.method === "initialize") { return respond( jsonRpc(rpc, { protocolVersion: "2025-06-18", capabilities: { tools: {} }, serverInfo: { name: "list-reject-fixture", version: "1.0.0" }, }), ); } if (rpc.method === "notifications/initialized") { return respond(new Response("", { status: 202 })); } if (rpc.method === "tools/list") { if (remaining > 0) { remaining -= 1; return respond(new Response("", { status: 401 })); } return respond( jsonRpc(rpc, { tools: [ { name: "echo_back", description: "Echoes", inputSchema: { type: "object", properties: {} }, }, ], }), ); } return respond(new Response("Unexpected JSON-RPC method", { status: 400 })); } const response = url.pathname === "/.well-known/oauth-protected-resource/mcp" ? Response.json({ resource: endpoint, authorization_servers: [issuer] }) : url.pathname === "/.well-known/oauth-authorization-server" ? Response.json({ issuer, authorization_endpoint: `${issuer}/authorize`, token_endpoint: `${issuer}/token`, registration_endpoint: `${issuer}/register`, response_types_supported: ["code"], code_challenge_methods_supported: ["S256"], }) : url.pathname === "/register" ? Response.json( { client_id: "replacement-client", redirect_uris: ["http://localhost/oauth/callback"], grant_types: ["authorization_code", "refresh_token"], response_types: ["code"], token_endpoint_auth_method: "none", }, { status: 201 }, ) : new Response("unexpected request", { status: 500 }); return respond(response); }), ); return { layer, requests };}; // `tools/call` responders. Both embed a "do-not-leak" sentinel the assertions// confirm never reaches the caller-facing failure.const httpStatusCallTool = (status: number): CallToolResponder => () => HttpServerResponse.text("do-not-leak: upstream auth challenge", { status }); const jsonRpcErrorCallTool = (code: number): CallToolResponder => (rpc) => HttpServerResponse.jsonUnsafe({ jsonrpc: "2.0", id: rpc.id ?? null, error: { code, message: "application-level do-not-leak" }, }); const seedCallToolExecutor = (input: { slug: string; callTool: CallToolResponder; /** Seed an oauth2-templated connection with a static access token, so the * transport gets a real authProvider — the production OAuth path where the * SDK's own challenge handling would otherwise swallow scope signals. */ oauth?: boolean;}) => Effect.acquireRelease( Effect.gen(function* () { const server = yield* serveCallToolServer(input.callTool); const config = makeTestConfig({ plugins: [memoryCredentialsPlugin(), mcpPlugin()] as const, }); const executor = yield* createExecutor(config); yield* executor.mcp.addServer({ name: "Call tool fixture", endpoint: server.url("/mcp"), slug: input.slug, remoteTransport: "streamable-http", ...(input.oauth ? { auth: { kind: "oauth2" as const } } : {}), }); yield* executor.connections.create({ owner: "org", name: ConnectionName.make("main"), integration: IntegrationSlug.make(input.slug), template: input.oauth ? AuthTemplateSlug.make("oauth2") : TEMPLATE, value: input.oauth ? "static-access-token" : "", }); return { config, executor, toolAddress: ToolAddress.make(`tools.${input.slug}.org.main.explode`), } as const; }), ({ config, executor }) => Effect.gen(function* () { yield* executor.close().pipe(Effect.ignore); yield* Effect.promise(() => config.testDb.close()).pipe(Effect.ignore); }), ); // ---------------------------------------------------------------------------// Manifest extraction// --------------------------------------------------------------------------- describe("extractManifestFromListToolsResult", () => { it.effect("extracts tools from a valid listTools response", () => Effect.sync(() => { const result = extractManifestFromListToolsResult({ tools: [ { name: "get_weather", description: "Get weather for a location", inputSchema: { type: "object", properties: { location: { type: "string" } }, }, }, { name: "search", description: "Search the web" }, ], }); expect(result.tools).toHaveLength(2); expect(result.tools[0]!.toolName).toBe("get_weather"); expect(result.tools[0]!.toolId).toBe("get_weather"); expect(result.tools[0]!.description).toBe("Get weather for a location"); expect(result.tools[1]!.toolName).toBe("search"); }), ); it.effect("sanitizes tool IDs", () => Effect.sync(() => { const result = extractManifestFromListToolsResult({ tools: [ { name: "My Tool!!", description: null }, { name: "My Tool!!", description: null }, ], }); expect(result.tools[0]!.toolId).toBe("my_tool"); expect(result.tools[1]!.toolId).toBe("my_tool_2"); }), ); it.effect("handles empty tools list", () => Effect.sync(() => { const result = extractManifestFromListToolsResult({ tools: [] }); expect(result.tools).toHaveLength(0); }), ); it.effect("extracts server metadata", () => Effect.sync(() => { const result = extractManifestFromListToolsResult( { tools: [] }, { serverInfo: { name: "test-server", version: "1.0.0" } }, ); expect(result.server?.name).toBe("test-server"); expect(result.server?.version).toBe("1.0.0"); }), ); it.effect("decodes upstream tool annotations", () => Effect.sync(() => { const result = extractManifestFromListToolsResult({ tools: [ { name: "delete", annotations: { destructiveHint: true } }, { name: "list", annotations: { readOnlyHint: true } }, { name: "ping" }, ], }); expect(result.tools[0]!.annotations?.destructiveHint).toBe(true); expect(result.tools[1]!.annotations?.readOnlyHint).toBe(true); expect(result.tools[2]!.annotations).toBeUndefined(); }), ); it.effect("carries the reserved `_meta` map through verbatim", () => Effect.sync(() => { const meta = { serverName: "time", shortDescription: "Current time", defer_loading: false, nested: { any: ["shape"] }, }; const result = extractManifestFromListToolsResult({ tools: [ { name: "time_get_current_time", description: "Get the current time", inputSchema: { type: "object" }, _meta: meta, }, { name: "no_meta", description: "Has no _meta" }, ], }); expect(result.tools[0]!._meta).toEqual(meta); expect(result.tools[1]!._meta).toBeUndefined(); }), ); // `_meta` is opaque and server-controlled, so a value that does not match the // spec's map shape must not take the rest of the list down with it. it.effect("ignores a malformed `_meta` without dropping the tool", () => Effect.sync(() => { const result = extractManifestFromListToolsResult({ tools: [{ name: "odd_meta", _meta: "not-a-map" }, { name: "plain" }], }); expect(result.tools.map((tool) => tool.toolName)).toEqual(["odd_meta", "plain"]); expect(result.tools[0]!._meta).toBeUndefined(); }), );}); // ---------------------------------------------------------------------------// Namespace derivation// --------------------------------------------------------------------------- describe("deriveMcpNamespace", () => { it.effect("derives from name", () => Effect.sync(() => { expect(deriveMcpNamespace({ name: "GitHub MCP" })).toBe("github_mcp"); }), ); it.effect("derives from endpoint", () => Effect.sync(() => { expect(deriveMcpNamespace({ endpoint: "https://api.example.com/mcp" })).toBe( "api_example_com", ); }), ); it.effect("derives from command", () => Effect.sync(() => { expect(deriveMcpNamespace({ command: "/usr/local/bin/my-mcp-server" })).toBe("my_mcp_server"); }), ); it.effect("falls back to 'mcp'", () => Effect.sync(() => { expect(deriveMcpNamespace({})).toBe("mcp"); }), );}); // ---------------------------------------------------------------------------// joinToolPath// --------------------------------------------------------------------------- describe("joinToolPath", () => { it.effect("joins namespace and toolId", () => Effect.sync(() => { expect(joinToolPath("github", "search")).toBe("github.search"); }), ); it.effect("returns toolId when namespace is undefined", () => Effect.sync(() => { expect(joinToolPath(undefined, "search")).toBe("search"); }), );}); // ---------------------------------------------------------------------------// Plugin lifecycle// --------------------------------------------------------------------------- describe("mcpPlugin", () => { it.effect("surfaces OAuth reauthorization from resolveTools as expired health", () => Effect.gen(function* () { const endpoint = "https://mcp.example.test/mcp"; const plugin = mcpPlugin(); const ledger = rejectedOAuthDiscoveryLayer(endpoint); const result = yield* plugin.resolveTools!({ config: { transport: "remote", endpoint, remoteTransport: "streamable-http", authenticationTemplate: [{ slug: "oauth2", kind: "oauth2" }], }, connection: { owner: "org", integration: IntegrationSlug.make("oauth_mcp"), name: ConnectionName.make("main"), }, template: AuthTemplateSlug.make("oauth2"), getValues: () => Effect.succeed({ token: "rejected-token" }), getValue: () => Effect.succeed("rejected-token"), httpClientLayer: ledger.layer, ctx: null as never, integration: null as never, storage: {}, }); expect(result).toMatchObject({ tools: [], incomplete: true, health: { status: "expired", detail: expect.stringContaining("reauthorization"), }, }); expect(ledger.requests.filter((url) => new URL(url).pathname === "/register")).toEqual([]); }), );Lines 1–500 of 1538. Load more
import { describe, expect, it } from "@effect/vitest"; import { Cause, Effect, Exit, Layer, Option, Predicate, Schema, Tracer } from "effect"; import { HttpClient, HttpClientRequest, HttpClientResponse, HttpServerResponse, } from "effect/unstable/http"; import { AuthTemplateSlug, ConnectionName, IntegrationSlug, OAuthClientSlug, ToolAddress, createExecutor, } from "@executor-js/sdk"; import { makeTestConfig, memoryCredentialsPlugin, scopesFromAuthorizeUrl, serveOAuthTestServer, serveTestHttpApp, } from "@executor-js/sdk/testing"; import { createMcpConnector } from "./connection"; import { mcpPlugin, userFacingProbeMessage } from "./plugin"; import { McpInvocationError } from "./errors"; import { extractManifestFromListToolsResult, deriveMcpNamespace, joinToolPath } from "./manifest"; import { makeAnnotationsMcpServer, serveMcpServer } from "../testing"; // removed: the v1 addSource / scopes / secrets / credential-binding / usages / // sources.configure / multi-scope shadowing suites. v2 has no scope stack, no // secrets table, and no credential bindings — an MCP server is registered as an // integration (`addServer`) and a connection IS the credential (created via // `connections.create` / `oauth.start`). Owner isolation is covered by // owner-isolation.test.ts; the end-to-end auth/header path is covered by // elicitation.test.ts + owner-isolation.test.ts. const TEMPLATE = AuthTemplateSlug.make("none"); const JsonRpcId = Schema.Union([Schema.String, Schema.Number, Schema.Null]); const JsonRpcRequest = Schema.Struct({ id: Schema.optional(JsonRpcId), method: Schema.String, }); type JsonRpcRequest = typeof JsonRpcRequest.Type; const decodeJsonRpcRequest = Schema.decodeUnknownOption(Schema.fromJsonString(JsonRpcRequest)); const jsonRpcResult = (request: JsonRpcRequest, result: unknown) => HttpServerResponse.jsonUnsafe({ jsonrpc: "2.0", id: request.id ?? null, result, }); // The call-tool fixtures share one JSON-RPC scaffold (handshake, tool listing, // unknown-method rejection); only the `tools/call` response varies. Each // scenario supplies that branch via a `CallToolResponder`. type CallToolResponder = (rpc: JsonRpcRequest) => ReturnType<typeof HttpServerResponse.text>; const callToolFixtureResponse = (rpc: JsonRpcRequest, callTool: CallToolResponder) => { if (rpc.method === "initialize") { return jsonRpcResult(rpc, { protocolVersion: "2025-06-18", capabilities: { tools: {} }, serverInfo: { name: "call-tool-fixture", version: "1.0.0" }, }); } if (rpc.method === "notifications/initialized") { return HttpServerResponse.text("", { status: 202 }); } if (rpc.method === "tools/list") { return jsonRpcResult(rpc, { tools: [ { name: "explode", description: "Returns a failure from tools/call", inputSchema: { type: "object", properties: {} }, }, ], }); } if (rpc.method === "tools/call") { return callTool(rpc); } return HttpServerResponse.text("Unexpected JSON-RPC method", { status: 400 }); }; const serveCallToolServer = (callTool: CallToolResponder) => serveTestHttpApp((request) => Effect.gen(function* () { if (request.method === "GET") { return HttpServerResponse.text("SSE disabled", { status: 405 }); } const body = yield* request.text.pipe(Effect.orDie); return Option.match(decodeJsonRpcRequest(body), { onNone: () => HttpServerResponse.text("Invalid JSON-RPC fixture request", { status: 400 }), onSome: (rpc) => callToolFixtureResponse(rpc, callTool), }); }), ); const rejectedOAuthDiscoveryLayer = (endpoint: string) => { const issuer = new URL(endpoint).origin; const requests: string[] = []; const layer = Layer.succeed(HttpClient.HttpClient)( HttpClient.make((request: HttpClientRequest.HttpClientRequest) => { requests.push(request.url); const url = new URL(request.url); const response = request.url === endpoint ? new Response("", { status: 401 }) : url.pathname === "/.well-known/oauth-protected-resource/mcp" ? Response.json({ resource: endpoint, authorization_servers: [issuer] }) : url.pathname === "/.well-known/oauth-authorization-server" ? Response.json({ issuer, authorization_endpoint: `${issuer}/authorize`, token_endpoint: `${issuer}/token`, registration_endpoint: `${issuer}/register`, response_types_supported: ["code"], code_challenge_methods_supported: ["S256"], }) : url.pathname === "/register" ? Response.json( { client_id: "replacement-client", redirect_uris: ["http://localhost/oauth/callback"], grant_types: ["authorization_code", "refresh_token"], response_types: ["code"], token_endpoint_auth_method: "none", }, { status: 201 }, ) : new Response("unexpected request", { status: 500 }); return Effect.succeed(HttpClientResponse.fromWeb(request, response)); }), ); return { layer, requests }; }; const clientRpcOf = (request: HttpClientRequest.HttpClientRequest): JsonRpcRequest | undefined => Predicate.isTagged(request.body, "Uint8Array") ? Option.getOrUndefined(decodeJsonRpcRequest(new TextDecoder().decode(request.body.body))) : undefined; /** Streamable-http fixture for the post-handshake auth wall: the handshake * methods succeed and `tools/list` answers 401 for the first * `revokedListResponses` calls (Infinity = the bearer stays revoked). The * OAuth discovery + DCR endpoints are served so an SDK fallback that DID see * the 401 could register — the ledger proves it never gets there. Entries are * `pathname` or `pathname#jsonRpcMethod`. */ const listRejectionFixtureLayer = (endpoint: string, revokedListResponses: number) => { const issuer = new URL(endpoint).origin; const requests: string[] = []; let remaining = revokedListResponses; const jsonRpc = (rpc: JsonRpcRequest, result: unknown) => Response.json({ jsonrpc: "2.0", id: rpc.id ?? null, result }); const layer = Layer.succeed(HttpClient.HttpClient)( HttpClient.make((request: HttpClientRequest.HttpClientRequest) => { const url = new URL(request.url); const rpc = request.url === endpoint ? clientRpcOf(request) : undefined; requests.push(rpc === undefined ? url.pathname : `${url.pathname}#${rpc.method}`); const respond = (response: Response) => Effect.succeed(HttpClientResponse.fromWeb(request, response)); if (request.url === endpoint) { if (rpc === undefined) return respond(new Response("SSE disabled", { status: 405 })); if (rpc.method === "initialize") { return respond( jsonRpc(rpc, { protocolVersion: "2025-06-18", capabilities: { tools: {} }, serverInfo: { name: "list-reject-fixture", version: "1.0.0" }, }), ); } if (rpc.method === "notifications/initialized") { return respond(new Response("", { status: 202 })); } if (rpc.method === "tools/list") { if (remaining > 0) { remaining -= 1; return respond(new Response("", { status: 401 })); } return respond( jsonRpc(rpc, { tools: [ { name: "echo_back", description: "Echoes", inputSchema: { type: "object", properties: {} }, }, ], }), ); } return respond(new Response("Unexpected JSON-RPC method", { status: 400 })); } const response = url.pathname === "/.well-known/oauth-protected-resource/mcp" ? Response.json({ resource: endpoint, authorization_servers: [issuer] }) : url.pathname === "/.well-known/oauth-authorization-server" ? Response.json({ issuer, authorization_endpoint: `${issuer}/authorize`, token_endpoint: `${issuer}/token`, registration_endpoint: `${issuer}/register`, response_types_supported: ["code"], code_challenge_methods_supported: ["S256"], }) : url.pathname === "/register" ? Response.json( { client_id: "replacement-client", redirect_uris: ["http://localhost/oauth/callback"], grant_types: ["authorization_code", "refresh_token"], response_types: ["code"], token_endpoint_auth_method: "none", }, { status: 201 }, ) : new Response("unexpected request", { status: 500 }); return respond(response); }), ); return { layer, requests }; }; // `tools/call` responders. Both embed a "do-not-leak" sentinel the assertions // confirm never reaches the caller-facing failure. const httpStatusCallTool = (status: number): CallToolResponder => () => HttpServerResponse.text("do-not-leak: upstream auth challenge", { status }); const jsonRpcErrorCallTool = (code: number): CallToolResponder => (rpc) => HttpServerResponse.jsonUnsafe({ jsonrpc: "2.0", id: rpc.id ?? null, error: { code, message: "application-level do-not-leak" }, }); const seedCallToolExecutor = (input: { slug: string; callTool: CallToolResponder; /** Seed an oauth2-templated connection with a static access token, so the * transport gets a real authProvider — the production OAuth path where the * SDK's own challenge handling would otherwise swallow scope signals. */ oauth?: boolean; }) => Effect.acquireRelease( Effect.gen(function* () { const server = yield* serveCallToolServer(input.callTool); const config = makeTestConfig({ plugins: [memoryCredentialsPlugin(), mcpPlugin()] as const, }); const executor = yield* createExecutor(config); yield* executor.mcp.addServer({ name: "Call tool fixture", endpoint: server.url("/mcp"), slug: input.slug, remoteTransport: "streamable-http", ...(input.oauth ? { auth: { kind: "oauth2" as const } } : {}), }); yield* executor.connections.create({ owner: "org", name: ConnectionName.make("main"), integration: IntegrationSlug.make(input.slug), template: input.oauth ? AuthTemplateSlug.make("oauth2") : TEMPLATE, value: input.oauth ? "static-access-token" : "", }); return { config, executor, toolAddress: ToolAddress.make(`tools.${input.slug}.org.main.explode`), } as const; }), ({ config, executor }) => Effect.gen(function* () { yield* executor.close().pipe(Effect.ignore); yield* Effect.promise(() => config.testDb.close()).pipe(Effect.ignore); }), ); // --------------------------------------------------------------------------- // Manifest extraction // --------------------------------------------------------------------------- describe("extractManifestFromListToolsResult", () => { it.effect("extracts tools from a valid listTools response", () => Effect.sync(() => { const result = extractManifestFromListToolsResult({ tools: [ { name: "get_weather", description: "Get weather for a location", inputSchema: { type: "object", properties: { location: { type: "string" } }, }, }, { name: "search", description: "Search the web" }, ], }); expect(result.tools).toHaveLength(2); expect(result.tools[0]!.toolName).toBe("get_weather"); expect(result.tools[0]!.toolId).toBe("get_weather"); expect(result.tools[0]!.description).toBe("Get weather for a location"); expect(result.tools[1]!.toolName).toBe("search"); }), ); it.effect("sanitizes tool IDs", () => Effect.sync(() => { const result = extractManifestFromListToolsResult({ tools: [ { name: "My Tool!!", description: null }, { name: "My Tool!!", description: null }, ], }); expect(result.tools[0]!.toolId).toBe("my_tool"); expect(result.tools[1]!.toolId).toBe("my_tool_2"); }), ); it.effect("handles empty tools list", () => Effect.sync(() => { const result = extractManifestFromListToolsResult({ tools: [] }); expect(result.tools).toHaveLength(0); }), ); it.effect("extracts server metadata", () => Effect.sync(() => { const result = extractManifestFromListToolsResult( { tools: [] }, { serverInfo: { name: "test-server", version: "1.0.0" } }, ); expect(result.server?.name).toBe("test-server"); expect(result.server?.version).toBe("1.0.0"); }), ); it.effect("decodes upstream tool annotations", () => Effect.sync(() => { const result = extractManifestFromListToolsResult({ tools: [ { name: "delete", annotations: { destructiveHint: true } }, { name: "list", annotations: { readOnlyHint: true } }, { name: "ping" }, ], }); expect(result.tools[0]!.annotations?.destructiveHint).toBe(true); expect(result.tools[1]!.annotations?.readOnlyHint).toBe(true); expect(result.tools[2]!.annotations).toBeUndefined(); }), ); it.effect("carries the reserved `_meta` map through verbatim", () => Effect.sync(() => { const meta = { serverName: "time", shortDescription: "Current time", defer_loading: false, nested: { any: ["shape"] }, }; const result = extractManifestFromListToolsResult({ tools: [ { name: "time_get_current_time", description: "Get the current time", inputSchema: { type: "object" }, _meta: meta, }, { name: "no_meta", description: "Has no _meta" }, ], }); expect(result.tools[0]!._meta).toEqual(meta); expect(result.tools[1]!._meta).toBeUndefined(); }), ); // `_meta` is opaque and server-controlled, so a value that does not match the // spec's map shape must not take the rest of the list down with it. it.effect("ignores a malformed `_meta` without dropping the tool", () => Effect.sync(() => { const result = extractManifestFromListToolsResult({ tools: [{ name: "odd_meta", _meta: "not-a-map" }, { name: "plain" }], }); expect(result.tools.map((tool) => tool.toolName)).toEqual(["odd_meta", "plain"]); expect(result.tools[0]!._meta).toBeUndefined(); }), ); }); // --------------------------------------------------------------------------- // Namespace derivation // --------------------------------------------------------------------------- describe("deriveMcpNamespace", () => { it.effect("derives from name", () => Effect.sync(() => { expect(deriveMcpNamespace({ name: "GitHub MCP" })).toBe("github_mcp"); }), ); it.effect("derives from endpoint", () => Effect.sync(() => { expect(deriveMcpNamespace({ endpoint: "https://api.example.com/mcp" })).toBe( "api_example_com", ); }), ); it.effect("derives from command", () => Effect.sync(() => { expect(deriveMcpNamespace({ command: "/usr/local/bin/my-mcp-server" })).toBe("my_mcp_server"); }), ); it.effect("falls back to 'mcp'", () => Effect.sync(() => { expect(deriveMcpNamespace({})).toBe("mcp"); }), ); }); // --------------------------------------------------------------------------- // joinToolPath // --------------------------------------------------------------------------- describe("joinToolPath", () => { it.effect("joins namespace and toolId", () => Effect.sync(() => { expect(joinToolPath("github", "search")).toBe("github.search"); }), ); it.effect("returns toolId when namespace is undefined", () => Effect.sync(() => { expect(joinToolPath(undefined, "search")).toBe("search"); }), ); }); // --------------------------------------------------------------------------- // Plugin lifecycle // --------------------------------------------------------------------------- describe("mcpPlugin", () => { it.effect("surfaces OAuth reauthorization from resolveTools as expired health", () => Effect.gen(function* () { const endpoint = "https://mcp.example.test/mcp"; const plugin = mcpPlugin(); const ledger = rejectedOAuthDiscoveryLayer(endpoint); const result = yield* plugin.resolveTools!({ config: { transport: "remote", endpoint, remoteTransport: "streamable-http", authenticationTemplate: [{ slug: "oauth2", kind: "oauth2" }], }, connection: { owner: "org", integration: IntegrationSlug.make("oauth_mcp"), name: ConnectionName.make("main"), }, template: AuthTemplateSlug.make("oauth2"), getValues: () => Effect.succeed({ token: "rejected-token" }), getValue: () => Effect.succeed("rejected-token"), httpClientLayer: ledger.layer, ctx: null as never, integration: null as never, storage: {}, }); expect(result).toMatchObject({ tools: [], incomplete: true, health: { status: "expired", detail: expect.stringContaining("reauthorization"), }, }); expect(ledger.requests.filter((url) => new URL(url).pathname === "/register")).toEqual([]); }), );