Construire son propre serveur MCP
Quand un connecteur existant ne suffit pas, et comment concevoir les outils qu’on expose.

Construisez votre propre serveur MCP quand aucun connecteur n’existe pour un système dont votre agent a besoin, ou quand celui qui existe expose les mauvaises choses : des endpoints d’API bruts que le modèle doit enchaîner lui-même, aucun contrôle des coûts, ou plus de droits d’écriture que vous n’êtes prêt à en donner. Dans tous les autres cas, prenez le connecteur existant. Le code d’un serveur est la partie facile. Le travail, c’est de décider quels outils exposer, comment les décrire, et ce que le serveur refuse de faire quoi que le modèle demande.
J’ai cofondé Derrick, un outil d’enrichissement de données et de prospection B2B, et j’en ai construit moi-même le serveur MCP. Ce chantier nourrit les conseils qui suivent, mais les exemples sont des modèles génériques, pas une description du fonctionnement du serveur de Derrick.
Construire ou réutiliser
| Situation | Décision |
|---|---|
| L’éditeur publie un serveur MCP qui couvre vos lectures et écritures | Le réutiliser, avec une clé restreinte |
| L’éditeur a une API mais pas de serveur MCP | Construire un serveur fin par-dessus l’API |
| La donnée vit dans votre propre base ou une application interne | Construire, avec un utilisateur en lecture seule d’abord |
| Un connecteur existe mais expose 60 endpoints de bas niveau | Construire un petit serveur avec les 5 outils dont la tâche a besoin |
| Il vous faut un garde-fou que le connecteur n’a pas (budget, périmètre, validation) | Construire, et mettre le garde-fou dans le serveur |
| La tâche enchaîne toujours les mêmes étapes dans le même ordre | Pas de serveur MCP : écrivez un script |
La dernière ligne est celle qui fait gagner le plus de temps. MCP vaut le coup quand le modèle doit choisir quoi appeler ensuite. Un pipeline figé n’a besoin d’aucun modèle pour choisir quoi que ce soit.
Pensez les outils autour des tâches, pas des endpoints
Le modèle choisit ses outils en lisant leurs noms et leurs descriptions, comme un nouveau collègue lit un menu. Une API pensée pour des développeurs demande souvent trois appels pour faire une chose utile : créer une tâche, l’interroger en boucle, récupérer le résultat. Exposez cela comme un seul outil qui attend et renvoie le résultat. L’agent a alors une décision à prendre au lieu de trois occasions de se tromper d’ordre.
Les règles que j’applique à chaque outil :
- Un outil, un travail qu’une personne saurait nommer :
find_account_by_domain,list_open_deals,log_qualification. Pasqueryniexecute. - Séparer lectures et écritures. Un outil de lecture ne modifie jamais rien : vous pouvez l’autoriser librement et encadrer les autres.
- Peu de paramètres, chacun avec un type, un exemple et une contrainte. Chaque paramètre optionnel est une occasion de plus pour le modèle de deviner.
- Moins que vous ne le pensez. Dix outils clairs valent mieux que quarante qui se recoupent, parce que deux outils aux descriptions proches finissent confondus.
Écrivez la définition de l’outil
Un outil se déclare avec un nom, une description et un schéma JSON pour ses entrées. La description est le texte le plus important de tout le serveur : c’est la seule consigne que reçoit le modèle au moment où il décide. Dites quand utiliser l’outil, ce qu’il coûte, ce qu’il renvoie et ce qu’il ne fera pas.
{
"name": "find_work_email",
"description": "Find the work email of one person at one company. Use it once you have the person's full name and the company's website domain. Charges credits only when an email is found. Returns the email with its verification status, or found=false. Never guess an address: if nothing is found, report it as not found.",
"inputSchema": {
"type": "object",
"properties": {
"full_name": {
"type": "string",
"description": "First and last name, e.g. Marie Dupont"
},
"company_domain": {
"type": "string",
"description": "Website domain without https or path, e.g. acme.fr"
}
},
"required": ["full_name", "company_domain"]
}
}
Les descriptions restent en anglais dans cet exemple, comme le code ; vous pouvez les écrire en français, le modèle les lira aussi bien. Le protocole permet aussi d’annoter un outil, par exemple pour signaler qu’il ne fait que lire. Certains clients s’en servent pour décider ce qui peut tourner sans demander. C’est utile, mais ce sont des déclarations : le serveur doit quand même faire respecter ce qu’il annonce. La spécification (en anglais) donne la forme complète d’un outil.
Renvoyez des résultats exploitables par le modèle
Ce que renvoie un outil est lu par un modèle qui a peu de place. Renvoyez les champs utiles à la tâche, dans une structure stable, et plafonnez la taille.
| Retour | Mauvais | Bon |
|---|---|---|
| Un enregistrement trouvé | L’objet API complet à 300 champs | Les 6 champs que la tâche utilise |
| Une longue liste | 5 000 lignes d’un coup | 50 lignes, un total et un curseur pour la page suivante |
| Rien trouvé | Une chaîne vide | {"found": false, "credits_charged": 0} |
| Une entrée invalide | Une trace d’erreur | « company_domain must look like acme.fr, without https » |
| Une panne en amont | Un timeout que le modèle relance sans fin | « Provider unavailable, nothing was charged, try again later » |
Le total de la deuxième ligne évite l’échec silencieux le plus courant : l’agent travaille sur la première page et annonce un succès sur toute la liste. Et les messages d’erreur sont des consignes. Rédigez-les pour que le modèle sache quoi corriger, ou qu’il doit s’arrêter et prévenir l’utilisateur.
Un gestionnaire minimal, quel que soit le SDK, a cette forme :
async function findWorkEmail({ full_name, company_domain }, user) {
if (!/^[a-z0-9.-]+\.[a-z]{2,}$/i.test(company_domain)) {
return fail("company_domain must look like acme.fr, without https or a path.");
}
const hit = await provider.lookup(full_name, company_domain, user.apiKey);
if (!hit) return ok({ found: false, credits_charged: 0 });
return ok({ found: true, email: hit.email, status: hit.status, credits_charged: hit.cost });
}
Les SDK officiels gèrent le protocole, et leurs API évoluent d’une version à l’autre : prenez le code de base dans le guide de construction d’un serveur plutôt que dans un vieil article de blog. Un détail de ce guide piège tout le monde une fois : un serveur qui communique par l’entrée et la sortie standard ne doit jamais écrire ses logs sur la sortie standard, sinon il corrompt les messages. Loggez sur la sortie d’erreur.
Mettez les garde-fous dans le serveur
Tout ce que vous écrivez dans les instructions de l’agent est une demande. Tout ce que le serveur impose est une règle. Les contrôles qui comptent vivent donc dans le serveur :
- Authentification par utilisateur. Chaque appel tourne avec la clé et les droits de l’appelant, jamais avec un compte administrateur partagé.
- Aperçu du coût pour tout ce qui est payant. Un premier appel avec
preview: truerenvoie le coût estimé et le solde. L’appel réel ne passe que s’il porte le montant renvoyé par l’aperçu. L’agent ne peut rien dépenser sans avoir montré le chiffre à la personne. - Des plafonds. Un nombre maximum de lignes par appel et par heure, pour qu’une boucle de relances ne vide pas un budget.
- Le contenu est une donnée. Le texte que le serveur renvoie depuis des pages web, des e-mails ou des notes CRM peut contenir des instructions. Le serveur l’étiquette comme contenu, et aucun outil n’exécute ce qui s’y trouve.
- Un journal de chaque appel : qui, quel outil, quels arguments, quel coût. C’est ce qui vous permet de répondre à « pourquoi ça s’est passé » une semaine plus tard.
La question des actions qu’un agent peut faire seul est traitée dans le chapitre sécurité. Le serveur est l’endroit où ces réponses deviennent impossibles à contourner.
Testez-le comme un produit
Avant de brancher le serveur sur un agent, listez ses outils avec l’Inspector officiel et appelez chacun à la main avec une entrée valide, une entrée invalide et une entrée qui doit ne rien renvoyer. Branchez-le ensuite et repassez les mêmes contrôles à travers l’agent, comme dans le chapitre sur la connexion.
Surveillez ensuite le contrôle de santé lui-même. Le chapitre sur les routines raconte comment, en novembre 2026, un service de données dont dépendent mes agents passait son contrôle de santé alors que toutes les vraies requêtes échouaient. Un contrôle de santé doit emprunter le vrai chemin : une vraie lecture, sur un enregistrement connu, comparée à la valeur attendue. Tout le reste vous dit que le processus tourne, pas que l’outil fonctionne.
Votre prochaine étape : prenez le système dont vos agents ont le plus besoin et qui n’a pas de bon connecteur, écrivez la définition de trois outils (deux lectures, une écriture) avec des descriptions qui donnent coût et limites, et faites-les lire à un collègue qui ne connaît pas le système. S’il ne sait pas quel outil utiliser pour une demande donnée, le modèle ne le saura pas non plus.