fix(mcp): honor plugin tool policy

This commit is contained in:
Vincent Koc
2026-05-03 19:38:51 -07:00
parent c956946b26
commit 471489159b
3 changed files with 54 additions and 0 deletions

View File

@@ -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.

View File

@@ -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.",

View File

@@ -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,
});
}