MCP (Model Context Protocol) : Le Standard qui Connecte l'IA à Tous vos Outils
Guide complet du Model Context Protocol (MCP) d'Anthropic : architecture client/serveur, création d'un serveur MCP pour le RAG, comparaison avec function calling et LangChain tools.
TL;DR
Le Model Context Protocol (MCP) est un standard ouvert créé par Anthropic qui permet aux LLM de se connecter à n'importe quel outil ou source de données via une interface unifiée. En 2026, MCP est adopté par Claude Desktop, Cursor, Windsurf, VS Code et de nombreux autres clients. Ce guide explique l'architecture MCP, montre comment construire un serveur MCP pour le RAG et compare MCP avec les alternatives (function calling, LangChain tools).
Qu'est-ce que le MCP ?
Le problème que MCP résout
Avant MCP, connecter un LLM à des outils nécessitait une intégration custom pour chaque combinaison LLM + outil :
AVANT MCP (N x M intégrations) :
┌──────────┐ ┌──────────┐
│ Claude │────→│ Slack │ Intégration custom 1
│ Claude │────→│ GitHub │ Intégration custom 2
│ GPT-4 │────→│ Slack │ Intégration custom 3
│ GPT-4 │────→│ GitHub │ Intégration custom 4
└──────────┘ └──────────┘
= 4 intégrations pour 2 LLM x 2 outils
AVEC MCP (N + M intégrations) :
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Claude │────→│ MCP │←────│ Slack │ 1 serveur MCP
│ GPT-4 │────→│ Protocol │←────│ GitHub │ 1 serveur MCP
└──────────┘ └──────────┘ └──────────┘
= 2 clients + 2 serveurs = 4 composants (vs 4 intégrations)
Architecture MCP
┌─────────────────────────────────────────────────────┐
│ CLIENT MCP │
│ (Claude Desktop, Cursor, VS Code, votre app) │
├─────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Serveur 1│ │ Serveur 2│ │ Serveur 3│ │
│ │ (GitHub) │ │ (RAG) │ │ (Slack) │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Resources│ │ Tools │ │ Prompts │ │
│ │ (repos) │ │ (search) │ │ (templates)│ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
└─────────────────────────────────────────────────────┘
Les 3 primitives MCP
| Primitive | Description | Exemple | Contrôlé par |
|---|---|---|---|
| Resources | Données exposées au LLM (lecture) | Fichiers, BDD, API responses | Application (client) |
| Tools | Actions que le LLM peut exécuter | Recherche, écriture, envoi | Modèle (LLM) |
| Prompts | Templates réutilisables | Analyse de code, résumé | Utilisateur |
L'écosystème MCP en 2026
Adoption par les clients
| Client | Support MCP | Statut | Notes |
|---|---|---|---|
| Claude Desktop | Natif | Production | Premier client MCP |
| Cursor | Natif | Production | IDE AI avec MCP intégré |
| Windsurf | Natif | Production | IDE AI concurrent |
| VS Code (Copilot) | Plugin | Production | Via extension MCP |
| Continue.dev | Natif | Production | IDE open source |
| Zed | Natif | Beta | Éditeur rapide |
| Claude Code | Natif | Production | CLI avec MCP |
| Cline | Natif | Production | Agent VS Code |
Serveurs MCP communautaires populaires
| Serveur | Fonction |
|---|---|
| mcp-server-github | Repos, issues, PRs |
| mcp-server-filesystem | Lecture/écriture fichiers |
| mcp-server-postgres | Requêtes PostgreSQL |
| mcp-server-slack | Messages, canaux |
| mcp-server-google-drive | Fichiers Google Drive |
| mcp-server-notion | Pages et bases Notion |
| mcp-server-puppeteer | Navigation web |
| mcp-server-memory | Mémoire persistante |
| mcp-server-brave-search | Recherche web |
| mcp-server-qdrant | Base vectorielle |
Construire un serveur MCP pour le RAG
Serveur MCP RAG en Python
DEVELOPERpythonfrom mcp.server import Server, NotificationOptions from mcp.server.models import InitializationOptions from mcp.types import ( Resource, Tool, TextContent, ImageContent, EmbeddedResource, ) import mcp.server.stdio import json # Créer le serveur MCP server = Server("rag-server") # ============ RESOURCES ============ @server.list_resources() async def list_resources() -> list[Resource]: """Liste les sources de données disponibles.""" return [ Resource( uri="rag://knowledge-base/status", name="État de la base de connaissances", description="Statistiques sur les documents indexés", mimeType="application/json", ), Resource( uri="rag://knowledge-base/sources", name="Sources indexées", description="Liste des sources de données actives", mimeType="application/json", ), ] @server.read_resource() async def read_resource(uri: str) -> str: if uri == "rag://knowledge-base/status": stats = await get_kb_stats() return json.dumps({ "total_documents": stats.total_docs, "total_chunks": stats.total_chunks, "last_updated": stats.last_update.isoformat(), "embedding_model": "text-embedding-3-small", "vector_db": "Qdrant", }) elif uri == "rag://knowledge-base/sources": sources = await get_active_sources() return json.dumps([ {"name": s.name, "type": s.type, "doc_count": s.count} for s in sources ]) raise ValueError(f"Ressource inconnue: {uri}") # ============ TOOLS ============ @server.list_tools() async def list_tools() -> list[Tool]: """Liste les outils RAG disponibles.""" return [ Tool( name="search_knowledge_base", description=( "Recherche dans la base de connaissances. " "Utilise la recherche sémantique pour trouver " "les documents les plus pertinents." ), inputSchema={ "type": "object", "properties": { "query": { "type": "string", "description": "La question ou le sujet à rechercher" }, "top_k": { "type": "integer", "description": "Nombre de résultats (défaut: 5)", "default": 5 }, "filter_source": { "type": "string", "description": "Filtrer par source (optionnel)" } }, "required": ["query"] } ), Tool( name="add_document", description=( "Ajoute un document à la base de connaissances. " "Le document sera découpé et indexé automatiquement." ), inputSchema={ "type": "object", "properties": { "content": { "type": "string", "description": "Le contenu du document" }, "title": { "type": "string", "description": "Le titre du document" }, "source": { "type": "string", "description": "La source du document" } }, "required": ["content", "title"] } ), Tool( name="get_answer", description=( "Pose une question et obtient une réponse " "basée sur la base de connaissances (RAG complet)." ), inputSchema={ "type": "object", "properties": { "question": { "type": "string", "description": "La question à poser" }, "context": { "type": "string", "description": "Contexte additionnel (optionnel)" } }, "required": ["question"] } ), ] @server.call_tool() async def call_tool(name: str, arguments: dict) -> list[TextContent]: """Exécute un outil RAG.""" if name == "search_knowledge_base": results = await search_vectors( query=arguments["query"], top_k=arguments.get("top_k", 5), filter_source=arguments.get("filter_source"), ) formatted = [] for i, r in enumerate(results): formatted.append( f"[Résultat {i+1}] (score: {r.score:.2f})\n" f"Source: {r.metadata.get('source', 'N/A')}\n" f"Contenu: {r.text}\n" ) return [TextContent( type="text", text="\n---\n".join(formatted) or "Aucun résultat trouvé." )] elif name == "add_document": doc_id = await index_document( content=arguments["content"], title=arguments["title"], source=arguments.get("source", "manual"), ) return [TextContent( type="text", text=f"Document ajouté avec succès (ID: {doc_id})" )] elif name == "get_answer": answer = await rag_pipeline( question=arguments["question"], context=arguments.get("context", ""), ) return [TextContent( type="text", text=f"Réponse: {answer.text}\n\n" f"Sources: {', '.join(answer.sources)}" )] raise ValueError(f"Outil inconnu: {name}") # ============ PROMPTS ============ @server.list_prompts() async def list_prompts(): return [ { "name": "analyze_document", "description": "Analyse un document et extrait les points clés", "arguments": [ { "name": "document", "description": "Le document à analyser", "required": True } ] } ] # ============ MAIN ============ async def main(): async with mcp.server.stdio.stdio_server() as (read, write): await server.run( read, write, InitializationOptions( server_name="rag-server", server_version="1.0.0", capabilities=server.get_capabilities( notification_options=NotificationOptions(), experimental_capabilities={}, ), ), ) if __name__ == "__main__": import asyncio asyncio.run(main())
Configuration dans Claude Desktop
DEVELOPERjson{ "mcpServers": { "rag-knowledge-base": { "command": "python", "args": ["/path/to/rag_mcp_server.py"], "env": { "QDRANT_URL": "http://localhost:6333", "OPENAI_API_KEY": "sk-..." } }, "github": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"], "env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_..." } } } }
Serveur MCP avec FastMCP (simplifié)
DEVELOPERpythonfrom fastmcp import FastMCP mcp = FastMCP("RAG Assistant") @mcp.tool() async def search_docs(query: str, top_k: int = 5) -> str: """Recherche dans la base de connaissances RAG.""" results = await vector_search(query, top_k) return "\n\n".join( f"[{i+1}] {r.text} (score: {r.score:.2f})" for i, r in enumerate(results) ) @mcp.tool() async def ask_rag(question: str) -> str: """Pose une question au système RAG.""" answer = await rag_pipeline(question) return f"{answer.text}\n\nSources: {answer.sources}" @mcp.resource("rag://stats") async def get_stats() -> str: """Statistiques de la base de connaissances.""" stats = await get_kb_stats() return f"{stats.total_docs} documents, {stats.total_chunks} chunks" if __name__ == "__main__": mcp.run()
MCP vs alternatives
Tableau comparatif
| Critère | MCP | OpenAI Function Calling | LangChain Tools | Custom API |
|---|---|---|---|---|
| Standard ouvert | Oui (Anthropic) | Non (propriétaire) | Non (framework) | Non |
| Interopérabilité | Tout client MCP | OpenAI uniquement | LangChain uniquement | Custom |
| Architecture | Client/Serveur | Request/Response | Chaîne | REST/GraphQL |
| Discovery | Automatique (list_tools) | Schema JSON | Déclaratif | Documentation |
| Streaming | Oui (SSE) | Oui | Oui | Possible |
| Stateful | Oui (session) | Non | Oui (memory) | Possible |
| Resources (données) | Oui (primitif natif) | Non | Non | Custom |
| Prompts templates | Oui (primitif natif) | Non | Oui (PromptTemplate) | Non |
| Sécurité | Contrôle granulaire | Limité | Limité | Custom |
| Écosystème | 100+ serveurs | N/A | 500+ outils | N/A |
| Complexité setup | Moyenne | Simple | Simple | Élevée |
Quand utiliser quoi
| Cas d'usage | Solution recommandée | Raison |
|---|---|---|
| App avec un seul LLM (OpenAI) | Function Calling | Plus simple, natif |
| App avec plusieurs LLM | MCP | Standard interopérable |
| Prototype rapide | LangChain Tools | Setup rapide, large écosystème |
| IDE / Desktop app | MCP | Écosystème de serveurs prêts |
| Pipeline RAG complexe | MCP + LangChain | Combinaison des forces |
| API publique | Custom API + MCP wrapper | Flexibilité maximale |
Migration de Function Calling vers MCP
DEVELOPERpython# AVANT: OpenAI Function Calling tools = [ { "type": "function", "function": { "name": "search_knowledge_base", "description": "Search the knowledge base", "parameters": { "type": "object", "properties": { "query": {"type": "string"} }, "required": ["query"] } } } ] response = openai.chat.completions.create( model="gpt-4o", messages=messages, tools=tools, ) # APRÈS: MCP (réutilisable par tout client MCP) @server.list_tools() async def list_tools(): return [Tool( name="search_knowledge_base", description="Search the knowledge base", inputSchema={ "type": "object", "properties": { "query": {"type": "string"} }, "required": ["query"] } )] # Même logique, mais accessible à Claude Desktop, # Cursor, VS Code, et tout client MCP
Cas d'usage avancés
MCP + RAG agentique
Combiner MCP avec un agent RAG pour des workflows complexes :
DEVELOPERpython# Agent RAG avec accès à plusieurs serveurs MCP class RAGAgent: def __init__(self): self.mcp_clients = { "rag": MCPClient("rag-server"), "github": MCPClient("github-server"), "slack": MCPClient("slack-server"), } async def process_query(self, query: str) -> str: # 1. Chercher dans la base RAG docs = await self.mcp_clients["rag"].call_tool( "search_knowledge_base", {"query": query, "top_k": 5} ) # 2. Si besoin, chercher du code sur GitHub if "code" in query.lower(): code = await self.mcp_clients["github"].call_tool( "search_code", {"query": query, "repo": "my-org/my-repo"} ) docs += f"\n\nCode trouvé:\n{code}" # 3. Générer la réponse response = await self.generate(query, docs) # 4. Optionnel: poster sur Slack if self.should_notify(query): await self.mcp_clients["slack"].call_tool( "post_message", { "channel": "#support", "text": f"Question traitée: {query[:100]}..." } ) return response
MCP pour l'enrichissement de données RAG
DEVELOPERpython# Serveur MCP qui enrichit les données avant indexation @mcp.tool() async def enrich_and_index(url: str) -> str: """Récupère, enrichit et indexe un document web.""" # 1. Récupérer le contenu content = await fetch_url(url) # 2. Extraire les métadonnées metadata = { "url": url, "title": extract_title(content), "date": extract_date(content), "language": detect_language(content), "summary": await generate_summary(content), } # 3. Découper et indexer chunks = chunk_document(content, chunk_size=500) doc_id = await index_chunks(chunks, metadata) return f"Indexé: {metadata['title']} ({len(chunks)} chunks)"
Sécurité et bonnes pratiques
Principes de sécurité MCP
| Principe | Description | Implémentation |
|---|---|---|
| Moindre privilège | Chaque serveur a accès uniquement à ce dont il a besoin | Permissions granulaires par outil |
| Consentement utilisateur | L'utilisateur approuve les actions sensibles | Confirmation avant écriture/envoi |
| Isolation | Les serveurs sont isolés les uns des autres | Processus séparés, pas de mémoire partagée |
| Audit | Toutes les actions sont loguées | Logging de chaque appel d'outil |
| Validation | Les entrées sont validées côté serveur | Schema JSON + validation custom |
Checklist de sécurité
DEVELOPERpython# Validation des entrées dans un serveur MCP @server.call_tool() async def call_tool(name: str, arguments: dict): # 1. Valider le schéma des arguments validate_schema(name, arguments) # 2. Vérifier les permissions if name in WRITE_TOOLS and not user_has_write_permission(): raise PermissionError("Écriture non autorisée") # 3. Rate limiting if not rate_limiter.check(name): raise RateLimitError("Trop de requêtes") # 4. Sanitizer les entrées sanitized = sanitize_inputs(arguments) # 5. Logger l'action audit_log(name, sanitized, user_id) # 6. Exécuter return await execute_tool(name, sanitized)
Timeline d'adoption MCP
Nov. 2024 : Anthropic lance MCP (open source)
Q1 2025 : Claude Desktop supporte MCP nativement ; OpenAI adopte MCP (Agents SDK, ChatGPT desktop)
Q2 2025 : Google DeepMind adopte MCP pour Gemini ; Cursor, Windsurf, Continue.dev, VS Code intègrent MCP
Nov. 2025 : Spécification 2025-11-25 (autorisation OAuth, elicitation)
Déc. 2025 : MCP confié à l'Agentic AI Foundation (Linux Foundation), co-fondée par Anthropic, Block et OpenAI
2026 : Adoption enterprise (Salesforce) ; révision majeure vers un cœur « stateless » (extensions MCP Apps et Tasks)
Pour aller plus loin
- RAG agents et orchestration : le guide parent sur les agents
- Small Language Models pour le RAG : combiner SLM et MCP
- Observabilité RAG : monitoring des appels MCP
- Guardrails RAG : sécuriser les outils MCP
- RAG et Notion : exemple d'intégration MCP + RAG
FAQ
MCP remplace-t-il le function calling d'OpenAI ?
Non, MCP et le function calling résolvent des problèmes différents mais complémentaires. Le function calling est spécifique à un provider (OpenAI, Anthropic) et définit comment le LLM appelle une fonction. MCP est un standard de communication entre le client et les serveurs d'outils, indépendant du LLM. Vous pouvez utiliser MCP avec du function calling en dessous. L'avantage de MCP est l'interopérabilité : un serveur MCP fonctionne avec tous les clients compatibles.
Puis-je utiliser MCP avec GPT-4o ou d'autres LLM non-Anthropic ?
Oui. MCP est un protocole ouvert, pas une fonctionnalité exclusive à Claude. Des clients comme Cursor et Continue.dev utilisent MCP avec GPT-4o, Gemini et d'autres modèles. L'implémentation côté client traduit les outils MCP en function calls natifs du LLM utilisé. Cela dit, l'intégration est la plus naturelle avec Claude Desktop.
Combien de temps faut-il pour construire un serveur MCP ?
Un serveur MCP simple (2-3 outils) peut être construit en 1-2 heures avec FastMCP ou le SDK Python officiel. Un serveur plus complexe avec resources, prompts et gestion d'erreurs avancée prend 1-3 jours. La courbe d'apprentissage est modérée si vous êtes familier avec les API asynchrones Python.
MCP est-il sécurisé pour une utilisation en production ?
MCP intègre des mécanismes de sécurité : isolation des processus, validation des schémas, et le principe du moindre privilège. Cependant, la sécurité dépend largement de votre implémentation côté serveur. Validez toujours les entrées, implémentez le rate limiting, et loguez toutes les actions. Pour les données sensibles, exécutez les serveurs MCP dans un environnement isolé (Docker, VM).
Ailog supporte-t-il le protocole MCP ?
Ailog travaille activement sur l'intégration MCP. L'API RAG d'Ailog peut déjà être exposée comme serveur MCP, permettant à Claude Desktop, Cursor ou tout client compatible d'interroger votre base de connaissances Ailog directement. Cela ouvre la porte à des workflows où votre assistant IA peut chercher dans vos données Ailog sans quitter son interface habituelle.
Tags
Articles connexes
Agents RAG : Orchestrer des systemes multi-agents
Architecturez des systemes RAG multi-agents : orchestration, specialisation, collaboration et gestion des echecs pour des assistants complexes.
Agentic RAG 2025 : Construire des Agents IA Autonomes (Guide Complet)
Guide complet Agentic RAG : architecture, design patterns, agents autonomes avec retrieval dynamique, orchestration multi-outils. Avec exemples LangGraph et CrewAI.
Function calling : RAG avec actions
Guide complet pour combiner RAG et function calling : agents qui recherchent ET agissent, integration d'APIs externes, actions automatisees et workflows interactifs.