← All posts

Mastra AI: A Practical Guide to the TypeScript Agent Framework

mastra ai guide

Quick answer: Mastra is an open-source TypeScript framework for building AI agents, created by the team behind Gatsby. It gives you agents, typed tools, durable workflows, and memory as first-class primitives, with a local dev playground and sane deployment story. If you live in the TypeScript ecosystem and want structure instead of a pile of ad-hoc LLM calls, it is one of the strongest options right now.

I have watched the JavaScript agent ecosystem go from "just call the OpenAI SDK in a loop" to a real set of frameworks, and Mastra is the one I keep recommending to TypeScript teams. Here is what it actually is, where it shines, and how to wire it up to the live web.

What Mastra Actually Is

Mastra is an agent framework, not a model provider. You bring your own LLM (it plugs into the standard model provider SDKs, so OpenAI, Anthropic, Google, and friends all work), and Mastra gives you the scaffolding around it:

  • Agents: an LLM plus instructions plus a set of tools it can call.
  • Tools: typed functions the agent can invoke, with schemas for inputs and outputs.
  • Workflows: explicit, durable, step-based graphs for when you want control flow you can reason about instead of "hope the agent figures it out."
  • Memory: conversation and working memory so agents remember things across turns.
  • RAG and evals: built-in support for document pipelines and for scoring agent output.

The pitch, in one line: it brings the structure that backend frameworks brought to web servers into agent development. Whether that pitch lands depends on whether you have felt the pain of maintaining a hand-rolled agent loop in production. I have, and I am sympathetic.

The Core Concepts, Quickly

The mental model is small, which I appreciate:

Primitive What it is When you reach for it
Agent LLM + instructions + tools Open-ended tasks, chat, tool use
Tool Typed function with a schema Anything the model cannot do alone
Workflow Explicit step graph Multi-step processes you must control
Memory Persisted context Conversations, long-running assistants

The agent versus workflow split is the design decision I like most. Agents are for tasks where the model should decide what to do next. Workflows are for processes where you decide, and the model fills in the steps. Most real products need both, and frameworks that only give you one tend to get abused.

A Minimal Mastra Agent

A basic agent looks roughly like this (check the current Mastra docs for exact imports, the API has evolved across versions):

import { Agent } from "@mastra/core/agent";
import { openai } from "@ai-sdk/openai";

export const supportAgent = new Agent({
  name: "support-agent",
  instructions:
    "You answer questions about our product. Be concise and honest. " +
    "If you do not know, say so.",
  model: openai("gpt-4o"),
});

const result = await supportAgent.generate(
  "What plans do we offer?"
);
console.log(result.text);

That runs, but it is also useless for anything requiring current information. The model only knows its training data. Which brings us to the part that actually matters.

Giving a Mastra Agent Web Access

Out of the box, an agent cannot fetch a URL or search the web. Tools fix that, and this is exactly the shape of problem link.sc exists for: one API that fetches any URL as clean markdown and runs web searches that return full-page content, so your tool code stays tiny.

Mastra tools are typed functions with schemas. Here is a web search tool backed by link.sc:

import { createTool } from "@mastra/core/tools";
import { z } from "zod";

export const webSearch = createTool({
  id: "web-search",
  description:
    "Search the web and return full-page content for the top results. " +
    "Use for anything involving current events or facts you are unsure of.",
  inputSchema: z.object({
    query: z.string().describe("The search query"),
  }),
  execute: async ({ context }) => {
    const res = await fetch("https://link.sc/v1/search", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.LINKSC_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ query: context.query, format: "markdown" }),
    });
    return await res.json();
  },
});

And a fetch tool for when the agent already has a URL:

export const fetchPage = createTool({
  id: "fetch-page",
  description: "Fetch a URL and return its content as clean markdown.",
  inputSchema: z.object({ url: z.string().url() }),
  execute: async ({ context }) => {
    const res = await fetch("https://link.sc/v1/fetch", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.LINKSC_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ url: context.url, format: "markdown" }),
    });
    const data = await res.json();
    return data.markdown;
  },
});

Attach both to the agent and it can now research, verify, and cite live sources:

export const researchAgent = new Agent({
  name: "research-agent",
  instructions:
    "Research questions using web search and page fetches. " +
    "Always cite the URLs you used.",
  model: openai("gpt-4o"),
  tools: { webSearch, fetchPage },
});

The reason I back these tools with an API instead of raw fetch plus an HTML parser: real websites fight you. JavaScript rendering, bot detection, consent walls, markup soup. link.sc handles that per-domain and hands back markdown the model can actually use. I wrote about the general pattern in giving your AI agent internet access, and the same logic applies here. If you would rather connect through MCP instead of custom tools, link.sc also runs an MCP server, which I covered in what MCP is and why it matters.

Honest Strengths and Limits

What I genuinely like about Mastra:

  • TypeScript-native. Schemas, inference, and editor support all work the way a TS developer expects. This matters more than it sounds; most agent tooling is Python-first and the JS ports feel like ports.
  • Workflows are real. Durable, resumable, explicit steps. Most "agent frameworks" have nothing here.
  • The dev experience. The local playground for poking at agents and tools before deploying is the kind of thing you do not appreciate until you have debugged an agent through console logs.

Where I would push back:

  • It is young. APIs have shifted between versions, and some abstractions are still settling. Pin your versions and expect to read changelogs.
  • Docs assume the happy path. The moment you do something unusual you are reading source. To be fair, that is true of nearly every framework in this space.
  • It will not save a bad agent design. If your task decomposition is wrong, Mastra gives you a well-typed wrong system. The framework is scaffolding, not judgment.

Should You Use It?

If your team writes TypeScript and you are building agents that need tools, memory, or multi-step workflows, yes, Mastra is a sensible default and better than rolling your own loop. If you are a Python shop, look at the Python-side frameworks instead; fighting your ecosystem is never worth it.

Whatever framework you pick, the pattern that decides whether your agent is useful stays the same: give it reliable web access through a tool, keep the tool thin, and let a dedicated API absorb the ugliness of the real web. The link.sc free tier is 500 credits a month, which is plenty to build and test with; details on the pricing page.


Building a Mastra agent that needs live web data? link.sc gives it search and fetch in about ten lines of tool code. Try it free.