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 .filter(|t| t.active)
74 .map(|t| t.to_json_schema())
75 .collect();
76
77 let json_array = $crate::serde_json::Value::Array(tools_json);
78 $crate::utils::return_success(json_array, result_buf, result_len)
79 }
80
81 /// Auto-generated execute_tool function
82 ///
83 /// Dispatches to the appropriate tool handler based on the tool name.
84 #[no_mangle]
85 pub unsafe extern "C" fn generated_execute_tool(
86 tool_name: *const ::std::os::raw::c_char,
87 args_json: *const u8,
88 args_len: usize,
89 result_buf: *mut *mut u8,
90 result_len: *mut usize,
91 ) -> i32 {
92 use ::std::ffi::CStr;
93
94 // Parse tool name
95 let name = match CStr::from_ptr(tool_name).to_str() {
96 Ok(s) => s,
97 Err(_) => return $crate::utils::return_error(
98 "Invalid tool name encoding",
99 result_buf,
100 result_len
101 ),
102 };
103
104 // Parse arguments
105 let args_slice = ::std::slice::from_raw_parts(args_json, args_len);
106 let args: $crate::serde_json::Value = match $crate::serde_json::from_slice(args_slice) {
107 Ok(v) => v,
108 Err(e) => return $crate::utils::return_error(
109 &format!("Invalid JSON arguments: {}", e),
110 result_buf,
111 result_len
112 ),
113 };
114
115 // Find and execute the tool (O(1) HashMap lookup!)
116 let tools = get_tools();
117 match tools.get(name) {
118 Some(tool) => {
119 if tool.active {
120 match (tool.handler)(&args) {
121 Ok(result) => $crate::utils::return_success(
122 result,
123 result_buf,
124 result_len
125 ),
126 Err(e) => $crate::utils::return_error(
127 &e,
128 result_buf,
129 result_len
130 ),
131 }
132 } else {
133 $crate::utils::return_error(
134 &format!("Inactive tool: {}", name),
135 result_buf,
136 result_len)
137 }
138 }
139 None => $crate::utils::return_error(
140 &format!("Unknown tool: {}", name),
141 result_buf,
142 result_len
143 ),
144 }
145 }
146 };
147}