Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
inspector — Inspectez, déboguez et testez visuellement les serveurs Model Context Protocol (MCP) depuis une interface web, une CLI ou une TUI, avec exploration des outils/ressources, journalisation des requêtes et prise en charge d'OAuth. | Kitploit
Outils/GitHubGitHub/modelcontextprotocol/inspector
Scripting et AutomatisationDébogueursUtilitaires et FrameworksAuthentification
GitHubmodelcontextprotocol/inspector

inspector

Inspectez, déboguez et testez visuellement les serveurs Model Context Protocol (MCP) depuis une interface web, une CLI ou une TUI, avec exploration des outils/ressources, journalisation des requêtes et prise en charge d'OAuth.

Voir le dépôt

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager
Site web
10.7k1.5kil y a 20h 38mVérifié par Kitploit

MCP Inspector

Un outil de développement pour inspecter les serveurs Model Context Protocol (MCP). Il est fourni sous la forme d'un package unique, @modelcontextprotocol/inspector, qui propose trois façons d'inspecter un serveur :

  • Web — une application monopage Vite + React + Mantine avec un backend Node.
  • CLI — un client en ligne de commande scriptable pour l'automatisation, la CI et les boucles de rétroaction rapides des agents.
  • TUI — une interface terminal interactive construite avec Ink.

Les trois s'exécutent via un seul binaire global mcp-inspector :```bash npx @modelcontextprotocol/inspector # web UI (default) npx @modelcontextprotocol/inspector --cli # CLI npx @modelcontextprotocol/inspector --tui # TUI

root@kitploit:~
> **Passage de v1 ?** Lisez le [guide de migration v1 → v2](https://github.com/modelcontextprotocol/inspector/blob/HEAD/docs/v1-to-v2-migration.md) — les drapeaux CLI, la nouvelle répartition `--config` vs. `--catalog`, le changement de version de Node, et ce qui n'est plus fourni.

> **État du dépôt.** Il s'agit de la ligne **v2** de l'Inspector. Le développement actif se fait sur **`v2/main`** (la branche de développement — toutes les PR v2 la ciblent), qui est fusionnée dans **`main`** aux versions jalons ; `main` est la branche par défaut et contient la dernière v2 publiée, publiée sur la balise npm `latest`. L'ancienne ligne **v1** vit sur **`v1/main`** — corrections de sécurité uniquement, publiées directement depuis cette branche sur la balise npm `v1-latest` (`npx @modelcontextprotocol/inspector@v1-latest`). Voir [`AGENTS.md`](https://github.com/modelcontextprotocol/inspector/blob/HEAD/AGENTS.md) pour les conventions de branches/tableau.

## Structure du projet

v2 n'est **pas** un workspace npm. Chaque client sous `clients/*` possède son propre `package.json` et `node_modules` ; le code partagé vit dans `core/` et est consommé via un alias de compilation `@inspector/core` (sans `package.json` propre). Un seul `npm install` à la racine installe en cascade dans chaque client (voir [Configuration](#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, endpoint overrides, 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, and the nullable-union
│   │                 #   schema collapse shared by the web and TUI form builders
│   ├── logging/      # Silent pino logger singleton
│   ├── mcp/          # InspectorClient runtime, state stores, transports, config import,
│   │                 #   and the RFC 6570 URI-template helpers the web form and TUI expand through
│   ├── 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

Chaque client a son propre README avec des détails spécifiques au client : web · cli · tui · launcher.

Les guides orientés tâches se trouvent dans docs/ :

  • Migrating from v1 to v2 — la carte v1 → v2 : correspondance des drapeaux CLI, sémantique de --config vs --catalog avec exemples avant/après, le changement de moteur Node (>=22.7.5 → >=22.19.0), les renommages de variables d'environnement et les sous-packages qui ne sont plus fournis.
  • MCP server configuration — à quel(s) serveur(s) l'Inspector se connecte : --catalog vs --config, cibles ad hoc, le séparateur --, le format de fichier et ses champs par serveur spécifiques à l'Inspector. Partagé par les trois clients ; les READMEs cli et tui délèguent leurs sections d'options serveur à ce document.
  • Reviewing an MCP App — la recette CLI-first → one-shot-web pour la revue automatisée d'outils d'App : sonde --app-info → navigation par deep-link → widget rendu, plus la prise en charge du transfert OAuth et du proxy.
  • Launcher and config consolidation — pourquoi le launcher exécute un client en processus plutôt que de le lancer séparément, et comment le processeur de configuration partagé s'intègre.

Setup

Nécessite Node >=22.19.0.```bash npm install # root install; postinstall cascades into every client

root@kitploit:~
- **Clone frais :** exécutez `npm install` à la racine du dépôt.
- **Après un pull qui modifie les dépendances d'un client :** relancez `npm install` à la racine pour resynchroniser chaque client.

La cascade (`scripts/install-clients.mjs`) est réservée au développement — elle se termine prématurément lorsque le paquet est installé comme dépendance, et le tarball publié ne contient que le `build/` de chaque client, donc les utilisateurs finaux ne sont pas affectés. Définissez `INSPECTOR_SKIP_CLIENT_INSTALL=1` pour l'ignorer.

**Où une dépendance est déclarée.** Les paquets du SDK MCP (`@modelcontextprotocol/client`, `core`, `server`, `server-legacy`, `ext-apps`) vivent uniquement dans le `package.json` **racine** — jamais dans celui d'un client. La résolution de Node remonte l'arborescence, donc l'installation racine se trouve dans la chaîne de chaque client, et le manifest racine est déjà ce contre quoi le tarball publié résout. Les déclarer par client installe une seconde copie qui peut dériver de celle de la racine, ce qui explique comment deux versions d'`ext-apps` (et du `@modelcontextprotocol/sdk` v1 transitif) se sont retrouvées dans l'arbre avant [#1970](https://github.com/modelcontextprotocol/inspector/issues/1970) — et une seconde copie de `client`/`core` est la panne pour laquelle `vitest.shared.mts` contient un contournement `dedupe`. Le même placement racine-uniquement vaut pour tout ce qui n'est atteint que par du code possédé par la racine et sans manifest propre (`test-servers/src`, `core/`), et `vitest.shared.mts` aliase ces éléments vers la racine du dépôt — `express` et `yaml`, tous deux atteints via `test-servers/src`, sont les deux actuels. **Qu'un tel paquet soit une `dependency` ou une `devDependency` découle de qui le consomme à l'exécution, et non de l'endroit où il est déclaré :** tout ce que `core/` importe à l'exécution doit être une **`dependency`** racine, car les builds client externalisent les paquets npm et une installation publiée les résout depuis le manifest racine, où les devDependencies sont absentes. `express` est réservé aux tests et est une devDependency ; `yaml` est actuellement dans `dependencies`. **`vite` et `@vitejs/plugin-react` sont des `dependencies` racine pour la même raison, pas par erreur** — ils ressemblent à des outils de build, mais `clients/web/server/start-vite-dev-server.ts` les importe à l'exécution pour `mcp-inspector --web --dev`, et `clients/web/tsup.runner.config.ts` les liste tous deux comme `external`, donc une installation publiée les résout depuis le manifest racine. Les déplacer dans `devDependencies` casserait `--web --dev` pour les consommateurs (et le `vite build` à la demande dans `ensure-web-build.ts`) tout en réussissant toutes les vérifications locales. Cela signifie qu'ils apparaissent sous `npm audit --omit=dev`, ce qui est une fonctionnalité : ils sont réellement dans l'arbre de production.

## Exécution pendant le développement

Pour itérer quotidiennement sur le web, exécutez Vite directement depuis le client web (HMR rapide, aucune construction du lanceur nécessaire) :```bash
cd clients/web && npm run dev

Les scripts pilotés par le lanceur ci-dessous exécutent le lanceur compilé, donc compilez d'abord (npm run build) :```bash npm run web # prod web launcher against clients/web/dist npm run web:dev # web launcher in --dev mode (Vite)

root@kitploit:~
## Le package partagé `@inspector/core`

![Architecture du code partagé : les quatre clients reposant sur le package partagé @inspector/core](https://assets.kitploit.com/production/public/readmes/50997/12951c8cb492b9d753ea13b98a8ea2475ef2e250a9e849227b05c3cfc7c5d31c/962dd293aaae81c94af868f086686d56189155e63449bfc6b537818e8725c812-display-v1.webp)

`core/` contient la logique partagée par les trois clients afin que le web, la CLI et la TUI se comportent de manière identique. Son point d'entrée est la classe **`InspectorClient`** (`core/mcp/`), qui gère la connexion à un serveur MCP, le cycle de vie des requêtes/réponses et un ensemble de stores d'état ; `core/react/` expose des hooks React sur ces stores, consommés par les arbres React du web et de la TUI (Ink). OAuth (`core/auth/`) est décomposé en logique isomorphe plus des backends navigateur/node/remote, afin que les mêmes flux fonctionnent dans le navigateur, dans Node et contre un backend distant.

`core/` n'a volontairement **pas de `package.json`** — il n'est pas publié séparément. Chaque client l'intègre via un alias `@inspector/core` :

- **CLI / TUI :** `esbuildOptions.alias` dans leur `tsup.config.ts` fait correspondre `@inspector/core` → le répertoire `core/` du dépôt, et `noExternal: [/^@inspector\/core/]` l'intègre directement dans le bundle.
- **Web :** le même alias dans `clients/web/vite.config.ts` pour l'application navigateur et le runner backend Node.

La publication de `core/` en tant que package indépendant (par ex. pour que des tiers puissent s'appuyer dessus) est volontairement différée — voir le ticket [#1636](https://github.com/modelcontextprotocol/inspector/issues/1636).

## Client web : « composants passifs » + Storybook

Le client web v2 est construit à partir de **composants de présentation (« passifs »)** — ils acceptent des données et des callbacks en props et ne contiennent que de la logique d'affichage, sans récupération de données directe ni état client. L'état provient des hooks `@inspector/core`, câblés près du sommet de l'arbre. Cela maintient les composants isolés, testables et documentables.

C'est cette approche qui fait de **Storybook** un citoyen de première classe ici : chaque composant d'écran et d'élément possède un fichier `*.stories.tsx` (96+ stories) qui le rend avec des props de fixture. Les **play functions** de Storybook font également office de tests d'interaction, exécutées en mode headless dans la CI (`npm run ci:storybook`, Chromium via Playwright).

Le style suit une convention stricte qui privilégie Mantine (les variantes de thème et les props de composants plutôt que les classes CSS, les propriétés CSS personnalisées `--inspector-*` plutôt que les littéraux de couleur bruts). Les règles complètes se trouvent dans [`AGENTS.md`](https://github.com/modelcontextprotocol/inspector/blob/HEAD/AGENTS.md) sous **instructions React** — lisez-les avant de toucher à l'interface web. Les composants d'éléments se trouvent dans `clients/web/src/components/elements/` ; les variantes de thème dans `clients/web/src/theme/`.

## Serveurs de test

`test-servers/` fournit des **serveurs MCP composables** utilisés par les suites d'intégration et de smoke tests, afin que les tests exercent un vrai serveur sur un vrai transport plutôt que des mocks. Un serveur est assemblé à partir de **presets** (fabriques de fixtures dans `test-servers/src/preset-registry.ts` — outils, ressources, prompts, tâches, élicitation, échantillonnage, OAuth, …) et peut être piloté de deux manières :

- **In-process** — importez les fabriques (`createTestServerHttp`, `createEchoTool`, …) et exécutez le serveur dans la boucle d'événements du test (utilisé par les chemins d'intégration HTTP).
- **En tant que sous-processus** — `test-servers/build/test-server-stdio.js` est lancé comme un véritable processus enfant stdio (utilisé par les smoke tests du CLI et les tests d'intégration stdio).

Configurez un serveur de manière déclarative avec une config JSON (voir `test-servers/configs/*.json`) en sélectionnant des presets, puis chargez-le via `--config`. Comme les serveurs sont lancés en tant que vrais sous-processus, la sortie de build doit exister au préalable :```bash
npm run test-servers:build   # (from clients/web) → tsc -p test-servers, emits test-servers/build/

L'alias Vite @modelcontextprotocol/inspector-test-server (dans clients/web/vite.config.ts) pointe vers test-servers/build/index.js, si bien que getTestMcpServerPath() se résout en un chemin .js réel.

Servir l'ère de protocole moderne

Un serveur streamable-HTTP peut aussi servir l'ère de protocole moderne (2026-07-28) via createMcpHandler du SDK :

  • Définissez transport.modern dans la configuration JSON — true pour un service sans état à double ère, ou { "legacy": "reject" } pour un mode strict moderne uniquement.
  • Ou passez modern sur ServerConfig pour un createTestServerHttp en processus.

C'est ce qui permet à une connexion Inspector négociant protocolEra: "auto" | "modern" d'atteindre la branche moderne (server/discover renseigné, sans session). Voir test-servers/configs/modern-http.json.

Configurations de démonstration

Chaque configuration ci-dessous est un serveur prêt à l'emploi pour tester manuellement une fonctionnalité. Chargez-en une avec --config et, sauf indication contraire, connectez-vous avec Ère du protocole = Moderne.

Applications MCP

mcp-app-http.json sert l'outil mcp_app_demo (_meta.ui.resourceUri) aux côtés de sa ressource UI mcp_app_demo_widget, afin que l'onglet Applications ait une véritable application à afficher. C'est un serveur streamable-HTTP ordinaire — connectez-vous avec l'ère de protocole par défaut (legacy), pas Moderne.

Ouvrez l'onglet Applications, sélectionnez mcp_app_demo, donnez-lui un titre et cliquez sur Ouvrir l'application : le widget s'affiche dans l'iframe sandbox et exerce la surface du protocole UI côté hôte — rendu en contexte hôte, size-changed, ui/message, et une ligne de journal dans le panneau Journaux de l'application. Comme le widget est servi via la page proxy sandbox, cette configuration est aussi celle qui reproduit #1859 (un clients/web/static/sandbox_proxy.html manquant se manifeste ici par un message « Sandbox non chargée » à la place du widget) — une défaillance qui n'est apparue que dans un paquet installé, jamais dans le dépôt.

Pour la version scriptée du même flux (sonde --app-info → lien profond → widget rendu), voir Revue d'une application MCP.

MRTR

modern-mrtr-http.json sert l'outil mrtr_confirm (préréglage mrtr_confirm, createMrtrTool) sur la branche moderne. Son gestionnaire renvoie inputRequired(...) intégrant une élicitation par formulaire, si bien que son invocation produit un véritable aller-retour : input_required → le client satisfait l'élicitation intégrée et réessaie avec un nouvel id → complete.

L'Inspector pilote MRTR manuellement (inputRequired: { autoFulfill: false }), si bien que l'élicitation intégrée s'arrête à la modale de requête en attente (étiquetée « input_required ») en attendant votre réponse, puis la nouvelle tentative aboutit. Utile pour examiner d'un coup d'œil à la fois cette UX de requête en attente et le regroupement des conversations MRTR de la vue Protocole.

mrtr-showcase-http.json regroupe tous les préréglages MRTR dans un seul serveur :

Exécutez mrtr_empty et répondez à son unique élicitation : l'onglet Protocole regroupe l'échange en une conversation MRTR se terminant par COMPLETE, et le panneau Résultats affiche « Résultat vide — L'appel d'outil a abouti et n'a renvoyé aucun contenu. » Sur la version cassée, ce même résultat s'affichait comme « Aucun résultat pour l'instant », le texte d'attente d'avant exécution du panneau (#1860) — si bien qu'un appel que l'utilisateur venait de voir réussir se lisait comme un appel qui n'avait jamais tourné. Un tableau content vide sans structuredContent est un CallToolResult légal, et le panneau ne se monte qu'une fois qu'un résultat existe ; la formulation du texte d'attente ne pouvait donc pas y être vraie. (La moitié voisine de la même lacune — un résultat dont la charge utile ne vit que dans structuredContent — a été comblée par #1908.)

Le préréglage legacy collect_elicitation appelle server.elicitInput, qui échoue sur la branche 2026-07-28 — les requêtes serveur→client n'y sont pas autorisées. MRTR est le remplacement moderne.

Onglet Réseau — en-têtes normalisés et taxonomie des erreurs

modern-network-http.json couvre SEP-2243 / SEP-2575. Il sert un outil get_weather dont l'argument city porte une annotation x-mcp-header: "City", si bien qu'un client moderne la reflète vers Mcp-Param-City.

Il sert aussi quatre outils trigger_* auxquels l'injecteur d'erreurs de spécification de la branche moderne (transport.modern.injectSpecErrors: true) répond avec un véritable code de statut HTTP accompagné d'un corps d'erreur JSON-RPC :

Ouvrez l'onglet Réseau pour voir les en-têtes Mcp-* mis en miroir surlignés, les valeurs sentinelles décodées et chaque erreur rendue distinctement.

La mise en miroir Mcp-Param-* est construite par l'Inspector, pas par le SDK. Le SDK ne fait la mise en miroir qu'à l'intérieur de client.callTool(), et l'ignore dans le navigateur (detectProbeEnvironment() !== "browser"). L'Inspector achemine tools/call via client.request() pour piloter MRTR manuellement, il construit donc lui-même les en-têtes mis en miroir (#1846) — sur chaque client, web compris, puisque la requête en amont du client web est émise par le backend Node plutôt que par le navigateur. Ainsi, get_weather est appelable depuis le web, la CLI et la TUI, sous la forme simple comme sous la forme « Exécuter en tant que tâche ».

x-mcp-header dans l'onglet Outils

xmcpheader-modern-http.json sert :

  • echo — un outil simple.
  • get_weather — une annotation valide x-mcp-header: "City" sur son argument city.
  • invalid_header_tool — une annotation utilisant le nom d'en-tête "Bad Header". L'espace en fait un jeton RFC 9110 invalide, si bien que la définition complète de l'outil est invalide.
  • trigger_invalid_params — répond avec une véritable erreur -32602 Invalid params dont le message ne concerne pas un outil manquant.

Ouvrez l'onglet Outils : le panneau de détail de get_weather affiche une section « En-têtes de requête mis en miroir (SEP-2243) » (city → Mcp-Param-City), et invalid_header_tool apparaît barré sous un séparateur « Exclus (SEP-2243) » avec la raison au survol. Un client Streamable HTTP conforme DOIT l'écarter de tools/list ; l'Inspector révèle pourquoi.

Sous le SDK v2, un tools/call rejeté avec -32602 s'affiche comme un panneau d'erreur distinct plutôt que comme un résultat isError — titré « Outil inconnu » lorsque le message nomme un outil manquant, ou « Paramètres invalides » sinon (exécutez trigger_invalid_params).

Récupération page par page

pagination-http.json sert 12 outils, 12 ressources et 12 invites (préréglages numbered_tools / numbered_resources / numbered_prompts, count: 12) avec un maxPageSize de 4 chacun, si bien que chaque liste se pagine en trois pages.

Activez « Récupérer les listes une page à la fois » (Paramètres du serveur — le réglage paginatedLists, ou l'interrupteur Paginé dans la barre latérale d'une liste) et les listes ne chargent que la page 1 (4 éléments) avec une commande Charger la page suivante et un statut N pages chargées. Chaque clic récupère les 4 suivants et les ajoute ; Actualiser ramène à la page 1. Interrupteur désactivé (le défaut), les mêmes listes agrègent automatiquement les trois pages à la connexion.

Sortie structurée

structured-output-http.json sert list_items (structuredContent imbriqué — des objets dans des tableaux dans un objet, la forme de #1908), get_temp (une charge utile plate à trois clés) et echo (pas d'outputSchema du tout). C'est un serveur streamable-HTTP ordinaire — connectez-vous avec l'ère de protocole par défaut (legacy).

Exécutez list_items depuis l'onglet Outils : le panneau de résultat affiche le résumé textuel content[] (« 2 éléments trouvés. ») et une section repliable Sortie structurée rendant la charge utile validée par le schéma en JSON joliment mis en forme et copiable. C'est cette section que la v2 laissait tomber — un outil déclarant un outputSchema y renvoie ses données réelles, et le bloc de texte ne fait généralement que les résumer. Exécutez echo pour confirmer que la section est absente lorsqu'un résultat ne porte pas de structuredContent.

Noms d'outils en double

duplicate-tool-names-http.json sert get_weather, get_temp, echo et add, puis répète get_weather et echo à la fin de tools/list avec le même name et un titre (duplicate) (duplicateToolNames). Aucun préréglage ne peut produire cette forme — le registerTool du SDK rejette un nom répété — mais un vrai serveur le peut et le fait, et l'Inspector doit le rendre fidèlement.

Connectez-vous (ère legacy par défaut), ouvrez l'onglet Outils et tapez get dans Rechercher des outils : la liste doit se réduire exactement aux trois lignes get_*. Sur la version cassée, elle conservait une ligne echo obsolète, car la barre latérale indexait les lignes uniquement par tool.name et les clés en collision laissaient un enfant orphelin pendant la réconciliation (#1957).

Les copies dupliquées sont ajoutées à la fin plutôt qu'à côté de leur jumeau, à dessein. React apparie d'abord une séquence initiale d'enfants ayant la même clé, si bien qu'un doublon adjacent en tête se trouve aligné et que le défaut se cache ; c'est la séparation de la paire qui le rend observable — et c'est aussi la forme réaliste, deux sources d'outils concaténées.

Arguments nullables

nullable-fields-http.json sert record_shipment, dont les quatre arguments sont chacun déclarés avec .nullish() de Zod — « facultatif et explicitement nullable ». Cela compile en anyOf: [<branch>, { "type": "null" }], si bien que le type réel (et, pour l'enum, sa liste enum) se trouve sur une branche plutôt qu'au niveau supérieur. get_temp l'accompagne avec une enum units simple, non nullable, pour comparaison. Streamable-HTTP ordinaire — connectez-vous avec l'ère de protocole par défaut (legacy).

Ouvrez l'onglet Outils et sélectionnez record_shipment : direction doit s'afficher comme une liste déroulante (envio / recebimento) avec un bouton d'effacement qui la remet à null, reference comme champ de texte, quantity comme champ numérique et express comme case à cocher. Sur la version cassée, chacun d'eux retombait dans la zone de texte JSON brut, qui ré-échappait son propre contenu à chaque frappe jusqu'à rendre la valeur inutilisable (#1928). L'outil renvoie en écho les arguments reçus, si bien que le panneau de résultat montre exactement ce qui a été envoyé.

La TUI avait la même lacune et vaut la peine d'être vérifiée contre ce même serveur (--tui, puis testez record_shipment) : direction est une liste de sélection, quantity un champ entier, express un booléen. Les deux clients partagent désormais une même étape d'aplatissement — normalizeNullableUnion dans core/json/nullableUnion.ts — précisément pour qu'ils ne puissent pas diverger sur les schémas qu'ils savent afficher.

Modèles de ressources RFC 6570

rfc6570-templates-http.json sert deux modèles de ressources tout droit issus de #1919 — events_by_topic (foobar://events/{topic}) et events_by_query (foobar://events{?topic}) — chacun renvoyant en écho l'URI sur laquelle il a été apparié, plus une ressource foobar://events ordinaire (voir ci-dessous). Streamable-HTTP ordinaire ; connectez-vous avec l'ère de protocole par défaut (legacy).

Ouvrez l'onglet Ressources et choisissez events_by_topic, puis saisissez foo/bar. La requête doit partir sous la forme foobar://events/foo%2Fbar, et le résultat renvoie en écho l'URI que le serveur a appariée. Sur la version cassée, la valeur était insérée à l'état brut, si bien que la barre oblique créait un second segment de chemin et que l'apparieur du SDK répondait -32602 Resource not found: foobar://events/foo/bar — la défaillance exacte du ticket. Il en va de même pour ?, #, %, les espaces et le texte non ASCII.

events_by_query est la moitié qui était invisible : l'ancien balayage /\{(\w+)\}/g ne voyait pas une expression portant un opérateur, si bien qu'aucune saisie topic n'était rendue du tout. Elle apparaît désormais, marquée Facultatif — RFC 6570 supprime toute l'expression lorsque la variable est indéfinie : lire avec le champ vide demande donc foobar://events, et le remplir demande foobar://events?topic=foo%2Fbar. L'aperçu d'URI à côté du titre montre la forme partiellement développée à mesure que vous saisissez, laissant les expressions non remplies telles qu'écrites.

La ressource foobar://events ordinaire est enregistrée délibérément, pas comme remplissage. UriTemplate.match() du SDK compile {?topic} en un \?topic=([^&]+) requis, si bien qu'un modèle seul ne peut pas servir la lecture à vide — match("foobar://events") renvoie null. Un vrai serveur expose la collection non filtrée comme sa propre ressource ; la démonstration fait de même pour que cette étape se résolve réellement.

Le client web et la TUI développent via un même helper partagé, core/mcp/uriTemplate.ts — le formulaire Ressources du web directement, la TUI via InspectorClient.readResourceFromTemplate — et les deux dérivent aussi leurs champs de formulaire de son analyseur, ce qui est la moitié qui rend le partage réel : un formulaire soumet des valeurs sous les noms qu'il a affichés, si bien qu'un analyseur qui déforme un nom laisse silencieusement tomber la valeur au moment de l'expansion. (La CLI n'en est pas consommatrice : elle n'a pas de formulaire de modèle, et son resources/read transmet tel quel le --uri déjà développé.)

Le UriTemplate du SDK est toujours utilisé, mais seulement pour valider un modèle (c'est sa construction qui rejette une expression non fermée). Son expandeur, lui, ne l'est pas, car il est incomplet de cinq manières — chacune mesurée par rapport au SDK épinglé, pas inférée :

Les lignes ; et :3 sont celles qu'un utilisateur voit directement : avec l'analyse du SDK, le formulaire affiche des champs littéralement étiquetés ;id et id:3. La ligne +/# est une corruption silencieuse plutôt qu'un sur-échappement — une adresse IPv6 littérale ou un chemin déjà encodé arrive au serveur altéré.

Un modèle qui ne peut pas être développé du tout — un modificateur hors grammaire ({id:abc}), ou une expression ne déclarant aucune variable ({}, {a,}, {?}) — bloque la lecture plutôt que d'envoyer quelque chose. Choisissez events_malformed (foobar://events/{topic:abc}) pour le constater : Lire la ressource est désactivé, la raison est affichée sous le formulaire, et l'aperçu montre le modèle tel que le serveur l'a déclaré. L'alternative est pire qu'il n'y paraît : x://{} se développerait autrement en x:// sans aucune saisie rendue, si bien que la vérification « tout ce qui est requis est rempli » du formulaire est vraie par vacuité et qu'il lit une URI qui n'est pas le modèle publié par le serveur.

Les littéraux sont également encodés en pct lors de l'expansion (RFC 6570 §3.1) : café/{var} envoie caf%C3%A9/value, et non de l'UTF-8 brut dans le chemin — ce que l'expandeur du SDK ne fait pas non plus. Et les noms qu'un modèle peut utiliser sont le varchar de RFC 6570 plus une tolérance étiquetée pour - et ~ : la suite de conformité rejette {default-graph-uri}, mais de vrais serveurs publient ces noms et l'apparieur du SDK les accepte tels quels, si bien que l'Inspector les développe et marque la variable conforming: false plutôt que de refuser une ressource qui fonctionne manifestement.

Une variable indéfinie est ce qui omet son expression — une variable définie comme chaîne vide se développe (x{?q} donne x?q=, x{;q} donne x;q, selon RFC 6570 §3.2.7). L'expandeur respecte cette distinction, si bien qu'un appelant tel que readResourceFromTemplate peut demander l'une ou l'autre URI. Réduire les deux à un seul cas est une préoccupation de formulaire, pas de modèle : les deux clients initialisent chaque variable déclarée avec "" et une saisie de texte ne peut pas exprimer « défini mais vide », si bien que chaque formulaire abandonne ses valeurs vides (definedValues) à l'entrée.

Le caractère requis est une propriété de l'expression, pas de la variable : RFC 6570 retire les noms indéfinis d'une expression à plusieurs noms, si bien que {a,b} avec seulement a rempli est développable et qu'un formulaire ne doit pas le bloquer. requiredGroups renvoie une entrée par expression non omettable et hasRequiredValues exige que chacune soit satisfaite par l'un quelconque de ses noms — ce qu'aucun drapeau par variable ne peut exprimer dès lors qu'un nom se répète dans plusieurs expressions ({a,b}{a,c} est satisfait en remplissant b et c).#### Extensions annoncées

advertised-extensions-http.json sert echo (toujours) et un outil get_weather conditionné par l'extension io.modelcontextprotocol/tasks (extensionGatedTools) : l'outil est enregistré mais démarre désactivé, et le serveur l'active à réception de notifications/initialized uniquement quand le client a déclaré cette extension dans ses capabilities.extensions.

  1. Connectez-vous — l'Inspector annonce l'extension Tasks par défaut, donc la liste des outils affiche à la fois echo et get_weather.
  2. Ouvrez Server Settings → Advertised Extensions, décochez Tasks (io.modelcontextprotocol/tasks), puis reconnectez-vous.
  3. Le client n'annonce désormais plus d'extensions, le serveur n'active jamais get_weather, et la liste des outils n'affiche que echo.

C'est le bouton de débogage pour un serveur qui modifie légitimement l'enregistrement des outils en fonction de ce que le client annonce. Branche historique avec état uniquement — la branche moderne par requête n'a pas d'oninitialized persistant.

Journalisation, les deux époques

logging-legacy-http.json et logging-modern-http.json servent tous deux logging: true ainsi qu'un outil send_notification qui émet un notifications/message à un niveau choisi. Le premier est un simple serveur HTTP streamable ; le second définit transport.modern: true.

  • Legacy — l'onglet Logs fournit un sélecteur Set Active Level limité à la session et un bouton Set. L'appel de send_notification diffuse le journal dans le panneau.
  • Modern — le même onglet affiche à la place Log Level per Request. Choisissez un niveau pour activer l'option et le client appose _meta["io.modelcontextprotocol/logLevel"] sur chaque requête suivante (vérifiez-le dans le corps de requête de l'onglet Network). Appeler send_notification diffuse le journal sur la réponse SSE de la requête. Repassez-le à Off et le même appel est silencieusement bloqué — la requête omet la clé logLevel, donc le journal n'arrive jamais.

Ce filtrage est conforme à la spécification (« un serveur NE DOIT PAS émettre de notifications/message pour une requête qui n'a pas opté ») car send_notification émet via le extra.log du SDK, à portée de requête et tenant compte du seuil (ctx.mcpReq.log). Sur la branche moderne, il lit l'opt-in logLevel par requête depuis l'enveloppe de la requête et abandonne le message lorsque le client n'a pas opté ou que le niveau est inférieur à la gravité demandée ; sur la branche historique, il respecte le niveau de session via logging/setLevel. Comme il émet via le notify de la requête, la réponse moderne passe en SSE et le journal emprunte le flux de la requête d'origine.

Abonnements aux ressources, les deux époques

subscriptions-legacy-http.json et subscriptions-modern-http.json servent tous deux trois numbered_resources avec subscriptions: true. Le premier sert également un outil update_resource ; le second définit transport.modern: true.

  • Legacy — ouvrez une ressource dans l'onglet Resources et cliquez sur Subscribe. Le client envoie resources/subscribe et la section Subscriptions répertorie l'URI sans indicateur de flux. Appelez update_resource avec cet URI et le serveur met à jour le contenu et émet notifications/resources/updated, en estampillant l'heure de dernière mise à jour de la tuile abonnée.
  • Modern — le même bouton Subscribe envoie à la place subscriptions/listen (son filtre porte resourceSubscriptions plus l'opt-in resourcesListChanged) et se résout sur notifications/subscriptions/acknowledged. La section Subscriptions affiche alors un badge d'état du flux (Connecting… → Listening) dans son en-tête, et se reconnecte en ré-énumérant la liste si le flux de longue durée est interrompu.

La configuration moderne omet délibérément update_resource. La branche moderne du SDK est sans état/par requête (createMcpHandler(() => createMcpServer(config))), donc l'outil s'exécuterait contre une instance de serveur jetable — la modification de contenu ne persisterait pas pour le prochain resources/read, et son resources/updated n'atteindrait pas le flux d'écoute séparé. Plus déroutant qu'utile.

Ainsi, l'aller-retour de notification de mise à jour en direct est démontré sur le serveur historique (session avec état), et le serveur moderne sert au comportement subscribe/listen/badge. Le chemin receive de l'Inspector est indépendant de l'époque, donc un véritable serveur moderne avec état qui achemine resources/updated vers le flux d'écoute pilote la tuile abonnée de la même manière.

Tâches, les deux époques

Legacy (tasks-legacy-http.json) annonce capabilities.tasks (tasks: { list, cancel }) avec les préréglages simple_task / progress_task / elicitation_task. Exécutez l'un de ces outils avec Run as task activé, et l'onglet Tasks l'affiche (rempli via tasks/list), interroge tasks/get, récupère la charge utile avec le tasks/result bloquant, et annule avec tasks/cancel.

Modern (tasks-modern-http.json) définit transport.modern: true et tasksExtension: true, annonce l'extension io.modelcontextprotocol/tasks (SEP-2663) et sert modern_task / modern_input_task. L'onglet Tasks est conditionné par l'extension négociée, et non par capabilities.tasks.

  • Exécutez modern_task en tant que tâche — l'appel tools/call renvoie un CreateTaskResult (resultType: "task", visible dans les onglets Protocol/Network), le client interroge tasks/get (pas de tasks/list), et la tâche terminée intègre son résultat (pas de tasks/result bloquant).
  • Exécutez modern_input_task — la tâche passe à input_required, révélant une sollicitation intégrée via la modale de requête en attente. Y répondre envoie tasks/update avec les inputResponses, et le prochain sondage se termine.

Le SDK v2 a supprimé tout le support des tâches et exclut par époque les méthodes de spécification tasks/* de l'ère moderne des deux côtés. L'Inspector pilote donc lui-même l'extension — la trame resultType: "task" est réécrite au niveau du transport en un CallToolResult portant le handle, et tasks/get / update / cancel empruntent un canal de requête brut avec l'enveloppe moderne complète. Le serveur de test sert tasks/* via un interceptor Express en amont du handler SDK, car la branche moderne du SDK y répondrait -32601.

Le bouton Refresh de l'onglet Tasks relance le sondage des handles déjà connus du client — l'ère moderne n'a pas de liste de tâches côté serveur.

Compilation```bash

npm run build # builds all clients: web → cli → tui → launcher

root@kitploit:~
Clients individuels : `build:web`, `build:cli`, `build:tui`, `build:launcher`. Le build web produit à la fois la SPA navigateur (`clients/web/dist`, Vite) et le runner serveur de prod Node (`clients/web/build`, tsup).

## Tests et le portail de qualité

Chaque client s'auto-valide depuis son propre dossier ; les scripts racine les enchaînent. Il n'existe **pas** de script `test` agrégé à la racine — utilisez `validate` (rapide) ou `coverage` (le portail).

| Script                              | What it does                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `npm run validate`                  | Exécute d'abord les trois gardes durables — `verify:format-coverage` (chaque fichier source suivi est soumis au format), `verify:typecheck-coverage` (chacun atterrit dans un projet tsconfig), `verify:dep-lockstep` (aucune dépendance atteignant un même programme `tsc` depuis deux installations ne présente d'écart entre elles) — puis `test:scripts` (les tests unitaires des parsers des gardes eux-mêmes), puis `validate:core` (le portail `format:check` + `lint` du `core/` partagé), puis pour chaque client : `format:check` + `lint` + **`typecheck`** (cli/tui/launcher ; le web typecheck via `tsc -b` dans son `build`) + `build` + tests unitaires rapides. Le contrôle rapide de la boucle interne.                                                                                                                                                                                                                                                                                                                |
| `npm run coverage`                  | Le **portail ≥90% par fichier** (lignes/instructions/fonctions/branches) sous instrumentation v8, par client. Appliqué en CI. Pour le web, cela exécute aussi le projet d'intégration et couvre le runtime `core/` partagé (y compris `core/json` et `core/client`).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `npm run smoke`                     | Tests de fumée de bout en bout via le launcher construit (dispatch `--help` + cli/tui/web de prod), plus deux tests de fumée Chromium sans tête : un test de fumée de démarrage qui exécute le bundle web de prod et vérifie un premier rendu propre (aucune erreur non capturée — exception synchrone ou rejet non géré, la façon dont un built-in Node atteignant le bundle navigateur se manifeste), et un test de fumée **MCP Apps** (`smoke:web:app`) qui pilote la séquence connect → open app → `data-app-status="ready"` contre un serveur App composable, couvrant le proxy sandbox et le pont de protocole UI.                                                                                                                                                                                                                                                                                                                                   |
| `npm run verify:build-gate`         | Exécute un vrai `vite build` avec un built-in Node forcé dans le graphe navigateur et vérifie que le build **échoue** via le portail #1769 (qui transforme l'avertissement d'externalisation navigateur de Vite en erreur bloquante). Protège contre la dérive de la formulation de l'avertissement lors d'une mise à jour de Vite qui désactiverait silencieusement le portail. Fait partie de `npm run ci`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `npm run verify:format-coverage`    | Analyse les globs `format:check` de chaque `package.json` (uniquement ceux atteignables depuis `validate`), énumère tous les fichiers source suivis, et **échoue** en listant tout fichier non couvert par un glob — le garde durable de l'invariant « chaque fichier source first-party est soumis au format » (#1792). S'exécute en premier dans `validate`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `npm run test:scripts`              | Tests unitaires pilotés par table (`node --test`) pour les parsers purs des gardes eux-mêmes (`scripts/lib/npm-scripts.mjs`, `scripts/lib/tsc-program.mjs` + les helpers exportés de `verify-typecheck-coverage.mjs` et `verify-dep-lockstep.mjs`), un cas par règle qu'ils encodent, plus `scripts/lib/resolve-node-bin.test.mjs` — le résolveur de binaires multi-plateforme (#1939), épinglé sur les formes réelles `bin`/`exports` des paquets que les scripts lancent réellement. S'exécute dans `validate` — et `verify:typecheck-coverage` garde *ce* portail à son tour (atteignable depuis `validate`, ensemble de tests non vide, chaque fichier de test correspondant au glob `test:scripts`), car `node --test` ignore silencieusement un fichier manqué par son glob et sort quand même avec 0.                                                                                                                                                                                                                                                                                                                                                       |
| `npm run verify:typecheck-coverage` | L'analogue typecheck-coverage du précédent (#1791) : pour chaque client Node (auto-découvert depuis le disque — inscrit via les projets de son script `typecheck`, ou pour un client `tsc -b` comme `clients/web` via les `references` de son `tsconfig.json`) il exécute ces projets avec `tsc --listFilesOnly`, les réunit, et **échoue** en listant tout `.ts`/`.tsx`/`.mts`/`.cts` suivi sous le client qui n'atterrit dans aucun projet (ainsi un nouveau config/helper de premier niveau ne peut pas rester silencieusement sans typecheck). Il exige aussi, par défaut-refus, que le TS first-party qu'aucun client ne possède (`test-servers/src`, le `vitest.shared.mts` racine, tout `core/`, et tout nouvel emplacement de premier niveau) atterrisse dans le passage tsc d'un projet client — donc un `*.tsx` de `core` que les projets du web n'atteignent pas est aussi attrapé. Il vérifie également que le portail est câblé (le passage typecheck de chaque client — son script `typecheck`, ou le `tsc -b` du web — est atteignable depuis son `validate`, et la chaîne racine exécute le `validate` de chaque client). S'exécute dans `validate`. |
| `npm run verify:dep-lockstep`       | Protège l'invariant « une version par dépendance qui traverse les installations » (#1896). v2 n'est pas un workspace, donc le projet de test d'un client compile le TypeScript first-party partagé — `core/`, `test-servers/src`, et le `vitest.shared.mts` détenu par la racine, qui résolvent tous leurs dépendances depuis l'installation **racine** — en même temps que les sources propres du client, plaçant le même paquet deux fois dans un même programme `tsc`. À la même version, c'est inoffensif ; en cas de déviation, TypeScript doit mettre en relation deux copies structurellement distinctes de chaque type, ce qui pour une surface générique récursive est exponentiel (zod `4.3.6` contre `4.4.3` a épuisé le tas tsc de 4GB dans `clients/web`). Il dérive son ensemble candidat de **ce qui entre réellement dans chaque programme** (#1965) — chaque projet tsconfig client listé avec `tsc --listFilesOnly` via le `scripts/lib/tsc-program.mjs` partagé, chaque fichier `node_modules` résolu mappé à son installation propriétaire, en conservant les paquets qui atteignent un programme depuis deux installations (un paquet dont les déclarations n'arrivent que via le `.d.ts` d'un autre paquet, comme le fait `@modelcontextprotocol/sdk`, est invisible à un scan des imports first-party). Évalue chaque copie à partir de l'entrée du lockfile pour le chemin d'installation exact que le programme a résolu, ne compare que les installations qui se sont rencontrées dans un même programme, et **échoue par défaut-refus** sur tout désaccord non présent dans la liste d'autorisation annotée `TOLERATED_SKEW` — vide aujourd'hui — avec un paquet autorisé toléré uniquement *au sein d'une même version majeure*. S'exécute dans `validate`. |
| `npm run ci`                        | **Commande obligatoire avant de pousser.** `validate` → `coverage` → `verify:build-gate` → `smoke` → Storybook. Un vrai sur-ensemble de la CI GitHub.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `npm run pack:verify`               | Smoke de publication — voir [Publishing](#publishing).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |

Des scripts par client existent aussi (`validate:web`, `coverage:cli`, `smoke:tui`, …), plus `validate:core` / `format:core` à la racine pour le paquet `core/` partagé, `format:scripts` pour l'outillage `scripts/` racine, et `format:shared` / `lint:shared` pour la surface "shared" racine (`test-servers/src/**`, `vitest.shared.mts`, le `eslint.config.js` racine). Exécutez `npm run format` avant de committer — le `format` racine corrige `core/`, les `scripts/` racine, la surface partagée et chaque client ; `validate` exécute le `format:check` non correctif et fait échouer la CI sur tout fichier non formaté.

**Le linting est sensible au typage.** Les cinq portées ESLint (`clients/{web,cli,tui,launcher}` plus le `core/` racine + le portail shared) activent `@typescript-eslint/no-floating-promises` en `error`, donc une promesse qui n'est ni attendue, ni retournée, ni terminée par `.catch(…)`, ni explicitement ignorée avec `void` échoue au `lint` — et donc au `validate` ([#1959](https://github.com/modelcontextprotocol/inspector/issues/1959)). La règle a besoin des informations de type, donc la config de chaque portée nomme un projet parser ; celle de la portée racine est **`tsconfig.lint.json`**, un projet dédié au lint couvrant `core/**`, `test-servers/src/**`, et `vitest.shared.mts`, qui n'ont pas de tsconfig propre. Il n'émet rien et ne modifie aucun typecheck — mais un nouvel emplacement TS first-party ajouté à la portée de lint racine doit être ajouté à son `include`. Voir **Instructions TypeScript** dans [`AGENTS.md`](https://github.com/modelcontextprotocol/inspector/blob/HEAD/AGENTS.md) pour savoir quand `void` est acceptable.

Pour les règles de test complètes — le portail ≥90% par fichier, où vivent les fichiers de test, les projets unitaires vs. intégration vs. storybook, et la politique `v8 ignore` — voir [`AGENTS.md`](https://github.com/modelcontextprotocol/inspector/blob/HEAD/AGENTS.md).

## Publishing

Le paquet racine `@modelcontextprotocol/inspector` est publié comme **un seul tarball avec un numéro de version unique** — pas de paquets `-web` / `-cli` / `-tui` / `-core` séparés. `npm run build` construit chaque client, puis `prepack` s'exécute avant `npm publish`. Les dépendances d'exécution sont déclarées dans le `package.json` racine ; les builds clients regroupent `@inspector/core` et externalisent les paquets npm résolus depuis l'installation racine.

### Ce qui est livré, et les invariants d'empaquetage

La liste d'autorisation `"files"` du `package.json` racine est la source de vérité pour le tarball. Quelques entrées non évidentes existent parce qu'elles sont lues **à l'exécution** ou ont été silencieusement supprimées par la packlist de npm — ne les retirez pas sans relancer `npm run pack:verify` :

- **Aucune source map.** Les bundlers clients définissent `sourcemap: false` (`clients/{cli,tui}/tsup.config.ts`, `clients/web/tsup.runner.config.ts`) ; Vite et le `tsc` du launcher n'en émettent déjà aucun. Les maps représentent ~la moitié de la taille dépaquetée et ne sont pas nécessaires à l'exécution — déboguez via `npm run dev` sur la source.
- **`clients/web/build` est livré via `clients/web/.npmignore`.** `clients/web/.gitignore` liste `build/`, et la packlist de npm honore ce `.gitignore` imbriqué par-dessus la liste d'autorisation `"files"` racine — donc le runner du serveur web de prod manquait silencieusement dans le tarball pendant que `clients/web/dist` passait (son `.gitignore` ne liste que `dist-ssr`). `clients/web/.npmignore` remplace le `.gitignore` pour la publication, donc `build/` (runner) et `dist/` (SPA) sont tous deux livrés. Les autres clients n'en ont pas besoin — aucun ne livre un `.gitignore` imbriqué.
- **`clients/web/static` livre le proxy sandbox MCP Apps.** `clients/web/static/sandbox_proxy.html` est un fichier source commité (pas un artefact de build), lu depuis le disque à l'exécution par `clients/web/server/sandbox-controller.ts` comme `<runner dir>/../static/sandbox_proxy.html`. Il manquait entièrement dans la liste d'autorisation `"files"` racine, donc chaque build publié faisait échouer l'onglet Apps avec **"Sandbox not loaded"** ([#1859](https://github.com/modelcontextprotocol/inspector/issues/1859)) alors que tout fonctionnait dans le dépôt. Comme le chemin est résolu _relativement à_ `clients/web/build`, le répertoire doit être livré à cet emplacement exact — `pack:verify` vérifie à la fois l'entrée du tarball et le chemin installé sur disque.
- **Une dépendance qui rend du React est empaquetée, pas externalisée.** Un paquet externalisé résout son propre `react` depuis l'endroit où npm l'a placé **lui** dans l'arbre du consommateur, ce qui n'est pas nécessairement l'endroit où le bundle résout le nôtre — npm place un paquet à côté d'un React satisfaisant *sa* plage peer, et ces plages sont plus larges que les nôtres. `ink-form` et `ink-scroll-view` déclarent `">=18"`, donc un projet détenant React 18 les satisfait et les fait remonter (hoist) pendant que le React 19 de l'Inspector se niche en dessous : deux copies de React, et la TUI meurt avec `TypeError: Cannot read properties of null (reading 'useState')` au moment où un formulaire de test d'outil ou une vue scroll monte ([#1952](https://github.com/modelcontextprotocol/inspector/issues/1952)). Les deux sont donc inlinés par `clients/tui/tsup.config.ts` et ne sont **pas** des dépendances racine : le tarball livre leur code à l'intérieur de `clients/tui/build/index.js` plutôt que de faire installer les paquets par les consommateurs. L'empaquetage épingle aussi leurs dépendances transitives à ce que l'installation de ce dépôt a résolu (notamment `ink-select-input@6` via `overrides`, que npm ignore pour un paquet installé comme dépendance). **`ink` est la seule exception, pour des raisons de coût :** l'empaqueter fonctionne mais ajoute ~1.4 MB (`react-reconciler` et `yoga-layout` s'ajoutent, plus un banner `createRequire` pour le CJS inliné), donc il reste externalisé — *pas* parce que son peer `">=19"` le rend sûr, ce qu'il ne fait pas. Ce qui rend cela tolérable, c'est la plage `react` racine : `"^19.0.0"` est délibérément ouverte sur toute la majeure afin que npm puisse dédupliquer notre React avec n'importe quel React 19 qu'un consommateur épingle, laissant un `ink` externalisé sur la même copie que celle utilisée par le bundle. **Rétrécir cette plage rouvre le bug pour le renderer lui-même** — `clients/tui/__tests__/tsupConfig.test.ts` l'épingle au plancher peer d'`ink`, et garde le reste de la séparation ; voir le [README du TUI](https://github.com/modelcontextprotocol/inspector/blob/HEAD/clients/tui/README.md#bundling-react-rendering-dependencies-must-be-inlined-1952).
- **Un numéro de version unique, lu depuis le `package.json` racine.** L'Inspector est livré comme un seul paquet avec une seule version, donc seul le `package.json` **racine** porte une `version` — les quatre `clients/*/package.json`s n'en ont délibérément aucune. Chaque client Node (CLI, TUI et le backend web) résout la version via le lecteur partagé `readInspectorVersion()` dans `core/node/version.ts`, qui remonte jusqu'au manifeste racine (toujours présent dans le tarball). Aucun `package.json` de client n'est lu à l'exécution, donc aucun n'a besoin d'être livré. Le **navigateur** web ne peut pas lire le système de fichiers ; il obtient sa version depuis le backend via `GET /api/config` (voir [#1639](https://github.com/modelcontextprotocol/inspector/issues/1639)).

### `npm run pack:verify` — smoke de publication contre le vrai tarballLes scripts `smoke:*` s'exécutent contre l'arbre de build du dépôt, ce qui n'est **pas** le package publié. `npm run pack:verify` (`scripts/pack-and-verify.mjs`) comble cette lacune : il construit, `npm pack` l'archive publiable (en vérifiant qu'aucune source map n'est embarquée et que les fichiers requis à l'exécution sont présents), installe l'archive dans un **consommateur jetable propre** — un répertoire temporaire neuf où il effectue un véritable `npm install <tgz>` (tire les dépendances d'exécution, exécute `postinstall`), exactement comme le ferait `npx @modelcontextprotocol/inspector` — et pilote le binaire `mcp-inspector` installé de bout en bout : dispatch `--help`, un vrai `--cli tools/list` sur stdio, et un démarrage `--web` de production qui doit servir `/` depuis le `dist` livré. Cela détecte les échecs de chemin/empaquetage du type « fonctionne en `--dev`, casse sous `npx …` ». Cela nécessite un accès réseau (l'installation tire des dépendances), c'est donc un contrôle local / de release, **pas** une partie de la boucle rapide `validate`/`ci`.

### Créer une release

La publication est automatisée par deux jobs déclenchés par release dans [`.github/workflows/main.yml`](https://github.com/modelcontextprotocol/inspector/blob/HEAD/.github/workflows/main.yml) (`github.event_name == 'release'`, tous deux `needs: build`) :

- **`publish`** — le package npm. Exécute `npm run pack:verify` comme porte de pré-publication, vérifie que le tag de release correspond à la version du `package.json` racine, puis `npm publish --access public --provenance` — un unique `npm publish` (v2 n'est pas un espace de travail npm, donc il n'y a pas de `publish-all`/`--workspaces` de type v1), avec une attestation de provenance signée via GitHub OIDC (`id-token: write`, `environment: release`, `NPM_TOKEN`).
- **`publish-github-container-registry`** — l'image conteneur (voir [Docker](#docker)).

Une release v2 est découpée depuis **`main`**, après que le travail du jalon y a été fusionné depuis `v2/main` — pas depuis `v2/main` lui-même. (La ligne v1 publie indépendamment depuis `v1/main` vers le tag `v1-latest` et ne touche jamais `main` ; voir [Statut du dépôt](#mcp-inspector).)

Parce qu'il y a **un seul numéro de version** (seul le `package.json` racine en a un — les clients n'en portent aucun, donc rien à synchroniser et pas d'étape `check-version`), le flux de release comporte trois étapes.

**1. Bump sur `v2/main`, avant la fusion du jalon.** Le bump fait partie du travail du jalon, il appartient donc à la branche de développement et arrive dans `main` avec tout le reste :

Remplacez le vrai numéro d'issue et la release ci-dessous — les commandes sont écrites pour être copiables-collables telles quelles (un bump mineur `2.2.0` → `2.3.0`) :```bash
git checkout -b v2/chore/2010-bump-2-3-0 v2/main
npm version minor --no-git-tag-version   # or major / patch; bump only, no tag
# PR → v2/main

⚠️ --no-git-tag-version est essentiel. Un npm version sans option crée aussi un tag, et le tag atterrirait sur un commit de v2/main — alors que la release doit être coupée depuis main, donc le tag doit pointer vers le commit de merge là-bas (étape 3). Tagger ici crée un tag sur un commit qui n'est jamais publié.

2. Fusionner v2/main → main via la branche habituelle de merge de milestone. Elle porte désormais le bump, donc la release atterrit sur main avec la version déjà correcte.

Entre les étapes 1 et 2, les deux branches diffèrent effectivement, et c'est attendu, pas une dérive : v2/main lit la version en cours de construction tandis que main lit encore celle actuellement publiée. Ce que cet ordre élimine, c'est la dérive post-release — une fois que le merge de milestone atterrit, elles concordent à nouveau, et v2/main n'est jamais laissée derrière main. Si vous voyez v2/main en avance sur main, une release est en cours ; si vous la voyez en retard, quelque chose a mal tourné.

3. Tagger le commit de main et rédiger la Release :```bash git fetch origin main git tag 2.3.0 origin/main && git push origin 2.3.0

then draft & publish a GitHub Release for that tag → triggers publish

root@kitploit:~
⚠️ **Posez le tag sur `origin/main`, pas sur votre `HEAD` local.** `git checkout main && git pull` se résout selon la stratégie de merge ou de rebase que vous avez configurée, donc un `main` local divergent peut silencieusement produire ou rejouer des commits locaux. Poser un tag sur `HEAD` à cet endroit crée un tag sur un commit qui n'est pas sur `origin/main`, et `git push origin <tag>` ne pousse que le tag — laissant une release dont le commit n'a jamais été publié. Nommer explicitement `origin/main` fait pointer le commit tagué exactement là où pointe la branche distante, quel que soit l'état local.

⚠️ **Pas de préfixe `v`.** Les tags de release de ce dépôt sont des `x.y.z` nus — `2.2.0`, `2.1.0`, `2.0.0` — donc posez le tag `2.3.0`, pas `v2.3.0`. Notez que le `tag-version-prefix` par défaut de npm est `v` et que le dépôt ne définit aucun `.npmrc`, donc un simple `npm version` aurait produit un tag préfixé `v` qui ne correspond pas à la convention. Poser le tag à la main (étape 3) est ce qui assure la conformité. L'étape d'assertion du workflow retire un éventuel `v` initial avant de comparer, donc un tag préfixé `v` serait quand même publié — il serait simplement incohérent avec toutes les releases précédentes.

Le commit cible de la release détermine quel workflow s'exécute, donc la publication n'a lieu que lorsqu'une release est créée à partir d'un commit portant ce workflow (v2).

**Pourquoi le bump se fait d'abord sur `v2/main` ([#2010](https://github.com/modelcontextprotocol/inspector/issues/2010)).** Auparavant, il se produisait sur la branche milestone-merge, qui est issue de `main` — le bump n'existait donc qu'*en aval* de `v2/main` et rien ne le ramenait en amont. `v2/main` est resté à `2.0.0` tout au long des releases 2.1.0 et 2.2.0. Ce n'est pas qu'un détail cosmétique : une branche issue d'une branche milestone-merge transporte silencieusement le bump dans une PR sans rapport (c'est arrivé sur [#2009](https://github.com/modelcontextprotocol/inspector/issues/2009), où un correctif de conteneur est arrivé avec un diff `2.0.0 → 2.2.0`), et tout ce qui lisait la version en développement — `readInspectorVersion()`, `--version`, `GET /api/config` — rapportait une version vieille de deux releases.

Ne « corrigez » **pas** une dérive future en fusionnant `main` dans `v2/main`. `main` porte tout l'historique v1 d'avant v2 (conservé via `ec5d8e13 chore: replace main's tree with v2` — ~230 commits que `v2/main` n'a pas), donc une fusion en retour greffe tout cela définitivement dans l'historique de la branche de développement pour livrer une modification de deux fichiers. Bumper d'abord signifie qu'il n'y a rien à fusionner en retour.

### Docker

Une image de conteneur est publiée sur GHCR (`ghcr.io/modelcontextprotocol/inspector`, `linux/amd64` + `linux/arm64`) par le workflow de release. Le [`Dockerfile`](https://github.com/modelcontextprotocol/inspector/blob/HEAD/Dockerfile) est un build en deux étapes : la première étape installe puis effectue un `npm pack` de la tarball publiable ; la seconde étape effectue un `npm install -g` de cette tarball, afin que l'image embarque exactement le même artefact que npm, avec un binaire `mcp-inspector` propre.```bash
# 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

Vous utilisez l'onglet Apps ? Publiez aussi 6275. Le sandbox MCP Apps est un second listener que le navigateur atteint directement, sur MCP_SANDBOX_PORT (par défaut 6275). Rien d'autre n'en a besoin, donc les commandes à port unique ci-dessus suffisent pour une inspection ordinaire — mais l'onglet Apps affiche un widget vide sans cela :```bash docker run --rm -p 127.0.0.1:6274:6274 -p 127.0.0.1:6275:6275
ghcr.io/modelcontextprotocol/inspector

root@kitploit:~
Publiez-le sur le **même numéro de port** à l'intérieur et à l'extérieur. L'URL sandbox est transmise au navigateur via `/api/config` comme `http://localhost:<container port>/sandbox` ; la remapper (`-p 9000:6275`) annonce donc un port que le navigateur ne peut pas atteindre. Utilisez plutôt `-e MCP_SANDBOX_PORT=9000 -p 127.0.0.1:9000:9000`.

**Conservez le préfixe `127.0.0.1:` sur le port publié.** Un simple `-p 6274:6274` publie sur **chaque interface hôte**, exposant l'Inspector sur votre réseau local. Le `HOST=0.0.0.0` du conteneur est un sujet distinct — il régit les interfaces _du conteneur_, et non celles de l'hôte — l'option `DANGEROUSLY_BIND_ALL_INTERFACES`, qui protège une liaison générique en dehors d'un conteneur, ne couvre donc pas ce cas. C'est plus critique ici que pour une application web ordinaire : le backend lance des processus à la demande, `GET /` incorpore le jeton API dans le HTML servi, et une requête arrivant **sans** en-tête `Origin` ignore complètement la liste d'origines autorisées — pour tout client autre qu'un navigateur, le jeton API est donc la seule protection. Une publication plus large nécessite une véritable barrière de contrôle d'accès devant l'Inspector — un proxy inverse authentifiant, un tunnel SSH, un réseau privé. Définir votre propre `MCP_INSPECTOR_API_TOKEN` ne **remplace** pas cette barrière : `GET /` divulgue le jeton en cours d'utilisation, donc un jeton personnalisé est récupéré aussi facilement qu'un jeton généré.

**Conserver les serveurs que vous ajoutez.** L'Inspector enregistre votre liste de serveurs dans `$HOME/.mcp-inspector/mcp.json`, qui dans l'image correspond à `/home/node/.mcp-inspector/mcp.json` — dans la couche inscriptible du conteneur ; `--rm` l'élimine donc et chaque exécution démarre avec une liste vide. Montez un volume à cet emplacement pour la conserver :```bash
docker run --rm -p 127.0.0.1:6274:6274 \
  -v mcp-inspector-data:/home/node/.mcp-inspector \
  ghcr.io/modelcontextprotocol/inspector

Le même volume conserve également les jetons OAuth et l'état stocké, de sorte qu'un serveur autorisé reste autorisé d'une exécution à l'autre. Utilisez -e MCP_CATALOG_PATH=/some/other/path.json pour placer le catalogue ailleurs — montez un volume couvrant le répertoire vers lequel vous le pointez. Si vous montez en bind-mount un répertoire hôte au lieu d'un volume nommé (-v "$PWD/inspector-data:/home/node/.mcp-inspector"), le répertoire conserve la propriété de l'hôte, donc sous Linux ajoutez --user "$(id -u):$(id -g)" ou chown le à l'uid 1000 — sinon l'utilisateur non-root node ne peut pas écrire et l'ajout d'un serveur échoue avec EACCES.

Mise à niveau depuis une image antérieure à ce correctif ? Les images précédentes ne créaient pas /home/node/.mcp-inspector, donc Docker créait le point de montage du volume en tant que root et l'utilisateur non-root node ne pouvait pas y écrire. Un volume vide se répare tout seul au premier lancement d'une image actuelle (Docker applique la propriété du répertoire de l'image à un volume vide), mais un volume qui contient déjà des fichiers conserve son ancienne propriété root et échoue toujours avec EACCES. Corrigez-le une fois :```bash docker run --rm -u 0 --entrypoint chown
-v mcp-inspector-data:/data ghcr.io/modelcontextprotocol/inspector
-R node:node /data

root@kitploit:~
L'image utilise par défaut `--web` lié à `0.0.0.0:6274` avec l'ouverture automatique du navigateur désactivée ; remplacez les arguments pour exécuter un autre mode (`docker run --rm ghcr.io/modelcontextprotocol/inspector --cli …`). Passez `-e MCP_INSPECTOR_API_TOKEN=…` pour définir un jeton connu (sinon un jeton est généré et affiché dans les journaux), ou `-e DANGEROUSLY_OMIT_AUTH=true` pour désactiver l'authentification. La liaison à `0.0.0.0` (toutes les interfaces réseau) est refusée par défaut hors conteneur — elle expose le backend qui lance des processus au réseau local — l'image opte donc explicitement pour `DANGEROUSLY_BIND_ALL_INTERFACES=true` (déjà défini dans le `Dockerfile`) ; un simple `HOST=0.0.0.0` sans cet indicateur se termine par une erreur. Si vous **reconfigurez le port publié** (`-p 127.0.0.1:8080:6274`), l'origine du navigateur (`http://localhost:8080`) ne correspond plus au port dans le conteneur, alors définissez `-e ALLOWED_ORIGINS=http://localhost:8080,http://127.0.0.1:8080` (ou exécutez `-e CLIENT_PORT=8080 -p 127.0.0.1:8080:8080`) sinon les connexions échoueront avec une erreur 403. `ALLOWED_ORIGINS` **remplace** la liste par défaut plutôt que de la fusionner, donc listez chaque forme de loopback à partir de laquelle vous naviguerez (voir le [README web](https://github.com/modelcontextprotocol/inspector/blob/HEAD/clients/web/README.md#host-binding--the-origin-allow-list)). L'image s'exécute en tant qu'utilisateur non-root `node` et possède un `HEALTHCHECK` qui sonde l'interface web — il suppose le mode `--web` par défaut, donc ajoutez `--no-healthcheck` lors de l'exécution de `--cli`/`--tui` (qui n'ont pas de serveur web).

## Contribution — `AGENTS.md` et `CLAUDE.md`

**[`AGENTS.md`](https://github.com/modelcontextprotocol/inspector/blob/HEAD/AGENTS.md) est le contrat pour modifier ce codebase, et il s'applique aussi bien aux humains qu'aux agents IA.** Ce n'est pas un simple texte générique pour agents — il contient les vraies conventions du projet : le flux de travail basé sur les issues et le tableau, les règles de branches/labels, les normes TypeScript et Mantine/React, les exigences de test et de couverture, ainsi que le passage obligatoire avant de pousser. Lisez-le avant de faire des modifications et tenez-le à jour lorsque vous changez la structure, les outils ou les règles.

`CLAUDE.md` est le point d'entrée que l'agent [Claude Code](https://claude.com/claude-code) charge automatiquement ; il inclut simplement `AGENTS.md` et ce README, de sorte que les agents et les humains travaillent à partir de la même source de vérité. Si vous utilisez un autre agent qui lit `AGENTS.md`, vous obtenez les mêmes règles.

Une règle clé qu'il convient de mentionner ici : **tout le travail est piloté par les issues.** Avant de commencer, trouvez ou créez une issue de suivi sur le tableau du projet v2 ; ouvrez les PR contre `v2/main` avec `Closes #<issue>`. Les recettes exactes (labels, identifiants de tableau, statuts) se trouvent dans `AGENTS.md`.

## Licence

MIT.
Télécharger l’outil
ConfigurationDémontreIssue
mcp-app-http.json (ère legacy)Une application MCP (ressource UI + outil d'application) dans l'onglet Applications#1859
modern-mrtr-http.jsonUn seul aller-retour MRTR—
mrtr-showcase-http.jsonChaque préréglage MRTR dans un seul serveur#1860
modern-network-http.jsonOnglet Réseau : en-têtes Mcp-* + taxonomie des erreurs#1628
xmcpheader-modern-http.jsonOnglet Outils : miroir x-mcp-header et exclusions#1632
pagination-http.jsonRécupération des listes page par page#1721
structured-output-http.jsonOnglet Outils : section structuredContent d'un résultat#1908
duplicate-tool-names-http.jsonUn tools/list qui répète un nom d'outil#1957
nullable-fields-http.jsonOnglet Outils : arguments nullables (anyOf + null)#1928
rfc6570-templates-http.jsonOnglet Ressources : expansion des modèles de ressources RFC 6570#1919
advertised-extensions-http.jsonEnregistrement d'outils conditionné par les extensions annoncées#1739
logging-{legacy,modern}-http.jsonJournalisation, les deux ères#1629
subscriptions-{legacy,modern}-http.jsonAbonnements aux ressources, les deux ères#1630
tasks-{legacy,modern}-http.jsonTâches, les deux ères#1631
PréréglageComportement
mrtr_confirmUn seul aller-retour
mrtr_two_stepDeux tours d'élicitation via requestState
mrtr_sampleÉchantillonnage intégré → le panneau Sampling
mrtr_rootsroots/list intégré, répondu automatiquement et silencieusement depuis les racines configurées (pas de modale)
mrtr_edgeUn tour uniquement inputRequests, puis un tour uniquement requestState
mrtr_emptySe termine avec un résultat vide — pas de content, pas de structuredContent
mrtr_loopNe se termine jamais → déclenche la limite MRTR_MAX_ROUNDS
OutilRéponse
trigger_header_mismatch400 / -32020
trigger_missing_capability400 / -32021
trigger_unsupported_version400 / -32022 (avec data.supported)
trigger_method_not_found404 / -32601
FormeComportement du SDK
{a,b}joint les valeurs en brut — aucun encodage, préfixe d'opérateur abandonné
{;id}; est absent de sa liste d'opérateurs, si bien que la variable s'analyse comme ;id
{id:3}le modificateur de préfixe est intégré au nom, donnant id:3
{+v} / {#v}encodeURI déforme les [/] réservés ([::1] → %5B::1%5D) et double-encode les triplets pct (%2F → %252F)
{v}encodeURIComponent laisse les sous-délimiteurs !'()* nus, que RFC 6570 exige encodés