1#![allow(dead_code)]
3
4use std::collections::HashMap;
5use std::path::Path;
6
7use super::mcpb_handler::{McpServerConfig, is_mcpb_source, load_mcpb_file};
8use super::plugin_directories::get_plugin_data_dir;
9use super::plugin_options_storage::{
10 get_plugin_storage_id, load_plugin_options, substitute_plugin_variables,
11 substitute_user_config_variables,
12};
13use crate::plugin::types::{LoadedPlugin, PluginError};
14
15#[derive(Clone, Debug)]
17pub struct ScopedMcpServerConfig {
18 pub config: McpServerConfig,
19 pub scope: String,
20 pub plugin_source: String,
21}
22
23pub async fn load_plugin_mcp_servers(
25 plugin: &LoadedPlugin,
26 errors: &mut Vec<PluginError>,
27) -> Result<Option<HashMap<String, McpServerConfig>>, String> {
28 let mut servers: HashMap<String, McpServerConfig> = HashMap::new();
29
30 let default_mcp_path = Path::new(&plugin.path).join(".mcp.json");
32 if default_mcp_path.exists() {
33 if let Ok(Some(default_servers)) =
34 load_mcp_servers_from_file(&plugin.path, ".mcp.json").await
35 {
36 servers.extend(default_servers);
37 }
38 }
39
40 if let Some(ref mcp_servers_spec) = plugin.manifest.mcp_servers {
42 if let Some(s) = mcp_servers_spec.as_str() {
43 if is_mcpb_source(s) {
44 if let Ok(Some(mcpb_servers)) = load_mcp_servers_from_mcpb(plugin, s, errors).await
45 {
46 servers.extend(mcpb_servers);
47 }
48 } else {
49 if let Ok(Some(json_servers)) = load_mcp_servers_from_file(&plugin.path, s).await {
50 servers.extend(json_servers);
51 }
52 }
53 } else if let Some(arr) = mcp_servers_spec.as_array() {
54 for spec in arr {
55 if let Some(s) = spec.as_str() {
56 if is_mcpb_source(s) {
57 if let Ok(Some(mcpb_servers)) =
58 load_mcp_servers_from_mcpb(plugin, s, errors).await
59 {
60 servers.extend(mcpb_servers);
61 }
62 } else if let Ok(Some(json_servers)) =
63 load_mcp_servers_from_file(&plugin.path, s).await
64 {
65 servers.extend(json_servers);
66 }
67 } else if let Some(_obj) = spec.as_object() {
68 }
70 }
71 } else if let Some(_obj) = mcp_servers_spec.as_object() {
72 }
74 }
75
76 if servers.is_empty() {
77 Ok(None)
78 } else {
79 Ok(Some(servers))
80 }
81}
82
83async fn load_mcp_servers_from_mcpb(
85 plugin: &LoadedPlugin,
86 mcpb_path: &str,
87 errors: &mut Vec<PluginError>,
88) -> Result<Option<HashMap<String, McpServerConfig>>, String> {
89 log::debug!("Loading MCP servers from MCPB: {}", mcpb_path);
90
91 let plugin_id = &plugin.source;
92
93 match load_mcpb_file(mcpb_path, Path::new(&plugin.path), plugin_id).await {
94 Ok(Ok(result)) => {
95 let server_name = result.manifest.name;
96 log::debug!("Loaded MCP server \"{}\" from MCPB", server_name);
97 Ok(Some(result.mcp_config))
98 }
99 Ok(Err(_needs_config)) => {
100 log::debug!("MCPB {} requires user configuration", mcpb_path);
101 Ok(None)
102 }
103 Err(e) => {
104 log::debug!("Failed to load MCPB {}: {}", mcpb_path, e);
105 errors.push(PluginError::McpbExtractFailed {
106 source: plugin_id.clone(),
107 plugin: plugin.name.clone(),
108 mcpb_path: mcpb_path.to_string(),
109 reason: e.to_string(),
110 });
111 Ok(None)
112 }
113 }
114}
115
116async fn load_mcp_servers_from_file(
118 plugin_path: &str,
119 relative_path: &str,
120) -> Result<Option<HashMap<String, McpServerConfig>>, String> {
121 let file_path = Path::new(plugin_path).join(relative_path);
122
123 let content = match tokio::fs::read_to_string(&file_path).await {
124 Ok(c) => c,
125 Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
126 Err(e) => {
127 log::debug!("Failed to load MCP servers from {:?}: {}", file_path, e);
128 return Ok(None);
129 }
130 };
131
132 match serde_json::from_str::<HashMap<String, McpServerConfig>>(&content) {
133 Ok(servers) => Ok(Some(servers)),
134 Err(e) => {
135 log::debug!("Invalid MCP server config in {:?}: {}", file_path, e);
136 Ok(None)
137 }
138 }
139}
140
141pub fn add_plugin_scope_to_servers(
143 servers: HashMap<String, McpServerConfig>,
144 plugin_name: &str,
145 plugin_source: &str,
146) -> HashMap<String, ScopedMcpServerConfig> {
147 servers
148 .into_iter()
149 .map(|(name, config)| {
150 let scoped_name = format!("plugin:{}:{}", plugin_name, name);
151 (
152 scoped_name,
153 ScopedMcpServerConfig {
154 config,
155 scope: "dynamic".to_string(),
156 plugin_source: plugin_source.to_string(),
157 },
158 )
159 })
160 .collect()
161}
162
163pub fn resolve_plugin_mcp_environment(
165 config: &McpServerConfig,
166 plugin: &LoadedPlugin,
167 user_config: Option<&HashMap<String, serde_json::Value>>,
168) -> McpServerConfig {
169 let resolve_value = |value: &str| -> String {
170 let mut resolved = substitute_plugin_variables(value, &plugin.path, &plugin.source);
171
172 if let Some(uc) = user_config {
173 resolved = substitute_user_config_variables(&resolved, uc);
174 }
175
176 expand_env_vars_in_string(&resolved)
177 };
178
179 let mut resolved = config.clone();
180
181 if let Some(ref cmd) = config.command {
182 resolved.command = Some(resolve_value(cmd));
183 }
184
185 if let Some(ref args) = config.args {
186 resolved.args = Some(args.iter().map(|a| resolve_value(a)).collect());
187 }
188
189 let mut resolved_env: HashMap<String, String> = HashMap::new();
190 resolved_env.insert("CLAUDE_PLUGIN_ROOT".to_string(), plugin.path.clone());
191 resolved_env.insert(
192 "CLAUDE_PLUGIN_DATA".to_string(),
193 get_plugin_data_dir(&plugin.source),
194 );
195
196 if let Some(ref env) = config.env {
197 for (key, value) in env {
198 if key != "CLAUDE_PLUGIN_ROOT" && key != "CLAUDE_PLUGIN_DATA" {
199 resolved_env.insert(key.clone(), resolve_value(value));
200 }
201 }
202 }
203 resolved.env = Some(resolved_env);
204
205 resolved
206}
207
208fn expand_env_vars_in_string(value: &str) -> String {
209 let mut result = value.to_string();
210 let mut start = 0;
211 while let Some(pos) = result[start..].find("${") {
212 let abs_pos = start + pos;
213 if let Some(end) = result[abs_pos + 2..].find('}') {
214 let var_name = &result[abs_pos + 2..abs_pos + 2 + end];
215 if let Ok(env_value) = std::env::var(var_name) {
216 result.replace_range(abs_pos..abs_pos + 2 + end + 1, &env_value);
217 start = abs_pos + env_value.len();
218 continue;
219 }
220 }
221 start = abs_pos + 2;
222 }
223 result
224}
225
226pub async fn extract_mcp_servers_from_plugins(
228 plugins: &[LoadedPlugin],
229 errors: &mut Vec<PluginError>,
230) -> HashMap<String, ScopedMcpServerConfig> {
231 let mut all_servers = HashMap::new();
232
233 for plugin in plugins {
234 if !plugin.enabled.unwrap_or(true) {
235 continue;
236 }
237
238 match load_plugin_mcp_servers(plugin, errors).await {
239 Ok(Some(servers)) => {
240 let scoped_servers =
241 add_plugin_scope_to_servers(servers, &plugin.name, &plugin.source);
242 log::debug!(
243 "Loaded {} MCP servers from plugin {}",
244 scoped_servers.len(),
245 plugin.name
246 );
247 all_servers.extend(scoped_servers);
248 }
249 _ => {}
250 }
251 }
252
253 all_servers
254}