import { describe, expect, it } from "@effect/vitest";import { Effect, Result } from "effect"; import { ConnectionId, CreateConnectionInput, SecretId, Scope, ScopeId, TokenMaterial, OAUTH2_PROVIDER_KEY, createExecutor, definePlugin, type SecretProvider,} from "@executor-js/sdk";import { makeTestConfig } from "@executor-js/sdk/testing"; import { mcpPlugin, userFacingProbeMessage } from "./plugin";import { MCP_OAUTH_CLIENT_ID_SLOT, MCP_OAUTH_CLIENT_SECRET_SLOT, MCP_OAUTH_CONNECTION_SLOT,} from "./types";import { extractManifestFromListToolsResult, deriveMcpNamespace, joinToolPath } from "./manifest";import { makeAnnotationsMcpServer, makeGreetingMcpServer, serveMcpServer } from "../testing"; const mcpOAuth2Config = { kind: "oauth2" as const, securitySchemeName: "OAuth2", flow: "authorizationCode" as const, tokenUrl: "https://auth.example.test/token", authorizationUrl: "https://auth.example.test/authorize", clientIdSlot: MCP_OAUTH_CLIENT_ID_SLOT, clientSecretSlot: MCP_OAUTH_CLIENT_SECRET_SLOT, connectionSlot: MCP_OAUTH_CONNECTION_SLOT, scopes: [],}; // ---------------------------------------------------------------------------// Memory secrets plugin — without a writable provider in the stack,// `executor.connections.create` has nowhere to persist its owned// access/refresh-token secret rows, so the per-user sign-in test below// can't mint a connection.// --------------------------------------------------------------------------- const makeMemorySecretsPlugin = () => { const store = new Map<string, string>(); const provider: SecretProvider = { key: "memory", writable: true, get: (id, scope) => Effect.sync(() => store.get(`${scope}${id}`) ?? null), set: (id, value, scope) => Effect.sync(() => { store.set(`${scope}${id}`, value); }), delete: (id, scope) => Effect.sync(() => store.delete(`${scope}${id}`)), list: () => Effect.sync(() => Array.from(store.keys()).map((k) => { const name = k.split("", 2)[1] ?? k; return { id: name, name }; }), ), }; return definePlugin(() => ({ id: "memory-secrets" as const, storage: () => ({}), secretProviders: [provider], }));}; // ---------------------------------------------------------------------------// 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(); }), );}); // ---------------------------------------------------------------------------// 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("creates executor with mcp plugin", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ plugins: [mcpPlugin()] as const, }), ); expect(executor.mcp).toBeDefined(); expect(executor.mcp.addSource).toBeTypeOf("function"); expect(executor.mcp.removeSource).toBeTypeOf("function"); expect(executor.mcp.refreshSource).toBeTypeOf("function"); expect(executor.mcp.probeEndpoint).toBeTypeOf("function"); expect(executor.mcp.getSource).toBeTypeOf("function"); expect((yield* executor.tools.list()).map((tool) => tool.id)).toContain( "executor.mcp.configureSource", ); expect(executor.oauth.start).toBeTypeOf("function"); expect(executor.oauth.complete).toBeTypeOf("function"); }), ); it.effect("sources list has no configured MCP sources initially", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [mcpPlugin()] as const })); const sources = yield* executor.sources.list(); expect(sources.filter((source) => !source.runtime)).toHaveLength(0); }), ); it.effect("tools list has no configured MCP source tools initially", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [mcpPlugin()] as const })); const tools = yield* executor.tools.list(); expect(tools.filter((tool) => !tool.id.startsWith("executor.mcp."))).toHaveLength(0); }), ); // When discovery fails (auth, network, etc.) we still want the source // row to land in the DB so users see it in the catalog — they can // retry via refresh once they fix the underlying problem. The error // still propagates to the caller so boot-time sync logs the reason. it.effect("registers source with 0 tools when discovery fails", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [mcpPlugin()] as const })); const result = yield* executor.mcp .addSource({ transport: "remote", scope: "test-scope", name: "broken", // Port 1 is reserved — will connection-refused immediately, // giving us a deterministic discovery failure without any // server mocks. endpoint: "http://127.0.0.1:1/mcp", remoteTransport: "auto", namespace: "broken_source", }) .pipe(Effect.result); expect(Result.isFailure(result)).toBe(true); const sources = yield* executor.sources.list(); const broken = sources.find((s) => s.id === "broken_source"); expect(broken).toBeDefined(); expect(broken?.kind).toBe("mcp"); expect(broken?.pluginId).toBe("mcp"); const tools = yield* executor.tools.list(); expect(tools.filter((t) => t.sourceId === "broken_source")).toHaveLength(0); const inspected = yield* executor.tools.invoke( "executor.mcp.getSource", { namespace: "broken_source", scope: "test-scope" }, { onElicitation: "accept-all" }, ); expect(inspected).toMatchObject({ ok: true, data: { source: { namespace: "broken_source", scope: "test-scope" } }, }); }), ); it.effect("static addSource reports saved remote source when discovery fails", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [mcpPlugin()] as const })); const result = yield* executor.tools.invoke( "executor.mcp.addSource", { transport: "remote", name: "broken static", endpoint: "http://127.0.0.1:1/mcp", remoteTransport: "auto", namespace: "broken_static_source", }, { onElicitation: "accept-all" }, ); expect(result).toMatchObject({ ok: true, data: { namespace: "broken_static_source", source: { id: "broken_static_source", scope: "test-scope" }, toolCount: 0, discovery: { status: "failed", }, }, }); const source = yield* executor.mcp.getSource("broken_static_source", "test-scope"); expect(source?.namespace).toBe("broken_static_source"); }), ); it.effect("static probeEndpoint returns actionable tool failures", () => Effect.gen(function* () { const config = makeTestConfig({ plugins: [mcpPlugin()] as const }); const executor = yield* createExecutor(config); const result = yield* executor.tools.invoke("executor.mcp.probeEndpoint", { endpoint: "http://127.0.0.1:1/mcp", }); expect(result).toMatchObject({ ok: false, error: { code: "mcp_connection_failed", }, }); yield* executor.close(); yield* Effect.promise(() => config.testDb.close()); }), ); it.effect("uses initial credential bindings for add-time tool discovery", () => Effect.gen(function* () { const server = yield* serveMcpServer(makeGreetingMcpServer, { auth: { validateAuthorization: (authorization) => Effect.succeed(authorization === "Bearer secret-token"), }, }); const executor = yield* createExecutor( makeTestConfig({ plugins: [makeMemorySecretsPlugin()(), mcpPlugin()] as const }), ); yield* executor.secrets.set({ id: SecretId.make("mcp-token"), scope: ScopeId.make("test-scope"), name: "MCP token", value: "secret-token", provider: "memory", }); const result = yield* executor.mcp.addSource({ transport: "remote", scope: "test-scope", name: "authenticated", endpoint: server.endpoint, namespace: "authenticated_mcp", headers: { Authorization: { kind: "secret", prefix: "Bearer " }, }, credentials: { scope: "test-scope", headers: { Authorization: { kind: "secret", secretId: "mcp-token" }, }, }, }); expect(result).toEqual({ toolCount: 1, namespace: "authenticated_mcp" }); const requests = yield* server.requests; expect(requests.some((request) => request.authorization === "Bearer secret-token")).toBe( true, ); }), ); it.effect("marks source oauth-backed when add-time credentials include oauth", () => Effect.gen(function* () { const server = yield* serveMcpServer(makeGreetingMcpServer, { auth: { validateAuthorization: (authorization) => Effect.succeed(authorization === "Bearer oauth-token"), }, }); const executor = yield* createExecutor( makeTestConfig({ plugins: [makeMemorySecretsPlugin()(), mcpPlugin()] as const }), ); const connectionId = ConnectionId.make("mcp-oauth2-initial"); yield* executor.connections.create( CreateConnectionInput.make({ id: connectionId, scope: ScopeId.make("test-scope"), provider: OAUTH2_PROVIDER_KEY, identityLabel: "Initial MCP OAuth", accessToken: TokenMaterial.make({ secretId: SecretId.make(`${connectionId}.access_token`), name: "Initial MCP OAuth Access Token", value: "oauth-token", }), refreshToken: null, expiresAt: null, oauthScope: null, providerState: null, }), ); const result = yield* executor.mcp.addSource({ transport: "remote", scope: "test-scope", name: "initial oauth", endpoint: server.endpoint, namespace: "initial_oauth_mcp", credentials: { scope: "test-scope", auth: { oauth2: { connection: { kind: "connection", connectionId }, }, }, }, }); expect(result).toEqual({ toolCount: 1, namespace: "initial_oauth_mcp" }); const stored = yield* executor.mcp.getSource("initial_oauth_mcp", "test-scope"); expect(stored?.config.transport).toBe("remote"); if (stored?.config.transport !== "remote") return; expect(stored.config.auth.kind).toBe("oauth2"); if (stored.config.auth.kind !== "oauth2") return; expect(stored.config.auth.connectionSlot).toBe(MCP_OAUTH_CONNECTION_SLOT); }), ); // ------------------------------------------------------------------------- // Multi-scope shadowing — regression suite covering the bug class where // store reads/writes that don't pin scope_id collapse onto whichever visible // row wins first. Each // scenario is reproducible against the pre-fix store. // // MCP discovery runs on addSource against an unreachable endpoint so // both addSource calls fail discovery but still persist the source row // (the behavior the "registers source with 0 tools" test above relies // on). This gives us two rows at the same namespace across two scopes // without needing an in-test MCP server. // ------------------------------------------------------------------------- const ORG_SCOPE = ScopeId.make("org-scope"); const USER_SCOPE = ScopeId.make("user-scope"); const stackedScopes = [ Scope.make({ id: USER_SCOPE, name: "user", createdAt: new Date() }), Scope.make({ id: ORG_SCOPE, name: "org", createdAt: new Date() }), ] as const; // `seedShadowed` wraps `executor.mcp.addSource` at a given scope with // a broken endpoint. Discovery fails (port 1 is reserved) so the call // returns Failure, but the source row still lands — exactly the // "registers source with 0 tools when discovery fails" behavior above. // We use `Effect.result` so the outer `yield*` never fails the test. const seedShadowed = ( addSource: (c: { readonly transport: "remote"; readonly scope: string; readonly name: string; readonly endpoint: string; readonly remoteTransport: "auto"; readonly namespace: string; }) => Effect.Effect<unknown, unknown>, args: { readonly scope: string; readonly name: string; readonly endpoint: string; }, ) => addSource({ transport: "remote", scope: args.scope, name: args.name, endpoint: args.endpoint, remoteTransport: "auto", namespace: "shared", }).pipe(Effect.result); it.effect("shadowed addSource does not wipe the outer-scope source", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ scopes: stackedScopes, plugins: [mcpPlugin()] as const, }), ); // Org-level base source — discovery fails but row persists.Lines 1–500 of 1202. Load more
import { describe, expect, it } from "@effect/vitest"; import { Effect, Result } from "effect"; import { ConnectionId, CreateConnectionInput, SecretId, Scope, ScopeId, TokenMaterial, OAUTH2_PROVIDER_KEY, createExecutor, definePlugin, type SecretProvider, } from "@executor-js/sdk"; import { makeTestConfig } from "@executor-js/sdk/testing"; import { mcpPlugin, userFacingProbeMessage } from "./plugin"; import { MCP_OAUTH_CLIENT_ID_SLOT, MCP_OAUTH_CLIENT_SECRET_SLOT, MCP_OAUTH_CONNECTION_SLOT, } from "./types"; import { extractManifestFromListToolsResult, deriveMcpNamespace, joinToolPath } from "./manifest"; import { makeAnnotationsMcpServer, makeGreetingMcpServer, serveMcpServer } from "../testing"; const mcpOAuth2Config = { kind: "oauth2" as const, securitySchemeName: "OAuth2", flow: "authorizationCode" as const, tokenUrl: "https://auth.example.test/token", authorizationUrl: "https://auth.example.test/authorize", clientIdSlot: MCP_OAUTH_CLIENT_ID_SLOT, clientSecretSlot: MCP_OAUTH_CLIENT_SECRET_SLOT, connectionSlot: MCP_OAUTH_CONNECTION_SLOT, scopes: [], }; // --------------------------------------------------------------------------- // Memory secrets plugin — without a writable provider in the stack, // `executor.connections.create` has nowhere to persist its owned // access/refresh-token secret rows, so the per-user sign-in test below // can't mint a connection. // --------------------------------------------------------------------------- const makeMemorySecretsPlugin = () => { const store = new Map<string, string>(); const provider: SecretProvider = { key: "memory", writable: true, get: (id, scope) => Effect.sync(() => store.get(`${scope}${id}`) ?? null), set: (id, value, scope) => Effect.sync(() => { store.set(`${scope}${id}`, value); }), delete: (id, scope) => Effect.sync(() => store.delete(`${scope}${id}`)), list: () => Effect.sync(() => Array.from(store.keys()).map((k) => { const name = k.split("", 2)[1] ?? k; return { id: name, name }; }), ), }; return definePlugin(() => ({ id: "memory-secrets" as const, storage: () => ({}), secretProviders: [provider], })); }; // --------------------------------------------------------------------------- // 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(); }), ); }); // --------------------------------------------------------------------------- // 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("creates executor with mcp plugin", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ plugins: [mcpPlugin()] as const, }), ); expect(executor.mcp).toBeDefined(); expect(executor.mcp.addSource).toBeTypeOf("function"); expect(executor.mcp.removeSource).toBeTypeOf("function"); expect(executor.mcp.refreshSource).toBeTypeOf("function"); expect(executor.mcp.probeEndpoint).toBeTypeOf("function"); expect(executor.mcp.getSource).toBeTypeOf("function"); expect((yield* executor.tools.list()).map((tool) => tool.id)).toContain( "executor.mcp.configureSource", ); expect(executor.oauth.start).toBeTypeOf("function"); expect(executor.oauth.complete).toBeTypeOf("function"); }), ); it.effect("sources list has no configured MCP sources initially", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [mcpPlugin()] as const })); const sources = yield* executor.sources.list(); expect(sources.filter((source) => !source.runtime)).toHaveLength(0); }), ); it.effect("tools list has no configured MCP source tools initially", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [mcpPlugin()] as const })); const tools = yield* executor.tools.list(); expect(tools.filter((tool) => !tool.id.startsWith("executor.mcp."))).toHaveLength(0); }), ); // When discovery fails (auth, network, etc.) we still want the source // row to land in the DB so users see it in the catalog — they can // retry via refresh once they fix the underlying problem. The error // still propagates to the caller so boot-time sync logs the reason. it.effect("registers source with 0 tools when discovery fails", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [mcpPlugin()] as const })); const result = yield* executor.mcp .addSource({ transport: "remote", scope: "test-scope", name: "broken", // Port 1 is reserved — will connection-refused immediately, // giving us a deterministic discovery failure without any // server mocks. endpoint: "http://127.0.0.1:1/mcp", remoteTransport: "auto", namespace: "broken_source", }) .pipe(Effect.result); expect(Result.isFailure(result)).toBe(true); const sources = yield* executor.sources.list(); const broken = sources.find((s) => s.id === "broken_source"); expect(broken).toBeDefined(); expect(broken?.kind).toBe("mcp"); expect(broken?.pluginId).toBe("mcp"); const tools = yield* executor.tools.list(); expect(tools.filter((t) => t.sourceId === "broken_source")).toHaveLength(0); const inspected = yield* executor.tools.invoke( "executor.mcp.getSource", { namespace: "broken_source", scope: "test-scope" }, { onElicitation: "accept-all" }, ); expect(inspected).toMatchObject({ ok: true, data: { source: { namespace: "broken_source", scope: "test-scope" } }, }); }), ); it.effect("static addSource reports saved remote source when discovery fails", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [mcpPlugin()] as const })); const result = yield* executor.tools.invoke( "executor.mcp.addSource", { transport: "remote", name: "broken static", endpoint: "http://127.0.0.1:1/mcp", remoteTransport: "auto", namespace: "broken_static_source", }, { onElicitation: "accept-all" }, ); expect(result).toMatchObject({ ok: true, data: { namespace: "broken_static_source", source: { id: "broken_static_source", scope: "test-scope" }, toolCount: 0, discovery: { status: "failed", }, }, }); const source = yield* executor.mcp.getSource("broken_static_source", "test-scope"); expect(source?.namespace).toBe("broken_static_source"); }), ); it.effect("static probeEndpoint returns actionable tool failures", () => Effect.gen(function* () { const config = makeTestConfig({ plugins: [mcpPlugin()] as const }); const executor = yield* createExecutor(config); const result = yield* executor.tools.invoke("executor.mcp.probeEndpoint", { endpoint: "http://127.0.0.1:1/mcp", }); expect(result).toMatchObject({ ok: false, error: { code: "mcp_connection_failed", }, }); yield* executor.close(); yield* Effect.promise(() => config.testDb.close()); }), ); it.effect("uses initial credential bindings for add-time tool discovery", () => Effect.gen(function* () { const server = yield* serveMcpServer(makeGreetingMcpServer, { auth: { validateAuthorization: (authorization) => Effect.succeed(authorization === "Bearer secret-token"), }, }); const executor = yield* createExecutor( makeTestConfig({ plugins: [makeMemorySecretsPlugin()(), mcpPlugin()] as const }), ); yield* executor.secrets.set({ id: SecretId.make("mcp-token"), scope: ScopeId.make("test-scope"), name: "MCP token", value: "secret-token", provider: "memory", }); const result = yield* executor.mcp.addSource({ transport: "remote", scope: "test-scope", name: "authenticated", endpoint: server.endpoint, namespace: "authenticated_mcp", headers: { Authorization: { kind: "secret", prefix: "Bearer " }, }, credentials: { scope: "test-scope", headers: { Authorization: { kind: "secret", secretId: "mcp-token" }, }, }, }); expect(result).toEqual({ toolCount: 1, namespace: "authenticated_mcp" }); const requests = yield* server.requests; expect(requests.some((request) => request.authorization === "Bearer secret-token")).toBe( true, ); }), ); it.effect("marks source oauth-backed when add-time credentials include oauth", () => Effect.gen(function* () { const server = yield* serveMcpServer(makeGreetingMcpServer, { auth: { validateAuthorization: (authorization) => Effect.succeed(authorization === "Bearer oauth-token"), }, }); const executor = yield* createExecutor( makeTestConfig({ plugins: [makeMemorySecretsPlugin()(), mcpPlugin()] as const }), ); const connectionId = ConnectionId.make("mcp-oauth2-initial"); yield* executor.connections.create( CreateConnectionInput.make({ id: connectionId, scope: ScopeId.make("test-scope"), provider: OAUTH2_PROVIDER_KEY, identityLabel: "Initial MCP OAuth", accessToken: TokenMaterial.make({ secretId: SecretId.make(`${connectionId}.access_token`), name: "Initial MCP OAuth Access Token", value: "oauth-token", }), refreshToken: null, expiresAt: null, oauthScope: null, providerState: null, }), ); const result = yield* executor.mcp.addSource({ transport: "remote", scope: "test-scope", name: "initial oauth", endpoint: server.endpoint, namespace: "initial_oauth_mcp", credentials: { scope: "test-scope", auth: { oauth2: { connection: { kind: "connection", connectionId }, }, }, }, }); expect(result).toEqual({ toolCount: 1, namespace: "initial_oauth_mcp" }); const stored = yield* executor.mcp.getSource("initial_oauth_mcp", "test-scope"); expect(stored?.config.transport).toBe("remote"); if (stored?.config.transport !== "remote") return; expect(stored.config.auth.kind).toBe("oauth2"); if (stored.config.auth.kind !== "oauth2") return; expect(stored.config.auth.connectionSlot).toBe(MCP_OAUTH_CONNECTION_SLOT); }), ); // ------------------------------------------------------------------------- // Multi-scope shadowing — regression suite covering the bug class where // store reads/writes that don't pin scope_id collapse onto whichever visible // row wins first. Each // scenario is reproducible against the pre-fix store. // // MCP discovery runs on addSource against an unreachable endpoint so // both addSource calls fail discovery but still persist the source row // (the behavior the "registers source with 0 tools" test above relies // on). This gives us two rows at the same namespace across two scopes // without needing an in-test MCP server. // ------------------------------------------------------------------------- const ORG_SCOPE = ScopeId.make("org-scope"); const USER_SCOPE = ScopeId.make("user-scope"); const stackedScopes = [ Scope.make({ id: USER_SCOPE, name: "user", createdAt: new Date() }), Scope.make({ id: ORG_SCOPE, name: "org", createdAt: new Date() }), ] as const; // `seedShadowed` wraps `executor.mcp.addSource` at a given scope with // a broken endpoint. Discovery fails (port 1 is reserved) so the call // returns Failure, but the source row still lands — exactly the // "registers source with 0 tools when discovery fails" behavior above. // We use `Effect.result` so the outer `yield*` never fails the test. const seedShadowed = ( addSource: (c: { readonly transport: "remote"; readonly scope: string; readonly name: string; readonly endpoint: string; readonly remoteTransport: "auto"; readonly namespace: string; }) => Effect.Effect<unknown, unknown>, args: { readonly scope: string; readonly name: string; readonly endpoint: string; }, ) => addSource({ transport: "remote", scope: args.scope, name: args.name, endpoint: args.endpoint, remoteTransport: "auto", namespace: "shared", }).pipe(Effect.result); it.effect("shadowed addSource does not wipe the outer-scope source", () => Effect.gen(function* () { const executor = yield* createExecutor( makeTestConfig({ scopes: stackedScopes, plugins: [mcpPlugin()] as const, }), ); // Org-level base source — discovery fails but row persists.