MCP (Model Context Protocol): The Standard Connecting AI to All Your Tools
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
| Primitive | Description | Example | Controlled by |
|---|---|---|---|
| Resources | Data exposed to the LLM (read) | Files, DBs, API responses | Application (client) |
| Tools | Actions the LLM can execute | Search, write, send | Model (LLM) |
| Prompts | Reusable templates | Code analysis, summary | User |
The MCP Ecosystem in 2026
Client Adoption
| Client | MCP Support | Status | Notes |
|---|---|---|---|
| Claude Desktop | Native | Production | First MCP client |
| Cursor | Native | Production | AI IDE with integrated MCP |
| Windsurf | Native | Production | Competing AI IDE |
| VS Code (Copilot) | Plugin | Production | Via MCP extension |
| Continue.dev | Native | Production | Open source IDE |
| Zed | Native | Beta | Fast editor |
| Claude Code | Native | Production | CLI with MCP |
| Cline | Native | Production | VS Code agent |
Popular Community MCP Servers
| Server | Function |
|---|---|
| mcp-server-github | Repos, issues, PRs |
| mcp-server-filesystem | File read/write |
| mcp-server-postgres | PostgreSQL queries |
| mcp-server-slack | Messages, channels |
| mcp-server-google-drive | Google Drive files |
| mcp-server-notion | Notion pages and databases |
| mcp-server-puppeteer | Web browsing |
| mcp-server-memory | Persistent memory |
| mcp-server-brave-search | Web search |
| mcp-server-qdrant | Vector database |
Building an MCP Server for RAG
RAG MCP Server in Python
DEVELOPERpythonfrom 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)
DEVELOPERpythonfrom 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
| Criterion | MCP | OpenAI Function Calling | LangChain Tools | Custom API |
|---|---|---|---|---|
| Open standard | Yes (Anthropic) | No (proprietary) | No (framework) | No |
| Interoperability | Any MCP client | OpenAI only | LangChain only | Custom |
| Architecture | Client/Server | Request/Response | Chain | REST/GraphQL |
| Discovery | Automatic (list_tools) | JSON Schema | Declarative | Documentation |
| Streaming | Yes (SSE) | Yes | Yes | Possible |
| Stateful | Yes (session) | No | Yes (memory) | Possible |
| Resources (data) | Yes (native primitive) | No | No | Custom |
| Prompt templates | Yes (native primitive) | No | Yes (PromptTemplate) | No |
| Security | Granular control | Limited | Limited | Custom |
| Ecosystem | 100+ servers | N/A | 500+ tools | N/A |
| Setup complexity | Medium | Simple | Simple | High |
When to Use What
| Use Case | Recommended Solution | Reason |
|---|---|---|
| Single LLM app (OpenAI) | Function Calling | Simpler, native |
| Multi-LLM app | MCP | Interoperable standard |
| Quick prototype | LangChain Tools | Quick setup, large ecosystem |
| IDE / Desktop app | MCP | Ready-made server ecosystem |
| Complex RAG pipeline | MCP + LangChain | Combining strengths |
| Public API | Custom API + MCP wrapper | Maximum 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
| Principle | Description | Implementation |
|---|---|---|
| Least privilege | Each server accesses only what it needs | Granular permissions per tool |
| User consent | User approves sensitive actions | Confirmation before write/send |
| Isolation | Servers are isolated from each other | Separate processes, no shared memory |
| Audit | All actions are logged | Logging every tool call |
| Validation | Inputs are validated server-side | JSON 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
- RAG Agents and Orchestration: the parent guide on agents
- Small Language Models for RAG: combining SLMs and MCP
- RAG Observability: monitoring MCP calls
- RAG Guardrails: securing MCP tools
- RAG and Notion: example of MCP + RAG integration
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
Related Posts
RAG Agents: Orchestrating Multi-Agent Systems
Architect multi-agent RAG systems: orchestration, specialization, collaboration and failure handling for complex assistants.
Agentic RAG: Building AI Agents with Dynamic Knowledge Retrieval
Comprehensive guide to Agentic RAG: architecture, design patterns, implementing autonomous agents with knowledge retrieval, multi-tool orchestration, and advanced use cases.
Conversational RAG: Memory and Multi-Session Context
Implement RAG with conversational memory: context management, multi-session history, and personalized responses.