
ESLint Plugin : Créer une règle personnalisée
Découvrez comment créer un plugin ESLint en TypeScript avec la nouvelle configuration "flat config" et publiez-le sur npm.
Sommaire
•
Qu'est-ce que le Model Context Prodocol (MCP) ?
•
Quelles différences entre le MCP et les APIs traditionnelles ?
•
Quels sont les bénéfices de créer un MCP Server ?
•
L'architecture du MCP
•
Choix du transport et des couches de communication
•
Les trois fonctionnalités piliers du Model Context Protocol
•
Cas d'usages d'implémentations existantes
•
Exemple d'implémentation pratique du serveur MCP
•
Test et debug avec l'inspecteur MCP
•
Intégration avec Claude Desktop
•
Conclusion
•
Ressources supplémentaires
Le Model Context Protocol (MCP) représente une avancée majeure dans l'écosystème des modèles de langage (LLMs). Ce protocole standardisé a été introduit par Anthropic en novembre 2024 et permet aux LLMs d'accéder en temps réel au contexte spécifique des utilisateurs depuis différentes sources de données.
Depuis son lancement, le MCP a connu une adoption remarquablement rapide dans l'industrie. OpenAI a adopté le standard MCP en mars 2025, suivi rapidement par Google en avril 2025 et par Microsoft lors d'une annonce de Build 2025, démontrant l'engagement de l'ensemble de l'industrie vers cette standardisation.
Dans cet article, nous allons voir comment implémenter un serveur MCP en TypeScript avec le SDK officiel @modelcontextprotocol/sdk
. Nous prendrons comme exemple un système de classement spatial avec des astronautes et des planètes, illustrant les concepts fondamentaux du protocole MCP.
Code source
Le code source complet de ce projet est disponible sur GitHub. Vous y trouverez tous les fichiers mentionnés dans cet article ainsi que des exemples d'utilisation supplémentaires.
Mais avant de plonger dans l'implémentation, prenons un moment pour comprendre ce qu'est le MCP et pourquoi il est devenu si important pour les applications modernes basées sur l'IA.
Le Model Context Protocol est un standard ouvert qui définit comment les modèles d'IA peuvent demander et recevoir du contexte spécifique à un utilisateur ou à une organisation. Ce protocole facilite l'intégration des LLMs avec des sources de données externes comme les outils de développement, les systèmes CRM, ou les bases de connaissances d'entreprise.
Le MCP s'appuie sur les fondations du Language Server Protocol (LSP), un standard éprouvé dans l'écosystème des éditeurs de code. Cette base solide garantit une architecture robuste et familière aux développeurs, tout en adaptant les concepts aux besoins spécifiques des modèles de langage (LLMs).
À retenir
Le MCP résout un problème fondamental des LLMs : leur incapacité à accéder aux données privées ou spécifiques d'une organisation. En standardisant la façon dont ces modèles peuvent demander et recevoir du contexte, le MCP permet des intégrations plus profondes et des réponses plus pertinentes.
La différence fondamentale entre le MCP et les APIs traditionnelles réside dans leur conception et leur finalité :
Les APIs REST, GraphQL ou RPC sont conçues pour la communication entre applications. Elles suivent des paradigmes techniques stricts :
Le MCP adopte une approche radicalement différente, pensée pour les capacités cognitives des LLMs :
Un MCP Server offre plusieurs avantages :
Le protocole MCP suit une architecture client-serveur simple :
flowchart LR subgraph subGraph0["Hôte MCP"] direction TB llm["🧠 LLM"] client["🧩 Client MCP"] transport["🔌 Transport client"] end subgraph subGraph1["Processus serveur"] server["🗄️ Serveur MCP"] tools["🛠️ Outils"] end llm <--> client client <--> transport transport <--> server server <--> tools
Le processus d'interaction suit un cycle orchestré qui transforme une simple question utilisateur en une série d'actions intelligentes et contextualisées :
À retenir
Cette orchestration permet au système de fonctionner de manière totalement transparente pour l'utilisateur final. L'utilisateur n'a pas conscience de la complexité technique sous-jacente - il formule simplement sa demande en langage naturel et reçoit une réponse enrichie et contextuelle.
C'est cette symbiose entre intelligence artificielle et données métier qui fait la force du protocole MCP.
Le choix du transport détermine comment votre serveur MCP communique avec les clients. Trois options principales sont disponibles :
Le transport STDIO est parfait pour :
// packages/mcp/src/mcp-server-stdio.ts
#!/usr/bin/env node
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { McpServer } from './mcp-server.js';
async function run() {
const mcpServer = new McpServer();
const transport = new StdioServerTransport();
await mcpServer.connect(transport);
console.error('MCP server started successfully on stdio');
}
run().catch((error) => {
console.error('Fatal error in run():', error);
process.exit(1);
});
Le transport SSE convient mieux pour :
// apps/mcp-server-sse/src/mcp-server-sse.ts
import { registerMcpServerSse } from '@repo/mcp/utils/register-mcp-server-sse';
import cors from 'cors';
import express from 'express';
const PORT = process.env.PORT ?? 3000;
const app = express();
app.use(cors());
registerMcpServerSse(app);
app.listen(PORT, () => {
console.error(`MCP SSE server started on port ${PORT}`);
console.error(`SSE endpoint: http://localhost:${PORT}/sse`);
});
Vous pouvez également créer votre propre couche de transport en implémentant les interfaces MCP. Cela permet d'adapter la communication à vos besoins spécifiques (WebSocket, TCP, etc.).
À retenir
Un serveur MCP peut exposer trois types de fonctionnalités :
Plusieurs grandes entreprises ont déjà adopté le MCP :
Ces implémentations permettent aux LLMs d'accéder à des données spécifiques comme les tickets Jira, les pull requests GitHub, ou les documents Notion, tout en respectant les permissions des utilisateurs.
Commençons par créer la classe principale de notre serveur MCP :
// packages/mcp/src/mcp-server.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
export class McpServer extends Server {
constructor() {
super(
{
name: 'spatial-ranking-server',
version: '1.0.0',
},
{
capabilities: {
prompts: {},
resources: {},
tools: {},
},
}
);
this.setupToolHandlers();
this.setupResourceHandlers();
this.setupPromptHandlers();
}
// Les méthodes d'implémentation suivront...
}
Cette classe hérite de la classe Server
du SDK MCP et déclare ses capacités lors de l'initialisation. Les trois méthodes de configuration organisent la logique selon les trois piliers du protocole MCP.
Les tools constituent l'élément le plus dynamique et interactif de votre serveur MCP. Ils permettent aux LLMs d'effectuer des actions concrètes et d'obtenir des résultats en temps réel. Contrairement aux ressources qui sont statiques, les tools peuvent modifier l'état de votre système, effectuer des calculs, ou interagir avec des APIs externes.
L'implémentation des tools suit un modèle en trois étapes : définition du schéma de validation, implémentation de la logique métier, et enregistrement des handlers sur le serveur. Cette approche garantit une validation robuste des entrées et une gestion d'erreur cohérente.
// packages/mcp/src/tools.ts import { z } from 'zod'; import { zodToJsonSchema } from 'zod-to-json-schema'; export const SearchAstronautSchema = z.object({ astronautName: z.string().describe("Nom de l'astronaute à rechercher"), }); export const AddPointsToAstronaut = z.object({ astronautName: z.string().describe("Nom de l'astronaute"), points: z.number().describe('Nombre de points à ajouter'), reason: z.string().describe("Raison de l'attribution des points").optional(), }); export const TOOLS = [ { description: 'Affiche le classement actuel des planètes par points', inputSchema: zodToJsonSchema(z.object({})), name: 'get_planet_rankings', }, { description: "Recherche d'un astronaute par son nom", inputSchema: zodToJsonSchema(SearchAstronautSchema), name: 'search_astronaut', }, { name: 'add_points_to_astronaut', description: 'Ajoute des points à un astronaute et met à jour son grade', inputSchema: zodToJsonSchema(AddPointsToAstronaut), }, ] as const;
// packages/mcp/src/tools.ts
export const getPlanetRankings = () => {
const planetsWithRanking = services.getPlanetsWithRanking();
return {
content: [
{
text:
`🏆 CLASSEMENT DES PLANÈTES - SAISON GALACTIQUE 2025\n\n` +
planetsWithRanking
.map(
(p) =>
`${p.position}. ${p.name}\n Points: ${p.points.toLocaleString()} | Astronautes: ${p.astronauts.length}`
)
.join('\n\n') +
`\n\n⭐ ${planetsWithRanking[0]!.name} mène la course !`,
type: 'text',
},
],
};
};
export const searchAstronaut = ({ astronautName }: z.infer<typeof SearchAstronautSchema>) => {
try {
const astronaut = services.searchAstronaut({ astronautName });
if (!astronaut) throw new Error(`Astronaut "${astronautName}" not found`);
return {
content: [
{
text:
`🚀 PROFIL ASTRONAUTE\n\n` +
`👤 Nom: ${astronaut.name}\n` +
`🌟 Grade: ${astronaut.rank.name}\n` +
`🪐 Planète: ${astronaut.planet.name}\n` +
`🏆 Points personnels: ${astronaut.points}\n` +
`💫 Contribution pour ça planète: ${((astronaut.points / astronaut.planet.points) * 100).toFixed(1)}%`,
type: 'text',
},
],
};
} catch (error) {
return {
content: [
{
text: `❌ ${error instanceof Error ? error.message : 'Unknown error'}`,
type: 'text',
},
],
};
}
};
export const addPointsToAstronaut = ({ astronautName, points, reason }: z.infer<typeof AddPointsToAstronaut>) => {
try {
const astronaut = services.searchAstronaut({ astronautName });
if (!astronaut) {
throw new Error(`Astronaut "${astronautName}" not found`);
}
const astronautUpdated = services.addRewardToAstronaut({ astronautId: astronaut.id, points, reason });
const gradeChange =
astronaut.rank.name !== astronautUpdated.rank.name
? `\n🎉 PROMOTION: ${astronaut.rank.name} → ${astronautUpdated.rank.name} !`
: '';
return {
content: [
{
type: 'text',
text:
`✅ POINTS ATTRIBUÉS\n\n` +
`👤 Astronaute: ${astronautUpdated.name}\n` +
`➕ Points ajoutés: +${points}\n` +
`📝 Raison: ${reason}\n` +
`🏆 Total personnel: ${astronautUpdated.points} pts\n` +
gradeChange,
},
],
};
} catch (error) {
return {
content: [
{
text: `❌ ${error instanceof Error ? error.message : 'Unknown error'}`,
type: 'text',
},
],
};
}
};
// packages/mcp/src/server.ts
private setupToolHandlers() {
this.setRequestHandler(ListToolsRequestSchema, () => ({
tools: TOOLS,
}));
this.setRequestHandler(CallToolRequestSchema, (request) => {
switch (request.params.name) {
case 'search_astronaut': {
const args = SearchAstronautSchema.parse(request.params.arguments);
return searchAstronaut(args);
}
case 'add_points_to_astronaut': {
const args = AddPointsSchema.parse(request.params.arguments);
return addPointsToAstronaut(args);
}
case 'get_planet_rankings':
return getPlanetRankings();
default:
throw new Error(`Tool inconnu: ${request.params.name}`);
}
});
}
Bonnes pratiques
Les ressources constituent la mémoire documentaire de votre serveur MCP. Contrairement aux tools qui exécutent des actions, les ressources fournissent un accès structuré à des données de référence, de la documentation, ou du contenu statique que les LLMs peuvent consulter pour enrichir leurs réponses.
Les ressources sont particulièrement utiles pour exposer des bases de connaissances, des documentations techniques, des guides d'utilisation, ou des données de configuration. Elles permettent aux LLMs d'accéder à ces informations sans avoir à les répéter dans chaque conversation, économisant ainsi des tokens et garantissant la cohérence des informations.
// packages/mcp/src/resources.ts export const RESOURCES = [ { name: 'rules.md', description: 'Règles complètes du système de classement spatial', mimeType: 'text/markdown', uri: 'file://rules.md', }, ];
// packages/mcp/src/server.ts
private setupResourceHandlers() {
this.setRequestHandler(ListResourcesRequestSchema, () => ({
resources: RESOURCES,
}));
this.setRequestHandler(ReadResourceRequestSchema, (request) => {
const { uri } = request.params;
if (uri === 'file://rules.md') {
return {
contents: [{
mimeType: 'text/markdown',
text: readFileSync('./resources/rules.md', 'utf-8'),
uri,
}],
};
}
throw new Error(`Ressource inconnue: ${uri}`);
});
}
Bonnes pratiques
file://docs/api.md
, db://schema/users
)file://docs/v2/api.md
)Les prompts représentent l'aspect le plus créatif du protocole MCP. Ils permettent de créer des templates de conversation intelligents et réutilisables qui peuvent être paramétrés dynamiquement. Ces templates offrent une approche standardisée pour générer des prompts complexes adaptés à des contextes spécifiques.
Les prompts MCP sont particulièrement puissants car ils peuvent intégrer des données en temps réel, permettant de créer des conversations contextualisées et personnalisées. Ils servent de point d'entrée pour des workflows complexes, des analyses automatisées, ou des rapports personnalisés.
// src/prompts.ts export const WeeklyReportSchema = z.object({ weekNumber: z.string() .describe('Numéro de la semaine') .optional() .default(getCurrentWeek()), }); export const PROMPTS = [ { name: 'weekly_report', description: 'Génère un rapport hebdomadaire des performances', arguments: [{ name: 'weekNumber', description: 'Numéro de la semaine (optionnel)', required: false, }], }, ] as const;
// src/prompts.ts
export const weeklyReport = (weekNumber: string) => {
const rankings = services.getPlanets().sort((a, b) => b.points - a.points);
const topAstronauts = services.getAstronauts().slice(0, 3);
return {
description: `Rapport hebdomadaire S${weekNumber}`,
messages: [{
role: 'user',
content: {
type: 'text',
text: `Génère un rapport professionnel pour la semaine ${weekNumber}.
Données actuelles:
- Classement: ${rankings.map(p => `${p.name} (${p.points} pts)`).join(', ')}
- Top 3: ${topAstronauts.map(a => `${a.name} (${a.points} pts)`).join(', ')}
Format: titre accrocheur, classement avec évolution, performances individuelles.`,
},
}],
};
};
// packages/mcp/src/server.ts
private setupPromptHandlers() {
this.setRequestHandler(ListPromptsRequestSchema, () => {
return {
prompts: PROMPTS,
};
});
this.setRequestHandler(GetPromptRequestSchema, (request) => {
try {
switch (request.params.name) {
case 'weekly_report': {
const args = GetWeeklyReportSchema.parse(request.params.arguments);
return weeklyReport(args.weekNumber);
}
default:
throw new Error(`Unknown prompt: ${request.params.name}`);
}
} catch (error) {
if (error instanceof z.ZodError) {
throw new Error(`Invalid input: ${JSON.stringify(error.errors)}`);
}
throw error;
}
});
}
Bonnes pratiques
L'inspecteur MCP est un outil essentiel pour tester et déboguer votre serveur. Il fournit une interface web interactive pour explorer vos tools, ressources et prompts.
# Lancement avec votre serveur npx @modelcontextprotocol/inspector node path/to/dist/mcp-server-stdio.js
L'inspecteur se lance sur http://localhost:6274
et affiche une interface similaire à celle-ci :
L'onglet "Tools" permet de tester chaque fonction individuellement. Voici un exemple de test du tool search_astronaut
:
L'inspecteur génère automatiquement des formulaires basés sur vos schémas Zod et affiche les réponses formatées.
L'onglet "Resources" liste toutes vos ressources disponibles et permet de les consulter directement :
L'onglet "Prompts" permet de tester vos templates avec différents paramètres :
Bonnes pratiques
Ajoutez votre serveur dans le fichier de configuration de Claude Desktop :
{ "mcpServers": { "spatial-system": { "command": "node", "args": ["path/to/your/dist/mcp-server-stdio.js"] } } }
Voici quelques exemples d'interactions avec Claude Desktop utilisant notre serveur MCP :
Prompt : "Peux-tu me montrer le classement actuel des planètes ?"
Claude appelle automatiquement le tool get_planet_rankings
et présente les résultats de manière engageante.
Prompt : "Donne-moi les détails sur l'astronaute Alice"
Le tool search_astronaut
est appelé avec le paramètre { "astronautName": "Alice" }
.
Prompt : "Ajoute 50 points à Alice pour sa mission réussie"
Claude utilise le tool add_points_to_astronaut
avec les paramètres :
{ "astronautName": "Alice", "points": 50, "reason": "mission réussie" }
Prompt : "Génère-moi le rapport hebdomadaire de cette semaine"
Le prompt weekly_report
est listé automatiquement, mais ils sont conçues pour être contrôlées par l'utilisateur, ce qui signifie que vous devez l'ajouter manuellement à votre conversation pour que Claude l'utilise dans sa génération de rapport personnalisé.
Prompt : "Explique-moi les règles du système de gamification"
La ressource rules.md
est listée automatiquement, mais vous devez l'ajouter manuellement à votre conversation pour que Claude puisse consulter son contenu et fournir une explication complète.
Vous pouvez voir une conversation complète démontrant toutes ces fonctionnalités dans cette conversation publique Claude Desktop.
À retenir
Le MCP représente bien plus qu'une simple évolution technique : c'est un changement paradigmatique dans la façon dont les LLMs interagissent avec nos systèmes d'information. Contrairement aux APIs traditionnelles qui nécessitent une programmation explicite pour chaque cas d'usage, le MCP permet aux modèles de langage de découvrir, comprendre et utiliser intelligemment les ressources disponibles selon le contexte de la conversation.
Le MCP apporte ainsi une expérience utilisateur plus fluide, plus naturelle et plus puissante : l’IA ne se contente plus de répondre à une question, elle agit comme un agent intelligent, capable de comprendre l’environnement métier, de naviguer dans des sources complexes, et d’automatiser des tâches à forte valeur ajoutée.
En somme, le MCP ne remplace pas les API traditionnelles, mais les complète et les transcende, en s’adaptant aux besoins exprimés. Il marque le passage d’un modèle statique à un modèle conversationnel, modulaire et interopérable.
Grâce au SDK TypeScript, il devient possible de créer des serveurs MCP robustes, capables d’exposer des fonctionnalités métier, des données contextuelles ou des outils dynamiques via une interface standardisée.
À retenir
Dans un prochain article, nous explorerons comment créer des clients MCP personnalisés et comment intégrer MCP dans des applications web modernes avec des frameworks comme Next.js.
Auteur(s)
Fabien Pasquet
Développeur Full Stack JS @ElevenLabs. Technologies de prédilection : Typescript, GraphQL, NodeJS et React
Vous souhaitez en savoir plus sur le sujet ?
Organisons un échange !
Notre équipe d'experts répond à toutes vos questions.
Nous contacterDécouvrez nos autres contenus dans le même thème
Découvrez comment créer un plugin ESLint en TypeScript avec la nouvelle configuration "flat config" et publiez-le sur npm.
Apprenez à concevoir une barre de recherche accessible pour le web, conforme RGAA. Bonnes pratiques, erreurs fréquentes à éviter et exemples concrets en HTML et React/MUI.
L'Anchor positioning API est arrivée en CSS depuis quelques mois. Expérimentale et uniquement disponible à ce jour pour les navigateurs basés sur Chromium, elle est tout de même très intéressante pour lier des éléments entre eux et répondre en CSS à des problématiques qui ne pouvaient se résoudre qu'en JavaScript.