prism_mcp_rs/plugin/
api.rs

1// ! Plugin API definitions
2// !
3// ! Module defines the core traits and types that plugins must implement
4// ! to be compatible with the MCP plugin system.
5
6use crate::core::error::McpResult;
7use crate::protocol::types::{CallToolResult as ToolResult, Tool};
8use async_trait::async_trait;
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11use std::any::Any;
12
13/// Core trait that all tool plugins must implement
14#[async_trait]
15pub trait ToolPlugin: Send + Sync {
16    /// Get plugin metadata
17    fn metadata(&self) -> PluginMetadata;
18
19    /// Get tool definition
20    fn tool_definition(&self) -> Tool;
21
22    /// Execute the tool with given arguments
23    async fn execute(&self, arguments: Value) -> McpResult<ToolResult>;
24
25    /// Initialize the plugin (called once after loading)
26    async fn initialize(&mut self) -> McpResult<()> {
27        Ok(())
28    }
29
30    /// Shutdown the plugin (called before unloading)
31    async fn shutdown(&mut self) -> McpResult<()> {
32        Ok(())
33    }
34
35    /// Check if the plugin is healthy
36    async fn health_check(&self) -> McpResult<()> {
37        Ok(())
38    }
39
40    /// Handle configuration updates
41    async fn configure(&mut self, _config: Value) -> McpResult<()> {
42        Ok(())
43    }
44
45    /// Get plugin as Any for downcasting
46    fn as_any(&self) -> &dyn Any;
47}
48
49/// Plugin metadata
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct PluginMetadata {
52    /// Unique plugin identifier
53    pub id: String,
54
55    /// Plugin name
56    pub name: String,
57
58    /// Plugin version (semver)
59    pub version: String,
60
61    /// Plugin author
62    pub author: Option<String>,
63
64    /// Plugin description
65    pub description: Option<String>,
66
67    /// Plugin homepage
68    pub homepage: Option<String>,
69
70    /// Plugin license
71    pub license: Option<String>,
72
73    /// Required MCP SDK version
74    pub mcp_version: String,
75
76    /// Plugin capabilities
77    pub capabilities: PluginCapabilities,
78
79    /// Plugin dependencies
80    pub dependencies: Vec<PluginDependency>,
81}
82
83/// Plugin capabilities
84#[derive(Debug, Clone, Default, Serialize, Deserialize)]
85pub struct PluginCapabilities {
86    /// Supports hot reload
87    pub hot_reload: bool,
88
89    /// Supports configuration updates
90    pub configurable: bool,
91
92    /// Supports health checks
93    pub health_check: bool,
94
95    /// Is thread-safe
96    pub thread_safe: bool,
97
98    /// Supports multiple instances
99    pub multi_instance: bool,
100
101    /// Custom capabilities
102    pub custom: Value,
103}
104
105/// Plugin dependency
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct PluginDependency {
108    /// Dependency plugin ID
109    pub plugin_id: String,
110
111    /// Required version range
112    pub version: String,
113
114    /// Is optional
115    pub optional: bool,
116}
117
118/// Plugin factory function type - FIXED signature
119pub type PluginFactory = unsafe extern "C" fn() -> *mut Box<dyn ToolPlugin>;
120
121/// Helper trait for plugin builders
122pub trait PluginBuilder: Send + Sync {
123    /// Build the plugin instance
124    fn build(self) -> Box<dyn ToolPlugin>;
125
126    /// Build with configuration
127    fn build_with_config(self, _config: Value) -> McpResult<Box<dyn ToolPlugin>>
128    where
129        Self: Sized,
130    {
131        Ok(self.build())
132    }
133}
134
135/// Standard plugin builder implementation
136pub struct StandardPluginBuilder<T: ToolPlugin + Default + 'static> {
137    config: Option<Value>,
138    _phantom: std::marker::PhantomData<T>,
139}
140
141impl<T: ToolPlugin + Default + 'static> Default for StandardPluginBuilder<T> {
142    fn default() -> Self {
143        Self::new()
144    }
145}
146
147impl<T: ToolPlugin + Default + 'static> StandardPluginBuilder<T> {
148    /// Create a new builder
149    pub fn new() -> Self {
150        Self {
151            config: None,
152            _phantom: std::marker::PhantomData,
153        }
154    }
155
156    /// Set configuration
157    pub fn with_config(mut self, config: Value) -> Self {
158        self.config = Some(config);
159        self
160    }
161}
162
163impl<T: ToolPlugin + Default + 'static> PluginBuilder for StandardPluginBuilder<T> {
164    fn build(self) -> Box<dyn ToolPlugin> {
165        Box::new(T::default())
166    }
167
168    fn build_with_config(self, config: Value) -> McpResult<Box<dyn ToolPlugin>> {
169        let mut plugin = T::default();
170        // Use async runtime to initialize with config
171        let runtime = tokio::runtime::Handle::current();
172        runtime.block_on(plugin.configure(config))?;
173        Ok(Box::new(plugin))
174    }
175}
176
177/// FIXED Macro to simplify plugin exports with correct ABI
178#[macro_export]
179macro_rules! export_plugin {
180    ($plugin_type:ty) => {
181        /// Plugin creation function with correct C ABI
182        #[unsafe(no_mangle)]
183        pub unsafe extern "C" fn _mcp_plugin_create() -> *mut Box<dyn $crate::plugin::ToolPlugin> {
184            let plugin = Box::new(<$plugin_type>::default());
185            let boxed: Box<dyn $crate::plugin::ToolPlugin> = plugin;
186            Box::into_raw(Box::new(boxed))
187        }
188
189        /// Plugin version function
190        #[unsafe(no_mangle)]
191        pub extern "C" fn _mcp_plugin_version() -> *const u8 {
192            concat!(env!("CARGO_PKG_VERSION"), "\0").as_ptr()
193        }
194
195        /// Plugin metadata function for introspection
196        #[unsafe(no_mangle)]
197        pub extern "C" fn _mcp_plugin_metadata() -> *const u8 {
198            let metadata = <$plugin_type>::default().metadata();
199            let json = serde_json::to_string(&metadata).unwrap_or_default();
200            let c_str = std::ffi::CString::new(json).unwrap_or_default();
201            c_str.into_raw() as *const u8
202        }
203    };
204}