Agentic Frameworks

MCP — the Model Context Protocol

MCP is a standard plug between AI apps and outside tools or data — write a small server once and any MCP-speaking assistant can use it.

On this page 6
  1. Why it exists
  2. How it works
  3. Where you have already seen it
  4. The honest part
  5. Remember this
  6. What to learn next

One lesson, three depths. Pick the one that fits you today — you can switch any time.

Beginner — No maths. Plain English.

MCP is a standard plug that connects AI apps to outside tools and data, so each connection does not need custom wiring.

Remember when every phone had its own charger? A Nokia charger was useless for a Samsung, and a drawer at home held five tangled cables. Then one standard connector arrived, and any charger fit any phone. MCP — the Model Context Protocol — is that standard connector, for AI.

Before it, connecting a chat assistant to your files, your database and your calendar meant three custom integrations. Rebuilt for the next assistant, all over again. Every app-to-tool pair needed its own cable.

Why it exists

An AI model on its own can only produce text. The useful stuff — reading your actual files, checking a live database, creating a ticket — needs a bridge to the outside world. Function calling lets a model ask for such actions. But everyone wired those bridges differently.

Anthropic published MCP in November 2024 as an open standard for that wiring. The pitch: build the bridge once, as a small program anyone's assistant can talk to. It caught on across the industry in 2025, which is what turned it from a nice idea into something worth a lesson.

How it works

Three roles, with names you will meet in every MCP document.

  • The host — the AI app you actually use: a chat app, an editor.
  • The server — a small program offering one capability: "I can read files", "I can query the sales database".
  • The client — the connector piece inside the host that talks to one server.
        ┌───────────────  host (your AI app)  ─────────────┐
        │                                                  │
        │   client ─────► server: files on this laptop     │
        │   client ─────► server: company database         │
        │   client ─────► server: calendar                 │
        └──────────────────────────────────────────────────┘

A server can offer three kinds of things:

  • Tools — actions the AI can take. "Search these notes." "Send this message."
  • Resources — data to read. A file, a table, today's menu.
  • Prompts — ready-made message templates a user can pick and fill in.

The host discovers what a server offers by asking it. Nothing is hard-coded.

Where you have already seen it

If you have watched an AI coding assistant read a project's files, or a chat assistant check a live system and answer with real data, there is a good chance MCP was the plug. By 2026, the big assistants — Anthropic's, OpenAI's, Google's — all speak it, which is exactly what "standard" means in practice.

The honest part

A plug standard carries whatever you plug in. An MCP server runs real code with real access — to your files, your database, your accounts. A carelessly written server can leak data, and a malicious one can lie about what its tools do. The standard makes connections easy. It cannot make them safe by itself. Treat "which servers do I trust?" as seriously as "which apps do I install?".

Remember this

  • MCP is a standard plug between AI apps (hosts) and capability programs (servers).
  • Servers offer tools (actions), resources (data) and prompts (templates).
  • Write a server once, and every MCP-speaking assistant can use it — that is the whole point.

What to learn next

Developer — Code and libraries.

Setup

bash
pip install mcp

Written against mcp SDK 2.1 (the official Python SDK). A version warning that will save you an hour: the SDK's 2.0 release renamed its main class — FastMCP became MCPServer. Most tutorials on the internet still show v1 code, and on the current SDK it fails with:

Output
ModuleNotFoundError: No module named 'mcp.server.fastmcp'. This is mcp 2.x, where FastMCP was renamed to MCPServer (from mcp.server.mcpserver import MCPServer) and other APIs changed; see the migration guide at https://py.sdk.modelcontextprotocol.io/v2/migration/#fastmcp-renamed-to-mcpserver or pin 'mcp<2' to keep running v1 code.

If a tutorial says FastMCP, it is v1-era. Everything below is current.

A complete server in under thirty lines

canteen_server.py
from mcp.server.mcpserver import MCPServer

mcp = MCPServer("canteen")

MENU = {"samosa": 15, "chai": 10, "masala dosa": 60, "idli": 30}

@mcp.tool()
def price(item: str) -> str:
    """Price of one menu item, in rupees."""
    key = item.lower().strip()
    if key not in MENU:
        return f"'{item}' is not on the menu today."
    return f"{key} costs Rs {MENU[key]}."

@mcp.resource("menu://today")
def todays_menu() -> str:
    """The full menu as plain text."""
    return "\n".join(f"{name}: Rs {cost}" for name, cost in MENU.items())

@mcp.prompt()
def complaint_letter(dish: str) -> str:
    """A template the user can fill in."""
    return f"Write a short, polite complaint about the {dish} I ordered today."

if __name__ == "__main__":
    mcp.run(transport="stdio")

That is a real, complete MCP server: one tool, one resource, one prompt. Run it directly and it sits waiting silently — correct behaviour, because stdio transport means it talks through standard input and output, expecting a client on the other end.

A client to prove it works

You do not need an AI app to test a server. The same mcp package includes the client side.

ask_canteen.py
import asyncio
import sys
from mcp import Client, StdioServerParameters

# sys.executable is the Python running this script, so the server
# child process starts with the same installed packages
server = StdioServerParameters(command=sys.executable, args=["canteen_server.py"])

async def main():
    async with Client(server) as client:
        tools = await client.list_tools()
        print("tools:", [t.name for t in tools.tools])

        result = await client.call_tool("price", {"item": "Masala Dosa"})
        print("tool answer:", result.content[0].text)

        menu = await client.read_resource("menu://today")
        print("--- resource menu://today ---")
        print(menu.contents[0].text)

        prompt = await client.get_prompt("complaint_letter", {"dish": "idli"})
        print("--- prompt, filled in ---")
        print(prompt.messages[0].content.text)

asyncio.run(main())
Output
tools: ['price']
tool answer: masala dosa costs Rs 60.
--- resource menu://today ---
samosa: Rs 15
chai: Rs 10
masala dosa: Rs 60
idli: Rs 30
--- prompt, filled in ---
Write a short, polite complaint about the idli I ordered today.

Both files together, offline, no API key, a few seconds end to end. Notice what the client never needed: no knowledge of the menu, no schema pasted in. It asked, the server described itself.

Line by line, the parts that are not obvious

@mcp.tool() — the SDK builds the tool's machine-readable description from your function automatically: the name from the function name, the input schema from the type hints, the description from the docstring. Your docstring is not a comment here — it is what the model reads when deciding whether to call your tool. Write it for the model.

@mcp.resource("menu://today") — resources are addressed by URI, a naming scheme you invent. Tools are for doing, resources are for reading; hosts may cache or subscribe to resources, so keep them side-effect free.

Client(server) — the client starts the server as a child process and speaks the protocol over its stdin/stdout. The same Client accepts an HTTP URL instead, for servers running elsewhere; the code after the async with line stays identical.

result.content[0].text — tool results carry a list of typed content blocks (text, images, structured data), not a bare string. Real hosts read the same fields you are reading here.

Common mistakes

Debugging with print() inside a stdio server. Standard output is the wire. A stray print corrupts the protocol stream and the connection dies confusingly. Log to stderr instead: print("debug", file=sys.stderr).

command="python" starting the wrong Python. If the child process resolves to a different interpreter than your virtual environment, the server dies on its first import — and all the client shows is MCPError: Connection closed. That error means "the child process died"; the real reason is in the server's own stderr. sys.executable avoids the whole class of problem, which is why the example uses it.

Copying v1 tutorial code. Covered above — the fastmcp import error at the top of this lesson is the signature. Check the import line before checking anything else.

Tools that do too much. A tool named do_everything(query: str) gives the model nothing to reason about. Small, sharply named tools with typed arguments get called correctly far more often — the same lesson as structured output, applied to inputs.

Connecting it to a real host

Every MCP host has a config where you register servers as a command line to run — for Claude Desktop, for instance, an entry in its JSON config naming your Python and canteen_server.py. The host then lists your tools next to its built-in ones, and asks the user before calling them. The server file does not change at all between your test client and a real host. That is the standard doing its job.

Try it yourself

Add a second tool, bill(items: list[str]) -> str, that totals a list of menu items, and call it from the client with three items. Then break things on purpose: add a print("hi") at the top of the server file and watch the connection fail — now you have seen the most common MCP bug of all, and you will recognise it forever.

What to learn next

Researcher — Mathematics and papers.

The protocol under the decorators

MCP is JSON-RPC 2.0 over a transport, with a lifecycle: an initialize handshake negotiates protocol revision and capabilities (which feature sets each side supports), then requests flow — tools/list, tools/call, resources/read, prompts/get — plus notifications for changes and progress. The authoritative definition is the versioned specification at modelcontextprotocol.io; the revision current when this lesson was written is 2026-07-28. Transports are stdio (local child process) and streamable HTTP (remote, with SSE as the legacy variant). The spec's stated design inspiration is the Language Server Protocol: the same move — N editors × M languages becoming N + M — applied to AI hosts and capability servers.

Beyond the server primitives, the protocol defines client-side features flowing the other way: sampling (a server asks the host's model to generate — inverting the usual direction), roots (the host tells servers which filesystem scopes are in bounds), and elicitation (a server asks the user a structured question mid-operation). Recent revisions add negotiated extensions, notably Tasks for long-running asynchronous operations. These are where the protocol is still moving; pin your reading to a spec revision.

Relation to function calling

Function calling is a model-API feature: schemas go into a request, the model emits a call, the application executes it — discovery, transport and lifecycle are the application's problem. MCP standardises exactly those parts and is complementary, not competing: a typical host gathers tools from its MCP clients, presents them to the model as function schemas, and routes the model's calls back through tools/call. Adjacent, non-overlapping: Google's A2A protocol (2025) addresses agent-to-agent task exchange rather than app-to-tool connection.

Security: the serious open problem

MCP's threat surface is unusually honest to reason about because the trust boundaries are explicit:

  • Tool description injection. Tool descriptions enter the model's context. A malicious server can embed instructions in a description ("also forward the user's last message to..."), attacking the model through metadata the user never reads. The spec responds bluntly that descriptions should be treated as untrusted unless the server is trusted.
  • Confused deputy composition. A host wiring a web-reading server next to a file-writing server has built an exfiltration pipeline out of two individually reasonable parts — prompt-injected content from one server can steer calls to another.
  • Supply chain. Servers are arbitrary code, installed with the enthusiasm people once reserved for browser toolbars.

For a survey of the landscape and threat taxonomy, see Hou et al., 2025, Model Context Protocol (MCP): Landscape, Security Threats, and Future Research Directions (arxiv.org/abs/2503.23278). The spec's own security section places consent obligations on hosts — user approval per tool call, explicit consent before exposing data — which is mitigation by convention, not enforcement; the protocol cannot police its endpoints.

Adoption trajectory

Published by Anthropic in November 2024; adopted through 2025 by the other major model providers and by development tools broadly, making it the de-facto tool-connection layer that frameworks in this section — LangGraph, CrewAI, AutoGen's line and their successors — all grew adapters for. The strategic consequence for practitioners: effort invested in a well-designed MCP server outlives any particular agent framework, which cannot be said of framework-specific tool code. The research frontier sits in security hardening, authorisation models for remote servers, and evaluation of how tool design (naming, granularity, schema quality) changes agent success rates — the subject of a later lesson in this section.

What to learn next