
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 in deinem Terminal anzeigt.
Der offizielle MCP Inspector verbindet sich als eigener Client, sodass er nie sieht, 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. Wenn ein Tool stillschweigend nicht aufgerufen wird, Fähigkeiten nicht übereinstimmen oder ein Aufruf einfach hängt, bleibt dir nur das Durchwühlen von Logs und Raten.
mcpsnoop sitzt stattdessen im echten Datenpfad. Umschließe deinen Server-Befehl damit und beobachte jeden JSON-RPC-Frame live, während dein echter Client und Server miteinander sprechen.
Diese Seite ist auch der Eintrag für die mcpsnoop GitHub Action, also hier das Ganze. Sie prüft eine aufgezeichnete Sitzung, meldet jeden Befund als Code-Scanning-Alert und lässt den Job bei dem scheitern, worauf du es eingestellt hast.```yaml permissions: security-events: write contents: read
steps:
Pin die gewünschte Version fest. Die neueste findest du auf der
[Releases-Seite](https://github.com/kerlenton/mcpsnoop/releases). Jede Eingabe,
was die Exit-Codes bedeuten und wie du es ohne die Action verdrahtest, steht
weiter unten in [The GitHub Action](#the-github-action).
## Schnellstart
Sieh es dir sofort an, ohne etwas einrichten zu müssen.```bash
mcpsnoop demo
Um es wirklich zu nutzen, 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. Tausche ihn gegen das aus, was du bereits verwendest, wie `python server.py`, `npx -y @scope/server` oder eine kompilierte Binärdatei.
Bei 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 es 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.
Innerhalb des überschriebenen Eintrags erscheinen die Schlüssel wieder in alphabetischer Reihenfolge. unwrap
stellt die Datei wieder her und entfernt das Backup, sobald kein Server mehr gewrappt ist.
Starte Claude Desktop nach beiden Vorgängen neu, da MCP-Server nur einmal beim
Start 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. Der Shim und die UI finden
sich von selbst, und die UI füllt vergangene Sitzungen von der Festplatte nach.
Für einen streambaren 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 enthält, dennoch als sichtbarer Frame statt als Nichts erscheint: die 401-Herausforderung, die 403 bei einer abgelehnten Origin, die 202, die eine Benachrichtigung bestätigt, und die 502, wenn das Ziel überhaupt nicht erreichbar ist. Ein WWW-Authenticate-Header einer 401 wird wörtlich beibehalten und im Inspektor angezeigt, da er das Authentifizierungsschema und die Ressourcenmetadaten für den nächsten Schritt benennt. Filtere nach Status mit status:401 in der TUI oder nach jedem Fehler mit status:err. Ein 4xx- oder 5xx-Status gilt als Fehler, sodass ein standardmäßiger mcpsnoop check-Lauf dabei fehlschlägt.
Keinen eigenen Server? Probier es real aus gegen einen veröffentlichten Testserver, gesteuert von deinem eigenen Client. Um eine Sitzung im Nachhinein zu untersuchen, siehe frühere Sitzungen aus Logs überprüfen.
Wenn du dieselben Shim-Flags projektweit wiederverwendest, lege 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
Wiederholen Sie `redact-key`, `redact-value` und `redact-path` jeweils in einer eigenen Zeile, um
mehrere Einträge von jeder Art hinzuzufügen.
Das sind alle Schlüssel, die es unterstützt.
Die Datei wird nur im aktuellen Arbeitsverzeichnis gesucht, nicht in übergeordneten
Verzeichnissen.
Explizite Befehlszeilen-Flags überschreiben Werte aus der Konfigurationsdatei.
## Befehle
| Befehl | Funktion |
|---|---|
| `mcpsnoop -- <server>` | einen Stdio-Server als transparenten Shim umschließen |
| `mcpsnoop` | die Live-TUI öffnen |
| `mcpsnoop http --target <url>` | einen Streamable-HTTP-Server proxieren |
| `mcpsnoop export` | eine Sitzung als json, html, text, har oder otlp rendern |
| `mcpsnoop check` | CI bei Fehlern, ungültigen Frames, Warnungen, Routing-Abweichungen, hängenden Aufrufen, verspäteten Ergebnissen oder einem Latenzbudget fehlschlagen lassen |
| `mcpsnoop baseline` | vertrauenswürdige Tool-Definitionen prüfen, akzeptieren oder zurücksetzen |
| `mcpsnoop diff` | Tools und Aufrufe über zwei erfasste Sitzungen hinweg vergleichen |
| `mcpsnoop open` | eine gespeicherte Sitzung in der TUI öffnen |
| `mcpsnoop inventory` | jeden Server auflisten, der auf diesem Rechner über mcpsnoop gelaufen ist |
| `mcpsnoop stats` | alle gespeicherten Erfassungen zu einer Zeile pro Server und Tool zusammenfassen |
| `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 |
Führen Sie `mcpsnoop help` für die vollständige Liste aus, oder `mcpsnoop help <command>` für die Flags eines einzelnen Befehls.
## Vergleich
| | MCP Inspector | mcpsnoop |
|---|:---:|:---:|
| Sieht Ihren echten Client- und Server-Verkehr | nein | ja |
| Kennzeichnet hängende Aufrufe und Stream-Fehler | nein | ja |
| Kennzeichnet unerwünschte Ausgaben, die den Stream beschädigen | nein | ja |
| Kennzeichnet fehlerhafte JSON-RPC-Frames | nein | ja |
| Erkennt Tool-Definitions-Abweichungen nach der Freigabe | nein | ja |
| Interaktive Terminal-Oberfläche | nein | ja |
| Null-Konfiguration, keine Flags oder Reihenfolge | nein | ja |
| Fähigkeits-Inspektor | teilweise | ja |
| Einen erfassten Aufruf wiedergeben | nein | ja, über Stdio und über HTTP |
| Sitzungsexport (json / html / text / otlp) | nein | ja |
| Einzelne Binärdatei, keine Laufzeitabhängigkeiten | nein | ja |
## Installation
### npm
Keine Go-Toolchain erforderlich. Die meisten MCP-Server sind in Node oder Python geschrieben, daher ist dies
der schnellste Einstieg.```bash
npx mcpsnoop -- node build/index.js
Das npm-Paket enthält selbst keinen Code. Sechs Plattform-Pakete bringen jeweils einen
Build mit, und npm installiert genau das eine, das zu deiner Maschine passt, sodass
beim Installieren nichts heruntergeladen und in einem Proxy nichts freigeschaltet werden muss.
Um es dauerhaft zu behalten, statt es bei jedem Lauf neu zu holen: npm i -g mcpsnoop.
go install github.com/kerlenton/mcpsnoop/cmd/mcpsnoop@latest
### Homebrew```bash
brew install mcpsnoop
Fertig gebaute Binärdateien für jede Plattform findest du auf der Seite Releases.
mcpsnoop bringt Vervollständigungen für bash, zsh, fish und PowerShell mit. Führe
mcpsnoop completion <shell> --help für die Einrichtungsschritte aus, 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, der Bytes unverändert weiterleitet und dabei eine Kopie jedes
Frames an den Hub sendet. mcpsnoop ohne Argumente ist dieser Hub und seine Live-TUI. Sie
verbinden sich über einen bekannten Socket und Protokolldateien auf der Festplatte, sodass keiner zuerst starten muss.
Der Hub lädt standardmäßig die neuesten 100 gespeicherten Sitzungen, wodurch der Startaufwand
begrenzt bleibt, ohne ältere Aufzeichnungen 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, wie viele Sitzungen geladen werden. Innerhalb einer Sitzung ist die Live-TUI doppelt begrenzt, da ein Hub, der einen gesprächigen Server beobachtet, sonst wächst, bis er beendet wird. Er behält höchstens 64 MiB an Frame-Inhalten und gibt die ältesten zuerst frei, sowie höchstens 200.000 Frames, wobei die ältesten darüber hinaus vollständig verworfen werden. Die erste Begrenzung ist das, woran eine Aufzeichnung großer Nutzlasten stößt, und die zweite das, was ein langer Strom kleiner Benachrichtigungen tut.
Keine der Begrenzungen ändert eine Antwort. Ein Frame, dessen Inhalt freigegeben wurde, behält seine Zeile,
sein Urteil und seinen Platz in der Zeitleiste, und sein Inspektor sagt, dass der Inhalt weg ist,
anstatt einen leeren Frame zu zeigen. Ein Frame, der vollständig verworfen wurde, nimmt zuerst die Statistiken
seines Tool-Aufrufs in die laufenden Summen auf, sodass die Tool-Zusammenfassung und das, was der Server dich im Kontext kostet, jeden Aufruf beschreiben, den die Sitzung
gemacht hat, nicht nur die jüngsten. Die Fußzeile des Streams sagt, wie viele ältere Frames nur auf der Festplatte
liegen, und r lehnt einen Frame ab, dessen Parameter es nicht mehr enthält, anstatt etwas anderes
abzuspielen.
mcpsnoop open <session-id> liest das Protokoll und hält alles davon, und das Exportieren
aus der TUI liest das Protokoll ebenfalls, sodass keines begrenzt ist. check, export und
diff erstellen absichtlich einen unbegrenzten Speicher, da ein Tor, das bei einer großen Aufzeichnung zu wenig meldet, schlimmer ist als eines, das den Speicher nutzt.
Der Verlaufsgrenzwert begrenzt, was geladen wird. mcpsnoop prune begrenzt, was behalten wird.
Es löscht gespeicherte Sitzungsprotokolle, die älter als ein Grenzwert 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
bleiben unangetastet, da eine Baseline über die Server-Bezeichnung und nicht über die Sitzung
referenziert wird.
Da es direkt in der Pipe sitzt, nicht abseits wie der Inspector, sieht es
genau das, was Ihr echter Client und Server einander sagen, egal in welcher Sprache
der Server geschrieben ist.
## Tastenkombinationen
| Taste | Aktion | | Taste | Aktion |
|---|---|---|---|---|
| `enter` | untersuchen / hineinzoomen | | `/` | filtern |
| `esc` | zurück | | `:` | Befehl |
| `j` / `k` | bewegen | | `r` / `R` | abspielen / bearbeiten und abspielen |
| `g` / `G` | oben / unten | | `c` | Fähigkeiten |
| `ctrl-f` / `ctrl-b` | Seite | | `s` | Tool-Zusammenfassung |
| `p` | pausieren | | `y` | kopieren |
| `shift`+`<Taste>` | nach Spalte sortieren | | `e` | exportieren |
| `ctrl-d` | Sitzung löschen | | `f` | folgen |
| `?` | Hilfe | | | |
Drücken Sie `?` in der App für die vollständige Liste.
## Filtern des Streams
Drücken Sie `/` in einer Sitzung und kombinieren Sie durch Leerzeichen getrennte Tokens, UND-verknüpft. Klartext
stimmt mit Methode, Tool, ID und Payload überein.
| Token | Filtert nach | Beispiel |
|---|---|---|
| `tool:` | Tool-Name | `tool:search` |
| `method:` | JSON-RPC-Methode | `method:tools/call` |
| `id:` | Anfrage-ID und jede Wiederholung, die sie fortsetzt | `id:7` |
| `task:` | Aufgaben-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` |
Stapeln Sie Tokens, um gezielt 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)
The last one only finds anything on a server speaking 2025-11-25 or earlier. The 2026-07-28 revision removed server-initiated requests, and a server that needs something from the client now answers the client's own request asking for it, then the client retries. mcpsnoop links those retries back to the request they continue, so the exchange reads as one call rather than several.
Turn any captured session into a portable file.```bash mcpsnoop export -T json|html|text|har|otlp [-o file|-] [session-id|log.jsonl|-]
| Format | Was du erhältst |
|---|---|
| `json` | korrelierte Aufrufe, Zähler pro Tool sowie p50/p95/p99-Latenz, langsamste Aufrufe, Fähigkeiten und Roh-Frames |
| `html` | eine eigenständige Browserdatei mit Suche und einklappbarem JSON |
| `text` | eine übersichtliche Klartext-Ausgabe |
| `har` | ein Eintrag pro korreliertem Aufruf, in Browser-Devtools und allem anderen, das HAR liest, öffnbar |
| `otlp` | OTLP-JSON mit einem Span pro korreliertem Aufruf, wobei der W3C-Trace-Kontext die Aufrufer-Traces verbindet, sofern vorhanden, und andernfalls ein Trace pro Sitzung |
MCP ist nicht HTTP, daher sind URL, Statuscode und Zeitmessungen eines HAR-Eintrags eine bewusste
Zuordnung jedes Aufrufs und keine Draht-Transkription.
Für OTLP liefert `_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 sitzungsabgeleiteten Trace bei und führt keinen State mit.
mcpsnoop beobachtet, statt teilzunehmen, fügt also 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
Lasse -o weg, um nach stdout zu schreiben, und lasse die Sitzung weg, um die neueste zu nehmen, oder übergib
-, um JSONL von stdin zu lesen. In der TUI drücke e, um die ausgewählte
Sitzung als HTML zu exportieren, oder führe :export json|html|text|har|otlp [pfad] aus dem Befehlsmodus aus.
Um eine vorhandene Aufzeichnung vor der Prüfung oder Weitergabe zu bereinigen, übergib dieselben
Schwärzungsflags, die während der Aufzeichnung 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'
Diese Flags überschreiben die exportierte Datei oder die In-Memory-TUI-Ansicht, niemals das
Quell-JSONL. `export` verweigert eine Ausgabe, die dieselbe Datei wie seine Eingabe benennt,
und schreibt über eine temporäre Datei, die an Ort und Stelle umbenannt wird, sodass ein Lauf,
der fehlschlägt, die vorherige Datei unversehrt lässt.
Das `inputSchema` und `outputSchema` eines Tools, wie sie in einem `tools/list`-
Ergebnis beworben werden, werden von `--redact-key` und `--redact-secrets` aus drei Gründen
in Ruhe gelassen.
- Ein Name innerhalb eines Schemas ist eine Typdeklaration und kein Wert.
- Der Name selbst bleibt so oder so im Log.
- Das Bereinigen des Subschemas unter einer Eigenschaft namens `token` würde die eigenen
Prüfungen des Tools mitnehmen.
Die Ausnahme gilt nur für diese Position, sodass ein Argument, das zufällig
`inputSchema` heißt, wie jedes andere bereinigt wird, und es stoppt bei `default`,
`const`, `examples` und `enum`, die Daten statt Struktur enthalten. Verwenden Sie
`--redact-path`, um etwas innerhalb eines Schemas zu benennen, oder `--redact-value`, das
Text überall dort abgleicht, wo er sitzt, außer in den beiden Schlüsselwörtern, die mcpsnoop
parst, `type` und `x-mcp-header`.
Was jedes Flag erreicht, unterscheidet sich, also prüfen Sie das Ergebnis, statt es
anzunehmen. Alle vier bereinigen JSON-RPC-Payloads, und `--redact-key`, `--redact-path` und
`--redact-secrets` erreichen nur diese. Nur `--redact-value` bereinigt auch stderr,
anderen Nicht-JSON-Text und das Innere eines Strings. Ein `Mcp-Param-*`-Header wird
zusammen mit dem Body-Wert bereinigt, den er spiegelt. Die übrigen Envelope-Metadaten,
Server-Labels, `Mcp-Name`, `Mcp-Method` und der HTTP-Status, bleiben so
erfasst. Redaktion ist ein Best-Effort-Verfahren, also verwenden Sie einen separaten Ausgabepfad und lesen Sie das
Ergebnis, bevor Sie es teilen.
### Gestreamte abgeschlossene Aufrufe an einen OTLP-Collector senden
Senden Sie Spans, während der Proxy läuft, indem Sie ihn auf einen OTLP/HTTP-JSON-
Traces-Endpunkt ausrichten. Wiederholen Sie `--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 bestem Bemühen und blockiert niemals den über einen Proxy geleiteten MCP-Verkehr. Wenn 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.
Vergleichen Sie zwei gespeicherte Sitzungen anhand der ID oder des JSONL-Pfads.```bash mcpsnoop diff before-session after-session mcpsnoop diff old.jsonl new.jsonl
Der Bericht zeigt Tools, die hinzugefügt oder entfernt wurden, Beschreibungs- und `inputSchema`-Änderungen, passende Tool-Aufrufe, deren Status sich geändert hat, sowie bemerkenswerte Laufzeitverschiebungen. Aufrufe werden nach Tool-Name und Argumenten abgeglichen, sodass neu angeordnete Aufrufe weiterhin korrekt verglichen werden. Standardmäßig müssen Laufzeitänderungen um mindestens 100 ms und den Faktor 2 abweichen. Verwenden Sie `--duration-threshold` und `--duration-ratio`, um diese Grenzwerte anzupassen.
Übergeben Sie `--exit-code`, um CI auf Regressionen zu prüfen. Es beendet sich mit einem Nicht-Null-Status, 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 schlechter geworden ist
- langsamer wird
Eine Icon-Änderung führt nicht dazu, da sie nur das Erscheinungsbild eines Tools verändert, ohne dessen Funktion zu ändern. Verbesserungen, also hinzugefügte Tools, behobene Aufrufe und Beschleunigungen, führen weiterhin zu einem Null-Status.
## Überprüfen von Sitzungen in CI
Prüfen Sie eine aufgezeichnete Agentenausführung auf Fehler, Stream-Beschädigung, Protokollwarnungen, Routing-Header-Abweichungen, Aufrufe, die nie eine Antwort erhalten haben, verworfene Frames, die die Aufzeichnung unvollständig lassen, Tool-Definitionsabweichungen oder die 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 schlagen den Check von sich aus fehl. Der Rest ist optional.
Übergebe eine kommagetrennte Teilmenge, um nur auf das zu prüfen, was ein Job betrifft, lasse die
Session weg, um den neuesten Capture zu prüfen, oder verwende -, um JSONL von stdin zu lesen.
| Signal | Schlägt fehl bei |
|---|---|
error | einem Anruf, der mit einem JSON-RPC-Fehler beantwortet wird, einem Ergebnis, das als isError markiert ist, oder einer Aufgabe, die in einem Fehler endete |
invalid | einem Frame auf dem Protokollkanal, der kein gültiges JSON-RPC ist, meist wenn ein Server in stdout protokolliert |
warn | einem Frame, der eine Erwartung verletzt, die die MCP- oder JSON-RPC-Spezifikation festlegt |
mismatch | einem Routing-Header, der nicht zum Body passt, einen Batch mitreitet oder fehlt, wo die Revision ihn verlangt |
pending | einer Anfrage, die noch offen ist, als der Capture endete, sodass der Aufrufer wartend zurückgelassen wurde |
late-result | einer Antwort, die eintraf, nachdem ihre Anfrage abgebrochen wurde |
drift | einer beworbenen Tool-Definition, die sich nach der Genehmigung der Baseline ändert |
deprecated | einem Feature, das die Spezifikation als veraltet markiert hat |
incomplete | Frames, die vorgelagert verworfen wurden, was jeden anderen Zähler zu einer Untergrenze statt einer Gesamtsumme macht |
schema | einem beworbenen Schema, das ein Konstrukt oder einen Dialekt verwendet, der schlecht über Clients hinweg funktioniert |
Jedes Signal wird gezählt, unabhängig davon, ob es als Gate dient, sodass ein Lauf zeigt, was er gefunden hat, bevor du entscheidest, was darauf 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
The dropped-frame count travels with the artifacts too, so a capture that
understates itself says so wherever it is opened:
- `missing_frames` in the JSON export
- `log.comment` in HAR
- the `mcpsnoop.session.missing_frames` resource attribute in OTLP```bash
mcpsnoop check build-agent
mcpsnoop check --fail-on error,invalid artifacts/session.jsonl
mcpsnoop check --fail-on mismatch gateway-run.jsonl
Der Exit-Code sagt, welche der beiden Möglichkeiten eingetreten ist, und ein CI-Wrapper benötigt den Unterschied. 1 bedeutet, dass der Check lief und etwas die Hürde nicht bestanden hat, die Befunde sind also real und eine Veröffentlichung wert. 2 bedeutet, dass der Check nie stattfand: ein Pfad, der nicht existiert, eine Datei, die kein Sitzungsprotokoll ist, ein Zustandsverzeichnis, das nichts enthält, ein Flag, das nicht geparst wird. Bei einer 2 wird nichts auf stdout geschrieben, sodass eine Pipeline niemals einen leeren Bericht hochlädt, als wäre er ein Urteil.
Über die Signalmengen hinaus die Form des Laufs festlegen. Diese kombinieren sich miteinander und mit --fail-on, und jeder Fehler führt zu Exit-Code 1, dem Code, der bedeutet, dass der Check lief und etwas gefunden hat.
| Flag | Schlägt fehl, wenn |
|---|---|
--max-duration <dur> | eine oder mehrere abgeschlossene Tool-Aufrufe das Budget überschritten haben, wobei deren Anzahl und der schlechteste Aufruf gemeldet werden |
--expect-tool <name> | das benannte Tool nie aufgerufen wurde (wiederholbar) |
--forbid-tool <name> | das benannte Tool aufgerufen wurde (wiederholbar) |
mcpsnoop check --expect-tool search --forbid-tool delete --max-duration 2s run.jsonl
### Melde es dort, wo CI bereits hinschaut
`--format junit` schreibt ein `<testcase>` pro Signal und Sitzung, und dessen 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
aggregierten Bericht pro Signal ausgibt, meldet SARIF ein Ergebnis pro Befund, das
die Sitzung, die Frame-Seq und den eigenen Warn- oder Drift-Text des Frames
enthält und auf die Zeile des Protokolls verweist, 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 der Bericht und
das Gate nie voneinander abweichen.
Ein Ergebnis verweist auf das Protokoll, aus dem der Befund stammt, und wie das geschieht, hängt davon ab, woher das Protokoll gelesen wurde.
file://-URI.Der Alert wird nur dann mit seinen umgebenden Zeilen gerendert, wenn dieser Pfad
eine Datei im analysierten Commit ist. Ein Capture, das der Workflow in
artifacts/ erzeugt hat, öffnet also einen Alert mit der Nachricht, der Regel
und der Zeilennummer, aber ohne Quellansicht. Ein Capture, das du vollständig
gerendert haben möchtest, zu committen ist der einzige Weg, einen solchen zu
erhalten.
Code Scanning lehnt eine Datei ab, deren Lauf mehr als 25.000 Ergebnisse enthält,
und zeigt nur die obersten 5.000 der akzeptierten an, daher ist der Bericht auf
5.000 begrenzt: zuerst die Befunde, bei denen das Gate fehlschlug, dann ein
mcpsnoop/report-truncated-Ergebnis, das angibt, wie viele ausgelassen wurden.
Die Text- und junit-Formate bleiben vollständig.
Alles unten ist das, was die Action für dich erledigt. Sie installiert mcpsnoop, prüft den Capture, legt die Befunde im Security-Tab ab und lässt den Job bei dem fehlschlagen, worauf du das Gate gesetzt hast.```yaml permissions: security-events: write contents: read
steps:
Pinne eine Version an, welche auch immer du möchtest. Die neueste ist auf der
[Releases-Seite](https://github.com/kerlenton/mcpsnoop/releases). Es gibt bewusst
kein schwebendes `v1`. Die angepinnte Version ist auch die Binärdatei, die die
Action installiert, sodass die beiden nie voneinander abweichen können und es keinen
Versionsstandard gibt, der veralten kann.
| Eingabe | |
|---|---|
| `session` | der `.jsonl`-Capture, der geprüft werden soll, relativ zum Repository-Root. Erforderlich |
| `fail-on` | wie `--fail-on`, standardmäßig das, was die CLI standardmäßig verwendet |
| `args` | alle anderen `check`-Flags, wie auf einer Kommandozeile in Anführungszeichen. `--format` wird abgelehnt, da die Action den Bericht liest |
| `upload-sarif` | den Bericht an Code Scanning senden. `true` |
| `category` | der Code-Scanning-Namespace. `mcpsnoop`. Variiere ihn pro Zweig einer Matrix, sonst überschreiben sich die Zweige gegenseitig |
| `fail-on-findings` | den Job bei einem Befund fehlschlagen lassen. `true`. Setze `false`, um die Alerts zu erfassen und die erforderliche Prüfung von Code Scanning entscheiden zu lassen |
| `version` | welche mcpsnoop-Version installiert werden soll. Standardmäßig die Version, die du angepinnt hast |
| `install` | `false`, wenn mcpsnoop bereits im PATH ist, was der Weg auf einer Plattform ist, für die keine Version gebaut wurde |
Ausgaben sind `outcome`, `sarif` und `exit-code`. `outcome` ist `passed`,
`findings` oder `error`, und der dritte Fall sollte separat behandelt werden. Er
bedeutet, dass nichts geprüft wurde, was nicht dasselbe ist wie nichts gefunden zu
haben. **Ein Lauf, der nicht prüfen konnte, schlägt den Job fehl, egal was
`fail-on-findings` sagt**, denn eine Pipeline, die grün wird, obwohl sie nichts
verifiziert hat, ist schlimmer als eine, die fehlschlägt.
Der Job benötigt `security-events: write`, sonst antwortet der Upload mit 403. Setze
`upload-sarif: false` in einem Repository ohne Code Scanning.
### Oder verdrahte es selbst
Die Action besteht aus vier Schritten und keiner Magie. Es von Hand zu tun erfordert
dieselbe Sorgfalt, die es erfordert. Der Upload muss auf den Läufen ausgeführt
werden, die einen Bericht haben, also denjenigen, die mit 0 oder 1 beendet wurden,
und nicht denjenigen, die mit 2 beendet wurden, und der Schritt, der den Job
fehlschlagen lässt, muss danach kommen, sonst erreichen die Befunde nie den Tab,
den sie erreichen sollen.```yaml
permissions:
# required for all workflows
security-events: write
# only required for workflows in private repositories
actions: read
contents: read
steps:
- name: Check captured MCP session
id: check
run: |
code=0
mcpsnoop check --format sarif artifacts/session.jsonl > mcpsnoop.sarif || code=$?
echo "exit-code=$code" >> "$GITHUB_OUTPUT"
# 2 means the check never happened, so there is no report to publish and
# nothing was verified. Stop here rather than uploading an empty file.
[ "$code" -le 1 ] || exit 1
- name: Upload mcpsnoop SARIF report
if: ${{ !cancelled() }}
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: mcpsnoop.sarif
category: mcpsnoop
- name: Fail on findings
# Separate, and after the upload, so the findings reach the Security tab on
# exactly the runs that have some.
if: ${{ !cancelled() && steps.check.outputs.exit-code == '1' }}
run: exit 1
Beim Streamable-HTTP-Transport routet ein Gateway anhand von Mcp-Method und Mcp-Name, während der Server den Body liest. Ein Header, der nicht zum Body passt, bedeutet also, dass beide zwei verschiedene Anfragen betrachten. Das mismatch-Signal deckt das ab, ebenso wie einen Header, der auf einem Batch reitet, den er nicht adressieren kann, und einen erforderlichen Header, der vollständig fehlt.
In 2026-07-28 ist ein fehlender Routing-Header ein Validierungsfehler, und ein konformer Server lehnt die Anfrage mit 400 und -32020 ab. mcpsnoop meldet dies erst, wenn bekannt ist, dass die Sitzung diese Revision oder später spricht, da frühere Revisionen diese Header überhaupt nicht definieren und deren Weglassen dort korrekt ist. Die eigene -32020-Ablehnung eines Servers zählt als dasselbe Signal.
Ein Name oder eine Ressourcen-URI, die nicht in einen HTTP-Feldwert passt, wird Base64-kodiert in einem =?base64?…?=-Sentinel übertragen, der vor dem Vergleich dekodiert wird. Ein Client, der korrekt kodiert, wird also nie markiert.
Bei HTTP-tools/call-Anfragen zeigt mcpsnoop außerdem jeden Mcp-Param-{Name}-Header an und vergleicht ihn, wenn 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 String-Vergleichs-Fehlpositive behandelt. Unbekannte Parameter-Header und Sitzungen ohne passende Tool-Definition bleiben rein beobachtend. Schlüssel- und wertbasierte Redaktion wird auf erfasste Parameter-Header-Werte angewendet, bevor sie einen Sink erreichen, und ein Wert, den mcpsnoop selbst bereinigt hat, wird nie als Abweichung gemeldet.
Die obigen Routing-Header waren die einzigen, die ein Frame trug, sodass die übrigen verbindlichen Header des Streamable-HTTP-Transports nichts erreichten, das sie prüfen konnte. Content-Type war der deutlichste Fall. Die Antwortseite las ihn bereits, um einen SSE-Stream von einem JSON-Body zu unterscheiden, und verwarf ihn dann.
Ein HTTP-Frame trägt nun die Header, zu denen der Transport Regeln festlegt, und zwei dieser Regeln sind prüfbar.
| Regel | Gemeldet als |
|---|---|
der Client MUSS ein Accept senden, das sowohl application/json als auch text/event-stream auflistet | warn bei der Anfrage |
ein Server, der eine JSON-RPC-Anfrage beantwortet, MUSS Content-Type: application/json oder text/event-stream zurückgeben | warn bei der Antwort |
Beide Sätze lesen sich in 2025-11-25 und 2026-07-28 gleich, sodass diese – anders als die Drift- und Erweiterungsprüfungen – kein Revisions-Gate benötigen. Origin wird ebenfalls erfasst, da Server es MÜSSEN validieren und bei Ungültigkeit MÜSSEN sie mit 403 antworten. mcpsnoop kann Ihre erlaubten Origins jedoch nicht kennen, daher zeigt es den Wert an, statt ihn zu bewerten.
Wildcards zählen. Ein Client, der */* sendet, hat beide Typen angeboten und wird nie gemeldet, und ein charset-Parameter auf einem Content-Type wird ignoriert. Ein Log, das erfasst wurde, bevor mcpsnoop diese Header aufzeichnete, bleibt still, statt jeden Frame darin für einen Header zu melden, den niemand notiert hat, und stdio hat sie überhaupt nie.
Authorization wird bewusst nicht erfasst. Eine Challenge in Token-Fakten zu verwandeln ist ein eigenes Problem, und ein Bearer-Token auf die Festplatte zu legen ist nicht die Antwort darauf. Mcp-Session-Id und Last-Event-ID werden ebenfalls nicht erfasst. Die Revision 2026-07-28 entfernte beide und weist einen Server an, sie zu ignorieren, sodass keine Regel mehr zu prüfen bleibt.
Das erste vollständige tools/list, das für ein Server-Label beobachtet wird, wird zur vertrauenswürdigen Baseline. Spätere Sitzungen vergleichen diese Baseline Feld für Feld:
Tools, die hinzugefügt oder entfernt wurden, werden ebenfalls verglichen, was eher ein Mengenvergleich als ein Feldvergleich ist.
Annotationen sind am wichtigsten, da ein Tool, das mit readOnlyHint genehmigt wurde und sich später selbst als destruktiv deklariert, genau der Teppichauszug ist, für den diese Prüfung existiert, und die Spezifikation weist Clients an, Annotationen als nicht vertrauenswürdig zu behandeln. Der Titel und die Icons werden nachverfolgt, weil sie das sind, was der Benutzer sieht, und die Spezifikation stuft den title eines Tools über annotations.title und seinen Namen ein. Die Sitzungstabelle und die Tool-Zusammenfassung kennzeichnen Drift, ohne MCP-Verkehr zu blockieren oder zu verändern.
Annotationen werden über ihre Spezifikations-Standardwerte verglichen, sodass ein Server, der einen Hinweis, auf den er sich bereits verließ, plötzlich ausschreibt, nicht gemeldet wird. Eine Baseline, die aufgezeichnet wurde, bevor mcpsnoop ein Feld nachverfolgte, funktioniert weiterhin für die Felder, die es aufzeichnet, und gibt an, für welche es keine Antwort geben kann. Nehmen Sie die Aufzeichnung mit mcpsnoop baseline --accept erneut vor, sobald Sie den aktuellen Definitionen vertrauen.
Eine Änderung dessen, was die Redaktion aufzeichnet, ändert auch, was die Drift vergleicht. Eine Baseline ohne --redact-value, die dann gegen eine Aufnahme mit einer solchen geprüft wird, meldet die bereinigten Felder als geändert, was korrekt ist, da sich die aufgezeichnete Definition tatsächlich geändert hat. Nehmen Sie die Aufzeichnung nach Änderung der Redaktionseinstellungen mit --accept erneut vor.
Verwenden Sie ein stabiles, eindeutiges --label für jeden Server, dessen Befehlsname oder Zielhost andernfalls kollidieren würde. Baselines werden im normalen mcpsnoop-Zustandsverzeichnis gespeichert, sodass MCPSNOOP_HOME und XDG_STATE_HOME gelten.```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 ephemeralen CI startet das Zustandsverzeichnis leer, sodass ein Lauf nichts zum Vergleichen hat und stattdessen die Baseline aufzeichnet, anstatt sie zu verifizieren. **Ein Lauf, der bei Drift fehlschlagen sollte und dann nichts verifiziert hat, besteht nicht** und gibt an, welches Verzeichnis beibehalten werden soll. Das ist der einzige Fall, in dem das Aufzeichnen einer Baseline ein Fehlschlag ist. Ohne `drift` in `--fail-on` ist das Aufzeichnen einer Baseline der normale Ablauf und ändert keinen Exit-Code.
Die Baseline muss also zwischen Läufen überleben, damit ein Drift-Gate eine Bedeutung hat. Zeigen Sie mit `--baseline` auf ein eingechecktes oder zwischengespeichertes Verzeichnis oder setzen Sie `MCPSNOOP_HOME` auf einen dauerhaft gespeicherten Pfad.```
recorded first-seen tool baseline (trusted, not verified)
check failed: drift
Hier ist die Übersetzung des Chunks 53 von 89:
Hinweis: Da der Eingabetext (INPUT) leer ist, gibt es keinen Inhalt zu übersetzen. Bitte stellen Sie den tatsächlichen Text des Chunks bereit, damit ich ihn gemäß den Regeln ins Deutsche übersetzen kann.```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.
### Eine Funktion erkennen, die keine der beiden Seiten ausgehandelt hat
SEP-2133 hat optionale Funktionen aus dem Kernprotokoll in Erweiterungen verschoben,
die in der `extensions`-Map der Fähigkeiten jeder Seite beworben werden. Tasks ist eine davon,
also bedeutet am 2026-07-28 ein `tasks/get`, eine `notifications/tasks` oder ein `tools/call`,
das mit einem Task-Handle beantwortet wird, nur dann etwas, wenn die andere Seite gesagt hat, dass sie Tasks spricht.
Wenn das nicht der Fall ist, ist die Spezifikation eindeutig: Die unterstützende Partei MUSS entweder auf das Kernverhalten zurückfallen oder die Anfrage ablehnen. Es trotzdem zu tun, ist der Grund, warum eine Funktion verdrahtet zu sein scheint und dann stillschweigend nichts tut – und was ein Leser stattdessen bekommt, ist ein `-32601` oder ein `-32021` einige Frames später, oder eine Aufgabe, die nie Fortschritte macht. mcpsnoop warnt bei dem Frame, der die Erweiterung erreicht hat, und benennt, welche Seite sie nie beworben hat.```
tool "slow" answered with a task handle uses the io.modelcontextprotocol/tasks
extension, which the client never advertised
Es ist eine warn, daher schlägt ein standardmäßiger check-Lauf damit fehl. Es bleibt still, wann immer die Erfassung nicht zeigen kann, was ausgehandelt wurde – also bei einer Erfassung, die nach dem Handshake beginnt, oder einer, deren Fähigkeiten durch Ihre eigene Schwärzung entfernt wurden – sowie bei Revisionen vor dem 28.07.2026, wo tasks/* Kernprotokoll sind und deren Verwendung korrekt ist.
Die Revision vom 28.07.2026 verwirft Roots, Sampling und Logging. Sie funktionieren mindestens ein Jahr lang weiter, daher markiert mcpsnoop sie, anstatt sie als Fehler zu behandeln. Der Stream, der Fähigkeitsinspektor und der Export kennzeichnen sie alle, und jede Markierung benennt den Ersatz.
Zwei der drei sind nun nur noch über eine Anfrage mit mehreren Round-Trips erreichbar, bei der der Methodenname in der inputRequests-Map des Servers liegt und nicht auf dem Frame selbst. Auch diese werden gekennzeichnet, sodass ein Server, der zum neuen Muster gewechselt ist, nicht stillschweigend aufhört, dies zu melden.```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 ein weiterhin zulässiges veraltetes Feature nutzt, CI von sich aus nie rot färbt.
### Flag-Schema-Konstrukte, die Clients schlecht verarbeiten
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, als der Client
liefert.
Die Tool-Übersicht, geöffnet mit `s`, hat eine SCHEMA-Spalte, die das Auffälligste
an jedem beworbenen Tool-Schema benennt, mit einem nachgestellten `+`, wenn es mehr als eine
Art gibt.
| Angezeigt | Bedeutung |
|---|---|
| `no root` | die `inputSchema` fehlt, ist kein JSON-Objekt oder hat einen Root-Typ, der nicht `"object"` ist |
| `dialect` | ein `$schema`, das eine andere Dialekt nennt als das 2020-12, auf das die Revision standardmäßig zurückfällt |
| `ext ref` | ein `$ref`, der aus dem Dokument herauszeigt, was auch der Fall ist, vor dem die Spezifikation Implementierer warnt, ihm nicht blind zu folgen |
| `oneOf`, `anyOf`, `allOf`, `not` | ein Kompositions-Keyword, das über Clients hinweg inkonsistent gehandhabt wird |
| `ref` | ein `$ref`, der innerhalb desselben Dokuments zeigt |
| `untyped` | eine Eigenschaft, die keinen Typ und keine andere Möglichkeit deklariert, anzugeben, was sie akzeptiert |
Alle außer der ersten sind Beobachtungen und keine Urteile. Ein Schema, das `oneOf` verwendet,
ist nicht falsch, nur wahrscheinlich von verschiedenen Clients unterschiedlich gelesen, und ein
Schema darf jede Dialekt deklarieren, die es möchte. `no root` ist die Ausnahme: Die
`Tool`-Definition verlangt `inputSchema` und legt ihren Root-Typ auf `"object"` fest, sodass
ein Client, der eine Auflistung validiert, dieses Tool rundweg ablehnt und es nie aufrufbar
wird, ohne dass auf der Leitung etwas erklärt, warum. `no root` führt die Spalte aus
diesem Grund an, und ein Schema, das mcpsnoops eigene Schwärzung bereinigt hat, wird nie gemeldet,
da ein unlesbares Schema kein falsches ist.
Diese Trennung entscheidet, was `check` mit ihnen tut. `no root` ist eine Warnung auf dem
`tools/list`-Frame, sodass es das Standard-Gate `error,invalid,warn` ohne
jedes Flag fehlschlagen lässt, was der Punkt ist: 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 und lassen den Lauf nur fehlschlagen, wenn Sie `schema` zu `--fail-on` hinzufügen. Beide
erreichen `--format junit` und `--format sarif`, und `export` führt 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 Verkehr, 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.
r gibt einen erfassten Aufruf erneut gegen einen Live-Server aus. Bei einer Stdio-Erfassung
steht der Befehl im Protokoll, also startet mcpsnoop eine isolierte Kopie und sendet die
Anfrage an diese. Eine HTTP-Erfassung hat keinen zu startenden Befehl, und der Endpunkt, den
sie aufzeichnet, wird um seine Benutzerinformationen und jeden Abfragewert bereinigt, sodass er den Server
benennt, ohne eine Adresse zum Anwählen zu sein.
Du gibst also an, wohin ein Replay geht, und mcpsnoop wählt niemals einen Produktionsendpunkt an,
nur weil jemand eine Taste gedrückt hat.```bash
mcpsnoop open --replay-target https://api.example.com/mcp session.jsonl
mcpsnoop open --replay-target https://api.example.com/mcp
--replay-header 'Authorization: Bearer sk-…' session.jsonl
Ohne `--replay-target` sagt eine HTTP-Sitzung dies aus, statt einen Schlüssel anzubieten, der
nicht funktionieren kann. Mit einem solchen fragt `r` weiterhin vor dem ersten Senden einer Sitzung,
genauso wie ein aufgezeichneter Befehl beantwortet wird, bevor er ausgeführt wird.
Eine Anmeldedaten erreichen den Server über `--replay-header` und sonst nirgendwo.
mcpsnoop zeichnet keinen `Authorization`-Header auf und spielt keinen ab, sodass nichts
erfasst wird, das eine Wiedergabe leaken könnte.
Das wiedergegebene POST trägt, was der Transport vorschreibt, was ein POST des
nackten erfassten Bodys nicht tut: `MCP-Protocol-Version`, ein `Accept`, das sowohl
`application/json` als auch `text/event-stream` auflistet, `Mcp-Method`, `Mcp-Name`, wo die
Spezifikation es verlangt, und jeden erfassten `Mcp-Param-*`. Diese werden wortwörtlich
aus der Erfassung erneut gesendet, Base64-Sentinel und alles, sodass sie nicht mit dem Body
uneins sein können, wie es eine Neuableitung könnte. Der eine Header, der nicht kopiert wird, ist die
Protokollversion, weil der wiedergegebene Body die Revision deklariert, die mcpsnoop spricht, und der
Header zum Body passen muss.
`Mcp-Name` wird aus dem gesendeten Body abgeleitet statt kopiert, weil die
Spezifikation es aus `params.name` oder `params.uri` bezieht und verlangt, dass ein Server
einen Header ablehnt, der mit dem Body uneins ist, sodass eine Bearbeitung, die das Tool
umbenannt, sonst den alten Namen senden würde. Die `Mcp-Param-*`-Header spiegeln die
erfassten Argumente wider, sodass eine bearbeitete Wiedergabe keine davon sendet, statt etwas
über einen Body zu behaupten, den jemand neu geschrieben hat. Eine Erfassung kann nur Header in dieser
einen Familie setzen. Ein Log ist eine Datei, die Leute herumreichen, und wenn man ihm erlaubt, jeden Header zu benennen,
könnte es die Pflicht-Header überschreiben oder eine Anmeldedaten hinzufügen, die niemand übergeben hat.
Ein `Mcp-Param-*`, den eine Redaktionsregel bereinigt hat, stoppt die Wiedergabe mit einem Grund. Das Senden
des Platzhalters würde mcpsnoops eigene Bytes auf einen Live-Server setzen, als hätte ein Benutzer
sie getippt.
Eine Weiterleitung wird verweigert statt befolgt. Die Adresse ist die, die du benannt und
beantwortet hast, und das Befolgen eines 307 würde diese Wahl dem entfernten Ende überlassen, den Body
erneut senden und, bei einem Hop, der nur den Port ändert, auch die Anmeldedaten. mcpsnoop
meldet, wohin der Server es senden wollte, und lässt dich entscheiden, ob du stattdessen
diese benennst.
Eine Antwort, die als einzelnes JSON-Objekt eintrifft, und eine, die als Ereignisstrom
eintrifft, werden beide gelesen, und ein Fehler wird benannt statt nummeriert:
- ein 401 meldet das Schema, das der Server verlangte
- ein `-32020` meldet, woran es etwas auszusetzen hatte
- ein Nicht-JSON-RPC 400 oder 404 sagt, dass die Adresse kein Streamable-HTTP-Endpunkt
dieser Revision ist
### Die Latenz des Servers von der des Benutzers unterscheiden
Bei Multi-Round-Trip-Anfragen ist ein Tool-Aufruf mehrere Anfragen, und die
Sekunden, die eine Person mit der Beantwortung einer Aufforderung verbracht hat, liegen innerhalb der Spanne. Das ist
bewusst so, da dieses Intervall normalerweise das ist, das du am meisten sehen möchtest, aber es
bedeutet, dass eine Zahl nicht beide Fragen beantworten kann.
Bei einer `book_flight`-Kette, bei der der Server 1,2 Sekunden arbeitete, während der Benutzer 37
brauchte, gibt `check --max-duration 5s` dem Tool die Schuld für 38,2 Sekunden. Es tut das weiterhin,
weil das Ändern der Bedeutung dieses Flags jede Pipeline lockern würde, die es bereits
setzt. Zwei Geschwister benennen stattdessen, was sie messen.```bash
mcpsnoop check --max-server-duration 1s session.jsonl # the server's share alone
mcpsnoop check --max-round-trips 2 session.jsonl # how chatty a tool is
## 🛡️ Sicherheitshinweis
**Wichtig:** Dieses Tool ist ausschließlich für autorisierte Sicherheitstests und Bildungszwecke gedacht. Der Autor übernimmt keinerlei Verantwortung für Missbrauch oder Schäden, die durch die Verwendung dieses Tools entstehen. Stelle sicher, dass du die geltenden Gesetze und Vorschriften einhältst, bevor du dieses Tool verwendest.
## 📜 Lizenz
Dieses Projekt ist unter der [MIT-Lizenz](https://github.com/kerlenton/mcpsnoop/blob/main/LICENSE) lizenziert – Details findest du in der [LIZENZ](https://github.com/kerlenton/mcpsnoop/blob/main/LICENSE)-Datei.
## 🙏 Danksagungen
- Besonderer Dank geht an die Open-Source-Community für ihre kontinuierliche Unterstützung und Beiträge.
- Inspiriert von verschiedenen Open-Source-Sicherheitsprojekten und -Forschungsergebnissen.
## 📧 Kontakt
Bei Fragen, Vorschlägen oder Feedback kannst du dich gerne an uns wenden:
- **E-Mail:** [[email protected]](mailto:[email protected])
- **GitHub Issues:** [Projekt-Repository](https://github.com/example/security-tool/issues)
## 🚀 Schnellstart
### Voraussetzungen
- Python 3.8 oder höher
- pip (Python-Paketmanager)
- Grundkenntnisse in Netzwerksicherheit
### Installation
```bash
git clone https://github.com/example/security-tool.git
cd security-tool
pip install -r requirements.txt
python security_tool.py --target example.com --scan-type full
Schneller Scan:
python security_tool.py --target example.com --scan-type quick
Detaillierter Scan mit Bericht:
python security_tool.py --target example.com --scan-type full --output report.html
Das Tool unterstützt benutzerdefinierte Plugins zur Erweiterung der Funktionalität:
# Beispiel-Plugin
class CustomPlugin:
def __init__(self):
self.name = "Benutzerdefiniertes Plugin"
def run(self, target):
# Plugin-Logik hier
return {"status": "erfolgreich"}
import requests
# Beispiel-API-Aufruf
response = requests.get("https://api.example.com/v1/scan", params={"target": "example.com"})
print(response.json())
Das Tool unterstützt mehrere Berichtsformate:
| Format | Beschreibung | Verwendung |
|---|---|---|
| JSON | Maschinenlesbares Format | --format json |
| HTML | Visueller Bericht | --format html |
| CSV | Tabellenkalkulationsformat | --format csv |
| Druckbares Dokument | --format pdf |
Problem: ModuleNotFoundError: No module named 'requests'
Lösung: Installiere die erforderlichen Abhängigkeiten:
pip install -r requirements.txt
Problem: Berechtigungsfehler bei der Ausführung
Lösung: Verwende sudo oder führe das Tool mit Administratorrechten aus:
sudo python security_tool.py --target example.com
Wir freuen uns über Beiträge! Bitte beachte die folgenden Schritte:
git checkout -b feature/AmazingFeature)git commit -m 'Füge erstaunliche Funktion hinzu')git push origin feature/AmazingFeature)Das Projekt wird aktiv gepflegt und weiterentwickelt. Neue Funktionen und Verbesserungen werden regelmäßig veröffentlicht. Schau regelmäßig im Repository nach Updates.
Vielen Dank an alle Sponsoren, die dieses Projekt unterstützen!
Wir respektieren deine Privatsphäre. Dieses Tool sammelt keine persönlichen Daten und sendet keine Informationen an externe Server, es sei denn, du konfigurierst es ausdrücklich so.
Dieses Tool wird "wie besehen" ohne jegliche Garantie bereitgestellt. Die Nutzung erfolgt auf eigenes Risiko. Der Autor haftet nicht für direkte oder indirekte Schäden, die durch die Verwendung dieses Tools entstehen.
Vielen Dank für die Nutzung dieses Tools! Wir hoffen, es hilft dir bei deinen Sicherheitsbemühungen.
assertion failed: 1 tool call exceeded the 1s server budget (worst: tool "book_flight" held for 1.2s)
assertion failed: 1 tool call exceeded the 2 round trip budget (worst: tool "book_flight" took 3)
```
Beide sind standardmäßig deaktiviert, sodass ein Standard-`check`-Lauf unbeeinflusst bleibt, und beide werden aus Frame-Zeitstempeln und einem Link ausgelesen, den mcpsnoop bereits abgeleitet hat, sodass keiner über Absichten rät.
Drücken Sie `i` in der TUI für die Aufschlüsselung oder lesen Sie `interactions` in den JSON-, Text- und HTML-Exporten. Jeder Eintrag ist eine logische Operation mit ihrer Round-Trip-Anzahl, ihrer Gesamtdauer, dem Anteil, den der Server hielt, und dem Anteil, den sie auf den Client wartete, plus einer Zeile pro Hop, die benennt, wonach jede Antwort fragte. Die Zusammenfassung pro Tool erhält eine `TRIPS`-Spalte, sodass ein gesprächiges Tool sichtbar ist, ohne etwas zu öffnen.
`export --format har` legt den Serveranteil in `wait` und den Rest in `blocked`, wofür dieses Feld gedacht ist, sodass ein Viewer aufhört, eine 38-Sekunden-Serverwartezeit zu zeichnen, die nie stattfand.
Die Zählungen und die beiden Anteile werden akkumuliert, wenn Frames eintreffen, und nicht abgeleitet, wenn Sie danach fragen, weil der Live-Speicher alte Frames freigibt, um innerhalb seines Budgets zu bleiben, und eine abgeleitete Antwort stillschweigend ein Fenster statt einer Kette wäre. Die Aufschlüsselung pro Hop wird aus den noch gehaltenen Frames gelesen und sagt dies, wenn sie nur Teil einer Kette ist. `ServerTime + ClientTurnaround` ergibt die Gesamtdauer konstruktionsbedingt und nicht durch Arithmetik, der jemand vertrauen muss.
`--max-round-trips` beurteilt eine Kette, die noch läuft, weil jede Anfrage, die sie bereits gestellt hat, zählbar ist und ein Server, der immer wieder fragt, genau die Operation erzeugt, die niemand je abschließt. `--max-server-duration` wartet auf ein Ende, was die Regel ist, die `--max-duration` bereits anwendet, da eine noch offene Operation keine Latenz zu beurteilen hat.
Eine Operation, die mcpsnoop nicht verknüpfen konnte, bleibt ihr eigener Einzel-Hop-Eintrag. `matchRetry` verweigert absichtlich eine mehrdeutige Verknüpfung, und diese Ansicht füllt diese Lücke nicht.
Eine Operation, die eine Anfrage benötigte, trägt keine Hop-Aufschlüsselung, weil ein einzelner Hop die Gesamtdaten darüber wortwörtlich wiederholt. Eine Kette meldet einen Hop pro Anfrage und sagt dies, wenn der Speicher nicht mehr jeden Frame hält oder wenn Arbeit sich außerhalb des Anfrage-Antwort-Paars abspielt, aus dem ein Hop besteht, was ein Task-Handle tut.
### Sehen, was ein Server Ihren Benutzer gefragt hat
Elicitation ist der eine Pfad in MCP, bei dem eine Person Daten in einen Server eingibt, und unter MRTR sind Frage und Antwort nicht länger zwei Hälften eines Austauschs. Die Frage ist in einem `InputRequiredResult` vergraben, die Antwort kommt in `inputResponses` bei einem Retry unter einer anderen ID zurück, und das Einzige, was sie verbindet, ist der Link, den mcpsnoop bereits ableitet.
Ohne diese Paarung liest sich eine abgelehnte Passwortanfrage wie ein einfacher Tool-Fehler.```
tools/call login_legacy [form] creds: decline after 3s
password string
```
Drücke `l` in der TUI oder lies `elicitations` in den JSON-, Text- und HTML-Exporten.
Jede Zeile nennt die Operation, die die Frage unterbrochen hat, den Modus, die Nachricht,
was gefragt wurde, was der Benutzer getan hat und wie lange er gebraucht hat. Eine Frage, die
kein Retry je beantwortet hat, wird als ausstehend angezeigt, was MRTR zu einem normalen
Ergebnis macht und nicht zu einem Fehler, da die Spezifikation Servern vorschreibt, nicht
anzunehmen, dass ein Client überhaupt erneut versucht.
Formularzeilen listen die Eigenschaftsnamen von `requestedSchema` und ihre deklarierten Typen auf. Eine
Eigenschaft, deren Subschema eine Redaktionsregel ersetzt hat, zeigt einen unbekannten Typ anstelle des
Platzhalters, weil ein Platzhalter nichts ist, was der Server deklariert hat. URL-Zeilen tragen die Adresse
vollständig, die die Spezifikation einem Client vor der Zustimmung anzeigen lässt, und nennen den Host
einzeln, den sie gegen Subdomain-Spoofing hervorheben soll.
Das Ledger trägt nie einen übermittelten Wert. Was ein Benutzer getippt hat, bleibt in der
Erfassung für den, der es braucht, und es aus einer Zusammenfassungsoberfläche herauszulassen, die
zum Exportieren und Weiterreichen gebaut wurde, hält das vollständig aus der Redaktionsgeschichte
heraus. Am wichtigsten ist das im URL-Modus, wo die Spezifikation Anmeldedaten absichtlich platziert.
Ein Retry beantwortet die Runde, aus der er ausgestellt wurde, und keine andere. MRTR teilt einem Server
mit, dass er, wenn ein Client einen Teil des Gefragten weglässt, in einer neuen Runde erneut fragen
soll, sodass eine frühere Runde mit einem unbeantworteten Schlüssel neben einem beantworteten
normaler Verkehr ist und die unbeantwortete Hälfte ausstehend bleibt, statt sich die Antwort der
späteren Runde zu borgen.
Eine aufgezeichnete Frage ist begrenzt. Die Nachricht, die URL und die Feldliste werden für die
Lebensdauer der Sitzung gehalten, außerhalb des Frame-Budgets, das Bodies freigibt,
sodass ein Server keine beliebig teure Frage stellen kann. Die Grenzen liegen weit über jeder
echten Frage, und eine abgeschnittene Nachricht sagt, dass sie abgeschnitten wurde.
Nichts hier warnt und nichts hier ändert einen `check`-Exit-Code. Ein Ledger
zeichnet auf, was passiert ist. Es bewertet es nicht.
### Finde das Tool, das bei jedem vierten Lauf versagt
`check` liest eine Sitzung und `diff` liest genau zwei, sodass ein Tool, das
gelegentlich versagt, unsichtbar bleibt, bis jemand die Erfassungen von Hand öffnet. Über
sechzehn Erfassungen eines Servers, dessen `run_query` etwa ein Viertel der Zeit
`isError` beantwortet, meldet `check` die neueste, ehrlich, als sauber.```bash
mcpsnoop stats
mcpsnoop stats --since 7d --label prod
mcpsnoop stats --limit 20 --format json
```
```
## 🛡️ Sicherheitshinweis
**Wichtig:** Dieses Tool ist ausschließlich für autorisierte Sicherheitstests und Bildungszwecke gedacht. Die unbefugte Nutzung dieses Tools gegen Systeme, die Ihnen nicht gehören oder für die Sie keine ausdrückliche schriftliche Genehmigung haben, ist illegal und unethisch. Der Autor übernimmt keinerlei Verantwortung für Missbrauch oder Schäden, die durch die Verwendung dieses Tools entstehen. Sie sind allein für die Einhaltung aller geltenden Gesetze und Vorschriften verantwortlich.
## 📦 Installation
```bash
git clone https://github.com/example/repo.git
cd repo
pip install -r requirements.txt
```
## 🚀 Schnellstart
```bash
python main.py --target example.com --output results.txt
```
## ⚙️ Konfiguration
Die Konfiguration erfolgt über eine YAML-Datei:
```yaml
target: example.com
threads: 10
timeout: 30
output: results.txt
```
## 🧩 Verwendung als Modul
```python
from tool import Scanner
scanner = Scanner(target="example.com")
results = scanner.run()
print(results)
```
## 📚 Dokumentation
Ausführliche Dokumentation finden Sie im [Wiki](https://github.com/example/repo/wiki).
## 🤝 Beiträge
Beiträge sind willkommen! Bitte lesen Sie die [CONTRIBUTING.md](https://github.com/kerlenton/mcpsnoop/blob/main/CONTRIBUTING.md) für Details zum Einreichen von Pull Requests.
## 📄 Lizenz
Dieses Projekt ist unter der MIT-Lizenz lizenziert – siehe die Datei [LICENSE](https://github.com/kerlenton/mcpsnoop/blob/main/LICENSE) für Details.
## 🙏 Danksagungen
- Allen Mitwirkenden und Unterstützern dieses Projekts.
- Der Open-Source-Community für ihre kontinuierliche Unterstützung.
read 16 logs of 16 in ~/.local/state/mcpsnoop/sessions
SERVER TOOL CALLS ERR PROTO FAIL% SESS p50 p95 p99 DEF flaky-demo run_query 13 3 0 23.1% 3/13 434ms 519ms 519ms 195B docs-mirror run_query 3 1 0 33.3% 1/3 357ms 434ms 434ms 195B docs-mirror search_docs 12 0 0 0.0% 0/3 377ms 386ms 386ms 200B flaky-demo search_docs 52 0 0 0.0% 0/13 42ms 58ms 59ms 200B
`ERR` und `PROTO` sind separate Spalten, weil die Spezifikation sie zu
getrennten Dingen macht. Ein Tool, das `isError` beantwortet, meldet etwas, auf das ein
Modell reagieren und es erneut versuchen kann. Ein JSON-RPC-Fehler bedeutet, dass die Anfrage oder der Server falsch ist.
`SESS` ist die Anzahl der Sitzungen, die einen Fehler sahen, über die Sitzungen, die das
Tool aufriefen – das ist die Frage „ein Lauf von zehn“, die eine Rate über Aufrufe nicht
beantworten kann.
Zeilen sind gemeinsam nach Server und Label verschlüsselt. Der Server ist der aufgezeichnete
Befehl und das Arbeitsverzeichnis für stdio und der Endpunkt für HTTP, dieselbe
Identität, die `inventory` verwendet. Jede Hälfte allein bündelt etwas, das sie nicht sollte: das
Label allein vermischt zwei Server, die einen Namen ableiten, was immer dann passiert, wenn
zwei Checkouts eines Projekts denselben Einstiegspunkt ausführen, und die Identität allein vermischt einen
Befehl, der absichtlich als `prod` und erneut als `staging` ausgeführt wird. Beide Fehler
verschmieren zwei saubere Verteilungen zu einer, die keines von beiden beschreibt.
Wenn zwei Zeilen sich ein Label teilen, trägt die Zelle `SERVER` das Arbeitsverzeichnis
oder den Endpunkt, der sie unterscheidet, und das JSON trägt `command`, `cwd` und
`endpoint` in jeder Zeile. Ein Name, der nie mehrdeutig war, bleibt unverändert, sodass die
gewöhnliche Tabelle unverändert ist.
Jede Sitzung in einem Log wird eingefaltet, nicht nur die erste, sodass eine Datei, die durch
Verkettung von Captures entsteht, alle zählt.
Perzentile werden über die rohen Dauern gepoolt. Ein Median von Medianen ist ein Median
von nichts. Eine Operation mit mehreren Round-Trips ist ein Aufruf mit einer Dauer, egal wie
viele Anfragen sie benötigte, und ein noch offener Aufruf zählt zu `CALLS`, während er
keine Latenz beiträgt.
Ein Capture ist jeweils resident. Ein Log wird geladen, in die laufenden
Zähler eingefaltet und verworfen, bevor das nächste geöffnet wird, sodass ein Verzeichnis mit Hunderten
das größte einzelne Capture kostet und nicht deren Summe.
`--limit` standardmäßig auf hundert der neuesten Logs, und der Kopf sagt, wie viele von wie
vielen gelesen wurden, sodass eine begrenzte Antwort nie für eine vollständige durchgeht.
`stats` berichtet und sperrt nicht: Es schreibt nichts, berührt keine Baseline, öffnet
keinen Socket und beendet mit 0, wann immer der Durchlauf erfolgreich war.
### Sehen, welche Server hier tatsächlich gelaufen sind
Die Erkenntnis, die Leute über Shadow MCP ständig wiederholen, ist, dass Organisationen
mehrere Male mehr laufende MCP-Server entdecken, als irgendjemand genehmigt hat, weil ein
Server oft nur eine Abhängigkeit ist, die jemand einem IDE-Plugin hinzugefügt hat. Dasselbe
passiert im Kleinen auf einem einzelnen Laptop, und mcpsnoop hat die Antwort die ganze Zeit
aufgezeichnet, ohne sie je zu zeigen.```bash
mcpsnoop inventory
mcpsnoop inventory --tools # also count what each server last advertised
mcpsnoop inventory --format json # for something else to read
Eine Zeile pro Server statt pro Sitzung. Der Zeilenschlüssel ist der aufgezeichnete Befehl
und das Arbeitsverzeichnis, niemals das Label, denn das Label stammt vom
letzten Pfadelement des Befehls, und node ~/one/build/index.js und
node ~/two/build/index.js leiten beide index.js ab. Eine HTTP-Sitzung schlüsselt stattdessen auf den
Endpunkt, den sie proxierte, da mcpsnoop dort nichts gestartet hat.
Das Lesen ist ein Envelope pro Log, der Meta-Frame, den der Proxy zuerst schreibt, sodass dies
über ein Verzeichnis mit großen Aufzeichnungen günstig bleibt. --tools ist die Ausnahme und
liest ein Log pro Server, den jeweils letzten Lauf von jedem, weshalb es ein Flag
statt einer Spalte ist. Selbst dann ist das Lesen begrenzt, denn ein Tool-Inventar ist
Sitzungszustand, den der Store nach und nach einarbeitet, sodass eine hundert Megabyte große
Aufzeichnung durch ein festes Fenster gelesen wird, statt vollständig gehalten zu werden, um eine einzelne Ganzzahl zu erzeugen.
Wenn es keine Zählung gibt, sagt die Zeile, welche von drei Dingen passiert ist, denn ein Log, das nicht gelesen werden konnte, ist kein Server, der nichts angekündigt hat, und ein Satz für beides würde mcpsnoop etwas Falsches behaupten lassen.
Ein Befehl, den eine --redact-Regel umgeschrieben hat, wird wie aufgezeichnet gedruckt und markiert,
statt als der Befehl ausgegeben zu werden, der ausgeführt wurde. Zwei Läufe eines Servers, einer
bereinigt und einer nicht, sind zwei Zeilen. mcpsnoop kann nicht wissen, was der Platzhalter ersetzt hat,
und das Zusammenführen würde bedeuten, zu raten, dass die verborgenen Hälften übereinstimmten. Ein Server, der unter
zwei --label-Werten läuft, ist eine Zeile, die beide Namen trägt, da der Schlüssel der
Befehl und nicht der Name ist.
Nichts in einer Zeile wird von mcpsnoop geschrieben. Ein Befehl stammt von dem, der den Server
installiert hat, ein Arbeitsverzeichnis kommt vom Dateisystem, und ein abgeleitetes Label
kommt vom Befehl. Ein Wert, der ein Steuerzeichen enthält, wird in Anführungszeichen gesetzt statt
roh gedruckt, sodass ein Verzeichnis, dessen Name einen Zeilenumbruch enthält, das Feld, in dem es
gedruckt wird, nicht schließen und die folgenden Zeilen als Server lesen lassen kann, die nie
liefen. Ein Argument, das ein Leerzeichen enthält, wird ebenfalls in Anführungszeichen gesetzt, denn node "~/My Project/ build/index.js" ist sonst nicht von zwei Argumenten zu unterscheiden.
Alles, was der Durchlauf nicht einarbeiten konnte, wird im Kopfbereich genannt statt verworfen. Leere Logs werden getrennt von beschädigten gezählt, da ein Null-Byte-Log der gewöhnliche Rückstand eines Laufs ist, dessen exec fehlschlug, oder eines HTTP-Proxys, den niemand aufrief.
Die Ausgabe wird nach Name statt nach Aktualität sortiert, sodass zwei Läufe über ein Verzeichnis dieselben Bytes erzeugen, was sie als Basislinie nutzbar macht, um später dagegen zu diffen.
Zwei Lücken bestehen konstruktionsbedingt statt aus Versehen. Ein Lauf mit
--trace-file schrieb außerhalb des Sitzungsverzeichnisses und wird nicht erscheinen, und
prune löscht Logs, sodass „erstmals gesehen“ nur so alt ist wie das, was noch auf der
Festplatte ist. mcpsnoop berichtet, was auf dieser Maschine durch es lief. Es scannt kein Netzwerk,
liest keine Client-Konfiguration, auf die es nicht verwiesen wurde, und bewertet nichts.
Ein Tool, das mit result.isError antwortet, funktioniert. Es hat nachgesehen und nichts gefunden, oder es
hat die Eingabe abgelehnt. Ein Server, der mit einem JSON-RPC-Fehler antwortet, ist defekt. Beides war eine
Zahl in der Tool-Zusammenfassung, was bedeutete, dass ein wohlerzogenes Tool, das Domänenfehler
meldet, genau wie ein defekter Server aussah und über einem sortiert wurde.
Die ERR-Spalte trennt sie. Rot ist die Serverseite, also ein JSON-RPC-
Fehler oder eine Aufgabe, die fehlgeschlagen endete, ohne zu sagen, warum. Die Warnfarbe ist das eigene isError des Tools. Ein Tool mit beidem zeigt die Zählungen verbunden, Rot zuerst, und eine
Zeile unter der Tabelle nennt die beiden Summen, wann immer es eine Warnzahl zu
erklären gibt. Der Export trägt dieselbe Aufteilung als protocol_errors und
tool_errors neben der errors-Summe, zu der sie sich immer addieren.
check --fail-on error ist unverändert und feuert weiterhin auf beides, da ein Gate,
das eines davon ignorierte, ein Gate wäre, das ein Server ausschalten könnte, indem er das andere zurückgibt.```bash
mcpsnoop export -T json | jq '.summary.tools[] | {name, errors, protocol_errors, tool_errors}'
### Sehen Sie, was der Server Sie 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 aus der Sitzung, die Sie tatsächlich erfasst haben.
Die Zeile `definitions` ist die feste Kosten: wie viel das `tools/list` dieses Servers wiegt, bevor ein einziger Aufruf getätigt wird. Die Spalte `DEF` schlüsselt das pro Tool auf, und `RESULT` zeigt, was die Antworten jedes Tools bisher gekostet haben. Die Tabelle bleibt nach Fehlern und Latenz sortiert, also überfliegen Sie `DEF`, um die teuren Definitionen zu finden. Der Export listet sie zuerst nach Gewicht. Eine Zeile unter der Tabelle nennt das einzelne schwerste Ergebnis, das eine Gesamtsumme verdeckt.
Die Definitionszahlen sind das JSON ohne unbedeutende Leerzeichen, sodass ein Server, der sein `tools/list` hübsch formatiert ausgibt, nicht als teurer gezählt wird als einer, der das nicht tut, und derselbe Server misst über Erfassungen hinweg gleich. `RESULT` sind die Bytes, wie sie ankamen: Ein Ergebnis ist eine einmalige Nutzlast und kein Vertrag, der eine Normalisierung wert wäre.```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 jeweils über Aufzeichnungen hinweg verfolgt werden können. mcpsnoop diff sagt dir, ob sich eine Beschreibung oder ein Schema zwischen zwei Sitzungen geändert hat. Der Export ist der Ort, an dem das Ausmaß dieser Änderung lebt.
Das sind Bytes, keine Tokens. Eine Token-Anzahl hängt vom Modell ab, daher würde deren Messung bedeuten, einen Tokenizer auszuliefern 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, statt eine Teilsumme als Gesamtsumme auszugeben.
Beim Multi-Round-Trip-Muster übergibt der Server dem Client ein undurchsichtiges requestState, und der Client muss es beim erneuten Versuch unverändert zurücksenden. Dem Server wird gesagt, er solle es als angreiferkontrollierte Eingabe behandeln, weil ein Client, der daran herummanipuliert, versuchen kann, das Serververhalten zu ändern oder eine Autorisierungsprüfung zu umgehen.
Da mcpsnoop in der Leitung sitzt, sieht es den Wert hinaus- und zurückkommen und kann daher sagen, wann der Vertrag gebrochen wurde. Es gibt drei Arten, wie das brechen kann, jede wird als Protokollwarnung beim erneuten Versuch gemeldet.
| Gemeldet | Bedeutung |
|---|---|
MRTR retry changed requestState | der Client hat etwas anderes zurückgesendet als das, was der Server ausgegeben hat |
MRTR retry is missing requestState | der Server hat eines ausgegeben und der erneute Versuch hat es weggelassen |
MRTR retry invented requestState | der erneute Versuch trug eines, das der Server nie ausgegeben hat |
Das sind Protokollverstöße des Clients und keine Beobachtungen von uns, daher laufen sie über das normale Warnsignal, und ein standardmäßiger check-Lauf schlägt bei einem davon fehl. Das ist beabsichtigt. Ein Client, der den Serverzustand verfälscht, ist es wert, einen Build zu stoppen.
Der Wert selbst wird nie 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 undurchsichtiger Bytes ist die gesamte Prüfung.
Ein Fall ist außer Reichweite. Wenn ein Server mit einem requestState und ohne inputRequests antwortet, passt ein manipulierter erneuter Versuch auf nichts und beantwortet keine Schlüssel, sodass nichts übrig bleibt, um ihn mit der ursprünglichen Anfrage zu verknüpfen, und er liest sich als unabhängiger Aufruf statt als Verstoß.
Ein abgebrochener Austausch stört den nächsten nicht und wird auch nicht für immer aufbewahrt. Vierundsechzig offene Austausche sind weit mehr, als jeder Client gleichzeitig hat, sodass eine Sitzung, die mehr hält, solche hält, die niemand abschließen wird, und die ältesten werden ausgemustert, weil die Spezifikation Servern sagt, diesem Zustand eine kurze Ablaufzeit zu geben und ihn danach abzulehnen. Das Ausmustern wird gezählt und nicht stillschweigend durchgeführt. Die Stream-Fußzeile zeigt N unlinked und der Export trägt session.retired_exchanges, weil ein erneuter Versuch, der für eine ausgemusterte Operation eintrifft, als eigener Aufruf gelesen wird, und ein Leser, der Zählungen vergleicht, verdient es, darüber informiert zu werden.
Das Ausmustern eines Austauschs lässt den Live-Speicher ihn auch freigeben. Eine geparkte Operation bleibt absichtlich ausstehend, sodass ihre Dauer den gesamten Austausch umfasst, und der Speicher weigert sich, einen ausstehenden Aufruf zu vergessen, weil möglicherweise noch eine Antwort kommt. Sobald die Obergrenze eine Operation ausgemustert hat, kann nichts sie mehr beantworten, sodass das Halten einen Aufruf am Leben hält, den kein Leser erreichen kann. Was die Sitzung meldet, bewegt sich nicht. Sie wird weiterhin als ausstehend gezählt und weiterhin in N unlinked gezählt, weil wie viel Speicher ein Datensatz belegt und was der Datensatz aussagt, unterschiedliche Fragen sind.
Ein abgebrochener Austausch stört den nächsten nicht. MRTR sagt Servern, dass sie nicht annehmen dürfen, dass ein Client jemals erneut versucht, sodass ein Benutzer, der eine Aufforderung ablehnt, eine Operation hinterlässt, die kein späterer Frame je abschließen wird. mcpsnoop sucht zuerst unter den Operationen, deren requestState-Vorhandensein mit dem des erneuten Versuchs übereinstimmt, was die Spezifikation in beide Richtungen zur Regel macht, sodass ein konformer erneuter Versuch immer noch die eine Operation findet, die er fortsetzt, selbst wenn ein abgebrochener Austausch am selben Tool daneben sitzt. Die Prüfung, die die drei oben genannten Verstöße meldet, läuft nur, wenn nichts übereinstimmt, sodass ein wirklich nicht konformer erneuter Versuch weiterhin benannt wird.
Halte die Aufzeichnung lokal auf dem Rechner, auf dem der Datenverkehr stattfindet, und nutze SSH für den Netzwerk-Hop, sodass mcpsnoop nie 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 nutzt 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 liegt im Statusverzeichnis des entfernten Systems, aufgelöst als `MCPSNOOP_HOME`,
sonst `XDG_STATE_HOME/mcpsnoop`, sonst `~/.local/state/mcpsnoop`. Standardmäßig nimmt mcpsnoop
das Linux-Homeverzeichnis `/home/<user>` aus deinem `user@host` an und gibt eine Erinnerung
auf stderr aus, wann immer es auf diese Vermutung zurückfällt. Wenn das entfernte System den Pfad 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 über SSH in die TUI, ohne dass eine lokale Kopie erforderlich ist.```bash ssh remote-user@remote-host 'cat ~/.local/state/mcpsnoop/sessions/session.jsonl' | mcpsnoop open -
Um stattdessen eine lokale Kopie zu behalten, kopieren Sie die Logs per scp in Ihr Sessions-Verzeichnis und führen
Sie 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 umschließen. Umschließen Sie daher 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.
Verwenden Sie für Remote-Workflows SSH-Tunneling oder SSH-Dateiübertragung, damit Transport-Authentifizierung, Verschlüsselung, Host-Verifizierung, Schlüsselrotation und Audit-Richtlinien in Ihrer bestehenden SSH-Einrichtung verbleiben.
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 durchlaufen.
Die schlüsselbasierte Schwärzung ersetzt ganze Werte unter passenden JSON-Objektschlüsseln, und derselbe Schlüsselsatz wird nach bestem Ermessen auf die Befehlszeilenargumente des umschlossenen Servers angewendet, sodass --api-key=sk-x und --token sk-x unter --redact-secrets bereinigt werden. Ein Argument, das ein Geheimnis ohne erkennbaren Flag-Namen trägt, kann nicht erkannt werden.
Der HTTP-Endpunkt ist davon nicht betroffen, da er keine Nutzlast ist, die Sie gesendet haben. --target ist ein Flag, das Sie übergeben müssen, um den Proxy überhaupt auszuführen, sodass seine URL unabhängig von Ihren Schwärzungseinstellungen im Sitzungsprotokoll erscheint. mcpsnoop zeichnet sie immer mit bereits entferntem Userinfo, jedem Query-Wert und Fragment auf – immer, konstruktionsbedingt und nicht durch Muster. Query-Schlüssel bleiben erhalten, da sie zwei Endpunkte eines Hosts unterscheiden, und das Fragment wird verworfen, da es den Server ohnehin nie erreicht hat. Was aufgezeichnet wird, identifiziert den Server und ist keine Adresse zum Anwählen.
Die pfadbasierte Schwärzung ersetzt nur Werte, die durch einen JSONPath-Ausdruck ausgewählt werden. Dies ist nützlich, wenn ein häufiger Schlüsselname an einer Stelle sensibel, an einer anderen jedoch unbedenklich ist. Wiederholen Sie --redact-path, um mehr als eine Stelle zu bereinigen.
Die wertbasierte Schwärzung wendet reguläre Ausdrücke auf beobachtete Zeichenfolgenwerte, stderr-Text und Nicht-JSON-Text-Frames an.
Alle drei sind nach bestem Ermessen. Regexe können Geheimnisse übersehen, harmlosen Text übermäßig abgleichen oder transformierte oder kodierte Werte nicht erkennen.
Schwärzung wird niemals zu einer Beschuldigung. Jede Prüfung, die ein beobachtetes Element mit einem 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 bleibt still, anstatt einen Server für die eigene Datenschutzeinstellung des Benutzers zu melden. Tool-Definitions-Drift ist die Ausnahme, und zwar bewusst, da das Aktivieren der Schwärzung ändert, was aufgezeichnet wird und daher, was eine Baseline enthält. Siehe Tool-Definitions-Drift 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+'
## Mitwirken
Issues und Pull Requests sind willkommen. Einzelheiten findest du in [CONTRIBUTING.md](https://github.com/kerlenton/mcpsnoop/blob/main/CONTRIBUTING.md).
## Lizenz
[MIT](https://github.com/kerlenton/mcpsnoop/blob/main/LICENSE)