Skip to content

Commit 015c56c

Browse files
committed
fix: MCP spec compliance — two-tier errors, -32803, trailing periods
- Add tool_not_found() (-32803), tool_error() (isError:true), and into_tool_error_if_needed() to JsonRpcResponse - Apply post-dispatch error conversion in handle_tools_call - Change unknown tool from -32601 to -32803 (TOOL_NOT_FOUND) - Remove trailing periods from all tool descriptions - Update 11 edge case tests to assert isError:true pattern
1 parent 9213918 commit 015c56c

5 files changed

Lines changed: 1620 additions & 43 deletions

File tree

src/mcp/protocol.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ impl JsonRpcError {
8383
}
8484
}
8585

86+
/// MCP error: Tool not found (-32803).
87+
pub fn tool_not_found(tool: impl Into<String>) -> Self {
88+
Self {
89+
code: -32803,
90+
message: "Tool not found".to_string(),
91+
data: Some(Value::String(tool.into())),
92+
}
93+
}
94+
8695
/// Standard error: Invalid params (-32602).
8796
pub fn invalid_params(detail: impl Into<String>) -> Self {
8897
Self {
@@ -137,6 +146,42 @@ impl JsonRpcResponse {
137146
error: Some(error),
138147
}
139148
}
149+
150+
/// Create a tool error response (isError: true per MCP spec).
151+
/// Use this for tool execution failures instead of `error()`.
152+
pub fn tool_error(id: Value, message: impl Into<String>) -> Self {
153+
Self::success(
154+
id,
155+
serde_json::json!({
156+
"content": [{"type": "text", "text": message.into()}],
157+
"isError": true
158+
}),
159+
)
160+
}
161+
162+
/// If this is a JSON-RPC error response, convert it to a tool error
163+
/// (isError: true) per MCP spec. Protocol errors pass through unchanged.
164+
pub fn into_tool_error_if_needed(self) -> Self {
165+
if let Some(ref err) = self.error {
166+
// Only convert tool execution errors, not protocol-level errors.
167+
// Protocol errors: parse (-32700), invalid request (-32600),
168+
// method not found (-32601), tool not found (-32803).
169+
match err.code {
170+
-32700 | -32600 | -32601 | -32803 => self, // keep as JSON-RPC error
171+
_ => {
172+
// Convert to isError: true
173+
let msg = if let Some(ref data) = err.data {
174+
format!("{}: {}", err.message, data)
175+
} else {
176+
err.message.clone()
177+
};
178+
Self::tool_error(self.id.clone(), msg)
179+
}
180+
}
181+
} else {
182+
self
183+
}
184+
}
140185
}
141186

142187
/// Parse a raw JSON string into a JSON-RPC request.

0 commit comments

Comments
 (0)