Every AI assistant needs tools. ChatGPT needs to search the web. Claude needs to read your files. Your custom agent needs to query your database, call APIs, send emails.
But every tool integration is custom. OpenAI has one format. Anthropic has another. LangChain has five. Your framework has its own. You write glue code for each tool, for each model, for each framework.
It's the smartphone charger problem β everyone had their own cable until USB-C showed up.
MCP (Model Context Protocol) is Anthropic's answer. One open standard for connecting AI models to tools and data. Any tool. Any model. Any framework. Plug and play.The Problem MCP Solves
Before MCP, connecting an AI to your tools looked like this:
Your Agent β Custom OpenAI function definition β Your APIYour Agent β Different LangChain tool wrapper β Same API
Your Agent β Another CrewAI tool class β Same API3 integrations for 1 API. Multiply by 50 tools. Multiply by 5 frameworks.
= 250 custom integrations. All doing the same thing.
With MCP:
Your Agent β MCP Client β MCP Server (your tool)
Any MCP client talks to any MCP server.
Write the server once. Every client works.
It's the difference between NΓM integrations and N+M.
MCP Architecture: Three Primitives
MCP defines three ways for a model to interact with the outside world:
- Tools β Actions the Model Can Take
Tools are functions the model can call. This is what you're used to from function calling, but standardized:
json
{
"name": "query_database",
"description": "Run a SQL query against the production database",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "SQL SELECT query to execute"
}
},
"required": ["query"]}
}
The model decides when to call tools. The user (or client) approves the call. The server executes it and returns results.
- Resources β Data the Model Can Read
Resources are like a filesystem for the model. They provide context without the model needing to "do" anything:
json
{
"uri": "file:///project/README.md",
"name": "Project README",
"mimeType": "text/markdown",
"description": "Main documentation for the project"
}
Resources can be:
Files on disk
Database rows
API responses
Live data feeds
Screenshots
The key difference from tools: resources are read-only context, tools are actions. The model doesn't "call" a resource β it reads it.
- Prompts β Reusable Templates
Prompts are pre-built interaction patterns that users can invoke:
json
{
"name": "code-review",
"description": "Review code for bugs, style, and best practices",
"arguments": [
{
"name": "language",
"description": "Programming language",
"required": true
},
{
"name": "code",
"description": "Code to review",
"required": true
}]
}
When invoked, the prompt returns a structured message sequence that guides the model's behavior.
π§
How MCP Communication Works
MCP uses JSON-RPC 2.0 over two transport types:
Transport: stdio
For local tools β the MCP server runs as a child process:
βββββββββββ stdin/stdout βββββββββββββββ
β MCP β ββββββββββββββ β MCP Server β
β Client β (JSON-RPC) β (your tool) β
β (Claude β β β
β Desktop) β e.g., file β
βββββββββββ β system tool β
βββββββββββββββTransport: SSE (Server-Sent Events) / Streamable HTTP
For remote tools β the MCP server runs on a network:
βββββββββββ HTTP/SSE βββββββββββββββ
β MCP β βββββββββββββββ β MCP Server β
β Client β (JSON-RPC) β (remote) β
β β β β
β β β e.g., GitHubβ
β β β API server β
βββββββββββ βββββββββββββββ
The Protocol Flow
- Client β Server: initialize (negotiate capabilities)
- Server β Client: initialized (confirm)
- Client β Server: tools/list (what tools do you have?)
- Server β Client: [list of tools with schemas]
- Client β Server: resources/list (what data can I read?)
- Server β Client: [list of resources with URIs]
--- User starts chatting ---
- Model decides to use a tool
- Client β Server: tools/call { name: "query_database", arguments: {...} }
- Server β Client: { result: [...rows...] }
- Model incorporates result into response
π
Building an MCP Server
Here's a complete MCP server in Python that provides database tools:
python
mcp_database_server.py
from mcp.server import Server
from mcp.types import Tool, TextContent
import sqlite3
import json
server = Server("database-tools")@server.list_tools()
async def list_tools():
return [
Tool(
name="query",
description="Run a read-only SQL query",
inputSchema={
"type": "object",
"properties": {
"sql": {
"type": "string",
"description": "SQL SELECT query"
}
},
"required": ["sql"]
}
),
Tool(
name="tables",
description="List all tables in the database",
inputSchema={
"type": "object",
"properties": {}
}
)
]@server.call_tool()
async def call_tool(name: str, arguments: dict):
db = sqlite3.connect("app.db")if name == "query":
sql = arguments["sql"]
if not sql.strip().upper().startswith("SELECT"):
return [TextContent(type="text", text="Error: Only SELECT queries allowed")]cursor = db.execute(sql)
columns = [desc[0] for desc in cursor.description]
rows = cursor.fetchall()
result = [dict(zip(columns, row)) for row in rows]
return [TextContent(type="text", text=json.dumps(result, indent=2))]elif name == "tables":
cursor = db.execute(
"SELECT name FROM sqlite_master WHERE type='table'"
)
tables = [row[0] for row in cursor.fetchall()]
return [TextContent(type="text", text=json.dumps(tables))]Run with stdio transport
if __name__ == "__main__":
import asyncio
from mcp.server.stdio import stdio_serverasyncio.run(stdio_server(server))Connecting to Claude Desktop
Add to your Claude Desktop config (claude_desktop_config.json):
json
{
"mcpServers": {
"database": {
"command": "python",
"args": ["mcp_database_server.py"],
"env": {
"DATABASE_PATH": "/path/to/app.db"
}
}}
}
Now Claude can query your database. It sees the tools, knows the schema, and asks for permission before running queries.
Building an MCP Client
If you're building your own agent and want to consume MCP tools:
python
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
# Connect to an MCP server
server_params = StdioServerParameters(
command="python",
args=["mcp_database_server.py"]
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# Initialize
await session.initialize()
# Discover tools
tools = await session.list_tools()
print(f"Available tools: {[t.name for t in tools.tools]}")
# Call a tool
result = await session.call_tool(
"query",
arguments={"sql": "SELECT * FROM users LIMIT 5"}
)
print(f"Result: {result.content[0].text}")MCP Ecosystem: What's Already Built
The MCP ecosystem is growing fast. Here's what exists:
Category MCP Servers What They Do
Files Filesystem, Google Drive, Dropbox Read/write files
| Code | GitHub, GitLab, Linear | Issues, PRs, code search |
|---|---|---|
| Data | PostgreSQL, SQLite, BigQuery | Database queries |
| Web | Brave Search, Puppeteer, Fetch | Web search and scraping |
| Comms | Slack, Gmail, Discord | Messages and email |
| Cloud | AWS, GCP, Kubernetes | Infrastructure management |
| Memory | Qdrant, Pinecone | Vector store operations |
| Dev | Docker, Sentry, Datadog | DevOps and monitoring |
bash
Install official MCP servers via npx
npx -y @modelcontextprotocol/server-filesystem /path/to/dir
npx -y @modelcontextprotocol/server-github
npx -y @modelcontextprotocol/server-postgres postgresql://...
π¬
MCP vs Direct Function Calling
You might ask: why not just use OpenAI function calling directly?
Feature Direct Function Calling MCP
Standard Per-provider (OpenAI, Anthropic, etc.) Universal
| Discovery | Hard-coded in prompt | Dynamic (tools/list) |
|---|---|---|
| Transport | HTTP only | stdio, HTTP, SSE |
| Resources | No concept | First-class primitive |
| Prompts | No concept | First-class primitive |
| Ecosystem | Build everything yourself | Growing library of servers |
| Server reuse | One-off implementations | Write once, use everywhere |
MCP doesn't replace function calling β it standardizes it. An MCP client translates MCP tools into whatever function calling format the underlying model uses (OpenAI, Anthropic, etc.).
π‘οΈ
Security: Human in the Loop
MCP is designed with security in mind:
Tool approval: The client (not the server) decides whether to execute a tool call. Claude Desktop shows you the tool call and waits for approval.
Resource boundaries: Servers declare what resources they expose. The client controls access.
Transport isolation: stdio servers run as sandboxed child processes.
No arbitrary code: The protocol is JSON-RPC β data only, no executable payloads.
User: "Delete all records from the users table"
Claude: "I'll need to run a SQL query to do that."
MCP flow:
- Claude generates: tools/call("query", {sql: "DELETE FROM users"})
- Client shows user: "Claude wants to run: DELETE FROM users"
- User clicks: "Deny" β Human stays in control
π¦
Try It Yourself
bash
Quick start: MCP with Claude Desktop
1. Install an MCP server
npm install -g @modelcontextprotocol/server-filesystem
2. Add to Claude Desktop config
~/Library/Application Support/Claude/claude_desktop_config.json
cat << 'EOF' > claude_desktop_config.json
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/Users/you/projects"
]
}}
}
EOF
3. Restart Claude Desktop
4. Ask Claude: "What files are in my projects directory?"
Claude will use the filesystem MCP server to list files
π
Practical Takeaways
MCP standardizes how AI connects to tools β one protocol, any model, any framework
Three primitives: Tools (actions), Resources (data), Prompts (templates) β covers all integration patterns
JSON-RPC over stdio or HTTP β local tools as child processes, remote tools over the network
Write a server once, every client works β no more NΓM integration problem
Human-in-the-loop by default β the client approves tool calls, not the model
The ecosystem is real β GitHub, Slack, PostgreSQL, filesystem servers already exist
π
What's Next?
Episode 79: OpenAI Function Calling Spec β MCP is the protocol. But what does the actual function calling look like at the API level? OpenAI's spec β JSON schemas, tool_choice, parallel tool calls β is the most widely implemented interface. We'll dissect every parameter.
β Previous
Ep 77: RoPE & Position Encoding
Next β
Ep 79: OpenAI Function Calling Spec
Next: Episode 79 β OpenAI Function Calling Spec
The model can write essays and solve math, but it can't check the weather. Function calling bridges that gap.
This is part of a 98-episode series covering AI engineering from tokens to production deployment.