MCP: Building AI Tools That Connect to Everything
If you’ve built AI integrations before you know the shape of the problem. Connecting Claude to Slack requires custom code. Switch to GPT? Rebuild it. Add GitHub? Write another integration. Multiply this across every tool and every AI provider, and you’re drowning in glue code.
MCP (Model Context Protocol) fixes this. Released by Anthropic in November 2024 and now governed by the Linux Foundation with backing from OpenAI, Google, and Microsoft, MCP has become the industry standard for connecting AI to external systems. Anthropic’s own framing is USB-C for AI, one protocol that lets any compliant client talk to any compliant server. The analogy is doing real work, and it’s also the sort of claim a protocol makes about itself on day one.
The short version: you build one MCP server and any compliant client can use it. The protocol is JSON-RPC 2.0, it has three primitives, and there are SDKs for Python and TypeScript among others.
The Problem MCP Solves
Before MCP, connecting an AI assistant to external tools meant the classic MรN integration problem. Five AI providers times ten tools equals fifty custom integrations. Each one with its own authentication, its own error handling, and its own maintenance burden.
MCP inverts that. Each tool exposes one server, each AI application implements one client, and the protocol handles what’s in between:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ MCP HOST โ
โ (Claude Desktop, Cursor, VS Code, custom apps) โ
โ โ
โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โ
โ โ MCP Client โ โ MCP Client โ โ MCP Client โ โ
โ โโโโโโโโฌโโโโโโโ โโโโโโโโฌโโโโโโโ โโโโโโโโฌโโโโโโโ โ
โโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโ
โ โ โ
JSON-RPC 2.0 JSON-RPC 2.0 JSON-RPC 2.0
โ โ โ
โโโโโโโโผโโโโโโโ โโโโโโโโผโโโโโโโ โโโโโโโโผโโโโโโโ
โ MCP Server โ โ MCP Server โ โ MCP Server โ
โ (GitHub) โ โ (Postgres) โ โ (Slack) โ
โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ
Core Architecture
MCP follows a client-server architecture built on JSON-RPC 2.0, inspired by the Language Server Protocol (LSP) that powers IDE features. Three key roles:
- Host: The AI application (Claude Desktop, Cursor, your custom agent) that runs the model and manages connections
- Client: A component within the host that maintains a 1:1 connection with a server
- Server: A program exposing tools, data, or prompts to clients
Servers expose three primitives:
| Primitive | Control | Purpose | Example |
|---|---|---|---|
| Tools | Model-controlled | Executable functions the LLM can invoke | Database queries, API calls |
| Resources | Application-controlled | Read-only data exposed to the model | File contents, schemas |
| Prompts | User-controlled | Reusable templates for workflows | Code review checklists |
Two transport mechanisms handle communication:
- stdio: Server runs as a local subprocess via stdin/stdout. Fast, no network overhead. Ideal for Claude Desktop integrations.
- Streamable HTTP: HTTP POST with Server-Sent Events for remote deployments. Supports multiple concurrent clients.
Building an MCP Server in Python
The Python SDK gives you FastMCP, which handles the protocol details so you don’t have to:
pip install "mcp[cli]"# server.py
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("MyServer")
@mcp.tool()
def query_database(query: str, limit: int = 100) -> str:
"""Execute a read-only SQL query against the database."""
# In production, execute against actual database
return f"Results for: {query} (limit {limit})"
@mcp.resource("config://{key}")
def get_config(key: str) -> str:
"""Read configuration values."""
configs = {"api_url": "https://api.example.com", "timeout": "30"}
return configs.get(key, "not found")
@mcp.prompt()
def review_code(language: str = "python") -> str:
"""Generate a code review prompt."""
return f"Review this {language} code for bugs, security issues, and style."
if __name__ == "__main__":
mcp.run() # stdio transport by defaultFor production HTTP deployments:
if __name__ == "__main__":
mcp.run(transport="streamable-http", port=8000)Test with the MCP Inspector: mcp dev server.py
Critical: For stdio servers, never print to stdout. It corrupts the JSON-RPC stream. Use stderr or a logging library configured to avoid stdout.
Building an MCP Server in TypeScript
npm install @modelcontextprotocol/sdk zod// src/index.ts
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
const server = new McpServer({
name: 'my-server',
version: '1.0.0'
});
server.registerTool(
'query_database',
{
description: 'Execute a read-only SQL query',
inputSchema: {
query: z.string().describe('SQL SELECT statement'),
limit: z.number().default(100)
}
},
async ({ query, limit }) => ({
content: [{ type: 'text', text: `Results for: ${query}` }]
})
);
const transport = new StdioServerTransport();
await server.connect(transport);Configuring Claude Desktop
Add servers to your config file. On macOS: ~/Library/Application Support/Claude/claude_desktop_config.json. On Windows: %APPDATA%\Claude\claude_desktop_config.json.
{
"mcpServers": {
"my-python-server": {
"command": "python",
"args": ["/path/to/server.py"],
"env": { "DATABASE_URL": "postgres://..." }
},
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"]
}
}
}Claude Desktop launches these on startup and the tools show up in your conversations. If a server doesn’t appear, it’s almost always because the command path isn’t what you think it is.
The Ecosystem
Major platforms support it:
| Client | MCP Support |
|---|---|
| Claude Desktop | Full native support |
| ChatGPT | Developer Mode (September 2025) |
| Cursor | Native integration, one-click installs |
| VS Code | GitHub Copilot MCP extension |
| Gemini | Confirmed by Google DeepMind |
| Windows 11 | Native Microsoft Copilot integration |
Pre-built servers cover common integrations:
- Official reference servers: Filesystem, Git, Memory, Fetch, Time
- Company-maintained: GitHub, Microsoft Azure, AWS, Cloudflare, Stripe, Atlassian, MongoDB, Docker
- Community registries: MCP.so, Smithery.ai, PulseMCP
When to Choose MCP vs Alternatives
Use MCP when:
- Building production systems with 5+ tool integrations
- Tools need to work across multiple AI providers
- Security isolation between tools is critical
- You want reusable integrations shared across teams
Use function calling when:
- Prototyping with 1-3 custom tools
- Working within a single LLM provider’s ecosystem
- Quick iteration matters more than long-term maintainability
Use direct API integration when:
- Ultra-low latency requirements (MCP adds protocol overhead)
- Non-LLM applications
- Legacy systems where MCP servers would require significant adaptation
They complement each other. Function calling is how the model says what it wants, MCP is how that request finds something to run it.
Production Best Practices
Single responsibility: Each server should do one thing. It’s tempting to build one server that does everything for your company, and you’ll regret it the first time you want to give somebody access to half of it.
Error handling: Return errors in the response with isError: true. Implement retry with exponential backoff:
import asyncio
import random
async def retry_with_backoff(operation, max_attempts=3):
for attempt in range(max_attempts):
try:
return await operation()
except Exception as e:
if attempt == max_attempts - 1:
raise
delay = 2 ** attempt + random.uniform(0, 1)
await asyncio.sleep(delay)Security: Remote servers must use OAuth 2.1 with recognized certificates. Provide tool annotations (readOnlyHint, destructiveHint) so hosts can implement consent flows. Never collect extraneous conversation data.
Observability: Use structured logging with correlation IDs. Track throughput, latency percentiles (p50/p95/p99), and error rates.
Current Limitations
Context window pressure: Every tool definition is loaded upfront and every one costs tokens. Past a few dozen tools it degrades, and you’ll want progressive disclosure rather than handing the model the whole catalog.
OAuth 2.1 adoption lag: The spec defines OAuth 2.1 for authentication, but many clients haven’t fully implemented it yet.
Enterprise compliance gaps: Native audit trails, RBAC, and data residency controls are thin. If you’re carrying GDPR or SOX obligations you’re building that layer yourself.
Stateful scaling: The protocol assumes stateful sessions by default. Use Streamable HTTP transport with external session stores for horizontal scaling.
What I’m still unsure about
MCP solves a real problem and it solves it in the boring, correct way, which is why it spread. Standardizing the connection layer doesn’t make anything smarter. It removes a category of work that was never interesting to begin with, and that’s a good enough reason for a protocol to exist.
The part I can’t call yet is whether the primitives survive contact with scale. Tools are clearly right. Resources and Prompts are used far less in practice than the spec implies they should be, and a protocol with a primitive that nobody reaches for usually ends up dropping it or redefining it. Context window pressure is pushing everyone toward loading tools on demand, which is a sensible fix and also an admission that the original design assumed a smaller world than the one it got.
If you want to form your own view, the fastest route isn’t the spec. It’s connecting Claude Desktop to the filesystem server and watching what actually crosses the wire.
