Créer un MCP Server from scratch : guide complet pour développeurs (2026)

Si vous voulez que Claude, GPT ou Cursor interroge votre base de donnees, appelle des APIs ou cherche dans vos notes locales—mais chaque modele exige un adaptateur different—vous heurtez le meme mur d integration N×M que MCP a ete concu pour resoudre. Ce guide pratique s adresse aux developpeurs backend et IA avec des bases Python ou TypeScript. A la fin, vous construirez, debuggerez et deployerez un MCP Server pret pour la production avec Tools, Resources, Prompts, transport HTTP, projet base de connaissances et Runbook Mac cloud en cinq etapes.

Diagramme de poste de travail developpeur montrant un MCP Server connectant des clients IA a des outils externes, bases de donnees et ressources fichiers

Table des matieres

1. Introduction : pourquoi l IA a besoin d outils externes

Les LLM sont de puissants moteurs de texte mais aveugles a vos systemes live : les cutoffs d entrainement bloquent les donnees temps reel, et les modeles ne peuvent pas executer d effets de bord sans Tool Use. Vous voulez un assistant qui lit votre vault Markdown, execute du SQL ou appelle des APIs internes—sans reecrire les integrations a chaque passage de Claude a GPT ou de Cursor a un autre IDE.

Trois points de douleur avant MCP

  1. Cablage d outils fragmente. OpenAI Function Calling, Claude Tool Use et LangChain Tools definissent les schemas differemment—N modeles × M tools = N×M adaptateurs.
  2. Pas de decouverte a l execution. Les endpoints REST sont dans des docs statiques ; l IA ne peut pas appeler tools/list pour savoir ce qui est disponible en session.
  3. Dependance au Host local. Cursor et Claude Desktop lancent des sous-processus STDIO sur macOS ; un Linux VPS ou un laptop ferme casse la chaine.

Model Context Protocol (MCP) standardise comment les Hosts decouvrent et invoquent les Tools, lisent les Resources et chargent les templates Prompt via JSON-RPC 2.0. Ce tutoriel va de zero a un Server deployable—pas seulement de la theorie. Pour le « pourquoi MCP est le HTTP de l IA » au niveau protocole, voir notre analyse approfondie du protocole MCP.

2. Qu est-ce que MCP ? Protocole avant le code

Anthropic a open-source MCP en novembre 2024. Il se situe entre les Clients IA (Claude Desktop, Cursor, apps custom) et le Server que vous construisez—exposant trois types de capabilities :

┌────────────────────┐ ┌─────────────────────┐ │ MCP Client │ ◄─────► │ MCP Server │ │ (Claude / Cursor) │ JSON │ (vous le construisez)│ │ │ -RPC │ │ └────────────────────┘ └─────────────────────┘ │ ┌─────────────┼─────────────┐ ▼ ▼ ▼ Tools Resources Prompts (actions) (lire donnees) (templates)

Cycle de vie : handshake initialize → negociation de capabilities → request/response → arret propre. Transports : STDIO (sous-processus local) ou HTTP + SSE / streamable HTTP (distant).

MCP vs Function Calling vs LangChain Tools

DimensionMCPOpenAI Function CallingLangChain Tools
StandardisationProtocole ouvertSpecifique au vendeurLie au framework
TransportSTDIO / HTTPHTTP uniquementHTTP uniquement
Cross-modelOuiNonPartiel
Resources / PromptsNatifNon supporteNon supporte
Decouverte runtimetools/listSchema statiqueSchema statique

3. Configuration de l environnement

Choisir votre langage

Python (recommande pour ce guide) : SDK officiel mcp avec decorateurs FastMCP. TypeScript : @modelcontextprotocol/sdk pour les equipes Node-native.

# Python python -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate pip install "mcp[cli]" httpx pydantic # TypeScript (reference) npm init -y npm install @modelcontextprotocol/sdk

Structure du projet

my-mcp-server/ ├── server.py # point d entree ├── tools/ │ ├── calculator.py │ ├── files.py │ ├── http_client.py │ ├── database.py │ └── datetime_tools.py ├── resources/ │ └── file_reader.py ├── prompts/ │ └── templates.py ├── tests/ │ └── test_tools.py ├── pyproject.toml └── README.md

Outils de debug

4. Hello World : votre premier MCP Server

from mcp.server.fastmcp import FastMCP mcp = FastMCP("my-first-server") @mcp.tool() def say_hello(name: str) -> str: """Greet a person by name.""" return f"Hello, {name}! This is your first MCP tool." if __name__ == "__main__": mcp.run()

Lancer et inspecter :

python server.py npx @modelcontextprotocol/inspector python server.py

Brancher dans Cursor (.cursor/mcp.json) :

{ "mcpServers": { "my-first-server": { "command": "python", "args": ["/absolute/path/to/server.py"], "env": {} } } }

Redemarrer Cursor, ouvrir les reglages MCP et confirmer que say_hello apparait dans la liste des tools.

5. Tools : cinq exemples pratiques

Les Tools sont des actions. Signatures de fonctions plus docstrings deviennent JSON Schema pour le modele. Utilisez des modeles Pydantic pour les entrees complexes.

Tool 1 — Calculatrice

import ast import operator @mcp.tool() def calculate(expression: str) -> str: """Safely evaluate a math expression like '2 + 2 * 3'.""" ops = { ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv, } def _eval(node): if isinstance(node, ast.Num): return node.n if isinstance(node, ast.BinOp): return ops[type(node.op)](_eval(node.left), _eval(node.right)) raise ValueError("Unsupported expression") return str(_eval(ast.parse(expression, mode="eval").body))

Tool 2 — Lecture/ecriture de fichiers

from pathlib import Path ALLOWED = Path("/data/notes").resolve() @mcp.tool() def read_file(path: str) -> str: """Read a text file under the allowed directory.""" target = (ALLOWED / path).resolve() if not str(target).startswith(str(ALLOWED)): raise PermissionError("Path outside allowed root") return target.read_text(encoding="utf-8") @mcp.tool() def write_file(path: str, content: str) -> str: """Write text to a file under the allowed directory.""" target = (ALLOWED / path).resolve() if not str(target).startswith(str(ALLOWED)): raise PermissionError("Path outside allowed root") target.parent.mkdir(parents=True, exist_ok=True) target.write_text(content, encoding="utf-8") return f"Wrote {len(content)} bytes to {path}"

Tool 3 — Requete HTTP (async)

import httpx @mcp.tool() async def fetch_url(url: str, timeout: int = 10) -> str: """GET a URL and return response text (truncated to 8 KB).""" async with httpx.AsyncClient(timeout=timeout) as client: r = await client.get(url) r.raise_for_status() return r.text[:8192]

Tool 4 — Requete base de donnees

import sqlite3 @mcp.tool() def query_sql(sql: str) -> list[dict]: """Run a read-only SELECT against the app SQLite database.""" if not sql.strip().upper().startswith("SELECT"): raise ValueError("Only SELECT queries allowed") conn = sqlite3.connect("app.db") conn.row_factory = sqlite3.Row rows = conn.execute(sql).fetchall() conn.close() return [dict(r) for r in rows]

Tool 5 — Heure et fuseau horaire

from datetime import datetime from zoneinfo import ZoneInfo @mcp.tool() def now_in_timezone(tz: str = "UTC") -> str: """Return current time in an IANA timezone (e.g. America/New_York).""" return datetime.now(ZoneInfo(tz)).isoformat()

Bonnes pratiques : retourner des types serialisables JSON ; erreurs structurees plutot que stack traces brutes ; timeouts sur appels reseau et DB ; valider chemins et SQL contre l injection.

6. Resources : contenu dynamique pour l IA

Les Resources fournissent des donnees ; les Tools executent des actions. MCP les adresse par URI.

import json @mcp.resource("config://app-settings") def get_app_settings() -> str: """Return application configuration as JSON.""" return json.dumps({"version": "1.0.0", "env": "production"}) @mcp.resource("user://{user_id}/profile") def get_user_profile(user_id: str) -> str: """Return a user profile by ID.""" user = db.lookup(user_id) # your data layer return json.dumps(user)

Types MIME : text/plain, application/json, ou binaire pour PDFs/images. Pour un Server filesystem, exposer listes de repertoires et contenus de fichiers en Resources pour que le modele lise le contexte sans muter l etat via Tools.

7. Prompts : bibliotheque de templates reutilisables

Les Prompts empaquetent des templates multi-tours avec parametres—utiles pour code review, triage d incident ou preparation d entretien.

from mcp.types import PromptMessage, TextContent @mcp.prompt() def code_review_prompt(language: str, code: str) -> list[PromptMessage]: """Structured code review template.""" return [ PromptMessage( role="user", content=TextContent( type="text", text=f"""Review this {language} code for: 1. Readability and structure 2. Bugs and security issues 3. Performance improvements ```{language} {code} ```""" ) ) ]

Les Hosts appellent prompts/list et prompts/get pour injecter des instructions coherentes—moins de derive de prompt entre sessions.

8. Transport HTTP : MCP Server distants

FonctionnaliteSTDIOHTTP + SSE / streamable HTTP
DeploiementSous-processus localServer distant
LatenceTres faibleDepend du reseau
Multi-clientUn processus HostPlusieurs clients
Ideal pourCursor, Claude DesktopSaaS equipe, tools partages
from mcp.server.fastmcp import FastMCP mcp = FastMCP("remote-server", host="0.0.0.0", port=8000) # Middleware auth en production : # - Validation header Bearer Token # - Cle API par tenant # - Allowlist CORS pour clients web # - Rate limiting (ex. 100 req/min) if __name__ == "__main__": mcp.run(transport="streamable-http")

Placer Nginx ou un load balancer cloud devant pour la terminaison TLS. Ne jamais exposer un endpoint MCP HTTP non authentifie sur Internet public.

9. Debug et tests

Workflow MCP Inspector

  1. Demarrer : npx @modelcontextprotocol/inspector python server.py
  2. Appeler tools/list et verifier les schemas
  3. Invoquer chaque Tool avec des entrees limites
  4. Inspecter les paires request/response JSON-RPC dans l UI

Exemple de test automatise

import pytest from mcp.client.session import ClientSession from mcp.client.stdio import StdioServerParameters, stdio_client @pytest.mark.asyncio async def test_calculator(): params = StdioServerParameters(command="python", args=["server.py"]) async with stdio_client(params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() result = await session.call_tool( "calculate", {"expression": "2 + 2"} ) assert "4" in result.content[0].text

Erreurs courantes

ErreurCauseCorrection
Tool absent du HostMauvais chemin dans mcp.jsonChemins absolus ; redemarrer le Host
Echec serialisation JSONRetour non serialisableRetourner str ou dict
Deconnexion timeoutTool sync lentPasser en async ; ajouter timeout
Permission deniedChemin hors allowlistConfigurer les racines autorisees

10. Deploiement en production

Runbook de production en cinq etapes

Etape 1 — Containeriser

FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 HEALTHCHECK CMD curl -f http://localhost:8000/health || exit 1 CMD ["python", "server.py"]

Etape 2 — Transport et authentification

Activer streamable HTTP ; exiger Bearer Token ou API Key a chaque requete ; restreindre CORS aux origines Host connues.

Etape 3 — Tester avant mise en production

Executer MCP Inspector plus suite pytest ; documenter les baselines de latence p95 tools/call.

Etape 4 — Observabilite

Logs JSON structures, compteurs Prometheus par nom de tool, Sentry pour exceptions non gerees, endpoint /health pour orchestrateurs.

Etape 5 — Heberger sur infrastructure durable

Pour les workflows STDIO avec Cursor, utiliser launchd sur un noeud Mac always-on avec limites CPU et memoire. Pour le mode HTTP, deployer sur Railway, Render, Cloud Run ou derriere Nginx sur VPS—with TLS et auth.

Declarer la version du protocole MCP dans les reponses initialize et versionner les Tools pour ne pas casser les Clients existants.

11. Projet : MCP Server base de connaissances personnelle

Objectif : Permettre a Cursor de repondre « Qu ai-je ecrit sur MCP la semaine derniere ? » en cherchant dans des notes Markdown locales.

Stack

Tools principaux

@mcp.tool() def index_notes(directory: str) -> str: """Scan Markdown files and rebuild the vector index.""" count = indexer.rebuild(directory) return f"Indexed {count} documents" @mcp.tool() def semantic_search(query: str, top_k: int = 5) -> list[dict]: """Search notes by meaning; return title, path, and snippet.""" return vector_store.search(query, k=top_k) @mcp.tool() def create_note(title: str, content: str) -> str: """Create a new Markdown note and index it.""" path = vault.create(title, content) indexer.add(path) return str(path)

Exposer les fichiers du vault en Resources (note://{slug}) pour apercu read-only. Dans Cursor, poser des questions en langage naturel ; le modele appelle semantic_search et cite des extraits du vault.

12. Perspectives de l ecosysteme MCP

Mi-2026, MCP est supporte par Claude Desktop, Cursor, extensions VS Code Copilot, tooling OpenAI Responses API et un marketplace en croissance. Servers de reference a etudier :

L adoption entreprise pousse les standards auth (OAuth 2.1, cles API scopees) et les registres de Server audites. Prochaines etapes : lire la spec sur modelcontextprotocol.io, publier votre Server sur GitHub, combiner MCP avec des frameworks d orchestration Agent.

13. Faits verifiables a citer (2026-06-16)

14. Conclusion : du tutoriel au MCP de niveau production

Vous avez le chemin complet : modele mental du protocole, setup SDK Python, cinq Tools, Resources, Prompts, mode HTTP, tests Inspector, packaging Docker et projet reference base de connaissances. Tout sur un laptop local fonctionne pour les demos—mais la fermeture du capot tue les sessions STDIO, les quirks de chemins et permissions se multiplient en equipe, et Linux VPS ou stacks Docker-only ne peuvent pas heberger les sous-processus natifs Cursor/Claude Desktop ou sidecars toolchain Apple sans contournements penibles.

Pour des MCP Server stables, auditables, 7×24 que Cursor et Claude Desktop atteignent de facon fiable, deployez sur un noeud Mac cloud VPSMAC M4 dedie : macOS bare-metal, launchd KeepAlive, SSH en minutes, environnements Python figeables, et place pour passerelles HTTP plus STDIO cote a cote. C est le chemin production 2026—plus controlable que parier sur une machine principale restee allumee, et bien plus simple que forcer des workflows Host macOS sur Linux VPS.