MCP - NormB/sipnab GitHub Wiki
sipnab can run as a Model Context Protocol server. An AI agent can then read a capture and answer your questions about it. You do not need to memorize CLI flags. The agent can be Claude Code, Claude Desktop, or any MCP-capable client.
It is a fourth output mode beside the TUI, the -N CLI and --json. The same
parts drive all four:
- the parser,
- the dialog state machine,
- the RTP store,
- the diagnostic engine.
So an agent sees exactly what the other modes see.
Point sipnab at a pcap. Stdio is the default transport, so nothing else applies:
sipnab --mcp -N -I capture.pcapThat is the whole server. It speaks JSON-RPC on stdout and waits.
You can ask it something with nothing but the installed binary and jq. Pipe
the three messages a client would send into it. This sample capture holds four
failed calls:
curl -LO https://github.com/NormB/sipnab/raw/main/tests/pcap-samples/sip-problem-call.pcap- The first message opens the session.
- The second confirms it.
- The third asks
triage_callwhy one call failed.
The server exits when its input ends:
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"shell","version":"0"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"triage_call","arguments":{"call_id":"[email protected]"}}}' \
| sipnab --mcp -N -I sip-problem-call.pcap --quiet \
| jq 'select(.id == 2) | .result.content[0].text | fromjson | {verdict, final_status_code, signaling}'{
"verdict": "signaling",
"final_status_code": 486,
"signaling": {
"hints": [
"Call failed: 486 Busy Here."
],
"problem": true
}
}From a source checkout, a one-shot helper does the same with less typing and prints the whole answer:
demos/mcp-stdio.sh tests/pcap-samples/sip-problem-call.pcap \
triage_call '{"call_id":"[email protected]"}'{
"call_id": "[email protected]",
"final_status_code": 486,
"media": {
"hints": [],
"nat_mismatch": false,
"no_media": false,
"one_way_audio": false,
"problem": false,
"stream_count": 0
},
"schema_version": 1,
"signaling": {
"hints": [
"Call failed: 486 Busy Here."
],
"problem": true
},
"source_exhausted": true,
"source_stopped_early": false,
"state": "Failed",
"verdict": "signaling"
}A verdict, not a packet list:
- signaling rather than media,
- the 486 that ended it,
- media explicitly ruled out rather than merely absent.
one_way_audio,nat_mismatchandno_mediaeach sayfalserather than going unmentioned.
source_exhausted and source_stopped_early ride on every answer this server
gives from the capture. Together they say the verdict rests on the whole file.
It does not rest on however much had loaded when the question arrived.
For Claude Desktop or Claude Code:
{
"mcpServers": {
"sipnab": {
"command": "sipnab",
"args": ["--mcp", "-N", "-I", "/path/to/capture.pcap"]
}
}
}To serve live traffic instead of a file, run as root or grant the binary
CAP_NET_RAW:
sudo sipnab --mcp -N -d eth0Why every stdio example carries -N
With the stdio transport, stdout is the JSON-RPC wire. So --mcp implies
-N/--no-tui.
-
sipnab refuses every stdout-writing flag (
--json,--report, …) at startup and exits 2, for example with--mcp uses stdout for the JSON-RPC wire and cannot be combined with stdout-writing flags (--json).Write the report from a separate run without
--mcp. -
The
-Nin these examples states what--mcpalready does.
Over HTTP the wire is a socket, so the TUI can stay up beside the server: see Query a capture over MCP while the TUI is open.
Stdio needs no token. It is a private pipe between client and server. A listening transport does need one. See MCP protocol.
Building with MCP support
MCP is feature-gated. Build with mcp for stdio, or mcp-http for the HTTP
transport:
cargo build --release --no-default-features --features native,hep,api,mcp,mcp-httpThe default build excludes mcp. sipnab --version prints the features of
the binary.
Use this when you want to watch a capture in the TUI while an agent asks questions about the same capture.
- One sipnab process does both.
- The TUI and the MCP server read the same dialogs and streams.
- So the agent's answers match what is on screen.
Start sipnab with the HTTP transport and without -N.
- This example uses the sample capture from See it work.
- It names a port so that it does not collide with a headless MCP server
already on the default
127.0.0.1:8731:
sipnab -I sip-problem-call.pcap --mcp --mcp-transport http --mcp-bind 127.0.0.1:8735The TUI opens as usual. The status line under the header says where MCP is listening:
MCP over HTTP at http://127.0.0.1:8735/mcp
With --mcp-bind 127.0.0.1:0 the kernel picks a free port, and the status line
shows the port it picked.
Point your agent at that URL. For Claude Code, use the claude mcp add command
in MCP deployment.
To check the server from a second terminal with curl and jq:
- open a session,
- confirm it,
- list the dialogs:
# Run all of these, in order.
URL=http://127.0.0.1:8735/mcp
H=(-H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream')
SID=$(curl -s -D - -o /dev/null "${H[@]}" "$URL" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"shell","version":"0"}}}' \
| tr -d '\r' | awk 'tolower($1) == "mcp-session-id:" {print $2}')
curl -s "${H[@]}" -H "Mcp-Session-Id: $SID" "$URL" -d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
curl -s "${H[@]}" -H "Mcp-Session-Id: $SID" "$URL" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_dialogs","arguments":{}}}' \
| sed -n 's/^data: //p' | jq '.result.content[0].text | fromjson | {returned, source_exhausted, call_ids: [.dialogs[].call_id]}'{
"returned": 5,
"source_exhausted": true,
"call_ids": [
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]",
"[email protected]"
]
}The same five calls are on the TUI's call list. source_exhausted is true
once sipnab has read the whole file.
Quit the TUI (q, then y) and the MCP server stops with it. The port closes
when the process exits.
What differs from a headless MCP server:
-
It needs a terminal. Without one the TUI cannot start and sipnab exits with an error. A systemd unit or a container runs
-N. -
The HTTP rules stay the same.
- A loopback bind needs no token.
- Any other bind needs one, as MCP deployment describes.
- The Host header check, the rate limits and the TLS flags are the ones a headless server reads.
-
A bind problem stops the run before the TUI opens. A port already in use, or a non-loopback bind with no token, prints the reason and exits 2.
-
sipnab refuses five opt-ins with the TUI up. Each acts on the process or on the relay rather than reading the capture. The operator at the terminal owns both. Add
-Nto use them:Flag Why sipnab refuses it beside the TUI --mcp-allow-shutdownshutdown_serverstops the capture and the servers, and the TUI stays on screen showing a stopped run.--mcp-allow-open-captureopen_capturereplaces the capture the operator is reading.--mcp-allow-tls-capturestart_tls_capturewrites a second capture into the stores the TUI shows.--mcp-allow-save-findingssave_findingswrites to the log, and a TUI run logs only errors, so nobody would see the finding.--mcp-allow-relay-queryquery_relaytransmits on the run's one transmit permit, which the TUI's relay statistics view holds. -
The server writes nothing over the screen. A TUI run logs only errors. To keep the server's log lines, send them to a file:
SIPNAB_LOG=info sipnab -I sip-problem-call.pcap --mcp --mcp-transport http --mcp-bind 127.0.0.1:8735 2>sipnab.logsipnab.logthen holdsMCP HTTP server listening on 127.0.0.1:8735and the rest of the run's log lines.
Most tools only read what sipnab holds. Thirteen write a file or act on the host or on TFPS. None of them can do either until you start the server with the flag that allows it:
| Tool | What it does | Allowed by |
|---|---|---|
export_capture |
Writes the held SIP messages to a pcap file | --mcp-file-root |
export_audio |
Writes a call's audio to a WAV file | --mcp-file-root |
compare_captures |
Reads two capture files and compares them | --mcp-file-root |
build_evidence_package |
Writes a directory of evidence for the calls it names | --mcp-file-root |
generate_repro |
Returns a SIPp scenario; writes it to a file only when given a filename
|
--mcp-file-root, for the file |
open_capture |
Replaces the capture the server is reading with a file from the file root |
--mcp-allow-open-capture and --mcp-file-root
|
save_findings |
Writes findings to the log | --mcp-allow-save-findings |
shutdown_server |
Stops the capture and the servers | --mcp-allow-shutdown |
start_tls_capture |
Starts a uprobe TLS capture on this host | --mcp-allow-tls-capture |
stop_tls_capture |
Stops the capture start_tls_capture began |
--mcp-allow-tls-capture, to have started one |
tfps_ban |
Asks TFPS to ban one source | --allow-action tfps:mcp |
tfps_unban |
Asks TFPS to lift a ban sipnab placed | --allow-action tfps:mcp |
actions_revert |
Backs out bans sipnab placed | --allow-action tfps:mcp |
Over HTTP, the three TFPS tools also need a token with the actions scope.
MCP Inspector is the reference client of the Model Context Protocol project. Point it at sipnab and it does these things:
- It lists every tool the binary registers.
- It shows the JSON Schema of each tool's arguments.
- It calls one by hand so you can read the answer.
It does for this surface what https://sipnab.com/api-reference/ does for the REST API.
sipnab registers 76 MCP tools. That is more than anyone reads in a table. The Tools tab is the fastest way to find the one you want.
The MCP project maintains Inspector, not sipnab. When a tool misbehaves in your own client, call it from Inspector to see whether the fault is in sipnab or in the client.
Inspector needs Node 22.19 or newer. npx fetches it on demand. So there is
nothing to install first.
This spawns the same stdio server your agent would spawn:
npx @modelcontextprotocol/inspector sipnab -- --mcp -N -I capture.pcapKeep the --. Everything after it becomes sipnab's own command line. Leave it
out and Inspector does this:
- it reads
--mcpas one of its own flags, - it spawns a bare
sipnab, - it hands you a live-capture permission error instead of a tool list.
The command prints a URL on http://127.0.0.1:6274 carrying a one-time token.
Open that URL, click Connect, then open the Tools tab.
Start sipnab with the HTTP transport as MCP deployment describes, then give Inspector the URL rather than a command:
npx @modelcontextprotocol/inspector --server-url http://127.0.0.1:8731/mcp --transport httpA bind that is not loopback demands a bearer token, so send the header the server expects:
npx @modelcontextprotocol/inspector --server-url https://capture.example.com/mcp --transport http --header "Authorization: Bearer $(cat ~/.config/sipnab/prod01.token)"Inspector ships a scriptable client as well as the browser one. So the tool
list is something a shell pipeline or a coding agent can read. --cli selects
it. Each run performs one request and exits:
npx @modelcontextprotocol/inspector --cli sipnab --mcp -N -I capture.pcap -- --method tools/list --format jsonThe -- separator works the opposite way in the two clients:
- The browser client takes sipnab's flags after
--. - The
--cliclient takes the server command first and its own flags after--.
Copy the fences above as written.
Calling a tool by hand works the same way:
npx @modelcontextprotocol/inspector --cli sipnab --mcp -N -I capture.pcap -- --method tools/call --tool-name triage_call --tool-arg [email protected] --format json--strict reports schema spellings a client may not read. It exits non-zero
only on error severity, so a zero exit status does not mean the report is
clean. Read the summary line:
npx @modelcontextprotocol/inspector --cli sipnab --mcp -N -I capture.pcap -- --method tools/list --strictExpect warnings about type unions only from output schemas:
- No input schema writes
typeas an array such as"type": ["string","null"], so a client that readstypeas one string can read every tool's arguments. -
Output schemas, which sipnab sends only with
--mcp-output-schemas, keep unions such as"type": ["string","null"], because a response carries an explicitnullfor an absent optional field.--strictmay warn about them.
scripts/mcp-schema-dump.sh
wraps the tools/list form and accepts either a capture file or an HTTP URL:
./scripts/mcp-schema-dump.sh capture.pcapRead that dump as a description of the binary you ran it against, not of this project.
- A feature-reduced build registers fewer tools than the tree defines.
- An older binary registers fewer still.
- The MCP tool reference documents every tool the full build registers, and what each one means.
No MCP tool reads or writes an operator note. To add notes to SIP messages,
use the TUI, then write them into a pcapng copy with --write-annotated. See
keybindings and the
CLI reference.
- sipnab never reads a packet comment back, so a note in a capture an MCP server opens is not visible to any tool.
- The only text an agent can record is a finding, written to the log with
save_findings.
Invariant "Operator notes are output, never input" in the invariants page states the rule and why it holds.
| You want to | Page |
|---|---|
| Run it against a remote server, keep a capture alive between sessions, or expose it as a service | MCP deployment |
| Know what a tool returns, field by field | MCP tool reference |
| Write a client, or review the security model | MCP protocol |
| Run what an agent does, end to end: triage over stdio or HTTP with a signed token, an evidence package with repro scripts, or an aggregate cut to a model's budget | Runnable client examples |