MCP server: One MCP server.

NOAA CDO

@cyanheadsMCP server · External

Search NOAA CDO stations and datasets, fetch historical weather observations.

Government open datav0.1.15Apache-2.0TypeScript
Freeno charge per call

One address for any MCP client. No sign-in needed on this server.

What it does

  1. Reads
  2. Writes
  3. High impact

At most: only reads or looks up information.

What it costs

Free

Odel charges nothing per call. The publisher may charge for its own service.

Tools

None listed · checked 13 Oct 2026

Odel could not list its tools without signing in.

Try it in the Inspector
README11 min read

@cyanheads/noaa-climate-mcp-server

Search NOAA climate stations and datasets, fetch historical weather observations via MCP. STDIO or Streamable HTTP.

10 Tools • 2 Resources

Version License Docker MCP SDK npm TypeScript Bun

Install in Claude Desktop Install in Cursor Install in VS Code

Framework


Overview

Historical weather and climate records from NOAA: station observations from Climate Data Online, the Storm Events Database, and Billion-Dollar Weather and Climate Disasters. Find stations, pull observations in the units you want, and look up storm damage or disaster costs.

Tools

ToolDescription
noaa_climate_list_datasetsList available CDO datasets with IDs, names, and temporal coverage
noaa_climate_list_data_categoriesList data category groups (Temperature, Precipitation, Wind, etc.)
noaa_climate_list_data_typesList specific measurement labels (TMAX, TMIN, PRCP, SNOW, etc.) by dataset or category
noaa_climate_list_location_categoriesList the 12 location categories that scope location search
noaa_climate_find_locationsSearch geographic locations by category (states, cities, counties, zip codes, climate regions), with an optional name filter
noaa_climate_find_stationsSearch weather stations by location, bounding box, dataset, and data type
noaa_climate_get_stationFetch full metadata for a single station by ID
noaa_climate_fetch_dataFetch historical observation records for a dataset and date range
noaa_climate_search_storm_eventsSearch the NCEI Storm Events Database for one year — tornadoes, hail, floods, hurricanes, with damage, casualties, and narratives
noaa_climate_get_billion_dollar_disastersQuery NOAA's Billion-Dollar Weather and Climate Disasters — CPI-adjusted costs and deaths per disaster, or per-year totals by disaster class

Resources

ResourceDescription
noaa://datasetsAll 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

Capability reference

noaa_climate_list_datasets tool

  • No required inputs; optional data type, location, station, and date-range filters; limit 1–1000 (default 25) with offset, sortable by id, name, mindate, maxdate, or datacoverage
  • Returns each dataset's ID, name, and date span — GHCND (daily, 1763–present), GSOM (monthly), GSOY (annual), NORMAL_DLY/MLY/ANN/HLY (1981–2010 normals)

noaa_climate_list_data_categories tool

  • Optional dataset, location, station, and date-range filters; limit 1–1000 (default 25) with offset, sortable by id or name
  • Returns the 42 measurement categories (Temperature, Precipitation, Wind, Pressure, Sunshine, and more), whose IDs narrow noaa_climate_list_data_types as datacategoryId

noaa_climate_list_data_types tool

  • Filter by datasetId (e.g. GHCND) or datacategoryId (e.g. TEMP); limit 1–1000 (default 25) with offset
  • Returns the datatype IDs noaa_climate_fetch_data takes — common GHCND types are TMAX, TMIN, PRCP, SNOW, SNWD, AWND — with coverage and date range when CDO carries them

noaa_climate_list_location_categories tool

  • Pagination and sort only: limit 1–1000 (default 25) with offset, sortable by id or name
  • Returns the 12 category IDs noaa_climate_find_locations accepts as locationCategoryId: CITY, ST, CNTY, CNTRY, ZIP, US_TERR, CLIM_REG, CLIM_DIV, HYD_ACC, HYD_CAT, HYD_REG, HYD_SUB

noaa_climate_find_locations tool

  • locationCategoryId scopes the search (e.g. ST returns all 51 states); optional datasetId, datacategoryId, and date-range filters; limit 1–1000 (default 25) with offset
  • Returns the location IDs noaa_climate_find_stations and noaa_climate_fetch_data take — FIPS:37, CITY:US530018, ZIP:98101
  • nameContains 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 cap

noaa_climate_find_stations tool

  • Filter by locationId, extent (lat/lon bounding box), datasetId, datatypeId (array), and date range; limit 1–1000 (default 25) with offset
  • Returns station IDs (GHCND:USW00024233, COOP:010008), names, coordinates, elevation, and data-coverage dates — the IDs feed noaa_climate_fetch_data as stationId

noaa_climate_get_station tool

  • Single required input: stationId, from noaa_climate_find_stations; the tool form of the noaa://stations/{stationId} resource
  • Returns name, coordinates, elevation, and the full data-coverage date range; fails as not_found when a well-formed ID resolves to nothing

noaa_climate_fetch_data tool

  • Requires datasetId, 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
  • Returns flat { date, datatype, station, value, attributes } records plus an effectiveQuery echo; date_range_exceeded names the latest maxEndDate CDO will accept
  • units: "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 tool

  • No token required; year is required (1950 through the current partial year); filter by episodeId (exact digit string from a returned event; blank means unset), state (the full NCEI name, e.g. "FLORIDA"), eventType, month, and minDamageInUsd; limit 1–100 (default 50) with offset
  • Each distinct episode narrative appears once in episodeNarratives, linked from events by episodeNarrativeRef; individual eventNarrative text stays on each event. Optional local ISO beginDate/endDate values can feed CDO date filters alongside the unchanged NCEI date-time and timezone fields.
  • Damage arrives as the raw magnitude-suffixed string ("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 contains
  • Fails as year_unavailable when NCEI publishes no file for the year, and malformed_export when a downloaded file is not the expected table

noaa_climate_get_billion_dollar_disasters tool

  • Individual disasters by default, or summary=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 offset
  • Every cost is whole US dollars whatever unit the export declares (echoed as declaredCostUnit); a state scope reports national costs (costBasis: "national") and binned per-year costRangeInUsd
  • Coverage runs 1980 through the last year NCEI has finished assessing (coveredYears), not the current calendar year

noaa://datasets resource

  • All CDO datasets as application/json — IDs, names, temporal coverage
  • Equivalent to noaa_climate_list_datasets with no filters and a high limit — injectable, zero-fetch context

noaa://stations/{stationId} resource

  • stationId from noaa_climate_find_stations; the resource form of noaa_climate_get_station
  • Returns station metadata; fails as not_found when a well-formed ID resolves to nothing

Features

Built 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:

  • Full CDO API v2 coverage — datasets, data categories, data types, locations, stations, and observations — plus two separate NCEI bulk-CSV corpora requiring no token: severe-weather events (Storm Events Database) and billion-dollar disaster costs
  • Client-side date-range validation enforced per dataset before hitting the API, reporting the exact upstream limit back to the caller
  • Unit normalization via CDO's units parameter avoids raw tenths-of-unit integer confusion
  • CDO's own rejection message is recovered and surfaced — an over-long date range, malformed date, missing parameter, or over-large limit reports the reason CDO gave instead of a bare status line
  • Billion-Dollar Disasters costs are converted to whole US dollars from whichever unit each NCEI export declares in its own preamble, since the unit differs by export

Agent-friendly output:

  • Paginated results across every list and search tool — zero-based offset in inputs and returned metadata; CDO metadata stays absent when upstream omits it or includemetadata=false. Coordinates and coverage retain upstream precision; coverage text uses raw fractions labeled 0–1.
  • Station, location, and dataset IDs flow naturally between tools — find a location, find stations in it, fetch data from those stations
  • Structured error contracts with typed reason codes and recovery hints — agents branch on data, not string parsing
  • Damage and cost fields are honest about upstream gaps — an unreported NCEI figure is omitted rather than reported as a confirmed zero

Getting started

Public Hosted Instance

A public instance is available at https://noaa-climate.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:

json
{
  "mcpServers": {
    "noaa-climate-mcp-server": {
      "type": "streamable-http",
      "url": "https://noaa-climate.caseyjhand.com/mcp"
    }
  }
}

Self-Hosted / Local

Add the following to your MCP client configuration file.

json
{
  "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):

json
{
  "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:

json
{
  "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:

sh
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

Installation

  1. Clone the repository:
sh
git clone https://github.com/cyanheads/noaa-climate-mcp-server.git
  1. Navigate into the directory:
sh
cd noaa-climate-mcp-server
  1. Install dependencies:
sh
bun install
  1. Configure environment:
sh
cp .env.example .env
# edit .env and set required vars

Configuration

All configuration is validated at startup via Zod schemas in src/config/server-config.ts. Key environment variables:

VariableDescriptionDefault
NOAA_CDO_TOKENRequired. NOAA CDO API token — obtain free at ncdc.noaa.gov/cdo-web/token—
MCP_TRANSPORT_TYPETransport: stdio or httpstdio
MCP_HTTP_PORTHTTP server port3010
MCP_HTTP_ENDPOINT_PATHHTTP endpoint path where the MCP server is mounted/mcp
MCP_SESSION_MODEHTTP session posture: stateful, stateless, or auto. Ships as stateless — no tool asks the caller for input mid-handlerstateless
MCP_PUBLIC_URLPublic origin override for TLS-terminating reverse-proxy deploymentsnone
MCP_AUTH_MODEAuthentication: none, jwt, or oauthnone
MCP_LOG_LEVELLog level (debug, info, warning, error, etc.)info
MCP_GC_PRESSURE_INTERVAL_MSOpt-in Bun-only forced-GC pressure loop (ms). Try 60000 if heap growth is observed under sustained HTTP load.0 (disabled)
STORAGE_PROVIDER_TYPEStorage backend: in-memory, filesystem, supabase, cloudflare-kv/r2/d1in-memory
OTEL_ENABLEDEnable OpenTelemetryfalse
OTEL_EXPORTER_OTLP_ENDPOINTOTLP base URL; traces go to /v1/traces and metrics to /v1/metrics. Nothing is exported without itnone
LOG_TOOL_FAILURE_PAYLOADSLog 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.

Running the server

Local development

  • Build and run the production version:

    sh
    # One-time build
    bun run rebuild
    
    # Run the built server
    bun run start:http
    # or
    bun run start:stdio
    
  • Run checks and tests:

    sh
    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

sh
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.

Project structure

DirectoryPurpose
src/mcp-server/toolsTool definitions (*.tool.ts). Ten tools across datasets, locations, stations, observations, storm events, and disaster costs.
src/mcp-server/resourcesResource definitions. Datasets catalog and station metadata resources.
src/services/cdoCDO HTTP client with retry, backoff, camelCase→lowercase parameter translation, and recovery of CDO's own rejection message.
src/services/csvIncremental RFC 4180 CSV reader shared by the two NCEI bulk-CSV corpora.
src/services/storm-eventsNCEI Storm Events bulk-CSV client — filename discovery, streamed decompression, damage parsing.
src/services/billion-dollar-disastersNCEI Billion-Dollar Disasters client — declared-unit resolution and conversion to whole US dollars.
src/configServer-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/catch in tool logic
  • Use ctx.log for logging, ctx.state for storage
  • Register new tools and resources in the createApp() arrays
  • Wrap external API calls as validate raw → normalize to domain types → return the output schema; never fabricate missing fields

Contributing

Issues are welcome. Run checks and tests before submitting:

sh
bun run devcheck
bun run test

License

This project is licensed under the Apache 2.0 License. See the LICENSE file for details.

Similar modules

Browse Government open data