diff --git a/server/src/agents.rs b/server/src/agents.rs index 29fae06..e448d40 100644 --- a/server/src/agents.rs +++ b/server/src/agents.rs @@ -1275,7 +1275,6 @@ impl AgentPool { } /// Return the port this server is running on. - #[allow(dead_code)] pub fn port(&self) -> u16 { self.port } diff --git a/server/src/http/project.rs b/server/src/http/project.rs index 7cdb77f..f737b82 100644 --- a/server/src/http/project.rs +++ b/server/src/http/project.rs @@ -35,9 +35,14 @@ impl ProjectApi { /// Persists the selected path for later sessions. #[oai(path = "/project", method = "post")] async fn open_project(&self, payload: Json) -> OpenApiResult> { - let confirmed = fs::open_project(payload.0.path, &self.ctx.state, self.ctx.store.as_ref()) - .await - .map_err(bad_request)?; + let confirmed = fs::open_project( + payload.0.path, + &self.ctx.state, + self.ctx.store.as_ref(), + self.ctx.agents.port(), + ) + .await + .map_err(bad_request)?; Ok(Json(confirmed)) } diff --git a/server/src/io/fs.rs b/server/src/io/fs.rs index 5580820..57eab0e 100644 --- a/server/src/io/fs.rs +++ b/server/src/io/fs.rs @@ -1,5 +1,6 @@ use crate::state::SessionState; use crate::store::StoreOps; +use crate::worktree::write_mcp_json as worktree_write_mcp_json; use serde::Serialize; use serde_json::json; use std::fs; @@ -504,12 +505,17 @@ pub async fn open_project( path: String, state: &SessionState, store: &dyn StoreOps, + port: u16, ) -> Result { let p = PathBuf::from(&path); ensure_project_root_with_story_kit(p.clone()).await?; validate_project_path(p.clone()).await?; + // Write .mcp.json so that claude-code can connect to the MCP server. + // Best-effort: failure should not prevent the project from opening. + let _ = worktree_write_mcp_json(&p, port); + { let mut root = state.project_root.lock().map_err(|e| e.to_string())?; *root = Some(p); @@ -751,6 +757,7 @@ mod tests { project_dir.to_string_lossy().to_string(), &state, &store, + 3001, ) .await; @@ -759,6 +766,32 @@ mod tests { assert_eq!(root, project_dir); } + #[tokio::test] + async fn open_project_writes_mcp_json_to_project_root() { + let dir = tempdir().unwrap(); + let project_dir = dir.path().join("myproject"); + fs::create_dir_all(&project_dir).unwrap(); + let store = make_store(&dir); + let state = SessionState::default(); + + open_project( + project_dir.to_string_lossy().to_string(), + &state, + &store, + 4242, + ) + .await + .unwrap(); + + let mcp_path = project_dir.join(".mcp.json"); + assert!(mcp_path.exists(), ".mcp.json should be written to project root"); + let content = fs::read_to_string(&mcp_path).unwrap(); + assert!( + content.contains("http://localhost:4242/mcp"), + ".mcp.json should contain the correct port" + ); + } + #[tokio::test] async fn close_project_clears_root() { let dir = tempdir().unwrap(); @@ -815,6 +848,7 @@ mod tests { project_dir.to_string_lossy().to_string(), &state, &store, + 3001, ) .await .unwrap(); diff --git a/server/src/main.rs b/server/src/main.rs index bfea949..c542fbe 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -37,12 +37,15 @@ async fn main() -> Result<(), std::io::Error> { JsonFileStore::from_path(PathBuf::from("store.json")).map_err(std::io::Error::other)?, ); + let port = resolve_port(); + // Auto-detect a .story_kit/ project in cwd or parent directories. if let Some(project_root) = find_story_kit_root(&cwd) { io::fs::open_project( project_root.to_string_lossy().to_string(), &app_state, store.as_ref(), + port, ) .await .unwrap_or_else(|e| { @@ -59,7 +62,6 @@ async fn main() -> Result<(), std::io::Error> { } let workflow = Arc::new(std::sync::Mutex::new(WorkflowState::default())); - let port = resolve_port(); // Filesystem watcher: broadcast channel for work/ pipeline changes. // Created before AgentPool so the pool can emit AgentStateChanged events.