Offline observational astronomy: positions, rise/set, moon phases, eclipses, and seasons.
One address for any MCP client. No sign-in needed on this server.
7 read
Grouped by tool name. At most: only reads or looks up information.
Odel charges nothing per call. The publisher may charge for its own service.
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
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.
| 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) |
| 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.
| 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.
astronomy_get_sky_position toolbody (sun, moon, mercury … pluto) or star is required, with star winning when both are set; star resolves against a bundled 32-star catalog by common name or Bayer designation (Sirius, α CMa), and a miss fails as star_not_found listing the catalogequatorial, refraction-corrected horizontal, and ecliptic coordinates plus magnitude, angular_diameter_arcsec, phase_angle_degrees, illuminated_fraction, sun_elongation_degrees, and constellationbody_metadata, the resource card's type, mean_radius_km, and naked_eye; a star has noneastronomy_get_rise_set toolbody searched forward from start (default now) for the next count cycles, default 1; 31 are served per call, and a count up to 366 returns the first 31 with a notice naming the start and count that continue the seriesrise_utc, set_utc, transit_utc, and transit_altitude_degrees; a circumpolar or never-rising body gets null rise/set and a note instead of an errorbody: "sun", twilight holds civil, nautical, and astronomical dusk/dawn pairs for the night after each cycle's day. When the first day begins after start, night_in_progress holds 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 before start remain includedastronomy_get_moon_phase tooltime (default now) and an optional timezoneilluminated_fraction, phase_name, age_days, next_quarters, and phase_longitude_degrees (0 new, 180 full), which is not the phase_angle_degrees of astronomy_get_sky_positionastronomy_find_events toolevent (solar_eclipse, lunar_eclipse, equinox, solstice, moon_quarter, opposition, conjunction, max_elongation, perigee_apogee) and the next count occurrences, default 1; 20 are served per call, and a count up to 200 returns the first 20 with a notice naming the start and count that continue the series; searches stop at the end of 2100body (body_required); a body the event doesn't apply to fails as body_not_supported, and conjunction with body2 takes the Moon or a planet different from body (invalid_body_pair otherwise) and reports separation_degreeslatitude and longitude together, else incomplete_observer) that adds local_visible and contact_altitudes_degreesstart; their contacts may precede startastronomy_list_visible tooltime (default now, not a window); min_altitude (default 0) and include_stars (default false) shape the listsky_condition (daylight, civil_twilight, nautical_twilight, astronomical_twilight, dark), sun_altitude_degrees, and bodies ranked brightest-and-highest first, each with a visibility_note that says when daylight, civil twilight, or Sun glare within 15° hides or dims itastronomy_get_ephemeris tooldesignation is passed to Horizons verbatim ("433;" for a numbered asteroid, "DES=1P;CAP" for a periodic comet, a negative SPK-ID for a spacecraft); start defaults to now, stop to 24 h later, and step to "1h" (a count plus m, h, d, mo, or y); latitude and longitude together add alt/aztruncated is true the notice names the start to resume from. target_name is the object Horizons resolvedambiguous_designation, observer_is_target, body_not_found, invalid_step, invalid_elevation, time_out_of_range, or request_rejected, each naming the input to correctASTRONOMY_ENABLE_HORIZONS=trueastronomy_get_satellite_passes toolnorad_id or name (ISS, Hubble/HST, and Tiangong/CSS resolve directly; other names are matched against CelesTrak, and an ambiguous one fails as ambiguous_satellite_name listing candidates); start must be within about a month of today, and days defaults to 7days up to 30 returns the first 10 with a notice naming the start and days that continue the searchnorad_id, satellite_name, and passes that are visible (sunlit, above 10° elevation, Sun below −6°), each with rise/peak/set times and azimuths, peak_altitude_degrees, duration_seconds, sunlit, and visible_start_utc/visible_end_utcASTRONOMY_ENABLE_SATELLITES=trueastronomy://body/{body} resource{body} is one of sun, moon, mercury … pluto, with name completion; anything else fails as unknown_bodyapplication/json card with body, name, type (star/planet/moon/dwarf), mean_radius_km, and naked_eyeastronomy_stargazing_plan promptlocation required; date (YYYY-MM-DD) optional, defaults to tonightastronomy_get_rise_set (sun, then moon), astronomy_get_moon_phase, and astronomy_list_visible with include_stars, plus the geocoding and weather steps this server doesn't coverBuilt 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:
time_out_of_range; the Horizons tool is bounded by each target's own data span insteadtimezone (or set ASTRONOMY_DEFAULT_TIMEZONE) to get *_local fields alongside UTC. The server never infers a timezone from coordinateselevation defaults to 0 m)horizons_unavailable or celestrak_unavailable and never falls back to core outputAgent-friendly output:
magnitude, angular_diameter_arcsec, phase_angle_degrees, and illuminated_fraction are null where the engine can't compute them, rendered as "unavailable", never zeroedvisibility_note comes from real magnitude, altitude, sky condition, and Sun elongation, not a synthetic scorereason and a recovery hint, so callers can correct and retryformat() pairs each rounded figure with its exact value in brackets (RA 4.4116 h [4.411597993526305]), so a content[]-only client loses no precisionA 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.
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
git clone https://github.com/cyanheads/astronomy-mcp-server.git
cd astronomy-mcp-server
bun install
cp .env.example .env
# edit .env only to enable the extensions or override a default
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.
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 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.
| 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/. |
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 are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.