1. What MCP is¶
Beginner · 11 min read
You already know tool calling: you describe a function as JSON, the model asks to call it, your code runs it. That works well inside one app. MCP is for the moment you want the same tools in many apps (Claude, Cursor, VS Code, your LangGraph agent, a teammate's chatbot) without rewriting the integration for each one.
1.1 The problem: N apps × M tools¶
Without a standard, every AI app writes its own connector for every system:
apps = ["Claude Desktop", "VS Code", "Cursor", "Your support bot", "Your LangGraph agent"]
systems = ["GitHub", "Postgres", "Jira", "Google Drive", "Orders API", "Slack"]
print(f"Custom connectors without a standard: {len(apps)} x {len(systems)} = {len(apps) * len(systems)}")
print(f"With MCP: {len(apps)} clients + {len(systems)} servers = {len(apps) + len(systems)}")
That's the same trick USB and the Language Server Protocol used: agree on one plug, and each side builds its half once. The Model Context Protocol is an open standard, started by Anthropic in late 2024 and now supported by every major model provider, IDE and agent framework.
1.2 Host, client, server¶
flowchart LR
subgraph Host [Host app: Claude, VS Code, your agent]
LLM[LLM] --- C1[MCP client]
LLM --- C2[MCP client]
end
C1 <-->|stdio| S1[Orders server<br/>local process]
C2 <-->|Streamable HTTP| S2[Docs server<br/>remote service]
S1 --- DB[(Orders DB)]
S2 --- KB[(Knowledge base)]
| Piece | What it is | Example |
|---|---|---|
| Host | The AI application the user talks to. Owns the LLM, the UI and the security decisions | Claude Desktop, Claude Code, Cursor, your FastAPI agent |
| Client | A connector inside the host. One client per server | mcp.Client(...) in Python |
| Server | A program that exposes capabilities over MCP | A GitHub server, a Postgres server, your orders_server.py |
The server never talks to the LLM directly. The host decides what to show the model, which tool calls to allow and what to send back.
1.3 What a server exposes¶
| Primitive | Who decides to use it | What it is | Example |
|---|---|---|---|
| Tools | The model | Actions with a JSON-schema input (and optionally output) | get_order_status(order_id), create_issue(title, body) |
| Resources | The app (or user) | Read-only data addressed by a URI | policy://refunds, orders://A100, file:///repo/README.md |
| Prompts | The user | Reusable prompt templates with arguments | /apology_email customer=Asha issue="late delivery" |
That "who decides" column is the one interviewers like. Tools are model-controlled: the LLM picks them during the loop. Resources are application-controlled: the host attaches them as context (for example when a user picks a file). Prompts are user-controlled: they usually appear as slash commands.
Servers can also ask the client for things during a call. Elicitation asks the user a question ("Really cancel order A100?") and is the one you'll use most. Sampling (borrow the host's LLM) and roots (which folders the server may touch) still work but are deprecated in the latest spec (see 1.5).
1.4 The wire format: JSON-RPC 2.0¶
Every MCP message is a JSON-RPC 2.0 request, response or notification. Here's what a client sends to list tools and call one, and what comes back:
import json
list_tools = {"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}
call_tool = {
"jsonrpc": "2.0", "id": 2, "method": "tools/call",
"params": {"name": "get_order_status", "arguments": {"order_id": "A100"}},
}
tool_result = {
"jsonrpc": "2.0", "id": 2,
"result": {
"content": [{"type": "text", "text": '{"status": "shipped", "eta": "Friday"}'}],
"structuredContent": {"status": "shipped", "eta": "Friday"},
"isError": False,
"resultType": "complete",
},
}
for msg in (list_tools, call_tool, tool_result):
print(json.dumps(msg))
{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}
{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "get_order_status", "arguments": {"order_id": "A100"}}}
{"jsonrpc": "2.0", "id": 2, "result": {"content": [{"type": "text", "text": "{\"status\": \"shipped\", \"eta\": \"Friday\"}"}], "structuredContent": {"status": "shipped", "eta": "Friday"}, "isError": false, "resultType": "complete"}}
Three things to notice:
- The
idties a response to its request, so several calls can be in flight at once. - A tool result carries
content(text, images, audio, links for the model to read) and, optionally,structuredContent(typed JSON your code can use directly). isError: truemeans "the tool ran and failed", and the model should read the message and try something else. A JSON-RPCerrorobject means "the request itself was wrong" (unknown method, bad params).
1.5 Protocol versions: handshake era vs stateless era¶
MCP versions are dates. The one change you must know about is the 2026-07-28 revision:
| 2024-11-05 → 2025-11-25 ("handshake era") | 2026-07-28 ("stateless") | |
|---|---|---|
| Starting a connection | initialize request + notifications/initialized, then a long-lived session |
No handshake. Optional server/discover to see versions and capabilities |
| Where version and capabilities go | Agreed once in initialize |
Sent in every request's _meta |
| HTTP sessions | Mcp-Session-Id header, sticky sessions |
Removed: any server replica can answer any request |
| Server asking the client (elicitation) | Server sends its own request mid-call | Multi round-trip: server returns resultType: "input_required", client retries with the answer |
| Change notifications | resources/subscribe, HTTP GET stream |
subscriptions/listen |
| Sampling, roots, logging | Active | Deprecated (still work for at least 12 months) |
Here's the same tools/call in both eras:
handshake_era = [
{"jsonrpc": "2.0", "id": 0, "method": "initialize",
"params": {"protocolVersion": "2025-11-25", "capabilities": {"elicitation": {}},
"clientInfo": {"name": "my-agent", "version": "1.0"}}},
{"jsonrpc": "2.0", "method": "notifications/initialized"},
{"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {"name": "get_order_status", "arguments": {"order_id": "A100"}}},
]
stateless_era = [
{"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {"name": "get_order_status", "arguments": {"order_id": "A100"},
"_meta": {"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {"elicitation": {}},
"io.modelcontextprotocol/clientInfo": {"name": "my-agent", "version": "1.0"}}}},
]
print(f"Handshake era: {len(handshake_era)} messages, methods {[m['method'] for m in handshake_era]}")
print(f"Stateless era: {len(stateless_era)} message, methods {[m['method'] for m in stateless_era]}")
Handshake era: 3 messages, methods ['initialize', 'notifications/initialized', 'tools/call']
Stateless era: 1 message, methods ['tools/call']
Why the change? Remote MCP servers run behind load balancers. A session pinned to one machine made them hard to
scale and restart. Stateless requests let a server work like any other HTTP API. The official SDKs speak both
eras: a modern client probes server/discover and falls back to initialize on older servers, so you'll meet
both in real systems.
Versions on these pages
Examples use the 2026-07-28 spec and the official Python SDK mcp 2.x, where the server class is
MCPServer (it was called FastMCP in mcp 1.x). Many tutorials still show from mcp.server.fastmcp import
FastMCP. That's the same idea with the old name.
1.6 MCP vs plain function calling¶
MCP doesn't replace tool calling. The model still sees a tool name, a description and a JSON schema. MCP standardises where those tools come from and how they're run.
| Plain function calling | MCP | |
|---|---|---|
| Where tools live | Inside your app's code | In a separate server process or service |
| Reuse across apps | Copy the code | Any MCP host can connect |
| Discovery | Hard-coded list | tools/list at runtime |
| Beyond tools | No | Resources, prompts, elicitation |
| Extra cost | None | A process or network hop, one more thing to secure |
Use plain tools when the tools belong to one app and nobody else needs them. Use MCP when tools are shared across apps or teams, when you want users to plug your system into Claude, Cursor or ChatGPT, or when a ready-made MCP server already exists for the system (GitHub, Postgres, Slack, Sentry and hundreds more).
Interview questions¶
What is MCP, in one sentence?
An open protocol that standardises how AI applications connect to external tools and data: servers expose tools, resources and prompts over JSON-RPC, and any MCP-capable host can use them, turning N×M custom integrations into N+M.
Explain host, client and server.
The host is the AI app the user uses; it owns the LLM and the security decisions. Inside it, one MCP client per connection talks to one MCP server. The server wraps a system (a database, an API, files) and exposes it as tools, resources and prompts. The server never talks to the model directly.
Tools vs resources vs prompts?
Tools are model-controlled actions with a JSON schema; the LLM decides to call them. Resources are application-controlled, read-only data addressed by URIs that the host attaches as context. Prompts are user-controlled templates, usually shown as slash commands.
What changed in the 2026-07-28 MCP spec?
MCP became stateless: no initialize handshake and no HTTP sessions. Every request carries its protocol version
and client capabilities in _meta, and servers must implement server/discover. Server-to-client requests such
as elicitation became multi round-trip (the server returns input_required, the client retries with the answer).
Sampling, roots and logging were deprecated. The goal was to let remote servers scale like ordinary HTTP APIs.
When would you not use MCP?
When the tools are used by a single app and nobody else needs them. A plain function-calling loop is simpler, faster and has less to secure. MCP pays off when tools are shared across apps or teams, or when you want external hosts to connect to your system.
Practice¶
- Add a seventh system to 1.1 and work out how many connectors each approach needs.
- Write the JSON-RPC message for
resources/readwith{"uri": "policy://refunds"}in the stateless format. - Pick three systems you use at work and decide for each: plain tools or MCP server? Why?
Next: 2. The protocol from scratch Back to: MCP overview · Notes overview