In this article
September 23, 2026
September 23, 2026

Delete the MCP servers and the agent is back to a long-lived key in a shell

The case against MCP is right about ergonomics and quiet about authorization. Here is what has to exist before direct API access is a safe default.

Explore with AI
Open in ChatGPT
Open in Claude
Open in Perplexity

If you remove an MCP server and call the API directly, nothing replaces the authorization layer you just deleted. The agent goes back to a long-lived key in a shell, the audience check on that credential disappears, and the record of which human approved what goes with it. MCP is currently the only place where user-delegated, audience-bound, revocable agent authorization is written down against a public specification. Whether the protocol earns its token cost is a separate question, and the two keep getting answered together.

The ergonomic case against MCP is winning right now, and it deserves to. On September 14, Maharshi Patel published "Why MCP Was Always a Bad Idea." A week later it reached the front page of Hacker News with 318 points and 309 comments when I read the thread. The argument is not new, Armin Ronacher made a version of it in July 2025, and we have published part of it ourselves.

What none of it addresses is what happens to authorization when the server comes out. This post is about that.

What the critics get right

The core claim is about context economics. An MCP server advertises its tools by loading schemas into the model's context window, and those schemas cost tokens on every turn whether or not the tools get used. Meanwhile the models got good enough to read --help output, parse a man page, and compose a shell pipeline. Ronacher's example still lands: the gh CLI beats the GitHub MCP server on context efficiency, and it does so without anyone writing or maintaining a wrapper.

Patel's second point is that most remote MCP servers wrap APIs that already exist and are already documented. That is true, and the redundancy is real. Cloudflare's Code Mode leaned into the same observation from the other direction: let the model write code that calls things, rather than making every call a protocol round trip.

The usage picture supports them too. Simon Willison, in the top comment on that thread, reckons Tools account for more than 95% of what people actually use MCP for, even though the spec also defines Resources, Prompts, and Elicitation. A protocol where one feature out of four carries nearly all the traffic is fair game for a rewrite.

So the ergonomic argument is settled. Worth noting what it is not about: Patel's post is about token cost and redundancy. He does not argue that MCP's security model was unnecessary, and nobody should put that claim in his mouth. The consequences for authorization are downstream of an argument that was not making them.

What leaves with the protocol

MCP is where agent authorization got specified in public. Not solved. Specified.

We have written up how MCP authorization works step by step, so rather than restate the whole flow, here are the three requirements that carry this argument. Under the current 2026-07-28 revision:

  • Clients MUST send an RFC 8707 resource parameter on both the authorization request and the token request, naming the canonical URI of the server the token is for, whether or not the authorization server supports it.
  • Servers MUST validate that a token was issued for them as the intended audience, and MUST NOT accept or transit anything else. Forwarding a token received from a client, which the spec calls token passthrough, is separately forbidden: it launders the audience, breaks the audit trail, and turns the server into an exfiltration path for a stolen token.
  • Servers MUST publish Protected Resource Metadata (RFC 9728), and clients MUST use it to discover the authorization server rather than being told where to authenticate.

That is not protocol ceremony. It is the list of questions an auditor asks: which credential, issued to whom, valid for what, approved by which user, recorded where. The direct-API path answers none of them, because nobody has written the document that would.

The wrapper you delete was also somewhere to put the token. A server or gateway can hold a credential the model never sees. An agent calling the service itself has to hold it.

Content negotiation is not a credential

The replacement sketched in Patel's post is honest about being a sketch:

"We should start to standardize how agents use HTTP APIs directly. For example, agent clients could attach headers to identify themselves as agents, and servers could automatically send them response data as Markdown or text instead of HTML or verbose JSON."

Accept: text/markdown is a genuinely good idea, and a growing number of documentation sites already honor it. It is also a rendering decision. It tells a server what shape of bytes to return, not who is asking or what they are allowed to do.

The post's claim that "much of the alternative already exists: documented HTTP APIs, standard content negotiation, and mature authentication mechanisms" holds for the first two. The mature authentication mechanism, in practice, is an API key in an environment variable that the model can read, that never expires, and that is scoped to everything the human who minted it can do.

Identity is not delegation

There is serious standards work on agents identifying themselves, and it is much better than a self-declared header.

Web Bot Auth is now a chartered IETF working group. The protocol draft, draft-ietf-webbotauth-httpsig-protocol-00, dated September 1, 2026, has agents sign requests with HTTP Message Signatures (RFC 9421). The operator hosts a key directory at a well-known URI, and requests carry Signature, Signature-Input, and Signature-Agent headers. Cloudflare has been running a version of it in production through its verified bots program.

Anyone dismissing this as "a header that says I am an agent" has not read it. Section 4.1 is careful on exactly that point: an unresolved Signature-Agent value is a claim rather than an identity, it becomes an identifier only once the verifier fetches the key and the signature verifies, and until then verifiers must not attach policy to it.

Then read the scope section. The protocol "does not authenticate human users, does not provide anonymous authentication, and does not define authorization or delegation." Section 4.1 adds that a valid signature says nothing about who operates the agent, whether it is benign, or whether the request is authorized. That is origin policy. The privacy guidance points the same way: signing keys should represent a role, company, or automation identity, and must not be tied to a specific human.

So Web Bot Auth answers "is this the same bot as last time, and does it belong to a domain." It does not answer "is this agent acting for Maria, and is Maria allowed to do this." Those are different questions, and only the second one is what an MCP access token carries.

A table comparing three mechanisms against six questions an API boundary needs answered. An API key in a shell answers almost none: partial on caller continuity, no operator binding, no human identity, only coarse key scope, not revocable or time-bound, and no audit record naming a person. Web Bot Auth answers the caller questions, yes on caller continuity and operator binding, but no on human identity, authorization, and audit, with only key rotation for revocation. MCP authorization answers the delegation questions, yes on human identity and revocation, partial on operator binding, scopes, and audit.

Cloudflare names the gap itself: the agent hitting your site is often not operated by the company that built it, and a platform runs automations for many end users, so the operator and the end user are different parties. Their experiment for carrying that chain is the RFC 7239 Forwarded header. It is an experiment, not a delegation grant.

Other proposals are circulating for the delegation half, including Dick Hardt's AAuth draft, which binds an agent identity to a signing key at a well-known URL. It is an individual submission, not working group adopted, and should not be cited as a standard.

The strongest objection is that MCP authorization is optional

Here is the rebuttal I would make to myself, and it lands.

Authorization in MCP is OPTIONAL. The specification says so. STDIO implementations are told not to follow it at all and to retrieve credentials from the environment instead, which is a long-lived key in a shell sitting inside MCP. Dynamic Client Registration, the mechanism most early servers shipped, is now deprecated in favor of Client ID Metadata Documents. The spec also concedes that it cannot enforce its own security principles at the protocol level.

MCP does not require per-tool consent either, whatever gets repeated in its defense. The key principles say hosts "must obtain explicit user consent before invoking any tool," but that sentence is lowercase, and the document's own BCP 14 note binds the keywords only in capitals. The normative requirement is a SHOULD to build robust consent flows. Consent in practice is a property of the host application, which is why the quality varies so much between clients.

And MCP's own flow has been attacked on its merits. In May, Christian Posta walked through a fully spec-compliant OAuth 2.1 authorization code flow against a dynamically discovered MCP server and showed it ending in a confused deputy: valid tokens handed to an attacker who can then call a legitimate service as you. His criticism is worth taking seriously precisely because he is not on the other side of the ergonomics argument. He thinks context bloat is a "how we got started with MCP" problem, not an MCP problem.

So a lot of installed MCP is already a subprocess reading an API key out of the environment, and on that stock I would be defending a document rather than a deployment.

Fair. The difference is direction of travel. The MCP HTTP transport has a specification that implementations are behind. The delete-everything plan has an implementation with no specification to be behind. One of those gaps closes by shipping. The other closes by someone writing the thing MCP already wrote.

What direct API access needs first

I would take the trade seriously the day an agent calling an API directly can do five things, none of which require MCP to be the vehicle.

  • Hold a token it cannot read. The credential terminates at a boundary the model does not control, whether that is a proxy, a gateway, or a sandbox. Willison's version, in the same comment: "a way to handle authentication that doesn't allow the agent to directly access API keys."
  • Present a token bound to one audience. Resource indicators and audience validation are already standardized and already mandatory in MCP. Direct API calls need the same rule, or a leaked token is a skeleton key.
  • Carry a delegation statement. Which user, which scopes, expiring when. Operator signatures deliberately exclude this, and a Forwarded value naming the operator is an experiment, not a grant.
  • Support an in-flight consent step. The 403 with error="insufficient_scope" and the required scopes named in a single challenge is the cheapest existing pattern for asking permission at the moment it is needed rather than up front.
  • Produce an audit record that survives review. Naming the human principal, not the service account. Per-client asymmetric keys exist for this reason: the Web Bot Auth draft rules out shared secrets because they break non-repudiation and make auditing difficult.

Those five are protocol-agnostic. You can satisfy them with MCP, with a gateway in front of raw HTTP, or with a hardened sandbox. What you cannot do is satisfy them with nothing, which is the default you land on when the server comes out and the API key goes into the environment.

Before you delete a server, write down where its token goes

Most MCP servers are wrappers, and most wrappers should go. That part of the argument survives contact with reality, and it was never the hard part.

The hard part is the boundary the wrapper happened to be standing on. For each server you remove, write one line: after this, the credential for this service lives here, issued by this party, bound to this audience, expiring then, approved by this user.

If the honest answer is an environment variable, forever, scoped to everything I can do, you did not simplify the stack. You moved the authorization problem into a shell where nothing is watching it.