Skip to content
Back to Blog
Agentic AI7 min read

Model Context Protocol (MCP): The New Standard for AI Tool Integration

A comprehensive technical guide to Anthropic's Model Context Protocol -- the open standard for connecting AI models to external tools, data sources, and services. Covers architecture, server implementation, and real-world integration patterns.

What Is the Model Context Protocol?

The Model Context Protocol (MCP) is an open standard created by Anthropic that defines how AI models connect to external tools, data sources, and services. Think of it as a USB-C port for AI -- a universal interface that lets any AI application talk to any data source or tool through a standardized protocol.

Before MCP, every AI application built its own bespoke integrations. Connecting Claude to a database required custom code. Connecting it to GitHub required different custom code. Connecting it to Slack required yet another integration. MCP replaces this M-times-N integration problem with a standardized protocol where each tool is implemented once as an MCP server and works with every MCP-compatible client.

Architecture

MCP follows a client-server architecture with three components:

MCP Hosts

The AI application that wants to use external tools. Claude Desktop, Claude Code, Cursor, and Windsurf are all MCP hosts. The host manages the user interface and LLM interaction.

MCP Clients

A protocol client embedded in the host that maintains a connection to one or more MCP servers. The client handles capability negotiation, message routing, and lifecycle management.

MCP Servers

Lightweight services that expose specific capabilities through the MCP protocol. Each server provides one or more of three primitive types:

Primitive Description Example
Tools Functions the LLM can invoke search_database, create_issue, send_email
Resources Data the LLM can read File contents, database records, API responses
Prompts Pre-built prompt templates Code review template, summarization template
  Host (Claude Desktop)
    |
    |-- MCP Client --> MCP Server (GitHub)
    |-- MCP Client --> MCP Server (PostgreSQL)
    |-- MCP Client --> MCP Server (Slack)

Building an MCP Server

MCP servers can be implemented in Python or TypeScript using the official SDKs. Here is a complete Python example that exposes a database query tool:

from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
import asyncpg
import json

server = Server("database-query")

# Connection pool (initialized on startup)
pool = None

@server.list_tools()
async def list_tools() -> list[Tool]:
    return [
        Tool(
            name="query_database",
            description="Execute a read-only SQL query against the production database. "
                        "Returns results as JSON. Only SELECT queries are allowed.",
            inputSchema={
                "type": "object",
                "properties": {
                    "sql": {
                        "type": "string",
                        "description": "The SQL SELECT query to execute"
                    },
                    "limit": {
                        "type": "integer",
                        "description": "Max rows to return (default 100)",
                        "default": 100
                    }
                },
                "required": ["sql"]
            }
        ),
        Tool(
            name="list_tables",
            description="List all tables in the database with their column definitions.",
            inputSchema={"type": "object", "properties": {}}
        )
    ]

@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    if name == "query_database":
        sql = arguments["sql"].strip()
        if not sql.upper().startswith("SELECT"):
            return [TextContent(
                type="text",
                text="Error: Only SELECT queries are allowed."
            )]

        limit = arguments.get("limit", 100)
        sql_with_limit = f"{sql} LIMIT {limit}"

        async with pool.acquire() as conn:
            rows = await conn.fetch(sql_with_limit)
            result = [dict(row) for row in rows]
            return [TextContent(type="text", text=json.dumps(result, default=str))]

    elif name == "list_tables":
        async with pool.acquire() as conn:
            tables = await conn.fetch("""
                SELECT table_name, column_name, data_type
                FROM information_schema.columns
                WHERE table_schema = 'public'
                ORDER BY table_name, ordinal_position
            """)
            return [TextContent(type="text", text=json.dumps(
                [dict(t) for t in tables], default=str
            ))]

async def main():
    global pool
    pool = await asyncpg.create_pool("postgresql://user:pass@localhost/mydb")
    async with stdio_server() as (read_stream, write_stream):
        await server.run(read_stream, write_stream)

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

TypeScript MCP Server Example

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = new Server(
  { name: "weather-service", version: "1.0.0" },
  { capabilities: { tools: {} } }
);

server.setRequestHandler("tools/list", async () => ({
  tools: [
    {
      name: "get_weather",
      description: "Get current weather for a city",
      inputSchema: {
        type: "object",
        properties: {
          city: { type: "string", description: "City name" },
        },
        required: ["city"],
      },
    },
  ],
}));

server.setRequestHandler("tools/call", async (request) => {
  if (request.params.name === "get_weather") {
    const { city } = request.params.arguments;
    const weather = await fetchWeatherAPI(city);
    return {
      content: [{ type: "text", text: JSON.stringify(weather) }],
    };
  }
  throw new Error(`Unknown tool: ${request.params.name}`);
});

const transport = new StdioServerTransport();
await server.connect(transport);

Configuring MCP in Claude Desktop

MCP servers are configured in claude_desktop_config.json:

{
  "mcpServers": {
    "database": {
      "command": "python",
      "args": ["/path/to/db_server.py"],
      "env": {
        "DATABASE_URL": "postgresql://user:pass@localhost/mydb"
      }
    },
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_TOKEN": "ghp_xxxx"
      }
    },
    "filesystem": {
      "command": "npx",
      "args": [
        "-y", "@modelcontextprotocol/server-filesystem",
        "/Users/dev/projects"
      ]
    }
  }
}

Transport Protocols

MCP supports two transport mechanisms:

stdio (Standard I/O)

The default transport for local MCP servers. The host spawns the server as a child process and communicates via stdin/stdout. This is simple, secure (runs locally), and requires no network configuration.

SSE (Server-Sent Events) over HTTP

For remote MCP servers that run on a different machine or as a cloud service. The client sends requests via HTTP POST and receives responses via an SSE stream.

from mcp.server.sse import SseServerTransport
from starlette.applications import Starlette
from starlette.routing import Route

transport = SseServerTransport("/messages")

async def handle_sse(request):
    async with transport.connect_sse(
        request.scope, request.receive, request._send
    ) as streams:
        await server.run(streams[0], streams[1])

app = Starlette(routes=[
    Route("/sse", endpoint=handle_sse),
    Route("/messages", endpoint=transport.handle_post_message, methods=["POST"]),
])

The MCP Ecosystem in 2026

The MCP ecosystem has grown rapidly since its November 2024 launch. As of January 2026, there are official servers for:

  • Data: PostgreSQL, MySQL, SQLite, Google Drive, Google Sheets
  • Development: GitHub, GitLab, Linear, Sentry
  • Communication: Slack, Gmail
  • Search: Brave Search, Google Search
  • Infrastructure: AWS, Docker, Kubernetes
  • Productivity: Notion, Google Calendar, Todoist

The community has contributed hundreds of additional servers. The MCP server registry at mcp.so lists over 500 community-built servers.

Security Considerations

MCP servers have direct access to sensitive systems (databases, APIs, file systems). Security must be built in:

  1. Principle of least privilege: Each MCP server should have the minimum permissions needed. A database MCP server for analytics should use a read-only database user.
  2. Input validation: Always validate LLM-generated inputs. Never execute raw SQL -- use parameterized queries or restrict to SELECT statements.
  3. Rate limiting: Prevent the LLM from making excessive tool calls that could overload downstream services.
  4. Audit logging: Log every tool invocation with the input, output, and context for security review.
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    # Log every tool call
    logger.info("tool_call", tool=name, args=arguments)

    # Rate limiting
    if not await rate_limiter.allow(name):
        return [TextContent(type="text", text="Rate limit exceeded. Try again later.")]

    # Input validation before execution
    if name == "query_database":
        sql = arguments.get("sql", "")
        if any(keyword in sql.upper() for keyword in ["DROP", "DELETE", "UPDATE", "INSERT", "ALTER"]):
            return [TextContent(type="text", text="Error: Only read operations allowed.")]
    # ... execute tool

MCP vs Direct Function Calling

MCP is complementary to, not a replacement for, LLM function calling. Function calling defines how the model decides to use tools within a single API call. MCP defines how those tools are discovered, connected, and managed across applications.

Aspect Function Calling MCP
Scope Single API call Cross-application
Tool Discovery Hardcoded in prompt Dynamic via protocol
Implementation In your app code Separate server process
Reusability Per-application Any MCP host
Standardization Provider-specific Open standard

Building Production MCP Servers

For production deployments, MCP servers need the same reliability engineering as any microservice:

  • Health checks: Implement a health endpoint the host can poll
  • Graceful shutdown: Handle SIGTERM properly to avoid data corruption
  • Error reporting: Return structured errors the LLM can understand and recover from
  • Configuration management: Use environment variables for secrets, not hardcoded values
  • Testing: Write integration tests that validate tool inputs and outputs

MCP has rapidly become the standard interface for AI tool integration. By investing in MCP server development, teams build reusable infrastructure that works across Claude, Claude Code, and the growing ecosystem of MCP-compatible applications.

Share this article
N

NYC News

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.