leona-mcp
An MCP server that lets an AI assistant look things up in the Quantum Atlas, Leona Quantum's public catalog of quantum algorithms, gates, states and benchmark circuits — and, with a personal access token, start a verified run, read its result, plan an algorithm and cost it from its sources, estimate physical resources, call a published Qapp, have a circuit checked, and list, read, edit and re-run your own Leona notebooks, all as the token's own account.
Three tools need nothing at all: they read one public API endpoint, the same
anonymous catalog listing the website uses. You need no account or key, and they
cannot spend money. Twelve more need a personal access token (minted on leonaqt.com,
Account → Access tokens) in the LEONA_API_TOKEN environment variable — never as a
tool argument, and it is never logged or echoed in an error. Without one, those twelve
tools answer with a message telling you to set it; the three read-only tools are
unaffected. The server runs on your machine and talks to your MCP client over stdio.
Tools
| Tool | Needs a token | What it does |
|---|---|---|
search_methods |
No | Filters the Atlas by free text, problem area, a qubit limit, a depth limit, and NISQ or fault-tolerant, using the same rules as the site's method finder. Results are unranked candidates, sorted by slug. |
get_method |
No | One record in full: description, explanation, the cost it states, its speedup class and whether that was checked against the primary paper, its verification tier, the literature it cites, and the OpenQASM 3 of its circuit when it has one. |
list_problem_areas |
No | The problem areas the Atlas uses, with a definition and a record count for each. |
run_verified |
Yes (run scope) |
Starts a Nala run — the same route the website's Run box calls — as your account, and polls for up to wait_s seconds for a verified result. |
get_run |
Yes | Reads one of your own runs by id, including its verification record. |
list_my_runs |
Yes | Lists your own runs, most recent first. |
plan_workflow |
Yes (any scope) | Plans an algorithm the way leonaqt.com/repository/plan does and returns the same numbers: the pipeline of Atlas blocks for a problem (factoring, search, ground-state energy, linear systems, MaxCut, amplitude and phase estimation, Hamiltonian simulation, elliptic-curve keys, differential equations), each stage's alternatives, and the cost lines evaluated from published formulas at your sizes. Every line carries its kind (exact, a bound, leading order, a paper's numerical estimate, a scaling with no constant, ...) and its source, and summary quotes each number with both. estimate_point is ready to pass to estimate_resources. Arithmetic only: nothing is run or stored. |
estimate_resources |
Yes | Turns a logical cost you state into physical qubits and runtime under a named assumption set. |
run_qapp |
Yes (run scope) |
Calls a published Qapp with input values, through the same route and the same sandboxed execution as the Qapp's own page, and polls for its result. |
check_circuit |
Yes (run scope) |
Checks a small OpenQASM 3 circuit (the tool description gives the widest per kind, from the contract's own limits) against one property: an output state, a unitary, an ideal measured distribution, or an energy. Returns pass, fail or inconclusive with a diagnosis, a plain passed boolean, a one-paragraph summary, and its teeth: how many deliberately broken copies of the circuit the check caught. An optional structure says how the circuit must be built (gate budgets, gates it may, may not or must use, measurements only at the end), so a circuit that loads its answer with prepare_state instead of building it fails. To check a circuit family rather than one circuit, send circuits (the circuit built at each of up to 8 sizes) with a reference written with n, such as ghz(n): each size is judged with its own teeth, and the check passes only if every size does. Judged by Leona's own code, one circuit at a time (a busy server answers 503; call again in a few seconds); nothing is stored. |
list_notebooks |
Yes | Lists your own notebooks (id, title, current version, latest status, updated), newest first, paginated with cursor. |
pull_notebook |
Yes | Reads one notebook version's .nb.py SOURCE — jupytext's percent-format authoring text, not compiled .ipynb JSON — the form a model edits far more reliably. Omit version for the current one. |
push_notebook |
Yes (run scope) |
Saves edited .nb.py source as a new version of an existing notebook — the same route the web editor and leona-notebooks push --to use. Runs it in Leona's sandbox by default and waits for a per-cell report; execute=false saves a draft with no run. |
notebook_status |
Yes | Reads a notebook's latest version: status, a plain ok, and each cell's outcome (including, on error, the exception and a traceback tail). Poll this after push_notebook/run_notebook if wait_s ran out. |
run_notebook |
Yes (run scope) |
Re-runs a notebook's current version with no edits, and waits for a per-cell report. To run edited source, use push_notebook instead. |
Everything the first three tools return comes from the published Atlas record, with a link back to its page on leonaqt.com. When a record does not carry a field, the answer says "not stated in the record" instead of guessing. A record is a claim from the sources it cites, not a result this server checked.
A run's status can be "succeeded" while its verification did not pass. Read
verifier_decision and verification_summary — every run tool's response also
carries a plain verified boolean, true only when verifier_decision is "pass" —
before telling anyone a result is verified.
A check_circuit pass means "checked against" what checked_against names, and
nothing more. Quote it that way, never as "verified": the reference can itself be the
wrong target. Its teeth say whether the check could have failed at all; a check that
caught none of the broken copies could not tell a broken circuit from yours.
A notebook version's status can be "ready" while ok is false. ok comes
from the stored run report, not from status: a version is ready the moment
execution finishes, whether or not a cell raised. Read ok and cells — never just
status — before telling anyone a notebook ran clean.
Editing a notebook never needs .ipynb. pull_notebook returns the .nb.py
percent-format source — a plain-text file of a YAML header, markdown cells and code
cells under # %% markers — and push_notebook takes that same text back. Neither
tool touches compiled .ipynb JSON, which is why they exist beside leona-notebooks pull/push rather than duplicating them: a model edits Python far more reliably than
JSON with escaped newlines inside string literals.
Example: "write a 3-qubit QFT and check it with Leona"
A Claude Code user with the server connected and a run-scope token set
(claude mcp add --transport stdio --env LEONA_API_TOKEN=lq_pat_... leona-atlas -- uvx --from "leona-mcp==0.3.0" leona-mcp) asks their agent to write a 3-qubit QFT and check it with Leona. Bitstrings
and Pauli strings use Qiskit's order, q0 rightmost, and qft(3) is Qiskit's QFT
including its final swaps. The requests and answers below come from a real run:
services/api/tests/test_check_circuit_route.py produces them through the MCP tool,
leona_client, the API route and the check engine, and fails if this README drifts from
what they answer.
The agent's first attempt leaves out the final swap. It calls:
{
"tool": "check_circuit",
"arguments": {
"qasm": "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nqubit[3] q;\nh q[2];\ncp(pi/2) q[1], q[2];\ncp(pi/4) q[0], q[2];\nh q[1];\ncp(pi/2) q[0], q[1];\nh q[0];\n",
"kind": "unitary",
"reference": "qft(3)"
}
}
Leona answers:
{
"status": "fail",
"basis": "circuit",
"checked_against": "Qiskit's QFT on 3 qubits, exact unitary up to global phase",
"measure": "largest entry of the difference, after removing global phase, 7.07e-01 (needs ≤ 1.00e-06)",
"detail": "It matches up to a reversal of the qubit order on one side. A QFT written without its final swaps does exactly this.",
"qubits": 3,
"subject_fingerprint": "c5fcd4e70621fe09aca5a0421af4d7be5f9e7f4ef3da09d3ae784bcbc4d66880",
"subject_qasm": "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nqubit[3] q;\nh q[2];\ncp(pi/2) q[1], q[2];\ncp(pi/4) q[0], q[2];\nh q[1];\ncp(pi/2) q[0], q[1];\nh q[0];\n",
"teeth": {
"status": "not_measured",
"reason": "Broken copies are tried only on a check that passes. This one failed, so there was nothing to test.",
"mutants": 0,
"equivalent": 0,
"caught": 0,
"survivors": [],
"could_not_run": 0,
"shortcut": null
},
"per_size": null,
"counts": null,
"structure": null,
"behaviour_status": null,
"behaviour_detail": "",
"passed": false,
"summary": "Checked against Qiskit's QFT on 3 qubits, exact unitary up to global phase: fail (largest entry of the difference, after removing global phase, 7.07e-01 (needs ≤ 1.00e-06)). It matches up to a reversal of the qubit order on one side. A QFT written without its final swaps does exactly this. Whether this check can catch a broken circuit was not measured: Broken copies are tried only on a check that passes. This one failed, so there was nothing to test.",
"teeth_note": "Whether this check can catch a broken circuit was not measured: Broken copies are tried only on a check that passes. This one failed, so there was nothing to test."
}
The agent adds swap q[0], q[2]; at the end and calls again:
{
"tool": "check_circuit",
"arguments": {
"qasm": "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nqubit[3] q;\nh q[2];\ncp(pi/2) q[1], q[2];\ncp(pi/4) q[0], q[2];\nh q[1];\ncp(pi/2) q[0], q[1];\nh q[0];\nswap q[0], q[2];\n",
"kind": "unitary",
"reference": "qft(3)"
}
}
Leona answers:
{
"status": "pass",
"basis": "circuit",
"checked_against": "Qiskit's QFT on 3 qubits, exact unitary up to global phase",
"measure": "largest entry of the difference, after removing global phase, 1.28e-15 (needs ≤ 1.00e-06)",
"detail": "",
"qubits": 3,
"subject_fingerprint": "077aef414f965daa2894271ba1cdea0a7c6090f70779a8f91a3c94ef0ab53509",
"subject_qasm": "OPENQASM 3.0;\ninclude \"stdgates.inc\";\nqubit[3] q;\nh q[2];\ncp(pi/2) q[1], q[2];\ncp(pi/4) q[0], q[2];\nh q[1];\ncp(pi/2) q[0], q[1];\nh q[0];\nswap q[0], q[2];\n",
"teeth": {
"status": "measured",
"reason": "",
"mutants": 11,
"equivalent": 0,
"caught": 11,
"survivors": [],
"could_not_run": 0,
"shortcut": null
},
"per_size": null,
"counts": null,
"structure": null,
"behaviour_status": null,
"behaviour_detail": "",
"passed": true,
"summary": "Checked against Qiskit's QFT on 3 qubits, exact unitary up to global phase: pass (largest entry of the difference, after removing global phase, 1.28e-15 (needs ≤ 1.00e-06)). Leona also made 11 deliberately broken copies of the circuit that change its output, and the check caught all 11."
}
Example: "how big a machine does RSA-2048 need, by Leona's sources?"
The same user asks their agent what factoring a 2048-bit RSA modulus costs. Any token
will do here; planning and estimating are arithmetic. The requests and answers below come
from a real run: services/api/tests/test_plan_route.py makes both calls through the MCP
tools, leona_client, the API routes, the planner port and the estimator, and fails if
this README drifts from what they answer. The planner's numbers are the ones
/repository/plan shows for the same input: the port is held equal to the TypeScript
planner on about 900 inputs (packages/py/planner/tests/test_planner_parity.py).
The agent calls:
{
"tool": "plan_workflow",
"arguments": {
"problem": "factoring",
"params": {
"bits": 2048
}
}
}
Leona answers (the summary, the cost lines, and the point to cost; the full answer also carries every stage, its alternatives and the methods' stated costs, the sources with their quotes, and the published whole-machine figures):
{
"summary": "Leona's workflow planner, for Factor an integer (RSA) at bits = 2,048 (given): Recover the period of a periodic function by Period finding in a finite cyclic group (the planner's default). Costs: Logical qubits (Gidney–Ekerå 2019): 6,190 logical qubits [leading-order; Gidney & Ekerå 2019 (arxiv:1905.09749), abstract]; Toffoli gates (Gidney–Ekerå 2019): 2.62e9 Toffoli gates [leading-order; Gidney & Ekerå 2019 (arxiv:1905.09749), abstract]; Measurement depth (Gidney–Ekerå 2019): 2.14e9 measurement layers [leading-order; Gidney & Ekerå 2019 (arxiv:1905.09749), abstract]; Logical qubits (Gidney 2025): 1,399 logical qubits [published; Gidney 2025 (arxiv:2505.15917), Table 5]; Toffoli gates per factoring (Gidney 2025): 6.5e9 Toffoli gates [published; Gidney 2025 (arxiv:2505.15917), Table 5]. Published at this size: Noisy qubits, 8 hours (Gidney–Ekerå 2019): 2e7 physical qubits [published; Gidney & Ekerå 2019 (arxiv:1905.09749), title and abstract]; Noisy qubits, under a week (Gidney 2025): < 1,000,000 physical qubits [published; Gidney 2025 (arxiv:2505.15917), abstract]. Figures here are rounded for reading; the lines carry exact values. For physical qubits and runtime, pass estimate_point to estimate_resources.",
"lines": [
{
"id": "ge2021-qubits",
"label": "Logical qubits (Gidney–Ekerå 2019)",
"value": 6190,
"unit": "logical qubits",
"kind": "leading-order",
"source": "ge2021-logical",
"formula": "3n + 0.002·n·lg n"
},
{
"id": "ge2021-toffolis",
"label": "Toffoli gates (Gidney–Ekerå 2019)",
"value": 2624225017.856,
"unit": "Toffoli gates",
"kind": "leading-order",
"source": "ge2021-logical",
"formula": "0.3n³ + 0.0005·n³·lg n"
},
{
"id": "ge2021-depth",
"label": "Measurement depth (Gidney–Ekerå 2019)",
"value": 2143289344,
"unit": "measurement layers",
"kind": "leading-order",
"source": "ge2021-logical",
"formula": "500n² + n²·lg n"
},
{
"id": "g2025-qubits",
"label": "Logical qubits (Gidney 2025)",
"value": 1399,
"unit": "logical qubits",
"kind": "published",
"source": "gidney2025-table5",
"formula": "Table 5, n = 2048"
},
{
"id": "g2025-toffolis",
"label": "Toffoli gates per factoring (Gidney 2025)",
"value": 6500000000,
"unit": "Toffoli gates",
"kind": "published",
"source": "gidney2025-table5",
"formula": "Table 5, n = 2048"
}
],
"logical": {
"logical_qubits": "ge2021-qubits",
"toffolis": "ge2021-toffolis",
"t_gates": null,
"queries": null,
"serial_depth": "ge2021-depth"
},
"estimate_point": {
"label": "factoring (Leona planner)",
"parameter_value": 2048,
"logical_qubits": 6190,
"toffoli_count": 2624225018,
"t_count": 0,
"non_clifford_depth": 2143289344
}
}
The agent passes estimate_point on:
{
"tool": "estimate_resources",
"arguments": {
"points": [
{
"label": "factoring (Leona planner)",
"parameter_value": 2048,
"logical_qubits": 6190,
"toffoli_count": 2624225018,
"t_count": 0,
"non_clifford_depth": 2143289344
}
]
}
}
Leona answers (the costed point; the full answer adds the frontier and each assumption set's citation):
{
"assumptions": "gidney-2025@v2",
"points": [
{
"label": "factoring (Leona planner)",
"refused": null,
"distance": {
"code_distance": 31,
"logical_operations": 132690604193744,
"required_error_per_operation": 7.536328635144969e-17,
"achieved_error_per_operation": 1.0000000000000008e-17,
"physical_per_logical": 2048
},
"fastest": {
"footprint": {
"data_patch_qubits": 12677120,
"routing_qubits": 12677120,
"factory_qubits": 417792,
"total_physical_qubits": 25772032
},
"runtime": {
"magic_states": 20993800144,
"factory_count": 17,
"throughput_seconds": 21410.58882332941,
"reaction_limited_seconds": 21432.893440000003,
"seconds": 21432.893440000003,
"binding_term": "reaction",
"factory_crossover": 17
}
},
"smallest": {
"footprint": {
"data_patch_qubits": 12677120,
"routing_qubits": 12677120,
"factory_qubits": 24576,
"total_physical_qubits": 25378816
},
"runtime": {
"magic_states": 20993800144,
"factory_count": 1,
"throughput_seconds": 363980.0099965999,
"reaction_limited_seconds": 21432.893440000003,
"seconds": 363980.0099965999,
"binding_term": "throughput",
"factory_crossover": 17
}
}
}
]
}
No tool here submits a hardware job. Submitting to hardware needs a token with the
separate hardware scope (ai-ops 376), which the run scope does not include, and even
with that scope this server has no tool that submits.
Example: "pull my coin-flip notebook, fix the bug, and run it"
Connect the server with a run-scope token (below), then just ask, in your own words:
a Claude Code user with the server connected asks their agent to fix a bug in one of
their notebooks. The agent finds it (list_notebooks), reads its source
(pull_notebook, version: null — the current version), edits the .nb.py text
directly, and saves the fix as a new version, run and reported in one call
(push_notebook):
{"tool": "list_notebooks", "arguments": {"limit": 5}}
{"tool": "pull_notebook", "arguments": {"notebook_id": "01996f3a-...-b21c"}}
{
"tool": "push_notebook",
"arguments": {
"notebook_id": "01996f3a-...-b21c",
"source": "# ---\n# title: Coin flip\n# kind: lesson\n# ---\n\n# %% role=run\nfrom qiskit import QuantumCircuit\nqc = QuantumCircuit(1, 1)\nqc.h(0)\nqc.measure(0, 0)\n",
"message": "measure the qubit before reading it out"
}
}
push_notebook waits for the run and answers with the per-cell report directly — no
separate poll needed unless wait_s runs out, in which case notebook_status reads
the same shape once it finishes.
Install
Requires Python 3.12 or later and uv. uvx fetches
the supported 0.3.0 server and its public dependencies from PyPI; Git access is not
required.
uvx --from "leona-mcp==0.3.0" leona-mcp --version
For a regular Python environment, use python -m pip install "leona-mcp==0.3.0",
then run leona-mcp. The server depends on the public leona-client package;
Leona's check engine, contracts, sandbox and planner stay on the service.
Connect a client
The stdio examples below launch the server directly. Atlas tools need no token;
acting tools need LEONA_API_TOKEN with the scope noted above.
The examples name the server leona-atlas. Any name works.
Claude Code
claude mcp add --transport stdio leona-atlas -- \
uvx --from "leona-mcp==0.3.0" leona-mcp
Options go before the name and everything after -- is the command. Add
--scope user to make it available in every project, or --scope project to write it
to the project's .mcp.json for everyone who clones it. The default scope is local.
Cursor
Add this to ~/.cursor/mcp.json, or to .cursor/mcp.json in a project:
{
"mcpServers": {
"leona-atlas": {
"type": "stdio",
"command": "uvx",
"args": [
"--from",
"leona-mcp==0.3.0",
"leona-mcp"
]
}
}
}
Claude Desktop
Open the config file (on macOS
~/Library/Application Support/Claude/claude_desktop_config.json, on Windows
%APPDATA%\Claude\claude_desktop_config.json), add the server, and restart the app:
{
"mcpServers": {
"leona-atlas": {
"command": "uvx",
"args": [
"--from",
"leona-mcp==0.3.0",
"leona-mcp"
]
}
}
}
Desktop apps often start without your shell's PATH. If the server does not start,
replace "uvx" with the full path that which uvx prints (for example
/Users/you/.local/bin/uvx).
Settings
| Variable | Default | Meaning |
|---|---|---|
LEONA_API_URL |
https://majorana-api-nikekeixtq-uw.a.run.app |
The Leona API to read. Point it at http://localhost:8000 to read a local API. |
LEONA_API_TOKEN |
unset | A personal access token, for run_verified/get_run/list_my_runs/plan_workflow/estimate_resources/run_qapp/check_circuit/list_notebooks/pull_notebook/push_notebook/notebook_status/run_notebook. Leave unset to use only the three read-only Atlas tools. |
In Claude Code, pass either with --env NAME=value before the server name. In the
JSON configs, add "env": {"LEONA_API_URL": "...", "LEONA_API_TOKEN": "..."} beside
args. Personal access tokens are a Leona feature still being rolled out
(proposal 7 Phase B); if run_verified answers that tokens are not available yet,
that is the account-wide switch, not this server.
The server reads the whole Atlas once (a few megabytes, in pages of 100) and reuses it for ten minutes. HTTP calls time out after 30 seconds. If the listing it gets back is incomplete or changes while it is being read, the tool call fails with a message saying so, rather than answering from part of the Atlas.
Develop
From the repository root:
uv sync --all-packages
uv run pytest packages/py/mcp -q
uv run leona-mcp # speaks MCP on stdin and stdout; Ctrl-C to stop
The Atlas filtering rules and the HTTP client live in leona_client
(packages/py/client), shared with the %nala Jupyter magic and the
leona-notebooks CLI — this package now holds only the MCP protocol layer. The
filtering rules are themselves copies of TypeScript in apps/web/lib/repository/,
which is the source of truth; packages/py/client/tests/test_mirrors.py fails when a
copied vocabulary stops matching it. See AGENTS.md in this directory, and in
packages/py/client, before changing a rule.
Metadata
Release files for leona-mcp 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| leona_mcp-0.3.0.tar.gz | 31.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| leona_mcp-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 64.8 kB
Release files / leona_mcp-0.3.0.tar.gz
| Download URL | leona_mcp-0.3.0.tar.gz |
|---|---|
| Size | 31.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7f0643d826ec26c35720d3017ee6cef41026d99e9e862e5dbb85ac5c7f31125e
|
|
BLAKE2b-256 checksum How to use checksums |
6877a80a87d87dac7d47445136e8091ff97a0306f1385977cbeaf5b9077c7fd4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / leona_mcp-0.3.0-py3-none-any.whl
| Download URL | leona_mcp-0.3.0-py3-none-any.whl |
|---|---|
| Size | 32.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5fe518f6d3b30462a7f417ae40b3cf6a7e1cde8fda774fcac5840355396af643
|
|
BLAKE2b-256 checksum How to use checksums |
c7fc9f1113f3c4579b6997d9cd447bb2f0e4d5bffe4fb94749a822129448b7fa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency log