Skip to main content

model_context_protocol/
macro_adapter.rs

1//! Adapter for macro-generated MCP servers.
2//!
3//! This module provides `MacroServerAdapter` which wraps types generated by
4//! `#[mcp_server]` macros and implements the `ToolProvider` trait.
5
6use std::sync::Arc;
7
8use serde_json::Value;
9
10use crate::protocol::McpToolDef;
11use crate::tool::{BoxFuture, DynTool, McpTool, ToolCallResult, ToolProvider};
12
13/// Trait for types generated by `#[mcp_server]` macros.
14///
15/// This is automatically implemented when you use `#[mcp_server]` on an impl block.
16pub trait MacroServer: Send + Sync {
17    /// List available tools.
18    fn list_tools(&self) -> Vec<McpToolDef>;
19
20    /// Call a tool by name.
21    fn call_tool(&self, name: &str, args: Value) -> ToolCallResult;
22}
23
24/// Adapter that wraps a macro-generated server and implements `ToolProvider`.
25///
26/// # Example
27///
28/// ```ignore
29/// use mcp::{MacroServerAdapter, McpServer};
30///
31/// #[mcp_server(name = "calculator")]
32/// pub struct Calculator;
33///
34/// #[mcp_server]
35/// impl Calculator {
36///     #[mcp_tool(description = "Add two numbers")]
37///     fn add(&self, a: f64, b: f64) -> f64 {
38///         a + b
39///     }
40/// }
41///
42/// let server = McpServer::builder()
43///     .name("calculator")
44///     .version("1.0.0")
45///     .with_tools_from(MacroServerAdapter::new(Calculator))
46///     .build();
47/// ```
48pub struct MacroServerAdapter<T: MacroServer> {
49    server: Arc<T>,
50}
51
52impl<T: MacroServer + 'static> MacroServerAdapter<T> {
53    /// Create a new adapter for a macro-generated server.
54    pub fn new(server: T) -> Self {
55        Self {
56            server: Arc::new(server),
57        }
58    }
59}
60
61impl<T: MacroServer + 'static> ToolProvider for MacroServerAdapter<T> {
62    fn tools(&self) -> Vec<DynTool> {
63        self.server
64            .list_tools()
65            .into_iter()
66            .map(|tool_def| {
67                let wrapper = MacroToolWrapper {
68                    definition: tool_def.clone(),
69                    server: self.server.clone(),
70                };
71                Arc::new(wrapper) as DynTool
72            })
73            .collect()
74    }
75}
76
77/// Internal wrapper that implements `McpTool` for a single tool from a macro server.
78struct MacroToolWrapper<T: MacroServer> {
79    definition: McpToolDef,
80    server: Arc<T>,
81}
82
83impl<T: MacroServer + 'static> McpTool for MacroToolWrapper<T> {
84    fn definition(&self) -> McpToolDef {
85        self.definition.clone()
86    }
87
88    fn call<'a>(&'a self, args: Value) -> BoxFuture<'a, ToolCallResult> {
89        let name = self.definition.name.clone();
90        Box::pin(async move { self.server.call_tool(&name, args) })
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    struct TestServer;
99
100    impl MacroServer for TestServer {
101        fn list_tools(&self) -> Vec<McpToolDef> {
102            vec![McpToolDef {
103                name: "test".to_string(),
104                description: Some("Test tool".to_string()),
105                group: None,
106                input_schema: serde_json::json!({"type": "object"}),
107            }]
108        }
109
110        fn call_tool(&self, name: &str, _args: Value) -> ToolCallResult {
111            if name == "test" {
112                Ok(vec![crate::protocol::ToolContent::text("ok")])
113            } else {
114                Err(format!("Unknown: {}", name))
115            }
116        }
117    }
118
119    #[test]
120    fn test_adapter_provides_tools() {
121        let adapter = MacroServerAdapter::new(TestServer);
122        let tools = adapter.tools();
123        assert_eq!(tools.len(), 1);
124        assert_eq!(tools[0].definition().name, "test");
125    }
126}