spec_kit_mcp/tools/
init.rs1use anyhow::{Context, Result};
6use async_trait::async_trait;
7use serde::{Deserialize, Serialize};
8use serde_json::{json, Value};
9use std::path::PathBuf;
10
11use crate::mcp::types::{ContentBlock, ToolDefinition, ToolResult};
12use crate::speckit::SpecKitCli;
13use crate::tools::Tool;
14
15#[derive(Debug, Deserialize, Serialize)]
17pub struct InitParams {
18 project_name: String,
20
21 #[serde(default = "default_project_path")]
23 project_path: PathBuf,
24}
25
26fn default_project_path() -> PathBuf {
27 PathBuf::from(".")
28}
29
30pub struct InitTool {
32 cli: SpecKitCli,
33}
34
35impl InitTool {
36 pub fn new(cli: SpecKitCli) -> Self {
38 Self { cli }
39 }
40}
41
42#[async_trait]
43impl Tool for InitTool {
44 fn definition(&self) -> ToolDefinition {
45 ToolDefinition {
46 name: "speckit_init".to_string(),
47 description: "Initialize a new spec-kit project with proper directory structure and configuration files".to_string(),
48 input_schema: json!({
49 "type": "object",
50 "properties": {
51 "project_name": {
52 "type": "string",
53 "description": "Name of the project to initialize"
54 },
55 "project_path": {
56 "type": "string",
57 "description": "Path where the project should be created",
58 "default": "."
59 }
60 },
61 "required": ["project_name"]
62 })
63 }
64 }
65
66 async fn execute(&self, params: Value) -> Result<ToolResult> {
67 let params: InitParams =
68 serde_json::from_value(params).context("Failed to parse init parameters")?;
69
70 tracing::info!(
71 project_name = %params.project_name,
72 project_path = %params.project_path.display(),
73 "Initializing spec-kit project"
74 );
75
76 let result = self
78 .cli
79 .init(¶ms.project_name, ¶ms.project_path)
80 .await?;
81
82 if !result.is_success() {
83 return Ok(ToolResult {
84 content: vec![ContentBlock::text(format!(
85 "Failed to initialize project: {}",
86 result.stderr
87 ))],
88 is_error: Some(true),
89 });
90 }
91
92 let message = format!(
93 "Successfully initialized spec-kit project '{}' at {}\n\n\
94 Next steps:\n\
95 1. Navigate to the project: cd {}\n\
96 2. Create constitution: Use speckit_constitution tool\n\
97 3. Define requirements: Use speckit_specify tool\n\
98 4. Create technical plan: Use speckit_plan tool\n\
99 5. Generate tasks: Use speckit_tasks tool\n\
100 6. Implement: Use speckit_implement tool",
101 params.project_name,
102 params.project_path.display(),
103 params.project_path.display()
104 );
105
106 Ok(ToolResult {
107 content: vec![ContentBlock::text(message)],
108 is_error: None,
109 })
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116 use tempfile::tempdir;
117
118 #[tokio::test]
119 async fn test_init_tool_definition() {
120 let cli = SpecKitCli::new();
121 let tool = InitTool::new(cli);
122 let def = tool.definition();
123
124 assert_eq!(def.name, "speckit_init");
125 assert!(!def.description.is_empty());
126 }
127
128 #[tokio::test]
129 async fn test_init_tool_execute() {
130 let cli = SpecKitCli::new_test_mode();
131 let tool = InitTool::new(cli);
132
133 let dir = tempdir().unwrap();
134 let params = json!({
135 "project_name": "test-project",
136 "project_path": dir.path().to_str().unwrap()
137 });
138
139 let result = tool.execute(params).await.unwrap();
140 assert!(result.is_error.is_none() || !result.is_error.unwrap());
141 }
142}