Skip to main content

poem_mcpserver/
tool.rs

1//! Types for tool.
2
3use std::{fmt::Display, future::Future};
4
5use schemars::{JsonSchema, Schema};
6use serde::Serialize;
7use serde_json::Value;
8
9use crate::{
10    content::IntoContents,
11    protocol::{
12        content::Content,
13        rpc::RpcError,
14        tool::{Tool as PTool, ToolsCallResponse},
15    },
16};
17
18/// Represents the result of a tool call.
19pub trait IntoToolResponse {
20    /// Returns the output schema of the tool response, if any.
21    fn output_schema() -> Option<Schema>;
22
23    /// Consumes the object and converts it into a tool response.
24    fn into_tool_response(self) -> ToolsCallResponse;
25}
26
27impl IntoToolResponse for () {
28    fn output_schema() -> Option<Schema> {
29        None
30    }
31
32    fn into_tool_response(self) -> ToolsCallResponse {
33        ToolsCallResponse {
34            content: vec![],
35            structured_content: None,
36            is_error: false,
37        }
38    }
39}
40
41impl<E> IntoToolResponse for Result<(), E>
42where
43    E: Display,
44{
45    fn output_schema() -> Option<Schema> {
46        None
47    }
48
49    fn into_tool_response(self) -> ToolsCallResponse {
50        match self {
51            Ok(_) => ToolsCallResponse {
52                content: vec![],
53                structured_content: None,
54                is_error: false,
55            },
56            Err(error) => ToolsCallResponse {
57                content: vec![Content::Text {
58                    text: error.to_string(),
59                }],
60                structured_content: None,
61                is_error: true,
62            },
63        }
64    }
65}
66
67impl<T> IntoToolResponse for T
68where
69    T: IntoContents,
70{
71    fn output_schema() -> Option<Schema> {
72        None
73    }
74
75    fn into_tool_response(self) -> ToolsCallResponse {
76        ToolsCallResponse {
77            content: self.into_contents(),
78            structured_content: None,
79            is_error: false,
80        }
81    }
82}
83
84impl<T, E> IntoToolResponse for Result<T, E>
85where
86    T: IntoContents,
87    E: Display,
88{
89    fn output_schema() -> Option<Schema> {
90        None
91    }
92
93    fn into_tool_response(self) -> ToolsCallResponse {
94        match self {
95            Ok(value) => ToolsCallResponse {
96                content: value.into_contents(),
97                structured_content: None,
98                is_error: false,
99            },
100            Err(error) => ToolsCallResponse {
101                content: vec![Content::Text {
102                    text: error.to_string(),
103                }],
104                structured_content: None,
105                is_error: true,
106            },
107        }
108    }
109}
110
111/// A Structured content.
112#[derive(Debug, Clone, Copy)]
113pub struct StructuredContent<T>(pub T);
114
115impl<T> IntoToolResponse for StructuredContent<T>
116where
117    T: Serialize + JsonSchema,
118{
119    fn output_schema() -> Option<Schema> {
120        Some(schemars::SchemaGenerator::default().into_root_schema_for::<T>())
121    }
122
123    fn into_tool_response(self) -> ToolsCallResponse {
124        ToolsCallResponse {
125            content: vec![Content::Text {
126                text: serde_json::to_string(&self.0).unwrap_or_default(),
127            }],
128            structured_content: Some(serde_json::to_value(&self.0).unwrap_or_default()),
129            is_error: false,
130        }
131    }
132}
133
134impl<T, E> IntoToolResponse for Result<StructuredContent<T>, E>
135where
136    T: Serialize + JsonSchema,
137    E: Display,
138{
139    fn output_schema() -> Option<Schema> {
140        Some(schemars::SchemaGenerator::default().into_root_schema_for::<T>())
141    }
142
143    fn into_tool_response(self) -> ToolsCallResponse {
144        match self {
145            Ok(value) => ToolsCallResponse {
146                content: vec![Content::Text {
147                    text: serde_json::to_string(&value.0).unwrap_or_default(),
148                }],
149                structured_content: Some(serde_json::to_value(&value.0).unwrap_or_default()),
150                is_error: false,
151            },
152            Err(error) => ToolsCallResponse {
153                content: vec![Content::Text {
154                    text: error.to_string(),
155                }],
156                structured_content: None,
157                is_error: true,
158            },
159        }
160    }
161}
162
163// impl IntoToolResponse for Json
164
165/// Represents a tools collection.
166pub trait Tools {
167    /// Returns the instructions for the tools.
168    fn instructions() -> &'static str;
169
170    /// Returns a list of tools.
171    fn list() -> Vec<PTool>;
172
173    /// Calls a tool.
174    fn call(
175        &mut self,
176        name: &str,
177        arguments: Value,
178    ) -> impl Future<Output = Result<ToolsCallResponse, RpcError>> + Send;
179}
180
181/// Empty tools collection.
182#[derive(Debug, Clone, Copy)]
183pub struct NoTools;
184
185impl Tools for NoTools {
186    #[inline]
187    fn instructions() -> &'static str {
188        ""
189    }
190
191    #[inline]
192    fn list() -> Vec<PTool> {
193        vec![]
194    }
195
196    #[inline]
197    async fn call(&mut self, name: &str, _arguments: Value) -> Result<ToolsCallResponse, RpcError> {
198        Err(RpcError::method_not_found(format!(
199            "tool '{name}' not found"
200        )))
201    }
202}