Files
quantum-bridge-mcp/CLAUDE.md
T
Vincent Bourdon 9af114e391 Initial import
2026-06-09 16:14:55 +02:00

9.1 KiB
Raw Blame History

quantum-bridge-mcp — Development Rules

Project

Pure Rust MCP quantum simulator. See docs/superpowers/specs/2026-04-28-quantum-bridge-mcp-design.md for the full spec.

Stack: rmcp (MCP SDK) + oq3_semantics (QASM3 parser) + spinoza (statevector engine) + tokio.

Bootstrap (greenfield project)

src/ and Cargo.toml do not exist yet. To initialize:

cargo new --name quantum-bridge-mcp .   # creates src/main.rs + Cargo.toml

Key dependencies to add to Cargo.toml (check current versions on crates.io):

[dependencies]
rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk" }
oq3_semantics = "0.1"       # official Qiskit OpenQASM3 parser in Rust
spinoza = "2"               # pure Rust statevector engine
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "1"
tracing = "0.1"
tracing-subscriber = "0.3"

[dev-dependencies]
proptest = "1"
criterion = { version = "0.5", features = ["html_reports"] }

[[bench]]
name = "simulation"
harness = false

Architecture

src/
├── main.rs              # Entry point: MCP stdio server (rmcp), reads from stdin, writes to stdout
├── tools/
│   ├── mod.rs           # MCP tool registration
│   ├── list_backends.rs # list_backends handler
│   ├── validate_circuit.rs  # validate_circuit handler
│   └── run_circuit.rs   # run_circuit handler
├── validator.rs         # oq3_semantics wrapper → structured diagnostics
├── executor.rs          # OpenQASM 3 AST → Spinoza gates → counts
└── error.rs             # thiserror error types
tests/
├── integration/         # MCP JSON-RPC roundtrip, tool schema conformance
├── reference/           # QASM3 circuits + expected statevectors (generated by scripts/gen_reference.py)
│   └── invalid/         # Invalid circuits + exact expected error messages (golden files)
└── proptest/            # Property-based tests (unitarity, normalization)
benches/
└── simulation.rs        # Criterion benchmarks (Bell < 5 ms, 20 qubits < 500 ms)
scripts/
└── gen_reference.py     # Prerequisites: Python 3.11 + pip install qiskit qiskit-aer
.github/workflows/
├── ci.yml               # Rust tests + Qiskit cross-validation
└── release.yml          # cargo-dist (precompiled binaries)

Central trait: Backend (executor.rs). LocalSimulator implements Backend in V1. MCP tools depend on Backend, never on LocalSimulator directly.


1. Test-Driven Development — absolute rule

Red → Green → Refactor. In that order. Always.

  1. Write the failing test first.
  2. Write the minimum code to make it pass.
  3. Refactor without breaking the tests.

Never ship functional code without a test covering it. A feature without a test does not exist.

Types of tests to write (see spec §8)

  • Unit: individual gates, isolated behaviors — inside the module (#[cfg(test)])
  • Statistical: probabilistic counts with chi² test (N=10k shots, fixed seed)
  • Qiskit cross-validation: tests/reference/ — QASM3 circuits + expected statevectors generated by scripts/gen_reference.py
  • Property-based: quantum invariants with proptest (unitarity, normalization)
  • Golden files: invalid circuits → exact error messages in tests/reference/invalid/
  • MCP protocol: JSON-RPC roundtrip, schema conformance
  • Benchmarks: Criterion in benches/, SLA Bell < 5 ms, 20 qubits < 500 ms

Test rules

  • Tests must be deterministic. Use a fixed seed for any probabilistic simulation.
  • One test = one behavioral assertion. Do not test multiple things in a single test.
  • Test names describe behavior: apply_h_to_zero_gives_equal_superposition, not test_h.
  • Never comment out a failing test — fix it or remove the feature.

2. Clean Code

  • Expressive names: parse_circuit not parse, num_qubits not nq, QubitIndex not usize.
  • One function = one responsibility. If the name contains "and", it's two functions.
  • Max 3 parameters; beyond that: config struct. No boolean parameters: use an enum or two functions.
  • Comments = why only (hidden constraint, non-obvious invariant). Not "what".
  • No commented-out dead code. No TODO/FIXME committed to main.

3. SOLID Principles (project applications)

  • SRP: validator.rs = parse+validate only. executor.rs = translate AST+simulate only. Tools = orchestration only.
  • OCP: adding IBM (V1.5) does not touch executor.rsBackend trait is mandatory.
  • LSP: every impl Backend respects the contract: run() never panics, returns Result.
  • ISP: separate traits CanValidate / CanExecute / CanIntrospect. No fat trait.
  • DIP: tools depend on &dyn Backend, not on LocalSimulator. Gates injected via &dyn GateSet.

4. Rust Architecture

Error handling

  • No unwrap() or expect() in production code. Only in tests (if truly justified).
  • All errors propagate via Result<T, E> with an explicit error type (not Box<dyn Error>).
  • Use thiserror to define error types. Each variant has a clear message.
  • Errors exposed via MCP include: structured code, human-readable message, circuit position if applicable.
// Good
#[derive(thiserror::Error, Debug)]
pub enum ValidationError {
    #[error("gate '{gate}' not supported (line {line})")]
    UnsupportedGate { gate: String, line: usize },
}

// Forbidden
fn parse(input: &str) -> bool { ... } // error swallowed

Modules and visibility

  • Everything is private by default. pub is intentional, not the default.
  • A module's public API = its contract. Minimize it.
  • No cascading pub use without intent. Re-export only what belongs to the public interface.

Types and newtypes

  • Use newtypes for domain concepts: QubitIndex(usize), ShotCount(u32) rather than bare usize everywhere.
  • Do not pass raw primitives where a domain type exists.
  • Config structs are separate from State structs. Do not mix configuration and mutable state.

No magic numbers

// Forbidden
if qubits > 28 { ... }

// Good
const MAX_LOCAL_QUBITS: usize = 28;
if qubits > MAX_LOCAL_QUBITS { ... }

5. Refactoring Rules

  • Refactor after tests pass, never before.
  • A refactoring does not change observable behavior. If the tests change, it's no longer a refactoring.
  • The rule of three: first time, just code. Second time, code and grimace. Third time, abstract.
  • Do not abstract prematurely. Three similar lines are not necessarily an abstraction.

6. Git & Commits

  • One commit = one coherent intent. Do not mix feature + fix + refactoring in the same commit.
  • Commit messages in English, conventional format: feat:, fix:, test:, refactor:, chore:, docs:.
  • Tests and the production code they cover go in the same commit.
  • Never commit code that breaks tests (cargo test must pass).
  • Never commit code that does not compile.
  • Prefer readable atomic commits over a single large WIP commit.

7. Development Commands

# Bootstrap (first setup) — verifies dependencies compile
cargo build

# Run the MCP server (stdio) locally
cargo run

# f32 mode: +30% speed, +1 qubit at equal RAM (negligible precision loss)
cargo run -- --f32

# Claude Desktop config (~/.config/claude/claude_desktop_config.json)
# { "mcpServers": { "quantum-bridge": { "command": "/path/to/quantum-bridge-mcp" } } }

# Tests (all)
cargo test

# Tests + Qiskit cross-validation
cargo test && python3 scripts/gen_reference.py --validate

# Benchmarks
cargo bench

# Strict lint
cargo clippy -- -D warnings

# Format
cargo fmt --check

# Full check (before commit)
cargo fmt --check && cargo clippy -- -D warnings && cargo test

CI rejects any code that fails any of these checks.


8. Quantum Gotchas

  • Endianness / bit-ordering: Qiskit and Spinoza may use opposite qubit-ordering conventions. Cross-validation (tests/reference/) is there to catch this. Never assume — verify against golden files.
  • Probabilities vs counts: run_circuit returns counts (integers), not probabilities. Normalize on the Claude side if needed.
  • Fixed seed in tests: any probabilistic simulation in tests uses a fixed seed. Without this, statistical tests are flaky.
  • Qubit limit: statevector = 2^n × 16 bytes. 25 qubits = 512 MB. Validate num_qubits ≤ MAX_LOCAL_QUBITS before simulation.

9. Environment

Environment variables:

  • RUST_LOG=debug — enables tracing logs during development
  • IBM_QUANTUM_TOKEN — IBM token (V1.5 only, not used in V1)

Prerequisites for cross-validation (CI only):

python3 -m pip install qiskit qiskit-aer  # Python 3.11+
python3 scripts/gen_reference.py          # generates tests/reference/

10. What Is Forbidden

  • panic!(), unwrap(), expect() in production code
  • todo!(), unimplemented!() committed to main
  • unsafe without a comment block justifying why and proving safety
  • Dependencies added without justification in the commit message
  • Disabled tests (#[ignore]) without a linked issue and resolution date
  • Dead code (#[allow(dead_code)]) without justification