mcp server
astronomy-mcp-server
Offline observational astronomy — planet and moon positions, rise/set, phases, eclipses, and seasons for any place and time via MCP. STDIO or Streamable HTTP.
Set up
Runs on your computer. Pick your agent and operating system.
Before you start
- Node.js 20 or newer
{
"mcpServers": {
"astronomy-mcp-server": {
"command": "npx",
"args": [
"-y",
"@cyanheads/astronomy-mcp-server"
]
}
}
}same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12
Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs
README
synced Oct 20, 2026Public Hosted Server: https://astronomy.caseyjhand.com/mcp
Overview
Observational astronomy for any place and time: sky positions, rise/set and twilight times, moon phases, and the next eclipse, equinox, opposition, or conjunction — or just ask what's visible right now. Computed locally by astronomy-engine, with optional JPL Horizons ephemerides and satellite passes.
Tools
| Tool | Description |
|---|---|
astronomy_get_sky_position | Apparent position of one body or named star: RA/Dec, alt/az, ecliptic, magnitude, phase, Sun elongation, constellation |
astronomy_get_rise_set | Rise, set, and transit times for a body, plus civil, nautical, and astronomical twilight for the Sun |
astronomy_get_moon_phase | Illuminated fraction, phase name, synodic age, and the next four quarter phases |
astronomy_find_events | Next eclipses, equinoxes, solstices, moon quarters, oppositions, conjunctions, greatest elongations, or apsides |
astronomy_list_visible | What's up now: naked-eye bodies (and optional bright stars) above the horizon, ranked, with a sky-condition gate |
astronomy_get_ephemeris | Small-body or spacecraft ephemeris from JPL Horizons (opt-in) |
astronomy_get_satellite_passes | Visible satellite passes from a CelesTrak element set propagated with SGP4 (opt-in) |
Resources
| Resource | Description |
|---|---|
astronomy://body/{body} | Reference card for a solar-system body: name, type, mean radius, naked-eye visibility |
Tool-only clients get the same type, radius, and naked-eye fields inline from astronomy_get_sky_position.
Prompts
| Prompt | Description |
|---|---|
astronomy_stargazing_plan | Plan a night of stargazing from a place, chaining the astronomy tools with geocoding and weather steps |
Design reference: docs/design.md.
Capability reference
astronomy_get_sky_position tool
body(sun,moon,mercury…pluto) orstaris required, withstarwinning when both are set;starresolves against a bundled 32-star catalog by common name or Bayer designation (Sirius,α CMa), and a miss fails asstar_not_foundlisting the catalog- Returns
equatorial, refraction-correctedhorizontal, andeclipticcoordinates plusmagnitude,angular_diameter_arcsec,phase_angle_degrees,illuminated_fraction,sun_elongation_degrees, andconstellation - Solar-system bodies also carry
body_metadata, the resource card'stype,mean_radius_km, andnaked_eye; a star has none
astronomy_get_rise_set tool
- One
bodysearched forward fromstart(default now) for the nextcountcycles, default 1; 31 are served per call, and acountup to 366 returns the first 31 with a notice naming thestartandcountthat continue the series - Each cycle carries
rise_utc,set_utc,transit_utc, andtransit_altitude_degrees; a circumpolar or never-rising body getsnullrise/set and anoteinstead of an error - For
body: "sun",twilightholds civil, nautical, and astronomical dusk/dawn pairs for the night after each cycle's day. When the first day begins afterstart,night_in_progressholds the preceding night, including polar nights without a sunset; the first cycle otherwise carries its own night. Each pair stays within that night, and times beforestartremain included
astronomy_get_moon_phase tool
- Geocentric: takes only
time(default now) and an optionaltimezone - Returns
illuminated_fraction,phase_name,age_days,next_quarters, andphase_longitude_degrees(0 new, 180 full), which is not thephase_angle_degreesofastronomy_get_sky_position
astronomy_find_events tool
- One
event(solar_eclipse,lunar_eclipse,equinox,solstice,moon_quarter,opposition,conjunction,max_elongation,perigee_apogee) and the nextcountoccurrences, default 1; 20 are served per call, and acountup to 200 returns the first 20 with a notice naming thestartandcountthat continue the series; searches stop at the end of 2100 - Body-relative events require
body(body_required); a body the event doesn't apply to fails asbody_not_supported, andconjunctionwithbody2takes the Moon or a planet different frombody(invalid_body_pairotherwise) and reportsseparation_degrees - Eclipses take an optional observer (
latitudeandlongitudetogether, elseincomplete_observer) that addslocal_visibleandcontact_altitudes_degrees - Eclipses are included when their peak is at or after
start; their contacts may precedestart
astronomy_list_visible tool
- Observer coordinates and one evaluation instant,
time(default now, not a window);min_altitude(default 0) andinclude_stars(default false) shape the list - Returns
sky_condition(daylight,civil_twilight,nautical_twilight,astronomical_twilight,dark),sun_altitude_degrees, andbodiesranked brightest-and-highest first, each with avisibility_notethat says when daylight, civil twilight, or Sun glare within 15° hides or dims it
astronomy_get_ephemeris tool
designationis passed to Horizons verbatim ("433;"for a numbered asteroid,"DES=1P;CAP"for a periodic comet, a negative SPK-ID for a spacecraft);startdefaults to now,stopto 24 h later, andstepto"1h"(a count plusm,h,d,mo, ory);latitudeandlongitudetogether add alt/az- Up to 200 rows per call; when
truncatedis true the notice names thestartto resume from.target_nameis the object Horizons resolved - Fails as
ambiguous_designation,observer_is_target,body_not_found,invalid_step,invalid_elevation,time_out_of_range, orrequest_rejected, each naming the input to correct - Registered only when
ASTRONOMY_ENABLE_HORIZONS=true
astronomy_get_satellite_passes tool
- Exactly one of
norad_idorname(ISS, Hubble/HST, and Tiangong/CSS resolve directly; other names are matched against CelesTrak, and an ambiguous one fails asambiguous_satellite_namelisting candidates);startmust be within about a month of today, anddaysdefaults to 7 - 10 days are searched per call; a
daysup to 30 returns the first 10 with a notice naming thestartanddaysthat continue the search - Returns
norad_id,satellite_name, andpassesthat are visible (sunlit, above 10° elevation, Sun below −6°), each with rise/peak/set times and azimuths,peak_altitude_degrees,duration_seconds,sunlit, andvisible_start_utc/visible_end_utc - Registered only when
ASTRONOMY_ENABLE_SATELLITES=true
astronomy://body/{body} resource
{body}is one ofsun,moon,mercury…pluto, with name completion; anything else fails asunknown_bodyapplication/jsoncard withbody,name,type(star/planet/moon/dwarf),mean_radius_km, andnaked_eye
astronomy_stargazing_plan prompt
- Arguments:
locationrequired;date(YYYY-MM-DD) optional, defaults to tonight - Returns one user message that chains
astronomy_get_rise_set(sun, then moon),astronomy_get_moon_phase, andastronomy_list_visiblewithinclude_stars, plus the geocoding and weather steps this server doesn't cover
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.
Astronomy-specific:
- Keyless, offline, deterministic core: the five core tools need no network, API key, or rate limit, and return the same answer for the same body, time, and observer
- Times outside the engine's high-accuracy span (≈1900–2100) fail as
time_out_of_range; the Horizons tool is bounded by each target's own data span instead - Times are ISO 8601 UTC, and a value with no zone is read as UTC; pass an IANA
timezone(or setASTRONOMY_DEFAULT_TIMEZONE) to get*_localfields alongside UTC. The server never infers a timezone from coordinates - No geocoding: resolve a place name to latitude/longitude and a timezone upstream, then pass the coordinates (
elevationdefaults to 0 m) - The two opt-in extensions each carry their own timeout and retry; an upstream failure surfaces as
horizons_unavailableorcelestrak_unavailableand never falls back to core output
Agent-friendly output:
- Uncertainty preserved:
magnitude,angular_diameter_arcsec,phase_angle_degrees, andilluminated_fractionarenullwhere the engine can't compute them, rendered as "unavailable", never zeroed - Deterministic visibility notes:
visibility_notecomes from real magnitude, altitude, sky condition, and Sun elongation, not a synthetic score - Typed error contracts: each declared failure carries a typed
reasonand a recovery hint, so callers can correct and retry - Exact values behind rounded text:
format()pairs each rounded figure with its exact value in brackets (RA 4.4116 h [4.411597993526305]), so acontent[]-only client loses no precision
Getting started
Public Hosted Instance
A public instance is available at https://astronomy.caseyjhand.com/mcp — no installation required. It serves Streamable HTTP; each client takes its own config shape.
For Claude Code:
claude mcp add --transport http astronomy-mcp-server https://astronomy.caseyjhand.com/mcp
For Cline (cline_mcp_settings.json):
{
"mcpServers": {
"astronomy-mcp-server": {
"type": "streamableHttp",
"url": "https://astronomy.caseyjhand.com/mcp"
}
}
}
For Cursor (mcp.json):
{
"mcpServers": {
"astronomy-mcp-server": {
"url": "https://astronomy.caseyjhand.com/mcp"
}
}
}
Claude Desktop adds remote servers under Settings → Connectors.
Self-Hosted / Local
Add the following to your MCP client configuration file. The core tools need no configuration; set ASTRONOMY_ENABLE_HORIZONS and/or ASTRONOMY_ENABLE_SATELLITES to true to register the extensions.
{
"mcpServers": {
"astronomy-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/astronomy-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"astronomy-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/astronomy-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"astronomy-mcp-server": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/astronomy-mcp-server:latest"]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
Prerequisites
- Bun v1.4.0 or higher (or Node.js v24+).
- No API key. The core is offline, and both extensions (JPL Horizons, CelesTrak) are keyless.
Installation
- Clone the repository:
git clone https://github.com/cyanheads/astronomy-mcp-server.git
- Navigate into the directory:
cd astronomy-mcp-server
- Install dependencies:
bun install
- Configure environment (optional):
cp .env.example .env
# edit .env only to enable the extensions or override a default
Configuration
Every variable is optional; the core runs with none set.
| Variable | Description | Default |
|---|---|---|
ASTRONOMY_ENABLE_HORIZONS | Register astronomy_get_ephemeris (JPL Horizons). | false |
ASTRONOMY_ENABLE_SATELLITES | Register astronomy_get_satellite_passes (CelesTrak + SGP4). | false |
ASTRONOMY_DEFAULT_TIMEZONE | Fallback IANA timezone when a call omits timezone. Unset means UTC-only output. | none |
ASTRONOMY_REQUEST_TIMEOUT_MS | HTTP timeout for Horizons and CelesTrak requests, in ms. | 15000 |
ASTRONOMY_TLE_CACHE_TTL_MS | In-process element-set cache TTL, in ms. The 2 h default follows CelesTrak's refetch guidance. | 7200000 |
ASTRONOMY_HORIZONS_BASE_URL | JPL Horizons API endpoint. | https://ssd.jpl.nasa.gov/api/horizons.api |
ASTRONOMY_CELESTRAK_BASE_URL | CelesTrak GP endpoint. | https://celestrak.org/NORAD/elements/gp.php |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | HTTP server port. | 3010 |
MCP_SESSION_MODE | HTTP session mode: stateless, stateful, or auto. | stateless |
MCP_AUTH_MODE | Authentication: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (debug, info, warning, error, etc.). | info |
OTEL_ENABLED | Enable OpenTelemetry. | false |
See .env.example for the full list of optional overrides.
Running the server
Local development
-
Build and run:
# One-time build bun run rebuild # Run the built server bun run start:stdio # or bun run start:http -
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security bun run test # Vitest test suite bun run lint:mcp # Validate MCP definitions against spec
Docker
docker build -t astronomy-mcp-server .
docker run --rm -p 3010:3010 astronomy-mcp-server
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.
Project structure
| 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 opt-in 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 | Offline compute core — astronomy-engine wrapper, body table, bright-star catalog. |
src/services/horizons | JPL Horizons HTTP client (extension). |
src/services/satellite | CelesTrak element-set fetch and SGP4 propagation (extension). |
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 request-scoped logging,ctx.statefor tenant-scoped storage - Register new tools and resources in the
createApp()arrays insrc/index.ts - Normalize raw engine/API values to the domain type; preserve uncertainty as
nulland never fabricate missing fields
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
License
Apache-2.0 — see LICENSE for details.