Accept story 40: MCP Server Obeys STORYKIT_PORT
Agent worktrees now get a .mcp.json written with the correct port from the running server. AgentPool receives the port at construction and passes it through to create_worktree, which writes .mcp.json on both new creation and reuse. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,15 @@ use crate::config::ProjectConfig;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
/// Write a `.mcp.json` file in the given directory pointing to the MCP server
|
||||
/// at the given port.
|
||||
pub fn write_mcp_json(dir: &Path, port: u16) -> Result<(), String> {
|
||||
let content = format!(
|
||||
"{{\n \"mcpServers\": {{\n \"story-kit\": {{\n \"type\": \"http\",\n \"url\": \"http://localhost:{port}/mcp\"\n }}\n }}\n}}\n"
|
||||
);
|
||||
std::fs::write(dir.join(".mcp.json"), content).map_err(|e| format!("Write .mcp.json: {e}"))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub struct WorktreeInfo {
|
||||
@@ -46,12 +55,14 @@ fn detect_base_branch(project_root: &Path) -> String {
|
||||
///
|
||||
/// - Creates the worktree at `{project_root}-story-{story_id}` (sibling directory)
|
||||
/// on branch `feature/story-{story_id}`.
|
||||
/// - Writes `.mcp.json` in the worktree pointing to the MCP server at `port`.
|
||||
/// - Runs setup commands from the config for each component.
|
||||
/// - If the worktree/branch already exists, reuses rather than errors.
|
||||
pub async fn create_worktree(
|
||||
project_root: &Path,
|
||||
story_id: &str,
|
||||
config: &ProjectConfig,
|
||||
port: u16,
|
||||
) -> Result<WorktreeInfo, String> {
|
||||
let wt_path = worktree_path(project_root, story_id);
|
||||
let branch = branch_name(story_id);
|
||||
@@ -60,6 +71,7 @@ pub async fn create_worktree(
|
||||
|
||||
// Already exists — reuse
|
||||
if wt_path.exists() {
|
||||
write_mcp_json(&wt_path, port)?;
|
||||
run_setup_commands(&wt_path, config).await?;
|
||||
return Ok(WorktreeInfo {
|
||||
path: wt_path,
|
||||
@@ -75,6 +87,7 @@ pub async fn create_worktree(
|
||||
.await
|
||||
.map_err(|e| format!("spawn_blocking: {e}"))??;
|
||||
|
||||
write_mcp_json(&wt_path, port)?;
|
||||
run_setup_commands(&wt_path, config).await?;
|
||||
|
||||
Ok(WorktreeInfo {
|
||||
@@ -205,6 +218,28 @@ async fn run_teardown_commands(wt_path: &Path, config: &ProjectConfig) -> Result
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_shell_command(cmd: &str, cwd: &Path) -> Result<(), String> {
|
||||
let cmd = cmd.to_string();
|
||||
let cwd = cwd.to_path_buf();
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
eprintln!("[worktree] Running: {cmd} in {}", cwd.display());
|
||||
let output = Command::new("sh")
|
||||
.args(["-c", &cmd])
|
||||
.current_dir(&cwd)
|
||||
.output()
|
||||
.map_err(|e| format!("Run '{cmd}': {e}"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(format!("Command '{cmd}' failed: {stderr}"));
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("spawn_blocking: {e}"))?
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -225,6 +260,22 @@ mod tests {
|
||||
.expect("git commit");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_mcp_json_uses_given_port() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
write_mcp_json(tmp.path(), 4242).unwrap();
|
||||
let content = std::fs::read_to_string(tmp.path().join(".mcp.json")).unwrap();
|
||||
assert!(content.contains("http://localhost:4242/mcp"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_mcp_json_default_port() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
write_mcp_json(tmp.path(), 3001).unwrap();
|
||||
let content = std::fs::read_to_string(tmp.path().join(".mcp.json")).unwrap();
|
||||
assert!(content.contains("http://localhost:3001/mcp"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_worktree_after_stale_reference() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
@@ -255,25 +306,3 @@ mod tests {
|
||||
assert!(wt_path.exists());
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_shell_command(cmd: &str, cwd: &Path) -> Result<(), String> {
|
||||
let cmd = cmd.to_string();
|
||||
let cwd = cwd.to_path_buf();
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
eprintln!("[worktree] Running: {cmd} in {}", cwd.display());
|
||||
let output = Command::new("sh")
|
||||
.args(["-c", &cmd])
|
||||
.current_dir(&cwd)
|
||||
.output()
|
||||
.map_err(|e| format!("Run '{cmd}': {e}"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(format!("Command '{cmd}' failed: {stderr}"));
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("spawn_blocking: {e}"))?
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user