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


Every AI application needs to talk to external systems — databases, APIs, file systems, search engines. Before 2025, every integration was custom: write a LangChain tool, build a custom function call, hack together a plugin. It was like the pre-USB era where every device had its own cable.


MCP (Model Context Protocol) changes that. It's an open protocol that standardizes how AI models connect to external tools and data sources — one protocol, any LLM, any tool.


What Is MCP?


MCP is an open standard (originally developed by Anthropic) that defines how AI applications discover and call external tools. Think of it as **USB-C for AI integrations** — a universal connector that any MCP-compatible client can use to talk to any MCP-compatible server.



┌─────────────────┐     ┌────────────────┐     ┌─────────────────┐

│  MCP Client     │◄───►│  MCP Protocol  │◄───►│  MCP Server     │

│  (Claude, VS    │     │  (JSON-RPC     │     │  (DB connector, │

│   Code, Cursor) │     │   over stdio   │     │   file system,  │

│                 │     │   or SSE)      │     │   search, API)  │

└─────────────────┘     └────────────────┘     └─────────────────┘


**The key insight**: MCP decouples tool implementation from the AI client. A single MCP server written once works with Claude Desktop, VS Code extensions, Cursor, and any other MCP-compatible client. No more "write this tool for LangChain, rewrite it for OpenAI, rewrite it for Claude."


Architecture


MCP has four core concepts:


1. Host (Client Application)

The AI-powered application the user interacts with: Claude Desktop, VS Code, Cursor, a custom chat app.


2. Client (MCP Client)

A lightweight transport layer inside the host that manages connections to MCP servers. It speaks the MCP protocol over the wire.


3. Server (MCP Server)

A lightweight service that exposes resources and tools through MCP. Each server is focused on one domain: filesystem access, database queries, API integrations.


4. Protocol

The standard JSON-RPC message format that clients and servers use to communicate. Two transport modes:

  • **stdio**: Server runs as a subprocess, communication over stdin/stdout (simple, secure)
  • **SSE** (Server-Sent Events): Network-based, for remote servers

  • Communication Flow


    
    User: "Find all bugs assigned to me"
    
        │
    
        ▼
    
    Host (Claude Desktop)
    
        │  MCP Client asks servers for available tools
    
        ▼
    
    MCP Server (GitHub) ───► Returns: list_issues, get_repo, search_code
    
        │
    
        ▼
    
    LLM decides: call list_issues with assignee:me
    
        │
    
        ▼
    
    MCP Server executes GitHub API call
    
        │
    
        ▼
    
    Returns issue data to LLM
    
        │
    
        ▼
    
    Host shows: "Found 3 bugs assigned to you:..."
    
    

    MCP vs Alternatives


    | Feature | MCP | OpenAI Function Calling | LangChain Tools | Custom API |

    |---------|-----|------------------------|-----------------|------------|

    | **Standardized** | ✅ Open standard | ❌ OpenAI-only | ❌ LangChain-only | ❌ Custom |

    | **Cross-client** | ✅ Any MCP client | ❌ OpenAI API only | ❌ LangChain only | ❌ One app only |

    | **Tool discovery** | ✅ Auto-discover tools | ✅ Schema-based | ✅ Defined in code | ❌ Hard-coded |

    | **Dynamic resources** | ✅ Yes (file system, DB) | ❌ No | ❌ No | ❌ No |

    | **Security model** | ✅ Host controls permissions | ⚠️ Per-call | ⚠️ Per-call | ⚠️ Per-call |

    | **Async/long-running** | ✅ Notifications, progress | ❌ Request-response only | ❌ Request-response only | ⚠️ Depends |

    | **Ecosystem** | Growing fast | Mature but narrow | Good | None |

    | **Setup complexity** | Low | Low | Medium | High |


    Why MCP Wins


    **Before MCP**: Every AI tool integration was bespoke

  • VS Code extension for GitHub ← custom code
  • Claude Desktop for database queries ← one-off Python script
  • Cursor for file operations ← built-in, not extensible
  • Custom chatbot for search ← another custom tool

  • **With MCP**: Write once, use everywhere

  • One GitHub MCP server works in Claude Desktop, VS Code, Cursor
  • One PostgreSQL MCP server works in any host
  • One filesystem MCP server works everywhere

  • How to Build an MCP Server


    The MCP SDK (available for Python, TypeScript, Java, and Go) makes building a server trivial. Here's a complete example in Python:


    Python MCP Server


    
    # server.py
    
    from mcp.server import Server
    
    from mcp.types import Tool, TextContent
    
    import httpx
    
    import json
    
    
    
    app = Server("weather-server")
    
    
    
    @app.list_tools()
    
    async def list_tools() -> list[Tool]:
    
        return [
    
            Tool(
    
                name="get_weather",
    
                description="Get current weather for a city",
    
                input_schema={
    
                    "type": "object",
    
                    "properties": {
    
                        "city": {"type": "string", "description": "City name"}
    
                    },
    
                    "required": ["city"],
    
                },
    
            )
    
        ]
    
    
    
    @app.call_tool()
    
    async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    
        if name == "get_weather":
    
            async with httpx.AsyncClient() as client:
    
                resp = await client.get(
    
                    f"https://api.openweathermap.org/data/2.5/weather",
    
                    params={"q": arguments["city"], "appid": "YOUR_KEY"}
    
                )
    
                data = resp.json()
    
                return [TextContent(
    
                    type="text",
    
                    text=json.dumps(data["main"], indent=2)
    
                )]
    
        raise ValueError(f"Unknown tool: {name}")
    
    
    
    if __name__ == "__main__":
    
        app.run(transport="stdio")
    
    

    Running the Server


    
    // claude_desktop_config.json
    
    {
    
      "mcpServers": {
    
        "weather": {
    
          "command": "python",
    
          "args": ["/path/to/server.py"]
    
        }
    
      }
    
    }
    
    

    Now your weather tool works in Claude Desktop, VS Code (with Cline or Continue extension), Cursor, and any other MCP host — with zero changes.


    TypeScript MCP Server


    
    import { Server } from "@modelcontextprotocol/sdk/server/index.js";
    
    import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
    
    
    
    const server = new Server(
    
      { name: "github-server", version: "1.0.0" },
    
      { capabilities: { tools: {} } }
    
    );
    
    
    
    server.setRequestHandler(ListToolsRequestSchema, async () => ({
    
      tools: [{
    
        name: "search_repos",
    
        description: "Search GitHub repositories",
    
        inputSchema: {
    
          type: "object",
    
          properties: {
    
            query: { type: "string" }
    
          },
    
          required: ["query"]
    
        }
    
      }]
    
    }));
    
    
    
    server.setRequestHandler(CallToolRequestSchema, async (request) => {
    
      const { name, arguments: args } = request.params;
    
      const response = await fetch(
    
        `https://api.github.com/search/repositories?q=${args.query}`
    
      );
    
      const data = await response.json();
    
      return {
    
        content: [{ type: "text", text: JSON.stringify(data.items.slice(0, 5)) }]
    
      };
    
    });
    
    
    
    const transport = new StdioServerTransport();
    
    await server.connect(transport);
    
    

    MCP Resources: Beyond Tools


    MCP isn't just about calling tools — it also defines **resources** (data sources the server exposes). This is where MCP surpasses simple function calling.


    
    @app.list_resources()
    
    async def list_resources() -> list[Resource]:
    
        return [
    
            Resource(
    
                uri="file:///logs/app.log",
    
                name="Application Logs",
    
                description="Live application log file",
    
                mime_type="text/plain",
    
            ),
    
            Resource(
    
                uri="postgres://users",
    
                name="Users Table",
    
                description="User database table schema and sample rows",
    
                mime_type="application/json",
    
            ),
    
        ]
    
    
    
    @app.read_resource()
    
    async def read_resource(uri: str) -> str:
    
        if uri == "file:///logs/app.log":
    
            return open("/var/log/app.log").read()[-10000:]
    
        if uri.startswith("postgres://"):
    
            # Query schema dynamically
    
            return json.dumps(query_table_schema(uri))
    
        raise ValueError(f"Unknown resource: {uri}")
    
    

    Resources give the AI client dynamic access to your data — it can read log files, query database schemas, browse file systems — all through the same protocol.


    MCP Prompts (Templates)


    MCP servers can also expose **prompt templates** — reusable instructions that guide how the LLM should behave when using your tools:


    
    @app.list_prompts()
    
    async def list_prompts() -> list[Prompt]:
    
        return [
    
            Prompt(
    
                name="analyze_logs",
    
                description="Analyze application logs for errors",
    
                arguments=[{
    
                    "name": "severity",
    
                    "description": "Minimum log severity level",
    
                    "required": False,
    
                }],
    
            )
    
        ]
    
    
    
    @app.get_prompt()
    
    async def get_prompt(name: str, arguments: dict) -> PromptMessage:
    
        severity = arguments.get("severity", "ERROR")
    
        return PromptMessage(
    
            role="user",
    
            content={
    
                "type": "text",
    
                "text": f"""Read the application logs and:
    
    1. Find all {severity} level entries
    
    2. Group them by component
    
    3. Identify patterns
    
    4. Suggest fixes
    
    
    
    Use the log resource and tools to investigate."""
    
            }
    
        )
    
    

    Production MCP Server Patterns


    1. Authentication and Credentials


    MCP servers should never hard-code credentials. Use environment variables or a config file:


    
    import os
    
    
    
    DB_URL = os.environ.get("MCP_DB_URL", "postgresql://localhost:5432")
    
    API_KEY = os.environ.get("MCP_API_KEY")
    
    
    
    app = Server("production-server")
    
    

    2. Rate Limiting


    Protect external APIs from aggressive tool calls:


    
    import time
    
    from collections import defaultdict
    
    
    
    class RateLimiter:
    
        def __init__(self, max_calls: int, window: float):
    
            self.max_calls = max_calls
    
            self.window = window
    
            self.calls = defaultdict(list)
    
    
    
        def check(self, key: str = "default") -> bool:
    
            now = time.time()
    
            self.calls[key] = [t for t in self.calls[key] if now - t < self.window]
    
            if len(self.calls[key]) >= self.max_calls:
    
                return False
    
            self.calls[key].append(now)
    
            return True
    
    

    3. Tool Result Caching


    Avoid repeating expensive operations:


    
    from functools import lru_cache
    
    from datetime import datetime, timedelta
    
    
    
    cache = {}
    
    
    
    def cached(ttl_seconds: int = 300):
    
        def decorator(func):
    
            def wrapper(*args, **kwargs):
    
                key = str(args) + str(kwargs)
    
                if key in cache:
    
                    result, timestamp = cache[key]
    
                    if datetime.now() - timestamp < timedelta(seconds=ttl_seconds):
    
                        return result
    
                result = func(*args, **kwargs)
    
                cache[key] = (result, datetime.now())
    
                return result
    
            return wrapper
    
        return decorator
    
    

    4. Structured Logging


    Every tool call should be traceable:


    
    import structlog
    
    
    
    logger = structlog.get_logger()
    
    
    
    # In the call_tool handler
    
    logger.info("tool_call", tool=name, arguments=arguments,
    
                duration_ms=elapsed_ms, success=True)
    
    

    MCP Ecosystem (2026)


    Popular MCP Servers


    | Server | Purpose | Official? |

    |--------|---------|-----------|

    | **Filesystem** | Read/write files, directory listing | ✅ Anthropic |

    | **GitHub** | Issues, PRs, repos, search | ✅ Anthropic |

    | **PostgreSQL** | Query databases, schema inspection | ✅ Anthropic |

    | **Slack** | Messages, channels, search | ✅ Anthropic |

    | **SQLite** | Local database operations | ✅ Community |

    | **Docker** | Container management | ✅ Community |

    | **Kubernetes** | Pod management, logs | ✅ Community |

    | **Puppeteer** | Browser automation | ✅ Community |

    | **Jira** | Issue tracking | ✅ Community |

    | **Sentry** | Error monitoring | ✅ Community |

    | **PagerDuty** | Incident management | ✅ Community |

    | **Elasticsearch** | Search operations | ✅ Community |


    MCP Client Hosts


    | Host | Type | MCP Support |

    |------|------|-------------|

    | **Claude Desktop** | Desktop app | ✅ Full support |

    | **VS Code** (Cline) | IDE extension | ✅ Full |

    | **VS Code** (Continue) | IDE extension | ✅ Full |

    | **Cursor** | IDE | ✅ Full |

    | **Windsurf** | IDE | ✅ Full |

    | **Zed** | Editor | ✅ Native |

    | **Sourcegraph Cody** | IDE extension | ✅ Full |

    | **JetBrains** | IDE | ✅ Via plugins |

    | **OpenAI** | API | ❌ (proprietary tools only) |

    | **Google Gemini** | API | ❌ (proprietary tools only) |


    When to Build vs Use Existing


    Build an MCP Server When:

  • You have a proprietary/internal tool or API
  • You need fine-grained access control
  • Your data requires special transformation
  • You want to combine multiple services into one server

  • Use an Existing Server When:

  • Standard integrations (GitHub, PostgreSQL, Slack)
  • Well-known APIs with existing community servers
  • File system or database access patterns

  • Security Considerations


    MCP's stdio transport is surprisingly secure — the server runs as a local subprocess with your permissions. But there are risks:


  • **Tool abuse**: A malicious prompt could make dangerous tool calls (DELETE FROM users). Mitigate by:
  • - Read-only servers for exploration, separate write servers

    - Parameter whitelists in tool handlers

    - Confirmation prompts for destructive operations


    2. **Resource leaks**: LLMs can read large files. Set size limits on resource responses:

    ```python

    MAX_RESOURCE_SIZE = 100_000 # bytes

    ```


    3. **Credentials in config**: The `claude_desktop_config.json` stores server launch commands. Never embed API keys — use environment variables.


    4. **Tool sandboxing**: Consider Docker containers for untrusted MCP servers:

    ```json

    {

    "mcpServers": {

    "community-server": {

    "command": "docker",

    "args": [

    "run", "--rm", "-i",

    "--network", "none",

    "community/mcp-server:latest"

    ]

    }

    }

    }

    ```


    Real-World Architecture: AI-Powered Dev Environment


    Here's how a real production setup looks:


    
    ┌─────────────────────────────────────────────────┐
    
    │                 VS Code (Host)                   │
    
    │  ┌──────────┐  ┌──────────┐  ┌───────────────┐  │
    
    │  │ Claude   │  │ Cline    │  │ Continue       │  │
    
    │  │ Desktop  │  │ (VS Code)│  │ (VS Code)      │  │
    
    │  └────┬─────┘  └────┬─────┘  └──────┬────────┘  │
    
    │       │              │               │           │
    
    └───────┼──────────────┼───────────────┼───────────┘
    
            │              │               │
    
            ▼              ▼               ▼
    
    ┌─────────────────────────────────────────────────┐
    
    │              MCP Protocol (JSON-RPC)             │
    
    ├─────────────────────────────────────────────────┤
    
    │  ┌──────────┐  ┌──────────┐  ┌───────────────┐  │
    
    │  │ GitHub   │  │ Postgres │  │ Filesystem     │  │
    
    │  │ Server   │  │ Server   │  │ Server         │  │
    
    │  └──────────┘  └──────────┘  └───────────────┘  │
    
    │  ┌──────────┐  ┌──────────┐  ┌───────────────┐  │
    
    │  │ Docker   │  │ Slack    │  │ Jira           │  │
    
    │  │ Server   │  │ Server   │  │ Server         │  │
    
    │  └──────────┘  └──────────┘  └───────────────┘  │
    
    └─────────────────────────────────────────────────┘
    
    

    Each server is independently runnable, testable, and replaceable. Add or remove capabilities by adding or removing MCP servers — no code changes needed.


    Getting Started


  • **Install Claude Desktop** → Add your first MCP server via config
  • 2. **Try existing servers** → GitHub, Filesystem, SQLite (Anthropic's official servers)

    3. **Build your first server** → Use the Python SDK, ~50 lines of code

    4. **Add to VS Code** → Install Cline or Continue extension

    5. **Go multi-host** → Same MCP server works across all clients


    The MCP ecosystem doubles roughly every quarter. What takes 50 lines today will be a one-line npm install by next quarter. The best time to build on top of it was last year — the second best time is now.


    Summary


    | Concept | What It Means |

    |---------|---------------|

    | **MCP** | Open protocol for AI-to-tool communication |

    | **Transport** | stdio (local) or SSE (remote) |

    | **Tools** | Functions the LLM can call |

    | **Resources** | Data sources the LLM can read |

    | **Prompts** | Templates for how to use tools |

    | **Host** | The app running the LLM |

    | **Server** | Exposes tools/resources via MCP |


    MCP is to AI integrations what HTTP is to web APIs — a universal standard that makes everything compose. Every new MCP-compatible tool and client increases the value of every other MCP component. The network effect is what makes it inevitable.