Repository navigation
Releases
v0.2.0
55 tools (0.1.0 had 41), six guided prompts, and one-line install from PyPI:
claude mcp add tshark-mcp -- uvx --from tshark-mcp-server tshark-mcp --allow-dir /path/to/pcapsAdded
- Claude Code plugin in
plugin/with a marketplace entry in.claude-plugin/marketplace.json:
/plugin install tshark-mcp --marketplace Kx96xx/tshark-mcpadds the server (started withuvx
from the main branch, so tools and skills match), asks for the capture directory and redaction
(off, ids, ids+ips), and adds seven skills:pcap-investigationand one per MCP prompt, each
pointing at that prompt.tests/test_plugin.pychecks the manifests against the package, that
every prompt has a skill and every tool a skill names exists, and starts the server with the
plugin's own arguments over stdio; a CIpluginjob also runsclaude plugin validateon the
plugin and the marketplace. - README first screen: a one-line
claude mcp addwithuvx --from tshark-mcp-server, PyPI version
and downloads badges, and a demo GIF recorded with vhs from a live MCP client session against the
PyPI package (docs/media/demo.tape, docs/media/demo_client.py). A "Found a bug or a wrong answer?"
section, issue forms for wrong answers and for bugs, questions routed to GitHub Discussions, and a
1280x640 social preview image (docs/media/social-preview.png). Dockerfile: tshark from Debian pinned to the 4.4 series (the build fails if the series
changes), the server installed from the checkout, a non-root user (uid 10001),/capturesas the
read-only capture mount, a healthcheck (package imports, tshark runs) and no port. A CIdocker
job builds it (never pushes it), checks the user, the tshark version and the refusal without
--allow-dir, and runs every task-success scenario over stdio insidedocker run -i --read-only --cap-drop ALL --network none.- MCP Registry metadata:
server.json(io.github.Kx96xx/tshark-mcp, PyPI package, stdio,
TSHARK_MCP_ALLOW_DIRSrequired), themcp-namemarker in README,glama.json, and a
registryjob in publish.yml that publishes server.json withmcp-publisher(pinned, digest
checked, GitHub OIDC) after the PyPI upload. A release fails early unless the tag, pyproject and
server.json versions agree.uvx tshark-mcp-servernow starts the server (second entry point
named after the distribution). tests/test_server_json.py validates server.json against the
registry schema (vendored) and keeps it in step with pyproject. - MCPB desktop bundle for Claude Desktop:
mcpb/manifest.json(manifest 0.4,uvserver type, so
the host installs Python and the dependencies and nothing compiled ships), settings for the
capture directories (several, required; each becomes one--allow-dir), redaction and the tshark
path, andmcpb/build.pyto write the bundle directory and keep the manifest's version and
tools list in step with the package (--check,--update). A CImcpbjob validates and packs
the bundle (never published) and starts the built bundle over stdio. tshark is also looked for
at/Applications/Wireshark.app/Contents/MacOS/tshark, since desktop hosts start the server
without the shell's PATH. - Task-success evals (
python -m evals, docs/EVALS.md): sixteen scenarios over the synthetic
captures, each an operator question, the calls that answer it and the facts the results must
hold, with call and token budgets. The harness runs them over the stdio server and scores them
deterministically, in CI throughtests/test_evals.py.--driver llmlets a model choose the
calls and scores its final answer as well; it is optional, off by default and never run in CI.
--server-dirand a command after--run the scenarios against another stdio server (a
container). AGENTS.md now namespath_not_allowedas the error for a path outside the allowlist. - Six MCP prompts as guided workflows:
one_way_audio,subscriber_cannot_attach,
trust_this_capture,working_vs_failing,ims_registration_or_smsandslow_data_session.
Each returns the tool calls in order, what to cite, when to stop and the shape of the answer;
Claude Code lists them as/mcp__tshark-mcp__<name>. Theirpatharguments complete
(completion/complete) to capture files and subdirectories inside the allowed directories,
at most 50 per answer. - Token-aware output on every tool with
limit:columnskeeps only the named row keys (frame
keys always stay; an unknown name is aninvalid_argumenterror listing the valid ones),
output="summary"returns everything but the rows plus the first three rows in under 4 KB with
paging pointing at the rest, andmax_output_kblowers the server's output cap for one call
(it can never raise it). Every successful result carriestokens_estimate(JSON bytes / 3.6).
The compact tool list writes an optional list argument as"type": ["array", "null"]. startandendon every tool that reads a capture: ISO 8601, epoch seconds,"+120s"after
the capture start or"-30s"before its end. The window goes ahead of the tool's filter on
every tshark run, including the-zstatistics ofconversations,expert_infoand
pcap_info; relative bounds resolve per file; results carrywindowand a warning when the
window misses the capture.pcap_infoaddswindow_hintandframes_in_window;
find_identifierskips files outside the window.capture_index: a directory of ring-buffer files in time order from capinfos, with per-file
span, packets, bytes and interfaces, holes and overlaps between files, name-versus-time
ordering, and which files cover astart/endwindow and whether they cover it whole.incident_summary: the first call on an unknown capture. Runs the overview and the protocol
tools the capture carries under one time budget and returns findings ranked by severity, each
one deterministic sentence from a fixed threshold table with its evidence (tool, arguments,
frames), confidence (from capture_quality's verdict for loss) and the next call; plus the
capture's trust and blind spots, top talkers, a timeline and the passes it skipped and why.
Tools called by another tool run in the caller's time window; the runner takes a per-call
deadline so every run of a multi-tool call shares one budget.diff_captures: before versus after a change for TCP/IP services. Runs tcp_connections,
dns_transactions, tls_handshakes, http_transactions and tcp_health on both captures, joins per
destination, DNS name and resolver, SNI, HTTP endpoint and TCP server, and labels each key
worse, vanished, appeared, better or same with a sentence and the delta; volumes are also per
minute because the captures differ in length.throughput_timeline: bytes, bps and TCP goodput (bytes newly acknowledged, copies and
retransmissions not counted) per interval, with the retransmitted bytes, zero windows,
window-full events, duplicate ACKs, SYNs never answered, DNS queries, TLS ClientHellos and
ICMP errors beside them; series per conversation, host or stream; a verdict per TCP stream
(loss-limited, receiver-limited, application-idle, sender-limited, fine) and issues that say
where goodput fell and why, and which counters burst.call_legs: the legs of one call that a B2BUA, SBC or CSCF chain gave different Call-IDs,
stitched by icid, RFC 7989 Session-ID, X- Call-ID headers, Replaces and Target-Dialog (high
confidence), a shared SDP media endpoint (medium) or the called number and time (low), with the
link and its confidence per leg, the delay each element added, header changes, which element
rewrote a status or Q.850 cause and the deepest failed leg explained with cause_lookup.diameter_avps(detail="key")digests per application:gy(Gy/Ro: per MSCC rating group,
requested, granted and used units, reporting reason, validity and quota holding time,
Final-Unit-Indication, MSCC Result-Code),rx(media components, flow status, bandwidth, first
Codec-Data line, Specific-Action, Abort-Cause),cx(authorization and assignment types,
identities, capability counts, authentication scheme and item count, deregistration reason),sh
(Data-Reference names, Sh-User-Data size and element names) ands13(IMEI, Equipment-Status).
Failed Result-Codes and Experimental-Result-Codes carryexplainedfrom cause_lookup. Rx
Experimental-Result-Codes 4261 and 5061 to 5068 are named in every Diameter tool.nas_procedures: EMM, ESM and S1AP procedures per UE from S1-MME captures (attach, TAU,
service request, detach, PDN connectivity, dedicated bearer activation, bearer modification and
deactivation, S1 and X2 handover, UE context release). S1 contexts are joined into UEs by IMSI
or assigned M-TMSI; each procedure has an outcome, EMM/ESM and S1AP causes named through
cause_lookup, timing and its steps; ciphered NAS is counted, not guessed (null_cipher=truereads
EEA0 traffic), and a Paging nobody answered is listed.nas-eps.null_decipherjoins the allowed
preferences.tunnels_overview: every GRE, ERSPAN, VXLAN, Geneve, IP-in-IP, IPsec (IKE, ESP, AH, NAT-T),
WireGuard, OpenVPN, L2TP and GTP-U tunnel in a capture with its outer endpoints, key or VNI,
packets and bytes each way, inner protocols, tunnel overhead, DF packets above the path MTU and
inner SYN MSS against the overhead; IKE exchanges and error notifies, WireGuard and OpenVPN
handshakes, ESP SAs per direction; issues such as unanswered WireGuard initiations, a missing
return SA or an MSS that does not fit once tunnelled.export_subset: the first and only write tool, off unless the server is started with
--export-dir DIR. It writes the packets matching a display filter (and start/end) to a new
pcapng file in that directory for a vendor or another team: never overwrites, never follows a
planted symlink, sanitised names,--export-max-mb(50) and `max_pack...
v0.1.0
First release. Everything below was built between 1 and 4 November 2026 in pull requests #1
to #87; the numbers in brackets are the pull requests. The first 33 tools came first, then eight
more (41 in all) and the hardening landed the same day (listed under Added, Changed and Security).
Added
tcp_connections: every TCP connection attempt with its outcome (connected, refused,
filtered, ICMP prohibited or unreachable, reset or unfinished handshake), SYN retries, connect
time, bytes, duration and who closed it;view="destinations"gives the success rate per
server and port and names a client a firewall drops while others connect (#68).icmp_errors: every ICMP and ICMPv6 error grouped per sender, kind and quoted flow, with the
MTU of fragmentation-needed errors, whether the quoted packet is in the capture, the sender's
hop distance, echo loss and RTT, and issues that tell a routing loop from a traceroute (#71).capture_metadata: the pcapng writer, interfaces, snaplen, capture filter, packet comments and
the Interface Statistics Block drop counters that tshark has no field for, read by a pure-Python
block walker that never decodes a packet;pcap_inforeportsmetadata_availableand
interface_drops(#76).cause_lookup: what a telecom cause or result code means, with the spec reference, typical
causes, related codes and the tool calls that find it in a capture. Names come from tshark's
own value strings (tshark -G values), so they match the other tools; a test fails when a
tshark release names a code that has neither curated text nor anunexplainedlisting.
Diameter codes are read as Result-Code or, withapplication, as that application's
Experimental-Result-Code. No capture is read (#78).http_transactions: HTTP/1.x requests paired with responses and HTTP/2 / gRPC streams with
status, sizes, response time and TTFB, per-endpoint status mix and percentiles, gRPC status
names, reset streams, unanswered requests and the ports carrying HTTP that tshark left raw (#80).call_kpis: ASR, SER (RFC 6076, with and without 487), NER, post-dial delay, ringing time and
ACD per peer, trunk, number prefix, user agent or interval, with failure codes, Q.850 causes and
the worst calls by Call-ID and frame. Calls still open when the capture stopped are counted as
near_capture_endand left out of the ratios;methodstates every definition (#82).gtp_ies: the GTPv2-C information elements behind each transaction (APN, PDN type, PAA,
serving network, RAT, ULI, indication flags, AMBR, F-TEIDs, PCO option names, bearer QoS, cause
with offending IE and source, RAN/NAS cause) andlikely_causesfor a reject, built from the
request. PAP and CHAP payloads in the PCO are never returned.detail="full"gives the IE tree (#84).roaming_attach_check: why a roamer cannot attach or get a bearer. The subscriber's S6a, MAP,
GTPv2-C, Gx and PFCP steps grouped into attempts, the ULA subscription held against the Create
Session request (APN subscribed, PDN type, home routing or local breakout, RAT against
Access-Restriction-Data, ODB, AMBR), the first failure with its reference and a one-sentence
verdict. Authentication vectors are counted, never returned (#86).
Changed
- Cancelling a request kills tshark within a quarter of a second instead of letting it run to
its timeout; the audit log recordscancelled. With aprogressToken,find_identifier
reports files done out of files planned and other tools one notification per finished tshark
run, at most one per second (#83). tools/listis compact by default: 102 KB instead of 263 KB for 41 tools. Output schemas
keep their top-level keys with JSON types and name the keys of each row list; the keys
every result may carry are left out; input schemas lose the generated titles and write
optional arguments as a type list; the twelve descriptions over 1,500 characters move their
detail to a resource, and the server instructions are rewritten to about 3,100 characters (they
had grown past the 4,096 some clients keep). Each tool's full description and schemas are
the resourceschema://tools/<name>;--schema-profile full(TSHARK_MCP_SCHEMA_PROFILE)
publishes the old list.tests/test_tool_list.pykeeps the compact list within budget (#87).- Tool errors are returned with
isError: true, so clients show and count them as failures;
the structured{"error": {...}}stays instructuredContentand the text content. Every
error carries afixhint;not_foundandnot_a_filelist nearby capture files in
candidates,invalid_filterandunknown_fieldlist close field names insuggestions,
timeoutandoutput_too_largename the arguments that narrow the call (#69).
Security
- Security hardening of the tshark child: an empty read-only personal profile and a scrubbed
environment, so no personalinit.lua, Lua script, plugin or extcap loads
(--allow-user-profileto opt back in); nice +10 and, on Linux, a 2 GB address-space limit
(--max-tshark-memory-mb, out-of-memory runs reportout_of_memory: true); at most two
tshark runs at once (--max-concurrent-runs,--busy-wait, then abusyerror); a capture
replaced during the run fails withcapture_changed;-ddecode-as rules are validated (#73).
The server
- Read-only MCP server over stdio. Every call runs
tshark -r <file>on a capture inside an
operator-allowed directory, through an argv list with-n, never a shell. Capture, write,
export and extension flags are refused before a process starts. - Hard limits, each enforced in code and none switchable off: file size, matching packets
dissected in full, frames read per run, JSON returned per call, raw stdout kept, wall clock
per run and per multi-pass call, files opened per directory search. - Display filters are linted and compiled against an empty capture before touching the real
file; field names must exist intshark -G fields; structured arguments (IMSI, APN, TEID,
Call-ID, ...) are linted to their shape before being embedded in a filter. - Typed
outputSchema, a title and the MCP annotationsreadOnlyHint,idempotentHintand
openWorldHint: falseon every tool; failures come back as{"error": {...}}(#1). - One output contract:
total_matched,returned,offset,next_offset,truncatedwith a
truncated_reason, and a 30 KB cap that keeps whole rows and says how to get the rest (#25). - Every row carries the frame numbers it was built from, and
packet_detailopens any frame (#30). - Text copied from packets is capped, stripped of control and bidi characters and named in
untrusted_text_fields(#42). - Redaction of subscriber identifiers with stable keyed tokens that work as arguments
(--redact ids/ids+ips), with a bounded pre-scan so even a firstconversationscall
hides UE addresses (#47, #50).
An IMEI written as TAC-SNR-CD inside aurn:gsma:imei:instance id is found too (#60). - JSON-lines audit log (
--log-file).
Tools (the first 33)
Orientation and capture trust: pcap_info, conversations, filter_packets, packet_detail
(#30), expert_info (#36), find_fields and check_filter (#38), capture_quality (#29, #44,
#49), find_identifier across a directory of captures (#1).
Diameter: diameter_transactions, diameter_sessions (#1), diameter_avps with a key view
(procedure flags with bit names, Subscription-Data digest) and a full AVP tree (#15).
GTP and PFCP: gtp_sessions (GTPv2-C paired by sequence and peer, across NATs and proxy
chains, #4, #22, #46, #51), gtpv1_sessions (#20), gtp_user_plane, gtp_tunnel_health
(inner TCP loss, reordering, ICMP and MTU per tunnel, #18, #35), pfcp_sessions (#37).
SIP, media and IMS: sip_dialogs, sip_call_detail (#33), rtp_streams with RFC 3550
jitter, RTCP and an E-model MOS estimate (#24), media_path_check for one-way audio, NAT and
488 cases (#34), dtmf_events (#39), ims_registration (#52; network-initiated de-registration
from reg-event NOTIFYs, #61), sms_over_ims (#54).
Transport and infrastructure: tcp_health (#32, #53), response_times (#31, #43),
tls_handshakes (#28, #41), dns_transactions (#23), sctp_associations,
sigtran_messages (M3UA/SCCP/TCAP/MAP).
Cross-protocol: trace_subscriber (one timeline per subscriber across Diameter, GTPv2-C,
GTPv1-C, PFCP, MAP and SIP, one shared time budget, #13, #16, #20, #37), compare_captures
(where a failing capture first departs from a working one, #17), message_rates (bursts,
storms and subscriber loops with the nodes involved, #19, #21, #48).
Fixed during testing against real captures
- Captures that end mid-packet no longer fail every tool; the result carries a warning (#2).
-ccounted frames read, not frames matched, so per-packet tools saw only the first 500
frames (#3).- MAP returnError reported the CHOICE index instead of the error code, and results without an
opcode were not named after their invoke (#5, #6). - A SIP REGISTER answered 401 then 200 was counted as failed; retransmissions were counted as
new requests; several SIP messages in one TCP segment are now all kept (#7, #14). - Diameter IMSI queries returned other subscribers' sessions from bundled SCTP packets (#8).
find_identifierdropped partly read files without hits and could overrun its time budget
(#9, #10); theapnfilter missed APNs carrying the operator identifier (#11).- Requests unanswered only because the capture stopped are marked
near_capture_endand
counted apart, for Diameter, GTPv2-C, GTPv1-C, PFCP and DNS (#51, #55).
Fixed by the bug hunt over public captures (4 November 2026)
Every tool was driven over 16 synthetic edge cases and 245 captures from the Wireshark
repository through the MCP stdio client, with default and hostile arguments, output
schemas validated and counts cross-checked against raw tshar...