1use crate::error::{McpError, McpResult};
35use crate::types::prompt::Prompt;
36use crate::types::resource::Resource;
37use crate::types::tool::Tool;
38use serde::{Deserialize, Serialize};
39use std::collections::HashMap;
40use std::sync::Arc;
41
42#[cfg(feature = "plugin-native")]
43mod native;
44#[cfg(feature = "plugin-wasm")]
45pub mod wasm;
46
47pub mod registry;
48
49pub trait McpPlugin: Send + Sync {
56 fn name(&self) -> &str;
58
59 fn version(&self) -> &str;
61
62 fn description(&self) -> Option<&str> {
64 None
65 }
66
67 fn author(&self) -> Option<&str> {
69 None
70 }
71
72 fn min_mcp_version(&self) -> Option<&str> {
74 None
75 }
76
77 fn register_tools(&self) -> Vec<ToolDefinition> {
79 vec![]
80 }
81
82 fn register_resources(&self) -> Vec<ResourceDefinition> {
84 vec![]
85 }
86
87 fn register_prompts(&self) -> Vec<PromptDefinition> {
89 vec![]
90 }
91
92 fn on_load(&mut self, _config: &PluginConfig) -> McpResult<()> {
94 Ok(())
95 }
96
97 fn on_unload(&mut self) -> McpResult<()> {
99 Ok(())
100 }
101
102 fn can_unload(&self) -> bool {
104 true
105 }
106}
107
108type ToolHandlerFn = Arc<
112 dyn Fn(
113 crate::types::messages::CallToolRequest,
114 ) -> std::pin::Pin<
115 Box<
116 dyn std::future::Future<Output = McpResult<crate::types::tool::CallToolResult>>
117 + Send
118 + 'static,
119 >,
120 > + Send
121 + Sync
122 + 'static,
123>;
124
125type ResourceHandlerFn = Arc<
126 dyn Fn(
127 crate::types::messages::ReadResourceRequest,
128 ) -> std::pin::Pin<
129 Box<
130 dyn std::future::Future<
131 Output = McpResult<crate::types::resource::ReadResourceResult>,
132 > + Send
133 + 'static,
134 >,
135 > + Send
136 + Sync
137 + 'static,
138>;
139
140type PromptHandlerFn = Arc<
141 dyn Fn(
142 crate::types::messages::GetPromptRequest,
143 ) -> std::pin::Pin<
144 Box<
145 dyn std::future::Future<Output = McpResult<crate::types::prompt::GetPromptResult>>
146 + Send
147 + 'static,
148 >,
149 > + Send
150 + Sync
151 + 'static,
152>;
153
154pub struct ToolDefinition {
156 pub tool: Tool,
157 pub handler: ToolHandlerFn,
158}
159
160impl ToolDefinition {
161 pub fn new<F, Fut, T>(tool: Tool, handler: F) -> Self
163 where
164 F: Fn(T) -> Fut + Send + Sync + Clone + 'static,
165 Fut: std::future::Future<Output = crate::types::tool::CallToolResult> + Send + 'static,
166 T: serde::de::DeserializeOwned + Send + 'static,
167 {
168 let handler = Arc::new(move |req: crate::types::messages::CallToolRequest| {
169 let f = handler.clone();
170 let args = req.arguments.clone();
171 Box::pin(async move {
172 let params: T = serde_json::from_value(args)
173 .map_err(|e| McpError::InvalidParams(e.to_string()))?;
174 Ok(f(params).await)
175 })
176 as std::pin::Pin<
177 Box<
178 dyn std::future::Future<
179 Output = McpResult<crate::types::tool::CallToolResult>,
180 > + Send
181 + 'static,
182 >,
183 >
184 });
185
186 Self { tool, handler }
187 }
188
189 pub fn from_handler(tool: Tool, handler: ToolHandlerFn) -> Self {
191 Self { tool, handler }
192 }
193}
194
195pub struct ResourceDefinition {
197 pub resource: Resource,
198 pub handler: ResourceHandlerFn,
199}
200
201impl ResourceDefinition {
202 pub fn new<F, Fut>(resource: Resource, handler: F) -> Self
203 where
204 F: Fn(crate::types::messages::ReadResourceRequest) -> Fut + Send + Sync + Clone + 'static,
205 Fut: std::future::Future<Output = McpResult<crate::types::resource::ReadResourceResult>>
206 + Send
207 + 'static,
208 {
209 let handler = Arc::new(move |req| {
210 let f = handler.clone();
211 Box::pin(f(req))
212 as std::pin::Pin<
213 Box<
214 dyn std::future::Future<
215 Output = McpResult<crate::types::resource::ReadResourceResult>,
216 > + Send
217 + 'static,
218 >,
219 >
220 });
221
222 Self { resource, handler }
223 }
224}
225
226pub struct PromptDefinition {
228 pub prompt: Prompt,
229 pub handler: PromptHandlerFn,
230}
231
232impl PromptDefinition {
233 pub fn new<F, Fut>(prompt: Prompt, handler: F) -> Self
234 where
235 F: Fn(crate::types::messages::GetPromptRequest) -> Fut + Send + Sync + Clone + 'static,
236 Fut: std::future::Future<Output = McpResult<crate::types::prompt::GetPromptResult>>
237 + Send
238 + 'static,
239 {
240 let handler = Arc::new(move |req| {
241 let f = handler.clone();
242 Box::pin(f(req))
243 as std::pin::Pin<
244 Box<
245 dyn std::future::Future<
246 Output = McpResult<crate::types::prompt::GetPromptResult>,
247 > + Send
248 + 'static,
249 >,
250 >
251 });
252
253 Self { prompt, handler }
254 }
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct PluginConfig {
262 #[serde(default)]
264 pub config: serde_json::Value,
265
266 #[serde(default = "default_true")]
268 pub enabled: bool,
269
270 #[serde(default)]
272 pub priority: i32,
273
274 #[serde(default)]
276 pub permissions: PluginPermissions,
277}
278
279fn default_true() -> bool {
280 true
281}
282
283impl Default for PluginConfig {
284 fn default() -> Self {
285 Self {
286 config: serde_json::Value::Null,
287 enabled: true,
288 priority: 0,
289 permissions: PluginPermissions::default(),
290 }
291 }
292}
293
294#[derive(Debug, Clone, Default, Serialize, Deserialize)]
296pub struct PluginPermissions {
297 #[serde(default)]
299 pub network: bool,
300
301 #[serde(default)]
303 pub filesystem: bool,
304
305 #[serde(default)]
307 pub env: bool,
308
309 #[serde(default)]
311 pub process: bool,
312
313 #[serde(default)]
315 pub custom: HashMap<String, bool>,
316}
317
318#[derive(Debug, Clone)]
322pub struct PluginMetadata {
323 pub name: String,
324 pub version: String,
325 pub description: Option<String>,
326 pub author: Option<String>,
327 pub min_mcp_version: Option<String>,
328 pub tool_count: usize,
329 pub resource_count: usize,
330 pub prompt_count: usize,
331}
332
333pub struct PluginManager {
337 plugins: HashMap<String, Box<dyn McpPlugin>>,
338 configs: HashMap<String, PluginConfig>,
339 #[cfg(feature = "plugin-hot-reload")]
340 watcher: Option<notify::RecommendedWatcher>,
341}
342
343impl PluginManager {
344 pub fn new() -> Self {
346 Self {
347 plugins: HashMap::new(),
348 configs: HashMap::new(),
349 #[cfg(feature = "plugin-hot-reload")]
350 watcher: None,
351 }
352 }
353
354 #[cfg(feature = "plugin-native")]
356 pub fn load_from_path(&mut self, path: &str) -> McpResult<()> {
357 self.load_from_path_with_config(path, PluginConfig::default())
358 }
359
360 #[cfg(feature = "plugin-native")]
362 pub fn load_from_path_with_config(
363 &mut self,
364 path: &str,
365 config: PluginConfig,
366 ) -> McpResult<()> {
367 if !config.enabled {
368 tracing::debug!("Plugin at {} is disabled, skipping", path);
369 return Ok(());
370 }
371
372 let mut plugin = native::load_plugin(path)?;
373 plugin.on_load(&config)?;
374
375 let name = plugin.name().to_string();
376 tracing::info!("Loaded plugin: {} v{}", name, plugin.version());
377
378 self.plugins.insert(name.clone(), plugin);
379 self.configs.insert(name, config);
380
381 Ok(())
382 }
383
384 #[cfg(feature = "plugin-wasm")]
386 pub fn load_wasm(&mut self, wasm_bytes: &[u8]) -> McpResult<()> {
387 self.load_wasm_with_config(wasm_bytes, PluginConfig::default())
388 }
389
390 #[cfg(feature = "plugin-wasm")]
392 pub fn load_wasm_with_config(
393 &mut self,
394 wasm_bytes: &[u8],
395 config: PluginConfig,
396 ) -> McpResult<()> {
397 if !config.enabled {
398 return Ok(());
399 }
400
401 let mut plugin = wasm::load_plugin(wasm_bytes)?;
402 plugin.on_load(&config)?;
403
404 let name = plugin.name().to_string();
405 tracing::info!("Loaded WASM plugin: {} v{}", name, plugin.version());
406
407 self.plugins.insert(name.clone(), plugin);
408 self.configs.insert(name, config);
409
410 Ok(())
411 }
412
413 pub fn register_plugin<P: McpPlugin + 'static>(
415 &mut self,
416 mut plugin: P,
417 config: PluginConfig,
418 ) -> McpResult<()> {
419 if !config.enabled {
420 return Ok(());
421 }
422
423 plugin.on_load(&config)?;
424
425 let name = plugin.name().to_string();
426 tracing::info!("Registered plugin: {} v{}", name, plugin.version());
427
428 self.plugins.insert(name.clone(), Box::new(plugin));
429 self.configs.insert(name, config);
430
431 Ok(())
432 }
433
434 pub fn unload(&mut self, name: &str) -> McpResult<()> {
436 if let Some(mut plugin) = self.plugins.remove(name) {
437 if !plugin.can_unload() {
438 self.plugins.insert(name.to_string(), plugin);
440 return Err(McpError::InvalidRequest(format!(
441 "Plugin {} cannot be unloaded",
442 name
443 )));
444 }
445
446 plugin.on_unload()?;
447 self.configs.remove(name);
448 tracing::info!("Unloaded plugin: {}", name);
449 }
450
451 Ok(())
452 }
453
454 pub fn get_metadata(&self, name: &str) -> Option<PluginMetadata> {
456 self.plugins.get(name).map(|plugin| {
457 let tools = plugin.register_tools();
458 let resources = plugin.register_resources();
459 let prompts = plugin.register_prompts();
460
461 PluginMetadata {
462 name: plugin.name().to_string(),
463 version: plugin.version().to_string(),
464 description: plugin.description().map(String::from),
465 author: plugin.author().map(String::from),
466 min_mcp_version: plugin.min_mcp_version().map(String::from),
467 tool_count: tools.len(),
468 resource_count: resources.len(),
469 prompt_count: prompts.len(),
470 }
471 })
472 }
473
474 pub fn list_plugins(&self) -> Vec<PluginMetadata> {
476 self.plugins
477 .keys()
478 .filter_map(|name| self.get_metadata(name))
479 .collect()
480 }
481
482 pub(crate) fn collect_tools(&self) -> Vec<ToolDefinition> {
484 let mut tools = Vec::new();
485
486 let mut plugins: Vec<_> = self.plugins.iter().collect();
488 plugins.sort_by_key(|(name, _)| {
489 self.configs
490 .get(*name)
491 .map(|c| -c.priority) .unwrap_or(0)
493 });
494
495 for (_, plugin) in plugins {
496 tools.extend(plugin.register_tools());
497 }
498
499 tools
500 }
501
502 pub(crate) fn collect_resources(&self) -> Vec<ResourceDefinition> {
504 let mut resources = Vec::new();
505
506 let mut plugins: Vec<_> = self.plugins.iter().collect();
507 plugins.sort_by_key(|(name, _)| self.configs.get(*name).map(|c| -c.priority).unwrap_or(0));
508
509 for (_, plugin) in plugins {
510 resources.extend(plugin.register_resources());
511 }
512
513 resources
514 }
515
516 pub(crate) fn collect_prompts(&self) -> Vec<PromptDefinition> {
518 let mut prompts = Vec::new();
519
520 let mut plugins: Vec<_> = self.plugins.iter().collect();
521 plugins.sort_by_key(|(name, _)| self.configs.get(*name).map(|c| -c.priority).unwrap_or(0));
522
523 for (_, plugin) in plugins {
524 prompts.extend(plugin.register_prompts());
525 }
526
527 prompts
528 }
529
530 #[cfg(feature = "plugin-hot-reload")]
532 pub fn enable_hot_reload(&mut self) -> McpResult<()> {
533 use notify::{RecommendedWatcher, Watcher};
534
535 let (tx, _rx) = std::sync::mpsc::channel();
536
537 let watcher = RecommendedWatcher::new(tx, notify::Config::default())
538 .map_err(|e| McpError::internal(format!("Failed to create watcher: {}", e)))?;
539
540 self.watcher = Some(watcher);
543
544 tracing::info!("Hot reload enabled");
545 Ok(())
546 }
547}
548
549impl Default for PluginManager {
550 fn default() -> Self {
551 Self::new()
552 }
553}