fix(pi-claude-cli): teach Claude to call deferred MCP tools by their prefixed names

Claude Code 2.x defers MCP tool schemas behind ToolSearch — tools are listed
but calling them by short name (e.g. fn_review_spec) returns "tool not found"
until ToolSearch loads the schema. Triage system prompts that say "call
fn_review_spec()" leave the model guessing whether to use the bare name or
the mcp__custom-tools__ form.

Append a system-prompt section listing every custom tool with its short and
MCP-prefixed names plus the ToolSearch + call protocol. Drives off the
Context.tools we now already thread through, so plain chats with no custom
tools get no addendum.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-25 16:23:24 -07:00
parent 655169725e
commit ad416b5581

View File

@@ -23,7 +23,20 @@ export interface PiMessage {
content: string | unknown[];
toolName?: string;
}
export type PiContext = { systemPrompt?: string; messages: PiMessage[] };
/**
* Minimal pi-ai Tool shape — the subset we read from `Context.tools` to build
* the deferred-tools system-prompt addendum.
*/
export interface PiToolLike {
name: string;
description?: string;
}
export type PiContext = {
systemPrompt?: string;
messages: PiMessage[];
tools?: ReadonlyArray<PiToolLike>;
};
import {
mapPiToolNameToClaude,
translatePiArgsToClaude,
@@ -396,9 +409,60 @@ export function buildSystemPrompt(
);
}
const customToolsAddendum = buildCustomToolsAddendum(context.tools);
if (customToolsAddendum) {
parts.push(customToolsAddendum);
}
return parts.join("\n\n");
}
/** Pi built-in tool names — these go through pi's wrapped built-ins, not MCP. */
const BUILT_IN_PI_TOOLS = new Set([
"read",
"write",
"edit",
"bash",
"grep",
"find",
]);
/**
* Build a system-prompt addendum that maps each custom pi tool to its
* MCP-exposed name (`mcp__custom-tools__<name>`) and explains Claude Code's
* deferred-tool protocol. Without this, the model sees instructions like
* "call fn_review_spec()" but only finds `mcp__custom-tools__fn_review_spec`
* via ToolSearch — and may report "tool not found" or skip the call.
*
* Returns an empty string when there are no custom tools so the addendum
* doesn't pollute prompts on plain chat sessions with only built-ins.
*/
function buildCustomToolsAddendum(
tools: ReadonlyArray<PiToolLike> | undefined,
): string {
if (!tools || tools.length === 0) return "";
const customNames = tools
.map((t) => t.name)
.filter((name) => !BUILT_IN_PI_TOOLS.has(name));
if (customNames.length === 0) return "";
const lines = customNames
.sort()
.map((name) => `- \`${name}\` is exposed as \`mcp__custom-tools__${name}\``);
return [
"## Custom tool naming (Claude Code deferred-tools protocol)",
"",
"The following pi extension tools are available, but Claude Code exposes",
"them under MCP-prefixed names. When a system prompt or task instruction",
"asks you to call one of these by its short name, use the MCP-prefixed",
"name instead. Their schemas are deferred — call `ToolSearch` with",
'`select:mcp__custom-tools__<name>` first, then call the tool directly.',
"",
...lines,
].join("\n");
}
/**
* Converts user message content to text.
* Handles string content and array of content blocks.