@cyanheads/noaa-cdo-mcp-server
Search NOAA CDO stations and datasets, fetch historical weather observations via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://noaa-cdo.caseyjhand.com/mcp
Tools
7 tools for working with the NOAA Climate Data Online (CDO) API v2:
| Tool | Description |
|---|---|
noaa_list_datasets | List available CDO datasets with IDs, names, and temporal coverage |
noaa_list_data_categories | List data category groups (Temperature, Precipitation, Wind, etc.) |
noaa_list_data_types | List specific measurement labels (TMAX, TMIN, PRCP, SNOW, etc.) by dataset or category |
noaa_find_locations | Search geographic locations by category (states, cities, counties, zip codes, climate regions) |
noaa_find_stations | Search weather stations by location, bounding box, dataset, and data type |
noaa_get_station | Fetch full metadata for a single station by ID |
noaa_fetch_data | Fetch historical observation records for a dataset and date range |
noaa_list_datasets
List all available NOAA CDO datasets — approximately 11 in total.
- Returns dataset IDs, names, data coverage fraction, and temporal range
- No required parameters — returns everything by default
- Optionally filter by data type, location, station, or date range
- Common datasets: GHCND (daily, 1763–present), GSOM (monthly), GSOY (annual), NORMAL_DLY/MLY/ANN/HLY (1981–2010 climate normals)
- Start here to orient before calling
noaa_fetch_data
noaa_list_data_categories
List data category groups that organize related measurement types.
- ~41 categories including Temperature, Precipitation, Wind, Pressure, Sunshine, Sky cover, Weather Type
- Optionally filter by dataset, location, station, or date range
- Use before
noaa_list_data_typesto narrow by measurement domain
noaa_list_data_types
List specific measurement labels for a dataset or category.
- Hundreds of data types across all datasets
- Filter by dataset (e.g.,
GHCND) or category (e.g.,TEMP) to narrow results - Common GHCND types:
TMAX(max temperature),TMIN(min temperature),PRCP(precipitation),SNOW(snowfall),SNWD(snow depth),AWND(average wind speed) - Returns ID, name, coverage fraction, and date range per type
noaa_find_locations
Search geographic locations by category.
- Category types:
ST(US states, ~52),CNTY(counties),CITY(cities),CNTRY(countries),ZIP(zip codes),CLIM_REG(NOAA climate regions),CLIM_DIV(climate divisions), hydrological categories - Use
locationCategoryId=STto list all states in one call; usesortField=namewith pagination to browse cities alphabetically - The CDO API has no name-search parameter — sort by name and page through results to find a specific location
- Returns location IDs (
FIPS:37,CITY:US530031,ZIP:98101) used in station search and data queries
noaa_find_stations
Search weather observation stations.
- Filter by location ID, bounding box (lat/lon), dataset, data type, and date range
- Returns station IDs, names, coordinates, elevation, and data coverage dates
- A station must have data for the dataset and date range you want — pass
datasetIdand date range to ensure compatibility - Common station ID formats:
GHCND:USC00450974,COOP:010008 - Station IDs returned here feed directly into
noaa_fetch_data
noaa_get_station
Fetch full metadata for a single weather station by ID.
- Returns name, coordinates (decimal degrees), elevation, and full data coverage date range
- Use to verify a station before querying data, or to check its temporal coverage
- Mirrors the
noaa://stations/{stationId}resource as a direct lookup
noaa_fetch_data
Fetch historical observation records from a NOAA CDO dataset.
- Requires
datasetId,startDate, andendDate; optionally scoped by station, location, and data type - Date range limits: sub-daily and daily datasets (GHCND, PRECIP_15, PRECIP_HLY, NORMAL_DLY, NORMAL_HLY) — 1 year max per request; monthly and annual datasets (GSOM, GSOY, NORMAL_MLY, NORMAL_ANN) — 10 years max
- Unit selection: strongly recommended — pass
units=metric(SI) orunits=standard(Fahrenheit/inches). Without it, GHCND values are raw tenths-of-unit integers (TMAX=256 = 25.6°C, PRCP=12 = 1.2mm); GSOM/GSOY are already scaled - Climate normals: for any NORMAL_* dataset, use
startDate=2010-01-01andendDate=2010-12-31— that is the API proxy year regardless of which 30-year period is described - Returns flat tuples of
{ date, datatype, station, value, attributes }with pagination metadata
Resources
| Type | Name | Description |
|---|---|---|
| Resource | noaa://datasets | All CDO datasets with IDs and temporal coverage — injectable context for orienting an agent before querying data |
| Resource | noaa://stations/{stationId} | Station metadata by ID — name, coordinates, elevation, and data coverage date range |
Features
Built on @cyanheads/mcp-ts-core:
- Declarative tool definitions — single file per tool, framework handles registration and validation
- Unified error handling across all tools
- Pluggable auth (
none,jwt,oauth) - Swappable storage backends:
in-memory,filesystem,Supabase,Cloudflare KV/R2/D1 - Structured logging with optional OpenTelemetry tracing
- Runs locally (stdio/HTTP) or on Cloudflare Workers from the same codebase
NOAA CDO-specific:
- Full CDO API v2 coverage — datasets, data categories, data types, locations, stations, and observations
- Client-side date range validation with per-dataset limits enforced before hitting the API
- Unit normalization via the CDO
unitsparameter — avoids raw tenths-of-unit integer confusion - Retry with exponential backoff for transient API failures
Agent-friendly output:
- Paginated results across all list and search tools —
limit,offset, and total count in every response - Station and dataset IDs flow naturally between tools — find a location, find stations in it, fetch data from those stations
- Structured error contracts with
reasoncodes and recovery hints — agents can branch on data, not string parsing - Dataset and station resources for injectable, zero-fetch context
Getting started
Self-Hosted / Local
Add the following to your MCP client configuration file.
{
"mcpServers": {
"noaa-cdo-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/noaa-cdo-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-cdo-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/noaa-cdo-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"NOAA_CDO_TOKEN": "your-token-here"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"noaa-cdo-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-cdo-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
Prerequisites
- Bun v1.3.0 or higher (or Node.js ≥24.0.0).
- A free NOAA CDO API token — required for all requests.
Installation
- Clone the repository:
git clone https://github.com/cyanheads/noaa-cdo-mcp-server.git
- Navigate into the directory:
cd noaa-cdo-mcp-server
- Install dependencies:
bun install
Configuration
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_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 |
Running the server
Local development
-
Build and run the production version:
# One-time build bun run rebuild # Run the built server bun run start:http # or bun run start:stdio -
Run checks and tests:
bun run devcheck # Lints, formats, type-checks, and more bun run test # Runs the test suite
Project structure
| Directory | Purpose |
|---|---|
src/mcp-server/tools | Tool definitions (*.tool.ts). Seven tools across datasets, locations, stations, and observations. |
src/mcp-server/resources | Resource definitions. Datasets catalog and station metadata resources. |
src/services/cdo | CDO HTTP client with retry, backoff, and camelCase→lowercase parameter translation. |
src/config | Server-specific environment variable parsing and validation with Zod. |
tests/ | Unit and integration tests, mirroring the src/ structure. |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches — no
try/catchin tool logic - Use
ctx.logfor logging,ctx.statefor storage - Register new tools and resources in the
createApp()arrays
Contributing
Issues and pull requests are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
License
This project is licensed under the Apache 2.0 License. See the LICENSE file for details.