Skip to main content
Tools in Mastra are type-safe, reusable functions that extend agent and workflow capabilities. Tools can perform actions, access external systems, and handle complex operations with validation and error handling.

Core Concept

Tools in Mastra:
  • Type-safe: Input/output validation with Zod schemas
  • Composable: Combine tools from multiple sources
  • Context-aware: Access Mastra resources and request context
  • Suspendable: Support async operations requiring approval
  • Observable: Automatic tracing and logging

Tool Sources

Agents compose tools from multiple sources:
  1. Assigned Tools: Explicitly configured tools
  2. Memory Tools: Tools from memory instances (e.g., working memory)
  3. Workspace Tools: File operations and code execution
  4. MCP Tools: Tools from Model Context Protocol servers
  5. Toolsets: Collections of related tools
const agent = new Agent({
  id: 'assistant',
  tools: { calculator },           // Assigned tools
  memory: new Memory({ ... }),     // Memory tools (working memory)
  workspace: workspace,            // Workspace tools
  mcpServers: { github: mcpServer } // MCP tools
});

Creating Tools

Basic Tool

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 or coordinates'),
    units: z.enum(['celsius', 'fahrenheit']).optional().default('celsius')
  }),
  execute: async ({ location, units }) => {
    const weather = await fetchWeather(location, units);
    return {
      temperature: weather.temp,
      condition: weather.condition,
      humidity: weather.humidity
    };
  }
});

Tool with Output Schema

Validate tool outputs:
const calculateTool = createTool({
  id: 'calculate',
  description: 'Perform arithmetic operations',
  inputSchema: z.object({
    operation: z.enum(['add', 'subtract', 'multiply', 'divide']),
    a: z.number(),
    b: z.number()
  }),
  outputSchema: z.object({
    result: z.number(),
    operation: z.string()
  }),
  execute: async ({ operation, a, b }) => {
    let result: number;
    switch (operation) {
      case 'add': result = a + b; break;
      case 'subtract': result = a - b; break;
      case 'multiply': result = a * b; break;
      case 'divide': result = a / b; break;
    }
    return { result, operation };
  }
});

Tool Execution Context

Tools receive an execution context with metadata:
const saveTool = createTool({
  id: 'save-data',
  description: 'Save data to storage',
  inputSchema: z.object({ key: z.string(), value: z.any() }),
  execute: async ({ key, value }, context) => {
    // Access Mastra instance
    const storage = context?.mastra?.getStorage();
    await storage?.set(key, value);
    
    // Access request context
    const userId = context?.requestContext?.get('userId');
    
    // Log activity
    console.log(`User ${userId} saved ${key}`);
    
    return { saved: true, key };
  }
});

Agent Context

When called from an agent:
const tool = createTool({
  id: 'agent-tool',
  execute: async (input, context) => {
    // Agent-specific context
    const { agent } = context;
    if (agent) {
      const { toolCallId, messages, threadId, resourceId } = agent;
      console.log(`Called in thread ${threadId}`);
    }
  }
});

Workflow Context

When called from a workflow:
const tool = createTool({
  id: 'workflow-tool',
  execute: async (input, context) => {
    // Workflow-specific context
    const { workflow } = context;
    if (workflow) {
      const { runId, workflowId, state, setState } = workflow;
      setState({ ...state, lastCall: Date.now() });
    }
  }
});

Request Context Schema

Validate request context values:
const apiTool = createTool({
  id: 'api-call',
  description: 'Call external API',
  inputSchema: z.object({ endpoint: z.string() }),
  requestContextSchema: z.object({
    apiKey: z.string(),
    userId: z.string()
  }),
  execute: async ({ endpoint }, context) => {
    const apiKey = context.requestContext.get('apiKey');
    const response = await fetch(endpoint, {
      headers: { 'Authorization': `Bearer ${apiKey}` }
    });
    return response.json();
  }
});

Tool Approval

Require explicit user approval for sensitive operations:
const deleteTool = createTool({
  id: 'delete-file',
  description: 'Delete a file',
  requireApproval: true,
  inputSchema: z.object({ filepath: z.string() }),
  execute: async ({ filepath }) => {
    await fs.unlink(filepath);
    return { deleted: true, filepath };
  }
});

// When the agent tries to use this tool, execution pauses
// until explicit approval is granted

Suspend and Resume

Tools can suspend execution for async operations:
const approvalTool = createTool({
  id: 'request-approval',
  description: 'Request approval for an action',
  inputSchema: z.object({ action: z.string(), details: z.string() }),
  suspendSchema: z.object({ approvalId: z.string() }),
  resumeSchema: z.object({ approved: z.boolean(), comment: z.string().optional() }),
  execute: async ({ action, details }, context) => {
    // Check if we're resuming
    if (context.agent?.resumeData) {
      const { approved, comment } = context.agent.resumeData;
      return { approved, comment: comment || 'No comment' };
    }
    
    // Create approval request
    const approvalId = await createApprovalRequest(action, details);
    
    // Suspend execution
    await context.agent?.suspend({ approvalId });
    
    // This code won't execute until resumed
    return { approved: false };
  }
});

Output Transformation

Transform tool output before sending to the model:
const searchTool = createTool({
  id: 'search-database',
  description: 'Search database for records',
  execute: async ({ query }) => {
    const results = await db.search(query);
    return {
      results,           // Full data for application
      count: results.length,
      query
    };
  },
  toModelOutput: (output) => {
    // Only send summary to model, not full data
    return {
      count: output.count,
      query: output.query,
      sample: output.results.slice(0, 3)
    };
  }
});

Provider-Specific Options

Configure provider-specific behavior:
const cachedTool = createTool({
  id: 'fetch-docs',
  description: 'Fetch documentation',
  providerOptions: {
    anthropic: {
      cacheControl: { type: 'ephemeral' }
    }
  },
  execute: async () => {
    return await fetchLargeDocs();
  }
});

MCP Tools

Define tools for Model Context Protocol:
const mcpTool = createTool({
  id: 'mcp-tool',
  description: 'MCP-enabled tool',
  mcp: {
    toolType: 'agent',
    annotations: {
      title: 'File Operations',
      readOnlyHint: false,
      destructiveHint: true,
      idempotentHint: false
    },
    _meta: {
      version: '1.0.0',
      author: '[email protected]'
    }
  },
  execute: async (input, context) => {
    // Access MCP context
    if (context.mcp) {
      const { extra, elicitation } = context.mcp;
      
      // Request user input via MCP
      const userInput = await elicitation.sendRequest({
        prompt: 'Confirm deletion?',
        schema: z.object({ confirm: z.boolean() })
      });
      
      if (!userInput.confirm) {
        return { cancelled: true };
      }
    }
    
    // Execute tool logic
  }
});

Toolsets

Group related tools into toolsets:
import { createToolset } from '@mastra/core/tools';

const fileToolset = createToolset({
  id: 'file-operations',
  description: 'Tools for file operations',
  tools: {
    read: createTool({ id: 'read-file', ... }),
    write: createTool({ id: 'write-file', ... }),
    delete: createTool({ id: 'delete-file', ... })
  }
});

const agent = new Agent({
  tools: fileToolset.tools
});

Dynamic Tools

Create tools dynamically based on request context:
const agent = new Agent({
  id: 'api-agent',
  tools: ({ requestContext }) => {
    const apiKey = requestContext.get('apiKey');
    const baseUrl = requestContext.get('baseUrl');
    
    return {
      fetchData: createTool({
        id: 'fetch-data',
        execute: async ({ endpoint }) => {
          return await fetch(`${baseUrl}${endpoint}`, {
            headers: { 'Authorization': `Bearer ${apiKey}` }
          });
        }
      })
    };
  }
});

Integration Tools

Use pre-built integration tools:
import { SlackIntegration } from '@mastra/slack';
import { GithubIntegration } from '@mastra/github';

const slack = new SlackIntegration({ apiKey: process.env.SLACK_API_KEY });
const github = new GithubIntegration({ apiKey: process.env.GITHUB_TOKEN });

const agent = new Agent({
  id: 'integration-agent',
  tools: {
    ...slack.tools,
    ...github.tools
  }
});

// Agent can now use Slack and GitHub tools
const result = await agent.generate(
  'Send a message to #general and create a GitHub issue'
);

Memory Tools

Memory instances provide working memory tools automatically:
import { Memory } from '@mastra/memory';

const agent = new Agent({
  id: 'assistant',
  memory: new Memory({
    name: 'conversation',
    options: {
      workingMemory: {
        enabled: true,
        schema: z.object({
          name: z.string(),
          preferences: z.array(z.string())
        })
      }
    }
  })
});

// Agent automatically has update_working_memory tool
const result = await agent.generate(
  'My name is Alice and I like coffee',
  { threadId: 'user-123' }
);
// Agent updates working memory: { name: 'Alice', preferences: ['coffee'] }

Workspace Tools

Workspace provides file and code execution tools:
import { Workspace } from '@mastra/workspace';

const agent = new Agent({
  id: 'coder',
  workspace: new Workspace({
    filesystem: new LocalFilesystem({ rootPath: './workspace' }),
    sandbox: new E2BSandbox({ apiKey: process.env.E2B_API_KEY })
  })
});

// Agent has file and code execution tools
const result = await agent.generate(
  'Create a Python script that calculates fibonacci numbers and run it'
);

Best Practices

Write descriptions that explain when to use the tool:
// Good
description: 'Get current weather conditions and forecast for a specific city or location'

// Avoid
description: 'Weather'
Always define input schemas with descriptions:
inputSchema: z.object({
  location: z.string().describe('City name or coordinates'),
  units: z.enum(['celsius', 'fahrenheit']).describe('Temperature units')
})
Return meaningful errors instead of throwing:
execute: async ({ endpoint }) => {
  try {
    const response = await fetch(endpoint);
    return response.json();
  } catch (error) {
    return {
      error: true,
      message: `Failed to fetch: ${error.message}`
    };
  }
}
Protect sensitive operations:
const deleteTool = createTool({
  id: 'delete-database',
  requireApproval: true,
  execute: async () => { ... }
});

Build docs developers (and LLMs) love