Recipes¶
Short worked examples covering the canonical workflows. Each recipe is written as the natural-language exchange you'd have with an MCP-capable chat client; the tool calls the LLM makes are shown inline.
Several recipes have a fully worked, reproducible counterpart under
examples/
— see the Worked example link at the end of each section.
Fetch a TLE and propagate it¶
The most common single-satellite question — current TLE for a named object, propagate it to a few epochs.
You: Fetch the latest TLE for the ISS and propagate it to 2026-05-23T12:00:00Z, 14:00, and 16:00.
The model calls tle_lookup with query="25544" (the ISS NORAD ID — or
the name "ISS (ZARYA)"). The CelesTrak adapter returns the current TLE
plus the parsed OMM JSON; the response is cached for six hours under the
XDG cache directory so a repeat call within that window is free.
It then calls sgp4_propagate with the fetched {line1, line2} and the
three epochs. The default frame="TEME" (SGP4's native output frame)
returns three StateVectors, each with explicit km and km/s units,
the frame string, and the epoch they're valid at. Pass frame="ICRF" or
frame="GCRS" to receive the same trajectory in an inertial Earth-centred
frame; the tool transforms via astropy with the correct per-epoch
obstime. See tle_lookup and
sgp4_propagate for the full schema.
A TLE for a fresh launch (Space-Track)¶
You: Get me the latest TLE for NORAD 62000 — it's a recent launch and I don't see it on CelesTrak.
CelesTrak's general-perturbations feed trails the catalogue for very recent
launches. The model retries tle_lookup against Space-Track, which carries
the deeper, more current GP records:
Space-Track requires a per-user account. If no credential is configured, the
call returns before any network request with the typed error
credential_required.spacetrack, listing the fields it needs. The LLM relays
that you must set ASTRODYNAMICS_MCP_SPACETRACK_USERNAME and
ASTRODYNAMICS_MCP_SPACETRACK_PASSWORD (stdio) or pass the credential in the
initialize _meta block (HTTP) — see Credentials. Once
the credential is present, the same call returns the TLE plus parsed OMM JSON,
cached on disk for six hours so a repeat lookup within that window costs no
request against Space-Track's per-account rate limit.
A numeric query becomes a NORAD_CAT_ID lookup and anything else an
OBJECT_NAME substring search; Space-Track has no CelesTrak-style group
keywords. See Data sources
for the auth, caching, and rate-limit story.
Ground-station passes for a named observer¶
You: Show me Hubble passes above 10° from Madrid over the next seven days.
The model calls tle_lookup with query="HUBBLE" (or NORAD ID 20580).
It then calls access_windows with:
observer={"name": "madrid"}— the named-station registry covers Madrid, Goldstone, Canberra, Svalbard, Wallops, Esrange, GSFC, and JPL. Passobserver={"lat": {...}, "lon": {...}, "alt": {...}}for anything else.- The fetched TLE.
start/endepochs (UTC ISO 8601 with a mandatory time component — bare dates are rejected).min_elevation_deg=10(degrees, not radians).
The tool returns a list of AccessWindows — each with AOS, LOS,
peak-elevation epoch and angle, range at AOS / peak / LOS, and pass
duration. Partial passes that straddle a window boundary are omitted; only
complete (AOS, peak, LOS) triples are emitted.
Filter on range at peak elevation by passing min_range_km /
max_range_km. See access_windows for the full
input list.
Worked example:
Hubble passes from Madrid
— full transcript plus a reproducible script that drives the
tle_lookup → access_windows chain against an in-process MCP
server.
A simple porkchop¶
You: Run a porkchop for Earth → Mars departure 2026-09 through 2026-12, arrival window 2027-04 through 2027-10.
The model calls porkchop with:
departure_body="earth",arrival_body="mars".depart_window=["2026-09-01T00:00:00Z", "2026-12-31T00:00:00Z"].arrive_window=["2027-04-01T00:00:00Z", "2027-10-31T00:00:00Z"].- Default
samples_per_axisandmu="sun".
The tool fetches both bodies' ephemerides from JPL Horizons (cached for a
week by default — planetary ephemerides drift on geological scales), runs
the Lambert solves across the (depart × arrive) grid, and by default
returns the summary shape: the best cell (lowest total Δv), the top
five cells, and an ASCII contour of the C3 grid. Pass output="full" to
receive every feasible cell. See
Output shaping for why the default trims the
grid.
Open the best cell from the summary and feed it straight back into a Lambert solve to recover the depart / arrive velocity vectors at native precision.
Worked example:
Mars launch window 2028
— planning-tier transcript driving porkchop over a 2028 window with
a synthetic Earth/Mars geometry; the run script asserts the best cell
sits inside a plausible Δv envelope.
Hohmann Δv between circular orbits¶
You: Compute the Hohmann Δv from a 250 km circular LEO to GEO.
The Hohmann transfer is the half-ellipse joining perigee at the
departure radius to apogee at the arrival radius. The model calls
lambert_solve with tof set to half the transfer ellipse's period
and supplies both circular-tangent velocities so the response's dv
field carries the two-impulse total directly.
// tools/call → lambert_solve
{
"r1": [6628.137, 0.0, 0.0],
"r2": [-42163.999..., 0.073..., 0.0], // 179.999° offset dodges Lambert's strictly-collinear branch
"tof": 19035.51,
"mu": "earth",
"depart_velocity": [0.0, 7.755, 0.0],
"arrive_velocity": [-0.000005, -3.075, 0.0]
}
Response carries dv ≈ 3.91 km/s plus the transfer arc's semi-major
axis (a = 24396 km) and eccentricity (e ≈ 0.73) — the textbook
Hohmann values.
Worked example: Hohmann LEO → GEO — full transcript with the geometric setup and the collinear-degeneracy workaround explained, plus a reproducible script asserting the Δv lands within ± 0.01 km/s of the textbook ≈ 3.912 km/s.
Time-scale conversions for log analysis¶
You: Convert these telemetry timestamps from GPS to UTC and TDB: 2026-05-23T12:00:00Z, 2026-05-23T12:00:01Z.
The model calls time_convert twice (or in a single multi-target call if
the prompt permits), once for each target scale. The tool wraps
astropy.time so leap-second handling, UT1-UTC corrections (sourced from
the cached IERS Bulletin A), and the TDB ↔ TT relativistic offset are all
correct.
The same tool handles ISO / JD / MJD / seconds-since-J2000 / Unix-time
representations on either side via the in_format / out_format
arguments. See time_convert for the full scale and
format lists; the time-scale Python type is
astrodynamics_mcp.schemas.base.TimeScale.
Plot a satellite ground track¶
Requires the [viz] extra — see Visualisation for the tool
surface and the attachment model.
You: Plot the ISS ground track over one orbit.
A ground track is the sub-satellite latitude/longitude path, read off the Earth-fixed frame. So the model fetches the TLE, then propagates it in ITRS — the rotating frame whose plane the sub-satellite point is taken from, and the cheapest input for the plot tool since no inertial-to-fixed rotation is needed. It samples one ~92-minute revolution (here 31 epochs, three minutes apart) and asks for the full series so the track is smooth:
// tools/call → sgp4_propagate
{
"tle": { "line1": "1 25544U …", "line2": "2 25544 51.6400 …" },
"epochs": ["2024-01-01T00:00:00Z", "…31 epochs…", "2024-01-01T01:30:00Z"],
"frame": "ITRS",
"output": "full"
}
It feeds that states series straight into the plot tool:
plot_ground_track returns three things at once: a PNG ImageContent (the
rendered track), a leading ASCII line, and a structured summary —
revolutions, and the latitude / longitude extent as {value, unit}
quantities. For this orbit the latitude extent comes back ≈ ±51.6°, exactly the
orbit's inclination — a circular orbit's track reaches its inclination at its
highest and lowest points. An image-capable client shows the PNG; a text-only
client still reads the extent from the summary, because the image is
additive, never the only answer.
Overlay ground stations by passing stations; they are drawn as markers on the
same graticule.
Worked example:
ISS ground track
— full transcript plus a reproducible script that drives the
sgp4_propagate → plot_ground_track chain and asserts the PNG and summary
come back together.
Export a trajectory as CZML¶
Requires the [viz] extra — see Visualisation.
You: Export one ISS orbit as CZML so I can load it into Cesium.
CZML animates an object's position over time in an inertial frame, so the model
propagates in TEME — sgp4's native inertial frame, which the CZML backend
renders with no transform — and hands the state series to czml_trajectory.
One revolution sampled every six minutes (16 epochs) keeps the path smooth
without bloating the document:
// tools/call → sgp4_propagate
{
"tle": { "line1": "1 25544U …", "line2": "2 25544 51.6400 …" },
"epochs": ["2024-01-01T00:00:00Z", "…16 epochs…", "2024-01-01T01:30:00Z"],
"frame": "TEME",
"output": "full"
}
The {r, v, frame, epoch} series is exactly what the export tool consumes
(velocity is optional but carried here):
czml_trajectory returns the CZML as an EmbeddedResource — an
application/json document keyed by a stable uri
(czml://trajectory/satellite) — alongside an ASCII line and a structured
summary carrying time_span and the packet / object / contact counts. The
document is a Cesium stream: a preamble with a clock spanning the window, plus
one satellite object whose time-tagged positions Cesium interpolates into an
animated orbit. A client saves resource.text to a .czml file or streams it
to a Cesium viewer; no chat client renders the globe inline (astrodynamics-mcp
ships no viewer). Annotate ground-station contacts by passing intervals — each
becomes a line of sight shown only during its access window.
Worked example:
CZML export for Cesium
— full transcript plus a reproducible script that drives the
sgp4_propagate → czml_trajectory chain and asserts a non-empty CZML
document comes back.
Query a body's state from SPICE kernels¶
Requires the [spice] extra — see SPICE integration
for the kernel model and the full tool surface.
You: Use SPICE to get the Moon's geocentric state on 2026-01-01T00:00:00Z.
An SPK state query needs two kernels furnished first — a leap-second kernel
(to resolve the UTC epoch) and a planetary ephemeris SPK (the states
themselves). The model furnishes both from the NAIF allowlist with
spice_load_kernel:
// tools/call → spice_load_kernel
{ "source": "https://naif.jpl.nasa.gov/pub/naif/generic_kernels/lsk/naif0012.tls" }
// tools/call → spice_load_kernel
{ "source": "https://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/planets/de440s.bsp" }
The pool is additive and persists across calls, so both stay loaded together;
a repeat URL load within the cache TTL skips the network (from_cache=true).
Then it queries the state — the Moon relative to the Earth, in the default
J2000 frame:
// tools/call → spice_state
{
"target": "MOON",
"observer": "EARTH",
"epochs": ["2026-01-01T00:00:00Z"]
}
The response carries one state per requested epoch — position (km) and
velocity (km/s) as {value, unit} quantities in the requested frame, each
epoch self-describing. aberration defaults to NONE (the geometric state),
so light_time comes back null; pass LT or LT+S for a light-time /
stellar-aberration-corrected state and the one-way light time is populated. A
missing kernel returns a typed error rather than a silent empty state — confirm
the pool with spice_list_kernels if a query reports a kernel gap.
Reach for the spice_* tools when an operation needs furnished kernels — an
SPK state, a non-Earth body-fixed frame (spice_frame_transform), body
constants (spice_body_parameters), or ET / SCLK time (spice_time_convert).
For plain UTC / TAI / TT and Earth-centred frames the kernel-free
time_convert and frame_transform
need no kernels at all.
Worked example:
SPICE Mars state
— full transcript furnishing an LSK + planetary SPK and cross-checking
spice_state against a Horizons-backed porkchop for Mars.
Run a GMAT mission from a skeleton¶
Requires the [gmat] extra — see GMAT integration for
install and the full tool surface.
You: Run a Hohmann transfer from a 7000 km circular LEO to GEO and give me the total Δv.
The client lists the GMAT skeleton resources, reads
gmat-skeleton://hohmann-transfer, and edits it for the requested geometry.
Before spending a full mission run, it parses the script with
gmat_validate_script:
// tools/call → gmat_validate_script
{ "script": "Create Spacecraft LEOSat; ... (edited skeleton text)" }
GMAT returns the declared resources and any parse errors. The model fixes a mistyped field name GMAT flagged, re-validates clean, then runs it:
// tools/call → gmat_run_mission
{
"script": "Create Spacecraft LEOSat; ... ",
"overrides": { "LEOSat.SMA": 7000 }
}
The response carries a run_id, the mission summary, and the small report
inline; the model reads the two-impulse Δv from the report rows. Ephemerides
and oversized reports come back as pointers — pull the full bytes with
gmat_read_run_artefact(run_id="…", name="EphemerisFile1"). To explore how
the Δv responds across a range of burn magnitudes instead of a single run,
reach for gmat_sweep; see
GMAT integration for the sweep and
validate-then-run patterns in full.