What MCP is
The Model Context Protocol was introduced by Anthropic in November 2024 as an open protocol. It defines how an AI application asks an external program what it can do (its tools, data, and prompt templates) and how it uses those capabilities. Messages follow JSON-RPC 2.0, a simple request and response format. The official documentation compares MCP to a USB-C port: one standard connector instead of a custom cable for every device.
Why it is useful
Build once, reuse everywhere
Without a standard, every app needs its own integration for every system. With MCP, one server for your database works in any MCP-compatible host.
Clear boundaries
The server holds credentials and business logic. The model only sees tool descriptions and results, and the host can ask the user before each call.
Shared ecosystem
Open-source and vendor servers exist for common systems such as file systems, Git hosting, databases, and browsers, so you rarely start from zero.
Hosts, clients, and servers
Host
The AI application the user works in, such as a chat app, an IDE, or your own agent. It runs the model and manages permissions.
Client
A connector inside the host. Each client keeps a one-to-one connection with one server.
Server
A program that exposes capabilities. It can run locally (started by the host, talking over stdio) or remotely (over Streamable HTTP).
Tools, resources, and prompts
| Primitive | What it is | Who decides to use it | Example | Methods |
|---|---|---|---|---|
| Tools | Functions that do something or compute something | The model (usually with user approval) | get_course_schedule(module) | tools/list, tools/call |
| Resources | Read-only data identified by a URI | The application or user attaches them as context | course://syllabus | resources/list, resources/read |
| Prompts | Reusable prompt templates with arguments | The user picks them, often as slash commands | summarize_module(module) | prompts/list, prompts/get |
Clients can offer features to servers too, such as sampling (a server asks the host's model to generate text). See the specification for the full list.
Interactive MCP workflow
Follow one question from the user, through the host and an MCP server, to an external system and back. Press Play or use Next.
How MCP connects agents to external systems
The model never talks to the database or API directly. It only requests a tool call. The host decides whether to allow it, the client sends it to the right server, and the server, which you control, performs the real operation with its own credentials and validation. This separation is what makes MCP integrations reusable and easier to secure.
Try it: MCP messages and a real server
Example 1 simulates both sides in one program so you can read the actual JSON-RPC messages. Input: five client requests. Output: each request and the server's reply. It is a simplification for learning and skips transport and parts of the handshake.
"""MCP concepts: a client and server exchanging JSON-RPC messages
The Model Context Protocol (MCP) uses JSON-RPC 2.0 messages between a client
(inside the AI application) and a server (which exposes capabilities).
A server can offer three kinds of capabilities:
* Tools: functions the model can call (tools/list, tools/call)
* Resources: data the application can read (resources/list, resources/read)
* Prompts: reusable prompt templates (prompts/list, prompts/get)
Illustrative simulation: this runs both sides in one process and skips the
transport (stdio or HTTP) and parts of the initialization handshake. Message
shapes are simplified. See the next example for the official Python SDK.
"""
import json
class TinyMCPServer:
"""A toy server exposing one tool, one resource, and one prompt."""
def __init__(self):
self.tools = {
"get_course_schedule": {
"description": "Return the schedule for a course module",
"inputSchema": {"type": "object", "properties": {"module": {"type": "integer"}}, "required": ["module"]},
}
}
self.resources = {"course://syllabus": "Module 1: Evolution of AI ... Module 10: Governance"}
self.prompts = {"summarize_module": "Summarize module {module} for a beginner in 3 bullet points."}
def handle(self, request: dict) -> dict:
method, params = request["method"], request.get("params", {})
if method == "initialize":
result = {"serverInfo": {"name": "course-server"}, "capabilities": {"tools": {}, "resources": {}, "prompts": {}}}
elif method == "tools/list":
result = {"tools": [{"name": n, **spec} for n, spec in self.tools.items()]}
elif method == "tools/call":
module = params["arguments"]["module"]
result = {"content": [{"type": "text", "text": f"Module {module} runs on Sunday at 4 pm"}]}
elif method == "resources/list":
result = {"resources": [{"uri": u} for u in self.resources]}
elif method == "resources/read":
uri = params["uri"]
result = {"contents": [{"uri": uri, "text": self.resources[uri]}]}
elif method == "prompts/get":
text = self.prompts[params["name"]].format(**params["arguments"])
result = {"messages": [{"role": "user", "content": {"type": "text", "text": text}}]}
else:
return {"jsonrpc": "2.0", "id": request["id"], "error": {"code": -32601, "message": "Method not found"}}
return {"jsonrpc": "2.0", "id": request["id"], "result": result}
class TinyMCPClient:
def __init__(self, server):
self.server, self.next_id = server, 1
def request(self, method: str, params: dict | None = None) -> dict:
msg = {"jsonrpc": "2.0", "id": self.next_id, "method": method}
if params:
msg["params"] = params
self.next_id += 1
print("client ->", json.dumps(msg))
reply = self.server.handle(msg)
print("server <-", json.dumps(reply), "\n")
return reply
client = TinyMCPClient(TinyMCPServer())
client.request("initialize")
client.request("tools/list")
client.request("tools/call", {"name": "get_course_schedule", "arguments": {"module": 5}})
client.request("resources/read", {"uri": "course://syllabus"})
client.request("prompts/get", {"name": "summarize_module", "arguments": {"module": 5}})
Example 2 uses the official Python SDK (version 2). The MCPServer class (named FastMCP in version 1 of the SDK) turns decorated Python functions into a tool, a resource, and a prompt. A client then starts the server as a subprocess and calls it. It needs the mcp package, so run it in Colab or locally; the notebook writes the server file and runs the client for you.
"""A real MCP server and client with the official Python SDK
This uses the official `mcp` Python SDK (https://github.com/modelcontextprotocol/python-sdk),
version 2 or later. In version 2 the high-level server class is MCPServer
(it was called FastMCP in version 1).
Part 1 writes a small server with one tool, one resource, and one prompt.
Part 2 starts the server as a subprocess over stdio and calls it from a client.
Run it in Google Colab or locally (Python 3.10+). In a plain script, run the
server and client as two separate files; the notebook does both for you.
"""
# %%
# !pip -q install "mcp[cli]>=2"
# %%
# %%writefile course_server.py
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("Course Helper")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
@mcp.resource("course://modules/{number}")
def module_title(number: int) -> str:
"""Title of a course module."""
titles = {1: "Evolution of AI", 2: "Components of AI Agents", 5: "Model Context Protocol"}
return titles.get(int(number), "Unknown module")
@mcp.prompt()
def explain(topic: str) -> str:
"""A reusable prompt template."""
return f"Explain {topic} to a beginner using one everyday analogy."
if __name__ == "__main__":
mcp.run() # stdio transport by default
# %%
# Part 2: a client that launches the server and talks to it.
import asyncio
import sys
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
params = StdioServerParameters(command=sys.executable, args=["course_server.py"])
# Server log messages go to a file (notebooks do not always expose a real stderr).
with open("server.log", "w") as log:
async with stdio_client(params, errlog=log) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
print("Tools:", [t.name for t in tools.tools])
result = await session.call_tool("add", arguments={"a": 2, "b": 3})
print("add(2, 3) ->", result.content[0].text)
resource = await session.read_resource("course://modules/5")
print("Resource:", resource.contents[0].text)
prompt = await session.get_prompt("explain", arguments={"topic": "MCP"})
print("Prompt:", prompt.messages[0].content.text)
asyncio.run(main())
The official MCP Inspector is a browser-based tool for connecting to a server, listing its capabilities, and calling tools by hand.
Using MCP safely
- Trust: only install servers from sources you trust. A server runs code on your machine or sees your data.
- Least privilege: give each server the minimum access it needs, for example a read-only database user.
- Consent: keep user approval on for tools that change data, send messages, or spend money.
- Untrusted content: tool results and descriptions can contain hidden instructions (prompt injection). Treat them as data.
Official documentation
- Model Context Protocol website
Introduction, concepts, and tutorials for building servers and clients.
- MCP specification
The authoritative protocol definition, including versions and message formats.
- Official Python SDK
Includes the high-level MCPServer class (FastMCP in version 1), client sessions, and examples.
- Reference servers
Example servers maintained alongside the protocol.