How do I define tools for an Agent?
how do i define tools for an Agent? do they automatically get "context" or some type of state they can use? how is this defined?
Now I have all the information I need. Here's the comprehensive answer:
##Defining Tools for an Agent
###1. Creating a Tool with createTool
You define tools using createTool from @mastra/core/tools. A tool needs an id, description, and an execute function. Optionally, you define inputSchema and outputSchema for validation:
import { createTool } from '@mastra/core/tools';
import { z } from 'zod';
const weatherTool = createTool({
id: 'get-weather',
description: 'Get current weather for a location',
inputSchema: z.object({
location: z.string().describe('City name'),
}),
outputSchema: z.object({
temperature: z.number(),
conditions: z.string(),
}),
execute: async (inputData, context) => {
// inputData is typed from inputSchema: { location: string }
// context is a ToolExecutionContext (see below)
return { temperature: 72, conditions: 'Sunny' };
},
});
Source:
packages/core/src/tools/tool.tslines 393–518 — thecreateToolfunction.
###2. Assigning Tools to an Agent
You pass tools as a Record<string, Tool> to the tools property of the Agent config:
import { Agent } from '@mastra/core/agent';
const agent = new Agent({
id: 'weather-agent',
name: 'Weather Agent',
instructions: 'You help users with weather information',
model: 'openai/gpt-4o',
tools: { weatherTool }, // key = tool name the agent will use
});
Tools can also be dynamic — the tools property accepts a DynamicArgument, meaning it can be a function that resolves tools based on request context:
tools: ({ requestContext }) => {
const tier = requestContext.get('tier');
return tier === 'premium' ? { weatherTool, advancedTool } : { weatherTool };
}
Source:
packages/core/src/agent/types.tslines 132–240 — theAgentConfiginterface.
##Yes, Tools Automatically Get Context!
The execute function's second parameter is a ToolExecutionContext that Mastra automatically injects at runtime. You don't need to set it up — it's populated by the framework depending on where the tool is called from (agent, workflow, MCP server, or direct invocation).
###The ToolExecutionContext Interface
Defined in packages/core/src/tools/types.ts lines 287–319:
interface ToolExecutionContext<TSuspend, TResume, TRequestContext> {
// ===== Common properties (always available) =====
mastra?: MastraUnion; // Access to the Mastra instance
requestContext?: RequestContext; // Key-value store for per-request data
abortSignal?: AbortSignal; // For cancellation
workspace?: Workspace; // File system & sandbox access
writer?: ToolStream; // For streaming data back during execution
// ===== Context-specific nested properties =====
agent?: AgentToolExecutionContext; // Present when called from an Agent
workflow?: WorkflowToolExecutionContext; // Present when called from a Workflow
mcp?: MCPToolExecutionContext; // Present when called from an MCP server
}
###Agent-specific context (context.agent)
When a tool is executed by an agent, context.agent is populated with (types.ts lines 32–48):
interface AgentToolExecutionContext<TSuspend, TResume> {
toolCallId: string; // Unique ID for this tool call
messages: any[]; // Conversation messages so far
suspend: (payload, options?) => Promise<void>; // Suspend tool for human approval
threadId?: string; // Memory thread ID
resourceId?: string; // Memory resource ID
resumeData?: TResume; // Data from resuming a suspended tool
writableStream?: WritableStream;
}
###Workflow-specific context (context.workflow)
When called from a workflow step (types.ts lines 50–60):
interface WorkflowToolExecutionContext<TSuspend, TResume> {
runId: string;
workflowId: string;
state: any;
setState: (state: any) => void;
suspend: (payload, options?) => Promise<void>;
resumeData?: TResume;
}
###MCP-specific context (context.mcp)
When called from an MCP server (types.ts lines 62–70):
interface MCPToolExecutionContext {
extra: RequestHandlerExtra; // MCP protocol context
elicitation: {
sendRequest: (request) => Promise<ElicitResult>; // Interactive user input
};
}
##How Context Gets Injected (Under the Hood)
When an agent needs to run a tool, it calls convertTools() (agent.ts line 3700](packages/core/src/agent/agent.ts)), which calls listAssignedTools() ([agent.ts line 2443). Each tool is converted via makeCoreTool(), which creates a CoreToolBuilder (builder.ts line 46`).
The CoreToolBuilder.createExecute() method ([builder.ts lines ~305–515](packages/core/src/tools/tool-builder/builder.ts)) is the adapter between the AI SDK's call signature and Mastra's (inputData, context)signature. It constructs thetoolContext` object with:
mastra— a proxy wrapping the Mastra instance (with tracing context)requestContext— theRequestContextfrom the current requestworkspace— for file/sandbox operationswriter— aToolStreamwrapping the output writeragent/workflow/mcp— nested based on execution source detection
// From builder.ts — simplified
const baseContext = {
mastra: wrappedMastra,
requestContext: execOptions.requestContext ?? options.requestContext ?? new RequestContext(),
workspace: execOptions.workspace ?? options.workspace,
writer: new ToolStream({ prefix: 'tool', callId, name, runId }, outputWriter),
abortSignal: execOptions.abortSignal,
suspend: ...,
resumeData: ...,
};
if (isAgentExecution) {
toolContext = { ...restBaseContext, agent: { toolCallId, messages, suspend, resumeData, threadId, resourceId } };
} else if (isWorkflowExecution) {
toolContext = { ...restBaseContext, workflow: { runId, workflowId, state, setState, suspend, resumeData } };
} else if (execOptions.mcp) {
toolContext = { ...baseContext, mcp: execOptions.mcp };
}
##Using Context in Practice
###Accessing Mastra services (storage, logger, etc.)
const saveTool = createTool({
id: 'save-data',
description: 'Save data to storage',
inputSchema: z.object({ key: z.string(), value: z.any() }),
execute: async (inputData, context) => {
const storage = context?.mastra?.getStorage();
const logger = context?.mastra?.getLogger();
logger?.debug('Saving data...');
await storage?.set(inputData.key, inputData.value);
return { saved: true };
},
});
Source: doc comment in
tool.tslines 57–67.
###Checking if running in agent vs. workflow
execute: async (inputData, context) => {
if (context?.workflow) {
console.log(`Running in workflow ${context.workflow.workflowId}`);
}
if (context?.agent) {
console.log(`Called by agent, toolCallId: ${context.agent.toolCallId}`);
}
return { processed: inputData.data.toUpperCase() };
}
###Using requestContext for per-request values
// In the tool — read values from requestContext
execute: async (inputData, context) => {
const indexName = context?.requestContext?.get('indexName') ?? 'default-index';
const apiKey = context?.requestContext?.get('apiKey');
// ...
}
Source:
packages/rag/src/tools/vector-query.ts— a real-world example.
###Validating requestContext with requestContextSchema
You can define a requestContextSchema on a tool to validate that required request context values are present before execution:
const tool = createTool({
id: 'secure-tool',
description: 'Requires auth context',
requestContextSchema: z.object({
userId: z.string(),
apiKey: z.string(),
}),
execute: async (inputData, context) => {
const { userId, apiKey } = context.requestContext!.all;
// Guaranteed to be valid at this point
return { success: true };
},
});
If the requestContext doesn't satisfy the schema, the tool returns a validation error without executing.
Source:
packages/core/src/tools/__tests__/request-context-schema.test.ts.
###Streaming data from tools with writer
execute: async (inputData, context) => {
await context?.writer?.custom({
type: 'data-progress',
data: { status: 'processing', taskName: inputData.taskName },
});
// ... do work ...
return { result: 'done' };
}
Source:
packages/core/src/tools/tool-stream.test.tslines 158–180.
###Suspend/resume (human-in-the-loop)
Tools can also be marked requireApproval: true and use context.agent.suspend() to pause for human input:
const deleteFileTool = createTool({
id: 'delete-file',
description: 'Delete a file',
requireApproval: true,
inputSchema: z.object({ filepath: z.string() }),
suspendSchema: z.object({ reason: z.string() }),
resumeSchema: z.object({ approved: z.boolean() }),
execute: async (inputData, context) => {
if (context?.agent?.resumeData?.approved) {
// User approved, proceed
return { deleted: true };
}
await context?.agent?.suspend({ reason: 'Confirm file deletion' });
},
});
In summary: You create tools with createTool, assign them to agents via the tools config object, and every tool's execute function automatically receives a rich ToolExecutionContext as its second argument — giving it access to mastra services, requestContext, workspace, writer, and context-specific properties for agent/workflow/MCP execution. You never need to wire this up manually.
END OF POST