You can now add tool hooks to your Mastra agents, letting you log, audit, and validate tool calls before, during, and after they run — or block them entirely.
Agent-level hooks fire at two points during a tool call:
beforeToolCall: Just before the tool is called — can block the call or return a pre-defined resultafterToolCall: After the tool has run and returned its output, or thrown an error (skipped when the call is blocked)
Tool-level hooks fire at four separate points during tool calls:
onInputStart: When the model has picked a tool and is about to start writing its argumentsonInputDelta: On each chunk of the arguments as they stream inonInputAvailable: When the full arguments are parsed and validated, ready to be passed to the toolonOutput: After the tool has run and returned its output (only when execution succeeded)
When using .stream(), tool-level hooks fire in real time, letting you monitor different stages of the execution. With .generate(), the same hooks fire in the same order, but won't be surfaced until the agent's run is complete.
Before hooks, adding logging or policy checks to a tool call meant manually writing them as part of its execute function. This approach still works, but only if you can modify the tool — and only if every agent using it needs the same behavior. Now with hooks that logic lives at the agent or workspace level, or can be configured per-call. Tools also expose their own hooks — one for each stage of the call lifecycle, defined on the tool itself.
Get started
Add hooks to your agent to run logic before and after every tool call.
@mastra/core@1.49.0 or later, added in PR #17637.Agent-level hooks
Use beforeToolCall for policy checks: return proceed: false to skip the tool call, and an output object matching the tool's outputSchema with your own values:
import { Agent } from "@mastra/core/agent";
import { weatherTool } from "../tools/weather-tool";
const BLOCKED_LOCATIONS = ["london", "paris"];
export const weatherAgent = new Agent({
// ...
tools: { weatherTool },
hooks: {
beforeToolCall: ({ toolName, input }) => {
if (toolName === "weatherTool") {
const { location } = input as { location: string };
if (BLOCKED_LOCATIONS.some((blocked) => location.toLowerCase().includes(blocked))) {
return {
proceed: false,
output: {
temperature: null,
feelsLike: null,
humidity: null,
windSpeed: null,
windGust: null,
conditions: `Weather data unavailable for "${location}" (blocked by policy).`,
location
}
};
}
}
},
afterToolCall: ({ toolName, output, error }) => {
if (error) {
console.error(`[afterToolCall] ${toolName} | ${error}`);
} else {
console.log(`[afterToolCall] ${toolName} | ${output}`);
}
}
}
// ...
});Per-call hooks
Pass hooks to .generate() or .stream() to override the agent-level hooks for a single execution:
await weatherAgent.generate("What's the weather in London?", {
hooks: {
beforeToolCall: ({ toolName }) => {
console.log(`[beforeToolCall] ${toolName}`);
}
}
});Tool-level hooks
Tools expose four hooks — one for each stage of a call:
import { createTool } from "@mastra/core/tools";
export const weatherTool = createTool({
// ...
onInputStart: ({ toolCallId }) => {
console.log(`[onInputStart] ${toolCallId}`);
},
onInputDelta: ({ inputTextDelta }) => {
console.log(`[onInputDelta] ${inputTextDelta}`);
},
onInputAvailable: ({ input, toolCallId }) => {
console.log(`[onInputAvailable] ${toolCallId} | ${input}`);
},
onOutput: ({ output, toolName }) => {
console.log(`[onOutput] ${toolName} | ${output}`);
},
execute: async (inputData) => {
// ...
}
});Workspace hooks
Workspace hooks work the same as agent-level hooks but the context also includes workspaceToolName:
import { Workspace } from "@mastra/core/workspace";
const workspace = new Workspace({
// ...
tools: {
hooks: {
beforeToolCall: ({ toolName, workspaceToolName, input }) => {
console.log(`[beforeToolCall] ${toolName} | ${workspaceToolName} | ${input}`);
},
afterToolCall: ({ toolName, output, error }) => {
if (error) {
console.error(`[afterToolCall] ${toolName} | ${error}`);
} else {
console.log(`[afterToolCall] ${toolName} | ${output}`);
}
}
}
}
});For more information and full configuration options, see:
