Skip to main content

model_context_protocol/
result.rs

1//! Result types for MCP tool implementations.
2//!
3//! Provides ergonomic return types that automatically convert to `CallToolResult`.
4
5use serde::Serialize;
6
7use crate::protocol::{CallToolResult, ToolContent};
8
9/// Result type for MCP tool implementations.
10///
11/// Converts automatically to `CallToolResult` for macro-generated code.
12///
13/// # Example
14///
15/// ```rust,ignore
16/// #[mcp_tool(description = "Read a value")]
17/// fn read(&self, key: String) -> ToolResult<Value> {
18///     self.store.get(&key).ok_or_else(|| format!("Key not found: {}", key))
19/// }
20/// ```
21pub type ToolResult<T> = Result<T, String>;
22
23/// Extension trait for converting values to `CallToolResult`.
24pub trait IntoCallToolResult {
25    /// Convert to a `CallToolResult`.
26    fn into_call_result(self) -> CallToolResult;
27}
28
29impl<T: Serialize> IntoCallToolResult for ToolResult<T> {
30    fn into_call_result(self) -> CallToolResult {
31        match self {
32            Ok(value) => {
33                let text = match serde_json::to_string_pretty(&value) {
34                    Ok(s) => s,
35                    Err(e) => format!("Serialization error: {}", e),
36                };
37                CallToolResult {
38                    content: vec![ToolContent::text(text)],
39                    is_error: Some(false),
40                }
41            }
42            Err(e) => CallToolResult {
43                content: vec![ToolContent::text(format!("Error: {}", e))],
44                is_error: Some(true),
45            },
46        }
47    }
48}
49
50impl IntoCallToolResult for CallToolResult {
51    fn into_call_result(self) -> CallToolResult {
52        self
53    }
54}
55
56/// Allows `ToolResult<T>` to convert to `CallToolResult` via `.into()`.
57impl<T: Serialize> From<ToolResult<T>> for CallToolResult {
58    fn from(result: ToolResult<T>) -> Self {
59        result.into_call_result()
60    }
61}
62
63/// Convenience function to create a successful tool result.
64pub fn tool_ok<T: Serialize>(value: T) -> ToolResult<T> {
65    Ok(value)
66}
67
68/// Convenience function to create a failed tool result.
69pub fn tool_err<T>(message: impl Into<String>) -> ToolResult<T> {
70    Err(message.into())
71}
72
73/// Create a successful `CallToolResult` with text content.
74pub fn success_result(text: impl Into<String>) -> CallToolResult {
75    CallToolResult {
76        content: vec![ToolContent::text(text)],
77        is_error: Some(false),
78    }
79}
80
81/// Create an error `CallToolResult` with text content.
82pub fn error_result(message: impl Into<String>) -> CallToolResult {
83    CallToolResult {
84        content: vec![ToolContent::text(message)],
85        is_error: Some(true),
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn test_ok_result_converts() {
95        let result: ToolResult<String> = Ok("success".to_string());
96        let call_result = result.into_call_result();
97        assert_eq!(call_result.is_error, Some(false));
98        assert_eq!(call_result.content.len(), 1);
99    }
100
101    #[test]
102    fn test_err_result_converts() {
103        let result: ToolResult<String> = Err("failed".to_string());
104        let call_result = result.into_call_result();
105        assert_eq!(call_result.is_error, Some(true));
106    }
107
108    #[test]
109    fn test_tool_ok_helper() {
110        let result = tool_ok("hello");
111        assert!(result.is_ok());
112        assert_eq!(result.unwrap(), "hello");
113    }
114
115    #[test]
116    fn test_tool_err_helper() {
117        let result: ToolResult<()> = tool_err("oops");
118        assert!(result.is_err());
119        assert_eq!(result.unwrap_err(), "oops");
120    }
121
122    #[test]
123    fn test_success_result() {
124        let result = success_result("Operation completed");
125        assert_eq!(result.is_error, Some(false));
126        assert!(result.content[0]
127            .as_text()
128            .unwrap()
129            .contains("Operation completed"));
130    }
131
132    #[test]
133    fn test_error_result() {
134        let result = error_result("Something went wrong");
135        assert_eq!(result.is_error, Some(true));
136        assert!(result.content[0]
137            .as_text()
138            .unwrap()
139            .contains("Something went wrong"));
140    }
141}