After diving deep into Anthropic’s Model Context Protocol (MCP) documentation, I decided to get my hands dirty and build a weather server following their comprehensive tutorial. This experience gave me valuable insights into how MCP bridges the gap between AI models and external tools, and I’m excited to share this practical journey with you.

What is MCP and Why Should You Care?

The Model Context Protocol is Anthropic’s solution to a fundamental challenge in AI development: how to safely and efficiently connect AI models to external tools and data sources. Instead of building custom integrations for every tool, MCP provides a standardized protocol that allows any compatible client to connect to any compatible server.

Think of it as a universal adapter for AI tools. Just like USB-C standardized device connections, MCP is standardizing AI-tool connections.

The Project: Building a Weather Server

Following Anthropic’s tutorial, I built a weather server that exposes two essential tools:

  • get-alerts: Fetches weather alerts for US states
  • get-forecast: Retrieves detailed weather forecasts for specific coordinates

The server integrates with the US National Weather Service API, providing real-time weather data that Claude can access and interpret.

My Setup Journey

Prerequisites and Environment Setup

I started with a clean Python environment. Here’s what I needed:

# Install uv (the modern Python package manager)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Create and set up the project
uv init weather-server
cd weather-server
uv venv
source .venv/bin/activate
uv add "mcp[cli]" httpx

Pro tip: I initially tried using pip, but uv proved much faster and more reliable for dependency management.

Importing packages and setting up the instance

The beauty of MCP lies in its simplicity. Using the FastMCP framework, I created a server with minimal boilerplate:

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

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

# Constants
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"

Helper functions

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')}
"""

Implementing the Weather Tools

The tutorial’s approach to tool implementation was particularly elegant. Each tool is decorated with @mcp.tool() and uses type hints for automatic schema generation:

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

Running the server

if __name__ == "__main__":
    # Initialize and run the server
    mcp.run(transport='stdio')

Testing and Integration

Local Testing

Before integrating with Claude Desktop, I tested the server locally:

uv run weather.py

The server runs on stdio, making it perfect for MCP’s communication protocol.

Claude Desktop Integration

This was where the magic happened. Configuring Claude Desktop required editing the configuration file (On my macbook, it’s located at ~/Library/Application\ Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "weather": {
      "command": "uv",
      "args": [
        "--directory",
        "/absolute/path/to/weather-server",
        "run",
        "weather.py"
      ]
    }
  }
}

Critical lesson: Always use absolute paths. Relative paths caused hours of debugging frustration.

Real-World Testing Results

After configuration, I tested the integration with various queries:

Successful Queries

  • “What’s the weather forecast for San Francisco?” ✅
  • “Are there any weather alerts in Texas?” ✅
  • “Give me the 5-day forecast for coordinates 40.7128, -74.0060” ✅

Insights from Testing

  1. Geographic Limitations: The NWS API only works for US locations. International queries fail gracefully with informative error messages.

  2. Response Quality: Claude effectively interprets the structured weather data and presents it in conversational format.

  3. Tool Selection: Claude intelligently chooses between the alerts and forecast tools based on user intent.

Lessons Learned and Best Practices

1. Configuration is Critical

The most common issues I encountered were configuration-related. Key tips:

  • Always restart Claude Desktop after config changes
  • Use absolute paths in server configurations
  • Verify JSON syntax in configuration files

2. API Design Matters

The NWS API’s structure influenced my tool design. Key considerations:

  • Clear parameter descriptions help Claude make better tool choices
  • Consistent error messaging improves user experience
  • Structured responses are easier for Claude to interpret

3. Debugging Strategies

When things go wrong:

  • Check Claude Desktop logs: ~/Library/Logs/Claude/mcp*.log
  • Test the server independently before integration
  • Validate JSON configurations with online validators

Future work

The tutorial’s foundation makes extensions straightforward. I’m considering:

  • More tools: Adding tools for this MCP server
  • Other languages: Implementing the server in other programming languages like Go or Rust
  • MCP Client: Building a custom MCP client to interact with MCP servers

Conclusion

Since Anthropic released the MCP protocol in November, I’ve been closely following the industry’s response to it. To be honest, I believe the industry has somewhat underestimated the significance of the MCP protocol. I am confident that MCP will become the de facto standard in this space.

Building this weather server was more than just following a tutorial—it was a glimpse into the future of AI tool integration. The combination of MCP’s standardized protocol, Claude’s intelligent tool selection, and practical real-world APIs creates a powerful foundation for AI-enhanced applications.

The weather server demonstrates how MCP can transform complex API interactions into simple, conversational experiences. Instead of remembering API endpoints, query parameters, and response formats, users can simply ask “What’s the weather like?” and get intelligent, contextual responses.

If you’re building AI applications that need external data or tools, invest time in understanding MCP. The initial learning curve pays dividends in reduced complexity and improved user experience.

The future of AI isn’t just about better models—it’s about better integration between AI and the tools we use every day. MCP is a significant step in that direction, and this weather server project shows just how accessible that future can be.