9.1 KiB
9.1 KiB
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.
- Write the failing test first.
- Write the minimum code to make it pass.
- 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 byscripts/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, nottest_h. - Never comment out a failing test — fix it or remove the feature.
2. Clean Code
- Expressive names:
parse_circuitnotparse,num_qubitsnotnq,QubitIndexnotusize. - 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/FIXMEcommitted 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.rs—Backendtrait is mandatory. - LSP: every
impl Backendrespects the contract:run()never panics, returnsResult. - ISP: separate traits
CanValidate/CanExecute/CanIntrospect. No fat trait. - DIP: tools depend on
&dyn Backend, not onLocalSimulator. Gates injected via&dyn GateSet.
4. Rust Architecture
Error handling
- No
unwrap()orexpect()in production code. Only in tests (if truly justified). - All errors propagate via
Result<T, E>with an explicit error type (notBox<dyn Error>). - Use
thiserrorto 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.
pubis intentional, not the default. - A module's public API = its contract. Minimize it.
- No cascading
pub usewithout intent. Re-export only what belongs to the public interface.
Types and newtypes
- Use newtypes for domain concepts:
QubitIndex(usize),ShotCount(u32)rather than bareusizeeverywhere. - Do not pass raw primitives where a domain type exists.
Configstructs are separate fromStatestructs. 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 testmust 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_circuitreturns 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. Validatenum_qubits ≤ MAX_LOCAL_QUBITSbefore simulation.
9. Environment
Environment variables:
RUST_LOG=debug— enables tracing logs during developmentIBM_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 codetodo!(),unimplemented!()committed to mainunsafewithout 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