MCP before and after

The Model Context Protocol turned every external service into a tool call. This is what changed for AI tooling once it did, and the attacks it opened on the developer's own machine.


Revisions26 · feb 05Read noteRevisions26 · feb 05Written while the protocol changed every few weeks and its security story was still catching up, so the date on each claim matters.

A personal note26 · mar 11The attack surface now rests on real vulnerabilities (CVE-2025-6514, rated 9.6) instead of hypotheticals. Three sections are new. Two follow the protocol itself (MCP Apps and OAuth 2.1); the one I would read first is the last, on how to connect an agent at all.

A personal note26 · jul 04One argument for that choice that the tables above miss. The token cost everyone repeats is the tool definitions; the bigger one is that a CLI keeps file contents out of the model's context entirely. It changed how I default for file-heavy work: a CLI first.

A personal note26 · oct 05Rechecked against the sources, now linked beside each claim. Three corrections change the advice. Claude Code asks before trusting a repo's .mcp.json servers, though headless and cloud runs do not. Its optional sandbox leaves MCP servers outside. The Playwright server loads about twenty tools by default, where I had written 47.

Before MCP, connecting an AI tool to anything external meant writing a bespoke integration. Each IDE plugin or API wrapper had to be built from scratch, for that specific tool, and maintained when the tool changed. If you wanted Claude to talk to your database and Cursor to talk to the same database, you wrote two integrations. If you added a third tool, you wrote a third, the N-times-M problem behind every interoperability story. MCP replaced that with one protocol and one server per service, which every client speaks, and this piece is about what changed before and after it.

The old world

Every AI coding tool had its own way of reaching external services. Cursor had its plugin system. GitHub Copilot had its extensions. Claude Desktop had nothing, a chat window with no arms.

If you wanted to give an AI access to your Git history, you had a few options:

Paste terminal output into the chat (copy, switch windows, paste, repeat).

Write a wrapper script that formatted output for the model's context window.

Build a native plugin for that specific tool, using that tool's specific API.

Option three was the right answer, but the economics were brutal. Every integration was a snowflake. A Slack integration for Cursor shared zero code with a Slack integration for Claude. The companies building AI tools spent engineering time on integrations instead of on the model layer. Third-party developers had to pick which tool to support because supporting all of them was a full-time job.

As a result, each tool had great integration with two or three services and terrible integration with everything else.

The new world

MCP flipped the architecture. Instead of NNN clients each implementing MMM integrations (N×MN \times MN×M in total), you write one MCP server per service and every client connects to it. The server exposes tools, resources, and prompts through a standard JSON-RPC protocol. The client discovers what's available and calls it.

The two side by side:

Native integration MCP server

Protocol Tool-specific API JSON-RPC over stdio or HTTP

Auth Per-tool, bespoke OAuth 2.1 (standardized)

Discovery Hardcoded Dynamic tool listing

Portability Zero: locked to one client Any MCP-compliant client

Maintenance Two codebases to update One server, many clients

A Postgres MCP server exposes query, list-tables, describe-schema as tools. Claude Code connects to it, and so does a custom agent you built last weekend; both get the same tool definitions from the same server process.

Inherited, not magic.

Claude Code inherits your shell permissions. It runs as your user, in your terminal, with access to everything your user can read, SSH keys included. MCP servers it connects to run under the same user. By default there is no sandbox, and the optional one fences only shell commands, with MCP servers left outside it1. If you can rm -rf /, Claude Code can too. It just needs you to approve the command (or to have granted blanket permission via --dangerously-skip-permissions).

1Anthropic, Configure the sandboxed Bash tool, Claude Code docs, 2026.

That access is deliberate: a sandboxed environment with no real access would make the tool useless for actual development work. It also means the security boundary is your judgment about what to approve.

Projects became portable

An .mcp.json file at the root of a repo declares which MCP servers a project needs. Clone the repo, and the AI tool reads the config and connects to the right servers. A project that needs GitHub and a custom internal API declares both, and any team member's client picks them up (Claude Code asks once before it trusts a project's servers in an interactive session, and loads them without asking in headless and cloud runs1).

1Anthropic, Connect Claude Code to tools via MCP, Claude Code docs, 2026.

Portable attack surface.

Portability cuts both ways. If a malicious .mcp.json lands in a repo (through a pull request or a compromised fork), every developer who opens that repo with an MCP-aware tool and approves its servers (or runs an agent headless, where nobody is asked) connects to the attacker's servers. The servers look normal. The tool descriptions say they do normal things. But the descriptions are the attack surface, and they are controlled by the server author. More on this in the §attack surface section.

The inversion

The paradigm shift.

Before MCP, tools were dumb and integrations were smart. You built the parsing and the retry logic into each connector because the tool on the other end just exposed raw functionality.

After MCP, the model is the integration layer. The server exposes capabilities with natural-language descriptions. The model reads those descriptions and decides how to compose them. The glue code that used to live in thousands of lines of integration logic now lives in the model's reasoning. Tool descriptions became the API documentation and the user manual at once.

This is powerful and dangerous for the same reason. When the model trusts tool descriptions to decide what to do, anyone who controls the description controls the model's behavior.

The attack surface

MCP's simplicity is also its vulnerability. A server is a process that exposes tool definitions (name, description, input schema) over JSON-RPC. The client trusts those definitions to decide what to call and how. Three classes of attack exploit this trust.

Tool poisoning

The tool description is free-form text that the model reads and trusts.

In April 2025, Invariant Labs demonstrated a cross-server exfiltration attack using WhatsApp MCP. The setup: a user installs a benign-looking MCP server (in the demonstration, a tool offering a fact of the day) alongside the WhatsApp MCP server. The trivia server's tool description contains hidden instructions (invisible in the client UI but visible to the model) telling it to read the user's WhatsApp message history and send it to an attacker-controlled number.

The model follows the instructions because they look like part of the tool's documentation. The data leaves through WhatsApp itself, so it slips past network-layer data loss prevention looking like a normal outgoing message. The user approved sending a WhatsApp message, and the agent used that approval to send their message history to a stranger.

Supply chain

CVE-2025-6514 scored 9.6 on CVSS. The mcp-remote npm package (a proxy that lets local MCP clients connect to remote servers over HTTP) had a command injection vulnerability in its authorization callback URL handling. A malicious remote MCP server could craft an OAuth redirect that triggered arbitrary OS command execution on the client machine (full command execution on Windows, arbitrary executables with limited arguments on macOS and Linux). Full RCE from a remote server, on the developer's workstation, triggered by connecting to the server.

The package had over 437,000 downloads at the time of disclosure1. The vulnerability existed from version 0.0.5 through 0.1.15, and the flaw was one unsanitized string: the authorization_endpoint the server returned went straight to the open package, which hands it to the operating system2.

1The Hacker News, Critical mcp-remote vulnerability enables remote code execution, July 2025. 2JFrog Security Research, Critical RCE vulnerability in mcp-remote, 9 July 2025, which calls it the first full remote code execution on a client OS from a remote MCP server; fixed in mcp-remote 0.1.16.

Rug pull

This one is the hardest to see. A server passes review with clean, honest tool descriptions and users approve it. Weeks later, the server pushes updated tool definitions with poisoned descriptions: the name and the input schema stay the same, and only the description (the part the model reads to decide behavior) changes.

MCP has no mechanism to alert the user that a tool definition changed after initial approval. The server controls the definitions. The client re-fetches them on each session. A tool you trusted on Monday can be a different tool on Tuesday, and nothing in the protocol tells you. Invariant's WhatsApp demonstration worked this way: its fact-of-the-day server advertised a harmless tool on first launch and switched to the poisoned one on the next.

The .mcp.json vector makes this worse. A project configuration file points to a remote server URL. The server operator can change tool definitions at any time. Every developer using that config gets the new definitions on their next session. One compromised server definition, propagated through a shared config, silently reaches every machine that opens the repo.

No silver bullet yet.

All three vectors have been demonstrated, and in the case of CVE-2025-6514 the flaw shipped in a production package. The MCP specification still has no mandatory integrity check for tool definitions, so nothing in the protocol tells a client that a server changed what it offers. The burden is on the user to verify what they connect to.

MCP in 2026

The protocol kept moving: two major additions landed, and its governance changed hands.

Agentic AI Foundation

On December 9, 2025, the Linux Foundation announced the Agentic AI Foundation (AAIF). Anthropic donated MCP to the foundation. OpenAI contributed AGENTS.md. Block contributed goose. Platinum members: AWS, Anthropic, Block, Bloomberg, Cloudflare, Google, Microsoft, OpenAI.

MCP is now an industry standard governed by a neutral foundation, under the same umbrella organization that hosts Linux and Kubernetes. Whether that governance produces faster security standards or slower-moving committees remains to be seen, but the risk of one company controlling the spec is gone.

By December 2025, MCP had crossed 97 million monthly SDK downloads across Python and TypeScript and more than 10,000 active public servers, and the main assistants and editors supported it first-class, ChatGPT and Gemini among them1.

1Anthropic, Donating the Model Context Protocol and establishing the Agentic AI Foundation, 9 December 2025.

MCP Apps

MCP Apps, announced on January 26, 2026, is the first official MCP extension. Before Apps, a tool could only return text, structured data that the model interpreted and summarized for the user. Apps let tools return interactive user interfaces rendered directly in the conversation.

The pattern: a tool declares a UI resource via the ui:// scheme, and the host renders it in a sandboxed iframe.

TYPESCRIPT Copy

registerAppTool(server, "show-analytics", { title: "Show Analytics", description: "Interactive analytics dashboard.", inputSchema: z.object({ dateRange: z.string() }), _meta: { ui: { resourceUri: "ui://analytics/dashboard.html" } }, }, async ({ dateRange }) => { const data = await queryMetrics(dateRange); return { content: [{ type: "text", text: JSON.stringify(data) }] }; });

The tool returns data and a UI to display it. The iframe is sandboxed, with no access to the parent window and no arbitrary network requests. All communication between the UI and the host goes through loggable JSON-RPC messages. The HTML content is pre-declared as templates, so hosts can review it before rendering.

It shipped in Claude at launch, and ChatGPT added it the same week1.

1Model Context Protocol, ext-apps, GitHub, 2026, whose README lists the other hosts, VS Code Insiders and Postman among them.

OAuth 2.1

The original MCP spec had no standardized authentication. Servers handled auth however they wanted, often with an API key in an environment variable and sometimes with nothing at all. The March 2025 revision introduced OAuth 2.1 with PKCE as the standard authorization mechanism.

A server that requires authentication returns a 401 with metadata pointing to its authorization endpoint:

HTTP Copy

HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer realm="mcp", resource_metadata="https://your-server.com/.well-known/oauth-protected-resource"

The client discovers the authorization server through RFC 9728 (Protected Resource Metadata), performs the OAuth flow with PKCE1, and uses the resulting token for subsequent requests.

1PKCE (Proof Key for Code Exchange) is mandatory for every MCP client. Many MCP clients run where storing a secret securely is difficult, a container or a developer laptop with shared dotfiles.

The June 2025 revision formally split the authorization server from the MCP server itself, classifying the server as an OAuth resource server, so a single OAuth provider (your company's Okta, say) can protect multiple MCP servers without each one reimplementing auth. The November 2025 revision went further: Client ID Metadata Documents became the recommended way for a client to register, in place of registering by hand with every server.

How to connect an agent to a service

There are three ways to connect an AI agent to external services, each solving a different problem, and the wrong one is paid for in tokens or in engineering time.

The three paths

A CLI wrapper is a shell command. Input goes in, output comes out. playwright-cli screenshot https://example.com returns a PNG. The AI calls it through Bash like any other command, with no protocol to speak and no auth to negotiate. The tool definition costs almost zero context window. It's just a command name and its flags.

An API call is a direct HTTP request to a service. POST /v1/screenshots with a JSON body, get a JSON response. The AI needs the endpoint's URL and schema, and a token to call it. More structured than a CLI, more portable across languages, but each service has its own API design and its own auth mechanism. If you need two services, you learn two APIs.

An MCP server is a process that wraps a service in a standard protocol. It exposes tools (name, description, input schema) over JSON-RPC. The AI reads the descriptions of what is available and decides what to call. One protocol for everything. But those tool definitions live in the context window, and they're not free1.

1By my rough count, a single MCP server with 8 tools can consume 800-2,000 tokens in tool definitions alone. Five servers with 8 tools each = 4,000-10,000 tokens before the model does anything useful. On a 200K context window that's ~5%. On a 32K window it's ~30%.

Tool definitions are the cost everyone quotes. There's a sharper divide the descriptions above don't show, and it's about where the data lives. A CLI runs in your environment, so it works on files that are already on disk. To hand a 2,000-line source file to another service, the agent runs one command that reads the file and pipes it across. The contents never enter the model's context. MCP has no disk to reach for: whatever the agent sends has to travel through the context first. To move that same file it emits every line as tool-call arguments, token by token, before the server ever sees it. On large payloads that's the difference between a job that fits the window and one that doesn't, and it's why file-heavy work such as a refactor or a log triage tends to stay on the CLI even after you've paid for the MCP.

When each one wins

CLI wrapper Direct API MCP server

Setup cost Minutes. Write a script. Hours. Learn the API. Minutes. Install the server.

Context cost Near zero Low (one endpoint) High (N tool definitions)

Discovery None: you hardcode it Manual: read the docs Automatic: the model reads tools

Portability Zero: your script, your machine Zero: your integration Total: any MCP client

Auth Your env vars Per-API (OAuth, API keys, etc.) OAuth 2.1 (standardized but heavy)

Best for One task, one team, tight scope Backend pipelines, server-to-server Open ecosystems, multiple clients

When each one fails

Choosing a CLI when you need MCP goes like this. You write a github-cli wrapper for your team and it works. Other teams want it too, and soon you're maintaining three copies of the same script, each with its own flags and its own output format. This is the N×MN \times MN×M problem that MCP was built to solve.

Choosing MCP when you need a CLI looks different. You install the Playwright MCP server to automate a browser. It exposes around twenty tools by default (from navigation to console capture) and more than sixty with its optional capabilities switched on (network interception and PDF export among them). Your agent needs three of them. But every enabled tool definition loads into the context window, and the model has to reason about which ones to use. Garry Tan, CEO of Y Combinator, hit exactly this wall, then wrote a 100-line CLI wrapper in 30 minutes that did the job better1.

1Garry Tan, post on X, 12 March 2026: MCP sucks honestly. It eats too much context window and you have to toggle it on and off and the auth sucks. I got sick of Claude in Chrome via MCP and vibe coded a CLI wrapper for Playwright tonight in 30 minutes only for my team to tell me Vercel already did it lmao. But it worked 100x better and was like 100LOC as a CLI.

Choosing an API when you need MCP starts well. You call the GitHub API directly from your agent and it works fine for GitHub; then you add Postgres and Slack. Each API has its own auth and its own pagination, and you're writing a bespoke integration for each one. If a second AI tool needs the same access, you write everything again, back to snowflakes.

Choosing MCP when you need an API is the last mismatch. Your backend needs to query a database on every request. A direct SQL call takes 3ms. Routing it through an MCP server adds a process boundary and a JSON-RPC roundtrip to what was a single function call. MCP is designed for AI agents that need to discover and compose tools, and when you already know exactly what to call, the protocol is pure overhead.

The selection rule.

If only one agent on one machine needs the tool, and you know exactly what it should do, write a CLI.

If your backend code needs the service and no model is involved in the decision, call the API.

If multiple clients need to discover and use the service, and the model decides when and how to call it, run an MCP server.

The most common mistake in early 2026 is using MCP for everything because it's the new thing. Denis Yarats, CTO of Perplexity, said at the company's Ask conference in March 2026 that it had moved away from MCPs and back to APIs and CLIs for its own agents1. The protocol solved the interoperability problem, and many problems are something else.

1Awesome Agents, Perplexity CTO moves away from MCP toward APIs and CLIs, March 2026; Perplexity's public MCP server stays supported.

The fossil record

Six months before this article, it would have been easy to conclude that MCP was a nice idea with fatal security flaws. A single npm package had given an attacker full RCE on developers' machines.

The protocol absorbed the hits and kept growing. CVE-2025-6514 was patched. Tool poisoning was documented and mitigated by vendors adding description-change alerts1. OAuth 2.1 replaced the authentication free-for-all. The Linux Foundation gave it governance. 97 million monthly downloads suggest the developer community voted with their npm install.

1Invariant Labs' MCP-Scan (April 2025) and similar tools hash tool definitions on first sight and alert on any later change. Not part of the spec, but the ecosystem is filling the gaps.

The security problems are real and documented, and MCP got adopted by nearly every major client at once, which no earlier AI integration protocol managed. The security story now has to catch up with that adoption, and that is the race I am watching.