Files
next-tjwater-agent/tests/integration/mcp-query-smoke.ts
T
jiang 6a49d2209e feat(agent): unify MCP tools and adopt Responses delivery
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.
2026-10-19 18:46:58 +08:00

72 lines
6.2 KiB
TypeScript

// Opt-in: MCP_TEST_PYTHON=<server env python> MCP_TEST_SERVER_ROOT=<server repo> bun tests/integration/mcp-query-smoke.ts
import { resolve } from "node:path";
import { McpQueryClient, type McpHttpFetch } from "../../src/product/mcp-query-client.js";
import { bindSessionCredential, releaseSessionCredential } from "../../src/product/credential-refresh.js";
import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
import { startQueryMcpServer } from "../../src/mcp/query-server.js";
import { setRuntimeSessionContext, removeRuntimeSessionContext } from "../../src/runtime/sessionContext.js";
const python = process.env.MCP_TEST_PYTHON;
const root = process.env.MCP_TEST_SERVER_ROOT;
if (!python || !root) throw new Error("MCP smoke requires an explicit isolated Server environment");
// The fixture owns its OAuth/resource settings. Local Agent MCP configuration
// (notably a private development issuer) must not override its loopback setup.
const fixtureEnv = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.startsWith("TJWATER_MCP_")));
const processHandle = Bun.spawn([python, resolve(root, "tests/fixtures/mcp_query_app.py")], { cwd: root, env: { ...fixtureEnv, PYTHONPATH: root }, stdout: "pipe", stderr: "pipe" });
const session = "fixture-host-session";
let gateway: ReturnType<typeof startQueryMcpServer> | undefined;
let nativeClient: Client | undefined;
try {
const reader = processHandle.stdout.getReader();
const output = await Promise.race([reader.read(), Bun.sleep(10_000).then(() => { throw new Error("MCP fixture did not start"); })]);
reader.releaseLock();
const resource = new TextDecoder().decode(output.value).trim().split("\n").at(-1)!;
if (!/^http:\/\/127\.0\.0\.1:\d+\/mcp$/.test(resource)) throw new Error("MCP fixture endpoint invalid");
const requests: string[] = [];
const grants: string[] = [];
const issuer = "http://localhost/realms/test";
const tokenEndpoint = "http://localhost/fixture/oauth/token";
const fixtureJwt = (kind: string) => `e30.${Buffer.from(JSON.stringify({ sub: "be3b27c8-0e58-4e3a-9df5-e62bc43f4e51", iss: "http://localhost/realms/test", aud: "tjwater-mcp", azp: "tjwater-agent-mcp", fixture: kind })).toString("base64url")}.fixture`;
const http: McpHttpFetch = async (input, init) => {
const url = String(input);
if (url === "http://localhost/.well-known/oauth-authorization-server/realms/test") return Response.json({
issuer, authorization_endpoint: "http://localhost/fixture/oauth/authorize", token_endpoint: tokenEndpoint,
response_types_supported: ["code"], token_endpoint_auth_methods_supported: ["client_secret_post"],
});
if (url === tokenEndpoint) {
const body = new URLSearchParams(String(init?.body));
if (body.get("resource") !== resource) throw new Error("OAuth grant omitted its resource indicator");
const grant = body.get("grant_type"); grants.push(grant ?? "");
return Response.json({ access_token: fixtureJwt(grant === "client_credentials" ? "catalog" : "user"), token_type: "Bearer", expires_in: 300 });
}
if (url === resource) requests.push(new Headers(init?.headers).get("MCP-Method") ?? init?.method ?? "GET");
return fetch(input, init);
};
const client = new McpQueryClient({ issuer: "http://localhost/realms/test", resource, audience: "tjwater-mcp", clientId: "tjwater-agent-mcp", clientSecret: "fixture" }, http);
const catalog = await client.prepare();
if (catalog.length !== 35) throw new Error("Query catalog discovery failed");
if (!catalog.some((tool) => tool.name === "run_simulation" && tool.inputSchema.properties?.start_time && tool.inputSchema.properties?.output_node_ids)) throw new Error("Standard hydraulic simulation schema missing");
bindSessionCredential(session, { authorization: `Bearer ${fixtureJwt("source")}`, projectId: "d44a9241-de48-4e60-9348-867dc466a031" });
setRuntimeSessionContext({ sessionId: session, clientSessionId: session, actorKey: "fixture", traceId: "fixture", projectId: "d44a9241-de48-4e60-9348-867dc466a031", projectKey: "fixture-project", purpose: "ordinary_chat" });
gateway = startQueryMcpServer({ internalToken: "MCP_TEST_ONLY", productClient: client, productTools: catalog, resolveReader: async () => null, executeLocal: async () => { throw new Error("unused fixture query"); } });
nativeClient = new Client({ name: "fixture-native", version: "1" }, { versionNegotiation: { mode: { pin: "2026-07-28" } } });
await nativeClient.connect(new StreamableHTTPClientTransport(new URL(gateway.url), { requestInit: { headers: { "x-agent-internal-token": "MCP_TEST_ONLY" } } }));
if ((await nativeClient.listTools()).tools.length !== 41) throw new Error("Merged MCP query catalog discovery failed");
const result = await nativeClient.callTool({ name: "list_elements", arguments: { element_type: "pipe", limit: 1 }, _meta: { "ai.opencode/sessionID": session } });
if (result.isError || JSON.stringify((result.structuredContent as { data?: unknown } | undefined)?.data) !== '[{"id":"P-1"}]') throw new Error("Typed query execution failed");
const nearbyTool = catalog.find((tool) => tool.name === "query_nearby_elements");
if (!nearbyTool?.inputSchema.properties?.center) throw new Error("Nearby model tool schema missing");
const nearby = await nativeClient.callTool({ name: "query_nearby_elements", arguments: { center: [121.5, 30.9], radius_m: 500, limit: 5 }, _meta: { "ai.opencode/sessionID": session } });
if (nearby.isError) throw new Error("Nearby model query failed through merged MCP");
if (!requests.includes("server/discover") || !requests.includes("tools/call") || requests.includes("initialize") || requests.includes("GET")) throw new Error("Transport used unexpected protocol requests");
if (grants.join(",") !== "client_credentials,urn:ietf:params:oauth:grant-type:token-exchange") throw new Error("OAuth grants were not validated");
console.log("PASS: local MCP 41-tool discovery, real Server schemas, OAuth discovery, resource-bound grants, host binding, query and revocation over MCP HTTP");
} finally {
await nativeClient?.close();
await gateway?.close();
removeRuntimeSessionContext(session);
releaseSessionCredential(session);
processHandle.kill("SIGTERM");
await processHandle.exited;
}