What
McpServerResolutionFailedError is raised at nine sites in src/gateway/adapters/mcp_server_adapter.py, for seven distinct conditions:
- a scope naming no workspace, in the local adapter (twice)
- a scope holding no token, in the remote adapter
- a peer answer that is not an object
- a
servers value that is not a list
- a peer answering several entries for one requested ID
- an entry whose ID does not match the one requested
- an entry the model cannot validate (twice)
The fixed public message is correct: the underlying detail quotes the stored server URL and its credential, so it must not travel. But the error carries nothing else either, so an operator looking at a 502 has nothing that tells the nine apart.
Why it matters
A hybrid gateway returning MCP server resolution failed gives the operator no way to tell a peer that answered badly from a gateway that was wired wrong. The two have different owners and different fixes.
Suggested shape
A required, typed reason on the error, named at each raise site. The wire response does not change, and no reason names any part of the peer's answer. #1738 does this and nothing more.
Recording the reason is a separate problem, because a failure's record belongs where its response is produced and neither surface does both. On this router, _McpRoute.handler renders the error response and writes no log, while the route handlers log the outcome and re-raise so it can render. On the completions path there is no HTTPException handler and no middleware log at all. #1752 covers both surfaces, and this issue closes once that lands.
Not a logger.warning beside each raise, and not one at each handling boundary either. Both are log-and-raise: they put the choice of how loud a condition is in code that cannot know whether a caller recovers from it, and they leave the cause reachable only by scraping log text. McpExecutionError in src/gateway/services/mcp_stateless.py is the local pattern for carrying a cause, holding a code and an execution state rather than a message.
Notes
Found while reviewing #1683. Deliberately left out of it: that change's acceptance criterion is identical behavior in all three modes, and adding log lines is new behavior.
What
McpServerResolutionFailedErroris raised at nine sites insrc/gateway/adapters/mcp_server_adapter.py, for seven distinct conditions:serversvalue that is not a listThe fixed public message is correct: the underlying detail quotes the stored server URL and its credential, so it must not travel. But the error carries nothing else either, so an operator looking at a 502 has nothing that tells the nine apart.
Why it matters
A hybrid gateway returning
MCP server resolution failedgives the operator no way to tell a peer that answered badly from a gateway that was wired wrong. The two have different owners and different fixes.Suggested shape
A required, typed
reasonon the error, named at each raise site. The wire response does not change, and no reason names any part of the peer's answer. #1738 does this and nothing more.Recording the reason is a separate problem, because a failure's record belongs where its response is produced and neither surface does both. On this router,
_McpRoute.handlerrenders the error response and writes no log, while the route handlers log the outcome and re-raise so it can render. On the completions path there is noHTTPExceptionhandler and no middleware log at all. #1752 covers both surfaces, and this issue closes once that lands.Not a
logger.warningbeside each raise, and not one at each handling boundary either. Both are log-and-raise: they put the choice of how loud a condition is in code that cannot know whether a caller recovers from it, and they leave the cause reachable only by scraping log text.McpExecutionErrorinsrc/gateway/services/mcp_stateless.pyis the local pattern for carrying a cause, holding a code and an execution state rather than a message.Notes
Found while reviewing #1683. Deliberately left out of it: that change's acceptance criterion is identical behavior in all three modes, and adding log lines is new behavior.