Skip to main content

prism_mcp_rs/plugin/
config.rs

1//! Plugin configuration management
2//!
3//! Module handles plugin configuration files and manifests.
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use std::collections::HashMap;
8use std::path::PathBuf;
9
10/// Plugin configuration
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct PluginConfig {
13    /// Plugin name/identifier
14    pub name: String,
15
16    /// Whether the plugin is enabled
17    #[serde(default = "default_enabled")]
18    pub enabled: bool,
19
20    /// Path to the plugin file (optional, will search in default locations)
21    pub path: Option<String>,
22
23    /// Plugin-specific configuration
24    pub config: Option<Value>,
25
26    /// Environment variables to set for the plugin
27    #[serde(default)]
28    pub env: HashMap<String, String>,
29
30    /// Auto-reload on file change
31    #[serde(default)]
32    pub auto_reload: bool,
33
34    /// Load priority (lower numbers load first)
35    #[serde(default = "default_priority")]
36    pub priority: i32,
37}
38
39fn default_enabled() -> bool {
40    true
41}
42
43fn default_priority() -> i32 {
44    100
45}
46
47/// Plugin manifest (plugin.yaml in plugin directory)
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct PluginManifest {
50    /// Plugin metadata
51    pub plugin: PluginInfo,
52
53    /// Required dependencies
54    #[serde(default)]
55    pub dependencies: Vec<Dependency>,
56
57    /// Tool definition
58    pub tool: ToolDefinition,
59
60    /// Build information
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub build: Option<BuildInfo>,
63
64    /// Installation instructions
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub install: Option<InstallInfo>,
67}
68
69/// Plugin information
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct PluginInfo {
72    /// Plugin ID
73    pub id: String,
74
75    /// Plugin name
76    pub name: String,
77
78    /// Plugin version
79    pub version: String,
80
81    /// Plugin author
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub author: Option<String>,
84
85    /// Plugin description
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub description: Option<String>,
88
89    /// Plugin homepage
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub homepage: Option<String>,
92
93    /// Plugin repository
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub repository: Option<String>,
96
97    /// Plugin license
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub license: Option<String>,
100
101    /// Keywords for discovery
102    #[serde(default)]
103    pub keywords: Vec<String>,
104
105    /// Required MCP SDK version
106    pub mcp_version: String,
107}
108
109/// Dependency specification
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct Dependency {
112    /// Dependency plugin ID
113    pub plugin: String,
114
115    /// Required version range
116    pub version: String,
117
118    /// Is optional
119    #[serde(default)]
120    pub optional: bool,
121}
122
123/// Tool definition
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct ToolDefinition {
126    /// Tool name
127    pub name: String,
128
129    /// Tool description
130    pub description: String,
131
132    /// Input schema (JSON Schema)
133    pub input_schema: Value,
134
135    /// Output schema (optional)
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub output_schema: Option<Value>,
138
139    /// Tool examples
140    #[serde(default)]
141    pub examples: Vec<ToolExample>,
142}
143
144/// Tool usage example
145#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct ToolExample {
147    /// Example name
148    pub name: String,
149
150    /// Example description
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub description: Option<String>,
153
154    /// Input arguments
155    pub input: Value,
156
157    /// Expected output
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub output: Option<Value>,
160}
161
162/// Build information
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct BuildInfo {
165    /// Build command
166    pub command: String,
167
168    /// Build directory
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub directory: Option<String>,
171
172    /// Output file
173    pub output: String,
174}
175
176/// Installation information
177#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct InstallInfo {
179    /// Pre-install script
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub pre_install: Option<String>,
182
183    /// Post-install script
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub post_install: Option<String>,
186
187    /// Required system dependencies
188    #[serde(default)]
189    pub system_deps: Vec<String>,
190}
191
192/// Plugin configuration set
193#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct PluginConfigSet {
195    /// Plugin configurations
196    pub plugins: Vec<PluginConfig>,
197
198    /// Global plugin settings
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub settings: Option<PluginSettings>,
201}
202
203/// Global plugin settings
204#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct PluginSettings {
206    /// Plugin directory paths
207    #[serde(default)]
208    pub plugin_dirs: Vec<PathBuf>,
209
210    /// Auto-load plugins from directories
211    #[serde(default = "default_auto_load")]
212    pub auto_load: bool,
213
214    /// Enable hot reload
215    #[serde(default = "default_hot_reload")]
216    pub hot_reload: bool,
217
218    /// Plugin isolation level
219    #[serde(default)]
220    pub isolation: IsolationLevel,
221
222    /// Maximum plugin load time (seconds)
223    #[serde(default = "default_load_timeout")]
224    pub load_timeout: u64,
225}
226
227fn default_auto_load() -> bool {
228    true
229}
230
231fn default_hot_reload() -> bool {
232    false
233}
234
235fn default_load_timeout() -> u64 {
236    30
237}
238
239/// Plugin isolation level
240#[derive(Debug, Clone, Serialize, Deserialize, Default)]
241#[serde(rename_all = "lowercase")]
242pub enum IsolationLevel {
243    /// No isolation (default)
244    #[default]
245    None,
246
247    /// Thread isolation
248    Thread,
249
250    /// Process isolation
251    Process,
252
253    /// Container isolation
254    Container,
255}
256
257impl PluginConfig {
258    /// Create a simple plugin configuration
259    pub fn simple(name: impl Into<String>) -> Self {
260        Self {
261            name: name.into(),
262            enabled: true,
263            path: None,
264            config: None,
265            env: HashMap::new(),
266            auto_reload: false,
267            priority: 100,
268        }
269    }
270
271    /// Set the plugin path
272    pub fn with_path(mut self, path: impl Into<String>) -> Self {
273        self.path = Some(path.into());
274        self
275    }
276
277    /// Set plugin configuration
278    pub fn with_config(mut self, config: Value) -> Self {
279        self.config = Some(config);
280        self
281    }
282
283    /// Enable auto-reload
284    pub fn with_auto_reload(mut self) -> Self {
285        self.auto_reload = true;
286        self
287    }
288}
289
290impl PluginManifest {
291    /// Load manifest from YAML file
292    pub async fn from_file(path: impl AsRef<std::path::Path>) -> Result<Self, String> {
293        let content = tokio::fs::read_to_string(path)
294            .await
295            .map_err(|e| e.to_string())?;
296
297        serde_yaml::from_str(&content).map_err(|e| e.to_string())
298    }
299
300    /// Save manifest to YAML file
301    pub async fn to_file(&self, path: impl AsRef<std::path::Path>) -> Result<(), String> {
302        let content = serde_yaml::to_string(self).map_err(|e| e.to_string())?;
303
304        tokio::fs::write(path, content)
305            .await
306            .map_err(|e| e.to_string())
307    }
308}
309
310impl PluginConfigSet {
311    /// Load configuration set from YAML file
312    pub async fn from_file(path: impl AsRef<std::path::Path>) -> Result<Self, String> {
313        let content = tokio::fs::read_to_string(path)
314            .await
315            .map_err(|e| e.to_string())?;
316
317        serde_yaml::from_str(&content).map_err(|e| e.to_string())
318    }
319
320    /// Save configuration set to YAML file
321    pub async fn to_file(&self, path: impl AsRef<std::path::Path>) -> Result<(), String> {
322        let content = serde_yaml::to_string(self).map_err(|e| e.to_string())?;
323
324        tokio::fs::write(path, content)
325            .await
326            .map_err(|e| e.to_string())
327    }
328
329    /// Sort plugins by priority
330    pub fn sort_by_priority(&mut self) {
331        self.plugins.sort_by_key(|p| p.priority);
332    }
333}