Skip to content

Development and Testing

farzad edited this page Aug 19, 2026 · 1 revision

Development and Testing

This project is intentionally small and educational — contributions should keep that spirit: prefer clarity over cleverness, and reuse existing patterns (see Architecture) rather than introducing new ones for a one-off feature. The canonical version of this guidance lives in CONTRIBUTING.md; this page expands on it.

Local setup

python -m venv .venv
.venv/Scripts/activate   # or `source .venv/bin/activate` on Linux/macOS
pip install -r requirements.txt
pip install -e .
pip install pytest ruff mypy

Before opening a PR

ruff check .
mypy src
pytest -q

All three must pass — this mirrors .github/workflows/ci.yml exactly (Python 3.10 and 3.11). mypy is kept at zero errors deliberately; suppress with a targeted # type: ignore only if genuinely unavoidable, not as a first resort.

Beyond the test suite: a live smoke test

Unit tests mock the CAN bus (tests/test_cli.py's FakeBus) — nothing in the suite starts a real server or virtual bus, so wiring/transport-level regressions won't show up there. For a real sanity check:

mcp-can demo --port 6278

then, in another shell, use MCP Inspector (npx @modelcontextprotocol/inspector, connect to http://localhost:6278/sse) or a small script with mcp.client.sse.sse_client/ClientSession to call a tool end-to-end.

Code layout, and where new code belongs

  • bus.py, dbc.py, obd.py, diagnostics.py — protocol/bus logic, no MCP or CLI awareness. New protocol behavior belongs here, not in server/ or cli.py.
  • server/fastmcp_server.py — MCP tool/resource definitions. server/schemas.py — the Pydantic models those tools return. server/live_state.py — the single background listener backing both the passive-listening tools and the dashboard SSE stream; passive tools should query live_state.frames_since(...), not open their own bus connection (see Architecture for why that used to be a real bug). Request/response tools (send_obd_request, send_diagnostic_request, activate_fault_scenario) are the exception — they open their own short-lived make_bus() instance.
  • simulator/runner.py — the simulator threads (SimThread, OBDResponderThread, DiagnosticResponderThread, FaultListenerThread). Each bus listener thread needs its own make_bus(...) instance — see Architecture.
  • cli.py — the mcp-can Typer CLI; mirrors the MCP tool surface where practical so both interfaces stay in sync.

Adding a new MCP tool

  1. Add the protocol logic (encode/decode/whatever) to a plain module first — testable without any bus or MCP machinery.
  2. Add a Pydantic return model to server/schemas.py.
  3. Register the tool in server/fastmcp_server.py::create_app(). Passive tool → read from live_state.frames_since(...) (see read_can_frames). Request/response tool → open its own bus with make_bus(), always shutdown_bus() in a finally (see send_obd_request). Either way, cap any duration_s/timeout_s against settings.max_duration_s.
  4. If the CLI should expose the same capability, mirror it as a Typer command in cli.py.
  5. Add a test: pure-logic tests belong next to the module they test (tests/test_diagnostics.py, tests/test_obd.py, tests/test_faults.py are good examples); if the tool touches the bus, prefer a FakeBus-style unit test (tests/test_cli.py) or the in-process TestClient(app.sse_app()) pattern (tests/test_server_app.py) over spinning up a real server/subprocess in the suite.

Test suite map

File Covers
test_dbc.py DBC loading, decode_frame, signal_int
test_obd.py OBD-II request/response parsing, PID decoding, DTC encode/decode
test_diagnostics.py UDS-style handle_service logic
test_decode_roundtrip.py Encode → decode roundtrip via cantools
test_state.py Correlated driving-state tick(), and a regression test that actually encodes CORRELATED_SIGNALS output via cantools (not just range-checks it) — see Architecture for the bug this class of test catches
test_faults.py Fault preset activation, control-frame roundtrip, every preset's overrides target a real signal and actually encode
test_cli.py CLI commands against a FakeBus
test_server_app.py MCP tool registration, /healthz//dashboard routes, CORS behavior, the frame-history-buffer tools against an in-process TestClient

Releasing to PyPI

See Docker and Deployment.

Reporting issues

Open a GitHub issue. Include your OS, Python version, and — if it's a CAN/MCP issue — whether you're using the virtual backend or real hardware (MCP_CAN_CAN_INTERFACE).

Clone this wiki locally