GuideAvancé

MCP (Model Context Protocol) : Le Standard qui Connecte l'IA à Tous vos Outils

29 juillet 2026
22 min de lecture
Équipe Ailog

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

PrimitiveDescriptionExempleContrôlé par
ResourcesDonnées exposées au LLM (lecture)Fichiers, BDD, API responsesApplication (client)
ToolsActions que le LLM peut exécuterRecherche, écriture, envoiModèle (LLM)
PromptsTemplates réutilisablesAnalyse de code, résuméUtilisateur

L'écosystème MCP en 2026

Adoption par les clients

ClientSupport MCPStatutNotes
Claude DesktopNatifProductionPremier client MCP
CursorNatifProductionIDE AI avec MCP intégré
WindsurfNatifProductionIDE AI concurrent
VS Code (Copilot)PluginProductionVia extension MCP
Continue.devNatifProductionIDE open source
ZedNatifBetaÉditeur rapide
Claude CodeNatifProductionCLI avec MCP
ClineNatifProductionAgent VS Code

Serveurs MCP communautaires populaires

ServeurFonction
mcp-server-githubRepos, issues, PRs
mcp-server-filesystemLecture/écriture fichiers
mcp-server-postgresRequêtes PostgreSQL
mcp-server-slackMessages, canaux
mcp-server-google-driveFichiers Google Drive
mcp-server-notionPages et bases Notion
mcp-server-puppeteerNavigation web
mcp-server-memoryMémoire persistante
mcp-server-brave-searchRecherche web
mcp-server-qdrantBase vectorielle

Construire un serveur MCP pour le RAG

Serveur MCP RAG en Python

DEVELOPERpython
from 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é)

DEVELOPERpython
from 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èreMCPOpenAI Function CallingLangChain ToolsCustom API
Standard ouvertOui (Anthropic)Non (propriétaire)Non (framework)Non
InteropérabilitéTout client MCPOpenAI uniquementLangChain uniquementCustom
ArchitectureClient/ServeurRequest/ResponseChaîneREST/GraphQL
DiscoveryAutomatique (list_tools)Schema JSONDéclaratifDocumentation
StreamingOui (SSE)OuiOuiPossible
StatefulOui (session)NonOui (memory)Possible
Resources (données)Oui (primitif natif)NonNonCustom
Prompts templatesOui (primitif natif)NonOui (PromptTemplate)Non
SécuritéContrôle granulaireLimitéLimitéCustom
Écosystème100+ serveursN/A500+ outilsN/A
Complexité setupMoyenneSimpleSimpleÉlevée

Quand utiliser quoi

Cas d'usageSolution recommandéeRaison
App avec un seul LLM (OpenAI)Function CallingPlus simple, natif
App avec plusieurs LLMMCPStandard interopérable
Prototype rapideLangChain ToolsSetup rapide, large écosystème
IDE / Desktop appMCPÉcosystème de serveurs prêts
Pipeline RAG complexeMCP + LangChainCombinaison des forces
API publiqueCustom API + MCP wrapperFlexibilité 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

PrincipeDescriptionImplémentation
Moindre privilègeChaque serveur a accès uniquement à ce dont il a besoinPermissions granulaires par outil
Consentement utilisateurL'utilisateur approuve les actions sensiblesConfirmation avant écriture/envoi
IsolationLes serveurs sont isolés les uns des autresProcessus séparés, pas de mémoire partagée
AuditToutes les actions sont loguéesLogging de chaque appel d'outil
ValidationLes entrées sont validées côté serveurSchema 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

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

RAGMCPModel Context ProtocolAnthropicoutilsagentsintégrationAPI

Articles connexes

Ailog Assistant

Ici pour vous aider

Salut ! Pose-moi des questions sur Ailog et comment intégrer votre RAG dans vos projets !