使用说明
@cyanheads/nws-weather-mcp-server
Get US weather forecasts, active alerts, and current observations via the National Weather Service API. STDIO or Streamable HTTP. 10 Tools • 2 Resources
Public Hosted Server: https://nws.caseyjhand.com/mcp
Overview
Real-time US weather from the National Weather Service: forecasts, active alerts, current conditions, and forecaster discussions for any point in the 50 states and US territories, plus zone forecasts, coastal and marine alerts, and a week of station history. No API key required.
Tools
| Tool | Description |
|---|---|
nws_get_forecast |
7-day or hourly forecast for coordinates. Resolves NWS grid internally. |
nws_search_alerts |
Active weather alerts filtered by area, point, zone, event, severity, urgency, certainty, and status. |
nws_get_alert |
One alert message by ID, current or superseded (~7-day retention). |
nws_get_alert_counts |
Active alert counts nationwide, per state/territory or marine area, and per marine region. |
nws_get_observations |
Current conditions by coordinates (nearest station) or station ID. |
nws_get_observation_history |
A station's recent observations, newest first, paged back through the ~7 days NWS keeps. |
nws_find_stations |
Nearby observation stations sorted by distance with bearing. |
nws_list_alert_types |
All valid alert event type names for filter discovery. |
nws_get_office_discussion |
Latest narrative product (AFD, HWO, ZFP, SPS, FWF) from a Weather Forecast Office; a product too large to inline comes back as a section outline. |
nws_get_zone_forecast |
Text forecast periods for an NWS forecast zone. |
Resources
| Resource | Description |
|---|---|
nws://alert-types |
Static list of all valid NWS alert event type names. |
skill://nws-weather-guide/SKILL.md |
Decision gates for combining forecasts, alerts, station trends, and forecaster discussions. |
Alert types are also reachable via nws_list_alert_types. The weather guide is available through the MCP Skills extension and in the plugin's skills/ directory.
Capability reference
nws_get_forecast tool
latitude+longitude; returns 14 named 12-hour periods (~7 days) by default, or withhourly: trueone-hour periods with dewpoint and humidity, 48 per page of the ~156 upstreamnextCursorcarries its mode, so an hourly walk continues hourly without resendinghourly: trueinclude_amounts: trueadds every nativequantitativePrecipitation,snowfallAmount, andiceAccumulationinterval asprecipitationAmounts(layer,uom,valueswithvalidTimeandvalue), plus the separateamountsUpdateTime. Default false; resend it on any cursor page that needs amounts. Interval strings, units, zeroes, and nulls stay unchanged. Missing layers and unavailable amounts are explained innotice; present empty layers carryvalues: []. Absent units or update time are null. An unavailable amount feed leaves the main forecast available.- Returns
updateTime, when the grid's forecast data was last updated, besidegeneratedAt; anoticeflags a grid not updated in more than 24 hours - Returns the
forecastZoneandcountyzone codes for chaining intonws_search_alerts - Marine coordinates fail with
marine_forecast_unsupported, and land points NWS serves no forecast grid for fail withno_forecast_grid
nws_search_alerts tool
- At most one location filter (
area,point,zone,region_type, orregion), or none for a national search;eventmatches case-insensitively and partially;statusdefaults toActual(alsoExercise,System,Test,Draft) limit(default 25) is capped at 25; a larger limit is served as 25 and reported throughrequestedLimit/effectiveLimitnextCursorcarries the query's filters and page size, so pass it back ascursoron its own; filters sent with it are ignored- Each
affectedZonesentry carries its zonetype(forecast/county/fire);referenceslists superseded messages newest first bysent; apointoutside NWS coverage fails withinvalid_point - Each alert carries
office(the issuing office code fornws_get_office_discussion, e.g.RIW), rawvtecstrings, thenwsHeadline, andhazardtags such asmaxHailSizeandmaxWindGust, verbatim
nws_get_alert tool
alert_id: an alertidfromnws_search_alerts, or areferences[].identifierfrom any alert; returns{ alert }in the same shape asnws_search_alerts, including superseded messages- A malformed ID, an unknown one, and one past NWS's ~7-day retention all fail with
alert_not_found
nws_get_alert_counts tool
- No input; returns national
totalAlerts(landAlerts+marineAlerts), plusareascounts keyed by state/territory or marine area code andregionscounts keyed by marine region (AL,AT,GL,GM,PA,PI), each listing only codes with an active alert - Counts cover every message status, Test and Exercise included, so
totalAlertscan exceed a nationalnws_search_alertstotalCount(status: Actualby default); an alert counts once in each area its zones fall in, soareascan sum pasttotalAlerts
nws_get_observations tool
- Look up by coordinates (resolves nearest station) or
station_iddirectly - Dual-unit display on every measurement (F/C, mph/km/h, inHg/hPa, mi/km, in/mm), temperatures to one decimal; 1-, 3-, and 6-hour precipitation amounts from hourly reports, with
0a trace andnullno amount reported; observations older than 2 hours carry a staleness notice, and a separate warning flags a station with most measurements unavailable
nws_get_observation_history tool
station_id(fromnws_find_stations); optionalstart(inclusive) andend(exclusive) as ISO 8601 date-times with seconds and an offset; newest first, back through the ~7 days NWS keepslimit(default 24) is capped at 100; a larger limit is served as 100 and reported throughrequestedLimit/effectiveLimit- Each observation carries the
nws_get_observationsmeasurement fields,nullwhere the station reported nothing; an unknown station fails withstation_not_found, and a malformed window or astartnot beforeendwithinvalid_time_window nextCursorappears only when older observations remain; there is nototalCount, since NWS reports none for a window
nws_find_stations tool
latitude+longitude; optionallimit(default 10) is capped at 50; a larger limit is served as 50 and reported throughrequestedLimit/effectiveLimit- Nearest first; each result carries the station ID for
nws_get_observations, distance (km), bearing, zone codes, elevation, and time zone
nws_list_alert_types tool
- Returns the full set of event types the NWS API recognizes (e.g., "Tornado Warning", "Heat Advisory")
- Use to discover valid values for the
eventfilter innws_search_alerts
nws_get_office_discussion tool
office: usually the 3-letter WFO code (e.g., "SEW" for Seattle) returned as theofficefield bynws_get_forecast;product_type:AFD(default),HWO,ZFP,SPS, orFWF(Fire Weather Planning Forecast)- Returns
productTextplusissuanceTime,issuingOffice,productName,productCode,wmoCollectiveId;content[]carries the product text in atextcode block, so fixed-width tables keep their columns; an unknown office, or one with no current product of the requested type, fails withno_products - A product too large to inline (most ZFPs and FWFs) returns an
outlineof its sections instead ofproductText; passsections(AFD section names, or zone codes for ZFP/HWO/SPS/FWF) to get the matching sections, and an unknown name fails withunknown_section - An alert zone typed
firegets its forecast from the FWF:officefrom the alert'sofficefield,product_type: "FWF",sections: [<zone code>]
nws_get_zone_forecast tool
zone_id: forecast zone code (e.g., "WAZ315") — returned bynws_get_forecast(forecastZone),nws_find_stations(forecastZonecolumn), andnws_search_alerts(thecodeof anaffectedZonesentry withtype: "forecast")- Returns named periods (e.g., "Today", "Tonight", "Monday") with narrative text from local forecasters
- County (
XXC###) and unknown zone codes fail withzone_not_found, and so do most fire zone codes, whose forecast is their segment of the office FWF onnws_get_office_discussion; a fire code that is also a public zone code returns the public zone's forecast; marine zones with no text forecast fail withmarine_forecast_unsupported, and land zones with none withzone_forecast_unavailable
nws://alert-types resource
- Static list of all valid NWS alert event type names, returned as
application/json - No parameters; cached publicly for 1 hour
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.
NWS-specific:
- Sends the required
User-Agentheader automatically (configurable viaNWS_USER_AGENT) — NWS returns 403 without one - Automatic coordinate-to-grid resolution via
/points, cached for 1h since grid cells rarely change - Request timeouts plus retry/backoff for transient NWS API failures
- Zero-auth access — no API keys required
- Paged results — pass the returned
nextCursorback ascursorfor the next page (omitted on the last page)
Agent-friendly output:
- Provenance — responses echo office codes, time zones, and zone codes so agents can chain into
nws_get_office_discussion,nws_get_zone_forecast, andnws_search_alerts - Guidance over silence — empty, truncated, or cursor-past-end results carry a
noticenaming the cause and the next step - Typed error reasons (
invalid_area_code,no_products,zone_not_found, …) so callers branch on data, not string parsing - Dual-unit measurements (F/C, mph/km/h, inHg/hPa, mi/km, in/mm), rounded to match the text output
Getting started
Connect to the hosted instance, no install:
claude mcp add --transport http nws-weather-mcp-server https://nws.caseyjhand.com/mcp
Or run it locally:
claude mcp add nws-weather-mcp-server -- npx -y @cyanheads/nws-weather-mcp-server
For other clients, add this to the MCP config file:
{
"mcpServers": {
"nws-weather-mcp-server": {
"command": "npx",
"args": ["-y", "@cyanheads/nws-weather-mcp-server"]
}
}
}
NWS_USER_AGENT optionally sets the contact identifier sent to NWS. For Docker or a source build, see Running the server.
Configuration
| Variable | Description | Default |
|---|---|---|
NWS_USER_AGENT |
User-Agent for NWS API requests. The API requires this header. | (nws-weather-mcp-server, ...) |
MCP_TRANSPORT_TYPE |
Transport: stdio or http. |
stdio |
MCP_HTTP_PORT |
Port for HTTP server. | 3010 |
MCP_HTTP_HOST |
Hostname for HTTP server. | 127.0.0.1 |
MCP_SESSION_MODE |
HTTP session mode: stateful, stateless, or auto. |
stateless |
MCP_LOG_LEVEL |
Log level: debug, info, notice, warning, error. |
info |
See .env.example for the full list including auth, storage, and OpenTelemetry options.
Running the server
Local development
Source development requires Bun v1.4+. The published executable also runs on Node.js v24+.
git clone https://github.com/cyanheads/nws-weather-mcp-server.git
cd nws-weather-mcp-server && bun install
cp .env.example .env
-
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 bun run test # Runs test suite
Docker
docker run --rm -p 3010:3010 ghcr.io/cyanheads/nws-weather-mcp-server:latest
The image defaults to HTTP transport, stateless session mode, and logs to /var/log/nws-weather-mcp-server. To run it over stdio from an MCP client, use "command": "docker" with "args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/nws-weather-mcp-server:latest"]. To build locally, docker build -t nws-weather-mcp-server .; OpenTelemetry peers are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
Project structure
| Directory | Purpose |
|---|---|
src/index.ts |
createApp() entry point — registers tools, the alert-types resource, and published skills. |
skills/ |
Published agent skills, also served over MCP. |
src/mcp-server/tools/definitions/ |
Tool definitions (*.tool.ts). |
src/mcp-server/resources/definitions/ |
Resource definitions (*.resource.ts). |
src/services/nws/ |
NWS API client and response types. |
src/config/ |
Environment variable parsing and validation with Zod. |
tests/ |
Unit and integration tests mirroring src/. |
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 domain-specific logging,ctx.statefor storage - Add new tools/resources to the barrel exports and the
createApp()arrays insrc/index.ts - Wrap NWS API calls: validate raw JSON → normalize to domain types → return the output schema; never fabricate missing fields
Contributing
Issues are welcome. Run checks before submitting:
bun run devcheck
bun run test
License
Apache-2.0 — see LICENSE for details.