Jusqu'à aujourd'hui, vous étiez locataire : vous installez des MCP écrits par d'autres et votre IA les utilise. Bien. Mais il arrive un moment où ça devient trop juste — vous avez une API interne, une base de données, un script que vous seul comprenez, et vous voulez que Claude y touche directement, sans copier-coller les sorties dans le chat. C'est là que vous cessez de consommer pour commencer à publier. Un serveur MCP est la prise standard : vous décrivez vos outils une seule fois, et n'importe quelle IA qui parle le protocole les invoque comme si elle en était l'auteur. FastMCP est le framework TypeScript qui enveloppe le SDK officiel de MCP (celui qu'Anthropic a ouvert) et vous débarrasse du boilerplate — le même bond qu'Express vous a offert par-dessus le module http de Node. Dans ce guide, on passe d'un tool 'add' sur stdio en quinze minutes à des tools avec des schémas Zod qui se valident tout seuls, des resources qui exposent des données, des prompts réutilisables, et enfin de la vraie production : authentification, HTTP streaming, sessions et edge. À la fin, vous aurez votre propre MCP que Claude Code invoque sérieusement — pas une démo, une tuyauterie à vous branchée sur votre monde.
Cela fait des semaines que votre IA est connectée à des outils. Vous lui demandez d'ouvrir un navigateur, elle le fait. Vous lui demandez de lire un repo, elle le lit. Tout ça, ce sont des MCP écrits par d'autres — des prises standard que quelqu'un a publiées et que vous avez installées. Ça marche parce qu'ils parlent un langage commun : le Model Context Protocol, le standard ouvert qui permet à n'importe quelle IA de découvrir et d'invoquer des outils externes sans se coupler à aucun.
Le moment arrive quand ce dont vous avez besoin n'existe pas en tant que MCP. Vous avez un endpoint interne qui renvoie l'état de vos commandes. Un tableur que vous seul savez lire. Un script de 40 lignes qui calcule quelque chose de spécifique à votre domaine. Aujourd'hui, chaque fois que vous voulez que l'IA l'utilise, vous faites la danse du coursier : vous exécutez, vous copiez la sortie, vous la collez dans le chat, vous attendez. Ça marche une fois. À la dixième, vous vous demandez pourquoi l'IA ne le fait pas toute seule.
Cette ressource boucle un arc. Dans agent-browser, vous avez appris à donner à votre IA une capacité externe pour consommer le monde — voir des pages, cliquer, extraire. Ici, vous faites pivoter la caméra : vous apprenez à publier vos propres capacités pour que d'autres IA les consomment. D'utilisateur à auteur. C'est le même protocole, vu de l'autre côté du câble.
Quand vous ouvrez le SDK officiel de MCP pour la première fois, la sensation est celle d'ouvrir le capot d'une voiture de course : tout est là, tout est correct, et rien n'est évident. Vous devez enregistrer des handlers à la main, mapper les requêtes du protocole, sérialiser les réponses dans le format exact qu'attend le client, gérer le cycle de connexion, parser les paramètres d'entrée et les valider vous-même. Pour exposer un seul tool qui additionne deux nombres, vous écrivez des dizaines de lignes de plomberie avant d'atteindre la ligne qui fait l'addition.
D'où vient cette douleur ? Du fait que le SDK officiel est volontairement bas niveau. C'est la base sur laquelle on construit, pas l'ergonomie finale — tout comme le module http de Node est correct mais personne n'écrit un serveur web directement avec lui, on utilise Express. Le SDK vous donne un accès total au protocole ; le prix, c'est que vous portez tout le boilerplate. C'est bien pour qui construit des frameworks. C'est hostile pour qui veut juste brancher son API.
FastMCP existe pour effacer précisément ce boilerplate. C'est un framework TypeScript qui enveloppe le SDK officiel de MCP — il ne le remplace pas, il se pose dessus — et vous donne une API déclarative : vous décrivez le tool, son schéma et sa fonction, et FastMCP se charge du reste (enregistrement, sérialisation, cycle de connexion, validation). Le tool d'addition passe de ce fatras de plomberie à un peu plus d'une douzaine de lignes. Et ce sont celles qui comptent : celles qui font vraiment l'addition.
Framework TypeScript pour construire des serveurs MCP sans boilerplate. Il se pose sur le SDK officiel de MCP (@modelcontextprotocol/sdk, celui qu'Anthropic a ouvert) et ajoute des tools avec validation par schéma (Zod et autres Standard Schema), resources, prompts, authentification, HTTP streaming, sessions et un CLI de développement. La façon la plus rapide de passer d'une idée à un serveur MCP que Claude invoque pour de vrai.
Publier un serveur MCP n'est pas quelque chose que vous faites une fois puis oubliez. C'est un réflexe qui s'active à des moments précis. Apprenez à les reconnaître et vous saurez toujours quand ça vaut la peine d'y consacrer un moment.
FastMCP s'installe comme n'importe quel paquet npm. Il tourne sur Node moderne avec TypeScript. Un nouveau projet minimal tient dans un dossier avec trois fichiers.
# Dans un nouveau dossier pour ton serveur mkdir mi-mcp && cd mi-mcp npm init -y # FastMCP + Zod (pour les schémas d'entrée) + tsx (pour lancer du TS sans compiler) npm install fastmcp zod npm install -D tsx typescript
a est un nombre, email est une chaîne au format e-mail, edad est un entier optionnel. FastMCP prend ce schéma et fait deux choses gratuitement : (1) il dit au client quels paramètres attend ton tool, pour que l'IA les remplisse bien ; et (2) il valide l'entrée avant qu'elle n'arrive à ton code. Si l'IA envoie n'importe quoi, c'est le schéma qui le rejette, pas ta fonction. C'est un garde à la porte que tu n'as pas à écrire. (FastMCP accepte aussi d'autres validateurs du même standard — ArkType, Valibot — mais Zod est le chemin le plus balisé.)On commence par le 'hello world' du MCP : un serveur avec un seul tool qui additionne deux nombres. C'est délibérément bête — l'intérêt n'est pas l'addition, c'est de voir le câble complet : définir le serveur, enregistrer un tool avec son schéma, le démarrer, et que Claude l'invoque. Une fois que tu as ce squelette, tout le reste n'est que variations.
// server.ts
import { FastMCP } from "fastmcp";
import { z } from "zod";
const server = new FastMCP({
name: "Mi Servidor",
version: "1.0.0",
});
server.addTool({
name: "add",
description: "Suma dos números",
parameters: z.object({
a: z.number(),
b: z.number(),
}),
execute: async (args) => {
return String(args.a + args.b);
},
});
server.start({
transportType: "stdio",
});Lis-le de haut en bas, car ce motif se répète dans tout ce que tu construiras. new FastMCP crée le serveur avec un nom et une version — c'est ce que Claude verra dans la liste des connexions. addTool enregistre un outil : un nom (avec lequel l'IA l'invoque), une description (que l'IA lit pour décider quand l'utiliser — écris-la bien, c'est du marketing adressé à la machine), les parameters en tant que schéma Zod, et execute, la fonction qui tourne pour de vrai. Tu renvoies une chaîne et FastMCP l'enveloppe dans le format du protocole à ta place.
transportType: "stdio" signifie que le serveur parle par l'entrée et la sortie standard — le même canal par lequel un programme de terminal reçoit et émet du texte. C'est le transport le plus simple : le client (Claude Code) lance ton serveur comme sous-processus et converse avec lui par ce tuyau. Zéro réseau, zéro port, zéro configuration. Parfait pour tes outils locaux. Quand tu voudras l'exposer sur internet, tu remplaceras ce transport par HTTP — mais pour commencer, stdio est tout ce dont tu as besoin.Avant de le brancher à Claude, teste-le isolé. FastMCP embarque un CLI qui démarre ton serveur et te laisse dialoguer avec lui, plus l'Inspector officiel de MCP pour le voir dans une interface visuelle. C'est ta boucle de développement : tu changes le code, tu le testes ici, et ce n'est que lorsque ça marche que tu le connectes au vrai client.
# Démarre ton serveur en mode développement (tu interagis avec les tools) npx fastmcp dev server.ts # Ouvre-le dans le MCP Inspector (interface visuelle pour inspecter tools/resources/prompts) npx fastmcp inspect server.ts
npx fastmcp dev comme ton établi : là tu vois l'erreur brute, la sortie exacte, le schéma tel que ton serveur le publie. Ce n'est que lorsque le tool fait ce que tu veux que tu franchis l'étape de le connecter au client. Tout comme tu ne déploies pas en production pour tester un if — tu testes en local d'abord.C'est là que ça cesse d'être un exercice et que ça devient réel. Un client MCP (Claude Desktop, Claude Code) lance ton serveur comme sous-processus et lui parle par stdio. Il a juste besoin de savoir comment le démarrer : quelle commande et quels arguments. Ça vit dans un fichier de configuration avec la liste des serveurs MCP.
{
"mcpServers": {
"mi-servidor": {
"command": "npx",
"args": ["tsx", "/ruta/absoluta/a/mi-mcp/server.ts"]
}
}
}command est l'exécutable qui lance ton serveur et args ses arguments — ici on utilise npx tsx pour lancer le TypeScript directement sans compiler. Utilise un chemin absolu : le client ne sait pas depuis quel dossier tu le lances. Une fois sauvegardé et le client redémarré, ton serveur apparaît dans la liste, et quand tu demanderas à Claude « additionne 128 et 45 », tu verras comment il invoque ton tool add au lieu de le calculer de tête. Ce moment — le premier tool à toi que l'IA appelle toute seule — c'est celui qui accroche.
./server.ts) dans args. Le client lance le sous-processus depuis son répertoire de travail, pas le tien, donc ./server.ts pointe vers le vide et le serveur ne démarre pas — en silence, le plus souvent. Si ton MCP n'apparaît pas ou échoue à se connecter, vérifie ça en premier : chemin absolu, toujours.Le tool add utilisait Zod pour le strict minimum. Mais la vraie puissance apparaît avec des tools réels, où l'entrée a une forme et des règles. Imagine un tool qui crée un utilisateur : l'e-mail doit avoir un format de courriel, l'âge doit être un entier positif, le rôle ne peut être que l'un d'une liste. Avec Zod, tu décris tout ça et FastMCP rejette l'entrée invalide avant qu'elle ne touche ton code. Tu n'écris jamais un if (!email.includes('@')). Le schéma est le garde.
server.addTool({
name: "crear_usuario",
description: "Crea un usuario en el sistema con validación completa",
parameters: z.object({
nombre: z.string().min(2),
email: z.string().email(),
edad: z.number().int().positive().optional(),
rol: z.enum(["admin", "editor", "lector"]),
}),
execute: async (args) => {
// Si llegaste aquí, args YA está validado: email es email, rol es válido.
const usuario = await miBaseDeDatos.insertar(args);
return `Usuario ${usuario.id} creado con rol ${args.rol}`;
},
});execute, args arrive typé en TypeScript, avec autocomplétion. Un seul bloc de code qui valide, documente et type. C'est ce que le SDK officiel t'obligeait à écrire trois fois à la main.Un tool est un verbe — l'IA l'exécute pour que quelque chose se produise. Mais parfois tu ne veux pas une action, tu veux donner du contexte : un fichier de logs, un document de configuration, le contenu d'un README. C'est à ça que servent les resources. Ce sont des données que ton serveur expose et que l'IA peut lire quand elle en a besoin, identifiées par une URI. Vois les tools comme les boutons d'une télécommande, et les resources comme les écrans qui affichent de l'information.
server.addResource({
uri: "file:///logs/app.log",
name: "Logs de la Aplicación",
mimeType: "text/plain",
async load() {
const contenido = await fs.readFile("/var/log/app.log", "utf-8");
return { text: contenido };
},
});L'uri est l'identifiant unique du resource — le client s'en sert pour le demander. mimeType dit à l'IA de quel type est le contenu (texte brut, JSON, markdown), pour qu'elle l'interprète bien. Et load est la fonction qui apporte le contenu quand quelqu'un le demande — paresseuse par conception : tu ne lis pas le fichier tant que ce n'est pas nécessaire. Pour des données binaires, tu renvoies { blob } en base64 au lieu de { text }. Un resource est la façon propre de dire « IA, voici des données fraîches quand tu en auras besoin », sans les dépenser dans le prompt avant l'instant exact.
Le troisième ingrédient du protocole, ce sont les prompts. Si tu as un modèle que tu utilises tout le temps — 'génère un message de commit à partir de ce diff', 'résume cette réunion avec ce format' — un prompt du MCP l'empaquette avec un nom et des arguments, et n'importe quel client peut l'invoquer. Tu arrêtes de copier-coller la même longue instruction : tu l'offres comme une capacité de plus de ton serveur.
server.addPrompt({
name: "git-commit",
description: "Genera un mensaje de commit a partir de un diff",
arguments: [
{
name: "changes",
description: "El diff de git o una descripción de los cambios",
required: true,
},
],
load: async (args) => {
return `Genera un mensaje de commit conciso para estos cambios:\n${args.changes}`;
},
});Tout ce qui précède tournait par stdio : local, à toi, un sous-processus sur ta machine. Parfait pour commencer. Mais quand tu veux que d'autres utilisent ton MCP — ton équipe, un client, toi-même depuis plusieurs endroits — tu dois l'exposer sur internet. C'est là qu'entre le transport HTTP streaming : au lieu d'un tuyau local, ton serveur écoute sur un port et répond en HTTP avec streaming d'événements. Le code de tes tools ne change pas d'une ligne ; tu ne changes que la façon dont il démarre.
server.start({
transportType: "httpStream",
httpStream: {
port: 8080,
endpoint: "/mcp", // opcional, por defecto es /mcp
},
});Voilà l'interrupteur complet. Mêmes tools, mêmes resources, mêmes prompts — servis désormais en HTTP sur le port 8080, endpoint /mcp. Un client distant pointe vers https://tu-dominio/mcp et se branche. C'est ici que ton MCP cesse d'être un outil personnel pour devenir un service. Et c'est ici qu'apparaît la question qui sépare un jouet d'un produit : qui peut l'appeler ?
Un MCP en HTTP sans authentification est une porte ouverte : quiconque connaît l'URL invoque tes tools, touche à ta base de données, dépense tes ressources. FastMCP résout ça avec une option authenticate : une fonction qui tourne à chaque connexion, inspecte la requête (headers, clé API, token), et décide si elle passe ou non. Si elle passe, elle renvoie un objet de session qui sera disponible dans chaque tool.
const server = new FastMCP({
name: "Mi Servidor",
version: "1.0.0",
authenticate: (request) => {
const apiKey = request.headers["x-api-key"];
if (apiKey !== process.env.MCP_API_KEY) {
throw new Response(null, { status: 401, statusText: "Unauthorized" });
}
return { id: 1, role: "user" }; // esto se vuelve la sesión
},
});
server.addTool({
name: "sayHello",
execute: async (args, { session }) => {
return `Hola, usuario ${session.id}!`; // session viene de authenticate
},
});authenticate. Ce ne sont pas des rivaux : un MCP de production est une tuyauterie avec cadenas. Sans cadenas, ta tuyauterie est un robinet public que n'importe qui ouvre.process.env.MCP_API_KEY. La clé ne s'écrit jamais en dur dans le code ni ne se pousse sur git — c'est la même règle d'or que la ressource sur les secrets : hors du repo, dans une variable d'environnement ou un gestionnaire de secrets. Un MCP avec la clé en dur est un MCP dont la porte affiche 'fermé' mais sans vraie serrure. Quiconque voit le code entre.Les vrais tools ne sont pas instantanés. Un tool qui traite un gros fichier, appelle une API lente ou génère du contenu met des secondes. FastMCP te donne, dans execute, un contexte avec des outils pour que ce tool respire : rapporter la progression, émettre du contenu en streaming, logger, et accéder à la session de l'utilisateur authentifié.
server.addTool({
name: "procesar_lote",
description: "Procesa un lote reportando progreso en vivo",
parameters: z.object({ total: z.number() }),
annotations: { streamingHint: true },
execute: async (args, { streamContent, reportProgress, log, session }) => {
log.info(`Iniciando lote para ${session.id}`);
await streamContent({ type: "text", text: "Empezando..." });
await reportProgress({ progress: 50, total: 100 });
// ... trabajo real ...
return "Completado";
},
});reportProgress({ progress, total }) dit au client 'j'en suis à 50 %', et l'IA (et l'humain) voient une barre au lieu d'un silence angoissant. streamContent émet la sortie par morceaux au fur et à mesure de sa génération, au lieu d'attendre la fin — la différence entre voir le texte apparaître et fixer un spinner. log laisse une trace structurée pour déboguer. Et session est l'utilisateur que authenticate a validé, ainsi chaque tool sait qui l'appelle. Avec ceux-là, tes tools cessent d'être des boîtes noires qui tardent pour devenir des processus observables.
Voici l'unique prompt que tu dois garder. Au lieu d'écrire le serveur à la main, tu le décris à ton IA — qui a déjà tout le contexte de FastMCP de ce guide sous les yeux — et elle génère le squelette complet, testé, prêt à connecter. Remplis les crochets avec ton cas réel et laisse-la construire. Un seul prompt, pas cinq : celui-ci démarre le projet entier.
Agis en tant qu'ingénieur senior MCP. Nous allons construire un serveur MCP en TypeScript avec FastMCP (le framework qui enveloppe le SDK officiel de MCP, celui qu'Anthropic a ouvert ; il s'installe avec `npm install fastmcp zod`).
MON SERVEUR :
- Nom : [nom de ton serveur, ex. "CRM Interno"]
- Ce qu'il expose : [décris en 2 lignes quels outils/données tu veux que l'IA touche]
LES TOOLS DONT J'AI BESOIN (actions que l'IA exécutera) :
1. [nombre_tool] — [ce qu'il fait] — entrées : [champs et leurs types/règles]
2. [nombre_tool] — [ce qu'il fait] — entrées : [champs et leurs types/règles]
RESOURCES (données que l'IA lira, si applicable) :
- [nom] — [quelles données il expose, ex. logs, config]
MODE : [commence par "stdio local" pour le développement | ensuite je migre vers "httpStream avec authentification par clé API" pour la production]
CONSTRUIS :
1. server.ts complet : `new FastMCP` avec nom et version, chaque tool avec `addTool` (name, description claire orientée pour que l'IA sache quand l'utiliser, parameters en tant que schéma Zod qui VALIDE réellement — .email(), .int().positive(), .enum() où il faut, et .optional() sur l'optionnel, execute typé).
2. Les resources avec `addResource` (uri, name, mimeType, load paresseux qui renvoie {text} ou {blob}) si je les ai demandés.
3. Le `server.start` avec le transport que j'ai choisi (stdio ou httpStream avec port/endpoint).
4. Si j'ai demandé la production : l'option `authenticate` qui lit la clé API depuis `process.env` (JAMAIS en dur), lance `new Response(null, {status: 401})` si ça ne colle pas et renvoie la session, plus un tool qui utilise `session`.
5. Le bloc JSON de `mcpServers` pour le connecter à Claude Code, avec `npx tsx` et un chemin ABSOLU.
6. Les commandes exactes : installer, tester avec `npx fastmcp dev server.ts`, et inspecter avec `npx fastmcp inspect server.ts`.
CONTRAINTES : zéro secret dans le code ; descriptions des tools écrites POUR que l'IA décide bien quand les invoquer ; valide toute entrée avec Zod, pas avec des if à la main. Explique-moi chaque tool en une ligne avant le code.npx fastmcp dev. Quand ça marche et que Claude l'invoque pour de vrai, reviens au même prompt en changeant le MODE vers production avec authentification. Ainsi tu construis par couches vérifiables au lieu de cracher un serveur géant non testé. Et applique l'habitude de toujours : d'abord un commit du squelette qui fonctionne, puis le reste.Publier un MCP a deux chemins selon où tu te trouves, et il vaut mieux savoir quand prendre chacun.
server.ts complet avec Zod, transport et config.mcpServers avec le bon chemin et les commandes fastmcp dev pour tester avant de brancher.npx fastmcp inspect server.ts ouvre l'Inspector visuel — là tu vois tes tools, resources et prompts comme les voit un client.Recule et regarde le chemin. Tu as commencé en consommant des MCP écrits par d'autres — ton IA utilisait des outils tiers. Tu as appris avec agent-browser à lui donner une capacité pour voir et toucher le monde. Et ici, avec FastMCP, tu as fait pivoter la caméra : maintenant tu publies tes propres capacités pour que n'importe quelle IA les consomme. Un tool add en quinze minutes est devenu des tools avec Zod, resources, prompts, authentification et HTTP. Tu as bouclé l'arc utilisateur→auteur. Tu n'es plus locataire de l'écosystème MCP : tu es propriétaire d'une prise.
Join 4,200+ builders. No credit card. Build your first app with AI in minutes.