1use async_trait::async_trait;
4use mocopr_core::prelude::*;
5use std::collections::HashMap;
6
7#[async_trait]
9pub trait ResourceHandler: Send + Sync {
10 async fn resource(&self) -> Resource;
12
13 async fn read(&self) -> Result<Vec<ResourceContent>>;
15
16 fn supports_subscription(&self) -> bool {
18 false
19 }
20
21 async fn subscribe(&self) -> Result<()> {
23 Err(Error::MethodNotFound("subscribe".to_string()))
24 }
25
26 async fn unsubscribe(&self) -> Result<()> {
28 Err(Error::MethodNotFound("unsubscribe".to_string()))
29 }
30}
31
32#[async_trait]
34pub trait ToolHandler: Send + Sync {
35 async fn tool(&self) -> Tool;
37
38 async fn call(&self, arguments: Option<serde_json::Value>) -> Result<ToolsCallResponse>;
40}
41
42#[async_trait]
44pub trait PromptHandler: Send + Sync {
45 async fn prompt(&self) -> Prompt;
47
48 async fn generate(
50 &self,
51 arguments: Option<HashMap<String, String>>,
52 ) -> Result<PromptsGetResponse>;
53}
54
55pub struct FileResourceHandler {
57 uri: url::Url,
58 name: String,
59 description: Option<String>,
60 mime_type: Option<String>,
61 file_path: std::path::PathBuf,
62}
63
64impl FileResourceHandler {
65 pub fn new(
66 uri: url::Url,
67 name: impl Into<String>,
68 file_path: impl Into<std::path::PathBuf>,
69 ) -> Self {
70 Self {
71 uri,
72 name: name.into(),
73 description: None,
74 mime_type: None,
75 file_path: file_path.into(),
76 }
77 }
78
79 pub fn with_description(mut self, description: impl Into<String>) -> Self {
80 self.description = Some(description.into());
81 self
82 }
83
84 pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
85 self.mime_type = Some(mime_type.into());
86 self
87 }
88}
89
90#[async_trait]
91impl ResourceHandler for FileResourceHandler {
92 async fn resource(&self) -> Resource {
93 Resource {
94 uri: self.uri.clone(),
95 name: self.name.clone(),
96 description: self.description.clone(),
97 mime_type: self.mime_type.clone(),
98 annotations: None,
99 }
100 }
101
102 async fn read(&self) -> Result<Vec<ResourceContent>> {
103 let content = tokio::fs::read_to_string(&self.file_path)
104 .await
105 .map_err(|e| {
106 Error::Protocol(mocopr_core::error::ProtocolError::ResourceNotFound(
107 format!("Failed to read file: {}", e),
108 ))
109 })?;
110
111 let text_content = TextContent::new(content);
112 let resource_content = ResourceContent {
113 uri: self.uri.clone(),
114 mime_type: self.mime_type.clone(),
115 contents: vec![Content::Text(text_content)],
116 };
117
118 Ok(vec![resource_content])
119 }
120}
121
122pub struct FunctionToolHandler {
124 tool_info: Tool,
125 handler: Box<dyn Fn(Option<serde_json::Value>) -> Result<ToolsCallResponse> + Send + Sync>,
126}
127
128impl FunctionToolHandler {
129 pub fn new<F>(
130 name: impl Into<String>,
131 description: impl Into<String>,
132 input_schema: serde_json::Value,
133 handler: F,
134 ) -> Self
135 where
136 F: Fn(Option<serde_json::Value>) -> Result<ToolsCallResponse> + Send + Sync + 'static,
137 {
138 let tool_info = Tool::new(name, input_schema).with_description(description);
139
140 Self {
141 tool_info,
142 handler: Box::new(handler),
143 }
144 }
145}
146
147#[async_trait]
148impl ToolHandler for FunctionToolHandler {
149 async fn tool(&self) -> Tool {
150 self.tool_info.clone()
151 }
152
153 async fn call(&self, arguments: Option<serde_json::Value>) -> Result<ToolsCallResponse> {
154 (self.handler)(arguments)
155 }
156}
157
158pub struct TemplatePromptHandler {
160 prompt_info: Prompt,
161 template: String,
162}
163
164impl TemplatePromptHandler {
165 pub fn new(
166 name: impl Into<String>,
167 description: impl Into<String>,
168 template: impl Into<String>,
169 arguments: Vec<PromptArgument>,
170 ) -> Self {
171 let prompt_info = Prompt::new(name)
172 .with_description(description)
173 .with_arguments(arguments);
174
175 Self {
176 prompt_info,
177 template: template.into(),
178 }
179 }
180}
181
182#[async_trait]
183impl PromptHandler for TemplatePromptHandler {
184 async fn prompt(&self) -> Prompt {
185 self.prompt_info.clone()
186 }
187
188 async fn generate(
189 &self,
190 arguments: Option<HashMap<String, String>>,
191 ) -> Result<PromptsGetResponse> {
192 let mut content = self.template.clone();
193
194 if let Some(args) = arguments {
195 for (key, value) in args {
196 let placeholder = format!("{{{}}}", key);
197 content = content.replace(&placeholder, &value);
198 }
199 }
200
201 let message = PromptMessage::user(content);
202
203 Ok(PromptsGetResponse {
204 description: self.prompt_info.description.clone(),
205 messages: vec![message],
206 meta: ResponseMetadata { _meta: None },
207 })
208 }
209}
210
211#[macro_export]
213macro_rules! tool_handler {
214 (
215 name: $name:expr,
216 description: $description:expr,
217 schema: $schema:expr,
218 handler: $handler:expr
219 ) => {
220 $crate::handlers::FunctionToolHandler::new($name, $description, $schema, $handler)
221 };
222}
223
224#[macro_export]
226macro_rules! file_resource {
227 (
228 uri: $uri:expr,
229 name: $name:expr,
230 path: $path:expr
231 ) => {
232 $crate::handlers::FileResourceHandler::new($uri.parse().unwrap(), $name, $path)
233 };
234 (
235 uri: $uri:expr,
236 name: $name:expr,
237 path: $path:expr,
238 description: $description:expr
239 ) => {
240 $crate::handlers::FileResourceHandler::new($uri.parse().unwrap(), $name, $path)
241 .with_description($description)
242 };
243 (
244 uri: $uri:expr,
245 name: $name:expr,
246 path: $path:expr,
247 description: $description:expr,
248 mime_type: $mime_type:expr
249 ) => {
250 $crate::handlers::FileResourceHandler::new($uri.parse().unwrap(), $name, $path)
251 .with_description($description)
252 .with_mime_type($mime_type)
253 };
254}
255
256#[macro_export]
258macro_rules! template_prompt {
259 (
260 name: $name:expr,
261 description: $description:expr,
262 template: $template:expr,
263 arguments: [ $( $arg:expr ),* ]
264 ) => {
265 $crate::handlers::TemplatePromptHandler::new(
266 $name,
267 $description,
268 $template,
269 vec![ $( $arg ),* ],
270 )
271 };
272}