story-kit: merge 208_bug_project_scaffold_does_not_write_mcp_json_to_project_root

This commit is contained in:
Dave
2026-02-26 14:57:14 +00:00
parent 0be7bcd8ae
commit 17fd3b2dc2
4 changed files with 45 additions and 5 deletions

View File

@@ -1275,7 +1275,6 @@ impl AgentPool {
} }
/// Return the port this server is running on. /// Return the port this server is running on.
#[allow(dead_code)]
pub fn port(&self) -> u16 { pub fn port(&self) -> u16 {
self.port self.port
} }

View File

@@ -35,9 +35,14 @@ impl ProjectApi {
/// Persists the selected path for later sessions. /// Persists the selected path for later sessions.
#[oai(path = "/project", method = "post")] #[oai(path = "/project", method = "post")]
async fn open_project(&self, payload: Json<PathPayload>) -> OpenApiResult<Json<String>> { async fn open_project(&self, payload: Json<PathPayload>) -> OpenApiResult<Json<String>> {
let confirmed = fs::open_project(payload.0.path, &self.ctx.state, self.ctx.store.as_ref()) let confirmed = fs::open_project(
.await payload.0.path,
.map_err(bad_request)?; &self.ctx.state,
self.ctx.store.as_ref(),
self.ctx.agents.port(),
)
.await
.map_err(bad_request)?;
Ok(Json(confirmed)) Ok(Json(confirmed))
} }

View File

@@ -1,5 +1,6 @@
use crate::state::SessionState; use crate::state::SessionState;
use crate::store::StoreOps; use crate::store::StoreOps;
use crate::worktree::write_mcp_json as worktree_write_mcp_json;
use serde::Serialize; use serde::Serialize;
use serde_json::json; use serde_json::json;
use std::fs; use std::fs;
@@ -504,12 +505,17 @@ pub async fn open_project(
path: String, path: String,
state: &SessionState, state: &SessionState,
store: &dyn StoreOps, store: &dyn StoreOps,
port: u16,
) -> Result<String, String> { ) -> Result<String, String> {
let p = PathBuf::from(&path); let p = PathBuf::from(&path);
ensure_project_root_with_story_kit(p.clone()).await?; ensure_project_root_with_story_kit(p.clone()).await?;
validate_project_path(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())?; let mut root = state.project_root.lock().map_err(|e| e.to_string())?;
*root = Some(p); *root = Some(p);
@@ -751,6 +757,7 @@ mod tests {
project_dir.to_string_lossy().to_string(), project_dir.to_string_lossy().to_string(),
&state, &state,
&store, &store,
3001,
) )
.await; .await;
@@ -759,6 +766,32 @@ mod tests {
assert_eq!(root, project_dir); 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] #[tokio::test]
async fn close_project_clears_root() { async fn close_project_clears_root() {
let dir = tempdir().unwrap(); let dir = tempdir().unwrap();
@@ -815,6 +848,7 @@ mod tests {
project_dir.to_string_lossy().to_string(), project_dir.to_string_lossy().to_string(),
&state, &state,
&store, &store,
3001,
) )
.await .await
.unwrap(); .unwrap();

View File

@@ -37,12 +37,15 @@ async fn main() -> Result<(), std::io::Error> {
JsonFileStore::from_path(PathBuf::from("store.json")).map_err(std::io::Error::other)?, 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. // Auto-detect a .story_kit/ project in cwd or parent directories.
if let Some(project_root) = find_story_kit_root(&cwd) { if let Some(project_root) = find_story_kit_root(&cwd) {
io::fs::open_project( io::fs::open_project(
project_root.to_string_lossy().to_string(), project_root.to_string_lossy().to_string(),
&app_state, &app_state,
store.as_ref(), store.as_ref(),
port,
) )
.await .await
.unwrap_or_else(|e| { .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 workflow = Arc::new(std::sync::Mutex::new(WorkflowState::default()));
let port = resolve_port();
// Filesystem watcher: broadcast channel for work/ pipeline changes. // Filesystem watcher: broadcast channel for work/ pipeline changes.
// Created before AgentPool so the pool can emit AgentStateChanged events. // Created before AgentPool so the pool can emit AgentStateChanged events.