Skip to main content

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)

Source distribution for leona-mcp 0.3.0
File Size Uploaded
leona_mcp-0.3.0.tar.gz 31.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for leona-mcp 0.3.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page