Skip to content

MODEL
CONTEXT
PROTOCOL

The revolutionary standard connecting Large Language Models with external tools and data sources through secure, standardized interfaces

Model Context Protocol (MCP) is an open standard for connecting tools, data, and applications to Large Language Models (LLMs).

🤖
LLM Client
Claude, GPT, or any AI assistant
→
🔗
MCP Protocol
Standardized communication layer
→
🖥️
MCP Server
Tool or resource provider
LLM Client: The AI assistant that needs access to external tools and data

What is MCP ?

Technical Architecture

MCP is a JSON-RPC 2.0 based protocol that establishes standardized communication between Large Language Models and external capabilities through a client-server architecture.

• Transport Layer: Bi-directional communication over stdio & Streamable HTTP (replaces older HTTP+SSE)
• Message Format: JSON-RPC 2.0 with structured request/response patterns
• Capability Discovery: Dynamic protocol negotiation and feature enumeration
• Security Model: Extensible framework with recommended security patterns

Protocol Specification

MCP defines core abstraction primitives organized by side:
Server-side primitives (3):
• Tools: Callable functions with JSON Schema parameter validation
• Resources: Addressable data sources with URI-based identification
• Prompts: Reusable templates with parameterization support

Client-side primitives (3):
• Sampling: Request delegation for LLM inference with context injection
• Elicitation: Interactive prompting for gathering user information
• Logging: Message reporting for client-side visibility and debugging

Protocol & Implementation Patterns

• Protocol Version: Client and server operate according to a protocol revision; for HTTP, client sends MCP-Protocol-Version header (e.g., "2025-06-18")
• Error Handling: JSON-RPC 2.0 error responses with structured error codes
• State Management: Stateless design with optional server-side session persistence
• Resource Lifecycle: Dynamic resource subscription and change notifications
• HTTP Authentication: OAuth 2.1 / Bearer tokens (spec-defined for HTTP transport)
• Rate Limiting: Server implementation pattern (not protocol-enforced)

Practical Workflow Example

Here's what happens when an AI assistant uses your MCP server:

🔍
1. Discovery
Request: AI: "What tools are available?"
Response: Server: "I have query_database, analyze_data, create_report"
⚙️
2. Tool Execution
Request: AI: "Run query_database with SELECT * FROM users"
Response: Server: Returns user data as JSON
📊
3. Follow-up
Request: AI: "Now analyze_data with those results"
Response: Server: Returns statistical analysis

Why MCP ?

Anthropic's Original Challenge

As Claude and other LLMs became more sophisticated, the demand for AI agents capable of performing real-world tasks exploded. Users wanted AI assistants that could interact with their tools, databases, APIs, and workflows—not just generate text.

The Problem: Every integration required custom, one-off development. Companies building Claude integrations for Slack, GitHub, databases, or internal tools had to create bespoke solutions. This created a fragmented ecosystem where:

• Each tool integration was vendor-specific and non-portable
• Security models were inconsistent across integrations
• Developers couldn't reuse their work across different LLM platforms
• Enterprise adoption was hindered by security and standardization concerns

The Agent Revolution Problem

AI Agents are the future, but they need standardized ways to interact with the world:

• Autonomous Task Execution: Agents need to call APIs, query databases, and manipulate files
• Enterprise Integration: Companies require secure, auditable connections to internal systems
• Developer Productivity: Building agent capabilities should be write-once, use-everywhere
• Security & Compliance: Enterprise-grade security with fine-grained access controls
• Ecosystem Growth: A marketplace of reusable agent capabilities

What MCP Fundamentally Solves

Before MCP

• Fragmented Integrations: Each LLM provider has custom APIs
• Vendor Lock-in: Tools built for Claude don't work with GPT-4
• Security Nightmares: Inconsistent auth and permission models
• Development Waste: Rebuilding the same integrations repeatedly
• Enterprise Hesitation: No standardized security or compliance

With MCP

• Universal Protocol: One server works with any MCP-compatible client
• Platform Agnostic: Build once, deploy to Claude, GPT, VS Code, etc.
• Enterprise Security: OAuth 2.1 transport auth with recommended best practices
• Developer Efficiency: Reusable components and standardized patterns
• Ecosystem Growth: Community-driven marketplace of capabilities

Real-World Agent Scenarios

DevOps Agent

An AI agent that monitors logs, deploys code, manages infrastructure, and responds to incidents—all through standardized MCP servers.

Analytics Agent

Query databases, generate reports, create visualizations, and send insights to stakeholders using consistent MCP interfaces.

Support Agent

Access customer data, update tickets, trigger workflows, and coordinate across multiple enterprise systems seamlessly.

Anthropic's Strategic Vision

By creating MCP, Anthropic is democratizing AI agent development and fostering an open ecosystem. Rather than creating a walled garden around Claude, they're establishing an industry standard that benefits everyone:

• Accelerated Innovation: Developers can focus on capabilities, not integration boilerplate
• Enterprise Adoption: Standardized security and compliance accelerate enterprise AI adoption
• Community Growth: A thriving ecosystem of MCP servers and tools
• Future-Proofing: As AI capabilities evolve, the infrastructure remains consistent

How MCP Works

MCP Architecture Overview

MCP operates on a simple but powerful client-server architecture with standardized communication protocols.

🤖

MCP Client

Claude, GPT-4, VS Code, etc.

• Initiates requests
• Consumes capabilities
• Manages conversations
⟷
JSON-RPC 2.0
stdio & Streamable HTTP
🖥️

MCP Server

Capability Provider

• Exposes tools/resources
• Handles authentication
• Processes requests
→
Integrates
🔗

External Systems

APIs, DBs, Files, Services

• Databases (SQL, NoSQL)
• REST/GraphQL APIs
• File systems, Cloud services

Core MCP Primitives

MCP defines fundamental primitives that enable rich interactions between clients and servers, organized by implementation side:

🔧

Tools (Server-side)

Callable functions with JSON Schema validation. Tools are actions the LLM can invoke to perform tasks.

Examples: query_database(), send_email(), create_file()
📄

Resources (Server-side)

Addressable data sources with URI-based identification. Resources provide read access to structured information.

Examples: file://docs/api.md, db://users/table
💬

Prompts (Server-side)

Reusable templates with parameterization support. Prompts provide structured context and instructions.

Examples: Code review templates, analysis frameworks
🎯

Sampling (Client-side)

Request delegation for LLM inference. Servers can request the client to perform specific reasoning tasks.

Examples: Classification, summarization, decision making
💭

Elicitation (Client-side)

Interactive prompting for gathering information from users. Servers can request the client to collect specific input or choices.

Examples: User confirmations, data input forms, preference selection
📊

Logging (Client-side)

Message reporting for client-side visibility. Servers can send log messages for debugging, monitoring, and user feedback.

Examples: Progress updates, debug information, error notifications

Communication Flow

Here's how a typical MCP interaction works in practice:

Step-by-Step Flow

🔍
1. Discovery
Client connects and discovers available capabilities
🔐
2. Authentication
Server validates client permissions and access rights
📤
3. Request
Client sends JSON-RPC request for specific tool/resource
⚙️
4. Processing
Server processes request and interacts with external systems
📥
5. Response
Server returns structured JSON-RPC response with results

Transport Options

stdio

Direct process communication via stdin/stdout. Ideal for local development and simple integrations.

Streamable HTTP

HTTP POST for client→server requests, with optional Server-Sent Events for server→client streaming. Browser-compatible and firewall-friendly.

Future Transports

Additional transport protocols may be considered in future revisions (no official WebSocket specification confirmed at this time).

Security & Error Handling

Security Best Practices

• Transport Authentication: OAuth 2.1 / Bearer tokens (HTTP transport)
• Server-side Patterns: Capability-based access control (recommended)
• Input Validation: JSON Schema parameter validation (provided by Tool definitions on server-side, not enforced by protocol)
• Execution Isolation: Sandboxed environments (recommended, not imposed by spec)
• Audit Logging: Request/response tracking (recommended, not imposed by spec)

Error Handling

• Structured error responses: Consistent error format
• JSON-RPC error codes: Structured error classification per JSON-RPC 2.0 spec
• Detailed error context: Human-readable descriptions
• Graceful degradation: Fallback mechanisms
• Retry policies: Recommended resilience patterns

Setup & Test Your First MCP Server

From installation to testing — get your MCP server running and connected to Claude Desktop in minutes

Claude Desktop Integration

Here's how to connect any MCP server to Claude Desktop so you can use your tools directly in conversations:

Step-by-Step Setup

📦
1. Install Dependencies
pip install mcp fastmcp
Install the MCP framework and FastMCP for rapid development
🖥️
2. Start Your Server
python production_mcp_server.py
Launch your MCP server in stdio mode (see complete server code in Practical Example below)
⚙️
3. Configure Claude Desktop
Add server to claude_desktop_config.json configuration
Tell Claude Desktop where to find your server and how to connect
✨
4. Restart & Test
Restart Claude Desktop → see your tools appear
Your tools will now appear in Claude conversations automatically

Claude Desktop Configuration

claude_desktop_config.json (Global config)

{
  "mcpServers": {
    "tars-tools": {
      "command": "python",
      "args": ["production_mcp_server.py"],
      "cwd": "/opt/tars-mcp",
      "env": {
        "PG_DSN": "postgresql://user:pass@host/db",
        "MINIO_ENDPOINT": "https://minio.local"
      },
      "permissions": [
        {
          "fs": {
            "read": ["/data/tars"],
            "write": ["/tmp"]
          }
        }
      ]
      // Note: "permissions" is a custom field for this example app
      // Not a standard MCP configuration field
    }
  }
}
What Happens Next
After restarting Claude Desktop, your tools (query_database, analyze_data, create_report) will automatically appear in conversations. Claude can now execute SQL queries, analyze data, and generate reports directly through your MCP server!

Debug with MCP Inspector

MCP Inspector is the official debugging tool that lets you interact directly with your server:

stdio Mode Testing

$ npx @modelcontextprotocol/inspector python production_mcp_server.py
MCP Inspector starting...
Server running on http://localhost:3000
✓ Connected to MCP server
• Automatic Discovery: Inspector lists all available tools and resources
• Interactive Testing: Click tools to test them with custom parameters
• Real-time Results: See JSON responses immediately
• Error Debugging: Clear error messages for troubleshooting

Streamable HTTP Mode Testing

$ python production_mcp_server.py --http
Server listening on port 8000
$ npx @modelcontextprotocol/inspector http://localhost:8000
✓ Connected via Streamable HTTP
• Remote Testing: Test servers running on different machines
• Production Simulation: Test exactly how browser clients connect
• Network Debugging: Monitor HTTP requests and responses
• Auth Testing: Test Bearer token authentication flows

Inspector Workflow Example

1. Launch Inspector
Inspector opens a web interface showing all available tools and resources
2. Test query_database
Click the tool, enter SQL query: SELECT * FROM users WHERE active = 1
3. View Results
See JSON response with user data, verify structure and content
Docker

Quick Deployment with Docker

Containerize your MCP server for easy deployment and distribution

Basic Containerization

Get your MCP server running in Docker with minimal configuration:

Simple Dockerfile

Dockerfile

FROM python:3.11-slim

WORKDIR /app

# Install dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt

# Copy source code
COPY . .

# Create non-root user
RUN useradd --create-home app && chown -R app:app /app
USER app

# Expose port for HTTP mode
EXPOSE 8000

# Default command
CMD ["python", "production_mcp_server.py"]

Quick Commands

# Build your image
docker build -t my-mcp-server .
# Run for Claude Desktop (stdio)
docker run -i my-mcp-server
# Run as HTTP server
docker run -p 8000:8000 my-mcp-server --http
Next Steps
Your server is now containerized! For production deployment with security, monitoring, and scaling, see the detailed Docker section below.

Practical Example

Complete tutorial: Build a production-ready MCP server with database access, file operations, and AI analysis capabilities.

Production MCP Server

Let's build a comprehensive MCP server that provides database access, file operations, and AI-powered analysis tools.

1. Advanced MCP Server Implementation

Two main approaches for MCP server development:

FastMCP 2.0 Framework
High-level framework with decorators and automatic tool registration.
Official MCP SDK
Low-level SDK for full control over server implementation.

production_mcp_server.py

import sqlite3
import json
import os
from typing import List, Dict, Any
from mcp.server.fastmcp import FastMCP
from mcp.types import Resource, Tool, TextContent

app = FastMCP("analytics-server")

# Database connection
DB_PATH = "analytics.db"

@app.tool()
def query_database(
    query: str,
    params: List[Any] = None
) -> Dict[str, Any]:
    """Execute SQL queries with safety checks"""
    # Safety: Only allow SELECT statements
    if not query.strip().upper().startswith('SELECT'):
        raise ValueError("Only SELECT queries are allowed")

    try:
        conn = sqlite3.connect(DB_PATH)
        conn.row_factory = sqlite3.Row
        cursor = conn.cursor()

        if params:
            cursor.execute(query, params)
        else:
            cursor.execute(query)

        results = [dict(row) for row in cursor.fetchall()]
        conn.close()

        return {
            "success": True,
            "data": results,
            "count": len(results)
        }
    except Exception as e:
        return {
            "success": False,
            "error": str(e)
        }

@app.tool()
def analyze_data(
    data: List[Dict[str, Any]],
    analysis_type: str = "summary"
) -> Dict[str, Any]:
    """Perform statistical analysis on data"""
    if not data:
        return {"error": "No data provided"}

    if analysis_type == "summary":
        numeric_fields = []
        for row in data:
            for key, value in row.items():
                if isinstance(value, (int, float)):
                    numeric_fields.append(key)

        summary = {}
        for field in set(numeric_fields):
            values = [row[field] for row in data if field in row]
            if values:
                summary[field] = {
                    "count": len(values),
                    "min": min(values),
                    "max": max(values),
                    "avg": sum(values) / len(values)
                }

        return {
            "type": "summary",
            "total_records": len(data),
            "numeric_analysis": summary
        }

    return {"error": f"Analysis type '{analysis_type}' not supported"}

@app.tool()
def create_report(
    title: str,
    data: Dict[str, Any],
    output_format: str = "json"
) -> Dict[str, Any]:
    """Generate and save analysis reports"""
    report = {
        "title": title,
        "timestamp": "2024-01-15T10:30:00Z",
        "data": data,
        "metadata": {
            "generated_by": "MCP Analytics Server",
            "format": output_format
        }
    }

    filename = f"reports/{title.lower().replace(' ', '_')}.{output_format}"
    os.makedirs("reports", exist_ok=True)

    if output_format == "json":
        with open(filename, 'w') as f:
            json.dump(report, f, indent=2)

    return {
        "success": True,
        "report_path": filename,
        "report_size": os.path.getsize(filename) if os.path.exists(filename) else 0
    }

# Resource: Expose database schema (FastMCP style)
@app.resource("schema://database", mime_type="application/json")
def get_database_schema() -> str:
    """Get database schema information"""
    try:
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()

        # Get all tables
        cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
        tables = [row[0] for row in cursor.fetchall()]

        schema = {}
        for table in tables:
            cursor.execute(f"PRAGMA table_info({table})")
            columns = [
                {
                    "name": row[1],
                    "type": row[2],
                    "nullable": not row[3],
                    "primary_key": bool(row[5])
                }
                for row in cursor.fetchall()
            ]
            schema[table] = columns

        conn.close()
        return json.dumps(schema, indent=2)

    except Exception as e:
        return json.dumps({
            "error": f"Error accessing schema: {str(e)}"
        })

# Initialize database
def init_database():
    """Create sample database with users table"""
    conn = sqlite3.connect(DB_PATH)
    cursor = conn.cursor()

    cursor.execute("""
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY,
            age INTEGER,
            signup_date TEXT,
            last_active TEXT,
            active INTEGER DEFAULT 1
        )
    """)

    # Insert sample data
    cursor.execute("DELETE FROM users")  # Clear existing
    sample_users = [
        (25, '2023-01-15', '2024-01-10', 1),
        (34, '2022-06-20', '2024-01-12', 1),
        (29, '2023-03-10', '2024-01-11', 1)
    ]
    cursor.executemany(
        "INSERT INTO users (age, signup_date, last_active, active) VALUES (?, ?, ?, ?)",
        sample_users
    )

    conn.commit()
    conn.close()

if __name__ == "__main__":
    init_database()
    app.run()

production_mcp_server_sdk.py

import sqlite3
import json
import asyncio
from typing import List, Dict, Any
from mcp.server.lowlevel import Server
from mcp.types import Resource, Tool, TextContent
import mcp.types as types

# Alternative: Official MCP SDK approach
server = Server("analytics-server")

@server.list_tools()
async def list_tools() -> List[Tool]:
    return [
        Tool(
            name="query_database",
            description="Execute SQL queries with safety checks",
            inputSchema={
                "type": "object",
                "properties": {
                    "query": {"type": "string"},
                    "params": {"type": "array", "items": {"type": "string"}}
                },
                "required": ["query"]
            }
        )
    ]

@server.call_tool()
async def call_tool(name: str, arguments: dict[str, Any]):
    if name == "query_database":
        query = arguments.get("query", "")
        params = arguments.get("params", [])

        # Safety check
        if not query.strip().upper().startswith('SELECT'):
            # soit du contenu texte
            return [types.TextContent(type="text", text=json.dumps({"success": False, "error": "Only SELECT queries allowed"}))]

        # Execute query logic here...
        # soit du contenu texte
        return [types.TextContent(type="text", text=json.dumps({"success": True, "data": [], "count": 0}))]
        # ou un dict (structured output) si tu déclares un outputSchema côté tool
        # return {"success": True, "data": [], "count": 0}

    raise ValueError(f"Unknown tool: {name}")

async def main():
    from mcp.server.stdio import stdio_server
    async with stdio_server() as (read_stream, write_stream):
        await server.run(read_stream, write_stream, server.create_initialization_options())

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

2. Detailed Client-Side Integration

Complete client implementation showing connection, discovery, and tool usage:

mcp_client.py

import asyncio
import json
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

class AnalyticsClient:
    def __init__(self):
        self.session = None

    async def connect(self):
        """Establish connection to MCP server"""
        # Correct pattern: use StdioServerParameters and context managers
        server = StdioServerParameters(command="python", args=["production_mcp_server.py"])
        async with stdio_client(server) as (read, write):
            async with ClientSession(read, write) as session:
                await session.initialize()
                self.session = session

        print("Connected to MCP server")

    async def discover_capabilities(self):
        """Discover available tools and resources"""
        # List available tools
        tools_result = await self.session.list_tools()
        print(f"Available tools: {len(tools_result.tools)}")

        for tool in tools_result.tools:
            print(f"  {tool.name}: {tool.description}")

        # List available resources
        resources_result = await self.session.list_resources()
        print(f"Available resources: {len(resources_result.resources)}")

        for resource in resources_result.resources:
            print(f"  {resource.name} ({resource.uri})")

        return tools_result.tools, resources_result.resources

    async def get_database_schema(self):
        """Fetch database schema resource"""
        schema_resource = await self.session.read_resource(
            "schema://database"
        )

        schema_data = json.loads(schema_resource.contents[0].text)
        print("Database Schema:")

        for table, columns in schema_data.items():
            print(f"  Table: {table}")
            for col in columns:
                pk_marker = " (PK)" if col["primary_key"] else ""
                print(f"    - {col['name']}: {col['type']}{pk_marker}")

        return schema_data

    async def analyze_users(self):
        """Complete workflow: query -> analyze -> report"""
        print("\nStarting user analysis workflow...")

        # Step 1: Query user data
        query_result = await self.session.call_tool(
            "query_database",
            {
                "query": "SELECT age, signup_date, last_active FROM users WHERE active = 1",
                "params": []
            }
        )

        # Handle different content types safely
        if not query_result.content or not query_result.content[0]:
            print("❌ Query failed")
            return

        content = query_result.content[0]
        if hasattr(content, 'text') and content.text:
            user_data = json.loads(content.text)
        else:
            # Handle direct JSON content if available
            user_data = content if isinstance(content, dict) else {}

        if not user_data.get('success', False):
            print(f"❌ Query error: {user_data.get('error', 'Unknown error')}")
            return

        print(f"Retrieved {user_data['count']} active users")

        # Step 2: Analyze the data
        analysis_result = await self.session.call_tool(
            "analyze_data",
            {
                "data": user_data["data"],
                "analysis_type": "summary"
            }
        )

        # Handle analysis result safely
        analysis_content = analysis_result.content[0]
        if hasattr(analysis_content, 'text') and analysis_content.text:
            analysis = json.loads(analysis_content.text)
        else:
            analysis = analysis_content if isinstance(analysis_content, dict) else {}
        print("Analysis completed:")
        print(f"  Total records: {analysis['total_records']}")

        for field, stats in analysis['numeric_analysis'].items():
            print(f"  {field}: avg={stats['avg']:.1f}, range={stats['min']}-{stats['max']}")

        # Step 3: Generate report
        report_result = await self.session.call_tool(
            "create_report",
            {
                "title": "User Analytics Report",
                "data": analysis,
                "output_format": "json"
            }
        )

        report_info = json.loads(report_result.content[0].text)
        print(f"Report saved: {report_info['report_path']}")
        print(f"   Size: {report_info['report_size']} bytes")

    async def disconnect(self):
        """Clean disconnect"""
        if self.session:
            await self.session.close()
            print("Disconnected from MCP server")

# Usage example with correct pattern
async def main():
    server = StdioServerParameters(command="python", args=["production_mcp_server.py"])
    async with stdio_client(server) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()

            # Discover capabilities
            tools = await session.list_tools()
            print(f"Available tools: {[tool.name for tool in tools.tools]}")

            # Get database schema
            resources = await session.list_resources()
            if resources.resources:
                schema = await session.read_resource(resources.resources[0].uri)
                print(f"Database schema loaded")

            # Analyze users
            result = await session.call_tool("query_database", {
                "query": "SELECT COUNT(*) as user_count FROM users",
                "params": []
            })
            print(f"👥 Analysis complete: {result.content[0].text}")

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

3. Execution Flow & Results

Terminal Output
$ python mcp_client.py
Connected to MCP server
Available tools: 3
query_database: Execute SQL queries with safety checks
analyze_data: Perform statistical analysis on data
create_report: Generate and save analysis reports
Available resources: 1
Database Schema (schema://database)
Database Schema:
Table: users
- id: INTEGER (PK)
- age: INTEGER
- signup_date: TEXT
- last_active: TEXT
Starting user analysis workflow...
Retrieved 1,247 active users
Analysis completed:
Total records: 1,247
age: avg=34.2, range=18-67
Report saved: reports/user_analytics_report.json
Size: 2,841 bytes
Disconnected from MCP server
Workflow Demonstrated
• Server Discovery: Automatic capability enumeration
• Resource Access: Schema introspection
• Tool Chaining: Query → Analyze → Report
• Error Handling: Graceful failure management
• Security: SQL injection protection
Production Features
• Database Integration: Safe SQL execution
• File Operations: Report generation
• Statistical Analysis: Data aggregation
• Resource Exposure: Schema as resource
• Async Operations: Non-blocking I/O

Advanced Integration Patterns

Tool Chaining

Combine multiple tools in sequence. Use query results as input for analysis, then generate reports from analysis output.

Real-time Updates

Implement resource subscriptions and notifications for live data updates using Streamable HTTP with Server-Sent Events.

Security Layers

Multi-level security: connection auth, per-tool permissions, input validation, and audit logging.

Security

Production Security & Deployment

Essential security patterns, authentication methods, and deployment best practices for production MCP servers

Security Implementation Examples

Ready-to-use security patterns for production MCP servers:

HTTP Bearer Token Authentication

secure_mcp_server.py

import os
from fastapi import FastAPI, Header, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from mcp.server.fastmcp import FastMCP

app = FastAPI()
mcp_app = FastMCP("secure-analytics-server")

# Security configuration
AUTH_TOKEN = os.getenv("MCP_AUTH_TOKEN")
ALLOWED_ORIGINS = os.getenv("ALLOWED_ORIGINS", "*").split(",")

# CORS middleware for browser clients
app.add_middleware(
    CORSMiddleware,
    allow_origins=ALLOWED_ORIGINS,
    allow_credentials=True,
    allow_methods=["POST", "GET", "OPTIONS"],
    allow_headers=["*"],
)

@app.middleware("http")
async def auth_middleware(request: Request, call_next):
    """Enforce Bearer token authentication for MCP endpoints"""
    if request.url.path.startswith("/mcp"):
        if not AUTH_TOKEN:
            raise HTTPException(
                status_code=500,
                detail="Server misconfiguration: AUTH_TOKEN not set"
            )

        auth_header = request.headers.get("authorization", "")
        if not auth_header.startswith("Bearer "):
            raise HTTPException(
                status_code=401,
                detail="Missing or invalid authorization header"
            )

        token = auth_header.split(" ", 1)[1]
        if token != AUTH_TOKEN:
            # Log failed authentication attempt
            print(f"Failed auth attempt from {request.client.host}: {token[:8]}...")
            raise HTTPException(
                status_code=401,
                detail="Invalid authentication token"
            )

    response = await call_next(request)
    return response

@app.get("/health")
async def health_check():
    """Public health check endpoint"""
    return {"status": "healthy", "auth": "required" if AUTH_TOKEN else "disabled"}

# Mount MCP app with authentication
app.mount("/mcp", mcp_app.app)

Capability-Based Access Control

capability_security.py

import re
from typing import Dict, List, Any
from enum import Enum

class Permission(Enum):
    READ_PUBLIC = "read:public"
    READ_PRIVATE = "read:private"
    WRITE_REPORTS = "write:reports"
    ADMIN_QUERY = "admin:query"

class CapabilityChecker:
    def __init__(self, user_permissions: List[str]):
        self.permissions = set(user_permissions)

    def can_access_database(self, query: str, schema: str = None) -> bool:
        """Check if user can execute specific database operations"""
        query_upper = query.strip().upper()

        # Only SELECT allowed for most users
        if not query_upper.startswith('SELECT'):
            return Permission.ADMIN_QUERY.value in self.permissions

        # Schema-based restrictions
        if schema and schema == "private":
            return Permission.READ_PRIVATE.value in self.permissions

        # Public data access
        return Permission.READ_PUBLIC.value in self.permissions

    def can_write_files(self, path: str) -> bool:
        """Check file write permissions with path restrictions"""
        if not Permission.WRITE_REPORTS.value in self.permissions:
            return False

        # Restrict to specific directories
        allowed_patterns = [
            r'^/tmp/reports/.*',
            r'^./reports/.*',
            r'^reports/.*'
        ]

        return any(re.match(pattern, path) for pattern in allowed_patterns)

# Example usage in MCP tools
@mcp_app.tool()
def secure_query_database(
    query: str,
    schema: str = "public",
    user_permissions: List[str] = ["read:public"]
) -> Dict[str, Any]:
    """Database query with capability-based security"""
    checker = CapabilityChecker(user_permissions)

    if not checker.can_access_database(query, schema):
        return {
            "success": False,
            "error": "Insufficient permissions for this operation",
            "required_permission": "admin:query" if not query.upper().startswith('SELECT') else f"read:{schema}"
        }

    # Whitelist allowed tables/columns
    if schema == "public":
        allowed_tables = ["users", "reports", "analytics"]
        # Extract table name from query (simplified)
        table_match = re.search(r'FROM\s+(\w+)', query, re.IGNORECASE)
        if table_match and table_match.group(1) not in allowed_tables:
            return {
                "success": False,
                "error": f"Access denied to table: {table_match.group(1)}"
            }

    # Execute query logic here...
    return {"success": True, "data": [], "permissions_used": list(checker.permissions)}

@mcp_app.tool()
def secure_create_report(
    title: str,
    data: Dict[str, Any],
    output_path: str = None,
    user_permissions: List[str] = ["read:public"]
) -> Dict[str, Any]:
    """Report creation with file system permissions"""
    checker = CapabilityChecker(user_permissions)

    # Default safe path if none provided
    if not output_path:
        output_path = f"reports/{title.lower().replace(' ', '_')}.json"

    if not checker.can_write_files(output_path):
        return {
            "success": False,
            "error": f"Cannot write to path: {output_path}",
            "allowed_patterns": ["reports/*", "/tmp/reports/*"]
        }

    # Create report logic here...
    return {
        "success": True,
        "report_path": output_path,
        "permissions_validated": True
    }

Production Security Setup

Environment Variables
export MCP_AUTH_TOKEN="your-secure-token-here"
export ALLOWED_ORIGINS="https://yourapp.com"
Client Configuration
Add Authorization: Bearer TOKEN header to all HTTP requests. Configure user permissions based on roles and access requirements.

SDK Compatibility & Best Practices

Production-tested SDK versions and transport layer recommendations for reliable MCP deployments

Tested SDK Versions

Recommended and tested combinations for production deployments:

Python Stack

# Core MCP SDK
mcp>=1.0.0
fastmcp>=2.0.0
# Production ASGI
uvicorn[standard]>=0.24.0
fastapi>=0.104.0
# Performance
uvloop>=0.19.0
✅ Tested Combinations:
• Python 3.11 + FastMCP 2.0 + uvicorn 0.24
• Python 3.12 + Official MCP SDK 1.0
• Recommended: FastMCP for rapid development, Official SDK for full control

Node.js Stack

# Core MCP SDK
@modelcontextprotocol/[email protected]
# Server framework
express>=4.18.0
# Development tools
typescript>=5.0.0
ts-node>=10.9.0
✅ Tested Combinations:
• Node.js 18.x + TypeScript 5.0
• Node.js 20.x + Express 4.18
• Recommended: TypeScript for type safety and better DX

Transport Layer Recommendations

stdio (Local Development)

Perfect For:
• Claude Desktop integration
• Local development and testing
• Simple one-to-one client-server connections
• Command-line tools and scripts
Implementation: Direct process spawning with stdin/stdout communication. Zero network overhead, maximum performance for local use cases.

Streamable HTTP (Production)

Perfect For:
• Browser-based clients
• Remote server deployments
• Load-balanced architectures
• Multi-client scenarios
Implementation: HTTP POST for requests, optional Server-Sent Events for streaming. Firewall-friendly, supports authentication, browser-compatible.

Decision Matrix

Use stdio when:
✅ Integrating with Claude Desktop
✅ Building CLI tools
✅ Local development environment
✅ Single-user applications
✅ Maximum performance needed
Use Streamable HTTP when:
✅ Building web applications
✅ Remote server access needed
✅ Multiple clients connecting
✅ Authentication required
✅ Load balancing / clustering

Performance & Reliability Tips

🚀

Performance

• Install uvloop for 2x better Python performance
• Use connection pooling for database operations
• Implement request batching for high-throughput scenarios
• Enable HTTP/2 for better multiplexing
🛡️

Reliability

• Implement proper timeout handling (30s+ for complex operations)
• Add retry logic with exponential backoff
• Use health checks and graceful shutdown
• Monitor memory usage and implement limits
📊

Monitoring

• Log all requests/responses for debugging
• Track tool usage patterns and performance
• Set up alerts for error rates and latency
• Monitor resource consumption and scaling needs
Resources

Resources

Official Documentation

• MCP Official Site - Home page, getting started guide
• MCP Specification (Current) - Technical details, requirements, transport
• MCP Documentation GitHub - Guides, READMEs, contributions
• Anthropic MCP Launch Article - Context, usage, announcements
• Claude MCP Documentation - Configuration and integration guide

Community & Support

• Community Discord for support and discussions
• GitHub Discussions for technical questions
• Sample implementations and templates
• Regular community meetups and workshops

Development Tools

• MCP Inspector for debugging servers
• TypeScript and Python SDKs
• VS Code extension for MCP development
• Testing frameworks and utilities

Ready-to-Use Servers

• Filesystem operations and file management
• Database connections (PostgreSQL, SQLite)
• Web search and API integrations
• Git and version control operations