feat: add scoped history and artifacts
This commit is contained in:
@@ -25,8 +25,9 @@ try {
|
||||
const response = await native.client.rpc.call({ rpcID: "tjwater.tools", method: "list", input: {}, location: { directory } });
|
||||
const result = response.output as Array<{ id: string; direct: boolean }>;
|
||||
assert.ok(result.some((tool) => tool.id === "present_answer" && tool.direct), "present_answer must be registered as a direct native tool");
|
||||
assert.ok(result.some((tool) => tool.id === "publish_task_report" && tool.direct), "publish_task_report must remain registered with rich report support");
|
||||
assert.equal((await native.client.permission.create({ sessionID, action: "present_answer", resources: ["*"], save: [] })).effect, "allow");
|
||||
console.log("Native present_answer discovery and permission passed. No inference requests.");
|
||||
console.log("Native answer and report tool discovery, plus answer permission, passed. No inference requests.");
|
||||
} finally {
|
||||
if (native && sessionID) await native.client.session.remove({ sessionID }).catch(() => {});
|
||||
native?.close();
|
||||
|
||||
@@ -5,6 +5,7 @@ import { bindSessionCredential, releaseSessionCredential } from "../../src/produ
|
||||
import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
|
||||
import { startQueryMcpServer } from "../../src/mcp/query-server.js";
|
||||
import { setRuntimeSessionContext, removeRuntimeSessionContext } from "../../src/runtime/sessionContext.js";
|
||||
import { PRODUCT_MCP_TOOLS, QUERY_MCP_TOOL_IDS } from "../../src/runtime/product-mcp-tools.js";
|
||||
|
||||
const python = process.env.MCP_TEST_PYTHON;
|
||||
const root = process.env.MCP_TEST_SERVER_ROOT;
|
||||
@@ -44,23 +45,31 @@ try {
|
||||
};
|
||||
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.length !== PRODUCT_MCP_TOOLS.length) 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");
|
||||
if ((await nativeClient.listTools()).tools.length !== QUERY_MCP_TOOL_IDS.length) 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");
|
||||
for (const [name, input] of [
|
||||
["get_project_info", {}],
|
||||
["get_node_connections", { node_id: "J1", limit: 1 }],
|
||||
["query_network_extent", { bounds: [121, 31, 121.1, 31.1], limit: 1 }],
|
||||
] as const) {
|
||||
const response = await nativeClient.callTool({ name, arguments: input, _meta: { "ai.opencode/sessionID": session } });
|
||||
if (response.isError) throw new Error(`Extended tool failed through merged MCP: ${name}`);
|
||||
}
|
||||
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");
|
||||
console.log(`PASS: local MCP ${QUERY_MCP_TOOL_IDS.length}-tool discovery, real Server schemas, OAuth discovery, resource-bound grants, host binding, extended query and revocation over MCP HTTP`);
|
||||
} finally {
|
||||
await nativeClient?.close();
|
||||
await gateway?.close();
|
||||
|
||||
@@ -61,6 +61,9 @@ try {
|
||||
await client.session.inbox.update({ sessionID: session.id, inboxID: item.id, delivery: "steer" });
|
||||
assert.equal((await client.session.inbox.list({ sessionID: session.id })).find((entry) => entry.id === item.id)?.delivery, "steer");
|
||||
await client.session.inbox.update({ sessionID: session.id, inboxID: item.id, delivery: "queue" });
|
||||
const withdrawn = await tracker.submit({ id: `msg_${"2".repeat(32)}`, text: "Withdraw this before delivery", delivery: "queue", status: "pending" });
|
||||
assert.equal((await runtime.cancelPendingUserInput(session.id, withdrawn.id)).status, "cancelled");
|
||||
assert.ok(!(await client.session.inbox.list({ sessionID: session.id })).some((entry) => entry.id === withdrawn.id));
|
||||
release();
|
||||
await wait("queued continuation", () => prompts.length >= 2);
|
||||
await Bun.sleep(100);
|
||||
@@ -77,8 +80,13 @@ try {
|
||||
assert.ok(tokens.join("").split("Local completed.").length >= 3, "managed stream closed before queued answer");
|
||||
assert.ok(JSON.stringify(prompts[1]).includes("Additional local user input"));
|
||||
assert.ok(!(await client.session.inbox.list({ sessionID: session.id })).some((entry) => entry.id === item.id));
|
||||
assert.ok(!JSON.stringify(prompts).includes(withdrawn.text));
|
||||
await assert.rejects(runtime.cancelPendingUserInput(session.id, item.id), { status: 409 });
|
||||
// Simulate delivery winning the race after the adapter's pending-list read.
|
||||
const staleRuntime = Object.assign(Object.create(runtime), { listPendingUserInputs: async () => [item] }) as OpencodeRuntimeAdapter;
|
||||
await assert.rejects(staleRuntime.cancelPendingUserInput(session.id, item.id), { status: 409 });
|
||||
await client.session.remove({ sessionID: session.id });
|
||||
console.log("Native queue delivery, pending steer/queue changes and automatic continuation passed; local fixture only.");
|
||||
console.log("Native queue delivery, pending changes, withdrawal and delivery-race rejection passed; local fixture only.");
|
||||
} finally {
|
||||
release();
|
||||
releaseSecond();
|
||||
|
||||
Reference in New Issue
Block a user