Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
mcpsnoop — 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. | Kitploit
Herramientas/GitHubGitHub/kerlenton/mcpsnoop
Utilidades de Propósito GeneralAnálisis Dinámico (Sandboxing)Mapeo de RedesProxies Web e InterceptaciónScripting y AutomatizaciónPruebas de Seguridad de APIsDepuradoresAnálisis de Registros
GitHubkerlenton/mcpsnoop

mcpsnoop

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.

3343220hace 14 díasRevisado por Kitploit
Ver Repositorio

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

mcpsnoop

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.

CI Go Reference MIT Marketplace

mcpsnoop demo

El problema

El Inspector 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 espera a que llegue una solicitud no puede mostrar la llamada que el modelo nunca hizo, o que hizo con argumentos incorrectos. Cuando una herramienta no se llama silenciosamente, las capacidades no coinciden, o una llamada simplemente se cuelga, te quedas revisando registros y adivinando.

mcpsnoop se sitúa en la ruta de datos real en su lugar. Envuelve el comando de tu servidor con él y observa cada trama JSON-RPC en vivo, mientras tu cliente y servidor reales conversan.

En CI

Esta página también es el listado de la Acción de GitHub mcpsnoop, así que aquí está todo. Comprueba una sesión capturada, registra cada hallazgo como una alerta de escaneo de código, y hace fallar el trabajo en lo que hayas establecido como condición.```yaml permissions: security-events: write contents: read

steps:

  • uses: kerlenton/[email protected] with: session: artifacts/session.jsonl
root@kitploit:~
Elige la versión que quieras. La más reciente está en la
[página de releases](https://github.com/kerlenton/mcpsnoop/releases). Cada entrada,
qué significan los códigos de salida y cómo conectarlo sin la acción están en
[La GitHub Action](#the-github-action) más abajo.

## Inicio rápido

Pruébalo de inmediato, sin necesidad de configurar nada.```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"] } } }

root@kitploit:~
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 la entrada de ese servidor, por lo que tu formato y todos los demás servidores se dejan intactos. Dentro de la entrada reescrita, las claves vuelven 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 acciones, 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

root@kitploit:~
No flags, no socket paths, no startup order to remember. The shim and the UI find
each other on their own, and the UI backfills past sessions from disk.

For a streamable-HTTP server, run mcpsnoop as a reverse proxy.```bash
mcpsnoop http --target http://localhost:3000/mcp --listen :7000

El estado HTTP de cada respuesta se muestra en el flujo, por lo que una respuesta que no contiene ningún mensaje JSON-RPC propio sigue siendo un marco visible en lugar de nada: el desafío 401, el 403 en un Origin rechazado, el 202 que confirma una notificación, y el 502 cuando no se puede alcanzar el destino en absoluto. El encabezado WWW-Authenticate de un 401 se conserva textualmente y se muestra en el inspector, ya que indica el esquema de autenticación y los metadatos del recurso a los 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 predeterminada de mcpsnoop check falla en ese caso.

¿No tienes un servidor propio? Pruébalo de verdad contra un servidor de prueba publicado, impulsado por tu propio cliente. Para inspeccionar una sesión después de que haya ocurrido, consulta revisar sesiones pasadas desde registros.

Archivo de configuración

Si reutilizas las mismas opciones de shim en un proyecto, colócalas 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

root@kitploit:~
Repite `redact-key`, `redact-value` y `redact-path` en líneas propias para añadir
más de uno de cada.

Esas son todas las claves que admite.

El archivo solo se busca en el directorio de trabajo actual, no en directorios
padres.

Las banderas explícitas de la 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 vivo |
| `mcpsnoop http --target <url>` | hace de proxy para un servidor HTTP transmisible |
| `mcpsnoop export` | renderiza una sesión a json, html, text, har u otlp |
| `mcpsnoop check` | falla el CI por errores, marcos inválidos, advertencias, desajustes de enrutamiento, llamadas colgadas, resultados tardíos o un presupuesto de latencia |
| `mcpsnoop baseline` | inspecciona, acepta o restablece definiciones de herramientas de confianza |
| `mcpsnoop diff` | compara herramientas y llamadas entre dos sesiones capturadas |
| `mcpsnoop open` | abre una sesión guardada en la TUI |
| `mcpsnoop inventory` | lista cada servidor que haya pasado por mcpsnoop en esta máquina |
| `mcpsnoop stats` | pliega cada captura almacenada en una fila por servidor y herramienta |
| `mcpsnoop prune` | elimina registros de sesiones guardadas 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>` | restaura la entrada de ese servidor a como estaba |
| `mcpsnoop remote <user@host>` | imprime el comando del túnel SSH |
| `mcpsnoop demo` | reproduce una sesión con guion |

Ejecuta `mcpsnoop help` para la lista completa, o `mcpsnoop help <comando>` para las banderas de uno.

## 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 marcos JSON-RPC malformados | no | sí |
| Detecta deriva en definiciones de herramientas tras la aprobación | no | sí |
| Interfaz de terminal interactiva | no | sí |
| Cero configuración, sin banderas ni orden | no | sí |
| Inspector de capacidades | parcial | sí |
| Reproduce una llamada capturada | no | sí, por stdio y por HTTP |
| Exportación de sesión (json / html / text / otlp) | no | sí |
| Binario único, sin dependencias de ejecución | no | sí |

## Instalación

### npm

No se necesita cadena de herramientas Go. La mayoría de los servidores MCP están escritos en Node o Python, así que esta
es la vía más corta.```bash
npx mcpsnoop -- node build/index.js

El paquete npm no incluye código propio. Cada uno de los seis paquetes de plataforma contiene una compilación, y npm instala únicamente la que coincide con tu máquina, por lo que no hay nada que descargar en el momento de la instalación ni nada que desbloquear en un proxy. Para mantenerlo disponible en lugar de obtenerlo en cada ejecución, usa npm i -g mcpsnoop.

Go```bash

go install github.com/kerlenton/mcpsnoop/cmd/mcpsnoop@latest

root@kitploit:~
### Homebrew```bash
brew install mcpsnoop

Binarios precompilados para cada plataforma están en la página de Releases.

Completado de shell

mcpsnoop incluye completado para bash, zsh, fish y PowerShell. Ejecuta mcpsnoop completion <shell> --help para ver los pasos de configuración, que cubren cómo habilitar el completado y la ruta de instalación para tu sistema operativo.

Cómo funciona

mcpsnoop se sitúa en la tubería entre tu cliente de IA y tus servidores MCP, copiando cada trama JSON-RPC a una interfaz de terminal en vivo

mcpsnoop es 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. Ambos se comunican 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 inicio sin eliminar 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 cuántas sesiones se cargan. Dentro de una sesión, la TUI en vivo está acotada dos veces, porque un hub que se queda observando un servidor hablador de otro modo crece hasta que se lo mata. Mantiene como máximo 64 MiB de cuerpos de trama, liberando los más antiguos primero, y como máximo 200 000 tramas, descartando por completo las más antiguas a partir de ahí. El primer límite es el que alcanza una captura de cargas útiles grandes y el segundo el que alcanza un flujo largo de notificaciones pequeñas.

Ninguno de los límites cambia una respuesta. Una trama cuyo cuerpo fue liberado conserva su fila, su veredicto y su lugar en la línea de tiempo, y su inspector dice que el cuerpo ya no está en lugar de mostrar una trama vacía. Una trama que se descartó por completo lleva primero las estadísticas de su llamada de herramienta a los totales acumulados, de modo que el resumen de herramientas y lo que el servidor te cuesta en contexto describen cada llamada que hizo la sesión, no solo las recientes. El pie de página del flujo dice cuántas tramas más antiguas están solo en disco, y r rechaza una trama cuyos parámetros ya no conserva en lugar de reproducir otra cosa.

mcpsnoop open <session-id> lee el registro y lo mantiene todo, y exportar desde la TUI también lee el registro, por lo que ninguno está acotado. check, export y diff construyen un almacén sin límites a propósito, ya que una puerta que subestima en una captura grande es peor que una que usa la memoria.

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

root@kitploit:~
`--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 las herramientas se
dejan intactas, ya que una línea base está vinculada por la etiqueta del servidor y no por la sesión.

Debido a que se encuentra en la tubería real, no a un lado como el Inspector, ve
exactamente lo que su cliente y servidor reales se dicen entre sí, sin importar en qué
esté escrito el servidor.

## Atajos de teclado

| Tecla | Acción | | Tecla | Acción |
|---|---|---|---|---|
| `enter` | inspeccionar / profundizar | | `/` | filtrar |
| `esc` | atrás | | `:` | comando |
| `j` / `k` | mover | | `r` / `R` | reproducir / editar y reproducir |
| `g` / `G` | arriba / abajo | | `c` | capacidades |
| `ctrl-f` / `ctrl-b` | página | | `s` | resumen de herramienta |
| `p` | pausa | | `y` | copiar |
| `shift`+`<tecla>` | ordenar por columna | | `e` | exportar |
| `ctrl-d` | eliminar sesión | | `f` | seguir |
| `?` | ayuda | | | |

Pulsa `?` en la aplicación para ver la lista completa.

## Filtrar el flujo

Pulsa `/` en una sesión y combina tokens separados por espacios, con AND. El texto sin formato
coincide con el método, la herramienta, el id y la carga útil.

| 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 lo continúe | `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 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)

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.

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|-]

root@kitploit:~
| Formato | Qué 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 autocontenido 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, con contexto de traza W3C que une las trazas del llamador cuando está presente y una traza por sesión en caso contrario |

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, no una transcripción del cable.

Para OTLP, el `_meta.traceparent` de una solicitud proporciona la traza y los IDs de span
padre de esa llamada, y `_meta.tracestate` viaja junto al span. Cuando el traceparent está
ausente o es inválido, mcpsnoop mantiene la traza derivada de la sesión y no transporta estado.
mcpsnoop observa en lugar de participar, por lo que no añade una entrada de proveedor propia
y transmite 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

Omita -o para escribir en stdout, y omita la sesión para tomar la más reciente, o pase - para leer JSONL desde stdin. En la TUI, presione e para exportar la sesión seleccionada como HTML, o ejecute :export json|html|text|har|otlp [path] desde el modo de comando.

Redacción

Para depurar una captura existente antes de inspeccionarla o compartirla, pase las mismas banderas de redacción utilizadas 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'

root@kitploit:~
Estas banderas 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 a través de un archivo temporal que se renombra en su lugar,
de modo que una ejecución que falle deja intacto el archivo anterior.

El `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`, por tres razones.

- Un nombre dentro de un esquema es una declaración de tipo, no un valor.
- El nombre en sí permanece en el registro de todos modos.
- Depurar el subesquema bajo una propiedad llamada `token` eliminaría también
  las propias comprobaciones de la herramienta.

La exención se aplica solo a esa posición, de modo que un argumento que
casualmente se llame `inputSchema` se depura como cualquier otro, y se detiene
en `default`, `const`, `examples` y `enum`, que contienen datos en lugar de
estructura. Usa `--redact-path` para nombrar algo dentro de un esquema, o
`--redact-value`, que coincide con texto dondequiera que esté, excepto en las
dos palabras clave que mcpsnoop analiza, `type` y `x-mcp-header`.

Lo que alcanza cada bandera difiere, así que revisa el resultado en lugar de
asumir. Las cuatro depuran cargas útiles JSON-RPC, y `--redact-key`,
`--redact-path` y `--redact-secrets` solo llegan a esas. Solo `--redact-value`
también depura stderr, otro texto no JSON y el interior de una cadena. Una
cabecera `Mcp-Param-*` se depura junto con el valor del cuerpo que refleja. El
resto de los metadatos del sobre, las etiquetas del servidor, `Mcp-Name`,
`Mcp-Method` y el estado HTTP, se dejan tal como se capturaron. La redacción es
de mejor esfuerzo, así que usa una ruta de salida separada y lee el resultado
antes de compartirlo.

### Transmitir llamadas completadas a un colector OTLP

Envía spans mientras el proxy está en ejecución apuntándolo a un endpoint de
trazas OTLP/HTTP JSON. Repite `--otlp-header` para la autenticación del
colector o las cabeceras 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 proxyado. Si el recolector no está disponible, mcpsnoop reintenta en segundo plano y descarta nuevos marcos de rastreo cuando su cola limitada está llena. El registro de sesión JSONL normal sigue siendo el registro duradero.

Comparar sesiones

Compare dos sesiones guardadas por id o ruta JSONL.```bash mcpsnoop diff before-session after-session mcpsnoop diff old.jsonl new.jsonl

root@kitploit:~
El informe muestra las herramientas que se añadieron o eliminaron, la descripción y los cambios en `inputSchema`, las llamadas a herramientas coincidentes cuyo estado cambió, y los cambios notables de duración. Las llamadas se emparejan por nombre de herramienta y argumentos, por lo que las llamadas reordenadas aún se comparan 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 la CI a regresiones. Sale con un 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ó
- se ralentiza

Un cambio de icono no lo hace, ya que altera el aspecto de una herramienta sin cambiar lo que hace. Las mejoras, es decir, herramientas añadidas, llamadas corregidas y aceleraciones, aún salen con cero.

## Comprobación de sesiones en CI

Condiciona una ejecución de agente grabada a errores, corrupción de flujo, advertencias de protocolo, discrepancias de encabezado de enrutamiento, llamadas que nunca recibieron respuesta, fotogramas descartados que dejan la captura incompleta, desviación de definición de herramientas o 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 fallan la comprobación por sí solos. El resto son opcionales. Pasa un subconjunto separado por comas para filtrar solo 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ñalFalla en
erroruna llamada respondida con un error JSON-RPC, un resultado marcado como isError, o una tarea que terminó en un fallo
invalidun frame en el canal de protocolo que no es JSON-RPC válido, normalmente un servidor registrando en stdout
warnun frame que rompe una expectativa establecida por la especificación MCP o JSON-RPC
mismatchun encabezado de enrutamiento que discrepa con el cuerpo, que monta un lote, o que falta donde la revisión lo requiere
pendinguna solicitud aún abierta cuando terminó la captura, por lo que el llamador quedó esperando
late-resultuna respuesta que llegó después de que su solicitud fuera cancelada
driftuna definición de herramienta anunciada que cambia después de que la línea base fue aprobada
deprecateduna característica que la especificación ha marcado como obsoleta
incompleteframes descartados aguas arriba, lo que convierte cada otro conteo en un mínimo en lugar de un total
schemaun esquema anunciado que usa una construcción o un dialecto que viaja mal entre clientes

Cada señal se cuenta ya sea que esté filtrando o no, por lo que una ejecución dice lo que encontró antes de que decidas qué debería fallar en ello.``` 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

root@kitploit:~
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
- 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

El código de salida indica cuál de dos cosas ocurrió, y un envoltorio de CI necesita la diferencia. 1 significa que la comprobación se ejecutó y algo falló en la puerta de validación, por lo que los hallazgos son reales y vale la pena publicarlos. 2 significa que la comprobación nunca ocurrió: una ruta que no existe, un archivo que no es un registro de sesión, un directorio de estado que no contiene nada, una bandera que no se analiza. Nada se escribe en stdout en un 2, por lo que una canalización nunca sube un informe vacío como si fuera un veredicto.

Afirmar lo que debe y no debe ocurrir

Más allá de los recuentos de señales, afirma la forma de la ejecución. Estos se combinan entre sí y con --fail-on, y cualquier fallo sale con 1, el código que significa que la comprobación se ejecutó y encontró algo.

BanderaFalla cuando
--max-duration <dur>una o más llamadas de herramienta completadas superaron el presupuesto, informando su recuento 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

root@kitploit:~
### Repórtalo donde CI ya lo revisa

`--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. Mientras que junit informa un agregado por señal, SARIF informa un resultado por hallazgo, que incluye la sesión, el Seq del frame y el texto propio de advertencia o deriva del frame, y apunta a la línea del registro desde la que se decodificó el frame. Una señal nombrada en --fail-on se informa en el nivel error y una fuera de él en el nivel note, de modo que el informe y la compuerta nunca discrepan.

Un resultado apunta al registro del que proviene el hallazgo, y cómo depende de dónde se leyó el registro.

  • Una ruta dentro del directorio de trabajo se vuelve relativa, que el code scanning resuelve contra la raíz del repositorio.
  • Una ruta en otro lugar del disco, o un id de sesión resuelto desde el directorio de estado, se convierte en un URI absoluto file://.
  • Leer desde stdin da un resultado sin ubicación alguna, ya que no hay archivo al que apuntar.

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 con el mensaje, la regla y el número de línea, pero sin vista de código fuente. Confirmar una captura que quieras mostrar completa es la única forma de obtener una.

Code scanning rechaza un archivo cuyo run contenga más de 25,000 resultados y muestra solo los 5,000 principales de lo que acepta, por lo que el informe está limitado a 5,000: primero los hallazgos en los que falló la compuerta, luego un resultado mcpsnoop/report-truncated que indica cuántos se omitieron. Los formatos de texto y junit permanecen completos.

La GitHub Action

Todo lo siguiente es lo que la acción hace por ti. Instala mcpsnoop, verifica la captura, archiva los hallazgos en la pestaña Security y hace fallar el job en lo que hayas configurado en la compuerta.```yaml permissions: security-events: write contents: read

steps:

  • uses: kerlenton/[email protected] with: session: artifacts/session.jsonl
root@kitploit:~
Fija una versión, la que quieras. La más reciente está en la
[página de releases](https://github.com/kerlenton/mcpsnoop/releases). No hay
un `v1` flotante, a propósito. La versión fijada es también el binario que
instala la acción, por lo que los dos nunca pueden discrepar y no hay una
versión predeterminada que quede obsoleta.

| Entrada | |
|---|---|
| `session` | la captura `.jsonl` a comprobar, relativa a la raíz del repositorio. Obligatoria |
| `fail-on` | igual que `--fail-on`, con el mismo valor predeterminado que usa la CLI |
| `args` | cualquier otra bandera de `check`, entre comillas como en una línea de comandos. Se rechaza `--format`, ya que la acción lee el informe |
| `upload-sarif` | envía el informe al code scanning. `true` |
| `category` | el espacio de nombres de code scanning. `mcpsnoop`. Varíalo por cada rama de una matriz, o las ramas se sobrescriben entre sí |
| `fail-on-findings` | hace fallar el job ante un hallazgo. `true`. Pon `false` para archivar las alertas y dejar que la comprobación obligatoria de code scanning decida |
| `version` | qué mcpsnoop instalar. Por defecto, la versión que fijaste |
| `install` | `false` cuando mcpsnoop ya está en PATH, que es la vía en una plataforma para la que no se ha compilado ninguna release |

Las salidas son `outcome`, `sarif` y `exit-code`. `outcome` es `passed`,
`findings` o `error`, y la tercera merece tratarse por separado. Significa que
no se comprobó nada, que no es lo mismo que no se encontrara nada. **Una
ejecución que no pudo comprobar hace fallar el job diga lo que diga
`fail-on-findings`**, porque un pipeline que se pone en verde habiendo
verificado nada es peor que uno que falla.

El job necesita `security-events: write`, o la subida responde 403. Pon
`upload-sarif: false` en un repositorio sin code scanning.

### O conéctalo tú mismo

La acción son cuatro pasos y nada de magia. Hacerlo a mano requiere el mismo
cuidado que requiere. La subida tiene que ejecutarse en las ejecuciones que
tienen un informe, que son las que salieron con 0 o 1 y no las que salieron
con 2, y el paso que hace fallar el job tiene que ir después, o los hallazgos
nunca llegan a la pestaña para la que existen.```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

Detectar un encabezado de enrutamiento que no coincide con el cuerpo

En el transporte HTTP streamable, una puerta de enlace enruta según Mcp-Method y Mcp-Name mientras que el servidor lee el cuerpo, por lo que un encabezado que no coincide con el cuerpo significa que ambos están viendo dos solicitudes diferentes. La señal mismatch cubre eso, un encabezado que viaja en un lote al que no puede dirigirse, y un encabezado obligatorio que falta por completo.

En 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 rechazo propio del servidor con -32020 cuenta como la misma señal.

Un nombre o URI de recurso que no cabe 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 encabezado Mcp-Param-{Name} y, cuando se conoce la definición de herramienta anunciada correspondiente, 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 por comparación de cadenas. Los encabezados de parámetros desconocidos y las sesiones sin una definición de herramienta correspondiente permanecen en modo observacional. La redacción basada en claves y valores se aplica a los valores de encabezados de parámetros capturados antes de que lleguen a un destino, y un valor que el propio mcpsnoop depuró nunca se informa como una discrepancia.

Comprobar los encabezados de transporte que la especificación hace obligatorios

Los encabezados de enrutamiento anteriores eran los únicos que llevaba una trama, por lo que el resto de los encabezados obligatorios del transporte HTTP streamable no llegaban a nada que pudiera verificarlos. Content-Type era el caso más crítico. El lado de la respuesta ya lo leía para distinguir un flujo SSE de un cuerpo JSON, y luego lo descartaba.

Una trama HTTP ahora lleva los encabezados sobre los que el transporte establece reglas, y dos de esas reglas son verificables.

ReglaSe informa como
el cliente DEBE enviar un Accept que liste tanto application/json como text/event-streamwarn en la solicitud
un servidor que responde a una solicitud JSON-RPC DEBE devolver Content-Type: application/json o text/event-streamwarn en la respuesta

Ambas frases se leen igual en 2025-11-25 y 2026-07-28, por lo que, a diferencia de las comprobaciones de deriva y extensiones, estas no necesitan una puerta de revisión. Origin también se registra, ya que los servidores DEBEN validarlo y DEBEN responder con 403 cuando no es válido, pero mcpsnoop no puede conocer tus orígenes permitidos, por lo que muestra el valor en lugar de juzgarlo.

Los comodines cuentan. Un cliente que envía */* ha ofrecido ambos tipos y nunca se informa, y un parámetro charset en un Content-Type se ignora. Un registro capturado antes de que mcpsnoop registrara estos encabezados permanece en silencio en lugar de informar cada trama en él por un encabezado que nadie anotó, y stdio nunca los tiene en absoluto.

Authorization no se captura deliberadamente. Convertir un desafío en hechos de token es un problema en sí mismo, y poner un token de portador en disco no es la respuesta a eso. Mcp-Session-Id y Last-Event-ID tampoco se capturan. La revisión 2026-07-28 eliminó ambos y le dice al servidor que los ignore, por lo que no queda ninguna regla que verificar.

Detectar deriva en las definiciones de herramientas

El primer tools/list completo observado 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

Las herramientas que se agregaron o eliminaron también se comparan, lo cual es una comparación de conjuntos en lugar de una de campos.

Las anotaciones son lo más importante, 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 les dice 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 clasifica 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 cambiar el tráfico MCP.

Las anotaciones se comparan a través de sus valores predeterminados de especificación, por lo que un servidor que comienza a explicitar una pista en la que ya confiaba no se informa. Una línea base registrada antes de que mcpsnoop rastreara un campo sigue funcionando para los campos que sí registra y dice cuáles no puede responder. Vuelve a registrar 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 uno informa los campos depurados como cambiados, lo cual es correcto, ya que la definición registrada realmente cambió. Vuelve a registrar 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 o host de destino de otro modo colisionaría. Las líneas base se almacenan bajo 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

root@kitploit:~
En CI efímero, el directorio de estado comienza vacío, por lo que una ejecución no tiene nada con qué comparar y registra la línea base en lugar de verificarla. **Una ejecución que pidió fallar ante desviaciones y luego no verificó nada no pasa**, e indica qué directorio persistir. Ese es el único caso en el que registrar una línea base es un fallo. Sin `drift` en `--fail-on`, registrarla es algo habitual y no cambia ningún código de salida.

Por lo tanto, la línea base debe sobrevivir entre ejecuciones para que una compuerta de desviación signifique algo. Apunta `--baseline` a un directorio verificado o en caché, o establece `MCPSNOOP_HOME` a una ruta persistida.```
recorded first-seen tool baseline (trusted, not verified)
check failed: drift

Cómo funciona

El script main.py se encarga de todo el proceso de enumeración. A continuación se muestra un desglose de sus funciones clave:

  • get_arguments(): Analiza los argumentos de la línea de comandos para especificar el archivo de entrada, el archivo de salida y el nivel de verbosidad.
  • read_file(file_path): Lee el archivo de entrada que contiene los nombres de usuario o direcciones de correo electrónico.
  • check_email(email): Valida el formato de una dirección de correo electrónico.
  • check_username(username): Valida el formato de un nombre de usuario.
  • enumerate_user(username): Realiza la enumeración real del usuario consultando la API de GitHub.
  • write_results(results, output_file): Escribe los resultados de la enumeración en el archivo de salida especificado.
  • main(): La función principal que orquesta todo el flujo de trabajo.

Uso

Para usar la herramienta, ejecuta el siguiente comando:

root@kitploit:~
python3 main.py -i input.txt -o output.txt -v

Argumentos

  • -i, --input: Ruta al archivo de entrada que contiene los nombres de usuario o direcciones de correo electrónico (obligatorio).
  • -o, --output: Ruta al archivo de salida donde se guardarán los resultados (obligatorio).
  • -v, --verbose: Habilita la salida detallada (opcional).

Ejemplo

Supongamos que tienes un archivo llamado users.txt con el siguiente contenido:

root@kitploit:~
john.doe
jane.smith
[email protected]

Para enumerar estos usuarios, ejecuta:

root@kitploit:~
python3 main.py -i users.txt -o results.txt -v

La herramienta procesará cada entrada, consultará la API de GitHub y guardará los resultados en results.txt.```bash mcpsnoop check --fail-on drift --baseline .mcpsnoop/baselines session.jsonl

root@kitploit:~
`drift` es opcional para `check`. La puerta predeterminada `error,invalid,warn` no cambia.

### Detectar una característica que ninguna de las partes negoció

SEP-2133 movió las características opcionales fuera del protocolo principal y las trasladó a extensiones,
anunciadas en el mapa `extensions` de las capacidades de cada lado. Tasks es una de
ellas, así que el 2026-07-28 una `tasks/get`, una `notifications/tasks` o una `tools/call`
respondida con un identificador de tarea solo significa algo cuando el otro lado dijo que
habla Tasks.

Cuando no lo hizo, la especificación es explícita: la parte que la soporta DEBE o bien
volver al comportamiento principal o rechazar la solicitud. Hacerlo de todos modos es por lo que una característica
parece estar conectada y luego no hace nada en silencio, y lo que un lector obtiene
en su lugar es un `-32601` o un `-32021` varios fotogramas después, o una tarea que nunca
progresa. mcpsnoop advierte sobre el fotograma que alcanzó la extensión y nombra
qué lado nunca la anunció.```
tool "slow" answered with a task handle uses the io.modelcontextprotocol/tasks
extension, which the client never advertised

Es un warn, por lo que una ejecución de check por defecto falla con él. Permanece silencioso siempre que la captura no pueda mostrar lo que se negoció, lo cual ocurre en una captura que comienza después del handshake o una cuyas capacidades hayan sido depuradas por tu propia redacción, y en revisiones anteriores al 2026-07-28, donde tasks/* son protocolo central y usarlos es correcto.

Marcar características de protocolo obsoletas

La revisión del 2026-07-28 depreca 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 flujo, 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 de múltiples idas y vueltas, donde el nombre del método se encuentra dentro del mapa inputRequests del servidor en lugar de en el propio frame. Esos también se marcan, para que un servidor que haya migrado al nuevo patrón no deje de informar silenciosamente.```bash mcpsnoop check --fail-on deprecated session.jsonl

root@kitploit:~
Como `drift`, `deprecated` es opcional. Una ejecución por defecto informa el recuento y permanece
en verde, por lo que una sesión que use una característica obsoleta aún legal nunca pone CI en rojo por
sí sola.

### Construcciones de esquema de marca 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 realmente soportan, y una herramienta que el modelo sigue
invocando incorrectamente suele ser una herramienta cuyo esquema pidió más de lo que el cliente
entrega.

El resumen de herramientas, abierto con `s`, tiene una columna SCHEMA que nombra lo más notable
de cada esquema de herramienta anunciado, 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 al que la revisión se ajusta por defecto |
| `ext ref` | un `$ref` que apunta fuera del documento, que también es el caso en el que la especificación advierte a los implementadores que no 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 tipo ni ninguna otra forma de indicar 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 valide un listado rechaza esa herramienta por completo y nunca llega a ser
invocable, sin nada en el cable que explique por qué. `no root` encabeza la columna por
esa razón, y un esquema que la propia redacción de mcpsnoop haya borrado nunca se informa,
ya que un esquema ilegible no es uno incorrecto.

Esa división decide qué hace `check` con ellos. `no root` es una advertencia en el marco
`tools/list`, por lo que falla la puerta por defecto `error,invalid,warn` sin
ninguna marca, que es el punto: un servidor que envía una herramienta inutilizable responde
a cada protocolo de forma normal 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 por su forma sola, y el esquema al que apunta nunca se lee.

Reproducir una llamada capturada a través de HTTP

r vuelve a emitir una llamada capturada contra un servidor en vivo. Para una captura de stdio, el comando está en el registro, por lo que mcpsnoop lanza una copia aislada y envía la solicitud a esa copia. Una captura HTTP no tiene un comando que lanzar, y el endpoint que registra se despoja de su userinfo y de cada valor de consulta, por lo que nombra al servidor sin ser una dirección a la que marcar.

Así que tú indicas a dónde va una reproducción, y mcpsnoop nunca marca un endpoint de producción solo porque alguien pulsó una tecla.```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

root@kitploit:~
Sin `--replay-target`, una sesión HTTP lo dice explícitamente en lugar de ofrecer una clave que no puede funcionar. Con ella, `r` sigue preguntando antes del primer envío de una sesión, igual que se responde a un comando grabado antes de ejecutarlo.

Una credencial llega al servidor a través de `--replay-header` y de ningún otro sitio. mcpsnoop no registra ninguna cabecera `Authorization` ni reproduce ninguna, así que no hay nada capturado que una reproducción pueda filtrar.

El POST reproducido lleva lo que el transporte hace obligatorio, algo que un POST del cuerpo capturado desnudo no hace: `MCP-Protocol-Version`, un `Accept` que lista tanto `application/json` como `text/event-stream`, `Mcp-Method`, `Mcp-Name` donde la especificación lo exige, y cada `Mcp-Param-*` capturado. Esos se reenvían textualmente desde la captura, centinela base64 incluido, por lo que no pueden discrepar del cuerpo como podría hacerlo una rederivación. La única cabecera que no se copia es la versión del protocolo, porque el cuerpo reproducido declara la revisión que habla mcpsnoop y la cabecera debe coincidir con el cuerpo.

`Mcp-Name` se deriva del cuerpo que se envía en lugar de copiarse, porque la especificación lo toma de `params.name` o `params.uri` y exige que un servidor rechace una cabecera que discrepe del cuerpo, así que una edición que renombre la herramienta enviaría de otro modo el nombre antiguo. Las cabeceras `Mcp-Param-*` reflejan los argumentos capturados, por lo que una reproducción editada no envía ninguna de ellas en lugar de afirmar algo sobre un cuerpo que alguien reescribió. Una captura solo puede establecer cabeceras de esa única familia. Un registro es un archivo que la gente se pasa, y permitirle nombrar cualquier cabecera le permitiría sobrescribir las obligatorias o añadir una credencial que nadie pasó.

Un `Mcp-Param-*` que una regla de redacción haya depurado detiene la reproducción con un motivo. Enviar el marcador de posición pondría los propios bytes de mcpsnoop en un servidor en vivo como si un usuario los hubiera tecleado.

Una redirección se rechaza en lugar de seguirse. La dirección es la que nombraste y por la que respondiste, y seguir un 307 entregaría esa elección al extremo remoto, reenviando el cuerpo y, en un salto que solo cambia el puerto, también la credencial. mcpsnoop informa a dónde quería enviarlo el servidor y te deja decidir si nombrar esa dirección en su lugar.

Una respuesta que llega como un único objeto JSON y una que llega como un flujo de eventos se leen ambas, y un fallo se nombra en lugar de numerarse:

- un 401 informa del esquema que exigió el servidor
- un `-32020` informa de a qué puso objeciones
- un 400 o 404 no JSON-RPC dice que la dirección no es un endpoint HTTP Streamable
  de esta revisión

### Distinguir la latencia del servidor de la del usuario

En peticiones de múltiples idas y vueltas, una llamada de herramienta son varias peticiones, y los segundos que una persona dedicó a responder una elicitación quedan dentro del intervalo. Eso es deliberado, ya que ese intervalo suele ser el que más quieres ver, pero significa que un solo número no puede responder a ambas preguntas.

En una cadena `book_flight` donde el servidor trabajó 1,2 segundos mientras el usuario tardó 37, `check --max-duration 5s` culpa a la herramienta por 38,2 segundos. Sigue haciéndolo, porque cambiar lo que significa esa opción aflojaría cada canalización que ya la establece. Dos hermanos nombran lo que miden en su lugar.```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

Aquí tienes la traducción al español del contenido del chunk 65 de 89:


Nota: El texto original no fue proporcionado en tu mensaje. Para poder traducirlo, necesito que pegues el contenido del chunk 65. Sin el texto de entrada, no puedo generar la traducción solicitada. Por favor, comparte el Markdown que deseas traducir y lo haré de inmediato, respetando todas las reglas especificadas (preservar código, rutas, URLs, estructura Markdown, etc.).``` 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)

root@kitploit:~
Ambos están desactivados por defecto, por lo que una ejecución de `check` por defecto no se ve afectada, y ambos se leen de las marcas de tiempo de los frames y de un enlace que mcpsnoop ya había inferido, así que ninguno adivina intenciones.

Pulsa `i` en la TUI para ver el desglose, o lee `interactions` en las exportaciones json, text y html. Cada entrada es una operación lógica con su número de viajes de ida y vuelta, su total, la parte que el servidor mantuvo y la parte que esperó al cliente, más una línea por salto que nombra qué pidió cada respuesta. El resumen por herramienta gana una columna `TRIPS` para que una herramienta habladora sea visible sin abrir nada.

`export --format har` pone la parte del servidor en `wait` y el resto en `blocked`, que es para lo que sirve ese campo, así un visor deja de dibujar una espera de servidor de 38 segundos que nunca ocurrió.

Los contadores y las dos partes se acumulan a medida que llegan los frames en lugar de derivarse cuando lo pides, porque el almacén en vivo libera frames antiguos para mantenerse dentro de su presupuesto y una respuesta derivada sería silenciosamente una ventana en lugar de una cadena. El desglose por salto se lee de los frames aún retenidos, y lo indica cuando es solo parte de uno. `ServerTime + ClientTurnaround` equivale al total por construcción, no por una aritmética en la que alguien tenga que confiar.

`--max-round-trips` juzga una cadena que aún está en curso, porque cada petición que ya ha hecho es contable y un servidor que pregunta una y otra vez produce exactamente la operación que nadie termina jamás. `--max-server-duration` espera a un final, que es la regla que `--max-duration` ya aplica, ya que una operación aún abierta no tiene latencia que juzgar.

Una operación que mcpsnoop no pudo enlazar permanece como su propia entrada de un solo salto. `matchRetry` rechaza un enlace ambiguo a propósito, y esta vista no rellena ese hueco.

Una operación que tomó una sola petición no lleva desglose de saltos, porque un único salto repite palabra por palabra los totales de arriba. Una cadena informa de un salto por petición, y lo indica cuando el almacén ya no retiene cada frame o cuando el trabajo se resolvió fuera del par petición-respuesta del que se compone un salto, como hace un identificador de tarea.

### Ver qué pidió un servidor a tu usuario

La elicitación es el único camino en MCP donde una persona escribe datos en un servidor, y bajo MRTR la pregunta y la respuesta ya no son dos mitades de un mismo intercambio. La pregunta está enterrada en un `InputRequiredResult`, la respuesta vuelve dentro de `inputResponses` en un reintento bajo un id diferente, y lo único que las une es el enlace que mcpsnoop ya infiere.

Sin ese emparejamiento, una solicitud de contraseña rechazada se lee como un simple error de herramienta.```
tools/call login_legacy [form] creds: decline after 3s
  password string

Pulsa l en la TUI, o lee elicitations en las exportaciones json, text y html. Cada fila nombra la operación que la pregunta interrumpió, el modo, el mensaje, qué se pidió, qué hizo el usuario y cuánto tiempo tardó. Una pregunta que ningún reintento respondió jamás aparece como pendiente, algo que MRTR convierte en un resultado ordinario en lugar de un error, ya que la especificación indica a los servidores que no deben asumir que un cliente reintentará en absoluto.

Las filas de formularios listan los nombres de las propiedades de requestedSchema y sus tipos declarados. Una propiedad cuyo subschema fue reemplazado por una regla de redacción muestra un tipo desconocido en lugar del placeholder, porque un placeholder no es algo que el servidor haya declarado. Las filas de URL llevan la dirección completa, que la especificación hace que un cliente muestre antes del consentimiento, y nombran el host por separado, que dice resaltar frente al spoofing de subdominios.

El ledger nunca lleva un valor enviado. Lo que el usuario escribió permanece en la captura para quien lo necesite, y dejarlo fuera de una superficie de resumen diseñada para exportarse y pegarse es lo que mantiene esto completamente fuera de la historia de redacción. Importa sobre todo en el modo url, donde la especificación coloca las credenciales a propósito.

Un reintento responde a la ronda desde la que se emitió y a ninguna otra. MRTR indica a un servidor que cuando un cliente omite parte de lo que se pidió, debe volver a preguntar en una nueva ronda, por lo que una ronda anterior que contenga una clave sin responder junto a una respondida es tráfico ordinario, y la mitad sin responder permanece pendiente en lugar de tomar prestada la respuesta de la ronda posterior.

Una pregunta registrada está acotada. El mensaje, la url y la lista de campos se mantienen durante la vida de la sesión, fuera del presupuesto de frames que libera los cuerpos, de modo que un servidor no puede hacer que uno sea arbitrariamente costoso. Los límites están muy por encima de cualquier pregunta real y un mensaje truncado indica que fue truncado.

Nada aquí advierte y nada aquí cambia un código de salida de check. Un ledger registra lo que ocurrió. No lo juzga.

Encuentra la herramienta que falla una de cada cuatro ejecuciones

check lee una sesión y diff lee exactamente dos, por lo que una herramienta que falla ocasionalmente permanece invisible hasta que alguien abre las capturas a mano. En dieciséis capturas de un servidor cuyo run_query responde isError aproximadamente una cuarta parte de las veces, check informa de la más reciente, honestamente, como limpia.```bash mcpsnoop stats mcpsnoop stats --since 7d --label prod mcpsnoop stats --limit 20 --format json

root@kitploit:~
Aquí tienes la traducción al español del contenido del chunk 71:

---

**Nota:** El texto original no fue proporcionado en tu mensaje. Para poder traducirlo, necesito que pegues el contenido del chunk 71 que deseas traducir. Sin el texto de entrada, no puedo generar la traducción solicitada. Por favor, incluye el Markdown original y lo traduciré de inmediato siguiendo todas las reglas especificadas.```
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 y PROTO son columnas separadas porque la especificación las trata como cosas separadas. Una herramienta que responde isError está informando algo sobre lo que un modelo puede actuar y reintentar. Un error JSON-RPC significa que la solicitud o el servidor están mal. SESS es el recuento de sesiones que vieron un fallo sobre las sesiones que llamaron a la herramienta, que es la pregunta de "una ejecución de cada diez" que una tasa sobre llamadas no puede responder.

Las filas se identifican por el servidor y la etiqueta juntos. El servidor es el comando registrado y el directorio de trabajo para stdio y el endpoint para HTTP, la misma identidad que usa inventory. Cualquiera de las dos mitades por separado agrupa algo que no debería: la etiqueta sola fusiona dos servidores que derivan de un mismo nombre, lo que ocurre siempre que dos copias de un proyecto ejecutan el mismo punto de entrada, y la identidad sola fusiona un comando ejecutado deliberadamente como prod y de nuevo como staging. Ambos errores mezclan dos distribuciones limpias en una que no describe ninguna de las dos.

Cuando dos filas comparten una etiqueta, la celda SERVER lleva el directorio de trabajo o endpoint que las distingue, y el JSON lleva command, cwd y endpoint en cada fila. Un nombre que nunca fue ambiguo se deja tal cual, así que la tabla ordinaria no cambia.

Cada sesión en un registro se pliega, no solo la primera, por lo que un archivo creado concatenando capturas cuenta todas ellas.

Los percentiles se agrupan sobre las duraciones brutas. Una mediana de medianas es una mediana de nada. Una operación con múltiples idas y vueltas es una llamada con una duración, sin importar cuántas solicitudes haya requerido, y una llamada aún abierta cuenta hacia CALLS sin contribuir latencia.

Una captura reside a la vez. Un registro se carga, se pliega en los contadores en curso y se descarta antes de que se abra el siguiente, por lo que un directorio con cientos cuesta la captura individual más grande en lugar de su suma.

--limit tiene como valor predeterminado un centenar de los registros más recientes y el encabezado indica cuántos de cuántos se leyeron, por lo que una respuesta acotada nunca pasa por una completa. stats informa y no bloquea: no escribe nada, no toca ninguna línea base, no abre ningún socket y sale con 0 siempre que el recorrido tenga éxito.

Ver qué servidores han ejecutado realmente aquí

La conclusión que la gente repite sobre Shadow MCP es que las organizaciones descubren varias veces más servidores MCP en ejecución de los que nadie aprobó, porque un servidor suele ser solo una dependencia que alguien añadió a un plugin de IDE. Lo mismo ocurre en miniatura en un solo portátil, y mcpsnoop ha estado registrando la respuesta todo el tiempo sin mostrarla nunca.```bash mcpsnoop inventory mcpsnoop inventory --tools # also count what each server last advertised mcpsnoop inventory --format json # for something else to read

root@kitploit:~
Una fila por servidor en lugar de por sesión. La clave de la fila es el comando registrado
y el directorio de trabajo, nunca la etiqueta, porque la etiqueta proviene del
último elemento de ruta del comando y tanto `node ~/one/build/index.js` como
`node ~/two/build/index.js` derivan `index.js`. Una sesión HTTP se identifica por el
endpoint que proxy, ya que mcpsnoop no lanzó nada allí.

La lectura es un sobre por registro, el marco de metadatos que el proxy escribe primero, por lo que
sigue siendo barata sobre un directorio de capturas grandes. `--tools` es la excepción y
lee un registro por servidor, la ejecución más reciente de cada uno, por lo que es una bandera
en lugar de una columna. Incluso entonces la lectura está acotada, porque un inventario de
herramientas es estado de sesión que la tienda incorpora sobre la marcha, de modo que una captura
de cien megabytes se lee a través de una ventana fija en lugar de mantenerse completa para producir un entero.

Cuando no hay recuento, la fila indica cuál de tres cosas ocurrió, porque un
registro que no pudo leerse no es un servidor que no anunció nada, y una sola
frase para ambos haría que mcpsnoop afirmara algo falso.

Un comando que una regla de `--redact` reescribió se imprime tal como se registró y se marca,
en lugar de presentarse como el comando que se ejecutó. Dos ejecuciones de un servidor, una
depurada y otra no, son dos filas. mcpsnoop no puede saber qué reemplazó el marcador de posición,
y fusionarlas implicaría adivinar que las mitades ocultas coincidían. Un servidor ejecutado bajo
dos valores de `--label` es una fila que lleva ambos nombres, ya que la clave es el
comando en lugar del nombre.

Nada en una fila lo escribe mcpsnoop. Un comando proviene de quien instaló el
servidor, un directorio de trabajo sale del sistema de archivos y una etiqueta derivada
proviene del comando. Un valor que contiene un carácter de control se cita en lugar de
imprimirse en bruto, de modo que un directorio cuyo nombre contiene un salto de línea no puede cerrar el
campo en el que se imprime y hacer que las líneas siguientes se lean como servidores que nunca
se ejecutaron. Un argumento que contiene un espacio también se cita, porque `node "~/My Project/
build/index.js"` de otro modo es indistinguible de dos argumentos.

Cualquier cosa que el recorrido no pudiera incorporar se nombra en el encabezado en lugar de descartarse.
Los registros vacíos se cuentan aparte de los dañados, ya que un registro de cero bytes es el
residuo ordinario de una ejecución cuyo exec falló o de un proxy HTTP al que nadie llamó.

La salida se ordena por nombre en lugar de por actualidad, de modo que dos ejecuciones sobre un directorio
producen los mismos bytes, que es lo que la hace utilizable como línea base para comparar
más adelante.

Hay dos vacíos por construcción, no por descuido. Una ejecución con
`--trace-file` escribió fuera del directorio de sesiones y no aparecerá, y
`prune` elimina registros, por lo que la primera vez vista solo es tan antigua como lo que aún está en
disco. mcpsnoop informa lo que se ejecutó en esta máquina a través de él. No escanea ninguna red,
no lee ninguna configuración de cliente a la que no se le haya apuntado, y no juzga nada.

### Distinguir un servidor roto de una herramienta que dice que no

Una herramienta que responde `result.isError` está funcionando. Miró y no encontró nada, o
rechazó la entrada. Un servidor que responde con un error JSON-RPC está roto. Ambos eran un
número en el resumen de herramientas, lo que significaba que una herramienta bien comportada que informa fallos de dominio
parecía exactamente un servidor roto, y se ordenaba por encima de uno.

La columna `ERR` los separa. El rojo es el lado del servidor, que es un error
JSON-RPC o una tarea que terminó fallida sin decir por qué. El color de advertencia es el
propio `isError` de la herramienta. Una herramienta con ambos muestra los recuentos unidos, rojo primero, y una
línea bajo la tabla nombra los dos totales siempre que haya un número de advertencia que
explicar. La exportación lleva la misma división como `protocol_errors` y
`tool_errors` junto al total de `errors` al que siempre suman.

`check --fail-on error` no cambia y sigue disparándose con cualquiera de los dos, ya que una
puerta que ignorara uno de ellos sería una puerta que un servidor podría desactivar devolviendo
el otro.```bash
mcpsnoop export -T json | jq '.summary.tools[] | {name, errors, protocol_errors, tool_errors}'

Vea cuánto le 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 desde la sesión que realmente capturó.

La línea definitions es el costo fijo: cuánto pesa el tools/list de este servidor antes de que se realice una sola llamada. La columna DEF desglosa eso 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 revise DEF para encontrar las definiciones costosas. La exportación las lista de mayor a menor peso. Una línea debajo de la tabla nombra el resultado individual más pesado, algo que un total oculta.

Las cifras de definición son el JSON sin espacios en blanco insignificantes, de modo que un servidor que imprime su tools/list con formato bonito no se cuenta como más costoso que uno que no lo hace, y el mismo servidor mide lo mismo entre capturas. RESULT son los bytes tal como llegaron: un resultado es una carga útil de una sola vez, no un contrato que valga la pena normalizar.```bash mcpsnoop export -T json | jq '.summary.definitions'

root@kitploit:~
La exportación incluye las mismas cifras, por herramienta y divididas en bytes de descripción y de esquema, de modo que una descripción voluminosa y un esquema voluminoso siguen siendo separables y cualquiera de ellos puede rastrearse entre capturas. `mcpsnoop diff` te indica si una descripción o un esquema cambió entre dos sesiones. La exportación es donde reside el tamaño de ese cambio.

**Estos son bytes, no tokens.** Un 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. Una `tools/list` inconclusa informa lo que vio como un mínimo y lo dice, en lugar de presentar una suma parcial como si fuera 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 manipule puede intentar alterar el comportamiento del servidor o eludir una comprobación de autorización.

Situado en el canal, 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 reportada 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` por defecto falla ante una de ellas**. 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 a ninguna clave, 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.

Un intercambio abandonado no perturba el siguiente, ni se conserva para siempre. Sesenta y cuatro intercambios abiertos son muchos más de los que cualquier cliente tiene a la vez, así que una sesión que mantiene más está manteniendo algunos que nadie terminará, y los más antiguos se retiran porque la especificación indica a los servidores que den a ese estado una caducidad corta y lo rechacen después. La retirada se cuenta en lugar de hacerse en silencio. El pie del flujo muestra `N unlinked` y la exportación lleva `session.retired_exchanges`, porque un reintento que sí llega para una operación retirada se lee como su propia llamada, y un lector que compara recuentos merece que se le informe.

Retirar una también permite que el almacén en vivo la libere. Una operación en pausa permanece pendiente a propósito, de modo que su duración abarca todo el intercambio, y el almacén se niega a olvidar una llamada pendiente porque aún puede llegar una respuesta. Una vez que el límite ha retirado una operación, nada puede responderla, así que mantenerla conserva una llamada viva a la que ningún lector puede acceder. Lo que informa la sesión no cambia. Sigue contada como pendiente y sigue contada en `N unlinked`, porque cuánta memoria ocupa un registro y qué dice el registro son preguntas distintas.

Un intercambio abandonado no perturba el siguiente. MRTR indica a los servidores que no deben asumir que un cliente reintentará jamás, así que un usuario que rechaza una elicitación deja una operación que ningún marco posterior resolverá jamás. mcpsnoop busca primero entre las operaciones cuya presencia de `requestState` coincide con la del reintento, lo que la especificación convierte en regla en ambas direcciones, de modo que un reintento conforme encuentra la única operación que continúa incluso cuando un intercambio abandonado en la misma herramienta está a su lado. La comprobación que informa las tres violaciones anteriores solo se ejecuta cuando nada coincide, así que un reintento genuinamente no conforme sigue siendo señalado.

## 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 por SSH, así que ambos extremos deben ejecutar Linux o macOS. En Windows, usa la copia del registro post-mortem que aparece 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 bajo el directorio de estado del remoto, resuelto como MCPSNOOP_HOME, si no, XDG_STATE_HOME/mcpsnoop, si no, ~/.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 otro lugar, indica la única pieza que no es la predeterminada.```bash

a non-Linux or custom home, macOS is /Users/ 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

root@kitploit:~
### Post-mortem

Transmite una sesión remota directamente al 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 mantener una copia local en su lugar, copie los registros mediante scp a su directorio de sesiones y ejecute 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

root@kitploit:~
## Seguridad

mcpsnoop ejecuta el comando del servidor que envuelves, así que envuelve únicamente 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 tu configuración de cliente.

Para flujos de trabajo remotos, usa túneles SSH o transferencia de archivos por SSH para que la autenticación del transporte, el cifrado, la verificación del host, la rotación de claves y la política de auditoría permanezcan en tu configuración SSH existente.

### Redacción de lo que capturas

Los frames capturados pueden incluir prompts, argumentos de herramientas, credenciales y resultados de herramientas. Si los payloads pueden contener secretos, opta por la redacción para limpiar las copias de la traza observada mientras los bytes proxyados siguen pasando sin cambios.

La redacción basada en claves reemplaza valores completos bajo claves JSON de objetos coincidentes, y el mismo conjunto de claves se aplica con el mejor esfuerzo a los argumentos de línea de comandos del servidor envuelto, de modo que `--api-key=sk-x` y `--token sk-x` se limpian con `--redact-secrets`. Un argumento que lleva un secreto sin un nombre de flag reconocible no se puede detectar.

El endpoint HTTP no forma parte de todo eso, porque no es un payload que hayas elegido enviar. `--target` es un flag que tienes que pasar para ejecutar el proxy en absoluto, por lo que su URL llegaría al registro de sesión independientemente de tu configuración de redacción. mcpsnoop lo registra con el userinfo, cada valor de query y el fragment ya eliminados, siempre, por construcción y no por patrón. Las claves de query sobreviven, ya que son lo que distingue dos endpoints de un mismo host, y el fragment se descarta porque nunca llegó al servidor para empezar. Lo que se registra identifica al servidor y no es una dirección para marcar.

La redacción basada en rutas reemplaza solo los valores seleccionados por una expresión JSONPath, lo cual es útil cuando un nombre de clave común es sensible en una ubicación pero seguro en otra. Repite `--redact-path` para limpiar más de una ubicación.

La redacción basada en valores aplica expresiones regulares a los valores de cadena observados, al texto de stderr y a los frames de texto que no son JSON.

Las tres son de mejor esfuerzo. Las regex pueden omitir secretos, coincidir en exceso con texto inofensivo o no detectar valores transformados o codificados.

La redacción nunca se convierte en una acusación. Cada comprobación que compara una cosa observada con otra, un encabezado de enrutamiento contra el cuerpo, un valor de `Mcp-Param` contra el argumento que refleja, el esquema de una herramienta contra lo que la revisión exige de uno, sabe cuándo mcpsnoop fue el lado que reescribió los bytes y permanece en silencio en lugar de informar sobre un servidor por la propia configuración de privacidad del usuario. La deriva de definición de herramientas es la excepción, y deliberadamente, ya que activar la redacción cambia lo que se registra y, por tanto, lo que contiene una línea base. Consulta [Detectar deriva de definición de herramientas](#detect-tool-definition-drift).```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+'

Contribución

Las incidencias y las solicitudes de extracción son bienvenidas. Consulta CONTRIBUTING.md para obtener los detalles.

Licencia

MIT

Descargar herramienta