
Wraps Open-Meteo's free weather APIs with eight MCP tools covering geocoding, 16-day forecasts, ERA5 historical climate back to 1940, marine conditions, CAMS air quality, and Copernicus terrain elevation. You geocode a place name to coordinates, then pull hourly or daily variables like temperature, precipitation, wind, wave height, or PM2.5. Historical queries over large date ranges spill to DataCanvas with SQL query tools for analysis. Supports both stdio and streamable HTTP transports, with a public hosted instance at open-meteo.caseyjhand.com. Useful when you need global weather context without API keys, or want to compare forecast models against reanalysis data in a single schema.
claude mcp add --transport http open-meteo https://open-meteo.caseyjhand.com/mcpRun in your terminal. Add --scope user to make it available in every project.
Review the command, arguments, and environment values before installing — MCP servers run with your local permissions.
Verified live against the running server on Jun 10, 2026.
openmeteo_geocodeResolve a place name to ranked coordinate matches with country, region, elevation, timezone, and population. Required prerequisite for name-based queries — all weather tools take latitude/longitude, not place names. Returns up to 10 matches ranked by population/relevance; use...3 paramsResolve a place name to ranked coordinate matches with country, region, elevation, timezone, and population. Required prerequisite for name-based queries — all weather tools take latitude/longitude, not place names. Returns up to 10 matches ranked by population/relevance; use...
countintegerlanguagestringname*stringopenmeteo_get_elevationTerrain elevation from the Copernicus Digital Elevation Model (~90m resolution) for one or more coordinate pairs. Accepts up to 100 pairs per call. Useful for geographic context, elevation-adjusted weather interpretation, or route planning.2 paramsTerrain elevation from the Copernicus Digital Elevation Model (~90m resolution) for one or more coordinate pairs. Accepts up to 100 pairs per call. Useful for geographic context, elevation-adjusted weather interpretation, or route planning.
latitudes*arraylongitudes*arrayopenmeteo_get_forecastWeather forecast for coordinates: hourly and/or daily variables for up to 16 days ahead, with optional past_days (up to 92) for recent history. Use past_days instead of openmeteo_get_historical for dates within the last 1–5 days, since ERA5 has a variable lag. Returns per-time...10 paramsWeather forecast for coordinates: hourly and/or daily variables for up to 16 days ahead, with optional past_days (up to 92) for recent history. Use past_days instead of openmeteo_get_historical for dates within the last 1–5 days, since ERA5 has a variable lag. Returns per-time...
daily_variablesarrayforecast_daysintegerhourly_variablesarraylatitude*numberlongitude*numberpast_daysintegerprecipitation_unitstringmm · inchdefault: mmtemperature_unitstringcelsius · fahrenheitdefault: celsiustimezonestringwind_speed_unitstringkmh · mph · ms · kndefault: kmhopenmeteo_get_historicalHistorical weather from the ERA5 reanalysis archive (1940–present). Requires start_date and end_date (ISO 8601 date, e.g., "2024-07-01"). ERA5 has a variable lag of up to ~5 days — for dates within the last week, use openmeteo_get_forecast with past_days instead. Uses the same...11 paramsHistorical weather from the ERA5 reanalysis archive (1940–present). Requires start_date and end_date (ISO 8601 date, e.g., "2024-07-01"). ERA5 has a variable lag of up to ~5 days — for dates within the last week, use openmeteo_get_forecast with past_days instead. Uses the same...
canvas_idstringdaily_variablesarrayend_date*stringhourly_variablesarraylatitude*numberlongitude*numberprecipitation_unitstringmm · inchdefault: mmstart_date*stringtemperature_unitstringcelsius · fahrenheitdefault: celsiustimezonestringwind_speed_unitstringkmh · mph · ms · kndefault: kmhopenmeteo_get_marineMarine weather forecast for a coastal or ocean coordinate: wave height, wave period, wave direction, wind-wave height, swell height, sea-surface temperature. Forecast horizon up to 7 days. Returns per-timestamp records — each entry contains a "time" field plus one key per requ...6 paramsMarine weather forecast for a coastal or ocean coordinate: wave height, wave period, wave direction, wind-wave height, swell height, sea-surface temperature. Forecast horizon up to 7 days. Returns per-timestamp records — each entry contains a "time" field plus one key per requ...
daily_variablesarrayforecast_daysintegerhourly_variablesarraylatitude*numberlongitude*numbertimezonestringopenmeteo_get_air_qualityModeled CAMS (Copernicus Atmosphere Monitoring Service) air quality forecast: PM2.5, PM10, nitrogen dioxide, sulphur dioxide, ozone, carbon monoxide, dust, pollen, and European/US AQI indices. This is modeled grid data, not measured station readings — for measured data, use op...5 paramsModeled CAMS (Copernicus Atmosphere Monitoring Service) air quality forecast: PM2.5, PM10, nitrogen dioxide, sulphur dioxide, ozone, carbon monoxide, dust, pollen, and European/US AQI indices. This is modeled grid data, not measured station readings — for measured data, use op...
forecast_daysintegerhourly_variablesarraylatitude*numberlongitude*numbertimezonestringopenmeteo_get_ensembleProbabilistic ensemble weather forecast — up to 51 ensemble members, up to 16 days ahead with optional past_days (0–92). Each member's values appear as separate columns named with a member suffix (e.g. temperature_2m_member01, temperature_2m_member02). Use the spread across me...12 paramsProbabilistic ensemble weather forecast — up to 51 ensemble members, up to 16 days ahead with optional past_days (0–92). Each member's values appear as separate columns named with a member suffix (e.g. temperature_2m_member01, temperature_2m_member02). Use the spread across me...
canvas_idstringdaily_variablesarrayforecast_daysintegerhourly_variablesarraylatitude*numberlongitude*numbermodelsstringpast_daysintegerprecipitation_unitstringmm · inchdefault: mmtemperature_unitstringcelsius · fahrenheitdefault: celsiustimezonestringwind_speed_unitstringkmh · mph · ms · kndefault: kmhopenmeteo_get_floodGloFAS (Global Flood Awareness System) river discharge forecast and historical reanalysis. Returns daily ensemble river discharge (m³/s) for the river nearest to the given coordinates — no river ID needed, the API snaps to the nearest stream. Forecast horizon up to 210 days ah...7 paramsGloFAS (Global Flood Awareness System) river discharge forecast and historical reanalysis. Returns daily ensemble river discharge (m³/s) for the river nearest to the given coordinates — no river ID needed, the API snaps to the nearest stream. Forecast horizon up to 210 days ah...
daily_variables*arrayend_datestringforecast_daysintegerlatitude*numberlongitude*numberstart_datestringtimezonestringopenmeteo_dataframe_queryRun a read-only SQL SELECT against tables staged on a DataCanvas by openmeteo_get_historical. Pass the canvas_id returned when openmeteo_get_historical spills (truncated: true). Tables are named by the spillover helper (e.g. spilled_<id>); use openmeteo_dataframe_describe to l...2 paramsRun a read-only SQL SELECT against tables staged on a DataCanvas by openmeteo_get_historical. Pass the canvas_id returned when openmeteo_get_historical spills (truncated: true). Tables are named by the spillover helper (e.g. spilled_<id>); use openmeteo_dataframe_describe to l...
canvas_id*stringsql*stringopenmeteo_dataframe_describeList the tables and their columns on a DataCanvas staged by openmeteo_get_historical. Call this first to discover table names before querying with openmeteo_dataframe_query.1 paramsList the tables and their columns on a DataCanvas staged by openmeteo_get_historical. Call this first to discover table names before querying with openmeteo_dataframe_query.
canvas_id*stringGeocode places, fetch global weather forecasts, historical climate, marine conditions, air quality, and terrain elevation via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://open-meteo.caseyjhand.com/mcp
Global weather from Open-Meteo: forecasts, the historical archive, marine conditions, air quality, probabilistic ensembles, river discharge, and CMIP6 climate projections. Geocode a place name, pull hourly and daily variables for its coordinates, and run SQL over large results staged to DataCanvas. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
| Tool | Description |
|---|---|
openmeteo_search_locations | Resolve a place name to ranked coordinates with country, region, timezone, and population |
openmeteo_get_forecast | Current conditions and hourly/daily forecast up to 16 days, with up to 92 past days |
openmeteo_get_historical | Hourly/daily history from the reanalysis archive, 1940 to present |
openmeteo_get_marine | Wave, swell, and sea-surface conditions: up to 8 forecast days or an archive range |
openmeteo_get_air_quality | Modeled CAMS pollutants, pollen, and AQI: current, up to 7 forecast days, or an archive range |
openmeteo_get_elevation | Copernicus DEM terrain elevation for up to 100 coordinate pairs |
openmeteo_get_ensemble | Per-member ensemble forecast (up to 64 members, up to 35 days) for exceedance and uncertainty |
openmeteo_get_flood | GloFAS river discharge forecast (up to 210 days) or reanalysis from 1984 |
openmeteo_get_climate | Bias-corrected daily CMIP6 projections, 1950–2050, across up to 7 models |
openmeteo_dataframe_describe | List the tables and columns staged on a DataCanvas |
openmeteo_dataframe_query | Run a read-only SQL SELECT against staged tables |
openmeteo_dataframe_drop | Remove one staged table or view from a DataCanvas (opt-in) |
openmeteo_search_locations toolname ("Paris", not "Paris, France"), with optional country (ISO 3166-1 alpha-2) and language (default en); count 1–10, default 5timezone, country / country_code, admin1 / admin2, population, and feature_code; no match fails as no_resultsnotice flags a top match with null or sub-100,000 population, which is what a historic exonym ("Bangalore", "Calcutta") often resolves toen is retried once in the language its script implies; when that retry finds the results, effective_language names it (上海 → zh), since their name, country, admin1, and admin2 come back localized in itopenmeteo_get_forecast toolcurrent_variables, hourly_variables, or daily_variables (up to 50 each); forecast_days 1–16 (default 7), past_days 0–92 (default 0)hourly / daily records with hourly_units / daily_units; current_variables adds a current object from 15-minute data (interval 900) plus current_unitspast_days over openmeteo_get_historical for the last ~5 days, where the archive's ERA5 components lagopenmeteo_get_historical toolstart_date and end_date required (YYYY-MM-DD, from 1940-01-01), plus at least one of hourly_variables / daily_variables, named as on the forecast toolmodels reads Best Match (IFS HRES + ERA5 + ERA5-Land, source varies by date); up to 8 models pin a source. The ERA5 family runs ~5 days behind, ecmwf_ifs has no delay, and cerra covers Europe onlymodels and the returned date_range; with 2+ models each column carries a model-name suffixopenmeteo_get_marine toolforecast_days 1–8 (upstream default 7) with past_days 0–92, or a start_date / end_date archive range back to at least 2022. Mixing them fails as forecast_window_conflict, a lone date as date_range_incompletehourly_variables / daily_variables (wave height, period, and direction, swell, sea-surface temperature); sheltered water returns near-zero waves, a land point returns nulls (named in the notice), and ocean_current_velocity is null off the open oceanopenmeteo_get_air_quality toolforecast_days 1–7 (upstream default 5) with past_days 0–92, or a start_date / end_date archive range from August 2022 (us_aqi starts a day later). Same forecast_window_conflict and date_range_incomplete rejections as the marine toolcurrent_variables (a current object, interval 3600) or hourly_variables: PM2.5, PM10, NO2, SO2, O3, CO, dust, pollen, European and US AQI. Pollen is modeled for Europe only, in pollen season; elsewhere it comes back null and the notice names itdata_source: "CAMS" marks every response as modeled grid data, not station measurementsopenmeteo_get_elevation toollatitudes[] / longitudes[] arrays of up to 100 pairs; unequal lengths fail as coordinate_count_mismatchelevations[] in input order, each { latitude, longitude, elevation_m } from the Copernicus DEM (~90 m); elevation_m is null where the DEM has no value (latitude 90), without failing the rest of the callopenmeteo_get_ensemble toolhourly_variables / daily_variables; forecast_days 1–35 (default 7), past_days 0–92. Most models end within 16 days (ncep_gefs05 runs to about day 34); past a model's horizon its columns are null and the notice names where its data ends. models takes one ensemble name (e.g. ecmwf_ifs025_ensemble, 51 members; gem_global_ensemble, 21); omitted or blank, it selects ecmwf_ifs025_ensemble, which the model field echoestemperature_2m_member01, …) across up to 64 members; member_count counts the perturbed members, not the control runopenmeteo_get_flood tooldaily_variables required (up to 20), all in m³/s: river_discharge, the daily discharge value, and the ensemble statistics river_discharge_mean / _median / _min / _max / _p25 / _p75. The statistics exist in forecast mode only; in reanalysis mode they repeat river_discharge and carry no spreadforecast_days 1–210, or a start_date / end_date reanalysis range from 1984-01-01. Mixing them fails as forecast_days_conflict, a lone date as date_range_incompleteopenmeteo_get_climate toolstart_date / end_date within 1950-01-01 to 2050-12-31 and daily_variables required (the API is daily-only); up to 7 CMIP6 models, e.g. CMCC_CM2_VHR4, MRI_AGCM3_2_Sopenmeteo_dataframe_describe toolcanvas_id a spilled weather tool returned; lists each table's name, kind, row_count, and columns (name, type, nullability), plus the canvas expires_atcanvas_not_enabled when canvas is off, or canvas_not_found when the ID is unknown or past its 24-hour sliding TTLopenmeteo_dataframe_query toolcanvas_id plus a read-only SELECT against the staged table_name; rows holds up to 100 leading rows, fewer when wide rows would take the response past its 80,000-character inline ceiling (values are never cut). row_count is the exact total, so continue at OFFSET <rows.length>, under an ORDER BY for a stable orderrow_count is a lower bound, and count_error names the reason (e.g. count_timeout)canvas_not_enabled, canvas_not_found, missing_table, system_catalog_access (system catalogs are blocked), or row_too_large (the first row alone exceeds the ceiling: select fewer columns, or shorten values with left(), substr(), or list_slice())openmeteo_dataframe_drop toolOPENMETEO_DATAFRAME_DROP_ENABLED=truecanvas_id plus the exact table_name from openmeteo_dataframe_describe; removes that one table or view and returns dropped and the remaining_tables. A name that is not staged returns dropped: false, so a repeated drop is safecanvas_not_enabled, canvas_not_found, or invalid_table_name (not a plain SQL identifier, or a reserved keyword)Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
Open-Meteo-specific:
*_units maps, on the same variable names across forecast and archiveCANVAS_PROVIDER_TYPE=duckdb an over-budget result is staged as a DuckDB table and the response carries canvas_id, table_name, and truncated: true; with the default none it returns a bounded preview and truncated: truemodels on the historical, ensemble, and climate tools is not an allowlist, so a model Open-Meteo adds later works without a server updateAgent-friendly output:
openmeteo_search_locations returns the IANA timezone alongside coordinates, ready for any weather tool's timezone input (default auto)variable_wrong_cadence before the upstream call, naming the field it belongs in; an over-wide request fails as request_too_large, naming the inputs to narrow; an upstream rate limit fails as rate_limited on every upstream-calling tool, and only Too many concurrent requests, which clears within seconds, is retried firstnotice per response for what the data alone won't show: variable names the endpoint doesn't serve, null values from a location or window outside a dataset's coverage, and where spilled rows wentA 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"
}
}
}
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
git clone https://github.com/cyanheads/open-meteo-mcp-server.git
cd open-meteo-mcp-server
bun install
Every variable is optional.
| Variable | Description | Default |
|---|---|---|
OPEN_METEO_API_BASE_URL | Forecast and elevation API base URL. | https://api.open-meteo.com |
OPEN_METEO_ARCHIVE_BASE_URL | Historical archive API base URL. | https://archive-api.open-meteo.com |
OPEN_METEO_MARINE_BASE_URL | Marine API base URL. | https://marine-api.open-meteo.com |
OPEN_METEO_AIR_QUALITY_BASE_URL | CAMS air quality API base URL. | https://air-quality-api.open-meteo.com |
OPEN_METEO_GEOCODING_BASE_URL | Geocoding API base URL. | https://geocoding-api.open-meteo.com |
OPEN_METEO_ENSEMBLE_BASE_URL | Ensemble API base URL. | https://ensemble-api.open-meteo.com |
OPEN_METEO_FLOOD_BASE_URL | GloFAS flood API base URL. | https://flood-api.open-meteo.com |
OPEN_METEO_CLIMATE_BASE_URL | CMIP6 climate API base URL. | https://climate-api.open-meteo.com |
CANVAS_PROVIDER_TYPE | duckdb stages over-budget weather results on a DataCanvas and enables the dataframe tools; none returns a bounded preview. | none |
OPENMETEO_DATAFRAME_DROP_ENABLED | true makes openmeteo_dataframe_drop callable; otherwise it is listed as disabled. | false |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | HTTP server port. | 3010 |
MCP_SESSION_MODE | HTTP session mode: stateless, stateful, or auto. | stateless |
MCP_AUTH_MODE | Authentication: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (debug, info, notice, warning, error). | info |
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 |
OTEL_ENABLED | Enable OpenTelemetry. | false |
See .env.example for the full list of optional overrides.
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 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.
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools, initializes the Open-Meteo service and DataCanvas. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools/definitions | Tool definitions (*.tool.ts), one file per tool. |
src/mcp-server/tools | Shared tool helpers — model catalog, cadence validation, columnar reshape, spillover, response notices. |
src/services/open-meteo | HTTP client for the Open-Meteo APIs — retry, error classification, coverage-gap rejection. |
src/services/canvas-accessor.ts | DataCanvas accessor shared by the spill-capable tools. |
tests/ | Unit, integration, fuzz, and smoke tests. |
See CLAUDE.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for request-scoped logging, ctx.state for tenant-scoped storagetools[] array in src/index.tsWeather data by Open-Meteo.com, licensed CC BY 4.0.
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.
MCP_LOG_LEVELdefault: infoSets the minimum log level for output (e.g., 'debug', 'info', 'warn').
CANVAS_PROVIDER_TYPEdefault: noneCanvas engine for openmeteo_get_historical spillover. Set to 'duckdb' to enable DataCanvas and the dataframe_query / dataframe_describe tools. Default 'none' (spillover disabled).
MCP_HTTP_HOSTdefault: 127.0.0.1The hostname for the HTTP server.
MCP_HTTP_PORTdefault: 3010The port to run the HTTP server on.
MCP_HTTP_ENDPOINT_PATHdefault: /mcpThe endpoint path for the MCP server.
MCP_AUTH_MODEdefault: noneAuthentication mode to use: 'none', 'jwt', or 'oauth'.