Your first query¶
This page runs MANNA by hand, without an LLM, so you can see what the tools return before you wire a client in. It uses the MCP Inspector CLI; the Tutorials do the same from Python.
Start the server¶
uvx manna-mcp
curl -s http://localhost:8000/health # {"status":"ok","version":"..."}
List the tools¶
npx -y @modelcontextprotocol/inspector --cli http://localhost:8000/mcp --method tools/list
Fifteen tools come back. Each has a description written for an LLM, a JSON
schema for its parameters, and annotations (readOnlyHint, openWorldHint).
Tool reference renders the same list.
Call a tool¶
npx -y @modelcontextprotocol/inspector --cli http://localhost:8000/mcp \
--method tools/call --tool-name resolve_target_name --tool-arg name=M87
{"resolved": true, "name": "M87", "ra": 187.7059, "dec": 12.3911, ...}
Now a query. ALMA’s obscore table supports standard ADQL geometry, and
ALMA’s own archive note recommends matching the observed field footprint
(s_region) with INTERSECTS, rather than CONTAINS against the pointing
centre (s_ra/s_dec), which misses mosaics:
npx -y @modelcontextprotocol/inspector --cli http://localhost:8000/mcp \
--method tools/call --tool-name run_adql_query \
--tool-arg endpoint=https://almascience.nrao.edu/tap \
--tool-arg "adql=SELECT TOP 5 obs_id, s_ra, s_dec, band_list FROM ivoa.obscore WHERE INTERSECTS(CIRCLE('ICRS', 187.7059, 12.3911, 0.1), s_region) = 1"
The result is an inline envelope:
Key |
Meaning |
|---|---|
|
the data |
|
always present, always a boolean; |
|
why, when |
|
short name of the archive the endpoint belongs to, when MANNA has notes for it |
|
a stable hash of the query and client-side snippets that save the result to |
|
instructions and tips written for the LLM |
What an LLM does with it¶
Given the same question in a chat client, a well-behaved model runs the sequence MANNA’s tool descriptions steer it toward:
resolve_target_name— coordinates for a named object.list_archives— which archive holds the right data, and its endpoint.describe_table— table-specific facts before writing ADQL (real column names, geometry idiom, enum values).run_adql_query— the query;mode="auto"promotes an oversize result to an async job (Large results).
The workflow tools collapse common sequences into one call:
find_observations_of_target, count_observations_near_target,
survey_archives_for_target, and preview_table.
When something goes wrong¶
Ask Data Lab for the same query with ADQL geometry and it fails, because Data Lab’s TAP passes geometry to PostgreSQL untranslated:
npx -y @modelcontextprotocol/inspector --cli http://localhost:8000/mcp \
--method tools/call --tool-name run_adql_query \
--tool-arg endpoint=https://datalab.noirlab.edu/tap \
--tool-arg "adql=SELECT TOP 5 ra, dec FROM gaia_dr3.gaia_source WHERE CONTAINS(POINT('ICRS', ra, dec), CIRCLE('ICRS', 229.022, -0.112, 0.1)) = 1"
{"error_class": "tap_query_error", "retry_strategy": "fix_and_retry",
"message": "...function point(unknown, double precision, double precision) does not exist..."}
No hint rides this particular payload. This archive note is an up-front
note, not an error hint: the raw PostgreSQL complaint never suggests the
fix (q3c_radial_query(ra, dec, <ra0>, <dec0>, <radius_deg>) = 't'), so the
guidance has to arrive before you write the query, not after it fails — it is
already in run_adql_query’s description, in the cheatsheet of archive quirks
(Tool reference). Read that cheatsheet before querying an
unfamiliar archive; it is cheaper than a round trip.
A query an archive understands and rejects for a reason the failure itself
implies — a disallowed ADQL string function, say — gets a real error hint
back: an archive note delivered only when a failed query matches its pattern,
riding the payload’s hint field. In the default archive set no error hint
fires today; the one archive that carries them, NRAO, is paused. Either way,
error_class and retry_strategy tell the model whether to fix the query,
wait, poll, or give up (Errors).