Comment créer un serveur MCP en local et le connecter à Claude (avec du code fonctionnel)

Créez un serveur MCP local à partir d’un dossier vide : installez le SDK TypeScript, enregistrez deux outils fonctionnels, testez-les dans le MCP Inspector, puis connectez le serveur à Claude Desktop et Claude Code. Inclut les fichiers de configuration, les corrections de chemins sous Windows et une liste de contrôle pour les erreurs qui font échouer la plupart des installations.

Comment créer un serveur MCP en local et le connecter à Claude (avec du code fonctionnel)
Cristian Da Conceicao
Fondateur de Picasso IA

La plupart des tutoriels MCP s’arrêtent au « hello world » et vous laissent face à un badge rouge Disconnected. Celui-ci se termine par un serveur qui tourne sur votre propre machine, Claude qui appelle ses outils, et une courte liste de contrôle pour les erreurs qui touchent la plupart des gens. Vous écrirez environ 70 lignes de TypeScript, les testerez dans un inspecteur qui fonctionne dans le navigateur, puis brancherez le résultat à la fois sur Claude Desktop et sur Claude Code.

Le serveur est un petit outil de notes : Claude peut enregistrer une note dans un fichier JSON sur votre disque, puis la rechercher plus tard. Il est volontairement simple, car la plomberie reste identique, qu’il s’agisse de lire un fichier de notes, d’interroger une base de données ou d’appeler un modèle d’image. J’ai compilé et exécuté le fichier serveur ci-dessous avec la version 1.32 du SDK TypeScript, si bien que le code se construit exactement comme affiché.

Développeur tapant dans un terminal sur un ordinateur portable, à un bureau en chêne, dans la lumière du matin

Ce que vous construisez réellement

MCP en deux paragraphes

Le Model Context Protocol (MCP) est un standard ouvert, présenté par Anthropic en novembre 2024, qui permet à une application d’IA de dialoguer avec des outils externes d’une manière unique et cohérente. Au lieu que chaque application invente son propre format de plugin, un serveur MCP expose des capacités, et un client MCP, comme Claude Desktop ou Claude Code, les découvre et les appelle. Les messages utilisent le format JSON-RPC 2.0, si bien qu’un serveur peut être écrit dans n’importe quel langage.

Un serveur peut proposer trois types de choses :

  • Tools (outils) : des fonctions que le modèle peut appeler, comme « enregistrer une note » ou « lancer une requête ».
  • Resources (ressources) : des données en lecture seule que l’application peut charger comme contexte, comme un fichier ou un enregistrement de base de données.
  • Prompts : des modèles de prompt réutilisables que l’utilisateur déclenche volontairement.

Ce tutoriel s’en tient aux outils, car ce sont les plus simples à tester et les plus utiles dès le premier jour.

Vue de dessus d’un carnet avec un schéma dessiné à la main, trois boîtes reliées par des flèches

Pourquoi l’exécuter en local

Un serveur local s’exécute comme processus enfant du client, sur votre machine, avec vos fichiers et vos droits. Rien n’est exposé à Internet, il n’y a aucune facture d’hébergement, et l’itération est rapide : vous modifiez un fichier, recompilez, redémarrez. Le transport utilisé est stdio : le client lance votre programme et communique avec lui via l’entrée et la sortie standard.

stdio (local)Streamable HTTP (distant)
Lieu d’exécutionProcessus enfant sur votre ordinateurUn serveur web que vous ou quelqu’un d’autre hébergez
Qui peut l’atteindreSeule l’application qui l’a lancéToute personne disposant de l’URL et des identifiants
AuthentificationAucune, il hérite de votre compte utilisateurObligatoire (OAuth ou jetons)
Idéal pourOutils personnels, accès aux fichiers, développementOutils d’équipe partagés, intégrations SaaS

💡 Bon à savoir : Streamable HTTP a remplacé l’ancien transport HTTP+SSE dans la révision 2025-03-26 de la spécification. Et comme un navigateur ne peut pas lancer un processus sur votre ordinateur, un serveur stdio ne peut pas être ajouté à claude.ai dans le navigateur. Seuls les serveurs distants y fonctionnent.

Vue en contre-plongée d’un mini PC graphite à côté d’un ordinateur portable argenté, reliés par un câble USB-C tressé

Préparer le projet

Ce qu’il faut installer

OutilVersionVérifier avec
Node.js20 LTS ou plus récentnode --version
npmFourni avec Nodenpm --version
Claude DesktopDernière version, macOS ou WindowsParamètres, puis Développeur
Claude Code (facultatif)Dernière versionclaude --version

Claude Desktop est disponible pour macOS et Windows. Sous Linux, utilisez la méthode Claude Code décrite dans la section de connexion ci-dessous ; le serveur lui-même est identique.

Créer et configurer le projet

mkdir local-notes-mcp && cd local-notes-mcp
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
mkdir src

Ouvrez package.json et ajoutez trois éléments à côté des dépendances créées par npm : l’indicateur de module ES et deux scripts.

{
  "name": "local-notes-mcp",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node build/index.js"
  }
}

Créez ensuite tsconfig.json à la racine du projet :

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "build",
    "rootDir": "src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "types": ["node"]
  },
  "include": ["src"]
}

⚠️ Attention : TypeScript 7, la version que npm installe aujourd’hui, ne charge plus les paquets @types de lui-même. Sans la ligne "types": ["node"], vous obtenez Cannot find name 'process' et des erreurs similaires à chaque import Node.

Gros plan de mains de développeur posées en pleine frappe, à un bureau éclairé par une lampe chaleureuse

Écrire vos deux premiers outils

Le fichier serveur complet

Enregistrez ceci sous src/index.ts. Il expose save_note et search_notes, et stocke tout dans un seul fichier JSON.

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { promises as fs } from "node:fs";
import os from "node:os";
import path from "node:path";

const NOTES_DIR = process.env.NOTES_DIR ?? path.join(os.homedir(), "mcp-notes");
const NOTES_FILE = path.join(NOTES_DIR, "notes.json");

type Note = { id: number; title: string; body: string; createdAt: string };

async function readNotes(): Promise<Note[]> {
  try {
    return JSON.parse(await fs.readFile(NOTES_FILE, "utf8"));
  } catch {
    return [];
  }
}

const server = new McpServer({ name: "local-notes", version: "1.0.0" });

server.registerTool(
  "save_note",
  {
    title: "Save note",
    description: "Save a short note with a title and a body to the local notes file.",
    inputSchema: {
      title: z.string().min(1).max(120).describe("Short title for the note"),
      body: z.string().min(1).describe("The text of the note"),
    },
  },
  async ({ title, body }) => {
    const notes = await readNotes();
    const note: Note = {
      id: notes.length + 1,
      title,
      body,
      createdAt: new Date().toISOString(),
    };
    await fs.mkdir(NOTES_DIR, { recursive: true });
    await fs.writeFile(NOTES_FILE, JSON.stringify([...notes, note], null, 2));
    return { content: [{ type: "text", text: `Saved note #${note.id}: ${title}` }] };
  }
);

server.registerTool(
  "search_notes",
  {
    title: "Search notes",
    description: "Find saved notes whose title or body contains a word or phrase.",
    inputSchema: {
      query: z.string().min(1).describe("Word or phrase to look for"),
    },
  },
  async ({ query }) => {
    const q = query.toLowerCase();
    const hits = (await readNotes()).filter((n) =>
      `${n.title} ${n.body}`.toLowerCase().includes(q)
    );
    if (hits.length === 0) {
      return { content: [{ type: "text", text: `No notes match "${query}".` }] };
    }
    const text = hits.map((n) => `#${n.id} ${n.title}\n${n.body}`).join("\n\n");
    return { content: [{ type: "text", text }] };
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);
console.error("local-notes MCP server running on stdio");

Le rôle de chaque partie

  • McpServer est la classe de haut niveau. Le name et le version que vous transmettez apparaissent dans la liste des serveurs du client et dans les journaux.
  • registerTool prend un nom d’outil, un objet de configuration (title, description, inputSchema) et un gestionnaire asynchrone.
  • Le schéma Zod est validé avant l’exécution de votre gestionnaire, puis converti en JSON Schema pour que Claude puisse le lire. Chaque chaîne .describe() parvient au modèle.
  • La valeur de retour est toujours { content: [...] }. Le texte brut est le type de contenu le plus simple ; les images et les liens de ressources sont aussi pris en charge.
  • StdioServerTransport lit les requêtes sur stdin et écrit les réponses sur stdout.

💡 Astuce : rédigez les descriptions pour le modèle, pas pour les humains. « Permet de trouver les notes enregistrées dont le titre ou le corps contient un mot ou une expression » indique à Claude quand appeler l’outil. « Recherche » ne le fait pas. Claude choisit ses outils surtout d’après leurs noms et leurs descriptions.

Comme search_notes ne modifie jamais rien, indiquez-le dans sa configuration : annotations: { readOnlyHint: true }. Ces indications sont consultatives, et chaque client décide lui-même du degré de confiance à leur accorder, mais elles permettent aux clients bien conçus de traiter les outils en lecture seule avec plus de souplesse.

Développeuse debout à un bureau réglable, en train de relire du code sur un grand écran

Ne jamais écrire sur stdout

Avec stdio, stdout est le canal du protocole. Un seul console.log("started") parasite envoie une ligne non JSON dans le flux, et la plupart des clients couperont la connexion ou afficheront le serveur comme en échec. Retenez cette règle et vous éviterez l’échec le plus courant du premier jour :

  • À faire : console.error("message"), qui écrit sur stderr, là où les clients collectent les journaux.
  • À éviter : console.log(...) ou process.stdout.write(...) n’importe où dans votre serveur, y compris dans les bibliothèques que vous importez.

Tester avant que Claude ne le fasse

Lancer le MCP Inspector

Le MCP Inspector est l’outil de débogage officiel. Il lance votre serveur exactement comme le ferait un client, et vous offre des boutons au lieu d’invites de commande.

npm run build
npx @modelcontextprotocol/inspector node build/index.js

Une page s’ouvre dans votre navigateur. Cliquez sur Connect, ouvrez l’onglet Tools et appuyez sur List Tools. Vous devriez voir save_note et search_notes avec leurs schémas. Lancez save_note avec un titre et un corps, puis search_notes avec un mot tiré de ce corps. Le premier appel renvoie Saved note #1: Standup, et le fichier de notes apparaît dans un dossier mcp-notes à l’intérieur de votre répertoire personnel (ou dans NOTES_DIR si vous l’avez défini).

Deux collègues penchés vers l’écran d’un ordinateur portable, à une table en bois partagée

Envoyer des messages JSON-RPC bruts

Si vous voulez voir le protocole lui-même, stdio utilise un message JSON par ligne. Placez ces trois lignes dans requests.jsonl :

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"smoke","version":"0.0.1"}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
{"jsonrpc":"2.0","id":2,"method":"tools/list"}

Transmettez-les ensuite au serveur et gardez stdin ouvert un moment pour que les réponses puissent s’afficher :

(cat requests.jsonl; sleep 2) | node build/index.js

La première réponse confirme la poignée de main, avec la version du protocole que le serveur a acceptée et votre serverInfo :

{"result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{"listChanged":true}},"serverInfo":{"name":"local-notes","version":"1.0.0"}},"jsonrpc":"2.0","id":1}

La deuxième réponse liste les deux outils avec leurs JSON Schemas. Cet échange représente tout ce que fait Claude lorsqu’il se connecte : poignée de main, liste des outils, appel des outils.

Le connecter à Claude

Modifier la configuration de Claude Desktop

Dans Claude Desktop, ouvrez Paramètres, puis Développeur, puis Modifier la configuration. Cela révèle claude_desktop_config.json :

SystèmeEmplacement du fichier de configuration
Windows%APPDATA%\Claude\claude_desktop_config.json
macOS~/Library/Application Support/Claude/claude_desktop_config.json

Ajoutez votre serveur sous mcpServers. Utilisez des chemins absolus, car Claude Desktop lance votre processus depuis son propre répertoire de travail, et non depuis le dossier de votre projet.

{
  "mcpServers": {
    "local-notes": {
      "command": "node",
      "args": ["C:/Users/you/projects/local-notes-mcp/build/index.js"],
      "env": {
        "NOTES_DIR": "C:/Users/you/mcp-notes"
      }
    }
  }
}

Sous macOS, le chemin ressemble à /Users/you/projects/local-notes-mcp/build/index.js. Enregistrez le fichier, puis quittez complètement Claude Desktop (sous Windows, depuis la zone de notification et non avec simplement le bouton de fermeture de la fenêtre) et rouvrez-le. Vos outils apparaissent dans le menu des outils du champ de saisie, et Claude vous demande l’autorisation avant d’en exécuter un.

Développeur travaillant à une table de café en marbre, avec un ordinateur portable argenté et un flat white

L’ajouter à Claude Code

Claude Code n’exige aucune modification de fichier. Une seule commande enregistre le serveur :

claude mcp add --transport stdio --env NOTES_DIR=/home/you/mcp-notes local-notes -- node /home/you/projects/local-notes-mcp/build/index.js

Tout ce qui suit le double tiret est la commande qui lance votre serveur. Vérifiez-la avec claude mcp list, ou tapez /mcp dans une session pour voir son état. Un indicateur de portée détermine qui reçoit le serveur :

PortéeStocké dansQui le voit
local (par défaut)Vos paramètres privés pour ce projetVous seul, dans ce projet
project (--scope project).mcp.json dans le dépôtToute personne qui le clone, après l’avoir approuvé
user (--scope user)Votre configuration utilisateurVous seul, dans tous les projets

Essayer un vrai prompt

Demandez à Claude quelque chose qui oblige à appeler un outil :

Enregistre une note intitulée « Standup » qui dit « Publier l’article MCP vendredi ». Cherche ensuite dans mes notes « vendredi ».

Claude appelle save_note, puis search_notes, et vous restitue le résultat. Ouvrez notes.json pour vérifier que les données ont bien été enregistrées sur votre disque. Si c’est le cas, vous disposez d’un serveur MCP local fonctionnel.

Corriger les échecs et sécuriser l’ensemble

Corriger les erreurs courantes

SymptômeCause probableCorrection
Le serveur apparaît comme en échec ou déconnectéSortie sur stdout, ou plantage au démarrageLancez node build/index.js à la main et lisez stderr ; supprimez chaque console.log
Aucun outil après modification de la configurationClient encore en cours d’exécution, ou JSON invalideQuittez complètement ; vérifiez les virgules en trop
spawn node ENOENTL’application ne trouve pas node dans son PATHUtilisez le chemin absolu du binaire node comme command
Fonctionne dans l’Inspector, échoue dans ClaudeChemins relatifs ou variables d’environnement manquantesChemins absolus, et placez les variables dans env
Les modifications du code n’ont aucun effetVous n’avez pas recompilé ni redémarréLancez npm run build, puis redémarrez le client

Deux points propres à Windows causent la moitié des problèmes restants. Les antislashs dans les chaînes JSON doivent être doublés (C:\\Users\\you\\...), ou vous pouvez simplement utiliser des barres obliques comme dans l’exemple ci-dessus. Et sous Windows natif, les serveurs lancés via npx ont généralement besoin d’un enrobage cmd /c dans command ; une simple commande node ne suffit pas.

Lorsqu’un problème persiste, consultez les journaux. Claude Desktop écrit un journal par serveur, dans ~/Library/Logs/Claude sous macOS et dans %APPDATA%\Claude\logs sous Windows. Vos propres lignes console.error y aboutissent.

Ingénieur renversé dans son fauteuil de bureau, avec un sourire soulagé sous la lumière chaude d’une lampe en laiton

Valeurs par défaut sûres à conserver

Un serveur stdio hérite de vos droits, donc traitez chaque outil comme du code capable d’agir en votre nom.

  • Limitez le rayon d’action. Gardez l’accès aux fichiers dans un seul dossier. Si un outil accepte un chemin, résolvez-le et rejetez tout ce qui se trouve hors du répertoire autorisé.
  • Validez chaque entrée. Les règles min, max et enum de Zod ne coûtent rien et bloquent les appels mal formés avant l’exécution de votre gestionnaire.
  • Gardez les secrets hors du code. Placez les jetons dans le bloc env de votre configuration, et ne versionnez pas ce fichier.
  • Lisez avant d’installer. N’ajoutez que des serveurs tiers dont vous avez examiné le code source. Ils s’exécutent avec les droits de votre compte.
  • Considérez la sortie des outils comme non fiable. Le texte que votre outil récupère sur des pages web ou des courriels peut contenir des instructions destinées au modèle. Renvoyez-le comme des données et gardez les actions d’écriture derrière une confirmation.

Passer de stdio à HTTP

Lorsque des coéquipiers ont besoin des mêmes outils, changez de transport. Le SDK fournit StreamableHTTPServerTransport, qui sert les mêmes McpServer en HTTP derrière votre propre authentification. Vos appels registerTool ne changent pas. Seuls le transport et l’enregistrement côté client changent, par exemple claude mcp add --transport http notes https://your-host/mcp.

Vous pouvez déjà observer ce schéma en conditions réelles. Le connecteur PicassoIA dans claude.ai est un serveur MCP distant qui propose la génération d’images, l’édition d’images et la génération de vidéos comme outils, et Claude les appelle sans aucun processus local.

Gros plan d’un rack serveur noir mat avec des câbles Ethernet gris soigneusement regroupés

Rédiger des spécifications d’outils sur PicassoIA

De bons outils commencent par de bons noms et de bonnes descriptions, et un grand modèle de langage est un moyen rapide de les rédiger avant d’écrire le code. PicassoIA héberge 75 modèles de texte dans sa catégorie Grands modèles de langage, dont Claude Sonnet 5, conçu pour les tâches de programmation. Voici comment l’utiliser pour concevoir des outils.

Comment utiliser Claude Sonnet 5 sur PicassoIA

  1. Ouvrez la page de Claude Sonnet 5 sur PicassoIA.
  2. Décrivez l’outil en langage courant : ce qu’il fait, ce qu’il reçoit, ce qu’il renvoie et s’il modifie quelque chose.
  3. Demandez un format de sortie fixe : un nom d’outil en snake_case, une description de deux phrases au maximum rédigée pour un modèle, un schéma Zod avec .describe() sur chaque champ, et trois cas limites qui doivent échouer à la validation.
  4. Collez le résultat dans un appel registerTool, recompilez et testez-le dans l’Inspector.
  5. Retravaillez la description, et non le schéma, lorsque Claude choisit le mauvais outil. C’est généralement la formulation qui pose problème.

Un prompt qui fonctionne bien :

Je construis un outil MCP appelé list_overdue_tasks. Il lit tasks.json, renvoie les tâches dont dueDate est antérieure à aujourd’hui et ne modifie rien. Rédige le nom de l’outil, une description de deux phrases pour un modèle d’IA, un schéma d’entrée Zod avec un describe() sur chaque champ, et trois entrées invalides qu’il doit rejeter.

Pour les refactorisations plus importantes, comme le découpage d’un serveur de 600 lignes en modules, essayez Claude Fable 5 ou Claude Opus 4.7 avec tout votre fichier collé.

Créer votre première image sur PicassoIA

Votre serveur de notes sert de modèle. Remplacez le fichier JSON par un appel à un modèle d’image, et Claude pourra générer des images sur demande. Vous n’avez pas besoin de le construire d’abord pour voir le résultat. Chaque photo de cet article a été générée avec P-Image, l’un des modèles texte vers image de Picasso IA.

Ouvrez Picasso IA, tapez une phrase décrivant une scène et lancez la génération. Essayez ensuite trois expériences :

  • Changez l’objectif. Réécrivez le même prompt avec « 35mm » puis « 85mm » et comparez le cadrage.
  • Changez la lumière. Remplacez « lumière de fenêtre matinale » par « lampe de bureau chaleureuse » et observez l’évolution de l’ambiance.
  • Changez l’angle. Demandez une vue de dessus, puis une contre-plongée du même sujet.

Construisez un outil que vous aimeriez voir intégré à Claude, branchez-le en suivant les étapes ci-dessus, puis passez dix minutes sur Picasso IA à créer les visuels de votre projet. Le serveur demande un après-midi. Les images, quelques secondes.

Partager cet article

Choisissez votre langue