Créer un MCP Server from scratch
Guide complet pour développeurs

Si votre Claude / GPT / Cursor ne peut discuter mais pas interroger une base de données, lire des fichiers ou appeler des API — vous n'avez pas besoin d'un prompt plus long, mais d'une couche d'outils réutilisable. Ce guide s'adresse aux développeurs backend et full-stack : du Hello World au MCP Server ChromaDB, en passant par Tools / Resources / Prompts, les transports stdio et HTTP+SSE, le débogage, les tests et le déploiement Docker en production. À la fin, vous aurez des outils personnalisés appelables depuis Cursor et une vision claire pour héberger le Server sur un Mac distant en 24/7 (pour le contexte protocolaire, commencez par notre article d'analyse du protocole MCP).

01

Pourquoi l'IA a besoin d'un MCP Server : un LLM sans outils n'est qu'un cerveau qui parle

Les grands modèles ont une date limite d'entraînement et ne peuvent accéder à votre CRM, vos dépôts Git ou vos API internes. Avant 2024, vous écriviez du Function Calling pour Claude, des Plugins pour GPT et un autre format pour Cursor — changer de modèle = tout recommencer. Un MCP Server encapsule les capacités d'outils dans un processus indépendant : écrivez une fois, utilisez dans Claude Desktop, Cursor et Gemini.

Scénarios typiques : interroger Postgres depuis Claude Desktop ; faire lire la documentation projet et modifier le code par un Agent Cursor ; appeler votre système de tickets via HTTP MCP depuis GPT — le même Server sous-jacent.

Ce que ce guide livre : pas seulement du concept, mais un chemin de say_hello vers un Server de base de connaissances production avec recherche vectorielle. Public visé : développeurs avec bases Python ou TypeScript qui veulent étendre l'IA dans leur IDE ou application Desktop.

Six points de friction : pourquoi « envelopper une API » ne suffit pas

  1. 01

    Verrouillage fournisseur : OpenAI Function Calling et Claude Tool Use utilisent des formats différents — chaque changement de fournisseur impose de réécrire la couche d'adaptation.

  2. 02

    Outils non découvrables : les API REST reposent sur une documentation statique ; l'IA ne peut pas appeler tools/list à l'exécution pour découvrir les capacités seule.

  3. 03

    Silos IDE : Cursor, extensions VS Code et plugins JetBrains définissent les outils séparément — vous maintenez N×M intégrations.

  4. 04

    Contexte et données fragmentés : les LLM ne lisent pas fiabilément config, préférences utilisateur ou logs en direct — il faut des Resources en lecture seule standardisées.

  5. 05

    Templates de prompt dispersés : code review, rapports d'incident, etc. n'ont pas de registre unifié ; chaque équipe copie-colle.

  6. 06

    Chaos local vs distant : les sous-processus stdio conviennent au développement, mais les passerelles HTTP production, l'authentification et le monitoring manquent de patron commun (voir notre guide gouvernance sous-processus stdio).

« Connecter un MCP Server à l'IA, c'est comme installer des extensions IDE pour un développeur — la frontière des capacités passe du chat à l'action sur le monde réel. »

02

Qu'est-ce que le MCP : du Function Calling au protocole ouvert

Évolution : Function Calling (2023)ChatGPT PluginsMCP (nov. 2024, open source Anthropic). Anthropic a conçu le MCP comme le « USB-C » entre l'IA et le monde extérieur : le Host (Cursor / Claude Desktop) intègre un Client MCP et ouvre une session 1:1 avec votre MCP Server.

Architecture : Client / Server et trois capacités

  • Tools (outils) : opérations appelables avec effets de bord — requêtes base, écriture fichiers, requêtes HTTP.
  • Resources (ressources) : contexte en lecture seule — configuration, extraits de docs, profils utilisateur, adressés par URI.
  • Prompts (modèles de prompt) : squelettes de conversation multi-tours — code review, post-mortem d'incident.

La communication repose sur JSON-RPC 2.0 : initializetools/list / tools/callresources/read. Deux cycles de vie au niveau transport :

  • stdio : le Host lance le Server en sous-processus ; stdin/stdout transportent JSON-RPC ; la session se termine à l'arrêt du processus.
  • HTTP + SSE : le Client se connecte à une URL distante ; le Server pousse un flux d'événements — adapté au partage d'équipe et à la montée en charge.

Spécification complète sur modelcontextprotocol.io.

MCP vs OpenAI FC vs outils LangChain

DimensionMCPOpenAI Function CallingOutils LangChain
OuvertureProtocole ouvert multi-fournisseurs, gouvernance AAIFLié à l'API OpenAIAbstraction framework, pas un standard de transport
Découvertetools/list à l'exécutionTableau functions inline dans la requêteEnregistrement en code, pas de découverte standard
Données lecture seuleResources + schéma URIPas d'équivalent de premier ordreConcept Retriever, pas protocolaire
Templates de promptInterface Prompts standardAucunClasse PromptTemplate
Transportstdio / HTTP+SSE / Streamable HTTPAPI HTTPS intégréeDépend du runtime Agent
RéutilisabilitéUn Server sert Cursor + Claude + GeminiÉcosystème OpenAI seulRéécriture inter-frameworks
03

Environnement de développement : Python FastMCP vs SDK TypeScript

Deux voies principales : Python mcp + FastMCP (data/scripts) et TypeScript @modelcontextprotocol/sdk (intégration Web/API, typage strict). Dépôts SDK : python-sdk, typescript-sdk.

bash
# Voie Python
python -m venv .venv && source .venv/bin/activate
pip install "mcp[cli]" httpx pydantic

# Voie TypeScript
npm init -y && npm install @modelcontextprotocol/sdk zod
npm install -D typescript tsx @types/node

Structure de projet recommandée

tree
my-mcp-server/
├── pyproject.toml          # ou package.json
├── src/
│   ├── server.py           # point d'entrée FastMCP
│   ├── tools/              # modules d'outils
│   ├── resources/          # fournisseurs Resource
│   └── prompts/            # templates Prompt
├── tests/
│   └── test_tools.py       # pytest + ClientSession
├── Dockerfile
└── README.md

Checklist en six étapes : environnement prêt

  1. 01

    Choisir la stack : équipes data/ML → Python ; full-stack Node → TypeScript.

  2. 02

    Créer un environnement virtuel et verrouiller les dépendances : pip freeze ou package-lock.json pour éviter la dérive de schéma.

  3. 03

    Installer MCP Inspector : npx @modelcontextprotocol/inspector pour déboguer JSON-RPC visuellement.

  4. 04

    Configurer Claude Desktop : éditer ~/Library/Application Support/Claude/claude_desktop_config.json et ajouter command/args du Server.

  5. 05

    Configurer Cursor : Settings → MCP → Add Server ; pour stdio, python -m src.server ou chemin absolu.

  6. 06

    Vérifier la connectivité Inspector : lancer le Server → connecter Inspector → confirmer que tools/list retourne une liste non vide.

json
// Exemple de config MCP Cursor / Claude Desktop
{
  "mcpServers": {
    "my-tools": {
      "command": "python",
      "args": ["-m", "src.server"],
      "env": { "API_KEY": "your-key" }
    }
  }
}
04

Hello World : lancer votre premier MCP Server en 30 secondes

Avec FastMCP, créez un outil say_hello et validez la chaîne complète : code → Inspector → Cursor.

python
# src/server.py
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("hello-server")

@mcp.tool()
def say_hello(name: str = "World") -> str:
    """Saluer une personne ou entité"""
    return f"Hello, {name}! MCP is working."

if __name__ == "__main__":
    mcp.run()  # transport stdio par défaut
bash
# Déboguer avec Inspector
npx @modelcontextprotocol/inspector python -m src.server

# ou lancer stdio directement
python -m src.server

Après avoir ajouté la même commande dans Cursor, demandez à l'Agent « utilise say_hello pour saluer NodeMini ». Si vous obtenez un résultat JSON, le handshake Client ↔ Server a réussi.

info

Astuce : FastMCP génère automatiquement le JSON Schema depuis les docstrings et annotations de type — pas besoin de décrire les paramètres manuellement.

05

Tools en pratique : du schéma à cinq outils production

Les Tools sont la capacité centrale du MCP : l'IA exécute des opérations avec effets de bord via tools/call. Chaque Tool expose un nom, une description et un inputSchema ; FastMCP utilise Pydantic pour la validation.

python
from pydantic import BaseModel, Field

class SearchInput(BaseModel):
    query: str = Field(..., description="Mot-clé de recherche")
    limit: int = Field(10, ge=1, le=100, description="Nombre de résultats")

@mcp.tool()
async def search_docs(params: SearchInput) -> str:
    """Rechercher dans la bibliothèque de documents"""
    results = await index.search(params.query, params.limit)
    return json.dumps(results, ensure_ascii=False)

Cinq patterns d'outils courants

  • calculator : eval sécurisé ou ast.literal_eval ; jamais exec.
  • file_read / file_write : liste blanche de répertoire racine pour éviter le path traversal.
  • fetch_url (async) : httpx avec timeout et liste blanche de domaines.
  • db_query : SQL lecture seule avec requêtes paramétrées ; pas de DDL.
  • get_current_time : retourne l'heure ISO8601 avec fuseau pour corriger les hallucinations temporelles du LLM.
python
import httpx
from datetime import datetime, timezone

@mcp.tool()
async def fetch_url(url: str) -> str:
    """HTTP GET du contenu URL (domaines en liste blanche)"""
    allowed = ("api.github.com", "nodemini.com")
    if not any(url.startswith(f"https://{d}") for d in allowed):
        raise ValueError(f"Domain not allowed: {url}")
    async with httpx.AsyncClient(timeout=10.0) as client:
        resp = await client.get(url)
        resp.raise_for_status()
        return resp.text[:8000]

@mcp.tool()
def get_current_time() -> str:
    """Retourner l'heure UTC actuelle"""
    return datetime.now(timezone.utc).isoformat()

Bonnes pratiques de gestion d'erreurs

  • Erreurs structurées : raise ValueError("message lisible") — le Client renvoie le message au LLM.
  • Distinction réessayable vs fatal : timeouts réseau marqués retryable ; refus de permission = échec immédiat.
  • Logs et masquage : journaliser nom d'outil et latence ; ne jamais loguer clés API complètes ou PII.
  • Idempotence : opérations d'écriture avec idempotency_key pour éviter les données corrompues par appels Agent en double.
06

Resources : contexte en lecture seule et schémas URI

Tool vs Resource : les Tools ont des effets de bord et sont invoqués par l'IA ; les Resources sont du contexte en lecture seule que le Host injecte avant la conversation ou que l'IA tire via resources/read. Schémas URI personnalisés — ex. config://, user://, file://.

python
@mcp.resource("config://app/settings")
def app_settings() -> str:
    """Configuration applicative statique (text/plain)"""
    return open("config/settings.json").read()

@mcp.resource("user://{user_id}/profile")
def user_profile(user_id: str) -> str:
    """Profil utilisateur dynamique (application/json)"""
    return json.dumps(get_user(user_id))

Types de contenu Resource

Type MIMEUsageExemple
text/plainLogs, READMEfile://logs/app.log
application/jsonConfig, réponses APIconfig://env
application/octet-streamBinaire (base64)Résumé PDF
text/event-streamSouscription temps réelTail de logs, flux metrics

Pattern Server Resource filesystem : implémenter resources/list pour scanner les répertoires, resources/read pour lire par URI, resources/subscribe pour surveiller les changements via watchfiles et pousser les mises à jour — idéal pour exposer la documentation du codebase à un Agent Cursor.

07

Prompts : modèles de conversation multi-tours réutilisables

Un Prompt MCP est un squelette de conversation enregistré sur le Server. Le Client récupère une liste de messages via prompts/get, avec rôles user / assistant et placeholders de paramètres — l'équipe partage code review et workflows d'incident sans fichiers prompt individuels.

python
from mcp.types import PromptMessage, TextContent

@mcp.prompt()
def code_review_prompt(language: str = "python") -> list[PromptMessage]:
    """Template multi-tours Code Review standardisé"""
    return [
        PromptMessage(role="user", content=TextContent(
            type="text",
            text=f"Vous êtes un ingénieur {language} senior. Auditez le diff suivant selon sécurité, performance et lisibilité."
        )),
        PromptMessage(role="assistant", content=TextContent(
            type="text",
            text="Collez le diff ou indiquez le numéro de PR — je produirai un review structuré selon la CHECKLIST."
        )),
    ]

Les templates multi-tours peuvent imbriquer des variables ({ticket_id}, {severity}). Le Server gère les versions ; quand le Client met à jour le Server, toute l'équipe obtient les standards de review à jour.

08

Transport HTTP : du stdio au déploiement Streamable HTTP en production

DimensionstdioHTTP + SSE / Streamable HTTP
DéploiementSous-processus local, lancé par le HostService autonome, connexion par URL
Montée en chargeMachine unique, scaling horizontal difficileÉquilibrage de charge, réplicas multiples
AuthentificationVariables d'environnement du HostBearer Token / API Key / mTLS
DébogageConnexion directe Inspectorcurl + client SSE
Idéal pourDev personnel, Cursor localPartage d'équipe, intégration SaaS
python
# Mode Streamable HTTP (FastMCP 2026)
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("prod-server", host="0.0.0.0", port=8080)

# ... enregistrer les tools ...

if __name__ == "__main__":
    mcp.run(transport="streamable-http")

Indispensables en production : middleware de validation Bearer Token, liste blanche CORS (domaines Host uniquement), rate limit (ex. 100 req/min/IP), HTTPS terminé au reverse proxy. Détails ops passerelle distante dans notre guide gouvernance passerelle HTTP.

warning

Attention : ne exposez jamais un MCP HTTP sur Internet sans authentification — en 2026, de nombreux Server sont encore trouvés sans protection. Ajoutez toujours auth et restrictions IP.

09

Débogage et tests : Inspector + tests unitaires pytest

MCP Inspector est le débogueur visuel officiel : connectez via stdio ou URL, envoyez manuellement tools/list et tools/call, inspectez les allers-retours JSON-RPC — bien plus efficace que de deviner depuis les logs Cursor.

python
# tests/test_tools.py
import pytest
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

@pytest.mark.asyncio
async def test_say_hello():
    params = StdioServerParameters(command="python", args=["-m", "src.server"])
    async with stdio_client(params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool("say_hello", {"name": "MCP"})
            assert "MCP" in result.content[0].text

Table de référence des erreurs courantes

SymptômeCauseCorrection
Server quitte immédiatement après le lancementstdout pollué par printLogs vers stderr ; jamais print sur stdout
tools/list videDécorateur non enregistré ou ordre d'import incorrectVérifier que @mcp.tool() s'exécute avant run()
Cursor affiche disconnectedChemin command incorrect ou venv non actifChemins absolus ; chemin python complet dans la config
JSON-RPC parse errorSortie non-JSON sur stdioDésactiver bannières debug ; niveau log WARNING+
10

Déploiement production : Docker, cloud et observabilité

dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY pyproject.toml .
RUN pip install --no-cache-dir .
COPY src/ src/
EXPOSE 8080
HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1
CMD ["python", "-m", "src.server", "--transport", "streamable-http"]

Choix de plateforme : Railway / Render pour validation rapide ; AWS ECS / GCP Cloud Run pour conformité entreprise ; VPS + Docker Compose coût minimal mais gestion des correctifs en interne.

Paramètres production citables (EEAT)

  • Version protocole : la spec MCP 2025-03-26 ajoute Streamable HTTP ; le Client négocie protocolVersion à initialize — le Server doit déclarer sa plage de compatibilité.
  • Baseline ressources : Server Tool léger ~128 MB RAM ; index vectoriel ChromaDB recommande ≥2 GB RAM et volume SSD persistant.
  • Stack observabilité : logs JSON structurés → Loki/CloudWatch ; Prometheus /metrics (QPS appels outils, latence P99) ; Sentry pour exceptions non gérées ; /health pour liveness K8s.
11

Projet pratique : MCP Server base de connaissances ChromaDB

Vectorisez Wiki interne / docs Markdown et exposez les outils index_document, search_knowledge et write_note pour qu'un Agent Cursor puisse « consulter la base de connaissances avant d'écrire le code ».

Exigences : indexation incrémentale (watchfiles sur docs/), recherche sémantique Top-K, écriture scratchpad optionnelle.

python
import chromadb
from chromadb.utils.embedding_functions import SentenceTransformerEmbeddingFunction

client = chromadb.PersistentClient(path="./data/chroma")
collection = client.get_or_create_collection(
    "wiki", embedding_function=SentenceTransformerEmbeddingFunction()
)

@mcp.tool()
def search_knowledge(query: str, top_k: int = 5) -> str:
    """Recherche sémantique dans la base de connaissances interne"""
    hits = collection.query(query_texts=[query], n_results=top_k)
    return json.dumps(hits["documents"][0], ensure_ascii=False)

@mcp.tool()
def index_document(path: str) -> str:
    """Indexer un fichier Markdown"""
    text = open(path).read()
    collection.upsert(ids=[path], documents=[text], metadatas=[{"path": path}])
    return f"Indexed: {path}"

Requête démo Cursor : « Recherche dans la base de connaissances les docs sur le déploiement MCP HTTP, puis résume une checklist de mise en ligne en trois étapes » — l'Agent appelle d'abord search_knowledge, puis répond depuis le contexte retrouvé. Remplacez le store vectoriel par Qdrant (gRPC distant) à plus grande échelle.

12

Écosystème, tendances 2026 et parcours d'apprentissage

Des Server officiels et communautaires sont prêts à l'emploi — pas besoin de tout reconstruire :

  • mcp-server-filesystem — lecture/écriture fichiers en sandbox
  • mcp-server-github — API PR/Issue
  • brave-search / postgres / slack — recherche, SQL, collaboration d'équipe

Tendances 2026 : marketplaces MCP, autorisation OAuth 2.1 des outils sur la roadmap spec, Streamable HTTP remplaçant progressivement le SSE pur. Checklist d'apprentissage : ① lire la spec → ② Hello World → ③ écrire 3 Tools → ④ ajouter une Resource → ⑤ pytest → ⑥ déployer Docker → ⑦ connecter Cursor.

Synthèse

De say_hello à la base ChromaDB, vous maîtrisez le MCP Server full-stack : exécution Tools, contexte Resources, templates Prompts, double transport, tests et ops production. Prochaine étape : forker un Server communautaire ou encapsuler vos API d'entreprise comme couche d'outils standard d'équipe.

Le stdio local convient aux expérimentations personnelles, mais plusieurs Server en parallèle, index vectoriels persistants et connexions HTTP longues poussent un portable 16 GB vers le swap fréquent. Les VPS Linux bon marché peinent avec les chaînes outils macOS exclusives. Les passerelles HTTP auto-construites sans affinité de session et authentification souffrent de fuites de connexion et d'exposition non autorisée — la stabilité long terme dépasse rarement les attentes.

Pour les équipes qui traitent le MCP comme infrastructure de production tout en exécutant Agents Cursor et CI iOS/macOS, héberger les MCP Server sur un Mac cloud exclusif en 24/7 est généralement plus prévisible qu'un portable local ou une VM générique. La location Mac Mini cloud NodeMini sert de couche d'exécution MCP + Agent : changez de LLM sous-jacent, les nœuds SSH et la config Server restent inchangés. Spécifications : tarifs de location ; prise en main : centre d'aide.

FAQ

Questions fréquentes

Python FastMCP est le chemin le plus rapide pour les outils data et scripts ; le SDK TypeScript offre typage strict et intégration Node native. Les deux sont entièrement compatibles au niveau protocole. Pour faire tourner plusieurs Server en 24/7, consultez les tarifs de location pour les configurations Mac distantes.

Function Calling est lié à OpenAI ; le MCP est un protocole ouvert transversal Claude, GPT, Gemini et Cursor, avec Resources et Prompts. Contexte dans notre article d'analyse du protocole MCP.

Le stdio léger peut tourner en local ; plusieurs Server + store vectoriel + connexions HTTP longues bénéficient d'un Mac distant exclusif. Étapes d'accès dans le centre d'aide ; ops dans notre guide gouvernance sous-processus stdio.