fix(mcp): mark whole-call tool failures with isError
Issue Origin
Observed or reproduced in a real environment
Bug Description
Some OpenViking MCP tools convert a failed operation into ordinary result content. The wire result has isError: false, so a conforming client or agent can treat the failed operation as successful.
This is an interoperability problem, not a message-quality problem. The current error text is actionable and should remain available.
External evidence shows the same failure class in another MCP server: apache/superset#43358 reports a permission denial returned with isError: false, after which an agent treated the forbidden write as successful. The MCP tools specification requires API, validation, and business-logic failures to use a tool result with isError: true: https://modelcontextprotocol.io/specification/2026-07-28/server/tools#error-handling
Steps to Reproduce
- Use current
upstream/mainat87ae94031b95d2f4e54b32d23f342167e069e1c2. - Call the MCP
list_watchestool while the watch scheduler is unavailable. - Inspect the resulting
CallToolResult.
Expected Behavior
The tool returns the existing actionable text and sets isError: true.
{
"content": [{"type": "text", "text": "Error: Watch scheduler not running"}],
"isError": true
}Actual Behavior
The same failed operation is returned as a successful tool result.
{
"content": [{"type": "text", "text": "Error: Watch scheduler not running"}],
"structuredContent": {"result": "Error: Watch scheduler not running"},
"isError": false
}The same source pattern exists in selected add_resource, cancel_watch, and glob whole-call failure paths.
Minimal Reproducible Example
handler = mcp._mcp_server.request_handlers[CallToolRequest]
request = CallToolRequest(
params=CallToolRequestParams(name="list_watches", arguments={})
)
result = (await handler(request)).root
assert result.isError is FalseError Logs
No exception is logged. The failure is encoded as successful content.OpenViking Version
upstream/main at 87ae94031b95d2f4e54b32d23f342167e069e1c2
Python Version
3.13
Operating System
macOS
Model Backend
Not applicable
Additional Context
A focused fix should preserve the current text, successful results, partial batch results, and HTTP or JSON-RPC authentication errors. It should not include grep, because PR #5029 already owns that failure path. The health tool should continue to return health state as data because the diagnostic call itself completed.
Searches of all OpenViking issue, discussion, PR, branch, and source states found no equivalent server-side change. PR #4724 concerns consumers reading camelCase isError; it does not change server emission.
Source: volcengine/OpenViking