Construire votre premier serveur MCP avec TypeScript : Outils, Ressources et Prompts

Le protocole qui connecte l'IA a tout. MCP (Model Context Protocol) est le standard ouvert qui permet aux agents IA comme Claude et Cursor de decouvrir et utiliser vos outils. Dans ce tutoriel, vous allez en construire un de zero.
Ce que vous allez apprendre
A la fin de ce tutoriel, vous serez capable de :
- Comprendre l'architecture MCP : serveurs, clients, hotes et transports
- Mettre en place un projet de serveur MCP en TypeScript depuis zero
- Enregistrer des outils que les agents IA peuvent invoquer
- Exposer des ressources qui fournissent du contexte aux LLMs
- Definir des prompts comme modeles reutilisables pour les taches courantes
- Connecter votre serveur a Claude Desktop et Cursor
- Tester et deboguer votre serveur avec le MCP Inspector
Prerequis
Avant de commencer, assurez-vous d'avoir :
- Node.js 20+ installe (
node --version) - Des connaissances en TypeScript (types, async/await, modules)
- Un editeur de code — VS Code ou Cursor recommande
- Claude Desktop ou Cursor installe (pour les tests)
- Une comprehension basique de JSON-RPC et des APIs
Qu'est-ce que MCP ?
Le Model Context Protocol est un standard ouvert cree par Anthropic qui definit comment les applications d'IA communiquent avec les sources de donnees et les outils externes. Pensez-y comme l'USB-C de l'IA — un connecteur unique et universel qui remplace le desordre des integrations personnalisees.
Vue d'ensemble de l'architecture
┌─────────────────────────────────────────────┐
│ MCP Host (Claude Desktop, Cursor, etc.) │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Client 1│ │ Client 2│ │ Client 3│ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
└───────┼────────────┼────────────┼──────────┘
│ │ │
┌────▼────┐ ┌────▼────┐ ┌───▼─────┐
│ Server A│ │ Server B│ │ Server C│
│ (Files) │ │ (DB) │ │ (APIs) │
└─────────┘ └─────────┘ └─────────┘
| Composant | Role |
|---|---|
| Hote (Host) | L'application IA (Claude Desktop, Cursor) |
| Client | Maintient une connexion 1:1 avec un serveur |
| Serveur (Server) | Expose les outils, les ressources et les prompts |
| Transport | Canal de communication (stdio, HTTP) |
Ce que les serveurs peuvent exposer
Les serveurs MCP fournissent trois primitives :
- Outils (Tools) — Fonctions que l'IA peut appeler (comme des endpoints API)
- Ressources (Resources) — Donnees en lecture seule auxquelles l'IA peut acceder (comme des fichiers)
- Prompts — Modeles reutilisables pour les workflows courants
Etape 1 : Configuration du projet
Creez un nouveau repertoire et initialisez le projet.
mkdir my-mcp-server && cd my-mcp-server
npm init -yInstallez le SDK MCP et les dependances.
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/nodeNote sur la version du SDK : Ce tutoriel utilise le MCP TypeScript SDK v1.x qui utilise @modelcontextprotocol/sdk. Le SDK v2 (prevu pour le T2 2026) reorganise les imports en @modelcontextprotocol/server et @modelcontextprotocol/node. Les concepts restent identiques — seuls les chemins d'import changent.
Initialisez TypeScript.
npx tsc --initMettez a jour votre tsconfig.json :
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"]
}Mettez a jour package.json pour marquer le projet comme ESM et ajouter les scripts de build :
{
"name": "my-mcp-server",
"version": "1.0.0",
"type": "module",
"bin": {
"my-mcp-server": "./build/index.js"
},
"scripts": {
"build": "tsc",
"start": "node build/index.js",
"dev": "tsc --watch"
}
}Creez le repertoire source :
mkdir srcEtape 2 : Creer le squelette du serveur
Creez src/index.ts — le point d'entree de votre serveur MCP.
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
// Create the MCP server
const server = new McpServer({
name: "my-mcp-server",
version: "1.0.0",
});
// Connect using stdio transport
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP server running on stdio");
}
main().catch(console.error);Points cles :
McpServerest la classe de haut niveau qui gere la negociation du protocoleStdioServerTransportcommunique via stdin/stdout — le standard pour les serveurs MCP locaux- Nous loggons dans
stderrcarstdoutest reserve pour les messages du protocole MCP
Compilez et verifiez :
npm run buildVous avez maintenant un serveur MCP fonctionnel (mais vide). Ajoutons des capacites.
Etape 3 : Enregistrer les outils
Les outils sont la primitive MCP la plus puissante. Ils permettent aux agents IA d'effectuer des actions — appeler des APIs, interroger des bases de donnees, transformer des donnees ou executer des calculs.
Votre premier outil : Un compteur de mots
Ajoutez ceci avant la fonction main() dans src/index.ts :
import { z } from "zod";
server.tool(
"count_words",
"Count the number of words in a given text",
{
text: z.string().describe("The text to count words in"),
},
async ({ text }) => {
const wordCount = text
.trim()
.split(/\s+/)
.filter((w) => w.length > 0).length;
return {
content: [
{
type: "text",
text: `The text contains ${wordCount} words.`,
},
],
};
}
);Decomposons la signature de server.tool() :
| Parametre | Objectif |
|---|---|
"count_words" | Nom unique de l'outil (snake_case par convention) |
"Count the..." | Description lisible par un humain pour l'IA |
{ text: z.string() } | Schema d'entree avec validation Zod |
async ({ text }) => ... | Fonction handler qui execute l'outil |
Un outil plus pratique : Verificateur de statut URL
server.tool(
"check_url",
"Check if a URL is reachable and return its HTTP status code",
{
url: z.string().url().describe("The URL to check"),
timeout: z
.number()
.optional()
.default(5000)
.describe("Timeout in milliseconds"),
},
async ({ url, timeout }) => {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const response = await fetch(url, {
method: "HEAD",
signal: controller.signal,
});
clearTimeout(timeoutId);
return {
content: [
{
type: "text",
text: JSON.stringify(
{
url,
status: response.status,
statusText: response.statusText,
reachable: true,
},
null,
2
),
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: JSON.stringify(
{
url,
reachable: false,
error:
error instanceof Error ? error.message : "Unknown error",
},
null,
2
),
},
],
isError: true,
};
}
}
);Remarquez le pattern :
- Utilisez Zod pour la validation des entrees — l'IA voit le schema et sait quoi envoyer
- Retournez
contentcomme un tableau de blocs de contenu (text,image, ouresource) - Definissez
isError: truequand l'outil echoue, pour que l'IA gere les erreurs gracieusement
Outil avec sortie structuree
Pour les outils qui retournent des donnees (pas juste des messages), vous pouvez definir un schema de sortie :
server.tool(
"calculate_bmi",
"Calculate Body Mass Index from weight and height",
{
weightKg: z.number().positive().describe("Weight in kilograms"),
heightM: z.number().positive().describe("Height in meters"),
},
async ({ weightKg, heightM }) => {
const bmi = weightKg / (heightM * heightM);
const category =
bmi < 18.5
? "Underweight"
: bmi < 25
? "Normal"
: bmi < 30
? "Overweight"
: "Obese";
return {
content: [
{
type: "text",
text: `BMI: ${bmi.toFixed(1)} (${category})`,
},
],
};
}
);Etape 4 : Exposer les ressources
Les ressources fournissent un contexte en lecture seule a l'IA. Contrairement aux outils (qui effectuent des actions), les ressources sont des donnees que l'IA peut consulter — fichiers de configuration, documentation, schemas de bases de donnees, ou etat du systeme en direct.
Ressource statique
server.resource(
"server-info",
"info://server",
async (uri) => {
return {
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(
{
name: "my-mcp-server",
version: "1.0.0",
capabilities: ["word counting", "URL checking", "BMI calculation"],
uptime: process.uptime(),
},
null,
2
),
},
],
};
}
);Ressource dynamique avec modeles
Les modeles de ressources vous permettent d'exposer des donnees parametrees — comme des enregistrements individuels d'une base de donnees.
// Simulated data store
const projects = new Map([
[
"web-app",
{
name: "Web Application",
status: "active",
tech: ["Next.js", "TypeScript", "PostgreSQL"],
},
],
[
"api",
{
name: "REST API",
status: "active",
tech: ["Hono", "Bun", "SQLite"],
},
],
[
"mobile",
{
name: "Mobile App",
status: "planning",
tech: ["React Native", "Expo"],
},
],
]);
// Resource template — the {id} is a URI parameter
server.resource(
"project",
"projects://{id}",
async (uri) => {
const id = uri.pathname.replace("//", "");
const project = projects.get(id);
if (!project) {
throw new Error(`Project "${id}" not found`);
}
return {
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(project, null, 2),
},
],
};
}
);Quand un agent IA se connecte a votre serveur, il peut parcourir les ressources disponibles et les lire pour obtenir du contexte — exactement comme parcourir des fichiers sur un disque.
Etape 5 : Definir les prompts
Les prompts sont des modeles reutilisables qui guident l'IA vers des taches specifiques. Ce sont comme des instructions sauvegardees qui acceptent des arguments.
server.prompt(
"code-review",
"Review code for bugs, security issues, and best practices",
{
code: z.string().describe("The code to review"),
language: z
.string()
.optional()
.default("typescript")
.describe("Programming language"),
focus: z
.enum(["bugs", "security", "performance", "all"])
.optional()
.default("all")
.describe("What to focus on in the review"),
},
async ({ code, language, focus }) => {
const focusInstructions = {
bugs: "Focus on finding logical errors, edge cases, and potential runtime exceptions.",
security:
"Focus on security vulnerabilities: injection, XSS, authentication flaws, data exposure.",
performance:
"Focus on performance: unnecessary allocations, O(n²) algorithms, missing caching.",
all: "Review for bugs, security vulnerabilities, performance issues, and adherence to best practices.",
};
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `Review the following ${language} code. ${focusInstructions[focus]}
\`\`\`${language}
${code}
\`\`\`
Provide your review as:
1. **Critical Issues** — Must fix before merging
2. **Warnings** — Should fix, but not blocking
3. **Suggestions** — Nice-to-have improvements
4. **Summary** — Overall code quality assessment`,
},
},
],
};
}
);Un autre prompt : Generateur de requetes SQL
server.prompt(
"generate-sql",
"Generate SQL queries from natural language descriptions",
{
description: z
.string()
.describe("Natural language description of the query"),
dialect: z
.enum(["postgresql", "mysql", "sqlite"])
.optional()
.default("postgresql")
.describe("SQL dialect to target"),
schema: z
.string()
.optional()
.describe("Database schema context (CREATE TABLE statements)"),
},
async ({ description, dialect, schema }) => {
let contextBlock = "";
if (schema) {
contextBlock = `\nHere is the database schema:\n\`\`\`sql\n${schema}\n\`\`\`\n`;
}
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `Generate a ${dialect} SQL query for the following request:
"${description}"
${contextBlock}
Requirements:
- Use proper ${dialect} syntax
- Include comments explaining the query
- Use parameterized queries where user input is involved
- Optimize for readability and performance`,
},
},
],
};
}
);Etape 6 : Code complet du serveur
Voici le fichier src/index.ts complet avec toutes les primitives enregistrees. C'est la version complete et executable :
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// ── Server ──────────────────────────────────────────────
const server = new McpServer({
name: "my-mcp-server",
version: "1.0.0",
});
// ── Tools ───────────────────────────────────────────────
server.tool(
"count_words",
"Count the number of words in a given text",
{ text: z.string().describe("The text to count words in") },
async ({ text }) => {
const wordCount = text
.trim()
.split(/\s+/)
.filter((w) => w.length > 0).length;
return {
content: [{ type: "text", text: `The text contains ${wordCount} words.` }],
};
}
);
server.tool(
"check_url",
"Check if a URL is reachable and return its HTTP status code",
{
url: z.string().url().describe("The URL to check"),
timeout: z.number().optional().default(5000).describe("Timeout in ms"),
},
async ({ url, timeout }) => {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
const response = await fetch(url, {
method: "HEAD",
signal: controller.signal,
});
clearTimeout(timeoutId);
return {
content: [
{
type: "text",
text: JSON.stringify(
{
url,
status: response.status,
statusText: response.statusText,
reachable: true,
},
null,
2
),
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: JSON.stringify(
{
url,
reachable: false,
error: error instanceof Error ? error.message : "Unknown error",
},
null,
2
),
},
],
isError: true,
};
}
}
);
// ── Resources ───────────────────────────────────────────
const projects = new Map([
["web-app", { name: "Web Application", status: "active", tech: ["Next.js", "TypeScript", "PostgreSQL"] }],
["api", { name: "REST API", status: "active", tech: ["Hono", "Bun", "SQLite"] }],
["mobile", { name: "Mobile App", status: "planning", tech: ["React Native", "Expo"] }],
]);
server.resource("server-info", "info://server", async (uri) => ({
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(
{
name: "my-mcp-server",
version: "1.0.0",
capabilities: ["word counting", "URL checking"],
uptime: process.uptime(),
},
null,
2
),
},
],
}));
server.resource("project", "projects://{id}", async (uri) => {
const id = uri.pathname.replace("//", "");
const project = projects.get(id);
if (!project) throw new Error(`Project "${id}" not found`);
return {
contents: [
{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(project, null, 2) },
],
};
});
// ── Prompts ─────────────────────────────────────────────
server.prompt(
"code-review",
"Review code for bugs, security issues, and best practices",
{
code: z.string().describe("The code to review"),
language: z.string().optional().default("typescript").describe("Programming language"),
focus: z
.enum(["bugs", "security", "performance", "all"])
.optional()
.default("all")
.describe("Review focus area"),
},
async ({ code, language, focus }) => {
const focusMap = {
bugs: "Focus on logical errors and edge cases.",
security: "Focus on security vulnerabilities.",
performance: "Focus on performance bottlenecks.",
all: "Review bugs, security, performance, and best practices.",
};
return {
messages: [
{
role: "user" as const,
content: {
type: "text" as const,
text: `Review this ${language} code. ${focusMap[focus]}\n\n\`\`\`${language}\n${code}\n\`\`\``,
},
},
],
};
}
);
// ── Start ───────────────────────────────────────────────
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP server running on stdio");
}
main().catch(console.error);Compilez le serveur :
npm run buildEtape 7 : Tester avec le MCP Inspector
Avant de vous connecter a Claude Desktop, utilisez le MCP Inspector pour tester votre serveur de maniere interactive.
npx @modelcontextprotocol/inspector node build/index.jsCela ouvre une interface web ou vous pouvez :
- Lister les outils — Voir tous les outils enregistres et leurs schemas
- Appeler les outils — Executer des outils avec des entrees de test et voir les reponses
- Parcourir les ressources — Voir les ressources disponibles et lire leur contenu
- Utiliser les prompts — Selectionner des prompts, remplir les arguments et voir les messages generes
Essayez d'appeler l'outil count_words avec "Hello world, this is my MCP server" — vous devriez voir la reponse The text contains 7 words.
Etape 8 : Connecter a Claude Desktop
Ouvrez votre fichier de configuration Claude Desktop :
- macOS :
~/Library/Application Support/Claude/claude_desktop_config.json - Windows :
%APPDATA%\Claude\claude_desktop_config.json
Ajoutez votre serveur :
{
"mcpServers": {
"my-mcp-server": {
"command": "node",
"args": ["/absolute/path/to/my-mcp-server/build/index.js"]
}
}
}Important : Utilisez le chemin absolu vers votre fichier build/index.js. Les chemins relatifs ne fonctionneront pas. Remplacez /absolute/path/to/ par le chemin reel de votre projet.
Redemarrez Claude Desktop. Vous devriez voir une icone de marteau indiquant que les outils sont disponibles. Essayez de demander a Claude :
- "Compte les mots dans ce paragraphe : ..."
- "Verifie si https://noqta.tn est accessible"
- "Utilise le prompt de revue de code pour revoir cette fonction"
Etape 9 : Connecter a Cursor
Cursor supporte nativement les serveurs MCP. Ajoutez votre serveur dans les parametres MCP de Cursor :
- Ouvrez Cursor Settings (Cmd+Shift+J sur macOS)
- Naviguez vers MCP Servers
- Cliquez sur Add Server
- Configurez :
- Name :
my-mcp-server - Type :
stdio - Command :
node /absolute/path/to/my-mcp-server/build/index.js
- Name :
Alternativement, creez un fichier .cursor/mcp.json a la racine de votre projet :
{
"mcpServers": {
"my-mcp-server": {
"command": "node",
"args": ["/absolute/path/to/my-mcp-server/build/index.js"]
}
}
}Vos outils sont maintenant disponibles dans le mode Agent de Cursor. L'IA peut decouvrir et appeler vos outils automatiquement pendant les sessions de developpement.
Etape 10 : Ajouter la gestion des erreurs et la journalisation
Les serveurs MCP en production necessitent une gestion des erreurs robuste. Voici un pattern pour des outils fiables :
server.tool(
"read_json_file",
"Read and parse a JSON file",
{
path: z.string().describe("Path to the JSON file"),
},
async ({ path }) => {
try {
const { readFile } = await import("node:fs/promises");
const content = await readFile(path, "utf-8");
const parsed = JSON.parse(content);
return {
content: [
{
type: "text",
text: JSON.stringify(parsed, null, 2),
},
],
};
} catch (error) {
if (error instanceof SyntaxError) {
return {
content: [{ type: "text", text: `Invalid JSON in file: ${path}` }],
isError: true,
};
}
const nodeError = error as NodeJS.ErrnoException;
if (nodeError.code === "ENOENT") {
return {
content: [{ type: "text", text: `File not found: ${path}` }],
isError: true,
};
}
return {
content: [
{
type: "text",
text: `Error reading file: ${nodeError.message}`,
},
],
isError: true,
};
}
}
);Pratiques cles :
- Retournez toujours un resultat, meme en cas d'erreur — ne lancez jamais d'exception depuis un handler d'outil
- Definissez
isError: truepour que l'IA sache que l'outil a echoue - Fournissez des messages d'erreur utiles pour que l'IA puisse se recuperer ou informer l'utilisateur
- Loggez dans stderr pour le debogage :
console.error("Debug:", someValue)
Depannage
Le serveur n'apparait pas dans Claude Desktop
- Verifiez le chemin du fichier de configuration et la syntaxe JSON
- Assurez-vous que le chemin absolu vers
build/index.jsest correct - Redemarrez completement Claude Desktop (quittez et rouvrez)
- Verifiez les logs :
~/Library/Logs/Claude/mcp*.logsur macOS
"Could not connect to MCP server"
- Testez votre serveur directement :
echo '{}' | node build/index.js— il ne doit pas planter - Assurez-vous que
#!/usr/bin/env nodeest en haut du fichier JS compile - Rendez le fichier executable :
chmod +x build/index.js
Les outils n'apparaissent pas
- Verifiez que les outils sont enregistres avant
server.connect(transport) - Verifiez les erreurs de compilation TypeScript :
npm run build - Utilisez le MCP Inspector pour verifier :
npx @modelcontextprotocol/inspector node build/index.js
Conseils de debogage
Ajoutez de la journalisation de debogage basee sur l'environnement :
const DEBUG = process.env.MCP_DEBUG === "true";
function debug(...args: unknown[]) {
if (DEBUG) console.error("[DEBUG]", ...args);
}
// Use in tool handlers:
debug("check_url called with:", url);Executez avec le debogage :
MCP_DEBUG=true node build/index.jsProchaines etapes
Maintenant que vous avez un serveur MCP fonctionnel, voici comment l'etendre :
- Ajouter une integration base de donnees — Connectez-vous a PostgreSQL, SQLite ou MongoDB et exposez les requetes comme outils
- Construire un transport HTTP — Deployez votre serveur comme API distante avec
StreamableHTTPServerTransport - Publier sur npm — Partagez votre serveur avec la communaute via
npx - Ajouter l'authentification — Implementez la validation de cle API pour les outils sensibles
- Explorer l'ecosysteme — Parcourez les exemples de serveurs MCP sur GitHub pour vous inspirer
Tutoriels connexes
- Adaptateur WordPress MCP : Rendre votre site pret pour les agents IA
- Automatisation agentique gouvernee par MCP
Conclusion
Vous avez construit un serveur MCP entierement fonctionnel qui expose des outils, des ressources et des prompts aux agents IA. Le Model Context Protocol devient rapidement le standard pour l'integration IA-outils, et savoir construire des serveurs vous place au coeur de cet ecosysteme.
Les points cles a retenir :
- Les outils permettent aux agents IA d'effectuer des actions — c'est la primitive la plus puissante
- Les ressources fournissent du contexte — des donnees que l'IA peut lire pour prendre de meilleures decisions
- Les prompts sont des modeles reutilisables — ils guident l'IA vers des workflows specifiques
- Le transport stdio est le standard pour les serveurs locaux — simple et fiable
- La gestion des erreurs est essentielle — retournez toujours des resultats, ne lancez jamais d'exceptions depuis les handlers
Commencez par un probleme reel : encapsulez une API que vous utilisez quotidiennement, exposez une base de donnees que vous interrogez souvent, ou creez des outils qui automatisent les workflows de votre equipe. Les meilleurs serveurs MCP resolvent des problemes specifiques et pratiques.
Discutez de votre projet avec nous
Nous sommes ici pour vous aider avec vos besoins en développement Web. Planifiez un appel pour discuter de votre projet et comment nous pouvons vous aider.
Trouvons les meilleures solutions pour vos besoins.
Articles connexes

Adaptateur MCP WordPress : Rendez votre site compatible avec les agents IA
Apprenez a installer et configurer l'adaptateur MCP WordPress pour rendre votre site accessible aux agents IA dans Cursor, Claude Desktop et autres outils compatibles MCP. Guide complet etape par etape avec des exemples pratiques.

Construire un Agent IA Autonome avec Agentic RAG et Next.js
Apprenez a construire un agent IA qui decide de maniere autonome quand et comment recuperer des informations depuis des bases de donnees vectorielles. Un guide pratique complet avec Vercel AI SDK et Next.js, accompagne d'exemples executables.

Construire des agents IA from scratch avec TypeScript : maîtriser le pattern ReAct avec le Vercel AI SDK
Apprenez à construire des agents IA depuis zéro avec TypeScript. Ce tutoriel couvre le pattern ReAct, l'appel d'outils, le raisonnement multi-étapes et les boucles d'agents prêtes pour la production avec le Vercel AI SDK.