foundry_mcp/core/ops/
update_spec.rs1use anyhow::Result;
4
5use crate::core::foundry;
6use crate::types::edit_commands::EditCommand;
7use crate::types::responses::{EditCommandsResponsePayload, FoundryResponse, ValidationStatus};
8
9#[derive(Debug, Clone)]
10pub struct Input {
11 pub project_name: String,
12 pub spec_name: String,
13 pub commands_json: String,
14}
15
16pub async fn run(input: Input) -> Result<FoundryResponse<EditCommandsResponsePayload>> {
17 let foundry = foundry::get_default_foundry()?;
18
19 validate_args(&input)?;
20 validate_project_exists(&foundry, &input.project_name).await?;
21
22 foundry.load_spec(&input.project_name, &input.spec_name).await.map_err(|_| {
24 anyhow::anyhow!(
25 "Spec '{}' not found in project '{}'. Use load_project tool to see available specs: {{\"name\": \"load_project\", \"arguments\": {{\"project_name\": \"{}\"}}}}",
26 input.spec_name,
27 input.project_name,
28 input.project_name
29 )
30 })?;
31
32 let commands: Vec<EditCommand> = serde_json::from_str(&input.commands_json)
33 .map_err(|e| anyhow::anyhow!("Invalid commands JSON: {}", e))?;
34
35 let result = foundry
36 .apply_edit_commands(&input.project_name, &input.spec_name, &commands)
37 .await?;
38
39 let response_data = EditCommandsResponsePayload {
40 applied_count: result.applied_count,
41 skipped_idempotent_count: result.skipped_idempotent_count,
42 file_updates: result.file_updates,
43 errors: if result.errors.is_empty() {
44 None
45 } else {
46 Some(result.errors)
47 },
48 preview_diff: result.preview_diff,
49 };
50
51 Ok(FoundryResponse {
52 data: response_data,
53 next_steps: result.next_steps,
54 validation_status: ValidationStatus::Complete,
55 workflow_hints: result.workflow_hints,
56 })
57}
58
59fn validate_args(input: &Input) -> Result<()> {
60 if input.project_name.trim().is_empty() {
61 return Err(anyhow::anyhow!("Project name cannot be empty"));
62 }
63 if input.spec_name.trim().is_empty() {
64 return Err(anyhow::anyhow!("Spec name cannot be empty"));
65 }
66 if input.commands_json.trim().is_empty() {
67 return Err(anyhow::anyhow!("'commands' parameter is required"));
68 }
69 Ok(())
70}
71
72async fn validate_project_exists(
73 foundry: &foundry::Foundry<crate::core::backends::filesystem::FilesystemBackend>,
74 project_name: &str,
75) -> Result<()> {
76 if !foundry.project_exists(project_name).await? {
77 return Err(anyhow::anyhow!(
78 "Project '{}' not found. Use list_projects tool to see available projects: {{\"name\": \"list_projects\", \"arguments\": {{}}}}",
79 project_name
80 ));
81 }
82 Ok(())
83}