mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
fix(agents): expose configured MCP tools in Pi profiles
This commit is contained in:
@@ -2,6 +2,7 @@ import { spawn, type ChildProcess } from "node:child_process";
|
||||
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { logDebug, logWarn } from "../logger.js";
|
||||
import { setPluginToolMeta } from "../plugins/tools.js";
|
||||
import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
|
||||
import { loadEmbeddedPiLspConfig } from "./embedded-pi-lsp.js";
|
||||
import {
|
||||
@@ -368,6 +369,10 @@ export async function createBundleLspToolRuntime(params: {
|
||||
continue;
|
||||
}
|
||||
reservedNames.add(normalizedName);
|
||||
setPluginToolMeta(tool, {
|
||||
pluginId: "bundle-lsp",
|
||||
optional: false,
|
||||
});
|
||||
tools.push(tool);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { logWarn } from "../logger.js";
|
||||
import { setPluginToolMeta } from "../plugins/tools.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
import {
|
||||
buildSafeToolName,
|
||||
@@ -10,6 +11,7 @@ import {
|
||||
TOOL_NAME_SEPARATOR,
|
||||
} from "./pi-bundle-mcp-names.js";
|
||||
import type { BundleMcpToolRuntime, SessionMcpRuntime } from "./pi-bundle-mcp-types.js";
|
||||
import type { AnyAgentTool } from "./tools/common.js";
|
||||
|
||||
function toAgentToolResult(params: {
|
||||
serverName: string;
|
||||
@@ -96,7 +98,7 @@ export async function materializeBundleMcpToolsForRun(params: {
|
||||
);
|
||||
}
|
||||
reservedNames.add(normalizeLowercaseStringOrEmpty(safeToolName));
|
||||
tools.push({
|
||||
const agentTool: AnyAgentTool = {
|
||||
name: safeToolName,
|
||||
label: tool.title ?? tool.toolName,
|
||||
description: tool.description || tool.fallbackDescription,
|
||||
@@ -109,7 +111,12 @@ export async function materializeBundleMcpToolsForRun(params: {
|
||||
result,
|
||||
});
|
||||
},
|
||||
};
|
||||
setPluginToolMeta(agentTool, {
|
||||
pluginId: "bundle-mcp",
|
||||
optional: false,
|
||||
});
|
||||
tools.push(agentTool);
|
||||
}
|
||||
|
||||
// Sort tools deterministically by name so the tools block in API requests is stable across
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getPluginToolMeta } from "../plugins/tools.js";
|
||||
import {
|
||||
createBundleMcpToolRuntime,
|
||||
materializeBundleMcpToolsForRun,
|
||||
@@ -58,6 +59,7 @@ describe("createBundleMcpToolRuntime", () => {
|
||||
});
|
||||
|
||||
expect(runtime.tools.map((tool) => tool.name)).toEqual(["bundleProbe__bundle_probe"]);
|
||||
expect(getPluginToolMeta(runtime.tools[0])?.pluginId).toBe("bundle-mcp");
|
||||
const result = await runtime.tools[0].execute("call-bundle-probe", {}, undefined, undefined);
|
||||
expect(result.content[0]).toMatchObject({
|
||||
type: "text",
|
||||
|
||||
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import "./test-helpers/fast-coding-tools.js";
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { setPluginToolMeta } from "../plugins/tools.js";
|
||||
import {
|
||||
cleanupEmbeddedPiRunnerTestWorkspace,
|
||||
createEmbeddedPiRunnerOpenAiConfig,
|
||||
@@ -53,24 +54,26 @@ vi.mock("./pi-bundle-mcp-tools.js", () => ({
|
||||
}),
|
||||
dispose: async () => {},
|
||||
}),
|
||||
materializeBundleMcpToolsForRun: async () => ({
|
||||
tools: [
|
||||
{
|
||||
name: "bundleProbe__bundle_probe",
|
||||
label: "bundle_probe",
|
||||
description: "Bundle MCP probe",
|
||||
parameters: { type: "object", properties: {} },
|
||||
execute: async () => ({
|
||||
content: [{ type: "text", text: "FROM-BUNDLE" }],
|
||||
details: {
|
||||
mcpServer: "bundleProbe",
|
||||
mcpTool: "bundle_probe",
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
dispose: async () => {},
|
||||
}),
|
||||
materializeBundleMcpToolsForRun: async () => {
|
||||
const tool = {
|
||||
name: "bundleProbe__bundle_probe",
|
||||
label: "bundle_probe",
|
||||
description: "Bundle MCP probe",
|
||||
parameters: { type: "object", properties: {} },
|
||||
execute: async () => ({
|
||||
content: [{ type: "text", text: "FROM-BUNDLE" }],
|
||||
details: {
|
||||
mcpServer: "bundleProbe",
|
||||
mcpTool: "bundle_probe",
|
||||
},
|
||||
}),
|
||||
};
|
||||
setPluginToolMeta(tool as any, { pluginId: "bundle-mcp", optional: false });
|
||||
return {
|
||||
tools: [tool],
|
||||
dispose: async () => {},
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@mariozechner/pi-ai", async () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { setPluginToolMeta } from "../../plugins/tools.js";
|
||||
import type { AnyAgentTool } from "../tools/common.js";
|
||||
import { applyFinalEffectiveToolPolicy } from "./effective-tool-policy.js";
|
||||
|
||||
@@ -117,4 +118,30 @@ describe("applyFinalEffectiveToolPolicy", () => {
|
||||
|
||||
expect(warnings.some((w) => w.includes("totally-made-up-tool"))).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps bundle MCP tools in the coding profile via plugin metadata", () => {
|
||||
const mcpTool = makeTool("bundleProbe__bundle_probe");
|
||||
setPluginToolMeta(mcpTool, { pluginId: "bundle-mcp", optional: false });
|
||||
|
||||
const filtered = applyFinalEffectiveToolPolicy({
|
||||
bundledTools: [mcpTool],
|
||||
config: { tools: { profile: "coding" } },
|
||||
warn: () => {},
|
||||
});
|
||||
|
||||
expect(filtered.map((tool) => tool.name)).toEqual(["bundleProbe__bundle_probe"]);
|
||||
});
|
||||
|
||||
it("lets explicit deny entries override the profile bundle MCP allowlist", () => {
|
||||
const mcpTool = makeTool("bundleProbe__bundle_probe");
|
||||
setPluginToolMeta(mcpTool, { pluginId: "bundle-mcp", optional: false });
|
||||
|
||||
const filtered = applyFinalEffectiveToolPolicy({
|
||||
bundledTools: [mcpTool],
|
||||
config: { tools: { profile: "coding", deny: ["bundle-mcp"] } },
|
||||
warn: () => {},
|
||||
});
|
||||
|
||||
expect(filtered).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,4 +14,10 @@ describe("tool-catalog", () => {
|
||||
expect(policy!.allow).toContain("video_generate");
|
||||
expect(policy!.allow).toContain("update_plan");
|
||||
});
|
||||
|
||||
it("includes bundle MCP tools in coding and messaging profile policies", () => {
|
||||
expect(resolveCoreToolProfilePolicy("coding")?.allow).toContain("bundle-mcp");
|
||||
expect(resolveCoreToolProfilePolicy("messaging")?.allow).toContain("bundle-mcp");
|
||||
expect(resolveCoreToolProfilePolicy("minimal")?.allow).not.toContain("bundle-mcp");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -318,10 +318,10 @@ const CORE_TOOL_PROFILES: Record<ToolProfileId, ToolProfilePolicy> = {
|
||||
allow: listCoreToolIdsForProfile("minimal"),
|
||||
},
|
||||
coding: {
|
||||
allow: listCoreToolIdsForProfile("coding"),
|
||||
allow: [...listCoreToolIdsForProfile("coding"), "bundle-mcp"],
|
||||
},
|
||||
messaging: {
|
||||
allow: listCoreToolIdsForProfile("messaging"),
|
||||
allow: [...listCoreToolIdsForProfile("messaging"), "bundle-mcp"],
|
||||
},
|
||||
full: {},
|
||||
};
|
||||
|
||||
@@ -128,7 +128,9 @@ export function applyToolPolicyPipeline(params: {
|
||||
const warnableGatedCoreEntries = step.suppressUnavailableCoreToolWarning
|
||||
? []
|
||||
: gatedCoreEntries.filter((entry) => !unavailableCoreWarningAllowlist.has(entry));
|
||||
const otherEntries = resolved.unknownAllowlist.filter((entry) => !isKnownCoreToolId(entry));
|
||||
const otherEntries = resolved.unknownAllowlist.filter(
|
||||
(entry) => !isKnownCoreToolId(entry) && !unavailableCoreWarningAllowlist.has(entry),
|
||||
);
|
||||
const warningEntries = [...warnableGatedCoreEntries, ...otherEntries];
|
||||
if (
|
||||
shouldWarnAboutUnknownAllowlist({
|
||||
|
||||
@@ -13,13 +13,17 @@ import {
|
||||
} from "./runtime/load-context.js";
|
||||
import type { OpenClawPluginToolContext } from "./types.js";
|
||||
|
||||
type PluginToolMeta = {
|
||||
export type PluginToolMeta = {
|
||||
pluginId: string;
|
||||
optional: boolean;
|
||||
};
|
||||
|
||||
const pluginToolMeta = new WeakMap<AnyAgentTool, PluginToolMeta>();
|
||||
|
||||
export function setPluginToolMeta(tool: AnyAgentTool, meta: PluginToolMeta): void {
|
||||
pluginToolMeta.set(tool, meta);
|
||||
}
|
||||
|
||||
export function getPluginToolMeta(tool: AnyAgentTool): PluginToolMeta | undefined {
|
||||
return pluginToolMeta.get(tool);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user