Building a Basic MCP Weather Server | Generated by AI

Home 2025.10

What is the Model Context Protocol (MCP)?

The Model Context Protocol (MCP) is an open standard that defines how applications and AI systems (like large language models) exchange context, such as tools, resources, and prompts. It enables seamless integration between LLMs (e.g., Claude) and external services, making it easier to build extensible AI agents. This guide focuses on setting up a basic MCP server using plain Python (no uv), based on the official quickstart tutorial for a simple weather server that connects to the National Weather Service API. This server exposes two tools: get_alerts (for state weather alerts) and get_forecast (for location forecasts).

Prerequisites

Step 1: Set Up Your Environment

  1. Create a new project directory:
    mkdir weather
    cd weather
    
  2. Create and activate a virtual environment:
    python -m venv .venv
    source .venv/bin/activate  # On Windows: .venv\Scripts\activate
    
  3. Upgrade pip (recommended for reliability):
    python -m pip install --upgrade pip
    
  4. Install dependencies (MCP SDK and HTTP client):
    pip install "mcp[cli]" httpx
    
  5. Create the server file:
    touch weather.py  # Or use your editor to create it
    

Step 2: Build the MCP Server

Open weather.py in your editor and add the following code. This uses the FastMCP class from the MCP SDK, which auto-generates tool schemas from type hints and docstrings.

from typing import Any
import httpx
from mcp.server.fastmcp import FastMCP

# Initialize the MCP server
mcp = FastMCP("weather")

# Constants for the National Weather Service API
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"

async def make_nws_request(url: str) -> dict[str, Any] | None:
    """Make a request to the NWS API with proper error handling."""
    headers = {
        "User-Agent": USER_AGENT,
        "Accept": "application/geo+json"
    }
    async with httpx.AsyncClient() as client:
        try:
            response = await client.get(url, headers=headers, timeout=30.0)
            response.raise_for_status()
            return response.json()
        except Exception:
            return None

def format_alert(feature: dict) -> str:
    """Format an alert feature into a readable string."""
    props = feature["properties"]
    return f"""
Event: {props.get('event', 'Unknown')}
Area: {props.get('areaDesc', 'Unknown')}
Severity: {props.get('severity', 'Unknown')}
Description: {props.get('description', 'No description available')}
Instructions: {props.get('instruction', 'No specific instructions provided')}
"""

@mcp.tool()
async def get_alerts(state: str) -> str:
    """Get weather alerts for a US state.

    Args:
        state: Two-letter US state code (e.g. CA, NY)
    """
    url = f"{NWS_API_BASE}/alerts/active/area/{state}"
    data = await make_nws_request(url)

    if not data or "features" not in data:
        return "Unable to fetch alerts or no alerts found."

    if not data["features"]:
        return "No active alerts for this state."

    alerts = [format_alert(feature) for feature in data["features"]]
    return "\n---\n".join(alerts)

@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
    """Get weather forecast for a location.

    Args:
        latitude: Latitude of the location
        longitude: Longitude of the location
    """
    # First get the forecast grid endpoint
    points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
    points_data = await make_nws_request(points_url)

    if not points_data:
        return "Unable to fetch forecast data for this location."

    # Get the forecast URL from the points response
    forecast_url = points_data["properties"]["forecast"]
    forecast_data = await make_nws_request(forecast_url)

    if not forecast_data:
        return "Unable to fetch detailed forecast."

    # Format the periods into a readable forecast
    periods = forecast_data["properties"]["periods"]
    forecasts = []
    for period in periods[:5]:  # Only show next 5 periods
        forecast = f"""
{period['name']}:
Temperature: {period['temperature']}°{period['temperatureUnit']}
Wind: {period['windSpeed']} {period['windDirection']}
Forecast: {period['detailedForecast']}
"""
        forecasts.append(forecast)

    return "\n---\n".join(forecasts)

def main():
    # Run the server over stdio
    mcp.run(transport='stdio')

if __name__ == "__main__":
    main()

Step 3: Test the Server Locally

Run the server:

python weather.py

It should start listening on stdio without output (that’s normal). To test manually, you’d need an MCP client, but proceed to integration for full testing.

Step 4: Connect to a Host (e.g., Claude for Desktop)

  1. Download and install Claude for Desktop from claude.ai/download.

  2. Configure the app by creating/editing ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or the equivalent on Windows (%APPDATA%\Claude\claude_desktop_config.json):
    {
      "mcpServers": {
        "weather": {
          "command": "python",
          "args": [
            "/ABSOLUTE/PATH/TO/weather/weather.py"  # Replace with your project path (use pwd to find it)
          ],
          "cwd": "/ABSOLUTE/PATH/TO/weather"  # Optional: Set working directory if needed
        }
      }
    }
    
    • Use absolute paths (e.g., /Users/yourname/weather/weather.py on macOS).
    • On Windows, use forward slashes / or double backslashes \\.
    • Ensure your virtual environment is activated when testing locally, but for Claude, it runs the Python executable from your system (make sure the venv’s site-packages are accessible or install globally if preferred—though venv is recommended).
    • Find Python path with which python (macOS/Linux) or where python (Windows).
  3. Fully restart Claude for Desktop (quit completely, e.g., Cmd+Q on macOS).

  4. Test in Claude:
    • Open Claude and click the “Search and tools” icon (slider icon).
    • You should see get_alerts and get_forecast listed.
    • Query examples:
      • “What are the active weather alerts in California?”
      • “What’s the weather forecast for latitude 37.77, longitude -122.41?” (San Francisco coords).
    • Claude will invoke the tools automatically and incorporate results into its response.
    • Note: This API only supports US locations.

Troubleshooting

This sets up a functional MCP server using plain Python. For TypeScript/Node.js alternatives or more examples, check the resources below.

References


Back

x-ai/grok-4-fast

Donate