Back to Resources
Architecture

Model Context Protocol (MCP): Complete Guide [2025]

Learn how Anthropic's Model Context Protocol enables AI systems to securely connect to data sources and tools. Complete guide with implementation examples and best practices.

May 21, 2025
16 min read
MCPModel Context ProtocolAI IntegrationAnthropicClaude

Model Context Protocol (MCP) is an open standard developed by Anthropic that enables AI systems to securely connect to data sources and tools. Think of it as a universal adapter that lets AI models access your databases, APIs, file systems, and services in a standardized way—without custom integrations for each tool.

What You'll Learn

  • What MCP is and why it matters for AI development
  • Core MCP concepts: servers, clients, resources, and tools
  • How to build and deploy MCP servers
  • Integrating MCP with Claude, LangChain, and custom applications
  • Security best practices and production considerations
  • Real-world use cases and implementation examples

What is Model Context Protocol?

MCP is a standardized protocol that defines how AI applications communicate with external data sources and tools. Instead of building custom integrations for every database, API, or service, you implement MCP once and any MCP-compatible AI system can use it.

The Problem MCP Solves

❌ Before MCP

  • Custom integration for each AI tool
  • Duplicate code across projects
  • No standardized security model
  • Hard to maintain and update
  • Vendor lock-in

✓ With MCP

  • Write once, use everywhere
  • Standardized protocol
  • Built-in security patterns
  • Easy to maintain
  • Works with any MCP client

Real-World Analogy

Think of MCP like USB-C for AI systems:

  • USB-C: One cable connects to any device (phone, laptop, monitor)
  • MCP: One protocol connects AI to any data source (database, API, file system)

Core MCP Concepts

MCP has four main components that work together to enable AI-data integration.

MCP Server

Exposes data and tools to AI systems

A server that provides access to:

  • Database queries
  • File system operations
  • API endpoints
  • Custom business logic

MCP Client

AI application that consumes MCP servers

Examples:

  • Claude Desktop
  • Custom AI agents
  • LangChain applications
  • IDE extensions

Resources

Data that can be read (files, database records)

resource://database/users/123

resource://files/docs/report.pdf

Read-only data access

Tools

Actions that can be executed (write, update, delete)

create_user(name, email)

send_email(to, subject, body)

Executable operations

Key Distinction: Resources vs Tools

Resources (Read)

For retrieving data without side effects. Like GET requests in REST APIs.

Tools (Write)

For performing actions with side effects. Like POST/PUT/DELETE in REST APIs.

How MCP Works

Understanding the communication flow between MCP clients and servers.

MCP Communication Flow

1. Discovery
Client connects to server and discovers available resources and tools
2. Request
AI decides to use a resource or tool based on user query
3. Execute
Server processes request and returns data or action result
4. Response
Client receives result and AI generates response to user
MCP Communication Example
yaml
# Example MCP Communication Flow

# 1. Client discovers available tools
GET /mcp/tools
Response: [
  {
    "name": "search_database",
    "description": "Search customer database",
    "parameters": {
      "query": "string",
      "limit": "number"
    }
  }
]

# 2. AI decides to use the tool
User: "Find customers in California"
AI: I'll use the search_database tool

# 3. Client calls the tool
POST /mcp/tools/search_database
{
  "query": "state:California",
  "limit": 10
}

# 4. Server executes and returns results
Response: {
  "results": [
    {"name": "John Doe", "email": "john@example.com", "state": "CA"},
    {"name": "Jane Smith", "email": "jane@example.com", "state": "CA"}
  ]
}

# 5. AI uses results to respond
AI: "I found 2 customers in California: John Doe and Jane Smith..."

Building an MCP Server

Let's build a simple MCP server that exposes database access to AI systems.

Create Your First MCP Server

1

Install MCP SDK

# Install the MCP Python SDK
pip install mcp

# Or for Node.js
npm install @modelcontextprotocol/sdk
2

Define Your Server

MCP Server Implementation
python
# server.py - Simple MCP Server
from mcp.server import Server
from mcp.types import Resource, Tool, TextContent
import sqlite3

# Initialize MCP server
server = Server("my-database-server")

# Define a resource (read-only data)
@server.resource("database://users/{user_id}")
async def get_user(user_id: str) -> Resource:
    """Get user information by ID"""
    conn = sqlite3.connect("users.db")
    cursor = conn.cursor()
    
    cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
    user = cursor.fetchone()
    conn.close()
    
    if not user:
        return Resource(
            uri=f"database://users/{user_id}",
            name=f"User {user_id}",
            mimeType="application/json",
            text="User not found"
        )
    
    return Resource(
        uri=f"database://users/{user_id}",
        name=f"User {user[1]}",  # user name
        mimeType="application/json",
        text=f'{{"id": {user[0]}, "name": "{user[1]}", "email": "{user[2]}"}}'
    )

# Define a tool (action with side effects)
@server.tool("create_user")
async def create_user(name: str, email: str) -> list[TextContent]:
    """Create a new user in the database"""
    conn = sqlite3.connect("users.db")
    cursor = conn.cursor()
    
    try:
        cursor.execute(
            "INSERT INTO users (name, email) VALUES (?, ?)",
            (name, email)
        )
        conn.commit()
        user_id = cursor.lastrowid
        
        return [TextContent(
            type="text",
            text=f"Successfully created user {name} with ID {user_id}"
        )]
    except Exception as e:
        return [TextContent(
            type="text",
            text=f"Error creating user: {str(e)}"
        )]
    finally:
        conn.close()

# Define another tool for searching
@server.tool("search_users")
async def search_users(query: str, limit: int = 10) -> list[TextContent]:
    """Search users by name or email"""
    conn = sqlite3.connect("users.db")
    cursor = conn.cursor()
    
    cursor.execute(
        "SELECT * FROM users WHERE name LIKE ? OR email LIKE ? LIMIT ?",
        (f"%{query}%", f"%{query}%", limit)
    )
    users = cursor.fetchall()
    conn.close()
    
    results = [
        {"id": u[0], "name": u[1], "email": u[2]}
        for u in users
    ]
    
    return [TextContent(
        type="text",
        text=f"Found {len(results)} users: {results}"
    )]

if __name__ == "__main__":
    # Run the server
    server.run()
3

Configure Server

Create a configuration file for your MCP server:

mcp-config.json
json
{
  "mcpServers": {
    "my-database": {
      "command": "python",
      "args": ["server.py"],
      "env": {
        "DATABASE_PATH": "./users.db"
      }
    }
  }
}
4

Test Your Server

Testing MCP Server
python
# Test the MCP server
python server.py

# In another terminal, test with MCP client
from mcp.client import Client

client = Client()
await client.connect("stdio", command="python", args=["server.py"])

# List available tools
tools = await client.list_tools()
print(tools)

# Call a tool
result = await client.call_tool("search_users", {"query": "john", "limit": 5})
print(result)

MCP Server Best Practices

  • Clear descriptions: Write detailed descriptions for tools and resources so AI knows when to use them
  • Input validation: Always validate and sanitize inputs before executing
  • Error handling: Return helpful error messages, not stack traces
  • Rate limiting: Implement rate limits to prevent abuse
  • Logging: Log all tool calls for debugging and auditing

MCP Integration Examples

How to use MCP servers with popular AI frameworks and applications.

1. Claude Desktop Integration

Claude Desktop has built-in MCP support. Just add your server to the config file.

Claude Desktop Configuration
json
# On macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
# On Windows: %APPDATA%\Claude\claude_desktop_config.json

{
  "mcpServers": {
    "database": {
      "command": "python",
      "args": ["/path/to/server.py"]
    },
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/files"]
    }
  }
}

After adding the config, restart Claude Desktop. Your MCP tools will appear automatically when relevant to the conversation.

2. LangChain Integration

Use MCP servers as tools in LangChain agents.

LangChain + MCP Integration
python
from langchain.agents import AgentExecutor, create_openai_functions_agent
from langchain_openai import ChatOpenAI
from langchain.tools import Tool
from mcp.client import Client
import asyncio

# Connect to MCP server
mcp_client = Client()
await mcp_client.connect("stdio", command="python", args=["server.py"])

# Get available MCP tools
mcp_tools = await mcp_client.list_tools()

# Convert MCP tools to LangChain tools
langchain_tools = []
for mcp_tool in mcp_tools:
    async def tool_func(**kwargs):
        result = await mcp_client.call_tool(mcp_tool.name, kwargs)
        return result.content[0].text
    
    langchain_tools.append(Tool(
        name=mcp_tool.name,
        description=mcp_tool.description,
        func=tool_func
    ))

# Create LangChain agent with MCP tools
llm = ChatOpenAI(model="gpt-4")
agent = create_openai_functions_agent(llm, langchain_tools)
agent_executor = AgentExecutor(agent=agent, tools=langchain_tools)

# Use the agent
result = agent_executor.invoke({
    "input": "Search for users named John and create a summary"
})
print(result)

3. Custom Application

Build a custom AI application that uses MCP servers.

Custom MCP-Powered AI Application
python
# custom_ai_app.py
from mcp.client import Client
from openai import OpenAI
import json

class MCPAIAssistant:
    def __init__(self, mcp_server_command, mcp_server_args):
        self.mcp_client = Client()
        self.openai_client = OpenAI()
        self.mcp_server_command = mcp_server_command
        self.mcp_server_args = mcp_server_args
        self.tools = []
    
    async def initialize(self):
        """Connect to MCP server and load tools"""
        await self.mcp_client.connect(
            "stdio",
            command=self.mcp_server_command,
            args=self.mcp_server_args
        )
        
        # Get available tools from MCP server
        mcp_tools = await self.mcp_client.list_tools()
        
        # Convert to OpenAI function format
        self.tools = [
            {
                "type": "function",
                "function": {
                    "name": tool.name,
                    "description": tool.description,
                    "parameters": tool.inputSchema
                }
            }
            for tool in mcp_tools
        ]
    
    async def chat(self, user_message: str):
        """Process user message with MCP tool access"""
        messages = [{"role": "user", "content": user_message}]
        
        # Call OpenAI with available tools
        response = self.openai_client.chat.completions.create(
            model="gpt-4",
            messages=messages,
            tools=self.tools
        )
        
        # Check if AI wants to use a tool
        if response.choices[0].message.tool_calls:
            for tool_call in response.choices[0].message.tool_calls:
                # Execute MCP tool
                tool_name = tool_call.function.name
                tool_args = json.loads(tool_call.function.arguments)
                
                result = await self.mcp_client.call_tool(tool_name, tool_args)
                
                # Add tool result to conversation
                messages.append({
                    "role": "function",
                    "name": tool_name,
                    "content": result.content[0].text
                })
            
            # Get final response from AI
            final_response = self.openai_client.chat.completions.create(
                model="gpt-4",
                messages=messages
            )
            return final_response.choices[0].message.content
        
        return response.choices[0].message.content

# Usage
async def main():
    assistant = MCPAIAssistant(
        mcp_server_command="python",
        mcp_server_args=["server.py"]
    )
    await assistant.initialize()
    
    response = await assistant.chat("Find all users with email containing 'gmail'")
    print(response)

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

Security Best Practices

MCP servers can access sensitive data and perform actions. Security is critical.

Security Checklist

  • Input validation: Sanitize all inputs before execution
  • Least privilege: Only expose necessary data and operations
  • Authentication: Verify client identity
  • Authorization: Check permissions for each operation
  • Rate limiting: Prevent abuse and DoS
  • Audit logging: Log all operations for security review
  • Secrets management: Never hardcode credentials
  • Error handling: Don't leak sensitive info in errors

Common Security Risks

SQL Injection

AI-generated queries can contain malicious SQL. Always use parameterized queries.

Path Traversal

File system access can be exploited. Validate and restrict paths.

Command Injection

Never execute shell commands with user input directly.

Data Leakage

AI might expose sensitive data in responses. Implement filtering.

Secure MCP Server
python
# Secure MCP Server Implementation
from mcp.server import Server
from mcp.types import Tool, TextContent
import re
import logging
from functools import wraps

server = Server("secure-server")
logger = logging.getLogger(__name__)

# Authentication decorator
def require_auth(func):
    @wraps(func)
    async def wrapper(*args, **kwargs):
        # Check authentication token
        auth_token = kwargs.get("_auth_token")
        if not verify_token(auth_token):
            return [TextContent(
                type="text",
                text="Authentication failed"
            )]
        return await func(*args, **kwargs)
    return wrapper

# Input validation
def validate_email(email: str) -> bool:
    """Validate email format"""
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return re.match(pattern, email) is not None

def sanitize_sql_input(value: str) -> str:
    """Remove potentially dangerous SQL characters"""
    # Only allow alphanumeric, spaces, and basic punctuation
    return re.sub(r'[^a-zA-Z0-9\s@._-]', '', value)

# Secure tool implementation
@server.tool("create_user")
@require_auth
async def create_user(
    name: str,
    email: str,
    _auth_token: str = None
) -> list[TextContent]:
    """Create a new user (requires authentication)"""
    
    # Input validation
    if not name or len(name) > 100:
        return [TextContent(
            type="text",
            text="Invalid name: must be 1-100 characters"
        )]
    
    if not validate_email(email):
        return [TextContent(
            type="text",
            text="Invalid email format"
        )]
    
    # Sanitize inputs
    name = sanitize_sql_input(name)
    email = sanitize_sql_input(email)
    
    try:
        # Use parameterized query (prevents SQL injection)
        conn = get_db_connection()
        cursor = conn.cursor()
        cursor.execute(
            "INSERT INTO users (name, email) VALUES (?, ?)",
            (name, email)  # Parameters, not string concatenation
        )
        conn.commit()
        user_id = cursor.lastrowid
        
        # Audit log
        logger.info(f"User created: id={user_id}, name={name}, email={email}")
        
        return [TextContent(
            type="text",
            text=f"User created successfully with ID {user_id}"
        )]
        
    except Exception as e:
        # Log error but don't expose details to client
        logger.error(f"Error creating user: {e}")
        return [TextContent(
            type="text",
            text="An error occurred while creating the user"
        )]
    finally:
        conn.close()

# Rate limiting
from collections import defaultdict
from datetime import datetime, timedelta

rate_limits = defaultdict(list)
MAX_REQUESTS_PER_MINUTE = 10

def check_rate_limit(client_id: str) -> bool:
    """Check if client has exceeded rate limit"""
    now = datetime.now()
    minute_ago = now - timedelta(minutes=1)
    
    # Remove old requests
    rate_limits[client_id] = [
        req_time for req_time in rate_limits[client_id]
        if req_time > minute_ago
    ]
    
    # Check limit
    if len(rate_limits[client_id]) >= MAX_REQUESTS_PER_MINUTE:
        return False
    
    # Add current request
    rate_limits[client_id].append(now)
    return True

Security Warning

MCP servers run with the permissions of the process that starts them. This means:

  • They can access any files the user can access
  • They can make network requests
  • They can execute system commands
  • Always run MCP servers with minimal necessary permissions

Real-World Use Cases

Database Access

Let AI query and update your databases naturally.

  • Customer support: Look up orders, update info
  • Analytics: Query metrics and generate reports
  • Data entry: Create records from natural language

File System Operations

AI can read, write, and organize files.

  • Code analysis: Read and understand codebases
  • Documentation: Generate docs from code
  • File organization: Sort and rename files

API Integration

Connect AI to external services and APIs.

  • CRM: Update Salesforce, HubSpot records
  • Communication: Send emails, Slack messages
  • Automation: Trigger workflows, webhooks

Development Tools

Enhance developer workflows with AI.

  • Git operations: Commit, branch, merge
  • Testing: Run tests, analyze results
  • Deployment: Deploy to staging/production

Key Takeaways

  • Universal standard: MCP is like USB-C for AI—one protocol to connect to any data source
  • Two main concepts: Resources (read data) and Tools (perform actions)
  • Easy to build: Create MCP servers with Python or Node.js in minutes
  • Wide compatibility: Works with Claude, LangChain, and custom applications
  • Security first: Always validate inputs, implement auth, and use least privilege
  • Practical use cases: Database access, file operations, API integration, dev tools

Start Building with MCP

MCP makes it easy to connect AI to your data and tools. Start with a simple server and expand from there.

Back to Resources