Koskamo

open-meteo-mcp-server

An MCP server that provides 12 tools for accessing global weather data via the Open-Meteo API. It supports geocoding places to coordinates, retrieving current and historical weather forecasts, marine conditions, air quality indices, terrain elevation, ensemble forecasts, flood data, and climate projections. It also includes SQL querying capabilities for large datasets.

Endpoint and access

No sign-in to connect and list tools. Individual tools may still need a vendor account, credentials, or permissions; check the documented prerequisites.

open-meteo-mcp-server logo, by cyanheadsCommunity listingStreamable HTTPNo sign-inEndpoint checked 2026-11-06Published by cyanheads
MCP endpoint (Streamable HTTP)
https://open-meteo.caseyjhand.com/mcp
Vendor documentation

What its tools do

11 tools, grouped by the hints the server advertises. Hints are self-declared, not verified behavior.

11 of 11 have a description · 11 of 11 declare behavior hints · 11 of 11 with parameters document them all

Checks and signals

Each line is separate evidence, not a trust score.

  • Passed Endpoint answered the live check ·
  • Availability 1 of 1 checks passed in 30 days

What you can do with it

  • Resolve place names to coordinates with country and timezone
  • Fetch hourly and daily weather forecasts up to 16 days
  • Retrieve historical weather data from 1940 to present
  • Query air quality and marine conditions data

Source and maintenance

Community listingRegistry 0.5.0
Maintenance checked

Protocol and capabilities

Server
open-meteo-mcp-server 0.5.0
MCP protocol negotiated in check
2025-11-25
Tools
11 advertised
Resources
1 advertised
Resource templates
1 advertised
Prompts
0 advertised
Server-advertised capabilities
{
  "tools": {
    "listChanged": true
  },
  "logging": {},
  "prompts": {
    "listChanged": true
  },
  "resources": {
    "subscribe": true,
    "listChanged": true
  },
  "extensions": {
    "io.modelcontextprotocol/skills": {
      "directoryRead": true
    }
  }
}

Server-provided instructions

Untrusted server output shown for inspection. It is not Koskamo guidance and is excluded from knowledge search.

Open-Meteo global weather server — keyless, no API key required for non-commercial use. Weather data by Open-Meteo.com (CC BY 4.0). Workflow: 1. openmeteo_search_locations — resolve a place name to coordinates (required first step for name-based queries) 2. openmeteo_get_forecast — up to 16 days ahead + 92 days past_days; current_variables for conditions right now, and/or hourly and daily variables. Omitting models reads Open-Meteo best_match; set models to compare deterministic models (2+ suffix every hourly and daily column with the model name; current_variables takes at most one model). A wide window spills like the historical tool 3. openmeteo_get_historical — archive from 1940; use start_date/end_date. Omitting models reads Open-Meteo Best Match (IFS HRES + ERA5 + ERA5-Land); set models to pin one source 4. openmeteo_get_marine — wave/swell for coastal and ocean points; current_variables for conditions right now, up to 8 forecast days, past_days, or a start_date+end_date archive range back to at least 2022; length_unit "imperial" returns heights in ft, and wind_speed_unit / temperature_unit set the ocean current speed and sea-surface temperature units 5. openmeteo_get_air_quality — CAMS modeled PM2.5, PM10, ozone, AQI; current_variables for values right now, up to 7 forecast days, past_days, or a start_date+end_date archive range; the CAMS global archive begins in August 2022 6. openmeteo_get_elevation — Copernicus DEM terrain elevation; accepts up to 1,000 coordinate pairs and samples the first 100 per call — resubmit latitudes.slice(next_offset) and longitudes.slice(next_offset) until next_offset is null; each sampled pair counts as one call against the per-IP limits below 7. openmeteo_get_ensemble — probabilistic ensemble forecast (up to 64 members, up to 35 days; most models end within 16, and the response notice names where the data ends); use for exceedance probabilities and uncertainty quantification. Omitting models selects ecmwf_ifs025_ensemble. A regional model queried outside its coverage area fails as a coverage_gap input error naming the gap — switch to a global model, do not retry 8. openmeteo_get_flood — GloFAS river discharge: forecast_days (the current run reaches about six months; later days are null) OR a date range from 1984 (start_date+end_date together); the two modes are mutually exclusive. Omitting models reads seamless_v4 — consolidated_v4 reanalysis before 2023-08-05, forecast-run data from then on — so set models: ["consolidated_v4"] for reanalysis through 2025-05-31. Coordinate-based — discharge comes from the GloFAS 0.05° grid cell nearest the point (the response latitude/longitude names it), which is not reliably on the river you mean; when the value is far below what the named river carries, step the coordinate 0.05° across the channel and compare 9. openmeteo_get_climate — bias-corrected daily CMIP6 climate projections (1950–2050, up to 7 models); use for multi-decade "what will conditions look like" questions DataCanvas workflow (on by default; CANVAS_PROVIDER_TYPE=none disables it): - openmeteo_get_forecast, openmeteo_get_historical, openmeteo_get_marine, openmeteo_get_air_quality, openmeteo_get_ensemble, openmeteo_get_flood, or openmeteo_get_climate with a large query returns canvas_id + truncated: true - openmeteo_dataframe_describe — list tables and columns on the canvas. Call it first: the staged table name is generated and its columns vary per request (per-member and per-model suffixes), so there is no valid SQL to write until the schema is read - openmeteo_dataframe_query — run SQL SELECT against staged tables Notes: - The open-meteo-guide skill (skill://open-meteo-guide/SKILL.md) carries the gates for exceedance odds, frost, comparisons to a normal, climate-shift questions, surf spots, river cells, and AQI timing - All weather tools take latitude/longitude — use openmeteo_search_locations first for place names - The archive default is the Best Match blend, so its provenance varies by date; its ERA5 components lag about a week while IFS HRES does not. For recent history, use openmeteo_get_forecast with past_days, or request models: ["ecmwf_ifs"] on openmeteo_get_historical - current_variables on openmeteo_get_forecast, openmeteo_get_marine, and openmeteo_get_air_quality answers "right now" — a current object plus current_units, and no hourly series needed. The block's interval field is its aggregation interval in seconds (900 on the forecast and marine endpoints, 3600 on air quality): summed or averaged values cover the interval ending at current.time, instantaneous values are valid at current.time - All responses use timezone=auto by default (localizes to the location) - Variable names are exact API names: temperature_2m, pm2_5, wave_height, river_discharge, etc. - hourly_variables and daily_variables take separate variable sets — cloud_cover is hourly, temperature_2m_max is daily. A variable passed in the wrong field is rejected before the request, naming the value and the field it belongs in - Large forecast/historical/marine/air-quality/ensemble/flood/climate queries spill to DataCanvas when CANVAS_PROVIDER_TYPE=duckdb (the default); with none they return a bounded preview and truncated: true, so narrow the request or enable canvas to reach the rest. A two-cadence preview carries both hourly and daily rows in either configuration — neither is dropped for the other, and the two share one inline budget - The models value on openmeteo_get_forecast, openmeteo_get_ensemble, openmeteo_get_climate, openmeteo_get_historical, and openmeteo_get_flood is not validated locally: a name the tool does not advertise still goes upstream, so a model Open-Meteo adds later works without a server update. When upstream rejects a multi-model request it names only the offending model — correct that one and leave the rest - A multi-model forecast or historical request in which some models have no data at the coordinate (a regional model outside its domain) still succeeds: Open-Meteo drops those models, and the notice names them — or, when only one model is left and its columns come back unsuffixed, says the values cannot be attributed to a model. When every requested model misses, the call fails as coverage_gap - The free Open-Meteo API (non-commercial use) allows fewer than 600 calls a minute, 5,000 an hour, and 10,000 a day per IP address, so every session this server serves draws on one shared budget. A request for more than 10 variables, or more than 2 weeks for one location, counts as more than one call, fractionally (2 weeks × 15 variables = 1.5 calls), and each coordinate pair an openmeteo_get_elevation call samples counts as one call — request only the variables and date range the question needs, and on a rate_limited error wait as the message directs before calling again

Tools

11 advertised

openmeteo_search_locations — Openmeteo Search Locations
Advertised by the server during the last successful anonymous check.
Advertised read-only

Resolve 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. Search by a bare place name (city, region, or landmark); never fold a qualifier into it — pass "Baoding", not "Baoding Hebei", and "Paris", not "Paris, France". To disambiguate places that share a name, set the country input (ISO 3166-1 alpha-2, e.g. "US") and/or read the admin1 and country fields on each ranked result — admin1 is a result field for choosing among matches, not a search input. Returns up to 10 matches ranked by population/relevance.

openmeteo_search_locations input schema
{
  "type": "object",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "required": [
    "name"
  ],
  "properties": {
    "name": {
      "type": "string",
      "maxLength": 100,
      "minLength": 1,
      "description": "Place name to search — a bare city, region, or landmark (\"Seattle\", \"Mount Rainier\"). Do not fold in a region or country qualifier (\"Baoding\", not \"Baoding Hebei\"); use the country input to disambiguate. A one- or two-character native-script name (\"서울\", \"大阪\") needs the full administrative name (\"서울특별시\", \"大阪市\") or the romanized name (\"Seoul\", \"Osaka\") — see the language field. Weather tools require coordinates — use the lat/lon from this result."
    },
    "count": {
      "type": "integer",
      "default": 5,
      "maximum": 10,
      "minimum": 1,
      "description": "Max results to return (1–10). Default 5. Return more when disambiguating common names like \"Springfield\" or \"Portland\"."
    },
    "country": {
      "type": "string",
      "pattern": "^[A-Za-z]{2}$",
      "description": "ISO 3166-1 alpha-2 country code (e.g. \"US\", \"FR\") to disambiguate places that share a name. Omit for a global search."
    },
    "language": {
      "type": "string",
      "default": "en",
      "description": "Language for matching and returning place names (ISO 639-1, e.g., \"en\", \"de\", \"zh\"). The API matches name against the localized index for this language, so set it to match the script of name — e.g. language \"zh\" for \"上海\", \"ru\" for \"Москва\". This resolves a native-script name of three or more characters, which is matched by normalized prefix; a one- or two-character name must equal an index entry exactly, so setting language alone will not find \"서울\" or \"大阪\" — retry those with the full administrative name (\"서울특별시\", \"大阪市\") or the romanized name (\"Seoul\", \"Osaka\"). Default \"en\"; a query in a recognized non-Latin script (CJK, Hangul, Cyrillic, Arabic, Greek, Hebrew, Thai, Devanagari) that misses under \"en\" is retried once with the language inferred from its script, and effective_language on the response names that language when the retry supplied the results."
    }
  },
  "additionalProperties": false
}
openmeteo_search_locations output schema
{
  "type": "object",
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "results",
        "count"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ],
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "count": {
      "type": "number",
      "description": "Number of results returned"
    },
    "error": {
      "type": "object",
      "required": [
        "code",
        "message"
      ],
      "properties": {
        "code": {
          "type": "integer",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "examples": [
                "no_results",
                "rate_limited",
                "upstream_unavailable",
                "upstream_timeout"
              ],
              "description": "Machine-readable failure mode. Declared by this tool: `no_results`: The search returned no matching places. `rate_limited`: Open-Meteo answered HTTP 429 because a free-tier request limit was reached. `upstream_unavailable`: Open-Meteo was unreachable, answered with a server error, or returned an unreadable response on all three attempts. `upstream_timeout`: Open-Meteo failed on all three attempts, and the last one got no answer within 15 seconds. Other values are possible when a failure originates below the handler."
            },
            "recovery": {
              "type": "object",
              "required": [
                "hint"
              ],
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "description": "Actionable next step for the caller.",
              "additionalProperties": {}
            },
            "retryable": {
              "type": "boolean",
              "description": "Whether retrying may succeed."
            }
          },
          "additionalProperties": {}
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        }
      },
      "description": "Present when the call failed. Absent on success.",
      "additionalProperties": {}
    },
    "notice": {
      "type": "string",
      "description": "Advisory on the confidence of the top match, present only when its population is null or under 100,000 — the shape a historic or colonial exonym returns, where the upstream index answers with an unrelated small feature and never surfaces the modern city. Names the returned place, country, and feature_code, and asks the caller to verify the coordinates or retry with the place’s current official name. Never changes results or count."
    },
    "results": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "id",
          "name",
          "latitude",
          "longitude",
          "elevation",
          "timezone",
          "country",
          "country_code",
          "admin1",
          "admin2",
          "population",
          "feature_code"
        ],
        "properties": {
          "id": {
            "type": "number",
            "description": "Open-Meteo place ID — stable reference for this location"
          },
          "name": {
            "type": "string",
            "description": "Place name"
          },
          "admin1": {
            "type": [
              "string",
              "null"
            ],
            "description": "State, province, or region"
          },
          "admin2": {
            "type": [
              "string",
              "null"
            ],
            "description": "County or district"
          },
          "country": {
            "type": [
              "string",
              "null"
            ],
            "description": "Country name — null for non-country features like continents and oceans"
          },
          "latitude": {
            "type": "number",
            "description": "Latitude in decimal degrees"
          },
          "timezone": {
            "type": [
              "string",
              "null"
            ],
            "description": "IANA timezone (e.g., \"America/Los_Angeles\") — pass to weather tools as the timezone parameter. Null when the API omits it."
          },
          "elevation": {
            "type": [
              "number",
              "null"
            ],
            "description": "Elevation in meters above sea level"
          },
          "longitude": {
            "type": "number",
            "description": "Longitude in decimal degrees"
          },
          "population": {
            "type": [
              "number",
              "null"
            ],
            "description": "Population (when available) — useful for disambiguating same-named cities"
          },
          "country_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO 3166-1 alpha-2 country code — null for non-country features like continents and oceans"
          },
          "feature_code": {
            "type": "string",
            "description": "GeoNames feature code describing the place type (e.g., \"PPLA2\" = state capital city, \"PPL\" = populated place)"
          }
        },
        "description": "A single geocoding result with coordinates and administrative context",
        "additionalProperties": false
      },
      "description": "Ranked matches (most relevant first). Never empty — when nothing matches, the tool fails with no_results instead of returning an empty array."
    },
    "effective_language": {
      "type": "string",
      "description": "ISO 639-1 language the results were matched in, present only when the query missed under the default \"en\" and the retry with a language inferred from its script found them. The results’ name, country, admin1, and admin2 are localized in that language. Absent when the results were matched in the requested language."
    }
  },
  "additionalProperties": false
}
openmeteo_search_locations annotations
{
  "readOnlyHint": true,
  "idempotentHint": true
}
openmeteo_get_elevation — Openmeteo Get Elevation
Advertised by the server during the last successful anonymous check.
Advertised read-only

Terrain elevation from the Copernicus Digital Elevation Model (~90m resolution) for one or more coordinate pairs. Accepts up to 1,000 pairs and samples the first 100 in one upstream request; when more were submitted, unprocessed_count says how many remain and next_offset is where to resume — resubmit latitudes.slice(next_offset) and longitudes.slice(next_offset) of the arrays just sent, until next_offset is null. Open-Meteo counts each sampled pair as one call against its per-IP limit of 600 a minute, so a seventh full slice inside one minute comes back rate_limited — space later slices about a minute apart. Useful for geographic context, elevation-adjusted weather interpretation, or route planning. A point the DEM has no value for (latitude 90) returns elevation_m null; the other points in the call are unaffected.

openmeteo_get_elevation input schema
{
  "type": "object",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "required": [
    "latitudes",
    "longitudes"
  ],
  "properties": {
    "latitudes": {
      "type": "array",
      "items": {
        "type": "number",
        "maximum": 90,
        "minimum": -90
      },
      "maxItems": 1000,
      "minItems": 1,
      "description": "Array of latitudes in decimal degrees (up to 1,000; the first 100 are sampled per call). Must be same length as longitudes."
    },
    "longitudes": {
      "type": "array",
      "items": {
        "type": "number",
        "maximum": 180,
        "minimum": -180
      },
      "maxItems": 1000,
      "minItems": 1,
      "description": "Array of longitudes in decimal degrees (up to 1,000; the first 100 are sampled per call). Must be same length as latitudes."
    }
  },
  "additionalProperties": false
}
openmeteo_get_elevation output schema
{
  "type": "object",
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "elevations",
        "unprocessed_count",
        "next_offset"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ],
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "error": {
      "type": "object",
      "required": [
        "code",
        "message"
      ],
      "properties": {
        "code": {
          "type": "integer",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "examples": [
                "coordinate_count_mismatch",
                "rate_limited",
                "upstream_unavailable",
                "upstream_timeout"
              ],
              "description": "Machine-readable failure mode. Declared by this tool: `coordinate_count_mismatch`: The latitudes and longitudes arrays have different lengths. `rate_limited`: Open-Meteo answered HTTP 429 because a free-tier request limit was reached. `upstream_unavailable`: Open-Meteo was unreachable, answered with a server error, or returned an unreadable response on all three attempts. `upstream_timeout`: Open-Meteo failed on all three attempts, and the last one got no answer within 15 seconds. Other values are possible when a failure originates below the handler."
            },
            "recovery": {
              "type": "object",
              "required": [
                "hint"
              ],
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "description": "Actionable next step for the caller.",
              "additionalProperties": {}
            },
            "retryable": {
              "type": "boolean",
              "description": "Whether retrying may succeed."
            }
          },
          "additionalProperties": {}
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        }
      },
      "description": "Present when the call failed. Absent on success.",
      "additionalProperties": {}
    },
    "notice": {
      "type": "string",
      "description": "Present only when more than 100 pairs were submitted: names how many were sampled and how many remain, and the slice of the submitted arrays to resubmit for the rest."
    },
    "elevations": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "latitude",
          "longitude",
          "elevation_m"
        ],
        "properties": {
          "latitude": {
            "type": "number",
            "description": "Input latitude"
          },
          "longitude": {
            "type": "number",
            "description": "Input longitude"
          },
          "elevation_m": {
            "type": [
              "number",
              "null"
            ],
            "description": "Terrain elevation in meters above sea level; null when the DEM has no value for this point"
          }
        },
        "description": "Elevation for a single coordinate pair",
        "additionalProperties": false
      },
      "description": "Elevation values for the sampled pairs (the first 100), in input order"
    },
    "next_offset": {
      "type": [
        "number",
        "null"
      ],
      "description": "Index to resume from: resubmit latitudes.slice(next_offset) and longitudes.slice(next_offset) of the arrays sent in this call. null when every submitted pair was sampled."
    },
    "unprocessed_count": {
      "type": "number",
      "description": "Submitted pairs that were not sampled in this call; 0 when 100 or fewer were submitted"
    }
  },
  "additionalProperties": false
}
openmeteo_get_elevation annotations
{
  "readOnlyHint": true,
  "idempotentHint": true
}
openmeteo_get_forecast — Openmeteo Get Forecast
Advertised by the server during the last successful anonymous check.
Advertised read-only

Weather 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 week, since the archive’s ERA5 components lag by about a week. Returns per-timestamp records — each hourly entry contains a "time" field (ISO 8601) plus one key per requested variable; each daily entry contains a "time" field (YYYY-MM-DD) plus requested variables. 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. Set current_variables for conditions at this instant — Open-Meteo serves those from 15-minute model data, which is more precise than picking the nearest hourly row, and the response carries a current object plus a current_units map. Set models to compare deterministic models side by side (e.g., ["ncep_gfs_seamless", "ecmwf_ifs025", "dwd_icon_seamless"]) — where they disagree, the forecast is uncertain. With 2+ models every hourly and daily key carries the model name as a suffix (temperature_2m_ecmwf_ifs025); with models omitted the data is Open-Meteo’s best_match blend. A wide window — a large past_days plus many hourly variables — produces thousands of records; these spill to a DataCanvas when canvas is enabled, returning canvas_id and table_name with truncated: true — inspect the staged columns with openmeteo_dataframe_describe, then query the full set with openmeteo_dataframe_query. With canvas disabled they return a bounded preview instead. At least one of current_variables, hourly_variables, or daily_variables is required.

openmeteo_get_forecast input schema
{
  "type": "object",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "required": [
    "latitude",
    "longitude"
  ],
  "properties": {
    "models": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 57,
      "description": "Deterministic forecast models to read from, to compare modeling centers side by side. Documented: best_match, ecmwf_ifs, ecmwf_ifs025, ecmwf_aifs025_single, cma_grapes_global, bom_access_global, ncep_gfs_seamless, ncep_gfs_global, ncep_hrrr_conus, ncep_nbm_conus, ncep_nam_conus, ncep_aigfs025, ncep_hgefs025_ensemble_mean, jma_seamless, jma_msm, jma_gsm, kma_seamless, kma_ldps, kma_gdps, dwd_icon_seamless, dwd_icon_global, dwd_icon_global_native, dwd_aicon_global_native, dwd_icon_eu, dwd_icon_eu_native, dwd_icon_d2, dwd_icon_d2_native, dwd_icon_d2_ruc_native, cmc_gem_seamless, cmc_gem_gdps, cmc_gem_rdps, cmc_gem_hrdps, cmc_gem_hrdps_west, meteofrance_seamless, meteofrance_arpege_world, meteofrance_arpege_europe, meteofrance_arome_france, meteofrance_arome_france_hd, italia_meteo_arpae_icon_2i, metno_seamless, metno_nordic, knmi_seamless, knmi_harmonie_arome_europe, knmi_harmonie_arome_netherlands, dmi_seamless, dmi_harmonie_arome_europe, ukmo_seamless, ukmo_global_deterministic_10km, ukmo_uk_deterministic_2km, meteoswiss_icon_seamless, meteoswiss_icon_ch1, meteoswiss_icon_ch2, geosphere_seamless, geosphere_arome_austria, chmi_aladin_seamless, chmi_aladin_central_europe_2km, chmi_aladin_cz_1km. Older spellings such as gfs_seamless, icon_seamless, icon_d2, and gfs_hrrr still resolve. Omit to use Open-Meteo's best_match blend. With 2+ models every hourly and daily column and unit key is suffixed with the model name (temperature_2m_max_ecmwf_ifs025); one model keeps plain names. Regional models serve only their own domain: when every requested model misses the coordinate the call fails as coverage_gap, and when only some do, Open-Meteo drops those and the notice names them, or, when one model is left with unsuffixed columns, says the values cannot be attributed to a model. current_variables takes at most one model. A name outside this list is sent upstream rather than rejected here."
    },
    "latitude": {
      "type": "number",
      "maximum": 90,
      "minimum": -90,
      "description": "Latitude in decimal degrees (e.g., 47.6062 for Seattle). Use openmeteo_search_locations to resolve a place name to coordinates."
    },
    "timezone": {
      "type": "string",
      "default": "auto",
      "description": "IANA timezone (e.g., \"America/Los_Angeles\") or \"auto\" to use the location's local timezone. Default \"auto\". The timezone from openmeteo_search_locations is ideal to pass here."
    },
    "canvas_id": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{10}$",
      "description": "DataCanvas token for wide past_days or multi-variable queries. When a result is too large to return inline — driven by total payload size, so a wide multi-variable pull can spill at any row count — it spills to this canvas: pass the returned token to openmeteo_dataframe_describe to list the staged table and its columns, then to openmeteo_dataframe_query to run SQL against it. Omit to create a fresh canvas."
    },
    "longitude": {
      "type": "number",
      "maximum": 180,
      "minimum": -180,
      "description": "Longitude in decimal degrees (e.g., -122.3321 for Seattle)."
    },
    "past_days": {
      "type": "integer",
      "default": 0,
      "maximum": 92,
      "minimum": 0,
      "description": "Include this many days of past data before today (0–92). Use for recent history — the archive’s ERA5 components lag by about a week. Default 0."
    },
    "forecast_days": {
      "type": "integer",
      "default": 7,
      "maximum": 16,
      "minimum": 1,
      "description": "Number of forecast days (1–16). Default 7."
    },
    "daily_variables": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 50,
      "description": "Daily summary variables (e.g., [\"temperature_2m_max\", \"temperature_2m_min\", \"precipitation_sum\", \"wind_speed_10m_max\", \"sunrise\", \"sunset\", \"uv_index_max\"]). Daily names only — an hourly name such as cloud_cover or temperature_2m belongs in hourly_variables and is rejected here; for a daily summary of an hourly variable use its published aggregate (cloud_cover_max, cloud_cover_mean, cloud_cover_min). At least one of current_variables, hourly_variables, or daily_variables is required."
    },
    "wind_speed_unit": {
      "enum": [
        "kmh",
        "mph",
        "ms",
        "kn"
      ],
      "type": "string",
      "default": "kmh",
      "description": "Wind speed unit: \"kmh\" (km/h), \"mph\", \"ms\" (m/s), or \"kn\" (knots). Default \"kmh\"."
    },
    "hourly_variables": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 50,
      "description": "Hourly variables to fetch (e.g., [\"temperature_2m\", \"precipitation\", \"wind_speed_10m\", \"relative_humidity_2m\", \"cloud_cover\", \"uv_index\", \"apparent_temperature\"]). Hourly names only — a daily aggregate such as temperature_2m_max or precipitation_sum belongs in daily_variables and is rejected here. At least one of current_variables, hourly_variables, or daily_variables is required."
    },
    "temperature_unit": {
      "enum": [
        "celsius",
        "fahrenheit"
      ],
      "type": "string",
      "default": "celsius",
      "description": "Temperature unit. Default \"celsius\"."
    },
    "current_variables": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 50,
      "description": "Variables to return for the current instant (e.g., [\"temperature_2m\", \"precipitation\", \"wind_speed_10m\", \"weather_code\"]). Uses Open-Meteo's 15-minute current-conditions data, so it answers \"what is it doing right now?\" without requesting an hourly series and picking a row. Takes the hourly variable names; a daily-only name such as temperature_2m_max comes back null with the unit \"undefined\" and is reported in the notice. Satisfies the variable requirement on its own."
    },
    "precipitation_unit": {
      "enum": [
        "mm",
        "inch"
      ],
      "type": "string",
      "default": "mm",
      "description": "Precipitation unit: \"mm\" or \"inch\". Default \"mm\"."
    }
  },
  "additionalProperties": false
}
openmeteo_get_forecast output schema
{
  "type": "object",
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "latitude",
        "longitude",
        "elevation",
        "timezone",
        "utc_offset_seconds",
        "record_count",
        "truncated"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ],
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "daily": {
      "type": "array",
      "items": {
        "type": "object",
        "propertyNames": {
          "type": "string"
        },
        "additionalProperties": {}
      },
      "description": "Per-day records. Each object has a \"time\" field (YYYY-MM-DD) plus one key per requested variable with its value — per model, suffixed with the model name, when 2+ models were requested. Units are in the daily_units map. Absent when only hourly_variables were requested. When truncated, contains only a preview — query canvas_id for the full dataset when one is present."
    },
    "error": {
      "type": "object",
      "required": [
        "code",
        "message"
      ],
      "properties": {
        "code": {
          "type": "integer",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "examples": [
                "invalid_variable",
                "current_multi_model",
                "variable_wrong_cadence",
                "no_variables_requested",
                "invalid_timezone",
                "request_too_large",
                "coverage_gap",
                "rate_limited",
                "upstream_unavailable",
                "upstream_timeout"
              ],
              "description": "Machine-readable failure mode. Declared by this tool: `invalid_variable`: An unknown variable name or forecast model was requested. `current_multi_model`: current_variables was combined with two or more models — Open-Meteo answers current conditions from a single model in one unsuffixed block, so the values could not be attributed — caught before the request is sent. `variable_wrong_cadence`: A variable sat in the cadence field Open-Meteo does not document it under — for example cloud_cover in daily_variables, or temperature_2m_max in hourly_variables — caught before the request is sent. `no_variables_requested`: None of current_variables, hourly_variables, or daily_variables named a variable — blank or whitespace-only entries do not count. `invalid_timezone`: timezone was blank, or upstream did not recognize the requested time zone. `request_too_large`: Open-Meteo refused the request as asking for too much data in one call. `coverage_gap`: Open-Meteo has no data at this coordinate for the requested dataset or model, because its grid or domain does not reach the point. `rate_limited`: Open-Meteo answered HTTP 429 because a free-tier request limit was reached. `upstream_unavailable`: Open-Meteo was unreachable, answered with a server error, or returned an unreadable response on all three attempts. `upstream_timeout`: Open-Meteo failed on all three attempts, and the last one got no answer within 15 seconds. Other values are possible when a failure originates below the handler."
            },
            "recovery": {
              "type": "object",
              "required": [
                "hint"
              ],
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "description": "Actionable next step for the caller.",
              "additionalProperties": {}
            },
            "retryable": {
              "type": "boolean",
              "description": "Whether retrying may succeed."
            }
          },
          "additionalProperties": {}
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        }
      },
      "description": "Present when the call failed. Absent on success.",
      "additionalProperties": {}
    },
    "hourly": {
      "type": "array",
      "items": {
        "type": "object",
        "propertyNames": {
          "type": "string"
        },
        "additionalProperties": {}
      },
      "description": "Per-hour records. Each object has a \"time\" field (ISO 8601) plus one key per requested variable with its value — one key per variable per model, suffixed with the model name (temperature_2m_ecmwf_ifs025), when 2+ models were requested. Units are in the hourly_units map. Absent when only daily_variables were requested. When truncated, contains only a preview — query canvas_id for the full dataset when one is present."
    },
    "models": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Forecast models requested — echoes the models parameter. Absent when models was omitted, which means the data came from Open-Meteo's best_match blend."
    },
    "notice": {
      "type": "string",
      "description": "Everything this response needs to say beyond the data, composed into one advisory: columns the endpoint returned with the unit \"undefined\" (a name it parsed but does not serve in the requested cadence, or — on a model-suffixed column — a variable that model does not serve); requested models Open-Meteo left out for having no data at this coordinate, or the plain-named columns a multi-model request cannot attribute to one model; current values that are null under a real unit (no coverage at this location and time); recognized variables whose requested window or location falls outside the data's coverage, with the timestamps that do carry values; and, when the result spilled, either the canvas and table holding the full row set plus the two dataframe tools that read it, or — with DataCanvas disabled — why there is no canvas_id and how to reach the rows the preview omits."
    },
    "current": {
      "type": "object",
      "required": [
        "time",
        "interval"
      ],
      "properties": {
        "time": {
          "type": "string",
          "description": "Timestamp of these values (ISO 8601, in the resolved timezone)"
        },
        "interval": {
          "type": "number",
          "description": "Aggregation interval of the current block, in seconds (900 = 15 minutes) — metadata, not a requested variable. Summed or averaged values such as precipitation and sunshine_duration cover the preceding interval seconds ending at current.time; instantaneous values such as temperature_2m are valid at current.time"
        }
      },
      "description": "Conditions at a single instant: one key per requested current variable alongside time and interval. Units are in the current_units map. Absent when current_variables was not requested.",
      "additionalProperties": {
        "type": [
          "string",
          "number",
          "null"
        ]
      }
    },
    "latitude": {
      "type": "number",
      "description": "Snapped latitude (Open-Meteo snaps to nearest grid point — with models set, the grid of the first listed model that covers the coordinate)"
    },
    "timezone": {
      "type": "string",
      "description": "Resolved IANA timezone"
    },
    "canvas_id": {
      "type": "string",
      "description": "DataCanvas token for the staged full dataset. Present only when truncated is true AND DataCanvas is enabled (CANVAS_PROVIDER_TYPE=duckdb) — absent otherwise, in which case the preview is all this response carries. Pass to openmeteo_dataframe_describe to list the staged table and its columns, then to openmeteo_dataframe_query to run SQL against it."
    },
    "elevation": {
      "type": "number",
      "description": "Terrain elevation at grid point (meters)"
    },
    "longitude": {
      "type": "number",
      "description": "Snapped longitude (with models set, the grid of the first listed model that covers the coordinate)"
    },
    "truncated": {
      "type": "boolean",
      "description": "True when the response was too large to return inline, so hourly and daily carry a bounded preview rather than the full set. With DataCanvas enabled the complete data is staged at canvas_id — every hourly and daily row, including any column the preview omits. With it disabled there is no canvas_id, and the omitted rows are reached only by narrowing the request."
    },
    "table_name": {
      "type": "string",
      "description": "DuckDB table name for the staged data — use as the FROM target in openmeteo_dataframe_query SQL; openmeteo_dataframe_describe lists its columns. Present only alongside canvas_id."
    },
    "daily_units": {
      "type": "object",
      "description": "Map of variable name → unit string for daily data, model-suffixed like the records when 2+ models were requested. Absent when no daily_variables were requested.",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {
        "type": "string"
      }
    },
    "hourly_units": {
      "type": "object",
      "description": "Map of variable name → unit string for hourly data (e.g., {\"temperature_2m\": \"°C\", \"precipitation\": \"mm\"}), keyed by the same model-suffixed names as the records when 2+ models were requested. Absent when no hourly_variables were requested.",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {
        "type": "string"
      }
    },
    "record_count": {
      "type": "number",
      "description": "Total number of records (hourly + daily rows) — the full upstream total when truncated is true, not the combined length of the hourly and daily previews."
    },
    "current_units": {
      "type": "object",
      "description": "Map of key → unit string for the current block, covering time and interval as well as each requested variable (e.g., {\"interval\": \"seconds\", \"temperature_2m\": \"°C\"}). Absent when no current_variables were requested.",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {
        "type": "string"
      }
    },
    "utc_offset_seconds": {
      "type": "number",
      "description": "UTC offset in seconds for this timezone at query time"
    }
  },
  "additionalProperties": false
}
openmeteo_get_forecast annotations
{
  "readOnlyHint": true,
  "idempotentHint": true
}
openmeteo_get_historical — Openmeteo Get Historical
Advertised by the server during the last successful anonymous check.
Advertised read-only

Historical weather from the Open-Meteo reanalysis archive (1940–present). Requires start_date and end_date (ISO 8601 date, e.g., "2024-07-01"). With models omitted the archive answers from Best Match, which blends IFS HRES, ERA5, and ERA5-Land seamlessly — so the source varies by date and no single update lag describes the response. Set models to pin a consistent source for a multi-decade series: the ERA5 family updates daily with about a week of delay, while IFS HRES has none, so for the last week either request models: ["ecmwf_ifs"] or use openmeteo_get_forecast with past_days. Available models: best_match (default, blends IFS HRES + ERA5 + ERA5-Land), ecmwf_ifs (global 9 km, updated every 6 hours, no delay), ecmwf_ifs_analysis_long_window (global 9 km, daily, 2 days delay), era5_seamless (ERA5 and ERA5-Land combined), era5 (global 0.25° (~25 km), daily, about a week delay), era5_land (global 0.1° (~11 km), daily, about a week delay), era5_ensemble (global 0.5° (~55 km), daily, about a week delay), cerra (Europe only, 5 km, no real-time updates). Uses the same variable names as the forecast API for direct comparison. Large date ranges (multi-year hourly) produce thousands of records — these spill to a DataCanvas when canvas is enabled, returning canvas_id and table_name with truncated: true; inspect the staged columns with openmeteo_dataframe_describe, then query the full set with openmeteo_dataframe_query. With canvas disabled they return a bounded preview instead. At least one of hourly_variables or daily_variables is required.

openmeteo_get_historical input schema
{
  "type": "object",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "required": [
    "latitude",
    "longitude",
    "start_date",
    "end_date"
  ],
  "properties": {
    "models": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 8,
      "description": "Archive models to read from: best_match (default, blends IFS HRES + ERA5 + ERA5-Land), ecmwf_ifs (global 9 km, updated every 6 hours, no delay), ecmwf_ifs_analysis_long_window (global 9 km, daily, 2 days delay), era5_seamless (ERA5 and ERA5-Land combined), era5 (global 0.25° (~25 km), daily, about a week delay), era5_land (global 0.1° (~11 km), daily, about a week delay), era5_ensemble (global 0.5° (~55 km), daily, about a week delay), cerra (Europe only, 5 km, no real-time updates). Omit to use Open-Meteo's Best Match default, which blends IFS HRES, ERA5, and ERA5-Land — pin a model instead when a consistent source matters across the range. With 2+ models each variable column is suffixed with the model name. cerra covers Europe only: requested alone elsewhere it is rejected as a coverage gap, and beside another model the archive drops it and the notice says so. A name outside this list is sent upstream rather than rejected here."
    },
    "end_date": {
      "type": "string",
      "format": "date",
      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      "description": "End date (YYYY-MM-DD, inclusive). Must be on or after start_date. For the last few days, either request models: [\"ecmwf_ifs\"] or use openmeteo_get_forecast with past_days."
    },
    "latitude": {
      "type": "number",
      "maximum": 90,
      "minimum": -90,
      "description": "Latitude in decimal degrees. Use openmeteo_search_locations to resolve a place name to coordinates."
    },
    "timezone": {
      "type": "string",
      "default": "auto",
      "description": "IANA timezone or \"auto\". Default \"auto\"."
    },
    "canvas_id": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{10}$",
      "description": "DataCanvas token for multi-year or multi-variable queries. When a result is too large to return inline — driven by total payload size, so a wide multi-variable pull can spill at any row count — it spills to this canvas: pass the returned token to openmeteo_dataframe_describe to list the staged table and its columns, then to openmeteo_dataframe_query to run SQL against it. Omit to create a fresh canvas."
    },
    "longitude": {
      "type": "number",
      "maximum": 180,
      "minimum": -180,
      "description": "Longitude in decimal degrees."
    },
    "start_date": {
      "type": "string",
      "format": "date",
      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      "description": "Start date (YYYY-MM-DD, e.g., \"2024-07-01\"). The archive covers from 1940-01-01; how close to today it reaches depends on the model — the ERA5 family runs about a week behind, IFS HRES is current."
    },
    "daily_variables": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 50,
      "description": "Daily summary variables (e.g., [\"temperature_2m_max\", \"temperature_2m_min\", \"precipitation_sum\", \"wind_speed_10m_max\"]). Daily names only — an hourly name such as cloud_cover or temperature_2m belongs in hourly_variables and is rejected here; for a daily summary of an hourly variable use its published aggregate (cloud_cover_max, cloud_cover_mean, cloud_cover_min). At least one of hourly_variables or daily_variables required."
    },
    "wind_speed_unit": {
      "enum": [
        "kmh",
        "mph",
        "ms",
        "kn"
      ],
      "type": "string",
      "default": "kmh",
      "description": "Wind speed unit. Default \"kmh\"."
    },
    "hourly_variables": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 50,
      "description": "Hourly archive variables (e.g., [\"temperature_2m\", \"precipitation\", \"wind_speed_10m\", \"relative_humidity_2m\", \"cloud_cover\", \"soil_moisture_0_to_7cm\"]). Hourly names only — a daily aggregate such as temperature_2m_max or precipitation_sum belongs in daily_variables and is rejected here. At least one of hourly_variables or daily_variables required."
    },
    "temperature_unit": {
      "enum": [
        "celsius",
        "fahrenheit"
      ],
      "type": "string",
      "default": "celsius",
      "description": "Temperature unit. Default \"celsius\"."
    },
    "precipitation_unit": {
      "enum": [
        "mm",
        "inch"
      ],
      "type": "string",
      "default": "mm",
      "description": "Precipitation unit. Default \"mm\"."
    }
  },
  "additionalProperties": false
}
openmeteo_get_historical output schema
{
  "type": "object",
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "latitude",
        "longitude",
        "elevation",
        "timezone",
        "date_range",
        "record_count",
        "truncated"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ],
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "daily": {
      "type": "array",
      "items": {
        "type": "object",
        "propertyNames": {
          "type": "string"
        },
        "additionalProperties": {}
      },
      "description": "Per-day records with \"time\" (YYYY-MM-DD) + variable keys. Absent when only hourly_variables were requested. When truncated, contains only a preview — query canvas_id for the full dataset when one is present."
    },
    "error": {
      "type": "object",
      "required": [
        "code",
        "message"
      ],
      "properties": {
        "code": {
          "type": "integer",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "examples": [
                "date_out_of_range",
                "date_order_invalid",
                "no_variables_requested",
                "invalid_variable",
                "variable_wrong_cadence",
                "invalid_timezone",
                "request_too_large",
                "coverage_gap",
                "rate_limited",
                "upstream_unavailable",
                "upstream_timeout"
              ],
              "description": "Machine-readable failure mode. Declared by this tool: `date_out_of_range`: start_date predates 1940-01-01, or the requested dates fall outside the coverage of the selected model. `date_order_invalid`: The end_date value is earlier than the start_date value. `no_variables_requested`: Neither hourly_variables nor daily_variables named a variable — blank or whitespace-only entries do not count. `invalid_variable`: An unknown variable name or unsupported archive model was requested. `variable_wrong_cadence`: A variable sat in the cadence field Open-Meteo does not document it under — for example cloud_cover in daily_variables, or temperature_2m_max in hourly_variables — caught before the request is sent. `invalid_timezone`: timezone was blank, or upstream did not recognize the requested time zone. `request_too_large`: Open-Meteo refused the request as asking for too much data in one call. `coverage_gap`: Open-Meteo has no data at this coordinate for the requested dataset or model, because its grid or domain does not reach the point. `rate_limited`: Open-Meteo answered HTTP 429 because a free-tier request limit was reached. `upstream_unavailable`: Open-Meteo was unreachable, answered with a server error, or returned an unreadable response on all three attempts. `upstream_timeout`: Open-Meteo failed on all three attempts, and the last one got no answer within 15 seconds. Other values are possible when a failure originates below the handler."
            },
            "recovery": {
              "type": "object",
              "required": [
                "hint"
              ],
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "description": "Actionable next step for the caller.",
              "additionalProperties": {}
            },
            "retryable": {
              "type": "boolean",
              "description": "Whether retrying may succeed."
            }
          },
          "additionalProperties": {}
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        }
      },
      "description": "Present when the call failed. Absent on success.",
      "additionalProperties": {}
    },
    "hourly": {
      "type": "array",
      "items": {
        "type": "object",
        "propertyNames": {
          "type": "string"
        },
        "additionalProperties": {}
      },
      "description": "Per-hour records with \"time\" (ISO 8601) + variable keys. Absent when only daily_variables were requested. When truncated, contains only a preview — query canvas_id for the full dataset when one is present."
    },
    "models": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Archive models requested — echoes the models parameter. Absent when models was omitted, which means the data came from Open-Meteo Best Match (IFS HRES + ERA5 + ERA5-Land) and the source varies by date."
    },
    "notice": {
      "type": "string",
      "description": "Everything this response needs to say beyond the data, composed into one advisory: columns the archive returned with the unit \"undefined\" (a name it parsed but does not serve in the requested cadence, or — on a model-suffixed column — a variable that model does not serve); requested models the archive left out for having no data at this coordinate, or the plain-named columns a multi-model request cannot attribute to one model; recognized variables whose requested window falls outside the data's coverage, with the timestamps that do carry values; and, when the result spilled, either the canvas and table holding the full row set plus the two dataframe tools that read it, or — with DataCanvas disabled — why there is no canvas_id and how to reach the rows the preview omits."
    },
    "latitude": {
      "type": "number",
      "description": "Snapped latitude"
    },
    "timezone": {
      "type": "string",
      "description": "Resolved IANA timezone"
    },
    "canvas_id": {
      "type": "string",
      "description": "DataCanvas token for the staged full dataset. Present only when truncated is true AND DataCanvas is enabled (CANVAS_PROVIDER_TYPE=duckdb) — absent otherwise, in which case the preview is all this response carries. Pass to openmeteo_dataframe_describe to list the staged table and its columns, then to openmeteo_dataframe_query to run SQL against it."
    },
    "elevation": {
      "type": "number",
      "description": "Elevation at grid point (meters)"
    },
    "longitude": {
      "type": "number",
      "description": "Snapped longitude"
    },
    "truncated": {
      "type": "boolean",
      "description": "True when the response was too large to return inline, so hourly and daily carry a bounded preview rather than the full set. With DataCanvas enabled the complete data is staged at canvas_id — every hourly and daily row, including any column the preview omits. With it disabled there is no canvas_id, and the omitted rows are reached only by narrowing the request."
    },
    "date_range": {
      "type": "object",
      "required": [
        "start",
        "end"
      ],
      "properties": {
        "end": {
          "type": "string",
          "description": "Actual end date of returned data"
        },
        "start": {
          "type": "string",
          "description": "Actual start date of returned data"
        }
      },
      "description": "Date range of returned data",
      "additionalProperties": false
    },
    "table_name": {
      "type": "string",
      "description": "DuckDB table name for the staged data — use as the FROM target in openmeteo_dataframe_query SQL; openmeteo_dataframe_describe lists its columns. Present only alongside canvas_id."
    },
    "daily_units": {
      "type": "object",
      "description": "Variable → unit string for daily data. Absent when no daily_variables were requested.",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {
        "type": "string"
      }
    },
    "hourly_units": {
      "type": "object",
      "description": "Variable → unit string for hourly data (e.g., {\"temperature_2m\": \"°C\", \"precipitation\": \"mm\"}). Absent when no hourly_variables were requested.",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {
        "type": "string"
      }
    },
    "record_count": {
      "type": "number",
      "description": "Total number of records (hourly + daily rows) — the full upstream total when truncated is true, not the combined length of the hourly and daily previews."
    }
  },
  "additionalProperties": false
}
openmeteo_get_historical annotations
{
  "readOnlyHint": true,
  "idempotentHint": true
}
openmeteo_get_marine — Openmeteo Get Marine
Advertised by the server during the last successful anonymous check.
Advertised read-only

Marine wave and ocean conditions for a coastal or ocean coordinate: wave height, wave period, wave direction, wind-wave height, swell height, sea-surface temperature. Forecast horizon up to 8 days, with optional past_days (up to 92) for recent history — or start_date and end_date together for an archive range, which returns real wave values back to at least 2022. One window per call: a date range is mutually exclusive with forecast_days and past_days, and needs both ends — a lone start_date or end_date is rejected. Returns per-timestamp records — each entry contains a "time" field plus one key per requested variable. Best for open-ocean and coastal exposed points — sheltered water such as a sound or bay returns near-zero wave values, and a land point well inland returns null values, which the notice reports. A land point near the coast is answered from a nearby sea cell with real values and no notice, so check the response latitude and longitude is the water you meant. 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: wave_height_max, wave_direction_dominant, wave_period_max. Note: ocean_current_velocity is null for non-open-ocean coordinates. Set current_variables for conditions at this instant — Open-Meteo serves those from 15-minute model data, and the response carries a current object plus a current_units map, alongside either window. length_unit "imperial" returns heights in ft, wind_speed_unit sets the ocean_current_velocity unit, and temperature_unit the sea_surface_temperature unit — converted upstream, with every unit read from the *_units maps. A wide window — a large past_days or date range plus many variables — produces thousands of records; these spill to a DataCanvas when canvas is enabled, returning canvas_id and table_name with truncated: true — inspect the staged columns with openmeteo_dataframe_describe, then query the full set with openmeteo_dataframe_query. With canvas disabled they return a bounded preview instead. At least one of current_variables, hourly_variables, or daily_variables is required.

openmeteo_get_marine input schema
{
  "type": "object",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "required": [
    "latitude",
    "longitude"
  ],
  "properties": {
    "end_date": {
      "type": "string",
      "format": "date",
      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      "description": "End date for the archive range (YYYY-MM-DD, inclusive). Must be on or after start_date. Requires start_date — the pair must be sent together, and neither combines with forecast_days or past_days."
    },
    "latitude": {
      "type": "number",
      "maximum": 90,
      "minimum": -90,
      "description": "Latitude of a coastal or ocean point. Use openmeteo_search_locations to resolve a place name. Sheltered water returns near-zero wave values; a land point well inland returns null values, which the notice reports, while one near the coast is answered from a nearby sea cell with no notice — check the response latitude and longitude is the water you meant."
    },
    "timezone": {
      "type": "string",
      "default": "auto",
      "description": "IANA timezone or \"auto\". Default \"auto\"."
    },
    "canvas_id": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{10}$",
      "description": "DataCanvas token for wide past_days, archive-range, or multi-variable queries. When a result is too large to return inline — driven by total payload size, so a wide multi-variable pull can spill at any row count — it spills to this canvas: pass the returned token to openmeteo_dataframe_describe to list the staged table and its columns, then to openmeteo_dataframe_query to run SQL against it. Omit to create a fresh canvas."
    },
    "longitude": {
      "type": "number",
      "maximum": 180,
      "minimum": -180,
      "description": "Longitude in decimal degrees."
    },
    "past_days": {
      "type": "integer",
      "default": 0,
      "maximum": 92,
      "minimum": 0,
      "description": "Include this many days of past data before today (0–92). Use for recent history instead of a start_date/end_date range. Default 0. Must stay 0 when start_date/end_date are used."
    },
    "start_date": {
      "type": "string",
      "format": "date",
      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      "description": "Start date for the archive range (YYYY-MM-DD, e.g., \"2024-07-01\"). Real wave values go back to at least 2022. Requires end_date — the pair must be sent together, and neither combines with forecast_days or past_days."
    },
    "length_unit": {
      "enum": [
        "metric",
        "imperial"
      ],
      "type": "string",
      "default": "metric",
      "description": "Unit for wave, wind-wave, and swell heights and sea_level_height_msl: \"metric\" (m) or \"imperial\" (ft). Periods stay in seconds and directions in degrees. Default \"metric\"."
    },
    "forecast_days": {
      "type": "integer",
      "maximum": 8,
      "minimum": 1,
      "description": "Forecast horizon in days (1–8). Omit for the upstream default of 7. Mutually exclusive with start_date/end_date — omit it entirely when pulling an archive range."
    },
    "daily_variables": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 50,
      "description": "Daily marine summary variables (e.g., [\"wave_height_max\", \"wave_direction_dominant\", \"wave_period_max\"]). Daily names only — an hourly name such as wave_height belongs in hourly_variables and is rejected here; for a daily summary use its published aggregate (wave_height_max). At least one of current_variables, hourly_variables, or daily_variables is required."
    },
    "wind_speed_unit": {
      "enum": [
        "kmh",
        "mph",
        "ms",
        "kn"
      ],
      "type": "string",
      "default": "kmh",
      "description": "Unit for ocean_current_velocity: \"kmh\" (km/h), \"mph\" (labelled \"mp/h\" in the units maps), \"ms\" (m/s), or \"kn\" (knots). Default \"kmh\"."
    },
    "hourly_variables": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 50,
      "description": "Hourly marine variables (e.g., [\"wave_height\", \"wave_direction\", \"wave_period\", \"wind_wave_height\", \"swell_wave_height\"]). Hourly names only — a daily aggregate such as wave_height_max or wave_direction_dominant belongs in daily_variables and is rejected here. At least one of current_variables, hourly_variables, or daily_variables is required."
    },
    "temperature_unit": {
      "enum": [
        "celsius",
        "fahrenheit"
      ],
      "type": "string",
      "default": "celsius",
      "description": "Unit for sea_surface_temperature: \"celsius\" or \"fahrenheit\". Default \"celsius\"."
    },
    "current_variables": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 50,
      "description": "Marine variables to return for the current instant (e.g., [\"wave_height\", \"wave_period\", \"wave_direction\", \"sea_surface_temperature\"]). Uses Open-Meteo's 15-minute current-conditions data, so it answers \"what are the waves right now?\" without requesting an hourly series and picking a row. Takes the hourly variable names — a daily aggregate such as wave_height_max belongs in daily_variables and is rejected here. Combines with past_days or a start_date/end_date range; the current block is always the present instant. Satisfies the variable requirement on its own."
    }
  },
  "additionalProperties": false
}
openmeteo_get_marine output schema
{
  "type": "object",
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "latitude",
        "longitude",
        "timezone",
        "record_count",
        "truncated"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ],
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "daily": {
      "type": "array",
      "items": {
        "type": "object",
        "propertyNames": {
          "type": "string"
        },
        "additionalProperties": {}
      },
      "description": "Per-day summary records with \"time\" (YYYY-MM-DD) + variable keys (e.g., wave_height_max, wave_direction_dominant, wave_period_max). Units are in the daily_units map; heights follow length_unit. Absent when only hourly_variables were requested. When truncated, contains only a preview — query canvas_id for the full dataset when one is present."
    },
    "error": {
      "type": "object",
      "required": [
        "code",
        "message"
      ],
      "properties": {
        "code": {
          "type": "integer",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "examples": [
                "invalid_variable",
                "variable_wrong_cadence",
                "no_variables_requested",
                "date_range_incomplete",
                "forecast_window_conflict",
                "date_order_invalid",
                "invalid_timezone",
                "request_too_large",
                "coverage_gap",
                "rate_limited",
                "upstream_unavailable",
                "upstream_timeout"
              ],
              "description": "Machine-readable failure mode. Declared by this tool: `invalid_variable`: An unknown marine variable name was requested. `variable_wrong_cadence`: A variable sat in a field Open-Meteo does not document it under — for example wave_height in daily_variables, or wave_height_max in hourly_variables or current_variables — caught before the request is sent. `no_variables_requested`: None of current_variables, hourly_variables, or daily_variables named a variable — blank or whitespace-only entries do not count. `date_range_incomplete`: Only one of start_date / end_date was provided — the marine archive requires the pair together. `forecast_window_conflict`: A start_date or end_date range was combined with forecast_days or a non-zero past_days value. `date_order_invalid`: The end_date value is earlier than the start_date value. `invalid_timezone`: timezone was blank, or upstream did not recognize the requested time zone. `request_too_large`: Open-Meteo refused the request as asking for too much data in one call. `coverage_gap`: Open-Meteo has no data at this coordinate for the requested dataset or model, because its grid or domain does not reach the point. `rate_limited`: Open-Meteo answered HTTP 429 because a free-tier request limit was reached. `upstream_unavailable`: Open-Meteo was unreachable, answered with a server error, or returned an unreadable response on all three attempts. `upstream_timeout`: Open-Meteo failed on all three attempts, and the last one got no answer within 15 seconds. Other values are possible when a failure originates below the handler."
            },
            "recovery": {
              "type": "object",
              "required": [
                "hint"
              ],
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "description": "Actionable next step for the caller.",
              "additionalProperties": {}
            },
            "retryable": {
              "type": "boolean",
              "description": "Whether retrying may succeed."
            }
          },
          "additionalProperties": {}
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        }
      },
      "description": "Present when the call failed. Absent on success.",
      "additionalProperties": {}
    },
    "hourly": {
      "type": "array",
      "items": {
        "type": "object",
        "propertyNames": {
          "type": "string"
        },
        "additionalProperties": {}
      },
      "description": "Per-hour records with \"time\" (ISO 8601) + one key per requested variable (e.g., wave_height, wave_direction, wave_period). Units are in the hourly_units map: heights follow length_unit, ocean_current_velocity follows wind_speed_unit, sea_surface_temperature follows temperature_unit. Absent when only daily_variables were requested. When truncated, contains only a preview — query canvas_id for the full dataset when one is present."
    },
    "notice": {
      "type": "string",
      "description": "Everything this response needs to say beyond the data, composed into one advisory: columns the endpoint returned with the unit \"undefined\" (a name it parsed but does not serve); current values that are null under a real unit (no coverage at this location, such as a point well inland); recognized variables whose requested window or location falls outside the data's coverage (a point well inland returns nulls), with the timestamps that do carry values; and, when the result spilled, either the canvas and table holding the full row set plus the two dataframe tools that read it, or — with DataCanvas disabled — why there is no canvas_id and how to reach the rows the preview omits."
    },
    "current": {
      "type": "object",
      "required": [
        "time",
        "interval"
      ],
      "properties": {
        "time": {
          "type": "string",
          "description": "Timestamp of these values (ISO 8601, in the resolved timezone)"
        },
        "interval": {
          "type": "number",
          "description": "Aggregation interval of the current block, in seconds (900 = 15 minutes on this endpoint) — metadata, not a requested variable. The marine variables are instantaneous, so each value is valid at current.time"
        }
      },
      "description": "Conditions at a single instant: one key per requested current variable alongside time and interval. Units are in the current_units map. Absent when current_variables was not requested.",
      "additionalProperties": {
        "type": [
          "string",
          "number",
          "null"
        ]
      }
    },
    "latitude": {
      "type": "number",
      "description": "Snapped latitude"
    },
    "timezone": {
      "type": "string",
      "description": "Resolved IANA timezone"
    },
    "canvas_id": {
      "type": "string",
      "description": "DataCanvas token for the staged full dataset. Present only when truncated is true AND DataCanvas is enabled (CANVAS_PROVIDER_TYPE=duckdb) — absent otherwise, in which case the preview is all this response carries. Pass to openmeteo_dataframe_describe to list the staged table and its columns, then to openmeteo_dataframe_query to run SQL against it."
    },
    "longitude": {
      "type": "number",
      "description": "Snapped longitude"
    },
    "truncated": {
      "type": "boolean",
      "description": "True when the response was too large to return inline, so hourly and daily carry a bounded preview rather than the full set. With DataCanvas enabled the complete data is staged at canvas_id — every hourly and daily row, including any column the preview omits. With it disabled there is no canvas_id, and the omitted rows are reached only by narrowing the request."
    },
    "table_name": {
      "type": "string",
      "description": "DuckDB table name for the staged data — use as the FROM target in openmeteo_dataframe_query SQL; openmeteo_dataframe_describe lists its columns. Present only alongside canvas_id."
    },
    "daily_units": {
      "type": "object",
      "description": "Variable → unit string for daily data. Absent when no daily_variables were requested.",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {
        "type": "string"
      }
    },
    "hourly_units": {
      "type": "object",
      "description": "Variable → unit string for hourly data, as Open-Meteo labels it (e.g., {\"wave_height\": \"ft\", \"wave_period\": \"s\"} under length_unit \"imperial\"). Absent when no hourly_variables were requested.",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {
        "type": "string"
      }
    },
    "record_count": {
      "type": "number",
      "description": "Total number of records (hourly + daily rows) — the full upstream total when truncated is true, not the combined length of the hourly and daily previews."
    },
    "current_units": {
      "type": "object",
      "description": "Key → unit string for the current block, covering time and interval as well as each requested variable (e.g., {\"interval\": \"seconds\", \"wave_height\": \"ft\"}). Absent when no current_variables were requested.",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}
openmeteo_get_marine annotations
{
  "readOnlyHint": true,
  "idempotentHint": true
}
openmeteo_get_air_quality — Openmeteo Get Air Quality
Advertised by the server during the last successful anonymous check.
Advertised read-only

Modeled CAMS (Copernicus Atmosphere Monitoring Service) air quality: 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 openaq-mcp-server. Forecast horizon up to 7 days, with optional past_days (up to 92) for recent history — or start_date and end_date together for an archive range; the CAMS global archive begins in August 2022, and earlier dates return rows of nulls. One window per call: a date range is mutually exclusive with forecast_days and past_days, and needs both ends — a lone start_date or end_date is rejected. 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. Pollen is modeled for Europe only, and only in pollen season — elsewhere the pollen values come back null, and the notice names them. Set current_variables for pollutant and AQI values at this instant — returned as a current object plus a current_units map, and enough on its own without hourly_variables; the block’s interval field is its aggregation interval in seconds (3600 on this endpoint). A wide window — a large past_days or date range plus many variables — produces thousands of records; these spill to a DataCanvas when canvas is enabled, returning canvas_id and table_name with truncated: true — inspect the staged columns with openmeteo_dataframe_describe, then query the full set with openmeteo_dataframe_query. With canvas disabled they return a bounded preview instead.

openmeteo_get_air_quality input schema
{
  "type": "object",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "required": [
    "latitude",
    "longitude"
  ],
  "properties": {
    "end_date": {
      "type": "string",
      "format": "date",
      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      "description": "End date for the archive range (YYYY-MM-DD, inclusive). Must be on or after start_date. Requires start_date — the pair must be sent together, and neither combines with forecast_days or past_days."
    },
    "latitude": {
      "type": "number",
      "maximum": 90,
      "minimum": -90,
      "description": "Latitude in decimal degrees. Use openmeteo_search_locations to resolve a place name."
    },
    "timezone": {
      "type": "string",
      "default": "auto",
      "description": "IANA timezone or \"auto\". Default \"auto\"."
    },
    "canvas_id": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{10}$",
      "description": "DataCanvas token for wide past_days, archive-range, or multi-variable queries. When a result is too large to return inline — driven by total payload size, so a wide multi-variable pull can spill at any row count — it spills to this canvas: pass the returned token to openmeteo_dataframe_describe to list the staged table and its columns, then to openmeteo_dataframe_query to run SQL against it. Omit to create a fresh canvas."
    },
    "longitude": {
      "type": "number",
      "maximum": 180,
      "minimum": -180,
      "description": "Longitude in decimal degrees."
    },
    "past_days": {
      "type": "integer",
      "default": 0,
      "maximum": 92,
      "minimum": 0,
      "description": "Include this many days of past data before today (0–92). Use for recent history instead of a start_date/end_date range. Default 0. Must stay 0 when start_date/end_date are used."
    },
    "start_date": {
      "type": "string",
      "format": "date",
      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      "description": "Start date for the archive range (YYYY-MM-DD, e.g., \"2024-07-01\"). The CAMS global archive begins in August 2022; earlier dates return rows of nulls, and us_aqi starts a day later than the pollutant series (european_aqi starts with it). Requires end_date — the pair must be sent together, and neither combines with forecast_days or past_days."
    },
    "forecast_days": {
      "type": "integer",
      "maximum": 7,
      "minimum": 1,
      "description": "Forecast horizon in days (1–7). Omit for the upstream default of 5. Mutually exclusive with start_date/end_date — omit it entirely when pulling an archive range."
    },
    "hourly_variables": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 50,
      "description": "Hourly air quality variables (e.g., [\"pm2_5\", \"pm10\", \"ozone\", \"nitrogen_dioxide\", \"european_aqi\", \"us_aqi\"]). At least one of current_variables or hourly_variables is required."
    },
    "current_variables": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 50,
      "description": "Air quality variables to return for the current instant (e.g., [\"pm2_5\", \"pm10\", \"european_aqi\", \"us_aqi\"]). Uses Open-Meteo's current-conditions data, so it answers \"what is the AQI now?\" without requesting an hourly series and picking a row; the returned interval is the block's aggregation interval, 3600 seconds on this endpoint. Satisfies the variable requirement on its own."
    }
  },
  "additionalProperties": false
}
openmeteo_get_air_quality output schema
{
  "type": "object",
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "latitude",
        "longitude",
        "timezone",
        "record_count",
        "data_source",
        "truncated"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ],
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "error": {
      "type": "object",
      "required": [
        "code",
        "message"
      ],
      "properties": {
        "code": {
          "type": "integer",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "examples": [
                "invalid_variable",
                "no_variables_requested",
                "date_range_incomplete",
                "forecast_window_conflict",
                "date_order_invalid",
                "invalid_timezone",
                "request_too_large",
                "rate_limited",
                "upstream_unavailable",
                "upstream_timeout"
              ],
              "description": "Machine-readable failure mode. Declared by this tool: `invalid_variable`: An unknown air quality variable name was requested. `no_variables_requested`: Neither current_variables nor hourly_variables named a variable — blank or whitespace-only entries do not count. `date_range_incomplete`: Only one of start_date / end_date was provided — the CAMS archive requires the pair together. `forecast_window_conflict`: A start_date or end_date range was combined with forecast_days or a non-zero past_days value. `date_order_invalid`: The end_date value is earlier than the start_date value. `invalid_timezone`: timezone was blank, or upstream did not recognize the requested time zone. `request_too_large`: Open-Meteo refused the request as asking for too much data in one call. `rate_limited`: Open-Meteo answered HTTP 429 because a free-tier request limit was reached. `upstream_unavailable`: Open-Meteo was unreachable, answered with a server error, or returned an unreadable response on all three attempts. `upstream_timeout`: Open-Meteo failed on all three attempts, and the last one got no answer within 15 seconds. Other values are possible when a failure originates below the handler."
            },
            "recovery": {
              "type": "object",
              "required": [
                "hint"
              ],
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "description": "Actionable next step for the caller.",
              "additionalProperties": {}
            },
            "retryable": {
              "type": "boolean",
              "description": "Whether retrying may succeed."
            }
          },
          "additionalProperties": {}
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        }
      },
      "description": "Present when the call failed. Absent on success.",
      "additionalProperties": {}
    },
    "hourly": {
      "type": "array",
      "items": {
        "type": "object",
        "propertyNames": {
          "type": "string"
        },
        "additionalProperties": {}
      },
      "description": "Per-hour records with \"time\" (ISO 8601) + one key per requested variable. Units: pm2_5/pm10/dust in μg/m³, carbon_monoxide in μg/m³, nitrogen_dioxide/sulphur_dioxide/ozone in μg/m³, european_aqi/us_aqi as index values. When truncated, contains only a preview — query canvas_id for the full dataset when one is present."
    },
    "notice": {
      "type": "string",
      "description": "Everything this response needs to say beyond the data, composed into one advisory: columns the endpoint returned with the unit \"undefined\" (a name it parsed but does not serve); current values that are null under a real unit (no coverage at this location and time, such as pollen outside Europe); recognized variables whose requested window or location falls outside CAMS coverage, with the timestamps that do carry values; and, when the result spilled, either the canvas and table holding the full row set plus the two dataframe tools that read it, or — with DataCanvas disabled — why there is no canvas_id and how to reach the rows the preview omits."
    },
    "current": {
      "type": "object",
      "required": [
        "time",
        "interval"
      ],
      "properties": {
        "time": {
          "type": "string",
          "description": "Timestamp of these values (ISO 8601, in the resolved timezone)"
        },
        "interval": {
          "type": "number",
          "description": "Aggregation interval of the current block, in seconds (3600 on this endpoint) — metadata, not a requested variable. Pollutant concentrations are instantaneous at current.time; the AQI indices are computed over their own averaging windows, not over interval"
        }
      },
      "description": "Pollutant and index values at a single instant: one key per requested current variable alongside time and interval. Units are in the current_units map. Absent when current_variables was not requested.",
      "additionalProperties": {
        "type": [
          "string",
          "number",
          "null"
        ]
      }
    },
    "latitude": {
      "type": "number",
      "description": "Snapped latitude"
    },
    "timezone": {
      "type": "string",
      "description": "Resolved IANA timezone"
    },
    "canvas_id": {
      "type": "string",
      "description": "DataCanvas token for the staged full dataset. Present only when truncated is true AND DataCanvas is enabled (CANVAS_PROVIDER_TYPE=duckdb) — absent otherwise, in which case the preview is all this response carries. Pass to openmeteo_dataframe_describe to list the staged table and its columns, then to openmeteo_dataframe_query to run SQL against it."
    },
    "longitude": {
      "type": "number",
      "description": "Snapped longitude"
    },
    "truncated": {
      "type": "boolean",
      "description": "True when the response was too large to return inline, so hourly carries a bounded preview rather than the full set. With DataCanvas enabled the complete data is staged at canvas_id. With it disabled there is no canvas_id, and the omitted rows are reached only by narrowing the request."
    },
    "table_name": {
      "type": "string",
      "description": "DuckDB table name for the staged data — use as the FROM target in openmeteo_dataframe_query SQL; openmeteo_dataframe_describe lists its columns. Present only alongside canvas_id."
    },
    "data_source": {
      "type": "string",
      "const": "CAMS",
      "description": "Data source identifier — this is modeled CAMS data, forecast or archive, not measured station data."
    },
    "hourly_units": {
      "type": "object",
      "description": "Variable → unit string for hourly data (e.g., {\"pm2_5\": \"μg/m³\", \"european_aqi\": \"EAQI\"}).",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {
        "type": "string"
      }
    },
    "record_count": {
      "type": "number",
      "description": "Total number of hourly records — the full upstream total when truncated is true, not the length of the hourly preview."
    },
    "current_units": {
      "type": "object",
      "description": "Key → unit string for the current block, covering time and interval as well as each requested variable (e.g., {\"interval\": \"seconds\", \"pm2_5\": \"μg/m³\"}). Absent when no current_variables were requested.",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {
        "type": "string"
      }
    }
  },
  "additionalProperties": false
}
openmeteo_get_air_quality annotations
{
  "readOnlyHint": true,
  "idempotentHint": true
}
openmeteo_get_ensemble — Openmeteo Get Ensemble
Advertised by the server during the last successful anonymous check.
Advertised read-only

Probabilistic ensemble weather forecast — up to 64 ensemble members, up to 35 days ahead with optional past_days (0–92). Most models end within 16 days; a model's columns are null past its horizon, and the response notice names where its data ends. 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 members to compute exceedance probabilities, quantify forecast uncertainty, and build decision thresholds. Available models: ecmwf_ifs025_ensemble (51 members, global 0.25°), ecmwf_aifs025_ensemble (51, global 0.25°), ecmwf_ifs_europe_ensemble (51, Europe 9 km), ecmwf_aifs_europe_ensemble (51, Europe 31 km), google_weathernext2_ensemble (64, global 0.25°), ncep_gefs_seamless (31, global blend), ncep_gefs025 (31, global 0.25°), ncep_gefs05 (31, global 50 km, 35 days), ncep_aigefs025 (31, global 0.25°), icon_seamless_eps (20–40, global/Europe blend), icon_global_eps (40, global 26 km), icon_eu_eps (40, Europe 13 km), icon_d2_eps (20, Central Europe 2 km), gem_global_ensemble (21, global 0.25°), bom_access_global_ensemble (18, global 40 km), ukmo_global_ensemble_20km (18, global 20 km), ukmo_uk_ensemble_2km (3, UK 2 km), meteoswiss_icon_ch1_ensemble (11, Central Europe 1 km), meteoswiss_icon_ch2_ensemble (21, Central Europe 2 km). Omit models to use ecmwf_ifs025_ensemble. A regional model returns no data outside the area it covers; that comes back as an input error naming the coverage gap, not a transient failure, so pick a global model or move the coordinate inside the region rather than retrying. A model name this list does not carry is still sent upstream, so a newly added one keeps working. Large multi-member, multi-day pulls produce thousands of records and spill to a DataCanvas when canvas is enabled, returning canvas_id and table_name with truncated: true — inspect the staged columns with openmeteo_dataframe_describe, then query the full set with openmeteo_dataframe_query. With canvas disabled they return a bounded preview instead. At least one of hourly_variables or daily_variables is required.

openmeteo_get_ensemble input schema
{
  "type": "object",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "required": [
    "latitude",
    "longitude"
  ],
  "properties": {
    "models": {
      "type": "string",
      "description": "Ensemble model to use, one name: ecmwf_ifs025_ensemble (51 members, global 0.25°), ecmwf_aifs025_ensemble (51, global 0.25°), ecmwf_ifs_europe_ensemble (51, Europe 9 km), ecmwf_aifs_europe_ensemble (51, Europe 31 km), google_weathernext2_ensemble (64, global 0.25°), ncep_gefs_seamless (31, global blend), ncep_gefs025 (31, global 0.25°), ncep_gefs05 (31, global 50 km, 35 days), ncep_aigefs025 (31, global 0.25°), icon_seamless_eps (20–40, global/Europe blend), icon_global_eps (40, global 26 km), icon_eu_eps (40, Europe 13 km), icon_d2_eps (20, Central Europe 2 km), gem_global_ensemble (21, global 0.25°), bom_access_global_ensemble (18, global 40 km), ukmo_global_ensemble_20km (18, global 20 km), ukmo_uk_ensemble_2km (3, UK 2 km), meteoswiss_icon_ch1_ensemble (11, Central Europe 1 km), meteoswiss_icon_ch2_ensemble (21, Central Europe 2 km). Member counts include the control run. Omit or leave blank to use ecmwf_ifs025_ensemble. A name outside this list is sent upstream rather than rejected here, so a model Open-Meteo adds later still works."
    },
    "latitude": {
      "type": "number",
      "maximum": 90,
      "minimum": -90,
      "description": "Latitude in decimal degrees. Use openmeteo_search_locations to resolve a place name to coordinates."
    },
    "timezone": {
      "type": "string",
      "default": "auto",
      "description": "IANA timezone (e.g., \"America/Los_Angeles\") or \"auto\" to use the location's local timezone. Default \"auto\"."
    },
    "canvas_id": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{10}$",
      "description": "DataCanvas token for large multi-member queries. When a result is too large to return inline — driven by total payload size, so a wide member fan-out can spill at any row count — it spills to this canvas: pass the returned token to openmeteo_dataframe_describe to list the staged table and its per-member columns, then to openmeteo_dataframe_query to run SQL against it. Omit to create a fresh canvas."
    },
    "longitude": {
      "type": "number",
      "maximum": 180,
      "minimum": -180,
      "description": "Longitude in decimal degrees."
    },
    "past_days": {
      "type": "integer",
      "default": 0,
      "maximum": 92,
      "minimum": 0,
      "description": "Include this many days of past ensemble data before today (0–92). Default 0."
    },
    "forecast_days": {
      "type": "integer",
      "default": 7,
      "maximum": 35,
      "minimum": 1,
      "description": "Forecast horizon in days (1–35). Default 7. Most ensemble models end within 16 days; past a model's horizon its columns come back null and the response notice names where its data ends."
    },
    "daily_variables": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 50,
      "description": "Daily variables to fetch across all ensemble members (e.g., [\"temperature_2m_max\", \"temperature_2m_min\", \"precipitation_sum\"]). Each variable appears as temperature_2m_max_member01, … Daily names only — an hourly name such as precipitation or temperature_2m belongs in hourly_variables and is rejected here; for a daily summary use its published aggregate (precipitation_sum, temperature_2m_max). At least one of hourly_variables or daily_variables required."
    },
    "wind_speed_unit": {
      "enum": [
        "kmh",
        "mph",
        "ms",
        "kn"
      ],
      "type": "string",
      "default": "kmh",
      "description": "Wind speed unit. Default \"kmh\"."
    },
    "hourly_variables": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 50,
      "description": "Hourly variables to fetch across all ensemble members (e.g., [\"temperature_2m\", \"precipitation\", \"wind_speed_10m\"]). Each variable appears as temperature_2m_member01, temperature_2m_member02, … in the output. Hourly names only — a daily-only aggregate such as precipitation_sum or wind_speed_10m_max belongs in daily_variables and is rejected here; temperature_2m_max and temperature_2m_min are an exception, published here as 3-hourly aggregations as well as daily. At least one of hourly_variables or daily_variables required."
    },
    "temperature_unit": {
      "enum": [
        "celsius",
        "fahrenheit"
      ],
      "type": "string",
      "default": "celsius",
      "description": "Temperature unit. Default \"celsius\"."
    },
    "precipitation_unit": {
      "enum": [
        "mm",
        "inch"
      ],
      "type": "string",
      "default": "mm",
      "description": "Precipitation unit. Default \"mm\"."
    }
  },
  "additionalProperties": false
}
openmeteo_get_ensemble output schema
{
  "type": "object",
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "latitude",
        "longitude",
        "elevation",
        "timezone",
        "model",
        "record_count",
        "truncated"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ],
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "daily": {
      "type": "array",
      "items": {
        "type": "object",
        "propertyNames": {
          "type": "string"
        },
        "additionalProperties": {}
      },
      "description": "Per-day records with \"time\" (YYYY-MM-DD) + per-member columns (e.g., temperature_2m_max_member01). Absent when only hourly_variables were requested. When truncated, contains a preview only — query canvas_id for the full dataset when one is present."
    },
    "error": {
      "type": "object",
      "required": [
        "code",
        "message"
      ],
      "properties": {
        "code": {
          "type": "integer",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "examples": [
                "no_variables_requested",
                "invalid_variable",
                "variable_wrong_cadence",
                "invalid_timezone",
                "request_too_large",
                "coverage_gap",
                "rate_limited",
                "upstream_unavailable",
                "upstream_timeout"
              ],
              "description": "Machine-readable failure mode. Declared by this tool: `no_variables_requested`: Neither hourly_variables nor daily_variables named a variable — blank or whitespace-only entries do not count. `invalid_variable`: An unknown variable name or unsupported model was requested. `variable_wrong_cadence`: A variable sat in the cadence field the ensemble API does not document it under — for example precipitation_sum in hourly_variables, or precipitation in daily_variables — caught before the request is sent. `invalid_timezone`: timezone was blank, or upstream did not recognize the requested time zone. `request_too_large`: Open-Meteo refused the request as asking for too much data in one call. `coverage_gap`: Open-Meteo has no data at this coordinate for the requested dataset or model, because its grid or domain does not reach the point. `rate_limited`: Open-Meteo answered HTTP 429 because a free-tier request limit was reached. `upstream_unavailable`: Open-Meteo was unreachable, answered with a server error, or returned an unreadable response on all three attempts. `upstream_timeout`: Open-Meteo failed on all three attempts, and the last one got no answer within 15 seconds. Other values are possible when a failure originates below the handler."
            },
            "recovery": {
              "type": "object",
              "required": [
                "hint"
              ],
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "description": "Actionable next step for the caller.",
              "additionalProperties": {}
            },
            "retryable": {
              "type": "boolean",
              "description": "Whether retrying may succeed."
            }
          },
          "additionalProperties": {}
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        }
      },
      "description": "Present when the call failed. Absent on success.",
      "additionalProperties": {}
    },
    "model": {
      "type": "string",
      "description": "Ensemble model used — echoes the requested models parameter, or ecmwf_ifs025_ensemble when models was omitted or blank."
    },
    "hourly": {
      "type": "array",
      "items": {
        "type": "object",
        "propertyNames": {
          "type": "string"
        },
        "additionalProperties": {}
      },
      "description": "Per-hour records with \"time\" (ISO 8601) + per-member columns for each requested variable (e.g., temperature_2m_member01, temperature_2m_member02). Absent when only daily_variables were requested. When truncated, contains a preview only — query canvas_id for the full dataset when one is present."
    },
    "notice": {
      "type": "string",
      "description": "Everything this response needs to say beyond the data, composed into one advisory: variables the endpoint returned with the unit \"undefined\" across every member (a name the selected model does not carry); recognized variables whose requested window runs past the model's horizon, with the timestamps that do carry values; and, when the result spilled, either the canvas and table holding the full row set plus the two dataframe tools that read it, or — with DataCanvas disabled — why there is no canvas_id and how to reach the rows the preview omits."
    },
    "latitude": {
      "type": "number",
      "description": "Snapped latitude (Open-Meteo snaps to nearest grid point)"
    },
    "timezone": {
      "type": "string",
      "description": "Resolved IANA timezone"
    },
    "canvas_id": {
      "type": "string",
      "description": "DataCanvas token for the staged full dataset. Present only when truncated is true AND DataCanvas is enabled (CANVAS_PROVIDER_TYPE=duckdb) — absent otherwise, in which case the preview is all this response carries. Pass to openmeteo_dataframe_describe to list the staged table and its per-member columns, then to openmeteo_dataframe_query to run SQL against it."
    },
    "elevation": {
      "type": "number",
      "description": "Terrain elevation at grid point (meters)"
    },
    "longitude": {
      "type": "number",
      "description": "Snapped longitude"
    },
    "truncated": {
      "type": "boolean",
      "description": "True when the response was too large to return inline, so hourly and daily carry a bounded preview rather than the full set. With DataCanvas enabled the complete data is staged at canvas_id — every hourly and daily row, including any column the preview omits. With it disabled there is no canvas_id, and the omitted rows are reached only by narrowing the request."
    },
    "table_name": {
      "type": "string",
      "description": "DuckDB table name for the staged data — use as the FROM target in openmeteo_dataframe_query SQL; openmeteo_dataframe_describe lists its columns, which is the only way to learn the per-member suffixes this request produced. Present only alongside canvas_id."
    },
    "daily_units": {
      "type": "object",
      "description": "Variable → unit string for daily data. Absent when no daily_variables were requested.",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {
        "type": "string"
      }
    },
    "hourly_units": {
      "type": "object",
      "description": "Variable → unit string for hourly data (e.g., {\"temperature_2m_member01\": \"°C\"}). Absent when no hourly_variables were requested.",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {
        "type": "string"
      }
    },
    "member_count": {
      "type": "number",
      "description": "Number of distinct perturbed ensemble members in the response, counted from the _memberNN column suffixes. The unsuffixed base column (the control run) is not included in this count."
    },
    "record_count": {
      "type": "number",
      "description": "Total number of records (hourly + daily rows) — the full upstream total when truncated is true, not the combined length of the hourly and daily previews."
    }
  },
  "additionalProperties": false
}
openmeteo_get_ensemble annotations
{
  "readOnlyHint": true,
  "idempotentHint": true
}
openmeteo_get_flood — Openmeteo Get Flood
Advertised by the server during the last successful anonymous check.
Advertised read-only

GloFAS (Global Flood Awareness System) daily river discharge (m³/s) from the GloFAS 0.05° grid cell nearest the given coordinates — no river ID needed. The response's latitude and longitude name that cell. It is not reliably on the river intended: a point near a confluence, a pair of parallel channels, or a bank can resolve to a cell with little flow. When the value is far below what the named river carries, step the coordinate 0.05° across the channel and compare the values. One mode per call: forecast_days for the outlook, or start_date and end_date together for a date range from 1984-01-01. The two modes are mutually exclusive, and a date range needs both ends — a lone start_date or end_date is rejected. forecast_days accepts up to 210, but the current GloFAS run sets how far the data reaches (about six months ahead); later days come back null and the notice names where the data ends. What a date range holds depends on the model. Omitting models reads seamless_v4: consolidated_v4 reanalysis before 2023-08-05, then forecast-run discharge from 2023-08-05 on, so a range crossing that date changes source mid-series. Set models to pin a source: seamless_v4 (default — consolidated_v4 reanalysis before 2023-08-05, forecast_v4 runs from 2023-08-05 into the live forecast), consolidated_v4 (reanalysis from 1984 through 2025-05-31; null after it and on every forecast_days call), forecast_v4 (forecast runs from 2023-06-01 into the live forecast; null before). consolidated_v4 is the only way to read reanalysis for 2023-08-05 through 2025-05-31. With 2+ models every daily column is suffixed with the model name (river_discharge_consolidated_v4). Available daily variables: "river_discharge" (the daily discharge value; the mean across ensemble members is "river_discharge_mean"), and the ensemble statistics "river_discharge_mean", "river_discharge_median", "river_discharge_min", "river_discharge_max", "river_discharge_p25" (25th percentile), and "river_discharge_p75" (75th percentile). The statistics carry spread across ensemble members on forecast-run days — the forecast_days outlook, and date-range days from 2023-08-05 under seamless_v4 or forecast_v4. On reanalysis days (seamless_v4 before 2023-08-05, consolidated_v4 through 2025-05-31) they repeat river_discharge and are not uncertainty estimates. Returns null for coordinates far from any river or in areas without GloFAS coverage. A wide date range produces thousands of daily records and spills to a DataCanvas when canvas is enabled, returning canvas_id and table_name with truncated: true — inspect the staged columns with openmeteo_dataframe_describe, then query the full set with openmeteo_dataframe_query. With canvas disabled it returns a bounded preview instead.

openmeteo_get_flood input schema
{
  "type": "object",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "required": [
    "latitude",
    "longitude"
  ],
  "properties": {
    "models": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 6,
      "description": "GloFAS models to read from: seamless_v4 (default — consolidated_v4 reanalysis before 2023-08-05, forecast_v4 runs from 2023-08-05 into the live forecast), consolidated_v4 (reanalysis from 1984 through 2025-05-31; null after it and on every forecast_days call), forecast_v4 (forecast runs from 2023-06-01 into the live forecast; null before). Omit to use Open-Meteo's seamless_v4 default. With 2+ models each daily column and unit key is suffixed with the model name (river_discharge_consolidated_v4); one model keeps plain names. A model with no data for the requested days answers with null values rather than an error, and the notice names those columns. A name outside this list (the GloFAS v3 selectors included) is sent upstream rather than rejected here."
    },
    "end_date": {
      "type": "string",
      "format": "date",
      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      "description": "End of the date range (YYYY-MM-DD, inclusive). Must be on or after start_date. Days from 2023-08-05 are forecast-run discharge under the default seamless_v4; consolidated_v4 reanalysis ends 2025-05-31 and answers later days with null. Requires start_date — the pair must be sent together, and neither combines with forecast_days."
    },
    "latitude": {
      "type": "number",
      "maximum": 90,
      "minimum": -90,
      "description": "Latitude in decimal degrees. Discharge is returned for the GloFAS 0.05° grid cell nearest this point — no river ID required, and that cell is not reliably on the river intended. When the value is far below what the named river carries, step the coordinate 0.05° across the channel and compare. Use openmeteo_search_locations to resolve a place name."
    },
    "timezone": {
      "type": "string",
      "default": "auto",
      "description": "IANA timezone or \"auto\". Default \"auto\"."
    },
    "canvas_id": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{10}$",
      "description": "DataCanvas token for wide date-range queries. When a result is too large to return inline — driven by total payload size, so a multi-variable pull can spill at any row count — it spills to this canvas: pass the returned token to openmeteo_dataframe_describe to list the staged table and its columns, then to openmeteo_dataframe_query to run SQL against it. Omit to create a fresh canvas."
    },
    "longitude": {
      "type": "number",
      "maximum": 180,
      "minimum": -180,
      "description": "Longitude in decimal degrees. With latitude it selects the nearest GloFAS 0.05° grid cell, which is not reliably on the river intended."
    },
    "start_date": {
      "type": "string",
      "format": "date",
      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      "description": "Start of a date range (YYYY-MM-DD, e.g., \"2023-01-01\"), from 1984-01-01. Under the default seamless_v4 a range holds consolidated_v4 reanalysis before 2023-08-05 and forecast-run discharge from 2023-08-05 on; request models: [\"consolidated_v4\"] for reanalysis through 2025-05-31. Requires end_date — the pair must be sent together, and neither combines with forecast_days."
    },
    "forecast_days": {
      "type": "integer",
      "maximum": 210,
      "minimum": 1,
      "description": "Number of forecast days ahead (1–210). The API accepts up to 210, but the current GloFAS run sets how far the data reaches (about six months); later days come back null. consolidated_v4 serves no forecast days — with it every row is null. Mutually exclusive with start_date/end_date — omit it entirely when pulling a date range."
    },
    "daily_variables": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 20,
      "description": "Daily discharge variables to fetch (e.g., [\"river_discharge\", \"river_discharge_p25\", \"river_discharge_p75\", \"river_discharge_min\", \"river_discharge_max\"]). Required. river_discharge is the daily discharge value; the _mean, _median, _min, _max, _p25, and _p75 statistics span the ensemble members on forecast-run days — the forecast_days outlook, and date-range days from 2023-08-05 under seamless_v4 or forecast_v4. On reanalysis days (seamless_v4 before 2023-08-05, consolidated_v4 through 2025-05-31) they repeat river_discharge and are not uncertainty estimates."
    }
  },
  "additionalProperties": false
}
openmeteo_get_flood output schema
{
  "type": "object",
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "latitude",
        "longitude",
        "timezone",
        "record_count",
        "daily",
        "truncated"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ],
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "daily": {
      "type": "array",
      "items": {
        "type": "object",
        "propertyNames": {
          "type": "string"
        },
        "additionalProperties": {}
      },
      "description": "Per-day records with \"time\" (YYYY-MM-DD) + one key per requested variable containing discharge in m³/s — per model, suffixed with the model name (river_discharge_consolidated_v4), when 2+ models were requested — or null for coordinates outside GloFAS coverage and days the selected model does not serve. When truncated, contains only a preview — query canvas_id for the full dataset when one is present."
    },
    "error": {
      "type": "object",
      "required": [
        "code",
        "message"
      ],
      "properties": {
        "code": {
          "type": "integer",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "examples": [
                "no_variables_requested",
                "date_range_incomplete",
                "forecast_days_conflict",
                "date_order_invalid",
                "date_out_of_range",
                "invalid_variable",
                "invalid_timezone",
                "request_too_large",
                "coverage_gap",
                "rate_limited",
                "upstream_unavailable",
                "upstream_timeout"
              ],
              "description": "Machine-readable failure mode. Declared by this tool: `no_variables_requested`: daily_variables named no variable — omitted, empty, or holding only blank or whitespace-only entries. `date_range_incomplete`: Only one of start_date / end_date was provided — GloFAS requires the pair together. `forecast_days_conflict`: A start_date or end_date range was combined with a forecast_days value. `date_order_invalid`: The end_date value is earlier than the start_date value. `date_out_of_range`: start_date predates 1984-01-01 or date range is otherwise invalid. `invalid_variable`: An unknown discharge variable or model name was requested. `invalid_timezone`: timezone was blank, or upstream did not recognize the requested time zone. `request_too_large`: Open-Meteo refused the request as asking for too much data in one call. `coverage_gap`: Open-Meteo has no data at this coordinate for the requested dataset or model, because its grid or domain does not reach the point. `rate_limited`: Open-Meteo answered HTTP 429 because a free-tier request limit was reached. `upstream_unavailable`: Open-Meteo was unreachable, answered with a server error, or returned an unreadable response on all three attempts. `upstream_timeout`: Open-Meteo failed on all three attempts, and the last one got no answer within 15 seconds. Other values are possible when a failure originates below the handler."
            },
            "recovery": {
              "type": "object",
              "required": [
                "hint"
              ],
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "description": "Actionable next step for the caller.",
              "additionalProperties": {}
            },
            "retryable": {
              "type": "boolean",
              "description": "Whether retrying may succeed."
            }
          },
          "additionalProperties": {}
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        }
      },
      "description": "Present when the call failed. Absent on success.",
      "additionalProperties": {}
    },
    "models": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "GloFAS models requested — echoes the models parameter. Absent when models was omitted, which means the data came from seamless_v4: consolidated_v4 reanalysis before 2023-08-05, forecast_v4 runs from 2023-08-05 on."
    },
    "notice": {
      "type": "string",
      "description": "Everything this response needs to say beyond the data, composed into one advisory: columns GloFAS returned with the unit \"undefined\" (a name it parsed but does not serve); recognized columns whose requested days fall outside the coordinate's discharge record or the selected model's span, with the dates that do carry values; and, when the result spilled, either the canvas and table holding the full row set plus the two dataframe tools that read it, or — with DataCanvas disabled — why there is no canvas_id and how to reach the rows the preview omits."
    },
    "latitude": {
      "type": "number",
      "description": "Snapped latitude — the GloFAS grid cell the discharge comes from"
    },
    "timezone": {
      "type": "string",
      "description": "Resolved IANA timezone"
    },
    "canvas_id": {
      "type": "string",
      "description": "DataCanvas token for the staged full dataset. Present only when truncated is true AND DataCanvas is enabled (CANVAS_PROVIDER_TYPE=duckdb) — absent otherwise, in which case the preview is all this response carries. Pass to openmeteo_dataframe_describe to list the staged table and its columns, then to openmeteo_dataframe_query to run SQL against it."
    },
    "longitude": {
      "type": "number",
      "description": "Snapped longitude — the GloFAS grid cell the discharge comes from"
    },
    "truncated": {
      "type": "boolean",
      "description": "True when the response was too large to return inline, so daily carries a bounded preview rather than the full set. With DataCanvas enabled the complete data is staged at canvas_id. With it disabled there is no canvas_id, and the omitted rows are reached only by narrowing the request."
    },
    "table_name": {
      "type": "string",
      "description": "DuckDB table name for the staged data — use as the FROM target in openmeteo_dataframe_query SQL; openmeteo_dataframe_describe lists its columns. Present only alongside canvas_id."
    },
    "daily_units": {
      "type": "object",
      "description": "Variable → unit string for daily data (e.g., {\"river_discharge\": \"m³/s\"}), model-suffixed like the records when 2+ models were requested.",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {
        "type": "string"
      }
    },
    "record_count": {
      "type": "number",
      "description": "Total number of daily discharge records — the full staged count when truncated is true, not the length of the daily preview."
    }
  },
  "additionalProperties": false
}
openmeteo_get_flood annotations
{
  "readOnlyHint": true,
  "idempotentHint": true
}
openmeteo_get_climate — Openmeteo Get Climate
Advertised by the server during the last successful anonymous check.
Advertised read-only

Long-range climate projections from bias-corrected daily CMIP6 models, covering 1950-01-01 to 2050-12-31 at any coordinate. Answers "what will conditions look like through 2050?" — the future-projection counterpart to openmeteo_get_historical (the observed archive, what happened). 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. A model name outside that list is sent upstream rather than rejected here, so a model Open-Meteo adds later still works; if upstream rejects the request, the error names the offending model on its own rather than the whole requested list. With 2+ models each variable appears once per model with the model name as suffix (e.g. temperature_2m_max_CMCC_CM2_VHR4); a single or omitted model returns plain variable names. Not all models carry all variables — missing combinations return null. Multi-decade daily pulls across several models produce thousands of records and spill to a DataCanvas when canvas is enabled, returning canvas_id and table_name with truncated: true — inspect the staged columns with openmeteo_dataframe_describe, then query the full set with openmeteo_dataframe_query. With canvas disabled they return a bounded preview instead.

openmeteo_get_climate input schema
{
  "type": "object",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "required": [
    "latitude",
    "longitude",
    "start_date",
    "end_date"
  ],
  "properties": {
    "models": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 7,
      "description": "CMIP6 models to include: CMCC_CM2_VHR4, FGOALS_f3_H, HiRAM_SIT_HR, MRI_AGCM3_2_S, EC_Earth3P_HR, MPI_ESM1_2_XR, NICAM16_8S. With 2+ models each variable column is suffixed with the model name (e.g. temperature_2m_max_MRI_AGCM3_2_S). Omit to use the API default (a single model, unsuffixed columns). A name outside this list is sent upstream rather than rejected here."
    },
    "end_date": {
      "type": "string",
      "format": "date",
      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      "description": "End date (YYYY-MM-DD, inclusive, max 2050-12-31). Must be on or after start_date."
    },
    "latitude": {
      "type": "number",
      "maximum": 90,
      "minimum": -90,
      "description": "Latitude in decimal degrees. Use openmeteo_search_locations to resolve a place name to coordinates."
    },
    "timezone": {
      "type": "string",
      "default": "auto",
      "description": "IANA timezone or \"auto\". Default \"auto\"."
    },
    "canvas_id": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{10}$",
      "description": "DataCanvas token for multi-decade or multi-model queries. When a result is too large to return inline — driven by total payload size, so a wide multi-model pull can spill at any row count — it spills to this canvas: pass the returned token to openmeteo_dataframe_describe to list the staged table and its per-model columns, then to openmeteo_dataframe_query to run SQL against it. Omit to create a fresh canvas."
    },
    "longitude": {
      "type": "number",
      "maximum": 180,
      "minimum": -180,
      "description": "Longitude in decimal degrees."
    },
    "start_date": {
      "type": "string",
      "format": "date",
      "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$",
      "description": "Start date (YYYY-MM-DD, e.g., \"2049-01-01\"). CMIP6 projections cover 1950-01-01 to 2050-12-31."
    },
    "daily_variables": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 50,
      "description": "Daily climate variables to fetch (e.g., [\"temperature_2m_max\", \"temperature_2m_min\", \"precipitation_sum\", \"wind_speed_10m_mean\", \"shortwave_radiation_sum\"]). Required — the Climate API is daily-only."
    },
    "wind_speed_unit": {
      "enum": [
        "kmh",
        "mph",
        "ms",
        "kn"
      ],
      "type": "string",
      "default": "kmh",
      "description": "Wind speed unit. Default \"kmh\"."
    },
    "temperature_unit": {
      "enum": [
        "celsius",
        "fahrenheit"
      ],
      "type": "string",
      "default": "celsius",
      "description": "Temperature unit. Default \"celsius\"."
    },
    "precipitation_unit": {
      "enum": [
        "mm",
        "inch"
      ],
      "type": "string",
      "default": "mm",
      "description": "Precipitation unit. Default \"mm\"."
    }
  },
  "additionalProperties": false
}
openmeteo_get_climate output schema
{
  "type": "object",
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "latitude",
        "longitude",
        "elevation",
        "timezone",
        "date_range",
        "record_count",
        "daily",
        "truncated"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ],
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "daily": {
      "type": "array",
      "items": {
        "type": "object",
        "propertyNames": {
          "type": "string"
        },
        "additionalProperties": {}
      },
      "description": "Per-day records with \"time\" (YYYY-MM-DD) + one key per requested variable — per-model suffixed keys when 2+ models were requested (e.g. temperature_2m_max_CMCC_CM2_VHR4). Null values mean the model does not carry that variable. When truncated, contains only a preview — query canvas_id for the full dataset when one is present."
    },
    "error": {
      "type": "object",
      "required": [
        "code",
        "message"
      ],
      "properties": {
        "code": {
          "type": "integer",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "examples": [
                "date_out_of_range",
                "date_order_invalid",
                "no_variables_requested",
                "invalid_variable",
                "invalid_timezone",
                "request_too_large",
                "rate_limited",
                "upstream_unavailable",
                "upstream_timeout"
              ],
              "description": "Machine-readable failure mode. Declared by this tool: `date_out_of_range`: start_date predates 1950-01-01 or end_date is after 2050-12-31. `date_order_invalid`: The end_date value is earlier than the start_date value. `no_variables_requested`: daily_variables named no variable — omitted, empty, or holding only blank or whitespace-only entries. `invalid_variable`: An unknown variable name or unsupported climate model was requested. `invalid_timezone`: timezone was blank, or upstream did not recognize the requested time zone. `request_too_large`: Open-Meteo refused the request as asking for too much data in one call. `rate_limited`: Open-Meteo answered HTTP 429 because a free-tier request limit was reached. `upstream_unavailable`: Open-Meteo was unreachable, answered with a server error, or returned an unreadable response on all three attempts. `upstream_timeout`: Open-Meteo failed on all three attempts, and the last one got no answer within 15 seconds. Other values are possible when a failure originates below the handler."
            },
            "recovery": {
              "type": "object",
              "required": [
                "hint"
              ],
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "description": "Actionable next step for the caller.",
              "additionalProperties": {}
            },
            "retryable": {
              "type": "boolean",
              "description": "Whether retrying may succeed."
            }
          },
          "additionalProperties": {}
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        }
      },
      "description": "Present when the call failed. Absent on success.",
      "additionalProperties": {}
    },
    "models": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Climate models requested — echoes the models parameter. Absent when models was omitted (API default model; the response carries no provenance)."
    },
    "notice": {
      "type": "string",
      "description": "Everything this response needs to say beyond the data, composed into one advisory: columns the endpoint returned with the unit \"undefined\" (a name it parsed but does not serve); recognized variables a selected model carries no values for, with the dates that do carry values; and, when the result spilled, either the canvas and table holding the full row set plus the two dataframe tools that read it, or — with DataCanvas disabled — why there is no canvas_id and how to reach the rows the preview omits."
    },
    "latitude": {
      "type": "number",
      "description": "Snapped latitude (Open-Meteo snaps to nearest grid point)"
    },
    "timezone": {
      "type": "string",
      "description": "Resolved IANA timezone"
    },
    "canvas_id": {
      "type": "string",
      "description": "DataCanvas token for the staged full dataset. Present only when truncated is true AND DataCanvas is enabled (CANVAS_PROVIDER_TYPE=duckdb) — absent otherwise, in which case the preview is all this response carries. Pass to openmeteo_dataframe_describe to list the staged table and its per-model columns, then to openmeteo_dataframe_query to run SQL against it."
    },
    "elevation": {
      "type": "number",
      "description": "Elevation at grid point (meters)"
    },
    "longitude": {
      "type": "number",
      "description": "Snapped longitude"
    },
    "truncated": {
      "type": "boolean",
      "description": "True when the response was too large to return inline, so daily carries a bounded preview rather than the full set. With DataCanvas enabled the complete data is staged at canvas_id. With it disabled there is no canvas_id, and the omitted rows are reached only by narrowing the request."
    },
    "date_range": {
      "type": "object",
      "required": [
        "start",
        "end"
      ],
      "properties": {
        "end": {
          "type": "string",
          "description": "Actual end date of returned data"
        },
        "start": {
          "type": "string",
          "description": "Actual start date of returned data"
        }
      },
      "description": "Date range of returned data",
      "additionalProperties": false
    },
    "table_name": {
      "type": "string",
      "description": "DuckDB table name for the staged data — use as the FROM target in openmeteo_dataframe_query SQL; openmeteo_dataframe_describe lists its columns, which is the only way to learn the per-model suffixes this request produced. Present only alongside canvas_id."
    },
    "daily_units": {
      "type": "object",
      "description": "Column → unit string for daily data (e.g., {\"temperature_2m_max_CMCC_CM2_VHR4\": \"°C\"}).",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {
        "type": "string"
      }
    },
    "record_count": {
      "type": "number",
      "description": "Total number of daily records — the full upstream total when truncated is true, not the length of the daily preview."
    }
  },
  "additionalProperties": false
}
openmeteo_get_climate annotations
{
  "readOnlyHint": true,
  "idempotentHint": true
}
openmeteo_dataframe_query — Openmeteo Dataframe Query
Advertised by the server during the last successful anonymous check.
Advertised read-only

Run a read-only SQL SELECT against tables staged on a DataCanvas by openmeteo_get_forecast, openmeteo_get_historical, openmeteo_get_marine, openmeteo_get_air_quality, openmeteo_get_ensemble, openmeteo_get_flood, or openmeteo_get_climate. Pass the canvas_id returned when any of those tools spills (truncated: true), and reference the exact table_name those tools return alongside it. Call openmeteo_dataframe_describe to list staged tables and their columns when you need to discover names. Returns up to 100 leading rows — fewer when wide rows would pass the 80,000-character inline ceiling — plus row_count — exact, or a lower bound flagged by count_error when counting fails or runs past 5 seconds; page the rest with LIMIT / OFFSET.

openmeteo_dataframe_query input schema
{
  "type": "object",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "required": [
    "canvas_id",
    "sql"
  ],
  "properties": {
    "sql": {
      "type": "string",
      "description": "Read-only SELECT statement. Reference table names from openmeteo_dataframe_describe; in the examples below, replace <table_name> with a staged table and <column> with one of its columns. time is ISO 8601 text — YYYY-MM-DDTHH:MM on hourly rows, YYYY-MM-DD on daily rows — as are daily sunrise and sunset, so date functions need a cast: CAST(time AS TIMESTAMP). WHERE time LIKE '%T%' selects the hourly rows of a table that holds both cadences. Monthly means: `SELECT strftime(CAST(time AS TIMESTAMP), '%Y-%m') AS month, AVG(<column>) AS mean FROM <table_name> GROUP BY month ORDER BY month`. An openmeteo_get_ensemble table carries one column per member (precipitation_member01, precipitation_member02, …) beside the unsuffixed control run; count members per row with `SELECT time, list_count(list_filter([*COLUMNS('^precipitation_member\\d+$')], lambda x: x > 0.5)) AS members_over, list_count([*COLUMNS('^precipitation_member\\d+$')]) AS members_reporting FROM <table_name> ORDER BY time` — the anchored pattern leaves out the control column, and members_reporting is the per-row denominator, since rows past a model's last forecast hour carry no member values. Write lambdas with the lambda x: syntax."
    },
    "canvas_id": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{10}$",
      "description": "Canvas ID returned by openmeteo_get_forecast, openmeteo_get_historical, openmeteo_get_marine, openmeteo_get_air_quality, openmeteo_get_ensemble, openmeteo_get_flood, or openmeteo_get_climate when truncated: true."
    }
  },
  "additionalProperties": false
}
openmeteo_dataframe_query output schema
{
  "type": "object",
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "rows",
        "row_count",
        "canvas_id"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ],
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "rows": {
      "type": "array",
      "items": {
        "type": "object",
        "propertyNames": {
          "type": "string"
        },
        "additionalProperties": {}
      },
      "description": "Leading result rows: at most 100, and fewer when the next row would take the response past its 80,000-character inline ceiling. Values are never cut. When row_count exceeds rows.length, continue by re-issuing the SQL with OFFSET <rows.length>, added to any OFFSET it already applies, with any LIMIT reduced by the same amount. Page under an ORDER BY: without one, DuckDB guarantees no row order between calls."
    },
    "error": {
      "type": "object",
      "required": [
        "code",
        "message"
      ],
      "properties": {
        "code": {
          "type": "integer",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "examples": [
                "canvas_not_enabled",
                "canvas_not_found",
                "system_catalog_access",
                "missing_table",
                "row_too_large"
              ],
              "description": "Machine-readable failure mode. Declared by this tool: `canvas_not_enabled`: CANVAS_PROVIDER_TYPE is not set to duckdb. `canvas_not_found`: The canvas_id is unknown or has expired under its 24 h sliding TTL. `system_catalog_access`: The SQL references a system catalog such as information_schema, sqlite_master, pg_catalog, or a duckdb_*() function. `missing_table`: The SQL references a table that is not staged on this canvas — a mistyped name, or one that expired (24 h sliding TTL) or was dropped. `row_too_large`: The first result row alone cannot fit the 80,000-character inline ceiling, and values are never cut. Other values are possible when a failure originates below the handler."
            },
            "recovery": {
              "type": "object",
              "required": [
                "hint"
              ],
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "description": "Actionable next step for the caller.",
              "additionalProperties": {}
            },
            "retryable": {
              "type": "boolean",
              "description": "Whether retrying may succeed."
            }
          },
          "additionalProperties": {}
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        }
      },
      "description": "Present when the call failed. Absent on success.",
      "additionalProperties": {}
    },
    "canvas_id": {
      "type": "string",
      "description": "Canvas ID that was queried."
    },
    "row_count": {
      "type": "number",
      "description": "Exact number of rows in the full result. When count_error is present, the total could not be counted and this is a lower bound: the rows the query confirmed exist."
    },
    "count_error": {
      "type": "string",
      "description": "Present only when counting the full result failed after its rows were read — the failure reason (e.g. sql_execution_error, or count_timeout when counting ran past 5 seconds). The rows are still valid; row_count is then a lower bound."
    }
  },
  "additionalProperties": false
}
openmeteo_dataframe_query annotations
{
  "readOnlyHint": true
}
openmeteo_dataframe_describe — Openmeteo Dataframe Describe
Advertised by the server during the last successful anonymous check.
Advertised read-only

List the tables and their columns on a DataCanvas staged by openmeteo_get_forecast, openmeteo_get_historical, openmeteo_get_marine, openmeteo_get_air_quality, openmeteo_get_ensemble, openmeteo_get_flood, or openmeteo_get_climate. Call this first to discover table names before querying with openmeteo_dataframe_query.

openmeteo_dataframe_describe input schema
{
  "type": "object",
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "required": [
    "canvas_id"
  ],
  "properties": {
    "canvas_id": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]{10}$",
      "description": "Canvas ID returned by openmeteo_get_forecast, openmeteo_get_historical, openmeteo_get_marine, openmeteo_get_air_quality, openmeteo_get_ensemble, openmeteo_get_flood, or openmeteo_get_climate when truncated: true."
    }
  },
  "additionalProperties": false
}
openmeteo_dataframe_describe output schema
{
  "type": "object",
  "anyOf": [
    {
      "not": {
        "required": [
          "error"
        ]
      },
      "required": [
        "canvas_id",
        "tables",
        "expires_at"
      ]
    },
    {
      "required": [
        "error"
      ]
    }
  ],
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "error": {
      "type": "object",
      "required": [
        "code",
        "message"
      ],
      "properties": {
        "code": {
          "type": "integer",
          "maximum": 9007199254740991,
          "minimum": -9007199254740991,
          "description": "JSON-RPC error code for this failure."
        },
        "data": {
          "type": "object",
          "properties": {
            "reason": {
              "type": "string",
              "examples": [
                "canvas_not_enabled",
                "canvas_not_found"
              ],
              "description": "Machine-readable failure mode. Declared by this tool: `canvas_not_enabled`: CANVAS_PROVIDER_TYPE is not set to duckdb. `canvas_not_found`: The canvas_id is unknown or has expired under its 24 h sliding TTL. Other values are possible when a failure originates below the handler."
            },
            "recovery": {
              "type": "object",
              "required": [
                "hint"
              ],
              "properties": {
                "hint": {
                  "type": "string"
                }
              },
              "description": "Actionable next step for the caller.",
              "additionalProperties": {}
            },
            "retryable": {
              "type": "boolean",
              "description": "Whether retrying may succeed."
            }
          },
          "additionalProperties": {}
        },
        "message": {
          "type": "string",
          "description": "Human-readable description of what went wrong."
        }
      },
      "description": "Present when the call failed. Absent on success.",
      "additionalProperties": {}
    },
    "tables": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "name",
          "kind",
          "row_count",
          "columns"
        ],
        "properties": {
          "kind": {
            "enum": [
              "table",
              "view"
            ],
            "type": "string",
            "description": "Whether this is a base table or a view."
          },
          "name": {
            "type": "string",
            "description": "Table name — pass to openmeteo_dataframe_query."
          },
          "columns": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "name",
                "type",
                "nullable"
              ],
              "properties": {
                "name": {
                  "type": "string",
                  "description": "Column name."
                },
                "type": {
                  "type": "string",
                  "description": "DuckDB type (e.g. VARCHAR, DOUBLE, TIMESTAMP)."
                },
                "nullable": {
                  "type": "boolean",
                  "description": "Whether the column allows NULL."
                }
              },
              "description": "A column: name, DuckDB type, and nullability.",
              "additionalProperties": false
            },
            "description": "Column schema."
          },
          "row_count": {
            "type": "number",
            "description": "Number of rows."
          }
        },
        "description": "A table or view on the canvas: name, kind, row count, and column schema.",
        "additionalProperties": false
      },
      "description": "Tables and views registered on this canvas."
    },
    "canvas_id": {
      "type": "string",
      "description": "Canvas ID that was inspected."
    },
    "expires_at": {
      "type": "string",
      "description": "ISO 8601 expiry after the sliding 24 h TTL."
    }
  },
  "additionalProperties": false
}
openmeteo_dataframe_describe annotations
{
  "readOnlyHint": true,
  "idempotentHint": true
}

Prompts

0 advertised

No prompt definitions available.

Resources

1 advertised

open-meteo-guide

Advertised by the server during the last successful anonymous check.

Tool choice, gates, and workflows for the openmeteo_* weather tools, covering threshold odds from the ensemble, frost, anomalies against a normal, 2050 projections, surf spots, river flood outlooks, and air quality for a set hour. Use before answering a weather, climate, marine, river, or air-quality question from openmeteo_* output, above all a probability, a comparison to normal or to the past, or a question about one beach or river.

open-meteo-guide URI
skill://open-meteo-guide/SKILL.md

MIME type: text/markdown

io.modelcontextprotocol/skills

Advertised by the server during the last successful anonymous check.

io.modelcontextprotocol/skills URI template
skill://{+path}

30-day availability

Aggregated from read-only remote checks; this is not a tool-execution or security guarantee.

Successful checks
1 of 1 (100%)
Average latency
675 ms
95th percentile latency
675 ms

Maintenance history

Public check and source-refresh events. Editorial and ownership records remain private.

  • · probe · passed · 675 ms · metadata changed · 11 tool(s), 1 resource(s), 1 resource template(s), 0 prompt(s)

Connect from your client

Each snippet registers the same endpoint; pick the client you use.

This server is operated by the named third party. Listing and endpoint checks are not an endorsement or a security audit. Review the operator's permissions, terms, and data handling before connecting. How listings are checked and corrected.

Claude Code

Run this once in a terminal. It registers the remote server. Use --scope project to write .mcp.json for the repo.
Claude Code configuration
claude mcp add --transport http open-meteo-mcp-server https://open-meteo.caseyjhand.com/mcp

Codex

Run this once. Codex stores MCP servers in ~/.codex/config.toml (or .codex/config.toml in a trusted project).
Codex configuration
codex mcp add open-meteo-mcp-server --url https://open-meteo.caseyjhand.com/mcp

Cursor

Merge this server into .cursor/mcp.json in your project, then enable open-meteo-mcp-server in your MCP settings.
Cursor configuration
{
  "mcpServers": {
    "open-meteo-mcp-server": {
      "url": "https://open-meteo.caseyjhand.com/mcp"
    }
  }
}

Cline

Merge this into cline_mcp_settings.json (Cline → MCP Servers → Configure).
Cline configuration
{
  "mcpServers": {
    "open-meteo-mcp-server": {
      "type": "streamableHttp",
      "url": "https://open-meteo.caseyjhand.com/mcp",
      "disabled": false
    }
  }
}

VS Code

Merge this server into .vscode/mcp.json. Use MCP: List Servers to start open-meteo-mcp-server and inspect its tools.
VS Code configuration
{
  "servers": {
    "open-meteo-mcp-server": {
      "type": "http",
      "url": "https://open-meteo.caseyjhand.com/mcp"
    }
  }
}

Gemini CLI

Run this once. It writes .gemini/settings.json in the current project; pass --scope user for ~/.gemini/settings.json.
Gemini CLI configuration
gemini mcp add --transport http open-meteo-mcp-server https://open-meteo.caseyjhand.com/mcp

Claude Desktop

Open Claude Settings > Connectors > Add custom connector, enter this server URL and enable it for the conversation. This is a manual remote-connector handoff, not a desktop JSON configuration.
Claude Desktop configuration
https://open-meteo.caseyjhand.com/mcp

Windsurf

Merge this into ~/.codeium/windsurf/mcp_config.json, then refresh MCP in Cascade. Windsurf remote servers use serverUrl, not url.
Windsurf configuration
{
  "mcpServers": {
    "open-meteo-mcp-server": {
      "serverUrl": "https://open-meteo.caseyjhand.com/mcp"
    }
  }
}

Let your agent find its tools

Find servers like open-meteo-mcp-server from your agent. Add Koskamo once to search the index for endpoints, access requirements, and client setup. Public discovery needs no key.