MCP Explained: What the Model Context Protocol Actually Solves

Photo by Google DeepMind

Photo by Google DeepMind
Two years ago, connecting an AI assistant to your tools meant writing a bespoke integration per model per tool: one glue layer for Claude and your database, another for ChatGPT and the same database, a third when the next model arrived. The Model Context Protocol — MCP, open-sourced by Anthropic in November 2024 — exists to kill that N-times-M matrix.
MCP has since become the de facto standard, supported by Claude, ChatGPT, VS Code, Cursor, and a long tail of agent frameworks. I use MCP servers daily in Claude Code, including ones I wrote to expose our own monitoring and infrastructure data. This post explains the protocol from the ground up: the architecture, the three primitives, and an honest answer to the question I get most — should we build our own server?
The official docs describe MCP as a USB-C port for AI applications, and the analogy holds up: a standardized connector so any AI application can talk to any external system without bespoke wiring. Instead of every team re-implementing tool calling for their database, calendar, or issue tracker against every model vendor's API, the system exposes itself once as an MCP server, and every MCP-capable application can use it.
The important nuance: MCP does not replace your model provider's tool-calling API — it standardizes the layer behind it. The model still emits tool calls; MCP standardizes how those capabilities are discovered, described, and invoked across applications. Build once, integrate everywhere is the entire pitch.
Three roles, and the names trip people up less when you see them in one diagram. A host is the AI application — Claude Desktop, an IDE, or your own product. The host runs one MCP client per connection, and each client talks to exactly one MCP server, locally over stdio or remotely over Streamable HTTP:
┌──────────────────────────────┐
│ Host (Claude, IDE, your app)│
│ ┌────────────┐ ┌──────────┐ │ one client per server
│ │ MCP client │ │MCP client│ │
│ └─────┬──────┘ └────┬─────┘ │
└────────┼─────────────┼───────┘
│ stdio │ Streamable HTTP
▼ ▼
┌────────────┐ ┌──────────────┐
│ MCP server │ │ MCP server │
│ (local: │ │ (remote: │
│ files, │ │ GitHub, │
│ postgres) │ │ Linear, …) │
└────────────┘ └──────────────┘
tools · resources · promptsTools
Functions the model can invoke: query a database, create a ticket, send a message. Each carries a name, a description, and a JSON Schema for inputs. This is the primitive you will use most.
Resources
Read-only context the application can load: files, table schemas, documents. Resources are addressed by URI and meant for hydrating context rather than taking actions.
Prompts
Reusable, parameterized prompt templates the server ships, surfacing domain workflows — a review-this-incident template from your incident tooling, for example.
The practical wins show up quickly. The ecosystem effect is the big one: there are pre-built servers for GitHub, Slack, Postgres, Google Drive, and hundreds of other systems, so an enormous amount of agent capability is now configuration rather than code. When I want Claude Code to inspect a production database or read our Grafana dashboards, I add a server entry to a config file — no SDK integration work at all.
The second win is organizational: the integration surface becomes a contract. The team that owns the ERP can ship and version an MCP server for it, and every AI initiative in the company consumes the same one — same auth, same audit logs, same allowed operations — instead of five teams writing five different glue layers against the raw API.
The honest decision table, based on the servers I have built and the ones I regret:
| Situation | Verdict |
|---|---|
| A public MCP server already exists for the system (GitHub, Postgres, Slack...) | Use it — write zero code |
| Internal system with an API that several AI use cases will need | Build a server — it pays off by the second consumer |
| One-off automation inside a single app you control end to end | Skip MCP — plain tool definitions in your own agent loop are simpler |
| You want non-engineers to use internal data safely from Claude | Build a server — with scoped, read-mostly tools |
| Exposing dangerous mutations (payments, deletions) to any model | Pause — design the permission model first, then build narrowly |
A useful MCP server is shockingly little code. With the official TypeScript SDK, a real tool — name, description, schema, handler — fits on one screen:
// A minimal MCP server with the official TypeScript SDK
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
import { z } from "zod"
const server = new McpServer({ name: "erp-tools", version: "1.0.0" })
server.tool(
"get_stock_level",
"Look up current warehouse stock for a SKU. Call this for any availability question.",
{ sku: z.string().describe("Product SKU, e.g. BRG-0042") },
async ({ sku }) => {
const row = await db.query("SELECT qty FROM stock WHERE sku = $1", [sku])
return { content: [{ type: "text", text: String(row?.qty ?? "SKU not found") }] }
},
)
await server.connect(new StdioServerTransport())Run it over stdio for local development, and graduate to Streamable HTTP behind your normal reverse proxy when other people need it. The skills from my MCP server development guide on this blog apply directly here; the short version is that tool descriptions deserve the same care as user-facing copy, because they are effectively prompts.
Treat every MCP server as an API gateway with a very creative client. Scope its credentials to the minimum the tools need, prefer read-only database users, log every invocation, and require approval flows in the host for anything irreversible. The protocol standardizes connectivity — it does not absolve you of authorization design, which is why I wrote separately about putting Cerbos in front of MCP auth.
MCP turned the messiest part of building with LLMs — connecting them to real systems — into a commodity interface. Learn the three primitives, reuse public servers wherever they exist, and build your own only when an internal system will serve more than one AI consumer. When you do build, the engineering is the easy part; the durable work is designing tools and permissions that you are comfortable handing to a model.
Sources and further reading