Skip to content

Contributing

Thanks for your interest in mcp-ltspice-qucs. This document covers the basics of getting a development environment running and submitting changes.

Before you contribute

By submitting a pull request to this repository, you agree to the terms of CLA.md. A short contributor license agreement that lets the Maintainer change how this Project is licensed in future without needing to track down every past contributor individually. You keep your own copyright; you're just granting the Maintainer the same relicensing rights over your contribution that they already have over the rest of the codebase.

No signature or bot step is required today: opening the PR is the agreement. Read CLA.md before you submit if you want the full terms.

Quick setup

git clone https://github.com/RFingAdam/mcp-ltspice-qucs
cd mcp-ltspice-qucs
uv sync
uv run pytest -q

On a machine with no simulators installed you should see 533 tests pass and 11 skip, and no failures. Every skip is gated on a missing binary (LTspice / ngspice / Qucs-S / Xyce) or is a tool with no required argument to make invalid; install a simulator (see the Installation guide) to exercise the simulator-driven paths.

Before opening a PR, run the same three commands CI runs. If these are clean, CI will be too:

uv run ruff format --check .
uv run ruff check .
uv run pytest -q

Layout

The repo is a uv workspace with four packages under packages/:

  • rf-mcp-common: shared envelope, Touchstone I/O, E-series snap, logging
  • mcp-ltspice: LTspice + ngspice runner, filter synthesis, spec eval
  • mcp-rf-analysis: skrf wrappers, regulatory band DBs, coex matrix
  • mcp-qucs-s: Qucs-S native S-param + harmonic balance + microstrip

See ARCHITECTURE for the inter-server contract.

Running tests

uv run pytest                          # all packages
uv run pytest packages/mcp-ltspice/    # one package
uv run pytest -k "synthesis"           # by keyword
uv run pytest -m ngspice               # only simulator-integration
uv run pytest --cov                    # with coverage

Simulator-gated tests

Tests that need a real simulator carry a marker and skip cleanly when the binary is absent: ngspice, ltspice, qucs, xyce. Availability is decided by the package's own discovery function (e.g. mcp_ltspice.runner.find_ltspice), so anything the runner can find: $LTSPICE_PATH, $WINEPREFIX, the standard Windows / macOS / Wine install paths: also un-skips the tests.

If -m ltspice skips when you believe LTspice is installed, run:

uv run python -c "from mcp_ltspice.runner import find_ltspice; print(find_ltspice())"

A None there means the runner cannot see it either. That is the bug to report, not the skip. Setting $LTSPICE_PATH to a file that does not exist logs a warning rather than failing silently.

To pin which simulator is used regardless of what is installed:

MCP_LTSPICE_SIMULATOR=ngspice uv run pytest -m ngspice

Linting & typing

Both run via the uv-managed dev tools:

uv run ruff format          # auto-format
uv run ruff check --fix     # autofix what's safe
uv run mypy packages/       # strict types

Pre-commit hooks (configured in .pre-commit-config.yaml, enable with uv run pre-commit install) run ruff format + ruff check + mypy on every commit.

Adding a new tool to a server

  1. Implement the underlying logic in a new module under packages/<server>/src/<pkg>/. Pure functions. Don't touch FastMCP yet.
  2. Write tests under packages/<server>/tests/ that exercise it without any simulator dependency where possible.
  3. Wire it into server.py with @mcp.tool(description=...). Wrap the call in Timer() + ok(...) / error(...) so the response envelope stays consistent.
  4. Run uv run pytest packages/<server> and confirm green.
  5. Add an entry to the per-server README.md tool catalogue.
  6. Update CHANGELOG.md under [Unreleased].

Adding a new MCP server

Three steps:

  1. Scaffold packages/<new>/src/<module>/ with the same layout as one of the existing servers. Add the module to tool.hatch.build.targets.wheel.packages (and its src to the sdist only-include), its command to project.scripts, and any new dependencies to project.dependencies in the root pyproject.toml.
  2. Import the envelope and Touchstone helpers from rf_mcp_common. Don't reimplement either.
  3. Use Touchstone files for any inter-server data exchange. The contract is documented in ARCHITECTURE.

Testing conventions

  • One module of source ↔ one test file, named test_<module>.py.
  • Tests should not write to the repo working tree; use the tmp_path pytest fixture for any file output.
  • Fixtures that build sample networks live in the package's conftest.py, not at the repo root, so packages stay decoupled.
  • Mark simulator-requiring tests with @pytest.mark.ngspice or @pytest.mark.ltspice so they skip cleanly when the simulator is absent.

Commit messages

Conventional Commits style: <type>(<scope>): <subject>. Common types: feat, fix, docs, test, refactor, chore, ci, perf.

Examples:

  • feat(rf-analysis): add 5G NR FR2 band lookup
  • fix(ltspice): handle resonance singularity in shunt LC trap
  • docs: clarify Touchstone interop contract

Releases

We follow Keep a Changelog + Semver. The repository publishes one PyPI package, mcp-ltspice-qucs, with one version:

  • Bump version in the root pyproject.toml and run uv lock
  • Move entries from [Unreleased] to a new dated section in CHANGELOG.md
  • Merge to main, then push a v<version> tag on that commit

The Release workflow (.github/workflows/release.yml) builds one sdist and one wheel, checks them, and publishes to PyPI through trusted publishing from the pypi environment.

Code of conduct

We follow the Contributor Covenant. See CODE_OF_CONDUCT.md.