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.
88 lines
5.5 KiB
TypeScript
88 lines
5.5 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
import express, { type Request } from "express";
|
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { config } from "../../src/config.js";
|
|
import { attachTrustedAgentContext, getLocalAgentContext, ProductContextError } from "../../src/context/localContext.js";
|
|
import { registerMcpPolicyBridge, registerMcpStatusRoutes } from "../../src/routes/mcpRoutes.js";
|
|
import { removeRuntimeSessionContext, setRuntimeSessionContext } from "../../src/runtime/sessionContext.js";
|
|
import { SessionMetadataStore } from "../../src/sessions/metadataStore.js";
|
|
|
|
describe("MCP routes", () => {
|
|
it("protects the internal policy bridge against browser and context injection", async () => {
|
|
const app = express(); app.use(express.json());
|
|
let builds = 0;
|
|
registerMcpPolicyBridge(app, { internalToken: "MCP_TEST_ONLY", runtime: { mcpReadonlyTools: () => { builds++; return ["mcp_fixture_read"]; } } });
|
|
const server = app.listen(0, "127.0.0.1");
|
|
await new Promise<void>((resolve) => server.once("listening", resolve));
|
|
const url = `http://127.0.0.1:${(server.address() as { port: number }).port}/internal/mcp/session-policy`;
|
|
const sessionID = "mcp-route-test";
|
|
setRuntimeSessionContext({ sessionId: sessionID, clientSessionId: sessionID, actorKey: "PRIVATE_ACTOR", projectKey: "PRIVATE_PROJECT", traceId: "test", purpose: "task_execution" });
|
|
const post = (body: unknown, headers: Record<string, string> = {}) => fetch(url, { method: "POST", headers: { "Content-Type": "application/json", "x-agent-internal-token": "MCP_TEST_ONLY", ...headers }, body: JSON.stringify(body) });
|
|
try {
|
|
expect((await post({ session_id: sessionID }, { "x-agent-internal-token": "wrong" })).status).toBe(403);
|
|
expect((await post({ session_id: sessionID }, { Origin: "https://browser" })).status).toBe(403);
|
|
expect((await post({ session_id: sessionID }, { "Sec-Fetch-Site": "same-origin" })).status).toBe(403);
|
|
expect((await post({ session_id: sessionID, project_id: "other" })).status).toBe(400);
|
|
expect((await post({ session_id: "missing" })).status).toBe(404);
|
|
expect(await (await post({ session_id: sessionID })).json()).toEqual({ mode: "readonly", readonly_tools: ["mcp_fixture_read"], query_tools: [] });
|
|
expect(builds).toBe(1);
|
|
} finally {
|
|
removeRuntimeSessionContext(sessionID);
|
|
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
}
|
|
});
|
|
|
|
it("requires verified session ownership and returns constant errors without credentials", async () => {
|
|
const originalFetch = globalThis.fetch;
|
|
const previousEnv = config.NODE_ENV;
|
|
const root = await mkdtemp(join(tmpdir(), "mcp-status-test-"));
|
|
const store = new SessionMetadataStore(root);
|
|
const app = express();
|
|
let calls = 0;
|
|
let failed = false;
|
|
globalThis.fetch = (async (input: Parameters<typeof fetch>[0], init: Parameters<typeof fetch>[1]) => {
|
|
if (String(input).endsWith("/api/v1/agent-auth-context")) {
|
|
const headers = new Headers(init?.headers);
|
|
return Response.json({ user_id: headers.get("Authorization") === "Bearer OWNER" ? "owner" : "other", project_id: headers.get("X-Project-Id"), network: "supply" });
|
|
}
|
|
return originalFetch(input, init);
|
|
}) as typeof fetch;
|
|
config.NODE_ENV = "production";
|
|
const owner = { header: (name: string) => ({ authorization: "Bearer OWNER", "x-project-id": "project" })[name.toLowerCase() as "authorization" | "x-project-id"] } as Request;
|
|
await attachTrustedAgentContext(owner);
|
|
await store.ensure({ ...getLocalAgentContext(owner), sessionId: "private-session" });
|
|
const router = express.Router();
|
|
router.use(async (req, res, next) => {
|
|
try { await attachTrustedAgentContext(req); next(); }
|
|
catch (error) { res.status(error instanceof ProductContextError ? error.status : 503).json({ message: "authentication failed" }); }
|
|
});
|
|
registerMcpStatusRoutes(router, { sessionMetadataStore: store, runtime: { mcpStatus: async () => {
|
|
calls++;
|
|
if (failed) throw new Error("SECRET_URL_AND_TOKEN");
|
|
return { configuration_errors: [], servers: [{ name: "mcp_fixture", type: "remote", status: "needs_auth", configured_protocol: "auto", tools: [] }] };
|
|
} } });
|
|
app.use("/api/v1", router);
|
|
const server = app.listen(0, "127.0.0.1");
|
|
await new Promise<void>((resolve) => server.once("listening", resolve));
|
|
const url = `http://127.0.0.1:${(server.address() as { port: number }).port}/api/v1/session/private-session/mcp`;
|
|
try {
|
|
expect((await originalFetch(url)).status).toBe(401);
|
|
const headers = { Authorization: "Bearer OWNER", "X-Project-Id": "project" };
|
|
expect((await originalFetch(url, { headers: { ...headers, Authorization: "Bearer OTHER" } })).status).toBe(404);
|
|
expect((await originalFetch(url, { headers: { ...headers, "X-Project-Id": "other" } })).status).toBe(404);
|
|
expect(calls).toBe(0);
|
|
expect(await (await originalFetch(url, { headers })).json()).toEqual({ servers: [{ name: "mcp_fixture", type: "remote", status: "needs_auth", configured_protocol: "auto", tools: [] }] });
|
|
failed = true;
|
|
const error = await originalFetch(url, { headers });
|
|
expect(error.status).toBe(503);
|
|
expect(await error.json()).toEqual({ message: "MCP runtime unavailable" });
|
|
} finally {
|
|
globalThis.fetch = originalFetch; config.NODE_ENV = previousEnv;
|
|
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
await rm(root, { recursive: true });
|
|
}
|
|
});
|
|
});
|