MCP Earns Its Complexity: The Case for a Protocol Boundary Over Platform Skills
Source: hackernews
David’s post “I still prefer MCP over skills” landed well on Hacker News this week, pulling 353 points and nearly 300 comments. The title’s “still” carries weight. It’s a position held against some friction, probably against the reasonable objection that skills are just simpler. I’ve been in the same camp since I started building my Discord bot’s tooling layer on MCP, and I want to explain why the preference isn’t stubbornness or NIH syndrome.
The real argument is about where you put the seam.
What the Seam Question Actually Means
Every system that connects an AI model to external capabilities has a boundary somewhere. On one side: the model, the context window, the conversation. On the other side: your filesystem, your APIs, your databases, your services. The question is what that boundary looks like and who controls it.
With platform-native skills, whether on claude.ai or inside a specific agent framework, that boundary is owned by the platform. The skill is defined in the platform’s terms, exposed through the platform’s interface, and understood by exactly one host. If that host changes its skill format, you update. If you want to run the same capability in a different client, you rewrite it.
With MCP, the boundary is a protocol. JSON-RPC 2.0 over a transport you choose, exposing capabilities through three standardized primitives: tools, resources, and prompts. The model on the other side doesn’t need to know anything about your implementation. Any compliant client can discover and use what you expose.
This distinction sounds abstract until you’ve actually moved something between contexts and felt what the protocol boundary costs versus what it saves.
The Three Primitives Are Doing Real Work
MCP’s design isn’t arbitrary. Tools, resources, and prompts map cleanly onto the three things you actually need from external integration.
Tools are callable functions. The client sends a tools/call request with a name and arguments; the server returns content. The tool schema is JSON Schema, so the model can reason about what arguments are valid:
{
"name": "search_codebase",
"description": "Search source files for a pattern",
"inputSchema": {
"type": "object",
"properties": {
"pattern": { "type": "string" },
"path": { "type": "string", "default": "." }
},
"required": ["pattern"]
}
}
Resources are readable data sources, addressed by URI. A resource at file:///src/config.ts or postgres://localhost/mydb/schema can be fetched by the client and injected into context. Resources can also be subscribed to, so the server notifies the client when the underlying data changes. This is the primitive that skills typically don’t have an equivalent for, and it’s genuinely useful for giving models live context without burning tokens on polling.
Prompts are named, parameterized prompt templates. A server can expose a code_review prompt that takes a language parameter and returns a structured message list. The client surfaces these to the user through whatever UI it has. This one is the least commonly used, but it solves a real problem: standardizing how prompt recipes travel alongside the tools they’re meant to work with.
That’s a complete surface area. Most things you’d want to do from a skill fit into one of these three shapes.
A Minimal MCP Server
The TypeScript SDK keeps setup from being painful:
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: "my-tools",
version: "1.0.0",
});
server.tool(
"get_recent_errors",
{
service: z.string(),
limit: z.number().default(10),
},
async ({ service, limit }) => {
const errors = await fetchErrorLog(service, limit);
return {
content: [{ type: "text", text: JSON.stringify(errors, null, 2) }],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
That server works immediately in Claude Desktop, Claude Code, Cursor, Zed, Cody, Continue, and every other client that has implemented MCP. The Python SDK is similarly concise. There are official SDKs in Kotlin, C#, Java, Rust, Swift, and Go. You write the server once against the protocol, and the client ecosystem is your distribution layer.
For local tools, stdio is the right transport. For remote services you want to expose over the network, the HTTP with Server-Sent Events transport works well, and the newer Streamable HTTP transport in the March 2025 spec revision addresses some of the latency overhead that the SSE approach had.
The Portability Payoff
I run a Discord bot where every capability, from querying GitHub PRs to scheduling messages to checking CI status, is implemented as an MCP server. When I added Claude Code to my workflow, those same servers were immediately available. I changed one config file, pointed a new client at existing processes, and everything I’d built was usable in a completely different context.
That portability isn’t theoretical. The MCP server registry already lists hundreds of community-maintained servers covering databases, cloud platforms, developer tools, and services. When you build against MCP rather than a platform’s skill system, you can plug into that ecosystem instead of starting from scratch on every new capability.
Skills, by contrast, accumulate platform-specific technical debt. You write a skill for one system, it works there, and when you want equivalent functionality somewhere else, you write it again in that system’s terms. The investment doesn’t compound the way a well-maintained MCP server does.
Where Skills Genuinely Win
This case isn’t one-sided. For simple behavioral customization, a skill is faster. If you want the model to always respond in a certain format, prefer certain libraries, or carry a specific persona, you don’t need a running process for that. A well-written system prompt or a lightweight skill configuration handles it without the operational overhead of a server.
MCP requires you to own the infrastructure. Your server process can crash. You need to handle reconnection, versioning, logging. For a solo project, that’s manageable; for a team that wants zero-maintenance tooling, it’s a real cost. Platform skills that Anthropic or another vendor runs on your behalf offload that entirely.
The honest summary: skills are appropriate when the capability is simple and you want someone else to run it. MCP is appropriate when the capability is complex, needs to be portable, or when you want full observability into what the model is calling and why.
The Ecosystem Bet Is Already Paying Off
The speed of MCP adoption has been notable. Anthropic built it into Claude Desktop at launch. Cursor added it. Zed added it. Sourcegraph’s Cody added it. The OpenAI and Google ecosystems have started producing compatible implementations, which would have seemed unlikely when MCP was first announced in November 2024.
That adoption curve means the portability argument gets stronger over time, not weaker. A skill you write for one platform stays on that platform. An MCP server you write today is usable by a client that doesn’t exist yet, because the protocol is the contract, not the platform’s API surface.
David’s position is right, and the “still” in his title is the honest part. There’s pressure to reach for the simpler path, especially when skills have gotten genuinely easier to set up. But simpler and better aren’t the same thing, and for any tooling you expect to use across more than one context, a protocol boundary is worth the overhead it costs at the start.