Construire Son Premier Serveur MCP
Le Model Context Protocol (MCP) est ce qui se rapproche le plus d’un adaptateur universel dans l’écosystème de l’IA. Il définit comment les modèles d’IA découvrent et appellent des outils, et comment ces outils renvoient des résultats structurés. Une fois votre serveur MCP opérationnel, n’importe quel client compatible — Claude, Claude Code, ou tout framework d’agents qui parle MCP — peut l’utiliser sans code de connexion supplémentaire.
Ce guide explique comment construire un vrai serveur MCP de zéro. Pas un exemple jouet, mais un pattern que vous pouvez étendre pour une utilisation en production.
Ce Qu’Est Vraiment MCP
MCP est un protocole JSON-RPC 2.0 sur stdio (ou HTTP+SSE pour les serveurs distants). Le client et le serveur échangent un petit ensemble de types de messages :
tools/list— le client demande quels outils sont disponiblestools/call— le client invoque un outil avec des argumentsresources/list/resources/read— le client lit des fichiers, des bases de données, ou tout contexte que le serveur expose
Le serveur déclare ses outils à l’avance avec un JSON Schema pour l’entrée de chaque outil. Le client utilise ce schéma pour construire des appels valides. Toute l’interaction est sans état du point de vue du client — le serveur gère en interne tout état nécessaire.
Cette simplicité est le but. MCP n’est pas un framework. C’est un contrat qui vous permet d’envelopper n’importe quoi — une base de données Postgres, un cluster Kubernetes, un dépôt GitHub, une API interne propriétaire — et de l’exposer comme un ensemble de fonctions typées et appelables que tout modèle d’IA peut utiliser.
Configuration
Vous aurez besoin de Node.js 18+ et d’un projet TypeScript. Le SDK officiel MCP réduit le code répétitif au minimum :
mkdir mcp-server && cd mcp-servernpm init -ynpm install @modelcontextprotocol/sdk zodnpm install -D typescript @types/node tsxAjoutez un tsconfig.json :
{ "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "strict": true, "outDir": "./dist" }, "include": ["src"]}Et un script de démarrage dans package.json :
{ "scripts": { "dev": "tsx src/index.ts", "build": "tsc", "start": "node dist/index.js" }}Construction du Serveur
Créez src/index.ts. La structure est toujours la même : initialiser le serveur, enregistrer les outils, connecter au transport stdio.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";import { z } from "zod";
const server = new McpServer({ name: "my-mcp-server", version: "1.0.0",});Enregistrez maintenant les outils. Chaque outil a un nom, une description que le modèle utilise pour décider quand l’appeler, et un schéma d’entrée :
server.tool( "get_weather", "Obtenir les conditions météorologiques actuelles pour une ville", { city: z.string().describe("Le nom de la ville pour obtenir la météo"), units: z.enum(["celsius", "fahrenheit"]).default("celsius"), }, async ({ city, units }) => { // Votre implémentation réelle ici const data = await fetchWeather(city, units); return { content: [ { type: "text", text: `Météo à ${city} : ${data.temp}°${units === "celsius" ? "C" : "F"}, ${data.condition}`, }, ], }; });Connectez le serveur au transport stdio :
const transport = new StdioServerTransport();await server.connect(transport);C’est le squelette complet du serveur. Tout le reste consiste à ajouter des outils.
Patterns de Conception des Outils
La qualité de votre serveur MCP repose sur la façon dont vous concevez les outils. Quelques patterns importants :
Des outils étroits et composables plutôt que des monolithes larges
Un outil qui fait une seule chose est plus utile qu’un qui en fait plusieurs. Le modèle peut combiner des outils étroits de manières inattendues ; un outil large le contraint.
Évitez ceci :
server.tool("manage_database", "Faire n'importe quoi avec la base de données", { operation: z.enum(["read", "write", "delete", "schema"]), query: z.string(), // ... de nombreux paramètres optionnels})Préférez ceci :
server.tool("query_records", "Exécuter une requête SELECT et retourner les lignes en JSON", { table: z.string(), where: z.string().optional().describe("Clause SQL WHERE, ex. 'status = active'"), limit: z.number().default(50),})
server.tool("get_schema", "Retourner les définitions de colonnes d'une table", { table: z.string(),})Décrivez les entrées avec précision
Le modèle lit vos chaînes describe() pour savoir quoi passer. Traitez-les comme de la documentation pour un développeur qui n’a jamais vu votre code :
{ date_range: z.string().describe( "Plage de dates ISO 8601 au format YYYY-MM-DD/YYYY-MM-DD. " + "Exemple : 2025-01-01/2025-03-31" ),}Les descriptions vagues produisent des entrées incorrectes. Les descriptions précises produisent des entrées correctes.
Retournez du texte structuré, pas du JSON brut
Les modèles analysent le texte plus fiablement que les blobs JSON bruts dans les résultats d’outils. Formatez votre sortie :
return { content: [ { type: "text", text: [ `${rows.length} enregistrement(s) trouvé(s) :`, ...rows.map(r => `- ${r.id} : ${r.name} (${r.status})`), ].join("\n"), }, ],};Gestion des erreurs
Retournez les erreurs comme résultats d’outils, pas comme exceptions levées. Le flag isError: true signale l’échec à l’agent :
async ({ table, where }) => { try { const rows = await db.query(table, where); return { content: [{ type: "text", text: formatRows(rows) }] }; } catch (err) { return { content: [ { type: "text", text: `Requête échouée : ${err instanceof Error ? err.message : "erreur inconnue"}. ` + `Vérifiez que le nom de la table est correct et que la clause WHERE utilise des noms de colonnes valides.`, }, ], isError: true, }; }}Un Exemple Réel : Serveur d’Outils GitHub
Voici un outil concret qui enveloppe l’API GitHub pour permettre à un agent de lire le contenu des dépôts :
import { Octokit } from "@octokit/rest";
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
server.tool( "get_file_contents", "Lire le contenu d'un fichier depuis un dépôt GitHub", { owner: z.string().describe("Propriétaire du dépôt (utilisateur ou organisation)"), repo: z.string().describe("Nom du dépôt"), path: z.string().describe("Chemin du fichier dans le dépôt"), ref: z.string().optional().describe("Branche, tag ou SHA de commit. Par défaut, la branche principale."), }, async ({ owner, repo, path, ref }) => { try { const { data } = await octokit.rest.repos.getContent({ owner, repo, path, ref });
if (Array.isArray(data)) { const listing = data.map(f => `${f.type === "dir" ? "📁" : "📄"} ${f.name}`).join("\n"); return { content: [{ type: "text", text: `Contenu du répertoire ${path} :\n${listing}` }], }; }
if (data.type !== "file" || !data.content) { return { content: [{ type: "text", text: `${path} n'est pas un fichier lisible` }], isError: true, }; }
const content = Buffer.from(data.content, "base64").toString("utf-8"); return { content: [{ type: "text", text: `Contenu de ${owner}/${repo}/${path} :\n\`\`\`\n${content}\n\`\`\`` }], }; } catch (err: unknown) { const message = err instanceof Error ? err.message : "Erreur inconnue"; return { content: [{ type: "text", text: `Échec de la lecture de ${path} : ${message}` }], isError: true, }; } });Connexion à Claude Desktop
Une fois votre serveur opérationnel, connectez-le à Claude Desktop en éditant sa configuration à ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) ou %APPDATA%\Claude\claude_desktop_config.json (Windows) :
{ "mcpServers": { "my-server": { "command": "node", "args": ["/chemin/absolu/vers/dist/index.js"], "env": { "GITHUB_TOKEN": "votre_token_ici" } } }}Redémarrez Claude Desktop. Vos outils apparaissent automatiquement dans le sélecteur d’outils.
Connexion à Claude Code
Les serveurs MCP se connectent à Claude Code via la CLI :
claude mcp add my-server node /chemin/absolu/vers/dist/index.jsClaude Code utilisera automatiquement vos outils lors de travaux sur des tâches qui en bénéficient.
Quoi Construire Ensuite
Un serveur MCP est essentiellement une frontière de capacités : ce que l’agent peut voir et faire. Quelques pistes valant la peine d’être explorées :
- Serveurs de bases de données — Exposez un accès en lecture seule à Postgres, SQLite ou DynamoDB avec des outils d’introspection de schéma
- Serveurs d’analyse de code — Enveloppez tree-sitter ou un serveur de protocole de langage pour donner aux agents une compréhension sémantique des bases de code
- Serveurs de monitoring — Connectez des systèmes de métriques (Prometheus, Datadog) pour que les agents puissent investiguer des incidents avec de vraies données d’observabilité
- Serveurs de documentation — Indexez et servez la documentation interne, permettant aux agents de répondre à des questions opérationnelles
L’écosystème MCP est encore jeune. La plupart des serveurs utiles n’ont pas encore été construits. Construisez celui qui résout un vrai problème dans votre environnement, et le protocole s’occupera du reste.
Articles Connexes
- Introduction au Développement Agentique
- Patterns d’Utilisation des Outils : Interfaces Agent-Outil Fiables
- Patterns Multi-Agents : Orchestrateurs, Workers et Pipelines