1use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use std::collections::HashMap;
8use std::path::PathBuf;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct PluginConfig {
13 pub name: String,
15
16 #[serde(default = "default_enabled")]
18 pub enabled: bool,
19
20 pub path: Option<String>,
22
23 pub config: Option<Value>,
25
26 #[serde(default)]
28 pub env: HashMap<String, String>,
29
30 #[serde(default)]
32 pub auto_reload: bool,
33
34 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct PluginManifest {
50 pub plugin: PluginInfo,
52
53 #[serde(default)]
55 pub dependencies: Vec<Dependency>,
56
57 pub tool: ToolDefinition,
59
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub build: Option<BuildInfo>,
63
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub install: Option<InstallInfo>,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct PluginInfo {
72 pub id: String,
74
75 pub name: String,
77
78 pub version: String,
80
81 #[serde(skip_serializing_if = "Option::is_none")]
83 pub author: Option<String>,
84
85 #[serde(skip_serializing_if = "Option::is_none")]
87 pub description: Option<String>,
88
89 #[serde(skip_serializing_if = "Option::is_none")]
91 pub homepage: Option<String>,
92
93 #[serde(skip_serializing_if = "Option::is_none")]
95 pub repository: Option<String>,
96
97 #[serde(skip_serializing_if = "Option::is_none")]
99 pub license: Option<String>,
100
101 #[serde(default)]
103 pub keywords: Vec<String>,
104
105 pub mcp_version: String,
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct Dependency {
112 pub plugin: String,
114
115 pub version: String,
117
118 #[serde(default)]
120 pub optional: bool,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct ToolDefinition {
126 pub name: String,
128
129 pub description: String,
131
132 pub input_schema: Value,
134
135 #[serde(skip_serializing_if = "Option::is_none")]
137 pub output_schema: Option<Value>,
138
139 #[serde(default)]
141 pub examples: Vec<ToolExample>,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct ToolExample {
147 pub name: String,
149
150 #[serde(skip_serializing_if = "Option::is_none")]
152 pub description: Option<String>,
153
154 pub input: Value,
156
157 #[serde(skip_serializing_if = "Option::is_none")]
159 pub output: Option<Value>,
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct BuildInfo {
165 pub command: String,
167
168 #[serde(skip_serializing_if = "Option::is_none")]
170 pub directory: Option<String>,
171
172 pub output: String,
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct InstallInfo {
179 #[serde(skip_serializing_if = "Option::is_none")]
181 pub pre_install: Option<String>,
182
183 #[serde(skip_serializing_if = "Option::is_none")]
185 pub post_install: Option<String>,
186
187 #[serde(default)]
189 pub system_deps: Vec<String>,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct PluginConfigSet {
195 pub plugins: Vec<PluginConfig>,
197
198 #[serde(skip_serializing_if = "Option::is_none")]
200 pub settings: Option<PluginSettings>,
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct PluginSettings {
206 #[serde(default)]
208 pub plugin_dirs: Vec<PathBuf>,
209
210 #[serde(default = "default_auto_load")]
212 pub auto_load: bool,
213
214 #[serde(default = "default_hot_reload")]
216 pub hot_reload: bool,
217
218 #[serde(default)]
220 pub isolation: IsolationLevel,
221
222 #[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#[derive(Debug, Clone, Serialize, Deserialize, Default)]
241#[serde(rename_all = "lowercase")]
242pub enum IsolationLevel {
243 #[default]
245 None,
246
247 Thread,
249
250 Process,
252
253 Container,
255}
256
257impl PluginConfig {
258 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 pub fn with_path(mut self, path: impl Into<String>) -> Self {
273 self.path = Some(path.into());
274 self
275 }
276
277 pub fn with_config(mut self, config: Value) -> Self {
279 self.config = Some(config);
280 self
281 }
282
283 pub fn with_auto_reload(mut self) -> Self {
285 self.auto_reload = true;
286 self
287 }
288}
289
290impl PluginManifest {
291 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 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 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 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 pub fn sort_by_priority(&mut self) {
331 self.plugins.sort_by_key(|p| p.priority);
332 }
333}