The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the Astronomy MCP Server listing page.
What's in the sky, computed offline — planet and moon positions, rise/set, phases, eclipses, and seasons for any place and time via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://astronomy.caseyjhand.com/mcp
Seven tools — five form the keyless, offline, deterministic core (always registered); two are network-backed extensions that register only when their config gate is enabled.
| Tool | Description |
|---|---|
astronomy_get_sky_position | Apparent position of one body or named star for an observer and instant — equatorial (RA/Dec), horizontal (alt/az), ecliptic, plus distance, magnitude, angular diameter, phase, and constellation. |
astronomy_get_rise_set | Rise, set, and culmination times for a body at a location, with maximum altitude at transit. For the Sun, also the three twilight pairs (civil/nautical/astronomical). |
astronomy_get_moon_phase | Moon phase for an instant: illuminated fraction, phase name, synodic age, phase angle, and the next four quarter phases with timestamps. |
astronomy_find_events | Forward search for the next occurrences of one sky-event class: eclipses, equinoxes, solstices, moon quarters, oppositions, conjunctions, greatest elongations, and apsides. |
astronomy_list_visible | The one-call "what's up right now" answer: every naked-eye body (and optional bright stars) above the horizon, ranked, annotated, and gated by the Sun's altitude into daylight/twilight/dark. |
astronomy_get_ephemeris | (gated extension) Time-series ephemeris for a small body (asteroid/comet) or spacecraft via JPL Horizons — covers what the in-process major-body set cannot. Off by default. |
astronomy_get_satellite_passes | (gated extension) Visible passes of a satellite (by NORAD catalog number, or by a name resolved against the catalog) over an observer, from a CelesTrak GP element set propagated with SGP4 in-process. Off by default. |
This server computes geometry — where a body is, when an event happens — not astrophysics. The core wraps astronomy-engine (sub-arcminute accuracy, ≈1900–2100), so given the same (body, time, observer) every core tool returns identical output with no network, no rate limit, and no API key. It does not geocode: resolve a place name to latitude/longitude upstream (e.g. via an OpenStreetMap server) and pass an IANA timezone to receive observer-local times alongside UTC.
astronomy_get_sky_positionApparent topocentric position of one solar-system body or a named bright star.
star (e.g. "Sirius", "Polaris") instead of body to target a catalog star; star takes precedence over bodynull magnitude / angular diameter / phase fields where the engine cannot compute them — never fabricatedastronomy_get_rise_setRise, set, and culmination times, with twilight for the Sun.
start and returns the next count cycles (default 1, max 31)body: "sun", bundles the three twilight pairs (civil −6°, nautical −12°, astronomical −18°) so a single call answers "when does the sun set and when is it truly dark"null rise/set fields with an explanatory note rather than an error — the fact is the answertimezone is suppliedastronomy_find_eventsForward search across nine event classes under one event enum.
solar_eclipse, lunar_eclipse, equinox, solstice, moon_quarter, opposition, conjunction, max_elongation, perigee_apogeelatitude/longitude) and report local visibility and contact times; lunar eclipses are geocentric and need no locationopposition, conjunction, max_elongation, perigee_apogee) require a body, gated to the bodies each event exists for: opposition to the superior planets (mars through pluto), conjunction to any planet, max_elongation to mercury and venus, perigee_apogee to the moon, earth, or a planetperigee_apogee on earth returns its perihelion and aphelion; conjunction on mercury or venus returns both the inferior and superior passes, labelled by conjunction_kindcount occurrences (default 1, max 20)astronomy_list_visibleThe workflow flagship — one call returns a ranked, condition-gated "what's up" list.
include_stars, the bundled bright stars), keeps those above the horizon, and ranks them brightest-and-highest firstvisibility_note to each body, computed from real magnitude and altitude — no synthetic scoredaylight / civil_twilight / nautical_twilight / astronomical_twilight / dark) and the Sun's altitude alongside the listtime is a single evaluation instant, not a window — for "tonight" pass a time after astronomical dusk (use astronomy_get_rise_set on the Sun to find it)min_altitude to skip objects grazing the horizonastronomy_get_ephemeris (gated extension)Time-series ephemeris for a small body or spacecraft via the keyless JPL Horizons API. Registered only when ASTRONOMY_ENABLE_HORIZONS is set.
"433;" (Eros), "1;" (Ceres)"DES=1P;CAP" (Halley), "DES=2P;CAP" (Encke)"-48" (Hubble)"433 Eros" or "1P/Halley" returns no match or an ambiguous record list and is rejected. Look up designations at ssd.jpl.nasa.gov/tools/sbdb_lookup.html.start/stop are ISO 8601 UTC and stop must be after start; step is a positive count plus a unit of m, h, d, mo, or y ("10m", "1h", "1d")latitude/longitude yields topocentric coordinates and adds alt/az — pass both or neither; one alone is rejected rather than silently downgraded to a geocentric query. Horizons is asked for refracted elevation on a topocentric request, so altitude_degrees carries the same refraction correction the offline core tools reportstart to resume from — one step past it, because Horizons includes the start instant in its output, so resuming at the last row returned repeats it. Re-call with that start, or split the range into smaller adjacent spans — keep the same step and repeat until truncated is false, which concatenates back into the original series with no repeated sample. Widening the step discards samples the original range asked forastronomy_get_satellite_passes (gated extension)Visible passes of a satellite over an observer. Registered only when ASTRONOMY_ENABLE_SATELLITES is set.
norad_id (e.g. 25544 for the ISS) or name — both, or neither, is rejected before any request goes outname is matched as a case-insensitive substring of the catalog name, so it resolves only when it picks out a single object — either the sole match, or the one match carrying that name outright. A broader query is rejected with the matching objects and their catalog numbers to choose from, capped at 20 and stating the full match count. The result echoes the query that resolved it as resolved_from_namestart is omitted rather than reported with start as its rise_utc; move start earlier to see it. A pass rising exactly at start is kept, so feeding a reported rise_utc back as start never loses itstart further from the element set's epoch than the horizon below is rejected as out of range on that distance alone, and an element set that will not propagate to a window inside the horizon is rejected as a reentry — so an empty passes list means only "no visible passes in this window"start must be within about a month of the element set's epoch, which for a tracked object is hours old — so in practice within about a month of today. Past that the mean elements no longer describe the orbit, and SGP4 keeps returning positions built from them, which is why the horizon is enforced on the epoch distance rather than on whether the propagation succeedsdays (default 7, max 10); optional observer-local pass times| Type | Name | Description |
|---|---|---|
| Resource | astronomy://body/{body} | Static reference card for a solar-system body — canonical name, type, mean radius (km), and naked-eye visibility. {body} is one of sun, moon, mercury … pluto. |
| Prompt | astronomy_stargazing_plan | Structures a "plan tonight's stargazing from <place>" workflow, chaining the tools in order and naming the cross-server geocoding and weather steps. Anchors every step to the requested night in the observer timezone, and opts into the bright-star catalog. |
All resource data is also reachable via tools — astronomy_get_sky_position returns the same body metadata inline — so tool-only clients lose nothing. Design reference: docs/design.md.
Built on @cyanheads/mcp-ts-core:
none, jwt, oauthin-memory, filesystem, Supabase, Cloudflare KV/R2/D1Astronomy-specific:
astronomy-engine is the source of truth for positional astronomy; no network, no rate limit, no API key for the five core toolstimezone is supplied; the server never guesses a timezone from coordinatesastronomy_list_visible and astronomy_get_sky_position answer for named starsAgent-friendly output:
null (not 0, not omitted) when unavailable, and format() renders "unavailable" rather than inventing a valueformat() is content-complete on every tool — content[]-only clients see the same fields as structuredContent clients, and the same numbers: a value reads as a rounded display figure followed by its exact counterpart in brackets, e.g. RA 4.4116 h [4.411597993526305], dropped when the rounding already round-trips. astronomy_list_visible rounds hardest, since its per-body line is read at a glance down a list of dozens of bodies, and still carries every value's exact tail — no listed body needs a second call to recover its coordinatesA public instance is available at https://astronomy.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
Add the following to your MCP client configuration file. The five core tools need no configuration; set ASTRONOMY_ENABLE_HORIZONS and/or ASTRONOMY_ENABLE_SATELLITES to true to register the gated extensions.
Or with npx (no Bun required):
Or with Docker:
For Streamable HTTP, set the transport and start the server:
All configuration is optional and validated at startup via Zod schemas in src/config/server-config.ts. The core runs with no configuration at all.
| Variable | Description | Default |
|---|---|---|
ASTRONOMY_ENABLE_HORIZONS | Register the astronomy_get_ephemeris tool (JPL Horizons). | false |
ASTRONOMY_ENABLE_SATELLITES | Register the astronomy_get_satellite_passes tool (CelesTrak + SGP4). | false |
ASTRONOMY_HORIZONS_BASE_URL | Override the JPL Horizons API endpoint. | https://ssd.jpl.nasa.gov/api/horizons.api |
ASTRONOMY_CELESTRAK_BASE_URL | Override the CelesTrak GP endpoint. | https://celestrak.org/NORAD/elements/gp.php |
ASTRONOMY_DEFAULT_TIMEZONE | Fallback IANA timezone when a tool call omits timezone. Unset = UTC-only output. | none |
ASTRONOMY_REQUEST_TIMEOUT_MS | HTTP timeout (ms) for Horizons and CelesTrak requests. | 15000 |
ASTRONOMY_TLE_CACHE_TTL_MS | In-process element-set cache TTL (ms) — respects CelesTrak's refetch guidance (~once/2h). | 7200000 |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | Port for the HTTP server. | 3010 |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (RFC 5424). | info |
LOGS_DIR | Directory for log files (Node.js only). | <project-root>/logs |
STORAGE_PROVIDER_TYPE | Storage backend. | in-memory |
OTEL_ENABLED | Enable OpenTelemetry instrumentation. | false |
See .env.example for the full list of optional overrides.
Build and run:
Run checks and tests:
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/astronomy-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools/resources/prompts and inits services. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts). Five core tools plus two gated extensions. |
src/mcp-server/resources | Resource definitions (*.resource.ts). Body reference card. |
src/mcp-server/prompts | Prompt definitions (*.prompt.ts). Stargazing plan. |
src/services/ephemeris | The offline compute core — astronomy-engine wrapper, body-radius table, and bundled bright-star catalog. |
src/services/horizons | JPL Horizons HTTP client (gated extension). |
src/services/satellite | CelesTrak GP/OMM fetch + SGP4 propagation (gated extension). |
tests/ | Unit and integration tests mirroring src/. |
See CLAUDE.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for request-scoped logging, ctx.state for tenant-scoped storagecreateApp() arrays in src/index.tsnull and never fabricate missing fieldsIssues and pull requests are welcome. Run checks and tests before submitting:
Apache-2.0 — see LICENSE for details.