Blog · Opinion · 20 October 2026

MCP is GraphQL all over again

The same reasons I disliked GraphQL, I dislike MCP. It's too open-ended and iffy, it's hard to cache, it's easy to attack, the auth is weird and an add-on, and everything about it is verbose. It feels like a bodge, and I think we'll suffer if we stay with it. We run an MCP server ourselves, and I measured that one too.

Preview This post is scheduled for 20 October 2026 and may change before then. It isn't on the blog yet.

The same reasons I disliked GraphQL, I dislike MCP. It's too open-ended and iffy, it's hard to cache and it's easy to attack. The auth is weird and an add-on, the rate limits are weird, and everything about it is verbose. It feels like a bodge to me, and I think we'll suffer if we stay with it.

I should say up front that we run an MCP server. Agents ask for one, so Anchor Terminal has one, and we grade 431 listings that come with a remote MCP server. I'm not writing this from outside. I've said before that MCP is not enough and that I don't think it's the protocol to solve the problem of agents using the web (personal agents sell errands they can't run). This is the long version.

How verbose is MCP?

Anthropic's engineers wrote in November 2025 that most MCP clients load every tool definition into the model's context up front [1], so the tool list rides along with every request. Some clients now load tools on demand, and many still don't. The size of a tool list is a tax on every turn, paid in tokens. On 16 October I asked four public MCP servers for theirs, ours included [10].

Tool lists on 16 October 2026, estimatedTokens sent to the model for each server's tool list

Tokens

  1. InboxAPI (27 tools)6,714
  2. Kiwi.com (2 tools)4,527
  3. Anchor Terminal (10 tools)4,008
  4. Microsoft Learn (3 tools)1,214

Bars start at zero. Values are labelled in each panel.

Kiwi.com's flight search is two tools, and they cost about 4,500 tokens before the agent has asked for anything. Ours is ten tools for about 4,000, which isn't something to be proud of. Then the answers. A single Kiwi search came back as 46,864 bytes, with the same 15 flights written out once as text for the model (26,681 characters) and again as structured data (18,148) [10]. The specification asks for it. A tool that returns structured content "SHOULD also return the serialized JSON in a TextContent block", for backwards compatibility [7].

46,864bytes in the answer
26,681characters of flights as text for the model
18,148characters of the same flights again as structured data
One Kiwi.com flight search over MCP, 16 October 2026. Source [10].

The people who built MCP and the people running it at scale are working around this. Anthropic's engineers wrote that tool definitions and intermediate results crowd the context window, and estimated that one example task would drop from 150,000 tokens to 2,000 if the agent wrote code against the tools instead of calling them one by one [1]. Cloudflare's MCP server for its whole API exposes two tools, search and execute, because describing its more than 2,500 endpoints as one tool each would take about 1.17 million tokens, and the code version takes about 1,000 [2].

150,000 → 2,000tokens for one example task, Anthropic, calling tools against writing code
1.17M → 1,000tokens to describe Cloudflare's 2,500 endpoints, as tools against as code
What it costs to describe an API to a model. Sources [1] and [2].

Both of them fixed it by taking the tool calls away from the model and letting it write code, and Cloudflare's code goes straight at its REST API [2].

Why can't you cache it?

Over HTTP, every message an MCP client sends is a POST to one endpoint [6]. There's no URL per resource and no ETag, so a CDN or a browser cache has nothing to hold on to. All four servers I measured answered their tool list with no-cache or no-store in the HTTP headers. The same search on our plain API is a GET with Cache-Control: public, max-age=60 [10].

MCPOne search over MCP, as Microsoft Learn and InboxAPI still speak it
  1. InitializeOverhead

    POST initialize, and the server answers with what it can do [6]

  2. Say you're readyOverhead

    POST notifications/initialized, which the client MUST send once initialised [14]

  3. List the toolsOverhead

    POST tools/list, about 4,000 tokens for ours, marked no-store [10]

  4. Call the toolThe answer

    POST tools/call with the search, also marked no-store [10]

  • The answer
  • Overhead
HTTPThe same search over plain HTTP
  1. SearchThe answer

    GET /api/v1/search?q=send+email, which any cache can keep for 60 seconds [10]

  • The answer

That's my GraphQL complaint. One endpoint and POST for everything, so nothing between the client and the server can see what a request is, cache it or limit it without learning a protocol of its own. MCP at least has fixed tools where GraphQL let the client write the query, but they share the part that hurts, an API that HTTP can't see into.

Why does it feel like a bodge?

Because it keeps rebuilding HTTP inside a POST body, one revision at a time. There have been five versions of the specification in twenty months, November 2024, March 2025, June 2025, November 2025 and July 2026 [15].

The July 2026 revision removed the initialize handshake that every earlier version required, removed sessions, the GET stream and resuming a broken stream, and deprecated Roots, Sampling and Logging [15]. Then it added ttlMs and cacheScope to tool lists, which the specification itself calls "analogous to HTTP Cache-Control: public vs Cache-Control: private" [16]. And it now copies the method and the tool name into Mcp-Method and Mcp-Name headers "so that intermediaries (load balancers, gateways, observability tooling) can route and inspect requests without parsing the body" [17]. You're just rebuilding GET requests instead of using them.

5versions in 20 months
9major changes in the July 2026 revision
3features deprecated in July 2026, Roots, Sampling and Logging
MCP's specification, October 2026. Source [15].

Our own server speaks the new version, and it says two things at once. Asked for its tool list on 16 October, the body said any client or proxy may cache it for five minutes (ttlMs: 300000, cacheScope: public), and the HTTP header on the same answer said no-store [10]. Two cache systems in one response, and both of them are ours. Fixing the header wouldn't help much, because shared caches don't store the answer to a POST, which is why MCP needed its own field in the first place.

The suffering is in the migration. Of the four servers I measured, Kiwi.com and ours answered the July 2026 version, and Microsoft Learn and InboxAPI didn't [10]. Every client now has to speak both, and the specification promises at least twelve months before anything deprecated is removed [15].

Why is the auth weird?

Because it was added later and has changed in every revision since. The first MCP specification, in November 2024, said authentication and authorisation were "not currently part of the core MCP specification" and left it to each implementation [19]. March 2025 added an OAuth 2.1 framework, replaced the transport and added JSON-RPC batching [3]. June 2025 removed the batching three months after adding it, reclassified MCP servers as OAuth resource servers and made clients send resource indicators so a malicious server couldn't get tokens meant for another [4]. November 2025 changed authorisation again, with OpenID Connect discovery, incremental scope consent and client ID metadata documents [5]. July 2026 deprecated dynamic client registration in favour of those metadata documents and added issuer checks [15].

SpecHow MCP's authorisation got here
  1. November 2024No auth

    Left out of the core specification [19]

  2. March 2025Changed

    OAuth 2.1 added, the transport replaced, batching added [3]

  3. June 2025Changed

    Batching removed, servers became OAuth resource servers, resource indicators required [4]

  4. November 2025Changed

    OpenID Connect discovery, incremental consent, client ID metadata documents [5]

  5. July 2026Changed

    Dynamic client registration deprecated, issuer checks added [15]

  • Changed
  • No auth

You can see it in what's deployed. Of the 431 listings we grade with a remote MCP server, 296 have more than one way to authenticate, an OAuth flow for the hosted server and an API key for the local one, say. 87 take an API key, 35 only OAuth and 11 nothing at all [11]. Most of them still take an API key in a header, which has been a solved problem for twenty years.

Anchor Terminal, 16 October 2026How the 431 listings with a remote MCP server authenticate

Listings

  1. More than one way296
  2. API key87
  3. OAuth only35
  4. No auth11
  5. Personal access token2

Bars start at zero. Values are labelled in each panel.

What's weird about the rate limits?

The specification says servers MUST rate limit tool invocations [7]. It doesn't say how a server tells the client it has been limited. There's no 429 and no Retry-After anywhere in it, up to and including the July 2026 revision [15]. HTTP solved this with a status code and a header the client can read and wait on. MCP leaves each server to invent its own, and Notion's documentation shows what that looks like. It retries once if the wait is two seconds or less, otherwise "it returns the rate limit as a tool error", and if you hit one, "prompt your LLM tool to reduce parallel operations" [13].

Why is it easy to attack?

Because whatever an MCP server sends ends up in the model's context, and the model can't reliably tell instructions from data. In April 2025 Invariant Labs showed that hidden instructions in a tool description, "invisible to users but visible to AI models", could get an agent to read SSH keys and config files and pass them on [18]. In May 2025 they showed something wider, a malicious issue in a public GitHub repository that got an agent using GitHub's MCP server to copy data from the owner's private repositories into a public pull request. Their point was that it's "not a flaw in the GitHub MCP server code itself", so GitHub couldn't patch it on the server [8] (an agent reading the same issue through GitHub's REST API would have had the same problem, so that one is about agents as much as MCP). In July 2025 JFrog disclosed that mcp-remote, a widely used proxy between desktop clients and remote MCP servers, would run a command a malicious server sent while connecting and authorising, scored 9.6 out of 10, in a package downloaded more than 437,000 times [9].

9.6CVSS score of CVE-2025-6514 in mcp-remote
437,000+downloads of mcp-remote when it was disclosed
Two public MCP vulnerabilities. Sources [8] and [9].

On 15 October Moe Katib wrote on LinkedIn that a developer had reported on Reddit that Notion's official MCP server slipped an upsell into his agent's answer. Claude organised his notes, then added a line pitching Notion Business, with a link. Asked to inspect the connector, it found a tool called check-mcp-next-steps whose instructions told the agent to finish the user's task first, add one short sentence tied to their work plus a link, and never mention limits, eligibility or frequency, and the link carried a tracking field called mcpUpsellOpportunityId [12]. Notion's public list of its MCP tools doesn't include it, though the same page says several tools return "an upgrade prompt" on lower plans [13]. We haven't been able to confirm the hidden tool ourselves. If it's true, it would be the whole problem in one tool. A tool description is an instruction to the model, written by the vendor, and the person reading the answer thinks their agent said it.

Our check for MCP servers has 29 rules, and many of them exist because of this. Hidden instructions in descriptions, safety hints that contradict what a tool does, parameters nobody documented. An API with an OpenAPI spec has the same problems in theory, and the MCP specification does say descriptions should be treated as untrusted, but in practice the clients paste them into the model's instructions on every turn.

What would I use instead?

I think we already have a more elegant solution. Plain HTTP. GETs a CDN can cache, an OpenAPI spec, an llms.txt and Markdown versions of pages for the model to read, a key in a header or a standard OAuth flow, 429 and Retry-After for rate limits, and 402 for payment (agents need payment channels). That's how we built Anchor Terminal, and our MCP server is a thin wrapper over the same API. Cloudflare's fix was to have the model write code against its API, which is what an agent with a plain HTTP API and a good spec does anyway [2].

We'll keep running our MCP server for as long as agents ask for it, and I'll cut our own tool list down, because 4,000 tokens is too much. What I don't know is whether MCP settles after July 2026 or keeps rebuilding HTTP one revision at a time, the way GraphQL grew its own caching and persisted queries. If you've run an MCP server in production and found ways round any of this, I'd like to hear them.

How I measured it

On 16 October 2026 I sent initialize, then tools/list, to the public MCP servers we list that need no key, Anchor Terminal, Kiwi.com, Microsoft Learn and InboxAPI (draw.io, tldraw and Checkatrade answered 403). I then sent server/discover and tools/list with the July 2026 protocol version. Kiwi.com and ours answered, Microsoft Learn said the method isn't available and InboxAPI couldn't parse the request. Token counts are the compact JSON of the tool list divided by four, the same estimate our check uses, so read them as sizes to compare, not as any one model's count. The Kiwi answer is the search-flight call from our flight test. The cache headers are as each server sent them. The comparison is our own search_tools over MCP against GET /api/v1/search for the same query.

References

Read on 16 October 2026.

  1. [1]Adam Jones and Conor Kelly, Code execution with MCP, Anthropic Engineering, 4 November 2025. Source for most clients loading every tool definition into context, intermediate results passing through the model, and the example estimate of 150,000 tokens down to 2,000. anthropic.com
  2. [2]InfoQ, Cloudflare's Code Mode MCP server, 16 April 2026. Source for the two tools, search and execute, the code calling Cloudflare's API, and the drop from about 1.17 million tokens to about 1,000 for more than 2,500 endpoints. infoq.com
  3. [3]Model Context Protocol, changelog for 2025-03-26. Source for the OAuth 2.1 framework, the new transport and JSON-RPC batching added in March 2025. modelcontextprotocol.io
  4. [4]Model Context Protocol, changelog for 2025-06-18. Source for batching being removed, servers becoming OAuth resource servers and resource indicators being required. modelcontextprotocol.io
  5. [5]Model Context Protocol, changelog for 2025-11-25. Source for OpenID Connect discovery, incremental scope consent and client ID metadata documents. modelcontextprotocol.io
  6. [6]Model Context Protocol, Transports, 2025-11-25. Source for every client message over HTTP being a POST to a single endpoint, and the initialize request. modelcontextprotocol.io
  7. [7]Model Context Protocol, Tools, 2025-11-25. Source for servers having to rate limit tool invocations, and structured results also being sent as text for backwards compatibility. The same wording is in the July 2026 revision. modelcontextprotocol.io
  8. [8]Invariant Labs, GitHub MCP exploited, 26 May 2025. Source for the attack through a public issue and the quote that it's not a flaw in the server code. invariantlabs.ai
  9. [9]The Hacker News, Critical mcp-remote vulnerability, 10 July 2025, on JFrog's research by Or Peles. Source for CVE-2025-6514, its 9.6 score, the command sent during connection and authorisation, the fix in 0.1.16 and the 437,000 downloads. thehackernews.com
  10. [10]Anchor Terminal, MCP measurements, 16 October 2026, described above. Source for the tool list sizes, the Kiwi answer, the cache headers, which servers answered the July 2026 version, our own server's cache fields and our GET comparison. #measure
  11. [11]Anchor Terminal, the directory, 16 October 2026. Source for the 431 graded listings with a remote MCP server (Streamable HTTP or SSE) and how each authenticates. anchorterminal.com
  12. [12]Moe Katib, LinkedIn post, 15 October 2026, on a developer's report on Reddit. Source for the upsell in Claude's answer, the tool name check-mcp-next-steps, its instructions and the mcpUpsellOpportunityId field. Not confirmed by Notion or by us. linkedin.com
  13. [13]Notion, Supported tools, Notion MCP documentation, read on 16 October 2026. Source for the published list of Notion's MCP tools, which doesn't include check-mcp-next-steps, the upgrade prompts some tools return, and how Notion's MCP server handles rate limits. developers.notion.com
  14. [14]Model Context Protocol, Lifecycle, 2025-11-25. Source for the client having to send the initialized notification after initialisation. modelcontextprotocol.io
  15. [15]Model Context Protocol, Key Changes for 2026-07-28. Source for the revision dates, the nine major changes, the removed handshake, sessions, GET stream and resumability, the deprecation of Roots, Sampling, Logging and dynamic client registration, the issuer checks, and the minimum twelve-month deprecation window. The specification's text has no 429 or Retry-After. modelcontextprotocol.io
  16. [16]Model Context Protocol, Caching, 2026-07-28. Source for ttlMs and cacheScope and the comparison with HTTP's Cache-Control. modelcontextprotocol.io
  17. [17]Model Context Protocol, Streamable HTTP, 2026-07-28. Source for the Mcp-Method and Mcp-Name headers and the quote about intermediaries. modelcontextprotocol.io
  18. [18]Invariant Labs, MCP Security Notification, Tool Poisoning Attacks, 1 April 2025. Source for hidden instructions in tool descriptions and the agent reading SSH keys and config files. invariantlabs.ai
  19. [19]Model Context Protocol, Overview, 2024-11-05. Source for authentication and authorisation not being part of the core specification in the first version. modelcontextprotocol.io

Your experience matters

Used a tool in the Terminal?

Tell people and their agents what worked and what didn't. Write a review of a tool you've used and confirm it by email or wallet.