@cyanheads/open-meteo-mcp-server
Geocode places, fetch global weather forecasts, ERA5 historical climate, marine conditions, air quality, and terrain elevation via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://open-meteo.caseyjhand.com/mcp
Tools
Ten tools covering geocoding, weather forecasts, historical climate, probabilistic ensemble forecasts, marine conditions, air quality, terrain elevation, river discharge, and SQL analytics over large datasets:
| Tool | Description |
|---|---|
openmeteo_geocode | Resolve a place name to ranked coordinate matches with country, region, elevation, timezone, and population |
openmeteo_get_forecast | Weather forecast for coordinates: hourly and/or daily variables for up to 16 days, with optional recent past data |
openmeteo_get_historical | Historical weather from the ERA5 reanalysis archive (1940–present); large ranges spill to DataCanvas |
openmeteo_get_marine | Marine forecast for coastal or ocean coordinates: wave height, period, direction, swell, and sea-surface temperature |
openmeteo_get_air_quality | Modeled CAMS air quality forecast: PM2.5, PM10, NO2, O3, CO, dust, pollen, and European/US AQI indices |
openmeteo_get_elevation | Terrain elevation from Copernicus DEM (~90m resolution) for up to 100 coordinate pairs per call |
openmeteo_get_ensemble | Probabilistic ensemble forecast: per-member hourly/daily time series (up to 51 members, 16 days) for exceedance and uncertainty analysis |
openmeteo_get_flood | GloFAS river discharge forecast (up to 210 days) and reanalysis (1984–present); coordinate-based, snaps to nearest river |
openmeteo_dataframe_describe | List tables and columns on a DataCanvas staged by openmeteo_get_historical or openmeteo_get_ensemble |
openmeteo_dataframe_query | Run a read-only SQL SELECT against tables staged on a DataCanvas |
openmeteo_geocode
Resolve a free-text place name to ranked coordinate matches. Required first step for name-based queries — all weather tools accept latitude/longitude, not place names.
- Returns name, country, admin1/admin2, latitude, longitude, elevation, IANA timezone, population, and GeoNames feature code
- Up to 10 ranked matches (default 5), most relevant first — use
countfor disambiguation when common names (e.g., "Springfield") may match multiple cities - Pass the timezone from a geocode result directly to weather tools as the
timezoneparameter - Returns an empty
resultsarray (not an error) when no places match
openmeteo_get_forecast
Weather forecast for a coordinate pair with hourly and/or daily variable selection.
- Up to 16 forecast days ahead (
forecast_days 1–16, default 7) past_days(0–92) covers recent history via the forecast model — use instead ofopenmeteo_get_historicalfor dates within the last ~5 days to avoid ERA5 lag- Common hourly variables:
temperature_2m,precipitation,wind_speed_10m,relative_humidity_2m,cloud_cover,uv_index,apparent_temperature,precipitation_probability,weather_code,surface_pressure,visibility,wind_direction_10m,wind_gusts_10m,dew_point_2m - Common daily variables:
temperature_2m_max,temperature_2m_min,precipitation_sum,wind_speed_10m_max,sunrise,sunset,uv_index_max,precipitation_hours,weather_code - At least one of
hourly_variablesordaily_variablesis required - Configurable temperature unit (Celsius/Fahrenheit), wind speed unit (km/h, mph, m/s, knots), and precipitation unit (mm/inch)
- Reshapes the API's columnar response into per-timestamp records with a parallel
hourly_units/daily_unitsmap
openmeteo_get_historical
Historical weather from the ERA5 reanalysis archive, covering 1940 to approximately 5 days ago.
- Requires
start_dateandend_date(YYYY-MM-DD); ERA5 has a variable ~1–5 day lag - Same variable vocabulary as
openmeteo_get_forecast— past and forecast data are directly comparable on one schema - At least one of
hourly_variablesordaily_variablesis required - Large date ranges (multi-year hourly queries) spill to DataCanvas when
CANVAS_PROVIDER_TYPE=duckdb— output includescanvas_idandtruncated: truewhen the inline record limit is exceeded - Spill → query workflow: call
openmeteo_dataframe_describewith thecanvas_idto list tables, thenopenmeteo_dataframe_queryto run SQL SELECT against the staged data
openmeteo_get_marine
Marine weather forecast for coastal and open-ocean coordinates.
- Up to 7 forecast days (
forecast_days 1–7, default 7) - Common hourly variables:
wave_height,wave_direction,wave_period,wind_wave_height,wind_wave_direction,wind_wave_period,swell_wave_height,swell_wave_direction,swell_wave_period - Common daily variables:
wave_height_max,wave_direction_dominant,wave_period_max - At least one of
hourly_variablesordaily_variablesis required - Inland or sheltered-water points return near-zero wave values (physically correct);
ocean_current_velocityis null for non-open-ocean coordinates
openmeteo_get_air_quality
Modeled CAMS air quality forecast. Forecast-only — there is no historical archive for CAMS data.
- Up to 7 forecast days (
forecast_days 1–7, default 5) - Common variables:
pm2_5,pm10,carbon_monoxide,nitrogen_dioxide,sulphur_dioxide,ozone,dust,european_aqi,us_aqi,alder_pollen,birch_pollen,grass_pollen,mugwort_pollen,olive_pollen,ragweed_pollen - At least one variable from
hourly_variablesis required - Grid-modeled data from CAMS — resolution is coarser than ground stations; for measured station readings, cross-reference
openaq-mcp-server - Output includes
data_source: "CAMS"to distinguish modeled from measured data
openmeteo_get_elevation
Terrain elevation from the Copernicus Digital Elevation Model (~90m resolution).
- Accepts parallel
latitudes[]andlongitudes[]arrays; both must have equal length (up to 100 pairs) - Returns results in input order:
{ latitude, longitude, elevation_m } - Useful for geographic context, elevation-adjusted weather interpretation, or route planning
openmeteo_get_ensemble
Probabilistic ensemble weather forecast exposing all individual model member trajectories.
- Up to 16 forecast days (
forecast_days 1–16, default 7) with optionalpast_days(0–92) - Each requested variable is returned as per-member columns:
temperature_2m_member01,temperature_2m_member02, … Use the spread across members to compute exceedance probabilities, interquantile ranges, and decision thresholds - Available ensemble models:
ecmwf_ifs025(51 members, global 0.25°),gfs025(31 members),icon_seamless(40 members, global/Europe blend),gem_global(21 members). Omitmodelsto use the API default - Response includes
model(system used) andmember_count(number of members) - At least one of
hourly_variablesordaily_variablesis required - Large multi-member, multi-day pulls spill to DataCanvas when
CANVAS_PROVIDER_TYPE=duckdb— output includescanvas_idandtruncated: true; query withopenmeteo_dataframe_query - Configurable temperature, wind speed, and precipitation units
openmeteo_get_flood
GloFAS (Global Flood Awareness System) river discharge forecast and reanalysis via the Open-Meteo Flood API.
- Coordinate-based — no river ID needed; the API snaps to the nearest river grid point automatically
- Forecast horizon up to 210 days; reanalysis history from 1984-01-01 to present
- Use
forecast_daysfor future outlook,start_date/end_datefor historical analysis; both can be combined - Available daily variables:
river_discharge(ensemble mean),river_discharge_mean,river_discharge_min,river_discharge_max,river_discharge_median,river_discharge_p25(25th percentile),river_discharge_p75(75th percentile) — all in m³/s - Returns null for coordinates outside GloFAS coverage (e.g., open ocean or areas without river network data)
- Discharge values reflect the GloFAS ensemble — percentile variables expose the uncertainty spread
Features
Built on @cyanheads/mcp-ts-core:
- Declarative tool definitions — single file per tool, framework handles registration and validation
- Unified error handling — handlers throw, framework catches, classifies, and formats
- Pluggable auth:
none,jwt,oauth - Swappable storage backends:
in-memory,filesystem,Supabase,Cloudflare KV/R2/D1 - Structured logging with optional OpenTelemetry tracing
- STDIO and Streamable HTTP transports
Open-Meteo–specific:
- No API key required for non-commercial use — zero-config out of the box
- Self-contained geocoding:
openmeteo_geocoderesolves place names so agents don't need a separate geocoder - ERA5 archive from 1940 to present with same variable schema as the forecast API — direct past/forecast comparisons on one schema
- Automatic columnar-to-record reshape: Open-Meteo returns parallel time/variable arrays; handlers convert to per-timestamp records with a
*_unitsmap - DataCanvas spillover for
openmeteo_get_historicalandopenmeteo_get_ensemble: large queries that exceed the inline record limit register a DuckDB dataframe for SQL querying - Configurable base URLs for all seven API endpoints (forecast, archive, marine, air quality, geocoding, ensemble, flood) — override for testing or self-hosted deployments
- Attribution: Weather data by Open-Meteo.com (CC BY 4.0). Non-commercial use is free and keyless; commercial use requires Open-Meteo's paid API tier (~10,000 req/day, 5,000/hour fair-use ceiling for non-commercial)
Agent-friendly output:
- Geocode-first workflow:
openmeteo_geocodereturns the IANA timezone alongside coordinates — pass it directly astimezoneto any weather tool - Recovery hints on all error contracts — invalid variable names surface correction guidance with common variable examples
- Coordinate snapping transparency — responses echo the snapped
latitude/longitude(Open-Meteo quantizes to the nearest model grid point) so agents can reason about grid alignment data_source: "CAMS"label on air quality results distinguishes modeled forecast data from measured station readings
Getting started
Public Hosted Instance
A public instance is available at https://open-meteo.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"open-meteo-mcp-server": {
"type": "streamable-http",
"url": "https://open-meteo.caseyjhand.com/mcp"
}
}
}
Self-Hosted / Local
Add the following to your MCP client configuration file.
{
"mcpServers": {
"open-meteo-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/open-meteo-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"open-meteo-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/open-meteo-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"open-meteo-mcp-server": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/open-meteo-mcp-server:latest"]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
Prerequisites
- Bun v1.3.0 or higher (or Node.js v24+).
- No API key required. Non-commercial use is free and keyless.
- Commercial use requires Open-Meteo's paid API tier.
Installation
- Clone the repository:
git clone https://github.com/cyanheads/open-meteo-mcp-server.git
- Navigate into the directory:
cd open-meteo-mcp-server
- Install dependencies:
bun install
Configuration
All configuration is validated at startup via Zod schemas. No API key is required for non-commercial use — all variables are optional.
| Variable | Description | Default |
|---|---|---|
MCP_TRANSPORT_TYPE | Transport: stdio or http | stdio |
MCP_HTTP_PORT | HTTP server port | 3010 |
MCP_HTTP_ENDPOINT_PATH | HTTP endpoint path | /mcp |
MCP_PUBLIC_URL | Public origin for TLS-terminating reverse-proxy deployments | — |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth | none |
MCP_LOG_LEVEL | Log level (debug, info, warning, error) | info |
MCP_GC_PRESSURE_INTERVAL_MS | Opt-in forced-GC interval (ms, Bun only). Set to 60000 if heap growth is observed under sustained HTTP traffic. | 0 |
LOGS_DIR | Directory for log files (Node.js only) | <project-root>/logs |
STORAGE_PROVIDER_TYPE | Storage backend: in-memory, filesystem, supabase, cloudflare-kv/r2/d1 | in-memory |
CANVAS_PROVIDER_TYPE | Canvas engine for openmeteo_get_historical spillover: duckdb or none | none |
OPEN_METEO_API_BASE_URL | Override for the main forecast + elevation API | https://api.open-meteo.com |
OPEN_METEO_ARCHIVE_BASE_URL | Override for the ERA5 historical archive API | https://archive-api.open-meteo.com |
OPEN_METEO_MARINE_BASE_URL | Override for the marine forecast API | https://marine-api.open-meteo.com |
OPEN_METEO_AIR_QUALITY_BASE_URL | Override for the CAMS air quality API | https://air-quality-api.open-meteo.com |
OPEN_METEO_GEOCODING_BASE_URL | Override for the geocoding API | https://geocoding-api.open-meteo.com |
OTEL_ENABLED | Enable OpenTelemetry tracing and metrics | false |
See .env.example for the full list of optional overrides.
Running the server
Local development
-
Build and run the production version:
# One-time build bun run rebuild # Run the built server bun run start:http # or bun run start:stdio -
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security bun run test # Vitest test suite
Docker
docker build -t open-meteo-mcp-server .
docker run --rm -p 3010:3010 open-meteo-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/open-meteo-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
Project structure
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools, initializes the Open-Meteo service |
src/config | Server-specific environment variable parsing and validation with Zod |
src/mcp-server/tools/definitions | Tool definitions (*.tool.ts) — one file per tool; includes dataframe-describe.tool.ts and dataframe-query.tool.ts |
src/services/open-meteo | Open-Meteo HTTP client wrapping all six endpoints with retry, error classification, and columnar reshape |
src/services/canvas-accessor.ts | DataCanvas accessor for openmeteo_get_historical spillover |
tests/ | Unit and integration tests mirroring src/ |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches — no
try/catchin tool logic - Use
ctx.logfor request-scoped logging,ctx.statefor tenant-scoped storage - Register new tools in the
tools[]array insrc/index.ts - Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
Contributing
Issues and pull requests are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
License
Apache-2.0 — see LICENSE for details.
Weather data by Open-Meteo.com — licensed CC BY 4.0.