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
- 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.
- Add an execute function to tools declared with createTool (or only include executable tools in the tools map).
- Verify key casing: tools object keys must match the tool names emitted by the model.
- If the run replays old history, either keep old tool ids registered as aliases or start a fresh thread/run.
- 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
- Only include tools with execute functions in the tools option
- Keep tool ids/keys consistent with what the model is told to call
- When renaming tools, keep old ids as registered aliases for replayed history
- Log available tool names in step setup to ease debugging
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
- Workflow ${workflowId} not found
- Workflow ID is required
- Failed to stream workflow: ${response.statusText}
- Response body is null
- Failed to observe workflow stream: ${response.statusText}
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/702058d829d915bf.
Report an issue: GitHub.