Building Weather Tools with MCP and FastMCP

A Complete Beginner’s Guide to Creating MCP Weather Services

From Setup to Working MCP Tools: Your First Weather Service

Author: Aaron Borgi
Email: [email protected]
Company: THINKOFIT

2025-11-07

Introduction

Welcome to the world of MCP (Model Context Protocol) applications! In this comprehensive guide, we’ll build a weather service that provides current weather and daily forecasts for any location in the UK using FastMCP and Python. This tutorial is designed for beginners who want to understand how to create tools that can be used by AI assistants and other applications.

By the end of this walkthrough, you’ll have:

  • Built a professional MCP server using FastMCP

  • Created tools that fetch real-time weather data

  • Learned about modern Python package management with uv

  • Understood how MCP enables AI tool integration

  • Created a testable, maintainable codebase

MCP (Model Context Protocol) is a revolutionary way to extend AI capabilities by providing external tools and data sources. FastMCP makes it incredibly easy to create these tools using Python, allowing AI assistants to access real-world data like weather information.

Understanding MCP and FastMCP

What is MCP?

MCP (Model Context Protocol) is a standardized way to connect AI models with external tools and data sources. Think of it as a bridge that allows AI assistants to:

  • Access real-time information

  • Perform actions in external systems

  • Use specialized tools and services

  • Extend their capabilities beyond their training data

Why FastMCP?

FastMCP is a Python library that makes building MCP servers incredibly simple. It provides:

  • Decorator-based tool creation: Define tools with simple Python functions

  • Automatic validation: Built-in parameter validation and error handling

  • Easy testing: Built-in client for testing your tools

  • Standard compliance: Follows MCP specifications exactly

Setting Up Your Development Environment with uv

Why Choose uv Over pip?

Before we start coding, let’s talk about uv - a modern Python package manager that’s revolutionizing how we handle Python projects.

uv vs pip - The Key Differences:

Speed:

  • uv is 10-100x faster than pip for installing packages

  • Uses Rust under the hood for maximum performance

  • Parallel downloads and installations

  • Smart caching reduces redundant downloads

Dependency Resolution:

  • Advanced dependency resolver prevents conflicts

  • Deterministic installs - same result every time

  • Better handling of complex dependency trees

  • Clearer error messages when conflicts occur

Project Management:

  • Built-in virtual environment management

  • Automatic Python version management

  • Project-based dependency tracking

  • Lock file generation for reproducible builds

Modern Features:

  • Compatible with pyproject.toml

  • Built-in formatter and linter integration

  • Cross-platform consistency

  • Better security with package verification

Installing uv

Installing uv is straightforward on all platforms:

On macOS and Linux:

curl -LsSf https://astral.sh/uv/install.sh | sh

On Windows:

powershell -c "irm https://astral.sh/uv/install.ps1 | iex"

Alternative installation methods:

  • With pip: pip install uv

  • With conda: conda install -c conda-forge uv

  • With homebrew: brew install uv

Creating Your Project

Now let’s create our weather MCP project:

# Create a new project
uv init uk-weather-mcp
cd uk-weather-mcp

# Add required dependencies
uv add mcp fastmcp httpx

# Create our main files
touch main.py
touch test.py

What uv did for us:

  • Created a new project directory with proper structure

  • Initialized a virtual environment automatically

  • Created pyproject.toml for dependency management

  • Added fastmcp and httpx to our dependencies

  • Generated a lock file for reproducible installs

Understanding the Weather APIs

Open-Meteo API

Our weather service uses the Open-Meteo API, which provides:

  • Free access: No API key required

  • Global coverage: Weather data for any location

  • Multiple endpoints: Geocoding and weather forecasting

  • High accuracy: Professional-grade weather data

Two main endpoints we’ll use:

1. Geocoding API:

  • URL: https://geocoding-api.open-meteo.com/v1/search

  • Purpose: Convert city names to coordinates

  • Input: City name or postcode

  • Output: Latitude and longitude

2. Forecast API:

  • URL: https://api.open-meteo.com/v1/forecast

  • Purpose: Get weather data for coordinates

  • Input: Latitude, longitude, and parameters

  • Output: Weather data in JSON format

Building the MCP Server

Project Structure

Our simple but effective project structure:

uk-weather-mcp/
├── main.py          # MCP server implementation
├── test.py          # Test client
├── pyproject.toml   # Project configuration
├── uv.lock         # Dependency lock file
└── .python-version  # Python version specification

Creating the Main Server

Let’s build our main.py file step by step:

from mcp.server.fastmcp import FastMCP
import httpx

# Initialize our MCP server
mcp = FastMCP("uk-weather")

# API endpoints
API_GEO = "https://geocoding-api.open-meteo.com/v1/search"
API_FORECAST = "https://api.open-meteo.com/v1/forecast"
UK_TZ = "Europe/London"

async def coords_for(city: str) -> tuple[float, float]:
    """Look up lat/lon for a city or UK postcode (first match)."""
    params = {"name": city, "count": 1}
    async with httpx.AsyncClient(timeout=10) as client:
        response = await client.get(API_GEO, params=params)
        response.raise_for_status()
        item = response.json()["results"][0]
        return item["latitude"], item["longitude"]

@mcp.tool()
async def current_weather(city: str) -> dict:
    """Get current weather for a UK city or postcode."""
    lat, lon = await coords_for(city=city)
    params = {
        "latitude": lat,
        "longitude": lon,
        "current_weather": "true",
        "timezone": UK_TZ,
    }
    async with httpx.AsyncClient(timeout=10) as client:
        response = await client.get(API_FORECAST, params=params)
        response.raise_for_status()
        return response.json()["current_weather"]

@mcp.tool()
async def daily_forecast(city: str, days: int = 5) -> dict:
    """Get daily weather forecast for a UK city or postcode."""
    lat, lon = await coords_for(city=city)
    params = {
        "latitude": lat,
        "longitude": lon,
        "timezone": UK_TZ,
        "daily": "temperature_2m_max,temperature_2m_min,precipitation_probability_max",
        "forecast_days": days,
    }
    async with httpx.AsyncClient(timeout=10) as client:
        response = await client.get(API_FORECAST, params=params)
        response.raise_for_status()
        return response.json()["daily"]

if __name__ == "__main__":
    mcp.run(transport="stdio")

Breaking Down the Code

1. Imports and Setup:

from mcp.server.fastmcp import FastMCP
import httpx

mcp = FastMCP("uk-weather")
  • FastMCP: The main class for creating MCP servers

  • httpx: Modern async HTTP client for API calls

  • FastMCP("uk-weather"): Creates our server with a descriptive name

2. API Configuration:

API_GEO = "https://geocoding-api.open-meteo.com/v1/search"
API_FORECAST = "https://api.open-meteo.com/v1/forecast"
UK_TZ = "Europe/London"
  • API_GEO: Endpoint for converting city names to coordinates

  • API_FORECAST: Endpoint for weather data

  • UK_TZ: Timezone setting for consistent UK time

3. Coordinate Lookup Function:

async def coords_for(city: str) -> tuple[float, float]:
    """Look up lat/lon for a city or UK postcode (first match)."""
    params = {"name": city, "count": 1}
    async with httpx.AsyncClient(timeout=10) as client:
        response = await client.get(API_GEO, params=params)
        response.raise_for_status()
        item = response.json()["results"][0]
        return item["latitude"], item["longitude"]

Why this function is important:

  • Abstraction: Hides coordinate lookup complexity

  • Reusability: Both weather functions use this

  • Error handling: raise_for_status() catches HTTP errors

  • Timeout protection: Prevents hanging requests

4. Current Weather Tool:

@mcp.tool()
async def current_weather(city: str) -> dict:
    """Get current weather for a UK city or postcode."""
    lat, lon = await coords_for(city=city)
    params = {
        "latitude": lat,
        "longitude": lon,
        "current_weather": "true",
        "timezone": UK_TZ,
    }
    async with httpx.AsyncClient(timeout=10) as client:
        response = await client.get(API_FORECAST, params=params)
        response.raise_for_status()
        return response.json()["current_weather"]

Key features:

  • @mcp.tool(): Decorator that registers this as an MCP tool

  • Type hints: Clear input/output types for validation

  • Docstring: Provides tool description for AI assistants

  • Current weather data: Real-time temperature, conditions, etc.

5. Daily Forecast Tool:

@mcp.tool()
async def daily_forecast(city: str, days: int = 5) -> dict:
    """Get daily weather forecast for a UK city or postcode."""
    lat, lon = await coords_for(city=city)
    params = {
        "latitude": lat,
        "longitude": lon,
        "timezone": UK_TZ,
        "daily": "temperature_2m_max,temperature_2m_min,precipitation_probability_max",
        "forecast_days": days,
    }
    async with httpx.AsyncClient(timeout=10) as client:
        response = await client.get(API_FORECAST, params=params)
        response.raise_for_status()
        return response.json()["daily"]

Advanced features:

  • Default parameter: days=5 provides sensible default

  • Flexible data: Max/min temperatures and precipitation

  • Configurable range: User can specify number of days

  • Structured output: Returns organized daily data

Understanding Async Programming

You’ll notice async and await throughout our code. This is crucial for MCP servers:

Why async matters:

  • Non-blocking: Server can handle multiple requests simultaneously

  • Efficient: Doesn’t waste CPU while waiting for API responses

  • Scalable: Better performance under load

  • MCP requirement: MCP protocol expects async operations

Async patterns in our code:

# Async function definition
async def coords_for(city: str) -> tuple[float, float]:

# Async HTTP client
async with httpx.AsyncClient(timeout=10) as client:

# Awaiting async operations
response = await client.get(API_GEO, params=params)
lat, lon = await coords_for(city=city)

Creating the Test Client

Understanding the Test Structure

Testing is crucial for ensuring our MCP server works correctly. Let’s create test.py:

from fastmcp import Client
import asyncio

async def main() -> None:
    """Test our MCP server by connecting via stdio."""
    # Connect via stdio to a local script
    async with Client("main.py") as client:
        # List available tools
        tools = await client.list_tools()
        print(f"Available tools: {tools}")
        
        # Test current weather
        result = await client.call_tool("current_weather", {"city": "London"})
        print(f"Current weather result: {result.content[0].text}")
        
        # Test daily forecast
        result = await client.call_tool("daily_forecast", {"city": "Manchester", "days": 3})
        print(f"Daily forecast result: {result.content[0].text}")

if __name__ == "__main__":
    asyncio.run(main())

Breaking Down the Test Code

1. Test Setup:

from fastmcp import Client
import asyncio

async def main() -> None:
    async with Client("main.py") as client:
  • Client: FastMCP’s built-in test client

  • asyncio: Python’s async event loop

  • Context manager: Ensures proper connection cleanup

  • stdio connection: Connects to our server via standard input/output

2. Tool Discovery:

tools = await client.list_tools()
print(f"Available tools: {tools}")
  • list_tools(): Discovers all available MCP tools

  • Introspection: Lets us see what our server provides

  • Debugging: Helps verify our tools are registered correctly

3. Testing Individual Tools:

# Test current weather
result = await client.call_tool("current_weather", {"city": "London"})
print(f"Current weather result: {result.content[0].text}")

# Test daily forecast
result = await client.call_tool("daily_forecast", {"city": "Manchester", "days": 3})
print(f"Daily forecast result: {result.content[0].text}")
  • call_tool(): Invokes specific MCP tools

  • Parameters: Passed as a dictionary

  • Result handling: Extracts text from MCP response format

  • Multiple tests: Verifies different tools and parameters

Running Your MCP Server

Starting the Server

To test your MCP server, run the test client:

uv run test.py

What happens when you run this:

  • uv automatically activates the virtual environment

  • The test client launches your MCP server

  • Server registers the two weather tools

  • Client tests both tools with sample data

  • Results are printed to the console

Expected Output

You should see output similar to:

Available tools: [
    {
        'name': 'current_weather',
        'description': 'Get current weather for a UK city or postcode.',
        'inputSchema': {'type': 'object', 'properties': {'city': {'type': 'string'}}}
    },
    {
        'name': 'daily_forecast',
        'description': 'Get daily weather forecast for a UK city or postcode.',
        'inputSchema': {'type': 'object', 'properties': {'city': {'type': 'string'}, 'days': {'type': 'integer'}}}
    }
]

Current weather result: {
    "temperature": 15.2,
    "windspeed": 8.4,
    "winddirection": 230,
    "weathercode": 1,
    "is_day": 1,
    "time": "2024-01-15T14:30"
}

Daily forecast result: {
    "time": ["2024-01-15", "2024-01-16", "2024-01-17"],
    "temperature_2m_max": [16.1, 14.8, 12.3],
    "temperature_2m_min": [8.2, 7.1, 5.9],
    "precipitation_probability_max": [20, 45, 80]
}

Understanding the Output

Tool Registration:

  • Both tools are properly registered with descriptions

  • Input schemas are automatically generated from function signatures

  • FastMCP handles all the MCP protocol details

Weather Data Structure:

  • Current weather: Temperature, wind, weather codes, time

  • Daily forecast: Arrays of daily values over multiple days

  • Real-time data: Actual weather from Open-Meteo API

Understanding MCP Tool Integration

How AI Assistants Use Your Tools

When your MCP server is connected to an AI assistant, here’s what happens:

1. Tool Discovery:

  • AI assistant calls list_tools() to see available tools

  • Reads tool descriptions and input schemas

  • Understands what each tool can do

2. Intelligent Tool Selection:

  • User asks: "What’s the weather like in Birmingham?"

  • AI recognizes this needs weather data

  • Selects current_weather tool

  • Calls tool with {"city": "Birmingham"}

3. Response Integration:

  • Receives structured weather data

  • Interprets the data (temperature, conditions, etc.)

  • Provides natural language response to user

  • May combine with other tools for richer answers

Why This Architecture Works

Separation of Concerns:

  • AI Assistant: Handles conversation and reasoning

  • MCP Server: Provides specialized data and tools

  • APIs: Supply real-time information

Flexibility:

  • Multiple AI assistants can use the same MCP server

  • Easy to add new tools without changing AI code

  • Tools can be developed and tested independently

Advanced Features and Error Handling

Built-in Error Handling

Our code includes several error handling mechanisms:

HTTP Error Handling:

response.raise_for_status()  # Raises exception for HTTP errors

Timeout Protection:

async with httpx.AsyncClient(timeout=10) as client:

What happens when things go wrong:

  • Invalid city: API returns empty results, causing IndexError

  • Network issues: httpx raises connection errors

  • API downtime: HTTP status errors are caught

  • Timeout: Request cancelled after 10 seconds

Understanding the Weather Data

Current Weather Fields:

  • temperature: Current temperature in Celsius

  • windspeed: Wind speed in km/h

  • winddirection: Wind direction in degrees

  • weathercode: Numeric weather condition code

  • is_day: Whether it’s daytime (1) or nighttime (0)

  • time: Timestamp of the reading

Daily Forecast Fields:

  • time: Array of dates

  • temperature_2m_max: Daily maximum temperatures

  • temperature_2m_min: Daily minimum temperatures

  • precipitation_probability_max: Chance of rain (%)

Using Your MCP Server

Connecting to AI Assistants

Once your MCP server is working, you can connect it to AI assistants that support MCP:

Common connection methods:

  • stdio: Direct connection via standard input/output

  • HTTP: Web-based API endpoint

  • WebSocket: Real-time bidirectional communication

Our server uses stdio:

if __name__ == "__main__":
    mcp.run(transport="stdio")

Configure Claude to Use MCP

  1. Open the Claude desktop application

  2. Go to Settings (usually found in the menu or profile section in the top left corner)

  3. Look for MCP or Extensions settings

  4. Click on File - Settings

  5. Click on Developer - Edit Config

  6. Configure the MCP Client to connect to your local server by adding this code snippet:

  7.         {
              "mcpServers": {
                "uk-weather": {
                  "command": "uv",
                  "args": [
                    "--directory",
                    "C:\\ABSOLUTE_PATH\\TO\\YOUR\\PROJECT\\DIRECTORY",
                    "run",
                    "main.py"
                  ]
                },
              }
            }
  8. Save the settings

Real-World Usage Examples

Example conversations with AI assistants:

User: "What’s the weather like in London today?" AI: Calls current_weather("London") and responds with current conditions.

User: "Should I plan a picnic in Manchester this weekend?" AI: Calls daily_forecast("Manchester", 7) and analyzes the forecast.

User: "Compare the weather in Edinburgh and Cardiff" AI: Calls both tools for different cities and provides comparison.

Project Management with uv

Understanding pyproject.toml

uv automatically created a pyproject.toml file for our project:

[project]
name = "uk-weather-mcp"
version = "0.1.0"
description = "MCP server for UK weather data"
dependencies = [
    "fastmcp>=0.1.0",
    "httpx>=0.25.0",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

Key sections:

  • [project]: Project metadata and dependencies

  • dependencies: Runtime requirements

  • [build-system]: How to build the project

Useful uv Commands

Dependency management:

# Add a new dependency
uv add requests

# Remove a dependency
uv remove requests

# Update all dependencies
uv sync

# Show dependency tree
uv tree

# Run scripts
uv run main.py
uv run test.py

Conclusion

Congratulations! You’ve successfully built a professional MCP server that provides weather tools for AI assistants. In this tutorial, you learned:

  • How to set up a modern Python project using uv

  • How to create MCP tools using FastMCP

  • How to integrate external APIs for real-time data

  • How to test MCP servers with built-in clients

  • How async programming enables efficient network operations

Your weather MCP server demonstrates the power of the Model Context Protocol in extending AI capabilities with real-world data. The tools you’ve created can be used by any MCP-compatible AI assistant to provide current weather information and forecasts for UK locations.

The modular architecture you’ve built makes it easy to extend with additional weather features, support for other countries, or entirely different types of tools. Whether you’re building weather services, data analysis tools, or system integrations, the patterns you’ve learned here will serve as a strong foundation.

Keep building, keep experimenting—and welcome to the future of AI

Happy coding!


Author: Aaron Borgi
Email: [email protected]
Company: THINKOFIT
Date: 2025-11-07

THINKOFIT

Empowering learners worldwide with transformative education, cutting-edge skills, and collaborative learning opportunities that drive personal and professional growth.

Accredited Institution
Secure Learning
Worldwide Access

Get in Touch

Student Support
[email protected]
Help Desk
+44 7490 371900
Main Campus
London, UK
© 2026 THINKOFIT Educational Platform
Empowering Learners. Building Skills. Creating Futures.