
Wireshark für MCP. Ein transparenter Proxy, der jeden echten Tool-Aufruf zwischen deinem KI-Client und deinen MCP-Servern live in deinem Terminal anzeigt.
Wireshark für MCP. Ein transparenter Proxy, der jeden echten Tool-Aufruf zwischen deinem KI-Client und deinen MCP-Servern live im Terminal anzeigt.
Der offizielle MCP Inspector verbindet sich als eigener Client und sieht daher nie, was dein Client (Cursor, Claude Code, Codex) tatsächlich an deinen Server sendet. Und alles, was auf eine eingehende Anfrage wartet, kann den Aufruf nicht anzeigen, den das Modell nie gemacht hat – oder mit den falschen Argumenten gemacht hat. Wenn ein Tool stillschweigend nicht aufgerufen wird, Fähigkeiten nicht übereinstimmen oder ein Aufruf einfach hängt, bleibt dir nur das Wühlen in Logs und das Rätselraten.
mcpsnoop sitzt stattdessen im echten Datenpfad. Umgib deinen Serverbefehl damit und sieh jeden JSON-RPC-Frame live, während dein echter Client und Server miteinander sprechen.
Sieh es dir sofort an, ohne etwas einrichten zu müssen.```bash mcpsnoop demo
Um es tatsächlich zu verwenden, binden Sie Ihren Server in die MCP-Konfiguration Ihres Clients ein.```json
{
"mcpServers": {
"my-server": {
"command": "mcpsnoop",
"args": ["--", "node", "build/index.js"]
}
}
}
Alles nach -- ist der Befehl, der normalerweise deinen Server startet. Ersetze
ihn durch das, was du bereits verwendest, z. B. python server.py,
npx -y @scope/server oder eine kompilierte Binärdatei.
In Claude Desktop musst du diese Änderung nicht von Hand vornehmen.```bash mcpsnoop wrap my-server # route my-server through mcpsnoop mcpsnoop unwrap my-server # put it back
`wrap` findet `claude_desktop_config.json`, kopiert sie beim ersten Mal nach `claude_desktop_config.json.mcpsnoop.bak` und überschreibt nur den Eintrag dieses einen Servers, sodass deine Formatierung und alle anderen Server unangetastet bleiben. Im neu geschriebenen Eintrag stehen die Schlüssel wieder in alphabetischer Reihenfolge. `unwrap` stellt die Datei wieder her und entfernt das Backup, sobald kein Server mehr umhüllt ist. Starte Claude Desktop danach neu, da MCP-Server beim Programmstart nur einmal gestartet werden.
Verwende dann deinen Client wie gewohnt und öffne die UI.```bash
mcpsnoop
Keine Flags, keine Socket-Pfade, keine Startreihenfolge, die man sich merken muss. Shim und UI finden sich von selbst, und die UI füllt frühere Sitzungen von der Festplatte nach.
Für einen streamable-HTTP-Server führen Sie mcpsnoop als Reverse-Proxy aus.```bash mcpsnoop http --target http://localhost:3000/mcp --listen :7000
Der HTTP-Status jeder Antwort erscheint im Stream, sodass eine Antwort, die
keine eigene JSON-RPC-Nachricht trägt, dennoch ein sichtbarer Frame ist und nicht nichts: die
401-Herausforderung, die 403 bei abgelehntem Origin, die 202, die eine
Benachrichtigung bestätigt, und die 502, wenn das Ziel gar nicht erreichbar ist. Der
`WWW-Authenticate`-Header der 401 wird unverändert beibehalten und im Inspector angezeigt, da er
das Authentifizierungsschema und die Ressourcen-Metadaten für den nächsten Schritt nennt. Filtern Sie nach Status
mit `status:401` im TUI oder nach jedem Fehler mit `status:err`. Ein 4xx- oder 5xx-
Status zählt als Fehler, daher schlägt ein standardmäßiger `mcpsnoop check`-Lauf dabei fehl.
Keinen eigenen Server? [Probieren Sie es tatsächlich aus](https://github.com/kerlenton/mcpsnoop/blob/HEAD/docs/TRY_IT.md) gegen einen veröffentlichten
Testserver, gesteuert durch Ihren eigenen Client. Um eine Sitzung im Nachhinein zu prüfen,
siehe [vergangene Sitzungen aus Logs überprüfen](https://github.com/kerlenton/mcpsnoop/blob/HEAD/docs/POST_MORTEM.md).
### Konfigurationsdatei
Wenn Sie dieselben Shim-Flags in einem Projekt wiederverwenden, legen Sie sie in einer
`.mcpsnoop.toml`-Datei im aktuellen Arbeitsverzeichnis ab.```toml
label = "filesystem"
trace-file = "trace.jsonl"
redact-secrets = true
redact-key = "token,authorization"
redact-value = "sk-[A-Za-z0-9]+"
redact-path = "$.params.arguments.password"
no-trace = false
Repeat redact-key, redact-value und redact-path jeweils in einer eigenen Zeile, um mehr als einen von jedem hinzuzufügen.
Das sind alle Schlüssel, die unterstützt werden.
Die Datei wird nur im aktuellen Arbeitsverzeichnis gesucht, nicht in übergeordneten Verzeichnissen.
Explizite Befehlszeilen-Flags überschreiben Werte aus der Konfigurationsdatei.
Führe mcpsnoop help für die vollständige Liste aus, oder mcpsnoop help <Befehl> für die Flags eines einzelnen Befehls.
go install github.com/kerlenton/mcpsnoop/cmd/mcpsnoop@latest
### Homebrew```bash
brew install mcpsnoop
Vorgefertigte Binärdateien für alle Plattformen befinden sich auf der Releases-Seite.
mcpsnoop wird mit Vervollständigungen für bash, zsh, fish und PowerShell ausgeliefert. Führe mcpsnoop completion <shell> --help aus, um die Einrichtungsschritte zu sehen, die das Aktivieren der Vervollständigung und den Installationspfad für dein Betriebssystem abdecken.
mcpsnoop vereint zwei Rollen in einer Binärdatei. mcpsnoop -- <server> ist der transparente Shim, den dein Client startet; er leitet Bytes unverändert weiter und übermittelt gleichzeitig eine Kopie jedes Frames an den Hub. mcpsnoop ohne Argumente ist dieser Hub und seine Live-TUI. Sie verbinden sich über einen bekannten Socket und Protokolle auf der Festplatte, sodass keiner von beiden zuerst gestartet werden muss.
Der Hub lädt standardmäßig die neuesten 100 gespeicherten Sitzungen, wodurch der Startaufwand begrenzt bleibt, ohne ältere Spuren zu löschen. Verwende mcpsnoop --history-limit N, um einen anderen Grenzwert zu wählen, oder mcpsnoop --history-limit 0, um den gesamten Verlauf zu laden. Ältere Sitzungen bleiben über mcpsnoop open <session-id> und mcpsnoop export <session-id> verfügbar.
Der Verlaufsgrenzwert begrenzt, was geladen wird; mcpsnoop prune begrenzt, was aufbewahrt wird. Er löscht gespeicherte Sitzungsprotokolle, die älter als ein Stichtag sind, und läuft niemals von selbst.```bash
mcpsnoop prune --older-than 30d --dry-run # list what would go, remove nothing
mcpsnoop prune --older-than 30d # delete after confirming
mcpsnoop prune --older-than 72h --yes # skip the prompt in a script
`--older-than` ist erforderlich (es gibt keinen Standardwert, der etwas löschen würde) und
akzeptiert eine Tagesanzahl wie `30d` oder eine Go-Dauer wie `72h`. Tool-Baselines werden
nicht angefasst, da eine Baseline über das Server-Label und nicht über die Sitzung identifiziert wird.
Da es direkt in der eigentlichen Pipe sitzt und nicht abseits wie der Inspector, sieht es
genau das, was dein realer Client und Server einander sagen, egal in welcher Sprache der
Server geschrieben ist.
## Tastenkürzel
| Taste | Aktion | | Taste | Aktion |
|---|---|---|---|---|
| `enter` | inspizieren / aufklappen | | `/` | filtern |
| `esc` | zurück | | `:` | Befehl |
| `j` / `k` | bewegen | | `r` | Aufruf erneut abspielen |
| `g` / `G` | Anfang / Ende | | `c` | Fähigkeiten |
| `ctrl-f` / `ctrl-b` | blättern | | `s` | Tool-Zusammenfassung |
| `p` | Pause | | `y` | kopieren |
| `shift`+`<key>` | nach Spalte sortieren | | `e` | exportieren |
| `ctrl-d` | Sitzung löschen | | `f` | folgen |
| `?` | Hilfe | | | |
Drücke `?` in der App für die vollständige Liste.
## Den Datenstrom filtern
Drücke `/` in einer Sitzung und kombiniere durch Leerzeichen getrennte, UND-verknüpfte Tokens. Klartext
wird gegen Methode, Tool, ID und Payload abgeglichen.
| Token | Filtert nach | Beispiel |
|---|---|---|
| `tool:` | Tool-Name | `tool:search` |
| `method:` | JSON-RPC-Methode | `method:tools/call` |
| `id:` | Anfrage-ID und alle Wiederholungen, die sie fortsetzen | `id:7` |
| `task:` | Task-ID | `task:01J...` |
| `dir:` | Richtung (`c2s`, `s2c`) | `dir:s2c` |
| `kind:` | Frame-Typ (`req`, `resp`, `notify`, `stderr`, `invalid`) | `kind:invalid` |
| `status:` | Aufrufergebnis (`ok`, `error`, `cancel`, `late`, `cancelled`, `pending`, `bad`, `warn`, `mismatch` oder ein HTTP-Status wie `401`) | `status:error` |
Kombiniere Tokens, um gezielter zu filtern.```text
tool:search status:pending # in-flight calls to one search tool
status:cancel # calls the client gave up on (status:cancelled is a cancelled task)
status:late # results that arrived after the cancellation
method:tools/call status:error # tool calls that failed
dir:s2c kind:req # server-initiated requests (servers before 2026-07-28)
Der letzte findet nur etwas auf einem Server, der ein Protokoll von 2025-11-25 oder früher spricht. Die Revision vom 2026-07-28 entfernte vom Server initiierte Anfragen, und ein Server, der etwas vom Client benötigt, beantwortet nun die eigene Anfrage des Clients und bittet darin darum, woraufhin der Client es erneut versucht. mcpsnoop verknüpft diese Wiederholungen mit der Anfrage, die sie fortsetzen, sodass der Austausch wie ein einziger Aufruf statt mehrerer erscheint.
Wandeln Sie jede aufgezeichnete Sitzung in eine portable Datei um.```bash mcpsnoop export -T json|html|text|har|otlp [-o file|-] [session-id|log.jsonl|-]
| Format | Was du bekommst |
|---|---|
| `json` | korrelierte Aufrufe, Zählungen pro Tool sowie p50/p95/p99-Latenz, langsamste Aufrufe, Fähigkeiten und Roh-Frames |
| `html` | eine eigenständige Browserdatei mit Suche und aufklappbarem JSON |
| `text` | eine übersichtliche Klartext-Ausgabe |
| `har` | ein Eintrag pro korreliertem Aufruf, in den Browser-Devtools und allem anderen, das HAR lesen kann, geöffnet werden kann |
| `otlp` | OTLP-JSON mit einem Span pro korreliertem Aufruf; W3C-Trace-Kontext verbindet Aufrufer-Traces, andernfalls wird pro Sitzung ein Trace verwendet |
MCP ist nicht HTTP, daher sind die URL, der Statuscode und die Zeitangaben eines HAR-Eintrags eine bewusste Zuordnung jedes Aufrufs und kein Transkript der tatsächlichen Übertragung.
Für OTLP liefert der `_meta.traceparent` einer Anfrage die Trace- und Parent-Span-IDs dieses Aufrufs, und `_meta.tracestate` reist auf dem Span mit. Wenn der Traceparent fehlt oder ungültig ist, behält mcpsnoop den aus der Sitzung abgeleiteten Trace bei und führt keinen State mit. mcpsnoop beobachtet, nimmt aber nicht teil, daher fügt es keinen eigenen Vendor-Eintrag hinzu und gibt den State des Aufrufers unverändert weiter.```bash
mcpsnoop export -T html -o out.html # an HTML file to open in a browser
mcpsnoop export -T text server.py-48213-7f3a1c9e2b04 # a specific session, as text
mcpsnoop export -T json | jq # the newest session, piped to jq
mcpsnoop export -T har -o session.har # a HAR file to open in browser devtools
mcpsnoop export -T otlp -o trace.json # import into an OTLP-compatible tracing backend
Wenn -o weggelassen wird, wird in die Standardausgabe geschrieben; ohne Session wird die neueste verwendet. Alternativ können Sie - übergeben, um JSONL von stdin zu lesen. In der TUI drücken Sie e, um die ausgewählte Session als HTML zu exportieren, oder führen Sie :export json|html|text|har|otlp [path] im Befehlsmodus aus.
Um eine vorhandene Aufzeichnung vor dem Anzeigen oder Teilen zu bereinigen, übergeben Sie dieselben Schwärzungsflags, die bei der Aufnahme verwendet wurden, an export oder open:```bash
mcpsnoop export session.jsonl --redact-secrets --redact-key project_token -o shared.json
mcpsnoop open session.jsonl --redact-path '$.params.arguments.password'
These Flags schreiben die exportierte Datei oder die In-Memory-TUI-Ansicht um, niemals das Quell-JSONL. `export` verweigert eine Ausgabe, die denselben Dateinamen wie die Eingabe trägt, und schreibt über eine temporäre Datei, die anschließend an Ort und Stelle umbenannt wird, sodass ein fehlgeschlagener Lauf die vorherige Datei unversehrt lässt.
Die `inputSchema` und `outputSchema` eines Tools, wie sie in einem `tools/list`-Ergebnis angekündigt werden, werden von `--redact-key` und `--redact-secrets` unangetastet gelassen. Ein Name in einem Schema ist eine Typdeklaration und kein Wert – der Name selbst bleibt in jedem Fall im Protokoll, und das Bereinigen des Unterschemas unter einer Eigenschaft namens `token` würde die eigenen Prüfungen des Tools mit entfernen. Die Ausnahme gilt nur für diese Position, sodass ein Argument, das zufällig `inputSchema` heißt, wie jedes andere bereinigt wird, und sie stoppt bei `default`, `const`, `examples` und `enum`, die Daten statt Struktur enthalten. Verwende `--redact-path`, um etwas innerhalb eines Schemas zu benennen, oder `--redact-value`, das Text überall dort abgleicht, wo er steht, außer in den beiden Schlüsselwörtern, die mcpsnoop parst, `type` und `x-mcp-header`.
Was jedes Flag erreicht, unterscheidet sich, also prüfe das Ergebnis, statt Annahmen zu treffen. Alle vier bereinigen JSON-RPC-Nutzdaten, und `--redact-key`, `--redact-path` und `--redact-secrets` erreichen nur diese. Nur `--redact-value` bereinigt zusätzlich stderr, anderen Nicht-JSON-Text und das Innere eines Strings. Ein `Mcp-Param-*`-Header wird zusammen mit dem korrespondierenden Body-Wert bereinigt; die übrigen Envelope-Metadaten, Server-Labels, `Mcp-Name`, `Mcp-Method` und der HTTP-Status bleiben unverändert wie erfasst. Die Schwärzung erfolgt nach bestem Bemühen; verwende daher einen separaten Ausgabepfad und lies das Ergebnis, bevor du es teilst.
### Abgeschlossene Aufrufe an einen OTLP-Collector streamen
Sende Spans, während der Proxy läuft, indem du ihn auf einen OTLP/HTTP-JSON-Traces-Endpunkt ausrichtest. Wiederhole `--otlp-header` für Collector-Authentifizierung oder Tenant-Header.```bash
mcpsnoop \
--otlp-endpoint http://localhost:4318/v1/traces \
--otlp-header "Authorization=Bearer $OTLP_TOKEN" \
-- node build/index.js
mcpsnoop http \
--target http://localhost:3000/mcp \
--otlp-endpoint http://localhost:4318/v1/traces
Die Zustellung erfolgt nach dem Best-Effort-Prinzip und blockiert nie den proxied MCP-Verkehr. Falls der Collector nicht verfügbar ist, wiederholt mcpsnoop den Vorgang im Hintergrund und verwirft neue Trace-Frames, wenn seine begrenzte Warteschlange voll ist. Das normale JSONL-Sitzungsprotokoll bleibt die dauerhafte Aufzeichnung.
Vergleiche zwei gespeicherte Sitzungen anhand ihrer ID oder des JSONL-Pfads.```bash mcpsnoop diff before-session after-session mcpsnoop diff old.jsonl new.jsonl
Der Bericht zeigt Werkzeuge, die hinzugefügt oder entfernt wurden, Änderungen an Beschreibung und `inputSchema`, passende Tool-Aufrufe, deren Status sich geändert hat, sowie bemerkenswerte Verschiebungen der Dauer. Aufrufe werden nach Tool-Name und Argumenten abgeglichen, sodass neu angeordnete Aufrufe weiterhin korrekt verglichen werden. Standardmäßig müssen sich Daueränderungen um mindestens 100 ms und den Faktor 2 unterscheiden; verwenden Sie `--duration-threshold` und `--duration-ratio`, um diese Grenzwerte anzupassen.
Übergeben Sie `--exit-code`, um CI anhand von Regressionen zu bewerten: Es wird mit einem Nicht-Null-Status beendet, wenn die Nachher-Sitzung ein Tool entfernt, eine Tool-Beschreibung, einen Titel, ein Eingabeschema, ein Ausgabeschema oder Annotationen ändert, einen Aufruf enthält, dessen Status sich verschlechtert hat, oder langsamer wird. Verbesserungen (hinzugefügte Tools, behobene Aufrufe, Beschleunigungen) enden weiterhin mit Exit-Code 0, ebenso wie eine Symboländerung, die das Aussehen eines Tools verändert, ohne seine Funktionsweise zu ändern.
## Sitzungen in CI prüfen
Bewerten Sie einen aufgezeichneten Agentenlauf anhand von Fehlern, Stream-Beschädigung, Protokollwarnungen, Routing-Header-Konflikten, Aufrufen, die nie eine Antwort erhalten haben, verworfenen Frames, die die Aufzeichnung unvollständig machen, Tool-Definitions-Abweichungen oder der Verwendung veralteter Protokollfunktionen.```bash
mcpsnoop check [--format text|junit|sarif] [--fail-on error,invalid,warn,mismatch,pending,late-result,drift,deprecated,incomplete,schema] [session-id|log.jsonl|-]
error, invalid und warn lassen die Prüfung bereits für sich allein fehlschlagen. Der Rest ist opt-in.
Übergebe eine durch Kommas getrennte Teilmenge, um nur auf das zu prüfen, was für einen Job von Belang ist, lasse die
Sitzung weg, um die neueste Aufzeichnung zu prüfen, oder verwende -, um JSONL von stdin zu lesen.
Jedes Signal wird gezählt, unabhängig davon, ob es als Gate dient; ein Lauf zeigt also, was er gefunden hat, bevor man entscheidet, worauf er fehlschlagen soll.``` session build-agent: errors=1 invalid=0 warnings=0 mismatches=0 pending=0 late_results=0 deprecated=0 missing_frames=0 schema_findings=1 schema findings: oneOf: search check failed: error
Die Anzahl der verworfenen Frames wandert ebenfalls mit den Artefakten, sodass ein Capture, das sich selbst untertreibt, dies überall dort anzeigt, wo es geöffnet wird: `missing_frames` im JSON-Export, `log.comment` in HAR und das Ressourcenattribut `mcpsnoop.session.missing_frames` in OTLP.```bash
mcpsnoop check build-agent
mcpsnoop check --fail-on error,invalid artifacts/session.jsonl
mcpsnoop check --fail-on mismatch gateway-run.jsonl
Neben den Signalzählungen können Sie die Form des Laufs prüfen. Diese Prüfungen lassen sich miteinander und
mit --fail-on kombinieren; jeder Fehler führt zu einem Exit-Code ungleich null.
| Flag | Schlägt fehl, wenn |
|---|---|
--max-duration <dur> | ein oder mehrere abgeschlossene Tool-Aufrufe das Budget überschritten haben; meldet deren Anzahl und den schlechtesten Aufruf |
--expect-tool <name> | das genannte Tool nie aufgerufen wurde (wiederholbar) |
--forbid-tool <name> | das genannte Tool aufgerufen wurde (wiederholbar) |
mcpsnoop check --expect-tool search --forbid-tool delete --max-duration 2s run.jsonl
### Melde es dort, wo die CI bereits sucht
`--format junit` schreibt ein `<testcase>` pro Signal und Sitzung, und seine Fehlschläge
folgen derselben `--fail-on`-Auswahl wie die Textausgabe.```yaml
- name: Check captured MCP session
run: |
mkdir -p test-results
mcpsnoop check --format junit artifacts/session.jsonl > test-results/mcpsnoop.xml
- name: Upload mcpsnoop JUnit report
if: always()
uses: actions/upload-artifact@v4
with:
name: mcpsnoop-junit
path: test-results/mcpsnoop.xml
--format sarif schreibt stattdessen ein SARIF-2.1.0-Protokoll. Während JUnit einen
zusammengefassten Bericht pro Signal ausgibt, meldet SARIF ein Ergebnis pro Fund,
mit der Session, der Seq des Frames und dem eigenen Warn- oder Drift-Text des
Frames, und verweist auf die Zeile des Protokolls, aus der der Frame dekodiert
wurde. Ein Signal, das in --fail-on genannt wird, wird auf der Stufe error
gemeldet und eines außerhalb davon auf der Stufe note, sodass Bericht und
Gate niemals voneinander abweichen.
Ein Ergebnis verweist auf das Protokoll mit einem Pfad relativ zum
Arbeitsverzeichnis, den Code Scanning dann gegen den Repository-Stamm auflöst.
Der Alert wird nur dann mit seinen umgebenden Zeilen gerendert, wenn dieser Pfad
eine Datei im analysierten Commit ist. Ein Capture, den der Workflow in
artifacts/ erzeugt hat, öffnet daher einen Alert mit der Nachricht, der Regel
und der Zeilennummer, aber ohne Quellansicht. Einen Capture, der vollständig
gerendert werden soll, fest einzuchecken, ist der einzige Weg, einen solchen zu
erhalten. Ein Protokoll, das aus dem State-Verzeichnis oder von stdin gelesen
wird, erhält überhaupt keinen Pfad.
Code Scanning lehnt eine Datei ab, deren Run mehr als 25.000 Ergebnisse enthält,
und zeigt nur die obersten 5.000 der akzeptierten an. Der Bericht ist daher auf
5.000 begrenzt: zuerst die Funde, an denen das Gate gescheitert ist, dann ein
mcpsnoop/report-truncated-Ergebnis, das angibt, wie viele ausgelassen wurden.
Die Text- und JUnit-Formate bleiben vollständig.
Um die Funde im Security-Tab zu platzieren, übergebe das SARIF-Protokoll an
upload-sarif. Der Job benötigt security-events: write, sonst antwortet der
Upload mit 403. check beendet sich bei einem Fund mit einem Status ungleich
Null, daher benötigt der Upload-Schritt if: always(), um bei den Runs, die
etwas zu melden haben, überhaupt ausgeführt zu werden; continue-on-error
übergibt die Bewertung an den Code-Scanning-Check, der bei einem Alert der Stufe
error fehlschlägt und als Pflicht-Check eingerichtet werden kann. Entferne es,
wenn du es vorziehst, dass der Check-Schritt selbst dafür sorgt, dass der Job rot
wird.```yaml
permissions:
security-events: write
actions: read contents: read
steps:
### Einen Routing-Header erkennen, der nicht zum Body passt
Beim Streamable-HTTP-Transport leitet ein Gateway anhand von `Mcp-Method` und `Mcp-Name` weiter, während der Server den Body liest. Ein Header, der nicht zum Body passt, bedeutet also, dass beide zwei unterschiedliche Requests betrachten. Das `mismatch`-Signal deckt das ab, ebenso einen Header, der auf einem Batch reitet, den er nicht adressieren kann, sowie einen vollständig fehlenden erforderlichen Header.
Seit 2026-07-28 ist ein fehlender Routing-Header ein Validierungsfehler, und ein konformer Server lehnt den Request mit `400` und `-32020` ab. mcpsnoop meldet dies erst, wenn bekannt ist, dass die Session diese Revision oder neuer spricht, da frühere Revisionen diese Header überhaupt nicht definieren und das Weglassen dort korrekt ist. Eine eigene `-32020`-Ablehnung eines Servers zählt als dasselbe Signal.
Ein Name oder eine Resource-URI, der beziehungsweise die nicht in einen HTTP-Feldwert passt, wird als Base64 in einem `=?base64?…?=`-Sentinel übertragen, der vor dem Vergleich dekodiert wird. Ein Client, der korrekt kodiert, wird daher nie markiert.
Bei HTTP-`tools/call`-Requests zeigt mcpsnoop außerdem jeden `Mcp-Param-{Name}`-Header an und vergleicht ihn, sobald die passende beworbene Tool-Definition bekannt ist, mit dem annotierten Argumentpfad. Verschachtelte Eigenschaften, der Base64-Sentinel, boolesche Werte und numerisch äquivalente sichere Ganzzahlen werden ohne False Positives durch String-Vergleich behandelt. Unbekannte Parameter-Header und Sessions ohne passende Tool-Definition bleiben rein beobachtend. Schlüssel- und wertbasierte Redaktion wird auf erfasste Parameter-Header-Werte angewendet, bevor sie eine Senke erreichen; ein Wert, den mcpsnoop selbst geschwärzt hat, wird niemals als Abweichung gemeldet.
### Abweichungen bei Tool-Definitionen erkennen
Die erste vollständige `tools/list`-Antwort, die für ein Server-Label beobachtet wird, wird zur vertrauenswürdigen Baseline. Spätere Sessions vergleichen diese Baseline Feld für Feld: die Beschreibung, den Titel, die Eingabe- und Ausgabe-Schemas, die Annotationen und die Icons sowie hinzugefügte oder entfernte Tools. Annotationen sind am wichtigsten, denn ein Tool, das mit `readOnlyHint` genehmigt wurde und sich später als destruktiv deklariert, ist genau der Rug-Pull, für den dieser Check existiert, und die Spezifikation weist Clients an, Annotationen als nicht vertrauenswürdig zu behandeln. Titel und Icons werden nachverfolgt, weil sie das sind, was der Benutzer sieht, und die Spezifikation stuft den `title` eines Tools höher ein als `annotations.title` und seinen Namen. Die Sessions-Tabelle und die Tool-Übersicht kennzeichnen Drift, ohne den MCP-Datenverkehr zu blockieren oder zu verändern.
Annotationen werden über ihre Spezifikations-Standardwerte verglichen, sodass ein Server, der einen Hinweis, auf den er sich ohnehin schon verlassen hat, nun explizit angibt, nicht gemeldet wird. Eine Baseline, die aufgezeichnet wurde, bevor mcpsnoop ein Feld nachverfolgt hat, funktioniert weiterhin für die Felder, die sie erfasst, und gibt an, für welche sie keine Antwort geben kann. Nehmen Sie die Aufzeichnung mit `mcpsnoop baseline --accept` erneut vor, sobald Sie den aktuellen Definitionen vertrauen.
Wenn sich ändert, was die Redaktion aufzeichnet, ändert sich auch, was die Drift-Erkennung vergleicht. Eine Baseline, die ohne `--redact-value` erstellt und dann gegen einen mit `--redact-value` erstellten Capture geprüft wird, meldet die geschwärzten Felder als geändert – das ist korrekt, da sich die aufgezeichnete Definition tatsächlich geändert hat. Nehmen Sie nach einer Änderung der Redaktionseinstellungen die Aufzeichnung mit `--accept` erneut vor.
Verwenden Sie für jeden Server, dessen Befehlsname oder Zielhost andernfalls kollidieren würde, ein stabiles, eindeutiges `--label`. Baselines werden im normalen mcpsnoop-State-Verzeichnis gespeichert, daher gelten `MCPSNOOP_HOME` und `XDG_STATE_HOME`.```bash
mcpsnoop check --fail-on drift session.jsonl
mcpsnoop baseline session.jsonl
mcpsnoop baseline --accept session.jsonl # trust a legitimate definition change
mcpsnoop baseline --reset session.jsonl # trust the next complete tools/list
In ephemerer CI startet das Zustandsverzeichnis leer, daher zeichnet der erste Lauf nur
die Baseline auf und meldet keine Drift. Die Baseline muss über mehrere Läufe hinweg erhalten
bleiben, damit spätere Läufe dagegen prüfen können. Weisen Sie --baseline auf ein eingechecktes
oder zwischengespeichertes Verzeichnis oder setzen Sie MCPSNOOP_HOME auf einen persistenten Pfad.```bash
mcpsnoop check --fail-on drift --baseline .mcpsnoop/baselines session.jsonl
`drift` ist für `check` opt-in; das Standard-Gate `error,invalid,warn` bleibt unverändert.
### Veraltete Protokollfunktionen markieren
Die Revision vom 2026-07-28 erklärt Roots, Sampling und Logging für veraltet. Sie bleiben mindestens ein Jahr lang funktionsfähig, also kennzeichnet mcpsnoop sie, anstatt sie als Fehler zu behandeln. Der Stream, der Capability-Inspektor und der Export kennzeichnen sie alle, und jede Markierung benennt den Ersatz.
Zwei der drei sind nun nur noch über eine Multi-Round-Trip-Anfrage erreichbar, bei der sich der Methodenname in der `inputRequests`-Map des Servers befindet und nicht im Frame selbst. Auch diese werden gekennzeichnet, damit ein Server, der auf das neue Muster umgestellt wurde, nicht stillschweigend die Meldung einstellt.```bash
mcpsnoop check --fail-on deprecated session.jsonl
Wie drift ist auch deprecated opt-in. Ein Standardlauf meldet die Anzahl und bleibt grün, sodass eine Sitzung, die eine weiterhin zulässige veraltete Funktion verwendet, CI nie von allein rot färbt.
Ein Server kann vollkommen gültig sein und für einen Agenten dennoch schwer zu nutzen sein. Clients unterscheiden sich darin, wie viel von JSON Schema sie tatsächlich unterstützen, und ein Tool, das das Modell immer wieder falsch aufruft, ist oft ein Tool, dessen Schema mehr verlangt hat, als der Client liefert.
Die Tool-Übersicht, die mit s geöffnet wird, hat eine SCHEMA-Spalte, die das Bemerkenswerteste am Schema jedes angekündigten Tools benennt, mit einem nachgestellten +, wenn es mehr als eine Art gibt.
Alle außer dem ersten sind Beobachtungen statt Urteile. Ein Schema, das oneOf verwendet, ist nicht falsch, sondern wird von verschiedenen Clients wahrscheinlich nur unterschiedlich gelesen; und ein Schema kann jeden beliebigen Dialekt deklarieren. no root ist die Ausnahme: Die Tool-Definition verlangt inputSchema und legt den Wurzeltyp auf "object" fest, sodass ein Client, der eine Auflistung validiert, dieses Tool vollständig ablehnt und es nie aufrufbar wird – ohne dass auf dem Draht etwas erklärt, warum. no root führt die Spalte aus diesem Grund an, und ein Schema, das durch mcpsnoops eigene Schwärzung unlesbar gemacht wurde, wird nie gemeldet, denn ein unlesbares Schema ist kein falsches.
Diese Unterscheidung bestimmt, was check mit ihnen macht. no root ist eine Warnung auf dem tools/list-Frame, sodass es ohne jedes Flag das Standard-Gate error,invalid,warn nicht besteht – genau das ist der Punkt: Ein Server, der ein unbrauchbares Tool ausliefert, beantwortet jeden Handshake normal und erhält schlicht nie einen tools/call. Die Beobachtungen werden als schema_findings gezählt und unter schema findings: gemeldet; sie lassen den Lauf nur fehlschlagen, wenn du schema zu --fail-on hinzufügst. Beide fließen in --format junit und --format sarif ein, und export enthält die Liste pro Tool unter summary.definitions.per_tool[].findings.```bash
mcpsnoop check session.jsonl # a non-object root already fails this
mcpsnoop check --fail-on schema session.jsonl # and now so do the observations
Die Spalte trägt die Warnfarbe und niemals das Rot der ERR-Spalte, und
mcpsnoop ändert weiterhin nichts an dem Datenverkehr, den es weiterleitet.
Es wird nichts aufgelöst oder abgerufen. Ein externer `$ref` wird allein an
seiner Form erkannt, und das Schema, auf das er verweist, wird nie gelesen.
### Sieh dir an, was dich der Server im Kontext kostet
Tool-Definitionen gelangen bei jeder Konversation in den Kontext des Modells
und Tool-Ergebnisse bei jedem Aufruf. Die Tool-Zusammenfassung (`s`) misst
beides anhand der Sitzung, die du tatsächlich erfasst hast.
Die Zeile `definitions` stellt die Fixkosten dar: wie viel die `tools/list`
dieses Servers wiegt, bevor ein einziger Aufruf getätigt wird. Die Spalte
`DEF` schlüsselt das pro Tool auf und `RESULT` ist, was die Antworten jedes
Tools bisher gekostet haben. Die Tabelle bleibt nach Fehlern und Latenz
sortiert; überfliege also `DEF`, um die teuren Definitionen zu finden; der
Export listet sie, die schwersten zuerst. Eine Zeile unter der Tabelle nennt
das einzelne schwerste Ergebnis, das eine Gesamtsumme verdeckt.
Bei den Kennzahlen der Definitionen handelt es sich um das JSON ohne
unbedeutende Leerzeichen, sodass ein Server, der seine `tools/list`
übersichtlich formatiert ausgibt, nicht als teurer gezählt wird als einer, der
das nicht tut, und derselbe Server über Aufzeichnungen hinweg dasselbe misst.
`RESULT` sind die Bytes, so wie sie angekommen sind: Ein Ergebnis ist eine
einmalige Nutzlast und kein Vertrag, den zu normalisieren sich lohnt.```bash
mcpsnoop export -T json | jq '.summary.definitions'
Der Export führt dieselben Zahlen, pro Tool und aufgeteilt in Beschreibungs- und Schema-Bytes, sodass eine umfangreiche Beschreibung und ein umfangreiches Schema getrennt bleiben und beides über Aufzeichnungen hinweg verfolgt werden kann. mcpsnoop diff sagt dir, ob sich eine Beschreibung oder ein Schema zwischen zwei Sitzungen geändert hat; die Größe dieser Änderung steckt im Export.
Das sind Bytes, keine Tokens. Eine Token-Zählung hängt vom Modell ab; sie zu messen würde bedeuten, einen Tokenizer mitzuliefern und zu entscheiden, wessen. Bytes sind exakt, und du kannst dein eigenes Verhältnis anwenden. Ein unvollständiges tools/list meldet, was es gesehen hat, als Untergrenze und sagt das auch, anstatt eine Teilsumme als Gesamtsumme auszugeben.
Beim Multi-Round-Trip-Muster übergibt der Server dem Client einen opaken requestState, und der Client muss ihn beim Wiederholungsversuch unverändert zurücksenden. Der Server wird angewiesen, ihn als angreiferkontrollierte Eingabe zu behandeln, denn ein Client, der daran herummanipuliert, kann versuchen, das Serververhalten zu ändern oder eine Autorisierungsprüfung zu umgehen.
In der Leitung sitzend sieht mcpsnoop den Wert hinausgehen und zurückkommen, sodass es sagen kann, wann der Vertrag gebrochen wurde. Es gibt drei Arten, wie er brechen kann, jede wird als Protokollwarnung beim Wiederholungsversuch gemeldet.
| Gemeldet | Bedeutung |
|---|---|
MRTR retry changed requestState | der Client hat etwas anderes zurückgesendet, als der Server ausgegeben hat |
MRTR retry is missing requestState | der Server hat einen ausgegeben, und der Wiederholungsversuch hat ihn weggelassen |
MRTR retry invented requestState | der Wiederholungsversuch trug einen, den der Server nie ausgegeben hat |
Das sind Protokollverletzungen durch den Client und nicht unsere Beobachtungen, also laufen sie über das normale Warnsignal, und ein standardmäßiger check-Lauf schlägt bei einer solchen fehl. Das ist beabsichtigt. Ein Client, der Serverzustand verfälscht, ist es wert, einen Build dafür zu stoppen.
Der Wert selbst wird niemals angezeigt oder protokolliert, und nichts dekodiert oder parst ihn. Es kann ein verschlüsselter Blob sein, der einen Principal und ein Token trägt, und der Vergleich opaker Bytes ist die gesamte Prüfung.
Ein Fall bleibt unerreichbar. Wenn ein Server mit einem requestState und ohne inputRequests antwortet, passt ein manipulierter Wiederholungsversuch auf nichts und beantwortet keine Schlüssel, sodass nichts mehr bleibt, um ihn mit der ursprünglichen Anfrage zu verknüpfen, und er erscheint als unabhängiger Aufruf statt als Verletzung.
Halte den Capture lokal auf dem Rechner, auf dem der Datenverkehr stattfindet, und nutze SSH für den Netzwerk-Hop, damit mcpsnoop niemals einen eigenen Remote-Transport benötigt.
Führe die TUI auf deiner Workstation aus und leite den mcpsnoop-Socket des entfernten Rechners zurück zu ihr. Der Live-Tunnel verwendet SSH-Unix-Socket-Weiterleitung, daher müssen beide Enden Linux oder macOS ausführen. Unter Windows verwende die Post-Mortem-Logkopie unten.```bash
mcpsnoop
ssh remote-user@remote-host 'mkdir -p ~/.local/state/mcpsnoop'
mcpsnoop remote remote-user@remote-host
mcpsnoop -- node build/index.js
Der Socket befindet sich im State-Verzeichnis des entfernten Systems, aufgelöst als `MCPSNOOP_HOME`, andernfalls als `XDG_STATE_HOME/mcpsnoop`, andernfalls als `~/.local/state/mcpsnoop`. Standardmäßig nimmt mcpsnoop das Linux-Homeverzeichnis `/home/<user>` aus deinem `user@host` an und gibt eine Erinnerung an stderr aus, wann immer es auf diese Vermutung zurückfällt. Falls das entfernte System woanders auflöst, benenne das eine nicht standardmäßige Element.```bash
# a non-Linux or custom home, macOS is /Users/<user> and root is /root
mcpsnoop remote --remote-home /Users/remote-user remote-user@remote-host
# an explicit MCPSNOOP_HOME on the remote
mcpsnoop remote --remote-mcpsnoop-home /srv/mcpsnoop remote-user@remote-host
# an explicit XDG_STATE_HOME on the remote
mcpsnoop remote --remote-xdg-state-home /var/lib/state remote-user@remote-host
Streamen Sie eine Remote-Sitzung direkt per SSH in die TUI – keine lokale Kopie nötig.```bash ssh remote-user@remote-host 'cat ~/.local/state/mcpsnoop/sessions/session.jsonl' | mcpsnoop open -
Um stattdessen eine lokale Kopie zu behalten, kopiere die Logs per scp in dein
Sessions-Verzeichnis und führe die TUI wie gewohnt aus.```bash
# copy the remote logs into your local sessions directory
mkdir -p ~/.local/state/mcpsnoop/sessions
scp remote-user@remote-host:'~/.local/state/mcpsnoop/sessions/*.jsonl' \
~/.local/state/mcpsnoop/sessions/
# open the TUI, it backfills the copied sessions
mcpsnoop
mcpsnoop führt den Serverbefehl aus, den Sie kapseln. Kapseln Sie also nur Server, denen Sie vertrauen, und führen Sie nicht vertrauenswürdige in einem Container aus. Es führt niemals etwas aus, das Sie nicht in Ihre Client-Konfiguration aufgenommen haben.
Erfasste Frames können Prompts, Tool-Argumente, Anmeldedaten und Tool-Ergebnisse enthalten. Wenn Nutzlasten Geheimnisse enthalten können, aktivieren Sie die Schwärzung, um die beobachteten Trace-Kopien zu bereinigen, während die weitergeleiteten Bytes unverändert passieren.
Die schlüsselbasierte Schwärzung ersetzt vollständige Werte unter passenden JSON-Objekt-Schlüsseln, und derselbe Schlüsselsatz wird bestmöglich auf die Befehlszeilenargumente des gekapselten Servers angewendet, sodass --api-key=sk-x und --token sk-x unter --redact-secrets geschwärzt werden. Ein Argument, das ein Geheimnis ohne erkennbaren Flag-Namen trägt, kann nicht erkannt werden.
Die pfadbasierte Schwärzung ersetzt nur Werte, die durch einen JSONPath-Ausdruck ausgewählt werden. Das ist nützlich, wenn ein gebräuchlicher Schlüsselname an einer Stelle sensibel, an einer anderen jedoch unbedenklich ist. Wiederholen Sie --redact-path, um mehr als eine Stelle zu schwärzen.
Die wertbasierte Schwärzung wendet reguläre Ausdrücke auf beobachtete Zeichenfolgenwerte, stderr-Text und Nicht-JSON-Text-Frames an.
Alle drei Verfahren sind Best-Effort-Verfahren. Reguläre Ausdrücke können Geheimnisse übersehen, harmlosen Text übermäßig abgleichen oder transformierte oder kodierte Werte nicht erkennen.
Die Schwärzung wird niemals zu einer Anschuldigung. Jede Prüfung, die eine beobachtete Sache mit einer anderen vergleicht – einen Routing-Header mit dem Body, einen Mcp-Param-Wert mit dem Argument, das er spiegelt, ein Tool-Schema mit dem, was die Revision von einem verlangt – weiß, wann mcpsnoop die Seite war, die die Bytes umgeschrieben hat, und schweigt, anstatt einen Server für die eigene Datenschutzeinstellung des Benutzers zu melden.
Tool-Definitionsdrift ist die Ausnahme, und das ist beabsichtigt, da das Aktivieren der Schwärzung ändert, was aufgezeichnet wird und damit, was eine Baseline enthält. Siehe Tool-Definitionsdrift erkennen.```bash
mcpsnoop --redact-secrets -- node build/index.js
mcpsnoop --redact-key token,api_key,password -- node build/index.js
mcpsnoop --redact-path '$.params.arguments.password' -- node build/index.js
mcpsnoop --redact-path '$.params.arguments.accounts[*].password' -- node build/index.js
mcpsnoop --redact-value 'sk-[A-Za-z0-9]+' -- node build/index.js
mcpsnoop http --target http://localhost:3000/mcp --redact-secrets --redact-value 'Bearer\s+\S+'
Für Remote-Workflows verwenden Sie SSH-Tunneling oder SSH-Dateiübertragung, sodass Transport-Authentifizierung,
Verschlüsselung, Host-Überprüfung, Schlüsselrotation und Audit-Richtlinie in Ihrer
vorhandenen SSH-Umgebung bleiben.
## Mitwirken
Issues und Pull-Requests sind willkommen. Einzelheiten finden Sie in [CONTRIBUTING.md](https://github.com/kerlenton/mcpsnoop/blob/HEAD/CONTRIBUTING.md).
## Lizenz
[MIT](https://github.com/kerlenton/mcpsnoop/blob/HEAD/LICENSE)
| Befehl | Funktion |
|---|
mcpsnoop -- <server> | einen Stdio-Server als transparenten Shim kapseln |
mcpsnoop | die Live-TUI öffnen |
mcpsnoop http --target <url> | einen streamable-HTTP-Server per Proxy anbinden |
mcpsnoop export | eine Sitzung als json, html, text, har oder otlp ausgeben |
mcpsnoop check | CI bei Fehlern, ungültigen Frames, Warnungen, Routing-Abweichungen, hängenden Aufrufen oder verspäteten Ergebnissen fehlschlagen lassen |
mcpsnoop baseline | vertrauenswürdige Tool-Definitionen anzeigen, akzeptieren oder zurücksetzen |
mcpsnoop diff | Tools und Aufrufe zweier aufgezeichneter Sitzungen vergleichen |
mcpsnoop open | eine gespeicherte Sitzung in der TUI öffnen |
mcpsnoop prune | gespeicherte Sitzungsprotokolle löschen, die älter als ein Grenzwert sind |
mcpsnoop wrap <server> | einen der Server von Claude Desktop über mcpsnoop leiten |
mcpsnoop unwrap <server> | den Eintrag dieses Servers wieder in den ursprünglichen Zustand versetzen |
mcpsnoop remote <user@host> | den SSH-Tunnel-Befehl ausgeben |
mcpsnoop demo | eine skriptgesteuerte Sitzung abspielen |
| MCP Inspector | mcpsnoop |
|---|
| Sieht den echten Client- und Server-Traffic | nein | ja |
| Markiert hängende Aufrufe und Stream-Fehler | nein | ja |
| Markiert fehlgeleitete Ausgaben, die den Stream beschädigen | nein | ja |
| Markiert fehlerhafte JSON-RPC-Frames | nein | ja |
| Erkennt Drift der Tool-Definitionen nach der Freigabe | nein | ja |
| Interaktive Terminal-Benutzeroberfläche | nein | ja |
| Null Konfiguration, keine Flags oder Reihenfolge | nein | ja |
| Capability-Inspektor | teilweise | ja |
| Einen aufgezeichneten Aufruf wiedergeben | nein | ja |
| Sitzungsexport (json / html / text / otlp) | nein | ja |
| Einzelne Binärdatei, keine Laufzeitabhängigkeiten | nein | ja |
| Signal | Schlägt fehl bei |
|---|
error | einem Aufruf, der mit einem JSON-RPC-Fehler beantwortet wurde, einem Ergebnis, das als isError markiert ist, oder einer Aufgabe, die mit einem Fehler endete |
invalid | einem Frame auf dem Protokollkanal, der kein gültiges JSON-RPC ist – in der Regel, wenn ein Server auf stdout protokolliert |
warn | einem Frame, der eine Erwartung verletzt, die die MCP- oder JSON-RPC-Spezifikation setzt |
mismatch | einem Routing-Header, der vom Body abweicht, in einem Batch mitfährt oder fehlt, wo die Revision ihn verlangt |
pending | einer Anfrage, die bei Ende der Aufzeichnung noch offen ist, sodass der Aufrufer warten gelassen wurde |
late-result | einer Antwort, die eintrifft, nachdem ihre Anfrage abgebrochen wurde |
drift | einer angebotenen Tool-Definition, die sich ändert, nachdem die Baseline genehmigt wurde |
deprecated | einem Feature, das die Spezifikation als veraltet markiert hat |
incomplete | Frames, die vorgelagert verworfen wurden, was jede andere Zählung zu einer Untergrenze statt einer Gesamtsumme macht |
schema | einem angebotenen Schema, das ein Konstrukt oder einen Dialekt verwendet, der zwischen Clients schlecht übertragbar ist |
| Angezeigt | Bedeutung |
|---|
no root | das inputSchema fehlt, kein JSON-Objekt ist oder einen Wurzeltyp hat, der nicht "object" ist |
dialect | ein $schema, das einen anderen Dialekt als 2020-12 benennt, den die Revision standardmäßig verwendet |
ext ref | ein $ref, der aus dem Dokument herauszeigt – genau der Fall, bei dem die Spezifikation Implementierer davor warnt, ihm blind zu folgen |
oneOf, anyOf, allOf, not | ein Kompositions-Schlüsselwort, das von Clients uneinheitlich gehandhabt wird |
ref | ein $ref, der innerhalb desselben Dokuments verweist |
untyped | eine Eigenschaft, die keinen Typ und keine andere Möglichkeit deklariert, anzugeben, was sie akzeptiert |