Tool reference¶
Every tool MANNA registers, generated from the running server at build time
(docs/_ext/manna_tools.py), so this page cannot drift from the code. The
layer tag says which of the four layers a tool belongs to
(Concepts). Annotations are the MCP tool hints the server
declares: every tool is readOnlyHint except abort_async_job, which deletes
an upstream job; tools that hit live archive services are openWorldHint.
Two descriptions embed live archive notes: list_archives carries each active
archive’s usage notes, and run_adql_query carries the up-front note cheatsheet
for the active archive set. What you see here is what an LLM sees for the
default active set (every archive except paused ones); MANNA_ARCHIVES
changes it.
Errors¶
Every tool shares one error contract. On failure the tool returns a payload
with error_class, message, retry_strategy, and (when available) hint.
The presence of error_class is the discriminator to branch on; there is no
isError key. See Errors for the taxonomy.
list_archives¶
Layer: Archive notes
Annotations: readOnlyHint
List the IVOA archives this server has first-class knowledge of.
Each entry includes the archive’s endpoint URLs (TAP / SIA / SCS), waveband, description, notable tables, and usage_notes — short agent-facing strings capturing archive-specific gotchas like non-standard table locations, sync-vs-async routing recommendations, target-name conventions, and ADQL quirks.
Call this FIRST when planning a query against an archive whose behavior you don’t already know — it’ll save you the trial-and-error of discovering quirks through failed queries. The notes are curated based on real friction encountered while building the server.
Pass short_name and/or waveband to narrow the result. With no
arguments it returns every known archive (the usage_notes are verbose,
so prefer short_name once you know which archive you need).
Archives not listed here still work via search_ivoa_registry followed
by describe_ivoa_service / run_adql_query — this tool only covers the
well-known set.
Returns: { “archives”: [ { “short_name”: “…”, “display_name”: “…”, “host_substrings”: [”…”], “tap_url”: “…”, “sia_url”: “…” | null, “scs_url”: “…” | null, “waveband”: “…”, “description”: “…”, “notable_tables”: [”…”], “usage_notes”: [ “Short agent-facing strings — read these BEFORE composing “ “a query. They capture real gotchas like non-standard “ “table names, required mode=’async’ routing, target-name “ “aliasing, etc.” ] }, … ], “count”: N }
Parameter |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
|
no |
— |
Optional. Return only the archive with this short_name (case-insensitive), e.g. ‘alma’. Use this when you already know which archive you want — it returns a single entry instead of the full set, saving context. Unknown names return an empty list (count: 0). Examples: |
|
|
no |
— |
Optional. Return only archives in this waveband (case-insensitive), e.g. ‘radio’, ‘optical’, ‘millimeter’. Combines with short_name (both must match). Examples: |
describe_table¶
Layer: Archive notes
Annotations: readOnlyHint, openWorldHint
Curated quirks + the real column list for one archive table.
Returns the table’s actual columns (name + datatype, fetched live from the archive’s tap_schema.columns) alongside table-specific curated facts: missing standard columns, value enums for filterable fields, notes, and cross_refs to related tables.
For a known archive, exactly one of these is always present:
columns: [{“name”: …, “datatype”: …}] — the real column list. Project these explicitly rather than SELECT *.column_list_recipe: the query to run yourself, if the fetch failed.
known: false means only that we carry no curated notes for the table — for
a known archive the column list is still returned. Use describe_ivoa_service
to discover which tables an archive has, or list_archives for valid
archive short_names.
Parameter |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
|
yes |
— |
Archive short_name (e.g. ‘alma’, ‘datalab’, ‘gaia’). Use list_archives to discover available names. Examples: |
|
|
yes |
— |
Fully qualified table name as it appears in the archive’s TAP schema (e.g. ‘ivoa.obscore’, ‘nsc_dr2.object’). Examples: |
resolve_target_name¶
Layer: Connections
Annotations: readOnlyHint, openWorldHint
Resolve an object name to RA/Dec (ICRS, decimal degrees).
Returns ra and dec suitable for CONTAINS(POINT('ICRS',ra,dec), CIRCLE('ICRS',<ra>,<dec>,<radius>))=1 ADQL predicates or as the
positional input to search_catalog_by_position.
On miss returns {"resolved": false, ...} — try an alternate
designation or use search_ivoa_registry to locate a catalog by keyword.
Parameter |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
|
yes |
— |
Astronomical object name to resolve to sky coordinates. Uses the CDS Sesame service (queries SIMBAD, then NED, then VizieR). Common name styles all work: Messier numbers (M87), NGC/IC designations, IAU names (3C 273), proper names (Cygnus A). Examples: |
run_adql_query¶
Layer: Connections · Result handling
Annotations: readOnlyHint, openWorldHint
Run an ADQL query against any IVOA-compliant TAP service.
BEFORE composing a query against an archive you don’t already know
cold, call list_archives first. It returns curated usage notes
for the well-known archives — non-standard table locations, required
mode=’async’ routing, ADQL quirks, target-name conventions — that
will save you trial-and-error here.
The server never holds result bytes. Small results come back inline; anything larger than the inline cap is routed to an async job whose result the client fetches itself (see get_async_job_results / fetch_recipe).
Returns one of two envelope shapes depending on what happened:
Inline result envelope (row_count, columns, rows). Returned when the result fits the inline cap: mode=’sync’ with a small result, or mode=’auto’ when the query finished within the sync timeout AND fit inline. No
modekey on the response.Promotion envelope (mode=’async’, job_url, fetch_recipe). Returned when mode=’async’, when mode=’auto’ and the sync attempt timed out, or when mode=’auto’ and the sync result was too large to inline. Disambiguate by checking payload.get(‘mode’) == ‘async’.
mode=’sync’ with an oversize result does NOT auto-promote — it raises validation_error telling you to re-run with mode=’async’.
For async results the server has already waited its default window before
answering; if phase is COMPLETED go straight to get_async_job_results(job_url).
Otherwise call get_async_job_status(job_url) — it waits server-side too, so
one call usually suffices — then get_async_job_results(job_url), or fetch
client-side with the pyvo fetch_recipe carried on the promotion envelope.
Pass the job_url back verbatim; it is the job’s only handle.
Successful result envelopes also carry query_fingerprint and a
save_recipe — after loading the result, execute save_recipe.code
client-side to persist a CSV + manna_cache/catalog.csv row so the
query need not be re-run later.
Archive quirks that give wrong results or unactionable errors — apply BEFORE querying:
NOIRLab Astro Data Lab (datalab.noirlab.edu): ADQL geometry (CONTAINS/CIRCLE/POINT) is NOT translated and errors. For a cone use q3c_radial_query(ra, dec,
, , <radius_deg>) = ‘t’; a ra/dec BETWEEN box also works but is a box, not a circle. ALMA Science Archive (almascience.nrao.edu): rows are per spectral-window, so COUNT(*) over-counts observations — count with COUNT(DISTINCT member_ous_uid).
Parameter |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
|
yes |
— |
Full TAP service URL. Example: ‘https://datalab.noirlab.edu/tap’ (NOIRLab Astro Data Lab) or ‘https://almascience.nrao.edu/tap’ (ALMA Science Archive). Discover other services via search_ivoa_registry. Examples: |
|
|
yes |
— |
ADQL query. Geometry support is archive-specific: standard CIRCLE/POINT/CONTAINS work on obscore services (ALMA, ESO) but NOT on Astro Data Lab, which passes them to PostgreSQL and needs q3c_radial_query(…) = ‘t’ instead — call list_archives for the archive’s quirks before composing. Use SELECT TOP N to cap row counts, project an explicit column list rather than SELECT * (describe_table returns the table’s real columns), and ORDER BY for deterministic results. Examples: |
|
|
no |
|
Hard cap on rows returned. Default 10_000. |
|
|
no |
|
Execution mode. ‘sync’ = TAP /sync only (default Slice-A behavior; times out as archive_error). ‘async’ = submit to TAP /async, wait up to the server’s default window, and return a job_url (phase may already be COMPLETED). ‘auto’ (default) = try sync first; on timeout, transparently promote to async. |
get_async_job_status¶
Layer: Connections
Annotations: readOnlyHint, openWorldHint
Wait (bounded) for an async TAP job, then report its live UWS phase.
The server blocks for up to wait_seconds — the server default when
omitted, clamped to the server maximum — re-reading the job every 2 s,
and answers as soon as the phase is terminal or the window expires. One
call with a wait replaces a burst of instant polls; wait_seconds=0 is
an immediate read. Nothing is cached locally between calls.
Returns {job_url, phase, started_at, ended_at, error_message, archive,
waited_seconds, next_steps}. next_steps says what to do for the phase
seen — including to call this tool again with a longer wait when the job
is still running.
Phases per UWS spec: PENDING, QUEUED, EXECUTING, COMPLETED, ERROR, ABORTED, ARCHIVED, HELD, SUSPENDED, UNKNOWN. The LLM branches on the string.
If the archive has deleted or expired the job, this raises job_gone (retry_strategy=abandon) — re-submit rather than continuing to poll.
Parameter |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
|
yes |
— |
The upstream job_url returned by run_adql_query when it went async (mode=’async’ or auto-promote). Pass it back verbatim — it is the job’s only handle. Examples: |
|
|
no |
— |
Seconds the server waits for the job to reach a terminal phase before answering. Omit for the server default; values are clamped to the server maximum. 0 answers immediately. One call with a wait replaces a burst of instant polls — do not call this tool in a tight loop. Examples: |
get_async_job_results¶
Layer: Connections · Result handling
Annotations: readOnlyHint, openWorldHint
Return access info for a COMPLETED async TAP job.
The server does NOT fetch the result bytes. It returns the upstream job_url, the direct result_url, and a pyvo fetch_recipe so the client loads the data itself (e.g. in a Jupyter kernel). Anonymous access only.
After calling this, execute the returned fetch_recipe code with your code-execution tool to load the data. The query already ran — do not re-submit it.
If the job is not yet COMPLETED, raises job_not_ready (retry_strategy=poll). If the job ended in ERROR, raises tap_query_error with the upstream message. If the archive no longer has the job, raises job_gone (retry_strategy=abandon).
Parameter |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
|
yes |
— |
The upstream job_url returned by run_adql_query when it went async (mode=’async’ or auto-promote). Pass it back verbatim — it is the job’s only handle. Examples: |
abort_async_job¶
Layer: Connections
Annotations: destructiveHint, idempotentHint, openWorldHint
Cancel a running async TAP job.
Sends UWS DELETE upstream. Idempotent: aborting an already-deleted or expired job returns the same {job_url, phase=ABORTED} shape rather than raising (abort_job swallows the 4xx).
Parameter |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
|
yes |
— |
The upstream job_url returned by run_adql_query when it went async (mode=’async’ or auto-promote). Pass it back verbatim — it is the job’s only handle. Examples: |
search_ivoa_registry¶
Layer: Connections
Annotations: readOnlyHint, openWorldHint
Discover IVOA-registered services matching the given constraints.
Returns a {services: […], row_count, truncated, truncation_reason} envelope. Each service entry has: ivoid, title, description, publisher, waveband, and one URL per capability (tap_url, sia_url, scs_url, ssa_url; null when the service doesn’t expose that capability).
Use for discovery before calling run_adql_query / search_images_by_position / search_catalog_by_position on a specific endpoint. Smaller default maxrec (50) than catalog tools — discovery is about choice, not bulk data.
Parameter |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
|
no |
— |
Free-text keywords to match against service titles/descriptions. Example: [‘Magellanic’, ‘photometry’]. Examples: |
|
|
no |
— |
Filter by service type: ‘tap’, ‘sia’, ‘scs’, ‘ssa’. Examples: |
|
|
no |
— |
Filter by waveband: ‘radio’, ‘infrared’, ‘optical’, ‘uv’, ‘euv’, ‘x-ray’, ‘gamma-ray’. Examples: |
|
|
no |
|
Hard cap on services returned. Default 50. |
describe_ivoa_service¶
Layer: Connections · Result handling
Annotations: readOnlyHint, openWorldHint
Introspect a specific IVOA service: its capabilities, and for TAP services its tables and columns.
Returns {ivoid, title, description, capabilities, tables, truncated, total_tables} (plus matched_tables when table_filter is used). Use after search_ivoa_registry to learn what’s queryable on a specific service before composing ADQL via run_adql_query.
Each table normally carries its full column list. For a very large service
the response degrades to a table catalog (name + description +
column_count per table, no per-column detail) and sets truncated: true.
When that happens, either pass table_filter=’ Parameter Type Required Default Description yes — Either an IVOID (starts with ‘ivo://’) or a TAP service URL. The tool resolves both forms via RegTAP. Examples: no — Optional case-insensitive keyword. When set, only tables whose name or description contains it are returned. Use this on large services (hundreds/thousands of tables) to find the ones you want AND get their columns in one call. Example: ‘allwise’, ‘gaia_source’, ‘obscore’. Examples: Layer: Connections · Result handling Run a Simple Cone Search (SCS) against a catalog endpoint. Returns the inline tabular envelope, same shape as run_adql_query.
For most uses, prefer run_adql_query — SCS is here for catalogs that
only expose the legacy protocol. Successful envelopes carry Parameter Type Required Default Description yes — Simple Cone Search endpoint URL. Example: ‘https://gaia.ari.uni-heidelberg.de/cone/gaiadr2?’ (Gaia ARI Heidelberg). Prefer run_adql_query for archives that expose a TAP endpoint — search_catalog_by_position is here for SCS-only legacy services. Examples: yes — Right ascension (ICRS), degrees. yes — Declination (ICRS), degrees. yes — Cone radius in degrees. no Hard cap on rows returned. Default 10_000. Layer: Connections · Result handling Discover images at a sky position via Simple Image Access (SIA 2.0 or 1.0). Returns the inline tabular envelope. Each row is image metadata; the
Most archives speak SIA2; NOIRLab Data Lab speaks SIA1. With the default
version=’auto’ you don’t need to know which — SIA2 is tried first and
SIA1 is used as a fallback. For all-sky discovery first, see search_ivoa_registry with
servicetype=’sia’. Successful envelopes carry Parameter Type Required Default Description yes — SIA endpoint URL — SIA 2.0 or 1.0. Example: ‘https://datalab.noirlab.edu/sia/coadd_all’ (NOIRLab Astro Data Lab) or ‘https://almascience.nrao.edu/sia2’ (ALMA Science Archive). search_images_by_position auto-detects the version (SIA2, falling back to SIA1 as used by NOIRLab Data Lab); pass the version argument to force one. Discover endpoints with search_ivoa_registry(servicetype=’sia’). Examples: yes — Right ascension (ICRS), degrees. yes — Declination (ICRS), degrees. yes — Field-of-view size in degrees. no — Optional waveband filter (e.g. ‘optical’, ‘infrared’). Examples: no — Optional image format (e.g. ‘image/fits’). Examples: no Hard cap on rows returned. Default 1_000. no SIA protocol version. ‘auto’ (default) tries SIA 2.0 and falls back to SIA 1.0 when the endpoint isn’t SIA2 (e.g. NOIRLab Data Lab is SIA 1.0). Force with ‘2’ or ‘1’. The ‘band’ filter applies to SIA2 only. Layer: Workflow tools Find observations of a target in one call (resolve -> select -> search). A purpose-driven workflow over resolve_target_name + list_archives +
search_images_by_position / search_catalog_by_position. Pass an object name (auto-resolved) or
explicit ‘RA DEC’; optionally steer archive choice with Returns the standard inline tabular envelope (same shape as search_images_by_position /
search_catalog_by_position — typed Soft-fails (no error_class) when a name can’t be resolved
({“resolved”: false, …}) or no archive matches the filter
({“count”: 0, “hint”: …}) — recover by adjusting the target/filter or
dropping to the atomic tools with an explicit endpoint. Parameter Type Required Default Description yes — Object name (resolved via CDS Sesame — ‘M87’, ‘Cygnus A’, ‘3C 273’) OR explicit ICRS coordinates as ‘RA DEC’ in decimal degrees (‘187.7059 12.3911’, comma optional). Names are auto-resolved; you do NOT need to call resolve_target_name first. Examples: no ‘image’ -> Simple Image Access (rows carry image/cutout access_urls). ‘catalog’ -> Simple Cone Search (source rows). no — Optional waveband to auto-select the archive — ‘radio’, ‘optical’, ‘infrared’, ‘millimeter’. Omit to use the highest-priority archive offering this service. Examples: no Search radius / field size in degrees. Default 0.1. no — Optional short_name override (‘alma’, ‘datalab’) to skip auto-selection. Use when you already know the archive. Examples: no Hard cap on rows returned. Default 1_000. Layer: Workflow tools Count observations/sources near a target in one call (resolve -> select -> COUNT). Selects an archive by its curated Returns Parameter Type Required Default Description yes — Object name (CDS Sesame-resolved — ‘M87’, ‘Cygnus A’) OR explicit ICRS ‘RA DEC’ in decimal degrees (‘187.7059 12.3911’, comma optional). You do NOT need to call resolve_target_name first. Examples: no Search radius in degrees. Default 0.1. no — Optional waveband to steer archive choice — ‘radio’, ‘optical’, ‘millimeter’. Omit to use the highest-priority countable archive. Examples: no — Optional short_name override (‘alma’, ‘datalab’) to skip auto-selection. Examples: Layer: Workflow tools Survey which archives hold data for a target, with per-archive counts. Resolves the target, then runs each countable archive’s curated positional
COUNT and returns one row per archive: {archive, display_name, waveband,
table, count, status, [job_url, next_steps]}. Parameter Type Required Default Description yes — Object name (Sesame-resolved) or explicit ICRS ‘RA DEC’ in decimal degrees. Examples: no Search radius in degrees. Default 0.1. no — Optional list of wavebands to restrict the fan-out. Examples: Layer: Workflow tools · Archive notes Columns + curated enums/notes + a sample of rows for one table, in one call. Returns the real column list (name + datatype), curated Parameter Type Required Default Description yes — Fully qualified table name (e.g. ‘ivoa.obscore’, ‘nsc_dr2.object’). Examples: no — Archive short_name. If omitted, inferred from curated schema knowledge. Examples: no How many example rows to sample (SELECT TOP n *). 0 disables.’
via run_adql_query (or describe_table for curated tables). See the
returned hints.
ivoid_or_urlstringivo://eso.org/tap_obs, https://datalab.noirlab.edu/tap.table_filterstring | nullallwise, gaia_source, obscore.search_catalog_by_position¶
Annotations: readOnlyHint, openWorldHintquery_fingerprint + save_recipe; execute
save_recipe.code client-side to persist the result and its catalog row.
endpointstringhttps://gaia.ari.uni-heidelberg.de/cone/gaiadr2?.ranumberdecnumberradius_degnumbermaxrecinteger10000search_images_by_position¶
Annotations: readOnlyHint, openWorldHintaccess_url column points at the image (a FITS file, or a cutout-service
URL for archives like Data Lab). The server does not download images —
fetch an access_url client-side (e.g. astropy.io.fits.open(access_url)).query_fingerprint + save_recipe; execute
save_recipe.code client-side to persist the result and its catalog row.
endpointstringhttps://datalab.noirlab.edu/sia/coadd_all, https://almascience.nrao.edu/sia2.ranumberdecnumbersize_degnumberbandstring | nulloptical.fmtstring | nullimage/fits.maxrecinteger1000version"auto" | "1" | "2""auto"find_observations_of_target¶
Annotations: readOnlyHint, openWorldHintwaveband or an
explicit archive.columns, rows, explicit truncated bool; note
this workflow tool’s envelope does NOT carry the query_fingerprint / save_recipe
cache fields the primitive tools attach — call the underlying search_images_by_position
/ search_catalog_by_position directly if you need those) plus:
resolved: {target, ra, dec, frame} — the coordinates actually used.plan: {service, chosen_archive, endpoint, alternatives, usage_notes}
— how the archive was picked, and its curated gotchas. Read
plan.usage_notes before trusting the rows.
targetstringM87, Cygnus A, 187.7059 12.3911.service"image" | "catalog""image"wavebandstring | nullmillimeter, optical, radio.radius_degnumber0.1archivestring | nullalma, datalab.maxrecinteger1000count_observations_near_target¶
Annotations: readOnlyHint, openWorldHintcount_target and runs the
archive-correct positional COUNT: q3c for Data Lab, CONTAINS/CIRCLE for
Gaia’s gaia_source and ESO’s obscore, INTERSECTS + COUNT(DISTINCT
member_ous_uid) for ALMA. An archive whose count_target is mode=’async’
runs with a bounded poll.count (int) plus a resolved block and a plan block
(chosen_archive, table, endpoint, adql, count_expr, mode, alternatives,
usage_notes). If a slow async job outruns the poll budget, returns
{"status":"pending","count":null,"job_url":...,"next_steps":...} — poll
get_async_job_status(job_url) until phase=COMPLETED, then get_async_job_results(job_url).
Pass job_url back verbatim; it is the job’s only handle (no server-side
job id). Soft-fails (no error_class) on an unresolvable target or when no
archive offers counting.
targetstringM87, 200.0 20.0.radius_degnumber0.1wavebandstring | nullmillimeter, optical, radio.archivestring | nullalma, datalab.survey_archives_for_target¶
Annotations: readOnlyHint, openWorldHintstatus ∈ {ok, pending,
error} is always explicit — never a silent zero. A summary block totals
archives_with_data / wavebands / pending / errors. Soft-fails (no
error_class) on an unresolvable target. For a single archive with finer
control, drop to count_observations_near_target or run_adql_query.
targetstringM87, NGC 253.radius_degnumber0.1wavebandsarray of string | null['millimeter', 'optical', 'radio'].preview_table¶
Annotations: readOnlyHint, openWorldHintvalue_enums,
notes, and sample_rows (best-effort — sample_status ∈ {ok, error,
disabled}). Reliable metadata is always returned even if the sample fails
(some archives reject unfiltered reads). Soft-fails (known: false + hint)
when the table/archive can’t be identified. Note known means “has
curated schema knowledge” (same convention as describe_table), NOT
“table identified” — for a known archive/endpoint the column list is
still returned even when known is false.
tablestringivoa.obscore, nsc_dr2.object.archivestring | nulldatalab, alma.sample_rowsinteger5