{
  "markdown": "<div align=\"center\">\n  <h1>@cyanheads/open-meteo-mcp-server</h1>\n  <p><b>Geocode places, fetch global weather forecasts, ERA5 historical climate, marine conditions, air quality, and terrain elevation via MCP. STDIO or Streamable HTTP.</b>\n  <div>11 Tools</div>\n  </p>\n</div>\n\n<div align=\"center\">\n\n[![Version](https://img.shields.io/badge/Version-0.3.5-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/open-meteo-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/open-meteo-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/open-meteo-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.0-blueviolet.svg?style=flat-square)](https://bun.sh/)\n\n</div>\n\n<div align=\"center\">\n\n[![Install in Claude Desktop](https://img.shields.io/badge/Install_in-Claude_Desktop-D97757?style=for-the-badge&logo=anthropic&logoColor=white)](https://github.com/cyanheads/open-meteo-mcp-server/releases/latest/download/open-meteo-mcp-server.mcpb) [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=open-meteo-mcp-server&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBjeWFuaGVhZHMvb3Blbi1tZXRlby1tY3Atc2VydmVyIl19) [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=for-the-badge&logo=visualstudiocode&logoColor=white)](https://vscode.dev/redirect?url=vscode:mcp/install?%7B%22name%22%3A%22open-meteo-mcp-server%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40cyanheads%2Fopen-meteo-mcp-server%22%5D%7D)\n\n[![Framework](https://img.shields.io/badge/Built%20on-@cyanheads/mcp--ts--core-67E8F9?style=flat-square)](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)\n\n</div>\n\n<div align=\"center\">\n\n**Public Hosted Server:** [https://open-meteo.caseyjhand.com/mcp](https://open-meteo.caseyjhand.com/mcp)\n\n</div>\n\n---\n\n## Tools\n\nEleven tools covering geocoding, weather forecasts, historical climate, probabilistic ensemble forecasts, marine conditions, air quality, terrain elevation, river discharge, CMIP6 climate projections, and SQL analytics over large datasets:\n\n| Tool | Description |\n|:---|:---|\n| `openmeteo_search_locations` | Resolve a place name to ranked coordinate matches with country, region, elevation, timezone, and population |\n| `openmeteo_get_forecast` | Weather forecast for coordinates: hourly and/or daily variables for up to 16 days, with optional recent past data; wide windows spill to DataCanvas |\n| `openmeteo_get_historical` | Historical weather from the ERA5 reanalysis archive (1940–present); large ranges spill to DataCanvas |\n| `openmeteo_get_marine` | Marine wave and ocean conditions for coastal or ocean coordinates: wave height, period, direction, swell, and sea-surface temperature; up to 8 forecast days, `past_days`, or a `start_date`/`end_date` archive range; large windows spill to DataCanvas |\n| `openmeteo_get_air_quality` | Modeled CAMS air quality: PM2.5, PM10, NO2, O3, CO, dust, pollen, and European/US AQI indices; up to 7 forecast days, `past_days`, or a `start_date`/`end_date` archive range; large windows spill to DataCanvas |\n| `openmeteo_get_elevation` | Terrain elevation from Copernicus DEM (~90m resolution) for up to 100 coordinate pairs per call |\n| `openmeteo_get_ensemble` | Probabilistic ensemble forecast: per-member hourly/daily time series (up to 51 members, 16 days) for exceedance and uncertainty analysis |\n| `openmeteo_get_flood` | GloFAS river discharge forecast (up to 210 days) or reanalysis (1984–present); coordinate-based, snaps to nearest river; large ranges spill to DataCanvas |\n| `openmeteo_get_climate` | Bias-corrected daily CMIP6 climate projections (1950–2050) across up to 7 models; large ranges spill to DataCanvas |\n| `openmeteo_dataframe_describe` | List tables and 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` |\n| `openmeteo_dataframe_query` | Run a read-only SQL SELECT against tables staged on a DataCanvas |\n\n### `openmeteo_search_locations`\n\nResolve a free-text place name to ranked coordinate matches. Required first step for name-based queries — all weather tools accept latitude/longitude, not place names.\n\n- Returns name, country, admin1/admin2, latitude, longitude, elevation, IANA timezone, population, and GeoNames feature code\n- Search by a bare place name — a city, region, or landmark (\"Baoding\", not \"Baoding Hebei\"; \"Paris\", not \"Paris, France\"); a compound \"City Region\" or \"City, Country\" string matches nothing\n- Disambiguate same-named places (e.g., \"Springfield\") with the optional `country` filter (ISO 3166-1 alpha-2, e.g. `US`) or by raising `count` (default 5, up to 10) and reading the `admin1`/`country` fields on each result — those are output fields for choosing among matches, not search inputs\n- Pass the timezone from an `openmeteo_search_locations` result directly to weather tools as the `timezone` parameter\n- Fails with a `no_results` error (not an empty array) when nothing matches — retry the bare place name without qualifiers, or for a physical feature/landmark search the nearest populated place instead\n\n---\n\n### `openmeteo_get_forecast`\n\nWeather forecast for a coordinate pair with hourly and/or daily variable selection.\n\n- Up to 16 forecast days ahead (`forecast_days 1–16`, default 7)\n- `past_days` (0–92) covers recent history via the forecast model — use instead of `openmeteo_get_historical` for dates within the last ~5 days to avoid ERA5 lag\n- 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`\n- Common daily variables: `temperature_2m_max`, `temperature_2m_min`, `precipitation_sum`, `wind_speed_10m_max`, `sunrise`, `sunset`, `uv_index_max`, `precipitation_hours`, `weather_code`\n- At least one of `hourly_variables` or `daily_variables` is required\n- Hourly and daily are separate variable sets. A variable Open-Meteo documents under the other cadence is rejected before the request, by name, with the field it belongs in and the same-cadence alternatives (`cloud_cover` in `daily_variables` → `cloud_cover_max`/`_mean`/`_min`). Names in neither set are passed upstream unchanged\n- Configurable temperature unit (Celsius/Fahrenheit), wind speed unit (km/h, mph, m/s, knots), and precipitation unit (mm/inch)\n- Reshapes the API's columnar response into per-timestamp records with a parallel `hourly_units` / `daily_units` map\n- A wide window (a large `past_days` plus many hourly variables) spills to DataCanvas when `CANVAS_PROVIDER_TYPE=duckdb` — output includes `canvas_id` and `truncated: true`; query with `openmeteo_dataframe_query`\n\n---\n\n### `openmeteo_get_historical`\n\nHistorical weather from the ERA5 reanalysis archive, covering 1940 to approximately 5 days ago.\n\n- Requires `start_date` and `end_date` (YYYY-MM-DD); ERA5 has a variable ~1–5 day lag\n- Same variable vocabulary as `openmeteo_get_forecast` — past and forecast data are directly comparable on one schema\n- At least one of `hourly_variables` or `daily_variables` is required\n- Hourly and daily are separate variable sets; a variable documented under the other cadence is rejected before the request, by name, with the field it belongs in\n- Large date ranges (multi-year hourly queries) spill to DataCanvas when `CANVAS_PROVIDER_TYPE=duckdb` — output includes `canvas_id` and `truncated: true` whenever a result is too large to return inline, which a wide multi-variable pull can be at any row count\n- Spill → query workflow: call `openmeteo_dataframe_describe` with the `canvas_id` to list tables, then `openmeteo_dataframe_query` to run SQL SELECT against the staged data\n\n---\n\n### `openmeteo_get_marine`\n\nMarine wave and ocean conditions for coastal and open-ocean coordinates.\n\n- Up to 8 forecast days (`forecast_days 1–8`, upstream default 7) with optional `past_days` (0–92)\n- Or an archive range via `start_date` and `end_date` — real wave values go back to at least 2022\n- One window per call: a date range is mutually exclusive with `forecast_days`/`past_days`, and needs both ends — a lone `start_date` or `end_date` is rejected\n- 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`\n- Common daily variables: `wave_height_max`, `wave_direction_dominant`, `wave_period_max`\n- At least one of `hourly_variables` or `daily_variables` is required\n- Hourly and daily are separate variable sets; a variable documented under the other cadence is rejected before the request, by name, with the field it belongs in\n- Inland or sheltered-water points return near-zero wave values (physically correct); `ocean_current_velocity` is null for non-open-ocean coordinates\n- Wide windows spill to DataCanvas when `CANVAS_PROVIDER_TYPE=duckdb` — output includes `canvas_id` and `truncated: true`; query with `openmeteo_dataframe_query`\n\n---\n\n### `openmeteo_get_air_quality`\n\nModeled CAMS air quality, forecast and archive.\n\n- Up to 7 forecast days (`forecast_days 1–7`, upstream default 5) with optional `past_days` (0–92)\n- Or an archive range via `start_date` and `end_date` — real CAMS values go back to at least `2022-10-01`; earlier dates return rows of nulls\n- One window per call: a date range is mutually exclusive with `forecast_days`/`past_days`, and needs both ends — a lone `start_date` or `end_date` is rejected\n- 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`\n- At least one variable from `hourly_variables` is required\n- Grid-modeled data from CAMS — resolution is coarser than ground stations; for measured station readings, cross-reference `openaq-mcp-server`\n- Output includes `data_source: \"CAMS\"` to distinguish modeled from measured data\n- Wide windows spill to DataCanvas when `CANVAS_PROVIDER_TYPE=duckdb` — output includes `canvas_id` and `truncated: true`; query with `openmeteo_dataframe_query`\n\n---\n\n### `openmeteo_get_elevation`\n\nTerrain elevation from the Copernicus Digital Elevation Model (~90m resolution).\n\n- Accepts parallel `latitudes[]` and `longitudes[]` arrays; both must have equal length (up to 100 pairs)\n- Returns results in input order: `{ latitude, longitude, elevation_m }`\n- Useful for geographic context, elevation-adjusted weather interpretation, or route planning\n\n---\n\n### `openmeteo_get_ensemble`\n\nProbabilistic ensemble weather forecast exposing all individual model member trajectories.\n\n- Up to 16 forecast days (`forecast_days 1–16`, default 7) with optional `past_days` (0–92)\n- Each requested variable is returned as per-member columns: `temperature_2m_member01`, `temperature_2m_member02`, … Use the spread across members to compute exceedance probabilities, interquantile ranges, and decision thresholds\n- Available ensemble models (member counts include the control run):\n  - Global — `ecmwf_ifs025_ensemble` (51), `ecmwf_aifs025_ensemble` (51), `google_weathernext2_ensemble` (64), `ncep_gefs_seamless` (31), `ncep_gefs025` (31), `ncep_gefs05` (31, 35-day horizon), `ncep_aigefs025` (31), `icon_seamless_eps` (20–40, global/Europe blend), `icon_global_eps` (40), `gem_global_ensemble` (21), `bom_access_global_ensemble` (18), `ukmo_global_ensemble_20km` (18)\n  - Regional — `ecmwf_ifs_europe_ensemble` (51), `ecmwf_aifs_europe_ensemble` (51), `icon_eu_eps` (40), `icon_d2_eps` (20), `meteoswiss_icon_ch2_ensemble` (21), `meteoswiss_icon_ch1_ensemble` (11), `ukmo_uk_ensemble_2km` (3). A regional model returns no data outside the area it covers. Upstream reports that two ways — `No data is available for this location` from the `meteoswiss_*` pair, an HTTP 200 carrying `nan` coordinates from the rest — and both surface as a non-retryable input error naming the coverage gap, so switch to a global model rather than retrying\n- Omit `models` to use the API default blend. The list is not an allowlist — a model name it does not carry is still sent upstream, so a model Open-Meteo adds later keeps working\n- Response includes `model` (system used) and `member_count` (perturbed members, excluding the control run)\n- At least one of `hourly_variables` or `daily_variables` is required\n- Hourly and daily are separate variable sets; a variable documented under the other cadence is rejected before the request, by name, with the field it belongs in. The ensemble API's own catalog applies — it publishes `temperature_2m_max` and `temperature_2m_min` as 3-hourly aggregations as well as daily, so those are accepted in either field\n- Large multi-member, multi-day pulls spill to DataCanvas when `CANVAS_PROVIDER_TYPE=duckdb` — output includes `canvas_id` and `truncated: true`; query with `openmeteo_dataframe_query`\n- Configurable temperature, wind speed, and precipitation units\n\n---\n\n### `openmeteo_get_flood`\n\nGloFAS (Global Flood Awareness System) river discharge forecast and reanalysis via the Open-Meteo Flood API.\n\n- Coordinate-based — no river ID needed; the API snaps to the nearest river grid point automatically\n- Forecast horizon up to 210 days; reanalysis history from 1984-01-01 to present\n- One mode per call: `forecast_days` for the future outlook, or `start_date` and `end_date` together for historical analysis. The two are mutually exclusive, and a date range needs both ends — a lone `start_date` or `end_date` is rejected\n- Available daily variables: `river_discharge` (ensemble mean), `river_discharge_mean`, `river_discharge_min`, `river_discharge_max`, `river_discharge_median`, `river_discharge_p25` (25th percentile), `river_discharge_p75` (75th percentile) — all in m³/s\n- Returns null for coordinates outside GloFAS coverage (e.g., open ocean or areas without river network data)\n- Discharge values reflect the GloFAS ensemble — percentile variables expose the uncertainty spread\n- Wide reanalysis ranges spill to DataCanvas when `CANVAS_PROVIDER_TYPE=duckdb` — output includes `canvas_id` and `truncated: true`; query with `openmeteo_dataframe_query`\n\n---\n\n### `openmeteo_get_climate`\n\nLong-range climate projections from bias-corrected daily CMIP6 models — the future-projection counterpart to `openmeteo_get_historical`.\n\n- Coverage: 1950-01-01 to 2050-12-31, daily resolution only\n- Available models: `CMCC_CM2_VHR4`, `FGOALS_f3_H`, `HiRAM_SIT_HR`, `MRI_AGCM3_2_S`, `EC_Earth3P_HR`, `MPI_ESM1_2_XR`, `NICAM16_8S`. Not an allowlist — an unlisted name is still sent upstream; when upstream rejects a multi-model request, the error names only the model outside the documented set, not the whole list\n- With 2+ models, each variable appears once per model with the model name as column suffix (e.g. `temperature_2m_max_CMCC_CM2_VHR4`); a single or omitted model returns plain variable names\n- Common daily variables: `temperature_2m_max`, `temperature_2m_min`, `temperature_2m_mean`, `precipitation_sum`, `rain_sum`, `snowfall_sum`, `wind_speed_10m_mean`, `wind_speed_10m_max`, `shortwave_radiation_sum`, `cloud_cover_mean`, `relative_humidity_2m_mean`, `pressure_msl_mean`\n- Not all models carry all variables — missing combinations return null (e.g. `CMCC_CM2_VHR4` has no `shortwave_radiation_sum`)\n- Multi-decade daily pulls across several models spill to DataCanvas when `CANVAS_PROVIDER_TYPE=duckdb` — output includes `canvas_id` and `truncated: true`; query with `openmeteo_dataframe_query`\n- Configurable temperature, wind speed, and precipitation units\n\n## Features\n\nBuilt on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core):\n\n- Declarative tool definitions — single file per tool, framework handles registration and validation\n- Unified error handling — handlers throw, framework catches, classifies, and formats\n- Pluggable auth: `none`, `jwt`, `oauth`\n- Swappable storage backends: `in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`\n- Structured logging with optional OpenTelemetry tracing\n- STDIO and Streamable HTTP transports\n\nOpen-Meteo–specific:\n\n- No API key required for non-commercial use — zero-config out of the box\n- Self-contained geocoding: `openmeteo_search_locations` resolves place names so agents don't need a separate geocoder\n- ERA5 archive from 1940 to present with same variable schema as the forecast API — direct past/forecast comparisons on one schema\n- Automatic columnar-to-record reshape: Open-Meteo returns parallel time/variable arrays; handlers convert to per-timestamp records with a `*_units` map\n- DataCanvas spillover for `openmeteo_get_forecast`, `openmeteo_get_historical`, `openmeteo_get_marine`, `openmeteo_get_air_quality`, `openmeteo_get_ensemble`, `openmeteo_get_flood`, and `openmeteo_get_climate`: a result too large to return inline registers a DuckDB dataframe for SQL querying, staging every hourly and daily row with its upstream numeric type intact. With `CANVAS_PROVIDER_TYPE=none` (the default) the same size check still applies — those tools return a bounded preview with `truncated: true` and no `canvas_id`, never an unbounded payload claiming to be complete\n- Configurable base URLs for all eight API endpoints (forecast, archive, marine, air quality, geocoding, ensemble, flood, climate) — override for testing or self-hosted deployments\n- **Attribution:** Weather data by [Open-Meteo.com](https://open-meteo.com/) (CC BY 4.0). Non-commercial use is free and keyless; commercial use requires Open-Meteo's paid API tier (~10,000 req/day, 5,000/hour fair-use ceiling for non-commercial)\n\nAgent-friendly output:\n\n- Location-first workflow: `openmeteo_search_locations` returns the IANA timezone alongside coordinates — pass it directly as `timezone` to any weather tool\n- Recovery hints on all error contracts — invalid variable names surface correction guidance with common variable examples\n- Cadence-aware variable validation on `openmeteo_get_forecast`, `openmeteo_get_historical`, `openmeteo_get_marine`, and `openmeteo_get_ensemble`: a variable documented under the opposite cadence is rejected before the upstream call, naming the exact value and the field it belongs in, so the next attempt converges instead of re-guessing against an error that echoes the whole requested list. This is not an allowlist — a name in neither documented set goes upstream untouched\n- Unserved-variable notice on all seven weather tools: Open-Meteo answers a variable name it parses but does not serve with an all-null column and the unit `\"undefined\"` rather than an error, so the result carries a notice naming those columns instead of presenting them as a data gap. `openmeteo_get_air_quality`, `openmeteo_get_flood`, and `openmeteo_get_climate` take a single cadence bucket, so they carry the notice without a cadence guard\n- Coordinate snapping transparency — responses echo the snapped `latitude`/`longitude` (Open-Meteo quantizes to the nearest model grid point) so agents can reason about grid alignment\n- `data_source: \"CAMS\"` label on air quality results distinguishes modeled data from measured station readings\n\n## Getting started\n\n### Public Hosted Instance\n\nA public instance is available at `https://open-meteo.caseyjhand.com/mcp` — no installation required. Point any MCP client at it via Streamable HTTP:\n\n```json\n{\n  \"mcpServers\": {\n    \"open-meteo-mcp-server\": {\n      \"type\": \"streamable-http\",\n      \"url\": \"https://open-meteo.caseyjhand.com/mcp\"\n    }\n  }\n}\n```\n\n### Self-Hosted / Local\n\nAdd the following to your MCP client configuration file.\n\n```json\n{\n  \"mcpServers\": {\n    \"open-meteo-mcp-server\": {\n      \"type\": \"stdio\",\n      \"command\": \"bunx\",\n      \"args\": [\"@cyanheads/open-meteo-mcp-server@latest\"],\n      \"env\": {\n        \"MCP_TRANSPORT_TYPE\": \"stdio\",\n        \"MCP_LOG_LEVEL\": \"info\"\n      }\n    }\n  }\n}\n```\n\nOr with npx (no Bun required):\n\n```json\n{\n  \"mcpServers\": {\n    \"open-meteo-mcp-server\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@cyanheads/open-meteo-mcp-server@latest\"],\n      \"env\": {\n        \"MCP_TRANSPORT_TYPE\": \"stdio\",\n        \"MCP_LOG_LEVEL\": \"info\"\n      }\n    }\n  }\n}\n```\n\nOr with Docker:\n\n```json\n{\n  \"mcpServers\": {\n    \"open-meteo-mcp-server\": {\n      \"type\": \"stdio\",\n      \"command\": \"docker\",\n      \"args\": [\"run\", \"-i\", \"--rm\", \"-e\", \"MCP_TRANSPORT_TYPE=stdio\", \"ghcr.io/cyanheads/open-meteo-mcp-server:latest\"]\n    }\n  }\n}\n```\n\nFor Streamable HTTP, set the transport and start the server:\n\n```sh\nMCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http\n# Server listens at http://localhost:3010/mcp\n```\n\n### Prerequisites\n\n- [Bun v1.3.0](https://bun.sh/) or higher (or Node.js v24+).\n- No API key required. Non-commercial use is free and keyless.\n- Commercial use requires [Open-Meteo's paid API tier](https://open-meteo.com/en/pricing).\n\n### Installation\n\n1. **Clone the repository:**\n\n```sh\ngit clone https://github.com/cyanheads/open-meteo-mcp-server.git\n```\n\n2. **Navigate into the directory:**\n\n```sh\ncd open-meteo-mcp-server\n```\n\n3. **Install dependencies:**\n\n```sh\nbun install\n```\n\n## Configuration\n\nAll configuration is validated at startup via Zod schemas. No API key is required for non-commercial use — all variables are optional.\n\n| Variable | Description | Default |\n|:---|:---|:---|\n| `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http` | `stdio` |\n| `MCP_HTTP_PORT` | HTTP server port | `3010` |\n| `MCP_HTTP_HOST` | HTTP server host | `localhost` |\n| `MCP_HTTP_ENDPOINT_PATH` | HTTP endpoint path | `/mcp` |\n| `MCP_HTTP_MAX_BODY_BYTES` | Maximum HTTP request body bytes; `0` disables the limit. | `1048576` |\n| `MCP_PUBLIC_URL` | Public origin for TLS-terminating reverse-proxy deployments | — |\n| `MCP_SESSION_MODE` | HTTP session mode: `stateful` or `stateless`. | `stateful` |\n| `MCP_HTTP_RESUMABILITY` | Replay missed SSE events for stateful HTTP sessions. No effect on stateless mode or protocol revision 2026-07-28. | `true` |\n| `MCP_HTTP_RESUMABILITY_MAX_EVENTS` | Events retained per stateful session for replay; oldest evicted first. | `512` |\n| `MCP_HTTP_RESUMABILITY_TTL_MS` | How long retained events remain replayable (ms). | `300000` |\n| `MCP_AUTH_MODE` | Auth mode: `none`, `jwt`, or `oauth` | `none` |\n| `MCP_LOG_LEVEL` | Log level (`debug`, `info`, `notice`, `warning`, `error`) | `info` |\n| `MCP_LOG_RATE_LIMIT_THRESHOLD` | Maximum repeated emissions per level and message in each log window; `0` disables suppression. | `10` |\n| `MCP_LOG_RATE_LIMIT_WINDOW_MS` | Repeated-log suppression window (ms). | `60000` |\n| `MCP_GC_PRESSURE_INTERVAL_MS` | Opt-in forced-GC interval (ms, Bun only). Set to `60000` if heap growth is observed under sustained HTTP traffic. | `0` |\n| `LOGS_DIR` | Directory for log files (Node.js only) | `<project-root>/logs` |\n| `STORAGE_PROVIDER_TYPE` | Storage backend: `in-memory`, `filesystem`, `supabase`, `cloudflare-kv/r2/d1` | `in-memory` |\n| `CANVAS_PROVIDER_TYPE` | Canvas engine for `openmeteo_get_forecast` / `openmeteo_get_historical` / `openmeteo_get_marine` / `openmeteo_get_air_quality` / `openmeteo_get_ensemble` / `openmeteo_get_flood` / `openmeteo_get_climate` spillover: `duckdb` or `none`. At `none` those tools still bound an over-budget response to a preview and set `truncated: true` — there is just no canvas holding the rows they omit | `none` |\n| `OPEN_METEO_API_BASE_URL` | Override for the main forecast + elevation API | `https://api.open-meteo.com` |\n| `OPEN_METEO_ARCHIVE_BASE_URL` | Override for the ERA5 historical archive API | `https://archive-api.open-meteo.com` |\n| `OPEN_METEO_MARINE_BASE_URL` | Override for the marine forecast API | `https://marine-api.open-meteo.com` |\n| `OPEN_METEO_AIR_QUALITY_BASE_URL` | Override for the CAMS air quality API | `https://air-quality-api.open-meteo.com` |\n| `OPEN_METEO_GEOCODING_BASE_URL` | Override for the geocoding API | `https://geocoding-api.open-meteo.com` |\n| `OPEN_METEO_ENSEMBLE_BASE_URL` | Override for the ensemble forecast API | `https://ensemble-api.open-meteo.com` |\n| `OPEN_METEO_FLOOD_BASE_URL` | Override for the GloFAS flood API | `https://flood-api.open-meteo.com` |\n| `OPEN_METEO_CLIMATE_BASE_URL` | Override for the CMIP6 climate projections API | `https://climate-api.open-meteo.com` |\n| `OTEL_ENABLED` | Enable OpenTelemetry tracing and metrics | `false` |\n\nSee [`.env.example`](./.env.example) for the full list of optional overrides.\n\n## Running the server\n\n### Local development\n\n- **Build and run the production version:**\n\n  ```sh\n  # One-time build\n  bun run rebuild\n\n  # Run the built server\n  bun run start:http\n  # or\n  bun run start:stdio\n  ```\n\n- **Run checks and tests:**\n\n  ```sh\n  bun run devcheck  # Lint, format, typecheck, security\n  bun run test      # Vitest test suite\n  ```\n\n### Docker\n\n```sh\ndocker build -t open-meteo-mcp-server .\ndocker run --rm -p 3010:3010 open-meteo-mcp-server\n```\n\nThe Dockerfile defaults to HTTP transport, stateless session mode, and logs to `/var/log/open-meteo-mcp-server`. OpenTelemetry peer dependencies are installed by default — build with `--build-arg OTEL_ENABLED=false` to omit them.\n\n## Project structure\n\n| Directory | Purpose |\n|:---|:---|\n| `src/index.ts` | `createApp()` entry point — registers tools, initializes the Open-Meteo service |\n| `src/config` | Server-specific environment variable parsing and validation with Zod |\n| `src/mcp-server/tools/definitions` | Tool definitions (`*.tool.ts`) — one file per tool; includes `dataframe-describe.tool.ts` and `dataframe-query.tool.ts` |\n| `src/services/open-meteo` | Open-Meteo HTTP client wrapping all nine endpoints with retry, error classification, and columnar reshape |\n| `src/services/canvas-accessor.ts` | DataCanvas accessor for `openmeteo_get_forecast` / `openmeteo_get_historical` / `openmeteo_get_marine` / `openmeteo_get_air_quality` / `openmeteo_get_ensemble` / `openmeteo_get_flood` / `openmeteo_get_climate` spillover |\n| `tests/` | Unit and integration tests mirroring `src/` |\n\n## Development guide\n\nSee [`CLAUDE.md`](./CLAUDE.md) for development guidelines and architectural rules. The short version:\n\n- Handlers throw, framework catches — no `try/catch` in tool logic\n- Use `ctx.log` for request-scoped logging, `ctx.state` for tenant-scoped storage\n- Register new tools in the `tools[]` array in `src/index.ts`\n- Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields\n\n## Contributing\n\nIssues and pull requests are welcome. Run checks and tests before submitting:\n\n```sh\nbun run devcheck\nbun run test\n```\n\n## License\n\nApache-2.0 — see [LICENSE](LICENSE) for details.\n\n---\n\n> Weather data by [Open-Meteo.com](https://open-meteo.com/) — licensed [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).\n",
  "bytes": 27551,
  "sha": "77779d8b2a4db8f5ea6e28f38d21f1ad15c2898becc707ce5e1b385947765227",
  "repo_slug": "cyanheads/open-meteo-mcp-server",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_cyanheads_open_meteo_mcp_serve_cd337571/readme"
}