mastra-ai/mastra · error · ToolNotFoundError

${inputData.toolName}

Error message

${inputData.toolName}

What it means

Inside createToolCallStep, when the workflow tries to execute an agent tool call, it resolves the tool by name (or tool id) from the tools available in the current scope; if no matching tool with an execute function exists, ToolNotFoundError(toolName) is thrown with the tool name as the message. This means the referenced tool was not registered on the agent/step (or lacks execute).

Source

Thrown at packages/core/src/loop/workflows/agentic-execution/tool-call-step.ts:995

          const agentBgConfig = agentBgConfigCheck;
          const managerConfig = readScoped(scopeCtx, BACKGROUND_TASK_MANAGER_CONFIG_KEY, 'backgroundTaskManagerConfig');

          const bgResolved = resolveBackgroundConfig({
            llmBgOverrides,
            toolName: inputData.toolName,
            toolConfig: toolBgConfig,
            agentConfig: agentBgConfig,
            managerConfig,
          });

          if (bgResolved.runInBackground) {
            // Resolve the tool executor from the current closure
            const stepTools = (readScoped(scopeCtx, STEP_TOOLS_KEY, 'stepTools') as Tools | undefined) || tools;
            const resolvedTool =
              stepTools?.[inputData.toolName] ||
              Object.values(stepTools || {})?.find((t: any) => 'id' in t && t.id === inputData.toolName);
            if (!resolvedTool?.execute) {
              throw new ToolNotFoundError(inputData.toolName);
            }
            let backgroundChunkTransformQueue: Promise<void> = Promise.resolve();
            const emittedReplayedToolCalls = new Set<string>();

            // Create a self-contained background task with per-stream hooks
            const bgTask = createBackgroundTask(backgroundTaskManager, {
              toolName: inputData.toolName,
              toolCallId: inputData.toolCallId,
              args: args as Record<string, unknown>,
              agentId,
              threadId: readScoped(scopeCtx, THREAD_ID_KEY, 'threadId'),
              resourceId: readScoped(scopeCtx, RESOURCE_ID_KEY, 'resourceId'),
              timeoutMs: bgResolved.timeoutMs,
              maxRetries: bgResolved.maxRetries,
              runId,
              context: {
                // Executor — uses the tool from the current closure
                executor: {

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Ensure every tool referenced by the agent is passed in the tools option of the workflow/agent step, with the exact name/id the model calls.
  2. Add an execute function to tools declared with createTool (or only include executable tools in the tools map).
  3. Verify key casing: tools object keys must match the tool names emitted by the model.
  4. If the run replays old history, either keep old tool ids registered as aliases or start a fresh thread/run.
  5. Log the available keys of stepTools and compare to the failing toolName in the message.

Example fix

// before
const weatherTool = createTool({ id: 'weather', inputSchema: z.object({ city: z.string() }) }); // no execute
await createWorkflow(...).then(agentStep({ tools: { weather: weatherTool } }));
// after
const weatherTool = createTool({ id: 'weather', inputSchema: z.object({ city: z.string() }), execute: async ({ context }) => getWeather(context.city) });
Defensive patterns

Strategy: validation

Validate before calling

const stepTools = tools || {};
const resolved = stepTools[inputData.toolName] || Object.values(stepTools).find((t: any) => t?.id === inputData.toolName);
if (!resolved?.execute) throw new Error(`Tool ${inputData.toolName} missing or has no execute()`);

Type guard

function isExecutableTool(t: unknown): t is { execute: (...args: unknown[]) => Promise<unknown>; id?: string } {
  return !!t && typeof t === 'object' && 'execute' in t && typeof (t as any).execute === 'function';
}

Try / catch

try {
  await workflow.start({ inputData });
} catch (e) {
  if (e instanceof ToolNotFoundError || (e instanceof Error && e.message === inputData.toolName)) {
    console.error(`Tool ${e.message} not registered; available:`, Object.keys(tools));
  }
  throw e;
}

Prevention

When it happens

Trigger: The model emits a tool call for a name not present in stepTools/tools (lookup by key or by tool.id fails), or the resolved tool has no execute function (e.g. a tool that only provides input schema, or a read-only/display tool registered without execute). Raised from toolCallStep, readOnlyStep, or step created by createToolCallStep.

Common situations: Typo or casing mismatch between the tool name the model emits and the key in the tools object; tool defined dynamically (createTool) without execute; tool registered on the agent but not passed into the workflow step's tools; replaying/legacy runs referencing tools that were renamed or removed; tool id changed while stored thread history still references the old id.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/702058d829d915bf. Report an issue: GitHub.