Le serveur MCP : outils, résultats et déploiement
Sur cette page
Leçon réécrite le 3 novembre 2026 d’après la page Build an MCP server de la documentation Plugins d’OpenAI et le code du SDK TypeScript de MCP (1.32.2). L’ancienne leçon décrivait des « actions » (
defineAction,ActionError) d’un SDK inexistant ; ce rôle revient aux outils MCP. Sources : Build an MCP server · SDK TypeScript MCP.
Le serveur, sa carte d’identité et ses instructions
Un serveur MCP se crée avec un nom et une version stables. Il peut aussi renvoyer, à l’initialisation, un champ instructions que ChatGPT et Codex lisent en plus des métadonnées des outils. Réservez-le aux consignes qui valent pour tous les outils — un ordre d’appel obligatoire, une limite de débit commune — et placez l’essentiel dans les 512 premiers caractères. N’y recopiez pas les descriptions des outils, et n’essayez pas d’y changer la personnalité du modèle.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const server = new McpServer(
{ name: "acme-projects", version: "1.0.0" },
{
instructions:
"Avant de modifier un projet, appelle get_project pour confirmer son identifiant et son statut.",
}
);
Un outil par action
Créez un outil par action distincte : list_projects, get_project, update_project plutôt qu’un outil à tout faire doté de nombreux modes. Chaque outil réunit un nom orienté action, un titre lisible, une description qui dit quand s’en servir, un schéma d’entrée explicite, un schéma de sortie s’il renvoie des données structurées, des annotations exactes et un gestionnaire qui autorise la requête avant d’agir. Le modèle se fonde sur ces métadonnées pour décider s’il appelle l’outil et comment : traitez-les comme une partie de l’expérience utilisateur.
import { z } from "zod";
const projet = z.object({ id: z.string(), name: z.string(), status: z.enum(["active", "archived"]) });
server.registerTool(
"get_project",
{
title: "Consulter un projet",
description:
"À utiliser quand l'utilisateur veut le détail d'un projet précis de son espace Acme. " +
"Nécessite l'identifiant renvoyé par list_projects.",
inputSchema: { id: z.string().min(1) },
outputSchema: { project: projet },
annotations: { readOnlyHint: true, openWorldHint: false, destructiveHint: false },
},
async ({ id }, extra) => {
const utilisateur = await authentifier(extra); // votre contrôle d'accès (leçon 8)
const trouve = await chargerProjet(utilisateur, id); // votre accès aux données
if (!trouve) {
return {
isError: true,
content: [{ type: "text", text: `Aucun projet ${id} accessible avec ce compte.` }],
};
}
return {
structuredContent: { project: trouve },
content: [{ type: "text", text: `Projet « ${trouve.name} » (${trouve.status}).` }],
};
}
);
authentifier et chargerProjet sont vos propres fonctions. Le point important est leur place : l’autorisation se vérifie dans le serveur, à chaque requête. Ne comptez jamais sur le modèle pour décider si un utilisateur a le droit d’accéder à une donnée.
Des résultats utiles sans interface
Un résultat d’outil peut porter trois choses :
structuredContent: des données concises, que le modèle peut examiner et réutiliser dans les appels suivants ;content: du texte, ou un autre contenu MCP, qui l’aide à répondre ;_meta: des données destinées au client, cachées au modèle.
Renvoyez assez d’informations pour que le modèle puisse finir le travail sans composant visuel, avec des identifiants stables que les outils suivants réutiliseront. Deux comportements du SDK méritent d’être connus. Un outil qui déclare un outputSchema doit renvoyer un structuredContent conforme dans tous ses résultats réussis, sinon le SDK lève une erreur de validation. Et une erreur métier — un projet introuvable, une action refusée — se signale par un résultat isError: true accompagné d’un texte explicatif, que le modèle peut relayer à l’utilisateur ; ce type de résultat échappe à la validation du schéma de sortie.
Ne mettez jamais de secrets, de jetons d’accès ou de données personnelles inutiles dans un résultat, et ne prenez pas _meta pour une cachette sûre : ce champ est caché au modèle, il n’est pas protégé.
Annotations et élicitation
Les annotations suivent le comportement réel de l’outil : readOnlyHint à true seulement s’il ne peut rien modifier, destructiveHint à true s’il peut produire un effet irréversible ou difficile à défaire, openWorldHint à true s’il touche l’internet public ou des entités externes ouvertes. Elles aident ChatGPT et Codex à choisir le bon comportement de confirmation ; elles ne remplacent ni l’autorisation, ni la validation, ni la confirmation dans votre serveur.
Quand le serveur a besoin d’une information structurée absente de l’appel — une date, un choix entre deux options —, il peut la demander par élicitation, un mécanisme du protocole MCP. Limitez-la à ce que l’utilisateur peut raisonnablement fournir ; ne vous en servez jamais pour recueillir des secrets ou contourner l’authentification normale.
Comptes multiples et connaissance d’entreprise
Deux compatibilités optionnelles améliorent l’intégration. Pour aider les utilisateurs qui connectent plusieurs comptes, exposez un outil de profil, authentifié et en lecture seule, marqué _meta["openai/profile"]: true : OpenAI s’en sert pour identifier les comptes connectés et aider l’utilisateur à les distinguer. Le profil se déduit des identifiants validés de la requête, et chaque appel reste limité à ces identifiants.
La fonction company knowledge de ChatGPT peut, elle, utiliser les outils en lecture seule de votre serveur. Pour qu’un plugin soit éligible comme source, implémentez les schémas d’entrée standard des outils search et fetch, marquez les autres outils de lecture readOnlyHint: true, et renvoyez des URL absolues, ouvrables par l’utilisateur, pour les sources que le modèle doit citer.
Déployer le point d’accès
Une soumission publique exige un serveur à une adresse HTTPS stable et publiquement joignable. Ce point d’accès doit prendre en charge le transport streamable HTTP, répondre à une URL stable (en général terminée par /mcp), tenir la latence et la disponibilité que demandent vos workflows, atteindre vos services et vos données, préserver les frontières d’authentification et d’autorisation, et produire des journaux et des métriques sur les échecs d’initialisation et d’appel d’outil.
Le Secure MCP Tunnel d’OpenAI permet de connecter un serveur privé à ChatGPT, mais il ne satisfait pas les exigences d’une soumission publique, pas plus qu’un tunnel temporaire ou une adresse locale. Si le serveur doit rester privé, placez devant lui un proxy HTTPS public : l’mTLS géré par OpenAI authentifie ChatGPT comme client, OAuth 2.1 authentifie vos utilisateurs, et une liste blanche d’adresses IP — avec les plages publiées par OpenAI pour les connecteurs ChatGPT, mises à jour automatiquement — peut s’y ajouter, sans jamais remplacer l’authentification.
Avant la mise en production : identifiants injectés par le gestionnaire de secrets de l’hébergeur, serveur d’autorisation et redirections configurés, délais et limites de débit sur les outils coûteux ou visibles de l’extérieur, réponses de débogage et données personnelles superflues retirées, journaux vérifiés pour qu’ils ne contiennent ni jetons ni résultats sensibles. Après le déploiement, rejouez MCP Inspector sur l’adresse de production.
Faire évoluer sans casser
Gardez les noms et les schémas des outils publiés rétrocompatibles : ajoutez des champs ou des outils, ne cassez pas les contrats existants. Si les métadonnées changent, rafraîchissez la connexion et rejouez votre jeu d’évaluation avant de soumettre. Pour une interface, changez l’identifiant de la ressource quand le HTML, le JavaScript ou le CSS évolue d’une manière qui pourrait casser un composant mis en cache.
Rappels de sécurité
- Toute entrée d’outil est non fiable : validez-la, et appliquez l’autorisation côté serveur
- Exigez une confirmation pour les écritures lourdes de conséquences
- Gardez secrets et données sensibles hors des métadonnées et des résultats
- Journalisez de quoi enquêter, sans identifiants ni données personnelles superflues
- Limitez le débit des actions coûteuses ou visibles de l’extérieur
Points clés à retenir
- Un serveur nommé et versionné, avec des
instructionscourtes et transverses - Un outil par action, avec titre, description d’intention, schémas, annotations et autorisation dans le gestionnaire
structuredContentconforme au schéma de sortie,isError: truepour les erreurs métier, rien de secret dans_meta- Publication : HTTPS stable et public en streamable HTTP ; le Secure MCP Tunnel ne suffit pas
- Contrats rétrocompatibles et identifiants de ressource versionnés