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                    structured_content: None,
40                    is_error: Some(false),
41                }
42            }
43            Err(e) => CallToolResult {
44                content: vec![ToolContent::text(format!("Error: {}", e))],
45                structured_content: None,
46                is_error: Some(true),
47            },
48        }
49    }
50}
51
52impl IntoCallToolResult for CallToolResult {
53    fn into_call_result(self) -> CallToolResult {
54        self
55    }
56}
57
58/// Allows `ToolResult<T>` to convert to `CallToolResult` via `.into()`.
59impl<T: Serialize> From<ToolResult<T>> for CallToolResult {
60    fn from(result: ToolResult<T>) -> Self {
61        result.into_call_result()
62    }
63}
64
65/// Convenience function to create a successful tool result.
66pub fn tool_ok<T: Serialize>(value: T) -> ToolResult<T> {
67    Ok(value)
68}
69
70/// Convenience function to create a failed tool result.
71pub fn tool_err<T>(message: impl Into<String>) -> ToolResult<T> {
72    Err(message.into())
73}
74
75/// Create a successful `CallToolResult` with text content.
76pub fn success_result(text: impl Into<String>) -> CallToolResult {
77    CallToolResult {
78        content: vec![ToolContent::text(text)],
79        structured_content: None,
80        is_error: Some(false),
81    }
82}
83
84/// Create an error `CallToolResult` with text content.
85pub fn error_result(message: impl Into<String>) -> CallToolResult {
86    CallToolResult {
87        content: vec![ToolContent::text(message)],
88        structured_content: None,
89        is_error: Some(true),
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn test_ok_result_converts() {
99        let result: ToolResult<String> = Ok("success".to_string());
100        let call_result = result.into_call_result();
101        assert_eq!(call_result.is_error, Some(false));
102        assert_eq!(call_result.content.len(), 1);
103    }
104
105    #[test]
106    fn test_err_result_converts() {
107        let result: ToolResult<String> = Err("failed".to_string());
108        let call_result = result.into_call_result();
109        assert_eq!(call_result.is_error, Some(true));
110    }
111
112    #[test]
113    fn test_tool_ok_helper() {
114        let result = tool_ok("hello");
115        assert!(result.is_ok());
116        assert_eq!(result.unwrap(), "hello");
117    }
118
119    #[test]
120    fn test_tool_err_helper() {
121        let result: ToolResult<()> = tool_err("oops");
122        assert!(result.is_err());
123        assert_eq!(result.unwrap_err(), "oops");
124    }
125
126    #[test]
127    fn test_success_result() {
128        let result = success_result("Operation completed");
129        assert_eq!(result.is_error, Some(false));
130        assert!(result.content[0]
131            .as_text()
132            .unwrap()
133            .contains("Operation completed"));
134    }
135
136    #[test]
137    fn test_error_result() {
138        let result = error_result("Something went wrong");
139        assert_eq!(result.is_error, Some(true));
140        assert!(result.content[0]
141            .as_text()
142            .unwrap()
143            .contains("Something went wrong"));
144    }
145}