The Problem MCP Solves
Every AI framework has invented its own way to connect LLMs to external tools:
- OpenAI has "Function Calling"
- LangChain has its Tool class
- CrewAI has BaseTool
- AutoGen has its own tool registration
This means every tool integration must be rewritten for every framework. A Slack integration for LangChain doesn't work in AutoGen. A database connector for CrewAI can't be reused in your custom agent.
Model Context Protocol (MCP) is Anthropic's answer: an open protocol that standardizes how AI models connect to tools, data sources, and context — the same way HTTP standardized how clients and servers communicate on the web.
What Is MCP?
MCP defines a standard interface between:
- MCP Clients — AI applications (Claude Desktop, your agent code)
- MCP Servers — lightweight processes that expose capabilities
An MCP Server can expose three types of capabilities:
| Capability | Description | Example |
|---|---|---|
| Tools | Functions the AI can call | search_database, send_email |
| Resources | Data the AI can read | Files, database rows, API responses |
| Prompts | Reusable prompt templates | Structured workflows, system prompts |
MCP Architecture
┌─────────────────────────────────────────────────────┐
│ Your Application │
│ │
│ ┌─────────────────┐ ┌──────────────────────┐ │
│ │ MCP Client │<────>│ Claude / LLM │ │
│ │ (your code or │ │ (decides when to │ │
│ │ Claude Desktop)│ │ call MCP tools) │ │
│ └────────┬────────┘ └──────────────────────┘ │
│ │ │
└───────────│──────────────────────────────────────────┘
│ JSON-RPC (stdio / HTTP / WebSocket)
│
┌────────┴────────────────────────────────┐
│ MCP Servers │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────┐ │
│ │ GitHub │ │Postgres │ │Slack │ │
│ │ Server │ │ Server │ │Server │ │
│ └──────────┘ └──────────┘ └───────┘ │
└─────────────────────────────────────────┘
The client manages connections to one or more servers. The LLM sees all server tools as if they were locally defined — but the implementation lives in isolated server processes.
Installation
pip install mcp # MCP SDK for Python
npm install @modelcontextprotocol/sdk # TypeScript SDK
# mcp_server.py
from mcp.server import Server
from mcp.server.models import InitializationOptions
from mcp.server.stdio import stdio_server
from mcp import types
import asyncio
import sqlite3
import json
# Initialize the MCP server
app = Server("company-data-server")
# -------------------------------------------------------
# TOOLS — functions the AI can call
# -------------------------------------------------------
@app.list_tools()
async def list_tools() -> list[types.Tool]:
return [
types.Tool(
name="query_employees",
description=(
"Query the employee database. Returns employee records matching "
"the given filters. Use this to look up staff information."
),
inputSchema={
"type": "object",
"properties": {
"department": {
"type": "string",
"description": "Filter by department (e.g. Engineering, Marketing)"
},
"min_years": {
"type": "integer",
"description": "Minimum years at company"
}
},
}
),
types.Tool(
name="send_notification",
description="Send a Slack notification to a channel or user.",
inputSchema={
"type": "object",
"properties": {
"channel": {"type": "string", "description": "Slack channel name"},
"message": {"type": "string", "description": "Message to send"},
},
"required": ["channel", "message"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
if name == "query_employees":
# Mock database query
conn = sqlite3.connect(":memory:")
# ... run query with arguments ...
results = [{"name": "Alice", "dept": "Engineering", "years": 3}]
return [types.TextContent(type="text", text=json.dumps(results, indent=2))]
elif name == "send_notification":
# Mock Slack call
channel = arguments["channel"]
message = arguments["message"]
print(f"[Slack] #{channel}: {message}")
return [types.TextContent(type="text", text=f"Notification sent to #{channel}")]
raise ValueError(f"Unknown tool: {name}")
# -------------------------------------------------------
# RESOURCES — data the AI can read
# -------------------------------------------------------
@app.list_resources()
async def list_resources() -> list[types.Resource]:
return [
types.Resource(
uri="company://docs/onboarding",
name="Onboarding Guide",
description="Employee onboarding documentation",
mimeType="text/markdown",
),
types.Resource(
uri="company://policies/expense",
name="Expense Policy",
description="Company expense reimbursement policy",
mimeType="text/markdown",
)
]
@app.read_resource()
async def read_resource(uri: str) -> str:
if uri == "company://docs/onboarding":
return "# Onboarding Guide\n\nWelcome to the team!..."
elif uri == "company://policies/expense":
return "# Expense Policy\n\nReimbursable expenses include..."
raise ValueError(f"Unknown resource: {uri}")
# -------------------------------------------------------
# Run the server over stdio transport
# -------------------------------------------------------
async def main():
async with stdio_server() as (read_stream, write_stream):
await app.run(
read_stream,
write_stream,
InitializationOptions(
server_name="company-data-server",
server_version="1.0.0",
capabilities=app.get_capabilities(
notification_options=None,
experimental_capabilities={}
)
)
)
if __name__ == "__main__":
asyncio.run(main())# mcp_client.py
import asyncio
from anthropic import Anthropic
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
client = Anthropic()
async def run_agent_with_mcp(user_message: str) -> str:
"""Run Claude as an agent connected to an MCP server."""
# Connect to the MCP server via stdio
server_params = StdioServerParameters(
command="python",
args=["mcp_server.py"],
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# Initialize the connection
await session.initialize()
# Discover available tools from the server
tools_response = await session.list_tools()
tools = [
{
"name": t.name,
"description": t.description,
"input_schema": t.inputSchema,
}
for t in tools_response.tools
]
print(f"Connected to MCP server. Available tools: {[t['name'] for t in tools]}")
# Run the agentic loop
messages = [{"role": "user", "content": user_message}]
while True:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
tools=tools,
messages=messages,
)
if response.stop_reason == "end_turn":
for block in response.content:
if hasattr(block, "text"):
return block.text
# Execute tool calls via MCP
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type == "tool_use":
# Call the tool on the MCP server
result = await session.call_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result.content[0].text if result.content else "",
})
if tool_results:
messages.append({"role": "user", "content": tool_results})
async def main():
answer = await run_agent_with_mcp(
"List all engineers with more than 2 years at the company, "
"then send a Slack notification to #general about the team."
)
print(answer)
asyncio.run(main())Configuring MCP in Claude Desktop
Claude Desktop has built-in MCP support. Add servers to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"company-data": {
"command": "python",
"args": ["/path/to/mcp_server.py"]
},
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/you/Documents"]
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_xxxx"
}
}
}
}
Claude Desktop will then show available tools from all connected servers in the UI.
Pre-Built MCP Servers
The MCP ecosystem is growing rapidly. Official servers from Anthropic include:
| Server | What it provides |
|---|---|
@modelcontextprotocol/server-filesystem | Read/write local files |
@modelcontextprotocol/server-github | GitHub repos, issues, PRs |
@modelcontextprotocol/server-postgres | Query PostgreSQL databases |
@modelcontextprotocol/server-slack | Slack messages and channels |
@modelcontextprotocol/server-google-maps | Geocoding and directions |
@modelcontextprotocol/server-brave-search | Web search via Brave |
Knowledge check
What is the main advantage of MCP over framework-specific tool implementations?
Summary
MCP brings standardization to the agent tool ecosystem:
- Client-server architecture — agents connect to tool servers via a standard protocol
- Three primitives — Tools (functions), Resources (data), Prompts (templates)
- Any client, any server — write once, use everywhere
- Growing ecosystem — dozens of pre-built servers for common services
- Process isolation — security through separation of concerns
In the next chapter, we'll survey real-world agentic AI use cases — how production systems across industries are deploying agents today.