Le Model Context Protocol (MCP) permet a vos outils IA de se connecter a des sources de donnees et des services externes. Les serveurs communautaires couvrent les cas generiques (GitHub, PostgreSQL, Slack), mais pour vos outils internes, vous devez creer votre propre serveur.
Ce tutoriel vous guide pas a pas pour creer un serveur MCP en TypeScript, du setup initial jusqu'a l'integration avec Claude Code.
Prerequis
- Node.js 18+ et npm
- TypeScript 5+
- Un outil compatible MCP (Claude Code, Cline) pour tester
- Connaissances de base en TypeScript
Setup du projet
Initialisez un nouveau projet :
mkdir mon-serveur-mcp
cd mon-serveur-mcp
npm init -y
npm install @modelcontextprotocol/sdk zod
Configurez TypeScript :
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true
},
"include": ["src/*/"]
Structure du serveur
Un serveur MCP expose trois types de primitives :
- Tools : des actions que l'IA peut executer (equivalent d'une fonction)
- Resources : des donnees que l'IA peut lire (equivalent d'un endpoint GET)
- Prompts : des templates de conversation pre-definis
Creez le fichier principal src/index.ts :
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: "mon-serveur",
version: "1.0.0",
});
// On ajoutera les tools et resources ici
const transport = new StdioServerTransport();
Le serveur communique via stdio (entree/sortie standard), ce qui le rend compatible avec tous les clients MCP.
Ajouter un tool
Un tool est une action que l'IA peut executer. Exemple : un tool qui recherche dans votre documentation interne.
server.tool(
"search-docs",
"Recherche dans la documentation interne du projet",
{
query: z.string().describe("Terme de recherche"),
limit: z.number().optional().default(5).describe("Nombre max de resultats"),
},
async ({ query, limit }) => {
// Ici, votre logique de recherche
const results = await searchInternalDocs(query, limit);
return {
content: [
{
type: "text",
text: JSON.stringify(results, null, 2),
},
],
};
}
Anatomie d'un tool
- Nom : identifiant unique (
search-docs) - Description : explique a l'IA quand utiliser ce tool
- Schema : parametres valides avec Zod (l'IA envoie des parametres types)
- Handler : fonction async qui execute l'action et retourne un resultat
La description est cruciale : c'est elle qui permet a l'IA de savoir quand utiliser votre tool. Soyez precis et concret.
Ajouter une resource
Une resource expose des donnees en lecture. Exemple : la configuration du projet.
server.resource(
"config://project",
"Configuration actuelle du projet (env, feature flags)",
async (uri) => {
const config = await loadProjectConfig();
return {
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(config, null, 2),
},
],
};
}
Les resources utilisent un schema d'URI pour l'identification. L'IA peut les lire mais pas les modifier.
Ajouter un prompt
Un prompt est un template de conversation pour un cas d'usage specifique :
server.prompt(
"debug-error",
"Analyse une erreur de production avec le contexte du projet",
{
errorId: z.string().describe("ID de l'erreur (ex: SENTRY-1234)"),
},
async ({ errorId }) => {
const error = await fetchError(errorId);
return {
messages: [
{
role: "user",
content: {
type: "text",
text: Analyse cette erreur de production et propose un fix :
Erreur: ${error.title}
Stack trace: ${error.stackTrace}
Occurrences: ${error.count} fois en ${error.timespan}
Derniere occurrence: ${error.lastSeen}
Contexte projet: API Spring Boot 3.4, PostgreSQL 17, deploye sur Kubernetes.,
},
},
],
};
}
Exemple complet : serveur pour une API interne
Voici un serveur MCP complet qui expose une API REST interne :
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const API_BASE = process.env.INTERNAL_API_URL || "http://localhost:3000";
const API_TOKEN = process.env.INTERNAL_API_TOKEN || "";
const server = new McpServer({
name: "internal-api",
version: "1.0.0",
});
// Tool : lister les utilisateurs
server.tool(
"list-users",
"Liste les utilisateurs de la plateforme avec filtrage",
{
role: z.enum(["admin", "user", "editor"]).optional(),
active: z.boolean().optional().default(true),
},
async ({ role, active }) => {
const params = new URLSearchParams();
if (role) params.set("role", role);
params.set("active", String(active));
const res = await fetch(${API_BASE}/api/users?${params}, {
headers: { Authorization: Bearer ${API_TOKEN} },
});
const data = await res.json();
return {
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
};
}
);
// Tool : creer un ticket support
server.tool(
"create-ticket",
"Cree un ticket de support interne",
{
title: z.string().describe("Titre du ticket"),
description: z.string().describe("Description detaillee"),
priority: z.enum(["low", "medium", "high", "critical"]),
},
async ({ title, description, priority }) => {
const res = await fetch(${API_BASE}/api/tickets, {
method: "POST",
headers: {
Authorization: Bearer ${API_TOKEN},
"Content-Type": "application/json",
},
body: JSON.stringify({ title, description, priority }),
});
const ticket = await res.json();
return {
content: [
{
type: "text",
text: Ticket cree : #${ticket.id} - ${ticket.title},
},
],
};
}
);
// Resource : documentation API
server.resource(
"docs://api-endpoints",
"Liste de tous les endpoints de l'API interne",
async (uri) => {
const res = await fetch(${API_BASE}/api/docs/openapi.json, {
headers: { Authorization: Bearer ${API_TOKEN} },
});
const spec = await res.json();
return {
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(spec, null, 2),
},
],
};
}
);
const transport = new StdioServerTransport();
Tester votre serveur
Avec Claude Code
Ajoutez votre serveur dans la configuration :
{
"mcpServers": {
"internal-api": {
"command": "npx",
"args": ["tsx", "/chemin/vers/mon-serveur-mcp/src/index.ts"],
"env": {
"INTERNAL_API_URL": "http://localhost:3000",
"INTERNAL_API_TOKEN": "votre-token"
}
}
}
Lancez Claude Code et verifiez avec /mcp que votre serveur apparait avec ses tools.
Test manuel
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | npx tsx src/index.tsBonnes pratiques
Descriptions precises : la qualite des descriptions de vos tools determine si l'IA les utilisera correctement. "Liste les utilisateurs" est moins bon que "Liste les utilisateurs de la plateforme, filtrable par role et statut actif/inactif". Validation stricte : utilisez Zod pour valider tous les parametres. L'IA peut envoyer des valeurs inattendues. Gestion d'erreurs : retournez des messages d'erreur clairs que l'IA peut comprendre et transmettre a l'utilisateur. Principe du moindre privilege : un serveur MCP pour la documentation n'a pas besoin d'un acces en ecriture a la base de donnees. Logging : loguez les appels pour comprendre comment l'IA utilise vos tools et identifier les ameliorations possibles.