WeatherWake MCP
Uses Google Maps Platform weather forecasts as an optional upstream source for previewing conditions and evaluating registered weather wakes.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@WeatherWake MCPwake me when gusts under 30 km/h and no rain for 3 h near Raleigh in 48 h"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
WeatherWake MCP
Park a weather condition instead of polling for it. An agent calls register_wake with a site and a rule such as "gust < 30 km/h and no precipitation for 3 h, within the next 48 h". The call returns at once and the session can end. A small local worker checks the forecast on a budgeted cadence. When the forecast shows the window (or the deadline passes), it POSTs a signed webhook so a durable workflow resumes.
Forecast-based trigger, not a safety authority. A
wake.metwebhook means a forecast showed the window you asked for. Forecasts can be wrong. The receiving workflow and the responsible person decide whether work goes ahead.
The problem
Agents that wait on weather tend to poll it inside a long chat ("check again in an hour") and keep an expensive session open while the sky decides. The waiting belongs outside the model. Register the condition once, let a cheap process watch the forecast within an API budget, and wake the workflow with a webhook that the receiver can verify.
Related MCP server: Rain Check
How it works
agent --register_wake--> MCP server --writes--> .weatherwake/wakes/<id>.json (returns in ~2 ms)
|
weatherwake worker (loop or cron) -- forecast: NWS / Google / synthetic
| (daily call budget, per-site cache, 30-180 min cadence)
condition met or expired --+--> outbox (durable, retries)
|
POST WEATHERWAKE_WEBHOOK_URL webhook-id / webhook-timestamp / webhook-signature
|
receiver: verify HMAC + timestamp + replay guard --> enqueue resume job (idempotent per wake)
|
resume worker / your workflow engine continues the parked runA wake is met when the forecast shows at least for_hours consecutive hours where every rule holds. The window must end before expires_at and start within lead_hours of now, and it must be seen on confirmations consecutive checks (spaced at least 60 min apart). A wake expires once no such window can still fit before the deadline. That check makes no upstream call.
Cadence: with no window in the forecast, the worker checks again after the max interval (180 min). With a window further out, it checks again lead_hours before the window starts, clamped to 30-180 min, because forecasts move. While it waits for confirmations, the spacing is 60 min. Wakes on the same site share one cached forecast for 20 min. Every upstream request first takes a unit from a per-UTC-day budget (default 200). Errors that will not fix themselves (outside NWS coverage, a missing key, the API not enabled) back off to the max interval.
Quickstart
bun install --frozen-lockfile
bun run demo # keyless: simulated clock + synthetic forecast; real MCP, worker, HTTP, receiver, queueLive (keyless, US only, via the National Weather Service):
cp .env.example .env
bun run src/cli.ts secret # paste into .env as WEATHERWAKE_WEBHOOK_SECRET (do not share it)
bun run receiver # terminal 1: verifying receiver on 127.0.0.1:8787
bun run worker # terminal 2: checks due wakes every 60 s, delivers webhooks
bun run src/cli.ts preview --lat 35.7796 --lng -78.6382 --rule "wind_gust_kmh < 30" --rule "precip_mm <= 0" --for-hours 3 --source nws
bun run src/cli.ts register --lat 35.7796 --lng -78.6382 --rule "wind_gust_kmh < 30" --rule "precip_mm <= 0" \
--for-hours 3 --within-hours 48 --resume '{"run":"crane-lift-42","step":"schedule-crew"}' --key crane-lift-42MCP server
{
"mcpServers": {
"weatherwake": {
"command": "bun",
"args": ["/abs/path/weatherwake-mcp/src/cli.ts", "serve"],
"env": { "WEATHERWAKE_DATA_DIR": "/abs/path/state", "WEATHERWAKE_WEBHOOK_URL": "https://your-app.example/hooks/weatherwake" }
}
}
}Put the signing secret (and an optional GOOGLE_MAPS_API_KEY) in the environment of both the server and the worker. Do not put them in the MCP config you share. The worker must use the same WEATHERWAKE_DATA_DIR.
Tool | Input | Upstream calls |
|
| none (returns immediately) |
| site + condition | 1 forecast fetch (or a cache hit) |
|
| none |
|
| none |
| none | none. Reports budget used today, cadence, worker heartbeat (warns if no tick in 10 min), signing configured |
The tools take no webhook URL: the target comes from WEATHERWAKE_WEBHOOK_URL, so a prompt cannot redirect wake-ups to another host.
Rule grammar: METRIC OP VALUE [missing ok|fail]. Every rule must hold in an hour. A missing value fails the hour unless the rule says missing ok. The numeric metrics are wind_gust_kmh, wind_speed_kmh, precip_mm, precip_probability_pct, thunderstorm_probability_pct, temperature_c, feels_like_c, cloud_cover_pct, relative_humidity_pct and visibility_km; they take < <= > >=. The metrics condition and precip_type take in / not_in with UPPER_CASE lists and come from Google only. "No precipitation for 3 h" is precip_mm <= 0 with for_hours: 3.
The webhook
Standard Webhooks format. Full field list and receiver contract: docs/WEBHOOK.md.
webhook-id: msg_... same id on every retry of one event
webhook-timestamp: 1793768400 unix seconds of this attempt
webhook-signature: v1,<base64 HMAC-SHA256(secret, "{id}.{timestamp}.{raw body}")>The body is canonical JSON (sorted keys, no whitespace). Its
typeiswake.metorwake.expired. It carrieswake_id,webhook_id(equal to the header),window(start, end, the end of the clear run, and hour values),condition,resume,source,attributionandstats.The secret is
whsec_+ base64 of 24-64 bytes. Several secrets can be configured for rotation: the first signs, all verify.The receiver (
src/receiver.ts,examples/receiver.ts) verifies the raw body in constant time. It rejects timestamps more than 300 s off and remembers accepted ids on disk, so a replay gets200 duplicateand does nothing. It then enqueues the resume job with idempotency keywake:<wake_id>.Interop:
tests/unit/signing.test.tschecks that the referencestandardwebhooksnpm package (1.1.1) verifies our signatures and that we verify its signatures.Delivery: 2xx means done. 408, 429, 5xx and network errors are retried with the same id (backoff 30 s doubling, 8 attempts). Any other 4xx is permanent. The worker never sends an unsigned webhook.
Durable workflow engines
docs/WORKFLOW_MAPPING.md maps the park / resume steps onto Vercel Workflow (createHook + resumeHook from workflow/api), Trigger.dev (wait.createToken / wait.forToken / wait.completeToken) and Hatchet (ctx.waitForEvent + hatchet.events.push). The mapping was written from each package's published type declarations and docs (workflow 5.2.3, @trigger.dev/sdk 4.7.6, @hatchet-dev/typescript-sdk 1.38.0 on npm, 2026-10-21). None of them is a dependency, and the snippets were not executed here.
Measured
From bun run report (scripts/report.ts → reports/weatherwake-mcp.md, reports/weatherwake-mcp.json) on Bun 1.4.3, darwin/x64. The forecasts are synthetic (fixtures/sequences) and run under a simulated clock. The MCP server, worker, cadence, budget, cache, HTTP delivery, signature checks, replay guard and resume queue are the real code. "15-min polling" is arithmetic for comparison (settle time / 15 min), not a measurement.
Run | Wake | Event | Settled (h) | Window start (h) | Window holds in synthetic truth | Checks | Upstream calls | Cache hits | Budget deferrals | Delivery (attempts) | 15-min polling (computed) |
storm-clears | storm-clears wake 1 | wake.met | 18 | 19 | yes | 7 | 7 | 0 | 0 | delivered (3) | 72 |
never-clears | never-clears wake 1 | wake.expired | 46 | - | - | 16 | 16 | 0 | 0 | delivered (1) | 184 |
flapping-c1 | flapping wake 1 | wake.met | 9 | 10 | no | 4 | 4 | 0 | 0 | delivered (1) | 36 |
flapping-c2 | flapping wake 1 | wake.met | 30 | 30 | yes | 13 | 13 | 0 | 0 | delivered (1) | 120 |
shared-site | gust<30 + dry, 3 h | wake.met | 18 | 19 | yes | 7 | 7 | 0 | 0 | delivered (1) | 72 |
shared-site | gust<40, 2 h | wake.met | 16 | 17 | yes | 7 | 1 | 6 | 0 | delivered (1) | 64 |
shared-site | dry + PoP<30, 6 h | wake.met | 17 | 18 | yes | 7 | 1 | 6 | 0 | delivered (1) | 68 |
budget-cap | never-clears wake 1 | wake.expired | 46 | - | - | 17 | 14 | 0 | 3 | delivered (1) | 184 |
What the runs show:
storm-clears: early forecasts put the clearing 6 h too late. The worker spaces checks 3 h apart and wakes at hour 18 for a window starting at hour 19, after 7 checks. The receiver answered 503 twice first, and the third attempt was accepted with the same
webhook-id.flapping: one forecast issuance shows a calm spell that never happens. With
confirmations: 1the wake fires on it, and the window does not hold in the synthetic truth. Withconfirmations: 2it fires 21 h later, on a window that holds.shared-site: three wakes on one site made 21 checks with 9 upstream calls.
budget-cap: with
WEATHERWAKE_MAX_CALLS_PER_DAY=6, 3 checks were deferred to the next UTC day instead of overspending.After delivery in the storm-clears run, the receiver answered the following probes. Replaying the same request got
200 duplicate. A tampered body got401 bad_signature, and so did a wrong secret. The same request 10 min later got401 timestamp_too_old. A request with no signature got401 missing_headers.register_wakethrough the in-memory MCP client: p50 1.94 ms over 20 calls, no upstream calls (this machine; see the report).
Timeline of the storm-clears run: reports/demo-timeline.md.
Local gate (bun run check, this machine): install, skills layout, strict YAML, Biome, tsc --noEmit, 71 unit + keyless e2e tests, 3 live tests, report, gitleaks. All green. The live tests: NWS preview and a worker tick against api.weather.gov passed. The Google Weather test skipped with 403 SERVICE_DISABLED, because the Weather API is not enabled in the key's project.
Configuration
All optional; see .env.example. Bun loads .env automatically.
Variable | Default |
|
|
| none: the worker refuses to deliver without one |
|
|
| 200 upstream calls per UTC day |
| 30 / 180 / 60 / 20 |
| 50 / 8 |
| none: |
Tests
bun run test # unit + keyless e2e: simulated scenarios, MCP tools, stdio server + worker processes, CLI
bun run test:live # NWS (keyless) + Google Weather (skips without a key or on 403 SERVICE_DISABLED); WEATHERWAKE_LIVE=0 skips all
bun run check # the full local gate (CI runs the same script, keyless)Limits
The forecast is the signal. A wake fires on a forecast window, not on observed weather. The synthetic flapping run shows a false wake with
confirmations: 1.Sources. NWS covers the US only; elsewhere you need a Google Maps Platform key with the Weather API enabled. The Google path is unit-tested against synthetic responses with the documented shape, but it was not verified live here: the Weather API is not enabled in the project used for this build (403 SERVICE_DISABLED). The rules
condition/precip_typeand thunderstorm probability need Google; NWS gives 6 h precipitation blocks, which are spread evenly per hour.Sites are lat/lng only. There is no geocoding.
The worker must be running (a loop or cron
worker --once). The MCP server alone never checks anything;wake_statuswarns when no worker has ticked.Single host. The state, outbox, replay guard and queues are files that rely on atomic
renameon one filesystem.Delivery retries stop after 8 attempts (about an hour). That is shorter than the Standard Webhooks example schedule. After that the wake shows
delivery.status: failed.Not measured here: token or dollar savings from ending the chat session, and whether Vercel Workflow, Trigger.dev or Hatchet bill a parked run. The idea's premise of an "idle 200K session" is not quantified by this repo.
Not included: Gemini (named in the idea as optional; not used), adapters that call the workflow engines (the mapping doc only).
Not affiliated
Not affiliated with, endorsed by, or sponsored by Google LLC, the US National Weather Service, Vercel Inc., Trigger.dev, Hatchet, or the Standard Webhooks project. Google Maps Platform is a trademark of Google LLC.
License
Apache-2.0. See LICENSE and NOTICE (code reused from the SkilledDragonCorp labs weatherops-mcp and pausabudget-gate).
Built by SkilledDragon LLC as an educational minilab. Questions: hello@skilleddragon.com.
This server cannot be deployed
Maintenance
Related MCP Connectors
Reliable scheduling for one-off and recurring webhooks, built for apps and agents.
Anonymous webhook capture, inspection, waiting, and response configuration for AI agents.
Reliable async execution for agent tool calls: schema gating, retries, idempotency, audit trail.
Scoped agent execution. Server-side credentials, policy, budgets and verifiable receipts.
Related MCP Servers
- AlicenseBqualityDmaintenanceEnables AI agents to create on-chain and web monitors, dead-man switches, cron triggers, and multi-agent coordination with pay-per-use x402 payments.1924 npmMIT
- AlicenseNot gradedqualityCmaintenanceEnables users to check whether an Australian field-service job can be worked at a location on a given day for a specific trade, using live weather forecasts, daylight hours, and public holiday data.MIT

polyflowofficial
AlicenseNot gradedqualityBmaintenanceEnables AI agents to run model-checked workflows durably, receiving one work order at a time with guarantees on admission.3 npmApache 2.0- AlicenseNot gradedqualityAmaintenanceEnables sleeping agents to monitor multiple event sources and wake only when durable composite or temporal conditions are met, supporting derived events and targeted wakeups without keeping an agent loop alive.133 npm1Apache 2.0