Tool reference¶
Every tool the server registers, with the input and output JSON schemas
the MCP wire actually carries. The catalogue below is generated at build
time from the live FastMCP registry — what you see is what an MCP
client receives when it issues tools/list against astrodynamics-mcp.
How to read this page¶
Each tool section carries:
- Description. The string the LLM sees in its tool catalogue. Tuned per the eval suite so the model picks the right tool and binds the right arguments under prompt variation.
- Input schema. The JSON-Schema the SDK validates every call
against. The same schema is emitted by
pydantic's.model_json_schema()for the tool's argument model; fielddescriptionandexamplesare visible to the LLM via the SDK. - Output schema. The JSON-Schema for the tool's response body. Every
numeric field follows the
{value, unit}discipline described under Data sources — there are no barekmorkm/sfloats on the wire.
For the underlying Python types and the docstrings, see the API reference.
Tools¶
access_windows¶
Compute ground-station / observer access intervals for a TLE-defined satellite over a UTC time window. e.g. access_windows(observer={'name': 'madrid'}, target_tle={'line1': '...', 'line2': '...'}, start='2024-01-01T00:00:00Z', end='2024-01-02T00:00:00Z', min_elevation_deg=10) returns the satellite's passes above 10° as seen from Madrid. observer is either a named-station dict ({'name': 'madrid' | 'goldstone' | 'canberra' | 'svalbard' | 'wallops' | 'esrange' | 'gsfc' | 'jpl'}) or explicit geodetic coordinates ({lat, lon, alt}). Epochs are UTC ISO 8601 with a mandatory time component ('2024-01-01T00:00:00Z'). min_elevation_deg is in degrees, not radians; below 5° passes are usually operationally useless thanks to horizon refraction and terrain masking — 10° is the standard amateur threshold, 15° for DSN-style large-dish operations. Optional min_range_km / max_range_km filter on the satellite's range at peak elevation (closest approach during the pass). Frame is implicit: ECEF observer + TEME-derived satellite position, transformed internally via skyfield.
Input schema:
{
"$defs": {
"NamedStation": {
"additionalProperties": false,
"description": "An observer identified by a name from the v0.1 station registry.",
"properties": {
"name": {
"description": "Short name of a known ground station. Resolved to lat/lon/alt inside the consuming tool. The v0.1 registry is intentionally small; pass explicit ObserverCoordinates for anything else.",
"enum": [
"madrid",
"goldstone",
"canberra",
"svalbard",
"wallops",
"esrange",
"gsfc",
"jpl"
],
"examples": [
"madrid",
"goldstone"
],
"title": "Name",
"type": "string"
}
},
"required": [
"name"
],
"title": "NamedStation",
"type": "object"
},
"ObserverCoordinates": {
"additionalProperties": false,
"description": "An observer specified by explicit geodetic coordinates.",
"properties": {
"lat": {
"$ref": "#/$defs/Quantity",
"description": "Geodetic latitude in degrees.",
"examples": [
{
"unit": "deg",
"value": 40.4168
}
]
},
"lon": {
"$ref": "#/$defs/Quantity",
"description": "Geodetic longitude in degrees (east-positive).",
"examples": [
{
"unit": "deg",
"value": -3.7038
}
]
},
"alt": {
"$ref": "#/$defs/Quantity",
"description": "Altitude above the WGS-84 ellipsoid in km.",
"examples": [
{
"unit": "km",
"value": 0.667
}
]
}
},
"required": [
"lat",
"lon",
"alt"
],
"title": "ObserverCoordinates",
"type": "object"
},
"Quantity": {
"additionalProperties": false,
"description": "Pydantic model for a scalar ``{value, unit}`` payload.\n\nUsed as the field type in every tool's pydantic output schema where the\nfield carries a scalar physical quantity. The JSON-schema export is what\nthe unit-discipline meta-test recognises as \"correctly wrapped\".",
"properties": {
"value": {
"description": "Numeric value of the quantity.",
"title": "Value",
"type": "number"
},
"unit": {
"description": "Unit string from the allowed-units registry.",
"title": "Unit",
"type": "string"
}
},
"required": [
"value",
"unit"
],
"title": "Quantity",
"type": "object"
},
"TleLines": {
"additionalProperties": false,
"description": "Two-line TLE as raw strings.\n\nBoth lines must be exactly 69 characters (the canonical TLE width).\nSub-69-character lines are the most common LLM mistake \u2014 they almost\nalways come from a chat client stripping trailing whitespace.",
"properties": {
"line1": {
"description": "First TLE line (begins with '1 '), exactly 69 characters.",
"examples": [
"1 25544U 98067A 24001.50000000 .00010000 00000-0 18000-3 0 9990"
],
"title": "Line1",
"type": "string"
},
"line2": {
"description": "Second TLE line (begins with '2 '), exactly 69 characters.",
"examples": [
"2 25544 51.6400 90.0000 0001000 90.0000 270.0000 15.50000000000000"
],
"title": "Line2",
"type": "string"
}
},
"required": [
"line1",
"line2"
],
"title": "TleLines",
"type": "object"
},
"TleOmm": {
"additionalProperties": false,
"description": "Parsed OMM (Orbit Mean Elements Message) JSON.\n\nSchema is intentionally loose at v0.1: we accept whatever the upstream\nOMM source (CelesTrak) emitted. Downstream tools that need specific\nfields raise typed errors when those fields are missing.",
"properties": {
"omm": {
"additionalProperties": true,
"description": "Parsed OMM JSON object (CCSDS standard fields like CCSDS_OMM_VERS, EPOCH, MEAN_ELEMENTS, \u2026). Fetched from CelesTrak by the tle_lookup tool or supplied directly.",
"examples": [
{
"CCSDS_OMM_VERS": "2.0",
"EPOCH": "2024-01-01T12:00:00.000000",
"MEAN_MOTION": 15.5
}
],
"title": "Omm",
"type": "object"
}
},
"required": [
"omm"
],
"title": "TleOmm",
"type": "object"
}
},
"properties": {
"observer": {
"anyOf": [
{
"$ref": "#/$defs/NamedStation"
},
{
"$ref": "#/$defs/ObserverCoordinates"
}
],
"description": "Ground station / observer location. Either a NamedStation ({name: ...}) where name is one of 'madrid', 'goldstone', 'canberra', 'svalbard', 'wallops', 'esrange', 'gsfc', 'jpl' \u2014 resolved to lat/lon/alt against the v0.1 registry \u2014 or explicit ObserverCoordinates ({lat_deg, lon_deg, height_km}) for arbitrary sites.",
"title": "Observer"
},
"target_tle": {
"anyOf": [
{
"$ref": "#/$defs/TleLines"
},
{
"$ref": "#/$defs/TleOmm"
}
],
"description": "The satellite to track, supplied as a TLE line pair ({line1, line2}) or as an OMM payload (the CCSDS-standard JSON returned by tle_lookup). Either form is accepted.",
"title": "Target Tle"
},
"start": {
"description": "Start of the search window, UTC ISO 8601 with a time component (e.g. '2024-01-01T00:00:00Z').",
"examples": [
"2026-05-23T12:00:00Z",
"2026-01-01T00:00:00.500Z"
],
"title": "Start",
"type": "string"
},
"end": {
"description": "End of the search window, UTC ISO 8601 with a time component. Must be strictly after `start`.",
"examples": [
"2026-05-23T12:00:00Z",
"2026-01-01T00:00:00.500Z"
],
"title": "End",
"type": "string"
},
"min_elevation_deg": {
"description": "Horizon mask, in degrees (not radians). Passes peaking below this elevation are filtered out. Conventional thresholds: 10\u00b0 for amateur ground stations, 15\u00b0 for DSN-style large-dish ops; values below 5\u00b0 are usually noisy due to refraction and terrain. Must be in [0, 90].",
"title": "Min Elevation Deg",
"type": "number"
},
"min_range_km": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Optional minimum range from observer to satellite at the pass peak (km). Passes whose closest approach is nearer than this are dropped. Useful for excluding very-low-altitude horizon noise.",
"title": "Min Range Km"
},
"max_range_km": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Optional maximum range from observer to satellite at the pass peak (km). Passes whose closest approach is farther than this are dropped.",
"title": "Max Range Km"
}
},
"required": [
"observer",
"target_tle",
"start",
"end",
"min_elevation_deg"
],
"title": "access_windowsArguments",
"type": "object"
}
Output schema:
{
"$defs": {
"AccessWindow": {
"additionalProperties": false,
"description": "A single ground-station pass: AOS to LOS with peak-elevation details.",
"properties": {
"aos": {
"description": "Acquisition-of-signal epoch (UTC ISO 8601).",
"examples": [
"2026-05-23T12:00:00Z",
"2026-01-01T00:00:00.500Z"
],
"title": "Aos",
"type": "string"
},
"los": {
"description": "Loss-of-signal epoch (UTC ISO 8601).",
"examples": [
"2026-05-23T12:00:00Z",
"2026-01-01T00:00:00.500Z"
],
"title": "Los",
"type": "string"
},
"peak_elevation_time": {
"description": "Epoch of maximum elevation during the pass (UTC ISO 8601).",
"examples": [
"2026-05-23T12:00:00Z",
"2026-01-01T00:00:00.500Z"
],
"title": "Peak Elevation Time",
"type": "string"
},
"peak_elevation": {
"$ref": "#/$defs/Quantity",
"description": "Maximum elevation above the local horizon during the pass (deg).",
"examples": [
{
"unit": "deg",
"value": 35.0
}
]
},
"range_at_aos": {
"$ref": "#/$defs/Quantity",
"description": "Observer-to-satellite range at AOS (km). Always at the horizon-mask elevation.",
"examples": [
{
"unit": "km",
"value": 1483.0
}
]
},
"range_at_peak": {
"$ref": "#/$defs/Quantity",
"description": "Observer-to-satellite range at peak elevation (km). Typically the closest approach during the pass; used by the `min_range_km` / `max_range_km` filters.",
"examples": [
{
"unit": "km",
"value": 600.0
}
]
},
"range_at_los": {
"$ref": "#/$defs/Quantity",
"description": "Observer-to-satellite range at LOS (km). Always at the horizon-mask elevation.",
"examples": [
{
"unit": "km",
"value": 1505.0
}
]
},
"duration": {
"$ref": "#/$defs/Quantity",
"description": "Time between AOS and LOS, in seconds.",
"examples": [
{
"unit": "s",
"value": 480.0
}
]
}
},
"required": [
"aos",
"los",
"peak_elevation_time",
"peak_elevation",
"range_at_aos",
"range_at_peak",
"range_at_los",
"duration"
],
"title": "AccessWindow",
"type": "object"
},
"Quantity": {
"additionalProperties": false,
"description": "Pydantic model for a scalar ``{value, unit}`` payload.\n\nUsed as the field type in every tool's pydantic output schema where the\nfield carries a scalar physical quantity. The JSON-schema export is what\nthe unit-discipline meta-test recognises as \"correctly wrapped\".",
"properties": {
"value": {
"description": "Numeric value of the quantity.",
"title": "Value",
"type": "number"
},
"unit": {
"description": "Unit string from the allowed-units registry.",
"title": "Unit",
"type": "string"
}
},
"required": [
"value",
"unit"
],
"title": "Quantity",
"type": "object"
}
},
"additionalProperties": false,
"description": "List of `AccessWindow`s for the requested observer and time range.",
"properties": {
"windows": {
"description": "Complete (AOS, peak, LOS) passes inside the requested window. Passes that begin before `start` or end after `end` are omitted \u2014 only complete triples are emitted. Range-filtered passes are also omitted.",
"items": {
"$ref": "#/$defs/AccessWindow"
},
"title": "Windows",
"type": "array"
}
},
"required": [
"windows"
],
"title": "AccessWindowsResponse",
"type": "object"
}
bplane_target¶
Compute B-plane coordinates (B·T, B·R), hyperbolic excess velocity v_∞, and asymptote declination for a hyperbolic planetocentric flyby, and optionally return the linearised one-step Δv at the input epoch that drives the B-plane to caller-supplied target coordinates. e.g. bplane_target(state={'r': {'value': [5000, 0, 0], 'unit': 'km'}, 'v': {'value': [0, 5.069, 0], 'unit': 'km/s'}, 'frame': 'ICRF', 'epoch': '2026-12-01T00:00:00Z'}, target_body='mars', target_epoch='2026-12-01T00:00:00Z', target_btr_km=1000.0) returns the current B-plane state plus the Δv that drives B·R toward 1000 km. Read-only mode (both target_btr_km and target_btt_km omitted) skips the targeting step. The input state must be planetocentric — r and v relative to target_body's centre — in an inertial frame (ICRF / GCRS / CIRS etc.); the tool does not transform the state. Epochs are UTC ISO 8601 with a mandatory time component ('2026-12-01T00:00:00Z'). target_epoch is the closest-approach epoch the caller is designing for; the Δv applies at state.epoch and is documented as such. The targeting solver is linearised about the input state; for large required Δv apply the result and re-call to converge, or hand off to a multi-iteration targeter. Non-hyperbolic states (specific energy ≤ 0) raise invalid_input.not_hyperbolic; unknown target_body raises invalid_input.unknown_body.
Input schema:
{
"$defs": {
"Frame": {
"description": "Reference frames used for state vectors and frame conversions.\n\nThe v0.1 set covers the inertial, Earth-rotating, and IAU body-fixed\nframes the wrapped upstreams (sgp4, astropy.coordinates, skyfield) all\nspeak. Adding a new frame here means adding the corresponding transform\npath to the frame_transform tool.",
"enum": [
"TEME",
"ICRF",
"GCRS",
"ITRS",
"CIRS",
"TIRS",
"IAU_EARTH",
"IAU_MARS",
"IAU_MOON"
],
"title": "Frame",
"type": "string"
},
"QuantityVector": {
"additionalProperties": false,
"description": "Pydantic model for a vector ``{value: [...], unit}`` payload.\n\nThe JSON-schema export uses ``type: array, items: {type: number}`` for\nthe ``value`` field \u2014 the unit-discipline meta-test treats this as the\ncanonical vector-quantity shape.",
"properties": {
"value": {
"description": "Numeric values of the quantity vector.",
"items": {
"type": "number"
},
"title": "Value",
"type": "array"
},
"unit": {
"description": "Unit string from the allowed-units registry.",
"title": "Unit",
"type": "string"
}
},
"required": [
"value",
"unit"
],
"title": "QuantityVector",
"type": "object"
},
"StateVector": {
"additionalProperties": false,
"description": "Cartesian state in a named frame at a named epoch.\n\nThe position and velocity are :class:`QuantityVector` instances so the\nunit (km vs m, km/s vs m/s) is on the wire \u2014 never implicit.",
"properties": {
"r": {
"$ref": "#/$defs/QuantityVector",
"description": "Cartesian position vector [x, y, z]. Unit must be a length (km / m / AU).",
"examples": [
{
"unit": "km",
"value": [
7000.0,
0.0,
0.0
]
}
]
},
"v": {
"$ref": "#/$defs/QuantityVector",
"description": "Cartesian velocity vector [vx, vy, vz]. Unit must be a velocity (km/s / m/s).",
"examples": [
{
"unit": "km/s",
"value": [
0.0,
7.5,
0.0
]
}
]
},
"frame": {
"$ref": "#/$defs/Frame",
"description": "Reference frame in which r and v are expressed.",
"examples": [
"TEME",
"ICRF"
]
},
"epoch": {
"description": "UTC ISO 8601 epoch at which the state is valid.",
"examples": [
"2026-05-23T12:00:00Z",
"2026-01-01T00:00:00.500Z"
],
"title": "Epoch",
"type": "string"
}
},
"required": [
"r",
"v",
"frame",
"epoch"
],
"title": "StateVector",
"type": "object"
}
},
"properties": {
"state": {
"$ref": "#/$defs/StateVector",
"description": "Hyperbolic planetocentric state at the maneuver epoch \u2014 position (km) and velocity (km/s) relative to `target_body`'s centre, in an inertial frame (ICRF or GCRS). The tool does not transform the input; pass a state already in body-centred inertial coordinates."
},
"target_body": {
"description": "Body the spacecraft is flying by, lower-case JPL Horizons name ('mercury', 'venus', 'earth', 'moon', 'mars', 'jupiter', 'saturn', 'uranus', 'neptune'). Selects \u03bc and the body radius used in the B-plane geometry.",
"title": "Target Body",
"type": "string"
},
"target_epoch": {
"description": "Reference epoch for the targeting evaluation, UTC ISO 8601 with a time component. Carried in the response for traceability; the linearised solver applies the returned \u0394v at `state.epoch`.",
"examples": [
"2026-05-23T12:00:00Z",
"2026-01-01T00:00:00.500Z"
],
"title": "Target Epoch",
"type": "string"
},
"target_btr_km": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Optional B-plane R-component target (km). When supplied, the tool returns the one-step linearised \u0394v that drives B\u00b7R toward this value. Pass either or both of `target_btr_km` and `target_btt_km` to invoke the targeting solver; omit both to get the B-plane element calculation only.",
"title": "Target Btr Km"
},
"target_btt_km": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"default": null,
"description": "Optional B-plane T-component target (km). Same semantics as `target_btr_km` \u2014 supplying both fully constrains the B-plane intercept; supplying just one solves the 1-DOF target.",
"title": "Target Btt Km"
}
},
"required": [
"state",
"target_body",
"target_epoch"
],
"title": "bplane_targetArguments",
"type": "object"
}
Output schema:
{
"$defs": {
"BplaneResidual": {
"additionalProperties": false,
"description": "Post-\u0394v recompute of the B-plane coords, plus the constraint-space error.\n\n``actual_b_t_after_dv`` and ``actual_b_r_after_dv`` are computed by\napplying the linear \u0394v to the input velocity and recomputing the\nB-plane geometry nonlinearly \u2014 the ground truth the caller will see\nonce the burn is executed. ``magnitude`` is the Euclidean distance\nfrom the requested target in the dimensions the caller constrained\n(1-D if only one target was supplied, 2-D if both).",
"properties": {
"actual_b_t_after_dv": {
"$ref": "#/$defs/Quantity",
"description": "B\u00b7T after applying the returned \u0394v to the input velocity and recomputing the B-plane geometry exactly (no linearisation). Compare against the requested target_btt_km to gauge the linear solver's accuracy.",
"examples": [
{
"unit": "km",
"value": 1995.0
}
]
},
"actual_b_r_after_dv": {
"$ref": "#/$defs/Quantity",
"description": "B\u00b7R after applying the returned \u0394v to the input velocity and recomputing the B-plane geometry exactly (no linearisation).",
"examples": [
{
"unit": "km",
"value": -3.0
}
]
},
"magnitude": {
"$ref": "#/$defs/Quantity",
"description": "Euclidean distance between the post-\u0394v (B\u00b7T, B\u00b7R) and the requested target in the constrained dimensions, km. A small magnitude means the linear solver hit the target; a large magnitude means a follow-up iteration is needed.",
"examples": [
{
"unit": "km",
"value": 5.4
}
]
}
},
"required": [
"actual_b_t_after_dv",
"actual_b_r_after_dv",
"magnitude"
],
"title": "BplaneResidual",
"type": "object"
},
"Quantity": {
"additionalProperties": false,
"description": "Pydantic model for a scalar ``{value, unit}`` payload.\n\nUsed as the field type in every tool's pydantic output schema where the\nfield carries a scalar physical quantity. The JSON-schema export is what\nthe unit-discipline meta-test recognises as \"correctly wrapped\".",
"properties": {
"value": {
"description": "Numeric value of the quantity.",
"title": "Value",
"type": "number"
},
"unit": {
"description": "Unit string from the allowed-units registry.",
"title": "Unit",
"type": "string"
}
},
"required": [
"value",
"unit"
],
"title": "Quantity",
"type": "object"
},
"QuantityVector": {
"additionalProperties": false,
"description": "Pydantic model for a vector ``{value: [...], unit}`` payload.\n\nThe JSON-schema export uses ``type: array, items: {type: number}`` for\nthe ``value`` field \u2014 the unit-discipline meta-test treats this as the\ncanonical vector-quantity shape.",
"properties": {
"value": {
"description": "Numeric values of the quantity vector.",
"items": {
"type": "number"
},
"title": "Value",
"type": "array"
},
"unit": {
"description": "Unit string from the allowed-units registry.",
"title": "Unit",
"type": "string"
}
},
"required": [
"value",
"unit"
],
"title": "QuantityVector",
"type": "object"
}
},
"additionalProperties": false,
"description": "Response from :func:`bplane_target`.",
"properties": {
"b_r": {
"$ref": "#/$defs/Quantity",
"description": "B\u00b7R component of the impact-parameter vector at the input state, km.",
"examples": [
{
"unit": "km",
"value": 0.0
}
]
},
"b_t": {
"$ref": "#/$defs/Quantity",
"description": "B\u00b7T component of the impact-parameter vector at the input state, km.",
"examples": [
{
"unit": "km",
"value": -8660.25
}
]
},
"v_infinity": {
"$ref": "#/$defs/QuantityVector",
"description": "Hyperbolic excess velocity vector at the incoming asymptote, km/s, in the input state's reference frame. Magnitude is the scalar v_\u221e; direction is the incoming asymptote unit vector S^.",
"examples": [
{
"unit": "km/s",
"value": [
1.464,
2.535,
0.0
]
}
]
},
"asymptote_declination": {
"$ref": "#/$defs/Quantity",
"description": "Declination of the incoming-asymptote unit vector S^ above the input frame's XY plane, deg. ICRF/GCRS users get an ecliptic-style declination; body-fixed frame users get the body's equatorial declination.",
"examples": [
{
"unit": "deg",
"value": 0.0
}
]
},
"dv_required": {
"anyOf": [
{
"$ref": "#/$defs/QuantityVector"
},
{
"type": "null"
}
],
"default": null,
"description": "Linearised one-step \u0394v at the input state's epoch that drives (B\u00b7T, B\u00b7R) to the requested targets. None when the tool ran in read-only mode (both target_btr_km and target_btt_km omitted).",
"examples": [
{
"unit": "km/s",
"value": [
0.0,
0.0006,
0.0
]
}
]
},
"residual": {
"anyOf": [
{
"$ref": "#/$defs/BplaneResidual"
},
{
"type": "null"
}
],
"default": null,
"description": "Nonlinear recompute of the B-plane coords after applying dv_required, plus the constraint-space error. None in read-only mode."
}
},
"required": [
"b_r",
"b_t",
"v_infinity",
"asymptote_declination"
],
"title": "BplaneTargetResponse",
"type": "object"
}
frame_transform¶
Transform a Cartesian state vector from its current frame into a different reference frame. e.g. frame_transform(state={r: ..., v: ..., frame: 'TEME', epoch: '2024-01-01T12:00:00Z'}, to_frame='ICRF') re-expresses the state in ICRF. Supported frames: ICRF (barycentric inertial), GCRS (Earth-centred inertial), ITRS (Earth-fixed rotating), TEME (SGP4's native output), CIRS (Earth-rotating intermediate), IAU_EARTH (alias for ITRS — same Earth-body-fixed frame, different naming). The epoch arg defaults to the state's own epoch; pass an override only when you deliberately want to transform 'as if the state were at a different time'. Earth-fixed frames (ITRS / IAU_EARTH) rotate at sidereal rate (~7.292e-5 rad/s); the epoch must match the state's epoch to within a few seconds or the transformed velocity will be off by ~1 km/s — don't pass epoch to override a stale state's own epoch. Epochs are UTC ISO 8601 with a mandatory time component. TIRS is not yet supported as a transform endpoint (use ITRS instead); IAU_MARS and IAU_MOON require SPICE-backed body-fixed rotations not available in this tool.
Input schema:
{
"$defs": {
"Frame": {
"description": "Reference frames used for state vectors and frame conversions.\n\nThe v0.1 set covers the inertial, Earth-rotating, and IAU body-fixed\nframes the wrapped upstreams (sgp4, astropy.coordinates, skyfield) all\nspeak. Adding a new frame here means adding the corresponding transform\npath to the frame_transform tool.",
"enum": [
"TEME",
"ICRF",
"GCRS",
"ITRS",
"CIRS",
"TIRS",
"IAU_EARTH",
"IAU_MARS",
"IAU_MOON"
],
"title": "Frame",
"type": "string"
},
"QuantityVector": {
"additionalProperties": false,
"description": "Pydantic model for a vector ``{value: [...], unit}`` payload.\n\nThe JSON-schema export uses ``type: array, items: {type: number}`` for\nthe ``value`` field \u2014 the unit-discipline meta-test treats this as the\ncanonical vector-quantity shape.",
"properties": {
"value": {
"description": "Numeric values of the quantity vector.",
"items": {
"type": "number"
},
"title": "Value",
"type": "array"
},
"unit": {
"description": "Unit string from the allowed-units registry.",
"title": "Unit",
"type": "string"
}
},
"required": [
"value",
"unit"
],
"title": "QuantityVector",
"type": "object"
},
"StateVector": {
"additionalProperties": false,
"description": "Cartesian state in a named frame at a named epoch.\n\nThe position and velocity are :class:`QuantityVector` instances so the\nunit (km vs m, km/s vs m/s) is on the wire \u2014 never implicit.",
"properties": {
"r": {
"$ref": "#/$defs/QuantityVector",
"description": "Cartesian position vector [x, y, z]. Unit must be a length (km / m / AU).",
"examples": [
{
"unit": "km",
"value": [
7000.0,
0.0,
0.0
]
}
]
},
"v": {
"$ref": "#/$defs/QuantityVector",
"description": "Cartesian velocity vector [vx, vy, vz]. Unit must be a velocity (km/s / m/s).",
"examples": [
{
"unit": "km/s",
"value": [
0.0,
7.5,
0.0
]
}
]
},
"frame": {
"$ref": "#/$defs/Frame",
"description": "Reference frame in which r and v are expressed.",
"examples": [
"TEME",
"ICRF"
]
},
"epoch": {
"description": "UTC ISO 8601 epoch at which the state is valid.",
"examples": [
"2026-05-23T12:00:00Z",
"2026-01-01T00:00:00.500Z"
],
"title": "Epoch",
"type": "string"
}
},
"required": [
"r",
"v",
"frame",
"epoch"
],
"title": "StateVector",
"type": "object"
}
},
"properties": {
"state": {
"$ref": "#/$defs/StateVector",
"description": "Input state vector carrying position (km), velocity (km/s), the source `frame`, and the `epoch` at which it is valid. Supported frames: ICRF, GCRS, ITRS, TEME, CIRS, IAU_EARTH. TIRS, IAU_MARS, and IAU_MOON are deliberately rejected \u2014 astropy does not expose them as transform endpoints at v0.1."
},
"to_frame": {
"$ref": "#/$defs/Frame",
"description": "Target frame for the output state. Same supported-frame list as the input. Identity (to_frame == state.frame) is a valid no-op that still returns a structured response."
},
"epoch": {
"anyOf": [
{
"description": "ISO 8601 UTC timestamp with a mandatory time component. Examples: '2026-05-23T12:00:00Z', '2026-05-23T12:00:00.500+00:00'. A bare date like '2026-05-23' is rejected.",
"examples": [
"2026-05-23T12:00:00Z",
"2026-01-01T00:00:00.500Z"
],
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Optional override for the epoch used in the transform, UTC ISO 8601 with a mandatory time component. When omitted, the tool uses `state.epoch`. Override is useful when re-evaluating an Earth-rotating-frame state at a different time than the state's native validity epoch.",
"title": "Epoch"
}
},
"required": [
"state",
"to_frame"
],
"title": "frame_transformArguments",
"type": "object"
}
Output schema:
{
"$defs": {
"Frame": {
"description": "Reference frames used for state vectors and frame conversions.\n\nThe v0.1 set covers the inertial, Earth-rotating, and IAU body-fixed\nframes the wrapped upstreams (sgp4, astropy.coordinates, skyfield) all\nspeak. Adding a new frame here means adding the corresponding transform\npath to the frame_transform tool.",
"enum": [
"TEME",
"ICRF",
"GCRS",
"ITRS",
"CIRS",
"TIRS",
"IAU_EARTH",
"IAU_MARS",
"IAU_MOON"
],
"title": "Frame",
"type": "string"
},
"QuantityVector": {
"additionalProperties": false,
"description": "Pydantic model for a vector ``{value: [...], unit}`` payload.\n\nThe JSON-schema export uses ``type: array, items: {type: number}`` for\nthe ``value`` field \u2014 the unit-discipline meta-test treats this as the\ncanonical vector-quantity shape.",
"properties": {
"value": {
"description": "Numeric values of the quantity vector.",
"items": {
"type": "number"
},
"title": "Value",
"type": "array"
},
"unit": {
"description": "Unit string from the allowed-units registry.",
"title": "Unit",
"type": "string"
}
},
"required": [
"value",
"unit"
],
"title": "QuantityVector",
"type": "object"
},
"StateVector": {
"additionalProperties": false,
"description": "Cartesian state in a named frame at a named epoch.\n\nThe position and velocity are :class:`QuantityVector` instances so the\nunit (km vs m, km/s vs m/s) is on the wire \u2014 never implicit.",
"properties": {
"r": {
"$ref": "#/$defs/QuantityVector",
"description": "Cartesian position vector [x, y, z]. Unit must be a length (km / m / AU).",
"examples": [
{
"unit": "km",
"value": [
7000.0,
0.0,
0.0
]
}
]
},
"v": {
"$ref": "#/$defs/QuantityVector",
"description": "Cartesian velocity vector [vx, vy, vz]. Unit must be a velocity (km/s / m/s).",
"examples": [
{
"unit": "km/s",
"value": [
0.0,
7.5,
0.0
]
}
]
},
"frame": {
"$ref": "#/$defs/Frame",
"description": "Reference frame in which r and v are expressed.",
"examples": [
"TEME",
"ICRF"
]
},
"epoch": {
"description": "UTC ISO 8601 epoch at which the state is valid.",
"examples": [
"2026-05-23T12:00:00Z",
"2026-01-01T00:00:00.500Z"
],
"title": "Epoch",
"type": "string"
}
},
"required": [
"r",
"v",
"frame",
"epoch"
],
"title": "StateVector",
"type": "object"
}
},
"additionalProperties": false,
"description": "Transformed state plus an IERS freshness anchor when EOP data was used.",
"properties": {
"state": {
"$ref": "#/$defs/StateVector",
"description": "Transformed state vector in the requested `to_frame`."
},
"iers_bulletin_a_fetched_at": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "IERS Bulletin A freshness anchor (ISO 8601 UTC). Non-null whenever the transform path touched an Earth-rotating frame (ITRS, GCRS, CIRS, IAU_EARTH), which depend on Earth-orientation parameters.",
"title": "Iers Bulletin A Fetched At"
}
},
"required": [
"state"
],
"title": "FrameTransformResponse",
"type": "object"
}
lambert_solve¶
Solve Lambert's problem — find the orbital transfer between two position vectors r1 and r2 in a given time-of-flight tof. e.g. lambert_solve(r1=[5000, 10000, 2100], r2=[-14600, 2500, 7000], tof=3600, mu='earth') returns the initial and final inertial velocities on the transfer arc plus the arc's classical orbital elements. r1 and r2 are heliocentric km when mu='sun'; geocentric km when mu='earth'; planetocentric km for other named bodies. Mixing frames will silently produce garbage — if your problem is interplanetary, use mu='sun' and pull body positions via the porkchop tool. Algorithm: izzo is fast and robust for the common case; switch to gooding if izzo fails on near-degenerate geometries. For revs > 0 the response enumerates the multi-rev solutions in all_solutions (low + high path per rev count). Supply depart_velocity AND arrive_velocity together to get the two-impulse Δv. Degenerate geometries (r1 == r2, infeasible tof, no convergence) surface as upstream.lambert_no_solution.
Input schema:
{
"properties": {
"r1": {
"description": "Departure position vector (km), 3-component [x, y, z] in the inertial frame of the central body identified by `mu`.",
"items": {
"type": "number"
},
"title": "R1",
"type": "array"
},
"r2": {
"description": "Arrival position vector (km), same frame and units as `r1`. The solver finds the transfer arc connecting `r1` to `r2` in `tof` seconds.",
"items": {
"type": "number"
},
"title": "R2",
"type": "array"
},
"tof": {
"description": "Time of flight from `r1` to `r2`, in seconds. Must be strictly positive and finite.",
"title": "Tof",
"type": "number"
},
"mu": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
}
],
"default": "earth",
"description": "Gravitational parameter of the central body. Pass a body name ('sun', 'mercury', 'venus', 'earth', 'moon', 'mars', 'jupiter', 'saturn', 'uranus', 'neptune') to use the JPL-published \u03bc, or a raw number in km\u00b3/s\u00b2 for a custom value.",
"title": "Mu"
},
"direction": {
"default": "prograde",
"description": "Sense of motion along the transfer arc. 'prograde' is the common case (eastward in Earth orbit, counter-clockwise viewed from the +Z pole); 'retrograde' flips the direction.",
"enum": [
"prograde",
"retrograde"
],
"title": "Direction",
"type": "string"
},
"revs": {
"default": 0,
"description": "Maximum number of complete revolutions to enumerate. 0 (default) returns only the zero-rev / direct-transfer solution. For revs \u2265 1 both low_path branches are enumerated; the primary v1/v2 echo the (revs, low_path=True) solution, all_solutions lists every feasible alternative.",
"title": "Revs",
"type": "integer"
},
"algorithm": {
"default": "izzo",
"description": "Lambert solver. 'izzo' (default) has the broadest convergence basin; 'izzo_revisited', 'gooding', and 'battin' are alternative implementations from `lamberthub` exposed for cross-validation.",
"enum": [
"izzo",
"izzo_revisited",
"gooding",
"battin"
],
"title": "Algorithm",
"type": "string"
},
"depart_velocity": {
"anyOf": [
{
"items": {
"type": "number"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Optional departure-state velocity (km/s), 3-component, in the same frame as `r1`. When supplied alongside `arrive_velocity`, the tool also returns the two-impulse \u0394v |v1 - depart_velocity| + |v2 - arrive_velocity|. Pass both or neither.",
"title": "Depart Velocity"
},
"arrive_velocity": {
"anyOf": [
{
"items": {
"type": "number"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Optional arrival-state velocity (km/s), 3-component, in the same frame as `r2`. Required together with `depart_velocity` to compute the two-impulse \u0394v; omit both to skip \u0394v computation.",
"title": "Arrive Velocity"
}
},
"required": [
"r1",
"r2",
"tof"
],
"title": "lambert_solveArguments",
"type": "object"
}
Output schema:
{
"$defs": {
"KeplerianElements": {
"additionalProperties": false,
"description": "Classical Keplerian orbital elements (a, e, i, RAAN, argp, nu).\n\nShared across any tool that emits an orbit's elements \u2014 Lambert solve\n(transfer arc), porkchop (transfer-elements grid), bplane (hyperbolic\napproach orbit). True anomaly ``nu`` is at the reference epoch (e.g.\nthe start of the Lambert transfer for ``lambert_solve``).\n\nSemi-major axis ``a`` is negative for hyperbolic orbits and undefined\nfor parabolic ones \u2014 callers that need to handle the parabolic edge\nshould branch on eccentricity rather than ``a``.",
"properties": {
"a": {
"$ref": "#/$defs/Quantity",
"description": "Semi-major axis. Length unit (km / m / AU).",
"examples": [
{
"unit": "km",
"value": 24371.0
}
]
},
"e": {
"$ref": "#/$defs/Quantity",
"description": "Eccentricity (dimensionless; unit must be the dimensionless '1').",
"examples": [
{
"unit": "1",
"value": 0.7
}
]
},
"i": {
"$ref": "#/$defs/Quantity",
"description": "Inclination, deg.",
"examples": [
{
"unit": "deg",
"value": 28.5
}
]
},
"raan": {
"$ref": "#/$defs/Quantity",
"description": "Right ascension of the ascending node, deg.",
"examples": [
{
"unit": "deg",
"value": 45.0
}
]
},
"argp": {
"$ref": "#/$defs/Quantity",
"description": "Argument of periapsis, deg.",
"examples": [
{
"unit": "deg",
"value": 90.0
}
]
},
"nu": {
"$ref": "#/$defs/Quantity",
"description": "True anomaly at the reference epoch, deg.",
"examples": [
{
"unit": "deg",
"value": 30.0
}
]
}
},
"required": [
"a",
"e",
"i",
"raan",
"argp",
"nu"
],
"title": "KeplerianElements",
"type": "object"
},
"LambertSolution": {
"additionalProperties": false,
"description": "One Lambert solution row inside :class:`LambertSolveResponse.all_solutions`.",
"properties": {
"v1": {
"$ref": "#/$defs/QuantityVector",
"description": "Initial velocity vector on the transfer arc (km/s)."
},
"v2": {
"$ref": "#/$defs/QuantityVector",
"description": "Final velocity vector on the transfer arc (km/s)."
},
"transfer_elements": {
"$ref": "#/$defs/KeplerianElements",
"description": "Classical orbital elements of the transfer arc."
},
"revs": {
"$ref": "#/$defs/Quantity",
"description": "Revolution count M, dimensionless. e.g. {value: 2, unit: '1'}.",
"examples": [
{
"unit": "1",
"value": 0
},
{
"unit": "1",
"value": 2
}
]
},
"low_path": {
"description": "Which of the two multi-rev branches this solution is. True for the low-path branch (always True for the degenerate M=0 case).",
"title": "Low Path",
"type": "boolean"
}
},
"required": [
"v1",
"v2",
"transfer_elements",
"revs",
"low_path"
],
"title": "LambertSolution",
"type": "object"
},
"Quantity": {
"additionalProperties": false,
"description": "Pydantic model for a scalar ``{value, unit}`` payload.\n\nUsed as the field type in every tool's pydantic output schema where the\nfield carries a scalar physical quantity. The JSON-schema export is what\nthe unit-discipline meta-test recognises as \"correctly wrapped\".",
"properties": {
"value": {
"description": "Numeric value of the quantity.",
"title": "Value",
"type": "number"
},
"unit": {
"description": "Unit string from the allowed-units registry.",
"title": "Unit",
"type": "string"
}
},
"required": [
"value",
"unit"
],
"title": "Quantity",
"type": "object"
},
"QuantityVector": {
"additionalProperties": false,
"description": "Pydantic model for a vector ``{value: [...], unit}`` payload.\n\nThe JSON-schema export uses ``type: array, items: {type: number}`` for\nthe ``value`` field \u2014 the unit-discipline meta-test treats this as the\ncanonical vector-quantity shape.",
"properties": {
"value": {
"description": "Numeric values of the quantity vector.",
"items": {
"type": "number"
},
"title": "Value",
"type": "array"
},
"unit": {
"description": "Unit string from the allowed-units registry.",
"title": "Unit",
"type": "string"
}
},
"required": [
"value",
"unit"
],
"title": "QuantityVector",
"type": "object"
}
},
"additionalProperties": false,
"description": "Response from :func:`lambert_solve`.\n\nTop-level ``v1`` / ``v2`` / ``transfer_elements`` echo the user-requested\n``(revs, low_path=True)`` solution for ergonomics; ``all_solutions``\nlists every feasible (M, low_path) pair from M=0 up to the requested\nrevolution count.",
"properties": {
"v1": {
"$ref": "#/$defs/QuantityVector",
"description": "Initial velocity for the primary solution (km/s)."
},
"v2": {
"$ref": "#/$defs/QuantityVector",
"description": "Final velocity for the primary solution (km/s)."
},
"transfer_elements": {
"$ref": "#/$defs/KeplerianElements",
"description": "Orbital elements of the primary solution's transfer arc."
},
"dv": {
"anyOf": [
{
"$ref": "#/$defs/Quantity"
},
{
"type": "null"
}
],
"default": null,
"description": "Two-impulse \u0394v (km/s) when both `depart_velocity` and `arrive_velocity` were supplied. None otherwise."
},
"all_solutions": {
"description": "Every feasible (M, low_path) Lambert solution from M=0 to the requested revolution count. Infeasible alternatives are skipped, not surfaced as errors.",
"items": {
"$ref": "#/$defs/LambertSolution"
},
"title": "All Solutions",
"type": "array"
}
},
"required": [
"v1",
"v2",
"transfer_elements",
"all_solutions"
],
"title": "LambertSolveResponse",
"type": "object"
}
porkchop¶
Generate an interplanetary porkchop scan — the (depart_epoch x arrive_epoch) grid of C3, arrival V-infinity, declination of the departure asymptote, and total-Δv proxy for a heliocentric transfer. e.g. porkchop(departure_body='earth', arrival_body='mars', depart_window=['2026-10-01T00:00:00Z','2026-12-01T00:00:00Z'], arrive_window=['2027-04-01T00:00:00Z','2027-10-01T00:00:00Z'], samples_per_axis=20) returns the minimum-total_dv 'best' cell, the five lowest-total_dv cells, and a compact ASCII contour for inline LLM display. Both windows are UTC ISO 8601 pairs [start, end]; the grid is samples_per_axis x samples_per_axis linspace-sampled across each window. Output shaping: the default output='summary' trims the response to best/top_cells/ascii_summary so it fits small-model context windows; pass output='full' to receive every feasible cell in grid (a 30x30 scan is ~250 KB and will overflow tight input caps). Body ephemerides come from JPL Horizons — the first call after a cold cache takes minutes (Horizons is slow), subsequent calls within the 7-day TTL are local. For broad exploratory scans, bring samples_per_axis down to 15-20 before launching a 50x50 grid. mu='sun' is the only v0.1 mu — heliocentric Lambert in ICRF ecliptic km / km/s. Misordered windows (arrive entirely before depart) raise invalid_input.porkchop_window_order. Horizons unreachable mid-grid raises data_source.horizons_unreachable with no partial results.
Input schema:
{
"properties": {
"departure_body": {
"description": "Body the spacecraft departs from. One of the JPL Horizons major-body names: 'mercury', 'venus', 'earth', 'mars', 'jupiter', 'saturn', 'uranus', 'neptune'. Must differ from `arrival_body`.",
"title": "Departure Body",
"type": "string"
},
"arrival_body": {
"description": "Body the spacecraft arrives at. Same body-name enum as `departure_body` and must be different from it.",
"title": "Arrival Body",
"type": "string"
},
"depart_window": {
"description": "Departure-epoch range as a two-element [start, end] list of UTC ISO 8601 strings, e.g. ['2028-03-01T00:00:00Z', '2028-06-01T00:00:00Z']. The axis is sampled uniformly across this range.",
"items": {
"type": "string"
},
"title": "Depart Window",
"type": "array"
},
"arrive_window": {
"description": "Arrival-epoch range as a two-element [start, end] list of UTC ISO 8601 strings, sampled uniformly along the other grid axis. Must end strictly after `depart_window` starts so at least one positive-time-of-flight cell exists.",
"items": {
"type": "string"
},
"title": "Arrive Window",
"type": "array"
},
"mu": {
"const": "sun",
"default": "sun",
"description": "Central-body gravitational parameter for the heliocentric Lambert solve. Only 'sun' is supported at v0.1; barycentric \u03bc is used.",
"title": "Mu",
"type": "string"
},
"samples_per_axis": {
"default": 30,
"description": "Grid resolution per axis \u2014 the full grid has samples_per_axis\u00b2 cells. Default 30 gives a 900-cell grid; higher resolves fine structure but costs more Horizons calls and Lambert solves. Capped at the upper end to keep the response size sane.",
"title": "Samples Per Axis",
"type": "integer"
},
"output": {
"default": "summary",
"description": "'summary' (default) returns the minimum-\u0394v 'best' cell, the ASCII C3 contour, and grid metadata only \u2014 the MCP-payload-friendly form. 'full' adds the per-cell grid; only request this when downstream really needs every cell.",
"enum": [
"summary",
"full"
],
"title": "Output",
"type": "string"
}
},
"required": [
"departure_body",
"arrival_body",
"depart_window",
"arrive_window"
],
"title": "porkchopArguments",
"type": "object"
}
Output schema:
{
"$defs": {
"PorkchopCell": {
"additionalProperties": false,
"description": "One (depart_epoch, arrive_epoch) cell of the porkchop grid.",
"properties": {
"depart_epoch": {
"description": "UTC ISO 8601 departure epoch for this cell.",
"examples": [
"2026-05-23T12:00:00Z",
"2026-01-01T00:00:00.500Z"
],
"title": "Depart Epoch",
"type": "string"
},
"arrive_epoch": {
"description": "UTC ISO 8601 arrival epoch for this cell.",
"examples": [
"2026-05-23T12:00:00Z",
"2026-01-01T00:00:00.500Z"
],
"title": "Arrive Epoch",
"type": "string"
},
"tof": {
"$ref": "#/$defs/Quantity",
"description": "Time of flight, days.",
"examples": [
{
"unit": "days",
"value": 180.0
}
]
},
"c3": {
"$ref": "#/$defs/Quantity",
"description": "Departure characteristic energy, km^2/s^2. C3 = |v_inf_dep|^2; the launch vehicle's required excess energy above the departure body's escape velocity.",
"examples": [
{
"unit": "km^2/s^2",
"value": 12.5
}
]
},
"v_inf_arrival": {
"$ref": "#/$defs/Quantity",
"description": "Magnitude of arrival V-infinity (hyperbolic excess speed at arrival), km/s.",
"examples": [
{
"unit": "km/s",
"value": 3.0
}
]
},
"dec_dep_asymptote": {
"$ref": "#/$defs/Quantity",
"description": "Declination of the departure-asymptote unit vector in the ICRF ecliptic frame, deg.",
"examples": [
{
"unit": "deg",
"value": -12.0
}
]
},
"total_dv": {
"$ref": "#/$defs/Quantity",
"description": "Two-impulse \u0394v proxy = |v_inf_dep| + |v_inf_arr|, km/s. Both legs treated as hyperbolic injections; mission-specific maneuver design lives downstream.",
"examples": [
{
"unit": "km/s",
"value": 6.4
}
]
}
},
"required": [
"depart_epoch",
"arrive_epoch",
"tof",
"c3",
"v_inf_arrival",
"dec_dep_asymptote",
"total_dv"
],
"title": "PorkchopCell",
"type": "object"
},
"Quantity": {
"additionalProperties": false,
"description": "Pydantic model for a scalar ``{value, unit}`` payload.\n\nUsed as the field type in every tool's pydantic output schema where the\nfield carries a scalar physical quantity. The JSON-schema export is what\nthe unit-discipline meta-test recognises as \"correctly wrapped\".",
"properties": {
"value": {
"description": "Numeric value of the quantity.",
"title": "Value",
"type": "number"
},
"unit": {
"description": "Unit string from the allowed-units registry.",
"title": "Unit",
"type": "string"
}
},
"required": [
"value",
"unit"
],
"title": "Quantity",
"type": "object"
}
},
"additionalProperties": false,
"description": "Response from :func:`porkchop`.\n\nThe shape is the same regardless of ``output`` \u2014 the parameter only\nselects how much of the grid travels back to the caller:\n\n- ``output=\"summary\"`` (default): ``grid`` is empty; ``top_cells``\n carries up to five lowest-``total_dv`` cells so the response fits\n small-model input caps.\n- ``output=\"full\"``: ``grid`` carries every feasible cell in row-major\n order (outer: arrive-epoch, inner: depart-epoch); ``top_cells`` is\n still populated for the canonical \"show me the alternatives\" case.\n\nInfeasible cells (``tof <= 0``, Lambert no-solution) are skipped\nsilently rather than carried as NaN \u2014 the ASCII summary marks them\nwith a space glyph for the visual.",
"properties": {
"best": {
"$ref": "#/$defs/PorkchopCell",
"description": "The feasible cell with the minimum total_dv across the scan."
},
"top_cells": {
"description": "Up to five lowest-total_dv cells, sorted ascending. Always includes `best` as the first entry; size is capped to keep the default response under small-model input limits.",
"items": {
"$ref": "#/$defs/PorkchopCell"
},
"title": "Top Cells",
"type": "array"
},
"grid": {
"description": "Every feasible cell in row-major order (outer: arrive-epoch, inner: depart-epoch). Populated only when the caller passes output='full'; empty otherwise.",
"items": {
"$ref": "#/$defs/PorkchopCell"
},
"title": "Grid",
"type": "array"
},
"ascii_summary": {
"description": "Compact text contour of C3 over the grid. samples_per_axis rows x samples_per_axis columns; each glyph is one of `.:-+*#@X` binned by C3 decile, infeasible cells rendered as a space. Rows are arrive-epoch indices (top = earliest), columns depart-epoch indices (left = earliest).",
"title": "Ascii Summary",
"type": "string"
}
},
"required": [
"best",
"top_cells",
"ascii_summary"
],
"title": "PorkchopResponse",
"type": "object"
}
sgp4_propagate¶
Propagate a TLE forward in time via SGP4/SDP4, returning Cartesian state vectors at the requested epochs. TLE input is either two raw 69-char lines ({line1, line2}) or a parsed OMM JSON object ({omm: {...}}). e.g. sgp4_propagate(tle={line1: ..., line2: ...}, epochs=['2026-05-23T12:00:00Z'], frame='TEME') returns one state vector in TEME. Epochs are UTC ISO 8601 with a mandatory time component (e.g. '2026-05-23T12:00:00Z') — a bare date is rejected. Default frame is TEME (SGP4's native output); for ICRF, GCRS, ITRS, or CIRS the tool transforms via astropy with per-epoch obstime. A list of one epoch is fine; 1000+ epochs is also fine — propagation cost scales linearly. Output shaping: the default output='summary' returns at most twelve evenly-spaced states (first and last epoch always included) so the response fits small-model input caps; pass output='full' to receive one state per epoch when you need the dense series. SGP4 propagation failures (decayed satellite, deep-space epoch beyond SDP4 validity) surface as upstream.sgp4_failure.
Input schema:
{
"$defs": {
"Frame": {
"description": "Reference frames used for state vectors and frame conversions.\n\nThe v0.1 set covers the inertial, Earth-rotating, and IAU body-fixed\nframes the wrapped upstreams (sgp4, astropy.coordinates, skyfield) all\nspeak. Adding a new frame here means adding the corresponding transform\npath to the frame_transform tool.",
"enum": [
"TEME",
"ICRF",
"GCRS",
"ITRS",
"CIRS",
"TIRS",
"IAU_EARTH",
"IAU_MARS",
"IAU_MOON"
],
"title": "Frame",
"type": "string"
},
"TleLines": {
"additionalProperties": false,
"description": "Two-line TLE as raw strings.\n\nBoth lines must be exactly 69 characters (the canonical TLE width).\nSub-69-character lines are the most common LLM mistake \u2014 they almost\nalways come from a chat client stripping trailing whitespace.",
"properties": {
"line1": {
"description": "First TLE line (begins with '1 '), exactly 69 characters.",
"examples": [
"1 25544U 98067A 24001.50000000 .00010000 00000-0 18000-3 0 9990"
],
"title": "Line1",
"type": "string"
},
"line2": {
"description": "Second TLE line (begins with '2 '), exactly 69 characters.",
"examples": [
"2 25544 51.6400 90.0000 0001000 90.0000 270.0000 15.50000000000000"
],
"title": "Line2",
"type": "string"
}
},
"required": [
"line1",
"line2"
],
"title": "TleLines",
"type": "object"
},
"TleOmm": {
"additionalProperties": false,
"description": "Parsed OMM (Orbit Mean Elements Message) JSON.\n\nSchema is intentionally loose at v0.1: we accept whatever the upstream\nOMM source (CelesTrak) emitted. Downstream tools that need specific\nfields raise typed errors when those fields are missing.",
"properties": {
"omm": {
"additionalProperties": true,
"description": "Parsed OMM JSON object (CCSDS standard fields like CCSDS_OMM_VERS, EPOCH, MEAN_ELEMENTS, \u2026). Fetched from CelesTrak by the tle_lookup tool or supplied directly.",
"examples": [
{
"CCSDS_OMM_VERS": "2.0",
"EPOCH": "2024-01-01T12:00:00.000000",
"MEAN_MOTION": 15.5
}
],
"title": "Omm",
"type": "object"
}
},
"required": [
"omm"
],
"title": "TleOmm",
"type": "object"
}
},
"properties": {
"tle": {
"anyOf": [
{
"$ref": "#/$defs/TleLines"
},
{
"$ref": "#/$defs/TleOmm"
}
],
"description": "The orbit to propagate, supplied as either a TLE line pair ({line1, line2} \u2014 each line exactly 69 chars per the TLE spec) or as an OMM payload (the CCSDS-standard JSON returned by tle_lookup). Either is accepted; the OMM form is preferred when passing programmatically since it does not depend on TLE column alignment.",
"title": "Tle"
},
"epochs": {
"description": "UTC ISO 8601 epochs at which to evaluate the propagated state. Each entry must include a time component (e.g. '2024-01-01T00:00:00Z'); date-only is rejected. The list defines both the evaluation grid and the output ordering.",
"items": {
"description": "ISO 8601 UTC timestamp with a mandatory time component. Examples: '2026-05-23T12:00:00Z', '2026-05-23T12:00:00.500+00:00'. A bare date like '2026-05-23' is rejected.",
"examples": [
"2026-05-23T12:00:00Z",
"2026-01-01T00:00:00.500Z"
],
"type": "string"
},
"title": "Epochs",
"type": "array"
},
"frame": {
"$ref": "#/$defs/Frame",
"default": "TEME",
"description": "Output frame. Supported here: TEME (SGP4-native, fastest), ICRF / GCRS (inertial, distinct origins), ITRS (Earth-fixed), CIRS (intermediate). For TIRS or IAU body-fixed frames use the frame_transform tool against a TEME state."
},
"output": {
"default": "summary",
"description": "'summary' (default) caps the response at a small number of evenly-spaced states across the epoch list, keeping the MCP payload tight. 'full' returns one state per input epoch \u2014 request only when downstream needs the full grid.",
"enum": [
"summary",
"full"
],
"title": "Output",
"type": "string"
}
},
"required": [
"tle",
"epochs"
],
"title": "sgp4_propagateArguments",
"type": "object"
}
Output schema:
{
"$defs": {
"Frame": {
"description": "Reference frames used for state vectors and frame conversions.\n\nThe v0.1 set covers the inertial, Earth-rotating, and IAU body-fixed\nframes the wrapped upstreams (sgp4, astropy.coordinates, skyfield) all\nspeak. Adding a new frame here means adding the corresponding transform\npath to the frame_transform tool.",
"enum": [
"TEME",
"ICRF",
"GCRS",
"ITRS",
"CIRS",
"TIRS",
"IAU_EARTH",
"IAU_MARS",
"IAU_MOON"
],
"title": "Frame",
"type": "string"
},
"QuantityVector": {
"additionalProperties": false,
"description": "Pydantic model for a vector ``{value: [...], unit}`` payload.\n\nThe JSON-schema export uses ``type: array, items: {type: number}`` for\nthe ``value`` field \u2014 the unit-discipline meta-test treats this as the\ncanonical vector-quantity shape.",
"properties": {
"value": {
"description": "Numeric values of the quantity vector.",
"items": {
"type": "number"
},
"title": "Value",
"type": "array"
},
"unit": {
"description": "Unit string from the allowed-units registry.",
"title": "Unit",
"type": "string"
}
},
"required": [
"value",
"unit"
],
"title": "QuantityVector",
"type": "object"
},
"StateVector": {
"additionalProperties": false,
"description": "Cartesian state in a named frame at a named epoch.\n\nThe position and velocity are :class:`QuantityVector` instances so the\nunit (km vs m, km/s vs m/s) is on the wire \u2014 never implicit.",
"properties": {
"r": {
"$ref": "#/$defs/QuantityVector",
"description": "Cartesian position vector [x, y, z]. Unit must be a length (km / m / AU).",
"examples": [
{
"unit": "km",
"value": [
7000.0,
0.0,
0.0
]
}
]
},
"v": {
"$ref": "#/$defs/QuantityVector",
"description": "Cartesian velocity vector [vx, vy, vz]. Unit must be a velocity (km/s / m/s).",
"examples": [
{
"unit": "km/s",
"value": [
0.0,
7.5,
0.0
]
}
]
},
"frame": {
"$ref": "#/$defs/Frame",
"description": "Reference frame in which r and v are expressed.",
"examples": [
"TEME",
"ICRF"
]
},
"epoch": {
"description": "UTC ISO 8601 epoch at which the state is valid.",
"examples": [
"2026-05-23T12:00:00Z",
"2026-01-01T00:00:00.500Z"
],
"title": "Epoch",
"type": "string"
}
},
"required": [
"r",
"v",
"frame",
"epoch"
],
"title": "StateVector",
"type": "object"
}
},
"additionalProperties": false,
"description": "A list of propagated state vectors.\n\nIn the default ``output=\"summary\"`` mode the list is capped to\n:data:`_SUMMARY_STATE_CAP` evenly-spaced entries (always including\nthe first and last requested epoch). Pass ``output=\"full\"`` to receive\none state per input epoch.",
"properties": {
"states": {
"description": "Propagated state vectors in the requested frame. In output='full' there is one entry per input epoch; in the default output='summary' the list is subsampled to at most twelve evenly-spaced entries (first and last always retained). Position in km, velocity in km/s, units explicit on every numeric field.",
"items": {
"$ref": "#/$defs/StateVector"
},
"title": "States",
"type": "array"
}
},
"required": [
"states"
],
"title": "Sgp4PropagateResponse",
"type": "object"
}
time_convert¶
Convert a time value between scales (UTC / TAI / TT / TDB / UT1 / GPS / TCB / TCG) and formats (iso / jd / mjd / j2000_seconds / unix). e.g. time_convert(value='2026-05-23T12:00:00', from_scale='UTC', to_scale='TAI') returns '2026-05-23T12:00:37' (the current leap-second-aware offset). UTC -> TAI is exactly TAI - UTC = 37 s as of 2026 but historical conversions need leap-second-aware machinery — don't subtract 37 yourself; the tool does it right. GPS -> UTC respects the GPS-UTC offset (currently 18 s). UTC -> UT1 sources the small (<1 s) offset from IERS Bulletin A — the response includes ut1_utc_seconds and an iers_fetched_at anchor (ISO 8601). Format hint: iso is the safest interchange; jd is the Julian Date as a float (limited precision near present-day dates); mjd is Modified Julian Date; j2000_seconds is seconds since J2000 (2000-01-01T12:00:00 TT); unix is seconds since 1970-01-01T00:00:00 UTC.
Input schema:
{
"$defs": {
"TimeScale": {
"description": "Time-scale identifiers used across the time/frame/access tool surfaces.\n\nInherits from ``str`` so pydantic round-trips the enum as its string\nvalue in JSON (``\"UTC\"`` rather than ``\"TimeScale.UTC\"``).",
"enum": [
"UTC",
"TAI",
"TT",
"TDB",
"UT1",
"GPS",
"TCB",
"TCG"
],
"title": "TimeScale",
"type": "string"
}
},
"properties": {
"value": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
}
],
"description": "The epoch to convert. String for ISO 8601 inputs ('2024-01-01T00:00:00Z'); number for numeric formats (JD, MJD, J2000-seconds, Unix). The exact interpretation is set by `in_format`.",
"title": "Value"
},
"from_scale": {
"$ref": "#/$defs/TimeScale",
"description": "Time scale of the input value: UTC, TAI, TT, TDB, UT1, GPS, TCB, or TCG. Conversion to/from UT1 triggers an IERS Bulletin A lookup; other scales are deterministic."
},
"to_scale": {
"$ref": "#/$defs/TimeScale",
"description": "Output time scale. Same enum as `from_scale`. Identity (from==to) is a valid no-op that still returns a structured response."
},
"in_format": {
"default": "iso",
"description": "How to parse `value`: 'iso' (default; ISO 8601 string with a mandatory time component), 'jd' / 'mjd' (Julian / Modified Julian Date as a number), 'j2000_seconds' (seconds since 2000-01-01T12:00:00 TT), or 'unix' (POSIX seconds).",
"enum": [
"iso",
"jd",
"mjd",
"j2000_seconds",
"unix"
],
"title": "In Format",
"type": "string"
},
"out_format": {
"default": "iso",
"description": "How to render the converted output. Same enum as `in_format`. Pick a numeric format when downstream wants math; pick 'iso' when downstream wants human-readable strings.",
"enum": [
"iso",
"jd",
"mjd",
"j2000_seconds",
"unix"
],
"title": "Out Format",
"type": "string"
}
},
"required": [
"value",
"from_scale",
"to_scale"
],
"title": "time_convertArguments",
"type": "object"
}
Output schema:
{
"$defs": {
"Quantity": {
"additionalProperties": false,
"description": "Pydantic model for a scalar ``{value, unit}`` payload.\n\nUsed as the field type in every tool's pydantic output schema where the\nfield carries a scalar physical quantity. The JSON-schema export is what\nthe unit-discipline meta-test recognises as \"correctly wrapped\".",
"properties": {
"value": {
"description": "Numeric value of the quantity.",
"title": "Value",
"type": "number"
},
"unit": {
"description": "Unit string from the allowed-units registry.",
"title": "Unit",
"type": "string"
}
},
"required": [
"value",
"unit"
],
"title": "Quantity",
"type": "object"
},
"TimeScale": {
"description": "Time-scale identifiers used across the time/frame/access tool surfaces.\n\nInherits from ``str`` so pydantic round-trips the enum as its string\nvalue in JSON (``\"UTC\"`` rather than ``\"TimeScale.UTC\"``).",
"enum": [
"UTC",
"TAI",
"TT",
"TDB",
"UT1",
"GPS",
"TCB",
"TCG"
],
"title": "TimeScale",
"type": "string"
}
},
"additionalProperties": false,
"description": "Converted time value plus UT1/IERS metadata when relevant.",
"properties": {
"value": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
}
],
"description": "Converted time value in the requested out_format and to_scale. A string for `iso`; a float for `jd`, `mjd`, `j2000_seconds`, `unix`.",
"title": "Value"
},
"scale": {
"$ref": "#/$defs/TimeScale",
"description": "The output time scale."
},
"format": {
"description": "The output format.",
"enum": [
"iso",
"jd",
"mjd",
"j2000_seconds",
"unix"
],
"title": "Format",
"type": "string"
},
"ut1_utc_seconds": {
"anyOf": [
{
"$ref": "#/$defs/Quantity"
},
{
"type": "null"
}
],
"default": null,
"description": "UT1-UTC offset used by the conversion when `from_scale` or `to_scale` is UT1 (s). None for conversions that do not touch UT1."
},
"iers_fetched_at": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "IERS Bulletin A freshness anchor (ISO 8601 UTC). Non-null when the conversion path touched UT1 and consulted IERS.",
"title": "Iers Fetched At"
}
},
"required": [
"value",
"scale",
"format"
],
"title": "TimeConvertResponse",
"type": "object"
}
tle_lookup¶
Fetch current two-line element sets (TLEs) from CelesTrak by NORAD catalogue ID, satellite name, or a CelesTrak group/category name. Returns parsed OMM JSON plus the raw two-line strings. e.g. tle_lookup('25544') for the ISS, tle_lookup('HUBBLE') for the Hubble Space Telescope, or tle_lookup('weather') for the multi-satellite weather category. Names are case-insensitive but CelesTrak prefers the exact catalog spelling — 'ISS (ZARYA)', not 'iss'. If a name lookup returns no results, fall back to the NORAD ID. Supported group keywords: 'active', 'stations', 'weather', 'visual', 'science', 'geo', 'gnss', 'military', 'last-30-days', 'starlink', 'oneweb'.
Input schema:
{
"properties": {
"query": {
"description": "What to look up: a NORAD catalogue ID like '25544', a satellite name (case-insensitive, prefers exact catalog spelling like 'ISS (ZARYA)' or 'HUBBLE'), or a CelesTrak group keyword ('active', 'stations', 'weather', 'visual', 'science', 'geo', 'gnss', 'military', 'last-30-days', 'starlink', 'oneweb'). Name lookups returning zero results should fall back to the NORAD ID.",
"title": "Query",
"type": "string"
},
"source": {
"const": "celestrak",
"default": "celestrak",
"description": "Upstream catalogue to query. Only 'celestrak' is supported at v0.1; Space-Track lands in v0.2.",
"title": "Source",
"type": "string"
}
},
"required": [
"query"
],
"title": "tle_lookupArguments",
"type": "object"
}
Output schema:
{
"$defs": {
"TleResult": {
"additionalProperties": false,
"description": "A single TLE-lookup result.\n\nCarries the satellite name, NORAD catalogue ID (as a string \u2014 NORAD IDs\nare categorical identifiers, not measurements), the raw two-line element\nstrings, the parsed OMM JSON, and per-result freshness flags propagated\nfrom the upstream-data adapter.",
"properties": {
"name": {
"description": "Satellite name as carried in OMM.OBJECT_NAME, e.g. 'ISS (ZARYA)'.",
"examples": [
"ISS (ZARYA)",
"HST"
],
"title": "Name",
"type": "string"
},
"norad_id": {
"description": "NORAD catalogue ID as a string. Stable per-satellite identifier; leading zeros preserved. e.g. '25544' for the ISS.",
"examples": [
"25544",
"20580"
],
"title": "Norad Id",
"type": "string"
},
"tle_line1": {
"description": "First TLE line, exactly 69 characters (starts with '1 ').",
"examples": [
"1 25544U 98067A 24001.50000000 .00010000 00000-0 18000-3 0 9990"
],
"title": "Tle Line1",
"type": "string"
},
"tle_line2": {
"description": "Second TLE line, exactly 69 characters (starts with '2 ').",
"examples": [
"2 25544 51.6400 90.0000 0001000 90.0000 270.0000 15.50000000000000"
],
"title": "Tle Line2",
"type": "string"
},
"omm": {
"additionalProperties": true,
"description": "Parsed OMM JSON (CCSDS standard fields like CCSDS_OMM_VERS, EPOCH, MEAN_MOTION, \u2026) returned by CelesTrak. CelesTrak-metadata fields that the upstream occasionally omits are backfilled with CCSDS-spec defaults.",
"title": "Omm",
"type": "object"
},
"fetched_at": {
"description": "UTC ISO 8601 timestamp when this TLE was retrieved from the upstream (or originally cached, when stale=true).",
"examples": [
"2026-05-23T12:00:00Z",
"2026-01-01T00:00:00.500Z"
],
"title": "Fetched At",
"type": "string"
},
"stale": {
"description": "True when CelesTrak was unreachable and the cached value was returned as a stale fallback. Operators should treat the TLE as best-effort.",
"title": "Stale",
"type": "boolean"
}
},
"required": [
"name",
"norad_id",
"tle_line1",
"tle_line2",
"omm",
"fetched_at",
"stale"
],
"title": "TleResult",
"type": "object"
}
},
"additionalProperties": false,
"description": "Top-level response from :func:`tle_lookup`.\n\nA list of :class:`TleResult`. Single-satellite queries return a single\nelement; group/category queries return one element per satellite in the\ncatalogue.",
"properties": {
"results": {
"description": "List of matching TLE results, one per satellite returned by CelesTrak.",
"items": {
"$ref": "#/$defs/TleResult"
},
"title": "Results",
"type": "array"
}
},
"required": [
"results"
],
"title": "TleLookupResponse",
"type": "object"
}