Installing Mastra Locally
To run Mastra, you need access to an LLM. Typically, you’ll want to get an API key from an LLM provider such as:
You can also run Mastra with a local LLM using:
- Ollama .
What You’ll Build
By the end of this guide, you’ll have built a Weather Agent that generates weather reports using OpenAI’s gpt-4o-mini and the Open-Meteo forecast endpoint. You can run it directly from the command line or test it in the Mastra Playground. With the Weather agent running you can ask questions like:
What is the weather in London?
Suggest indoor alternatives in Berlin if it rains tomorrow.
Is it windy in San Francisco today?
Prerequisites
- Node.js
v20.0
or higher - Access to a supported large language model (LLM)
Automatic Installation
Create a New Project
There are two options when creating a new project with our CLI:
We recommend starting a new Mastra project using the interactive setup, which guides you through scaffolding your project step by step.
Interactive
To create a project, run:
npx
npx create-mastra@latest
After the prompts, create-mastra
will:
- Set up your project directory with TypeScript
- Install dependencies
- Configure your selected components and LLM provider
- Configure the MCP server in your IDE (if selected) for instant access to docs, examples, and help while you code
If you’re using a different IDE, you can install the MCP server manually by following the instructions in the MCP server docs. Also note that there are additional steps for Cursor and Windsurf to activate the MCP server.
Non-Interactive
You can run the Mastra CLI in fully non-interactive mode by passing all required flags, for example:
npx create-mastra@latest --project-name hello-mastra --example --components tools,agents,workflows --llm openai
See the mastra init documentation for a full list of available CLI options.
Set Up your API Key
Create a .env
file in your project root directory and add your API key:
OPENAI_API_KEY=<your-api-key>
Replace
your-api-key
with your API key.
Manual Installation
If you prefer to set up your Mastra project manually, follow these steps:
Create a New Project
Create a new project and change directory:
mkdir hello-mastra
cd hello-mastra
Initialize a TypeScript project including the @mastra/core
package:
npm
npm init -y
npm install typescript tsx @types/node mastra@latest --save-dev
npm install @mastra/core@latest zod @ai-sdk/openai
Add the dev
and build
scripts to package.json
:
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "mastra dev",
"build": "mastra build"
}
}
Initialize TypeScript
Create a tsconfig.json
file in your project root with the following configuration:
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"outDir": "dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", ".mastra"]
}
This TypeScript configuration is optimized for Mastra projects, using modern module resolution and strict type checking.
Set Up your API Key
Create a .env
file in your project root directory and add your API key:
OPENAI_API_KEY=<your-api-key>
Replace
your-api-key
with your API key.
Create a Tool
Create a weather-tool.ts
file:
mkdir -p src/mastra/tools && touch src/mastra/tools/weather-tool.ts
Add the following code:
import { createTool } from "@mastra/core/tools";
import { z } from "zod";
interface WeatherResponse {
current: {
time: string;
temperature_2m: number;
apparent_temperature: number;
relative_humidity_2m: number;
wind_speed_10m: number;
wind_gusts_10m: number;
weather_code: number;
};
}
export const weatherTool = createTool({
id: "get-weather",
description: "Get current weather for a location",
inputSchema: z.object({
location: z.string().describe("City name"),
}),
outputSchema: z.object({
temperature: z.number(),
feelsLike: z.number(),
humidity: z.number(),
windSpeed: z.number(),
windGust: z.number(),
conditions: z.string(),
location: z.string(),
}),
execute: async ({ context }) => {
return await getWeather(context.location);
},
});
const getWeather = async (location: string) => {
const geocodingUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(location)}&count=1`;
const geocodingResponse = await fetch(geocodingUrl);
const geocodingData = await geocodingResponse.json();
if (!geocodingData.results?.[0]) {
throw new Error(`Location '${location}' not found`);
}
const { latitude, longitude, name } = geocodingData.results[0];
const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m,apparent_temperature,relative_humidity_2m,wind_speed_10m,wind_gusts_10m,weather_code`;
const response = await fetch(weatherUrl);
const data: WeatherResponse = await response.json();
return {
temperature: data.current.temperature_2m,
feelsLike: data.current.apparent_temperature,
humidity: data.current.relative_humidity_2m,
windSpeed: data.current.wind_speed_10m,
windGust: data.current.wind_gusts_10m,
conditions: getWeatherCondition(data.current.weather_code),
location: name,
};
};
function getWeatherCondition(code: number): string {
const conditions: Record<number, string> = {
0: "Clear sky",
1: "Mainly clear",
2: "Partly cloudy",
3: "Overcast",
45: "Foggy",
48: "Depositing rime fog",
51: "Light drizzle",
53: "Moderate drizzle",
55: "Dense drizzle",
56: "Light freezing drizzle",
57: "Dense freezing drizzle",
61: "Slight rain",
63: "Moderate rain",
65: "Heavy rain",
66: "Light freezing rain",
67: "Heavy freezing rain",
71: "Slight snow fall",
73: "Moderate snow fall",
75: "Heavy snow fall",
77: "Snow grains",
80: "Slight rain showers",
81: "Moderate rain showers",
82: "Violent rain showers",
85: "Slight snow showers",
86: "Heavy snow showers",
95: "Thunderstorm",
96: "Thunderstorm with slight hail",
99: "Thunderstorm with heavy hail",
};
return conditions[code] || "Unknown";
}
Create an Agent
Create a weather-agent.ts
file:
mkdir -p src/mastra/agents && touch src/mastra/agents/weather-agent.ts
Add the following code:
import { openai } from "@ai-sdk/openai";
import { Agent } from "@mastra/core/agent";
import { weatherTool } from "../tools/weather-tool";
export const weatherAgent = new Agent({
name: "Weather Agent",
instructions: `You are a helpful weather assistant that provides accurate weather information.
Your primary function is to help users get weather details for specific locations. When responding:
- Always ask for a location if none is provided
- If the location name isn't in English, please translate it
- Include relevant details like humidity, wind conditions, and precipitation
- Keep responses concise but informative
Use the weatherTool to fetch current weather data.`,
model: openai("gpt-4o-mini"),
tools: { weatherTool },
});
Create a Workflow
Create a weather-workflow.ts
file:
mkdir -p src/mastra/workflows && touch src/mastra/workflows/weather-workflow.ts
Add the following code:
import { openai } from "@ai-sdk/openai";
import { Agent } from "@mastra/core/agent";
import { createStep, createWorkflow } from "@mastra/core/workflows";
import { z } from "zod";
const llm = openai("gpt-4o-mini");
const agent = new Agent({
name: "Weather Agent",
model: llm,
instructions: `
You are a local activities and travel expert who excels at weather-based planning. Analyze the weather data and provide practical activity recommendations.
For each day in the forecast, structure your response exactly as follows:
📅 [Day, Month Date, Year]
═══════════════════════════
🌡️ WEATHER SUMMARY
• Conditions: [brief description]
• Temperature: [X°C/Y°F to A°C/B°F]
• Precipitation: [X% chance]
🌅 MORNING ACTIVITIES
Outdoor:
• [Activity Name] - [Brief description including specific location/route]
Best timing: [specific time range]
Note: [relevant weather consideration]
🌞 AFTERNOON ACTIVITIES
Outdoor:
• [Activity Name] - [Brief description including specific location/route]
Best timing: [specific time range]
Note: [relevant weather consideration]
🏠 INDOOR ALTERNATIVES
• [Activity Name] - [Brief description including specific venue]
Ideal for: [weather condition that would trigger this alternative]
⚠️ SPECIAL CONSIDERATIONS
• [Any relevant weather warnings, UV index, wind conditions, etc.]
Guidelines:
- Suggest 2-3 time-specific outdoor activities per day
- Include 1-2 indoor backup options
- For precipitation >50%, lead with indoor activities
- All activities must be specific to the location
- Include specific venues, trails, or locations
- Consider activity intensity based on temperature
- Keep descriptions concise but informative
Maintain this exact formatting for consistency, using the emoji and section headers as shown.
`,
});
const forecastSchema = z.object({
date: z.string(),
maxTemp: z.number(),
minTemp: z.number(),
precipitationChance: z.number(),
condition: z.string(),
location: z.string(),
});
function getWeatherCondition(code: number): string {
const conditions: Record<number, string> = {
0: "Clear sky",
1: "Mainly clear",
2: "Partly cloudy",
3: "Overcast",
45: "Foggy",
48: "Depositing rime fog",
51: "Light drizzle",
53: "Moderate drizzle",
55: "Dense drizzle",
61: "Slight rain",
63: "Moderate rain",
65: "Heavy rain",
71: "Slight snow fall",
73: "Moderate snow fall",
75: "Heavy snow fall",
95: "Thunderstorm",
};
return conditions[code] || "Unknown";
}
const fetchWeather = createStep({
id: "fetch-weather",
description: "Fetches weather forecast for a given city",
inputSchema: z.object({
city: z.string().describe("The city to get the weather for"),
}),
outputSchema: forecastSchema,
execute: async ({ inputData }) => {
if (!inputData) {
throw new Error("Input data not found");
}
const geocodingUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(inputData.city)}&count=1`;
const geocodingResponse = await fetch(geocodingUrl);
const geocodingData = (await geocodingResponse.json()) as {
results: { latitude: number; longitude: number; name: string }[];
};
if (!geocodingData.results?.[0]) {
throw new Error(`Location '${inputData.city}' not found`);
}
const { latitude, longitude, name } = geocodingData.results[0];
const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=precipitation,weathercode&timezone=auto,&hourly=precipitation_probability,temperature_2m`;
const response = await fetch(weatherUrl);
const data = (await response.json()) as {
current: {
time: string;
precipitation: number;
weathercode: number;
};
hourly: {
precipitation_probability: number[];
temperature_2m: number[];
};
};
const forecast = {
date: new Date().toISOString(),
maxTemp: Math.max(...data.hourly.temperature_2m),
minTemp: Math.min(...data.hourly.temperature_2m),
condition: getWeatherCondition(data.current.weathercode),
precipitationChance: data.hourly.precipitation_probability.reduce(
(acc, curr) => Math.max(acc, curr),
0,
),
location: inputData.city,
};
return forecast;
},
});
const planActivities = createStep({
id: "plan-activities",
description: "Suggests activities based on weather conditions",
inputSchema: forecastSchema,
outputSchema: z.object({
activities: z.string(),
}),
execute: async ({ inputData }) => {
const forecast = inputData;
if (!forecast) {
throw new Error("Forecast data not found");
}
const prompt = `Based on the following weather forecast for ${forecast.location}, suggest appropriate activities:
${JSON.stringify(forecast, null, 2)}
`;
const response = await agent.stream([
{
role: "user",
content: prompt,
},
]);
let activitiesText = "";
for await (const chunk of response.textStream) {
process.stdout.write(chunk);
activitiesText += chunk;
}
return {
activities: activitiesText,
};
},
});const weatherWorkflow = createWorkflow({
id: "weather-workflow",
inputSchema: z.object({
city: z.string().describe("The city to get the weather for"),
}),
outputSchema: z.object({
activities: z.string(),
}),
})
.then(fetchWeather)
.then(planActivities);
weatherWorkflow.commit();
export { weatherWorkflow };
Register Agent
Create the Mastra entry point and register agent:
touch src/mastra/index.ts
Add the following code:
import { Mastra } from "@mastra/core";
import { PinoLogger } from "@mastra/loggers";
import { LibSQLStore } from "@mastra/libsql";
import { weatherWorkflow } from "./workflows/weather-workflow";
import { weatherAgent } from "./agents/weather-agent";
export const mastra = new Mastra({
workflows: { weatherWorkflow },
agents: { weatherAgent },
storage: new LibSQLStore({
// stores telemetry, evals, ... into memory storage, if it needs to persist, change to file:../mastra.db
url: ":memory:",
}),
logger: new PinoLogger({
name: "Mastra",
level: "info",
}),
});
This registers your agent with Mastra so that
mastra dev
can discover and serve it.
Start the Mastra Server
Mastra provides commands to serve your agents via REST endpoints
Development Server
Run the following command to start the Mastra server:
npm run dev
If you have the mastra CLI installed, run:
mastra dev
This command creates REST API endpoints for your agents which are available on the following links:
- Playground: http://localhost:4111/
- Mastra API: http://localhost:4111/api
- OpenAPI Spec: http://localhost:4111/openapi.json
- Swagger UI – API explorer: http://localhost:4111/swagger-ui
Test the Endpoint
You can also test the agent’s endpoint using curl
or fetch
:
curl
curl -X POST http://localhost:4111/api/agents/weatherAgent/generate \
-H "Content-Type: application/json" \
-d '{"messages": ["What is the weather in London?"]}'
Run from the command line
If you’d like to call agents directly from the command line, create a simple script to get and invoke an agent.
Create a test file:
touch src/test-agent.ts
Install dotenv
to load environment variables (e.g. your API keys):
npm
bash copy npm install dotenv --save-dev
If you’re using a .env
file, add the following code:
import "dotenv/config";
import { mastra } from "./mastra";
async function main() {
const agent = await mastra.getAgent("weatherAgent");
const result = await agent.generate("What is the weather in London?");
console.log("Agent response:", result.text);
}
main();
Or, if you’re using a .env.development
file, add the following code:
import { config } from "dotenv";
config({ path: ".env.development" });
import { mastra } from "./mastra";
async function main() {
const agent = await mastra.getAgent("weatherAgent");
const result = await agent.generate("What is the weather in London?");
console.log("Agent response:", result.text);
}
main();
Run the script to test the agent:
npx tsx src/test-agent.ts
You should see output similar to the following:
Agent response: The current weather in London is as follows:
- **Temperature:** 12.9°C (Feels like 9.7°C)
- **Humidity:** 63%
- **Wind Speed:** 14.7 km/h
- **Wind Gusts:** 32.4 km/h
- **Conditions:** Overcast
Let me know if you need more information!
Use Mastra on the Client
To use Mastra in your frontend applications, use our type-safe client SDK to call your agents via the Mastra REST API.
Existing Project Installation
To add Mastra to an existing project, see our Local development docs: