A safety-first interface to a HackRF One software-defined radio, in two layers:
hackrf-api— a localhost HTTP proxy that is the only thing allowed to touch the device. It validates every parameter, holds an exclusive session lease (with heartbeat + reaper so a dead client can't starve the radio), serializes hardware access, and runs thehackrf_*tools via a scopedsudorule. Receive-only.hackrf-mcp— an MCP server (stdio) that exposes the proxy to an agent (Claude Code) as typed tools. It owns one session on the agent's behalf and heartbeats it; when the agent disconnects, the lease auto-releases.
agent ──MCP(stdio)──▶ hackrf-mcp ──HTTP──▶ hackrf-api ──sudo hackrf_*──▶ HackRF One
(trust boundary: validation, lease, serialization)
- macOS or Linux (Windows untested). Privilege model differs by OS — see
internal/device/device.go's package doc comment for the details; the short version:- macOS: needs Homebrew's
hackrftools and the scoped sudoers rule installed bysetup_hackrf_sudo.sh(macOS's libusb/IOKit path requires root to open the device). - Linux: install the distro's
hackrf/libhackrf0packages (e.g.apt install hackrf libhackrf0) — no sudoers rule needed or used. Those packages ship a udev rule (typically/lib/udev/rules.d/60-libhackrf0.rules) that sets the device node to groupplugdev, mode0660; add the running user toplugdev(sudo usermod -aG plugdev $USER, then re-login ornewgrp plugdev) andhackrf_infoworks with no sudo at all. If the device was already plugged in before installing the packages, replug it (orsudo udevadm control --reload-rules && sudo udevadm trigger) so the new rule actually applies to the existing device node.
- macOS: needs Homebrew's
- A HackRF One on USB.
- Go 1.26+.
go build -o bin/hackrf-api ./cmd/hackrf-api
go build -o bin/hackrf-mcp ./cmd/hackrf-mcp
./scripts/start-api.sh # start the proxy (keep running)
claude mcp add hackrf -- "$PWD/bin/hackrf-mcp" -base http://127.0.0.1:8723| Method | Path | Purpose |
|---|---|---|
| GET | /health |
liveness + device-busy |
| GET | /info |
device identity (session-less) |
| POST | /sessions |
open the lease; body {settings} |
| GET | /sessions/{id} |
session + jobs |
| POST | /sessions/{id}/heartbeat |
keep the lease alive |
| DELETE | /sessions/{id} |
release the lease |
| POST | /sessions/{id}/jobs |
enqueue a job (see below) |
| GET | /sessions/{id}/jobs/{jid} |
poll job state/result |
| POST | /sessions/{id}/jobs/{jid}/stop |
stop a job |
| GET | /sessions/{id}/jobs/{jid}/latest |
latest stream frame (snapshot) |
| GET | /sessions/{id}/jobs/{jid}/events |
detected signal events |
| GET | /sessions/{id}/jobs/{jid}/stream |
live SSE frame feed |
Job types: sweep, capture (oneshot) and spectrum_stream, iq_stream
(streaming). Settings/overrides: sample_rate_hz, lna_gain (0–40/8dB),
vga_gain (0–62/2dB), amp_enable.
hackrf_info, hackrf_open_session, hackrf_sweep, hackrf_capture,
hackrf_start_spectrum_stream, hackrf_get_spectrum, hackrf_get_events,
hackrf_stop_stream, hackrf_close_session.
- Receive-only by default. The transmit path is compiled-in but disabled
unless the server is started with
-enable-tx. The MCP/agent surface exposes no transmit tools at all — an agent cannot key the radio. - The
sudocancel uses SIGTERM (forwarded by sudo), not SIGINT/SIGKILL, so stopping a stream never orphans a roothackrf_*process holding the device. - Captures are interleaved int8 IQ (2 bytes/sample) under
data/captures/.
If -enable-tx is set, every transmit request must pass a deny-by-default
policy (internal/txpolicy):
- An absolute denylist of safety-of-life / protected bands (GPS, aviation incl. ILS & 121.5/243 MHz, radio altimeter, cellular, public-safety, marine distress) — enforced in every mode, including the broad override.
- A mode (
dummy_load,amateur,ism, or a hard-gatedoverride_broad) with its own allowlist, required attestations, and power cap. - A final
confirm_transmitflag — authorization alone never keys the radio; the request must explicitly confirm intent to emit.
The HackRF One is not an FCC-certified transmitter and has no output filtering. Transmitting may be illegal without appropriate authorization (e.g. an amateur radio license, or use into a shielded dummy load). You are solely responsible for operating within your local laws and license privileges. The policy layer is a conservative backstop, not a guarantee of legal compliance. Receiving is generally unrestricted; transmitting is not.
GNU General Public License v3.0 — you may use, modify, and redistribute, provided derivative works remain under the GPLv3.