
mcpsnoop v0.12.0
Wireshark para MCP. Un proxy transparente que muestra cada llamada de herramienta real entre tu cliente de IA y tus servidores MCP, en vivo en tu terminal.
Wireshark para MCP. Un proxy transparente que muestra cada llamada real a herramientas entre tu cliente de IA y tus servidores MCP, en vivo en tu terminal.
El problema
El Inspector de MCP oficial se conecta como su propio cliente, por lo que nunca ve lo que tu cliente (Cursor, Claude Code, Codex) envía realmente a tu servidor. Y cualquier cosa que espere a que llegue una solicitud no puede mostrar la llamada que el modelo nunca hizo, o que hizo con los argumentos incorrectos. Cuando una herramienta no se llama silenciosamente, las capacidades no coinciden, o una llamada simplemente se queda colgada, te quedas revisando registros y adivinando.
mcpsnoop se sitúa en la ruta de datos real en su lugar. Envuelve tu comando de servidor con él y observa cada frame JSON-RPC en vivo, mientras tu cliente y servidor reales conversan.
Inicio rápido
Vívelo de inmediato, sin nada que configurar.```bash mcpsnoop demo
Para usarlo de verdad, envuelve tu servidor en la configuración MCP de tu cliente.```json
{
"mcpServers": {
"my-server": {
"command": "mcpsnoop",
"args": ["--", "node", "build/index.js"]
}
}
}
Todo lo que va después de -- es el comando que normalmente inicia tu servidor. Sustitúyelo
por lo que ya uses, como python server.py, npx -y @scope/server, o un
binario compilado.
En Claude Desktop no tienes que hacer esa edición a mano.```bash mcpsnoop wrap my-server # route my-server through mcpsnoop mcpsnoop unwrap my-server # put it back
`wrap` encuentra `claude_desktop_config.json`, lo copia a
`claude_desktop_config.json.mcpsnoop.bak` la primera vez, y reescribe solo esa
entrada de un servidor, por lo que tu formato y todos los demás servidores se dejan intactos.
Dentro de la entrada reescrita, las claves vuelven a estar en orden alfabético. `unwrap`
restaura el archivo y elimina la copia de seguridad una vez que ningún servidor está envuelto.
Reinicia Claude Desktop después de cualquiera de las dos operaciones, ya que los servidores MCP se lanzan una sola vez al
inicio.
Luego usa tu cliente como de costumbre y abre la interfaz de usuario.```bash
mcpsnoop
Sin flags, sin rutas de socket, sin orden de inicio que recordar. El shim y la interfaz de usuario se encuentran entre sí por sí solos, y la interfaz de usuario rellena las sesiones pasadas desde el disco.
Para un servidor streamable-HTTP, ejecuta mcpsnoop como proxy inverso.```bash mcpsnoop http --target http://localhost:3000/mcp --listen :7000
El estado HTTP de cada respuesta aparece en el stream, de modo que una respuesta que no lleva ningún mensaje JSON-RPC propio sigue siendo un frame visible en lugar de nada: el desafío 401, el 403 en un Origin rechazado, el 202 que reconoce una notificación, y el 502 cuando el destino no se puede alcanzar en absoluto. La cabecera `WWW-Authenticate` de un 401 se conserva tal cual y se muestra en el inspector, ya que nombra el esquema de autenticación y los metadatos del recurso al que acudir a continuación. Filtra por estado con `status:401` en la TUI, o por cualquier fallo con `status:err`. Un 4xx o 5xx cuenta como error, por lo que una ejecución por defecto de `mcpsnoop check` falla en él.
¿No tienes servidor propio? [Pruébalo de verdad](https://github.com/kerlenton/mcpsnoop/blob/HEAD/docs/TRY_IT.md) contra un servidor de pruebas publicado, impulsado por tu propio cliente. Para inspeccionar una sesión una vez ocurrida, consulta [revisar sesiones pasadas desde los logs](https://github.com/kerlenton/mcpsnoop/blob/HEAD/docs/POST_MORTEM.md).
### Archivo de configuración
Si reutilizas las mismas opciones de shim en un proyecto, ponlas en un archivo `.mcpsnoop.toml` en el directorio de trabajo actual.```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
Repite redact-key, redact-value y redact-path en sus propias líneas para añadir más de uno de cada uno.
Esas son todas las claves que admite.
El archivo solo se busca en el directorio de trabajo actual, no en los directorios padre.
Las opciones explícitas de línea de comandos anulan los valores del archivo de configuración.
Comandos
| Comando | Qué hace |
|---|---|
mcpsnoop -- <server> | envuelve un servidor stdio como un shim transparente |
mcpsnoop | abre la TUI en directo |
mcpsnoop http --target <url> | hace de proxy para un servidor HTTP de streaming |
mcpsnoop export | renderiza una sesión a json, html, text, har u otlp |
mcpsnoop check | hace fallar la CI en errores, tramas no válidas, avisos, desajustes de enrutamiento, llamadas colgadas o resultados tardíos |
mcpsnoop baseline | inspecciona, acepta o restablece definiciones de herramientas confiadas |
mcpsnoop diff | compara herramientas y llamadas entre dos sesiones capturadas |
mcpsnoop open | abre una sesión guardada en la TUI |
mcpsnoop prune | elimina registros de sesión guardados más antiguos que un límite |
mcpsnoop wrap <server> | enruta uno de los servidores de Claude Desktop a través de mcpsnoop |
mcpsnoop unwrap <server> | restablece la entrada de ese servidor a su estado original |
mcpsnoop remote <user@host> | imprime el comando de túnel SSH |
mcpsnoop demo | reproduce una sesión con guion |
Ejecuta mcpsnoop help para ver la lista completa, o mcpsnoop help <command> para ver las opciones de un comando.
Cómo se compara
| MCP Inspector | mcpsnoop | |
|---|---|---|
| Ve tu tráfico real de cliente y servidor | no | sí |
| Señala llamadas colgadas y errores de flujo | no | sí |
| Señala salida dispersa que corrompe el flujo | no | sí |
| Señala tramas JSON-RPC malformadas | no | sí |
| Detecta desviaciones en las definiciones de herramientas tras la aprobación | no | sí |
| Interfaz de terminal interactiva | no | sí |
| Cero configuración, sin opciones ni orden específico | no | sí |
| Inspector de capacidades | parcial | sí |
| Reproduce una llamada capturada | no | sí |
| Exportación de sesión (json / html / text / otlp) | no | sí |
| Un único binario, sin dependencias de ejecución | no | sí |
Instalación
Go```bash
go install github.com/kerlenton/mcpsnoop/cmd/mcpsnoop@latest
### Homebrew```bash
brew install mcpsnoop
Los binarios precompilados para todas las plataformas están en la página de Releases.
Completado de shell
mcpsnoop incluye funciones de completado para bash, zsh, fish y PowerShell. Ejecuta
mcpsnoop completion <shell> --help para ver los pasos de configuración, que cubren
la activación del completado y la ruta de instalación para tu sistema operativo.
Cómo funciona
mcpsnoop combina dos roles en un solo binario. mcpsnoop -- <server> es el shim
transparente que tu cliente lanza, reenviando bytes tal cual mientras envía una
copia de cada trama al hub. mcpsnoop sin argumentos es ese hub y su TUI en vivo.
Se emparejan a través de un socket conocido y registros en disco, por lo que ninguno tiene que iniciarse primero.
El hub carga las 100 sesiones guardadas más recientes por defecto, manteniendo
acotado el trabajo de arranque sin borrar las trazas más antiguas. Usa
mcpsnoop --history-limit N para elegir otro límite, o mcpsnoop --history-limit 0
para cargar el historial completo. Las sesiones más antiguas siguen disponibles
mediante mcpsnoop open <session-id> y mcpsnoop export <session-id>.
El límite de historial acota lo que se carga; mcpsnoop prune acota lo que se conserva.
Elimina los registros de sesiones guardadas más antiguos que un punto de corte, y nunca se ejecuta por sí 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` es obligatorio (no hay un valor predeterminado que elimine nada) y
acepta un recuento de días como `30d` o una duración de Go como `72h`. Las líneas base de herramientas se
dejan intactas, ya que una línea base está asociada a la etiqueta del servidor y no a la sesión.
Debido a que se encuentra en la tubería real, no a un lado como el Inspector, ve
exactamente lo que tu cliente y servidor reales se dicen entre sí, sin importar en qué
esté escrito el servidor.
## Atajos de teclado
| Key | Action | | Key | Action |
|---|---|---|---|---|
| `enter` | inspeccionar / profundizar | | `/` | filtrar |
| `esc` | atrás | | `:` | comando |
| `j` / `k` | mover | | `r` | reproducir una llamada |
| `g` / `G` | inicio / fin | | `c` | capacidades |
| `ctrl-f` / `ctrl-b` | página | | `s` | resumen de herramientas |
| `p` | pausa | | `y` | copiar |
| `shift`+`<key>` | ordenar por columna | | `e` | exportar |
| `ctrl-d` | eliminar sesión | | `f` | seguir |
| `?` | ayuda | | | |
Pulsa `?` en la aplicación para ver la lista completa.
## Filtrado del flujo
Pulsa `/` en una sesión y combina tokens separados por espacios, aplicando AND. El texto plano coincide con el método, la herramienta, el id y el payload.
| Token | Filtra por | Ejemplo |
|---|---|---|
| `tool:` | nombre de la herramienta | `tool:search` |
| `method:` | método JSON-RPC | `method:tools/call` |
| `id:` | id de solicitud, y cualquier reintento que continúe con él | `id:7` |
| `task:` | id de tarea | `task:01J...` |
| `dir:` | dirección (`c2s`, `s2c`) | `dir:s2c` |
| `kind:` | tipo de trama (`req`, `resp`, `notify`, `stderr`, `invalid`) | `kind:invalid` |
| `status:` | resultado de la llamada (`ok`, `error`, `cancel`, `late`, `cancelled`, `pending`, `bad`, `warn`, `mismatch`, o un estado HTTP como `401`) | `status:error` |
Apila tokens para obtener algo más específico.```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)
El último solo encuentra algo en un servidor que hable la versión 2025-11-25 o anterior. La revisión de 2026-07-28 eliminó las solicitudes iniciadas por el servidor, y un servidor que necesita algo del cliente ahora responde a la propia solicitud del cliente pidiéndolo, luego el cliente reintenta. mcpsnoop vincula esos reintentos a la solicitud que continúan, de modo que el intercambio se lee como una sola llamada en lugar de varias.
Exportando sesiones
Convierte cualquier sesión capturada en un archivo portátil.```bash mcpsnoop export -T json|html|text|har|otlp [-o file|-] [session-id|log.jsonl|-]
| Formato | Lo que obtienes |
|---|---|
| `json` | llamadas correlacionadas, recuentos por herramienta y latencia p50/p95/p99, llamadas más lentas, capacidades y tramas sin procesar |
| `html` | un archivo de navegador autónomo con búsqueda y JSON plegable |
| `text` | un volcado de texto plano legible |
| `har` | una entrada por llamada correlacionada, abrible en las herramientas de desarrollo del navegador y en cualquier otra cosa que lea HAR |
| `otlp` | JSON OTLP con un span por llamada correlacionada; el contexto de traza W3C une las trazas del llamador; de lo contrario, se usa una traza por sesión |
MCP no es HTTP, por lo que la URL, el código de estado y los tiempos de una entrada HAR son un
mapeo deliberado de cada llamada en lugar de una transcripción de la red.
Para OTLP, el `_meta.traceparent` de una solicitud proporciona el ID de traza y de span padre de esa llamada, y `_meta.tracestate` viaja junto con el span. Cuando el traceparent está ausente o no es válido, mcpsnoop mantiene la traza derivada de la sesión y no lleva ningún estado. mcpsnoop observa en lugar de participar, por lo que no añade una entrada de proveedor propia y pasa el estado del llamador sin cambios.```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
Omite -o para escribir en stdout, y omite la sesión para tomar la más reciente, o pasa
- para leer JSONL desde stdin. En la TUI, presiona e para exportar la sesión
seleccionada como HTML, o ejecuta :export json|html|text|har|otlp [path] desde el modo de comandos.
Para depurar una captura existente antes de inspeccionarla o compartirla, pasa los mismos
indicadores de redacción utilizados durante la captura 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'
Estas opciones reescriben el archivo exportado o la vista TUI en memoria, nunca el JSONL de origen. `export` rechaza una salida que nombre el mismo archivo que su entrada y escribe mediante un archivo temporal que se renombra en su lugar, de modo que una ejecución que falla deja el archivo anterior intacto.
Los `inputSchema` y `outputSchema` de una herramienta, tal como se anuncian en un resultado de `tools/list`, no se ven afectados por `--redact-key` ni `--redact-secrets`. Un nombre dentro de un esquema es una declaración de tipo más que un valor; el nombre en sí permanece en el registro en cualquier caso, y depurar el subesquema bajo una propiedad llamada `token` se llevaría consigo las comprobaciones propias de la herramienta. La exención se aplica solo a esa posición, así que un argumento que resulte llamarse `inputSchema` se depura como cualquier otro, y se detiene en `default`, `const`, `examples` y `enum`, que contienen datos en lugar de estructura. Use `--redact-path` para nombrar algo dentro de un esquema, o `--redact-value`, que coincide con texto esté donde esté, excepto en las dos palabras clave que mcpsnoop analiza, `type` y `x-mcp-header`.
El alcance de cada opción difiere, así que compruebe el resultado en lugar de asumir. Las cuatro opciones depuran cargas útiles JSON-RPC, y `--redact-key`, `--redact-path` y `--redact-secrets` solo llegan a esas. Solo `--redact-value` depura también stderr, otro texto que no sea JSON y el interior de una cadena. Un encabezado `Mcp-Param-*` se depura junto con el valor del cuerpo al que refleja; el resto de los metadatos del sobre, las etiquetas de servidor, `Mcp-Name`, `Mcp-Method` y el estado HTTP se dejan tal como se capturaron. El redactado es de mejor esfuerzo, así que use una ruta de salida separada y lea el resultado antes de compartirlo.
### Transmitir llamadas completadas a un collector OTLP
Envíe spans mientras el proxy está en ejecución apuntándolo a un endpoint OTLP/HTTP JSON de trazas. Repita `--otlp-header` para la autenticación del collector o los encabezados de 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 entrega es de mejor esfuerzo y nunca bloquea el tráfico MCP a través de un proxy. Si el recopilador no está disponible, mcpsnoop reintenta en segundo plano y descarta nuevas tramas de traza cuando su cola acotada está llena. El registro de sesión JSONL normal sigue siendo el registro persistente.
Comparando sesiones
Compare dos sesiones guardadas por id o ruta JSONL.```bash mcpsnoop diff before-session after-session mcpsnoop diff old.jsonl new.jsonl
El informe muestra las herramientas añadidas o eliminadas, los cambios en la descripción y en `inputSchema`, las llamadas a herramientas coincidentes cuyo estado cambió y los cambios notables de duración. Las llamadas se comparan por nombre de herramienta y argumentos, por lo que las llamadas reordenadas siguen comparándose correctamente. De forma predeterminada, los cambios de duración deben diferir al menos 100 ms y 2x; usa `--duration-threshold` y `--duration-ratio` para ajustar esos límites.
Pasa `--exit-code` para condicionar el CI a las regresiones: sale con código distinto de cero cuando la sesión posterior elimina una herramienta, cambia la descripción, el título, el esquema de entrada, el esquema de salida o las anotaciones de una herramienta, tiene una llamada cuyo estado empeoró o se ralentiza. Las mejoras (herramientas añadidas, llamadas corregidas, aceleraciones) siguen saliendo con cero, y también lo hace un cambio de icono, que altera el aspecto de una herramienta sin cambiar lo que hace.
## Comprobación de sesiones en CI
Condiciona una ejecución de agente grabada a errores, corrupción del flujo, advertencias de protocolo, discrepancias en el encabezado de enrutamiento, llamadas que nunca recibieron respuesta, tramas descartadas que dejan la captura incompleta, deriva en la definición de herramientas o el uso de características de protocolo obsoletas.```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 y warn hacen fallar la comprobación por sí solos. El resto son opt-in.
Pasa un subconjunto separado por comas para que la comprobación solo falle por lo que le importa a un trabajo, omite la
sesión para comprobar la captura más reciente, o usa - para leer JSONL desde stdin.
| Señal | Falla en |
|---|---|
error | una llamada respondida con un error JSON-RPC, un resultado marcado como isError, o una tarea que terminó en fallo |
invalid | un frame en el canal de protocolo que no es JSON-RPC válido, normalmente un servidor que escribe en stdout |
warn | un frame que rompe una expectativa que establece la especificación MCP o JSON-RPC |
mismatch | una cabecera de enrutamiento que no coincide con el cuerpo, que viaja en un lote, o que falta donde la revisión lo requiere |
pending | una solicitud aún abierta cuando terminó la captura, por lo que el llamador quedó esperando |
late-result | una respuesta que llegó después de que su solicitud fue cancelada |
drift | una definición de herramienta anunciada que cambia después de que la línea base fue aprobada |
deprecated | una característica que la especificación ha marcado como obsoleta |
incomplete | frames descartados upstream, lo que hace que cualquier otro recuento sea un mínimo en lugar de un total |
schema | un esquema anunciado que usa una construcción o un dialecto que viaja mal entre clientes |
Cada señal se cuenta sea o no un criterio de fallo, por lo que una ejecución dice lo que encontró antes de que decidas qué debería hacerla fallar.``` 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
El recuento de fotogramas perdidos también viaja con los artefactos, por lo que una captura que
se subestima a sí misma lo indica dondequiera que se abra: `missing_frames` en la exportación
JSON, `log.comment` en HAR, y el atributo de recurso `mcpsnoop.session.missing_frames`
en OTLP.```bash
mcpsnoop check build-agent
mcpsnoop check --fail-on error,invalid artifacts/session.jsonl
mcpsnoop check --fail-on mismatch gateway-run.jsonl
Más allá de los recuentos de señales, verifique la forma de la ejecución. Estos se combinan entre sí y con --fail-on, y cualquier fallo sale con un código distinto de cero.
| Indicador | Falla cuando |
|---|---|
--max-duration <dur> | una o más llamadas a herramientas completadas excedieron el presupuesto; informa su cantidad y la peor llamada |
--expect-tool <name> | la herramienta nombrada nunca fue llamada (repetible) |
--forbid-tool <name> | la herramienta nombrada fue llamada (repetible) |
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
### Repórtalo donde la CI ya busca
`--format junit` escribe un `<testcase>` por señal y sesión, y sus fallos
siguen la misma selección de `--fail-on` que la salida de texto.```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 escribe un registro SARIF 2.1.0 en su lugar. Donde junit informa un
agregado por señal, SARIF informa un resultado por hallazgo, llevando la sesión,
el Seq del marco y el propio texto de advertencia o deriva del marco, y apuntando a la
línea del registro desde la que se decodificó el marco. Una señal nombrada en --fail-on se
informa con nivel error y una fuera de él con nivel note, por lo que el informe y
la compuerta nunca discrepan.
Un resultado apunta al registro con una ruta relativa al directorio de trabajo, que
code scanning resuelve luego contra la raíz del repositorio. La alerta se muestra con
sus líneas circundantes solo cuando esa ruta es un archivo en el commit analizado, por lo que una
captura que el flujo de trabajo generó en artifacts/ abre una alerta que lleva el
mensaje, la regla y el número de línea, pero sin vista del código fuente. Hacer commit de una captura
que quieras mostrar completa es la única manera de conseguir una. Un registro leído desde el
directorio de estado o desde stdin no recibe ruta alguna.
Code scanning rechaza un archivo cuya ejecución contenga más de 25 000 resultados y
solo muestra los 5 000 primeros de los que acepta, por lo que el informe se limita a 5 000:
primero los hallazgos por los que falló la compuerta, y luego un resultado mcpsnoop/report-truncated
que indica cuántos quedaron fuera. Los formatos text y junit permanecen completos.
Para poner los hallazgos en la pestaña Security, entrega el registro SARIF a
upload-sarif. El trabajo necesita security-events: write, o la subida responde
403. check sale con código distinto de cero ante un hallazgo, por lo que el paso de subida necesita if: always()
para ejecutarse siquiera en las ejecuciones que tienen algo que informar; continue-on-error
transfiere el veredicto a la comprobación de code scanning, que falla ante una alerta de nivel
error y puede convertirse en una comprobación obligatoria. Elimínalo si prefieres que el propio
paso de comprobación sea el que ponga el trabajo en rojo.```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
### Detectar un encabezado de enrutamiento que discrepa con el cuerpo
En el transporte streamable-HTTP, una puerta de enlace enruta según `Mcp-Method` y `Mcp-Name`
mientras el servidor lee el cuerpo, por lo que un encabezado que discrepa del cuerpo significa
que ambos están viendo dos solicitudes distintas. La señal `mismatch` cubre ese caso,
un encabezado que viaja en un lote al que no puede dirigirse, y un encabezado obligatorio ausente
por completo.
En la revisión 2026-07-28, un encabezado de enrutamiento faltante es un fallo de validación, y un servidor
conforme rechaza la solicitud con `400` y `-32020`. mcpsnoop solo lo señala una vez
que se sabe que la sesión habla esa revisión o una posterior, ya que las revisiones anteriores
no definen estos encabezados en absoluto y omitirlos allí es correcto. El propio rechazo
`-32020` de un servidor cuenta como la misma señal.
Un nombre o URI de recurso que no quepa en un valor de campo HTTP viaja en Base64 dentro
de un centinela `=?base64?…?=`, que se decodifica antes de la comparación, por lo que un cliente
que codifica correctamente nunca se marca.
En las solicitudes HTTP `tools/call`, mcpsnoop también muestra cada cabecera `Mcp-Param-{Name}`
y, cuando se conoce la definición de herramienta anunciada coincidente, la compara con la
ruta de argumento anotada. Las propiedades anidadas, el centinela Base64, los booleanos y los
enteros seguros numéricamente equivalentes se manejan sin falsos positivos de comparación de
cadenas. Las cabeceras de parámetros desconocidas y las sesiones sin una definición de herramienta
coincidente permanecen solo como observación. La redacción basada en claves y valores se aplica a los
valores de cabecera de parámetros capturados antes de que lleguen a un sumidero, y un valor que mcpsnoop
haya depurado por sí mismo nunca se notifica como discrepancia.
### Detectar deriva en la definición de herramientas
La primera `tools/list` completa observada para una etiqueta de servidor se convierte en su línea base
de confianza. Las sesiones posteriores comparan esa línea base campo por campo: la descripción,
el título, los esquemas de entrada y salida, las anotaciones y los iconos, además de las herramientas
añadidas o eliminadas. Las anotaciones son lo que más importa, ya que una herramienta aprobada
con `readOnlyHint` que luego se declara destructiva es el tirón de alfombra para el que existe esta
comprobación, y la especificación indica a los clientes que traten las anotaciones como no confiables.
El título y los iconos se rastrean porque son lo que ve el usuario, y la especificación coloca el
`title` de una herramienta por encima de `annotations.title` y de su nombre. La tabla de sesiones
y el resumen de herramientas marcan la deriva sin bloquear ni modificar el tráfico MCP.
Las anotaciones se comparan mediante sus valores predeterminados en la especificación, por lo que un servidor que empieza
a explicitar una pista en la que ya se apoyaba no se notifica. Una línea base
registrada antes de que mcpsnoop rastreara un campo sigue funcionando para los campos que sí
registra e indica cuáles no puede responder; vuelve a registrarla con
`mcpsnoop baseline --accept` una vez que confíes en las definiciones actuales.
Cambiar lo que registra la redacción cambia lo que compara la deriva. Una línea base tomada
sin `--redact-value` y luego verificada contra una captura tomada con esa opción
notifica los campos depurados como cambiados, lo cual es correcto, ya que la definición registrada
realmente cambió. Vuelve a registrarla con `--accept` después de cambiar la configuración de redacción.
Usa una `--label` estable y única para cada servidor cuyo nombre de comando u host de destino
colisionaría de otro modo. Las líneas base se almacenan en el directorio de estado normal de mcpsnoop,
por lo que se aplican `MCPSNOOP_HOME` y `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
En un CI efímero, el directorio de estado comienza vacío, por lo que la primera ejecución solo registra
la línea base y no reporta desviaciones. La línea base debe persistir entre ejecuciones para
que las ejecuciones posteriores puedan verificarse contra ella. Apunte --baseline a un directorio incluido en el repositorio o en caché,
o establezca MCPSNOOP_HOME en una ruta persistente.```bash
mcpsnoop check --fail-on drift --baseline .mcpsnoop/baselines session.jsonl
`drift` es opt-in para `check`; el filtro predeterminado `error,invalid,warn` no cambia.
### Marcar características de protocolo obsoletas
La revisión del 2026-07-28 marca como obsoletos Roots, Sampling y Logging. Siguen funcionando
durante al menos un año, por lo que mcpsnoop los marca en lugar de tratarlos como errores.
El stream, el inspector de capacidades y la exportación los marcan todos, y cada marcador
nombra el reemplazo.
Dos de los tres ahora solo son accesibles mediante una solicitud multi round-trip, donde
el nombre del método se encuentra dentro del mapa `inputRequests` del servidor en lugar de en la
trama misma. Esos también se marcan, de modo que un servidor que haya migrado al nuevo patrón
no deje de informar en silencio.```bash
mcpsnoop check --fail-on deprecated session.jsonl
Igual que drift, deprecated es opcional (opt-in). Una ejecución por defecto informa del recuento y permanece en verde, por lo que una sesión que utilice una característica obsoleta aún legal nunca vuelve rojo el CI por sí sola.
Construcciones de esquema que los clientes manejan mal
Un servidor puede ser perfectamente válido y aun así ser difícil de usar para un agente. Los clientes difieren en cuánto de JSON Schema soportan realmente, y una herramienta que el modelo sigue invocando incorrectamente suele ser una herramienta cuyo esquema pidió más de lo que el cliente ofrece.
El resumen de herramientas, abierto con s, tiene una columna SCHEMA que nombra lo más notable del esquema de cada herramienta anunciada, con un + al final cuando hay más de un tipo.
| Mostrado | Significado |
|---|---|
no root | el inputSchema está ausente, no es un objeto JSON, o tiene un tipo raíz distinto de "object" |
dialect | un $schema que nombra un dialecto distinto del 2020-12 que la revisión usa por defecto |
ext ref | un $ref que apunta fuera del documento, que es también el caso en el que la especificación advierte a los implementadores que no lo sigan a ciegas |
oneOf, anyOf, allOf, not | una palabra clave de composición, manejada de forma inconsistente entre clientes |
ref | un $ref que apunta dentro del mismo documento |
untyped | una propiedad que no declara ningún tipo ni ninguna otra forma de decir qué acepta |
Todas menos la primera son observaciones más que veredictos. Un esquema que usa oneOf no es incorrecto, solo es probable que distintos clientes lo lean de forma diferente, y un esquema puede declarar el dialecto que quiera. no root es la excepción: la definición de Tool requiere inputSchema y fija su tipo raíz a "object", por lo que un cliente que valida un listado rechaza esa herramienta directamente y nunca llega a ser invocable, sin nada en la conexión que diga por qué. no root encabeza la columna por esa razón, y un esquema que la propia redacción de mcpsnoop haya depurado nunca se informa, ya que un esquema ilegible no es uno incorrecto.
Esa distinción decide qué hace check con ellos. no root es una advertencia en el frame tools/list, por lo que falla la compuerta por defecto error,invalid,warn sin ninguna bandera, que es el punto: un servidor que incluye una herramienta inutilizable responde a cada handshake con normalidad y simplemente nunca recibe un tools/call. Las observaciones se cuentan como schema_findings y se informan bajo schema findings:, y solo hacen fallar la ejecución cuando añades schema a --fail-on. Ambas llegan a --format junit y --format sarif, y export lleva la lista por herramienta bajo 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 columna lleva el color de advertencia y nunca el rojo de la columna ERR, y
mcpsnoop sigue sin cambiar nada del tráfico que reenvía.
No se resuelve ni se obtiene nada. Un `$ref` externo se reconoce solo por su
forma, y el esquema al que apunta nunca se lee.
### Comprueba cuánto te cuesta el servidor en contexto
Las definiciones de herramientas entran en el contexto del modelo en cada
conversación, y los resultados de las herramientas en cada llamada. El resumen
de herramientas (`s`) mide ambos a partir de la sesión que realmente
capturaste.
La línea `definitions` es el coste fijo: lo que pesa el `tools/list` de este
servidor antes de que se haga una sola llamada. La columna `DEF` lo desglosa
por herramienta y `RESULT` es lo que han costado las respuestas de cada
herramienta hasta ahora. La tabla se mantiene ordenada por errores y latencia,
así que revisa `DEF` para encontrar las definiciones caras; la exportación las
lista de mayor a menor peso. Una línea bajo la tabla indica el resultado más
pesado, que un total oculta.
Las cifras de definiciones son el JSON sin los espacios en blanco
insignificantes, de modo que un servidor que formatea su `tools/list` de forma
legible no se considera más caro que uno que no lo hace, y el mismo servidor
mide lo mismo entre capturas. `RESULT` son los bytes tal y como llegaron: un
resultado es una carga de una sola vez, no un contrato que merezca la pena
normalizar.```bash
mcpsnoop export -T json | jq '.summary.definitions'
La exportación incluye las mismas cifras, por herramienta y divididas en descripción y
bytes de esquema, de modo que una descripción grande y un esquema grande siguen siendo separables y cualquiera
de los dos se puede rastrear entre capturas. mcpsnoop diff te indica si una descripción o un
esquema cambió entre dos sesiones; la exportación es donde vive el tamaño de ese
cambio.
Estos son bytes, no tokens. El recuento de tokens depende del modelo, así que
medirlo implicaría incluir un tokenizador y elegir de quién. Los bytes son
exactos y puedes aplicar tu propia proporción. Un tools/list incompleto informa de lo que
vio como un mínimo y lo dice, en lugar de hacer pasar una suma parcial por el
total.
Detectar un cliente que altera el estado del servidor
En el patrón de múltiples idas y vueltas, el servidor entrega al cliente un
requestState opaco y el cliente debe devolverlo intacto en el reintento. Se le
indica al servidor que lo trate como entrada controlada por el atacante, porque un cliente que
lo manipula puede intentar alterar el comportamiento del servidor o eludir una comprobación
de autorización.
Situado en la tubería, mcpsnoop ve el valor salir y volver, por lo que puede decir cuándo se rompió el contrato. Hay tres formas en que puede romperse, cada una notificada como advertencia de protocolo en el reintento.
| Reportado | Significado |
|---|---|
MRTR retry changed requestState | el cliente devolvió algo distinto de lo que emitió el servidor |
MRTR retry is missing requestState | el servidor emitió uno y el reintento lo omitió |
MRTR retry invented requestState | el reintento llevaba uno que el servidor nunca emitió |
Estas son violaciones de protocolo por parte del cliente, no observaciones nuestras, así que
viajan por la señal de advertencia ordinaria y una ejecución de check predeterminada falla ante una.
Eso es deliberado. Un cliente que altera el estado del servidor merece detener una compilación.
El valor en sí nunca se muestra ni se registra, y nada lo decodifica ni lo analiza. Puede ser un blob cifrado que lleva un principal y un token, y comparar bytes opacos es toda la comprobación.
Un caso queda fuera de alcance. Cuando un servidor responde con un requestState y sin
inputRequests, un reintento manipulado no coincide con nada y no responde claves, así que no
queda nada que lo vincule a la solicitud original y se lee como una llamada no
relacionada en lugar de una violación.
Observar desde otra máquina
Mantén la captura local en la máquina donde ocurre el tráfico y usa SSH para el salto de red, de modo que mcpsnoop nunca necesite un transporte remoto propio.
Vista en vivo
Ejecuta la TUI en tu estación de trabajo y reenvía el socket de mcpsnoop de la máquina remota de vuelta a ella. El túnel en vivo usa reenvío de sockets Unix sobre SSH, por lo que ambos extremos deben ejecutar Linux o macOS. En Windows, usa la copia del registro póstumo que se indica a continuación.```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
El socket reside en el directorio de estado del remoto, resuelto como `MCPSNOOP_HOME`,
de lo contrario `XDG_STATE_HOME/mcpsnoop`, de lo contrario `~/.local/state/mcpsnoop`. Por defecto, mcpsnoop
asume el home de Linux `/home/<user>` a partir de tu `user@host` e imprime un recordatorio
en stderr cada vez que recurre a esa suposición. Si el remoto se resuelve en otra ubicación,
indica la única pieza no predeterminada.```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
Transmite una sesión remota directamente a la TUI a través de SSH, sin necesidad de copia local.```bash ssh remote-user@remote-host 'cat ~/.local/state/mcpsnoop/sessions/session.jsonl' | mcpsnoop open -
Para tener una copia local, copia los logs a tu directorio sessions con scp y ejecuta la TUI como de costumbre.```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
Seguridad
mcpsnoop ejecuta el comando de servidor que envuelves, así que envuelve solo servidores en los que confíes y ejecuta los que no sean de confianza en un contenedor. Nunca ejecuta nada que no hayas puesto en la configuración de tu cliente.
Las tramas capturadas pueden incluir prompts, argumentos de herramientas, credenciales y resultados de herramientas. Si las cargas útiles pueden contener secretos, activa la supresión para depurar las copias de la traza observada mientras los bytes que atraviesan el proxy siguen pasando sin cambios.
La supresión basada en claves reemplaza valores completos bajo las claves de objeto JSON que coinciden, y el mismo conjunto de claves se aplica, en la medida de lo posible, a los argumentos de línea de comandos del servidor envuelto, de modo que --api-key=sk-x y --token sk-x se depuran con --redact-secrets. Un argumento que contenga un secreto sin un nombre de bandera reconocible no puede detectarse.
La supresión basada en rutas reemplaza solo los valores seleccionados por una expresión JSONPath, lo que resulta útil cuando un nombre de clave común es sensible en una ubicación pero seguro en otra. Repite --redact-path para depurar más de una ubicación.
La supresión basada en valores aplica expresiones regulares a los valores de cadena observados, al texto de stderr y a las tramas de texto no JSON.
Las tres son de mejor esfuerzo. Las expresiones regulares pueden pasar por alto secretos, coincidir en exceso con texto inofensivo o no llegar a ver valores transformados o codificados.
La supresión nunca se convierte en una acusación. Cada comprobación que compara una cosa observada con otra, una cabecera de enrutamiento con el cuerpo, un valor Mcp-Param con el argumento que refleja, el esquema de una herramienta con lo que la revisión exige de ella, sabe cuándo fue mcpsnoop quien reescribió los bytes y guarda silencio en lugar de señalar a un servidor por la propia configuración de privacidad del usuario.
La deriva de la definición de herramientas es la excepción, y de forma deliberada, ya que activar la supresión cambia lo que se registra y, por tanto, lo que contiene una línea base. Consulta Detectar la deriva de la definición de herramientas.```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+'
Para flujos de trabajo remotos, use túneles SSH o transferencia de archivos por SSH para que la autenticación de transporte, el cifrado, la verificación del host, la rotación de claves y la política de auditoría permanezcan en su configuración SSH existente.
## Contribuciones
Las incidencias y las solicitudes de extracción son bienvenidas. Consulte [CONTRIBUTING.md](https://github.com/kerlenton/mcpsnoop/blob/HEAD/CONTRIBUTING.md) para
obtener los detalles.
## Licencia
[MIT](https://github.com/kerlenton/mcpsnoop/blob/HEAD/LICENSE)