Mastra allows developers to design state-of-the-art intelligent agents using tools, large language models (LLMs), memory, and event workflows. In this guide, you will build a functional personal assistant targeted at meeting preparation using Mastra and MCP integration.
The main features of the intelligent assistant, which will be created step-by-step, include:
- Using
mastra createToolfor tool creation with Zod schemas. - Integrating MCP servers for efficient agent-to-agent communication.
- Adding Slack and web research services like Exa.ai for real-time interactions.
- Leveraging Mastra's memory systems to make interactions consistent and context-aware.
- Event-based task scheduling for intelligent agent-driven workflows.
Prerequisites for Setting Up Mastra Agents
Before you start building intelligent agents with Mastra, ensure you have the following:
prerequisites
- Installed Node.js (v18+) and npm (latest version) on your machine.
- Installed Mastra CLI using
npm install -g mastra-clior runnpx create-mastra. - Valid API keys for services like Anthropic, cal.com, Slack, and Exa.ai.
- An Ngrok account for secure tunneling to expose your local development server.
- Familiarity with TypeScript, JSON, and npm-based project structures.
Step-by-Step Guide to Bootstrap a Mastra Project
In this section, you'll initialize a new project and set up the basic components.
steps
Create a new Mastra project:
bashnpx create-mastra- Choose a project name (e.g.,
meeting-assistant) and where to initialize the files.
- Choose a project name (e.g.,
Select a model provider during setup (e.g., Anthropic or OpenAI) and provide its API key.
Enable the recommended Mastra tools library for agent skills.
Initialize Git for your project, and install dependencies:
bashnpm installStart the development server:
bashnpm run devMastra Studio should now be available at
http://localhost:4111. The Studio interface lets you manage agents, tools, memory, and workflows.
Configuring the Intelligent Agent
Define the initial properties and purpose of your agent.
steps
Edit your agent configuration file in Mastra (e.g.,
index.ts) to initialize the agent:typescriptimport { createAgent } from "mastra"; const meetingAssistant = createAgent({ id: "meeting-assistant", name: "Meeting Assistant", systemPrompt: ` You are a personal meeting assistant. Your job is to help the user prepare for meetings by researching the people they'll meet and providing concise, actionable briefs. Your responses should be scannable with bullet points. `, model: "anthropic:claude-sonnet-4.5", }); export default meetingAssistant;Define the functionality your agent will serve (summarizing meeting information, facilitating event preparation, and more).
Restart the Mastra server using:
bashnpm run devConfirm the agent appears in Mastra Studio under Agents.
Integrating Slack for Agent Interaction
Enable Slack bot support to interact with your agent via chat.
steps
Install Vercel's Chat SDK and Slack adapter:
bashnpm install @vercel/chat-sdk @vercel/chat-sdk-slackConfigure Ngrok for local testing:
bashngrok http 4111Create a Slack app via the Slack developer console:
- Use a manifest to define bot permissions (
chat:write,app_mentions:read,channels:history, etc.). - Update the bot's event subscription URL with the Ngrok URL (e.g., https://.ngrok.io/webhook/slack).
- Paste this URL into the
Event Subscriptionsection of your Slack app manifest.
- Use a manifest to define bot permissions (
Install your Slack app in a workspace, and copy the Bot Token and Signing Secret into
.env:SLACK_BOT_TOKEN=your-bot-token SLACK_SIGNING_SECRET=your-signing-secretAdd Slack interaction logic to the chatbot:
typescriptimport { createSlackAdapter } from "@vercel/chat-sdk-slack"; import { Chat } from "@vercel/chat-sdk"; const slackAdapter = createSlackAdapter({ token: process.env.SLACK_BOT_TOKEN as string, signingSecret: process.env.SLACK_SIGNING_SECRET as string, }); const bot = new Chat({ adapters: [slackAdapter], }); bot.on("message", async (message) => { if (message.text.includes("prep")) { const response = await meetingAssistant.generate({ prompt: "Please prepare for the meeting." }); await message.reply(response.text); } }); export default bot;Restart the server and test your bot in Slack by mentioning it with a message like:
@Meeting Assistant Prepare for a meetingVerify the bot successfully responds.
Adding Intelligent Research Tools
You'll next add tools to enable your agent to research using the Exa.ai API.
steps
Install the Exa.js library:
bashnpm install exa.jsAdd your
EXA_API_KEYto.env:EXA_API_KEY=your_exa_api_keyCreate a tool using
mastra createTooland Zod for schema validation:typescriptimport { createTool } from "mastra"; import { z } from "zod"; import Exa from "exa.js"; const exa = new Exa(String(process.env.EXA_API_KEY)); const searchWeb = createTool({ id: "search-web", description: "Search the web for information on a person, company, or topic.", inputSchema: z.object({ query: z.string(), numResults: z.number().default(5), }), execute: async (input) => { const results = await exa.searchAndContents(input.query, input.numResults); return results.map((r) => ({ title: r.title, url: r.url, text: r.text, })); }, }); export default searchWeb;Use the tool in your agent's setup:
typescriptimport searchWeb from "./tools/research-tools"; const meetingAssistant = createAgent({ id: "meeting-assistant", name: "Meeting Assistant", tools: [searchWeb], model: "anthropic:claude-sonnet-4.5", });Test the new tool in Slack:
@Meeting Assistant Research information about OpenAIConfirm that your agent responds with meaningful research results using Exa.ai.
Enhancing Agents With Memory Systems
Enable memory to provide contextually-aware responses.
steps
Configure episodic memory to track recent chat history:
typescriptimport { MessageHistoryMemory } from "mastra"; meetingAssistant.memory = new MessageHistoryMemory({ pastMessageCount: 10 });Add working memory for storing user preferences:
typescriptimport { WorkingMemory } from "mastra"; meetingAssistant.workingMemory = new WorkingMemory({ enabled: true, template: ` { "name": "<user's name>", "role": "<user's role>", "company": "<user's company>", "topicsOfInterest": [] } `, });Enable semantic memory with embedding-based recall:
typescriptimport { SemanticMemoryPersistence, FastEmbed } from "mastra"; meetingAssistant.semanticMemory = new SemanticMemoryPersistence({ embedder: new FastEmbed(), options: { topK: 3, messageRange: 2 }, });Test memory features in Slack by asking:
@Meeting Assistant Who have you done research on for me?
Setting Up Event-Based Workflows With a Custom Scheduler
Event-driven workflows can make agents more autonomous by reacting to external triggers like scheduled events or webhooks.
steps
Install Drizzle ORM for database abstraction and SQLite for storage:
bashnpm install drizzle-orm sqliteCreate a new SQLite database with a
scheduled_tasksschema:typescriptimport { drizzle } from "drizzle-orm"; import schema from "./schema"; export const db = drizzle("sqlite.db", { schema });Define a task scheduling system:
typescriptconst TaskHandlers = new Map<string, (payload: any) => Promise<void>>(); export function registerTask(type: string, handler: (payload: any) => Promise<void>) { TaskHandlers.set(type, handler); } export async function scheduleTask({ type, payload, scheduledAt, }: { type: string; payload: any; scheduledAt: string; }) { const task = { type, payload: JSON.stringify(payload), scheduledAt, status: "pending" }; await db.insert("scheduled_tasks").values(task); }Register and process the tasks:
typescriptregisterTask("follow-up", async (payload) => { await bot.postToThread(payload.threadId, payload.message); }); setInterval(async () => { const tasks = await db .selectFrom("scheduled_tasks") .where("status", "=", "pending") .where("scheduledAt", "<=", new Date().toISOString()) .selectAll() .execute(); for (const task of tasks) { const handler = TaskHandlers.get(task.type); if (handler) { await handler(JSON.parse(task.payload)); await db .updateTable("scheduled_tasks") .set({ status: "completed" }) .where("id", "=", task.id) .execute(); } } }, 30000); // every 30 secondsSchedule a follow-up reminder within
cal.comwebhook:typescriptif (event.action === "booking.created") { await scheduleTask({ type: "follow-up", payload: { threadId: threadId, message: "How did your meeting with [attendee name] go?" }, scheduledAt: new Date(event.endTime).toISOString(), }); }Test the functionality by scheduling a meeting, then verifying the follow-up occurs at the expected time.
FAQ
What is mastra createTool?
mastra createTool is a utility in Mastra that allows developers to define custom tools, using JSON input schemas validated with libraries like Zod, which agents can invoke to extend their functionality.
How do I connect MCP to Mastra agents?
To connect MCP to Mastra agents, configure the MCP client in the Mastra setup file by specifying the MCP server URL and authentication parameters. Then, set up inter-agent communication through Mastra's built-in functions for agent collaboration.
Can I use a different LLM provider with Mastra?
Yes. Mastra supports multiple LLM providers, including Anthropic, OpenAI, Groq, Google, and others. You can swap model configurations as needed during the project setup.
How does Mastra handle memory types?
Mastra supports four memory types:
- Episodic Memory: Tracks recent messages in a conversation.
- Working Memory: Keeps a persistent user profile with structured attributes.
- Semantic Memory: Uses embeddings to recall relevant information from old conversations.
- Observational Memory: Condenses and reflects observations over time.
Official reference: Mastra documentation.