A low-cost USB protocol sniffer for Raspberry Pi RP2040, extended with a Model Context Protocol (MCP) server so that AI assistants can autonomously operate the hardware.
This is a fork of Alex Taradov's usb-sniffer-lite. The MCP extension was architected and implemented through a collaborative session with Claude (Anthropic), with the goal of making low-level USB hardware directly accessible to AI agents for debugging and reverse engineering workflows.
What this means in practice: Claude (or any MCP-capable assistant) can initiate a capture, stop it after a chosen duration, and ingest the results — either as a compact token-efficient summary or as a fully decoded transaction view — without any manual terminal interaction. The assistant observes raw USB traffic and reasons about it directly.
Supports Low Speed (1.5 Mbit/s) and Full Speed (12 Mbit/s) capture. The firmware enumerates as a USB CDC virtual COM port (no driver needed on any OS) plus a vendor bulk endpoint for high-throughput binary streaming.
The sniffer taps the D+ and D- lines of a USB cable. The RP2040 runs a PIO state machine that decodes NRZI signalling in real time on Core 1, while Core 0 handles USB device enumeration and data transfer to the host.
The MCP extension adds three layers on top of the original firmware:
- EP0 vendor control channel — four new USB control requests (
GET_STATUS,SET_MODE,SET_SPEED,START,STOP) added to the firmware, callable without claiming any interface or installing a driver. sniffer_device.py— a thin Python wrapper around those control requests.sniffer_mcp.py— a FastMCP server that exposes capture and analysis as tools.
Splice a short USB cable and connect D+ and D- directly to the RP2040 GPIO pins. No level shifting is needed; USB FS/LS signal levels are within the RP2040's tolerance.
| RP2040 Pin | Function | USB Cable Colour | Required |
|---|---|---|---|
| GND | Ground | Black | Yes |
| GPIO 10 | D+ | Green | Yes |
| GPIO 11 | D− | White | Yes |
| GPIO 12 | Start (internal) | — | No |
| GPIO 18 | Trigger | — | No |
| GPIO 25 | Status LED | — | No |
| GPIO 26 | Error LED | — | No |
The trigger input is internally pulled up; active low. When enabled in the settings, capture pauses until the trigger pin is pulled low — useful for marking a specific point of interest from the target device itself.
The spliced cable does not need to be pretty. Below is one that took under ten minutes:
A dedicated board integrating a FE8.1 USB hub is available, so only one host port is
needed for both the sniffer and the target. Schematics and Gerber files are in the
hardware directory.
More photos of a clean build on a custom RP2040 breakout
are in doc/Hardware.md.
cd firmware
mkdir build && cd build
PICO_SDK_PATH=/path/to/pico-sdk cmake ..
make -j$(nproc)
python3 ../patch_uf2_crc.py UsbSnifferLite.uf2 UsbSnifferLite_patched.uf2The patch_uf2_crc.py step is required — the custom boot sector needs its CRC
recalculated after linking.
Hold BOOTSEL and plug in (or tap reset while holding BOOTSEL), then:
# With picotool (recommended):
picotool load -f UsbSnifferLite_patched.uf2 --update -x
# Or copy to the mass storage drive:
cp UsbSnifferLite_patched.uf2 /Volumes/RPI-RP2/A pre-built binary is available in bin/.
Connect to the virtual COM port with any serial terminal (115200 baud, though baud rate is irrelevant for CDC). The following single-key commands are supported:
| Key | Action |
|---|---|
h |
Print help and current settings |
s |
Start capture |
p |
Stop capture |
b |
Display current buffer |
e |
Toggle capture speed (Low / Full) |
d |
Cycle stream mode (Disabled → Text/CDC → Raw/Bulk) |
g |
Toggle trigger (Enabled / Disabled) |
l |
Cycle capture limit |
t |
Cycle time display format |
a |
Cycle data display format |
f |
Toggle empty frame folding |
Example capture logs: USB FS enumeration · data transfer.
For decoding standard descriptors and requests, the USB Descriptor and Request Parser is very useful.
Requirements: pip install pyusb mcp
Add the server to your AI assistant's MCP configuration. Adjust the path to match where you cloned this repository.
Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json
on macOS, %APPDATA%\Claude\claude_desktop_config.json on Windows):
{
"mcpServers": {
"sniffer": {
"command": "python3",
"args": ["/path/to/usb-sniffer-MCP/firmware/sniffer_mcp.py"]
}
}
}Cursor — Settings → Features → MCP Servers → Add New:
- Type:
command - Name:
sniffer - Command:
python3 /path/to/usb-sniffer-MCP/firmware/sniffer_mcp.py
Zed (~/.config/zed/settings.json):
{
"experimental.mcp_servers": {
"sniffer": {
"command": "python3",
"args": ["/path/to/usb-sniffer-MCP/firmware/sniffer_mcp.py"]
}
}
}Restart the application after editing the config.
| Tool | Description |
|---|---|
sniffer_get_status() |
Read stream mode, capture speed, running state, and frame count |
sniffer_set_speed(speed) |
Set bus speed: "low" or "full". Safe while running; takes effect on next start |
sniffer_set_mode(mode) |
Set stream mode: "disabled", "text", or "raw" (raw auto-starts capture) |
sniffer_start() |
Start capture with current settings |
sniffer_stop() |
Stop an active capture |
sniffer_capture(output_path, duration_seconds, speed) |
High-level: configure, capture for N seconds, stop, return summary |
Once a .bin file has been written by sniffer_capture(), the assistant can read
it back in one of two modes depending on the task and token budget:
sniffer_read_raw(capture_path, max_frames, fold_sof) — low-token mode
One compact line per frame, SOFs folded into a count. Good for large captures, timeline reconstruction, and "what happened" queries. Approximately 25–40 tokens per frame.
--- 47 SOF frames ---
#0048 SETUP addr=0x05/ep=0
#0049 DATA0 [8B: 80 06 00 01 00 00 12 00]
#0050 ACK
#0051 IN addr=0x05/ep=0
#0052 DATA1 [18B: 12 01 00 02 00 00 00 40 66 66 10 66 01 01 ...]
#0053 ACK
sniffer_read_decoded(capture_path, max_transactions, fold_sof, filter_addr, filter_ep) — full-flavor mode
Groups frames into logical token→data→handshake transactions. Expands SETUP packets into named fields. Decodes standard USB descriptors (Device, Configuration, Interface, Endpoint, String). Supports filtering by device address and endpoint. Good for deep protocol analysis and unknown-device identification. Approximately 80–150 tokens per transaction.
#0048 SETUP addr=0x05 ep=0
bmRequestType = 0x80 (IN, Standard, Device)
bRequest = GET_DESCRIPTOR
wValue = 0x0100 (type=Device, index=0)
wIndex = 0x0000
wLength = 18
→ ACK
#0051 IN addr=0x05 ep=0
DATA1 [18B] 12 01 00 02 00 00 00 40 66 66 10 66 01 01 01 02 03 01
[Device Descriptor]
bcdUSB = 2.00
idVendor = 0x6666
idProduct = 0x6610
bcdDevice = 1.01
→ ACK
1. sniffer_set_speed("full")
2. sniffer_capture("session.bin", duration_seconds=10)
3. sniffer_read_raw("session.bin") # get the lay of the land
4. sniffer_read_decoded("session.bin", # drill into enumeration
filter_addr=0, max_transactions=50)
firmware/
capture.c / capture.h — PIO capture engine (Core 1)
usb_mcp.c / usb_mcp.h — EP0 vendor control request handler (new)
usb_bulk.c / usb_bulk.h — EP4 IN bulk streaming ring buffer
sniffer_device.py — USB control layer (pyusb wrapper)
sniffer_mcp.py — MCP server (FastMCP, 8 tools)
frame_parser.py — Binary frame reassembly and field extraction
packet_decoder.py — USB protocol interpretation (transactions, descriptors)
capture.py — Standalone bulk capture script
Original firmware and hardware design by Alex Taradov — BSD-3-Clause.
MCP extension developed by Stephen Arsenault in collaboration with Claude (Anthropic).

