Skip to content

Model Context Protocol (MCP)#

The Model Context Protocol MCP is an open standard from Anthropic for connecting an LLM host (Claude Desktop, an IDE, a custom agent) to external tools, resources, and prompts. It replaces the per-app, ad-hoc tool wiring that every product was reinventing.

flowchart LR
  H[Host<br/>Claude Desktop / IDE / agent] -->|MCP client| C[Client]
  C <-->|stdio or HTTP+SSE| S1[MCP Server<br/>filesystem]
  C <-->|stdio or HTTP+SSE| S2[MCP Server<br/>postgres]
  C <-->|stdio or HTTP+SSE| S3[MCP Server<br/>github]

    classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
    classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
    classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
    class H,C client;
    class S1,S2,S3 external;

An MCP server exposes three primitives: tools (callable functions), resources (readable data like files or DB rows), and prompts (reusable templates). An MCP client lives inside the host and speaks JSON-RPC over a transport, typically stdio for local servers or HTTP plus Server-Sent Events for remote ones.

The win is interop. Write a Postgres MCP server once, and Claude Desktop, your CLI agent, and a teammate's IDE plugin can all use it without bespoke glue. Compare this to the alternative: each app defines its own tool schema format, auth flow, and dispatch logic, and integrations multiply as N apps x M tools.

MCP is to LLM tools roughly what the Language Server Protocol is to editors. The same standardization payoff: one server, many clients.

Problem statement

Every LLM app reinvents tool integration: schemas, auth, transport, lifecycle. A user who wants Claude Desktop, their IDE, and a custom agent to all read from Postgres must wire three separate plugins. Design a protocol that lets one tool provider serve many LLM hosts, and lets one host consume many providers.

The Model Context Protocol MCP is an open spec, released by Anthropic in late 2024, that defines a JSON-RPC interface between an LLM host application and external servers that expose context. By the time a model decides to call a tool, the host has already enumerated capabilities and surfaced them via the standard tool-use mechanism, so the model itself does not need to know MCP exists.

Architecture#

flowchart TB
  subgraph Host[Host process e.g. Claude Desktop]
    LLM[LLM]
    direction LR
    subgraph Clients[MCP clients, one per server]
      C1[Client A]
      C2[Client B]
      C3[Client C]
    end
  end
  C1 <-->|stdio| S1[Server: filesystem<br/>local process]
  C2 <-->|stdio| S2[Server: postgres<br/>local process]
  C3 <-->|HTTP + SSE| S3[Server: github<br/>remote service]

  LLM -. uses .- C1
  LLM -. uses .- C2
  LLM -. uses .- C3

    classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
    classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
    classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
    class LLM,C1,C2,C3 client;
    class Host service;
    class S1,S2,S3 external;

Three roles:

Role What it does Example
Host The LLM app the user sees Claude Desktop, Cursor, Zed, custom agent
Client Lives inside the host, one per server, manages the connection Anthropic's mcp SDK
Server Standalone process or service exposing tools, resources, prompts mcp-server-postgres, mcp-server-github

Each host typically runs one client per server. A host with five MCP servers has five client instances, five transports, and five capability sets.

Transports#

MCP defines two standard transports:

  1. stdio: server is a subprocess; JSON-RPC messages on stdin and stdout. Used for local tools (filesystem, git, sqlite). Zero network surface.
  2. HTTP + SSE: server is a remote service. Client POSTs requests; server pushes notifications via Server-Sent Events. Used for hosted servers and cross-machine deployments.

A newer Streamable HTTP transport is replacing classic SSE in the spec, consolidating both directions into a single connection.

sequenceDiagram
  autonumber
  participant H as Host
  participant C as Client
  participant S as Server
  H->>C: start(server config)
  C->>S: initialize {protocolVersion, capabilities}
  S-->>C: initialized {capabilities, serverInfo}
  C->>S: tools/list
  S-->>C: [tool definitions]
  C->>S: resources/list
  S-->>C: [resource URIs]
  Note over H,S: Steady state. Host enumerates capabilities.
  H->>C: user prompts LLM, model emits tool_use
  C->>S: tools/call {name, args}
  S-->>C: tool result
  C-->>H: result back to LLM context

Primitives#

Tools#

Same shape as native LLM tool calling: name, description, JSON Schema for input. The MCP server registers them; the host surfaces them to the LLM via whichever API (Anthropic, OpenAI, Gemini) the host uses. The model never sees that the tool came from an MCP server.

# A minimal MCP server in Python using the official SDK
from mcp.server import Server
from mcp.types import Tool, TextContent

server = Server("postgres-mcp")

@server.list_tools()
async def list_tools() -> list[Tool]:
    return [
        Tool(
            name="query",
            description="Run a read-only SQL query against the database.",
            inputSchema={
                "type": "object",
                "properties": {"sql": {"type": "string"}},
                "required": ["sql"],
            },
        )
    ]

@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    if name == "query":
        rows = await db.fetch(arguments["sql"])
        return [TextContent(type="text", text=str(rows))]
    raise ValueError(f"unknown tool: {name}")

Resources#

Resources are addressable, readable blobs: a file, a DB row, a wiki page. Identified by URI like postgres://db/users/42 or file:///work/notes.md. Hosts can subscribe to changes.

@server.list_resources()
async def list_resources():
    return [
        Resource(
            uri="postgres://db/schema",
            name="Database schema",
            mimeType="application/json",
        )
    ]

@server.read_resource()
async def read_resource(uri: str) -> str:
    if uri == "postgres://db/schema":
        return json.dumps(await db.introspect())

Why have resources at all when you have tools? Resources let the host (and user) decide what to attach to a conversation, rather than the model having to discover it via tool calls. A user can drag a resource into a Claude Desktop chat; the host reads it once and pastes its content into context.

Prompts#

Reusable, parameterized prompt templates registered by a server. A git-commit-message server might expose a prompt that takes a diff and returns the canonical instructions for writing a Conventional Commits message. The user picks the prompt from a UI, the host fills in parameters, the LLM runs it.

Capability negotiation#

flowchart LR
  C[Client] -->|initialize<br/>protocolVersion + capabilities| S[Server]
  S -->|initialized<br/>protocolVersion + capabilities| C
  C -->|tools/list resources/list prompts/list| S
  S -->|definitions| C

    classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
    classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
    class C client;
    class S external;

Each side advertises what it supports: tools, resources, prompts, logging, sampling. The intersection determines what features are usable for that session. A server can omit prompts if it has none; the client will not call prompts/list.

Sampling: server-initiated LLM calls#

A unique MCP feature: a server can ask the host's LLM to do inference on its behalf via sampling/createMessage. Example: a web-search server retrieves pages, then calls back to the LLM to summarize them before returning. The host controls cost and consent (user can deny). This is what makes MCP servers feel "smart" rather than just dumb data sources.

MCP vs ad-hoc tool calling#

Aspect Ad-hoc tools MCP
Tool definition Hardcoded in app Discovered at runtime
Reuse across apps None; rewrite per host One server, many hosts
Auth and lifecycle App-specific Standard initialize handshake
Transport App-specific HTTP stdio or HTTP+SSE
Resources / prompts No standard First-class
User adds new tool App release required Drop in a server config

The cost: an extra hop and a process. For latency-sensitive paths, native tool calling is still faster. MCP shines when the tool inventory is large, heterogeneous, or user-extensible.

Security considerations#

MCP servers are arbitrary code. A user installing mcp-server-postgres is giving it credentials and trust. Sandbox where possible: run stdio servers as a less-privileged user, use OS sandboxes (App Sandbox on macOS, Bubblewrap on Linux), and audit the resources a server reads. Remote MCP servers should use OAuth or signed tokens, never long-lived API keys in plaintext config.

The model is also a risk surface. A server returning attacker-controlled tool results (e.g. a web page with hidden instructions) can drive prompt injection into the next inference step. Strip or fence untrusted content before re-feeding to the LLM.

Quick reference#

Mental model#

LSP for LLM tools. One server, many hosts.

Roles#

  • Host: the LLM app (Claude Desktop, IDE, agent)
  • Client: inside the host, one per server
  • Server: standalone process exposing tools, resources, prompts

Transports#

  • stdio: subprocess, JSON-RPC over stdin and stdout, local
  • HTTP + SSE (or Streamable HTTP): remote services

Primitives#

Primitive Purpose
Tools Callable functions (same shape as native tool calling)
Resources Addressable readable blobs (URIs)
Prompts Reusable parameterized templates

Handshake#

  1. initialize (versions, capabilities)
  2. initialized
  3. tools/list, resources/list, prompts/list
  4. Steady state: tools/call, resources/read, prompts/get

Sampling#

Server can ask host's LLM to do inference via sampling/createMessage. Host controls cost and consent.

When MCP beats ad-hoc#

  • Large or user-extensible tool inventory
  • Cross-app reuse (Claude Desktop + IDE + agent)
  • Standard auth and lifecycle

When ad-hoc beats MCP#

  • Single-app, fixed toolset
  • Latency-critical (extra process hop)
  • Embedded inside the host code anyway

Security#

  • Server = arbitrary code; sandbox where possible
  • Remote: OAuth or signed tokens, not long-lived keys
  • Strip untrusted server output before re-feeding LLM (prompt injection)

Refs#

  • spec: modelcontextprotocol.io
  • SDKs: @modelcontextprotocol/sdk (TS), mcp (Python)
  • Anthropic MCP announcement (Nov 2024)

FAQ#

What is the Model Context Protocol?#

MCP is an open standard from Anthropic that lets an LLM host like Claude Desktop or an IDE connect to external tool, resource, and prompt providers through a uniform JSON-RPC interface.

How is MCP different from function calling?#

Function calling is a per-request schema embedded in a single LLM API call. MCP is a transport and discovery layer where servers advertise tools, resources, and prompts that any host can connect to.

What transports does MCP support?#

MCP supports stdio for local processes and HTTP plus Server-Sent Events for remote servers. Stdio is common for desktop integrations, HTTP+SSE for hosted services and multi-tenant deployments.

How do you build an MCP server?#

Use the official MCP SDK in TypeScript or Python, declare the tools and resources you expose, and run the server over stdio or HTTP. Most servers are under 200 lines of code for the basics.

Is MCP only for Claude?#

No. The protocol is open and several IDEs, agents, and runtimes now act as MCP hosts. Servers written once work across any host that speaks the protocol.

Video walkthrough

Introduction to the Model Context Protocol (MCP) : via Anthropic