-
Notifications
You must be signed in to change notification settings - Fork 0
MCP Walkthrough
MCP Server is the reference: every flag, tool, and error code. This page walks a first-time sipnab user through each deployment scenario, command by command, on every machine involved. Steps are tagged with the host they run on: [server] (where sipnab runs), [laptop] (where your MCP client / Claude Code runs), or [proxy] (your SIP proxy, in the HEP scenario).
Every command here was verified end to end against a real build at 0.5.20. The
flag names are held to the current CLI by docs_drift_test, but the walkthrough
itself has not been re-run since, so treat the transcript as illustrative rather
than freshly measured.
The client steps use Claude Code; the server side is identical for every MCP-capable agent. If you drive Codex CLI, Cursor, VS Code, Gemini CLI, or Windsurf instead, do the same scenario and swap the registration step — Registering other MCP clients has the exact config for each.
| You have | You want | Scenario |
|---|---|---|
| A pcap and an agent on the same machine | Interactive post-mortem | 1 |
| sipnab on a production server, Claude Code on your laptop | Ad-hoc remote debugging, zero server setup | 2A |
| Same, but a capture that's always on | Persistent queryable service | 2B / 2C |
| OpenSIPS / Kamailio proxies you can't run sipnab on (or a Homer box) | Central capture host aggregating HEP | 3 |
| Agents outside your network | Hardened public endpoint | 4 |
| Many capture hosts | One client config covering all of them | 5 |
| No human in the loop | Scheduled / scripted diagnostics | 6 |
Two invariants that apply everywhere:
-
The binary must have MCP compiled in. All released artifacts
(installer script, tarballs,
.deb,.rpm) are built with the full feature set, so this is only a concern for source builds — but always confirm withsipnab --version: the features list must includemcp(stdio) and, for the HTTP scenarios,mcp-http. -
--mcprequires-N. In stdio mode stdout is the JSON-RPC wire, so the TUI and stdout-writing flags (--json,--report, …) are rejected. Corollary: one sipnab process is either your TUI or your MCP server, never both — run two processes if you want both.
On each machine that will run sipnab (in scenario 1 that's the laptop itself):
-
[server] Install. The installer picks the right build for your OS, CPU, and glibc, verifies its sha256, and installs to
/usr/local/bin:curl -fsSL https://www.sipnab.com/install.sh | shDebian/Ubuntu and RHEL/Fedora users can use the
.deb/.rpmpackages instead (headless servers: the-noaudiovariant skips the ALSA dependency) — see the install guide for all channels. -
[server] Verify the features:
sipnab --version # sipnab 0.5.63 (...) features: native,tui,audio,tls,hep,api,mcp,mcp-http,metricsIf
mcpis missing you have a source build without features — rebuild withcargo install sipnab --features full. -
[server] Smoke-test the MCP server with no client involved. Use any pcap you have (or grab a public sample first:
curl -LO https://github.com/NormB/sipnab/raw/main/tests/pcap-samples/SIP_CALL_RTP_G711):# Run all of these, in order. { echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test","version":"0"}}}' sleep 0.3 echo '{"jsonrpc":"2.0","method":"notifications/initialized"}' sleep 0.1 echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"stats","arguments":{}}}' sleep 0.5 } | sipnab --mcp -N -I SIP_CALL_RTP_G711 --quiet | tail -1
Expected: a JSON-RPC result whose
textpayload contains"dialog_count". If you see that, sipnab's MCP server works on this machine and every remaining problem is wiring, not sipnab.
The MCP client launches sipnab as a child process and talks JSON-RPC over the pipe. No port, no token, nothing to deploy.
-
[laptop] Do Step 0 on this machine (here the "server" is your laptop).
-
[laptop] Register the server with Claude Code. The
--separatesclaude mcp add's own flags from the command it should launch:claude mcp add sipnab -- sipnab --mcp -N -I "$PWD/capture.pcap" --quietClaude Desktop instead: edit
~/Library/Application Support/Claude/claude_desktop_config.json(macOS) or%APPDATA%\Claude\claude_desktop_config.json(Windows):{ "mcpServers": { "sipnab": { "command": "sipnab", "args": ["--mcp", "-N", "-I", "/path/to/capture.pcap", "--quiet"] } } }and restart Claude Desktop.
-
[laptop] Verify the client sees it:
claude mcp list # sipnab ✓ connected -
[laptop] Use it. Start
claudeand ask:which calls in this capture had one-way audio, and why?
Watch the agent call
find_problems {"kinds":["one-way"]}, thenrtp_stats/get_dialog_reporton what it finds.
Live interface capture needs CAP_NET_RAW, and your MCP client isn't
running as root.
-
[laptop] Grant the capability to the binary once:
sudo setcap cap_net_raw+ep /usr/local/bin/sipnab
-
[laptop] Find your interface name (
ip -br link), then:claude mcp add sipnab-live -- sipnab --mcp -N -d eth0 --quiet
-
[laptop] Verify with
claude mcp list, generate or wait for SIP traffic, and ask the agent forstats—dialog_countshould climb.
State is per-session: the capture starts when the agent connects and dies
with it. For "always capturing, query whenever" on one box, use the
scenario-2B service bound to 127.0.0.1 (loopback needs no token) and add
it with claude mcp add --transport http sipnab http://127.0.0.1:8731/mcp.
A designed-for use case, not a workaround: the MCP surface is read-only by construction (no tool sends SIP or mutates capture state), every response is bounded, and non-loopback HTTP binds refuse to start without a bearer token. Three wirings, in increasing order of setup.
The MCP "command" is simply ssh. Nothing listens on the server; your SSH
key is the authentication; when the session ends, nothing is left running.
-
[server] Do Step 0. That's all the server setup there is.
-
[laptop] Confirm non-interactive SSH works — a password prompt would hang the MCP client forever:
ssh -o BatchMode=yes prod01.example.net true && echo SSH OK
If that fails, set up key auth first (
ssh-keygen,ssh-copy-id). -
[laptop] Register the server. Use the absolute path to sipnab — non-interactive SSH sessions get a minimal PATH that often misses
/usr/local/bin:claude mcp add sipnab-prod -- \ ssh prod01.example.net /usr/local/bin/sipnab --mcp -N \ -I /var/spool/captures/outage-0722.pcap --quiet(The pcap path is a path on the server.)
-
[laptop] Verify the client picked it up — the entry should read
sipnab-prod ✓ connected:claude mcp list
Then start an agent against it and ask, for example, "summarize the failed calls in this capture":
claude
Live capture over SSH works too, if the remote binary has the capability
(sudo setcap cap_net_raw+ep /usr/local/bin/sipnab on the server, once):
claude mcp add sipnab-prod-live -- \
ssh prod01.example.net /usr/local/bin/sipnab --mcp -N -d eth0 --quietEach agent session spawns a fresh sipnab: perfect for pcap post-mortems, wrong for accumulating live state — that's 2B/2C.
For a capture that runs continuously and answers agents whenever they ask. Keep this shape on a trusted network (LAN/VPN): the token authenticates, but the transport is plaintext HTTP. Across untrusted networks use 2C or 4.
-
[server] Do Step 0.
-
[server] Create the unprivileged user the service will run as:
sudo useradd --system --home /nonexistent --shell /usr/sbin/nologin sipnab
Then grant the binary capture rights. Skip this second command if you'll feed HEP instead (scenario 3): a HEP listener is a plain UDP socket, so
cap_net_rawwould be privilege the service never uses.sudo setcap cap_net_raw+ep /usr/local/bin/sipnab
-
[server] Generate the bearer token file:
# Run all of these, in order. sudo mkdir -p /etc/sipnab head -c 32 /dev/urandom | base64 | sudo tee /etc/sipnab/mcp.token >/dev/null sudo chmod 600 /etc/sipnab/mcp.token
-
[server] Install the systemd unit as
/etc/systemd/system/sipnab-mcp.service.--mcp-allowed-hostmust name whatever the laptop will put in the URL — without it, DNS-rebind protection answers403 Forbidden: Host header is not allowed:[Unit] Description=sipnab MCP server After=network-online.target Wants=network-online.target [Service] Type=simple ExecStart=/usr/local/bin/sipnab --mcp -N --mcp-transport http \ --mcp-bind 0.0.0.0:8731 \ --mcp-token-file /etc/sipnab/mcp.token \ --mcp-allowed-host prod01.example.net \ -d eth0 User=sipnab Group=sipnab NoNewPrivileges=true ProtectSystem=strict ProtectHome=true PrivateTmp=true ReadOnlyPaths=/etc/sipnab Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target
-
[server] Start it and check it came up:
# Run all of these, in order. sudo systemctl daemon-reload sudo systemctl enable --now sipnab-mcp systemctl status sipnab-mcp --no-pager
If it exits immediately with a token error, you bound non-loopback without a readable token file — that refusal is deliberate (fail closed).
-
[server] Verify locally before involving the laptop. The
curlreads$TOKENout of the shell the first line sets, so the two only work together:# Run all of these, in order. TOKEN=$(sudo cat /etc/sipnab/mcp.token) curl -sS http://127.0.0.1:8731/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "Authorization: Bearer $TOKEN" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'
Expected: a
serverInfoblock naming the sipnab instructions string. -
[server] Open tcp/8731 to your laptop's network in whatever firewall governs this host (nftables/ufw/cloud security group) — ideally to specific source addresses, not the world.
-
[laptop] Copy the token, then register the server. First, somewhere to keep it that only you can read:
mkdir -p ~/.config/sipnab && chmod 700 ~/.config/sipnab
Now fetch the token. Run this one by itself and read what it prints: your local shell creates and truncates
prod01.tokenbeforesshruns, so a failed connection or asudothat wants a password leaves you holding an empty file rather than no file at all.ssh prod01.example.net sudo cat /etc/sipnab/mcp.token > ~/.config/sipnab/prod01.token
Check that the file is non-empty and matches the server's, then close its permissions:
chmod 600 ~/.config/sipnab/prod01.tokenFinally register the server. The
$(cat …)resolves when you run this, so an empty token file here registers an empty bearer and every later call comes back401:claude mcp add --transport http \ --header "Authorization: Bearer $(cat ~/.config/sipnab/prod01.token)" \ sipnab-prod http://prod01.example.net:8731/mcp -
[laptop] Verify the client connected — the entry should read
sipnab-prod ✓ connected:claude mcp list
Then start an agent and ask it something only the running capture can answer, for example "any calls with one-way audio right now?":
claude
The best of both: the service runs continuously but binds only loopback, so there's no token to manage and no port to firewall. The SSH tunnel is the auth and the encryption.
-
[server] Follow 2B steps 1–2, then install the same unit with two changes: bind loopback and drop the token/allowed-host flags (loopback binds require neither):
ExecStart=/usr/local/bin/sipnab --mcp -N --mcp-transport http \ --mcp-bind 127.0.0.1:8731 \ -d eth0sudo systemctl daemon-reload && sudo systemctl enable --now sipnab-mcp
-
[server] Verify locally — same curl as 2B step 6, minus the
Authorizationheader. -
[laptop] Open the tunnel (add
autosshor a systemd user unit if you want it self-healing):ssh -f -N -L 8731:127.0.0.1:8731 prod01.example.net
-
[laptop] Register against localhost — the default host allowlist already accepts
127.0.0.1:claude mcp add --transport http sipnab-prod http://127.0.0.1:8731/mcp
Then confirm the client reaches the server through the tunnel:
claude mcp list
-
[laptop] When the tunnel drops (laptop sleep, network change), MCP calls fail with connection errors; re-run step 3. That's the one operational cost of this shape.
| 2A ssh-stdio | 2B HTTP+token | 2C tunnel | |
|---|---|---|---|
| Server setup | install only | unit + token | unit |
| Open port | none | 8731 | none |
| Live state persists between sessions | no | yes | yes |
| Several people/agents at once | one server each | yes | yes (each tunnels) |
| Crosses untrusted networks safely | yes (SSH) | no — use 4 | yes (SSH) |
You usually can't (and shouldn't) run a packet-capturing debugger on every production SIP proxy. Instead the proxies mirror signaling to one capture host via HEP — OpenSIPS, Kamailio, and FreeSWITCH all speak it — and one sipnab MCP service sees calls from the whole estate. The HEP listener is a plain UDP socket: no capture privileges, no setcap, fully unprivileged.
-
[server] Do Step 0 and create the user (2B step 2, without the
setcap). -
[server] Install
/etc/systemd/system/sipnab-mcp.service— this variant listens for HEP on udp/9063 and serves MCP on loopback (pair it with the 2C tunnel; for the 2B token shape instead, use its--mcp-bind/token/allowed-host lines):[Unit] Description=sipnab MCP server (HEP listener) After=network-online.target Wants=network-online.target [Service] Type=simple ExecStart=/usr/local/bin/sipnab --mcp -N --mcp-transport http \ --mcp-bind 127.0.0.1:8731 \ -L 0.0.0.0:9063 --hep-parse User=sipnab Group=sipnab NoNewPrivileges=true ProtectSystem=strict PrivateTmp=true Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target
sudo systemctl daemon-reload && sudo systemctl enable --now sipnab-mcp
-
[server] Open udp/9063 from the proxies' addresses in the host firewall.
-
[proxy] Point each proxy's HEP mirror at the capture host.
OpenSIPS (3.x) —
opensips.cfg:loadmodule "proto_hep.so" loadmodule "tracer.so" modparam("tracer", "trace_id", "[sipnab]uri=hep:capture01.example.net:9063;version=3;transport=udp")and in the main route (traces the dialog's SIP both ways):
trace("sipnab", "t", "sip");Kamailio — the
siptracemodule withduplicate_uripointed atsip:capture01.example.net:9063and HEP mode enabled; see the siptrace docs for the handful of modparams. -
[server] Verify HEP is arriving. Place a test call through a proxy, then:
curl -sS http://127.0.0.1:8731/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'
then watch
journalctl -u sipnab-mcp -f— ano packets for 30swarning means the HEP sender isn't reaching the-Lport (firewall, wrong port, wrong host). -
[laptop] Wire up exactly as scenario 2C steps 3–4 (or 2B steps 8–9 for the token shape). Then ask across the estate: "search all proxies' traffic for Call-ID X and render the ladder."
A Homer / heplify-server box is a natural home for sipnab — the HEP plumbing from the proxies already exists. Two things to arrange:
-
Port: heplify-server owns udp/9060, so sipnab takes its own
(udp/9063 above) and each proxy mirrors to both destinations —
OpenSIPS: add a second
trace_idand a secondtrace()call; Kamailio: a second duplicate destination. If a sender can't dup, put a small UDP fan-out (e.g. socat) in front. -
Budget: cap sipnab's footprint with
[limits](below) so the box's primary tenant keeps its headroom.
Anything else on the host — an OpenTelemetry collector, Prometheus, etc. — is simply a neighbor process; sipnab neither speaks OTLP nor conflicts with it.
When agents connect from outside your network and SSH isn't an option: keep sipnab on loopback and let nginx own the public endpoint.
-
[server] Build the loopback service with a token: 2B steps 1–3, then a unit whose ExecStart binds loopback but keeps auth and allows the public hostname (nginx forwards the client's
Host:header):ExecStart=/usr/local/bin/sipnab --mcp -N --mcp-transport http \ --mcp-bind 127.0.0.1:8731 \ --mcp-token-file /etc/sipnab/mcp.token \ --mcp-allowed-host capture.example.com \ -d eth0 -
[server] Install nginx and certbot:
sudo apt install nginx certbot python3-certbot-nginx
Then issue the certificate. certbot needs
capture.example.comalready resolving to this host, asks for a contact address, and rewrites the nginx config in place — run it on its own so you can answer it:sudo certbot --nginx -d capture.example.com
-
[server] Site config (
/etc/nginx/sites-available/sipnab-mcp, symlink intosites-enabled,sudo nginx -t && sudo systemctl reload nginx):server { listen 443 ssl; server_name capture.example.com; ssl_certificate /etc/letsencrypt/live/capture.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/capture.example.com/privkey.pem; location /mcp { proxy_pass http://127.0.0.1:8731; proxy_set_header Host $host; proxy_buffering off; # SSE responses must stream proxy_read_timeout 3600s; } }
proxy_buffering offis load-bearing: the streamable-HTTP transport answers withtext/event-stream, and buffering proxies stall it. -
[server] Firewall: open tcp/443 only; 8731 stays closed to the outside.
-
[laptop] Token copy as in 2B step 8, then:
claude mcp add --transport http \ --header "Authorization: Bearer $(cat ~/.config/sipnab/capture.token)" \ sipnab-prod https://capture.example.com/mcp -
[laptop] Verify with
claude mcp list; on failure, test the path layer by layer: curl againsthttps://capture.example.com/mcpfrom the laptop, then the loopback curl (2B step 6) on the server.
Run scenario 2B, 2C, or 4 on each capture host, then give each its own entry in one client config:
-
[each server] Any persistent wiring above (2B shown here).
-
[laptop] Register them all — the loop is one command and does nothing unless you take the whole of it:
# Run all of these, in order. for h in nyc1 chi1 lax1; do claude mcp add --transport http \ --header "Authorization: Bearer $(cat ~/.config/sipnab/$h.token)" \ "sipnab-$h" "https://$h.example.net/mcp" done
Then confirm all three registered:
claude mcp list
-
[laptop] Ask cross-fleet questions — tool names are namespaced per server (
mcp__sipnab-nyc1__list_dialogs,mcp__sipnab-chi1__list_dialogs, …), so the agent can fan out:the caller says the 14:02 UTC call dropped; find its Call-ID on all three sites and compare the ladders.
Nothing about MCP requires an interactive session.
Agent-in-cron — headless Claude Code against any wiring above:
-
[laptop or ops host] Confirm the MCP server is registered (
claude mcp list), then test a one-shot run:claude -p "Using the sipnab MCP tools: list problem dialogs from the \ last 24h, get reports for the worst three, and summarize likely root \ causes." --allowedTools "mcp__sipnab-prod__*"
-
[laptop or ops host] Wrap it in cron/systemd-timer, writing to a file or a ticket system:
0 7 * * * claude -p "..." --allowedTools "mcp__sipnab-prod__*" \ > /var/log/sipnab/daily-triage.md 2>&1
No LLM at all — the MCP server is also just a stable JSON-RPC API for
scripts: the Python/TypeScript clients in
mcp.md § Client cookbook drive the same tools
directly (e.g. a nightly job calling find_problems and opening a ticket
when the count is nonzero).
Every scenario above is client-agnostic on the server side: stdio wirings (1, 2A) hand the client a command to launch, HTTP wirings (2B, 2C, 4) hand it a URL plus a bearer token. Only the registration step differs per agent. The table maps it; snippets follow.
| Client | Config lives in | stdio | Streamable HTTP |
|---|---|---|---|
| Claude Code | claude mcp add |
✓ | ✓ --transport http + header |
| Claude Desktop | claude_desktop_config.json |
✓ | via Settings → Connectors |
| Codex CLI | ~/.codex/config.toml |
✓ command/args
|
✓ url + bearer_token_env_var
|
| Cursor | ~/.cursor/mcp.json |
✓ command/args
|
✓ url + headers
|
| VS Code (Copilot agent mode) | .vscode/mcp.json |
✓ type: stdio
|
✓ type: http + headers
|
| Gemini CLI | ~/.gemini/settings.json |
✓ command
|
✓ httpUrl + headers
|
| Windsurf (Cascade) | ~/.codeium/windsurf/mcp_config.json |
✓ command/args
|
✓ serverUrl + headers
|
For the remote-stdio wiring (scenario 2A), every stdio snippet below works
unchanged with command set to ssh and the sipnab invocation moved into
args — exactly as in the Claude Code example there.
~/.codex/config.toml (shared by the ChatGPT desktop app and IDE
extension), one TOML table per server — command means stdio, url means
streamable HTTP, mixing both is rejected:
# stdio — scenario 1/2A
[mcp_servers.sipnab]
command = "sipnab"
args = ["--mcp", "-N", "-I", "/path/to/capture.pcap", "--quiet"]
# streamable HTTP — scenario 2B/2C/4; token read from the named env var
[mcp_servers.sipnab-prod]
url = "https://capture.example.com/mcp"
bearer_token_env_var = "SIPNAB_MCP_TOKEN"bearer_token_env_var names a variable read from the environment codex is
launched with, so the export and the launch have to happen in the same shell:
# Run all of these, in order.
export SIPNAB_MCP_TOKEN=$(cat ~/.config/sipnab/prod01.token)
codex # then e.g.: "which calls had one-way audio?"Or from the CLI: codex mcp add sipnab -- sipnab --mcp -N -I capture.pcap --quiet
~/.cursor/mcp.json (or per-project .cursor/mcp.json). ${env:VAR}
interpolation keeps the token out of the file:
{
"mcpServers": {
"sipnab": {
"command": "sipnab",
"args": ["--mcp", "-N", "-I", "/path/to/capture.pcap", "--quiet"]
},
"sipnab-prod": {
"url": "https://capture.example.com/mcp",
"headers": { "Authorization": "Bearer ${env:SIPNAB_MCP_TOKEN}" }
}
}
}.vscode/mcp.json — note the root key is servers and every entry needs
an explicit type. MCP tools appear in agent mode only (invisible in
Ask/Edit):
{
"servers": {
"sipnab": {
"type": "stdio",
"command": "sipnab",
"args": ["--mcp", "-N", "-I", "/path/to/capture.pcap", "--quiet"]
},
"sipnab-prod": {
"type": "http",
"url": "https://capture.example.com/mcp",
"headers": { "Authorization": "Bearer ${env:SIPNAB_MCP_TOKEN}" }
}
}
}~/.gemini/settings.json — exactly one transport key per server:
command (stdio), httpUrl (streamable HTTP), or url (legacy SSE).
Use httpUrl for sipnab:
{
"mcpServers": {
"sipnab": {
"command": "sipnab",
"args": ["--mcp", "-N", "-I", "/path/to/capture.pcap", "--quiet"]
},
"sipnab-prod": {
"httpUrl": "https://capture.example.com/mcp",
"headers": { "Authorization": "Bearer your-token-here" }
}
}
}~/.codeium/windsurf/mcp_config.json — remote servers use serverUrl,
and ${file:...} interpolation reads the token straight from the file you
copied in scenario 2B step 8:
{
"mcpServers": {
"sipnab": {
"command": "sipnab",
"args": ["--mcp", "-N", "-I", "/path/to/capture.pcap", "--quiet"]
},
"sipnab-prod": {
"serverUrl": "https://capture.example.com/mcp",
"headers": { "Authorization": "Bearer ${file:/home/you/.config/sipnab/prod01.token}" }
}
}
}Two universal gotchas, regardless of client: the Host: your client sends
must be in sipnab's --mcp-allowed-host allowlist (403 otherwise), and
plaintext-HTTP registrations belong on trusted networks only — the same
rules as scenarios 2B and 4.
Two distinct costs; both are small, and both are cappable.
The capture path dwarfs the MCP path and is the one to size. Reference numbers (benchmarks, modest 14-core aarch64 host): 1.2M pkts/s single-core offline reconstruction on a ~93%-RTP corpus, 2.9M at two cores. For scale: a proxy doing 100 CPS with ~10 SIP messages per call generates ~1k signaling packets/s — three orders of magnitude below one core's budget. What actually costs:
-
Live
-dcapture of media: RTP dominates packet counts. If you only need signaling analysis, don't capture media (BPF filter on port 5060, or feed HEP — proxies mirror signaling only), and RTP tracking cost disappears. -
HEP ingest is the cheapest input: an unprivileged UDP socket, and
hep_rate_limit(default 50k pps) hard-caps what sipnab will accept. -
Memory is bounded, not open-ended:
[limits]defaults cap tracked dialogs (100k), RTP streams (50k), messages per dialog (500), and TCP reassembly (10k). Tighten these on a shared box; adialog_limit = 20000-class config keeps sipnab a well-behaved tenant.
The MCP query path is noise by comparison: read-only lookups against
in-memory stores, every response bounded (limit ≤ 1000, snippets ≤ 4 KB,
≤ 1000 messages per page). An agent conversation makes a handful of tool
calls; there is no polling loop unless you build one. If you want zero
load on the SIP server itself, that's scenario 3: the proxy pays only for
HEP mirroring and sipnab lives elsewhere.
What the design already gives you (details in mcp.md § Security model):
- Read-only, no control plane. No MCP tool sends SIP, writes files, or changes capture state — a compromised or confused agent can disclose data, not take over the server. Capture lifecycle belongs to systemd.
-
Fail-closed remote access. Non-loopback HTTP binds refuse to start
without a bearer token; tokens compare in constant time; DNS-rebind
protection rejects unexpected
Host:headers; the listener binds after privilege drop.
What remains your call:
-
Captured SIP is sensitive. Dialogs carry phone numbers, IPs,
User-Agents, digest
Authorizationheaders, and (if captured) media stats. Two consequences: treat the MCP endpoint with the same care as the pcaps themselves, and remember that whatever a tool returns is sent to the agent's model provider — if that's a cloud LLM, capture content leaves your network by design. Scope what the server can see (BPF filter, signaling-only HEP) to what you're comfortable exporting. - Prefer wirings with no listening surface. 2A/2C expose nothing; 2B is plaintext HTTP (LAN/VPN only); 4 is the only shape that belongs on the public internet, and even there sipnab stays on loopback behind TLS with a token.
-
Token hygiene. Use
--mcp-token-file(0600, root-owned) rather than--mcp-token/env — flags and environments leak viapsand unit files. Rotation/expiry via signed tokens is available (auth.md). -
Contain the process. Run as a dedicated user with the systemd
hardening shown above (
NoNewPrivileges,ProtectSystem); HEP ingest needs no capabilities at all.
Work outward from the server; each layer has a definitive test.
| Layer | Test | Good sign |
|---|---|---|
| Binary | sipnab --version |
features include mcp / mcp-http
|
| MCP core | Step 0's stdio one-liner |
"dialog_count" in the reply |
| HTTP service | loopback curl (2B step 6) |
serverInfo block |
| Network path | same curl from the laptop | same |
| Client | claude mcp list |
✓ connected |
HTTP status decoder: 401 wrong/missing bearer token · 403 Host: not
in the allowlist (--mcp-allowed-host) · 404 path isn't exactly /mcp ·
406 missing Accept: application/json, text/event-stream. More in
mcp.md § Troubleshooting; the
raw HTTP test there is a working curl carrying every
required header.
Website · Repository · Issues · Generated from docs/ — edit there, not here.
Getting started
Using the TUI
CLI & automation
Configuration
Integrations (API & MCP)
Development & internals