Model Context Protocol (MCP)
Learn Anthropic's open standard, host/client/server architecture, the 6 core MCP primitives, and tools, resources, and prompt capabilities.
Establish Transport pipe connection
Host client (IDE or AI assistant) connects to the server process over standard output streams.
▶📚 Prerequisites(1)
🎓 Learning objectives
- •Explain the host-client-server topology in Model Context Protocol
- •Detail the 6 core MCP Primitives across Client (Sampling, Roots, Elicitations) and Server (Tools, Resources, Prompts)
- •Write MCP Client and Server implementations from scratch
What is it?
Model Context Protocol (MCP) is an open standard by Anthropic (released Nov 2024) for connecting LLMs to external tools, data sources, and services through a unified interface. Think of it as the USB-C of AI integrations — instead of every AI application implementing custom connectors for every service, any MCP-compatible client (Claude, Cursor, VS Code) can connect to any MCP-compatible server (GitHub, Postgres, Slack, filesystem).
Why it exists
Before MCP, integrating an LLM with external tools required custom implementation for each tool in each application. A team using Claude for internal tools had to write their own file system access, database connectors, and API wrappers from scratch. The same connectors were written dozens of times across the ecosystem. MCP standardizes the interface so connectors can be written once and reused anywhere.
Problem it solves
The integration explosion problem: an AI application that needs to connect to 10 services previously required 10 custom integrations × N applications = N×10 custom connectors. With MCP, each service writes one MCP server, and any MCP client connects to all of them. This is the same insight that made REST APIs, npm, and app stores successful: standardize the interface, let the ecosystem build the implementations.
Intuition
MCP is the USB-C standard for AI. Before USB-C, every device had its own connector — proprietary, incompatible, requiring adapters. USB-C standardized the physical and protocol interface so any device could connect to any cable. MCP does the same for LLM tool integrations.
If you come from Java/Spring Boot: MCP is like JDBC for AI tools. JDBC standardized the interface to databases (connect, query, fetch results) so any Java application could talk to any SQL database without knowing its internals. MCP standardizes the interface to AI tools (list tools, call tool, return result) so any LLM application can use any tool without custom integration code.
If you come from React/Frontend: MCP is like the fetch API spec. Before fetch, every browser had XMLHttpRequest with inconsistent behavior. Fetch standardized the interface for HTTP requests. MCP standardizes the interface for LLM tool calls — same protocol whether you are calling a file system, a database, or a web service.
Analogy
MCP is the standardized electrical outlet of AI tools. A lamp manufacturer does not design a different plug for every country's grid — they build to the standard and trust the infrastructure will be there. An MCP server is the appliance (specific capability); the MCP client is the outlet (LLM application); the MCP protocol is the electrical standard (voltage, frequency, socket shape).
Technical explanation
MCP architecture: client-server model over stdio (for local servers) or HTTP+SSE (for remote servers).
MCP defines 6 primitives:
- Tools: functions the LLM can call (like OpenAI function calling)
- Resources: data the server exposes (files, database rows, API responses)
- Prompts: pre-defined prompt templates with parameters
- Sampling: the server can request LLM completions (reverse direction)
- Roots: filesystem paths the server is allowed to access
- Notifications: async event push from server to client
Transport protocols: stdio: local server runs as subprocess, communication over stdin/stdout. Zero network overhead, appropriate for developer tools (filesystem, git, code analysis) HTTP+SSE: remote server over HTTPS. Server-Sent Events for streaming. Appropriate for cloud services (Slack, GitHub, Notion APIs)
Message format: JSON-RPC 2.0 over the transport. Client → Server: method calls. Server → Client: results and errors.
Security model: each MCP server declares its capabilities upfront. The LLM client presents available tools to the model, which decides whether and how to call them. Servers can enforce authentication (API keys, OAuth). Local stdio servers inherit the process permissions of the host application.
Architecture
MCP system topology:
[Host Application: Claude Desktop / VS Code / Cursor] ↕ MCP Client (built into host) ├── [MCP Server: filesystem] (stdio) │ Tools: read_file, write_file, list_directory │ Resources: file:///path/to/dir ├── [MCP Server: postgres] (stdio) │ Tools: query_database, list_tables, describe_table ├── [MCP Server: github] (HTTP+SSE) │ Tools: create_pr, list_issues, search_repos └── [MCP Server: slack] (HTTP+SSE) Tools: send_message, list_channels, search_messages
When LLM needs a tool: LLM → tool call (JSON-RPC) → MCP Client routes to correct server → MCP Server executes → Result returned to LLM context
Workflow
- Create MCP server: implement the MCP server SDK (Python or TypeScript available)
- Define tools: annotate functions with @mcp.tool() decorator, include description and input_schema
- Expose resources (optional): define static or dynamic data the client can read
- Configure transport: stdio for local tools, HTTP+SSE for cloud services
- Add authentication: validate API keys or OAuth tokens in tool handler
- Register with client: add server config to Claude Desktop config.json, VS Code settings, or Cursor .mcp.json
- Test: the LLM automatically discovers and uses available tools when appropriate
- Publish: list on mcp.so or GitHub to share with the community
Example
MCP server in Python (filesystem access)
from mcp.server import Server, NotificationOptions from mcp.server.models import InitializationOptions from mcp.types import Tool, TextContent import mcp.server.stdio
app = Server("file-server")
@app.list_tools() async def list_tools(): return [Tool( name="read_file", description="Read the contents of a file at the given path", inputSchema={"type": "object", "properties": {"path": {"type": "string", "description": "File path to read"}}, "required": ["path"]} )]
@app.call_tool() async def call_tool(name: str, arguments: dict): if name == "read_file": with open(arguments["path"]) as f: return [TextContent(type="text", text=f.read())]
if name == "main": mcp.server.stdio.run(app, InitializationOptions(server_name="file-server", server_version="1.0"))
Real-world usage
Claude Desktop (Anthropic): ships with built-in MCP client. Users add MCP servers via config file — filesystem, web browsing, coding tools. 1000+ community MCP servers available on mcp.so within 6 months of launch.
Cursor IDE: uses MCP for connecting the code editor AI to databases, documentation, and version control. Teams define project-specific MCP servers in .cursor/mcp.json.
Block (Square): one of the first enterprise adopters — built internal MCP servers for their payment data, customer service systems, and compliance databases. Any LLM-powered internal tool can now query these systems without custom per-tool integration.
Trade-offs
stdio vs HTTP transport: stdio requires the MCP server to run locally (same machine as the LLM client). Fast, zero network latency, no auth needed for local resources. HTTP+SSE enables remote/cloud servers but adds latency and requires authentication. Use stdio for developer tools, HTTP for SaaS integrations.
Tool granularity: fine-grained tools (one tool per operation) give the LLM more precise control but require more tool-selection decisions. Coarse tools (one tool with many parameters) simplify selection but may cause the LLM to use wrong parameter combinations. Aim for "one tool does one thing clearly."
MCP vs direct API integration: MCP adds a protocol layer. For a single-tool integration in one application, direct API calls are simpler. MCP pays off when (a) multiple LLM applications need the same integration, or (b) you want the tool to be discoverable by any MCP-compatible AI client.
Visual explanation
Model Context Protocol Topology: ┌──────────────────────────┐ JSON-RPC 2.0 ┌──────────────────────────┐ │ MCP HOST │ ◄──────────────────────────────► │ MCP SERVER │ │ (Claude Desktop / IDEs) │ Transport: stdio / HTTP-SSE │ (Data Source / Tool Set) │ │ │ │ │ │ ┌────────────────────┐ │ │ ┌────────────────────┐ │ │ │ MCP CLIENT │ │ │ │ • Tools (Call) │ │ │ └────────────────────┘ │ │ │ • Resources(Read)│ │ └──────────────────────────┘ │ │ • Prompts (Get) │ │ │ └────────────────────┘ │ └──────────────────────────┘
Advantages
- —
Standardizes integrations (reduces N*M integrations to N+M)
- —
Dynamic capability discovery — hosts adapt to server capabilities
- —
Security model with explicit permissions and Roots boundaries
- —
Works offline with stdio transport — no network required
Disadvantages
- —
Stdio pipes can crash if subprocess writes random logs to stdout instead of stderr
- —
HTTP+SSE has higher complexity and requires server hosting
- —
No built-in authentication for stdio (relies on OS-level process isolation)
Common mistakes
- —
Writing tool descriptions that are too brief. The LLM decides whether to call a tool based entirely on its name and description. "search" is a bad tool name. "search_internal_knowledge_base(query: str) — searches the company wiki and internal documentation for relevant articles" is a good tool spec.
- —
Returning too much data from tool calls. A database query returning 1000 rows as tool output floods the context window. Always paginate, filter, and summarize tool results server-side: "Found 1000 rows matching query. Showing top 10 by relevance: [summarized results]."
- —
No error handling in tool implementations. If your MCP tool throws an unhandled exception, the LLM receives a cryptic error and often retries in a loop. Return structured error messages: {"error": "database_timeout", "message": "Query exceeded 5s limit, try a more specific filter"}.
- —
Giving the LLM write access to production systems without guardrails. An MCP tool that can delete database records or send emails should require explicit confirmation step — either via a "dry_run" parameter or a separate "confirm_action" tool the LLM must call before execution.
- —
Not versioning your MCP server. When you add, rename, or change tool schemas, LLM applications that cached the tool definitions will break. Version your server ("file-server@1.0") and document breaking changes in a changelog.
🎤 Interview questions
How does the initialize/initialized lifecycle sequence work in the Model Context Protocol?
Why is tool overload problematic for language models, and how can a Server Manager resolve it?