Construire un serveur MCP pour Claude : guide pas-à-pas

Un guide exécutable pour écrire ton premier serveur MCP en Python, le brancher à Claude Desktop, exposer des tools custom et éviter les pièges classiques (auth, rate limit, credentials) avant de le passer en prod.

Un serveur MCP, c'est du code que tu écris une fois pour donner à Claude un accès propre à tes outils. Ta base Postgres, ton API interne, ton bucket S3, ton CRM maison. Au lieu de recopier la logique d'auth et d'appels dans chaque conversation ou chaque script, tu exposes ça comme un service que Claude Desktop découvre et appelle tout seul. Ce guide te fait écrire un serveur qui tourne, en Python, connecté à Claude Desktop, en environ une heure.

Si tu débutes plus largement sur l'écosystème, le guide complet pour apprendre Claude couvre le contexte. Ici on va direct dans le code. Le protocole a été publié par Anthropic en novembre 2024 et s'est imposé comme standard : au 28 juillet 2026, Claude liste plus de 950 serveurs MCP dans son répertoire de connecteurs.

Ce qu'est un serveur MCP (et ce que ça n'est pas)

MCP (Model Context Protocol) est un protocole client-serveur basé sur JSON-RPC. Le client (Claude Desktop, Claude Code, une app tierce compatible) parle à un ou plusieurs serveurs via stdio en local ou HTTP en distant. Le serveur expose trois types de primitives :

  • Tools : des fonctions que Claude peut appeler (get_ticket, create_invoice, run_query)
  • Resources : des données que Claude peut lire (fichiers, entrées de base, documents)
  • Prompts : des templates de conversation invoquables via slash-command

MCP n'est pas un remplaçant du function calling de l'API. Le function calling reste ce que Claude utilise sous le capot pour décider d'appeler un outil. MCP est la couche au-dessus qui rend une intégration réutilisable : tu écris ton connecteur Notion une fois, tu le brancheras dans dix projets sans recoder la couche transport, la découverte de tools ou la sérialisation.

Comparé à un plugin ChatGPT ou une extension navigateur, MCP tourne côté client, avec tes credentials, sur ta machine. Rien ne remonte chez Anthropic tant que tu ne le décides pas.

Nuance importante : la spec MCP a évolué vers un modèle stateless le 28 juillet 2026. Si tu maintiens un serveur MCP en production, jette un œil à ce qui change côté architecture. Pour un premier serveur en local, la version que le SDK Python installe par défaut fait le job.

Prérequis et choix du SDK

Ce qu'il te faut avant de commencer :

  • Python 3.10+ ou Node 18+
  • Claude Desktop installé et connecté à ton compte (la version desktop, pas juste l'app web)
  • Un éditeur, un terminal, cinq minutes

Deux SDK officiels : mcp en Python, @modelcontextprotocol/sdk en TypeScript. Critère de choix simple. Python si tu veux scripter vite un accès à un pandas, une base, un modèle ML. TypeScript si le serveur vivra à côté d'une app Node/Next existante et partagera des types avec elle. La structure des deux SDK est très proche : décorateurs pour lister les tools, handler pour les appels, boucle stdio. Les exemples ci-dessous sont en Python pour la lisibilité, la traduction en TS est mécanique.

Un dernier point avant le code : si ton objectif est plutôt de automatiser ta stack avec Claude et MCP sans écrire de serveur, l'annuaire des serveurs MCP existants couvre déjà Notion, Slack, HubSpot, GitHub, Postgres et beaucoup d'autres. Écrire ton propre serveur n'a de sens que si ton outil interne n'est pas déjà couvert.

Écrire un premier serveur MCP en 30 lignes

Crée un dossier, un venv, installe le SDK :

mkdir mcp-weather && cd mcp-weather
python -m venv .venv
source .venv/bin/activate
pip install mcp

Crée server.py :

import asyncio
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent

server = Server("weather")

@server.list_tools()
async def list_tools() -> list[Tool]:
    return [
        Tool(
            name="get_weather",
            description="Retourne la meteo d'une ville (mock).",
            inputSchema={
                "type": "object",
                "properties": {"city": {"type": "string"}},
                "required": ["city"],
            },
        )
    ]

@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    if name == "get_weather":
        city = arguments["city"]
        return [TextContent(type="text", text=f"Il fait 18 degres a {city}, ciel couvert.")]
    raise ValueError(f"Tool inconnu: {name}")

async def main():
    async with stdio_server() as (read, write):
        await server.run(read, write, server.create_initialization_options())

if __name__ == "__main__":
    asyncio.run(main())

Trois blocs à retenir. list_tools déclare ce que le serveur sait faire (nom, description, schéma JSON des arguments). call_tool reçoit un nom et un dict d'arguments, retourne du contenu texte. La boucle stdio_server gère le transport, tu n'y touches pas.

Lance python server.py. Le processus reste ouvert et attend des messages sur stdin. C'est normal, il n'affiche rien. À ce stade rien n'est encore branché à Claude, on va s'en occuper.

Connecter le serveur à Claude Desktop

Ouvre le fichier de config de Claude Desktop :

  • macOS : ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows : %APPDATA%\Claude\claude_desktop_config.json
  • Linux : ~/.config/Claude/claude_desktop_config.json

Ajoute (ou crée) cette structure :

{
  "mcpServers": {
    "weather": {
      "command": "/chemin/absolu/vers/mcp-weather/.venv/bin/python",
      "args": ["/chemin/absolu/vers/mcp-weather/server.py"]
    }
  }
}

Utilise des chemins absolus, Claude Desktop ne résout pas le PATH shell. Ferme complètement Claude Desktop (Cmd+Q sur macOS, pas juste la fenêtre) et relance. Une icône d'outils apparaît près de la barre de saisie. Clique dessus, tu dois voir get_weather. Tape « Quelle est la météo à Lyon ? ». Claude propose d'appeler le tool, tu valides, la réponse mock s'affiche.

Si l'icône n'apparaît pas, va voir les logs. Sur macOS : ~/Library/Logs/Claude/mcp*.log. Les erreurs classiques : chemin Python invalide (le venv n'est pas trouvé), JSON du config mal formé (une virgule en trop et tout casse en silence), permissions d'exécution manquantes sur le script. Un tail -f ~/Library/Logs/Claude/mcp-server-weather.log en parallèle du relancement de Claude te dira ce qui bloque. Si ta config Desktop plus large te pose problème, le guide pour configurer Claude Desktop couvre les cas généraux.

Aller plus loin : resources et prompts

Les tools couvrent 80 % des cas. Les deux autres primitives complètent le tableau.

Resources. Une resource est un contenu adressable par URI que Claude peut lire à la demande. Utile pour exposer un catalogue de fichiers, une liste de tickets, une doc interne. Tu déclares @server.list_resources() qui retourne des objets Resource(uri, name, mimeType), puis @server.read_resource() qui reçoit l'URI et retourne le contenu. Dans une conversation, l'utilisateur peut attacher une resource au contexte via l'UI Claude Desktop (l'icône trombone montre les resources disponibles).

Prompts. Un prompt est un template invocable via slash-command. Tu déclares @server.list_prompts() et @server.get_prompt(). L'utilisateur tape /nom-du-prompt et Claude reçoit le template pré-rempli avec les arguments. Pratique pour standardiser des workflows récurrents : /daily-standup, /review-pr, /incident-report.

Un serveur MCP mature mixe les trois. Un connecteur Linear expose des tools (créer, fermer un ticket), des resources (lire les tickets ouverts d'un projet) et des prompts (/triage-bugs).

Cas d'usage concrets pour un opérationnel

Sortir de l'exemple météo. Trois cas réalistes pour un freelance ou une petite équipe.

1. Postgres en lecture seule. Tool run_query(sql) qui exécute une requête sur ta base d'analytics. Piège classique : ne pas donner un rôle avec droits d'écriture. Crée un utilisateur Postgres dédié, GRANT SELECT sur les tables concernées, rien d'autre. Ajoute un LIMIT par défaut côté serveur si le SQL n'en contient pas, sinon Claude va te renvoyer un million de lignes le premier jour.

2. Créer des tâches dans un outil de gestion. Tool create_task(title, project, due_date) qui pousse dans Linear, Todoist ou ClickUp via leur API REST. Piège : le rate limit. La plupart des API tolèrent 60 à 100 requêtes par minute. Ajoute un simple asyncio.Semaphore ou un compteur pour éviter que Claude enchaîne 200 créations en boucle si tu lui demandes « crée un ticket pour chaque bug de cette liste ».

3. Lire et résumer les emails support. Resource qui expose les 50 derniers emails d'une inbox IMAP, tool reply_email(id, body) qui répond via SMTP. Piège : le scope. Commence par IMAP en lecture seule, sans le tool reply. Fais tourner une semaine, regarde ce que Claude essaie de faire, ajoute l'écriture ensuite quand tu es à l'aise avec les patterns d'appel.

Ces trois cas remplacent souvent des scripts Python isolés qui traînent dans un dossier tools/. Packager en MCP les rend accessibles depuis n'importe quelle conversation Claude, sans copier-coller de contexte.

Sécurité et bonnes pratiques avant de mettre en prod

Un serveur MCP tourne en local, sous ton compte utilisateur, avec tes permissions. Un tool execute_shell(cmd) qui passe la commande à subprocess.run sans filtre est une porte ouverte à un rm -rf mal placé. Règles minimales :

  • Jamais de shell arbitraire. Si tu as besoin d'exécuter des commandes, whiteliste. allowed_commands = {"ls", "cat", "git status"}, refuse tout le reste.
  • Valide tous les inputs. Pydantic ou jsonschema côté Python. Un path qui contient ../ doit être rejeté, pas nettoyé silencieusement.
  • Lecture seule par défaut. Sépare les serveurs qui lisent (bas risque) et ceux qui écrivent (haut risque). Tu peux avoir notion-read et notion-write comme deux entrées distinctes dans le config, activer seulement le second quand tu en as besoin.
  • Credentials hors du config.json. Utilise le keychain OS (keyring en Python) ou des variables d'environnement injectées via le champ env du config MCP. Ne commit jamais un claude_desktop_config.json avec des tokens dedans.
  • Versionne le serveur. C'est du code de prod, même si ça tourne sur ton laptop. Git, tests, changelog. Le jour où tu casses un tool et Claude ne peut plus créer de ticket, tu veux pouvoir git bisect.

Pour partager un serveur avec ton équipe, passe au transport SSE ou HTTP au lieu de stdio. Le serveur tourne alors sur une VM ou un container accessible par plusieurs utilisateurs. La spec 2026-07-28 a rendu ce mode plus propre en supprimant l'état côté protocole. Si tu es plus à l'aise avec l'API Anthropic directement, tu peux aussi consommer ton serveur MCP depuis un script sans passer par Claude Desktop.

Une fois ton premier serveur en prod, la question suivante est d'en faire un vrai produit, avec UI, comptes, facturation. C'est exactement ce qu'on couvre en cinq semaines dans le programme Construire votre produit IA en 5 semaines, en partant de connecteurs MCP comme brique de base d'agents plus larges. Le code que tu viens d'écrire est déjà la moitié du chemin.

Pilier 6 · Mastery

Automatiser sa stack avec Claude et MCP

Connecter Claude à votre CRM, Notion, Slack, et construire des workflows fiables. Le territoire des opérationnels et des Builders.

Découvrir le pilier complet →Formation Claude Agent →