Skip to main content
Glama

SMHI MCP Server

A Model Context Protocol (MCP) server that provides Swedish weather data through SMHI's open APIs, deployed as a Cloudflare Worker.

Deploy to Cloudflare Workers

🌀️ Features

  • Real-time Weather Data: Access current weather conditions from SMHI weather stations

  • Weather Forecasts: Get detailed forecasts for any location in Sweden

  • Station Search: Find weather stations by name using fuzzy matching

  • Multi-Resolution Data: Access hourly, daily, and monthly weather data

  • Historical Data: Query past weather records with pagination and date filtering

  • MCP Protocol: Full compatibility with Claude and other MCP clients

  • Serverless: Runs on Cloudflare Workers with global edge deployment

  • No API Keys Required: Uses SMHI's free open data APIs

  • Smart Caching: Multi-level caching with Cloudflare Cache API and R2 storage for optimal performance

  • Rate Limiting: Built-in request rate limiting to respect SMHI API limits

Forecasts use SMHI's SNOW1gv1 endpoint. The legacy PMP3gv2 forecast API was retired by SMHI on 2026-03-31.

Related MCP server: mcp-norwegian-weather

πŸš€ Live Demo

Deployed Server: https://smhi-mcp.hakan-3a6.workers.dev

πŸ“‹ Available Tools (16 Total)

Snowmobile Conditions Tools

Tool

Description

Parameters

list_snowmobile_conditions

List weather stations for snowmobile conditions by region with capability info

None

get_station_temperature

Get latest temperature reading from a specific station

station_id (string)

get_station_snow_depth

Get latest snow depth reading from a specific station

station_id (string)

get_weather_forecast

Get weather forecast for coordinates

lat (number), lon (number)

Legacy Tools (Deprecated)

Tool

Description

Parameters

list_temperature_stations

[DEPRECATED] Use list_snowmobile_conditions instead

None

list_snow_depth_stations

[DEPRECATED] Use list_snowmobile_conditions instead

None

Multi-Resolution Data Tools

Tool

Description

Parameters

get_station_precipitation

Get precipitation data with multiple resolutions

station_id, parameter (5=daily, 7=hourly, 14=15min, 23=monthly), period

get_temperature_multi_resolution

Get temperature data with multiple resolutions

station_id, parameter (1=hourly, 2=daily-mean, 19=min, 20=max, 22=monthly), period

get_station_metadata

Get detailed station metadata and available periods

station_id, parameter

Historical Data & Pagination

Tool

Description

Parameters

get_historical_data

Get historical data with pagination and date filtering. Reads the CSV archive for corrected-archive and the JSON feed for the latest-* periods, which SMHI does not publish as CSV

station_id, parameter, period, limit, cursor, reverse, fromDate, toDate

list_all_temperature_stations

Get all temperature stations with pagination

cursor (optional)

list_all_snow_depth_stations

Get all snow depth stations with pagination

cursor (optional)

list_all_precipitation_stations

Get all precipitation stations with pagination

parameter, cursor

Station Search Tools

Tool

Description

Parameters

search_stations_by_name

Search stations by name within a specific parameter type

query, parameter (1=temp, 5=precip, 8=snow), limit, threshold, active_only

search_stations_by_name_multi_param

Search stations by name across all parameter types

query, limit, threshold, active_only

get_stations_near_location

Find nearest stations to given coordinates

latitude, longitude, parameter, radius_km, limit, active_only

Search Parameters Details

  • query (required): Station name to search for (fuzzy matching)

  • parameter (search_stations_by_name only): Parameter type filter

    • 1 = Temperature stations

    • 5 = Daily precipitation stations

    • 7 = Hourly precipitation stations

    • 8 = Snow depth stations

  • limit (optional, default: 10): Maximum number of results to return

  • threshold (optional, default: 0.3): Minimum similarity score (0.0-1.0) for fuzzy matching

  • active_only (optional, default: true): Only return stations with recent data activity

Coordinate Search Parameters

  • latitude (required): Latitude in WGS84 decimal degrees (e.g., 65.353 for Adak)

  • longitude (required): Longitude in WGS84 decimal degrees (e.g., 18.5837 for Adak)

  • parameter (optional, default: "1"): Parameter type (1=temp, 5=precip, 8=snow)

  • radius_km (optional, default: 50): Maximum search radius in kilometers

  • limit (optional, default: 10): Maximum number of stations to return

  • active_only (optional, default: true): Only return active stations

πŸ› οΈ Quick Start

Option 1: Use the Deployed Server

Connect your MCP client to: https://smhi-mcp.hakan-3a6.workers.dev

Option 2: Deploy Your Own

# Clone the repository
git clone https://github.com/yourusername/smhi-mcp.git
cd smhi-mcp

# Install dependencies
npm install

# Create R2 bucket for caching (required)
wrangler r2 bucket create smhi-historical-data

# Deploy to Cloudflare Workers
npm run deploy

Option 3: Local Development

# Start local development server
npm run dev

# The server will be available at http://localhost:8787

πŸ“‘ MCP Protocol Examples

Initialize Connection

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {}
  }
}

List Available Tools

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list"
}

Get Weather Forecast (Stockholm)

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "get_weather_forecast",
    "arguments": {
      "lat": 59.3293,
      "lon": 18.0686
    }
  }
}

List Snowmobile Conditions Stations

{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tools/call",
  "params": {
    "name": "list_snowmobile_conditions",
    "arguments": {}
  }
}

Get Station Temperature

{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "tools/call",
  "params": {
    "name": "get_station_temperature",
    "arguments": {
      "station_id": "159880"
    }
  }
}

Search for Weather Stations

{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "tools/call",
  "params": {
    "name": "search_stations_by_name_multi_param",
    "arguments": {
      "query": "Stockholm",
      "limit": 5,
      "threshold": 0.3,
      "active_only": true
    }
  }
}

Find Nearest Stations by Coordinates

{
  "jsonrpc": "2.0",
  "id": 6,
  "method": "tools/call",
  "params": {
    "name": "get_stations_near_location",
    "arguments": {
      "latitude": 65.353,
      "longitude": 18.5837,
      "parameter": "1",
      "radius_km": 50,
      "limit": 5
    }
  }
}

Get Historical Data with Pagination

{
  "jsonrpc": "2.0",
  "id": 6,
  "method": "tools/call",
  "params": {
    "name": "get_historical_data",
    "arguments": {
      "station_id": "159880",
      "parameter": "1",
      "period": "corrected-archive",
      "limit": 20,
      "fromDate": "2024-01-01",
      "toDate": "2024-01-31"
    }
  }
}

πŸ§ͺ Testing

Using cURL

# Test MCP initialization
curl -X POST -H "Content-Type: application/json" \
  -d '{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2024-11-05", "capabilities": {}}}' \
  https://smhi-mcp.hakan-3a6.workers.dev

# List snowmobile conditions stations
curl -X POST -H "Content-Type: application/json" \
  -d '{"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "list_snowmobile_conditions", "arguments": {}}}' \
  https://smhi-mcp.hakan-3a6.workers.dev

# Test weather forecast for Stockholm
curl -X POST -H "Content-Type: application/json" \
  -d '{"jsonrpc": "2.0", "id": 4, "method": "tools/call", "params": {"name": "get_weather_forecast", "arguments": {"lat": 59.3293, "lon": 18.0686}}}' \
  https://smhi-mcp.hakan-3a6.workers.dev

# Search for weather stations
curl -X POST -H "Content-Type: application/json" \
  -d '{"jsonrpc": "2.0", "id": 5, "method": "tools/call", "params": {"name": "search_stations_by_name_multi_param", "arguments": {"query": "Stockholm", "limit": 3}}}' \
  https://smhi-mcp.hakan-3a6.workers.dev

# Find nearest stations to coordinates (Adak area)
curl -X POST -H "Content-Type: application/json" \
  -d '{"jsonrpc": "2.0", "id": 6, "method": "tools/call", "params": {"name": "get_stations_near_location", "arguments": {"latitude": 65.353, "longitude": 18.5837, "radius_km": 50}}}' \
  https://smhi-mcp.hakan-3a6.workers.dev

Using Make Commands

# Run built-in tests
make test-mcp
make test-forecast
make test-stations

Unit Tests

npm test          # vitest run

Covers transport behaviour (test/mcp-transport.test.js) and request accounting (test/request-limits.test.js). Both exist because of a real incident β€” see below.

πŸ›‘οΈ Protecting the request quota

The 2026-08 runaway incident

GET /mcp used to answer with a finite text/event-stream body: one data: line, then the stream closed. EventSource treats a closed stream as a dropped connection and reconnects after ~1s, forever. Three concurrent clients doing that generated ~420,000 Worker requests in four days against a 100,000/day account limit, and tripped Cloudflare's daily limit email.

The fix: a server with no server-initiated messages must answer 405 on GET, per the MCP Streamable HTTP spec. Conforming clients then stop retrying. test/mcp-transport.test.js locks this in.

What the in-Worker limiter can and cannot do

checkRequestLimits() now runs for every method, not just POST (it previously sat in the POST branch only, which is why the GET flood went unnoticed). But be clear about its scope:

An in-Worker limiter cannot protect the Cloudflare request quota. By the time it runs, the request is already billed. A 429 costs exactly as much quota as a 200.

Its real value is shedding load off the upstream SMHI APIs and failing loudly rather than silently. It is also backed by caches.default, which is per-colo, so the count is per-edge-location, not global.

Actually protecting the quota

Requests must be rejected before they reach the Worker:

  1. Put the service on a real zone (not *.workers.dev) and add a WAF rate-limiting rule. Traffic blocked at the WAF is not billed as a Worker invocation.

  2. Disable the workers.dev route once a custom domain is in place, so the unprotected hostname stops being reachable.

  3. Note that the endpoint currently has no authentication β€” anyone with the URL can invoke it.

πŸ—οΈ Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   MCP Client    β”‚    β”‚  Cloudflare     β”‚    β”‚   SMHI APIs     β”‚
β”‚   (Claude)      │◄──►│    Worker       │◄──►│  (opendata)     β”‚
β”‚                 β”‚    β”‚  (smhi-mcp)     β”‚    β”‚                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The server acts as a bridge between MCP clients and SMHI's weather APIs:

  1. MCP Client sends JSON-RPC requests

  2. Cloudflare Worker processes requests and calls SMHI APIs

  3. SMHI APIs provide real-time weather data

  4. Worker formats response according to MCP protocol

⚑ Performance & Caching

The server implements a multi-level caching strategy for optimal performance:

Cloudflare Cache API

  • Current weather data: 15 minutes TTL

  • Weather forecasts: 30 minutes TTL

  • Station metadata: 1 week TTL

  • Historical data: 24 hours TTL

R2 Storage (CSV Data)

  • Standard CSV files: 24 hours TTL

  • Recent year data: 1 hour TTL (more volatile)

  • Historical data (>2 years): 1 week TTL (stable)

Rate Limiting

  • 30 requests per minute maximum to respect SMHI API limits

  • Automatic request throttling with user-friendly error messages

Configuration Requirements

# wrangler.toml - R2 bucket for CSV caching
[[r2_buckets]]
binding = "HISTORICAL_DATA"
bucket_name = "smhi-historical-data"

πŸ“Š Data Sources

All weather data comes from SMHI (Swedish Meteorological and Hydrological Institute):

  • Observations API: Real-time weather station data

  • Forecast API: Weather predictions up to 10 days

  • Station Metadata: Information about monitoring stations

Sample Weather Stations

Station ID

Name

Location

Note

159880

Arvidsjaur

Northern Sweden

Temperature & precipitation

155960

TΓ€rnaby/Hemavan

Mountain region

Temperature data

155940

MosekΓ€lla/Hemavan

Mountain region

Same location, different parameter names

132170

Storlien-Storvallen

Norwegian border

Alpine weather station

188850

KatterjΓ₯kk/RiksgrΓ€nsen

Arctic region

Northernmost station

Note: Some stations have different names depending on the parameter type. Use search_stations_by_name_multi_param to find stations across all data types.

Active Filtering: By default, search tools only return stations with recent data activity. Set active_only: false to include all stations, including inactive ones.

πŸ”§ Configuration

Required Configuration

The worker requires an R2 bucket for CSV caching:

# wrangler.toml - Required R2 configuration
[[r2_buckets]]
binding = "HISTORICAL_DATA" 
bucket_name = "smhi-historical-data"

Optional Environment Variables

# wrangler.toml - Optional configuration
[env.production.vars]
# Add public configuration here

# Optional secrets for authentication
[[env.production.secrets]]
API_KEY = "your-api-key-for-auth"

Authentication (Optional)

To enable API key authentication, set the API_KEY secret:

wrangler secret put API_KEY

πŸ“‹ Development Commands

# Install dependencies
make install

# Start development server
make dev

# Deploy to Cloudflare
make deploy

# View logs
make logs

# Show deployment status
make status

# Run tests
make test-mcp

Modular Architecture

The codebase has been refactored into a modular structure:

src/
β”œβ”€β”€ api/          # SMHI API integration
β”œβ”€β”€ config/       # Constants and configuration
β”œβ”€β”€ data/         # Station data and presets
β”œβ”€β”€ handlers/     # WebSocket and SSE handlers
β”œβ”€β”€ mcp/          # MCP protocol server
β”œβ”€β”€ middleware/   # Rate limiting and middleware
β”œβ”€β”€ services/     # Weather data services
β”œβ”€β”€ tools/        # MCP tool definitions
└── utils/        # Caching and utility functions

🌍 Claude Integration

To use this server with Claude:

  1. Desktop App: Add the server URL in Claude's MCP settings

  2. API: Include the server in your MCP client configuration

  3. Custom Integration: Use the JSON-RPC protocol directly

Example Claude conversations:

You: "Show me weather stations for snowmobile conditions"
Claude: "Let me get the snowmobile conditions monitoring stations for you."
[Uses list_snowmobile_conditions]
Claude: "Here are 18 weather stations organized by region for snowmobile conditions. The Arctic/Mountain region has KatterjΓ₯kk/RiksgrΓ€nsen with both temperature and snow depth data..."

You: "What's the current temperature in Arvidsjaur?"
Claude: "Let me check the current temperature in Arvidsjaur for you."
[Uses get_station_temperature with station_id "159880"]
Claude: "The current temperature in Arvidsjaur is 15.8Β°C."

You: "Find weather stations near Stockholm"
Claude: "Let me search for weather stations near Stockholm."
[Uses search_stations_by_name_multi_param with query "Stockholm"]
Claude: "I found several weather stations near Stockholm, including Stockholm A (98230) and Stockholm-Observatoriekullen (98210)."

You: "Show me temperature data for MosekΓ€lla from January 2024"
Claude: "Let me search for MosekΓ€lla and get the temperature data."
[Uses search_stations_by_name_multi_param with query "MosekΓ€lla"]
[Uses get_historical_data with station_id "155940", fromDate "2024-01-01", toDate "2024-01-31"]
Claude: "Found MosekΓ€lla (station 155940) - here's the temperature data for January 2024..."

You: "Find weather stations near Adak (65.353Β°N, 18.5837Β°E)"
Claude: "Let me find the nearest weather stations to those coordinates."
[Uses get_stations_near_location with latitude 65.353, longitude 18.5837, radius_km 50]
Claude: "I found 4 temperature stations within 50km: MalΓ₯ (20.5km), MalΓ₯-BrΓ€nnan A (22.4km), BuresjΓΆn A (40.6km), and Arvidsjaur A (41.3km)."

🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Development Setup

  1. Fork the repository

  2. Create a feature branch: git checkout -b feature/amazing-feature

  3. Make your changes

  4. Test locally: npm run dev

  5. Deploy and test: npm run deploy

  6. Submit a pull request

πŸ“„ License

This project is open source and available under the MIT License.

πŸ“ž Support


Built with ❀️ for the MCP ecosystem | Powered by SMHI Open Data | Deployed on Cloudflare Workers

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    An MCP server that aggregates data from six weather sources to provide current conditions, forecasts, air quality, and aviation METAR information, specifically optimized for European and Nordic locations. It supports multi-source data merging and functions without API keys for several integrated providers.
    7
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    An MCP server that provides current weather conditions and hourly forecasts for locations across Norway using the MET Norway Locationforecast 2.0 API. It features coordinate support, geocoding for any Norwegian location, and built-in configurations for major cities.
    2
    11
    MIT