On this page
Mastra is a TypeScript-first AI agent framework designed for building production-ready AI applications. CrawlForge gives those agents the ability to fetch, extract, and analyze live web data. Together, they let you build agents that can research topics, monitor competitors, and extract structured data from any website.
This guide shows you how to wire CrawlForge tools into Mastra agents with working TypeScript examples.
Table of Contents
- What Is Mastra?
- Prerequisites
- Step 1: Set Up Your Mastra Project
- Step 2: Create CrawlForge Tool Definitions
- Step 3: Build a Web Research Agent
- Step 4: Build a Data Extraction Workflow
- Step 5: Add Error Handling and Retries
- Credit Cost Reference
- Architecture Overview
- Next Steps
What Is Mastra?
Mastra is the modern TypeScript framework for AI-powered applications and agents. It provides primitives for agent creation, tool integration, workflows, and memory -- all with full type safety. Think of it as the Express.js of AI agents: minimal, composable, and production-oriented.
Mastra agents can use external tools through a standardized tool interface. CrawlForge tools map directly to this interface, giving your agents 20 web scraping capabilities without writing HTTP client code.
Prerequisites
- Node.js 18+ and TypeScript 5+
- A CrawlForge account with an API key (1,000 free credits)
- Basic familiarity with TypeScript and async/await
Step 1: Set Up Your Mastra Project
Create a new Mastra project and install dependencies:
npm create mastra@latest my-web-agent
cd my-web-agent
# Install additional dependencies
npm install dotenv node-fetchAdd your CrawlForge API key to .env:
CRAWLFORGE_API_KEY=cf_live_your_key_here
ANTHROPIC_API_KEY=sk-ant-your_key_hereStep 2: Create CrawlForge Tool Definitions
Create a tools file that wraps CrawlForge's API as Mastra-compatible tools:
// src/tools/crawlforge.ts
import { createTool } from '@mastra/core';
import { z } from 'zod';
const CRAWLFORGE_BASE = 'https://crawlforge.dev/api/v1/tools';
async function callCrawlForge(tool: string, params: Record<string, unknown>) {
const response = await fetch(`${CRAWLFORGE_BASE}/${tool}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.CRAWLFORGE_API_KEY}`,
},
body: JSON.stringify(params),
});
if (!response.ok) {
throw new Error(`CrawlForge ${tool} failed: ${response.status}`);
}
return response.json();
}
// Extract clean content from a URL (2 credits)
export const extractContent = createTool({
id: 'crawlforge-extract-content',
description: 'Extract clean, readable content from any web page. Costs 2 credits.',
inputSchema: z.object({
url: z.string().url().describe('The URL to extract content from'),
}),
execute: async ({ context }) => {
return callCrawlForge('extract_content', { url: context.url });
},
});
// Search the web (5 credits)
export const searchWeb = createTool({
id: 'crawlforge-search-web',
description: 'Search the web using Google. Returns titles, URLs, and snippets. Costs 5 credits.',
inputSchema: z.object({
query: z.string().describe('Search query string'),
limit: z.number().optional().default(10).describe('Max results to return'),
}),
execute: async ({ context }) => {
return callCrawlForge('search_web', {
query: context.query,
limit: context.limit,
});
},
});
// Fetch raw HTML (1 credit)
export const fetchUrl = createTool({
id: 'crawlforge-fetch-url',
description: 'Fetch raw HTML content from a URL. Cheapest option at 1 credit.',
inputSchema: z.object({
url: z.string().url().describe('The URL to fetch'),
}),
execute: async ({ context }) => {
return callCrawlForge('fetch_url', { url: context.url });
},
});
// Extract structured data with CSS selectors (2 credits)
export const scrapeStructured = createTool({
id: 'crawlforge-scrape-structured',
description: 'Extract data using CSS selectors. Costs 2 credits.',
inputSchema: z.object({
url: z.string().url().describe('The URL to scrape'),
selectors: z.record(z.string()).describe('CSS selectors mapping field names to selectors'),
}),
execute: async ({ context }) => {
return callCrawlForge('scrape_structured', {
url: context.url,
selectors: context.selectors,
});
},
});Step 3: Build a Web Research Agent
Create an agent that can search the web and extract content for research tasks:
// src/agents/researcher.ts
import { Agent } from '@mastra/core';
import { searchWeb, extractContent, fetchUrl } from '../tools/crawlforge';
export const researcherAgent = new Agent({
name: 'Web Researcher',
instructions: `You are a web research agent. When asked to research a topic:
1. Use search_web to find relevant pages (5 credits)
2. Use extract_content on the top 2-3 results (2 credits each)
3. Synthesize findings into a structured summary
Always report the total credits used.
Prefer fetch_url (1 credit) over extract_content (2 credits) when you only need raw HTML.
Never use search_web if the user provides a specific URL.`,
model: {
provider: 'ANTHROPIC',
name: 'claude-sonnet-4-20250514',
},
tools: {
searchWeb,
extractContent,
fetchUrl,
},
});Run the agent:
// src/index.ts
import { researcherAgent } from './agents/researcher';
async function main() {
const result = await researcherAgent.generate(
'Research the current state of MCP protocol adoption. ' +
'Which companies are using it and for what use cases?'
);
console.log(result.text);
// Output: Structured research summary with sources
// Credits used: ~11 (1 search + 3 extractions)
}
main();Step 4: Build a Data Extraction Workflow
Mastra workflows let you chain tools into deterministic pipelines. Here is a competitive pricing monitor:
// src/workflows/pricing-monitor.ts
import { Workflow, Step } from '@mastra/core';
import { searchWeb, scrapeStructured } from '../tools/crawlforge';
const findCompetitors = new Step({
id: 'find-competitors',
execute: async ({ context }) => {
// Search for competitors (5 credits)
const results = await searchWeb.execute({
context: { query: `${context.product} pricing plans`, limit: 5 },
});
return { urls: results.results.map((r: { link: string }) => r.link) };
},
});
const extractPricing = new Step({
id: 'extract-pricing',
execute: async ({ context }) => {
const pricingData = [];
// Extract pricing from each competitor (2 credits each)
for (const url of context.urls.slice(0, 3)) {
try {
const data = await scrapeStructured.execute({
context: {
url,
selectors: {
planNames: '.plan-name, .pricing-tier h3',
prices: '.price, .plan-price',
features: '.feature-list li, .plan-features li',
},
},
});
pricingData.push({ url, ...data });
} catch (error) {
pricingData.push({ url, error: 'Extraction failed' });
}
}
return { pricingData };
// Total: 5 + (3 * 2) = 11 credits
},
});
export const pricingMonitorWorkflow = new Workflow({
name: 'Pricing Monitor',
steps: [findCompetitors, extractPricing],
});Step 5: Add Error Handling and Retries
Production agents need resilient error handling. Here is a pattern for CrawlForge tool calls:
// src/tools/resilient-crawlforge.ts
import { createTool } from '@mastra/core';
import { z } from 'zod';
const CRAWLFORGE_BASE = 'https://crawlforge.dev/api/v1/tools';
async function callWithRetry(
tool: string,
params: Record<string, unknown>,
maxRetries = 2
): Promise<unknown> {
let lastError: Error | null = null;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(`${CRAWLFORGE_BASE}/${tool}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.CRAWLFORGE_API_KEY}`,
},
body: JSON.stringify(params),
});
if (response.status === 429) {
// Rate limited -- wait and retry
const retryAfter = parseInt(response.headers.get('Retry-After') || '2');
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
return response.json();
} catch (error) {
lastError = error as Error;
if (attempt < maxRetries) {
await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1)));
}
}
}
throw lastError;
}
export const resilientExtractContent = createTool({
id: 'crawlforge-extract-content-resilient',
description: 'Extract content with automatic retry on failure. 2 credits per successful call.',
inputSchema: z.object({
url: z.string().url(),
}),
execute: async ({ context }) => {
return callWithRetry('extract_content', { url: context.url });
},
});Credit Cost Reference
| Credits | Tools | Mastra Use Case |
|---|---|---|
| 1 | fetch_url, extract_text, extract_links, extract_metadata | Quick data fetching in agent tools |
| 2 | scrape_structured, extract_content, map_site, process_document, localization | Workflow extraction, site audits, document processing |
| 3 | track_changes, analyze_content | Change detection, content analysis |
| 4 | summarize_content, crawl_deep | Summaries, multi-page crawling |
| 5 | search_web, batch_scrape, scrape_with_actions, stealth_mode | Research agents, bulk operations |
| 10 | deep_research | Comprehensive analysis agents |
Architecture Overview
| Component | Role |
|---|---|
| Mastra Agent | Orchestrates tool calls, maintains conversation context |
| Mastra Tools | Typed wrappers around CrawlForge API endpoints |
| Mastra Workflow | Deterministic multi-step pipelines for batch operations |
| CrawlForge API | Executes web scraping, returns structured data |
| Credit System | Tracks usage per API key, enforces limits |
The Mastra agent decides which CrawlForge tool to call based on the task. The tool wrapper handles HTTP communication, and CrawlForge executes the actual scraping. Credits are deducted atomically on each successful tool call.
Next Steps
- Mastra Quickstart Guide -- official Mastra documentation
- CrawlForge API Reference -- full endpoint documentation
- Build a Research Assistant -- similar pattern using Claude directly
- Deep Research Automation -- advanced research workflows
Build your first web-aware AI agent today. Sign up for CrawlForge (1,000 free credits), scaffold a Mastra project, and give your agents the power to scrape the entire web.
Try this yourself — no signup needed
Run any of CrawlForge's 27 scraping and extraction tools in the playground, then start free with 1,000 credits.
1,000 free credits • Refills monthly • No credit card required
Tags
About the Author
Stay updated with the latest insights
Get tutorials, product updates, and web scraping tips delivered to your inbox.
No spam. Unsubscribe anytime.