mcp_ectors/examples/
counter_router.rs

1use serde_json::Value;
2use std::sync::Arc;
3use tokio::sync::Mutex;
4use actix::ResponseFuture;
5
6use crate::router::{router::CapabilitiesBuilder, Router};
7use mcp_spec::{handler::ResourceError, prompt::Prompt, protocol::{CallToolResult, GetPromptResult, ReadResourceResult, ServerCapabilities}, Content, Resource, ResourceContents::TextResourceContents, Tool, ToolError};
8
9/// **A simple counter router that implements `RouterHandler`**
10#[derive(Clone)]
11pub struct CounterRouter {
12    counter: Arc<Mutex<i32>>,
13}
14
15impl CounterRouter {
16    pub fn new() -> Self {
17        Self {
18            counter: Arc::new(Mutex::new(0)),
19        }
20    }
21
22    async fn increment(&self) -> Result<i32, ()> {
23        let mut counter = self.counter.lock().await;
24        *counter += 1;
25        Ok(*counter)
26    }
27
28    async fn decrement(&self) -> Result<i32, ()> {
29        let mut counter = self.counter.lock().await;
30        *counter -= 1;
31        Ok(*counter)
32    }
33
34    async fn get_value(&self) -> Result<i32, ()> {
35        let counter = self.counter.lock().await;
36        Ok(*counter)
37    }
38
39    fn create_resource_text(&self, uri: &str, name: &str) -> Resource {
40        Resource::new(uri, Some("text/plain".to_string()), Some(name.to_string())).unwrap()
41    }
42
43}
44
45
46impl Router for CounterRouter {
47    fn name(&self) -> String {
48        "counter".to_string()
49    }
50
51    fn instructions(&self) -> String {
52        "This server provides a counter tool that can increment and decrement values. The counter starts at 0 and can be modified using the 'increment' and 'decrement' tools. Use 'get_value' to check the current count.".to_string()
53    }
54
55    fn capabilities(&self) -> ServerCapabilities {
56        CapabilitiesBuilder::new()
57            .with_tools(false)
58            .with_resources(false, false)
59            .build()
60    }
61
62    fn list_tools(&self) -> Vec<Tool> {
63        vec![
64            Tool::new(
65                "increment".to_string(),
66                "Increment the counter by 1".to_string(),
67                serde_json::json!({
68                    "type": "object",
69                    "properties": {},
70                    "required": []
71                }),
72            ),
73            Tool::new(
74                "decrement".to_string(),
75                "Decrement the counter by 1".to_string(),
76                serde_json::json!({
77                    "type": "object",
78                    "properties": {},
79                    "required": []
80                }),
81            ),
82            Tool::new(
83                "get_value".to_string(),
84                "Get the current counter value".to_string(),
85                serde_json::json!({
86                    "type": "object",
87                    "properties": {},
88                    "required": []
89                }),
90            ),
91        ]
92    }
93
94    fn call_tool(
95        &self,
96        tool_name: &str,
97        _arguments: Value,
98    ) -> ResponseFuture<Result<CallToolResult, ToolError>> {
99        let this = self.clone();
100        let tool_name = tool_name.to_string();
101
102        Box::pin(async move {
103            match tool_name.as_str() {
104                "increment" => {
105                    let value = this.increment().await.expect("increment does not work");
106                    let result = CallToolResult{ content: vec![Content::text(value.to_string())], is_error: Some(false) };
107                    Ok(result)
108                }
109                "decrement" => {
110                    let value = this.decrement().await.expect("decrement does not work");
111                    let result = CallToolResult{ content: vec![Content::text(value.to_string())], is_error: Some(false) };
112                    Ok(result)
113                }
114                "get_value" => {
115                    let value = this.get_value().await.expect("get value does not work");
116                    let result = CallToolResult{ content: vec![Content::text(value.to_string())], is_error: Some(false) };
117                    Ok(result)
118                }
119                _ => Err(ToolError::NotFound(format!("Tool {} not found", tool_name))),
120            }
121        })
122    }
123
124    fn list_resources(&self) -> Vec<Resource> {
125        vec![
126            self.create_resource_text("str://///Users/maarten/ai/test", "cwd"),
127            self.create_resource_text("memo://insights", "memo-name"),
128        ]
129    }
130
131    fn read_resource(
132        &self,
133        uri: &str,
134    ) -> ResponseFuture<Result<ReadResourceResult, ResourceError>> {
135        let uri = uri.to_string();
136        Box::pin(async move {
137            match uri.as_str() {
138                "str://///Users/maarten/ai/test/" => {
139                    let cwd = TextResourceContents{ uri, mime_type: Some("text/plain".to_string()), text: "/Users/maarten/ai/test/".to_string() };
140                    let result = ReadResourceResult{ contents: vec![cwd] };
141                    Ok(result)
142                }
143                "memo://insights" => {
144                    let cwd = TextResourceContents{ uri, mime_type: Some("text/plain".to_string()), text: "Business Intelligence Memo\n\nAnalysis has revealed 5 key insights ...".to_string() };
145                    let result = ReadResourceResult{ contents: vec![cwd] };
146                    Ok(result)
147                }
148                _ => Err(ResourceError::NotFound(format!(
149                    "Resource {} not found",
150                    uri
151                ))),
152            }
153        })
154    }
155    
156    fn list_prompts(&self) -> Vec<Prompt> {
157        vec![]
158    }
159    
160    fn get_prompt(&self, _prompt_name: &str) -> ResponseFuture<Result<GetPromptResult, ResourceError>> {
161
162        let result = GetPromptResult{ description: None, messages: vec![] };
163        Box::pin(async move {
164            Ok(result)
165        })
166    }
167}