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
# 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
Install MCP SDK
# Install the MCP Python SDK
pip install mcp
# Or for Node.js
npm install @modelcontextprotocol/sdkDefine Your Server
# 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()
Configure Server
Create a configuration file for your MCP server:
{
"mcpServers": {
"my-database": {
"command": "python",
"args": ["server.py"],
"env": {
"DATABASE_PATH": "./users.db"
}
}
}
}Test Your Server
# 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.
# 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.
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_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 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.