Créez votre premier serveur MCP en Python : outils, ressources, commandes et inspecteur
Créez un serveur MCP en Python avec FastMCP : un outil pratique doté d’outils, de ressources et de prompts, gérant les entrées/sorties par rapport au HTTP, offrant un débogage via l’Inspecteur, ainsi qu’une seule configuration pour 3 clients.
Imaginez trois fichiers Python presque identiques, nommés Claude, Cursor et ChatGPT, chacun appelant l’API Slack, analysant la réponse et la reformant pour un client IA spécifique. Imaginez maintenant remplacer ces trois fichiers par un seul fichier que chacun de ces clients peut lire directement. Lorsque Slack modifie un champ, il suffit de le corriger une seule fois. Ce fichier unique est un serveur MCP, et sa création ne prend qu’environ une après-midi.
MCP en quelques phrases
Le Model Context Protocol est suffisamment simple pour être décrit en trois messages. Un client se connecte à un serveur et demande quelles actions il peut effectuer. Le serveur répond en indiquant les outils que le modèle peut utiliser, les données que l’hôte peut récupérer et les prompts que l’utilisateur peut choisir. Le client appelle ensuite un outil avec des arguments, le serveur le met en œuvre et répond. C’est tout ce qui se passe sur le réseau.
Ce qui rend MCP intéressant à utiliser, ce n’est pas le format des messages mais l’accord commun. Claude Code, Cursor, Copilot de VS Code et d’autres outils utilisent tous le même mécanisme de communication, de sorte qu’un fichier Python écrit aujourd’hui peut être associé à n’importe lequel d’entre eux plus tard.
Un serveur peut offrir l’une des trois sortes de fonctionnalités suivantes :
- Outils : ce sont des fonctions appelables que le modèle sélectionne par nom, par exemple une fonction pour rechercher dans une base de données, envoyer une branche ou condenser un journal.
- Ressources : il s’agit de matériel en lecture seule, comme un fichier de configuration, un schéma ou des commits récents, que l’hôte télécharge lorsqu’il en a besoin en arrière-plan.
- Promptes : ce sont des modèles réutilisables que l’utilisateur choisit ; ils apparaissent sous forme de commande avec un slash dans Claude Code ou comme une entrée de menu dans Claude Desktop.
En pratique, la plupart des serveurs fournissent des outils, tandis que les ressources et les instructions viennent en second plan. Si vous ne devez en apprendre qu’un des trois, apprenez à utiliser les outils. Le mode de transmission des messages et le fonctionnement de l’authentification sont importants plus tard. Pour l’instant, rappelez-vous ceci : un serveur, de nombreux clients, trois éléments à exposer.
Pourquoi les intégrations se multipliaient autrefois
Avant MCP, connecter un outil d’IA à un backend signifiait écrire un adaptateur entre la structure de votre backend et le format de requête utilisé par le client. Un client attendait le schéma des outils d’Anthropic, un autre utilisait son propre format, et un troisième voulait la structure de requête d’OpenAI. La recherche de Slack restait identique, tandis que trois couches d’encapsulation se développaient autour d’elle.
Les calculs deviennent rapidement complexes. Trois clients et deux backends signifient six adaptateurs à gérer. Un quatrième client porte ce nombre à huit, et un troisième backend à douze, chacun avec sa propre logique d’authentification, de tentatives de réessai et de particularités. MCP permet de décomposer ce système. Les clients et les backends ne communiquent plus entre eux ; ils communiquent tous deux avec le protocole, de sorte que l’adoption d’un nouveau client basé sur l’IA ne nécessite qu’une entrée de configuration plutôt qu’une nouvelle intégration.
La conception est délibérément déséquilibrée. Le client décide de tout : quels serveurs charger, quand appeler les outils et comment afficher les résultats. Les responsabilités du serveur sont bien plus limitées : il se contente de déclarer ses outils et de répondre lorsqu’on les sollicite. Cette asymétrie permet au protocole de rester suffisamment compact pour être rapidement mis en œuvre par tous les principaux clients.
Un outil de stand-up dans un seul fichier
Un outil d’aide pour les présentations orales est un outil interne classique que les ingénieurs réécrivent constamment. Il lit le journal local de Git et résume ce qu’un développeur a fait la veille. Ici, il sera présenté sous forme d’outil, de ressource et de prompt dans un seul fichier Python.
Vous avez besoin du paquet FastMCP, la couche Python de haut niveau sur laquelle reposent de nombreux serveurs MCP.
pip install fastmcp
Créez standup_server.py. Il commence par importer la bibliothèque et définir l’outil.
# standup_server.py
import subprocess
from typing import TypedDict
from fastmcp import FastMCP
mcp = FastMCP("standup-helper")
class StandupSummary(TypedDict):
branch: str
since: str
commit_count: int
commits: list[str]
@mcp.tool()
def summarize_standup(
branch: str = "main",
since: str = "yesterday",
) -> StandupSummary:
"""Summarize recent git activity for a standup.
Reads the local git log on the given branch since the
given time window. Returns commit count and one-line
subjects for each commit. Used by AI clients via MCP.
"""
try:
result = subprocess.run(
[
"git", "log",
f"--since={since}",
"--pretty=format:%h %s",
branch,
],
capture_output=True,
text=True,
timeout=5,
check=True,
)
except (subprocess.CalledProcessError,
subprocess.TimeoutExpired) as exc:
return {
"branch": branch,
"since": since,
"commit_count": 0,
"commits": [f"git error: {exc}"],
}
lines = [
line for line in result.stdout.splitlines() if line
]
return {
"branch": branch,
"since": since,
"commit_count": len(lines),
"commits": lines,
}
# resources and prompts come next
Dans l’ombre, le décorateur effectue quatre tâches : il lit les indications de type pour générer un schéma JSON, utilise la documentation comme description affichée au modèle, liste l’outil parmi les capacités du serveur lors de l’échange initial, et dirige les appels JSON-RPC entrants vers la fonction correspondante.
Prêtez attention à l’appel de sous-processus avec un délai d’attente strict de cinq secondes. Si une commande git bloque, l’application hôte qui attend la réponse de l’outil bloque également, c’est pourquoi chaque appel externe nécessite un délai d’attente. La branche d’erreur renvoie également la même structure de dictionnaire que la branche de succès, de sorte que le modèle reçoit toujours une structure prévisible. Un outil plus complet pourrait également appeler la CLI GitHub pour les demandes de fusion ouvertes, en ajoutant un autre appel de sous-processus et en ajoutant la sortie à la dictionnaire.
Viennent ensuite les ressources et le prompt, qui complètent le fichier.
# standup_server.py (continued)
@mcp.resource("recent_commits://main")
def recent_commits_main() -> str:
"""Last 10 commits on the main branch, plain text.
Resources are pulled by the host opportunistically.
They are not invoked by the model the way tools are.
"""
result = subprocess.run(
[
"git", "log",
"-n", "10",
"--pretty=format:%h %ad %s",
"--date=short",
"main",
],
capture_output=True,
text=True,
timeout=5,
)
return result.stdout or "(no commits found)"
@mcp.prompt("standup_template")
def standup_template(focus: str = "shipping work") -> str:
"""Reusable standup question exposed as a prompt
template. Surfaces as a slash command in clients that
expose prompts (e.g. /standup_template in Claude Code).
"""
return (
f"Summarize what I worked on yesterday, focusing on "
f"{focus}. Use the summarize_standup tool to get the "
f"git log, then write a one-paragraph standup note."
)
if __name__ == "__main__":
mcp.run()
Un URI de ressource n’est rien d’autre qu’une chaîne de caractères. Aucun schéma strict ne règle sa structure, bien que la convention soit un schéma suivi d’un chemin, et FastMCP prend en charge les modèles URI lorsqu’il faut des paramètres. Le prompt n’est rien de plus qu’une fonction qui renvoie du texte formaté. Les clients le présentent sous forme de modèle que l’utilisateur invoque, ce que Claude Code expose sous forme de commande avec un slash. Les prompts ne sont jamais injectés automatiquement, l’utilisateur les choisit toujours lui-même.
La dernière ligne lance le serveur. Il n’y a ni port, ni framework web, ni boucle d’événements à gérer, car FastMCP s’occupe de la gestion des entrées/sorties standard.
Transports et authentification
Le transport détermine la manière dont les messages JSON-RPC voyagent entre le client et le serveur.
Utilisez stdio pour les serveurs locaux. L’hôte lance votre script en tant que processus enfant. Stdio contourne tout le stack réseau : pas de port, pas de TLS et pas d’authentification, car le processus enfant hérite des limites de confiance de l’hôte.
Utilisez Streamable HTTP lorsque le serveur doit conserver un état entre les requêtes ou être accessible depuis plusieurs machines. Un serveur distant fait face au réseau, il nécessite donc une authentification stricte.
# bottom of standup_server.py
if __name__ == "__main__":
# Default transport is stdio. The host (Claude Code,
# Cursor, Claude Desktop, etc.) launches this script
# as a subprocess and talks to it over stdin/stdout.
# No port, no TLS, no auth. The trust boundary is
# whoever launched the host.
mcp.run()
# To expose the same server over the network instead,
# use Streamable HTTP. SSE was deprecated in the
# March 2025 spec update. Do not use it for new code.
#
# Production HTTP also needs an auth layer in front.
# OAuth 2.1 with Dynamic Client Registration is the
# current pattern. See Week 22 for the full flow.
#
# mcp.run(
# transport="streamable-http",
# host="0.0.0.0",
# port=8000,
# )
Appeler run sans arguments signifie utiliser stdio, et le bloc commenté montre à quoi ressemble la mise à niveau HTTP. Cette version reste basée sur stdio, ce qui est le choix approprié pour la plupart des outils de développement internes. Un outil qui ne lit que des fichiers locaux ou un répertoire local ne devrait absolument pas être accessible via une interface réseau.
Le cycle de développement local
Ne testez pas un nouveau serveur en le connectant directement à un client IA. Les clients sont conçus pour être résilients : ils gèrent silencieusement les incohérences de schéma et ignorent les fonctionnalités manquantes afin de maintenir le fonctionnement du chat, et lorsque l’outil échoue, le modèle s’excuse simplement.
Préférez plutôt l’inspecteur MCP officiel, une interface de développement basée sur un navigateur qui communique avec votre serveur via stdio.
npx @modelcontextprotocol/inspector python standup_server.py
La commande lance votre script en tant que sous-processus et ouvre un tableau de bord local. Vous pouvez lire le échange brut, voir précisément quelles capacités votre serveur a annoncées, et exécuter des outils et des ressources en temps réel.
Trois problèmes surviennent souvent dans ce cycle :
- Désaccord de schéma. La valeur de retour de la fonction ne correspond pas à son indicateur de type, et l’Inspecteur le signale immédiatement.
- Capacité manquante. Vous appelez une fonction que le serveur n’a jamais annoncée, et le tableau de bord affiche ce qui a réellement été enregistré lors de la négociation.
- Contamination des sorties. Tout appel accidentel à
print()endommage le flux JSON-RPC, car stdio transporte les messages. Dirigez les journaux vers l’erreur standard à l’aide du module logging de Python, que le protocole réserve aux diagnostics lisible.
FastMCP propose également un mode de développement qui redémarre le serveur lorsque le fichier est modifié, ce qui est utile lors des itérations dans l’Inspecteur.
Un serveur, plusieurs clients
Le design s’avère alors payant : il s’agit d’un seul fichier Python, vérifié dans l’Inspecteur, qui peut être associé à différents clients IA sans aucun code spécifique au client. Chaque client lit un fichier de configuration indiquant les serveurs locaux à lancer, et la structure JSON reste identique dans Claude Code, Cursor et Claude Desktop : un objet mcpServers contenant la commande à exécuter ainsi que ses arguments.
{
"mcpServers": {
"standup-helper": {
"command": "python",
"args": ["/Users/you/code/standup_server.py"]
}
}
}
{
"mcpServers": {
"standup-helper": {
"command": "python",
"args": ["/Users/you/code/standup_server.py"]
}
}
}
{
"mcpServers": {
"standup-helper": {
"command": "python",
"args": ["/Users/you/code/standup_server.py"]
}
}
}
La clé, la commande et les arguments sont identiques dans les trois cas. Seule l’emplacement du fichier de configuration diffère d’un client à l’autre.
Ouvrez n’importe lequel d’eux et demandez une réunion rapide. Le client lance le processus Python, négocie les capacités via stdio, affiche l’outil de résumé au modèle et redirige l’appel du modèle vers votre script. Git s’exécute, le dictionnaire est renvoyé et le modèle écrit le résumé, en suivant toujours le même chemin de code.
ChatGPT n’est pas sur cette liste car ses connecteurs fonctionnent avec des serveurs distants via Streamable HTTP. Intégrer cet outil d’assistance nécessiterait une URL publique et une couche d’authentification, ce qui correspond à un profil opérationnel très différent de cette version strictement locale.
Quand ne pas utiliser MCP
Le protocole n’est pas un outil universel. Certaines situations ne s’y prêtent pas bien :
- Chemins très sensibles à la latence. Chaque appel à une fonction ajoute un aller-retour JSON-RPC, donc une fonction située à l’intérieur d’une boucle très fermée devrait utiliser une intégration directe via l’SDK.
- Sortie interactive personnalisée. Si vous avez besoin d’un graphique spécifique ou d’un formulaire personnalisé, vous devez créer une intégration personnalisée, car le protocole sépare les données de leur présentation.
- Configurations que vous contrôlez entièrement. Lorsqu’une seule équipe développe à la fois le client IA et le backend, les relier directement peut être plus facile à maintenir que d’insérer MCP entre eux.
Un petit protocole, un grand changement
Trois primitives, deux mécanismes de transport et une séquence de handshake JSON-RPC forment l’ensemble du protocole. C’est justement cette simplicité qui en fait la force : elle sépare ceux qui développent l’intelligence artificielle de ceux qui fournissent le contexte. Écrire le premier serveur ne prend qu’une après-midi, et cela en vaut la peine chaque fois qu’un nouvel outil d’IA apparaît.