MCP: The Model Context Protocol and Why It Matters
By Diesel
architecturemcpprotocolsstandards
## The Integration Hellscape
Every AI agent needs tools. Search the web. Query a database. Read files. Call APIs. And every framework has invented its own way of defining and connecting those tools.
LangChain has `Tool` classes. OpenAI has function calling JSON schemas. Anthropic has tool definitions. AutoGen has its own format. CrewAI has yet another. If you want your tool to work across frameworks, you write the same integration five different ways.
This is the state of the world before MCP. It's messy, it's wasteful, and it doesn't scale.
The Model Context Protocol (MCP) is Anthropic's answer to this problem. And regardless of what you think about Anthropic, the protocol itself solves a real problem worth understanding.
## What MCP Actually Is
Strip away the marketing and MCP is a JSON-RPC 2.0 protocol for connecting AI models to external tools and data sources. That's it. A client (the AI application) connects to a server (the tool provider), and they speak a common language.
```
AI Application (MCP Client)
↕ JSON-RPC over stdio/SSE/WebSocket
MCP Server (Tool Provider)
↕ whatever it needs internally
External Service / Database / API
```
The protocol defines three core primitives:
**Tools**: functions the AI can call. Takes parameters, returns results. "Search the web for X." "Query the database with Y."
**Resources**: data the AI can read. URIs that resolve to content. "The contents of file Z." "The schema of database W."
**Prompts**: pre-built prompt templates that servers can expose. "Summarize this document using template V."
```typescript
// MCP Server exposing a tool
const server = new MCPServer({
tools: [{
name: "search_documents",
description: "Search the document store for relevant content",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "Search query" },
limit: { type: "number", default: 10 },
},
required: ["query"],
},
handler: async (params) => {
const results = await documentStore.search(params.query, params.limit);
return { content: [{ type: "text", text: JSON.stringify(results) }] };
},
}],
});
```
## Why JSON-RPC and Not REST
This is a design decision worth appreciating. REST is great for CRUD operations on resources. But tool interactions aren't CRUD. They're RPC. "Call this function with these arguments." JSON-RPC maps directly to that mental model.
It also enables bidirectional communication. The server can send notifications to the client. "Hey, that long-running search just completed." "The database schema changed." With REST, you'd need webhooks or polling. With JSON-RPC over a persistent connection, it's built in.
## The Transport Layer
MCP supports multiple transports:
**stdio**: client spawns the server as a subprocess. Communication over stdin/stdout. Simple, local, no network overhead. Perfect for local tools and development. The related post on [tool use fundamentals](/blog/tool-use-in-ai-agents) goes further on this point.
**Server-Sent Events (SSE)**: client connects to the server over HTTP. Server pushes events. Good for remote servers where you need one-way streaming.
**WebSocket**: full bidirectional communication. The production choice for remote MCP servers that need real-time interaction.
```typescript
// stdio transport (local tool)
const transport = new StdioTransport({
command: "npx",
args: ["my-mcp-server"],
});
// SSE transport (remote server)
const transport = new SSETransport({
url: "https://tools.example.com/mcp/sse",
headers: { Authorization: `Bearer ${token}` },
});
```
The transport is separate from the protocol. Your MCP server doesn't care how it's connected. It receives JSON-RPC messages and sends JSON-RPC responses. This separation means you can develop locally with stdio and deploy remotely with SSE without changing your server code.
## Building an MCP Server
Here's a complete MCP server that provides file system access. It's surprisingly little code.
```typescript
import { MCPServer } from "@modelcontextprotocol/sdk";
const server = new MCPServer({
name: "filesystem",
version: "1.0.0",
tools: [
{
name: "read_file",
description: "Read the contents of a file",
inputSchema: {
type: "object",
properties: {
path: { type: "string" },
},
required: ["path"],
},
handler: async ({ path }) => {
const content = await fs.readFile(path, "utf-8");
return { content: [{ type: "text", text: content }] };
},
},
{
name: "list_directory",
description: "List files in a directory",
inputSchema: {
type: "object",
properties: {
path: { type: "string" },
recursive: { type: "boolean", default: false },
},
required: ["path"],
},
handler: async ({ path, recursive }) => {
const entries = await listDir(path, recursive);
return { content: [{ type: "text", text: JSON.stringify(entries) }] };
},
},
],
resources: [
{
uri: "file://{path}",
name: "File contents",
description: "Read any file by URI",
handler: async (uri) => {
const path = uri.replace("file://", "");
const content = await fs.readFile(path, "utf-8");
return { contents: [{ uri, text: content }] };
},
},
],
});
server.listen();
```
That's a fully functional MCP server. Any MCP client can connect to it and use the tools. No framework-specific adapters. No custom serialization. Just the protocol.
## Why This Matters for Agent Architecture
The real value of MCP isn't the protocol itself. It's what it enables architecturally.
### Tool Marketplace
When every tool speaks the same protocol, you get composability for free. Want to add GitHub integration to your agent? Install the GitHub MCP server. Want database access? Install the database MCP server. No custom integration code. No adapter patterns.
```json
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["@github/mcp-server"]
},
"postgres": {
"command": "npx",
"args": ["@pg/mcp-server", "--connection", "postgres://localhost/mydb"]
},
"filesystem": {
"command": "npx",
"args": ["@fs/mcp-server", "--root", "/home/user/projects"]
}
}
}
```
Three tools. Three lines of config each. Your agent can now interact with GitHub, PostgreSQL, and the file system. No code written.
### Security Boundaries
MCP servers run as separate processes. This is a security feature, not a limitation. A compromised tool can't access your agent's memory, other tools, or the LLM API. Each server runs with its own permissions, its own environment, its own failure domain.
```
Agent Process (privileged, has LLM API key)
↕ MCP (sandboxed communication)
Tool Process (limited, has database credential only)
```
If the tool process crashes, the agent gets an error response. It doesn't crash. If the tool tries to do something unauthorized, the transport layer catches it.
### Multi-Model Support
Because MCP is model-agnostic, the same tools work with any LLM. Build your MCP server once. Use it with Claude, GPT, Llama, Mistral, whatever. The server doesn't know or care what model is calling it. It receives a function call and returns a result. For a deeper look, see [building an MCP server from scratch](/blog/building-mcp-server-custom-tools).
This is the USB-C analogy. Before USB-C, every phone had its own charger. Now one cable works everywhere. MCP is doing the same thing for AI tools.
## Where MCP Falls Short (Honest Assessment)
No protocol is perfect. Here are the gaps:
**Discovery**: there's no standard way to discover MCP servers. You need to know they exist and configure them manually. A registry or marketplace would help.
**Authentication**: the protocol doesn't standardize authentication between client and server. Each server does its own thing. OAuth flows, API keys, whatever. This creates friction for multi-server setups.
**Streaming results**: for long-running tools, you want to stream intermediate results back to the agent. MCP supports this through notifications, but the pattern isn't as clean as it could be.
**State management**: MCP servers are stateless by default. If you need to maintain state between tool calls (like a database transaction), you're implementing that yourself.
These are solvable problems. And the community is solving them. The core protocol is solid enough that these gaps don't negate the value.
## The Practical Impact
I've been building with MCP for a while now, and the biggest change is velocity. Adding a new tool used to mean writing adapter code, handling serialization, managing the tool lifecycle, updating prompts. Now it means writing the tool logic, wrapping it in an MCP server, and adding a line to the config.
The second biggest change is testability. MCP servers are standalone processes. You can test them independently. Send them JSON-RPC messages, verify the responses. No need to spin up the entire agent to test a tool. The related post on [broader agent communication standards](/blog/agent-communication-protocols) goes further on this point.
The third change is portability. Built a tool for one project? It works in every project. Built a tool for Claude? It works with any MCP-compatible client. That's the power of a protocol over a framework.
## Should You Adopt MCP?
If you're building production agent systems, yes. Not because it's perfect. Because it's the closest thing to a standard we have, and standards reduce friction for everyone.
If you're building tools that other people will use, definitely yes. MCP compatibility means your tool works everywhere. That's distribution.
If you're experimenting with agents in a notebook, probably not yet. The overhead of running separate processes isn't worth it for quick experiments.
The bet on MCP is a bet on standardization. And in the history of computing, standardization has always won. Not because standards are the best possible design. Because they're the design everyone agrees on. That's more valuable than perfection.