Tutoriel · MCP · TypeScript

Créer son premier serveur MCP en TypeScript

Initialiser le projet, déclarer un premier outil avec le SDK officiel, le servir en stdio sans polluer la sortie standard, puis le tester dans MCP Inspector et un agent.

Ce que fait un serveur MCP

Le Model Context Protocol (MCP) standardise la façon dont un agent IA découvre et utilise des capacités externes. Un serveur MCP en déclare trois sortes : des outils que le modèle appelle, des ressources qu’on lui fait lire et des prompts que l’utilisateur déclenche. Le client MCP, intégré à l’agent, s’y connecte, liste ces capacités et relaie les appels.

Les échanges sont des messages JSON-RPC. On ne les écrit pas à la main : le SDK officiel TypeScript s’occupe de la négociation initiale, de la validation des paramètres et du transport. Il reste à décrire ce qu’on expose.

Fil rouge de la série : un serveur pour le support client d’Acme Assurances, une entreprise fictive, qui donne à un agent l’accès aux tickets et à la base de connaissances.

Initialiser le projet

Un projet Node classique suffit, avec deux dépendances : le SDK et Zod, qui décrit les paramètres des outils.

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

Le SDK est publié en modules ES : le package.json déclare "type": "module", ce qui autorise aussi le await au premier niveau du fichier.

{
  "name": "mcp-support",
  "version": "0.1.0",
  "type": "module",
  "bin": { "mcp-support": "dist/server.js" },
  "scripts": {
    "build": "tsc",
    "inspect": "npx @modelcontextprotocol/inspector node dist/server.js"
  }
}

Côté TypeScript, la résolution NodeNext respecte les chemins d’import du SDK, qui se terminent par .js même depuis un fichier .ts.

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "dist",
    "rootDir": "src",
    "strict": true,
    "skipLibCheck": true
  },
  "include": ["src"]
}

Un premier outil

Le serveur tient en un fichier src/server.ts : on crée un McpServer, on enregistre un outil, puis on le connecte au transport stdio.

#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// Données fictives : en vrai, un appel à l'outil de ticketing.
const tickets = [
  { id: "T-1001", titre: "Attestation introuvable", statut: "ouvert", priorite: "haute" },
  { id: "T-1002", titre: "Changement d'adresse", statut: "resolu", priorite: "basse" },
  { id: "T-1003", titre: "Erreur sur l'échéancier", statut: "ouvert", priorite: "normale" },
];

const server = new McpServer({ name: "acme-support", version: "0.1.0" });

server.registerTool(
  "lister_tickets",
  {
    title: "Lister les tickets",
    description: "Liste les tickets du support Acme, filtrés par statut. Renvoie identifiant, titre et priorité.",
    inputSchema: {
      statut: z.enum(["ouvert", "resolu"]).default("ouvert").describe("Statut des tickets à lister"),
    },
  },
  async ({ statut }) => {
    const resultat = tickets.filter((t) => t.statut === statut);
    const lignes = resultat.map((t) => `${t.id} [${t.priorite}] ${t.titre}`);
    return { content: [{ type: "text", text: lignes.join("\n") || "Aucun ticket." }] };
  }
);

await server.connect(new StdioServerTransport());
console.error("acme-support prêt (stdio)");

Trois éléments font un outil :

  • un nom stable, en snake_case, que le modèle utilisera pour l’appeler ;
  • une description : c’est elle que le modèle lit pour décider s’il appelle l’outil, et comment ;
  • un schéma d’entrée Zod : le SDK le convertit en JSON Schema pour le client et valide les arguments avant d’appeler le handler, qui reçoit des valeurs typées.

Le handler renvoie une liste de contenus, ici un simple texte. Le deuxième article montre comment renvoyer en plus une sortie structurée.

La règle d’or du stdio

Avec le transport stdio, l’agent lance le serveur comme un processus enfant et dialogue avec lui par l’entrée et la sortie standard. La sortie standard appartient au protocole : un seul console.log y glisse du texte qui n’est pas du JSON-RPC, et le client coupe la connexion avec une erreur souvent obscure.

Tout message de diagnostic passe donc par console.error, c’est-à-dire la sortie d’erreur, que les agents affichent dans leurs journaux. Une bibliothèque tierce bavarde sur stdout mérite d’être configurée en conséquence.

Tester avec MCP Inspector

Avant de brancher un agent, on vérifie le serveur avec MCP Inspector, l’outil de test officiel : une interface web qui lance le serveur, liste ses capacités et permet d’appeler chaque outil avec des arguments choisis.

npm run build
npm run inspect

On y contrôle que l’outil apparaît avec sa description, que le schéma propose bien les deux statuts et qu’un argument invalide est refusé avant d’atteindre le handler. C’est aussi l’endroit où l’on voit passer les messages bruts, utile quand un client refuse le serveur sans explication.

Brancher le serveur sur un agent

La plupart des clients MCP (agents de code, applications de bureau) déclarent un serveur stdio par la commande qui le lance. Dans Claude Code, un fichier .mcp.json à la racine du projet le partage avec toute l’équipe :

{
  "mcpServers": {
    "acme-support": {
      "command": "node",
      "args": ["/chemin/vers/mcp-support/dist/server.js"]
    }
  }
}

On peut alors demander à l’agent : « Quels tickets ouverts sont prioritaires ? » Il repère l’outil lister_tickets grâce à sa description, l’appelle avec statut: "ouvert" et répond à partir du résultat.

Et ensuite

Ce serveur fonctionne, mais un outil réel doit gérer les erreurs, signaler s’il modifie des données et renvoyer un résultat exploitable par programme : c’est l’objet du prochain article. Pour un cas d’usage concret sur un CRM, voir aussi Brancher un agent IA sur Salesforce avec MCP.

🍪 Gestion des cookies
Ce site utilise des cookies pour mesurer l'audience (Google Analytics) et lire des vidéos (YouTube). Vous pouvez personnaliser votre choix.
Mentions légales
Réglages des cookies
Essentiels Nécessaires au fonctionnement du site — ne peuvent pas être désactivés.
Google Analytics Analyse d'audience (pages vues, sources de trafic). Aucune donnée personnelle identifiante.
YouTube Permet de lire les vidéos YouTube intégrées sur cette page.