import { describe, expect, it } from "bun:test"; import { mcpLocalEnvironment, mcpReadonlyTools, normalizeMcpConfig } from "../../src/runtime/mcp-config.js"; import { canAutomaticallyApprove } from "../../src/routes/chatPermissionPolicy.js"; describe("MCP configuration and grants", () => { it("defaults to HTTP auto negotiation and stdio legacy while retaining explicit timeouts and protocol", () => { const config = normalizeMcpConfig({ timeout: { execution: 90_000 }, servers: { mcp_http: { type: "remote", url: "https://example.com/mcp" }, mcp_stdio: { type: "local", command: ["bun", "server.ts"], timeout: { catalog: 2000 } }, mcp_modern: { type: "local", command: ["server"], protocol: "2026-07-28", codemode: false }, }, }); expect(config.timeout).toEqual({ startup: 10_000, catalog: 10_000, execution: 90_000 }); expect(config.servers.mcp_http.protocol).toBe("auto"); expect(config.servers.mcp_stdio).toMatchObject({ protocol: "legacy", timeout: { catalog: 2000 } }); expect(config.servers.mcp_modern).toMatchObject({ protocol: "2026-07-28", codemode: false }); expect(normalizeMcpConfig(undefined).servers).toEqual({}); for (const servers of [ { unnamed: { type: "local", command: ["server"] } }, { mcp_bad: { type: "remote", url: "file:///secret" } }, { mcp_bad: { type: "remote", url: "https://user:secret@example.com/mcp" } }, { mcp_bad: { type: "remote", url: "https://example.com", protocol: "future" } }, { mcp_bad: { type: "local", command: [""] } }, ]) expect(() => normalizeMcpConfig({ servers })).toThrow(); }); it("requires an exact service allow rule, keeps later denials and applies round mode to MCP requests", () => { expect(mcpReadonlyTools({ permissions: [{ action: "mcp_*", resource: "*", effect: "allow" }] })).toEqual([]); const permissions = [ { action: "mcp_*", resource: "*", effect: "ask" }, { action: "mcp_fixture_read", resource: "*", effect: "allow" }, { action: "mcp_fixture_write", resource: "*", effect: "allow" }, { action: "mcp_fixture_write", resource: "*", effect: "deny" }, ]; expect(mcpReadonlyTools({ permissions })).toEqual(["mcp_fixture_read"]); expect(mcpReadonlyTools({ permissions, agents: { instruction: { permissions: [{ action: "mcp_fixture_read", resource: "*", effect: "deny" }] } } })).toEqual([]); for (const action of ["mcp_fixture_write", "opencode_read_mcp_resource", "mcp_tjwater_query_scada_readings"]) { expect(canAutomaticallyApprove("request", action)).toBe(false); expect(canAutomaticallyApprove("always", action)).toBe(true); } }); it("shadows inherited credentials and reserved overrides for a trusted stdio process", () => { const env = mcpLocalEnvironment({ PATH: "/bin", HOME: "/home/test", DEEPSEEK_API_KEY: "TEST_ONLY", AGENT_INTERNAL_TOKEN: "TEST_ONLY", NODE_OPTIONS: "TEST_ONLY" }, { MCP_API_TOKEN: "MCP_ONLY", AGENT_INTERNAL_TOKEN: "OVERRIDE", OPENCODE_CONFIG_CONTENT: "OVERRIDE", }); expect(env.PATH).toBe("/bin"); expect(env.MCP_API_TOKEN).toBe("MCP_ONLY"); for (const key of ["DEEPSEEK_API_KEY", "AGENT_INTERNAL_TOKEN", "NODE_OPTIONS", "OPENCODE_CONFIG_CONTENT"]) expect(env[key]).toBe(""); }); });