
mcpsnoop v0.15.0
Wireshark per MCP. Un proxy trasparente che mostra ogni vera tool call tra il tuo client AI e i tuoi server MCP, in tempo reale nel tuo terminale.
Wireshark per MCP. Un proxy trasparente che mostra ogni chiamata effettiva agli strumenti tra il tuo client AI e i tuoi server MCP, in diretta nel tuo terminale.
Il problema
L'MCP Inspector ufficiale si connette come client a sé stante, quindi non vede mai ciò che il tuo client (Cursor, Claude Code, Codex) invia realmente al tuo server. E qualsiasi cosa attenda che una richiesta arrivi non può mostrare la chiamata che il modello non ha mai fatto, o fatta con gli argomenti sbagliati. Quando uno strumento non viene chiamato silenziosamente, le funzionalità non combaciano, o una chiamata resta in sospeso, ti ritrovi a scavare nei log e a tirare a indovinare.
mcpsnoop si inserisce invece nel percorso dati reale. Avvolgi il comando del tuo server con esso e guarda ogni frame JSON-RPC in diretta, mentre il tuo client e il tuo server reali dialogano.
Avvio rapido
Vedilo subito, senza nulla da configurare.```bash mcpsnoop demo
Per usarlo davvero, avvolgi il tuo server nella configurazione MCP del tuo client.```json
{
"mcpServers": {
"my-server": {
"command": "mcpsnoop",
"args": ["--", "node", "build/index.js"]
}
}
}
Tutto ciò che segue -- è il comando che normalmente avvia il tuo server. Inserisci
quello che già usi, come python server.py, npx -y @scope/server, o un
binario compilato.
Su Claude Desktop non devi fare quella modifica a mano.```bash mcpsnoop wrap my-server # route my-server through mcpsnoop mcpsnoop unwrap my-server # put it back
`wrap` trova `claude_desktop_config.json`, lo copia in
`claude_desktop_config.json.mcpsnoop.bak` la prima volta e riscrive solo
la voce di quel singolo server, così la tua formattazione e tutti gli altri
server restano intatti. All'interno della voce riscritta le chiavi tornano
in ordine alfabetico. `unwrap` ripristina il file e rimuove il backup quando
nessun server è più avvolto. Riavvia Claude Desktop dopo l'una o l'altra
operazione, poiché i server MCP vengono avviati una sola volta all'avvio.
Quindi usa il tuo client come al solito e apri la UI.```bash
mcpsnoop
Nessun flag, nessun percorso di socket, nessun ordine di avvio da ricordare. Lo shim e la UI si trovano da soli, e la UI recupera le sessioni passate dal disco.
Per un server streamable-HTTP, esegui mcpsnoop come reverse proxy.```bash mcpsnoop http --target http://localhost:3000/mcp --listen :7000
Lo stato HTTP di ogni risposta appare nello stream, quindi una risposta che non trasporta alcun messaggio JSON-RPC proprio è comunque un frame visibile, non un nulla: la sfida 401, il 403 su un Origin rifiutato, il 202 che riconosce una notifica e il 502 quando il target non può essere raggiunto affatto. L'header `WWW-Authenticate` di un 401 viene conservato verbatim e mostrato nell'ispettore, poiché indica lo schema di autenticazione e i metadati della risorsa a cui andare successivamente. Filtra per stato con `status:401` nella TUI, oppure per qualsiasi errore con `status:err`. Un 4xx o 5xx conta come errore, quindi un'esecuzione predefinita di `mcpsnoop check` fallisce su di esso.
Non hai un server tuo? [Provalo sul serio](https://github.com/kerlenton/mcpsnoop/blob/HEAD/docs/TRY_IT.md) contro un server di test pubblicato, guidato dal tuo client. Per ispezionare una sessione dopo che è avvenuta, vedi [rivedere sessioni passate dai log](https://github.com/kerlenton/mcpsnoop/blob/HEAD/docs/POST_MORTEM.md).
### File di configurazione
Se riutilizzi gli stessi flag shim in un progetto, inseriscili in un file `.mcpsnoop.toml` nella directory di lavoro corrente.```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
Ripeti redact-key, redact-value e redact-path su righe separate per aggiungerne più di uno per ciascuno.
Queste sono tutte le chiavi supportate.
Il file viene cercato solo nella directory di lavoro corrente, non nelle directory superiori.
Le opzioni esplicite da riga di comando sovrascrivono i valori del file di configurazione.
Comandi
| Comando | Cosa fa |
|---|---|
mcpsnoop -- <server> | avvolge un server stdio come uno shim trasparente |
mcpsnoop | apre la TUI live |
mcpsnoop http --target <url> | fa da proxy a un server HTTP streamabile |
mcpsnoop export | genera una sessione in json, html, text, har o otlp |
mcpsnoop check | fa fallire la CI su errori, frame non validi, avvisi, disallineamenti di routing, chiamate bloccate o risultati in ritardo |
mcpsnoop baseline | ispeziona, accetta o reimposta le definizioni degli strumenti affidabili |
mcpsnoop diff | confronta strumenti e chiamate tra due sessioni catturate |
mcpsnoop open | apre una sessione salvata nella TUI |
mcpsnoop prune | elimina i log delle sessioni salvate più vecchi di una soglia |
mcpsnoop wrap <server> | instrada uno dei server di Claude Desktop attraverso mcpsnoop |
mcpsnoop unwrap <server> | ripristina la voce di quel server com'era |
mcpsnoop remote <user@host> | stampa il comando del tunnel SSH |
mcpsnoop demo | riproduce una sessione scriptata |
Esegui mcpsnoop help per l'elenco completo, oppure mcpsnoop help <command> per i flag di un singolo comando.
Come si confronta
| MCP Inspector | mcpsnoop | |
|---|---|---|
| Vede il traffico reale di client e server | no | sì |
| Segnala chiamate bloccate ed errori di streaming | no | sì |
| Segnala output spurio che corrompe lo stream | no | sì |
| Segnala frame JSON-RPC malformati | no | sì |
| Rileva la deriva delle definizioni degli strumenti dopo l'approvazione | no | sì |
| UI terminale interattiva | no | sì |
| Zero configurazione, senza flag o ordinamenti | no | sì |
| Ispettore di capacità | parziale | sì |
| Riproduce una chiamata catturata | no | sì |
| Esportazione sessione (json / html / text / otlp) | no | sì |
| Binario singolo, nessuna dipendenza a runtime | no | sì |
Installazione
Go```bash
go install github.com/kerlenton/mcpsnoop/cmd/mcpsnoop@latest
### Homebrew```bash
brew install mcpsnoop
Binari precompilati per ogni piattaforma sono nella pagina Releases.
Completamenti per shell
mcpsnoop include completamenti per bash, zsh, fish e PowerShell. Esegui
mcpsnoop completion <shell> --help per i passaggi di configurazione, che coprono l'abilitazione
dei completamenti e il percorso di installazione per il tuo sistema operativo.
Come funziona
mcpsnoop è due ruoli in un unico binario. mcpsnoop -- <server> è lo
shim trasparente che il tuo client avvia, inoltrando i byte verbatim mentre invia una copia di ogni
frame all'hub. mcpsnoop senza argomenti è quell'hub e la sua TUI live. Loro
comunicano attraverso un socket noto e log su disco, quindi nessuno dei due deve avviarsi per primo.
L'hub carica le 100 sessioni salvate più recenti per impostazione predefinita, mantenendo limitato il lavoro
di avvio senza eliminare le tracce più vecchie. Usa mcpsnoop --history-limit N per scegliere
un altro limite, oppure mcpsnoop --history-limit 0 per caricare l'intera cronologia. Le sessioni
più vecchie rimangono disponibili tramite mcpsnoop open <session-id> e
mcpsnoop export <session-id>.
Il limite di cronologia limita ciò che viene caricato; mcpsnoop prune limita ciò che viene conservato.
Elimina i log delle sessioni salvate più vecchi di una soglia, e non viene mai eseguito da solo.```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` è obbligatorio (non esiste un default che cancellerebbe qualcosa) e accetta un conteggio in giorni come `30d` o una durata Go come `72h`. Le baseline degli strumenti vengono lasciate intatte, poiché una baseline è associata all'etichetta del server piuttosto che alla sessione.
Poiché si trova nel pipe effettivo, non in disparte come l'Inspector, vede esattamente ciò che il tuo client e server reali si dicono a vicenda, qualunque sia il linguaggio in cui è scritto il server.
## Tasti di scelta rapida
| Tasto | Azione | | Tasto | Azione |
|---|---|---|---|---|
| `enter` | ispeziona / approfondisci | | `/` | filtra |
| `esc` | indietro | | `:` | comando |
| `j` / `k` | sposta | | `r` | riproduci una chiamata |
| `g` / `G` | in alto / in basso | | `c` | capacità |
| `ctrl-f` / `ctrl-b` | pagina | | `s` | riepilogo strumento |
| `p` | pausa | | `y` | copia |
| `shift`+`<key>` | ordina per colonna | | `e` | esporta |
| `ctrl-d` | elimina sessione | | `f` | segui |
| `?` | aiuto | | | |
Premi `?` nell'app per l'elenco completo.
## Filtrare il flusso
Premi `/` in una sessione e combina token separati da spazi, con AND logico. Il testo semplice corrisponde a metodo, strumento, id e payload.
| Token | Filtra per | Esempio |
|---|---|---|
| `tool:` | nome dello strumento | `tool:search` |
| `method:` | metodo JSON-RPC | `method:tools/call` |
| `id:` | id della richiesta e qualsiasi nuovo tentativo che la prosegue | `id:7` |
| `task:` | id dell'attività | `task:01J...` |
| `dir:` | direzione (`c2s`, `s2c`) | `dir:s2c` |
| `kind:` | tipo di frame (`req`, `resp`, `notify`, `stderr`, `invalid`) | `kind:invalid` |
| `status:` | esito della chiamata (`ok`, `error`, `cancel`, `late`, `cancelled`, `pending`, `bad`, `warn`, `mismatch`, o uno stato HTTP come `401`) | `status:error` |
Combina i token per essere più specifico.```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)
L'ultimo trova qualcosa solo su un server che parla la versione 2025-11-25 o precedenti. La revisione del 2026-07-28 ha rimosso le richieste avviate dal server; un server che ha bisogno di qualcosa dal client ora risponde alla richiesta del client chiedendogliela, poi il client ritenta. mcpsnoop collega quei tentativi alla richiesta che proseguono, così lo scambio viene letto come una singola chiamata piuttosto che come più chiamate.
Esportare le sessioni
Trasforma qualsiasi sessione catturata in un file portabile.```bash mcpsnoop export -T json|html|text|har|otlp [-o file|-] [session-id|log.jsonl|-]
| Formato | Cosa ottieni |
|---|---|
| `json` | chiamate correlate, conteggi per strumento e latenza p50/p95/p99, chiamate più lente, capacità e frame grezzi |
| `html` | un file browser autonomo con ricerca e JSON richiudibile |
| `text` | un dump di testo semplice e leggibile |
| `har` | una voce per chiamata correlata, apribile negli strumenti di sviluppo del browser e in qualsiasi altro programma che legga HAR |
| `otlp` | JSON OTLP con uno span per chiamata correlata; il contesto di traccia W3C unisce le tracce del chiamante, altrimenti viene usata una traccia per sessione |
MCP non è HTTP, quindi l'URL, il codice di stato e i tempi di una voce HAR sono una
mappatura deliberata di ogni chiamata piuttosto che una trascrizione di rete.
Per OTLP, il `_meta.traceparent` di una richiesta fornisce l'ID di traccia e di span
genitore di quella chiamata, e `_meta.tracestate` viaggia insieme allo span. Quando il
traceparent è assente o non valido, mcpsnoop mantiene la traccia derivata dalla sessione
e non trasporta alcuno stato. mcpsnoop osserva piuttosto che partecipare, quindi non
aggiunge una propria voce di vendor e passa lo stato del chiamante inalterato.```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
Omiti -o per scrivere su stdout, e ometti la sessione per prendere la più recente, oppure passa
- per leggere JSONL da stdin. Nella TUI, premi e per esportare la sessione
selezionata come HTML, oppure esegui :export json|html|text|har|otlp [path] dalla modalità comando.
Per ripulire una cattura esistente prima di ispezionarla o condividerla, passa gli stessi
flag di redazione usati durante la cattura a export o 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 rewrite the exported file or the in-memory TUI view, never the
source JSONL. `export` refuses an output that names the same file as its input,
and writes through a temporary file that is renamed into place, so a run that
fails leaves the previous file whole.
L'`inputSchema` e l'`outputSchema` di uno strumento, come pubblicizzati in un risultato di `tools/list`, non vengono modificati da `--redact-key` e `--redact-secrets`. Un nome all'interno di uno schema è una dichiarazione di tipo piuttosto che un valore; il nome stesso rimane comunque nel log, e ripulire il sottoschema sotto una proprietà chiamata `token` si porterebbe dietro anche i controlli dello strumento. L'esenzione vale solo per quella posizione; quindi un argomento che capita di chiamarsi `inputSchema` viene ripulito come qualsiasi altro, e la pulizia si ferma a `default`, `const`, `examples` e `enum`, che contengono dati piuttosto che struttura. Usa `--redact-path` per indicare qualcosa all'interno di uno schema, oppure `--redact-value`, che trova corrispondenze di testo ovunque si trovi, tranne nelle due parole chiave che mcpsnoop analizza, `type` e `x-mcp-header`.
Ciò che ogni flag raggiunge è diverso, quindi controlla il risultato piuttosto che dare per scontato. Tutti e quattro ripuliscono i payload JSON-RPC, e `--redact-key`, `--redact-path` e `--redact-secrets` raggiungono solo quelli. Solo `--redact-value` ripulisce anche stderr, altro testo non JSON e l'interno di una stringa. Un header `Mcp-Param-*` viene ripulito insieme al valore del corpo che rispecchia; gli altri metadati dell'involucro, le etichette dei server, `Mcp-Name`, `Mcp-Method` e lo stato HTTP, vengono lasciati così come catturati. La redazione è best effort, quindi usa un percorso di output separato e leggi il risultato prima di condividerlo.
### Trasmettere in streaming le chiamate completate a un collector OTLP
Invia span mentre il proxy è in esecuzione puntandolo verso un endpoint di trace JSON OTLP/HTTP. Ripeti `--otlp-header` per l'autenticazione del collector o per gli header tenant.```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
La consegna è best-effort e non blocca mai il traffico MCP in proxy. Se il collector non è disponibile, mcpsnoop riprova in background e scarta i nuovi frame di traccia quando la sua coda limitata è piena. Il normale log di sessione JSONL rimane il record durevole.
Confronto delle sessioni
Confronta due sessioni salvate per id o percorso JSONL.```bash mcpsnoop diff before-session after-session mcpsnoop diff old.jsonl new.jsonl
Il report mostra gli strumenti aggiunti o rimossi, le modifiche alla descrizione e allo `inputSchema`, le chiamate di strumenti corrispondenti il cui stato è cambiato e i notevoli cambiamenti di durata. Le chiamate vengono abbinate per nome dello strumento e argomenti, quindi le chiamate riordinate vengono comunque confrontate correttamente. Per impostazione predefinita, le variazioni di durata devono differire di almeno 100 ms e 2x; usa `--duration-threshold` e `--duration-ratio` per regolare queste soglie.
Passa `--exit-code` per controllare la CI sulle regressioni: esce con codice non zero quando la sessione successiva rimuove uno strumento, modifica una descrizione, un titolo, uno schema di input, uno schema di output o delle annotazioni, ha una chiamata il cui stato è peggiorato o rallenta. I miglioramenti (strumenti aggiunti, chiamate corrette, accelerazioni) escono comunque con zero, e così anche un cambio di icona, che altera l'aspetto di uno strumento senza cambiarne il funzionamento.
## Verifica delle sessioni in CI
Applica un controllo a una sessione registrata dell'agente in base a errori, corruzione dello stream, avvisi di protocollo, mancate corrispondenze dell'intestazione di routing, chiamate che non hanno mai ricevuto risposta, frame scartati che lasciano la cattura incompleta, deriva delle definizioni degli strumenti o uso di funzionalità di protocollo deprecate.```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 e warn fanno fallire il controllo da soli. Gli altri sono opzionali.
Passa un sottoinsieme separato da virgole per limitare il controllo solo a ciò che interessa a un job, ometti
la sessione per controllare la cattura più recente, oppure usa - per leggere JSONL da stdin.
| Segnale | Fallisce su |
|---|---|
error | una chiamata a cui viene risposto con un errore JSON-RPC, un risultato marcato isError, o un task terminato con un fallimento |
invalid | un frame sul canale di protocollo che non è JSON-RPC valido, di solito un server che scrive su stdout |
warn | un frame che viola un'aspettativa posta dalle specifiche MCP o JSON-RPC |
mismatch | un header di routing in disaccordo con il body, che cavalca un batch, o mancante dove la revisione lo richiede |
pending | una richiesta ancora aperta quando la cattura è terminata, quindi il chiamante è rimasto in attesa |
late-result | una risposta arrivata dopo che la sua richiesta è stata cancellata |
drift | una definizione di tool pubblicizzata che cambia dopo che la baseline è stata approvata |
deprecated | una funzionalità che la specifica ha deprecato |
incomplete | frame scartati a monte, il che rende ogni altro conteggio un minimo piuttosto che un totale |
schema | uno schema pubblicizzato che usa una costruzione o un dialetto che si propaga male tra i client |
Ogni segnale viene conteggiato sia che sia abilitato o meno, quindi un'esecuzione riporta ciò che ha trovato prima che tu decida su cosa deve fallire.``` 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
Il conteggio dei frame persi viaggia anche con gli artefatti, quindi una cattura che si sottostima lo dichiara ovunque venga aperta: `missing_frames` nell'export JSON, `log.comment` in HAR e l'attributo di risorsa `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
Oltre ai conteggi dei segnali, verifica la forma dell'esecuzione. Questi si combinano tra loro e con --fail-on, e qualsiasi errore termina con codice di uscita non zero.
| Flag | Fallisce quando |
|---|---|
--max-duration <dur> | una o più chiamate di strumento completate hanno superato il budget; riporta il loro numero e la chiamata peggiore |
--expect-tool <name> | lo strumento indicato non è mai stato chiamato (ripetibile) |
--forbid-tool <name> | lo strumento indicato è stato chiamato (ripetibile) |
a contract for the run: search must run, delete must not, nothing over 2s
mcpsnoop check --expect-tool search --forbid-tool delete --max-duration 2s run.jsonl
### Segnalalo dove la CI guarda già
`--format junit` scrive un `<testcase>` per segnale e sessione, e i suoi fallimenti
seguono la stessa selezione `--fail-on` dell'output testuale.```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 scrive un log SARIF 2.1.0 invece. Dove junit riporta un
aggregato per segnale, SARIF riporta un risultato per finding, trasportando la sessione,
il frame Seq e il testo di warning o drift del frame stesso, e indicando la
riga del log da cui il frame è stato decodificato. Un segnale nominato in --fail-on è
riportato al livello error e uno al di fuori di esso al livello note, quindi il report e
il gate non sono mai in disaccordo.
Un risultato punta al log con un percorso relativo alla directory di lavoro, che
code scanning risolve poi rispetto alla radice del repository. L'avviso viene visualizzato con
le righe circostanti solo quando quel percorso è un file nel commit analizzato, quindi una
cattura generata dal workflow in artifacts/ apre un avviso che contiene il
messaggio, la regola e il numero di riga ma nessuna vista del codice sorgente. Committare una cattura
che si desidera visualizzare per intero è l'unico modo per ottenerne una. Un log letto dalla directory di stato
o da stdin non riceve alcun percorso.
Code scanning rifiuta un file la cui esecuzione contiene più di 25.000 risultati e
mostra solo i primi 5.000 di quelli che accetta, quindi il report è limitato a 5.000:
i finding su cui il gate ha fallito per primi, poi un risultato mcpsnoop/report-truncated
che indica quanti sono stati esclusi. I formati text e junit rimangono completi.
Per inserire i finding nella scheda Security, passa il log SARIF a
upload-sarif. Il job necessita di security-events: write, altrimenti l'upload risponde
403. check esce con codice non zero in presenza di un finding, quindi lo step di upload necessita di if: always()
per eseguirsi comunque sulle run che hanno qualcosa da segnalare; continue-on-error
consegna il verdetto al check di code scanning, che fallisce su un avviso a livello error
e può essere impostato come check richiesto. Rimuovilo se preferisci che lo
step check stesso faccia diventare rosso il job.```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 continue-on-error: true run: mcpsnoop check --format sarif artifacts/session.jsonl > mcpsnoop.sarif
- name: Upload mcpsnoop SARIF report if: always() uses: github/codeql-action/upload-sarif@v4 with: sarif_file: mcpsnoop.sarif category: mcpsnoop
### Rileva un header di routing in disaccordo con il body
Sul trasporto streamable-HTTP un gateway instrada in base a `Mcp-Method` e `Mcp-Name`
mentre il server legge il body, quindi un header in disaccordo con il body significa
che i due stanno osservando due richieste diverse. Il segnale `mismatch` copre questo caso,
un header che viaggia su un batch che non può indirizzare e un header obbligatorio mancante
del tutto.
In 2026-07-28 un header di routing mancante è un errore di validazione e un server
conforme rifiuta la richiesta con `400` e `-32020`. mcpsnoop lo segnala solo dopo
che la sessione è nota per supportare quella revisione o successiva, poiché le revisioni
precedenti non definiscono affatto questi header e ometterli lì è corretto. Il rifiuto
`-32020` del server stesso conta come lo stesso segnale.
Un nome o un URI di risorsa che non rientra in un valore di campo HTTP viaggia in Base64 in
un sentinel `=?base64?…?=`, che viene decodificato prima del confronto, quindi un client
che codifica correttamente non viene mai segnalato.
Nelle richieste HTTP `tools/call` mcpsnoop mostra anche ogni header `Mcp-Param-{Name}`
e, quando è nota la definizione dello strumento pubblicizzato corrispondente, la confronta
con il percorso annotato dell'argomento. Proprietà annidate, sentinel Base64, booleani e
interi sicuri numericamente equivalenti vengono gestiti senza falsi positivi da confronto
di stringhe. Gli header di parametri sconosciuti e le sessioni senza una definizione dello
strumento corrispondente restano a livello osservativo. La redazione basata su chiave e
valore si applica ai valori degli header dei parametri catturati prima che raggiungano un
sink, e un valore ripulito dallo stesso mcpsnoop non viene mai segnalato come disaccordo.
### Rileva la deriva delle definizioni degli strumenti
La prima `tools/list` completa osservata per un'etichetta di server diventa la sua baseline
attendibile. Le sessioni successive confrontano quella baseline campo per campo: la
descrizione, il titolo, gli schemi di input e output, le annotazioni e le icone, oltre
agli strumenti aggiunti o rimossi. Le annotazioni sono ciò che conta di più, poiché uno
strumento approvato con `readOnlyHint` che in seguito si dichiara distruttivo è il tiro
del tappeto per cui questo controllo esiste, e la specifica dice ai client di trattare le
annotazioni come non attendibili. Il titolo e le icone vengono tracciati perché sono ciò
che l'utente vede, e la specifica colloca il `title` di uno strumento al di sopra di
`annotations.title` e del suo nome. La tabella delle sessioni e il riepilogo degli
strumenti segnalano la deriva senza bloccare o modificare il traffico MCP.
Le annotazioni vengono confrontate tramite i loro valori predefiniti di specifica, quindi
un server che inizia a esplicitare un suggerimento su cui faceva già affidamento non viene
segnalato. Una baseline registrata prima che mcpsnoop tracciasse un campo continua a
funzionare per i campi che registra e indica quali non può verificare; registra di nuovo
con `mcpsnoop baseline --accept` una volta che ti fidi delle definizioni correnti.
Modificare ciò che la redazione registra modifica ciò che la deriva confronta. Una baseline
acquisita senza `--redact-value` e poi verificata con una cattura effettuata con
quell'opzione segnala i campi ripuliti come modificati, il che è corretto, poiché la
definizione registrata è davvero cambiata. Registra di nuovo con `--accept` dopo aver
modificato le impostazioni di redazione.
Usa un `--label` stabile e univoco per ogni server il cui nome di comando o host di
destinazione entrerebbe altrimenti in collisione. Le baseline sono archiviate nella
normale directory di stato di mcpsnoop, quindi si applicano `MCPSNOOP_HOME` e
`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 una CI effimera la directory di stato parte vuota, quindi la prima esecuzione registra solo la baseline e non segnala alcuna deriva. La baseline deve persistere tra le esecuzioni affinché quelle successive possano verificare rispetto ad essa. Punta --baseline a una directory salvata nel repository o in cache, oppure imposta MCPSNOOP_HOME su un percorso persistente.```bash
mcpsnoop check --fail-on drift --baseline .mcpsnoop/baselines session.jsonl
`drift` è opt-in per `check`; il gate predefinito `error,invalid,warn` rimane invariato.
### Contrassegna le funzionalità del protocollo deprecate
La revisione del 2026-07-28 depreca Roots, Sampling e Logging. Continuano a funzionare per almeno un anno, quindi mcpsnoop li contrassegna invece di trattarli come errori. Il flusso, l'ispettore delle capacità e l'esportazione li contrassegnano tutti, e ogni marcatore indica il sostituto.
Due dei tre sono ora raggiungibili solo tramite una richiesta multi round-trip, in cui il nome del metodo si trova all'interno della mappa `inputRequests` del server anziché sul frame stesso. Anche questi vengono contrassegnati, così un server che è passato al nuovo pattern non smette silenziosamente di segnalare.```bash
mcpsnoop check --fail-on deprecated session.jsonl
Come drift, deprecated è opt-in. Un'esecuzione predefinita riporta il
conteggio e resta verde, quindi una sessione che usa una funzionalità deprecata
ma ancora legale non fa mai diventare rossa la CI da sola.
Segnala i costrutti di schema che i client gestiscono male
Un server può essere perfettamente valido e comunque difficile da usare per un agente. I client differiscono su quanto del JSON Schema supportano davvero, e uno strumento che il modello continua a chiamare in modo errato è spesso uno strumento il cui schema ha richiesto più di quanto il client offra.
Il sommario degli strumenti, aperto con s, ha una colonna SCHEMA che indica la
caratteristica più notevole dello schema di ogni strumento pubblicizzato, con un
+ finale quando ce n'è più di uno.
| Mostrato | Significato |
|---|---|
no root | inputSchema è assente, non è un oggetto JSON o ha un tipo radice diverso da "object" |
dialect | un $schema che indica un dialetto diverso dal 2020-12 a cui la revisione fa default |
ext ref | un $ref che punta all'esterno del documento, che è anche il caso in cui la spec avverte gli implementatori di non seguirlo alla cieca |
oneOf, anyOf, allOf, not | una parola chiave di composizione, gestita in modo incoerente tra i client |
ref | un $ref che punta all'interno dello stesso documento |
untyped | una proprietà che non dichiara alcun tipo né altri modi per dire cosa accetta |
Tutte le voci tranne la prima sono osservazioni, non verdetti. Uno schema che usa
oneOf non è sbagliato, è solo probabile che venga letto in modo diverso da
client diversi, e uno schema può dichiarare il dialetto che preferisce. no root è l'eccezione: la definizione di Tool richiede inputSchema e fissa il
tipo radice a "object", quindi un client che valida un elenco rifiuta del tutto
quello strumento e non diventa mai chiamabile, senza nulla sul wire che spieghi
perché. Per questo no root apre la colonna, e uno schema che la stessa
redazione di mcpsnoop ha ripulito non viene mai segnalato: uno schema illeggibile
non è sbagliato.
Questa distinzione determina cosa fa check con queste voci. no root è un
avviso sul frame tools/list, quindi fa fallire il gate predefinito
error,invalid,warn senza alcun flag, ed è proprio questo il punto: un server
che espone uno strumento inutilizzabile risponde normalmente a ogni handshake e
semplicemente non riceve mai una tools/call. Le osservazioni vengono
conteggiate come schema_findings e segnalate sotto schema findings:, e
fanno fallire l'esecuzione solo quando aggiungi schema a --fail-on.
Entrambi arrivano a --format junit e --format sarif, e export riporta
l'elenco per strumento sotto 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
La colonna porta il colore di avviso e mai il rosso della colonna ERR, e mcpsnoop continua a non cambiare nulla nel traffico che inoltra.
Niente viene risolto o recuperato. Un `$ref` esterno viene riconosciuto solo dalla sua forma, e lo schema a cui punta non viene mai letto.
### Scopri quanto ti costa il server nel contesto
Le definizioni degli strumenti entrano nel contesto del modello a ogni conversazione, e i risultati degli strumenti a ogni chiamata. Il riepilogo degli strumenti (`s`) misura entrambi a partire dalla sessione che hai effettivamente catturato.
La riga `definitions` è il costo fisso: quanto pesa `tools/list` di questo server prima che venga effettuata una singola chiamata. La colonna `DEF` lo suddivide per strumento e `RESULT` è ciò che le risposte di ciascuno strumento sono costate finora. La tabella rimane ordinata per errori e latenza, quindi scorri `DEF` per trovare le definizioni costose; l'export le elenca prima quelle più pesanti. Una riga sotto la tabella indica il singolo risultato più pesante, che un totale nasconde.
Le cifre relative alle definizioni sono il JSON con rimossi gli spazi bianchi insignificanti, quindi un server che stampa in modo pretty il proprio `tools/list` non viene conteggiato come più costoso di uno che non lo fa, e lo stesso server misura uguale tra diverse catture. `RESULT` è il numero di byte così come sono arrivati: un risultato è un payload una tantum, non un contratto che vale la pena normalizzare.```bash
mcpsnoop export -T json | jq '.summary.definitions'
L'export riporta le stesse cifre, per strumento e suddivise in byte di descrizione e
di schema, così una descrizione voluminosa e uno schema voluminoso restano separabili e
entrambi possono essere monitorati tra una cattura e l'altra. mcpsnoop diff ti dice se una
descrizione o uno schema è cambiato tra due sessioni; l'export è il punto in cui vive la
dimensione di quel cambiamento.
Questi sono byte, non token. Il conteggio dei token dipende dal modello, quindi
misurarli significherebbe includere un tokenizer e scegliere di chi. I byte sono
esatti e puoi applicare il tuo rapporto. Una tools/list non terminata riporta ciò
che ha visto come minimo e lo dichiara, invece di spacciare una somma parziale per il
totale.
Rilevare un client che manomette lo stato del server
Nel pattern multi round-trip, il server consegna al client un requestState
opaco e il client deve restituirlo invariato al retry. Al server viene detto
di trattarlo come input controllato dall'attaccante, perché un client che
lo manomette può tentare di alterare il comportamento del server o aggirare un
controllo di autorizzazione.
In mezzo al canale, mcpsnoop vede il valore uscire e tornare, quindi può dire quando il contratto è stato violato. Tre sono i modi in cui può rompersi, ciascuno segnalato come avviso di protocollo al retry.
| Segnalazione | Significato |
|---|---|
MRTR retry changed requestState | il client ha restituito qualcosa di diverso da quanto emesso dal server |
MRTR retry is missing requestState | il server ne ha emesso uno e il retry lo ha omesso |
MRTR retry invented requestState | il retry ne conteneva uno che il server non ha mai emesso |
Queste sono violazioni di protocollo da parte del client, non osservazioni nostre, quindi
viaggiano sul normale segnale di avviso e un'esecuzione predefinita di check fallisce su una di esse.
È una scelta deliberata. Un client che manomette lo stato del server merita di bloccare una build.
Il valore stesso non viene mai mostrato né registrato, e nulla lo decodifica o lo analizza. Può essere un blob cifrato che trasporta un principal e un token, e il confronto di byte opachi è l'intero controllo.
Un caso è fuori portata. Quando un server risponde con un requestState e nessun
inputRequests, un retry manomesso non corrisponde a nulla e non restituisce alcuna chiave, quindi
non resta nulla per collegarlo alla richiesta originale e viene letto come una chiamata
non correlata piuttosto che come una violazione.
Osservare da un'altra macchina
Tieni la cattura sulla macchina in cui avviene il traffico e usa SSH per il salto di rete, così mcpsnoop non ha mai bisogno di un trasporto remoto proprio.
Vista live
Esegui la TUI sulla tua workstation e inoltra la socket mcpsnoop della macchina remota verso di essa. Il tunnel live usa l'inoltro di Unix socket via SSH, quindi entrambe le estremità devono eseguire Linux o macOS. Su Windows, usa la copia del log post-mortem qui sotto.```bash
on your workstation, start the TUI
mcpsnoop
create the remote socket directory once
ssh remote-user@remote-host 'mkdir -p ~/.local/state/mcpsnoop'
print the tunnel command, then run the printed ssh -R line
mcpsnoop remote remote-user@remote-host
on the remote host, wrap your server as usual
mcpsnoop -- node build/index.js
Il socket risiede nella directory di stato del remoto, risolta come `MCPSNOOP_HOME`,
altrimenti `XDG_STATE_HOME/mcpsnoop`, altrimenti `~/.local/state/mcpsnoop`. Di default mcpsnoop
assume la home Linux `/home/<user>` dal tuo `user@host` e stampa un promemoria
su stderr ogni volta che ripiega su quella ipotesi. Se il remoto risolve altrove,
indica l'unico componente non predefinito.```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
Post-mortem
Trasmetti in streaming una sessione remota direttamente nella TUI via SSH, senza bisogno di copie locali.```bash ssh remote-user@remote-host 'cat ~/.local/state/mcpsnoop/sessions/session.jsonl' | mcpsnoop open -
Per conservare invece una copia locale, scp i log nella tua directory delle sessioni ed esegui
la TUI come al solito.```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
Sicurezza
mcpsnoop esegue il comando del server che avvolgi, quindi avvolgi solo server di cui ti fidi ed esegui quelli non affidabili in un container. Non esegue mai nulla che non hai inserito nella configurazione del client.
I frame catturati possono includere prompt, argomenti degli strumenti, credenziali e risultati degli strumenti. Se i payload possono trasportare segreti, attiva la redazione per ripulire le copie della traccia osservata mentre i byte inoltrati passano comunque invariati.
La redazione basata su chiavi sostituisce interi valori sotto le chiavi JSON corrispondenti e lo stesso set di chiavi viene applicato al meglio delle possibilità agli argomenti della riga di comando del server avvolto, quindi --api-key=sk-x e --token sk-x vengono ripuliti con --redact-secrets. Un argomento che trasporta un segreto senza un nome di flag riconoscibile non può essere rilevato.
La redazione basata su percorsi sostituisce solo i valori selezionati da un'espressione JSONPath, il che è utile quando un nome di chiave comune è sensibile in una posizione ma sicuro in un'altra. Ripeti --redact-path per ripulire più di una posizione.
La redazione basata su valori applica espressioni regolari ai valori stringa osservati, al testo di stderr e ai frame di testo non JSON.
Tutte e tre sono al meglio delle possibilità. Le espressioni regolari possono non vedere i segreti, abbinare in eccesso testo innocuo o non riuscire a individuare valori trasformati o codificati.
La redazione non si trasforma mai in un'accusa. Ogni controllo che confronta una cosa osservata con un'altra, un'intestazione di routing con il corpo, un valore Mcp-Param con l'argomento che rispecchia, lo schema di uno strumento con ciò che la revisione richiede a uno strumento, sa quando è stato mcpsnoop a riscrivere i byte e rimane in silenzio invece di segnalare un server per l'impostazione di privacy dell'utente.
La deriva delle definizioni degli strumenti è l'eccezione, e volutamente, poiché attivare la redazione cambia ciò che viene registrato e quindi ciò che una baseline contiene. Vedi Rileva la deriva delle definizioni degli strumenti.```bash
built-in preset of common secret keys
mcpsnoop --redact-secrets -- node build/index.js
or name your own keys
mcpsnoop --redact-key token,api_key,password -- node build/index.js
scrub one location without redacting every field named password
mcpsnoop --redact-path '$.params.arguments.password' -- node build/index.js
wildcards scrub every matching array element
mcpsnoop --redact-path '$.params.arguments.accounts[*].password' -- node build/index.js
scrub obvious token-shaped values outside known keys
mcpsnoop --redact-value 'sk-[A-Za-z0-9]+' -- node build/index.js
combine the layers in http mode
mcpsnoop http --target http://localhost:3000/mcp --redact-secrets --redact-value 'Bearer\s+\S+'
Per i flussi di lavoro remoti, usa il tunnelling SSH o il trasferimento file via SSH, così autenticazione del trasporto, cifratura, verifica dell'host, rotazione delle chiavi e policy di audit restano nella tua configurazione SSH esistente.
## Contributing
Issue e pull request sono benvenuti. Vedi [CONTRIBUTING.md](https://github.com/kerlenton/mcpsnoop/blob/HEAD/CONTRIBUTING.md) per i dettagli.
## License
[MIT](https://github.com/kerlenton/mcpsnoop/blob/HEAD/LICENSE)