Skip to main content

What Is Mastra?

Mastra is an open-source TypeScript framework for building AI agents and workflows. It gives developers everything needed to ship production-grade AI features: a model router with 3,000+ models, agents with memory and tool use, multi-step workflows with branching logic, RAG pipelines, evals, and observability, all in one framework. It integrates with Next.js, React, Express, SvelteKit, Hono, and any other modern TypeScript stack. Unlike no-code or desktop agent tools, Mastra agents are code. They live in your codebase, deploy with your app, and connect to your existing infrastructure.

Why Connect ZenRows to Mastra?

  • Ship agents that access any site: Mastra agents call tools to do their work. Without ZenRows, any page protected by Cloudflare, DataDome, or similar systems returns a blocked or empty response. Add ZenRows and your agents bypass the blocks to fetch the actual content every time.
  • Structured output, ready for your app: ZenRows returns clean Markdown or JSON instead of raw HTML. Mastra agents receive structured, LLM-friendly data, resulting in less post-processing, fewer tokens, and cleaner output to pass downstream.
  • Give any model on Mastra’s router real-time web access: Mastra supports 3,000+ models from OpenAI, Anthropic, Google, Groq, Mistral, and more. ZenRows MCP enables real-time web access regardless of which model your agent uses.
  • Multi-agent workflows with live web data: Mastra supports supervisor agents that coordinate subagents. Each subagent can call ZenRows independently, and a coordinator synthesizes the results.
  • Ship web data features into your product without building a scraping layer: With ZenRows MCP, your agent calls a tool and gets structured content back. No scraping infrastructure to maintain and no browser pool to spin up alongside your app.
  • Keep your RAG pipelines current: Connect ZenRows with Mastra and your RAG ingestion workflow can pull live content from any source on demand, including pages behind bot protection and heavy JavaScript rendering that a plain HTTP fetch would never reach.

What You Can Build with Mastra and ZenRows

  • A competitive pricing monitor shipped inside your product: Build an agent your users can query that scrapes live pricing across sites via ZenRows and returns a structured answer. Deploy it as part of your Next.js or Express app using Mastra’s server adapters.
  • An e-commerce research workflow: Chain a multi-step Mastra workflow to scrape a product category page, extract product names and prices, enrich with reviews from a second page, run a structured-output step to rank by value, and return a clean JSON response to your frontend.
  • A sales intelligence agent: Build an internal copilot that researches a prospect before a call, scraping their company site, recent news, and job postings via ZenRows, and returns a structured brief via your Slack channel or internal tool.
  • Content automation for a CMS: Set up a Mastra workflow that scrapes a set of sources on a schedule, extracts key content, runs it through a summarization or transformation step, and writes structured output directly to your CMS or knowledge base.
  • A data analysis agent with live market data: Extend Mastra’s text-to-SQL and data analysis agent template with ZenRows to pull live market data, competitor pricing, or industry benchmarks from the web, then feed it into the analysis pipeline alongside your internal database.

Prerequisites

  • Node.js 18+ installed.
  • A Mastra project scaffolded via npm create mastra@latest, which creates the full project structure (src/mastra/agents, tools, workflows, index.ts) that you’ll build on top of.
  • An Anthropic API key (or your preferred provider).
  • A ZenRows API key from the ZenRows Playground.
If you haven’t created a project yet, run:
npm create mastra@latest
The setup wizard will ask for your project name and LLM provider. Choose Anthropic to follow this guide. It will prompt you to enter your Anthropic API key during setup and automatically write it to your .env file as ANTHROPIC_API_KEY.

Setup

1

Install the required packages

Install @mastra/mcp and the Anthropic AI SDK provider:
npm install @mastra/mcp@latest @ai-sdk/anthropic
@mastra/mcp enables MCP client support in your Mastra project. @ai-sdk/anthropic is the Anthropic provider for the AI SDK, enabling your agent to connect directly to Claude without going through Mastra’s model router.
2

Configure ZenRows as an MCP server

Create an MCPClient that points to the ZenRows MCP server.Create src/mastra/mcp/zenrows-client.ts:
TypeScript
// npm install @mastra/mcp@latest
import { MCPClient } from '@mastra/mcp'

// Connect to the MCP server using the MCPClient class
export const zenrowsClient = new MCPClient({
  id: 'zenrows-mcp-client',
  servers: {
    zenrows: {
      command: 'npx',
      args: ['-y', '@zenrows/mcp'],
      env: {
        ZENROWS_API_KEY: process.env.ZENROWS_API_KEY!,
      },
    },
  },
})
Ensure both API keys are in your .env file. ANTHROPIC_API_KEY should already be there from the project setup wizard. Add ZENROWS_API_KEY alongside it:
ANTHROPIC_API_KEY=YOUR_ANTHROPIC_API_KEY
ZENROWS_API_KEY=YOUR_ZENROWS_API_KEY
Alternatively, connect via the remote HTTP server instead:
TypeScript
export const zenrowsClient = new MCPClient({
  id: 'zenrows-mcp-client',
  servers: {
    zenrows: {
      url: new URL('https://mcp.zenrows.com/mcp'),
      requestInit: {
        headers: {
          Authorization: `Bearer ${process.env.ZENROWS_API_KEY}`,
        },
      },
    },
  },
})
3

Add ZenRows tools to your agent

Create a product research agent powered by Claude Sonnet that scrapes live product listings via ZenRows and returns structured results. Import the client and pass its tools to your agent using listTools().Create src/mastra/agents/research-agent.ts:
TypeScript
// npm install @mastra/mcp@latest @ai-sdk/anthropic
import { Agent } from '@mastra/core/agent'
import { anthropic } from '@ai-sdk/anthropic'
import { zenrowsClient } from '../mcp/zenrows-client.js'

export const researchAgent = new Agent({
  id: 'research-agent',
  name: 'Research Agent',
  instructions: `You are a web research assistant. Use ZenRows tools to
    scrape pages and extract structured data. Always return clean,
    structured results.`,
  model: anthropic('claude-sonnet-4-6'),
  tools: await zenrowsClient.listTools(),
})
listTools() loads all ZenRows tools into the agent. If you only need a subset of tools, load them first and filter to keep the prompt lighter:
TypeScript
// npm install @mastra/mcp@latest @ai-sdk/anthropic
import { Agent } from '@mastra/core/agent'
import { anthropic } from '@ai-sdk/anthropic'
import { zenrowsClient } from '../mcp/zenrows-client.js'

// Fetch the list of tools from the ZenRows MCP server
const allTools = await zenrowsClient.listTools()

export const researchAgent = new Agent({
  id: 'research-agent',
  name: 'Research Agent',
  instructions: `You are a web research assistant. Use ZenRows tools to
    scrape pages and extract structured data. Always return clean,
    structured results.`,
  model: anthropic('claude-sonnet-4-6'),
  // Only include zenrows_scrape if it's available
  tools: allTools['zenrows_scrape'] ? { zenrows_scrape: allTools['zenrows_scrape'] } : {},
})
Register the agent in your Mastra instance. src/mastra/index.ts already exists from the project scaffold. Add researchAgent to the imports and include it in the agents object:
TypeScript
import { researchAgent } from './agents/research-agent.js'

// Add researchAgent to your existing Mastra instance
export const mastra = new Mastra({
  agents: { researchAgent },
})
4

Run your agent

Start the Mastra dev server:
npm run dev
Test via Swagger UIOpen http://localhost:4111/swagger-ui in your browser to see all your registered agents as callable API endpoints.Go to http://localhost:4111/agents, select research-agent, and you’ll see all available ZenRows MCP tools connected to your agent in the right sidebar.Fill in a scraping prompt and send it:
Scrape https://www.scrapingcourse.com/antibot-challenge and summarize the page.
Sample response:
I'll scrape that page for you and provide a summary.

[zenrows_scrape]

Summary

The page at https://www.scrapingcourse.com/antibot-challenge is a test page designed to
challenge web scrapers with anti-bot protection.

Key Content:
- Site: Scraping Course (scrapingcourse.com)
- Page Title: "Antibot Challenge"
- Main Message: "You bypassed the Antibot challenge! :D"

This appears to be a training/testing page that implements anti-bot measures.
[Truncated for brevity]
ZenRows is connected as an MCPClient, not an MCPServer. MCPClient connects your Mastra agent to an external MCP server (in this case, ZenRows) to consume its tools. MCPServer does the opposite: it exposes your Mastra agents and tools to other MCP-compatible clients. Use MCPClient here because you want Mastra to consume ZenRows tools, not the other way around.
Test in codeYou can also call the agent directly from a script. Create src/scripts/test-agent.ts:
TypeScript
// Import .env config and the mastra instance from the index file
import 'dotenv/config'
import { mastra } from '../mastra/index.js'

// Get the research agent from the mastra instance
const agent = mastra.getAgentById('research-agent')

// Generate a response using a research query
const response = await agent.generate(
  'Get the top 5 deals on https://www.amazon.com/s?k=headset based on product price and rating.'
)

console.log(response.text)
Run it from your terminal:
npx tsx src/scripts/test-agent.ts
Expose as a custom API endpointOnce you’re happy with the agent, expose it as a proper HTTP endpoint so any frontend, Slack bot, or external service can call it. registerApiRoute is a helper from @mastra/core/server that lets you define custom HTTP routes on Mastra’s built-in server.Update src/mastra/index.ts:
TypeScript
import { researchAgent } from './agents/research-agent.js'
import { registerApiRoute } from '@mastra/core/server'

export const mastra = new Mastra({
  agents: { researchAgent },
  server: {
    apiRoutes: [
      registerApiRoute('/research', {
        method: 'POST',
        handler: async (c: any) => {
          const mastra = c.get('mastra')
          const agent = mastra.getAgentById('research-agent')
          const { prompt } = await c.req.json()
          const response = await agent.generate(prompt)
          return c.json({ result: response.text })
        },
      }),
    ],
  },
})
With the dev server running, your agent is now callable at:
curl -X POST http://localhost:4111/research \
  -H "Content-Type: application/json" \
  -d "{\"prompt\": \"Scrape the top headlines from news.ycombinator.com and summarize the top 3 stories in one paragraph.\"}"

Dynamic Tools for Multi-Tenant Apps

If you’re building a SaaS app where each user provides their own ZenRows API key, use listToolsets() instead of listTools(). This lets you pass per-request credentials without reinitializing the agent. In this pattern, zenrows-client.ts is not used. Instead, a new MCPClient is created per request using the user’s API key and discarded after the response is received. This logic lives in your request handler inside a registerApiRoute handler in index.ts. Add the import at the top of index.ts:
TypeScript
import { MCPClient } from '@mastra/mcp'
Then add a new route inside server.apiRoutes:
TypeScript
registerApiRoute('/research-user', {
  method: 'POST',
  handler: async (c: any) => {
    const { prompt, apiKey } = await c.req.json()
    const userMcp = new MCPClient({
      servers: {
        zenrows: {
          url: new URL('https://mcp.zenrows.com/mcp'),
          requestInit: {
            headers: {
              Authorization: `Bearer ${apiKey}`,
            },
          },
        },
      },
    })
    const mastra = c.get('mastra')
    const agent = mastra.getAgentById('research-agent')
    const response = await agent.generate(prompt, {
      toolsets: await userMcp.listToolsets(),
    })
    await userMcp.disconnect()
    return c.json({ result: response.text })
  },
}),
With the dev server running, test the endpoint by passing the prompt and the user’s ZenRows API key in the request body:
curl -X POST http://localhost:4111/research-user \
  -H "Content-Type: application/json" \
  -d "{\"prompt\": \"Scrape https://www.scrapingcourse.com/antibot-challenge and summarize the page.\", \"apiKey\": \"YOUR_ZENROWS_API_KEY\"}"
In this pattern, ZENROWS_API_KEY doesn’t need to be set in your .env. Each request supplies its own key at runtime, so there is no shared key on the server.

Troubleshooting

This Site Can’t Be Reached or localhost:4111 Unreachable

The Mastra dev server isn’t running. Start it with:
npm run dev
If the server starts but the port is still unreachable, check that no other process is using port 4111. You can also confirm the server is up by checking the Mastra startup log in your terminal before making any requests.

500 Internal Server Error on Custom Routes

Check that you’re using mastra.getAgentById('research-agent') and not mastra.getAgent('research-agent') in your route handlers. The correct method is getAgentById. Using getAgent will cause a 500 with no clear error message in the response.

Could Not Find Config for Provider Anthropic Error

This occurs when using Mastra’s model router string format (anthropic/claude-sonnet-4-6) without Mastra’s gateway configured. You may also see a follow-up error such as "model: anthropic/claude-sonnet-4-6 not found" if the router tries to pass the full string directly to the Anthropic API. The fix is to use Anthropic’s AI SDK provider directly instead of the router string:
npm install @ai-sdk/anthropic
For other providers, the pattern is the same with a different package. For example, @ai-sdk/openai for OpenAI and @ai-sdk/google for Google. See the AI SDK providers docs for the full list. When using the AI SDK directly, pass only the model ID without the anthropic/ prefix:
TypeScript
import { anthropic } from '@ai-sdk/anthropic'

// Do not use 'anthropic/claude-sonnet-4-6'
// Use this instead:
model: anthropic('claude-sonnet-4-6'),

Prompt Is Too Long Error

Mastra injects all registered tool schemas into the prompt on every request. When combined with page content returned by ZenRows, this can push the total token count past the model’s context limit. Option 1: Load tools first and select only the ones you need, such as zenrows_scrape:
TypeScript
const allTools = await zenrowsClient.listTools()

export const researchAgent = new Agent({
  id: 'research-agent',
  name: 'Research Agent',
  instructions: `You are a web research assistant. Use ZenRows tools to
    scrape pages and extract structured data. Always return clean,
    structured results.`,
  model: anthropic('claude-sonnet-4-6'),
  tools: allTools['zenrows_scrape'] ? { zenrows_scrape: allTools['zenrows_scrape'] } : {},
})
Option 2: Switch to a model with a larger context window. Claude Opus 4.6 supports up to 1M tokens. Update the model field in research-agent.ts:
model: anthropic('claude-opus-4-6'),

Cannot Find Name ‘process’ or Relative Import Path Errors

Add "types": ["node"] to your tsconfig.json compiler options:
{
  "compilerOptions": {
    "types": ["node"]
  }
}

ZenRows Tools Not Appearing on the Agent

  • Confirm @mastra/mcp is installed: npm list @mastra/mcp.
  • Check that ZENROWS_API_KEY is set in your .env and that dotenv is loading it.
  • When running scripts directly with npx tsx, add import 'dotenv/config' at the top of the file. The dev server loads .env automatically, but standalone scripts don’t.
  • Make sure await zenrowsClient.listTools() is called at the module level in research-agent.ts, outside the Agent constructor. The await is required and must be at the top level of the file.

Authentication Error from ZenRows

Verify your API key on the ZenRows dashboard. If using the HTTP connection option, ensure the Authorization header is set to Bearer YOUR_ZENROWS_API_KEY with no extra whitespace.

Workflow Step Not Receiving ZenRows Output

Check the outputSchema of your scrape step matches what the next step expects. Use Mastra Studio at http://localhost:4111 to inspect step inputs and outputs visually.

Further Reading

Frequently Asked Questions

Yes. ZenRows MCP works regardless of which model you configure on your agent. That said, Claude Sonnet and Opus handle MCP tool call sequences most reliably. Mastra’s model router gives you access to thousands of models across 100+ providers. See the full list at the Mastra Providers page. You can switch models without changing anything else in your agent code.
Yes. Use listToolsets() instead of listTools() and create a new MCPClient per request with the user’s key. In this case, you don’t need to set ZENROWS_API_KEY in your .env. Each request is self-contained. See the Dynamic Tools for Multi-Tenant Apps section for the full implementation.
Use listTools() for single-user or internal tools where the API key is shared. Use listToolsets() for multi-tenant SaaS apps where each user provides their own credentials, avoiding the need to reinitialize the agent per request.
Yes. Set requireToolApproval: true on the ZenRows server block in your MCPClient config:
TypeScript
export const zenrowsClient = new MCPClient({
    servers: {
        zenrows: {
            command: 'npx',
            args: ['-y', '@zenrows/mcp'],
            env: {
            ZENROWS_API_KEY: process.env.ZENROWS_API_KEY!,
            },
            requireToolApproval: true,
        },
    },
})
This integrates with Mastra’s human-in-the-loop approval flow.
Yes. Add multiple servers to the same MCPClient:
TypeScript
export const mcpClient = new MCPClient({
    id: 'multi-mcp-client',
    servers: {
        zenrows: {
            command: 'npx',
            args: ['-y', '@zenrows/mcp'],
            env: {
            ZENROWS_API_KEY: process.env.ZENROWS_API_KEY!,
            },
        },
        github: {
            command: 'npx',
            args: ['-y', '@modelcontextprotocol/server-github'],
            env: {
            GITHUB_PERSONAL_ACCESS_TOKEN: process.env.GITHUB_TOKEN!,
            },
        },
    },
})
All tools from all servers are available to the agent via a single listTools() call.
Yes. Mastra Studio (http://localhost:4111) lets you run your agent interactively, inspect every tool call including ZenRows requests and responses, view token usage, and debug step by step without writing test scripts. See the Mastra Agent Studio overview for more information.