mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:40:43 +00:00
test: mock bundle MCP materialization boundary
This commit is contained in:
@@ -129,9 +129,15 @@ export async function createBundleMcpToolRuntime(params: {
|
||||
workspaceDir: string;
|
||||
cfg?: OpenClawConfig;
|
||||
reservedToolNames?: Iterable<string>;
|
||||
createRuntime?: (params: {
|
||||
sessionId: string;
|
||||
workspaceDir: string;
|
||||
cfg?: OpenClawConfig;
|
||||
}) => SessionMcpRuntime;
|
||||
}): Promise<BundleMcpToolRuntime> {
|
||||
const { createSessionMcpRuntime } = await import("./pi-bundle-mcp-runtime.js");
|
||||
const runtime = createSessionMcpRuntime({
|
||||
const createRuntime =
|
||||
params.createRuntime ?? (await import("./pi-bundle-mcp-runtime.js")).createSessionMcpRuntime;
|
||||
const runtime = createRuntime({
|
||||
sessionId: `bundle-mcp:${crypto.randomUUID()}`,
|
||||
workspaceDir: params.workspaceDir,
|
||||
cfg: params.cfg,
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
writeClaudeBundle,
|
||||
writeExecutable,
|
||||
} from "./bundle-mcp-shared.test-harness.js";
|
||||
import { __testing } from "./pi-bundle-mcp-tools.js";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const SDK_SERVER_MCP_PATH = require.resolve("@modelcontextprotocol/sdk/server/mcp.js");
|
||||
@@ -17,6 +16,7 @@ const SDK_SERVER_SSE_PATH = require.resolve("@modelcontextprotocol/sdk/server/ss
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
export async function cleanupBundleMcpHarness(): Promise<void> {
|
||||
const { __testing } = await import("./pi-bundle-mcp-tools.js");
|
||||
await __testing.resetSessionMcpRuntimeManager();
|
||||
await Promise.all(tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })));
|
||||
}
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
createBundleMcpToolRuntime,
|
||||
materializeBundleMcpToolsForRun,
|
||||
} from "./pi-bundle-mcp-materialize.js";
|
||||
import {
|
||||
cleanupBundleMcpHarness,
|
||||
makeTempDir,
|
||||
startSseProbeServer,
|
||||
writeBundleProbeMcpServer,
|
||||
} from "./pi-bundle-mcp-test-harness.js";
|
||||
import type { McpCatalogTool } from "./pi-bundle-mcp-types.js";
|
||||
import type { SessionMcpRuntime } from "./pi-bundle-mcp-types.js";
|
||||
|
||||
afterEach(async () => {
|
||||
await cleanupBundleMcpHarness();
|
||||
});
|
||||
|
||||
function makeToolRuntime(tools?: McpCatalogTool[]): SessionMcpRuntime {
|
||||
function makeToolRuntime(
|
||||
params: {
|
||||
tools?: McpCatalogTool[];
|
||||
serverName?: string;
|
||||
resultText?: string;
|
||||
} = {},
|
||||
): SessionMcpRuntime {
|
||||
const serverName = params.serverName ?? "bundleProbe";
|
||||
const tools = params.tools ?? [
|
||||
{
|
||||
serverName,
|
||||
safeServerName: serverName,
|
||||
toolName: "bundle_probe",
|
||||
description: "Bundle probe",
|
||||
inputSchema: { type: "object", properties: {} },
|
||||
fallbackDescription: "Bundle probe",
|
||||
},
|
||||
];
|
||||
return {
|
||||
sessionId: "session-collision",
|
||||
workspaceDir: "/tmp",
|
||||
@@ -29,25 +35,16 @@ function makeToolRuntime(tools?: McpCatalogTool[]): SessionMcpRuntime {
|
||||
version: 1,
|
||||
generatedAt: 0,
|
||||
servers: {
|
||||
bundleProbe: {
|
||||
serverName: "bundleProbe",
|
||||
launchSummary: "bundleProbe",
|
||||
toolCount: 1,
|
||||
[serverName]: {
|
||||
serverName,
|
||||
launchSummary: serverName,
|
||||
toolCount: tools.length,
|
||||
},
|
||||
},
|
||||
tools: tools ?? [
|
||||
{
|
||||
serverName: "bundleProbe",
|
||||
safeServerName: "bundleProbe",
|
||||
toolName: "bundle_probe",
|
||||
description: "Bundle probe",
|
||||
inputSchema: { type: "object", properties: {} },
|
||||
fallbackDescription: "Bundle probe",
|
||||
},
|
||||
],
|
||||
tools,
|
||||
}),
|
||||
callTool: async () => ({
|
||||
content: [{ type: "text", text: "FROM-BUNDLE" }],
|
||||
content: [{ type: "text", text: params.resultText ?? "FROM-BUNDLE" }],
|
||||
isError: false,
|
||||
}),
|
||||
dispose: async () => {},
|
||||
@@ -81,19 +78,18 @@ describe("createBundleMcpToolRuntime", () => {
|
||||
expect(runtime.tools.map((tool) => tool.name)).toEqual(["bundleProbe__bundle_probe-2"]);
|
||||
});
|
||||
|
||||
it("loads configured stdio MCP tools without a bundle", async () => {
|
||||
const workspaceDir = await makeTempDir("openclaw-bundle-mcp-tools-");
|
||||
const serverScriptPath = path.join(workspaceDir, "servers", "configured-probe.mjs");
|
||||
await writeBundleProbeMcpServer(serverScriptPath);
|
||||
|
||||
it("materializes configured MCP tools through the session runtime boundary", async () => {
|
||||
const created: Parameters<
|
||||
NonNullable<Parameters<typeof createBundleMcpToolRuntime>[0]["createRuntime"]>
|
||||
>[0][] = [];
|
||||
const runtime = await createBundleMcpToolRuntime({
|
||||
workspaceDir,
|
||||
workspaceDir: "/workspace",
|
||||
cfg: {
|
||||
mcp: {
|
||||
servers: {
|
||||
configuredProbe: {
|
||||
command: "node",
|
||||
args: [serverScriptPath],
|
||||
args: ["configured-probe.mjs"],
|
||||
env: {
|
||||
BUNDLE_PROBE_TEXT: "FROM-CONFIG",
|
||||
},
|
||||
@@ -101,96 +97,70 @@ describe("createBundleMcpToolRuntime", () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
createRuntime: (params) => {
|
||||
created.push(params);
|
||||
return makeToolRuntime({
|
||||
serverName: "configuredProbe",
|
||||
resultText: "FROM-CONFIG",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
expect(runtime.tools.map((tool) => tool.name)).toEqual(["configuredProbe__bundle_probe"]);
|
||||
const result = await runtime.tools[0].execute(
|
||||
"call-configured-probe",
|
||||
{},
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
expect(result.content[0]).toMatchObject({
|
||||
type: "text",
|
||||
text: "FROM-CONFIG",
|
||||
});
|
||||
expect(result.details).toEqual({
|
||||
mcpServer: "configuredProbe",
|
||||
mcpTool: "bundle_probe",
|
||||
});
|
||||
} finally {
|
||||
await runtime.dispose();
|
||||
}
|
||||
});
|
||||
expect(created).toHaveLength(1);
|
||||
expect(created[0].sessionId).toMatch(/^bundle-mcp:/);
|
||||
expect(created[0].workspaceDir).toBe("/workspace");
|
||||
expect(created[0].cfg?.mcp?.servers?.configuredProbe).toMatchObject({
|
||||
command: "node",
|
||||
args: ["configured-probe.mjs"],
|
||||
});
|
||||
|
||||
it("loads configured SSE MCP tools via url", async () => {
|
||||
vi.useRealTimers();
|
||||
const sseServer = await startSseProbeServer();
|
||||
|
||||
try {
|
||||
const workspaceDir = await makeTempDir("openclaw-bundle-mcp-sse-");
|
||||
const runtime = await createBundleMcpToolRuntime({
|
||||
workspaceDir,
|
||||
cfg: {
|
||||
mcp: {
|
||||
servers: {
|
||||
sseProbe: {
|
||||
url: `http://127.0.0.1:${sseServer.port}/sse`,
|
||||
transport: "sse",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
expect(runtime.tools.map((tool) => tool.name)).toEqual(["sseProbe__sse_probe"]);
|
||||
const result = await runtime.tools[0].execute("call-sse-probe", {}, undefined, undefined);
|
||||
expect(result.content[0]).toMatchObject({
|
||||
type: "text",
|
||||
text: "FROM-SSE",
|
||||
});
|
||||
expect(result.details).toEqual({
|
||||
mcpServer: "sseProbe",
|
||||
mcpTool: "sse_probe",
|
||||
});
|
||||
} finally {
|
||||
await runtime.dispose();
|
||||
}
|
||||
} finally {
|
||||
await sseServer.close();
|
||||
}
|
||||
expect(runtime.tools.map((tool) => tool.name)).toEqual(["configuredProbe__bundle_probe"]);
|
||||
const result = await runtime.tools[0].execute(
|
||||
"call-configured-probe",
|
||||
{},
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
expect(result.content[0]).toMatchObject({
|
||||
type: "text",
|
||||
text: "FROM-CONFIG",
|
||||
});
|
||||
expect(result.details).toEqual({
|
||||
mcpServer: "configuredProbe",
|
||||
mcpTool: "bundle_probe",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns tools sorted alphabetically for stable prompt-cache keys", async () => {
|
||||
const runtime = await materializeBundleMcpToolsForRun({
|
||||
runtime: makeToolRuntime([
|
||||
{
|
||||
serverName: "multi",
|
||||
safeServerName: "multi",
|
||||
toolName: "zeta",
|
||||
description: "z",
|
||||
inputSchema: { type: "object", properties: {} },
|
||||
fallbackDescription: "z",
|
||||
},
|
||||
{
|
||||
serverName: "multi",
|
||||
safeServerName: "multi",
|
||||
toolName: "alpha",
|
||||
description: "a",
|
||||
inputSchema: { type: "object", properties: {} },
|
||||
fallbackDescription: "a",
|
||||
},
|
||||
{
|
||||
serverName: "multi",
|
||||
safeServerName: "multi",
|
||||
toolName: "mu",
|
||||
description: "m",
|
||||
inputSchema: { type: "object", properties: {} },
|
||||
fallbackDescription: "m",
|
||||
},
|
||||
]),
|
||||
runtime: makeToolRuntime({
|
||||
tools: [
|
||||
{
|
||||
serverName: "multi",
|
||||
safeServerName: "multi",
|
||||
toolName: "zeta",
|
||||
description: "z",
|
||||
inputSchema: { type: "object", properties: {} },
|
||||
fallbackDescription: "z",
|
||||
},
|
||||
{
|
||||
serverName: "multi",
|
||||
safeServerName: "multi",
|
||||
toolName: "alpha",
|
||||
description: "a",
|
||||
inputSchema: { type: "object", properties: {} },
|
||||
fallbackDescription: "a",
|
||||
},
|
||||
{
|
||||
serverName: "multi",
|
||||
safeServerName: "multi",
|
||||
toolName: "mu",
|
||||
description: "m",
|
||||
inputSchema: { type: "object", properties: {} },
|
||||
fallbackDescription: "m",
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
expect(runtime.tools.map((tool) => tool.name)).toEqual([
|
||||
|
||||
Reference in New Issue
Block a user