MCP Inspector
Una herramienta para desarrolladores para probar y depurar servidores MCP.
Documentación
MCP Inspector
Una herramienta de desarrollo para inspeccionar servidores Model Context Protocol (MCP). Se distribuye como un único paquete, @modelcontextprotocol/inspector, que ofrece tres formas de inspeccionar un servidor:
- Web — una aplicación de una sola página Vite + React + Mantine con un backend Node.
- CLI — un cliente de línea de comandos programable para automatización, CI y bucles de retroalimentación rápidos para agentes.
- TUI — una interfaz de terminal interactiva construida con Ink.
Los tres se ejecutan a través de un único binario global mcp-inspector:
npx @modelcontextprotocol/inspector # web UI (default)
npx @modelcontextprotocol/inspector --cli # CLI
npx @modelcontextprotocol/inspector --tui # TUI
¿Actualizando desde v1? Lee la guía de migración v1 → v2 — banderas de CLI, la nueva división
--configvs.--catalog, el salto de versión de Node y lo que ya no se incluye.
Estado del repositorio. Esta es la línea v2 del Inspector. El desarrollo activo ocurre en
v2/main(la rama develop — todos los PRs de v2 apuntan a ella), que se fusiona enmainen los lanzamientos de hitos;maines la rama por defecto y contiene la última v2 publicada, publicada en la etiqueta npmlatest. La línea legada v1 vive env1/main— solo correcciones de seguridad, publicada directamente desde esa rama a la etiqueta npmv1-latest(npx @modelcontextprotocol/inspector@v1-latest). ConsultaAGENTS.mdpara conocer las convenciones de ramas/tableros.
Estructura del proyecto
v2 no es un workspace de npm. Cada cliente bajo clients/* mantiene su propio package.json y node_modules; el código compartido vive en core/ y se consume a través de un alias de compilación @inspector/core (sin package.json propio). Un único npm install en la raíz propaga las instalaciones a cada cliente (consulta Setup).
inspector/
├── clients/
│ ├── web/ # Web client (Vite + React + Mantine). src/ = browser app; server/ = Node dev/prod backend
│ ├── cli/ # CLI client (tsup bundle, @inspector/core alias)
│ ├── tui/ # TUI client (Ink + React, tsup bundle)
│ └── launcher/ # Shared launcher — provides the `mcp-inspector` bin, dispatches to web/cli/tui
├── core/ # Shared code consumed via the `@inspector/core` alias (no package.json)
│ ├── auth/ # OAuth: providers, discovery, storage, mid-session recovery (browser/node/remote backends)
│ ├── client/ # Install-level client config (`client.json`): browser-safe parse/validate + Node load/save, remote backend, secrets
│ ├── json/ # JSON + parameter/argument conversion utilities
│ ├── logging/ # Silent pino logger singleton
│ ├── mcp/ # InspectorClient runtime, state stores, transports, config import
│ ├── node/ # Node-only shared helpers: version reader, hostUrl (host normalize/canonicalize + all-interfaces/loopback detection)
│ ├── react/ # React hooks over the state stores
│ └── storage/ # File I/O helpers for the OAuth persist backends
├── test-servers/ # Composable MCP test servers + fixtures used by integration tests
├── scripts/ # Root build/verify tooling (install cascade, smokes, verify-build-gate, verify-format-coverage, verify-dep-lockstep, pack:verify)
├── docs/ # Task-oriented guides (v1→v2 migration, server configuration, MCP App review, launcher/config plan)
├── specification/ # Design/build specifications
├── AGENTS.md # Contribution rules for agents AND humans (see below)
└── README.md # You are here
Cada cliente tiene su propio README con detalles específicos del cliente: web · cli · tui · launcher.
Las guías orientadas a tareas viven bajo docs/:
- Migrando de v1 a v2 — el mapa v1 → v2: mapeo de banderas de CLI, semántica de
--configvs.--catalogcon ejemplos de antes/después, el salto de versión de Node (>=22.7.5→>=22.19.0), renombramientos de variables de entorno y los sub-paquetes que ya no se incluyen. - Configuración del servidor MCP — a qué servidor(es) se conecta el Inspector:
--catalogvs.--config, destinos ad-hoc, el separador--, el formato de archivo y sus campos específicos del Inspector por servidor. Compartido por los tres clientes; los README de cli y tui delegan sus secciones de opciones de servidor a esta guía. - Revisando una App MCP — la receta CLI-primero → web de una sola vez para la revisión automatizada de App-tools: sonda
--app-info→ navegación por enlace profundo → widget renderizado, además de traspaso OAuth y soporte de proxy. - Consolidación del launcher y la configuración — por qué el launcher ejecuta un cliente en proceso en lugar de lanzarlo, y cómo encaja el procesador de configuración compartido.
Configuración
Requiere Node >=22.19.0.
npm install # root install; postinstall cascades into every client
- Clon nuevo: ejecuta
npm installen la raíz del repositorio. - Después de un pull que cambie las dependencias de un cliente: vuelve a ejecutar
npm installen la raíz para re-sincronizar cada cliente.
La cascada (scripts/install-clients.mjs) es solo para desarrollo — sale anticipadamente cuando el paquete se instala como dependencia, y el tarball publicado incluye solo el build/ de cada cliente, por lo que los usuarios finales no se ven afectados. Establece INSPECTOR_SKIP_CLIENT_INSTALL=1 para omitirla.
Dónde se declara una dependencia. Los paquetes del SDK de MCP (@modelcontextprotocol/client, core, server, server-legacy, ext-apps) viven solo en el package.json raíz — nunca en el de un cliente. La resolución de Node asciende, por lo que la instalación raíz está en la cadena de cada cliente, y el manifiesto raíz ya es contra lo que resuelve el tarball publicado. Declararlas por cliente instala una segunda copia que puede desviarse de la raíz, que es como dos versiones de ext-apps (y de la @modelcontextprotocol/sdk v1 transitiva) terminaron en el árbol antes de #1970 — y una segunda copia de client/core es el fallo para el que vitest.shared.mts lleva un workaround de dedupe. La misma colocación solo-en-raíz se aplica a cualquier cosa alcanzada únicamente a través de código propiedad de la raíz sin manifiesto propio (test-servers/src, core/), y vitest.shared.mts los aliasa a la raíz del repositorio — express y yaml, ambos alcanzados a través de test-servers/src, son los dos actuales. Si tal paquete es un dependency o un devDependency se deduce de quién lo consume en tiempo de ejecución, no de dónde se declara: cualquier cosa que core/ importe en tiempo de ejecución debe ser un dependency raíz, porque los clientes externalizan los paquetes npm al compilar y una instalación publicada los resuelve desde el manifiesto raíz, donde las devDependencies están ausentes. express es solo para pruebas y es una devDependency; yaml actualmente reside en dependencies.
Ejecución durante el desarrollo
Para la iteración web diaria, ejecuta Vite directamente desde el cliente web (HMR rápido, sin necesidad de compilar el launcher):
cd clients/web && npm run dev
Los scripts controlados por el launcher a continuación ejecutan el launcher compilado, así que compila primero (npm run build):
npm run web # prod web launcher against clients/web/dist
npm run web:dev # web launcher in --dev mode (Vite)
El paquete compartido @inspector/core

core/ contiene la lógica compartida por los tres clientes para que web, CLI y TUI se comporten de manera idéntica. Su punto de entrada es la clase InspectorClient (core/mcp/), que posee la conexión a un servidor MCP, el ciclo de vida de solicitud/respuesta y un conjunto de almacenes de estado; core/react/ expone hooks de React sobre esos almacenes que tanto el árbol de React web como el de TUI (Ink) consumen. OAuth (core/auth/) está factorizado en lógica isomórfica más backends de navegador/node/remoto para que los mismos flujos funcionen en el navegador, en Node y contra un backend remoto.
core/ intencionalmente no tiene package.json — no se publica por sí solo. Cada cliente lo agrupa a través de un alias @inspector/core:
- CLI / TUI:
esbuildOptions.aliasen sustsup.config.tsmapea@inspector/core→ el directoriocore/del repositorio, ynoExternal: [/^@inspector\/core/]lo inserta en el bundle. - Web: el mismo alias en
clients/web/vite.config.tspara la aplicación de navegador y el ejecutor de backend Node.
Publicar core/ como paquete propio (p. ej. para que terceros construyan sobre él) está deliberadamente diferido — consulta el issue #1636.
Cliente web: "componentes tontos" + Storybook
El cliente web v2 está construido a partir de componentes presentacionales ("tontos") — aceptan datos y callbacks como props y contienen solo lógica de visualización, sin obtención de datos directa ni estado de cliente. El estado proviene de los hooks @inspector/core, conectados cerca de la parte superior del árbol. Esto mantiene los componentes aislados, comprobables y documentables.
Ese enfoque es lo que hace que Storybook sea de primera clase aquí: cada componente de pantalla y elemento tiene un archivo *.stories.tsx (más de 96 historias) que lo renderiza contra props de fixture. Las play functions de Storybook funcionan como pruebas de interacción, ejecutadas sin cabeza en CI (npm run ci:storybook, Chromium vía Playwright).
El estilo sigue una convención estricta primero-Mantine (variantes de tema y props de componentes sobre clases CSS, propiedades personalizadas CSS de --inspector-* sobre literales de color crudos). Las reglas completas viven en AGENTS.md bajo React instructions — léelas antes de tocar la UI web. Los componentes de elemento viven en clients/web/src/components/elements/; las variantes de tema en clients/web/src/theme/.
Servidores de prueba
test-servers/ proporciona servidores MCP componibles utilizados por las suites de integración y smoke, de modo que las pruebas ejercitan un servidor real sobre un transporte real en lugar de mocks. Un servidor se ensambla a partir de presets (fábricas de fixtures en test-servers/src/preset-registry.ts — tools, resources, prompts, tasks, elicitation, sampling, OAuth, …) y se puede controlar de dos maneras:
- En proceso — importa las fábricas (
createTestServerHttp,createEchoTool, …) y ejecuta el servidor dentro del bucle de eventos de la prueba (utilizado por las rutas de integración HTTP). - Como subproceso —
test-servers/build/test-server-stdio.jsse lanza como un hijo stdio real (utilizado por las pruebas smoke de CLI y las de integración stdio).
Configura un servidor declarativamente con un config JSON (consulta test-servers/configs/*.json) seleccionando presets, luego cárgalo vía --config. Dado que los servidores se lanzan como subprocesos reales, la salida de compilación debe existir primero:
npm run test-servers:build # (from clients/web) → tsc -p test-servers, emits test-servers/build/
El alias de Vite @modelcontextprotocol/inspector-test-server (en clients/web/vite.config.ts) apunta a test-servers/build/index.js para que getTestMcpServerPath() resuelva a una ruta .js real.
Sirviendo la era del protocolo moderno
Un servidor HTTP streamable también puede servir la era del protocolo moderna (2026-07-28) a través del createMcpHandler del SDK:
- Establece
transport.modernen el config JSON —truepara servicio stateless de doble era, o{ "legacy": "reject" }para estricto solo-moderno. - O pasa
modernen elServerConfigpara uncreateTestServerHttpen proceso.
Esto es lo que permite que una conexión del Inspector que negocia protocolEra: "auto" | "modern" alcance la rama moderna (server/discover poblado, sin sesión). Consulta test-servers/configs/modern-http.json.
Configs de demostración
Cada config a continuación es un servidor listo para ejercitar una característica manualmente. Carga uno con --config, y salvo que se indique, conéctate con Protocol Era = Modern.
| Config | Demuestra | Issue |
|---|---|---|
mcp-app-http.json (era legacy) | Una App MCP (recurso de UI + app tool) en la pestaña Apps | #1859 |
modern-mrtr-http.json | Un solo round-trip de MRTR | — |
mrtr-showcase-http.json | Cada preset de MRTR en un servidor | — |
modern-network-http.json | Pestaña Network: encabezados Mcp-* + taxonomía de errores | #1628 |
xmcpheader-modern-http.json | Pestaña Tools: espejado y exclusiones de x-mcp-header | #1632 |
pagination-http.json | Obtención de listas página por página | #1721 |
structured-output-http.json | Pestaña Tools: la sección structuredContent de un resultado | #1908 |
duplicate-tool-names-http.json | Un tools/list que repite un nombre de tool | #1957 |
advertised-extensions-http.json | Registro de tools condicionado a extensiones anunciadas | #1739 |
logging-{legacy,modern}-http.json | Registro, ambas eras | #1629 |
subscriptions-{legacy,modern}-http.json | Suscripciones de recursos, ambas eras | #1630 |
tasks-{legacy,modern}-http.json | Tasks, ambas eras | #1631 |
Apps MCP
mcp-app-http.json sirve el tool mcp_app_demo (_meta.ui.resourceUri) junto con su recurso de UI mcp_app_demo_widget, de modo que la pestaña Apps tiene una App real para renderizar. Es un servidor HTTP streamable normal — conéctate con la era de protocolo predeterminada (legacy), no Modern.
Abre la pestaña Apps, selecciona mcp_app_demo, dale un título y haz clic en Open App: el widget se renderiza dentro del iframe del sandbox y ejercita la superficie del protocolo de UI del lado del host — renderizado de contexto de host, size-changed, ui/message y una línea de log en el panel App logs. Dado que el widget se sirve a través de la página proxy del sandbox, este config también es lo que reproduce #1859 (un clients/web/static/sandbox_proxy.html faltante aparece aquí como un mensaje "Sandbox not loaded" en lugar del widget) — un fallo que solo apareció en un paquete instalado, nunca en el repositorio.
Para la versión scriptada del mismo flujo (--app-info probe → deep link → widget renderizado), consulta Revisando una aplicación MCP.
MRTR
modern-mrtr-http.json sirve la herramienta mrtr_confirm (ajuste predefinido mrtr_confirm, createMrtrTool) sobre la vía moderna. Su manejador devuelve inputRequired(...) que incrusta una elicitación de formulario, por lo que invocarla produce un viaje de ida y vuelta real: input_required → el cliente cumple la elicitación incrustada y reintenta con un nuevo id → complete.
El Inspector impulsa MRTR manualmente (inputRequired: { autoFulfill: false }), por lo que la elicitación incrustada se detiene en el modal de solicitud pendiente (etiquetado "input_required") para que respondas, y luego el reintento se completa. Es útil para observar tanto esa experiencia de solicitud pendiente como la agrupación de conversaciones MRTR en la vista Protocol.
mrtr-showcase-http.json agrupa todos los ajustes predefinidos de MRTR en un solo servidor:
| Ajuste | Comportamiento |
|---|---|
mrtr_confirm | Una sola ronda |
mrtr_two_step | Dos rondas de elicitación vía requestState |
mrtr_sample | Muestreo incrustado → panel de Sampling |
mrtr_roots | roots/list incrustado, respondido automáticamente desde las raíces configuradas (sin modal) |
mrtr_edge | Una ronda solo de inputRequests, luego una ronda solo de requestState |
mrtr_loop | Nunca se completa → activa el límite MRTR_MAX_ROUNDS |
El ajuste predefinido
collect_elicitationde la vía heredada llama aserver.elicitInput, que genera un error en la vía del 2026-07-28: las solicitudes de servidor a cliente no están permitidas allí. MRTR es el reemplazo moderno.
Pestaña Network: encabezados estandarizados y taxonomía de errores
modern-network-http.json cubre SEP-2243 / SEP-2575. Sirve una herramienta get_weather cuyo argumento city lleva una anotación x-mcp-header: "City", por lo que un cliente moderno la refleja en Mcp-Param-City.
También sirve cuatro herramientas trigger_* que el inyector de errores de especificación de la vía moderna (transport.modern.injectSpecErrors: true) responde con un estado HTTP real más un cuerpo de error JSON-RPC:
| Herramienta | Respuesta |
|---|---|
trigger_header_mismatch | 400 / -32020 |
trigger_missing_capability | 400 / -32021 |
trigger_unsupported_version | 400 / -32022 (con data.supported) |
trigger_method_not_found | 404 / -32601 |
Abre la pestaña Network para ver los encabezados Mcp-* reflejados destacados, los valores centinela decodificados y cada error representado de manera distinta.
El reflejo de
Mcp-Param-*lo construye el Inspector, no el SDK. El SDK solo refleja dentro declient.callTool(), y lo omite en el navegador (detectProbeEnvironment() !== "browser"). El Inspector enrutatools/calla través declient.request()para impulsar MRTR manualmente, por lo que construye los encabezados reflejados por sí mismo (#1846) — en cada cliente, incluido el web, ya que la solicitud ascendente del cliente web la emite el backend Node en lugar del navegador. Así queget_weatherse puede invocar desde web, CLI y TUI por igual, tanto en la forma simple como en la forma "Ejecutar como tarea".
x-mcp-header en la pestaña Tools
xmcpheader-modern-http.json sirve:
echo— herramienta simple.get_weather— una anotación válidax-mcp-header: "City"en su argumentocity.invalid_header_tool— una anotación que usa el nombre de encabezado"Bad Header". El espacio lo convierte en un token RFC 9110 no válido, por lo que la definición completa de la herramienta es no válida.trigger_invalid_params— respondido con un error real-32602 Invalid paramscuyo mensaje no trata sobre una herramienta faltante.
Abre la pestaña Tools: el panel de detalles de get_weather muestra una sección "Mirrored request headers (SEP-2243)" (city → Mcp-Param-City), y invalid_header_tool aparece tachado bajo un divisor "Excluded (SEP-2243)" con el motivo al pasar el cursor. Un cliente conforming Streamable HTTP DEBE descartarlo de tools/list; el Inspector muestra el porqué.
Con el SDK v2, una tools/call que se rechaza con -32602 se representa como un panel de error distinto en lugar de un resultado isError — con el encabezado "Unknown Tool" cuando el mensaje nombra una herramienta faltante, o "Invalid Parameters" en caso contrario (ejecuta trigger_invalid_params).
Obtención página por página
pagination-http.json sirve 12 herramientas, 12 recursos y 12 prompts (preajustes numbered_tools / numbered_resources / numbered_prompts, count: 12) con un maxPageSize de 4 cada uno, por lo que cada lista se pagina en tres páginas.
Activa "Fetch Lists One Page at a Time" (Configuración del servidor — la opción paginatedLists, o el interruptor Paginated en la barra lateral de listas) y las listas se cargan solo en la página 1 (4 elementos) con un control Load next page y un estado de N páginas cargadas. Cada clic obtiene los siguientes 4 y los agrega; Refresh restablece a la página 1. Con el interruptor desactivado (el valor predeterminado), las mismas listas agregan automáticamente las tres páginas al conectarse.
Salida estructurada
structured-output-http.json sirve list_items (structuredContent anidado — objetos dentro de matrices dentro de un objeto, la forma de #1908), get_temp (una carga útil plana de tres claves) y echo (sin outputSchema en absoluto). Es un servidor streamable-HTTP simple — conéctate con la era de protocolo predeterminada (heredada).
Ejecuta list_items desde la pestaña Tools: el panel de resultados muestra el resumen de texto content[] ("Found 2 items.") y una sección colapsable Structured Output que renderiza la carga útil validada por esquema como JSON formateado y copiable. Esa sección es lo que v2 estaba omitiendo: una herramienta que declara un outputSchema devuelve sus datos reales allí, y el bloque de texto generalmente solo los resume. Ejecuta echo para confirmar que la sección está ausente cuando un resultado no lleva structuredContent.
Nombres de herramienta duplicados
duplicate-tool-names-http.json sirve get_weather, get_temp, echo y add, luego repite get_weather y echo al final de tools/list con el mismo name y un título (duplicate) (duplicateToolNames). Ningún preajuste puede producir esta forma: el registerTool del SDK rechaza un nombre repetido, pero un servidor real puede y lo hace, y el Inspector debe representarlo fielmente.
Conéctate (era heredada predeterminada), abre la pestaña Tools y escribe get en Search tools: la lista debe reducirse exactamente a las tres filas get_*. En la versión rota mantenía una fila echo obsoleta, porque la barra lateral usaba solo tool.name para las filas y las claves en conflicto dejaban huérfano a un hijo durante la reconciliación (#1957).
Las copias duplicadas se agregan en lugar de colocarse junto a su gemela a propósito. React combina primero una secuencia principal de hijos con la misma clave, por lo que un duplicado adyacente a la cabecera coincide y el defecto se oculta; separar el par es lo que lo hace observable — y también es la forma realista, dos fuentes de herramientas concatenadas.
Extensiones anunciadas
advertised-extensions-http.json sirve echo (siempre) y una herramienta get_weather condicionada a la extensión io.modelcontextprotocol/tasks (extensionGatedTools): la herramienta se registra pero comienza deshabilitada, y el servidor la habilita en notifications/initialized solo cuando el cliente declara esa extensión en su capabilities.extensions.
- Conéctate: el Inspector anuncia la extensión Tasks por defecto, por lo que la lista de Tools muestra tanto
echocomoget_weather. - Abre Server Settings → Advertised Extensions, desmarca Tasks (io.modelcontextprotocol/tasks) y vuelve a conectar.
- El cliente ahora no anuncia extensiones, el servidor nunca habilita
get_weathery la lista de Tools muestra soloecho.
Este es el control de depuración para un servidor que legítimamente cambia el registro de herramientas según lo que el cliente anuncia. Solo vía heredada con estado: la vía moderna por solicitud no tiene oninitialized persistente.
Registro (logging), ambas eras
logging-legacy-http.json y logging-modern-http.json sirven logging: true además de una herramienta send_notification que emite un notifications/message en un nivel elegido. El heredado es un servidor streamable-HTTP simple; el moderno establece transport.modern: true.
- Heredada — la pestaña Logs ofrece un selector Set Active Level con ámbito de sesión y un botón Set. Llamar a
send_notificationtransmite el registro al panel. - Moderna — la misma pestaña muestra en cambio Log Level per Request. Elige un nivel para aceptar y el cliente estampa
_meta["io.modelcontextprotocol/logLevel"]en cada solicitud subsiguiente (verifica en el cuerpo de la solicitud en la pestaña Network). Llamar asend_notificationtransmite el registro sobre la respuesta SSE de la solicitud. Vuelve a ponerlo en Off y la misma llamada se bloquea silenciosamente: la solicitud omite la clavelogLevel, por lo que el registro nunca llega.
Ese bloqueo es fiel a la especificación ("un servidor NO DEBE emitir notifications/message para una solicitud que no aceptó") porque send_notification emite a través del extra.log del SDK con ámbito de solicitud y consciente del umbral (ctx.mcpReq.log). En la vía moderna lee la aceptación por solicitud logLevel del sobre de la solicitud y descarta el mensaje cuando el cliente no aceptó o el nivel está por debajo de la severidad solicitada; en la heredada respeta el nivel de sesión de logging/setLevel. Debido a que emite a través del notify de la solicitud, la respuesta moderna se actualiza a SSE y el registro viaja en la transmisión de la solicitud originaria.
Suscripciones a recursos, ambas eras
subscriptions-legacy-http.json y subscriptions-modern-http.json sirven tres numbered_resources con subscriptions: true. El heredado también sirve una herramienta update_resource; el moderno establece transport.modern: true.
- Heredada — abre un recurso en la pestaña Resources y haz clic en Subscribe. El cliente envía
resources/subscribey la sección Subscriptions lista el URI sin adornos de transmisión. Llama aupdate_resourcecon ese URI y el servidor actualiza el contenido y emitenotifications/resources/updated, estampando la hora de última actualización del mosaico suscrito. - Moderna — el mismo Subscribe envía en su lugar
subscriptions/listen(su filtro llevaresourceSubscriptionsmás la aceptaciónresourcesListChanged) y se resuelve ennotifications/subscriptions/acknowledged. La sección Subscriptions muestra entonces una insignia de estado de transmisión (Connecting…→Listening) en su encabezado, y se reconecta volviendo a listar si la transmisión de larga duración se cae.
La configuración moderna omite deliberadamente update_resource. La vía moderna del SDK es sin estado/por solicitud (createMcpHandler(() => createMcpServer(config))), por lo que la herramienta se ejecutaría contra una instancia de servidor desechable: el cambio de contenido no persistiría para el siguiente resources/read, y su resources/updated no llegaría a la transmisión de escucha separada. Más confuso que útil.
Así que el viaje de ida y vuelta de notificación de actualización en vivo se demuestra en el servidor heredado (con sesión con estado), y el servidor moderno es para el comportamiento de suscripción/escucha/insignia. La ruta de recepción del Inspector es transparente a la era, por lo que un servidor moderno real con estado que enruta resources/updated a la transmisión de escucha impulsa el mosaico suscrito de la misma manera.
Tareas, ambas eras
Heredada (tasks-legacy-http.json) anuncia capabilities.tasks (tasks: { list, cancel }) con los preajustes simple_task / progress_task / elicitation_task. Ejecuta una de esas herramientas con Run as task activado, y la pestaña Tasks la lista (poblada vía tasks/list), consulta tasks/get, obtiene la carga útil con el bloqueante tasks/result y cancela con tasks/cancel.
Moderno (tasks-modern-http.json) establece transport.modern: true y tasksExtension: true, publicitando la extensión io.modelcontextprotocol/tasks (SEP-2663) y sirviendo modern_task / modern_input_task. La pestaña Tareas está condicionada a la extensión negociada, no a capabilities.tasks.
- Ejecutar
modern_taskcomo tarea —tools/calldevuelve unCreateTaskResult(resultType: "task", visible en las pestañas Protocolo/Red), el cliente consultatasks/get(sintasks/list), y la tarea completada integra su resultado (sintasks/resultbloqueante). - Ejecutar
modern_input_task— la tarea pasa ainput_required, mostrando una elicitación integrada a través del modal de solicitud pendiente. Responderla envíatasks/updatecon elinputResponses, y la siguiente consulta se completa.
El SDK v2 eliminó todo el soporte de tareas y restringe por era los métodos de especificación tasks/* fuera de la era moderna en ambos lados. Así que el Inspector impulsa la extensión por sí mismo — el marco resultType: "task" se reescribe en el transporte como un CallToolResult que lleva el identificador, y tasks/get / update / cancel viajan por un canal de solicitudes de cable crudo con el envoltorio moderno completo. El servidor de prueba sirve tasks/* desde un interceptor Express antes del manejador del SDK, ya que la rama moderna del SDK los respondería -32601.
El botón Actualizar de la pestaña Tareas vuelve a consultar los identificadores ya conocidos por el cliente — la era moderna no tiene lista de tareas del lado del servidor.
Construcción
npm run build # builds all clients: web → cli → tui → launcher
Clientes individuales: build:web, build:cli, build:tui, build:launcher. La compilación web produce tanto la SPA del navegador (clients/web/dist, Vite) como el ejecutor del servidor de producción de Node (clients/web/build, tsup).
Pruebas y control de calidad
Cada cliente se autovalida desde su propia carpeta; los scripts raíz los encadenan. No existe un script agregado raíz test — use validate (rápido) o coverage (el control).
| Script | Qué hace |
|---|---|
npm run validate | Ejecuta primero los tres guards durables — verify:format-coverage (cada archivo fuente rastreado está sujeto a formato), verify:typecheck-coverage (cada uno cae en un proyecto tsconfig), verify:dep-lockstep (ninguna dependencia que las fuentes compartidas importen directamente se desvía entre instalaciones) — luego test:scripts (las pruebas unitarias de parser de los propios guards), luego validate:core (el gate compartido core/ format:check + lint), y luego por cliente: format:check + lint + typecheck (cli/tui/launcher; web typechecks via tsc -b dentro de su build) + build + pruebas unitarias rápidas. El check rápido del bucle interno. |
npm run coverage | El gate por archivo ≥90% (líneas/declaraciones/funciones/ramas) bajo instrumentación v8, por cliente. Aplicado en CI. Para web, esto también ejecuta el proyecto de integración y cubre el runtime compartido core/ (incluyendo core/json y core/client). |
npm run smoke | Smokes de extremo a extremo a través del launcher compilado (dispatch --help + prod cli/tui/web), más dos smokes de Chromium sin interfaz: un smoke de arranque que ejecuta el bundle web de producción y verifica un primer render limpio (sin error no capturado — excepción síncrona o rechazo no manejado, cómo se manifiesta un built-in de Node que llega al bundle del navegador), y un smoke MCP Apps (smoke:web:app) que impulsa conectarse → abrir app → data-app-status="ready" contra un servidor de App componible, cubriendo el proxy de sandbox y el puente de protocolo UI. |
npm run verify:build-gate | Ejecuta un vite build real con un built-in de Node forzado en el grafo del navegador y verifica que el build falla a través del gate #1769 (que convierte la advertencia de externalización de Vite en un error duro). Protege contra la deriva de la frase de advertencia en una subida de Vite y la desactivación silenciosa del gate. Parte de npm run ci. |
npm run verify:format-coverage | Analiza los globs de format:check de cada package.json (solo los alcanzables desde validate), enumera todos los archivos fuente rastreados y falla listando cualquier archivo no cubierto por un glob — el guard durable para el invariante "cada archivo fuente de primera parte está sujeto a formato" (#1792). Se ejecuta primero en validate. |
npm run test:scripts | Pruebas unitarias dirigidas por tabla (node --test) para los parsers puros del propio guard (scripts/lib/npm-scripts.mjs + los helpers exportados de verify-typecheck-coverage.mjs y verify-dep-lockstep.mjs), un caso por regla que codifican. Se ejecuta en validate — y verify:typecheck-coverage protege este gate a su vez (alcanzable desde validate, conjunto de pruebas no vacío, cada archivo de prueba coincide con el glob de test:scripts), ya que node --test salta silenciosamente un archivo que su glob no captura y aún así sale con 0. |
npm run verify:typecheck-coverage | El análogo de cobertura de typecheck del anterior (#1791): para cada cliente Node (auto-descubierto desde disco — inscrito a través de los proyectos de su script typecheck, o para un cliente tsc -b como clients/web a través de su tsconfig.json references) ejecuta esos proyectos con tsc --listFilesOnly, los une, y falla listando cualquier .ts/.tsx/.mts/.cts rastreado bajo el cliente que no caiga en ningún proyecto (para que un nuevo config/helper de nivel superior no pueda pasar sin typecheck silenciosamente). También requiere, denegado por defecto, el TS de primera parte que ningún cliente posee (test-servers/src, el vitest.shared.mts raíz, todo core/, y cualquier nueva ubicación de nivel superior) que caiga en el pase tsc de algún proyecto de cliente — así un core *.tsx web cuyos proyectos no alcanzan también es capturado. También verifica que el gate esté cableado (el pase de typecheck de cada cliente — su script typecheck, o el tsc -b de web — es alcanzable desde su validate, y la cadena raíz ejecuta el validate de cada cliente). Se ejecuta en validate. |
npm run verify:dep-lockstep | Protege el invariante "una versión por dependencia que cruza instalaciones" (#1896). v2 no es un workspace, así que el proyecto de pruebas de un cliente compila el TypeScript compartido de primera parte — core/, test-servers/src, y el vitest.shared.mts propiedad de la raíz, todos los cuales resuelven sus dependencias desde la instalación raíz — junto con las fuentes del propio cliente, poniendo el mismo paquete en un programa tsc dos veces. En la misma versión eso es inofensivo; desviado, TypeScript debe relacionar dos copias estructuralmente distintas de cada tipo, lo que para una superficie recursiva-genérica es exponencial (zod 4.3.6 vs 4.4.3 agotó el heap tsc de 4GB en clients/web). Deriva su conjunto candidato de lo que esas fuentes compartidas importan, compara las entradas de nivel superior de los lockfiles commiteados, y falla denegado por defecto en cualquier desviación no en la allowlist anotada TOLERATED_SKEW — y un paquete en la allowlist se tolera solo dentro de una versión mayor. Se ejecuta en validate. |
npm run ci | Comando obligatorio pre-push. validate → coverage → verify:build-gate → smoke → Storybook. Un superconjunto real de CI de GitHub. |
| npm run pack:verify | Publicar prueba de humo — ver Publicación. |
Los scripts por cliente también existen (validate:web, coverage:cli, smoke:tui, …), además de validate:core / format:core en la raíz para el paquete compartido core/, format:scripts para las herramientas scripts/ de la raíz, y format:shared / lint:shared para la superficie "compartida" de la raíz (test-servers/src/**, vitest.shared.mts, el eslint.config.js raíz). Ejecuta npm run format antes de hacer commit — el format raíz corrige core/, el scripts/ raíz, la superficie compartida y cada cliente; validate ejecuta el format:check sin corregir y hace fallar el CI ante cualquier archivo sin formatear.
Para las reglas completas de testing — el umbral por archivo de ≥90 %, dónde viven los archivos de test, los proyectos unit vs. integration vs. storybook, y la política de v8 ignore — consulta AGENTS.md.
Publicación
El paquete @modelcontextprotocol/inspector de la raíz se distribuye como un solo tarball con un único número de versión — no hay paquetes separados -web / -cli / -tui / -core. npm run build compila cada cliente, y luego prepack se ejecuta antes de npm publish. Las dependencias de runtime se declaran en el package.json de la raíz; los builds de los clientes empaquetan @inspector/core y externalizan los paquetes npm resueltos desde la instalación de la raíz.
Qué se distribuye y las invariantes del empaquetado
La allowlist de package.json "files" de la raíz es la fuente de verdad para el tarball. Existen algunas entradas no obvias porque se leen en tiempo de ejecución o porque npm las descartó silenciosamente con su packlist — no las elimines sin volver a ejecutar npm run pack:verify:
- Sin source maps. Los bundlers de los clientes establecen
sourcemap: false(clients/{cli,tui}/tsup.config.ts,clients/web/tsup.runner.config.ts); Vite y eltscdel launcher ya no emiten ninguna. Los mapas son aproximadamente la mitad del tamaño descomprimido y no se necesitan en runtime — depura víanpm run devsobre el código fuente. clients/web/buildse distribuye víaclients/web/.npmignore.clients/web/.gitignorelistabuild/, y el packlist de npm respeta ese.gitignoreanidado por encima de la allowlist"files"de la raíz — así que el runner del servidor web de producción faltaba silenciosamente en el tarball mientrasclients/web/distse colaba (su.gitignoresolo listadist-ssr).clients/web/.npmignoreanula el.gitignorepara la publicación de modo que tantobuild/(runner) comodist/(SPA) se distribuyan. Los demás clientes no necesitan esto — ninguno incluye un.gitignoreanidado.clients/web/staticincluye el proxy del sandbox de MCP Apps.clients/web/static/sandbox_proxy.htmles un archivo fuente versionado (no un artefacto de build), leído del disco en tiempo de ejecución porclients/web/server/sandbox-controller.tscomo<runner dir>/../static/sandbox_proxy.html. Faltaba por completo en la allowlist"files"de la raíz, por lo que todos los builds publicados fallaban en la pestaña Apps con "Sandbox not loaded" (#1859) mientras funcionaba bien en el repositorio. Como la ruta se resuelve relativa aclients/web/build, el directorio debe distribuirse en esa ubicación exacta —pack:verifyverifica tanto la entrada del tarball como la ruta instalada en disco.- Una dependencia que renderiza React se empaqueta, no se externaliza. Un paquete externalizado resuelve su propio
reactdesde donde npm lo haya colocado a él en el árbol del consumidor, que no es necesariamente donde el bundle resuelve el nuestro — npm coloca un paquete junto a un React que satisfaga su rango de peer, y esos rangos son más laxos que los nuestros.ink-formyink-scroll-viewdeclaran">=18", así que un proyecto con React 18 los satisface y los instala en el nivel superior mientras el React 19 del Inspector queda anidado debajo: dos copias de React, y el TUI muere conTypeError: Cannot read properties of null (reading 'useState')en el momento en que un formulario de prueba de herramienta o una vista de scroll se monta (#1952). Por lo tanto, ambos se insertan medianteclients/tui/tsup.config.tsy no son dependencias de la raíz: el tarball distribuye su código dentro declients/tui/build/index.jsen lugar de que los consumidores los instalen. El empaquetado también fija sus dependencias transitivas a lo que resolvió la instalación de este repositorio (en particularink-select-input@6víaoverrides, que npm ignora para un paquete instalado como dependencia).inkes la única excepción, por costo: empaquetarlo funciona pero añade ~1.4 MB (react-reconcileryyoga-layoutvienen incluidos, además de un bannercreateRequirepara el CJS insertado), así que permanece externo — no porque su peer de">=19"lo haga seguro, que no lo hace. Lo que hace tolerable eso es el rango dereactde la raíz:"^19.0.0"está deliberadamente abierto a toda la versión mayor para que npm pueda deduplicar nuestro React con cualquier React 19 que un consumidor fije, dejando uninkexterno sobre la misma copia que usa el bundle. Estrechar ese rango reabre el bug para el propio renderer —clients/tui/__tests__/tsupConfig.test.tslo fija al piso de peer deink, y protege el resto de la separación; consulta el README del TUI. - Un único número de versión, leído del
package.jsonde la raíz. El Inspector se distribuye como un solo paquete con una sola versión, así que solo elpackage.jsonraíz lleva unversion— los cuatroclients/*/package.jsons deliberadamente no tienen ninguno. Cada cliente Node (CLI, TUI y el backend web) resuelve la versión mediante el lector compartido dereadInspectorVersion()encore/node/version.ts, que asciende hasta el manifiesto de la raíz (siempre presente en el tarball). Ningúnpackage.jsonde cliente se lee en runtime, así que ninguno necesita distribuirse. El navegador web no puede leer el sistema de archivos; obtiene su versión del backend víaGET /api/config(consulta #1639).
npm run pack:verify — smoke de publicación contra el tarball real
Los scripts de smoke:* se ejecutan contra el árbol de build del repositorio, que no es el paquete publicado. npm run pack:verify (scripts/pack-and-verify.mjs) cierra esa brecha: compila, hace npm pack del tarball publicable (verificando que no se distribuyan source maps y que los archivos requeridos en runtime estén presentes), instala el tarball en un consumidor desechable limpio — un directorio temporal nuevo donde ejecuta un npm install <tgz> real (trae las dependencias de runtime, ejecuta postinstall), exactamente como lo haría npx @modelcontextprotocol/inspector — y maneja el bin de mcp-inspector instalado de extremo a extremo: dispatch de --help, un --cli tools/list real sobre stdio, y un arranque de --web de producción que debe servir / desde el dist distribuido. Detecta fallos de ruta/empaquetado del tipo "funciona en --dev, se rompe bajo npx …". Requiere acceso a red (la instalación trae dependencias), así que es una verificación local / de release, no parte del bucle rápido de validate/ci.
Cómo hacer un release
La publicación está automatizada por dos jobs con compuerta de release en .github/workflows/main.yml (github.event_name == 'release', ambos con needs: build):
publish— el paquete npm. Ejecutanpm run pack:verifycomo compuerta previa a la publicación, verifica que la etiqueta del release coincida con la versión delpackage.jsonraíz, y luegonpm publish --access public --provenance— un úniconpm publish(v2 no es un workspace npm, así que no hay unpublish-all/--workspacesal estilo v1), con una atestación de procedencia firmada vía GitHub OIDC (id-token: write,environment: release,NPM_TOKEN).publish-github-container-registry— la imagen de contenedor (consulta Docker).
Un release v2 se corta desde main, después de que el trabajo del milestone se haya fusionado allí desde v2/main — no desde v2/main mismo. (La línea v1 publica de forma independiente desde v1/main a la etiqueta v1-latest y nunca toca main; consulta Estado del repositorio.)
Como hay un único número de versión (solo el package.json de la raíz tiene uno — los clientes no llevan ninguno, así que no hay nada que mantener sincronizado ni paso de check-version), el flujo de release es simplemente:
npm version <major|minor|patch> # bumps the root package.json + tags
git push --follow-tags
# then draft & publish a GitHub Release for that tag → triggers `publish`
El commit objetivo del release determina qué workflow se ejecuta, así que esto solo publica cuando un release se corta desde un commit que lleva este workflow (v2).
Docker
El workflow de release publica una imagen de contenedor en GHCR (ghcr.io/modelcontextprotocol/inspector, linux/amd64 + linux/arm64). El Dockerfile es un build de dos etapas: la primera etapa instala y hace npm pack del tarball publicable; la segunda etapa hace npm install -g de ese tarball, de modo que la imagen distribuya exactamente el mismo artefacto que npm, con un bin de mcp-inspector limpio.
# run the web UI (reads the auth token from the container logs)
docker run --rm -p 127.0.0.1:6274:6274 ghcr.io/modelcontextprotocol/inspector
# or build the image locally
docker build -t mcp-inspector .
docker run --rm -p 127.0.0.1:6274:6274 mcp-inspector
Mantén el prefijo 127.0.0.1: en el puerto publicado. Un -p 6274:6274 simple publica en cada interfaz de host, poniendo el Inspector en tu red local. El HOST=0.0.0.0 del contenedor es una preocupación aparte — gobierna las interfaces del contenedor, no las del host — así que la opción de aceptación DANGEROUSLY_BIND_ALL_INTERFACES que protege un bind con comodín fuera de un contenedor no cubre esto. Importa más aquí que para una aplicación web ordinaria: el backend lanza procesos bajo petición, GET / incrusta el token de API en el HTML servido, y una solicitud que llega sin header de Origin se salta por completo la allowlist de orígenes — así que para cualquier cliente que no sea navegador, el token de API es la única protección. Publicar de forma más amplia requiere un límite real de control de acceso delante del Inspector — un proxy inverso que autentique, un túnel SSH, una red privada. Establecer tu propio MCP_INSPECTOR_API_TOKEN no sustituye eso: GET / revela cualquier token en uso, así que uno personalizado se cosecha con la misma facilidad que uno generado.
Conservar los servidores que agregas. El Inspector guarda tu lista de servidores en $HOME/.mcp-inspector/mcp.json, que en la imagen es /home/node/.mcp-inspector/mcp.json — dentro de la capa escribible del contenedor, así que --rm la descarta y cada ejecución arranca con una lista vacía. Monta un volumen allí para conservarla:
docker run --rm -p 127.0.0.1:6274:6274 \
-v mcp-inspector-data:/home/node/.mcp-inspector \
ghcr.io/modelcontextprotocol/inspector
El mismo volumen también persiste los tokens OAuth y el estado almacenado, así que un servidor autorizado permanece autorizado entre ejecuciones. Usa -e MCP_CATALOG_PATH=/some/other/path.json para poner el catálogo en otro lugar — monta un volumen que cubra el directorio al que lo apuntes. Si montas un directorio de host en modo bind en lugar de un volumen con nombre (-v "$PWD/inspector-data:/home/node/.mcp-inspector"), el directorio conserva la propiedad del host, así que en Linux agrega --user "$(id -u):$(id -g)" o haz chown de él al uid 1000 — de lo contrario el usuario no-root node no puede escribir y agregar un servidor falla con EACCES.
¿Actualizando desde una imagen anterior a esta corrección? Las imágenes anteriores no creaban /home/node/.mcp-inspector, así que Docker creaba el punto de montaje del volumen como root y el usuario no-root node no podía escribir en él. Un volumen vacío se repara solo en la primera ejecución de una imagen actual (Docker aplica la propiedad del directorio de la imagen a un volumen vacío), pero uno que ya tiene archivos conserva su propiedad root anterior y sigue fallando con EACCES. Arrégralo una vez:
docker run --rm -u 0 --entrypoint chown \
-v mcp-inspector-data:/data ghcr.io/modelcontextprotocol/inspector \
-R node:node /data
La imagen usa por defecto --web vinculado a 0.0.0.0:6274 con el auto-apertura del navegador deshabilitada; anula los argumentos para ejecutar otro modo (docker run --rm ghcr.io/modelcontextprotocol/inspector --cli …). Pasa -e MCP_INSPECTOR_API_TOKEN=… para establecer un token conocido (de lo contrario se genera uno y se imprime en los logs), o -e DANGEROUSLY_OMIT_AUTH=true para deshabilitar la autenticación. Vincular 0.0.0.0 (todas las interfaces de red) se rechaza por defecto fuera de un contenedor — expone el backend que lanza procesos a la red local — así que la imagen opta explícitamente con DANGEROUSLY_BIND_ALL_INTERFACES=true (ya establecido en el Dockerfile); un HOST=0.0.0.0 simple sin esa bandera sale con error. Si remapeas el puerto publicado (-p 127.0.0.1:8080:6274), el origen del navegador (http://localhost:8080) ya no coincide con el puerto dentro del contenedor, así que establece -e ALLOWED_ORIGINS=http://localhost:8080,http://127.0.0.1:8080 (o ejecuta -e CLIENT_PORT=8080 -p 127.0.0.1:8080:8080) o las conexiones darán 403. ALLOWED_ORIGINS reemplaza la lista por defecto en lugar de fusionar, así que lista cada forma de loopback desde la que navegarás (consulta el README web). La imagen se ejecuta como el usuario no-root node y tiene un HEALTHCHECK que sondea la interfaz web — asume el modo --web por defecto, así que agrega --no-healthcheck al ejecutar --cli/--tui (que no tienen servidor web).
Contribuciones — AGENTS.md y CLAUDE.md
AGENTS.md es el contrato para cambiar este repositorio y aplica tanto a humanos como a agentes de IA. No es una plantilla solo para agentes: contiene las convenciones reales del proyecto: el flujo de trabajo de issues y tablero, las reglas de ramas y etiquetas, los estándares de TypeScript y Mantine/React, los requisitos de pruebas y cobertura, y el control obligatorio previo al push. Léelo antes de hacer cambios y mantenlo actualizado cuando cambies la estructura, las herramientas o las reglas.
CLAUDE.md es el punto de entrada que el agente Claude Code carga automáticamente; simplemente incluye AGENTS.md y este README, de modo que tanto agentes como humanos trabajan con la misma fuente de verdad. Si usas un agente diferente que lee AGENTS.md, obtienes las mismas reglas.
Una regla clave que vale la pena destacar aquí: todo el trabajo está impulsado por issues. Antes de comenzar, encuentra o crea un issue de seguimiento en el tablero del proyecto v2; abre PRs contra v2/main con Closes #<issue>. Las recetas exactas (etiquetas, IDs de tablero, estados) están en AGENTS.md.
Licencia
MIT.