A down MCP upstream should be reported as a source failure, not disguised as an empty tool list or an empty result set. Keep the source identity visible, classify the failure before the agent sees it, and return an actionable MCP tool error when the model can recover or a protocol error when the request itself cannot be processed. The practical goal is simple: the agent should know whether to retry later, ask a human, use another source, or stop.
Do Not Turn Outages Into Silence
The worst failure mode is a polite lie. If GitHub, Sentry or a private internal MCP server is unreachable, returning [] from tools/list tells the agent that no tools exist. Returning an empty search result from tools/call tells it that the upstream was queried and found nothing. Both erase the operational fact the user needs: the source was not available.
MCP gives you two useful protocol surfaces. Discovery uses tools/list to tell the client which tools are available to the requesting client, and invocation uses tools/call with a tool name and arguments. A gateway or proxy should treat those surfaces separately. Discovery should preserve the source map as much as policy allows; invocation should report the particular call's failure class, timing and retry posture.
A good boundary sentence in the returned text is blunt: source=github status=unreachable reason=connect_timeout retry_after=60s. That is much safer than No issues found. The model may still decide badly, but at least it is deciding from the right premise.
Separate Four Kinds Of Failure
Use a small taxonomy and make every tool call land in one bucket. The first bucket is policy denial: the key, user or workspace is not allowed to see or call the tool. That should not be described as an outage. The second is upstream reachability: DNS, TCP, TLS, HTTP transport, process start or stream failure before a valid MCP response. The third is upstream tool execution: the upstream server answered, but the API behind the tool failed, timed out or returned a domain error. The fourth is protocol error: the request is malformed, the tool is unknown, or the proxy cannot produce a valid JSON-RPC response.
The difference matters because each bucket leads to a different agent action. A policy denial should tell the agent it cannot proceed with this credential. A reachability failure invites retry, fallback or human escalation. An execution error may be recoverable by changing arguments. A protocol error usually means the client or proxy has a bug, not that the user should try a different search term.
| Condition | Agent-facing meaning | Useful next action |
|---|---|---|
| Missing grant | The tool is withheld or denied. | Ask for a different key or permission. |
| Connect timeout | The source was not queried. | Retry later or use another source. |
| API rate limit | The source answered, but work was refused. | Back off and preserve intent. |
| Unknown tool | The request cannot be routed. | Refresh discovery or fix client state. |
Use The Right MCP Error Shape
The MCP tools specification distinguishes JSON-RPC protocol errors from tool execution errors. If the proxy receives tools/call for a tool name it cannot route, a JSON-RPC error is appropriate because the request itself is invalid for this server. If the route exists but the upstream source cannot be reached, return a tool result with isError: true and a short, structured message the model can use.
{
"jsonrpc": "2.0",
"id": 42,
"result": {
"resultType": "complete",
"content": [
{
"type": "text",
"text": "source=sentry status=unreachable class=connect_timeout retryable=true retry_after=60s request_id=tc_7f3"
}
],
"isError": true
}
}Do not include stack traces, bearer tokens, raw upstream headers or full response bodies. The message is for the model and the user; the detailed trace belongs in logs keyed by request_id. Keep the vocabulary stable enough for evals and operational dashboards: unreachable, timeout, auth_failed, rate_limited, denied, invalid_arguments.
For malformed calls, keep the protocol error equally clear:
{
"jsonrpc": "2.0",
"id": 43,
"error": {
"code": -32602,
"message": "Unknown tool: sentry/search_events"
}
}
Keep Discovery Stable During Incidents
When an upstream is down, removing its tools from discovery creates a second incident: clients and models lose the distinction between unavailable and nonexistent. Some clients cache or hold their initial tool list for a session, so a disappearing tool may stay in context anyway, while another client started later sees a different world. That makes incident review needlessly strange.
A proxy can keep discovery deterministic by returning the same source namespace and annotating operational state in descriptions or a source-listing tool. The exact representation is implementation-specific, but the important contract is visible state. If linear/search_issues exists and Linear is unreachable, the agent should see that the source exists and that calls are currently expected to fail.
{
"tools": [
{
"name": "linear/search_issues",
"description": "Search Linear issues. Current source status: unreachable; calls return a retryable tool error until the upstream MCP server recovers.",
"inputSchema": {
"type": "object",
"properties": {
"query": { "type": "string" }
},
"required": ["query"]
}
}
]
}This is also where the design connects to namespacing. If several servers expose search, the failure must name the source, not just the tool. The related guide on namespacing MCP tools across servers covers the collision problem; here, the same prefix is what makes an outage intelligible.
Make Client Config Boring
Failure handling is easier to test when every client reaches the same control point. Claude Code can add a remote HTTP MCP endpoint with a command like this:
claude mcp add --transport http workspace-tools https://api.example.com/mcp --header "Authorization: Bearer $WORKSPACE_MCP_KEY"Cursor can point at the same remote endpoint from .cursor/mcp.json:
{
"mcpServers": {
"workspace-tools": {
"url": "https://api.example.com/mcp",
"headers": {
"Authorization": "Bearer ${env:WORKSPACE_MCP_KEY}"
}
}
}
}VS Code uses MCP configuration as well, including workspace configuration files. The operational point is not the syntax; it is that the outage policy should not live separately in three client configs. Put timeout bounds, retry classification, logging and source-state reporting behind the endpoint. Then test the same failure from each client with the MCP Inspector or a small harness that issues tools/list followed by a known tools/call.
Rollout check
Before letting agents depend on a source, simulate an expired upstream token, a closed network port, a slow response and a valid empty result. Those four transcripts should be impossible to confuse.
How SPYN3 reports this
When an upstream does not answer, SPYN3 returns an error that names the source and the reason. It never returns an empty result that reads like "nothing found". A source that was unreachable at its last check keeps its tools listed, marked as unreachable, because they very probably still exist. One failing source does not take the endpoint down. The other folders keep working while that one is reported.
What To Log
The agent-facing response should be small, but the operational log should be rich. Record the workspace, client, credential id, source, tool, arguments shape, upstream endpoint class, timeout budget, start time, finish time, final failure class and retry decision. Do not rely on HTTP status alone. A 504 from a proxy, an MCP isError result and an upstream API rate limit are different events even if the user only sees a failed tool call.
Use the same ids in logs and tool responses. If the text result says request_id=tc_7f3, support should be able to find the inbound MCP request, upstream attempt and bounded timeout record without asking the user to rerun the agent. That is the difference between observable reliability and a transcript that merely says something went wrong.
The final test is semantic: when the source is down, the agent should say the source is down. It should not claim a search found nothing, create a workaround from stale memory, or keep calling a write-capable tool with the same arguments. Clear failure surfacing is not cosmetic. It is part of the contract that lets agents use company systems without inventing certainty.
Built for the next step
Do not hide broken sources
Surface upstream failure honestly so agents do not infer from missing evidence.
Failure behaviorSPYN3 is a consolidated MCP layer: one hosted endpoint in front of every system you connect — MCP servers, Postgres, REST APIs, Google Drive — with tools foldered by source and read/write permissions per key. Switching agents means moving one URL. Free plan available; current capabilities are documented on the product facts page.
Sources
- MCP specification: tools — tool discovery, invocation and security considerations. Verified 14 Oct 2026
- MCP specification: transports — stdio and Streamable HTTP transport boundaries. Verified 14 Oct 2026
- Anthropic docs: Claude Code MCP setup — Claude Code MCP commands, scopes, transports and JSON configuration. Verified 14 Oct 2026
- Cursor documentation: Model Context Protocol — Cursor MCP transports, OAuth and configuration locations. Verified 14 Oct 2026
- VS Code documentation: add and manage MCP servers — VS Code MCP configuration, trust and logging behavior. Verified 14 Oct 2026
- MCP documentation: Inspector — testing and debugging MCP servers with the Inspector. Verified 14 Oct 2026
