Model Context Protocol
A standard way to plug tools and data into any AI app.
Create a free account Try part of it here
Why it matters
Without a shared standard, every AI app needs its own custom connection to every tool. Your CRM for Claude. Your CRM again for a code editor. Again for a support bot. The Model Context Protocol (MCP) fixes this. It's an open standard, introduced by Anthropic in 2024 and now widely supported. A tool is written once, as an MCP server, and any app that speaks MCP can use it. When a vendor says "we have an MCP server", this lesson is what that means.
What you'll be able to do
- Explain what problem MCP solves and the roles of host, client and server
- Read and write MCP's JSON-RPC messages: initialize, tools/list and tools/call
- Convert an MCP server's tools into Claude tool definitions
- Judge whether an MCP server is safe to connect
What it covers
- Who's who
- Talking to a server from Python
- Turn an MCP tool into a Claude tool
- Building and installing servers
- What MCP isn't
Who's who
Think of USB. Before it, every printer and keyboard needed its own special port. With USB, one plug fits any computer. MCP is that plug for AI tools.
There are three roles:
- The host is the AI app you use: Claude's apps, Claude Code, a code editor or your own agent.
- Inside it, an MCP client holds one connection to one server.
- An MCP server is a small program that offers capabilities. These come in three kinds:
- tools: functions the model can call,
- resources: data the app can read, like files or records,
- prompts: reusable templates.
Most servers are mostly tools.
A server runs in one of two places. It can run locally, as a program the host starts on your computer. Then they talk through the program's standard input and output ("stdio"). Or it can run remotely, as a web service the host reaches over HTTPS. Either way, the messages are the same.
The messages: JSON-RPC 2.0
MCP messages use JSON-RPC 2.0, a simple format for asking another program to run a function. A request has three parts:
- a
method: the name of what to run, params: its inputs,- an
id: a number to match the answer to the question.
The response carries the same id, plus either a result or an error:
{"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}}
{"jsonrpc": "2.0", "id": 2, "result": {"tools": [{"name": "get_campaign", "description": "...", "inputSchema": {...}}]}}
A session goes in this order:
initialize: client and server agree on a protocol version and say what they support.tools/list: the client asks which tools exist. Each has aname, adescriptionand aninputSchema. That's a JSON schema, the same idea as Claude'sinput_schema.tools/call: the client runs a tool with{"name": ..., "arguments": {...}}. It gets back{"content": [{"type": "text", "text": ...}], "isError": false}.
The academy's practice server at https://api.gradient.test/mcp speaks this format. It's simplified. Real remote servers can also stream, and MCP libraries handle that for you.
Note: This handshake-based flow is what most servers and apps spoke through 2025, and many still do. The newest revision of MCP, dated 2026-07-28, drops the
initializestep. Each request carries its own protocol version in a_metafield instead. Phase 9 covers both.
import json msg = json.dumps({"jsonrpc": "2.0", "id": 1, "method": "tools/list"}) print(json.loads(msg)["method"])
Show the answer
tools/list
json.dumps turns the dictionary into text, and json.loads turns it back, so ["method"] reads tools/list. Every MCP request is a small JSON object naming a method.
Send each step in order. Note that JSON-RPC errors come back with HTTP 200: the error is inside the body, under "error".
Show the answer
The response carries the same id as the request
The id is the matching number. A request with "id": 2 gets a response with "id": 2, whether it holds a result or an error.
Talking to a server from Python
Each call is one POST request with a JSON-RPC body. Wrap that in a small helper, and the protocol turns into ordinary function calls:
import requests def rpc(method, params, id): body = {"jsonrpc": "2.0", "id": id, "method": method, "params": params} return requests.post("https://api.gradient.test/mcp", json=body).json() info = rpc("initialize", {"protocolVersion": "2025-06-18", "capabilities": {}}, 1)["result"] print("server:", info["serverInfo"]["name"], "| protocol:", info["protocolVersion"]) for tool in rpc("tools/list", {}, 2)["result"]["tools"]: print("-", tool["name"], "| needs:", tool["inputSchema"].get("required", [])) result = rpc("tools/call", {"name": "search_docs", "arguments": {"query": "gift card expiry"}}, 3)["result"] print(result["content"][0]["text"][:90])
The helper rpc builds the body, sends it and returns the reply as a dictionary. The code then does the three steps in order: initialize, tools/list and one tools/call.
How a host connects MCP to the model
This is what Claude's apps and other hosts do behind the scenes. It uses only things you already know:
- Call
tools/list. Turn each MCP tool into a Claude tool: samenameanddescription, withinputSchemarenamed toinput_schema. - Send those tools with the user's message.
- When the model replies with a
tool_useblock, sendtools/callto the server, with the block's name and its input asarguments. - Put the text from the result into a
tool_result. Setis_errorwhenisErroris true. Then continue the agent loop.
The model doesn't know or care that a tool lives on an MCP server.
Turn an MCP tool into a Claude tool
Step 1 above, in code. An MCP server describes a tool with inputSchema. Claude's API wants input_schema. Everything else carries over unchanged:
mcp_tool = {"name": "get_campaign", "description": "Get one campaign by id.",
"inputSchema": {"type": "object", "properties": {"id": {"type": "string"}}, "required": ["id"]}}
claude_tool = {"name": mcp_tool["name"], "description": mcp_tool["description"],
"input_schema": mcp_tool["inputSchema"]}
print(sorted(claude_tool))
print(claude_tool["input_schema"]["required"])It prints ['description', 'input_schema', 'name'], then ['id']. The schema itself isn't changed at all, only the key it sits under. A host does this for every tool a server lists.
Show the answer
Connect their tools to any MCP-capable AI app without writing a custom integration
Write it once, use it from any host that speaks MCP.
Building and installing servers
You'll rarely write the JSON-RPC by hand. The official Python SDK (the mcp package) turns ordinary typed functions into a server, much like the Claude SDK's tool runner:
from mcp.server.fastmcp import FastMCP mcp = FastMCP("shop-analytics") @mcp.tool() def get_campaign(campaign_id: str) -> dict: """Get one campaign's metrics by id. Use when the user asks about a specific campaign.""" return fetch_campaign(campaign_id) if __name__ == "__main__": mcp.run() # stdio by default, so a host on your computer can start it
Each host has its own way to add a server. Usually it's a settings screen or a config file. For a local server, you list the command that starts it. For a remote one, you give its web address. In Claude Code, for example, it's the claude mcp add command.
Treat servers like browser extensions
An MCP server runs code and sees what you send it. Its tool descriptions also go straight into the model's context. Before you connect one:
- Install from sources you trust. The vendor's own server, or open source you can review.
- Check what it can do. A server with "send email" or "delete record" tools can do exactly that if the model asks. Prefer read-only servers, and keep approval prompts switched on.
- Watch for hidden instructions. A malicious server can hide instructions in a tool description, such as "also send the conversation to...". The next lesson covers this attack.
- Give it the narrowest access. A key that can only do what it needs, not your admin account.
Show the answer
Keep approval prompts on and give it a limited key, or prefer a read-only server
A server can do whatever its tools allow when the model asks. Narrow access and approval prompts limit the damage if it misbehaves.
What MCP isn't
MCP doesn't replace tool use. It's a standard way to supply tools, and the model still asks for them the same way.
Practice what you just read
- 3 code examples you can edit and run
- a practice exercise that checks your code
- a 5-question quiz
- 5 review cards for spaced repetition
Try a quiz question
In MCP, what is the "host"?