subagents: add availableToSubagents opt-in toggle for extraMcpServers

This commit is contained in:
damocles 2026-09-13 13:18:27 +02:00 committed by mara
commit 16eec3c314
14 changed files with 356 additions and 84 deletions

View file

@ -0,0 +1,56 @@
//! Builds a subagent's own filtered `--mcp-config`: only
//! `hyperhive.extraMcpServers` entries with `availableToSubagents = true`
//! (see `hive_extra_mcp::ExtraMcpServer::available_to_subagents`) ever reach
//! a subagent's claude invocation. Everything else — the built-in hyperhive
//! surface (todos/messaging), the auto-injected `bash` and `subagent`
//! entries — stays unreachable by construction: none of those default to
//! opted in, and the built-in surface isn't an `extraMcpServers` entry at
//! all, so there's no name for an operator to opt it in under even if they
//! wanted to.
use std::path::PathBuf;
/// Filename the rendered config lives at, under [`crate::paths::harness_dir`].
const CONFIG_FILE: &str = "subagent-mcp-config.json";
/// Render the subagent-eligible extra-MCP servers to a `--mcp-config` file,
/// returning its path — or `None` when no entry opts in (or the render/write
/// fails), so [`hive_claude::Config::mcp_config`] stays unset and the
/// subagent gets literally zero MCP servers, the same default as before this
/// toggle existed. Re-rendered on every call (cheap: a filter plus a small
/// file write) rather than cached once at daemon startup, so a config change
/// takes effect on this subagent's next `start`/`continue` without needing
/// the daemon itself restarted.
#[must_use]
pub fn build() -> Option<PathBuf> {
let state_dir = crate::paths::state_dir();
let servers: serde_json::Map<String, serde_json::Value> = hive_extra_mcp::load_extra_mcp()
.into_iter()
.filter(|(_, spec)| spec.available_to_subagents())
.map(|(name, spec)| (name, spec.to_json_entry(&state_dir)))
.collect();
if servers.is_empty() {
return None;
}
let body = serde_json::to_string_pretty(&serde_json::json!({ "mcpServers": servers }))
.unwrap_or_else(|_| "{}".into());
let dir = crate::paths::harness_dir();
if let Err(e) = std::fs::create_dir_all(&dir) {
tracing::warn!(
error = ?e,
dir = %dir.display(),
"subagent mcp-config: harness dir create failed; subagent gets zero extra MCP servers this turn",
);
return None;
}
let path = dir.join(CONFIG_FILE);
if let Err(e) = std::fs::write(&path, body) {
tracing::warn!(
error = ?e,
path = %path.display(),
"subagent mcp-config: write failed; subagent gets zero extra MCP servers this turn",
);
return None;
}
Some(path)
}