JuliusBrussee/caveman · error
unknown tool: ${params?.name}
Error message
unknown tool: ${params?.name} What it means
The caveman-delegate MCP server (a stdio JSON-RPC handler) throws when a tools/call request names anything other than its single advertised tool. It is strict per the MCP contract: tools/list returns exactly one tool (TOOL), and calls with other names are protocol errors, not silent no-ops.
Source
Thrown at agents/delegate/caveman-delegate-mcp.mjs:140
cwd: { type: "string", description: "Working directory (default: current)" },
},
required: ["task"],
},
};
async function handle(msg) {
const { id, method, params } = msg;
if (method === "initialize") {
return {
protocolVersion: params?.protocolVersion || PROTOCOL_FALLBACK,
capabilities: { tools: { listChanged: false } },
serverInfo: { name: "caveman-delegate", version: "0.1.0" },
};
}
if (method === "tools/list") return { tools: [TOOL] };
if (method === "ping") return {};
if (method === "tools/call") {
if (params?.name !== TOOL.name) throw new Error(`unknown tool: ${params?.name}`);
if (process.env.CAVEMAN_DELEGATE_CHILD === "1") throw new Error("cave_delegate_depth_exceeded");
const task = params?.arguments?.task;
if (!task || typeof task !== "string") throw new Error("cave_delegate_missing_task");
const r = await runWorker(task, params?.arguments?.cwd);
const lines = [r.out || "(worker produced no output)"];
if (r.usage) {
const u = r.usage;
let usageLine = `delegate usage (measured, provider-reported): input ${u.input + u.cacheRead + u.cacheWrite} (cacheRead ${u.cacheRead}, cacheWrite ${u.cacheWrite}), output ${u.output}`;
if (u.cost > 0) usageLine += `, cost $${u.cost.toFixed(4)}`;
lines.push("---", usageLine);
}
if (r.code !== 0) {
lines.push("---", `worker exit ${r.code}: ${r.err.slice(-500)}`);
return { content: [{ type: "text", text: lines.join("\n") }], isError: true };
}
return { content: [{ type: "text", text: lines.join("\n") }] };
}
if (method?.startsWith("notifications/")) return undefined;View on GitHub (pinned to 27d5a3981a)
Solutions
- Call tools/list first and use the exact name it returns
- Update your client to the current delegate tool name exposed by the server
- If wrapping the server, assert the name string in one place rather than retyping it
Example fix
// before
{ "method": "tools/call", "params": { "name": "delegate", "arguments": { ... } } }
// after — use the exact name from tools/list
const [{ name }] = (await rpc("tools/list")).tools;
await rpc("tools/call", { name, arguments: { task } }); Defensive patterns
Strategy: validation
Validate before calling
const tools = (await rpc("tools/list")).tools;
const names = new Set(tools.map((t) => t.name));
if (!names.has(requestedName)) {
throw new Error(`unknown tool ${requestedName}; available: ${[...names].join(", ")}`);
} Type guard
function isKnownTool(name, advertised) {
return advertised.some((t) => t.name === name);
} Try / catch
try { await rpc("tools/call", { name, arguments }); }
catch (e) {
if (/^unknown tool: /.test(String(e?.message))) { /* re-list tools, correct the name, retry once */ }
else throw e;
} Prevention
- Always discover tool names via tools/list at runtime instead of hard-coding
- Re-check names after upgrading the delegate server
When it happens
Trigger: A client sending tools/call with a misspelled or outdated tool name (for example after a rename), or a client that never called tools/list and guessed the name.
Common situations: Version skew between client hard-coded tool names and the server's current name; typos in hand-rolled MCP clients; copy-paste from a different MCP server's config.
Related errors
- ${key} is required
- filters must be an object
- unknown trace filter(s): ${unknown.sort().join(", ")}
- filter ${key} must be a non-empty array of non-empty strings
- filter status_class values must be 2xx, 4xx, or 5xx
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/5c1db8313253995d.
Report an issue: GitHub.