Le Model Context Protocol (MCP) est devenu en 2026 le standard de l’industrie pour connecter des sources de données et des outils métier aux LLM. Lancé par Anthropic et adopté par Cursor, Continue, Cline et de nombreux autres clients IA, MCP permet d’écrire UN serveur réutilisable qui expose vos APIs métier — votre CRM, votre ERP, votre base produits, vos tickets — et de le brancher à n’importe quel agent compatible. Voici le tutoriel complet pour créer votre propre MCP server custom et l’utiliser depuis Claude Agent SDK ou Claude Code.
Ce tutoriel s’inscrit dans notre série Claude Agent SDK. Pour les bases, voir notre guide complet Claude Agent SDK 2026.
Pourquoi MCP plutôt que des outils inline ?
- Réutilisabilité : un MCP server peut servir plusieurs agents (assistant interne, agent client, agent dev) sans dupliquer le code
- Compatibilité multi-clients : votre serveur fonctionne avec Claude Code, Cursor, Continue, Cline, et tout futur client MCP
- Séparation propre : la logique métier vit dans son repo, son cycle de vie, ses tests, indépendamment des prompts
- Versionnage : vous pouvez déployer une nouvelle version du MCP sans toucher aux agents qui l’utilisent
- Sécurité : un MCP server peut tourner avec ses propres credentials (API keys, DB) sans les exposer à chaque agent
- Langage au choix : MCP server en TypeScript, Python, Go, Rust, n’importe quel langage qui peut parler stdio ou HTTP
Architecture MCP en bref
Un MCP server expose trois types de capacités :
- Tools : fonctions appelables (similaire aux tools du SDK)
- Resources : données lisibles (fichiers, configurations, URI)
- Prompts : templates de prompts prêts à l’emploi
La communication se fait soit via stdio (standard input/output, pour les serveurs locaux), soit via HTTP/SSE (pour les serveurs distants ou hébergés). Stdio est plus simple à démarrer ; HTTP/SSE est nécessaire pour les serveurs partagés ou en cloud.
Cas d’usage : MCP CRM
Pour ce tutoriel, on construit un MCP server qui expose les opérations courantes d’un CRM : lister clients, lire un client, créer une note, lister les opportunités. Une fois le serveur prêt, n’importe quel agent peut l’utiliser pour rédiger des emails de suivi, générer des rapports, ou analyser des données.
Prérequis
- Node.js 20+ ou Bun
- Connaissance TypeScript intermédiaire
- Notion d’API REST
- Niveau attendu : intermédiaire
- Temps : 2-3 heures
Étape 1 — Setup
mkdir mcp-crm && cd mcp-crm
bun init -y
bun add @modelcontextprotocol/sdk zod
Étape 2 — Squelette du serveur
// src/server.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
const server = new Server(
{ name: "mcp-crm", version: "1.0.0" },
{ capabilities: { tools: {} } },
);
// Lister les outils disponibles
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "list_clients",
description: "Liste les clients du CRM",
inputSchema: {
type: "object",
properties: {
search: { type: "string", description: "Mot-clé de recherche optionnel" },
limit: { type: "number", description: "Nombre max de résultats", default: 20 },
},
},
},
{
name: "get_client",
description: "Récupère les détails d'un client par ID",
inputSchema: {
type: "object",
properties: { id: { type: "string" } },
required: ["id"],
},
},
{
name: "create_note",
description: "Crée une note interne sur un client",
inputSchema: {
type: "object",
properties: {
clientId: { type: "string" },
content: { type: "string" },
},
required: ["clientId", "content"],
},
},
],
}));
const transport = new StdioServerTransport();
await server.connect(transport);
Étape 3 — Implémenter les tools
// src/server.ts (suite)
import { listClients, getClient, createNote } from "./crm-api";
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
if (name === "list_clients") {
const result = await listClients({
search: args?.search as string,
limit: (args?.limit as number) ?? 20,
});
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
if (name === "get_client") {
const result = await getClient(args?.id as string);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
if (name === "create_note") {
const result = await createNote({
clientId: args?.clientId as string,
content: args?.content as string,
});
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
}
return {
content: [{ type: "text", text: `Unknown tool: ${name}` }],
isError: true,
};
} catch (error: any) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
});
Étape 4 — Le module CRM API
// src/crm-api.ts
const CRM_BASE_URL = process.env.CRM_BASE_URL ?? "https://crm.exemple.sn/api/v1";
const CRM_TOKEN = process.env.CRM_TOKEN!;
async function fetchCRM(path: string, init: RequestInit = {}) {
const res = await fetch(`${CRM_BASE_URL}${path}`, {
...init,
headers: {
"Authorization": `Bearer ${CRM_TOKEN}`,
"Content-Type": "application/json",
...init.headers,
},
});
if (!res.ok) throw new Error(`CRM ${res.status}: ${await res.text()}`);
return res.json();
}
export async function listClients({ search, limit }: { search?: string; limit: number }) {
const params = new URLSearchParams();
if (search) params.set("search", search);
params.set("limit", String(limit));
return fetchCRM(`/clients?${params}`);
}
export async function getClient(id: string) {
return fetchCRM(`/clients/${id}`);
}
export async function createNote({ clientId, content }: { clientId: string; content: string }) {
return fetchCRM(`/clients/${clientId}/notes`, {
method: "POST",
body: JSON.stringify({ content, createdBy: "agent-mcp" }),
});
}
Étape 5 — Tester le serveur localement
# Démarrer en mode dev
bun run --watch src/server.ts
# Tester avec MCP inspector (outil officiel)
npx @modelcontextprotocol/inspector bun run src/server.ts
L’inspector ouvre une UI web où vous voyez les tools exposés et pouvez les appeler avec des paramètres pour vérifier les réponses. Très pratique pour debugger avant de brancher un agent.
Étape 6 — Brancher à Claude Code
Dans le fichier ~/.claude/settings.json ou en CLI :
{
"mcpServers": {
"crm": {
"command": "bun",
"args": ["run", "/chemin/absolu/vers/mcp-crm/src/server.ts"],
"env": {
"CRM_TOKEN": "secret-token-prod",
"CRM_BASE_URL": "https://crm.exemple.sn/api/v1"
}
}
}
}
Lancez claude. L’agent voit maintenant les outils list_clients, get_client, create_note. Demandez : « Liste les 5 derniers clients ajoutés et résume leur activité. » L’agent va appeler list_clients, puis get_client pour chacun, et synthétiser.
Étape 7 — Brancher au Claude Agent SDK
// Dans votre code Agent SDK
import { query } from "@anthropic-ai/claude-agent-sdk";
const result = query({
prompt: "Donne-moi un point sur le compte client ID 42",
options: {
mcpServers: {
crm: {
command: "bun",
args: ["run", "/chemin/vers/mcp-crm/src/server.ts"],
env: {
CRM_TOKEN: process.env.CRM_TOKEN!,
},
},
},
},
});
Étape 8 — Déployer en HTTP/SSE
Pour partager le MCP server entre plusieurs machines (équipe distante, CI, agent en cloud), utilisez le transport HTTP/SSE plutôt que stdio :
// src/server-http.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";
const app = express();
const server = new Server(/* config */);
// Auth basique pour limiter l'accès
app.use((req, res, next) => {
if (req.headers["authorization"] !== `Bearer ${process.env.MCP_TOKEN}`) {
return res.status(401).end();
}
next();
});
app.get("/sse", async (req, res) => {
const transport = new SSEServerTransport("/messages", res);
await server.connect(transport);
});
app.listen(3001);
Le serveur peut alors être hébergé sur Coolify (voir notre guide Coolify) avec HTTPS automatique. Les agents s’y connectent en pointant l’URL.
Étape 9 — Bonnes pratiques production
- Logging structuré : chaque tool call dans une base d’audit avec timestamp, agent appelant, paramètres, résultat
- Rate limiting : protéger les APIs métier en aval (limite par agent et par tool)
- Validation stricte avec Zod côté serveur, ne jamais faire confiance aux paramètres reçus
- Permissions par tool : certains tools écrivent (create_note, update_client), d’autres lisent uniquement. Différenciez les credentials et les permissions
- Versionnage : tag SemVer dans
name: "mcp-crm@1.2.0", versioning d’API stable - Health check : un tool
pingqui retourne OK si tout va bien, pour superviser depuis Uptime Kuma - Documentation des tools :
descriptionclaire etinputSchemaprécis. C’est ce qui aide le LLM à utiliser correctement votre serveur
MCP servers populaires existants
Avant d’écrire le vôtre, vérifiez s’il existe déjà :
- GitHub, GitLab, Linear, Notion, Slack — officiels Anthropic ou communauté
- PostgreSQL, MySQL, MongoDB — accès en lecture aux DB
- Filesystem, Git, Memory, Playwright — utilitaires
- Stripe, Jira, Trello — services SaaS
Catalogue communautaire sur github.com/modelcontextprotocol/servers.
Adaptation Afrique de l’Ouest
Pour les PME ouest-africaines, créer un MCP server custom autour de votre stack métier (Odoo, ERPNext, votre propre application interne, votre catalogue Excel converti en API) débloque tout un écosystème d’agents IA productifs : assistant facturation, agent SEO, agent support. Hébergez le MCP en local sur un VPS Hetzner ou un serveur national, branchez-y un agent Claude, et vous avez un automate qui comprend votre métier.
Erreurs fréquentes
| Erreur | Cause | Solution |
|---|---|---|
| Tools non listés dans Claude Code | Path absolu manquant ou mal écrit | Utiliser chemin absolu, tester avec MCP inspector d’abord |
| JSON parsing failed | Réponse non JSON valide | Toujours envelopper dans content: [{ type: « text », text: … }] |
| Server crash silencieux | Exception non catchée | try/catch global dans le handler, logger l’erreur |
| Token expiré | Pas de refresh logic | Implémenter refresh automatique du token CRM |
| Données sensibles fuitent | Logs trop verbeux | Masquer mots de passe, tokens, PII dans les logs |