Introducing Tool Hooks for Mastra Agents

Observe and control every tool call your agent makes.

Paul ScanlonPaul Scanlon·

Jul 14, 2026

·

4 min read

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:

  1. beforeToolCall: Just before the tool is called — can block the call or return a pre-defined result
  2. afterToolCall: 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:

  1. onInputStart: When the model has picked a tool and is about to start writing its arguments
  2. onInputDelta: On each chunk of the arguments as they stream in
  3. onInputAvailable: When the full arguments are parsed and validated, ready to be passed to the tool
  4. onOutput: 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.

note
Requires @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:

TypeScriptsrc/mastra/agents/weather-agent.ts
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:

TypeScriptsrc/mastra/tools/weather-tool.ts
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:

TypeScriptsrc/mastra/workspaces.ts
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:

Share:
Paul Scanlon
Paul ScanlonTechnical Product Marketing Manager

Paul Scanlon sits between Developer Education and Product Marketing at Mastra. Previously, he was a Technical Product Marketing Manager at Neon and worked in Developer Relations at Gatsby, where he created educational content and developer experiences.

All articles by Paul Scanlon