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

short_name

string | null

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: alma, datalab, gaia.

waveband

string | null

no

—

Optional. Return only archives in this waveband (case-insensitive), e.g. ‘radio’, ‘optical’, ‘millimeter’. Combines with short_name (both must match). Examples: millimeter, optical, radio.

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

archive

string

yes

—

Archive short_name (e.g. ‘alma’, ‘datalab’, ‘gaia’). Use list_archives to discover available names. Examples: alma, datalab, gaia.

table

string

yes

—

Fully qualified table name as it appears in the archive’s TAP schema (e.g. ‘ivoa.obscore’, ‘nsc_dr2.object’). Examples: ivoa.obscore, nsc_dr2.object.

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

name

string

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: NGC 1275, Cygnus A, M87, 3C 273.

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:

  1. 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 mode key on the response.

  2. 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

endpoint

string

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: https://datalab.noirlab.edu/tap, https://almascience.nrao.edu/tap.

adql

string

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: SELECT TOP 100 ra, dec, gmag FROM smash_dr2.object WHERE q3c_radial_query(ra, dec, 185.43, -31.99, 0.2) = 't', SELECT TOP 100 obs_id, s_ra, s_dec FROM ivoa.obscore WHERE CONTAINS(POINT('ICRS', s_ra, s_dec), CIRCLE('ICRS', 187.7, 12.39, 0.1)) = 1.

maxrec

integer

no

10000

Hard cap on rows returned. Default 10_000.

mode

"sync" | "async" | "auto"

no

"auto"

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

job_url

string

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: https://almascience.eso.org/tap/async/1234567.

wait_seconds

number | null

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: 30.

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

job_url

string

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: https://almascience.eso.org/tap/async/1234567.

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

job_url

string

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: https://almascience.eso.org/tap/async/1234567.

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

keywords

array of string | null

no

—

Free-text keywords to match against service titles/descriptions. Example: [‘Magellanic’, ‘photometry’]. Examples: ['magellanic'], ['RR Lyrae', 'variable'].

servicetype

string | null

no

—

Filter by service type: ‘tap’, ‘sia’, ‘scs’, ‘ssa’. Examples: tap, sia.

waveband

string | null

no

—

Filter by waveband: ‘radio’, ‘infrared’, ‘optical’, ‘uv’, ‘euv’, ‘x-ray’, ‘gamma-ray’. Examples: optical, infrared.

maxrec

integer

no

50

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=’’ to narrow to the tables you want (a narrow match returns their columns inline), or get one table’s columns by querying tap_schema.columns WHERE table_name = ‘

’ via run_adql_query (or describe_table for curated tables). See the returned hints.

Parameter

Type

Required

Default

Description

ivoid_or_url

string

yes

—

Either an IVOID (starts with ‘ivo://’) or a TAP service URL. The tool resolves both forms via RegTAP. Examples: ivo://eso.org/tap_obs, https://datalab.noirlab.edu/tap.

table_filter

string | null

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: allwise, gaia_source, obscore.