From 9fd3cceec182edb6329925401d102b6427f286c8 Mon Sep 17 00:00:00 2001 From: Rahul Tyagi Date: Fri, 10 Jul 2026 22:03:58 +0530 Subject: [PATCH 1/2] fix(crew): auto-trust project MCP so the seeded first message sends Adding .mcp.json means every fresh Claude session in a new crew worktree hits the one-time 'trust MCP servers?' prompt. The blind seed-typing in pty.rs then types the task into that dialog instead of the input box, so the first message is never sent. Launch/resume claude with --settings '{"enableAllProjectMcpServers":true}' to auto-enable the project's .mcp.json servers, so no prompt appears and the seed reaches the REPL. --- tui/src/app.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/tui/src/app.rs b/tui/src/app.rs index 2e3c0c9..abf8e46 100644 --- a/tui/src/app.rs +++ b/tui/src/app.rs @@ -1273,13 +1273,20 @@ pub fn interactive_argv(harness: &str, model: &str) -> Vec { } } match harness { - "claude" => vec!["claude".into(), "--model".into(), model.into()], + // auto-trust this repo's .mcp.json so the "trust MCP servers?" prompt + // doesn't intercept the seeded first message in a fresh worktree. + "claude" => vec!["claude".into(), "--model".into(), model.into(), + "--settings".into(), CLAUDE_MCP_SETTINGS.into()], "opencode" => vec!["opencode".into()], "cursor" => vec!["cursor-agent".into(), "--model".into(), model.into()], _ => vec!["claude".into()], } } +/// Inline settings that auto-enable project (.mcp.json) MCP servers so Claude +/// Code never shows the one-time trust prompt (which would swallow our seed). +const CLAUDE_MCP_SETTINGS: &str = "{\"enableAllProjectMcpServers\":true}"; + /// argv to RESUME a harness in an existing worktree (harness restores the prior /// session). Overridable via `NEXUM_RESUME_CMD_`. pub fn resume_argv(harness: &str, model: &str) -> Vec { @@ -1289,7 +1296,8 @@ pub fn resume_argv(harness: &str, model: &str) -> Vec { } } match harness { - "claude" => vec!["claude".into(), "--continue".into(), "--model".into(), model.into()], + "claude" => vec!["claude".into(), "--continue".into(), "--model".into(), model.into(), + "--settings".into(), CLAUDE_MCP_SETTINGS.into()], "opencode" => vec!["opencode".into(), "--continue".into()], "cursor" => vec!["cursor-agent".into(), "--resume".into()], _ => interactive_argv(harness, model), @@ -1685,6 +1693,14 @@ mod tests { let _ = std::fs::remove_dir_all(&dir); } + #[test] + fn claude_mcp_settings_auto_trusts_project_servers() { + // valid JSON with the exact key Claude Code reads (wired into the claude + // launch/resume argv so the trust prompt never swallows the seed). + let v: serde_json::Value = serde_json::from_str(CLAUDE_MCP_SETTINGS).unwrap(); + assert_eq!(v["enableAllProjectMcpServers"], serde_json::json!(true)); + } + #[test] fn on_path_detects_binaries() { assert!(on_path("sh"), "sh should be on PATH"); From 9c0faed7ea70df46e101713d38ebf345bfc627ff Mon Sep 17 00:00:00 2001 From: Rahul Tyagi Date: Fri, 10 Jul 2026 23:22:48 +0530 Subject: [PATCH 2/2] fix(crew): load delegate MCP explicitly (--mcp-config --strict) to kill the trust prompt The --settings enableAllProjectMcpServers approach did not reliably suppress the 'found new MCP in project nexum-delegate' trust prompt, which still swallowed the seeded first message. Instead pass the repo's .mcp.json to claude via --mcp-config --strict-mcp-config: a CLI-provided server is trusted (no discovery prompt) and strict mode ignores auto-discovered configs. When the repo has no .mcp.json, no MCP flags are added (nothing is discovered, so no prompt). Verified the flags load headlessly (exit 0). --- tui/src/app.rs | 67 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/tui/src/app.rs b/tui/src/app.rs index abf8e46..39f6427 100644 --- a/tui/src/app.rs +++ b/tui/src/app.rs @@ -887,6 +887,13 @@ impl App { Ok(id) } + /// Absolute path to this repo's `.mcp.json` if present — passed to claude so + /// its MCP servers load trusted (no discovery prompt). + fn mcp_config_path(&self) -> Option { + let p = self.store.repo_root.join(".mcp.json"); + if p.is_file() { Some(p.to_string_lossy().into_owned()) } else { None } + } + /// Launch an interactive PTY agent from the form. Task is required. pub fn spawn_from_form(&mut self) { if self.new_form.task_text().trim().is_empty() { @@ -925,7 +932,8 @@ impl App { ) }; - let argv = interactive_argv(&harness, &model); + let mcp = self.mcp_config_path(); + let argv = interactive_argv(&harness, &model, mcp.as_deref()); let task_text = self.new_form.task_text(); match AgentProc::spawn( id.clone(), harness.clone(), model.clone(), prompt, worktree.clone(), &argv, rows, cols, @@ -973,7 +981,8 @@ impl App { fn resume(&mut self, id: &str) { let Some(rec) = self.registry.iter().find(|r| r.id == id).cloned() else { return }; let (cols, rows) = self.term_size; - let argv = resume_argv(&rec.harness, &rec.model); + let mcp = self.mcp_config_path(); + let argv = resume_argv(&rec.harness, &rec.model, mcp.as_deref()); // no task seed on resume — the harness restores the prior conversation match AgentProc::spawn( rec.id.clone(), rec.harness.clone(), rec.model.clone(), String::new(), @@ -1264,43 +1273,56 @@ fn merge_keep(mut rows: Vec) -> Vec { rows } -/// argv for a harness's INTERACTIVE REPL. Overridable via -/// `NEXUM_INTERACTIVE_CMD_` (whitespace-split) so tests inject a stub. -pub fn interactive_argv(harness: &str, model: &str) -> Vec { +/// Claude MCP flags: load the delegation server explicitly from `mcp` and +/// ignore auto-discovered project/user configs. Passing the server on the CLI +/// marks it trusted, so the "found new MCP in project" prompt never appears (it +/// would otherwise swallow our seeded first message). Empty when there's no +/// `.mcp.json` — then nothing is discovered, so no prompt either. +fn claude_mcp_flags(mcp: Option<&str>) -> Vec { + match mcp { + Some(path) => vec!["--mcp-config".into(), path.into(), "--strict-mcp-config".into()], + None => Vec::new(), + } +} + +/// argv for a harness's INTERACTIVE REPL. `mcp` = path to a `.mcp.json` to load +/// explicitly (claude). Overridable via `NEXUM_INTERACTIVE_CMD_` +/// (whitespace-split) so tests inject a stub. +pub fn interactive_argv(harness: &str, model: &str, mcp: Option<&str>) -> Vec { if let Ok(over) = std::env::var(format!("NEXUM_INTERACTIVE_CMD_{}", harness.to_uppercase())) { if !over.trim().is_empty() { return over.split_whitespace().map(|s| s.to_string()).collect(); } } match harness { - // auto-trust this repo's .mcp.json so the "trust MCP servers?" prompt - // doesn't intercept the seeded first message in a fresh worktree. - "claude" => vec!["claude".into(), "--model".into(), model.into(), - "--settings".into(), CLAUDE_MCP_SETTINGS.into()], + "claude" => { + let mut v = vec!["claude".into(), "--model".into(), model.into()]; + v.extend(claude_mcp_flags(mcp)); + v + } "opencode" => vec!["opencode".into()], "cursor" => vec!["cursor-agent".into(), "--model".into(), model.into()], _ => vec!["claude".into()], } } -/// Inline settings that auto-enable project (.mcp.json) MCP servers so Claude -/// Code never shows the one-time trust prompt (which would swallow our seed). -const CLAUDE_MCP_SETTINGS: &str = "{\"enableAllProjectMcpServers\":true}"; - /// argv to RESUME a harness in an existing worktree (harness restores the prior /// session). Overridable via `NEXUM_RESUME_CMD_`. -pub fn resume_argv(harness: &str, model: &str) -> Vec { +pub fn resume_argv(harness: &str, model: &str, mcp: Option<&str>) -> Vec { if let Ok(over) = std::env::var(format!("NEXUM_RESUME_CMD_{}", harness.to_uppercase())) { if !over.trim().is_empty() { return over.split_whitespace().map(|s| s.to_string()).collect(); } } match harness { - "claude" => vec!["claude".into(), "--continue".into(), "--model".into(), model.into(), - "--settings".into(), CLAUDE_MCP_SETTINGS.into()], + "claude" => { + let mut v = vec!["claude".into(), "--continue".into(), "--model".into(), model.into()]; + v.extend(claude_mcp_flags(mcp)); + v + } "opencode" => vec!["opencode".into(), "--continue".into()], "cursor" => vec!["cursor-agent".into(), "--resume".into()], - _ => interactive_argv(harness, model), + _ => interactive_argv(harness, model, mcp), } } @@ -1694,11 +1716,12 @@ mod tests { } #[test] - fn claude_mcp_settings_auto_trusts_project_servers() { - // valid JSON with the exact key Claude Code reads (wired into the claude - // launch/resume argv so the trust prompt never swallows the seed). - let v: serde_json::Value = serde_json::from_str(CLAUDE_MCP_SETTINGS).unwrap(); - assert_eq!(v["enableAllProjectMcpServers"], serde_json::json!(true)); + fn claude_mcp_flags_load_explicitly_and_strict() { + // with a .mcp.json → pass it on the CLI (trusted, no prompt) + strict + let f = claude_mcp_flags(Some("/repo/.mcp.json")); + assert_eq!(f, vec!["--mcp-config", "/repo/.mcp.json", "--strict-mcp-config"]); + // without one → no MCP flags (nothing discovered, no prompt) + assert!(claude_mcp_flags(None).is_empty()); } #[test]