I’ve been running MCP servers in production for a few months now, integrating them with our internal tooling and customer-facing agents. While the protocol is fantastic for flexibility and developer experience, I’ve hit a recurring, gnawing issue: its approach to error handling feels dangerously permissive for anything beyond a basic demo.
The core of the problem, as I see it, is that the protocol specification often treats errors as opaque strings passed back to the client, with minimal structured guidance on error types, severity, or retry semantics. From a security and reliability perspective, this creates several tangible problems:
* **Ambiguous failure modes obscure attack surfaces.** If a tool call fails because of an authentication error, a resource quota violation, a malformed input, or a downstream server outage, the client often receives the same class of "error" object. This makes it extremely difficult for the agent runtime to implement sensible policy. Should it retry? Should it alert a human? Should it stop using that tool entirely? Without structured error categories, we're forced to parse fragile natural language strings, which is a recipe for bugs.
* **Information leakage is too easy.** It's tempting for MCP server developers to return verbose, debugging-friendly error messages. These can inadvertently leak internal system details—paths, configuration snippets, stack traces—to the client, which in our case is an LLM that might include those details in its output to an end-user.
* **It complicates prompt security and abuse detection.** We want to log and monitor for suspicious tool use patterns. A structured error like `"type": "PERMISSION_DENIED"` is easy to alert on. An unstructured string like `"Failed to execute: access token invalid"` requires regex gymnastics and is brittle across different tool implementations.
In practice, this means we’ve had to wrap every MCP server we run in a custom middleware layer that attempts to normalize and classify errors before they reach our agent. This adds latency, complexity, and a whole new layer of code we have to maintain.
I’m curious how others are handling this. Are we over-engineering it?
* Are you relying on the LLM to interpret and act on error strings intelligently? In our experience, that’s highly model-dependent and unreliable.
* Have you extended the protocol with custom error fields or out-of-band signaling (like headers or separate channels) to convey error severity?
* Does anyone think this looseness is actually a feature, preserving flexibility, and that the security burden should fall entirely on the MCP server implementation?
For a protocol with "security" in its subforum name, I expected more built-in guardrails for one of the most critical aspects of any RPC system: knowing *why* and *how* something failed.
Budget and monitor.