Model Context Protocol (MCP)
A standard protocol for connecting agents to tool ecosystems. Servers, clients, capabilities.
What is MCP?
The Model Context Protocol (MCP) is an open standard for connecting AI applications to external tools, data sources and capabilities. An MCP server exposes tools (functions), resources (data), and prompts (templates). An MCP client (your agent) connects to servers and uses what they expose.
MCP decouples tools from agents: write a server once, any MCP-compatible agent can use it. This enables tool ecosystems — a community of reusable tool servers.
Why MCP matters
Before MCP, every agent framework had its own tool format. Switching frameworks meant rewriting tools. MCP standardises the interface, so a Postgres MCP server works with any MCP client. This unlocks: reusable tool servers, vendor-neutral ecosystems, and rapid integration of new capabilities.
MCP architecture
MCP server exposes: tools (callable functions with schemas), resources (readable data), prompts (parameterised templates). MCP client (your agent) connects, discovers capabilities, and calls them. Communication via JSON-RPC over stdio or HTTP. The agent treats MCP tools like native tools — the protocol is transparent to the model.
An MCP server and client
# Server: exposes a Postgres query tool
from mcp.server import Server
from mcp.types import Tool
server = Server("postgres-tools")
@server.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="query_postgres",
description="Run a read-only SQL query against the products database. Use for product/inventory questions.",
inputSchema={
"type": "object",
"properties": {
"sql": {"type": "string", "description": "SELECT query"}
},
"required": ["sql"],
},
),
]
@server.call_tool()
async def call_tool(name: str, args: dict) -> str:
if name == "query_postgres":
sql = args["sql"]
if not sql.strip().upper().startswith("SELECT"):
return json.dumps({"error": "Read-only: SELECT only"})
rows = await db.fetch(sql)
return json.dumps([dict(r) for r in rows])
# Client: agent uses MCP tools
from mcp.client import Client
async def run_agent_with_mcp(task: str):
client = Client()
# Connect to multiple MCP servers — tool ecosystem
await client.connect_stdio("postgres-tools", command=["python", "mcp.py"])
await client.connect_stdio("filesystem-tools", command=["python", "fs_mcp.py"])
# Discover all tools across servers
tools = await client.list_tools()
# tools is a unified list — agent doesn't care which server hosts which
response = await llm_call(
messages=[{"role": "user", "content": task}],
tools=[t.to_openai_format() for t in tools],
)
# Execute tool calls — client routes to the right server
for tc in response.tool_calls:
result = await client.call_tool(tc.name, tc.args)
# feed back as observation...Experiment: tool ecosystem
See how MCP enables a reusable tool ecosystem.
What to observe
MCP decouples tools from agents. Write a server once; any MCP-compatible agent uses it. For multi-agent systems, this eliminates tool duplication and drift. For single agents, the overhead may not be worth it yet — but the ecosystem is growing.
Production MCP
Production MCP: pin server versions, enforce read-only where possible, audit every tool call, sandbox server processes (don't trust arbitrary servers), and version the capabilities you expose. For external servers (community), review the source before connecting — they run with your agent's privileges.
Challenge
You connect a community MCP server that exposes a 'run_shell' tool. Your agent uses it. What's the security risk and how do you mitigate it?
Production checklist
Production checklist
0 of 8 checked
Knowledge check
What's the primary benefit of MCP over per-agent custom tools?
Complete
You can now build and consume MCP servers for reusable tool ecosystems. This completes the Agentic Workflows series.
Mark this chapter as complete
Track your progress and unlock the next chapter.