datarekha

MCP — Model Context Protocol

The emerging standard for plugging tools into agents. Write your integration once; every MCP-compatible client can use it.

6 min read Intermediate Agentic AI Lesson 13 of 71

What you'll learn

  • What MCP is and why it exists
  • The architecture — servers, clients, and the three primitives
  • How a minimal MCP server looks in Python
  • Why this matters for the agent ecosystem

Before you start

A platform team ships a “look up an order” tool for their support agent. Then sales wants it in their internal chat assistant. Then an engineer wants it in Cursor. Then someone files a ticket asking why the Claude Desktop integration disagrees with the production version. Four copies of the same forty-line function, all subtly different. By mid-2025 this was every enterprise team’s reality.

For the first two years of the agent boom, every team built tool integrations from scratch. Want your agent to read GitHub? Write a GitHub tool. Want it in Cursor too? Write the same tool again, in Cursor’s plugin format. Slack? Same code, third format. Each new LLM client meant rebuilding the same connectors.

Model Context Protocol (MCP) is the fix. It’s an open standard, originally proposed by Anthropic and now governed as a vendor-neutral open specification at modelcontextprotocol.io, that defines how an LLM client talks to a tool provider. MCP is not Anthropic-only — any LLM host or agent framework can implement it, and most major ones already do. The bet is simple: write your integration once as an MCP server, and every MCP-compatible client can use it.

MCP-compatible clients include Claude Desktop and Claude Code, Cursor, Cline, Continue, VS Code (via the Copilot Chat MCP adapter), Windsurf, Zed, ChatGPT (announced support), and the major agent frameworks (LangChain, LangGraph, MS Agent Framework, Google ADK). The ecosystem has settled on MCP the way the web settled on HTTP.

TryMCP on the wire

Step through a real MCP handshake — message by message

Click any message chip to expand the actual JSON-RPC payload. The lane shows direction: client is the MCP host (Cursor, Claude Desktop, your agent); server is your orders service. The full handshake is just eight messages.

Click a message above to inspect its payload.

Why it exists

Before MCP, the integration matrix looked like this:

N tools  ×  M LLM clients  =  N×M custom integrations

Every new client meant porting every tool. Every new tool meant shipping in every client’s format. Teams ended up maintaining 4-5 copies of the same connector.

MCP collapses it:

N tools  +  M LLM clients  =  N + M  (each speaks MCP)

The shape is the same idea as USB or LSP (Language Server Protocol). Define the protocol once, and the N×M problem becomes N+M.

The architecture

The current MCP spec separates three roles:

  • MCP host — the AI application (Claude Desktop, Claude Code, Cursor, an agent framework). It coordinates one or more MCP clients.
  • MCP client — a component inside the host that maintains a dedicated connection to one MCP server. One client per server.
  • MCP server — exposes capabilities (your tools, your data). Runs as a process the client launches (local) or as an HTTP service it connects to (remote).

A single host typically runs several clients in parallel — one for filesystem access, one for GitHub, one for your internal database, one for Slack. The host stitches them together; the servers don’t know about each other.

MCP hostClaude Desktop / Cursor / LangGraph agentclient 1 (filesystem)client 2 (github)client 3 (your-db)client N…MCP server: filesystemtools • resourcesMCP server: githubtools • promptsMCP server: your-dbtools • resourcesstdiostreamable HTTPstdioone host • many clients • one server per client

The MCP host coordinates a client per server. Each client speaks JSON-RPC over stdio (local) or streamable HTTP (remote).

The three primitives

An MCP server exposes one or more of three things, each with list, get, and (for tools) call JSON-RPC methods:

  • Tools — executable functions the model can call. Same idea as plain tool-calling, but discoverable via MCP instead of hard-coded in the client. query_database, create_issue, send_message. Model-initiated.
  • Resources — read-only data sources the host can pull into context. Files, database rows, API responses. The host decides when to read them. Application-initiated.
  • Prompts — reusable prompt templates the server publishes for the user to invoke (think slash-commands). The server author packages domain expertise — “here’s the right way to ask my server to do X”. User-initiated.

Tools are the workhorse. Most MCP servers are 90% tools, with a few resources for things like “list current open PRs” and prompts for common workflows.

The spec also defines a few client-exposed primitives that servers can call back into: sampling (server asks the host’s LLM to complete something, so server authors don’t need their own API key), elicitation (server asks the user for input/confirmation), and logging. You won’t need these for most servers; mention them when you do.

A minimal MCP server in Python

The official Python SDK is the mcp package. FastMCP is the high-level builder; the shape is striking in how plain it looks — a decorator on a function, and you’re done. (There are also official SDKs for TypeScript, Java, Kotlin, and C#.)

# A minimal MCP server with FastMCP.
# In production: pip install mcp, then run with: python server.py
# The real import is: from mcp.server.fastmcp import FastMCP
# Here we mock FastMCP so the shape — decorator on a function — is visible.

class FastMCP:
    def __init__(self, name):
        self.name = name
        self._tools = {}
        self._resources = {}

    def tool(self):
        def deco(fn):
            self._tools[fn.__name__] = fn
            return fn
        return deco

    def resource(self, uri):
        def deco(fn):
            self._resources[uri] = fn
            return fn
        return deco

    def run(self):
        print(f"server '{self.name}' would now listen on stdio")
        print(f"  tools:     {list(self._tools.keys())}")
        print(f"  resources: {list(self._resources.keys())}")

server = FastMCP("orders-server")

# 1. A tool — pydoc becomes the description the LLM reads.
@server.tool()
def search_orders(email: str, limit: int = 10) -> dict:
    """Search a customer's recent orders by email address.

    Use this when the user asks about past orders or order status.
    Returns up to 'limit' orders, newest first.
    """
    # In real code: query your DB
    return {"orders": [{"id": "A-101", "total": 42.00, "status": "shipped"}]}

# 2. Another tool
@server.tool()
def refund_order(order_id: str, reason: str) -> dict:
    """Issue a refund for an order. Requires order_id and a reason string.

    This is a destructive action. The client should confirm with the user
    before invoking.
    """
    return {"order_id": order_id, "refunded": True, "reason": reason}

# 3. A resource — read-only data the client can pull into context
@server.resource("orders://recent")
def recent_orders() -> str:
    """The 10 most recent orders across all customers."""
    return "order A-101 ($42.00), order A-102 ($17.50), ..."

# In a real server you'd call server.run() to start the stdio loop.
server.run()

# Try the tool directly:
print()
print(server._tools["search_orders"]("alex@example.com"))
server 'orders-server' would now listen on stdio
  tools:     ['search_orders', 'refund_order']
  resources: ['orders://recent']

{'orders': [{'id': 'A-101', 'total': 42.0, 'status': 'shipped'}]}

Notice what the decorators bought you: search_orders and refund_order are just normal Python functions, yet the server now knows their names, and the real SDK would turn each docstring and type hint into the JSON schema the LLM reads. Calling search_orders directly returns the same dict an MCP client would get back over the wire. The framework handles serialization, schema generation, and the wire protocol — you write a function.

To actually run this in production, you’d:

pip install mcp
python server.py

Then in Claude Desktop’s config (or Cursor’s, or wherever), you point at this server. Suddenly every agent in those clients can use your search_orders and refund_order tools.

Transport and security

MCP defines two production transports (the wire protocol the client and server use to exchange JSON-RPC messages):

  • stdio — the client launches the server as a child process and talks to it over stdin/stdout. This is what Claude Desktop, Claude Code, and Cursor use for local servers. Simple, no network exposure.
  • Streamable HTTP — the current spec for remote servers. HTTP POST for client-to-server messages with optional Server-Sent Events for streaming back. Replaces the older HTTP+SSE-only transport, which is now considered legacy for new servers.

For remote servers, the spec recommends OAuth 2.0 for obtaining bearer tokens, and also allows API keys and custom headers. For stdio servers, auth is process-level — whoever launched the server inherits its capabilities, so any secrets are typically loaded from the host environment or a config file.

Treat any MCP server you write the way you’d treat any API endpoint: scope permissions, rotate tokens, log access.

Why this matters

Three things shifted once MCP took hold:

  • Tool integrations got reusable. A GitHub MCP server written by GitHub itself is now used by Claude Desktop, Cursor, ChatGPT, Windsurf, Zed, and a long tail of agent frameworks. The integration exists once.
  • Internal tools are MCP-ified. Enterprise teams ship MCP servers for their data warehouse, their CRM, their ticket system. Every internal agent — and every developer’s IDE — gets the same access with the same auth story.
  • Agent frameworks adopted it. LangChain, LangGraph, MS Agent Framework, and Google ADK all ship MCP client adapters. You can mix: use LangGraph to orchestrate, point it at three MCP servers, and skip writing connector code entirely.

When you’d write an MCP server vs. just register a tool

It’s still fine — and often correct — to just hand-register tools in your agent code, especially for one-off internal agents. The decision point is reuse:

  • Tool is used in one agent → just register it inline. No MCP needed.
  • Tool is used in multiple agents or clients (IDE, chat, internal bot) → MCP server. Write once, share.
  • Tool wraps a system you don’t own (third-party API) → check if there’s already an MCP server for it. Most popular services have one.

In one breath

  • MCP standardizes how an LLM client connects to a tool provider — write the integration once as a server, and every MCP client can use it.
  • It turns the N×M connector problem (every tool rebuilt for every client) into N+M — the same move USB and LSP made.
  • Architecture: a host runs one client per server; each server exposes capabilities over JSON-RPC via stdio (local) or Streamable HTTP (remote).
  • Three primitives: tools (model-initiated actions), resources (app-initiated read-only data), prompts (user-initiated templates) — most servers are mostly tools.
  • Reach for a server when a tool is reused across clients; and treat any server you install as untrusted code — it runs with your privileges.

Quick check

Quick check

0/3
Q1What problem does MCP solve?
Q2Your team has built a Slack integration tool used only inside one specific internal agent. Should you bother making it an MCP server?
Q3What are the three core primitives an MCP server can expose?

Wrap-up of the series

You’ve now seen Anthropic’s five workflow patterns plus the agent shape, the evaluator-optimizer loop, the production tool-calling contract, and the MCP standard that ties the ecosystem together. From here:

  • Pick a framework (LangChain, LangGraph, MAF, or ADK) and build a tool-using agent against your own data.
  • Wrap your most-used tools as an MCP server so every agent — and every IDE assistant on your team — can use them.
  • Build an eval harness before you ship. The patterns are the easy part; knowing whether your agent is actually working is the hard part.

Sign in to track your progress

Completed lessons, your XP, level, and streak save to your account — it's free and takes a few seconds.

Practice this in an interview

All questions
What is the Model Context Protocol (MCP) and what problem does it solve?

MCP is an open protocol from Anthropic that standardizes how LLM applications discover and connect to external tools, data sources, and prompts through a common client-server interface. It replaces bespoke per-integration glue with a single protocol, so any MCP-compatible host can use any MCP server, and has been adopted across the broader ecosystem.

How do MCP tool poisoning, cross-server shadowing, and rug pulls differ?

Tool poisoning embeds malicious influence in a tool description or result. Cross-server shadowing lets one low-trust server steer use of another server's capability. A rug pull changes a previously reviewed schema, description, implementation, dependency, or destination later. Defend with isolated trust contexts, capability snapshots, change review, sandboxing and runtime authorization.

Why is OAuth token passthrough dangerous in an MCP server?

Forwarding a client token intended for a downstream API erases the MCP server's own audience boundary and can create a confused deputy. The server should accept tokens intended for itself, authorize the client, and obtain a distinct downstream credential with the target audience and minimum scope.

What is tool use or function calling in LLMs, and how do you design good tools for an agent?

Function calling lets an LLM output a structured request to invoke an external function with arguments, which the runtime executes and feeds back, enabling agents to act in the world. Good tool design uses clear names and descriptions, minimal well-typed parameters, narrow single-purpose scope, least privilege, and informative error messages so the model can choose and call them reliably.

Related lessons

Explore further

Skip to content