vercel/ai · error

Tool "${toolCall.toolName}" not found

Error message

Tool "${toolCall.toolName}" not found

What it means

executeTool looks up the requested tool by name in the provided tools map and throws when no tool with that name is registered. This happens when the model emitted a tool call that is not part of the tools passed to the workflow agent run.

Source

Thrown at packages/workflow/src/workflow-agent.ts:2949

function getToolCallbackMessages(
  messages: LanguageModelV4Prompt,
): ModelMessage[] {
  const withoutAssistantToolCall =
    messages.at(-1)?.role === 'assistant' ? messages.slice(0, -1) : messages;
  return withoutAssistantToolCall as unknown as ModelMessage[];
}

async function executeTool(
  toolCall: { toolCallId: string; toolName: string; input: unknown },
  tools: ToolSet,
  messages: LanguageModelV4Prompt,
  context?: unknown,
  download?: DownloadFunction,
  sandbox?: SandboxSession,
): Promise<WorkflowToolExecutionResult> {
  const tool = tools[toolCall.toolName];
  if (!tool) throw new Error(`Tool "${toolCall.toolName}" not found`);
  if (typeof tool.execute !== 'function') {
    throw new Error(
      `Tool "${toolCall.toolName}" does not have an execute function. ` +
        `Client-side tools should be filtered before calling executeTool.`,
    );
  }
  // Input is already parsed and validated by streamModelCall's parseToolCall
  const parsedInput = toolCall.input;
  let toolResult: unknown;

  try {
    // Extract execute function to avoid binding `this` to the tool object.
    // If we called `tool.execute(...)` directly, JavaScript would bind `this`
    // to `tool`, which contains non-serializable properties like `inputSchema`.
    // When the execute function is a workflow step (marked with 'use step'),
    // the step system captures `this` for serialization, causing failures.
    const { execute } = tool;
    toolResult = await execute(parsedInput, {

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Ensure every tool the model may call is present in the `tools` map passed to the agent/workflow step.
  2. Log toolCall.toolName and compare against Object.keys(tools) to find the mismatch.
  3. Filter invalid tool calls out of the prompt before execution (client-side tools are handled separately).
  4. Sync tool definitions between client and server if they are defined in two places.

Example fix

// before
await executeTool({ toolCall, tools: { getWeather } }); // model called 'search'
// after
await executeTool({ toolCall, tools: { getWeather, search } });
Defensive patterns

Strategy: validation

Validate before calling

if (!tools[toolCall.toolName]) {
  throw new Error(`Tool "${toolCall.toolName}" is not registered; available: ${Object.keys(tools).join(', ')}`);
}

Type guard

function isRegisteredTool(tools: Record<string, unknown>, name: string) {
  return Object.prototype.hasOwnProperty.call(tools, name);
}

Try / catch

try {
  await executeTool({ toolCall, tools });
} catch (e) {
  if (e instanceof Error && e.message.endsWith('not found')) {
    console.error(`Unknown tool ${toolCall.toolName}; known: ${Object.keys(tools)}`);
    // append tool-result error message to the conversation instead of crashing
  } else throw e;
}

Prevention

When it happens

Trigger: The model emits a tool call whose toolName is not present in the `tools` object passed to executeTool / the agent step; stale tool registry after changing tools; tool name mismatch between model configuration and execution context.

Common situations: Renaming a tool on the client but not the agent definition; multi-step conversations where earlier tool definitions were removed; passing a subset of tools to executeTool; typos in tool names.

Related errors


AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30). Data as JSON: /api/errors/37b6c44379a47418. Report an issue: GitHub.