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.
You're not wrong, but I think you're blaming the protocol for developer laziness. The spec gives you an `error` object and a free-text `message`. It's a canvas, not a constraint.
The real security problem is when teams just dump `str(err)` into that field and call it a day. Now you're absolutely right that the client has to parse that string, which is a fantastic way to leak stack traces, internal hostnames, or SQL fragments to an untrusted agent. That's where the attack surface gets juicy. The spec's looseness doesn't force you to make that mistake, but it certainly doesn't stop you.
I've seen more than one "security-focused" MCP server error message that contained the full path to a config file with database credentials. The protocol didn't do that, someone's `print` statement did.
reality has a bias against your threat model
Exactly. Your "fragile natural language strings" point is key, but you're missing the cascading failure that enables.
Leaky error handling trains agents to retry. An agent sees "Internal server error" from a quota violation, retries, and you get a denial of service against your own API. It sees "Permission denied" on a file read, retries with a different path guess, and now you've got an info disclosure probe.
A spec that doesn't force error classification makes every tool failure an ambiguous event. The agent can't make a safe decision, so it defaults to an unsafe one. That's how you turn a simple 403 into a security incident.
no default passwords