mcp_execution_cli/commands/
generate.rs1use super::common::{build_server_config, load_server_from_config};
10use anyhow::{Context, Result};
11use mcp_execution_codegen::progressive::ProgressiveGenerator;
12use mcp_execution_core::cli::{ExitCode, OutputFormat};
13use mcp_execution_files::FilesBuilder;
14use mcp_execution_introspector::Introspector;
15use serde::Serialize;
16use std::path::PathBuf;
17use tracing::{debug, info, warn};
18
19#[derive(Debug, Serialize)]
21struct GenerationResult {
22 server_id: String,
24 server_name: String,
26 tool_count: usize,
28 output_path: String,
30}
31
32#[allow(clippy::too_many_arguments)]
66pub async fn run(
67 from_config: Option<String>,
68 server: Option<String>,
69 args: Vec<String>,
70 env: Vec<String>,
71 cwd: Option<String>,
72 http: Option<String>,
73 sse: Option<String>,
74 headers: Vec<String>,
75 name: Option<String>,
76 output_dir: Option<PathBuf>,
77 output_format: OutputFormat,
78) -> Result<ExitCode> {
79 let (server_id, server_config) = if let Some(config_name) = from_config {
81 debug!(
82 "Loading server configuration from ~/.claude/mcp.json: {}",
83 config_name
84 );
85 load_server_from_config(&config_name)?
86 } else {
87 build_server_config(server, args, env, cwd, http, sse, headers)?
88 };
89
90 info!("Connecting to MCP server: {}", server_id);
91
92 let mut introspector = Introspector::new();
94 let server_info = introspector
95 .discover_server(server_id, &server_config)
96 .await
97 .context("failed to introspect MCP server")?;
98
99 info!(
100 "Discovered {} tools from server '{}'",
101 server_info.tools.len(),
102 server_info.name
103 );
104
105 if server_info.tools.is_empty() {
106 warn!("Server has no tools to generate code for");
107 return Ok(ExitCode::SUCCESS);
108 }
109
110 let mut server_info = server_info;
113 if let Some(ref custom_name) = name {
114 server_info.id = mcp_execution_core::ServerId::new(custom_name);
115 }
116
117 let server_dir_name = server_info.id.to_string();
119
120 let generator = ProgressiveGenerator::new().context("failed to create code generator")?;
122
123 let generated_code = generator
124 .generate(&server_info)
125 .context("failed to generate TypeScript code")?;
126
127 info!(
128 "Generated {} files for progressive loading",
129 generated_code.file_count()
130 );
131
132 let vfs = FilesBuilder::from_generated_code(generated_code, "/")
136 .build()
137 .context("failed to build VFS")?;
138
139 let base_dir = if let Some(custom_dir) = output_dir {
142 custom_dir
143 } else {
144 dirs::home_dir()
145 .context("failed to get home directory")?
146 .join(".claude")
147 .join("servers")
148 };
149
150 let output_path = base_dir.join(&server_dir_name);
151
152 info!("Exporting files to: {}", output_path.display());
153
154 std::fs::create_dir_all(&output_path).context("failed to create output directory")?;
156
157 vfs.export_to_filesystem(&output_path)
159 .context("failed to export files to filesystem")?;
160
161 let result = GenerationResult {
163 server_id: server_info.id.to_string(),
164 server_name: server_info.name.clone(),
165 tool_count: server_info.tools.len(),
166 output_path: output_path.display().to_string(),
167 };
168
169 match output_format {
171 OutputFormat::Json => {
172 println!("{}", serde_json::to_string_pretty(&result)?);
173 }
174 OutputFormat::Text => {
175 println!("Server: {} ({})", result.server_name, result.server_id);
176 println!("Generated {} tool files", result.tool_count);
177 println!("Output: {}", result.output_path);
178 }
179 OutputFormat::Pretty => {
180 println!("✓ Successfully generated progressive loading files");
181 println!(" Server: {} ({})", result.server_name, result.server_id);
182 println!(" Tools: {}", result.tool_count);
183 println!(" Location: {}", result.output_path);
184 }
185 }
186
187 Ok(ExitCode::SUCCESS)
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193 use mcp_execution_core::ServerId;
194 use mcp_execution_introspector::{ServerCapabilities, ServerInfo, ToolInfo};
195 use serde_json::json;
196
197 fn create_mock_server_info() -> ServerInfo {
198 ServerInfo {
199 id: ServerId::new("test-server"),
200 name: "Test Server".to_string(),
201 version: "1.0.0".to_string(),
202 tools: vec![ToolInfo {
203 name: mcp_execution_core::ToolName::new("test_tool"),
204 description: "A test tool".to_string(),
205 input_schema: json!({
206 "type": "object",
207 "properties": {
208 "param": {"type": "string"}
209 }
210 }),
211 output_schema: None,
212 }],
213 capabilities: ServerCapabilities {
214 supports_tools: true,
215 supports_resources: false,
216 supports_prompts: false,
217 },
218 }
219 }
220
221 #[test]
222 fn test_generation_result_serialization() {
223 let result = GenerationResult {
224 server_id: "test".to_string(),
225 server_name: "Test Server".to_string(),
226 tool_count: 5,
227 output_path: "/path/to/output".to_string(),
228 };
229
230 let json = serde_json::to_string(&result).unwrap();
231 assert!(json.contains("\"server_id\":\"test\""));
232 assert!(json.contains("\"tool_count\":5"));
233 }
234
235 #[test]
236 fn test_progressive_generator_creation() {
237 let generator = ProgressiveGenerator::new();
238 assert!(generator.is_ok());
239 }
240
241 #[test]
242 fn test_progressive_code_generation() {
243 let generator = ProgressiveGenerator::new().unwrap();
244 let server_info = create_mock_server_info();
245
246 let result = generator.generate(&server_info);
247 assert!(result.is_ok());
248
249 let code = result.unwrap();
250 assert!(code.file_count() > 0);
251 }
252}