open-meteo-mcp-server
Public Hosted Server: https://open-meteo.caseyjhand.com/mcp
Tools
Eleven tools covering geocoding, weather forecasts, historical climate, probabilistic ensemble forecasts, marine conditions, air quality, terrain elevation, river discharge, CMIP6 climate projections, and SQL analytics over large datasets:
Tool | Description |
| Resolve a place name to ranked coordinate matches with country, region, elevation, timezone, and population |
| Weather forecast for coordinates: hourly and/or daily variables for up to 16 days, with optional recent past data; wide windows spill to DataCanvas |
| Historical weather from the ERA5 reanalysis archive (1940–present); large ranges spill to DataCanvas |
| Marine wave and ocean conditions for coastal or ocean coordinates: wave height, period, direction, swell, and sea-surface temperature; up to 8 forecast days, |
| Modeled CAMS air quality: PM2.5, PM10, NO2, O3, CO, dust, pollen, and European/US AQI indices; up to 7 forecast days, |
| Terrain elevation from Copernicus DEM (~90m resolution) for up to 100 coordinate pairs per call |
| Probabilistic ensemble forecast: per-member hourly/daily time series (up to 51 members, 16 days) for exceedance and uncertainty analysis |
| GloFAS river discharge forecast (up to 210 days) or reanalysis (1984–present); coordinate-based, snaps to nearest river; large ranges spill to DataCanvas |
| Bias-corrected daily CMIP6 climate projections (1950–2050) across up to 7 models; large ranges spill to DataCanvas |
| List tables and columns on a DataCanvas staged by |
| Run a read-only SQL SELECT against tables staged on a DataCanvas |
openmeteo_search_locations
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
Search by a bare place name — a city, region, or landmark ("Baoding", not "Baoding Hebei"; "Paris", not "Paris, France"); a compound "City Region" or "City, Country" string matches nothing
Disambiguate same-named places (e.g., "Springfield") with the optional
countryfilter (ISO 3166-1 alpha-2, e.g.US) or by raisingcount(default 5, up to 10) and reading theadmin1/countryfields on each result — those are output fields for choosing among matches, not search inputsPass the timezone from an
openmeteo_search_locationsresult directly to weather tools as thetimezoneparameterFails with a
no_resultserror (not an empty array) when nothing matches — retry the bare place name without qualifiers, or for a physical feature/landmark search the nearest populated place instead
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 lagCommon 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_2mCommon daily variables:
temperature_2m_max,temperature_2m_min,precipitation_sum,wind_speed_10m_max,sunrise,sunset,uv_index_max,precipitation_hours,weather_codeAt least one of
hourly_variablesordaily_variablesis requiredHourly and daily are separate variable sets. A variable Open-Meteo documents under the other cadence is rejected before the request, by name, with the field it belongs in and the same-cadence alternatives (
cloud_coverindaily_variables→cloud_cover_max/_mean/_min). Names in neither set are passed upstream unchangedConfigurable 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_unitsmapA wide window (a large
past_daysplus many hourly variables) spills to DataCanvas whenCANVAS_PROVIDER_TYPE=duckdb— output includescanvas_idandtruncated: true; query withopenmeteo_dataframe_query
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 lagSame variable vocabulary as
openmeteo_get_forecast— past and forecast data are directly comparable on one schemaAt least one of
hourly_variablesordaily_variablesis requiredHourly and daily are separate variable sets; a variable documented under the other cadence is rejected before the request, by name, with the field it belongs in
Large date ranges (multi-year hourly queries) spill to DataCanvas when
CANVAS_PROVIDER_TYPE=duckdb— output includescanvas_idandtruncated: truewhenever a result is too large to return inline, which a wide multi-variable pull can be at any row countSpill → query workflow: call
openmeteo_dataframe_describewith thecanvas_idto list tables, thenopenmeteo_dataframe_queryto run SQL SELECT against the staged data
openmeteo_get_marine
Marine wave and ocean conditions for coastal and open-ocean coordinates.
Up to 8 forecast days (
forecast_days 1–8, upstream default 7) with optionalpast_days(0–92)Or an archive range via
start_dateandend_date— real wave values go back to at least 2022One window per call: a date range is mutually exclusive with
forecast_days/past_days, and needs both ends — a lonestart_dateorend_dateis rejectedCommon 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_periodCommon daily variables:
wave_height_max,wave_direction_dominant,wave_period_maxAt least one of
hourly_variablesordaily_variablesis requiredHourly and daily are separate variable sets; a variable documented under the other cadence is rejected before the request, by name, with the field it belongs in
Inland or sheltered-water points return near-zero wave values (physically correct);
ocean_current_velocityis null for non-open-ocean coordinatesWide windows spill to DataCanvas when
CANVAS_PROVIDER_TYPE=duckdb— output includescanvas_idandtruncated: true; query withopenmeteo_dataframe_query
openmeteo_get_air_quality
Modeled CAMS air quality, forecast and archive.
Up to 7 forecast days (
forecast_days 1–7, upstream default 5) with optionalpast_days(0–92)Or an archive range via
start_dateandend_date— real CAMS values go back to at least2022-10-01; earlier dates return rows of nullsOne window per call: a date range is mutually exclusive with
forecast_days/past_days, and needs both ends — a lonestart_dateorend_dateis rejectedCommon 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_pollenAt least one variable from
hourly_variablesis requiredGrid-modeled data from CAMS — resolution is coarser than ground stations; for measured station readings, cross-reference
openaq-mcp-serverOutput includes
data_source: "CAMS"to distinguish modeled from measured dataWide windows spill to DataCanvas when
CANVAS_PROVIDER_TYPE=duckdb— output includescanvas_idandtruncated: true; query withopenmeteo_dataframe_query
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 thresholdsAvailable ensemble models (member counts include the control run):
Global —
ecmwf_ifs025_ensemble(51),ecmwf_aifs025_ensemble(51),google_weathernext2_ensemble(64),ncep_gefs_seamless(31),ncep_gefs025(31),ncep_gefs05(31, 35-day horizon),ncep_aigefs025(31),icon_seamless_eps(20–40, global/Europe blend),icon_global_eps(40),gem_global_ensemble(21),bom_access_global_ensemble(18),ukmo_global_ensemble_20km(18)Regional —
ecmwf_ifs_europe_ensemble(51),ecmwf_aifs_europe_ensemble(51),icon_eu_eps(40),icon_d2_eps(20),meteoswiss_icon_ch2_ensemble(21),meteoswiss_icon_ch1_ensemble(11),ukmo_uk_ensemble_2km(3). A regional model returns no data outside the area it covers. Upstream reports that two ways —No data is available for this locationfrom themeteoswiss_*pair, an HTTP 200 carryingnancoordinates from the rest — and both surface as a non-retryable input error naming the coverage gap, so switch to a global model rather than retrying
Omit
modelsto use the API default blend. The list is not an allowlist — a model name it does not carry is still sent upstream, so a model Open-Meteo adds later keeps workingResponse includes
model(system used) andmember_count(perturbed members, excluding the control run)At least one of
hourly_variablesordaily_variablesis requiredHourly and daily are separate variable sets; a variable documented under the other cadence is rejected before the request, by name, with the field it belongs in. The ensemble API's own catalog applies — it publishes
temperature_2m_maxandtemperature_2m_minas 3-hourly aggregations as well as daily, so those are accepted in either fieldLarge multi-member, multi-day pulls spill to DataCanvas when
CANVAS_PROVIDER_TYPE=duckdb— output includescanvas_idandtruncated: true; query withopenmeteo_dataframe_queryConfigurable 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
One mode per call:
forecast_daysfor the future outlook, orstart_dateandend_datetogether for historical analysis. The two are mutually exclusive, and a date range needs both ends — a lonestart_dateorend_dateis rejectedAvailable 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³/sReturns 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
Wide reanalysis ranges spill to DataCanvas when
CANVAS_PROVIDER_TYPE=duckdb— output includescanvas_idandtruncated: true; query withopenmeteo_dataframe_query
openmeteo_get_climate
Long-range climate projections from bias-corrected daily CMIP6 models — the future-projection counterpart to openmeteo_get_historical.
Coverage: 1950-01-01 to 2050-12-31, daily resolution only
Available models:
CMCC_CM2_VHR4,FGOALS_f3_H,HiRAM_SIT_HR,MRI_AGCM3_2_S,EC_Earth3P_HR,MPI_ESM1_2_XR,NICAM16_8S. Not an allowlist — an unlisted name is still sent upstream; when upstream rejects a multi-model request, the error names only the model outside the documented set, not the whole listWith 2+ models, each variable appears once per model with the model name as column suffix (e.g.
temperature_2m_max_CMCC_CM2_VHR4); a single or omitted model returns plain variable namesCommon daily variables:
temperature_2m_max,temperature_2m_min,temperature_2m_mean,precipitation_sum,rain_sum,snowfall_sum,wind_speed_10m_mean,wind_speed_10m_max,shortwave_radiation_sum,cloud_cover_mean,relative_humidity_2m_mean,pressure_msl_meanNot all models carry all variables — missing combinations return null (e.g.
CMCC_CM2_VHR4has noshortwave_radiation_sum)Multi-decade daily pulls across several models spill to DataCanvas when
CANVAS_PROVIDER_TYPE=duckdb— output includescanvas_idandtruncated: true; query withopenmeteo_dataframe_queryConfigurable temperature, wind speed, and precipitation units
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,oauthSwappable storage backends:
in-memory,filesystem,Supabase,Cloudflare KV/R2/D1Structured 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_search_locationsresolves place names so agents don't need a separate geocoderERA5 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
*_unitsmapDataCanvas spillover for
openmeteo_get_forecast,openmeteo_get_historical,openmeteo_get_marine,openmeteo_get_air_quality,openmeteo_get_ensemble,openmeteo_get_flood, andopenmeteo_get_climate: a result too large to return inline registers a DuckDB dataframe for SQL querying, staging every hourly and daily row with its upstream numeric type intact. WithCANVAS_PROVIDER_TYPE=none(the default) the same size check still applies — those tools return a bounded preview withtruncated: trueand nocanvas_id, never an unbounded payload claiming to be completeConfigurable base URLs for all eight API endpoints (forecast, archive, marine, air quality, geocoding, ensemble, flood, climate) — 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:
Location-first workflow:
openmeteo_search_locationsreturns the IANA timezone alongside coordinates — pass it directly astimezoneto any weather toolRecovery hints on all error contracts — invalid variable names surface correction guidance with common variable examples
Cadence-aware variable validation on
openmeteo_get_forecast,openmeteo_get_historical,openmeteo_get_marine, andopenmeteo_get_ensemble: a variable documented under the opposite cadence is rejected before the upstream call, naming the exact value and the field it belongs in, so the next attempt converges instead of re-guessing against an error that echoes the whole requested list. This is not an allowlist — a name in neither documented set goes upstream untouchedUnserved-variable notice on all seven weather tools: Open-Meteo answers a variable name it parses but does not serve with an all-null column and the unit
"undefined"rather than an error, so the result carries a notice naming those columns instead of presenting them as a data gap.openmeteo_get_air_quality,openmeteo_get_flood, andopenmeteo_get_climatetake a single cadence bucket, so they carry the notice without a cadence guardCoordinate snapping transparency — responses echo the snapped
latitude/longitude(Open-Meteo quantizes to the nearest model grid point) so agents can reason about grid alignmentdata_source: "CAMS"label on air quality results distinguishes modeled 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/mcpPrerequisites
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.gitNavigate into the directory:
cd open-meteo-mcp-serverInstall dependencies:
bun installConfiguration
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 |
| Transport: |
|
| HTTP server port |
|
| HTTP server host |
|
| HTTP endpoint path |
|
| Maximum HTTP request body bytes; |
|
| Public origin for TLS-terminating reverse-proxy deployments | — |
| HTTP session mode: |
|
| Replay missed SSE events for stateful HTTP sessions. No effect on stateless mode or protocol revision 2026-07-28. |
|
| Events retained per stateful session for replay; oldest evicted first. |
|
| How long retained events remain replayable (ms). |
|
| Auth mode: |
|
| Log level ( |
|
| Maximum repeated emissions per level and message in each log window; |
|
| Repeated-log suppression window (ms). |
|
| Opt-in forced-GC interval (ms, Bun only). Set to |
|
| Directory for log files (Node.js only) |
|
| Storage backend: |
|
| Canvas engine for |
|
| Override for the main forecast + elevation API |
|
| Override for the ERA5 historical archive API |
|
| Override for the marine forecast API |
|
| Override for the CAMS air quality API |
|
| Override for the geocoding API |
|
| Override for the ensemble forecast API |
|
| Override for the GloFAS flood API |
|
| Override for the CMIP6 climate projections API |
|
| Enable OpenTelemetry tracing and metrics |
|
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:stdioRun 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-serverThe 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 |
|
|
| Server-specific environment variable parsing and validation with Zod |
| Tool definitions ( |
| Open-Meteo HTTP client wrapping all nine endpoints with retry, error classification, and columnar reshape |
| DataCanvas accessor for |
| Unit and integration tests mirroring |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
Handlers throw, framework catches — no
try/catchin tool logicUse
ctx.logfor request-scoped logging,ctx.statefor tenant-scoped storageRegister new tools in the
tools[]array insrc/index.tsWrap 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 testLicense
Apache-2.0 — see LICENSE for details.
Weather data by Open-Meteo.com — licensed CC BY 4.0.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/cyanheads/open-meteo-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server