Développement IA

Comment Claude Code, Codex et les autres agents de code IA appellent les outils MCP : JSON-RPC, Tool Schema et permissions (2026)

Environ 16 min

Le texte protocole a déjà séparé Schema et Function Calling. Celui-ci ne tombe que sur les agents de code : le modèle remplit les arguments ; l’hôte envoie du JSON-RPC. Connecter, lister, Schema, puis permission sont quatre pas — pas la phrase « brancher MCP ».

Avant de lire

Pas Ce qu’il verrouille Qui exécute
Config .mcp.json / config.toml stdio ou http ; une url a besoin de type
Découvrir tools/list + inputSchema L’hôte liste ; le modèle ne voit pas le fil
Appel arguments sur tools/call Le modèle remplit le JSON ; l’hôte envoie le RPC
Permission mcp__server__tool / enabled_tools Connecté ≠ exécution auto

MCP est un protocole hôte-serveur. Le modèle voit des noms d’outil et un Schema de paramètres.

C’est l’hôte qui appelle MCP, pas le modèle

Claude Code, Codex et Cursor sont des clients MCP. Après le début de session, l’hôte se connecte, envoie tools/list en JSON-RPC 2.0 et passe chaque inputSchema au modèle. Le modèle n’émet que du JSON d’arguments. L’hôte envoie tools/call et crée les effets de bord. Sans ce saut, MCP n’est qu’un fichier de config.

  • Config : comment le serveur démarre. stdio est un processus local. http est distant. Les deux sont des objets, pas un accord verbal.
  • Schema : order_id doit passer inputSchema. Un object large invite des clés inventées.
  • Permission : Claude écrit mcp__serveur__outil. Codex utilise enabled_tools et un mode d’approbation. Cursor utilise mcpAllowlist. Connecter un serveur n’ouvre pas ces portes.

Schema contre Function Calling au niveau protocole : MCP Tool Schema vs Function Calling. Le JSON d’outil propre à un agent de code : Agents de code IA et appels d’outils JSON. Pourquoi Schema est une porte : Pourquoi l’IA a besoin de JSON Schema. Ce texte ne redéfinit pas ces mots. Il montre seulement comment l’hôte fait tourner MCP.

Premier objet : la config serveur

Le fichier projet de Claude Code est .mcp.json à la racine du dépôt. Il peut aller en contrôle de version. Les entrées privées vivent dans ~/.claude.json. Une url doit porter type : http (streamable-http est un alias) ou sse / ws. Une url sans type est un stdio cassé et est sautée. Service de commandes fictif — pas de secrets réels :

{
  "mcpServers": {
    "orders": {
      "type": "stdio",
      "command": "node",
      "args": ["./servers/orders-mcp.js"]
    }
  }
}

Codex écrit le même fait dans ~/.codex/config.toml ou .codex/config.toml d’un projet de confiance, sous [mcp_servers.orders]. stdio utilise command / args. Le distant utilise url. Cursor utilise .cursor/mcp.json ou le fichier utilisateur ; l’éditeur et le CLI le partagent. Un nouveau serveur de projet attend une approbation dans Claude Code. Il ne se connecte pas tout seul.

Deuxième objet : tools/list et inputSchema

Une fois connecté, l’hôte demande quels outils existent. C’est du JSON-RPC, pas un HTTP que le modèle envoie :

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list"
}

Le serveur renvoie une table. Chaque ligne est un nom, une description et un JSON Schema. La spec exige une capacité tools. La liste peut changer avec l’autorisation. Elle ne doit pas jitter par connexion. L’agent de code en fait des outils visibles du modèle. Dans Claude Code le nom appelable est mcp__orders__lookup_order. Un serveur de plugin embarque aussi le nom du plugin. Si la table est grande, les clients officiels différent les outils et les déplient à la recherche. Ne pas verser toute la table dans le contexte par défaut.

{
  "name": "lookup_order",
  "description": "Look up an order by ID. Confirm before any refund.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "order_id": { "type": "string", "pattern": "^[A-Z]-\\d{4}$" }
    },
    "required": ["order_id"],
    "additionalProperties": false
  }
}

Troisième objet : le modèle remplit, l’hôte tools/call

Le modèle voit le nom publié par l’hôte. Dans une session Claude Code, un appel ressemble à ceci :

{
  "type": "tool_use",
  "id": "call_3c91",
  "name": "mcp__orders__lookup_order",
  "input": { "order_id": "A-1042" }
}

L’hôte vérifie input contre inputSchema, puis traduit en RPC envoyé au serveur. Le nom sur le fil est le lookup_order enregistré, sans préfixe mcp__ :

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "lookup_order",
    "arguments": { "order_id": "A-1042" }
  }
}

En succès, renvoyer content. En échec, mettre isError et écrire une raison en objet. Ne pas laisser tomber une phrase de prose. Le modèle lit le renvoi, pas la socket.

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [
      { "type": "text", "text": "{\"order_id\":\"A-1042\",\"status\":\"paid\"}" }
    ],
    "isError": false
  }
}

Quatrième objet : permission d’exécution

La spec MCP dit qu’un appel d’outil doit pouvoir être refusé. Les agents de code écrivent ce refus en règles, pas en prompt. Claude Code utilise le nom appelable complet :

{
  "permissions": {
    "allow": ["mcp__orders__lookup_order"],
    "ask": ["mcp__orders__create_note"],
    "deny": ["mcp__orders__refund_order"]
  }
}

L’ordre officiel reste deny, ask, allow. Un deny nu mcp__* retire MCP du contexte. Codex utilise enabled_tools comme liste blanche, disabled_tools comme liste noire ensuite, et auto / prompt / approve par défaut, surchargeable par outil. Cursor demande par défaut ; mcpAllowlist dans permissions.json s’écrit orders:lookup_order. Les portes Shell / Git / fichiers : Modèle de permissions des agents de code. MCP est une quatrième table, pas un substitut de ces trois.

Cinq pas vers un MCP exécutable

Ne pas partir de « veuillez brancher MCP ». Écrire des objets :

  1. Écrire d’abord la config serveur. Choisir stdio ou http. Mettre type sur chaque url. Les secrets restent en variables d’environnement, pas dans le dépôt.
  2. Lancer tools/list vous-même. Noter noms et required. Un outil sans Schema ne va pas au modèle.
  3. Serrer inputSchema. pattern, enum, additionalProperties: false. Une consultation ne doit pas gagner une clé de remboursement.
  4. Écrire la permission par outil. Consultation en lecture sur allow. Écritures sur ask. Remboursement sur deny. Codex : d’abord couper la table avec enabled_tools.
  5. Garder trois traces JSON d’un succès : tool_use, tools/call, result. Si elles s’alignent, le saut est réel.

Comment Schema devient une porte : API Agents + JSON Schema. Ce texte demande seulement que chaque pas MCP d’un agent de code soit un objet envoyé par l’hôte.

Relire un écart MCP en étalant trois traces JSON

Un échec est souvent un décalage de nom, de Schema ou de permission — pas un modèle « incapable d’appeler MCP ».

  • formater inputSchema à côté d’arguments. Chercher champs manquants et clés en trop.
  • Avec extraire en JSONPath, extraire $.params.name et $.input.order_id.
  • JSON Diff un passage et un refus. Corriger règle et Schema avant le prompt.
  • Vérifier le nom hôte mcp__orders__lookup_order contre le nom RPC lookup_order. Les serveurs plugin ajoutent un segment plugin.
  • Ne pas coller config, jetons ou journaux de session dans un formateur public. JSONSort tourne dans le navigateur, en local.

Si les trois traces se reconstruisent contre Schema, vous avez collecté MCP. Sinon vous avez collecté « j’ai cliqué Connecter ».

Pièges courants

  • Prendre la connexion pour un appel. Connecter signifie seulement qu’un processus ou un HTTP est debout. Sans list, Schema et permission, le modèle ne peut toujours pas appeler.
  • Prendre MCP pour Function Calling. Pour le modèle les deux sont remplir du JSON. Pour l’hôte, MCP ajoute un saut JSON-RPC. Le texte protocole a déjà séparé les mots. Ici il suffit de voir le saut.
  • Une url sans type. Claude Code la saute comme un stdio cassé. Le serveur distant disparaît.
  • Configurer un serveur et ne jamais couper les outils. Une grande table entre dans le contexte. Le différé officiel et enabled_tools existent pour cela.
  • Mettre « auto-approuver tous les MCP » par défaut. L’approbation globale Cursor, le bypass Claude et l’auto partout Codex sont pour des environnements isolés. Les sessions du jour s’écrivent par outil.
  • Prendre le .mcp.json de projet pour un fichier privé. Il est partagé en contrôle de version. Les collègues verront une approbation en attente. Les secrets restent en config utilisateur.

FAQ

L’agent de code envoie-t-il lui-même du JSON-RPC ?

Non. Le modèle remplit un nom d’outil et des arguments. L’hôte construit id, method et params. Si le journal montre tools/call, c’est le client, pas le modèle.

Quelle porte d’abord, inputSchema ou permission ?

La forme d’abord, puis l’exécution. Si Schema échoue, ne pas envoyer de RPC. S’il passe, ask ou deny peuvent encore partir. Les portes verrouillent des choses différentes.

stdio ou http ?

Fichiers locaux ou processus local : stdio. Service hébergé : http. Claude Code préfère http pour le distant ; SSE est déprécié. WebSocket convient à un serveur qui pousse des événements.

S’il y a trop d’outils ?

Claude Code peut différer les outils MCP et les déplier à la recherche. Codex coupe la table avec enabled_tools. Ne pas verser cinquante outils dans une session.

Un subagent peut-il utiliser ces outils MCP ?

Cela dépend du produit. Permissions et allowed-tools de Claude Code veulent le nom appelable complet. Les subagents de l’API Agents héritent MCP, pas vos function. Ne pas supposer qu’une table se copie dans chaque session enfant.

Conclusion

Un agent de code qui appelle MCP n’est pas un modèle qui frappe le serveur. L’hôte liste, le modèle remplit du JSON, l’hôte ensuite tools/call. Schema et permission sont deux portes.

Écrivez quel nom d’outil, quel Schema et quelle règle A-1042 emprunterait. Si vous pouvez pointer trois objets, MCP est connecté. Les modèles changent. Ces trois-là doivent d’abord tenir.

Pour aller plus loin

Journal : Première publication