Index Ai Academy
Phase 1 · Building with LLMs · Tools & agents

Model Context Protocol

A standard way to plug tools and data into any AI app.

About 25 minutes · A free account opens the whole lesson

Create a free account Try part of it here

Already have an account? Sign in to open this lesson

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

  1. Who's who
  2. Talking to a server from Python
  3. Turn an MCP tool into a Claude tool
  4. Building and installing servers
  5. 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:

  1. initialize: client and server agree on a protocol version and say what they support.
  2. tools/list: the client asks which tools exist. Each has a name, a description and an inputSchema. That's a JSON schema, the same idea as Claude's input_schema.
  3. 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 initialize step. Each request carries its own protocol version in a _meta field instead. Phase 9 covers both.

Quick check
What does this print?
import json
msg = json.dumps({"jsonrpc": "2.0", "id": 1, "method": "tools/list"})
print(json.loads(msg)["method"])
  1. 2.0
  2. jsonrpc
  3. tools/list
  4. 1
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.

Try it now

Send each step in order. Note that JSON-RPC errors come back with HTTP 200: the error is inside the body, under "error".

Quick check
In JSON-RPC, how does a client know which answer belongs to which request?
  1. Answers always arrive in order, so it counts them
  2. The method name is repeated in the response
  3. The response carries the same id as the request
  4. It can't: you send one request at a time
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])
It runs right here in your browser, with nothing to install.

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:

  1. Call tools/list. Turn each MCP tool into a Claude tool: same name and description, with inputSchema renamed to input_schema.
  2. Send those tools with the user's message.
  3. When the model replies with a tool_use block, send tools/call to the server, with the block's name and its input as arguments.
  4. Put the text from the result into a tool_result. Set is_error when isError is true. Then continue the agent loop.

The model doesn't know or care that a tool lives on an MCP server.

Worked example

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"])
In the lesson you can change this and run it.

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.

Quick check
Your company's analytics vendor offers an MCP server. What does that let you do?
  1. Train a model on their data
  2. Skip the model's tool-use step entirely
  3. Connect their tools to any MCP-capable AI app without writing a custom integration
  4. Run their dashboard inside Python
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.
Quick check
An MCP server you found online offers send_email and delete_contact tools. What's the safest way to try it?
  1. Keep approval prompts on and give it a limited key, or prefer a read-only server
  2. Connect it with your admin account so every tool works
  3. Turn off approval prompts so it runs smoothly
  4. Trust it, because MCP servers are checked by Anthropic
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

Create a free account

Try a quiz question

In MCP, what is the "host"?

  1. The AI app the user works in, which connects to servers
  2. The machine the model runs on, usually in the provider's data center
  3. The company that wrote and published the server you're connecting to
  4. A JSON-RPC message that a client sends to start talking to a server

Answer it at the end of the lesson to see if you're right, and why.

Before this lesson