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:
- 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.
- Input validation: Always validate LLM-generated inputs. Never execute raw SQL -- use parameterized queries or restrict to SELECT statements.
- Rate limiting: Prevent the LLM from making excessive tool calls that could overload downstream services.
- 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.
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.