Skip to main content

mocopr_server/
handlers.rs

1//! Handler traits and implementations for MCP server features
2
3use async_trait::async_trait;
4use mocopr_core::prelude::*;
5use std::collections::HashMap;
6
7/// Trait for handling resource operations
8#[async_trait]
9pub trait ResourceHandler: Send + Sync {
10    /// Get the resource information
11    async fn resource(&self) -> Resource;
12
13    /// Read the resource content
14    async fn read(&self) -> Result<Vec<ResourceContent>>;
15
16    /// Check if the resource supports subscriptions
17    fn supports_subscription(&self) -> bool {
18        false
19    }
20
21    /// Subscribe to resource updates
22    async fn subscribe(&self) -> Result<()> {
23        Err(Error::MethodNotFound("subscribe".to_string()))
24    }
25
26    /// Unsubscribe from resource updates
27    async fn unsubscribe(&self) -> Result<()> {
28        Err(Error::MethodNotFound("unsubscribe".to_string()))
29    }
30}
31
32/// Trait for handling tool operations
33#[async_trait]
34pub trait ToolHandler: Send + Sync {
35    /// Get the tool information
36    async fn tool(&self) -> Tool;
37
38    /// Execute the tool with given arguments
39    async fn call(&self, arguments: Option<serde_json::Value>) -> Result<ToolsCallResponse>;
40}
41
42/// Trait for handling prompt operations
43#[async_trait]
44pub trait PromptHandler: Send + Sync {
45    /// Get the prompt information
46    async fn prompt(&self) -> Prompt;
47
48    /// Generate the prompt with given arguments
49    async fn generate(
50        &self,
51        arguments: Option<HashMap<String, String>>,
52    ) -> Result<PromptsGetResponse>;
53}
54
55/// File-based resource handler
56pub 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
122/// Simple function-based tool handler
123pub 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
158/// Template-based prompt handler
159pub 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 for creating simple tool handlers
212#[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 for creating file resource handlers
225#[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 for creating template prompt handlers
257#[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}