Aller au contenu principal
MCP

Créer un MCP Server en TypeScript

Tutoriel complet pour créer votre propre MCP Server avec le SDK TypeScript : tools, resources, prompts, test local et intégration Claude Code.

Pourquoi créer son propre MCP ?

Les MCP communautaires couvrent les cas d'usage les plus courants. Mais votre stack technique a ses spécificités : une API interne, un format de données maison, un workflow propre à votre équipe. Créer un MCP custom vous permet de connecter Claude Code à exactement ce dont vous avez besoin.

Ce tutoriel vous guide de zéro jusqu'à un MCP fonctionnel, testé et intégré dans Claude Code.

Pré-requis

Avant de commencer, vérifiez que vous avez :

  • Node.js 18+ installé (node --version)
  • npm ou pnpm disponible
  • Claude Code installé et fonctionnel
  • Des bases en TypeScript (types, async/await, imports)

Scaffolding du projet

1

Initialiser le projet

Créez un nouveau dossier et initialisez le projet :

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

Le SDK @modelcontextprotocol/sdk fournit tout le nécessaire pour créer un MCP Server. zod sert à valider les paramètres des outils. tsx permet d'exécuter du TypeScript directement.

2

Configurer TypeScript

Créez un fichier tsconfig.json minimal :

{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"strict": true,
"esModuleInterop": true,
"outDir": "dist",
"rootDir": "src",
"declaration": true
},
"include": ["src"]
}
3

Créer la structure de fichiers

mkdir src
touch src/index.ts

Votre arborescence ressemble à ça :

mcp-weather/
├── src/
│   └── index.ts       # Point d'entrée du serveur MCP
├── package.json
└── tsconfig.json
4

Configurer package.json

Ajoutez le champ bin et les scripts dans votre package.json :

{
"name": "mcp-weather",
"version": "1.0.0",
"type": "module",
"bin": {
"mcp-weather": "dist/index.js"
},
"scripts": {
"build": "tsc",
"start": "tsx src/index.ts",
"dev": "tsx watch src/index.ts"
}
}

Le champ type: "module" est obligatoire pour utiliser les imports ES modules.

Créer le serveur MCP de base

Ouvrez src/index.ts et commencez par la structure minimale :

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Créer le serveur avec ses métadonnées
const server = new McpServer({
name: "mcp-weather",
version: "1.0.0",
});

Ce squelette crée un serveur MCP vide. Il ne fait rien pour l'instant, mais il est déjà valide. Ajoutons les fonctionnalités une par une.

Définir un Tool : récupérer la météo

Un tool est une fonction que Claude Code peut appeler. Chaque tool a un nom, une description, un schéma de paramètres (via zod) et un handler qui retourne le résultat.

// Simuler une API météo (remplacez par un vrai appel HTTP en production)
function getWeatherData(city: string): {
city: string;
temperature: number;
condition: string;
humidity: number;
} {
// Données simulées pour le tutoriel
const weatherData: Record<string, { temperature: number; condition: string; humidity: number }> = {
paris: { temperature: 18, condition: "Nuageux", humidity: 65 },
lyon: { temperature: 22, condition: "Ensoleillé", humidity: 45 },
marseille: { temperature: 26, condition: "Ensoleillé", humidity: 55 },
lille: { temperature: 14, condition: "Pluvieux", humidity: 80 },
};
const normalized = city.toLowerCase().trim();
const data = weatherData[normalized];
if (!data) {
return {
city,
temperature: 20,
condition: "Données non disponibles",
humidity: 50,
};
}
return { city, ...data };
}
// Enregistrer le tool sur le serveur
server.tool(
"get-weather",
"Récupère la météo actuelle pour une ville donnée",
{
city: z.string().describe("Nom de la ville (ex: Paris, Lyon, Marseille)"),
},
async ({ city }) => {
const weather = getWeatherData(city);
return {
content: [
{
type: "text" as const,
text: [
`Météo à ${weather.city} :`,
`- Température : ${weather.temperature}°C`,
`- Conditions : ${weather.condition}`,
`- Humidité : ${weather.humidity}%`,
].join("\n"),
},
],
};
}
);

Ajouter un second tool : prévisions

Vous pouvez ajouter autant de tools que nécessaire. Voici un second outil pour les prévisions :

server.tool(
"get-forecast",
"Récupère les prévisions météo des 3 prochains jours pour une ville",
{
city: z.string().describe("Nom de la ville"),
days: z.number().min(1).max(7).default(3).describe("Nombre de jours (1-7)"),
},
async ({ city, days }) => {
const conditions = ["Ensoleillé", "Nuageux", "Pluvieux", "Orageux", "Brumeux"];
const forecast = Array.from({ length: days }, (_, i) => {
const date = new Date();
date.setDate(date.getDate() + i + 1);
const dayStr = date.toLocaleDateString("fr-FR", {
weekday: "long",
day: "numeric",
month: "long",
});
const temp = Math.round(15 + Math.random() * 15);
const condition = conditions[Math.floor(Math.random() * conditions.length)];
return `${dayStr} : ${temp}°C, ${condition}`;
});
return {
content: [
{
type: "text" as const,
text: `Prévisions pour ${city} :\n${forecast.join("\n")}`,
},
],
};
}
);

Définir une Resource

Les resources sont des données en lecture seule que Claude Code peut consulter pour enrichir son contexte. Elles sont identifiées par des URIs.

// Resource statique : liste des villes supportées
server.resource(
"cities-list",
"weather://cities",
async (uri) => ({
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify({
cities: ["Paris", "Lyon", "Marseille", "Lille"],
note: "Autres villes : données estimées",
}),
},
],
})
);

Définir un Prompt

Les prompts sont des templates d'interaction optimisés. Ils permettent à Claude Code de proposer des workflows pré-configurés.

server.prompt(
"weather-report",
"Génère un rapport météo complet pour une ville",
{
city: z.string().describe("Nom de la ville pour le rapport"),
},
({ city }) => ({
messages: [
{
role: "user" as const,
content: {
type: "text" as const,
text: [
`Génère un rapport météo complet pour ${city}.`,
"Utilise l'outil get-weather pour la météo actuelle",
"et get-forecast pour les prévisions.",
"Présente le tout dans un format clair et lisible.",
].join(" "),
},
},
],
})
);

Lifecycle : démarrer le serveur

Le lifecycle d'un MCP Server comprend deux phases clés : l'initialisation et l'arrêt.

Ajoutez le code de démarrage à la fin de src/index.ts :

// Démarrer le serveur avec le transport stdio
async function main(): Promise<void> {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP Weather server running on stdio");
}
main().catch((error: unknown) => {
console.error("Fatal error:", error);
process.exit(1);
});

Tester localement

Avant d'intégrer votre MCP dans Claude Code, testez-le en isolation.

1

Vérifier que le serveur démarre

# Compiler et exécuter
npm run build
# Tester que le serveur répond
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}' | node dist/index.js

Vous devriez voir une réponse JSON-RPC avec les capabilities de votre serveur.

2

Tester avec l'inspecteur MCP

Le SDK fournit un outil d'inspection interactif :

npx @modelcontextprotocol/inspector node dist/index.js

L'inspecteur ouvre une interface web où vous pouvez :

  • Voir la liste des tools, resources et prompts
  • Appeler chaque tool avec des paramètres de test
  • Vérifier les réponses
3

Vérifier la sortie

Appelez manuellement un tool pour vérifier le format de sortie :

echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get-weather","arguments":{"city":"Paris"}}}' | node dist/index.js

La réponse doit contenir le texte formaté de la météo.

Intégrer dans Claude Code

Une fois votre MCP testé, connectez-le à Claude Code.

Option 1 : via la CLI

# Depuis le dossier de votre projet MCP
claude mcp add weather -- node /chemin/absolu/vers/mcp-weather/dist/index.js

Option 2 : via le fichier .mcp.json

Créez ou modifiez le fichier .mcp.json à la racine du projet qui utilisera le MCP :

{
"mcpServers": {
"weather": {
"command": "node",
"args": ["/chemin/absolu/vers/mcp-weather/dist/index.js"]
}
}
}

Option 3 : en développement avec tsx

Pour itérer rapidement sans recompiler :

{
"mcpServers": {
"weather": {
"command": "npx",
"args": ["tsx", "/chemin/absolu/vers/mcp-weather/src/index.ts"]
}
}
}

Exemple complet : le fichier src/index.ts final

Voici le fichier complet pour référence :

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: "mcp-weather",
version: "1.0.0",
});
// --- Données simulées ---
function getWeatherData(city: string) {
const weatherData: Record<
string,
{ temperature: number; condition: string; humidity: number }
> = {
paris: { temperature: 18, condition: "Nuageux", humidity: 65 },
lyon: { temperature: 22, condition: "Ensoleillé", humidity: 45 },
marseille: { temperature: 26, condition: "Ensoleillé", humidity: 55 },
lille: { temperature: 14, condition: "Pluvieux", humidity: 80 },
};
const normalized = city.toLowerCase().trim();
const data = weatherData[normalized];
if (!data) {
return { city, temperature: 20, condition: "Données non disponibles", humidity: 50 };
}
return { city, ...data };
}
// --- Tools ---
server.tool(
"get-weather",
"Récupère la météo actuelle pour une ville donnée",
{ city: z.string().describe("Nom de la ville") },
async ({ city }) => {
const w = getWeatherData(city);
return {
content: [
{
type: "text" as const,
text: `Météo à ${w.city} :\n- Température : ${w.temperature}°C\n- Conditions : ${w.condition}\n- Humidité : ${w.humidity}%`,
},
],
};
}
);
server.tool(
"get-forecast",
"Récupère les prévisions météo des prochains jours pour une ville",
{
city: z.string().describe("Nom de la ville"),
days: z.number().min(1).max(7).default(3).describe("Nombre de jours (1-7)"),
},
async ({ city, days }) => {
const conditions = ["Ensoleillé", "Nuageux", "Pluvieux", "Orageux"];
const lines = Array.from({ length: days }, (_, i) => {
const date = new Date();
date.setDate(date.getDate() + i + 1);
const label = date.toLocaleDateString("fr-FR", {
weekday: "long",
day: "numeric",
month: "long",
});
const temp = Math.round(15 + Math.random() * 15);
return `${label} : ${temp}°C, ${conditions[Math.floor(Math.random() * conditions.length)]}`;
});
return {
content: [{ type: "text" as const, text: `Prévisions pour ${city} :\n${lines.join("\n")}` }],
};
}
);
// --- Resources ---
server.resource("cities-list", "weather://cities", async (uri) => ({
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify({
cities: ["Paris", "Lyon", "Marseille", "Lille"],
note: "Autres villes : données estimées",
}),
},
],
}));
// --- Prompts ---
server.prompt(
"weather-report",
"Génère un rapport météo complet pour une ville",
{ city: z.string().describe("Nom de la ville") },
({ city }) => ({
messages: [
{
role: "user" as const,
content: {
type: "text" as const,
text: `Génère un rapport météo complet pour ${city}. Utilise get-weather pour la météo actuelle et get-forecast pour les prévisions.`,
},
},
],
})
);
// --- Démarrage ---
async function main(): Promise<void> {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP Weather server running on stdio");
}
main().catch((error: unknown) => {
console.error("Fatal error:", error);
process.exit(1);
});

Publication npm (optionnel)

Si vous voulez partager votre MCP avec la communauté :

1

Préparer le package

Ajoutez un shebang en première ligne de src/index.ts :

#!/usr/bin/env node

Mettez à jour le package.json avec les champs requis pour npm :

{
"name": "@votre-scope/mcp-weather",
"description": "MCP Server pour consulter la météo",
"keywords": ["mcp", "weather", "claude-code"],
"license": "MIT",
"files": ["dist"],
"bin": {
"mcp-weather": "dist/index.js"
}
}
2

Compiler et publier

npm run build
npm publish --access public

Les utilisateurs pourront alors l'installer avec :

claude mcp add weather -- npx -y @votre-scope/mcp-weather

Erreurs courantes et solutions

ErreurCause probableSolution
Cannot find moduleChemin incorrect dans .mcp.jsonUtilisez un chemin absolu vers dist/index.js
SyntaxError: Unexpected tokenFichier TS exécuté sans tsxCompilez d'abord (npm run build) ou utilisez tsx
Aucun outil visible dans Claude CodeLe serveur plante au démarrageTestez manuellement avec l'inspecteur MCP
stdout is not a valid JSON-RPC messageconsole.log() dans le codeRemplacez par console.error() pour les logs
Tool appelé mais pas de réponseHandler qui ne retourne rienVérifiez que le handler retourne un objet content

Prochaines étapes