Build a Weather MCP Server with mcp-framework
Learn how to build a fully functional Weather MCP Server using mcp-framework. This beginner-friendly tutorial walks you through creating tools, handling API calls, and connecting your server to Claude Desktop.
title: "Build a Weather MCP Server with mcp-framework" description: "Learn how to build a fully functional Weather MCP Server using mcp-framework. This beginner-friendly tutorial walks you through creating tools, handling API calls, and connecting your server to Claude Desktop." order: 1 keywords:
- mcp weather server
- mcp-framework tutorial
- build mcp server
- weather api mcp
- model context protocol beginner date: "2026-04-01" level: "beginner" duration: "20 min"
Build a Weather MCP Server from scratch using mcp-framework. You will create a WeatherTool class that fetches real weather data and exposes it to AI assistants like Claude. By the end, you will have a working server you can connect to Claude Desktop.
What You Will Build
In this tutorial, you will create an MCP server that provides weather information to AI assistants. When a user asks Claude about the weather, your server will fetch real data from the Open-Meteo API and return it in a structured format.
An MCP (Model Context Protocol) server exposes tools, resources, and prompts that AI assistants can use. Think of it as a plugin system for LLMs -- your server gives Claude new capabilities like checking the weather.
Prerequisites
Before starting, make sure you have:
- Node.js 18+ installed
- npm or yarn package manager
- A text editor (VS Code recommended)
- Basic TypeScript knowledge
Project Setup
Create a new project with mcp-framework CLI
The fastest way to get started is using the mcp-framework CLI to scaffold your project:
npx mcp-framework create weather-server
cd weather-server
This generates the project structure, installs dependencies, and sets up TypeScript configuration automatically.
Explore the project structure
Your new project looks like this:
The src/index.ts file is your server entry point, and the src/tools/ directory is where you define your MCP tools.
Remove the example tool
Delete the scaffolded example so we can start fresh:
rm src/tools/ExampleTool.ts
Building the WeatherTool
Now let's create the core of our server -- the WeatherTool class. With mcp-framework, each tool is a class that extends MCPTool.
Create the Tool File
Create src/tools/WeatherTool.ts:
import { MCPTool } from "mcp-framework";
import { z } from "zod";
interface WeatherData {
temperature: number;
windSpeed: number;
weatherCode: number;
humidity: number;
}
class WeatherTool extends MCPTool<typeof inputSchema> {
name = "get_weather";
description = "Get current weather data for a given location by latitude and longitude";
schema = {
latitude: {
type: z.number().min(-90).max(90),
description: "Latitude of the location (-90 to 90)",
},
longitude: {
type: z.number().min(-180).max(180),
description: "Longitude of the location (-180 to 180)",
},
city: {
type: z.string().optional(),
description: "Optional city name for display purposes",
},
};
async execute(input: z.infer<typeof inputSchema>): Promise<string> {
const { latitude, longitude, city } = input;
const url = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m,wind_speed_10m,weather_code,relative_humidity_2m`;
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Weather API returned ${response.status}`);
}
const data = await response.json();
const current = data.current;
const weather: WeatherData = {
temperature: current.temperature_2m,
windSpeed: current.wind_speed_10m,
weatherCode: current.weather_code,
humidity: current.relative_humidity_2m,
};
const locationLabel = city || `${latitude}, ${longitude}`;
return JSON.stringify({
location: locationLabel,
temperature: `${weather.temperature}°C`,
windSpeed: `${weather.windSpeed} km/h`,
humidity: `${weather.humidity}%`,
condition: this.getWeatherCondition(weather.weatherCode),
}, null, 2);
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
return JSON.stringify({ error: `Failed to fetch weather: ${message}` });
}
}
private 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 snowfall",
73: "Moderate snowfall",
75: "Heavy snowfall",
95: "Thunderstorm",
};
return conditions[code] || "Unknown";
}
}
const inputSchema = z.object({
latitude: z.number().min(-90).max(90),
longitude: z.number().min(-180).max(180),
city: z.string().optional(),
});
export default WeatherTool;
Always return JSON strings from your tool's execute method. This makes it easy for the AI assistant to parse and reason about the data. Include both the raw values and human-readable formats.
Understanding the Code
Let's break down the key parts of the WeatherTool:
The Schema Definition
The schema property defines what inputs your tool accepts. mcp-framework uses Zod for validation, so invalid inputs are automatically rejected before execute is called.
schema = {
latitude: {
type: z.number().min(-90).max(90),
description: "Latitude of the location (-90 to 90)",
},
// ...
};
The Execute Method
The execute method is where your tool's logic lives. It receives validated input and returns a string. Here we call the Open-Meteo API (free, no key required) and format the results.
We use Open-Meteo because it is completely free and requires no API key. For production weather servers, you might want to use OpenWeatherMap or WeatherAPI for more detailed data.
Configuring the Server Entry Point
Your src/index.ts should already be set up by the CLI. Verify it looks like this:
import { MCPServer } from "mcp-framework";
const server = new MCPServer();
server.start();
That is it. mcp-framework automatically discovers tools in the src/tools/ directory, so there is no manual registration needed.
mcp-framework scans the src/tools/, src/resources/, and src/prompts/ directories at startup and registers everything it finds. Just export a default class and it works.
Building and Testing
Build the project
npm run build
This compiles your TypeScript to JavaScript in the dist/ directory.
Test locally with the MCP inspector
npx @modelcontextprotocol/inspector node dist/index.js
The MCP Inspector lets you interact with your server in a browser. Try calling get_weather with latitude 40.7128 and longitude -74.006 (New York City).
Connect to Claude Desktop
Add your server to Claude Desktop's configuration file. On macOS, edit ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"weather": {
"command": "node",
"args": ["/absolute/path/to/weather-server/dist/index.js"]
}
}
}
Restart Claude Desktop and ask it: "What's the weather in New York?"
Adding a City Lookup Tool
Let's add a second tool so users can search by city name instead of coordinates. Create src/tools/CityLookupTool.ts:
import { MCPTool } from "mcp-framework";
import { z } from "zod";
class CityLookupTool extends MCPTool<typeof inputSchema> {
name = "lookup_city";
description = "Look up the latitude and longitude of a city by name";
schema = {
city: {
type: z.string().min(1),
description: "The city name to look up",
},
};
async execute(input: z.infer<typeof inputSchema>): Promise<string> {
const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(input.city)}&count=1`;
const response = await fetch(url);
const data = await response.json();
if (!data.results || data.results.length === 0) {
return JSON.stringify({ error: `City "${input.city}" not found` });
}
const result = data.results[0];
return JSON.stringify({
city: result.name,
country: result.country,
latitude: result.latitude,
longitude: result.longitude,
}, null, 2);
}
}
const inputSchema = z.object({
city: z.string().min(1),
});
export default CityLookupTool;
Now Claude can first look up a city, then get its weather -- all automatically.
mcp-framework vs Official SDK
| Feature | mcp-framework | Official TypeScript SDK |
|---|---|---|
| Project scaffolding | Built-in CLI | Manual setup |
| Tool definition | Class-based with decorators | Functional handlers |
| Auto-discovery | Yes, scans directories | No, manual registration |
| Boilerplate | Minimal | More setup code |
| Flexibility | Convention-based | Full control |
Next Steps
You now have a working weather MCP server. Here are some ways to extend it:
- Add hourly forecasts with a new tool
- Support multiple weather providers as a fallback
- Add caching to reduce API calls
- Check out the SDK-based version for comparison