Créer son propre serveur MCP en 30 minutes

Un serveur MCP se crée en une trentaine de lignes de Python avec le SDK officiel : vous écrivez des fonctions annotées, le SDK en déduit le schéma des paramètres et gère le protocole. Comptez 30 minutes pour un premier serveur qui expose deux outils et une ressource, testé dans l’Inspector puis branché à Claude, Cursor ou VS Code. Ce tutoriel suit la version 2 du SDK Python, sortie le 28 juillet 2026 (v2.3.0 au 2 octobre 2026).
Ce que vous allez construire
Un serveur « Notes » qui donne à votre assistant IA un accès en lecture seule à un dossier de notes Markdown :
- un outil
chercher_notesqui renvoie les notes contenant un mot ; - un outil
lire_notequi renvoie le contenu d’une note ; - une ressource
notes://listeavec la liste des fichiers.
Le choix de la lecture seule n’est pas anodin : c’est la règle de base pour un premier serveur. Un outil qui écrit, supprime ou envoie quelque chose peut être déclenché par erreur, ou par une injection de prompt cachée dans un document que l’agent a lu.
Si le vocabulaire vous est nouveau, la page Serveur MCP : définition et les meilleurs serveurs à connaître pose les bases, et le terme serveur MCP est défini dans le glossaire.
Prérequis
- Python 3.11 ou plus récent (exigence du SDK v2).
- uv, le gestionnaire de paquets Python recommandé par la documentation du SDK.
pipfonctionne aussi. - Un hôte MCP pour l’essai final : Claude Desktop, Claude Code, Cursor ou VS Code avec GitHub Copilot.
- Un dossier avec quelques fichiers
.mdpour tester.
Les trois briques d’un serveur MCP
La documentation du SDK Python les distingue selon qui décide de s’en servir :
| Brique | Qui la déclenche | Équivalent web | Exemple |
|---|---|---|---|
| Outil (tool) | Le modèle | Requête POST : agit, peut modifier | Chercher dans une base, créer un ticket |
| Ressource (resource) | L’application hôte | Requête GET : lit, ne modifie rien | Le contenu d’un fichier |
| Prompt | L’utilisateur | Commande enregistrée | Une commande « /résumer » |
Le serveur ne parle jamais directement au modèle : c’est le client MCP, à l’intérieur de l’hôte, qui lui transmet les appels.
Étape 1 : créer le projet
uv init serveur-notes
cd serveur-notes
uv add "mcp[cli]"
L’option [cli] installe la commande mcp (mcp dev, mcp run, mcp install) en plus du SDK. Avec pip : pip install "mcp[cli]".
Étape 2 : écrire le serveur
Créez server.py :
from pathlib import Path
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ToolError
# Dossier exposé, en lecture seule. Rien en dehors de ce dossier n'est accessible.
NOTES = Path.home() / "notes"
mcp = MCPServer("Notes")
def _fichier(nom: str) -> Path:
"""Chemin d'une note, refusé s'il sort du dossier NOTES."""
chemin = (NOTES / nom).resolve()
if not chemin.is_relative_to(NOTES.resolve()) or chemin.suffix != ".md":
raise ToolError(f"Note refusée : {nom!r}")
if not chemin.is_file():
raise ToolError(f"Aucune note nommée {nom!r}")
return chemin
@mcp.tool()
def chercher_notes(mot: str) -> list[str]:
"""Liste les notes Markdown qui contiennent ce mot (sans tenir compte des majuscules)."""
mot = mot.lower()
return sorted(
f.name for f in NOTES.glob("*.md")
if mot in f.read_text(encoding="utf-8", errors="ignore").lower()
)
@mcp.tool()
def lire_note(nom: str) -> str:
"""Renvoie le contenu complet d'une note, par exemple « reunion-lundi.md »."""
return _fichier(nom).read_text(encoding="utf-8", errors="ignore")
@mcp.resource("notes://liste")
def liste() -> str:
"""Toutes les notes disponibles, une par ligne."""
return "\n".join(sorted(f.name for f in NOTES.glob("*.md")))
if __name__ == "__main__":
mcp.run()
Trois points à remarquer :
- Les annotations de type sont le schéma.
mot: strsuffit : le SDK génère le schéma JSON que le client montre au modèle. Vous n’écrivez ni validation ni analyse de requête. - La docstring est la description que le modèle lit pour choisir l’outil. Soignez-la : c’est elle qui décide si l’outil sera appelé au bon moment.
_fichierbloque la sortie du dossier. Sans ce contrôle, un nom comme../.ssh/id_rsaferait lire n’importe quel fichier de votre compte. C’est le défaut le plus fréquent des serveurs qui touchent au disque.
Si vous venez de la version 1 du SDK : FastMCP s’appelle désormais MCPServer, et l’import mcp.server.fastmcp n’existe plus. Le guide de migration liste les autres changements.
Étape 3 : tester dans l’Inspector
uv run mcp dev server.py
La commande lance le MCP Inspector et affiche une adresse locale à ouvrir dans le navigateur. Vérifiez trois choses :
- onglet Tools :
chercher_notesetlire_noteapparaissent avec leur description et un champ de saisie ; - un appel à
lire_noteavec../secret.mdrenvoie bien l’erreur « Note refusée » ; - onglet Resources :
notes://listerenvoie vos fichiers.
Testez toujours le serveur seul avant de le brancher : une erreur se lit bien plus facilement ici qu’à travers un hôte.
Étape 4 : le brancher à votre assistant
Tous les hôtes font la même chose : ils lancent votre serveur comme un processus et lui parlent par l’entrée et la sortie standard (transport stdio). Il suffit donc de leur donner la commande de lancement, toujours avec un chemin absolu :
uv run --with "mcp[cli]" mcp run /chemin/absolu/vers/server.py
Claude Desktop — le SDK écrit la configuration pour vous :
uv run mcp install server.py
Quittez complètement Claude Desktop (pas seulement la fenêtre) puis relancez-le.
Claude Code — une commande :
claude mcp add notes -- uv run --with "mcp[cli]" mcp run /chemin/absolu/vers/server.py
Tapez /mcp dans une session pour vérifier que notes est connecté.
Cursor — fichier .cursor/mcp.json à la racine du projet :
{
"mcpServers": {
"notes": {
"command": "uv",
"args": ["run", "--with", "mcp[cli]", "mcp", "run", "/chemin/absolu/vers/server.py"]
}
}
}
VS Code — fichier .vscode/mcp.json ; la clé est servers et chaque entrée déclare son type :
{
"servers": {
"notes": {
"type": "stdio",
"command": "uv",
"args": ["run", "--with", "mcp[cli]", "mcp", "run", "/chemin/absolu/vers/server.py"]
}
}
}
Ces commandes et fichiers sont ceux de la page Connect to a real host de la documentation officielle. Pour l’installation d’un serveur existant (et non le vôtre), suivez plutôt Installer un serveur MCP dans Claude, ChatGPT ou Cursor.
Étape 5 : l’essayer pour de vrai
Demandez à votre assistant : « Dans mes notes, retrouve ce qui a été décidé sur le budget et résume-le. » Il devrait appeler chercher_notes avec « budget », puis lire_note sur les fichiers trouvés, et vous demander l’autorisation avant chaque appel si votre hôte est configuré ainsi. Gardez cette validation activée tant que vous n’avez pas observé le comportement du serveur sur plusieurs jours.
Problèmes fréquents
| Symptôme | Cause probable | Solution |
|---|---|---|
| Le serveur n’apparaît pas dans l’hôte | Chemin relatif vers server.py | Mettre le chemin absolu, puis relancer l’hôte |
| « command not found: uv » côté hôte | L’hôte lance le serveur avec un PATH minimal | Remplacer uv par son chemin absolu (which uv ou where uv) |
| Rien ne change après une modification de la configuration | L’hôte lit sa configuration au démarrage | Quitter complètement l’hôte et le relancer |
| Le serveur plante au premier appel | Un print() écrit sur la sortie standard, réservée au protocole en stdio | Écrire les journaux sur la sortie d’erreur (sys.stderr) ou avec logging |
ImportError: mcp.server.fastmcp | Code écrit pour le SDK v1 | Passer à from mcp.server import MCPServer |
Lancez aussi la commande de démarrage à la main : un serveur stdio sain n’affiche rien et attend. Une trace d’erreur ou un arrêt immédiat indique le vrai problème.
Aller plus loin
- Le partager :
uv run mcp run server.py --transport streamable-httpsert le même serveur en HTTP surhttp://localhost:8000/mcp. Avant de l’exposer sur Internet, ajoutez une authentification (le SDK prend en charge OAuth) et limitez ce que chaque outil peut faire. - Le tester automatiquement : le SDK fournit un
Clientqui se connecte directement à l’objetmcpen mémoire, sans processus ni port, pratique pour des testspytest. - Le publier : le registre MCP officiel référence les serveurs publics ; notre annuaire des serveurs MCP les reprend avec l’activité de leur dépôt et un niveau de risque.
- Le choisir plutôt que le coder : un serveur existe peut-être déjà pour votre besoin. Le guide Choisir un serveur MCP explique comment vérifier qu’il est maintenu et sans danger, et MCP et skills malveillants : vérifier avant d’installer détaille les pièges.
Checklist avant de vous en servir au quotidien
- Le serveur fonctionne seul (
mcp dev) et chaque outil a une docstring claire - Les chemins, adresses ou requêtes acceptés sont limités au strict nécessaire
- Aucun outil ne supprime, n’envoie ou ne paie sans validation humaine
- Aucun secret (clé d’API, mot de passe) n’est écrit dans
server.py: passez-les par variables d’environnement - Le chemin vers
server.pydans la configuration de l’hôte est absolu - Vous avez passé le diagnostic de sécurité d’agent IA sur l’ensemble de votre configuration
Questions fréquentes
Faut-il savoir programmer pour créer un serveur MCP ?
Il faut connaître les bases de Python ou de TypeScript : écrire une fonction, installer un paquet, lancer un script. Le SDK officiel se charge du protocole, du schéma des paramètres et de la validation : un serveur utile tient en une trentaine de lignes.
Python ou TypeScript pour un serveur MCP ?
Les deux SDK sont officiels et maintenus par le projet Model Context Protocol. Prenez le langage de l'outil que vous voulez exposer : Python pour des scripts de données ou d'automatisation, TypeScript si votre code existant est en Node.js. Le protocole est le même, un client ne voit pas la différence.
Mon serveur MCP peut-il être utilisé par d'autres personnes ?
En local (transport stdio), seul votre ordinateur le lance. Pour le partager, servez le même code en Streamable HTTP derrière une adresse web, avec une authentification : le SDK fournit le transport, mais l'hébergement, l'accès et la sécurité restent à votre charge.
Quelle différence entre un outil et une ressource MCP ?
Un outil est une fonction que le modèle décide d'appeler pour agir ou chercher (comme une requête POST). Une ressource est une donnée que l'application charge dans le contexte du modèle, sans effet de bord (comme une requête GET). Un troisième type, le prompt, est un modèle de message que l'utilisateur déclenche par son nom.