Skip to main content

mcp_plugin_api/
macros.rs

1//! Macros for simplifying plugin declaration
2
3/// Declare tools and auto-generate list_tools and execute_tool functions
4///
5/// This macro takes a list of Tool definitions and generates:
6/// - A static tool registry (HashMap for O(1) lookup)
7/// - The `generated_list_tools` function
8/// - The `generated_execute_tool` function
9///
10/// These generated functions can be used directly in the `declare_plugin!` macro.
11///
12/// # Example
13///
14/// ```ignore
15/// use mcp_plugin_api::*;
16/// use serde_json::{json, Value};
17///
18/// fn handle_hello(args: &Value) -> Result<Value, String> {
19///     let name = args["name"].as_str().unwrap_or("World");
20///     Ok(json!({ "message": format!("Hello, {}!", name) }))
21/// }
22///
23/// fn handle_goodbye(args: &Value) -> Result<Value, String> {
24///     Ok(json!({ "message": "Goodbye!" }))
25/// }
26///
27/// declare_tools! {
28///     tools: [
29///         Tool::new("hello", "Say hello")
30///             .param_string("name", "Name to greet", false)
31///             .handler(handle_hello),
32///         
33///         Tool::new("goodbye", "Say goodbye")
34///             .handler(handle_goodbye),
35///     ]
36/// }
37///
38/// declare_plugin! {
39///     list_tools: generated_list_tools,
40///     execute_tool: generated_execute_tool,
41///     free_string: mcp_plugin_api::utils::standard_free_string
42/// }
43/// ```
44#[macro_export]
45macro_rules! declare_tools {
46    (tools: [ $($tool:expr),* $(,)? ]) => {
47        // Generate a static HashMap of tools using OnceLock for thread-safe lazy init
48        static TOOLS: ::std::sync::OnceLock<::std::collections::HashMap<::std::string::String, $crate::tool::Tool>> 
49            = ::std::sync::OnceLock::new();
50        
51        fn get_tools() -> &'static ::std::collections::HashMap<::std::string::String, $crate::tool::Tool> {
52            TOOLS.get_or_init(|| {
53                let mut map = ::std::collections::HashMap::new();
54                $(
55                    let tool = $tool;
56                    map.insert(tool.name.clone(), tool);
57                )*
58                map
59            })
60        }
61        
62        /// Auto-generated list_tools function
63        ///
64        /// Returns a JSON array of all tool definitions.
65        #[no_mangle]
66        pub unsafe extern "C" fn generated_list_tools(
67            result_buf: *mut *mut u8,
68            result_len: *mut usize,
69        ) -> i32 {
70            let tools = get_tools();
71            let tools_json: ::std::vec::Vec<$crate::serde_json::Value> = tools
72                .values()
73                .map(|t| t.to_json_schema())
74                .collect();
75            
76            let json_array = $crate::serde_json::Value::Array(tools_json);
77            $crate::utils::return_success(json_array, result_buf, result_len)
78        }
79        
80        /// Auto-generated execute_tool function
81        ///
82        /// Dispatches to the appropriate tool handler based on the tool name.
83        #[no_mangle]
84        pub unsafe extern "C" fn generated_execute_tool(
85            tool_name: *const ::std::os::raw::c_char,
86            args_json: *const u8,
87            args_len: usize,
88            result_buf: *mut *mut u8,
89            result_len: *mut usize,
90        ) -> i32 {
91            use ::std::ffi::CStr;
92            
93            // Parse tool name
94            let name = match CStr::from_ptr(tool_name).to_str() {
95                Ok(s) => s,
96                Err(_) => return $crate::utils::return_error(
97                    "Invalid tool name encoding",
98                    result_buf,
99                    result_len
100                ),
101            };
102            
103            // Parse arguments
104            let args_slice = ::std::slice::from_raw_parts(args_json, args_len);
105            let args: $crate::serde_json::Value = match $crate::serde_json::from_slice(args_slice) {
106                Ok(v) => v,
107                Err(e) => return $crate::utils::return_error(
108                    &format!("Invalid JSON arguments: {}", e),
109                    result_buf,
110                    result_len
111                ),
112            };
113            
114            // Find and execute the tool (O(1) HashMap lookup!)
115            let tools = get_tools();
116            match tools.get(name) {
117                Some(tool) => {
118                    match (tool.handler)(&args) {
119                        Ok(result) => $crate::utils::return_success(
120                            result,
121                            result_buf,
122                            result_len
123                        ),
124                        Err(e) => $crate::utils::return_error(
125                            &e,
126                            result_buf,
127                            result_len
128                        ),
129                    }
130                }
131                None => $crate::utils::return_error(
132                    &format!("Unknown tool: {}", name),
133                    result_buf,
134                    result_len
135                ),
136            }
137        }
138    };
139}