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"

Quick Summary

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.

MCP Server

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
20 minestimated completion time

Project Setup

1

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.

2

Explore the project structure

Your new project looks like this:

weather-server
src
tools
ExampleTool.ts
index.ts
package.json
tsconfig.json

The src/index.ts file is your server entry point, and the src/tools/ directory is where you define your MCP tools.

3

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}&current=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;
Structured Return Values

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.

No API Key Needed

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.

Auto-Discovery

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

1

Build the project

npm run build

This compiles your TypeScript to JavaScript in the dist/ directory.

2

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

3

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

Featuremcp-frameworkOfficial TypeScript SDK
Project scaffoldingBuilt-in CLIManual setup
Tool definitionClass-based with decoratorsFunctional handlers
Auto-discoveryYes, scans directoriesNo, manual registration
BoilerplateMinimalMore setup code
FlexibilityConvention-basedFull 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

Frequently Asked Questions