What the wire format doesn’t promise
The Model Context Protocol standardizes a great deal: how a client and server agree on a protocol version, how a tool is described, how a call is invoked, how an error is shaped. What it standardizes is a wire format and a set of message types. What it does not standardize — and says so about itself — is what a deployment must do to keep a long-running, retried, multi-tenant session correct once that wire format is in production. The specification’s own security principles page states plainly that “MCP itself cannot enforce these security principles at the protocol level” [3], and the pattern repeats well beyond security: large parts of the operational contract are written as “SHOULD,” not “MUST,” which is a polite way of saying the protocol leaves the hard part to the implementer.
This piece catalogues ten failure modes that live in exactly that gap, drawn from the specification’s own text, from vulnerability disclosures, and from issue trackers where real deployments hit the gap first. It is deliberately narrow. It is not about a model choosing the wrong tool, an agent’s architecture losing context across a handoff, or an evaluation harness mismeasuring success — those are different failure surfaces, covered elsewhere. This is about what happens to a correctly chosen tool call once it crosses a network boundary between a client and a server that do not fully trust, fully agree with, or fully see each other. The organization OWASP has built specifically to track this class of problem gives a sense of scale: its MCP Top 10 project catalogues ten categories of risk in production MCP deployments, from token mismanagement through to what it names outright as “Lack of Audit and Telemetry” [16] — an admission, from a security project, that most of this is currently invisible until it is someone’s incident report.
1. Schema drift breaks a deployed agent without a single error
A tool’s inputSchema and outputSchema are declared once, at the point a client calls
tools/list, and are assumed stable for the life of that list. Nothing in the specification
requires a server to version its schemas, and a server operator can redeploy a tool with a renamed
parameter with no more ceremony than an ordinary code push. The specification’s own tool-result
guidance underlines how thin the safety net is: where an output schema exists, servers “MUST
provide structured results that conform to this schema,” but clients merely “SHOULD validate
structured results against this schema” [4] — a check on the calling side that is
recommended, not required, and therefore frequently absent.
A documented account from a developer running MCP tools in production shows what that gap looks
like from the inside. A search tool’s parameter was renamed from query to search_query; the
server did not reject the old name, it “silently ignored” the unrecognized field and substituted
an empty-string default, returning a well-formed, error-free response with no results — and the
model, seeing a clean response, “confidently explained the empty results as expected,” reporting
back that the query was “too specific” [5]. No exception fired anywhere in
the stack. The author’s proposed fixes — schema snapshots with diff detection between connections,
input validation against the current schema before a call is issued, scheduled canary queries
against known-good inputs, and semantic versioning for tool schemas [5] — are
all client-side and server-side discipline invented to cover a gap the protocol leaves open by
design.
2. Timeout budgets nobody actually agreed on
The lifecycle specification is honest about the tension: implementations “SHOULD” set timeouts on every request, “MAY” reset the timeout clock on progress notifications, but “SHOULD” always enforce a hard maximum regardless of that progress, specifically “to limit the impact of a misbehaving client or server” [1]. That is a reasonable rule read in isolation. It becomes a production incident when the client’s hard maximum and the server’s expected task duration were never negotiated against each other, because the specification gives no mechanism for negotiating them.
Two independent SDK issue trackers show the same mismatch from opposite directions. Against the
official Python SDK, a reporter found that a tool doing nothing more than await asyncio.sleep(10) was unconditionally cancelled at ten seconds, the server “generates unhandled
exceptions upon timeout” and the client connection was “terminated unexpectedly” — a hard,
undocumented ceiling baked into the transport layer itself [8]. Against the
official TypeScript SDK, a separate reporter described the mirror problem: legitimate multi-source
lookups “may legitimately take more than a minute,” but “the MCP client times out before the
response is returned” at a fixed 60-second ceiling with no documented way to raise it for that one
call [7]. Neither ceiling was wrong on its own terms — a server author
picked ten seconds to fail fast, a client author picked sixty to avoid hanging forever — but
nothing in the protocol makes those two numbers visible to each other before a call is made.
3. A retry that becomes a second charge, ticket, or message
A timeout tells a caller only that it did not hear back in time. It does not tell the caller whether the request never arrived, arrived and executed, or executed and lost its response on the way out. The Model Context Protocol carries no idempotency-key mechanism and no exactly-once delivery guarantee; behaviour hints on a tool are explicitly advisory rather than enforced. Combine an ambiguous timeout with an agent loop biased toward trying again, and the failure is not theoretical.
A fully reproducible, still-open report against Atlassian’s own remote MCP server shows the
mechanism cleanly: every call to a createJiraIssue tool created two identical Jira tickets,
“regardless of whether the call is part of a batch or a standalone single-ticket request,” with
the two issues logged four to twenty milliseconds apart — the reporter’s evidence table shows one
pair, ticket PS-4544 and PS-4545, created 4 milliseconds apart, each with its own distinct internal
Jira ID, ruling out a display artefact [6]. The reporter’s own
diagnosis, drawn from ruling out a doubled client call and a batching bug, points at “a retry-on-
timeout, connection handling issue, or request duplication” somewhere in the path between the MCP
server and the underlying REST API [6] — precisely the ambiguous-loss
scenario the protocol leaves unaddressed.
The shape of the exposure is simple enough to write down. Let
The only term a protocol upgrade could move is
4. A capability list that stops matching what the server offers
A server that adds, removes or renames a tool after initialization is expected to send
notifications/tools/list_changed, and a client that receives it is expected to re-issue
tools/list — but both halves of that exchange are written as “SHOULD,” not “MUST”
[4], and the gap between the recommendation and the implementation shows up across
unrelated clients rather than in just one.
Against Claude Desktop, a reverse-engineered defect report — verified across, in the reporter’s words, “336 version bumps” of the application — found that the client “receives the notification, parses it, and discards it,” because the internal handler that would refresh the tool list is gated on a configuration callback the application never actually wires up; the practical effect is that “any MCP server that adds, removes, or renames a tool after initialize is invisible to Claude Desktop until the user fully restarts the app” [9]. Against OpenAI’s Codex, a separate reporter found the identical symptom by a different mechanism: a server exposing one lightweight bootstrap tool that later unlocks dozens more sends the change notification correctly, but Codex’s deferred tool-search cache is never invalidated, so “none of the newly available tools can be discovered or called,” and the same defect “affects MCP servers with plugin systems,” where enabling or disabling a plugin silently fails to update what the agent can see [10]. A third, independent analysis frames why this keeps recurring rather than being a one-off bug: because clients are not required to declare which optional protocol features they actually implement, “the MCP clients have no incentive to adopt all the new MCP protocol features,” and server authors are pushed toward “a generic tool… less precise, [that] lacks schema validation” rather than trusting dynamic discovery at all [11].
5. Two protocol revisions in the same fleet
The specification’s version-negotiation rule is unambiguous on paper: a client sends the protocol
version it supports, and “if the client does not support the version in the server’s response, it
SHOULD disconnect” [1]. Over HTTP, a client that omits the negotiated-version
header entirely causes the server to fall back to an assumption — the specification directs it to
“assume protocol version 2025-03-26” for backward compatibility [2] — which
means a fleet’s actual behaviour under a missing header depends on which historical default each
server implementation chose to honour.
What that looks like from inside a maintainer’s own backlog is visible in a still-open issue
against a popular Go server library, filed to track adoption of the newer 2025-11-25 revision
alongside the existing 2025-06-18, 2025-03-26 and 2024-11-05 versions the library already
supports. The issue walks through, line by line, what has to change: the negotiation function that
already falls back to the library’s own latest version when a client’s requested version is
unrecognized, and — the harder question — “should this default be updated to 2025-11-25, or
should it remain at an older version for backward compatibility?” with the maintainer’s own
tentative answer being to keep the older default and “document this behaviour” rather than force
every existing client through an upgrade [12]. That is a maintainer solving, by
hand, exactly the compatibility matrix the protocol’s own “SHOULD disconnect” rule assumes will
rarely matter in practice.
6. A token that expires mid-task, not between tasks
Authorization in MCP is deliberately stateless at the transport level: every request carries its own bearer token, invalid or expired tokens “MUST receive a HTTP 401 response,” and authorization servers “SHOULD issue short-lived access tokens to reduce the impact of leaked tokens” [3]. Short-lived tokens are good security practice. They are also a timer running underneath every long tool call, and nothing in the base exchange guarantees that timer will not expire in the middle of one.
A report filed against OpenAI’s Codex App documents the failure directly: a remote MCP server
using OAuth worked immediately after login, then broke when “the 15-minute access token expires,”
because the authorization server required the RFC 8707 resource parameter — mandatory under the
protocol’s own newer authorization revision — on every token refresh, not only on the initial
grant, and the client’s refresh request omitted it; the resulting failure was not a graceful
re-authentication prompt but an outright block: “required MCP server can prevent creation of a new
task” [13]. The reporter had already configured the correct resource value
for login; it simply was not carried through to the silent background refresh a session depends on
to survive past the token’s own short lifetime.
7. A result that is well-formed and wrong
A tool call can fail two structurally different ways, and the specification is explicit that only
one of them is visible as an error: protocol errors are “issues with the request structure itself,”
while a tool that runs and produces bad output returns a normal, successful-looking JSON-RPC
response, distinguished only by an isError flag the tool author chooses to set — or does not
[4]. A response that is silently incomplete rather than explicitly failed passes
every check a client bothers to run, because nothing marks it as anything other than success.
A defect against GitHub’s own Copilot CLI shows this happening by pure accident rather than malice. The CLI already had a safety mechanism: any tool output larger than 30KB gets written to a temporary file, with a small pointer message returned to the model instead of the raw content. The bug was an ordering error — MCP response text was truncated to 10KB by an internal formatting step before that 30KB safety check ever ran, so the 30KB mechanism never triggered on a response that was, by then, already cut down to 10KB. The reporter’s own summary is unambiguous: “any MCP tool response larger than ~10KB is silently corrupted… with no indication that data was lost” [20]. The model received a truncated file listing, a truncated search result, a truncated log — and, having no signal that anything was missing, reasoned over it as though it were complete.
8. A scope broad enough that one injected instruction becomes a breach
The over-broad-permissions failure does not require a compromised tool. Invariant Labs’s disclosure against the official GitHub MCP server demonstrated an attack that worked against fully trusted, unmodified tooling: an attacker plants a prompt injection inside an ordinary public GitHub issue, and when a user’s agent — authorized with a token broad enough to read that user’s private repositories — processes the issue, it “can be coerced into pulling private repository data into context, and leaking it in an autonomously-created PR” [14]. The researchers’ stated mitigation is scope reduction: limit an agent’s access “to only the repositories it needs to interact with — following the principle of least privilege” [14], which is another way of saying the exploit only works because the token was broader than any single task required.
A user-filed reproduction against the GitHub MCP server issue tracker confirms the mechanism and adds a detail about how blunt the available controls were at the time: testing a broad-scope token against a public-repository-only token, the reporter found the narrower token still “did read the private repositories but not write information from them,” and noted plainly that “OAuth does not specify to what I am allowing access” [15] — the authorization surface offered no finer grain than the token’s overall scope. OWASP’s own taxonomy gives this failure a name rather than treating it as one server’s bug: “Privilege Escalation via Scope Creep” is its own numbered category, distinct from outright credential theft [16].
9. A transport that assumes the session survives
Streamable HTTP, the transport that replaced the original SSE design specifically to fix this
class of problem, still documents its own limits candidly: an open stream’s “disconnection MAY
occur at any time (e.g., due to network conditions),” resumption via a Last-Event-ID header is
something a server “MAY” support rather than must, and a server “MAY terminate the session
at any time,” after which every further request against that session ID gets a flat 404
[2]. Every one of those is a MAY, which means a client that assumed otherwise
finds out the hard way.
Two independent client projects logged the same assumption failing in production. Against Claude Code, a report describes MCP tools that “appear in the tool list at session start, then silently stop working mid-session” with “no way to reconnect without restarting the entire… session,” losing all conversation context in the process [18]. Against the open-source agent OpenCode, a parallel report on remote servers describes “no automatic reconnection” and “reactive failure handling only,” where a dropped idle connection is discovered only when the next tool call fails outright, rather than through any proactive health check [19]. The more severe version of the same trust assumption was CVE-2025- 49596, a critical vulnerability (CVSS 9.4) in Anthropic’s own MCP Inspector tool: the local proxy between the Inspector’s browser interface and the servers it drives accepted commands from “unauthenticated requests,” so a malicious webpage using DNS rebinding to reach the local proxy port could execute arbitrary commands on a developer’s machine with no further access required [17]. A transport that assumes whoever can reach it is meant to be there is the same failure as a session that assumes whoever holds the connection is still authorized to use it.
10. Nobody can say which tools actually ran
The specification’s advice to clients is, again, advisory rather than enforced: “Log tool usage for audit purposes” is one bullet point among several security considerations, not a requirement [4]. OWASP’s own top-ten list gives this the bluntest name in the whole taxonomy — “Lack of Audit and Telemetry” is its own numbered category, sitting alongside injection and credential theft as a first-class production risk rather than a nice-to-have [16].
The infrastructure to fix this is arriving from outside MCP itself, and its own documentation
concedes the problem is structural, not incidental: OpenTelemetry’s semantic-conventions project
now defines dedicated span types for agent systems — an execute_tool span alongside chat and
invoke_agent spans — and explicitly scopes its work to cover “GenAI clients, MCP (Model Context
Protocol), and provider-specific conventions” as separate, previously disconnected signal sources
[21]. That a standards body felt the need to name MCP specifically as a gap in
existing observability tooling is itself evidence of the tenth failure mode: without instrumentation
bolted on from outside the protocol, the honest answer to “which tools did the agent actually call,
with what arguments, and what came back” is frequently: check the model’s own summary of what it
did, which is exactly the account failure mode seven shows cannot be trusted.
Where the fault line actually runs
These ten do not have one root cause, and treating them as one problem invites one wrong fix. Three are things the protocol simply has no answer for at all — non-idempotent retries, schema versioning, and the absence of a caller-supplied deduplication key are not oversights so much as scope the specification never took on. Four are things the protocol does specify but only as a recommendation, which implementations then skip under ordinary competitive pressure to ship: the list-changed notification, the enforcement of output-schema conformance, audit logging, and a hard timeout ceiling independent of progress. The remaining three sit downstream of the protocol entirely, in how a specific deployment wires identity, scope and transport trust around it — token refresh logic, OAuth scope grants, and a proxy’s assumption about who can reach it. Knowing which bucket a given production incident falls into changes the fix: the first bucket needs a new protocol primitive, the second needs a client or server that actually implements what already exists, and the third needs an operator decision that no specification revision will make on anyone’s behalf.
Predictions, and what would falsify them
These are forecasts, kept separate from the sourced incidents above. Horizon: 12 August 2028. Assumption throughout: MCP or a close successor remains the dominant open tool-calling protocol, and no single vendor’s proprietary interface displaces it entirely.
One. A caller-supplied idempotency identifier, deduplicated by the server rather than merely
hinted at by an annotation, will appear in a future MCP revision, because the current gap has
already produced reproducible, publicly filed duplication incidents rather than theoretical risk.
Disconfirmed if the specification two revisions from 2025-06-18 still carries only advisory
behaviour hints with no request-identity mechanism a server is obliged to honour.
Two. tools/list_changed handling will move from a recommendation to a requirement, or major
clients will converge on implementing it correctly regardless of its normative strength, because
three independent, unrelated clients have now shipped the identical defect. Disconfirmed if
audits of widely used MCP clients in 2028 still show a majority silently discarding the
notification.
Three. Tool-call observability will standardize around the OpenTelemetry GenAI conventions specifically, rather than each agent framework inventing its own tracing schema, because the alternative is what OWASP has already flagged as a named, numbered risk category rather than a nice-to-have. Disconfirmed if the leading agent frameworks in 2028 still ship incompatible, non-interoperable tracing formats for tool calls.
Four. Mid-session credential refresh will be tested as its own failure class in MCP client conformance suites, distinct from initial-login authentication, because the failure mode is already documented against a major client and traces to a specific, nameable omission rather than an exotic edge case. Disconfirmed if 2028-era MCP client test suites still validate only the initial OAuth handshake and not token refresh mid-task.
None of these requires a change in what language models can do. Each follows from gaps that are already visible in the specification’s own text and in the incidents this piece has traced back to it.
What to take away
A tool protocol standardizes the shape of a request and the shape of a response. It does not, by itself, guarantee that a schema stays stable, that a timeout means what both sides think it means, that a retried call has the effect of one call rather than two, that a capability list is current, that two protocol revisions in the same fleet interoperate, that a token survives the task it authorized, that a truncated result announces itself as truncated, that a broad scope stays broad only where it needs to, that a dropped connection is noticed, or that anyone can later say what actually happened. Every one of those ten properties is either explicitly left to the deployment, or specified as a recommendation that a specific, documented implementation has already skipped.
The practical response is not to distrust the protocol — MCP’s version negotiation, its structured- error channel, and its OAuth-based authorization model are each, on their own terms, reasonable engineering. It is to read the specification the way this piece has: noting every MUST as a guarantee and every SHOULD as a place a production system still has to supply its own answer, and building the schema diffing, the idempotency keys, the reconnection logic, the scope audits and the tool-call telemetry as first-class parts of the deployment rather than as an afterthought bolted on once the first incident report arrives.