Skip to Content
DocsWorkflowsAgents and Tools

Agents and Tools

Workflow steps are composable and typically run logic directly within the execute function. However, there are cases where calling an agent or tool is more appropriate. This pattern is especially useful when:

  • Generating natural language responses from user input using an LLM.
  • Abstracting complex or reusable logic into a dedicated tool.
  • Interacting with third-party APIs in a structured or reusable way.

Workflows can use Mastra agents or tools directly as steps, for example: createStep(testAgent) or createStep(testTool).

Agents

To include an agent in a workflow, define it in the usual way, then either add it directly to the workflow using createStep(testAgent) or, invoke it from within a step’s execute function using .generate().

Example Agent

This agent uses OpenAI to generate a fact about a city, country, and timezone.

src/mastra/agents/test-agent.ts
import { openai } from "@ai-sdk/openai"; import { Agent } from "@mastra/core/agent"; export const testAgent = new Agent({ name: "test-agent", description: "Create facts for input city, country and timezone", instructions: ` You are a helpful and factual assistant. You will be given a city, country, and timezone. Return exactly one interesting fact about each of the three values. Your response must follow this exact format: City Fact: <a short paragraph about the city> Country Fact: <a short paragraph about the country> Timezone Fact: <a short paragraph about the timezone> Only return the three facts in plain text, with each label clearly marked. No additional commentary or formatting. Ensure all facts are accurate and sourced from reputable references. `, model: openai("gpt-4o") });

Agents As Step

In this example the cityCoordinatesStep performs a geocoding lookup using the provided city, then returns the resolved city, country, and timezone.

The .map function transforms the object output into a prompt string that can be used by the testAgent.

The step is composed into the workflow using the sequential .then() method, allowing it to receive input from the workflow and return structured output.

src/mastra/workflows/test-workflow.ts
import { testAgent } from "../agents/test-agent"; const cityCoordinatesStep = createStep({ id: "city-step", description: "Gets details about a city", inputSchema: z.object({ city: z.string() }), outputSchema: z.object({ city_name: z.string(), country_name: z.string(), country_timezone: z.string() }), execute: async ({ inputData }) => { const { city } = inputData; const geocodingResponse = await fetch(`https://geocoding-api.open-meteo.com/v1/search?name=${city}`); const geocodingData = await geocodingResponse.json(); const { name, country, timezone } = geocodingData.results[0]; return { city_name: name, country_name: country, country_timezone: timezone }; } }); const cityFactsStep = createStep(testAgent); export const testWorkflow = createWorkflow({ id: "test-workflow", description: 'Test workflow', inputSchema: z.object({ city: z.string() }), outputSchema: z.object({ outcome: z.string() }) }) .then(cityCoordinatesStep) .map(({ inputData }) => { const { city_name, country_name, country_timezone } = inputData; return { prompt: ` Provide facts about the city: ${city_name}, country: ${country_name} and timezone: ${country_timezone} ` }; }) .then(cityFactsStep) .commit();

Agent Generate

In this example, the cityFactsStep performs a geocoding lookup using the provided city, then builds a prompt combining the resolved city, country, and timezone.

This prompt is passed to the testAgent, which generates a plain-text response with one fact for each.

The step is composed into the workflow using the sequential .then() method, allowing it to receive input from the workflow and return structured output.

src/mastra/workflows/test-workflow.ts
import { testAgent } from "../agents/test-agent"; const cityFactsStep = createStep({ id: "city-facts-step", description: "Returns facts about city, country and timezone from the agent", inputSchema: z.object({ city: z.string() }), outputSchema: z.object({ outcome: z.string() }), execute: async ({ inputData }) => { const { city } = inputData; const geocodingResponse = await fetch(`https://geocoding-api.open-meteo.com/v1/search?name=${city}`); const geocodingData = await geocodingResponse.json(); const { name, country, timezone } = geocodingData.results[0]; const prompt = ` Provide facts about the city: ${city_name}, country: ${country_name} and timezone: ${country_timezone} `; const { text } = await testAgent.generate([ { role: "user", content: prompt } ]); return { outcome: text }; } }); export const testWorkflow = createWorkflow({ id: "test-workflow", description: 'Test workflow', inputSchema: z.object({ city: z.string() }), outputSchema: z.object({ outcome: z.string() }) }) .then(cityFactsStep) .commit();

Tools

To use a tool within a workflow, define it in the usual way, then either add it directly to the workflow using createStep(testTool) or, invoke it from within a step’s execute function using .execute().

Example Tool

The example below uses the Open Meteo API to retrieve geolocation details for a city, returning its name, country, and timezone.

src/mastra/tools/test-tool.ts
import { createTool } from "@mastra/core"; import { z } from "zod"; export const testTool = createTool({ id: "city-tool", description: "Gets coordinates for city", inputSchema: z.object({ city: z.string() }), outputSchema: z.object({ city_name: z.string(), country_name: z.string(), country_timezone: z.string() }), execute: async ({ context }) => { const { city } = context; const geocodingResponse = await fetch(`https://geocoding-api.open-meteo.com/v1/search?name=${city}`); const geocodingData = await geocodingResponse.json(); const { name, country, timezone } = geocodingData.results[0]; return { city_name: name, country_name: country, country_timezone: timezone }; } });

Tools As Step

In this example, the cityCoordinatesStep uses the above example testTool which performs a geocoding lookup using the provided city, then returns the resolved city, country, and timezone.

The .map function transforms the object output into a prompt string that can be used by the testAgent.

The step is composed into the workflow using the sequential .then() method, allowing it to receive input from the workflow and return structured output.

src/mastra/workflows/test-workflow.ts
import { testAgent } from "../agents/test-agent"; import { testTool } from "../tools/test-tool"; const cityCoordinatesStep = createStep(testTool); const cityFactsStep = createStep({ id: "city-facts-step", description: "Returns facts about city, country and timezone from the agent", inputSchema: z.object({ prompt: z.string() }), outputSchema: z.object({ outcome: z.string() }), execute: async ({ inputData }) => { const { prompt } = inputData; const { text } = await testAgent.generate([ { role: "user", content: prompt } ]); return { outcome: text }; } }); export const testWorkflow = createWorkflow({ id: "test-workflow", description: "Test workflow", inputSchema: z.object({ city: z.string() }), outputSchema: z.object({ outcome: z.string() }) }) .then(cityCoordinatesStep) .map(({ inputData }) => { const { city_name, country_name, country_timezone } = inputData; return { prompt: ` Provide facts about the city: ${city_name}, country: ${country_name} and timezone: ${country_timezone} ` }; }) .then(cityFactsStep) .commit();

Tool Execute

In this example, the cityFactsStep uses the above example testTool which performs a geocoding lookup using the provided city, then builds a prompt combining the resolved city, country, and timezone.

This prompt is passed to the testAgent, which generates a plain-text response with one fact for each.

The step is composed into the workflow using the sequential .then() method, allowing it to receive input from the workflow and return structured output.

src/mastra/workflows/test-workflow.ts
import { RuntimeContext } from "@mastra/core/di"; import { testAgent } from "../agents/test-agent"; import { testTool } from "../tools/test-tool"; const runtimeContext = new RuntimeContext(); const cityFactsStep = createStep({ id: "city-facts-step", description: "Returns facts about city, country and timezone from the agent", inputSchema: z.object({ city: z.string() }), outputSchema: z.object({ outcome: z.string() }), execute: async ({ inputData }) => { const { city } = inputData; const { city_name, country_name, country_timezone } = await testTool.execute({ context: { city }, runtimeContext }); const prompt = ` Provide facts about the city: ${city_name}, country: ${country_name} and timezone: ${country_timezone} `; const { text } = await testAgent.generate([ { role: "user", content: prompt } ]); return { outcome: text }; } }); export const testWorkflow = createWorkflow({ id: "test-workflow", description: 'Test workflow', inputSchema: z.object({ city: z.string() }), outputSchema: z.object({ outcome: z.string() }) }) .then(cityFactsStep) .commit();

Workflow As Tool

In this example the cityStringWorkflow workflow has been added to the main Mastra instance.

src/mastra/index.ts
import { Mastra } from "@mastra/core/mastra"; import { testWorkflow, cityStringWorkflow } from "./workflows/test-workflow"; export const mastra = new Mastra({ ... workflows: { testWorkflow, cityStringWorkflow }, });

Once a workflow has been registered it can be referenced using getWorkflow from withing a tool.

src/mastra/tools/test-tool.ts
export const cityCoordinatesTool = createTool({ id: "city-tool", description: "Convert city details", inputSchema: z.object({ city: z.string() }), outputSchema: z.object({ outcome: z.string() }), execute: async ({ context, mastra }) => { const { city } = context; const geocodingResponse = await fetch(`https://geocoding-api.open-meteo.com/v1/search?name=${city}`); const geocodingData = await geocodingResponse.json(); const { name, country, timezone } = geocodingData.results[0]; const workflow = mastra?.getWorkflow("cityStringWorkflow"); const run = workflow?.createRun(); const { result } = await run?.start({ inputData: { city_name: name, country_name: country, country_timezone: timezone } }); return { outcome: result.outcome }; } });

MCP Server

You can convert your workflows into tools by passing them into an instance of a Mastra MCPServer. This allows any MCP-compatible client to access your workflow.

The workflow description becomes the tool description and the input schema becomes the tool’s input schema.

When you provide workflows to the server, each workflow is automatically exposed as a callable tool for example:

  • run_testWorkflow.
src/test-mcp-server.ts
import { MCPServer } from "@mastra/mcp"; import { testAgent } from "./mastra/agents/test-agent"; import { testTool } from "./mastra/tools/test-tool"; import { testWorkflow } from "./mastra/workflows/test-workflow"; async function startServer() { const server = new MCPServer({ name: "test-mcp-server", version: "1.0.0", workflows: { testWorkflow } }); await server.startStdio(); console.log("MCPServer started on stdio"); } startServer().catch(console.error);

To verify that your workflow is available on the server, you can connect with an MCPClient.

src/test-mcp-client.ts
import { MCPClient } from "@mastra/mcp"; async function main() { const mcp = new MCPClient({ servers: { local: { command: "npx", args: ["tsx", "src/test-mcp-server.ts"] } } }); const tools = await mcp.getTools(); console.log(tools); } main().catch(console.error);

Run the client script to see your workflow tool.

npx tsx src/test-mcp-client.ts

More Resources