DSN Lifeline
Monitors NASA's Deep Space Network via the public DSN Now feed to detect when watched spacecraft are talking to Earth. It tracks active downlinks, dishes, stations, contact state, and feed freshness for the configured spacecraft watchlist.
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., "@DSN Lifelinecheck for new DSN alerts and acknowledge them"
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.
DSN Lifeline
DSN Lifeline watches NASA's Deep Space Network through the public DSN Now feed and queues an alert when one of your favourite spacecraft starts talking to Earth. It runs as a Cloudflare Worker with one SQLite Durable Object, and it exposes the queue as a pull-based MCP server. An AI assistant routine calls pending_alerts on a schedule, tells you about anything new, and calls ack_alerts when done. The server never pushes anything to a phone or any other outside host.
Architecture
flowchart LR
A[NASA DSN Now feed<br/>eyes.nasa.gov] -->|fetch every 60 s| B[Cloudflare Worker<br/>Durable Object alarm]
B --> C[(SQLite Durable Object<br/>snapshot, contacts, alerts)]
C --> D[MCP tools<br/>pending_alerts / ack_alerts]
D --> E[Assistant routine<br/>polls and messages the user]src/index.js checks the key and the body size, then hands POST /mcp to one Durable Object named main. src/lifeline.js is a thin shell over the alert queue, the contact tracker, and the saved state. An alarm runs every 60 seconds, fetches at most one feed, and fetches the config at most once per 24 hours. The alarm is re-armed in finally.
A craft counts as talking when an active downlink matches its table spacecraftID on two accepted snapshots in a row. A contact closes after three missed accepted snapshots and reopens if the gap is 14 good polls or fewer. Rejected polls do not count as absence.
Related MCP server: mcp-meteors
Tools
Tool | Arguments | Purpose |
|
| Which watched crafts are talking, and on which dish and station |
| none | Current watchlist |
|
| Add a craft, optionally as a favourite that raises alerts |
|
| Remove a craft |
|
| Silence a craft for a while |
| none | Feed freshness, coverage, backoff, and row budget |
| none | Unacknowledged alerts still inside their 20 minute window |
|
| Mark alerts done after the user was told |
| none | Queue one fixed test alert, not counted as a favourite |
pending_alerts returns id, kind, craft, dish, station, and time for each alert. The same array is the text of the tool result, as JSON, so a client can ack without reading the structured block. Kinds are fav, stale, canary, shape, and test. ack_alerts ignores ids that are missing or already finished, and an empty ids list returns INVALID_INPUT. Codes are 2 to 12 characters, lowercase letters and digits. Unknown fields return INVALID_INPUT.
Watched crafts are chosen from a local table in src/crafts.js: Europa Clipper (eurc), Psyche (psyc), IMAP (imap), and SWFO-L1 (swfo).
Setup
Requires Node 22.
Install and test:
npm ci npm test npm run typecheckLocal run: copy
.dev.vars.exampleto.dev.vars, setAPI_KEYto a random string of at least 20 characters, thennpm run dev..dev.varsis gitignored.Deploy with Wrangler:
npx wrangler login npx wrangler secret put API_KEY npx wrangler deploySet
DEFAULT_TZ(an IANA zone such asAmerica/Toronto, which is also the default) as a plain variable in the Cloudflare dashboard.wrangler.tomlsetskeep_vars = trueso it survives deploys.Point an MCP client at
https://dsn-lifeline.<your-subdomain>.workers.dev/mcpwith headerAuthorization: Bearer <API_KEY>orX-API-Key: <API_KEY>.
GET /health returns {"ok":true,"spec_version":2} and does not call the Durable Object.
Assistant routine
Run this prompt as a routine every 5 minutes in an assistant that supports MCP tools:
Every 5 minutes, call the DSN Lifeline tool pending_alerts.
Read the text of the result. It is a JSON array. Each item has id, kind, craft, dish, station, and time.
If the array is empty, stay quiet.
For each alert, send the user one chat message:
- kind fav, when dish and station are both present: "<craft> is talking at <time> on dish <dish> at <station>."
- kind fav, when dish or station is missing: "<craft> is talking at <time>."
- kind stale: "DSN Lifeline: no fresh snapshot for 6 hours."
- kind shape: "DSN Lifeline: the feed shape looks wrong."
- kind canary: "DSN Lifeline: no spacecraft talking seen in 24h. Check the matcher."
- kind test: "DSN Lifeline: test alert at <time>."
Then call ack_alerts with the ids from that JSON.Example: asking "Is Europa Clipper talking?" calls dsn_now and yields something like Europa Clipper, dish DSS14, complex Goldstone, talking yes, snapshot age 42 seconds. When a favourite craft talks for the first time in a local day, pending_alerts returns one fav row with the craft, dish, station, and local time.
Safety
The feed is untrusted XML. The server never copies feed names, debug strings, or target names into an alert, a log line, or a tool result.
The key is required.
API_KEYmust be at least 20 characters. A missing, short, or wrong key returns 401 and does not reveal whether the server is configured. That path never touches the Durable Object. A valid key with a timezone the server cannot use returnsNOT_CONFIGURED.Chat lines are a fixed template. The routine uses the craft name from the local table, a dish matching
DSSplus two digits, a station of Goldstone, Canberra, or Madrid, and a local time. Those lines match^[A-Za-z0-9 .,:()'-]{1,140}$. When dish or station is missing, the routine omits that part.Outbound hosts are fixed. Only
eyes.nasa.gov. There is no feed base URL override and no notification host.Auth runs first. The key check and the 16,384 byte body cap both happen before any Durable Object call.
Design decisions
Free-tier row budget. Durable Object SQLite on the free plan allows 100,000 row writes per day. A quiet day here costs about two writes a minute (the alarm plus one snapshot), around 2,880 rows, so roughly 3k writes a day. At 70,000 rows today the poll interval becomes 120 seconds until UTC midnight. At 80,000 the server latches a budget flag for that UTC day and stops queueing chat alerts. source_status reports the counter. Pending alerts are capped at 100 and the oldest is dropped to make room.
100-parameter batching. Durable Object SQLite limits a statement to 100 bound parameters. ack_alerts updates ids in batches of 100, so a long id list cannot fail on the cap.
Constant-time auth. Both the presented key and the expected key are hashed with SHA-256 and the digests are compared with timingSafeEqual. Hashing first gives equal-length inputs, so neither the length nor the content of the key leaks through timing.
Size caps. Request bodies over 16,384 bytes are rejected, using Content-Length when present and a capped stream read when it is absent. A malformed Content-Length is rejected before any buffering. Feed bodies over 256 KB are rejected.
Pull, not push. A push path needs an outbound notification host, a stored topic or token, and delivery retries. Pull keeps the server's only outbound host as NASA, leaves no push credential to leak, and lets the assistant decide how and when to tell the user. The cost is latency of up to one routine interval. Each alert stays relevant for 20 minutes, so a 5 minute routine has several chances to see it.
Testing
npm test runs Node's built-in test runner. Tests use a fake clock, a fake fetch, and node:sqlite, so they never call NASA. node scripts/write-fixtures.js rewrites the XML fixtures.
Last run on Node 22 (v22.23.3): 121 tests across 20 test files, 121 pass, 0 fail, 0 skipped (120 existing plus the new version test). npm run typecheck (TypeScript strict with checkJs) reports no errors. CI runs both on every push (.github/workflows/ci.yml).
How it was built
The spec was locked before coding. Behaviour was written test first, and each change then went through an independent review loop in which a reviewer who had only the spec and the diff looked for faults, and findings were fixed and re-checked. The 1.0.1 version fix followed the same rule: a failing test asserting that serverInfo.version equals the package.json version was written before the bump.
Limitations
Only Psyche (
psyc, -255) has been seen on the live feed. The IDs for Europa Clipper (-159), IMAP (-43), and SWFO-L1 (-231) are not live-verified, so those alerts may never fire until the IDs are checked.Step 0 gate result, from one recorded run of
gates/step0: 60 of 60 polls succeeded, and Psyche was talking in 24 of the 60. The gate is a throwaway Worker. Do not deploy it, because a run polls NASA.There is no push of any kind. Alerts only reach you if a routine polls the queue.
The feed has no documented stability guarantee. The server checks the shape and queues a
shapealert when it looks wrong.Tests do not run inside the real Workers runtime.
test/workerd-shape.test.jsonly checks the export shape.
Limits
Watchlist max 12. Favourite alerts: 1 per craft per local day, 6 created per local day, including dropped rows and one retry if that alert expires while the contact is still talking and the cap still has room. System alerts (stale, canary, and shape, counted by the local day they were created): 10 created per local day, including dropped rows, and one retry while the condition still holds. Pending queue: 100. Each alert is relevant for 20 minutes. Rows older than 2 days are deleted once each UTC day. Deleting them does not queue that stale or canary alert again. At local midnight the local day changes, a craft can raise one favourite again on the new local day, and the daily caps reset. An alert queued before local midnight stays until it is acknowledged or its 20 minute window ends. The caps do not reset at UTC midnight, which is only the row-budget day. Muting or unwatching a craft drops its pending favourite alerts. watch_craft changes: 10 per local day. mute_craft and unwatch_craft: 30 calls per local day each. test_alert: 3 per hour. All tools: 60 calls per UTC hour.
Credits
Spacecraft contact data comes from NASA/JPL's DSN Now feed. This project is not affiliated with or endorsed by NASA or JPL. Check their terms of use before running it against the feed, and keep the polling rate modest.
License
MIT. See LICENSE.
This server cannot be deployed
Maintenance
Related MCP Connectors
- SpanlyOAuthcom.spanly
MCP observability. Query live traffic, errors, duration, and alerts from your AI agent.
An MCP connector that lets your AI agents ask you questions, request permission approvals, and send alerts as push notifications to your phone and mac notch, so you can unblock and approve your agents from anywhere.
Read-only MCP tools for AI agent discovery, structured resources, and NIULAI information.
A live, curated feed of new AI agent capabilities across MCP, SDKs, models, and APIs.
Related MCP Servers
- AlicenseBqualityDmaintenanceEnables AI assistants to interact with Yamcs mission control systems through natural language, providing tools and resources for telemetry, commands, links, storage, instances, and alarms.344MIT
- AlicenseNot gradedqualityBmaintenanceProvides NASA fireball, near-Earth asteroid, and close approach data via MCP, enabling users to query space object information using natural language.31 npmMIT
- AlicenseAqualityCmaintenanceMCP server providing AI agents with access to NASA and space/astronomy data including APOD, Mars rover photos, near-Earth asteroids, exoplanets, Earth imagery, natural events, and space weather.11MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to access curated space and spaceflight RSS/Atom feeds via tools to list feeds, read feed items, and fetch any feed by URL.27 npmMIT