MCP, Real-Time·· by Michael Wybraniec

MCP Servers: Connecting AI to Real-Time Data

Learn how to create a Model Context Protocol (MCP) server using Python and the Open-Meteo API to provide weather data to language models like GPT-4 and Claude.

MCP servers are revolutionizing how language models interact with external data sources. Think of them as sophisticated plugins that extend AI capabilities beyond their training data. In this comprehensive tutorial, we'll build a weather MCP server that connects Claude, GPT-4, or any LLM to real-time weather information using Python and the Open-Meteo API.

MCP servers extend language model capabilities by connecting them to data sources and services. They're agnostic applications that facilitate integration with any data or service you can imagine. Think function calling, but the functions are plugins that can do virtually anything.

MCP servers expose three main primitives:

  • Resources (client-controlled): Passive data exposure for context
  • Tools (model-controlled): Executable functionality for actions
  • Prompts (user-controlled): Reusable workflows and templates

Weather data provides an excellent example for MCP server development because it demonstrates real-world API integration with practical applications. Language models excel at transforming raw weather data (temperature, wind speed, humidity) into natural language descriptions and actionable recommendations.

We'll use the Open-Meteo API because it's:

  • Free for non-commercial use
  • No API key required
  • Easily configurable through query parameters
  • Perfect for LLM integration

Before diving into code, ensure you have the necessary tools installed. We'll use uv, a Rust-based Python package manager that makes dependency management seamless.

# Create project directory
mkdir mcp-server-weather
cd mcp-server-weather

# Initialize uv project
uv init

# Create and activate virtual environment
uv venv
source .venv/bin/activate

# Install dependencies
uv add "mcp[cli]" httpx
  [{"type": "text", "text": "Forecast for 40.7128, -74.006:\n\nToday:\nTemperature: 66\u00b0F\nWind: 13 to 16 mph W\nMostly Sunny\n---\nTonight:\nTemperature: 55\u00b0F\nWind: 5 to 13 mph W\nPartly Cloudy\n---\nMonday:\nTemperature: 71\u00b0F\nWind: 8 mph W\nSunny\n---\nMonday Night:\nTemperature: 59\u00b0F\nWind: 3 to 8 mph W\nMostly Clear\n---\nTuesday:\nTemperature: 77\u00b0F\nWind: 3 to 7 mph SW\nSunny\n---\nTuesday Night:\nTemperature: 65\u00b0F\nWind: 5 to 8 mph S\nMostly Clear\n---\nWednesday:\nTemperature: 82\u00b0F\nWind: 5 to 12 mph SW\nSunny\n---\nWednesday Night:\nTemperature: 69\u00b0F\nWind: 8 to 12 mph SW\nMostly Clear\n---\nThursday:\nTemperature: 85\u00b0F\nWind: 7 to 12 mph SW\nMostly Sunny\n---\nThursday Night:\nTemperature: 72\u00b0F\nWind: 7 to 12 mph SW\nPartly Cloudy\n---\nFriday:\nTemperature: 82\u00b0F\nWind: 7 to 12 mph SW\nChance Rain Showers\n---\nFriday Night:\nTemperature: 70\u00b0F\nWind: 7 to 12 mph SW\nChance Rain Showers\n---\nSaturday:\nTemperature: 76\u00b0F\nWind: 9 mph W\nChance Rain Showers\n---\nSaturday Night:\nTemperature: 67\u00b0F\nWind: 6 to 9 mph NW\nChance Rain Showers\n---", "uuid": "fb196843-938b-4a75-b1a5-b660892f9817"}]

Here you can see an MCP Server get-weather API response!

Start by creating the server scaffolding. This establishes the foundation for our MCP server using FastMCP, which simplifies server creation and management.

FastMCP provides a streamlined interface for defining tools, resources, and prompts. By leveraging its built-in features, you can focus on implementing functionality without worrying about low-level server details.

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

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

# Constants
OPENMETEO_API_BASE = "https://api.open-meteo.com/v1"
USER_AGENT = "weather-app/1.0"

# Helper function for API requests
async def make_openmeteo_request(url: str) -> dict[str, Any] | None:
    """Make a request to the Open-Meteo API with proper error handling."""
    headers = {
        "User-Agent": USER_AGENT,
        "Accept": "application/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

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

Tools are the heart of MCP servers. They expose executable functionality that language models can invoke. Our weather tool will fetch current conditions and forecasts based on geographic coordinates.

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

    Args:
        latitude: Latitude of the location
        longitude: Longitude of the location
    """
    
    url = f"{OPENMETEO_API_BASE}/forecast?latitude={latitude}&longitude={longitude}&current=temperature_2m,is_day,showers,cloud_cover,wind_speed_10m,wind_direction_10m,pressure_msl,snowfall,precipitation,relative_humidity_2m,apparent_temperature,rain,weather_code,surface_pressure,wind_gusts_10m"
    
    data = await make_openmeteo_request(url)

    if not data:
        return "Unable to fetch current weather data for this location."

    return data

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

    Args:
        latitude: Latitude of the location
        longitude: Longitude of the location  
        days: Number of days to forecast (1-16)
    """
    
    url = f"{OPENMETEO_API_BASE}/forecast?latitude={latitude}&longitude={longitude}&daily=temperature_2m_max,temperature_2m_min,precipitation_sum,wind_speed_10m_max,weather_code&forecast_days={days}"
    
    data = await make_openmeteo_request(url)
    
    if not data:
        return "Unable to fetch forecast data for this location."
    
    return data

The MCP Inspector provides a web-based interface for testing your server before integrating it with language models. This tool is invaluable for debugging and validation.

# Start MCP server in development mode
mcp dev server.py

# Open browser to http://localhost:5173
# Connect to the server
# Navigate to Tools tab
# Test get_current_weather with coordinates
# Example: latitude=63.4463991, longitude=10.8127596

When building MCP servers, resist the urge to format returned data within your tools. Instead, return raw data and let the language model process and format it appropriately. This approach leverages the LLM's natural language capabilities while maintaining data integrity.

Best Practices:

  • Return complete datasets rather than filtered summaries
  • Use descriptive tool names and documentation
  • Implement proper error handling for API failures
  • Keep tools focused on single responsibilities
  • Leverage async/await for better performance

Once you have a basic weather server running, consider adding these enhancements:

Location Services: Integrate the Open-Meteo Geocoding API to convert city names to coordinates, reducing reliance on the LLM for coordinate generation.

Historical Data: Add tools for accessing historical weather patterns and climate data.

Alerts and Warnings: Implement weather alert monitoring for severe weather conditions.

Multiple Locations: Support batch requests for comparing weather across multiple locations.

@mcp.tool()
async def get_location(city: str, country: str = "") -> str:
    """Get coordinates for a city using geocoding.
    
    Args:
        city: Name of the city
        country: Optional country name for disambiguation
    """
    
    query = f"{city},{country}" if country else city
    url = f"https://geocoding-api.open-meteo.com/v1/search?name={query}&count=1&language=en&format=json"
    
    data = await make_openmeteo_request(url)
    
    if not data or not data.get('results'):
        return "Location not found."
    
    return data['results'][0]

Your MCP server can integrate with various language models including Claude Desktop, GPT-4 through compatible clients, and other MCP-compatible applications. The server communicates through standard input/output (stdio), making it universally compatible.

To use with Claude Desktop, add your server configuration to the MCP settings file, typically located at ~/Library/Application Support/Claude/claude_desktop_config.json on macOS.

{
  "mcpServers": {
    "weather": {
      "command": "uv",
      "args": ["run", "python", "server.py"],
      "cwd": "/path/to/your/mcp-server-weather"
    }
  }
}

Building MCP servers opens new possibilities for AI integration with real-world data sources. This weather server demonstrates fundamental concepts that apply to any external API or service integration. The Model Context Protocol represents a significant step toward more capable and connected AI systems.

The complete source code for this tutorial is available in the exercise files repository. Experiment with different APIs, add new tools, and explore the endless possibilities of MCP server development.

Michael Wybraniec

Michael Wybraniec

Freelance, MCP Servers, Full-Stack Dev, Architecture