@cyanheads/astronomy-mcp-server

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.

7 Tools • 1 Resource • 1 Prompt • 1 Skill

[![Version](https://img.shields.io/badge/Version-0.5.0-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/astronomy-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.4.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/astronomy-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/astronomy-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.4-blueviolet.svg?style=flat-square)](https://bun.sh/)
[![Install in Claude Desktop](https://img.shields.io/badge/Install_in-Claude_Desktop-D97757?style=for-the-badge&logo=anthropic&logoColor=white)](https://github.com/cyanheads/astronomy-mcp-server/releases/latest/download/astronomy-mcp-server.mcpb) [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=astronomy-mcp-server&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBjeWFuaGVhZHMvYXN0cm9ub215LW1jcC1zZXJ2ZXIiXX0=) [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=for-the-badge&logo=visualstudiocode&logoColor=white)](https://vscode.dev/redirect?url=vscode:mcp/install?%7B%22name%22%3A%22astronomy-mcp-server%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40cyanheads%2Fastronomy-mcp-server%22%5D%7D) [![Framework](https://img.shields.io/badge/Built%20on-@cyanheads/mcp--ts--core-67E8F9?style=flat-square)](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)
**Public Hosted Server:** [https://astronomy.caseyjhand.com/mcp](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`](https://github.com/cosinekitty/astronomy), 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 | ### Skills | Skill | Description | |:---|:---| | [`astronomy-guide`](./skills/astronomy-guide/SKILL.md) | Chaining the tools for observing questions: anchoring dates to the observer's local night, what "visible" means per tool, Moon interference, eclipses from a place, and the opt-in Horizons and satellite tools | Plugin installs load it from `skills/`; MCP clients that support the Skills extension read it as `skill://astronomy-guide/SKILL.md`. Design reference: [`docs/design.md`](./docs/design.md). ## Capability reference ### `astronomy_get_sky_position` tool - `body` (`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 catalog - Returns `equatorial`, refraction-corrected `horizontal`, and `ecliptic` coordinates plus `magnitude`, `angular_diameter_arcsec`, `phase_angle_degrees`, `illuminated_fraction`, `sun_elongation_degrees`, and `constellation` - Solar-system bodies also carry `body_metadata`, the resource card's `type`, `mean_radius_km`, and `naked_eye`; a star has none --- ### `astronomy_get_rise_set` tool - One `body` 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 series - Each cycle carries `rise_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 error - For `body: "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 included --- ### `astronomy_get_moon_phase` tool - Geocentric: takes only `time` (default now) and an optional `timezone` - Returns `illuminated_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_position` --- ### `astronomy_find_events` tool - One `event` (`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 2100 - Body-relative events require `body` (`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_degrees` - Eclipses take an optional observer (`latitude` and `longitude` together, else `incomplete_observer`) that adds `local_visible` and `contact_altitudes_degrees` - Eclipses are included when their peak is at or after `start`; their contacts may precede `start` --- ### `astronomy_list_visible` tool - Observer coordinates and one evaluation instant, `time` (default now, not a window); `min_altitude` (default 0) and `include_stars` (default false) shape the list - Returns `sky_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 it --- ### `astronomy_get_ephemeris` tool - `designation` 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/az - Up to 200 rows per call; when `truncated` is true the notice names the `start` to resume from. `target_name` is the object Horizons resolved - Fails as `ambiguous_designation`, `observer_is_target`, `body_not_found`, `invalid_step`, `invalid_elevation`, `time_out_of_range`, or `request_rejected`, each naming the input to correct - Registered only when `ASTRONOMY_ENABLE_HORIZONS=true` --- ### `astronomy_get_satellite_passes` tool - Exactly one of `norad_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 7 - 10 days are searched per call; a `days` up to 30 returns the first 10 with a notice naming the `start` and `days` that continue the search - Returns `norad_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_utc` - Registered only when `ASTRONOMY_ENABLE_SATELLITES=true` --- ### `astronomy://body/{body}` resource - `{body}` is one of `sun`, `moon`, `mercury` … `pluto`, with name completion; anything else fails as `unknown_body` - `application/json` card with `body`, `name`, `type` (`star`/`planet`/`moon`/`dwarf`), `mean_radius_km`, and `naked_eye` --- ### `astronomy_stargazing_plan` prompt - Arguments: `location` required; `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`, and `astronomy_list_visible` with `include_stars`, plus the geocoding and weather steps this server doesn't cover ## Features Built on [`@cyanheads/mcp-ts-core`](https://github.com/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 set `ASTRONOMY_DEFAULT_TIMEZONE`) to get `*_local` fields 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 (`elevation` defaults to 0 m) - The two opt-in extensions each carry their own timeout and retry; an upstream failure surfaces as `horizons_unavailable` or `celestrak_unavailable` and never falls back to core output Agent-friendly output: - Uncertainty preserved: `magnitude`, `angular_diameter_arcsec`, `phase_angle_degrees`, and `illuminated_fraction` are `null` where the engine can't compute them, rendered as "unavailable", never zeroed - Deterministic visibility notes: `visibility_note` comes from real magnitude, altitude, sky condition, and Sun elongation, not a synthetic score - Typed error contracts: each declared failure carries a typed `reason` and 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 a `content[]`-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: ```sh claude mcp add --transport http astronomy-mcp-server https://astronomy.caseyjhand.com/mcp ``` For Cline (`cline_mcp_settings.json`): ```json { "mcpServers": { "astronomy-mcp-server": { "type": "streamableHttp", "url": "https://astronomy.caseyjhand.com/mcp" } } } ``` For Cursor (`mcp.json`): ```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. ```json { "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): ```json { "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: ```json { "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: ```sh MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http # Server listens at http://localhost:3010/mcp ``` ### Prerequisites - [Bun v1.4.0](https://bun.sh/) or higher (or Node.js v24+). - No API key. The core is offline, and both extensions (JPL Horizons, CelesTrak) are keyless. ### Installation 1. **Clone the repository:** ```sh git clone https://github.com/cyanheads/astronomy-mcp-server.git ``` 2. **Navigate into the directory:** ```sh cd astronomy-mcp-server ``` 3. **Install dependencies:** ```sh bun install ``` 4. **Configure environment (optional):** ```sh 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](https://github.com/cyanheads/mcp-ts-core/tree/main/docs/telemetry). | `false` | See [`.env.example`](./.env.example) for the full list of optional overrides. ## Running the server ### Local development - **Build and run:** ```sh # One-time build bun run rebuild # Run the built server bun run start:stdio # or bun run start:http ``` - **Run checks and tests:** ```sh bun run devcheck # Lint, format, typecheck, security bun run test # Vitest test suite bun run lint:mcp # Validate MCP definitions against spec ``` ### Docker ```sh 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`](./CLAUDE.md) for development guidelines and architectural rules. The short version: - Handlers throw, framework catches — no `try/catch` in tool logic - Use `ctx.log` for request-scoped logging, `ctx.state` for tenant-scoped storage - Register new tools and resources in the `createApp()` arrays in `src/index.ts` - Normalize raw engine/API values to the domain type; preserve uncertainty as `null` and never fabricate missing fields ## Contributing Issues are welcome. Run checks and tests before submitting: ```sh bun run devcheck bun run test ``` ## License Apache-2.0 — see [LICENSE](./LICENSE) for details.