Move project and host queries into native MCP with host-owned OAuth/context, while retaining memory, skills, actions and publication plugins. Add nearby-model queries and explicit chat/task/learning model defaults. Adopt Responses HTTP/SSE and native steer/queue inbox delivery, preserving scoped session ownership, pending input edits and investigation communication. Disable local-provider discovery: Server /health allowed native vLLM to probe /v1/models on port 8000. A native before/after regression prevents recurrence. Validation: 170 focused Bun tests; service/plugin typechecks; native discovery, provider-discovery and input-inbox smoke checks; no paid model inference.
54 lines
3.2 KiB
TypeScript
54 lines
3.2 KiB
TypeScript
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("");
|
|
});
|
|
});
|