
Wraps NOAA's Climate Data Online API v2 to search weather stations and pull historical observations going back to 1763. You get seven tools covering the full discovery flow: list datasets (GHCND daily, GSOM monthly, climate normals), find locations by state or zip, search stations by bounding box or data type, and fetch observation records with automatic date range validation. The fetch tool enforces per-dataset limits (one year for daily, ten for monthly) and handles unit conversion since raw GHCND values come as tenths-of-unit integers. Includes resources for dataset metadata and station lookups. Useful when you need programmatic access to temperature, precipitation, wind, or snow data without writing pagination and validation logic yourself.
Search NOAA climate stations and datasets, fetch historical weather observations via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://noaa-climate.caseyjhand.com/mcp
NOAA Climate Data Online (CDO) API v2 for historical weather observations, plus two separate NCEI bulk-CSV corpora — the Storm Events Database and Billion-Dollar Weather and Climate Disasters. Search locations and stations, fetch historical observations with date-range validation and unit conversion, and query severe-weather events or disaster costs from any MCP client. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
| Tool | Description |
|---|---|
noaa_climate_list_datasets | List available CDO datasets with IDs, names, and temporal coverage |
noaa_climate_list_data_categories | List data category groups (Temperature, Precipitation, Wind, etc.) |
noaa_climate_list_data_types | List specific measurement labels (TMAX, TMIN, PRCP, SNOW, etc.) by dataset or category |
noaa_climate_list_location_categories | List the 12 location categories that scope location search |
noaa_climate_find_locations | Search geographic locations by category (states, cities, counties, zip codes, climate regions), with an optional name filter |
noaa_climate_find_stations | Search weather stations by location, bounding box, dataset, and data type |
noaa_climate_get_station | Fetch full metadata for a single station by ID |
noaa_climate_fetch_data | Fetch historical observation records for a dataset and date range |
noaa_climate_search_storm_events | Search the NCEI Storm Events Database for one year — tornadoes, hail, floods, hurricanes, with damage, casualties, and narratives |
noaa_climate_get_billion_dollar_disasters | Query NOAA's Billion-Dollar Weather and Climate Disasters — CPI-adjusted costs and deaths per disaster, or per-year totals by disaster class |
| Resource | Description |
|---|---|
noaa://datasets | All CDO datasets with IDs and temporal coverage — injectable context for orienting an agent before querying data |
noaa://stations/{stationId} | Station metadata by ID — name, coordinates, elevation, and data coverage date range |
noaa_climate_list_datasets toollimit 1–1000 (default 25) with offset, sortable by id, name, mindate, maxdate, or datacoveragenoaa_climate_list_data_categories toollimit 1–1000 (default 25) with offset, sortable by id or namenoaa_climate_list_data_types as datacategoryIdnoaa_climate_list_data_types tooldatasetId (e.g. GHCND) or datacategoryId (e.g. TEMP); limit 1–1000 (default 25) with offsetnoaa_climate_fetch_data takes — common GHCND types are TMAX, TMIN, PRCP, SNOW, SNWD, AWND — with coverage and date range when CDO carries themnoaa_climate_list_location_categories toollimit 1–1000 (default 25) with offset, sortable by id or namenoaa_climate_find_locations accepts as locationCategoryId: CITY, ST, CNTY, CNTRY, ZIP, US_TERR, CLIM_REG, CLIM_DIV, HYD_ACC, HYD_CAT, HYD_REG, HYD_SUBnoaa_climate_find_locations toollocationCategoryId scopes the search (e.g. ST returns all 51 states); optional datasetId, datacategoryId, and date-range filters; limit 1–1000 (default 25) with offsetnoaa_climate_find_stations and noaa_climate_fetch_data take — FIPS:37, CITY:US530018, ZIP:98101nameContains adds a case-insensitive name match within one category of at most 4,000 locations (every category but ZIP); it fails as name_filter_requires_category without a category and name_filter_category_too_large past the capnoaa_climate_find_stations toollocationId, extent (lat/lon bounding box), datasetId, datatypeId (array), and date range; limit 1–1000 (default 25) with offsetGHCND:USW00024233, COOP:010008), names, coordinates, elevation, and data-coverage dates — the IDs feed noaa_climate_fetch_data as stationIdnoaa_climate_get_station toolstationId, from noaa_climate_find_stations; the tool form of the noaa://stations/{stationId} resourcenot_found when a well-formed ID resolves to nothingnoaa_climate_fetch_data tooldatasetId, startDate, endDate; optional stationId, locationId, datatypeId arrays. The span cap is 1 year for GHCND, PRECIP_15, PRECIP_HLY, NORMAL_DLY, NORMAL_HLY, NEXRAD2, NEXRAD3 and 10 years for GSOM, GSOY, NORMAL_MLY, NORMAL_ANN; any NORMAL_* dataset takes startDate=2010-01-01 / endDate=2010-12-31{ date, datatype, station, value, attributes } records plus an effectiveQuery echo; date_range_exceeded names the latest maxEndDate CDO will acceptunits: "metric" or "standard" is strongly recommended — without it, GHCND values are raw tenths-of-unit integers (e.g. TMAX=256 is 25.6°C)noaa_climate_search_storm_events toolyear is required (1950 through the current partial year); filter by state (the full NCEI name, e.g. "FLORIDA"), eventType, month, and minDamageInUsd; limit 1–100 (default 50) with offset"1.20M") and a parsed dollar amount, omitted rather than zeroed when NCEI reported none; a zero-match response names the event types and states the year containsyear_unavailable when NCEI publishes no file for the year, and malformed_export when a downloaded file is not the expected tablenoaa_climate_get_billion_dollar_disasters toolsummary=true for per-year counts and costs by disaster class; filter by startYear/endYear, disasterType (one of seven NCEI classes), minCostInUsd, and state (two-letter postal code); limit 1–100 (default 50) with offsetdeclaredCostUnit); a state scope reports national costs (costBasis: "national") and binned per-year costRangeInUsdcoveredYears), not the current calendar yearnoaa://datasets resourceapplication/json — IDs, names, temporal coveragenoaa_climate_list_datasets with no filters and a high limit — injectable, zero-fetch contextnoaa://stations/{stationId} resourcestationId from noaa_climate_find_stations; the resource form of noaa_climate_get_stationnot_found when a well-formed ID resolves to nothingBuilt on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
NOAA-specific:
units parameter avoids raw tenths-of-unit integer confusionlimit reports the reason CDO gave instead of a bare status lineAgent-friendly output:
limit, offset, and total count in every responsereason codes and recovery hints — agents branch on data, not string parsingA public instance is available at https://noaa-climate.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"noaa-climate-mcp-server": {
"type": "streamable-http",
"url": "https://noaa-climate.caseyjhand.com/mcp"
}
}
}
Add the following to your MCP client configuration file.
{
"mcpServers": {
"noaa-climate-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/noaa-climate-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"NOAA_CDO_TOKEN": "your-token-here"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"noaa-climate-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/noaa-climate-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"NOAA_CDO_TOKEN": "your-token-here"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"noaa-climate-mcp-server": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "-e", "NOAA_CDO_TOKEN=your-token-here", "ghcr.io/cyanheads/noaa-climate-mcp-server:latest"]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 NOAA_CDO_TOKEN=your-token-here bun run start:http
# Server listens at http://localhost:3010/mcp
git clone https://github.com/cyanheads/noaa-climate-mcp-server.git
cd noaa-climate-mcp-server
bun install
cp .env.example .env
# edit .env and set required vars
All configuration is validated at startup via Zod schemas in src/config/server-config.ts. Key environment variables:
| Variable | Description | Default |
|---|---|---|
NOAA_CDO_TOKEN | Required. NOAA CDO API token — obtain free at ncdc.noaa.gov/cdo-web/token | — |
MCP_TRANSPORT_TYPE | Transport: stdio or http | stdio |
MCP_HTTP_PORT | HTTP server port | 3010 |
MCP_HTTP_ENDPOINT_PATH | HTTP endpoint path where the MCP server is mounted | /mcp |
MCP_SESSION_MODE | HTTP session posture: stateful, stateless, or auto. Ships as stateless — no tool asks the caller for input mid-handler | stateless |
MCP_PUBLIC_URL | Public origin override for TLS-terminating reverse-proxy deployments | none |
MCP_AUTH_MODE | Authentication: none, jwt, or oauth | none |
MCP_LOG_LEVEL | Log level (debug, info, warning, error, etc.) | info |
MCP_GC_PRESSURE_INTERVAL_MS | Opt-in Bun-only forced-GC pressure loop (ms). Try 60000 if heap growth is observed under sustained HTTP load. | 0 (disabled) |
STORAGE_PROVIDER_TYPE | Storage backend: in-memory, filesystem, supabase, cloudflare-kv/r2/d1 | in-memory |
OTEL_ENABLED | Enable OpenTelemetry | false |
OTEL_EXPORTER_OTLP_ENDPOINT | OTLP base URL; traces go to /v1/traces and metrics to /v1/metrics. Nothing is exported without it | none |
LOG_TOOL_FAILURE_PAYLOADS | Log each failed tool call's arguments and result, redacted by key name and capped at LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES (default 16384). A secret inside a free-form value is not redacted. | false |
See .env.example for the full list of optional overrides.
Build and run the production version:
# One-time build
bun run rebuild
# Run the built server
bun run start:http
# or
bun run start:stdio
Run checks and tests:
bun run devcheck # Lints, formats, type-checks, and more
bun run test # Runs the test suite
bun run test:live # Opt-in: resolves every documented example identifier against the live CDO API
docker build -t noaa-climate-mcp-server .
docker run --rm -e NOAA_CDO_TOKEN=your-token-here -p 3010:3010 noaa-climate-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/noaa-climate-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
| Directory | Purpose |
|---|---|
src/mcp-server/tools | Tool definitions (*.tool.ts). Ten tools across datasets, locations, stations, observations, storm events, and disaster costs. |
src/mcp-server/resources | Resource definitions. Datasets catalog and station metadata resources. |
src/services/cdo | CDO HTTP client with retry, backoff, camelCase→lowercase parameter translation, and recovery of CDO's own rejection message. |
src/services/csv | Incremental RFC 4180 CSV reader shared by the two NCEI bulk-CSV corpora. |
src/services/storm-events | NCEI Storm Events bulk-CSV client — filename discovery, streamed decompression, damage parsing. |
src/services/billion-dollar-disasters | NCEI Billion-Dollar Disasters client — declared-unit resolution and conversion to whole US dollars. |
src/config | Server-specific environment variable parsing and validation with Zod. |
tests/ | Unit and integration tests, mirroring the src/ structure. |
See CLAUDE.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for logging, ctx.state for storagecreateApp() arraysIssues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
This project is licensed under the Apache 2.0 License. See the LICENSE file for details.
NOAA_CDO_TOKEN*NOAA CDO API token. Obtain free at https://www.ncdc.noaa.gov/cdo-web/token.
MCP_LOG_LEVELdefault: infoSets the minimum log level for output (e.g., 'debug', 'info', 'warn').
MCP_HTTP_HOSTdefault: 127.0.0.1The hostname for the HTTP server.
MCP_HTTP_PORTdefault: 3010The port to run the HTTP server on.
MCP_HTTP_ENDPOINT_PATHdefault: /mcpThe endpoint path for the MCP server.
MCP_PUBLIC_URLPublic origin override for deployments behind a TLS-terminating reverse proxy (e.g. https://mcp.example.com).
MCP_AUTH_MODEdefault: noneAuthentication mode to use: 'none', 'jwt', or 'oauth'.