GuideAdvanced

MCP (Model Context Protocol): The Standard Connecting AI to All Your Tools

July 29, 2026
22 min read
Ailog Team

Complete guide to Anthropic's Model Context Protocol (MCP): client/server architecture, building an MCP server for RAG, comparison with function calling and LangChain tools.

TL;DR

The Model Context Protocol (MCP) is an open standard created by Anthropic that enables LLMs to connect to any tool or data source through a unified interface. In 2026, MCP is adopted by Claude Desktop, Cursor, Windsurf, VS Code, and many other clients. This guide explains MCP architecture, shows how to build an MCP server for RAG, and compares MCP with alternatives (function calling, LangChain tools).

What Is MCP?

The Problem MCP Solves

Before MCP, connecting an LLM to tools required a custom integration for each LLM + tool combination:

BEFORE MCP (N x M integrations):
┌──────────┐     ┌──────────┐
│ Claude   │────→│ Slack    │  Custom integration 1
│ Claude   │────→│ GitHub   │  Custom integration 2
│ GPT-4    │────→│ Slack    │  Custom integration 3
│ GPT-4    │────→│ GitHub   │  Custom integration 4
└──────────┘     └──────────┘
= 4 integrations for 2 LLMs x 2 tools

WITH MCP (N + M integrations):
┌──────────┐     ┌──────────┐     ┌──────────┐
│ Claude   │────→│   MCP    │←────│ Slack    │  1 MCP server
│ GPT-4    │────→│ Protocol │←────│ GitHub   │  1 MCP server
└──────────┘     └──────────┘     └──────────┘
= 2 clients + 2 servers = 4 components (vs 4 integrations)

MCP Architecture

┌─────────────────────────────────────────────────────┐
│                    MCP CLIENT                        │
│  (Claude Desktop, Cursor, VS Code, your app)        │
├─────────────────────────────────────────────────────┤
│                                                      │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐          │
│  │ Server 1 │  │ Server 2 │  │ Server 3 │          │
│  │ (GitHub) │  │  (RAG)   │  │ (Slack)  │          │
│  └──────────┘  └──────────┘  └──────────┘          │
│       │              │              │                │
│       ▼              ▼              ▼                │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐          │
│  │ Resources│  │  Tools   │  │ Prompts  │          │
│  │ (repos)  │  │ (search) │  │(templates)│          │
│  └──────────┘  └──────────┘  └──────────┘          │
│                                                      │
└─────────────────────────────────────────────────────┘

The 3 MCP Primitives

PrimitiveDescriptionExampleControlled by
ResourcesData exposed to the LLM (read)Files, DBs, API responsesApplication (client)
ToolsActions the LLM can executeSearch, write, sendModel (LLM)
PromptsReusable templatesCode analysis, summaryUser

The MCP Ecosystem in 2026

Client Adoption

ClientMCP SupportStatusNotes
Claude DesktopNativeProductionFirst MCP client
CursorNativeProductionAI IDE with integrated MCP
WindsurfNativeProductionCompeting AI IDE
VS Code (Copilot)PluginProductionVia MCP extension
Continue.devNativeProductionOpen source IDE
ZedNativeBetaFast editor
Claude CodeNativeProductionCLI with MCP
ClineNativeProductionVS Code agent

Popular Community MCP Servers

ServerFunction
mcp-server-githubRepos, issues, PRs
mcp-server-filesystemFile read/write
mcp-server-postgresPostgreSQL queries
mcp-server-slackMessages, channels
mcp-server-google-driveGoogle Drive files
mcp-server-notionNotion pages and databases
mcp-server-puppeteerWeb browsing
mcp-server-memoryPersistent memory
mcp-server-brave-searchWeb search
mcp-server-qdrantVector database

Building an MCP Server for RAG

RAG MCP Server in Python

DEVELOPERpython
from mcp.server import Server, NotificationOptions from mcp.server.models import InitializationOptions from mcp.types import ( Resource, Tool, TextContent, ) import mcp.server.stdio import json # Create the MCP server server = Server("rag-server") # ============ RESOURCES ============ @server.list_resources() async def list_resources() -> list[Resource]: """List available data sources.""" return [ Resource( uri="rag://knowledge-base/status", name="Knowledge base status", description="Statistics about indexed documents", mimeType="application/json", ), Resource( uri="rag://knowledge-base/sources", name="Indexed sources", description="List of active data sources", 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"Unknown resource: {uri}") # ============ TOOLS ============ @server.list_tools() async def list_tools() -> list[Tool]: """List available RAG tools.""" return [ Tool( name="search_knowledge_base", description=( "Search the knowledge base. " "Uses semantic search to find " "the most relevant documents." ), inputSchema={ "type": "object", "properties": { "query": { "type": "string", "description": "The question or topic to search" }, "top_k": { "type": "integer", "description": "Number of results (default: 5)", "default": 5 }, "filter_source": { "type": "string", "description": "Filter by source (optional)" } }, "required": ["query"] } ), Tool( name="add_document", description=( "Add a document to the knowledge base. " "The document will be chunked and indexed automatically." ), inputSchema={ "type": "object", "properties": { "content": { "type": "string", "description": "The document content" }, "title": { "type": "string", "description": "The document title" }, "source": { "type": "string", "description": "The document source" } }, "required": ["content", "title"] } ), Tool( name="get_answer", description=( "Ask a question and get an answer " "based on the knowledge base (full RAG)." ), inputSchema={ "type": "object", "properties": { "question": { "type": "string", "description": "The question to ask" }, "context": { "type": "string", "description": "Additional context (optional)" } }, "required": ["question"] } ), ] @server.call_tool() async def call_tool(name: str, arguments: dict) -> list[TextContent]: """Execute a RAG tool.""" 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"[Result {i+1}] (score: {r.score:.2f})\n" f"Source: {r.metadata.get('source', 'N/A')}\n" f"Content: {r.text}\n" ) return [TextContent( type="text", text="\n---\n".join(formatted) or "No results found." )] 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 added successfully (ID: {doc_id})" )] elif name == "get_answer": answer = await rag_pipeline( question=arguments["question"], context=arguments.get("context", ""), ) return [TextContent( type="text", text=f"Answer: {answer.text}\n\n" f"Sources: {', '.join(answer.sources)}" )] raise ValueError(f"Unknown tool: {name}") # ============ 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 in 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_..." } } } }

MCP Server with FastMCP (Simplified)

DEVELOPERpython
from fastmcp import FastMCP mcp = FastMCP("RAG Assistant") @mcp.tool() async def search_docs(query: str, top_k: int = 5) -> str: """Search the RAG knowledge base.""" 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: """Ask a question to the RAG system.""" answer = await rag_pipeline(question) return f"{answer.text}\n\nSources: {answer.sources}" @mcp.resource("rag://stats") async def get_stats() -> str: """Knowledge base statistics.""" stats = await get_kb_stats() return f"{stats.total_docs} documents, {stats.total_chunks} chunks" if __name__ == "__main__": mcp.run()

MCP vs Alternatives

Comparison Table

CriterionMCPOpenAI Function CallingLangChain ToolsCustom API
Open standardYes (Anthropic)No (proprietary)No (framework)No
InteroperabilityAny MCP clientOpenAI onlyLangChain onlyCustom
ArchitectureClient/ServerRequest/ResponseChainREST/GraphQL
DiscoveryAutomatic (list_tools)JSON SchemaDeclarativeDocumentation
StreamingYes (SSE)YesYesPossible
StatefulYes (session)NoYes (memory)Possible
Resources (data)Yes (native primitive)NoNoCustom
Prompt templatesYes (native primitive)NoYes (PromptTemplate)No
SecurityGranular controlLimitedLimitedCustom
Ecosystem100+ serversN/A500+ toolsN/A
Setup complexityMediumSimpleSimpleHigh

When to Use What

Use CaseRecommended SolutionReason
Single LLM app (OpenAI)Function CallingSimpler, native
Multi-LLM appMCPInteroperable standard
Quick prototypeLangChain ToolsQuick setup, large ecosystem
IDE / Desktop appMCPReady-made server ecosystem
Complex RAG pipelineMCP + LangChainCombining strengths
Public APICustom API + MCP wrapperMaximum flexibility

Migration from Function Calling to MCP

DEVELOPERpython
# BEFORE: 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, ) # AFTER: MCP (reusable by any MCP client) @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"] } )] # Same logic, but accessible to Claude Desktop, # Cursor, VS Code, and any MCP client

Advanced Use Cases

MCP + Agentic RAG

Combine MCP with a RAG agent for complex workflows:

DEVELOPERpython
# RAG Agent with access to multiple MCP servers 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. Search the RAG knowledge base docs = await self.mcp_clients["rag"].call_tool( "search_knowledge_base", {"query": query, "top_k": 5} ) # 2. If needed, search code on 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 found:\n{code}" # 3. Generate the response response = await self.generate(query, docs) # 4. Optional: post to Slack if self.should_notify(query): await self.mcp_clients["slack"].call_tool( "post_message", { "channel": "#support", "text": f"Query handled: {query[:100]}..." } ) return response

MCP for RAG Data Enrichment

DEVELOPERpython
# MCP server that enriches data before indexing @mcp.tool() async def enrich_and_index(url: str) -> str: """Fetch, enrich, and index a web document.""" # 1. Fetch the content content = await fetch_url(url) # 2. Extract metadata metadata = { "url": url, "title": extract_title(content), "date": extract_date(content), "language": detect_language(content), "summary": await generate_summary(content), } # 3. Chunk and index chunks = chunk_document(content, chunk_size=500) doc_id = await index_chunks(chunks, metadata) return f"Indexed: {metadata['title']} ({len(chunks)} chunks)"

Security and Best Practices

MCP Security Principles

PrincipleDescriptionImplementation
Least privilegeEach server accesses only what it needsGranular permissions per tool
User consentUser approves sensitive actionsConfirmation before write/send
IsolationServers are isolated from each otherSeparate processes, no shared memory
AuditAll actions are loggedLogging every tool call
ValidationInputs are validated server-sideJSON schema + custom validation

Security Checklist

DEVELOPERpython
# Input validation in an MCP server @server.call_tool() async def call_tool(name: str, arguments: dict): # 1. Validate argument schema validate_schema(name, arguments) # 2. Check permissions if name in WRITE_TOOLS and not user_has_write_permission(): raise PermissionError("Write not authorized") # 3. Rate limiting if not rate_limiter.check(name): raise RateLimitError("Too many requests") # 4. Sanitize inputs sanitized = sanitize_inputs(arguments) # 5. Log the action audit_log(name, sanitized, user_id) # 6. Execute return await execute_tool(name, sanitized)

MCP Adoption Timeline

Nov 2024: Anthropic launches MCP (open source)
Q1 2025: Claude Desktop supports MCP natively; OpenAI adopts MCP (Agents SDK, ChatGPT desktop)
Q2 2025: Google DeepMind adopts MCP for Gemini; Cursor, Windsurf, Continue.dev, VS Code integrate MCP
Nov 2025: 2025-11-25 specification (OAuth authorization, elicitation)
Dec 2025: MCP donated to the Agentic AI Foundation (Linux Foundation), co-founded by Anthropic, Block, and OpenAI
2026: Enterprise adoption (Salesforce); major revision toward a stateless core (MCP Apps and Tasks extensions)

Going Further

FAQ

Does MCP replace OpenAI's function calling?

No, MCP and function calling solve different but complementary problems. Function calling is provider-specific (OpenAI, Anthropic) and defines how the LLM calls a function. MCP is a communication standard between the client and tool servers, independent of the LLM. You can use MCP with function calling underneath. MCP's advantage is interoperability: one MCP server works with all compatible clients.

Can I use MCP with GPT-4o or other non-Anthropic LLMs?

Yes. MCP is an open protocol, not a Claude-exclusive feature. Clients like Cursor and Continue.dev use MCP with GPT-4o, Gemini, and other models. The client-side implementation translates MCP tools into native function calls for the LLM being used. That said, the integration is most natural with Claude Desktop.

How long does it take to build an MCP server?

A simple MCP server (2-3 tools) can be built in 1-2 hours with FastMCP or the official Python SDK. A more complex server with resources, prompts, and advanced error handling takes 1-3 days. The learning curve is moderate if you are familiar with Python async APIs.

Is MCP secure for production use?

MCP includes security mechanisms: process isolation, schema validation, and the principle of least privilege. However, security largely depends on your server-side implementation. Always validate inputs, implement rate limiting, and log all actions. For sensitive data, run MCP servers in an isolated environment (Docker, VM).

Does Ailog support the MCP protocol?

Ailog is actively working on MCP integration. Ailog's RAG API can already be exposed as an MCP server, allowing Claude Desktop, Cursor, or any compatible client to query your Ailog knowledge base directly. This opens the door to workflows where your AI assistant can search your Ailog data without leaving its usual interface.

Tags

RAGMCPModel Context ProtocolAnthropictoolsagentsintegrationAPI

Related Posts

Ailog Assistant

Ici pour vous aider

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