diff --git a/CHANGELOG.md b/CHANGELOG.md index e87306a167c..2daf7c675ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ Docs: https://docs.openclaw.ai - Gateway/sessions: memoize repeated thinking-option enrichment and skip unused cost fallback checks while listing sessions, reducing per-row work on large multi-agent stores. Fixes #76931. - Agents/tools: use config-only runtime snapshots for plugin tool registration and live runtime config getters, avoiding expensive full secrets snapshot clones on the core-plugin-tools prep path. Fixes #76295. - Agents/tools: honor the effective tool denylist before constructing optional PDF/media tool factories, so `tools.deny: ["pdf"]` skips PDF setup before later policy filtering. Fixes #76997. +- MCP/plugin tools: apply global `tools.profile`, `tools.alsoAllow`, and `tools.deny` policy while exposing plugin tools over the standalone MCP bridge, so ACP clients do not see policy-hidden plugin tools or miss opt-in optional tools. Thanks @vincentkoc. - Plugin tools: honor explicit tool denylists while selecting plugin tool runtimes, so denied plugin tools are not materialized for direct command or gateway surfaces before later policy filtering. Thanks @vincentkoc. - Agents/bootstrap: keep pending `BOOTSTRAP.md` and bootstrap truncation notices in system-prompt Project Context instead of copying setup text or raw warning diagnostics into WebChat user/runtime context. Fixes #76946. - Channels/WhatsApp: allow `@whiskeysockets/libsignal-node` in `onlyBuiltDependencies` so pnpm v9+ `blockExoticSubdeps` no longer rejects the baileys git-tarball subdep and silences all inbound agent replies. Fixes #76539. Thanks @ottodeng and @vincentkoc. diff --git a/src/mcp/plugin-tools-serve.test.ts b/src/mcp/plugin-tools-serve.test.ts index f89649c48eb..0b275e951b8 100644 --- a/src/mcp/plugin-tools-serve.test.ts +++ b/src/mcp/plugin-tools-serve.test.ts @@ -87,6 +87,32 @@ describe("plugin tools MCP server", () => { expect(connectToolsMcpServerToStdioMock).toHaveBeenCalledOnce(); }); + it("threads global plugin tool policy into plugin resolution", async () => { + getRuntimeConfigMock.mockReturnValueOnce({ + plugins: { enabled: true }, + tools: { + alsoAllow: ["memory_search"], + deny: ["memory_forget"], + }, + } as never); + const { servePluginToolsMcp } = await import("./plugin-tools-serve.js"); + + await servePluginToolsMcp(); + + expect(ensureStandalonePluginToolRegistryLoadedMock).toHaveBeenCalledWith( + expect.objectContaining({ + toolAllowlist: expect.arrayContaining(["memory_search"]), + toolDenylist: ["memory_forget"], + }), + ); + expect(resolvePluginToolsMock).toHaveBeenCalledWith( + expect.objectContaining({ + toolAllowlist: expect.arrayContaining(["memory_search"]), + toolDenylist: ["memory_forget"], + }), + ); + }); + it("lists registered plugin tools and serializes non-array tool content", async () => { const execute = vi.fn().mockResolvedValue({ content: "Stored.", diff --git a/src/mcp/plugin-tools-serve.ts b/src/mcp/plugin-tools-serve.ts index 8aa64660780..d9f537a3f12 100644 --- a/src/mcp/plugin-tools-serve.ts +++ b/src/mcp/plugin-tools-serve.ts @@ -8,6 +8,13 @@ */ import { pathToFileURL } from "node:url"; import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { pickSandboxToolPolicy } from "../agents/sandbox-tool-policy.js"; +import { + collectExplicitAllowlist, + collectExplicitDenylist, + mergeAlsoAllowPolicy, + resolveToolProfilePolicy, +} from "../agents/tool-policy.js"; import type { AnyAgentTool } from "../agents/tools/common.js"; import { getRuntimeConfig } from "../config/config.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; @@ -16,12 +23,32 @@ import { routeLogsToStderr } from "../logging/console.js"; import { ensureStandalonePluginToolRegistryLoaded, resolvePluginTools } from "../plugins/tools.js"; import { connectToolsMcpServerToStdio, createToolsMcpServer } from "./tools-stdio-server.js"; +function resolvePluginToolPolicy(config: OpenClawConfig): { + toolAllowlist?: string[]; + toolDenylist?: string[]; +} { + const profilePolicy = mergeAlsoAllowPolicy( + resolveToolProfilePolicy(config.tools?.profile), + config.tools?.alsoAllow, + ); + const globalPolicy = pickSandboxToolPolicy(config.tools); + const toolAllowlist = collectExplicitAllowlist([profilePolicy, globalPolicy]); + const toolDenylist = collectExplicitDenylist([profilePolicy, globalPolicy]); + return { + ...(toolAllowlist.length > 0 ? { toolAllowlist } : {}), + ...(toolDenylist.length > 0 ? { toolDenylist } : {}), + }; +} + function resolveTools(config: OpenClawConfig): AnyAgentTool[] { + const pluginToolPolicy = resolvePluginToolPolicy(config); ensureStandalonePluginToolRegistryLoaded({ context: { config }, + ...pluginToolPolicy, }); return resolvePluginTools({ context: { config }, + ...pluginToolPolicy, suppressNameConflicts: true, }); }