mcp server
mcp-sigdetect
MCP server: SDR signal analysis (OOK/FSK demod, line-code decoding, protocol ID) callable from Claude or any MCP client
Set up
Runs on your computer. Pick your agent and operating system.
Before you start
- uv (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
{
"mcpServers": {
"mcp-sigdetect": {
"command": "uvx",
"args": [
"mcp-sigdetect"
]
}
}
}same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12
Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs
README
synced Oct 27, 2026mcp-sigdetect
An MCP (Model Context Protocol) server that turns Claude (or any
MCP-aware client) into an autonomous SDR signal analyst. Hand it an IQ
capture and it will work through the methodology in
PLAYBOOK.md — find bursts, identify the modulation,
demodulate, recover bits, decode the line code, extract hex payloads,
and suggest a protocol — using only numpy for DSP. No GNU Radio, no
external decoders required.
What you get
Twelve callable tools, each a single step from the playbook:
| Tool | Purpose |
|---|---|
inspect_iq | basic stats + parse filename for sample rate / freq |
find_bursts | activity windowing + cluster retransmission sets |
identify_modulation | classify one burst as OOK / FSK / PSK / LoRa / unknown |
demod_ook | hysteresis pulse-train extraction |
demod_fsk | discriminator + autocorrelation bit-clock recovery |
decode_pwm | pulse-width → bits |
decode_pdm | gap-distance → bits, with sync detection |
decode_manchester | IEEE / G.E. Thomas chip-pair decoding |
bits_to_hex | pack bits to hex (MSB- or LSB-first) |
find_sync | search a bit string for a sync byte (any orientation) |
identify_protocol | match (freq, modulation, bitrate, sync) → known protocol |
run_pipeline | end-to-end best-effort pass |
Verified on three reference captures (433 MHz keyfob, 908 MHz Z-Wave, 315 MHz beacon). See Validation below.
Install
Requires Python 3.10+.
git clone https://github.com/haxorthematrix/mcp-sigdetect.git
cd mcp-sigdetect
python3 -m venv .venv
source .venv/bin/activate # or .venv\Scripts\activate on Windows
pip install -e .
The install pulls in mcp[cli] and numpy. That's it.
Use it from Claude Code
# One-time setup
claude mcp add sigdetect -- \
/absolute/path/to/mcp-sigdetect/.venv/bin/python -m sigdetect.server
Then in Claude Code:
"Use the sigdetect server to analyze the IQ file at
/path/to/capture.cs16and tell me what the device is and what it transmitted."
Claude will plan the call sequence (usually inspect_iq →
find_bursts → identify_modulation → demod → decode → hex →
protocol), invoke the tools, and write you a report. You can also
ask it to call specific tools individually if you want manual control.
Use it from Claude Desktop
Edit ~/Library/Application Support/Claude/claude_desktop_config.json
(macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"sigdetect": {
"command": "/absolute/path/to/mcp-sigdetect/.venv/bin/python",
"args": ["-m", "sigdetect.server"]
}
}
}
Restart Claude Desktop. The tools appear under the connector indicator at the bottom of the chat.
Use it from the Anthropic API / SDK
import anthropic
client = anthropic.Anthropic()
msg = client.messages.create(
model="claude-opus-4-7",
max_tokens=4096,
mcp_servers=[{
"type": "stdio",
"command": "/path/to/mcp-sigdetect/.venv/bin/python",
"args": ["-m", "sigdetect.server"],
}],
messages=[{
"role": "user",
"content": "Analyze /path/to/capture.cs16 and report findings",
}],
)
print(msg.content[-1].text)
(The exact MCP attachment surface evolves with the SDK; check the Anthropic Python SDK docs at time of use.)
Use it standalone (no LLM)
You can drive every tool from a Python REPL:
from sigdetect.server import run_pipeline
result = run_pipeline(path="my-capture.cs16",
sample_rate=2_000_000.0,
center_freq_hz=433_920_000.0)
import json; print(json.dumps(result, indent=2))
Or call the lower-level modules directly:
from sigdetect.io import load_iq
from sigdetect.activity import find_bursts
from sigdetect.modulation import identify_modulation
from sigdetect.demod import ook_pulses
from sigdetect.encoding import decode_pdm, extract_repeated_blocks, bits_to_hex
iq, meta = load_iq("my-capture.cs16")
bursts = find_bursts(iq, meta.sample_rate)
s, e = max(bursts, key=lambda b: b[1] - b[0])
mod = identify_modulation(iq, meta.sample_rate, s, e)
print(mod.modulation, mod.confidence)
pulses = ook_pulses(iq[s:e], meta.sample_rate)
pdm = decode_pdm(pulses.gap_widths_us)
reps = extract_repeated_blocks(pdm.bits)
print(reps["top_block"], "x", reps["repeats"],
"→", bits_to_hex(reps["top_block"], msb_first=True))
Use it from URH
There is a companion fork of URH —
urh-ng-ai — that adds
an AI Deep Analysis button to each signal panel. The button
exports the currently-selected IQ slice and routes it through this
server. See that repo for installation.
Supported IQ file formats
Filename extension drives detection; override with fmt=:
| Extension | dtype | bytes/sample | typical source |
|---|---|---|---|
.cs16, .complex16s | int16 I/Q | 4 | HackRF default |
.cu8 | uint8 I/Q | 2 | RTL-SDR native |
.cs8 | int8 I/Q | 2 | |
.cf32, .complex | float32 I/Q | 8 | GNU Radio default |
Filename can encode parameters; common patterns are auto-parsed:
| Pattern | Resolves to |
|---|---|
433_92MHz / 915MHz | center freq |
2MSps / 8MSps | sample rate |
trailing -315 | 315 MHz (compact form) |
trailing -90842 | 908.42 MHz (5-digit / 10 kHz units) |
trailing -4339 | 433.9 MHz (4-digit / 100 kHz units) |
If parsing fails, pass sample_rate= and center_freq_hz=
explicitly to any tool.
Modulation decision tree
identify_modulation runs four tests and reconciles them:
- Magnitude variance (
std/meanof strong samples) — high variance is consistent with OOK. - Bimodal magnitude distribution — high fractions of both near-zero and near-peak samples confirm OOK; near-zero alone indicates low-duty-cycle PDM-style OOK.
- Instantaneous frequency histogram — two or more clear modes indicate FSK; tone separation gives deviation.
- Spectrum peaks — recorded for reporting; symmetric ±Δ peaks can be either DSB-SC AM or FSK at ±Δ deviation (test #3 discriminates).
PSK and LoRa are recognized but not demodulated end-to-end; hand off
to GNU Radio / gr-lora_sdr if you have one of those.
Supported encodings
| Encoding | Recovers from | Typical use |
|---|---|---|
| PWM | pulse-widths → bit value | PT2262, HCS300, EV1527 |
| PDM / PIWM | gap durations → bit value, sync markers | gate remotes, weather sensors |
| Manchester | chip pairs (IEEE or G.E. Thomas) | TPMS, some RFID |
| NRZ FSK | inst-freq sign at bit centers | Z-Wave R2/R3, BLE |
decode_pdm knows about repeating-block structures (preamble + sync
- data block × N) and returns the most-common block via
extract_repeated_blocks. That's how the 433.92 MHz keyfob test case recovers identical 41-bit codes across all repeats.
Validation
Tested against three reference captures (not included in this repo; provide your own). For each, the expected output:
433.92 MHz gate remote (OOK PDM, two button presses each pressed twice):
bursts: 4 (in 4 clusters)
modulation: OOK (conf 0.97)
burst 1: hex_msb = 41 90 4D 22 40 00 (14 repeats of 41-bit code)
burst 2: hex_msb = 41 90 4D 21 3F 00 (13 repeats)
burst 3: hex_msb = 41 90 4D 22 40 00 (= button 1, repressed)
burst 4: hex_msb = 41 90 4D 21 3F 00 (= button 2, repressed)
protocol: 433.92 MHz remote / weather (OOK PDM or PWM)
908.42 MHz Z-Wave (GFSK, 100 kbps):
bursts: 42 (in 4 clusters)
modulation: FSK (conf 0.86) -- ±18 kHz deviation around carrier
sync byte 0xF0 found in every burst
protocol: Z-Wave US (Ch1 908.40 R2, Ch2 908.42 R3) conf=0.70
315 MHz OOK beacon (preamble-dominated):
bursts: 8 (in 4 clusters)
modulation: OOK (conf 0.94)
hex: (no clean data extracted — long alternating preamble, no varied
payload visible in the envelope)
protocol: 315 MHz car RKE / TPMS / garage opener (OOK)
A test_pipeline.py is included that runs run_pipeline() against any
file path you point it at.
Known limitations
- FSK bit-rate estimation on whitened / random data is hard
because there's no periodic pattern for the autocorrelator to lock
to.
run_pipelineapplies protocol-aware hints (e.g. Z-Wave US → retry at 100 kbps). For other protocols, passbit_rate_bpsexplicitly todemod_fsk. - PSK and LoRa are not demodulated. Identification only.
- Big captures (>100 M samples) work but the hysteresis OOK loop
is a Python
forand gets slow. Rewrite in Cython if you need it hot. - DPSK, DBPSK, OQPSK, MSK, GMSK beyond what GFSK demod recovers via discriminator — handle with GNU Radio.
Contributing
PRs welcome, especially:
- More entries in
sigdetect/protocol.py(any 868 / 915 MHz protocol) - A real PSK / Costas-loop demodulator
- LoRa de-chirp + Hamming + whitening
- Better bit-rate estimation for whitened FSK (PLL or Gardner loop)
Run validate.py against your test data before sending the PR.
License
MIT. See LICENSE.
See also
PLAYBOOK.md— the methodology this server implementsurh-ng-ai— companion URH fork with an "AI Deep Analysis" button that calls this server- Anthropic MCP — protocol docs