Skip to Content
DocsGetting StartedInstallation

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:

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

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 create-mastra@latest

After the prompts, create-mastra will:

  1. Set up your project directory with TypeScript
  2. Install dependencies
  3. Configure your selected components and LLM provider
  4. 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:

.env
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 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:

.env
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:

src/mastra/tools/weather-tool.ts
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}&current=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:

src/mastra/agents/weather-agent.ts
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:

src/mastra/workflows/weather-workflow.ts
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}&current=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:

src/mastra/index.ts
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:

Test the Endpoint

You can also test the agent’s endpoint using curl or fetch:

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):

bash copy npm install dotenv --save-dev

If you’re using a .env file, add the following code:

src/test-agent.ts
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:

src/test-agent.ts
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:

Next Steps