EN DIRECT
Breaking Claude Code Opus 5 Auto Mode31/08/26 · Anthropic|A milestone in expanding access to AI31/08/26 · OpenAI|Claude Session URL appended to commit messages and PR descriptions by default30/08/26 · Anthropic|Vuk97/forward-implementation-first: Stop your coding agent from stalling real work on self-invented bookkeeping - receipts, hashes, locks, certification rituals. Ship first, then verify. Skill for Claude Code, Codex, and other agents.30/08/26 · Anthropic|useagenthq/useagent: Hand off the work. Get back the result. The open-source AI coworker for your team: agents with their own cloud computer, your tools and context, handing back finished work - websites, decks, spreadsheets, reports, PRs. Runs Claude Code, Codex, OpenCode on your subscription.29/08/26 · Anthropic|Good Culture Is the Biggest Productivity Hack, Not AI29/08/26|Debian votes to allow "responsible use of generative AI"29/08/26|Breaking Claude Code Opus 5 Auto Mode31/08/26 · Anthropic|A milestone in expanding access to AI31/08/26 · OpenAI|Claude Session URL appended to commit messages and PR descriptions by default30/08/26 · Anthropic|Vuk97/forward-implementation-first: Stop your coding agent from stalling real work on self-invented bookkeeping - receipts, hashes, locks, certification rituals. Ship first, then verify. Skill for Claude Code, Codex, and other agents.30/08/26 · Anthropic|useagenthq/useagent: Hand off the work. Get back the result. The open-source AI coworker for your team: agents with their own cloud computer, your tools and context, handing back finished work - websites, decks, spreadsheets, reports, PRs. Runs Claude Code, Codex, OpenCode on your subscription.29/08/26 · Anthropic|Good Culture Is the Biggest Productivity Hack, Not AI29/08/26|Debian votes to allow "responsible use of generative AI"29/08/26|
AvancéNouveau⚙️

Créer son serveur MCP minimal : la boucle se ferme

Étape 7/9 du parcours Skills & MCP. Les deux tools du fil rouge en code Python commenté ligne par ligne : structure d'un serveur, docstrings-déclencheurs, garde-fous d'écriture, erreurs conçues pour le modèle — et la boucle skill + MCP enfin complète.

16 min de lecturePublié le 31 août 2026 · aujourd'hui
📖 SKILLS le manuel d'expertise la skill de l'étape 3 dit COMMENT ✓ acquis 🧠 MODÈLE décide et orchestre lit le manuel, actionne les mains, produit le CR fenêtre de contexte 🤚 MCP les mains votre serveur de l'étape 7 fournit QUOI et FAIT ◉ vous êtes ici — on fabrique lit appelle résultat 🗺️ La carte du parcours — étape 7/9 : la boucle se ferme Pour la première fois, tout est allumé : le manuel, le cerveau, les mains — et c'est vous qui avez tout construit.

Ce qu'on construit — et pourquoi c'est court

Le cahier des charges tient en deux lignes, hérité du fil rouge : donner au modèle le moyen de récupérer les notes brutes d'une réunion, et de diffuser le compte-rendu produit. Deux tools, pas un de plus — vous connaissez la règle depuis l'étape 6 : chaque tool est un privilège, et un privilège se justifie.

⚙️ L'architecture du serveur minimal 🧠 Hôte modèle + skill CR lance le serveur en processus enfant 🤚 serveur_notes.py 🔧 get_meeting_notes 🔧 send_summary 🗄️ Notes l'outil de prise de notes 📮 Diffusion messagerie de l'équipe stdio lecture écriture ⚠️ Un tool qui regarde, un tool qui agit — et tout le soin va au second.

Le langage : Python, avec le SDK officiel du protocole. Pourquoi lui pour apprendre : sa façon de déclarer les tools est la plus lisible de l'écosystème — une fonction, un décorateur, une docstring, et le SDK fabrique tout le reste (le JSON-RPC de l'étape 5, la découverte, les schémas). Le concept étant identique en TypeScript et ailleurs, ce que vous apprenez ici se transpose tel quel.

Le squelette : dix lignes qui font un serveur

"""Serveur MCP du fil rouge : notes de réunion + diffusion."""
import os
from mcp.server.fastmcp import FastMCP

# Le nom que verront l'hôte et les journaux (étape 6 : nommez clair)
mcp = FastMCP("notes-reunion")

# Les secrets viennent de l'environnement — jamais du code.
# C'est le "env" de la configuration vue à l'étape 6.
NOTES_TOKEN = os.environ["NOTES_API_TOKEN"]

if __name__ == "__main__":
    mcp.run()   # transport stdio par défaut : l'hôte nous lance en enfant

Trois observations avant d'aller plus loin :

C'est vraiment tout. Créer l'objet serveur, le lancer. La négociation de session, la réponse à tools/list, la validation JSON-RPC : le SDK s'en charge. Votre travail se concentre là où est votre valeur — les tools.

Le secret est déjà bien placé. os.environ["NOTES_API_TOKEN"] matérialise ce qu'on répète depuis l'étape 2 : le serveur détient les identifiants, le code ne les contient pas, le modèle ne les verra jamais. Et l'accès par environ[...] (plutôt qu'un .get(...) silencieux) fait planter le serveur au démarrage si le token manque — un serveur qui refuse de démarrer mal configuré vaut mieux qu'un serveur qui échouera à mi-tâche.

Le transport est celui de l'étape 6. mcp.run() parle stdio : l'hôte nous lance en processus enfant, exactement le cas « serveur local » de l'arbitrage local/distant.

Tool n°1 — get_meeting_notes : la main qui regarde

@mcp.tool()
def get_meeting_notes(date: str) -> str:
    """Récupère les notes brutes des réunions d'une date donnée.

    À utiliser pour obtenir la matière première d'un compte-rendu,
    d'un CR ou d'une synthèse de réunion.

    Args:
        date: la date des réunions, au format AAAA-MM-JJ.
    """
    notes = notes_api.fetch(date=date, token=NOTES_TOKEN)
    if not notes:
        return (f"Aucune note trouvée pour le {date}. "
                "Vérifie la date, ou demande à l'utilisateur "
                "de quelle réunion il s'agit.")
    return format_notes(notes)

Disséquons, car chaque ligne applique un principe déjà rencontré :

Le décorateur fait le pont. @mcp.tool() transforme une fonction Python ordinaire en tool découvert par l'hôte. Le nom de la fonction devient le nom du tool ; les annotations de types (date: str) deviennent le schéma d'entrée que le protocole validera au temps ② de la séquence de l'étape 5. Vous écrivez du Python, le SDK publie du MCP.

La docstring est la description — donc le déclencheur. Relisez-la : elle dit ce que fait le tool, quand l'utiliser, avec les mots du métier (« compte-rendu », « CR », « synthèse »). C'est très exactement la méthode du temps ② de l'étape 3, appliquée aux tools : le modèle choisit ses tools en lisant leurs descriptions, comme il choisit ses skills. Une docstring vague produit un tool jamais appelé — ou appelé à contretemps. Le test de la porte s'applique ici aussi, mot pour mot.

Le cas vide est une réponse, pas une erreur. « Aucune note pour cette date » n'est pas une panne : c'est une information, formulée pour que le modèle sache quoi faire ensuite (vérifier la date, redemander à l'utilisateur). Retenez le geste — il devient central deux sections plus bas.

Tool n°2 — send_summary : la main qui agit

DESTINATAIRES_AUTORISES = ("equipe@exemple.fr", "direction@exemple.fr")

@mcp.tool()
def send_summary(recipient: str, summary: str) -> str:
    """Diffuse un compte-rendu finalisé à une liste de l'équipe.

    À utiliser uniquement quand le compte-rendu est terminé et
    que l'utilisateur a demandé sa diffusion.

    Args:
        recipient: la liste de diffusion (parmi celles de l'équipe).
        summary: le compte-rendu complet, au format maison.
    """
    if recipient not in DESTINATAIRES_AUTORISES:
        return (f"Destinataire refusé : {recipient}. "
                f"Listes autorisées : {', '.join(DESTINATAIRES_AUTORISES)}. "
                "Aucun envoi effectué.")
    if len(summary) < 200:
        return ("Compte-rendu suspicieusement court : envoi refusé. "
                "Vérifie que le CR complet a bien été produit "
                "avant de demander la diffusion.")
    mailer.send(to=recipient, body=summary, token=MAIL_TOKEN)
    return f"Compte-rendu diffusé à {recipient}."

C'est ici que se joue la différence entre un serveur de démonstration et un serveur qu'on ose brancher. Deux garde-fous, deux philosophies :

L'allowlist de destinataires. Le tool refuse structurellement d'envoyer hors des listes prévues — même si le modèle le demandait, même si un contenu lu dans les notes l'y poussait (vous vous souvenez du temps ④ de l'étape 5 : ce qui entre dans le contexte peut influencer le modèle). C'est le moindre privilège appliqué dans le code, là où aucune instruction, aucune injection, aucun raisonnement ne peut le contourner. Une skill qui dit « n'envoie qu'à l'équipe » est un vœu ; une allowlist est un contrôle — la distinction de l'étape 4, devenue exécutable.

La validation de vraisemblance. Un CR de moins de 200 caractères n'est probablement pas un CR : le tool le dit et n'envoie pas. Chaque tool d'écriture mérite sa question : « à quoi ressemble un appel que je devrais refuser ? » — puis le refus se code. C'est votre dernier filet avant l'irréversible.

Et remarquez la docstring : « uniquement quand le compte-rendu est terminé et que l'utilisateur a demandé sa diffusion ». On borne le quand dès la description — le déclencheur lui-même est un garde-fou.

Les erreurs : ce que le modèle voit quand ça casse

Le sujet le plus sous-estimé du développement MCP. Quand un tool échoue, son message d'erreur entre dans la fenêtre de contexte — et le modèle raisonne dessus pour décider de la suite. Votre message d'erreur n'est donc pas un journal pour développeur : c'est une instruction de rattrapage pour le modèle. Comparez :

# ❌ Ce que le modèle ne peut pas exploiter :
#    "KeyError: 'items' at notes_api.py line 42" — du bruit, et le
#    risque d'exposer des chemins, des versions, voire des fragments
#    de configuration dans le contexte.

# ✅ Ce que le modèle sait exploiter :
return ("Le service de notes est momentanément injoignable. "
        "Réessaie dans un instant ; si cela persiste, préviens "
        "l'utilisateur sans inventer de contenu.")

Les trois règles du message d'erreur bien élevé : il dit ce qui s'est passé (en langage de tâche, pas d'implémentation), il dit quoi faire ensuite (réessayer, demander à l'utilisateur, abandonner proprement), il interdit l'invention (« sans inventer de contenu » — la ligne qui évite le CR fabriqué à partir de rien un jour de panne). Et symétriquement : jamais de stack trace brute, jamais de détail d'infrastructure — tout ce que vous mettez dans une erreur finit dans le contexte, donc potentiellement dans une conversation.

Brancher son propre serveur

Votre serveur se branche exactement comme ceux de l'étape 6 — vous êtes juste passé de l'autre côté du comptoir :

{
  "mcpServers": {
    "notes-reunion": {
      "command": "python",
      "args": ["/chemin/vers/serveur_notes.py"],
      "env": {
        "NOTES_API_TOKEN": "${NOTES_API_TOKEN}",
        "MAIL_TOKEN": "${MAIL_TOKEN}"
      }
    }
  }
}

Et le ritual de l'étape 6 s'applique à votre propre production : au redémarrage de l'hôte, vérifiez que la découverte affiche vos deux tools et rien d'autre ; premier essai en lecture (get_meeting_notes sur une date connue) ; et gardez la confirmation d'envoi active sur send_summary — votre propre code mérite la même période d'observation qu'un serveur tiers.

La boucle se ferme

Remontez à la carte en tête d'étape : pour la première fois du parcours, tout est allumé. Déroulons une dernière fois le fil rouge, de bout en bout, avec vos pièces :

  1. « Fais-moi le compte-rendu de la réunion de ce matin et envoie-le à l'équipe »
  2. La skill de l'étape 3 se charge — sa description a intercepté « compte-rendu »
  3. Le modèle appelle votre get_meeting_notes — sa docstring a fait le reste
  4. Les notes remontent dans le contexte ; le modèle applique le format maison, les cas limites, la page maximum
  5. Le modèle appelle votre send_summary — l'allowlist veille, la diffusion part

Le manuel a dit comment (étapes 2-4), les mains ont fourni quoi et fait (étapes 5-7), le cerveau a orchestré — et chaque maillon, vous l'avez écrit, testé, gouverné. Il reste à blinder l'ensemble (étape 8) et à savoir où l'écosystème peut vous éviter d'écrire la prochaine pièce (étape 9).

💡
LE concept de cette étape : dans un serveur MCP, tout ce que le modèle lit est du design. La docstring est le déclencheur (même méthode que les descriptions de skills), le message d'erreur est une instruction de rattrapage (jamais une stack trace), et les garde-fous vivent dans le code (allowlist, validation) — là où aucune injection ni aucun raisonnement ne peut les contourner. Le protocole, lui, est l'affaire du SDK.
📚Pour aller plus loin

Pour les geeks : stdio a une règle d'or — stdout appartient au protocole. En transport stdio, la sortie standard du processus est le canal JSON-RPC : le moindre print() de débogage y injecte du texte au milieu des trames et corrompt la session — le bug le plus classique du premier serveur, et le plus déroutant (« ça marchait, j'ai ajouté un print, plus rien »). Le journal va donc sur stderr ou dans un fichier : logging.basicConfig(stream=sys.stderr) dès la première ligne, et l'interdiction de print en revue de code. Deuxième subtilité du même tonneau : les bibliothèques que vous importez peuvent elles-mêmes écrire sur stdout — un SDK verbeux, une barre de progression — et casser la session sans qu'une seule ligne de votre code soit fautive ; au moindre comportement erratique, auditez ce que vos dépendances impriment. Troisième réflexe : les schémas d'entrée générés depuis vos annotations de types sont votre première validation, mais pas la dernière — le type dit « c'est une chaîne », votre code doit encore dire « c'est une date plausible ». Type-check au bord, logique métier au centre.

📚Pour aller plus loin

Pour les geeks : tester un serveur qu'un modèle consomme. Trois étages, du plus rapide au plus complet. Étage 1 — les fonctions nues : vos tools sont des fonctions Python ; testez-les comme telles (pytest, cas nominal, cas vide, cas refusé par les garde-fous) sans démarrer le moindre serveur — c'est là que se vérifie l'allowlist, en trois assertions. Étage 2 — le serveur en isolation : l'outil d'inspection officiel du protocole (l'Inspector) se branche sur votre serveur et vous montre exactement ce qu'un hôte verrait — les tools découverts, leurs schémas, leurs descriptions — et vous laisse appeler chaque tool à la main ; c'est le pendant MCP du curl sur une API, et le bon endroit pour relire vos docstrings avec les yeux du modèle. Étage 3 — le déclenchement en conditions réelles : la grille de l'étape 3 s'applique aux tools — formulations qui doivent déclencher l'appel, formulations voisines qui ne doivent pas, et le cas de collision si plusieurs serveurs exposent des tools proches. Un serveur dont les trois étages passent se branche sereinement ; un serveur testé « à la main dans la conversation » uniquement se débuggera en production.

Les quatre pièges du premier serveur : 1. ❌ Le print() en stdio — stdout appartient au protocole : un seul print de débogage corrompt la session ; le journal va sur stderr 2. ❌ Le secret dans le code — un token en dur finira dans Git : l'environnement, toujours (et le serveur doit refuser de démarrer s'il manque) 3. ❌ La docstring pour développeur — « Fetches notes via the API » ne déclenche rien : la docstring s'écrit pour le modèle, quoi + quand, mots du métier 4. ❌ Le garde-fou dans la skill — « n'envoie qu'à l'équipe » en instruction est un vœu ; l'allowlist dans le code est un contrôle — l'irréversible se protège côté serveur

📍 Parcours Skills & MCP — étape 7/9

  1. 🗺️ La carte avant le territoire
  2. 🔬 Anatomie d'une skill
  3. 🛠️ Créer sa première skill
  4. 🏛️ Skills en entreprise : gouvernance
  5. 🔌 MCP : le protocole expliqué
  6. Utiliser un serveur MCP existant
  7. ⚙️ Créer son serveur MCP minimal ← vous êtes ici
  8. 🛡️ Sécuriser ses MCP
  9. 📡 L'écosystème : où trouver, où ça bouge

Prochaine étape → Sécuriser ses MCP : la surface d'attaque complète — injection indirecte, confused deputy, exfiltration par chaînage — et les parades, du moindre privilège à la supervision. L'étape que vos RSSI liront en premier.

Votre serveur tourne ? La fiche Sécurité des agents IA vous donne l'avant-goût de l'étape 8 en 90 secondes.

Tags
skillsmcpagentsparcours-skills-mcppythondeveloppement
⚡ FICHE #005Skills & MCP : la fiche qui référence tout le parcours2 MIN

À lire ensuite