# The Stronger the Harness, the Thinner MCP Gets · KinfeMichael Tariku

Source: /articles/the-stronger-the-harness-the-thinner-mcp-gets

[Blog](/articles)October 8, 2026

# The Stronger the Harness, the Thinner MCP Gets

Is MCP dead? Not quite, but it is moving down the stack. As agent harnesses learn to write and keep their own code, MCP stops being the way agents discover and call tools, and becomes the auth and policy boundary around them. A look at harnesses, OpenAPI, code mode, and where MCP is actually going.

[KinfeMichael Tariku](https://x.com/KinfishT)

@KinfishT

Note

> This reflects the state of MCP, agent harnesses and the surrounding tooling as of October 2026. The spec moves fast, and some details here will age.

The **MCP vs API** debate keeps coming back, and its loudest version goes like this: **"MCP is a dead end for serious agent systems."**

Every time it comes up, people split into two armies, and both are convinced the other has never shipped anything.

One side: _"My agent can write code. It can call the API directly. Why would I wrap it in another protocol?"_

The other side: _"So every agent rewrites the Stripe client from scratch, every time, with your API key sitting in its context? Enjoy your token bill."_

My honest conclusion is that **both sides are right, about different agents.** The thing nobody said out loud is the variable that decides who wins:

> **The stronger the harness, the less MCP is needed as a way to call tools, and the more it matters as a way to control them.**

To be clear up front: this isn't an "MCP is useless" post. MCP still solves real problems, and I'll list them. I'm pointing at a few drawbacks, and at a line that keeps getting thinner: as harnesses grow, more of what MCP does for the model, the harness can do on its own. What's left on MCP's side is the part that matters most.

MCP isn't dying. It's **moving down the stack.** Let's talk about why, and where it lands.

---

## First, What Do We Mean by "Harness"?

A model on its own is a text predictor. A **harness** is everything around it that turns it into an agent: the loop, the tools, the file system, the shell, memory, permissions, sub-agents.

Not all agents have the same harness, and that's the whole argument:

| Agent | What it can do | What it needs from the outside world |
| --- | --- | --- |
| **Chat assistant** (Claude.ai, ChatGPT) | Talk, call predefined tools | Someone else must hand it ready-made tools |
| **Tool-calling agent** (an app with a fixed tool list) | Pick from a menu | A menu: schemas, descriptions, a way to call them |
| **Coding harness** (Claude Code, Codex, OpenCode, your own) | Read files, write code, run it, keep it, reuse it | Documentation and credentials. It can build the rest |

A chat assistant is a person who can only order from the menu. A coding harness is a person who can walk into the kitchen.

MCP was designed, very sensibly, for the first two rows. The interesting question is what happens to it as more and more agents move into the third row.

---

## MCP Is Four Jobs Wearing One Protocol

People argue about "MCP" as if it were one thing. It's actually bundling four separate jobs:

1. **Describe:** "here are my tools, their names, inputs and what they do" (`tools/list`)

2. **Invoke:** "call this tool with these arguments" (`tools/call`)

3. **Transport:** JSON-RPC over stdio or Streamable HTTP

4. **Authorize:** OAuth 2.1, audience-bound tokens, and the server as the place where policy lives

The trick is that a strong harness **absorbs the first three** and **badly needs the fourth.**

Let's go through them one by one.

---

## Job 1: Describe. The Harness Already Has Better Sources

To call an API, an agent needs to know what exists. MCP's answer is: list every tool, with its JSON schema, and put the whole list in the model's context.

That's great for a chat app. For a coding harness, it's expensive in a very specific way: the description is **resent with every request**, whether the agent needs it or not. Connect ten servers with thirty tools each, and you've spent thousands of tokens before the user has typed anything.

Meanwhile, most serious APIs already describe themselves:

- an **OpenAPI** spec

- a typed **SDK** (TypeScript types are documentation that compiles)

- a **CLI** with `--help`

- actual docs

A harness doesn't need any of that pasted into its context. It can **read it from disk when it's relevant**, which is exactly how _you_ use documentation. You don't memorize the Stripe reference before writing a line of code. You open it when you need it.

This idea has a name now: **progressive disclosure.** Load what's needed, when it's needed.

---

## Job 2: Invoke. Code Beats Tool Calls

This is the part the "MCP is dead" take gets right, even if the headline overshoots.

With classic tool calling, every step goes through the model:

ModelContext windowevery tool schema + every result01 calllist_zones02 50 zones→ context03 callcreate_record04 result→ contextMCP server300 tools, all in the promptlist_zones · create_record · …

Fig. 01 — Classic tool calling Pause motion

One tool call per round trip. Every tool schema and every result passes through the model’s context.

Every intermediate result flows back through the context window. If tool A returns a 50-page document and tool B needs it, the model reads the whole thing just to pass it along, like a manager who insists on photocopying every memo before forwarding it.

With **code**, the agent writes the plan once and runs it:

ts Copy

```ts
// Written once, run in a sandbox. Only the end result comes back.
const doc = await drive.getDocument("Q3 plan")
const summary = extractActionItems(doc) // in code, not in context
await salesforce.updateOpportunity("acme-q3", { notes: summary })
console.log("updated", summary.length, "items")
```

The 50 pages never touch the context window. Loops, retries, filtering, joins: all in code, where they're cheap and deterministic.

And this isn't a hot take anymore. It's where the people who _build_ MCP are going:

- Anthropic's engineering team published [code execution with MCP](https://www.anthropic.com/engineering/code-execution-with-mcp): tools exposed as a code API that the model calls from a sandbox. In their example (Google Drive to Salesforce), token use dropped from about **150,000 to about 2,000**, a reduction of roughly 98.7% for that workflow.

- Cloudflare's [Code Mode](https://blog.cloudflare.com/code-mode/) converts MCP tools into a TypeScript API and asks the model to write code against it. Their follow-up put [the entire Cloudflare API behind two tools](https://blog.cloudflare.com/code-mode-mcp/), `search()` and `execute()`, at around **1,000 tokens**. The same API as classic MCP tools would have been about **1.17 million**.

Their explanation of _why_ it works is my favorite line in this whole debate, paraphrased: models have seen an enormous amount of real TypeScript, and only a small set of contrived tool-calling examples. **Code is the native language of the model. Tool-call JSON is a second language it learned in a weekend course.**

Notice the irony, though. **Both of those still use MCP underneath.** MCP stopped being the interface the model sees and became the backend that the code calls. Keep that in mind; it's the whole thesis.

### The Questions Everyone Asks About Code Mode

Whenever code mode comes up, the same handful of questions follow. They're good questions, so here are short answers (the longer ones are further down):

- **"Isn't this just programmatic tool calling? This isn't new."** Mostly, yes. Anthropic ships it under exactly that name, and people have been giving agents "a sandbox and two tools" for a while. What's new is the _scale_ of adoption, and that the companies behind MCP now recommend it.

- **"Any semblance of security just left the chat." / "Doesn't this defeat MCP as a safe tool layer?"** Only if the generated code holds the keys. Done right, it never does. The keys stay outside the sandbox, and MCP becomes the safe layer _behind_ the code instead of in front of the model. That's a whole section below.

- **"What about hallucinated tool calls?"** Code fails _louder_ than JSON tool calls. If the model invents a method that doesn't exist or passes the wrong arguments, a typed API catches it before anything runs, and the error message tells the model exactly what to fix. A made-up JSON tool call, by comparison, often just fails quietly at runtime. You can push this further by checking the whole script against your tool schemas _before_ the first call fires, so a bad plan never touches a real system.

- **"At this point, why not just give it the database? So… an API?"** Because "can reach it" isn't the same as "should reach all of it." The more direct the access, the more the boundary matters: scoped credentials, read-only by default, approvals for anything destructive. And yes, "so… an API?" is closer to right than it sounds. More on that in a moment.

### The Catch: Every Server Shouldn't Bring Its Own Sandbox

Here's the drawback of code mode that I think gets missed, and it's about _where_ it lives.

Right now, code mode mostly ships **inside each MCP server**. Cloudflare's server has its own `search` and its own `execute`, running in its own sandbox. Imagine every other server follows the same pattern:

ModelSees search + executeonce per connected serverCloudflare MCPsearchexecuteown sandboxcf.dns.create()reaches this serverand nothing elseGitHub MCPsearchexecuteown sandboxgh.issues.create()reaches this serverand nothing elseN servers · N searches · N sandboxes · no shared path

Fig. 02 — Code mode, per server Pause motion

Each MCP server ships its own search, execute and sandbox. Code in one sandbox can’t reach another server.

With four servers, that's four search tools, four execute tools and four sandboxes, all doing the same job. Worse, **they can't talk to each other.** Code running in Cloudflare's sandbox can only call Cloudflare. So "find the failing deploy on Cloudflare, open a GitHub issue, and post it to Linear" can't be one program anymore. The model has to hop between sandboxes and carry the results itself, which is the exact round-tripping code mode was supposed to remove. We'd have rebuilt the tool-sprawl problem one level up.

The fix is to move that layer **into the harness**, or into something that sits next to it:

Agent harnessModelWrites one scriptacross every serverOne searchover every server’stools, loaded onlywhen neededOne shared sandboxcloudflare.deploys()github.issues.create()linear.post()one program; dataflows between callsin codeCloudflare MCPtools + keysstay at the serverGitHub MCPtools + keysstay at the serverLinear MCPtools + keysstay at the server

Fig. 03 — Code mode, in the harness Pause motion

One search, one sandbox, one script across every server. The servers keep their tools and credentials.

One search over every connected tool. One sandbox. One program that can call Cloudflare, GitHub and Linear in the same script, with the data flowing between them in code. The MCP servers go back to doing what they're good at: describing their tools and guarding the credentials. The harness does the composing.

I think this is simply the right abstraction. **The harness owns composition: search, sandbox, the script. The servers own capabilities and trust: tool definitions, credentials, policy.** Each side does the thing it's actually good at, and you get one sandbox to secure instead of one per server.

Server-side code mode still has a place. For a gigantic API like Cloudflare's, or for a chat client with no sandbox of its own, the server doing it is the only option. But as a _default_, I'm convinced **search and execute belong to the harness, not to every server.** It's the same pattern as the rest of this post: as the harness grows, it absorbs the interface work, and the servers keep the trust work.

### Getting Harness-Level Code Mode Today

You don't have to wait for every harness to ship this. Here's what exists right now, from "built in" to "bolt on":

- **Built into the harness.** [Goose](https://goose-docs.ai/docs/guides/managing-tools/code-mode/), Block's open-source agent, shipped [Code Mode](https://goose-docs.ai/blog/2025/12/15/code-mode-mcp/) as a platform extension in v1.17. It wraps _all_ your other extensions as JavaScript modules and runs the model's code in one Deno-based runtime, so the model sees a handful of tools instead of dozens. This is the pattern done properly: one layer, inside the harness, across every server.

- **Bolted onto Claude Code or Codex.** [cmcp](https://github.com/assimelha/cmcp) is a small open-source proxy: you register _it_ as your only MCP server, add your real servers behind it, and the agent gets just `search()` and `execute()`. It generates TypeScript types from each tool's schema and runs the code in a QuickJS sandbox. It's a tiny project, but it shows how little is needed to retrofit this onto a harness that doesn't have it yet.

- **A gateway in the middle.** Executor (more on it below) does the same aggregation, one catalog with search, describe and call, and adds the parts a quick proxy skips: stored credentials, per-tool policies and approvals.

- **Experiments.** Armin Ronacher's [pi-codemode-mcp](https://github.com/mitsuhiko/pi-codemode-mcp) explores connecting MCP servers to the Pi harness through code instead of tool calls.

- **Building your own harness.** Anthropic's [code execution with MCP](https://www.anthropic.com/engineering/code-execution-with-mcp) describes the pattern on the client side: turn every connected MCP server into a code API inside one execution environment. Cloudflare's Agents SDK can wrap _any_ MCP server in code mode from the agent's side, not just Cloudflare's own.

One honest caveat for the bolt-on options: a proxy or gateway that sits in front of _all_ your servers sees all of your traffic, and often holds all of your tokens. That's fine, and often better than scattering them, but pick one you trust, or run it yourself. I see proxies as the stopgap. The end state is harnesses shipping this natively, the way Goose already does.

There's one thing this move doesn't solve by itself: if the harness's sandbox now calls Cloudflare, GitHub _and_ Stripe, it needs to do it **without holding all three sets of keys.** That's the next problem, and the one MCP is best placed to solve. More on that below.

---

## Job 3: Transport. HTTP Was Fine All Along

For a coding harness, transport is a non-problem. It has `fetch`. It has `curl`. It has a shell. JSON-RPC over Streamable HTTP is a perfectly good transport, but it isn't something a harness _needs_. An SDK over plain HTTPS does the job.

(Honestly, transport was never the exciting part of MCP. Nobody adopted MCP because they were thrilled about JSON-RPC 2.0.)

---

## Everything Above the API Is a Wrapper

Here's a framing that cleared up a lot of this debate for me. Stripe, GitHub, Cloudflare: the real thing is the **HTTP API**. Everything else is a client sitting on top of it:

code Copy

```
┌── SDK     (for programs)
             ├── CLI     (for terminals)
  HTTP API ──┼── Web UI  (for people)
             ├── MCP     (for agents)
             └── Skill   (for harnesses)
```

An SDK is the API wrapped in your language's functions. A CLI is the API wrapped in commands and flags. And an MCP server, for most services, is the API wrapped in a protocol that agents speak. Same endpoints, different envelope.

So "MCP vs API" is a slightly broken question. It's really **"which wrapper fits this caller?"** A chat assistant can only open the MCP envelope. A coding harness can open any of them, and the SDK or CLI is often the cheaper one to open.

Two honest caveats, so this doesn't become a slogan:

- **Some MCP servers wrap no API at all.** Blender, your local files, a browser session. There, MCP _is_ the interface.

- **MCP adds things the other wrappers don't**: a login flow designed for agents, risk annotations, the ability to ask the user a question mid-task. That's not "just a different protocol." That's agent-specific plumbing, and it's the part worth keeping.

But as a default mental model? **The API is the source of truth. Everything above it is packaging.** Pick the packaging your caller can open most cheaply.

---

## So Is "Write It Once and Save It" Just MCP With Extra Steps?

I've noticed this with my own setup. When a harness works with an API for a while, it doesn't rewrite the integration every time. It writes it **once**, saves it to the file system or to git, and reuses it on every run after that. And when I look at what it ends up with, it's a saved, reusable, documented integration that the model calls. Which is, more or less, an MCP server.

Here's the honest version: **it's MCP's first three jobs, done in code instead of protocol.** In a harness it looks like this:

code Copy

```
.agents/skills/stripe/
  SKILL.md   ← when to use it, auth, gotchas (read when relevant)
  client.ts  ← generated once from the OpenAPI spec, reviewed
  refunds.ts ← small, tested helpers the agent wrote and kept
```

md Copy

```md
<!-- SKILL.md -->
# Stripe
Use for refunds, invoices and customer lookups.
- Import from ./client.ts. Never construct raw HTTP calls.
- Auth: STRIPE_KEY is injected by the runtime.
  Never print it or pass it to tools.
- Refunds over $500 need a human: call `requestApproval()` first.
```

Compared to a stack of MCP tool definitions:

- **Discovery:** the agent reads one short file, and only when Stripe is relevant. Nothing sits in context the rest of the time.

- **Invocation:** typed code, composed freely, intermediate results stay out of context.

- **Reuse:** it's in git. It gets reviewed like any other code. It gets _better_ over time.

So yes: it's "MCP with extra steps." But the steps are steps the harness is already good at (reading files, writing code, running it), and the result is cheaper and more composable for that harness.

What it is **not** is a replacement for job number four.

---

## Job 4: Authorize. This Is Where MCP Earns Its Keep

Look at that `SKILL.md` again: _"STRIPE_KEY is injected by the runtime."_

That one line is doing a lot of unearned work. In the harness-only world, **the agent's process holds the real key.** Every script it writes can call any endpoint that key allows. A prompt injection, a malicious README, a poisoned web page: suddenly that key is one creative tool call away from doing something you didn't intend.

This is the part of MCP that a stronger harness does **not** absorb, because it's not a capability problem. It's a **trust** problem. Making the agent smarter doesn't make it more trustworthy; arguably it makes the blast radius bigger.

What MCP gives you here:

- **The model never holds the credential.** The client attaches an OAuth token; the server holds the upstream API key. The model sees `refund_order({ id })` and a result, nothing else.

- **Least privilege by construction.** If the server doesn't expose `delete_customer`, no amount of prompting produces it.

- **Audience-bound tokens.** OAuth 2.1 with PKCE, resource indicators and no token passthrough. A token for one server is useless at another.

- **One choke point for policy and audit.** Rate limits, approvals, logs, "refunds over $500 need a human": in one deterministic place, not scattered across scripts an agent wrote at 3 AM.

- **Enterprise control.** With enterprise-managed authorization (ID-JAG, which Okta calls Cross App Access), a company's IdP decides which agent may reach which app for which employee, and disabling the employee cuts off their agents. Atlassian has already [shipped this for its Rovo MCP server](https://www.atlassian.com/blog/development/enterprise-managed-authorization-rovo-mcp-xaa-id-jag).

None of that is about how the model _discovers_ tools. All of it is about how a company can **let** a model touch real systems and still sleep at night.

---

## Code Mode's Dirty Secret: Where Do the Keys Live?

Code mode has a question nobody likes to answer in the demo: **the code the model writes needs to call Stripe. Stripe needs a secret key. So where is the key?**

The naive answer is "in the sandbox's environment variables." I believe that's the wrong answer. A sandbox isolates the _process_. It doesn't isolate the _secret_. If `STRIPE_KEY` is readable inside the box, then a prompt-injected agent doesn't need to escape anything. It just reads the variable, or prints it to a log that flows straight back into the model's context.

From what I've seen, the serious platforms have converged on one idea: **the key never enters the box. It gets added on the way out.** I see three shapes of it:

**1. Placeholder swap at the egress proxy.** Inside the sandbox, the "key" is a fake string. All network traffic is forced through a proxy outside the sandbox, which swaps the placeholder for the real secret, but only for the approved host:

code Copy

```
inside the sandbox:
  Authorization: Bearer __STRIPE_KEY__   ← placeholder, useless

the proxy, outside the sandbox:
  to api.stripe.com? → swap in the real key → forward
  to evil.com?       → no swap, or blocked
```

[Deno Sandbox](https://news.ycombinator.com/item?id=46874822) works this way: code only sees a placeholder, and the real value appears only on requests to the host that secret is tied to. [Vercel Sandbox](https://vercel.com/changelog/safely-inject-credentials-in-http-headers-with-vercel-sandbox) injects headers at the network layer outside the VM, overwriting whatever the code tried to set. Infisical's open-source Agent Vault does the same as a proxy you run yourself.

**2. Capabilities instead of keys.** Cloudflare's [Dynamic Workers](https://blog.cloudflare.com/dynamic-workers/) can start the sandbox with no network access at all, then hand it an object, not a credential. The code calls `cf.dns.create(...)`, and the parent Worker adds the credentials on the outbound request. The agent can't leak a secret it never held. It holds a _door_, not a _key_.

**3. A gateway that holds everything.** The agent never talks to Stripe at all. It calls a tool on a gateway, an MCP server or something like Executor (more on that below), and the gateway makes the real call with the credential it stores.

What I find telling is what all three have in common: **a trusted boundary outside the agent's code that owns the credential.** I believe that boundary is exactly the job MCP is growing into. In my view, code mode doesn't remove the need for it. It makes the need _louder_.

My strong opinion on this:

- **I'd go as far as calling a raw production secret in a sandbox's environment a bug.** Not a trade-off, a bug. If your code-mode setup works that way, I'd fix that before shipping anything.

- **But I believe injection only stops theft, not misuse.** As one write-up put it well: [the sandbox isolates the agent, it doesn't isolate the secret](https://williamzujkowski.github.io/posts/2026-07-02-agentic-ai-sandbox-secret-proxying-gap/). And even with a proxy, a tricked agent can still _use_ the key through the proxy: refund the wrong order, delete the wrong branch. So you still need narrow scopes, short-lived tokens, host allow-lists, per-tool policies and approvals for anything destructive.

- **Watch the logs.** From what I've seen, a lot of real credential leaks aren't clever attacks. They're a `console.log` whose output gets piped back into the model's context, and from there into a transcript.

I think the industry is clearly feeling this. Whenever I look at what people are saying about agent credentials, I see the same complaint over and over: static API keys in config files, agents with far more access than they need. I also see vendors racing to ship credential vaults, proxies and managed credential APIs for agents. I don't think that's a niche concern anymore. I believe it's the next layer of the stack being built.

**This is also what we're building toward with [Farm.js](https://farmjs.dev/agents).** Today, Farm.js can already expose selected API routes as MCP tools, keeping each route's own validation and putting authorization in one place on the server. The direction I believe in, and the one we're working on, goes a step further: the MCP tools that connect to your services, and the code mode that composes them, should live directly in the harness. The harness gets one search and one sandbox. The connections to GitHub, Stripe or your own APIs stay behind a boundary that owns the credentials. Getting there is one of the goals of [farmjs.dev/agents](https://farmjs.dev/agents), and honestly, this post is partly me explaining why.

---

## One Gateway Instead of Twenty Connections

There's a second, more mundane auth problem: **sprawl.**

Today, a typical setup looks like this. Two tools for GitHub, a couple for Jira, a couple for Slack, a Linear server, a Postgres server. Each one is configured separately, in _each_ agent: once in Claude Code, again in Cursor, again in Codex. Each one has its own login or pasted API key. Each one dumps its tool list into the context.

[Executor](https://executor.sh) (open source, MIT, [on GitHub](https://github.com/UsefulSoftwareCo/executor)) is a good example of the fix:

code Copy

```
Before (per agent, repeated for each):
   Claude Code ──┬── GitHub MCP  (own login)
                 ├── Jira MCP    (own login)
                 ├── Slack MCP   (own login)
                 └── Stripe key  (pasted in config)

 After:
   Claude Code ─┐
   Cursor ──────┼── Executor ──┬── GitHub  (OAuth, once)
   Codex ───────┘  (one MCP)   ├── Jira
                               ├── Slack
                               └── Stripe  (key in vault)
```

How it works:

- **You connect each service once**, to Executor: MCP servers, OpenAPI specs, GraphQL APIs, even several accounts of the same service (work GitHub and personal GitHub).

- **Every agent connects to just Executor**, and authenticates to it once.

- **The agent doesn't get every tool in its context.** It searches the catalog, looks up the one tool it needs, then calls it. Classic progressive disclosure, code-mode style, with the code running in an isolated runtime.

- **Executor injects the credentials at call time**, so the agent's code never sees the raw token. Secrets can live in an encrypted file, your system keychain or 1Password.

- **Each tool gets a policy**: always allowed, needs approval, or blocked.

So yes: you authenticate to Executor, Executor stores your tokens, and it makes the calls to your services on your behalf. That's an important detail to sit with: **the gateway now holds the keys to everything.** That's the right design. One well-guarded place is better than tokens scattered across five agent configs. But it means the gateway's own security, and where it runs, matter a lot. (Executor can run locally or self-hosted, so the keys don't have to leave your machine, or as a hosted service if you'd rather not run it.)

---

## The Defense: Where MCP Is Simply the Right Answer

Before I go full "harness maximalist," let me steelman the other side properly, because it has real points:

**1. Most agents aren't coding harnesses.** The biggest AI surfaces in the world are chat apps, used by people who will never open a terminal. For them, MCP isn't a choice; it's the _only_ way their assistant reaches their tools. "MCP only helps chat apps" sounds like a dismissal until you remember how big the chat apps are.

**2. Things with no API.** Blender, Unity, Figma's canvas, a browser session, a local database, a Kubernetes cluster. Plenty of what agents should reach doesn't have a clean OpenAPI spec to generate a client from. An MCP server is the adapter.

**3. The M×N problem is real.** If every harness writes its own Stripe integration, we're back to every-app-integrates-with-every-service. MCP moves that cost to the service owner, once. Skills and generated clients help _inside_ one harness. They don't help the ecosystem.

**4. Messy systems need a human-designed interface.** Picture an ERP with 5,500 tables. "Point the agent at the database" is not a plan there. A curated tool surface (`find_open_orders`, not `SELECT *`) is domain knowledge, encoded once.

**5. And OpenAPI isn't a silver bullet either.** Let's be fair to MCP:

- many specs are **stale, incomplete or auto-generated** from code that lies about its own types

- OpenAPI describes _endpoints_, not _intent_. It tells you `POST /v1/refunds` exists, not when an agent should use it or what it must check first

- OpenAPI has security _schemes_, but no story for **agent** authorization: no consent, no audience binding, no enterprise policy

- 400 endpoints is not a tool surface; it's a phone book

So the honest picture is: **OpenAPI is a great source of truth for describing an API. MCP is a better envelope for letting an agent use one safely.** They're not rivals; they're layers.

---

## To Be Clear: What MCP Is Still Solving Today

I don't want this post to read as "MCP doesn't do anything." It does a lot, and some of it is easy to forget precisely because it already works. Here's what MCP is solving **right now**, that a plain API or a harness-written client doesn't give you for free:

- **One integration, every client.** Write an MCP server once and it works in Claude, ChatGPT, Cursor, VS Code and every other host that speaks MCP. Before MCP, every assistant had its own plugin format. That alone was a huge unlock.

- **A standard way for agents to log in.** OAuth 2.1 with PKCE, discovery through well-known metadata, clients that can identify themselves without manual setup, tokens bound to one server. Before MCP, every agent integration invented its own auth story, usually "paste your API key here."

- **Credentials that never reach the model.** The client holds the token, the server holds the upstream key, the model only sees tool names and results. A harness with a key in its environment can't say the same.

- **Intent, not just endpoints.** A good MCP tool says _when_ to use it and how risky it is. Tool annotations like `readOnlyHint` and `destructiveHint` let a host decide what needs a human's approval. OpenAPI has no field for "this one deletes things, ask first."

- **More than tools.** MCP also has resources (data the app can attach), prompts (reusable workflows a user can trigger), elicitation (the server can ask the user a question mid-task) and sampling (the server can ask the client's model for help). An OpenAPI spec has no concept of any of these.

- **Local things, safely.** A stdio MCP server can expose your local database, your editor or Blender to an agent without opening a network port or writing a custom bridge.

- **An ecosystem that already exists.** Thousands of servers are already written. "Just generate a client from the spec" assumes a good spec exists. Often the MCP server is the best-maintained interface a product has for agents.

- **A place for the company to say no.** Scopes, approvals, audit logs, enterprise-managed authorization. The governance story for agents is being built on MCP, not around it.

So when I say "the harness eats the interface," I mean **one** of MCP's jobs gets cheaper to do another way, for **one** kind of agent. Everything on this list still stands.

---

## The Line Gets Thinner as the Harness Grows

So this isn't "MCP is useless." It's two simpler claims: MCP has some real **drawbacks** for capable agents, and the gap between _what MCP gives you_ and _what a good harness can do on its own_ keeps shrinking.

The drawbacks, plainly:

- **Context cost.** Every connected server puts its tool list in front of the model on every request, used or not.

- **Indirection.** Every intermediate result travels back through the model, even when it's only being passed to the next call.

- **A second interface to maintain.** Many MCP servers are thin wrappers over APIs that already have a spec, an SDK and a CLI.

- **JSON tool calls aren't the model's strongest language.** Code is.

And here's how the line moves as the harness gets stronger:

| Harness strength | What MCP is doing for you |
| --- | --- |
| Chat assistant, no code | Everything: discovery, invocation, auth, safety |
| Tool-calling agent | Discovery, invocation, auth, safety |
| Coding harness with a file system | Mostly auth and safety; discovery and invocation can move into code |
| Coding harness with skills, code mode and a sandbox | Auth, policy, audit: the boundary, not the menu |

Each step down the table, MCP does less of the _interface_ work and more of the _trust_ work. The line gets thinner and thinner. It doesn't disappear, because the bottom row still needs someone to hold the keys, enforce scopes and log what happened, and that's exactly what MCP is good at.

---

## Where This Is Going

Here's my bet, with my confidence attached so you can hold me to it:

**1. Progressive disclosure becomes the default way agents consume tools.** _(High confidence.)_ Stuffing every tool schema into the prompt will look as dated as loading every JavaScript file in the `<head>`. Search, load on demand, code against typed APIs. This is already happening inside MCP itself (code mode, tool search) and outside it (Skills). And it settles in the **harness**: one search and one sandbox across every server, not a separate `search` and `execute` bolted onto each one.

**2. MCP's center of gravity moves from "tool calling" to "trust boundary".** _(High confidence.)_ The interesting MCP work will be auth, identity, policy, audit, enterprise management, sandboxed execution. The model-facing part gets thinner; the server-facing part gets thicker. MCP becomes less like a menu and more like a **firewall with documentation.**

**3. OpenAPI and MCP converge into one contract.** _(Medium-high confidence.)_ Today people hand-write MCP servers that wrap APIs that already have specs. That's busywork. The endpoint definition should be the single source of truth, generating the OpenAPI spec, the MCP tools, the typed SDK _and_ the agent skill from one place. Which is why I think this belongs in **frameworks**, not in hand-written wrappers.

**4. Raw JSON tool calling declines for coding agents.** _(Medium confidence.)_ For agents that can run code, "the model emits a JSON blob and waits" becomes the fallback, not the main path. Chat agents will keep classic tool calls for a long time.

**5. MCP does not die.** _(High confidence.)_ Chat surfaces, enterprises, no-API tools and the M×N problem keep it essential. Some people predict nobody will use it in ten years. I'd take the other side of that bet. Protocols that solve trust don't disappear; they become boring infrastructure. (Ask OAuth.)

The one-line version:

> **Harnesses eat the _interface_. MCP keeps the _boundary_.**

---

## What This Looks Like in a Framework

This is the part I care about as someone building one.

If the endpoint is the source of truth, the framework should be able to project it into every surface an agent (or a human) might need. In Farm.js, an API route is defined once with `createEndpoint`, and the same definition already feeds the generated OpenAPI reference _and_, when you opt in, an MCP tool:

ts Copy

```ts
// src/app/api/projects/route.ts
import { createEndpoint } from "@farm.js/core/api";
import { z } from "zod";

export const GET = createEndpoint(
  {
    method: "GET",
    query: z.object({
      limit: z.coerce.number().int().positive().default(20),
    }),
    mcp: {
      name: "list_projects",
      description: "List visible projects.",
    },
  },
  listProjects,
);
```

One definition, and:

- the **OpenAPI** spec documents it, with a typed `limit` query param

- the **MCP** endpoint exposes it as `list_projects`, keeping the route's own validation, middleware and error handling

- authorization is decided **at the server**, per request, not by whatever code an agent wrote

The next step, and the one I think every framework will take, is to project that same contract into **harness-native form** too: a typed client and a skill file the agent can load on demand. Same validation, same auth boundary, three doors in: humans read OpenAPI, chat agents use MCP, coding harnesses use code.

Write the contract once. Let the framework worry about which door the caller came in through.

---

## Things I'd Build From Here

If you're looking for gaps (and I always am), this shift opens a few:

- **OpenAPI → harness skill generator.** Take a spec, emit a typed client plus a short `SKILL.md` with intent, auth notes and gotchas. The boring 80% of "write it once and save it," automated.

- **An agent-grade spec linter.** Score an OpenAPI spec on whether an agent can actually use it: missing descriptions, vague errors, undocumented pagination, side effects that aren't marked. Most specs would fail, which is the point.

- **An MCP auth gateway for small teams.** I had this on my list, then found it already exists: [Executor](https://executor.sh) is pretty much that, open source. The gap that's still open is one level up: gateways like it plugging into a company's IdP, so IT decides which agent gets which tool for which employee (the ID-JAG flow), instead of each person connecting accounts by hand.

- **Code-mode by default.** An MCP client that never puts tool schemas in context, only a `search` and an `execute`, with a sandbox.

---

## What the Wider Debate Says

Before wrapping up, I went through what others have written and measured on both sides. Here's my short version of it.

**The critics are right about cost.**

- In [Scalekit's benchmark](https://www.scalekit.com/blog/mcp-vs-cli-use) of GitHub's MCP server against the `gh` CLI, MCP used **4 to 32 times more tokens**, and 28% of its runs failed on timeouts.

- [Quandri](https://www.quandri.io/engineering-blog/mcp-is-dead) found 77 tools across four servers eating about 10% of a 200k context window before any work started.

- [Vercel replaced about 80% of an agent's tools](https://vercel.com/blog/we-removed-80-percent-of-our-agents-tools) with a single bash tool in a sandbox, and success went from 80% to 100% while it got more than three times faster.

- [Mario Zechner](https://mariozechner.at/posts/2025-11-02-what-if-you-dont-need-mcp/) and [Armin Ronacher](https://lucumr.pocoo.org/2025/7/3/tools/) showed small scripts and generated code beating big tool lists. (To be fair, [Mario's own benchmark](https://mariozechner.at/posts/2025-08-15-mcp-vs-cli/) found MCP and a CLI equally reliable once the tools were designed well. Tool design mattered more than the protocol.)

**But the fix uses MCP; it doesn't replace it.**

[Anthropic](https://www.anthropic.com/engineering/code-execution-with-mcp), [Cloudflare](https://blog.cloudflare.com/code-mode/) and [Goose](https://goose-docs.ai/blog/2025/12/21/code-mode-doesnt-replace-mcp/) all landed on the same answer: let the model write code, and call MCP underneath it. Some of the loudest early critics moved the same way. Armin Ronacher [now argues](https://lucumr.pocoo.org/2026/10/6/codemode/) that MCP benefits greatly from code mode, and [Simon Willison came back to MCP](https://simonwillison.net/2026/Jul/31/stateless-mcp/) after the stateless spec, because a fixed set of tools is much easier to audit than a shell with internet access.

**The defenders are right about trust.**

- A malicious public GitHub issue got an agent to [leak private repositories](https://invariantlabs.ai/blog/mcp-github-vulnerability) through the GitHub MCP server.

- A poisoned support ticket got an agent holding an over-privileged key to [leak a Supabase table](https://generalanalysis.com/blog/supabase-mcp-blog).

- Instructions hidden in tool descriptions [worked about a third of the time](https://arxiv.org/abs/2508.14925) across 45 real servers.

- Researchers from OpenAI, Anthropic and Google DeepMind [beat 12 out of 12](https://simonwillison.net/2025/Nov/2/new-prompt-injection-papers/) published prompt-injection defenses.

I believe no filter will save us here. What helps is someone outside the agent's code holding the keys, limiting what each tool can do and keeping a record. That's exactly where MCP has been investing: OAuth 2.1, [enterprise-managed authorization](https://blog.modelcontextprotocol.io/posts/enterprise-managed-auth/) already supported by Okta, Atlassian, Linear and others, and [enterprise allow-lists](https://github.blog/changelog/2026-08-06-mcp-allowlists-in-enterprise-managed-settings/) for which servers are allowed at all.

**Even MCP's own maintainers agree on where the interface work belongs.** At the [MCP Dev Summit](https://aaif.io/blog/mcp-is-now-enterprise-infrastructure-everything-that-happened-at-mcp-dev-summit-north-america-2026/), lead maintainer David Soria Parra called context bloat a client problem rather than a protocol problem, fixed by clients loading tools on demand. In other words: the harness should handle it.

The line from all of this that stuck with me most came from a [comment Simon Willison quoted](https://simonwillison.net/2026/Jun/19/sean-lynch/): _"Maybe the idealized form of MCP is just an auth gateway for the API."_ I'd say it slightly differently, a trust boundary rather than just an auth gateway, but I think that's where this is all heading.

---

## Conclusion

The "MCP is dead" take is half right. For a strong harness, MCP as a _tool menu_ is often the expensive way to do something the harness could do better in code. Anthropic and Cloudflare have, more or less, said the same thing with numbers attached.

But "dead" confuses the interface with the boundary. As agents get more capable, the question shifts from _"can the agent call this API?"_ (yes, obviously, it can write the client in a minute) to _"should it, as whom, with which permissions, and who can see what it did?"_

That second question doesn't get easier as models get smarter. It gets harder.

That's the job MCP is growing into. The menu gets thinner. The boundary gets thicker.

And honestly? That's a much better job.

To be clear one last time: MCP is still solving real problems today, and plenty of agents need all of it. My point is narrower. For capable harnesses, the line between "use MCP" and "just write the code" keeps getting thinner, and what's left on MCP's side of that line is the trust boundary. That's not a protocol dying. That's a protocol finding its job.

All posts Follow @KinfishT
