Compare commits

..
Author SHA1 Message Date
陈大猫 bb81b72ff0 fix(ai): avoid empty CodeBuddy tools whitelist that hides MCP tools (#4214)
Omit tools in MCP mode and explicitly disallow CodeBuddy built-ins, preserving injected MCP visibility and the skills-mode Bash whitelist.

Fixes #4214
2026-11-12 14:13:14 +08:00
2 changed files with 61 additions and 9 deletions

No files matched your search

@@ -24,6 +24,21 @@ const UI_DISALLOWED_TOOLS = ["AskUserQuestion"];
const MCP_MODE_TOOLS = [];
const SKILLS_MODE_TOOLS = ["Bash"];
// Deduped built-in catalog from @tencent-ai/agent-sdk 0.3.258 cli/product.json.
const CODEBUDDY_BUILTIN_TOOLS = [
"Agent", "Bash", "PowerShell", "Glob", "Grep", "Read", "Edit", "Write",
"NotebookEdit", "WebFetch", "ListMcpResources", "ReadMcpResource", "WaitForMcpServers",
"TaskCreate", "TaskGet", "TaskUpdate", "TaskList", "WebSearch", "EnterPlanMode",
"ExitPlanMode", "KillShell", "TaskStop", "TaskOutput", "SlashCommand", "Skill",
"SkillManage", "AskUserQuestion", "AskUserForStructuredInput", "LSP", "StructuredOutput",
"ToolSearch", "DeferExecuteTool", "ImageGen", "ImageEdit", "VideoGen", "Artifact",
"ArtifactControl", "ComputerUse", "TeamCreate", "TeamDelete", "SendMessage",
"SendUserMessage", "EnterWorktree", "LeaveWorktree", "CronCreate", "CronDelete",
"CronList", "DelegateTool", "WeChatReply", "WeComReply", "PushNotification",
"ReportFindings", "Workflow", "Monitor", "REPL", "A2AGetAgentCard", "A2ASendMessage",
"A2AGetTask", "A2ACancelTask",
];
const CODEBUDDY_IMAGE_MEDIA_TYPES = new Set(["image/jpeg", "image/png", "image/gif", "image/webp"]);
function isCodebuddyImageAttachment(attachment) {
@@ -261,18 +276,19 @@ function buildCodebuddyQueryOptions({
allowDangerouslySkipPermissions: true,
extraArgs: { "dangerously-skip-permissions": null },
mcpServers: toSdkMcpServers(injectedMcpServers),
tools: builtinTools,
// `tools` is the built-in tool whitelist. In mcp mode it is [], so CodeBuddy
// built-ins stay disabled while injected Netcatty MCP tools remain visible.
// Do not mirror that empty list into allowedTools: the SDK treats
// allowedTools as an auto-approval list, and allowedTools: [] prevents MCP
// tool calls from running under bypassPermissions.
disallowedTools: [...UI_DISALLOWED_TOOLS],
// Block built-ins explicitly in mcp mode: tools: [] becomes --tools "",
// which also hides injected MCP tools in CodeBuddy CLI v2.183.0.
// Leave allowedTools unset: an empty auto-approval list prevents MCP tool
// calls from running under bypassPermissions.
disallowedTools: builtinTools.length > 0
? [...UI_DISALLOWED_TOOLS]
: [...new Set([...UI_DISALLOWED_TOOLS, ...CODEBUDDY_BUILTIN_TOOLS])],
// Keep the SDK isolated from user/project settings so local hooks, plugins,
// or extra MCP servers cannot expand Netcatty's controlled tool boundary.
settingSources: [],
env,
};
if (builtinTools.length > 0) options.tools = builtinTools;
const selection = splitCodebuddyModelSelection(model);
if (selection.model) options.model = selection.model;
if (abortController) options.abortController = abortController;
@@ -59,15 +59,51 @@ test("buildCodebuddyQueryOptions wires SDK options in isolated mode", () => {
assert.equal(opts.pathToCodebuddyCode, "/opt/codebuddy/bin/codebuddy");
assert.equal(opts.abortController, ac);
assert.equal(opts.resume, "sess-1");
assert.deepEqual(opts.tools, []);
// allowedTools must stay unset in mcp mode: tools:[] disables built-ins, while
assert.ok(!("tools" in opts));
// allowedTools:[] would prevent injected Netcatty MCP tools from running.
assert.ok(!("allowedTools" in opts));
assert.ok(opts.disallowedTools.includes("Bash"));
assert.ok(opts.disallowedTools.includes("AskUserQuestion"));
assert.equal(opts.mcpServers["netcatty-remote-hosts"].type, "stdio");
assert.deepEqual(opts.mcpServers["netcatty-remote-hosts"].env, { NETCATTY_MCP_PORT: "1" });
});
test("mcp and default mode block built-ins while preserving injected MCP servers", () => {
for (const toolIntegrationMode of ["mcp", undefined]) {
const opts = buildCodebuddyQueryOptions({
toolIntegrationMode,
injectedMcpServers: [{ name: "netcatty", command: "/abs/electron" }],
});
assert.ok(!("tools" in opts));
assert.ok(!("allowedTools" in opts));
assert.equal(opts.permissionMode, "bypassPermissions");
for (const name of ["Bash", "PowerShell", "Read", "Edit", "Write", "Agent", "Skill", "AskUserQuestion"]) {
assert.ok(opts.disallowedTools.includes(name), `${name} must stay disabled`);
}
assert.equal(opts.mcpServers.netcatty.command, "/abs/electron");
}
});
test("mcp mode omits --tools under the SDK serialization rule", () => {
for (const toolIntegrationMode of ["mcp", undefined]) {
const opts = buildCodebuddyQueryOptions({ toolIntegrationMode });
const args = [];
// @tencent-ai/agent-sdk lib/transport/process-transport.js
if (opts.tools !== undefined) {
args.push("--tools", opts.tools.join(","));
}
assert.deepEqual(args, []);
}
});
test("skills mode keeps the Bash whitelist and UI exclusions", () => {
const opts = buildCodebuddyQueryOptions({ toolIntegrationMode: "skills" });
assert.deepEqual(opts.tools, ["Bash"]);
assert.deepEqual(opts.disallowedTools, ["AskUserQuestion"]);
assert.ok(!opts.disallowedTools.includes("Bash"));
assert.ok(!("allowedTools" in opts));
});
test("built-in tools are mode-aware", () => {
assert.deepEqual(codebuddyBuiltinTools("mcp"), []);
assert.deepEqual(codebuddyBuiltinTools(undefined), []);