Hmbown/CodeWhale · error · ServerError

unknown_tool

unknown_tool

Error message

unknown tool "${step.tool}"

What it means

run_actions validates each step's tool name against the server's registered TOOL_NAMES set. Any step referencing a tool the MCP server does not expose (typo, removed/renamed tool, or the nesting-disallowed run_actions itself caught earlier) throws unknown_tool. It exists so a batched sequence fails fast at validation rather than mid-dispatch.

Solutions

  1. Check the server's tool list (tools/list or TOOL_NAMES) and use an exact registered name for every step
  2. Fix the typo in step.tool
  3. Split the batch: run only steps with valid tools and replace or drop the invalid one
  4. If a needed tool is genuinely missing, upgrade the computer-use plugin/server to a version that exposes it

Example fix

// before
steps: [{ tool: "screenshots" }]
// after
steps: [{ tool: "screenshot" }]
Defensive patterns

Strategy: validation

Validate before calling

const TOOL_NAMES = new Set(["screenshot","zoom","left_click","type","key","get_app_state","find_elements","focus","run_actions","app_script"]); // use the server's actual tool list
if (!steps.every(s => typeof s?.tool === "string" && TOOL_NAMES.has(s.tool))) throw new Error("run_actions step has unregistered tool");

Type guard

const isValidStep = (s) => !!s && typeof s.tool === "string" && TOOL_NAMES.has(s.tool);

Try / catch

try { await runActions({ steps }) } catch (e) { if (e.code === "unknown_tool") { /* fix or drop steps where !TOOL_NAMES.has(step.tool) */ } else throw e; }

Prevention

When it happens

Trigger: Calling run_actions with a steps array whose step.tool is not in TOOL_NAMES — e.g. misspelled 'screenshots', a tool name from a different MCP server, or an alias the server never registered.

Common situations: Model or client hallucinating tool names; renaming tools across server versions while cached prompts still reference old names; copy-pasting step lists between computer-use servers with different tool catalogs.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/35187010249e03eb. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/plugins/computer-use/mcp/server.mjs:856

  let binding;
  let dispatched = false;
  try {
    binding = await bindComputer(computer);
    if (binding.needsObservation && !ROUTE_INSPECTION_TOOLS.has(name)) {
      throw new ServerError("computer_observation_required", "Computer route changed — call screenshot or get_app_state on the registered target before acting");
    }
    // Per-app consent: the first call that targets an application on the local
    // computer must carry a recorded user decision. open_application returns
    // the grant so its resolved identity can be aliased below.
    const gateResult = await consentCheck(computer, name, args);
    if (name === "run_actions") {
      const steps = args.steps;
      if (!Array.isArray(steps) || steps.length < 1 || steps.length > 8) throw new ServerError("bad_args", "run_actions needs 1..8 steps");
      const results = [];
      for (const [i, step] of steps.entries()) {
        if (!step || typeof step.tool !== "string") throw new ServerError("bad_args", `step ${i} needs a tool name`);
        if (step.tool === "run_actions") throw new ServerError("bad_args", "run_actions cannot nest");
        if (!TOOL_NAMES.has(step.tool)) throw new ServerError("unknown_tool", `unknown tool "${step.tool}"`);
        const result = await callTool({ name: step.tool, arguments: { ...(step.arguments ?? {}), computer: computer.id } });
        const body = JSON.parse(result.content[0].text);
        results.push({ tool: step.tool, ok: body.ok !== false, receipt: body });
        if (body.ok === false || result.isError) {
          return { content: [{ type: "text", text: JSON.stringify(fail(computer, body.error?.code ?? "step_failed", body.error?.message ?? "step failed", { tool: "run_actions", switched, stopped_at: i, steps: results })) }], isError: true };
        }
      }
      return { content: [{ type: "text", text: JSON.stringify(receipt(computer, { ok: true, tool: "run_actions", switched, steps: results })) }] };
    }
    if (name === "find_elements") {
      const st = args.state_id ? appStates.get(args.state_id) : null;
      if (args.state_id && !st) throw new ServerError("unknown_state", `state_id "${args.state_id}" is unknown or expired — call get_app_state again`);
      if (st) {
        if (st.computerId && st.computerId !== computer.id) {
          throw new ServerError("state_wrong_computer", `state_id "${args.state_id}" belongs to computer "${st.computerId}", not "${computer.id}"`);
        }
        const filtered = filterElements(st.elements, {
          detail: "summary", query: args.query, role: args.role,

View on GitHub (pinned to 73e0f67d83)