Skip to main content
All AI journeys
A4 · Agent Engineer
Pro

Build your first MCP server

Model Context Protocol from scratch

The new interoperability standard for LLMs and tools — build a working MCP server end to end.

1 chapter authored

12-chapter journey · 1 chapters authored so far

  1. 0MCP — the interoperability standard that turns your LLM into every LLM's toolOne protocol, N clients, M servers — why Anthropic's Model Context Protocol replaced a matrix of custom integrations10 min read

11 more chapters queued for follow-on sessions — this is a multi-session flagship at URL Shortener template quality (~90K chars total target). What's here today is fully authored and reference-quality.

Chapter 0
beginner
10 min read

MCP — the interoperability standard that turns your LLM into every LLM's tool

One protocol, N clients, M servers — why Anthropic's Model Context Protocol replaced a matrix of custom integrations

Every AI-tools company hit the same wall in 2024. You built a great LLM feature. You wanted to give the model access to your users' data — their calendar, their GitHub, their Slack, their Notion, their filesystem. So you built integrations. One for each.

The integrations worked. You shipped. Now Cursor came out. They also wanted calendar + GitHub + Slack + Notion + filesystem access. They built their own integrations. Then Claude Desktop wanted them. Then Zed. Then Cline. Then twenty other coding assistants.

Every AI application had to write the same 40 integrations. Every integration provider had to support N different LLM apps calling their API in slightly different ways. The whole industry was building the same M×N matrix.

MCP is the fix. Model Context Protocol, published by Anthropic in November 2024, is the USB-C of AI tooling: one standard protocol between LLM apps ("clients") and data/tool providers ("servers"). Write your integration once as an MCP server, and every MCP-compatible client (Claude Desktop, Cursor, Zed, Cline, VS Code Copilot, Windsurf, and every LLM app shipped after mid-2025) can use it — with zero custom work on the client side.

If you understand MCP, you understand the interoperability story of every AI product shipping after 2025. And building an MCP server is genuinely small — the entire spec fits in a single afternoon.

The M×N problem MCP solves — and the M+N architecture that replaces it

flowchart TB subgraph before[BEFORE MCP: M × N custom integrations] direction LR C1[Claude Desktop] C2[Cursor] C3[Zed] C4[VS Code AI] S1[GitHub API] S2[Slack API] S3[Notion API] S4[Filesystem] C1 --> S1 C1 --> S2 C1 --> S3 C1 --> S4 C2 --> S1 C2 --> S2 C2 --> S3 C2 --> S4 C3 --> S1 C3 --> S2 C3 --> S3 C3 --> S4 C4 --> S1 C4 --> S2 C4 --> S3 C4 --> S4 end subgraph after[AFTER MCP: M + N via one protocol] direction LR CC1[Claude Desktop] CC2[Cursor] CC3[Zed] CC4[VS Code AI] Bus[[MCP Protocol<br/>JSON-RPC 2.0<br/>stdio or SSE transport]] SS1[GitHub MCP Server] SS2[Slack MCP Server] SS3[Notion MCP Server] SS4[Filesystem MCP Server] CC1 --> Bus CC2 --> Bus CC3 --> Bus CC4 --> Bus Bus --> SS1 Bus --> SS2 Bus --> SS3 Bus --> SS4 end classDef clientNode fill:#dbeafe,stroke:#2563eb,color:#1e3a8a classDef serverNode fill:#dcfce7,stroke:#16a34a,color:#14532d classDef busNode fill:#c4b5fd,stroke:#7c3aed,color:#4c1d95 class C1,C2,C3,C4,CC1,CC2,CC3,CC4 clientNode class S1,S2,S3,S4,SS1,SS2,SS3,SS4 serverNode class Bus busNode

The math: with 10 clients and 100 tools, M×N = 1,000 integrations to build and maintain. With MCP, M+N = 110 pieces (10 clients speak MCP + 100 servers speak MCP). Every client automatically supports every server on the ecosystem. This is exactly why USB, HTTP, and TCP/IP won — a shared protocol is a coordination technology, not a technology technology.

MCP client vs MCP server — the vocabulary that trips everyone up

Two roles. Confusion arises because "client" in MCP is the LLM APPLICATION, not the LLM itself, and "server" is the tool provider, not the LLM inference server.

  • MCP host — the LLM application the user is using (Claude Desktop, Cursor). One host runs one or more MCP clients.
  • MCP client — a component inside the host, one per connected server. Handles one connection to one server.
  • MCP server — a process that exposes tools, resources, and prompts to any MCP client. This is what YOU build in this journey.
text
══════════ MCP HOST / CLIENT / SERVER ══════════ ┌─────────────────── MCP HOST ────────────────────┐ │ Claude Desktop (or Cursor, Zed, VS Code AI...) │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ Client1 │ │ Client2 │ │ Client3 │ │ │ └────┬────┘ └────┬────┘ └────┬────┘ │ └───────┼─────────────┼─────────────┼──────────────┘ ↓ ↓ ↓ stdio pipe SSE/HTTP stdio pipe ↓ ↓ ↓ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ Server │ │ Server │ │ Server │ │ (files) │ │ (GitHub)│ │ (Slack) │ └─────────┘ └─────────┘ └─────────┘ MCP SERVERS (what you build)

You build a server. The host discovers it (via a config file or a registry). The host creates a client. The client connects, calls initialize, then queries tools/list and shows the tools to the LLM as available.

The 3 primitives MCP servers expose

Every MCP server exposes some combination of three primitives. Nothing more. This is the entire surface area of the protocol.

flowchart TD Server([MCP Server]) --> P1 Server --> P2 Server --> P3 P1[TOOLS<br/>Functions the LLM can CALL<br/>Model-controlled<br/>Examples read_file, search_github,<br/>create_slack_message<br/>Similar to OpenAI function calling<br/>but discovered at runtime] P2[RESOURCES<br/>Data the LLM can READ<br/>User- or app-controlled<br/>Examples file:///path/to/doc<br/>github://issue/123<br/>Returned as text or binary<br/>Attached to conversation like an upload] P3[PROMPTS<br/>Templates the USER can INVOKE<br/>User-controlled<br/>Examples /summarize-pr<br/>/generate-tests<br/>Slash commands from the client UI<br/>with parameters] classDef toolNode fill:#dcfce7,stroke:#16a34a,color:#14532d classDef resourceNode fill:#fef3c7,stroke:#d97706,color:#78350f classDef promptNode fill:#dbeafe,stroke:#2563eb,color:#1e3a8a class P1 toolNode class P2 resourceNode class P3 promptNode

The distinction that matters:

  • Tools are model-controlled — the LLM decides when to call them, using the schema you provide. Same shape as OpenAI function calling.
  • Resources are user- or app-controlled — the human picks a resource (e.g. attaches file:///README.md) or the app pre-loads them. The LLM does not decide to fetch them.
  • Prompts are user-controlled slash commands. /summarize-pr 4721 in Cursor triggers a prompt template, which the client expands into a full user message.

Most servers only implement tools. Adding resources is the next tier. Prompts are the least common.

The wire protocol — JSON-RPC 2.0 over stdio or SSE

MCP is JSON-RPC 2.0. Two transport options:

  • stdio — the client spawns the server as a child process, sends JSON-RPC messages on stdin, reads responses from stdout. This is what all local servers use (filesystem, git, sqlite). Zero infrastructure.
  • SSE (Server-Sent Events) over HTTP — for remote servers, hosted on the internet. HTTP POST for requests, SSE for streaming responses. Used for cloud services.

The complete lifecycle:

text
══════════ MCP CONNECTION LIFECYCLE ══════════ 1. Client → Server: initialize { "method": "initialize", "params": { "protocolVersion": "2025-06-18", "capabilities": { ... }, "clientInfo": { name: "Cursor", version: "0.42" } } } 2. Server → Client: capabilities + info { "result": { "protocolVersion": "2025-06-18", "capabilities": { "tools": {}, "resources": {} }, "serverInfo": { name: "my-server", version: "1.0" } } } 3. Client → Server: notifications/initialized (one-way, no response expected) 4. Client → Server: tools/list { "method": "tools/list" } 5. Server → Client: available tools with JSON schemas { "result": { "tools": [ { "name": "search_docs", "description": "Search internal docs...", "inputSchema": { ... JSON Schema ... } } ] } } 6. USER PROMPTS THE LLM through the client LLM sees tools/list and decides to call one 7. Client → Server: tools/call { "method": "tools/call", "params": { "name": "search_docs", "arguments": { "query": "auth" } } } 8. Server → Client: tool result { "result": { "content": [ { "type": "text", "text": "Found 3 documents..." } ] } } 9. LLM uses the result to continue its response. Steps 6-9 repeat as many times as the LLM chooses.

A minimal MCP server — 25 lines of Python

The reference implementation is at github.com/modelcontextprotocol. Here's a fully functional server that exposes one tool:

python
from mcp.server.fastmcp import FastMCP mcp = FastMCP("my-first-server") @mcp.tool() def search_docs(query: str, limit: int = 10) -> str: """Search internal documentation. Returns matching snippets.""" # Your actual search logic here. results = my_search_function(query, limit) return "\n---\n".join(r.text for r in results) @mcp.resource("docs://{path}") def read_doc(path: str) -> str: """Read a specific documentation page.""" return open(f"./docs/{path}.md").read() if __name__ == "__main__": mcp.run(transport="stdio")

That's a working MCP server. The FastMCP SDK converts your typed function signatures into JSON Schema automatically (Pydantic under the hood). Docstrings become tool descriptions. Run this file, add it to Claude Desktop's config, restart the app, and the LLM has your tools.

Add to Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json):

json
{ "mcpServers": { "my-docs": { "command": "python", "args": ["/path/to/my_server.py"] } } }

Restart Claude Desktop. You'll see "my-docs" appear in the hammer icon menu with your search_docs tool listed.

The L4 → L7 MCP server ladder

  • L4 (starter): stdio transport, one process, no auth, tools only. What you shipped above. Perfect for local dev tools (filesystem, git, sqlite).
  • L5 (production local): stdio + persistent state (SQLite or a real DB), better error handling, structured logging, tests. Ships with a package.json / pyproject.toml so users can npx @you/mcp-server or uvx you-mcp-server without cloning.
  • L6 (remote server): SSE transport, hosted on the internet, OAuth 2.1 authentication (the MCP spec's standard flow as of 2025-06). Multiple concurrent clients. Rate limiting. Observability. Think GitHub's official MCP server or Cloudflare's remote MCP servers.
  • L7 (enterprise MCP): multi-tenant, RBAC per user + per organization, audit logs for every tool call, egress network policy, DLP for resources, in-flight redaction of secrets. This is where enterprise MCP gateways (like Cloudflare, Anthropic Enterprise, and emerging vendors) operate.

Why MCP matters for system-design interviews

MCP tests are showing up in L5+ AI system-design loops. The questions:

  • "Design an internal MCP server that exposes your CRM to LLM assistants."
  • "Design a multi-tenant remote MCP gateway that lets employees at 100 different companies use their own tools securely."
  • "How would you migrate a legacy plugin API (like Zapier's) to MCP?"

Understanding MCP means understanding the LLM tooling stack the industry is standardizing on. Ignore it and your L5/L6 loop answers will feel dated by end of 2025.

The most common mistakes

1. Overloading tools with too many options. LLMs are good at picking from 5-15 tools. Not 50. Break big servers into focused smaller ones.

2. Not returning structured results. {"type": "text", "text": "..."} is fine for prose, but for lists of results, return one content item per result so the LLM can address them separately.

3. Blocking on network calls. stdio servers are single-threaded by default. Use async (asyncio, Node async) or the LLM's next tool call blocks on your previous slow one.

4. Skipping auth in remote servers. Remote MCP servers MUST use OAuth 2.1 with PKCE per the spec. Non-authenticated remote servers are a data-exfiltration risk.

5. Failing to test with `mcp-inspector`. The @modelcontextprotocol/inspector tool lets you connect to your server, list tools, invoke them, and see the raw wire messages. Every debug session starts here.


What's next in this journey:

  • Chapter 1: The FastMCP SDK deep-dive — Pydantic schemas, async tool handlers, resource URIs, prompt templates
  • Chapter 2: Building an SSE/HTTP MCP server for remote use, with OAuth 2.1 + PKCE
  • Chapter 3: The MCP inspector + debugging protocol traces
  • Chapter 4: MCP + agents — how ReAct loops discover tools dynamically via MCP
  • Chapter 5: Multi-tenant MCP gateway — Cloudflare's remote MCP pattern for enterprise
  • Chapter 6: MCP security — capability negotiation, secret redaction, prompt-injection defense at the MCP boundary

Sources cited in this chapter:

Key takeaway

MCP is JSON-RPC 2.0 over stdio (local) or SSE (remote), exposing 3 primitives: tools (model-controlled), resources (user-controlled data), and prompts (user-controlled slash commands). It solves the M×N integration problem — with 10 clients × 100 tools you'd write 1000 custom integrations; with MCP you write 110. Building a working server is ~25 lines with FastMCP. Ladder: L4 = stdio + tools; L5 = production local with tests + npm/uvx distribution; L6 = remote SSE + OAuth 2.1; L7 = multi-tenant enterprise gateway with RBAC and audit. Understanding MCP is table-stakes for L5+ AI system-design interviews as of 2025.

You can now answer
  • What is the M×N problem MCP solves, and how does M+N replace it?
  • What is the difference between MCP host, MCP client, and MCP server?
  • What are the 3 primitives MCP servers expose, and who controls each?
  • What is the difference between the stdio and SSE transports, and when do you use each?
  • How many lines is a working MCP server with FastMCP?
  • How does MCP relate to OpenAI function calling — same or different?
  • What does the L4 → L7 MCP server ladder look like?
  • What are the 5 most common MCP server mistakes and how to avoid them?