The TypeScript AI agent space has been dominated by Python frameworks for a while now. LangChain, LangGraph, CrewAI — they all have TypeScript ports, but they’re ports. You can feel the seams. Mastra is different: it was built TypeScript-first, and it shows in how the abstractions feel.
Here’s the thing — most JavaScript developers building LLM-powered applications aren’t coming from a Python background. They want typed APIs, first-class async patterns, and integrations that feel native to the Node.js ecosystem. Mastra is built around that expectation.
What Mastra Actually Is
Mastra is an open-source TypeScript framework for building AI agents, workflows, and RAG pipelines. It’s model-agnostic — works with OpenAI, Anthropic, Google Gemini, and local models via Ollama — and provides the scaffolding that makes production agent development manageable rather than chaotic.
The core building blocks:
Agents are the familiar construct: an LLM with access to tools, memory, and instructions. What Mastra adds is strong typing on tool inputs and outputs via Zod schemas, so your agent’s tool calls are validated at runtime rather than failing silently when the LLM returns a malformed JSON payload.
Workflows are deterministic execution graphs — sequential or branching logic that chains together agents, API calls, and data transformations. This is where Mastra distinguishes itself from pure agent frameworks. Sometimes you don’t want an LLM deciding the next step; you want structured execution with LLM calls at specific points.
Syncs are background processes that keep your RAG knowledge base up to date by ingesting data from external sources on a schedule.
Getting Started
npm create mastra@latest my-agent-app
cd my-agent-app
npm install
A minimal agent with a tool:
import { Mastra, createTool } from "@mastra/core";
import { z } from "zod";
const weatherTool = createTool({
id: "get-weather",
description: "Get current weather for a city",
inputSchema: z.object({
city: z.string().describe("City name"),
}),
outputSchema: z.object({
temperature: z.number(),
condition: z.string(),
}),
execute: async ({ context }) => {
// Your weather API call here
return { temperature: 18, condition: "overcast" };
},
});
const mastra = new Mastra({
agents: {
weatherAgent: {
name: "Weather Agent",
instructions: "You help users understand current weather conditions.",
model: { provider: "ANTHROPIC", name: "claude-sonnet-4-6" },
tools: { weatherTool },
},
},
});
const agent = mastra.getAgent("weatherAgent");
const response = await agent.generate("What's the weather like in London?");
console.log(response.text);
The Zod schemas on inputSchema and outputSchema do real work here — they validate what gets passed to your tool and what it returns, and they’re used to generate the JSON schema that gets sent to the LLM so it knows what format to use when calling the tool.
Workflows for Deterministic Logic
Where agents let the LLM decide the next action, workflows give you explicit control. This is useful for anything that has a defined structure but needs LLM capability at specific steps — content pipelines, data enrichment, customer onboarding flows.
import { createWorkflow, createStep } from "@mastra/core/workflows";
const enrichContactStep = createStep({
id: "enrich-contact",
inputSchema: z.object({ email: z.string().email() }),
outputSchema: z.object({ name: z.string(), company: z.string() }),
execute: async ({ inputData }) => {
// Call enrichment API
return { name: "Jane Smith", company: "Acme Ltd" };
},
});
const draftemailStep = createStep({
id: "draft-email",
inputSchema: z.object({ name: z.string(), company: z.string() }),
outputSchema: z.object({ subject: z.string(), body: z.string() }),
execute: async ({ inputData, mastra }) => {
const agent = mastra?.getAgent("emailAgent");
const result = await agent?.generate(
`Draft a personalised intro email for ${inputData.name} at ${inputData.company}`
);
// Parse subject and body from result
return { subject: "Hello from us", body: result?.text ?? "" };
},
});
const contactWorkflow = createWorkflow({
id: "contact-outreach",
inputSchema: z.object({ email: z.string().email() }),
})
.then(enrichContactStep)
.then(draftemailStep)
.commit();
The .then() chaining passes the output of each step as the input to the next. Branches and parallel execution are also supported — you can fan out to multiple steps and wait for all of them before continuing.
Built-in Observability
One of the practical advantages Mastra has over rolling your own agent setup is built-in tracing. Every agent call, tool invocation, and workflow step is traced via OpenTelemetry, and Mastra ships integrations with Langfuse, Braintrust, and other observability platforms.
You configure it once:
const mastra = new Mastra({
telemetry: {
serviceName: "my-agent-app",
enabled: true,
export: {
type: "otlp",
endpoint: "https://your-langfuse-endpoint",
},
},
// ... rest of config
});
And every interaction is traced without any additional instrumentation. In production, this is the difference between debugging blind and actually understanding why your agent made a particular decision.
Memory and RAG
Mastra handles memory with a pluggable storage layer — PostgreSQL (via @mastra/pg), LibSQL, or an in-memory store for development. Agents can read from and write to memory between sessions without you managing the storage logic.
For RAG, Mastra provides MastraVector — a wrapper around popular vector databases (Pinecone, Qdrant, pgvector) with a unified interface for upsert and similarity search. You can attach a vector store to an agent and it’ll automatically query it as part of tool retrieval.
Is It Worth Switching?
If you’re already deep in a Python-based agent stack that’s working, switching frameworks is rarely worth it. But for TypeScript teams starting a new agent project — or migrating from a Python port that never felt quite right — Mastra is worth a serious look. The typed tool contracts alone will save you a meaningful amount of debugging time, and the workflow abstraction handles the cases where you want deterministic structure rather than pure agent autonomy.
The project is actively maintained, has a growing set of third-party integrations (including an official MCP client), and the documentation has improved considerably over the past six months. It’s past the “interesting experiment” stage.