> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zenrows.com/llms.txt
> Use this file to discover all available pages before exploring further.

# How to Integrate ZenRows with Mastra

> Add the ZenRows MCP to Mastra and give your agents reliable access to any website without getting blocked. Turn live web data into structured output and ship AI features your users actually want right from your TypeScript application.

## What Is Mastra?

<a href="https://mastra.ai" target="_blank" rel="nofollow noopener noreferrer">Mastra</a> 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](https://app.zenrows.com/playground).

If you haven't created a project yet, run:

```bash theme={null}
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

<Steps>
  <Step title="Install the required packages">
    Install `@mastra/mcp` and the Anthropic AI SDK provider:

    ```bash theme={null}
    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.
  </Step>

  <Step title="Configure ZenRows as an MCP server">
    Create an `MCPClient` that points to the ZenRows MCP server.

    Create `src/mastra/mcp/zenrows-client.ts`:

    ```typescript TypeScript theme={null}
    // 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:

    ```bash theme={null}
    ANTHROPIC_API_KEY=YOUR_ANTHROPIC_API_KEY
    ZENROWS_API_KEY=YOUR_ZENROWS_API_KEY
    ```

    Alternatively, connect via the remote HTTP server instead:

    ```typescript TypeScript theme={null}
    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}`,
            },
          },
        },
      },
    })
    ```
  </Step>

  <Step title="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 TypeScript theme={null}
    // 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 TypeScript theme={null}
    // 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 TypeScript theme={null}
    import { researchAgent } from './agents/research-agent.js'

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

  <Step title="Run your agent">
    Start the Mastra dev server:

    ```bash theme={null}
    npm run dev
    ```

    **Test via Swagger UI**

    Open `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:

    ```text wrap theme={null}
    Scrape https://www.scrapingcourse.com/antibot-challenge and summarize the page.
    ```

    Sample response:

    ```text wrap theme={null}
    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]
    ```

    <Note>
      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.
    </Note>

    **Test in code**

    You can also call the agent directly from a script. Create `src/scripts/test-agent.ts`:

    ```typescript TypeScript theme={null}
    // 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:

    ```bash theme={null}
    npx tsx src/scripts/test-agent.ts
    ```

    **Expose as a custom API endpoint**

    Once 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 TypeScript theme={null}
    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:

    ```bash theme={null}
    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.\"}"
    ```
  </Step>
</Steps>

## 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 TypeScript theme={null}
import { MCPClient } from '@mastra/mcp'
```

Then add a new route inside `server.apiRoutes`:

```typescript TypeScript theme={null}
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:

```bash theme={null}
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\"}"
```

<Note>
  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.
</Note>

## Troubleshooting

### This Site Can't Be Reached or localhost:4111 Unreachable

The Mastra dev server isn't running. Start it with:

```bash theme={null}
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:

```bash theme={null}
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 <a href="https://ai-sdk.dev/providers/ai-sdk-providers" target="_blank" rel="nofollow noopener noreferrer">AI SDK providers docs</a> for the full list.

When using the AI SDK directly, pass only the model ID without the `anthropic/` prefix:

```typescript TypeScript theme={null}
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 TypeScript theme={null}
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`:

```typescript theme={null}
model: anthropic('claude-opus-4-6'),
```

### Cannot Find Name 'process' or Relative Import Path Errors

Add `"types": ["node"]` to your `tsconfig.json` compiler options:

```json theme={null}
{
  "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 <a href="https://app.zenrows.com" target="_blank" rel="nofollow noopener noreferrer">ZenRows dashboard</a>. 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

* <a href="https://mastra.ai/docs/mcp/overview" target="_blank" rel="nofollow noopener noreferrer">Mastra MCP Overview</a>
* <a href="https://mastra.ai/docs/agents/overview" target="_blank" rel="nofollow noopener noreferrer">Mastra Agents</a>
* <a href="https://mastra.ai/docs/workflows/overview" target="_blank" rel="nofollow noopener noreferrer">Mastra Workflows</a>
* <a href="https://mastra.ai/reference/tools/mcp-client" target="_blank" rel="nofollow noopener noreferrer">MCPClient Reference</a>
* <a href="https://mastra.ai/docs/mcp/overview#dynamic-tools" target="_blank" rel="nofollow noopener noreferrer">Dynamic Tools for Multi-Tenant Apps</a>
* [ZenRows MCP Documentation](https://docs.zenrows.com/integrations/mcp/mcp-overview)

## Frequently Asked Questions

<Accordion title="Does ZenRows work with all models on Mastra's model router?">
  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 <a href="https://mastra.ai/models" target="_blank" rel="nofollow noopener noreferrer">Mastra Providers page</a>. You can switch models without changing anything else in your agent code.
</Accordion>

<Accordion title="Can each user in my app use their own ZenRows API key?">
  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](#dynamic-tools-for-multi-tenant-apps) section for the full implementation.
</Accordion>

<Accordion title="Should I use listTools() or listToolsets()?">
  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.
</Accordion>

<Accordion title="Can I require human approval before ZenRows runs?">
  Yes. Set `requireToolApproval: true` on the ZenRows server block in your `MCPClient` config:

  ```typescript TypeScript theme={null}
  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.
</Accordion>

<Accordion title="Can I use ZenRows alongside other MCP servers in the same agent?">
  Yes. Add multiple servers to the same `MCPClient`:

  ```typescript TypeScript theme={null}
  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.
</Accordion>

<Accordion title="Can I test my ZenRows agent in Mastra Studio?">
  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 <a href="https://mastra.ai/docs/studio/overview" target="_blank" rel="nofollow noopener noreferrer">Mastra Agent Studio overview</a> for more information.
</Accordion>
