Comma Panda Mcp — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Comma Panda Mcp (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
<div align="center">
Reverse-engineer your car's CAN bus by talking to Claude.
An MCP server that turns a comma.ai Panda into a conversational CAN reverse-engineering rig — record → diff → find the signal → confirm → send it back — without writing a line of glue code.
</div>
Finding the one CAN frame that controls your blinker — or your steering angle, or your door locks — is a slow, fiddly loop: log traffic, do the thing, log again, diff bytes by hand, guess the CRC, replay it, repeat. Every step lives in a different script.
panda-mcp collapses that loop into a conversation. You tell Claude "record the bus, I'll flip the blinker, now find what changed" and it drives the Panda, runs comma.ai's own diff heuristics, cracks the CRC, and replays the candidate frame — all through 26 typed MCP tools.
And because it ships with a full mock CAN bus, you can learn the entire workflow on your laptop on the train home, with no Panda and no car.
You: Connect in mock mode, record a baseline, then record again — I'll toggle the blinker.
Claude: [device_connect → capture_start → capture_stop ×2]
You: Which bits only showed up while blinking?
Claude: [analyze_diff_new_bits] → 0xE1, byte 0 bit 0 flipped 0→1. That's your blinker.
You: Prove it. Send it back.
Claude: [device_set_safety_mode('alloutput') → send_frame 0xE1 ...] → sent 20×. 🚗💡can_bit_transition (clean 0→1/1→0 flips across a split) and can_unique (bits that appear only against a baseline).log for Cabana / SavvyCAN / can-utilsgit clone https://github.com/<you>/panda-mcp.git
cd panda-mcp
py -m venv .venv
.venv/Scripts/python -m pip install -e . # mock mode only
.venv/Scripts/python -m pip install -e ".[panda]" # + real hardware (pandacan)Register the server with Claude Code by pointing it at the bundled claude.json:
{
"mcpServers": {
"panda": {
"command": ".venv/Scripts/python.exe",
"args": ["-m", "panda_mcp.server"],
"env": { "PYTHONPATH": "src" }
}
}
}Then just ask Claude to device_connect(mock=true) and start exploring.
No Panda? No car? No problem. device_connect(mock=true) spins up a deterministic synthetic CAN stream designed to make every analysis tool light up:
| Arb ID | Rate | Payload | Demonstrates |
|---|---|---|---|
0x1A0 | 20 Hz | rolling counter nibble + J1850 CRC8 over bytes 0–6 | crc_detect_field, crc_brute_force |
0x2B0 | 10 Hz | static DEADBEEF… | baseline noise for diffs |
0x3C0 | 50 Hz | int16 "steering angle" sweeping via sin(t) | analyze_find_value |
0x0E1 | 1 Hz | one toggle bit driven by a virtual "blinker" | analyze_diff_transition |
Everything you learn in mock mode maps 1:1 onto real hardware — only the mock=true flag changes.
┌──────────────┐ MCP tools ┌─────────────────────────────────────────┐
│ Claude │ ─────────────► │ panda_mcp.server │
│ (you, chat) │ ◄───────────── │ FastMCP · 26 tools │
└──────────────┘ results └───────────────────┬─────────────────────┘
│
┌────────────────────────┬───────────────────┼────────────────────┐
▼ ▼ ▼ ▼
┌────────────┐ ┌──────────────┐ ┌──────────────┐ ┌────────────┐
│ device.py │ │ session.py │ │ analysis.py │ │ crc.py │
│ Panda + │ │ bg-threaded │ │ bit-state │ │ automotive │
│ MockPanda │ │ capture store│ │ diff engines │ │ CRC engine │
└─────┬──────┘ └──────────────┘ └──────────────┘ └────────────┘
│ USB
▼
┌────────────┐
│ Panda Red │ STM32H725 · CAN/CAN-FD
│ 🚗 car │
└────────────┘| Module | Responsibility |
|---|---|
server.py | FastMCP entry point — all 26 tools, argument parsing, safety gating |
device.py | Panda abstraction + MockPanda synthetic stream + safety-mode constants |
session.py | Background-threaded capture store and snapshots (all in-memory) |
analysis.py | Bit-state tracking, the two diff engines, find_value, per-ID stats |
crc.py | CRC catalogue, brute-force identification, CRC-field detection |
model.py | The CanFrame dataclass |
<table> <tr><th>Group</th><th>Tool</th><th>What it does</th></tr>
<tr><td rowspan="6"><b>Device</b></td> <td><code>device_connect</code></td><td>Connect to a Panda (or <code>mock=true</code>)</td></tr> <tr><td><code>device_status</code></td><td>Connection, firmware, safety mode, bus errors</td></tr> <tr><td><code>device_set_safety_mode</code></td><td><code>silent</code> (listen-only) / <code>alloutput</code> (unlock TX)</td></tr> <tr><td><code>device_set_can_speed</code></td><td>Set a bus bitrate in kbps</td></tr> <tr><td><code>device_flash</code></td><td>Flash stock or custom firmware</td></tr> <tr><td><code>device_recover</code></td><td>DFU recovery for a bricked device</td></tr>
<tr><td rowspan="5"><b>Capture</b></td> <td><code>capture_start</code></td><td>Begin recording (bus/ID/duration/count filters)</td></tr> <tr><td><code>capture_stop</code></td><td>Stop and summarize</td></tr> <tr><td><code>capture_list</code></td><td>List all captures</td></tr> <tr><td><code>capture_get</code></td><td>Page through raw frames</td></tr> <tr><td><code>capture_delete</code></td><td>Discard a capture</td></tr>
<tr><td rowspan="4"><b>Analyze</b></td> <td><code>analyze_stats</code></td><td>Per-ID count, period, length, dynamic bytes</td></tr> <tr><td><code>analyze_diff_transition</code></td><td>Clean bit flips before/after a split time</td></tr> <tr><td><code>analyze_diff_new_bits</code></td><td>Bits unique to a capture vs. baselines</td></tr> <tr><td><code>analyze_find_value</code></td><td>Locate a number → ID + offset + endianness</td></tr>
<tr><td rowspan="2"><b>Snapshot</b></td> <td><code>snapshot_take</code></td><td>Freeze latest payload per (bus, ID)</td></tr> <tr><td><code>snapshot_diff</code></td><td>XOR-diff two snapshots</td></tr>
<tr><td rowspan="4"><b>Send</b></td> <td><code>send_frame</code></td><td>Transmit one frame (optionally repeated)</td></tr> <tr><td><code>send_bulk</code></td><td>Transmit many frames at once</td></tr> <tr><td><code>send_fuzz</code></td><td>Sweep one byte across a range</td></tr> <tr><td><code>send_replay</code></td><td>Replay a capture with original timing</td></tr>
<tr><td rowspan="4"><b>CRC</b></td> <td><code>crc_compute</code></td><td>Compute a catalogued CRC over hex data</td></tr> <tr><td><code>crc_list</code></td><td>List CRC algorithms + parameters</td></tr> <tr><td><code>crc_brute_force</code></td><td>Identify the algorithm (with init sweep)</td></tr> <tr><td><code>crc_detect_field</code></td><td>Find the CRC byte + algo across a capture</td></tr>
<tr><td><b>Export</b></td> <td><code>export_candump</code></td><td>Write a candump <code>.log</code> (Cabana/SavvyCAN)</td></tr> </table>
device_connect(mock=true)
capture_start() → cap-aaaa # baseline: do nothing
capture_stop(cap-aaaa)
capture_start() → cap-bbbb # now toggle the blinker
capture_stop(cap-bbbb)
analyze_diff_new_bits(cap-bbbb, [cap-aaaa]) # bits unique to "blinking"
# …or, if you toggled mid-capture:
analyze_diff_transition(cap-bbbb, split_ts=5.0)
device_set_safety_mode('alloutput')
send_frame(arb_id='0xE1', data='01000000000000', bus=0, count=20, interval_ms=50)analyze_find_value(cap-xxxx, value=2000, bit_length=16)
# → 0x3C0, byte_offset 0, little-endian, 14 matchescrc_detect_field(cap-xxxx, arb_id='0x1A0')
# → crc_index 7, confirmed: crc8_sae_j1850 (init 0xFF) across 30 frames ✅
crc_brute_force(frame='10123456780000CC') # single-frame identification
crc_compute(data='10123456780000', algorithm='crc8_sae_j1850')You do _not_ need custom firmware to send frames. Transmission is unlocked in software via the safety mode:
device_set_safety_mode('alloutput')device_flash / device_recover exist only for firmware updates, custom on-device safety logic, or un-bricking:
device_flash() — build & flash the stock firmware (the Panda's own flash())device_flash('/path/fw.bin') — flash a custom binarydevice_recover() — DFU recovery for an unresponsive deviceThe Panda Red is an STM32H725; flashing runs over USB DFU (VID 0x0483, PID 0xdf11). The device reboots afterward — call device_connect again.
[!WARNING] Injecting frames onto a moving vehicle's powertrain bus can disable brakes, steering, or throttle. People can get hurt.
silent). Transmission requires an explicit device_set_safety_mode('alloutput') — there is no way to send by accident..venv/Scripts/python -m pytest -q # 7 hardware-free end-to-end testsThe whole test suite runs against MockPanda — capture, both diff engines, find_value, CRC detection, the safety gate, and replay — so CI never needs a device.
src/panda_mcp/
server.py FastMCP entry point · all 26 tools
device.py Panda abstraction + MockPanda synthetic stream
session.py background-threaded capture store + snapshots
analysis.py bit-state diffing + find_value + per-ID stats
crc.py automotive CRC catalogue + brute force + field detection
model.py CanFrame
tests/
test_smoke.py hardware-free end-to-end testscantools — decode/encode against a .dbc.candump/CSV as sessionsContributions welcome — open an issue or PR. If you add a tool, add a mock signal that exercises it so the test suite stays hardware-free.
The two diff engines are faithful ports of comma.ai's own examples — can_bit_transition.py and can_unique.py. Hardware access is via the pandacan library. Huge thanks to comma.ai for open-sourcing the Panda.
Released under the MIT License — same as panda.
<div align="center"> <sub>Built for CAN tinkerers. Not affiliated with comma.ai.</sub> </div>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.