Issue #3 · Tuesday, May 19, 2026

MCP Is Just an SDK

Model Context Protocol — explained through sdks

The Concept

MCP — Model Context Protocol — is the specification that lets LLM clients talk to external tools and data sources through a single, uniform interface. It's the layer Claude Desktop, Cursor, and a growing list of agent runtimes use to load capabilities at runtime instead of compiling them in. Every tutorial I've read about it leads with "AI integration standard," which is true and useless.

Here's the framing that actually transfers: MCP is what an SDK becomes when the consumer is a language model. If you've ever shipped a client library — or even consumed one carefully — you already understand most of MCP. The remaining 20% is the part where the caller can't read your docs.

If You Already Know SDKs, You Already Know Most of This

What does an SDK actually do? It wraps a backend with a stable, typed interface so callers don't have to think about transport, serialization, auth, or versioning. The Stripe SDK doesn't add capability — Stripe's HTTP API has the same capability. The SDK adds a contract: here are the methods, here are their signatures, here's how errors come back, here's how we'll evolve this without breaking you.

MCP does the same thing for tool and data access, with the wire shifted one layer up. An MCP server exposes a set of named tools, each with a JSON Schema for its inputs and a structured response shape for its outputs. A client connects, asks "what do you have?", gets the schemas back, and can now invoke any of them. That's an SDK. The only differences are that the contract is published over the wire instead of compiled into a library, and the consumer is an LLM client deciding at runtime which method to call.

The mapping:

SDK MCP
Methods on a client object tools exposed by a server
Read-only accessors / data fetchers resources (URI-addressable read endpoints)
Prebuilt helpers / templates prompts (named, parameterized prompt templates)
Type signatures JSON Schema on each tool's inputSchema
Constructor / config initialize handshake (protocol version + capabilities)
SDK version MCP protocol version, negotiated per session
Library installed in your app Server process the client launches or connects to
Errors thrown by the client JSON-RPC error responses

The reason this analogy holds isn't aesthetic. It's that MCP solves the same problem an SDK solves: it decouples a capability provider from its consumers. Build the server once, every MCP-aware client can consume it. That's the entire point of an SDK — write once, integrate many.

What's Actually New

There are three real differences. They matter.

The caller can't read your docs. When a programmer uses an SDK, they read the README and pick the right method. When an LLM uses an MCP server, it picks methods from the description field returned by list_tools. The description is the documentation, and it's read by a model that has never seen your code. This means the description is part of the contract, not commentary on it. A clear, narrow description with explicit constraints is the difference between a tool that gets called correctly and one that gets called constantly with bad arguments.

Discovery is at runtime, not compile time. You don't import an MCP server. The client connects, sends tools/list, and gets back whatever the server decides to expose right now. Servers can change what they expose mid-session. This makes MCP closer to LSP or ODBC than to a traditional SDK — the contract is negotiated, not linked. Practical consequence: a single LLM app can pick up new capabilities without redeploying, just by adding a server to its config.

The transport is part of the spec. SDKs assume HTTP or gRPC and wrap it. MCP defines two transports — stdio (for local subprocesses) and HTTP+SSE (for remote servers) — and standardizes the JSON-RPC 2.0 framing on both. You don't get to invent your own. This is the dull-but-load-bearing kind of standardization, the same kind that made USB-C useful: any compliant client can talk to any compliant server, and nobody has to write glue.

Under the Hood

A minimal MCP server in TypeScript, exposing one tool:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

const server = new Server(
  { name: "runbook-server", version: "0.1.0" },
  { capabilities: { tools: {} } }
);

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "search_runbooks",
      description:
        "Full-text search over team runbooks. Returns up to 5 matching " +
        "paragraphs with their source path. Use for operational questions " +
        "about deploys, incidents, on-call procedures.",
      inputSchema: {
        type: "object",
        properties: {
          query: { type: "string", description: "Search query" },
          limit: { type: "integer", minimum: 1, maximum: 20, default: 5 },
        },
        required: ["query"],
      },
    },
  ],
}));

server.setRequestHandler(CallToolRequestSchema, async (req) => {
  if (req.params.name !== "search_runbooks") {
    throw new Error(`Unknown tool: ${req.params.name}`);
  }
  const { query, limit = 5 } = req.params.arguments as {
    query: string;
    limit?: number;
  };
  const hits = await searchRunbooks(query, limit); // your code
  return {
    content: [{ type: "text", text: formatHits(hits) }],
  };
});

await server.connect(new StdioServerTransport());

That's the whole shape. ListToolsRequestSchema is your schema export. CallToolRequestSchema is your method dispatcher. The transport is one line. Everything else is the body of your function — exactly the code you'd write inside an SDK method.

The handshake is worth understanding once because it surfaces in every debugging session:

  1. Client opens the transport (spawns your process, or opens an SSE connection).
  2. Client sends initialize with its protocol version and capabilities.
  3. Server responds with its protocol version and capabilities.
  4. Client sends notifications/initialized.
  5. Normal request/response begins. tools/list, tools/call, resources/read, etc.

If a server appears in a client's config but never shows up as available, the failure is almost always at step 2 or 3 — version mismatch, server crashed before responding, or stderr noise polluting the stdio framing. (Stdio transport reserves stdout for JSON-RPC; logging to stdout corrupts the stream. Always log to stderr.)

Resources and prompts follow the same pattern: list_* returns metadata, get_* / read_* returns content. If you understand tools/list + tools/call, you understand all three primitives.

Decision Framework

Use MCP when:

  • You're building tool-using behavior across more than one LLM client. If a capability needs to work in Claude Desktop and a custom agent and an IDE plugin, build it as an MCP server once instead of three integrations.
  • The client app isn't yours. If you're shipping capability to Claude Desktop or Cursor or anything else you don't control, MCP is the only contract those hosts speak. You don't get to invent a different one.
  • You want runtime-pluggable capabilities. Add a server, restart the client, the new tools appear. No build step.

Don't use MCP when:

  • You're building a single agent with a fixed toolbelt. Just register the tools directly with whatever LLM SDK you're already using. The MCP indirection costs you a process boundary and a JSON-RPC round-trip per call. For a closed system, it's overhead with no payoff.
  • Latency budget is tight. stdio MCP adds milliseconds; remote MCP adds tens to hundreds. If you're inside a tight inner loop (per-token tool routing, real-time UI), call the function directly.
  • The "tool" is a one-line prompt fragment. Don't build a server for something that belongs in the system prompt.

What Your Manager Thinks It Does vs. What It Actually Does

Your manager thinks MCP is an "AI integration framework" or "the USB-C of AI."

What it actually is: a JSON-RPC contract for exposing tools, resources, and prompts to LLM clients. It's the SDK pattern lifted to the wire so that the contract is language-agnostic and discoverable at runtime. The reason it caught on isn't the protocol design — JSON-RPC is thirty years old. It's that it gave hosts (Claude Desktop, IDEs, agent runtimes) a single integration point and gave capability providers a single distribution surface. That's a network-effect play, not a technical breakthrough.

The reframe for your next meeting: "MCP is the LSP of AI tools. Same shape — host process, capability servers, JSON-RPC over stdio, runtime discovery. Anyone who's used a language server in VS Code already knows the architecture."

Ship This Weekend

Build an MCP server that exposes your team's runbooks (or any folder of markdown) as a search_runbooks tool, and connect it to Claude Desktop.

  1. npm init -y && npm install @modelcontextprotocol/sdk

  2. Drop the server above into server.ts. Replace searchRunbooks with a real implementation — ripgrep shelled out, or a tiny in-memory index over glob("**/*.md"). Keep it under 100 lines.

  3. Add the server to Claude Desktop's MCP config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):

    {
      "mcpServers": {
        "runbooks": {
          "command": "node",
          "args": ["/absolute/path/to/server.js"]
        }
      }
    }
    
  4. Restart Claude Desktop. Ask: "What's our deploy rollback procedure?" Watch it call search_runbooks and answer from your actual docs.

Two things you'll learn. First, the description field on your tool matters more than the function body — try shipping it once with a vague description and once with a precise one and watch the calling behavior change. Second, the moment you have one MCP server running, adding the second one is trivial — and that's the actual value proposition. Capability composition stops being an integration project and becomes a config edit.

Further Reading

Get Upshift every Tuesday

One AI concept per week, explained through systems you already know.