per-agent extra MCP servers via hyperhive.extraMcpServers
new NixOS option in harness-base.nix:
hyperhive.extraMcpServers.<key> = {
command = "/path/to/server";
args = [ ... ];
env = { KEY = "value"; };
allowedTools = [ "send_message" "join_room" ]; # or ["*"]
};
declared as attrsOf submodule so agents stack arbitrarily many.
the module writes the whole map as JSON to
/etc/hyperhive/extra-mcp.json at activation; the harness reads
that file in mcp::render_claude_config and merges each entry
into the rendered --mcp-config under its own mcpServers.<key>
block. allowed_mcp_tools(flavor) extends the --allowedTools
arg with mcp__<key>__<pattern> for every entry — "*" (the
default) becomes mcp__<key>__* so every tool from that server
is auto-approved, or pass a concrete list to tighten.
collision guard: an extra server keyed "hyperhive" is dropped
with a warn-log so user config can't shadow the built-in
surface. malformed JSON / missing file fall back to "no
extras" silently.
prompt note added: agents see "(some agents only) extra MCP
tools surfaced as mcp__<server>__<tool>" and learn they're
declared via agent.nix. retires the matching TODO under
Per-agent extension. matrix-chat agents + bitburner-agent
migration unblocked.
This commit is contained in:
parent
50ef806266
commit
7d6d8e96c1
4 changed files with 142 additions and 27 deletions
|
|
@ -4,6 +4,7 @@ Tools (hyperhive surface):
|
|||
|
||||
- `mcp__hyperhive__recv(wait_seconds?)` — drain one more message from your inbox (returns `(empty)` if nothing pending after the wait). Without `wait_seconds` it long-polls 30s. To **wait** for work when you have nothing else useful to do this turn, call with a long wait (e.g. `wait_seconds: 180`, the max) — you'll be woken instantly when a message arrives, otherwise return after the timeout. That is strictly better than calling `recv` repeatedly with short waits: lower latency on new work, fewer turns, no busy-loop. Never use a fixed `sleep` shell command for the same purpose.
|
||||
- `mcp__hyperhive__send(to, body)` — message a peer (by their name) or the operator (recipient `operator`, surfaces in the dashboard).
|
||||
- (some agents only) **extra MCP tools** surfaced as `mcp__<server>__<tool>` — these are agent-specific (matrix client, scraper, db connector, etc.) declared in your `agent.nix` under `hyperhive.extraMcpServers`. Treat them as first-class tools alongside the hyperhive surface; the operator already auto-approved them at deploy time.
|
||||
- `mcp__hyperhive__ask_operator(question, options?, multi?, ttl_seconds?)` — surface a question to the human operator on the dashboard. Returns immediately with a question id — do NOT wait inline. When the operator answers, a system message with event `operator_answered { id, question, answer }` lands in your inbox; handle it on a future turn. Use this for clarifications, permission for risky actions, or choice between options. `options` is advisory: a short fixed-choice list when applicable, otherwise leave empty for free text. `multi: true` lets the operator pick multiple (checkboxes), answer comes back comma-joined. `ttl_seconds` auto-cancels with answer `[expired]` when the decision becomes moot.
|
||||
|
||||
Need new packages, env vars, or other NixOS config for yourself? You can't edit your own config directly — message the manager (recipient `manager`) describing what you need + why. The manager evaluates the request (it doesn't rubber-stamp), edits `/agents/{label}/config/agent.nix` on your behalf, commits, and submits an approval that the operator can accept on the dashboard; on approve hive-c0re rebuilds your container with the new config.
|
||||
|
|
|
|||
|
|
@ -580,10 +580,24 @@ pub fn allowed_mcp_tools(flavor: Flavor) -> Vec<String> {
|
|||
"ask_operator",
|
||||
],
|
||||
};
|
||||
names
|
||||
let mut out: Vec<String> = names
|
||||
.iter()
|
||||
.map(|t| format!("mcp__{SERVER_NAME}__{t}"))
|
||||
.collect()
|
||||
.collect();
|
||||
// Extra MCP servers declared via `hyperhive.extraMcpServers` in
|
||||
// the agent's NixOS config. Each entry maps its `allowedTools`
|
||||
// pattern list to `mcp__<server>__<pattern>` so claude can call
|
||||
// them without per-tool operator approval. `["*"]` (the default)
|
||||
// expands to `mcp__<server>__*` — every tool from that server.
|
||||
for (server, spec) in load_extra_mcp() {
|
||||
if server == SERVER_NAME {
|
||||
continue;
|
||||
}
|
||||
for pat in spec.allowed_tools {
|
||||
out.push(format!("mcp__{server}__{pat}"));
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Combined allow-list passed to `--allowedTools` (auto-approve) — covers
|
||||
|
|
@ -605,20 +619,78 @@ pub fn builtin_tools_arg() -> String {
|
|||
ALLOWED_BUILTIN_TOOLS.join(",")
|
||||
}
|
||||
|
||||
/// Where the NixOS module writes the per-agent extra-MCP spec (see
|
||||
/// `nix/templates/harness-base.nix`). Each entry becomes an additional
|
||||
/// `mcpServers.<key>` block in the rendered claude config + a
|
||||
/// `mcp__<key>__<tool>` pattern in `--allowedTools`.
|
||||
const EXTRA_MCP_PATH: &str = "/etc/hyperhive/extra-mcp.json";
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct ExtraMcpServer {
|
||||
command: String,
|
||||
#[serde(default)]
|
||||
args: Vec<String>,
|
||||
#[serde(default)]
|
||||
env: std::collections::BTreeMap<String, String>,
|
||||
#[serde(default = "default_allowed_tools")]
|
||||
#[serde(rename = "allowedTools")]
|
||||
allowed_tools: Vec<String>,
|
||||
}
|
||||
|
||||
fn default_allowed_tools() -> Vec<String> {
|
||||
vec!["*".to_owned()]
|
||||
}
|
||||
|
||||
/// Read + parse the extra-MCP spec. Returns an empty map on missing /
|
||||
/// unparsable file (the agent has none configured, or the file is
|
||||
/// malformed — both cases degrade to "no extra servers").
|
||||
fn load_extra_mcp() -> std::collections::BTreeMap<String, ExtraMcpServer> {
|
||||
let Ok(raw) = std::fs::read_to_string(EXTRA_MCP_PATH) else {
|
||||
return std::collections::BTreeMap::new();
|
||||
};
|
||||
serde_json::from_str(&raw).unwrap_or_else(|e| {
|
||||
tracing::warn!(
|
||||
path = EXTRA_MCP_PATH,
|
||||
error = ?e,
|
||||
"extra-mcp spec parse failed; ignoring",
|
||||
);
|
||||
std::collections::BTreeMap::new()
|
||||
})
|
||||
}
|
||||
|
||||
/// Render the MCP config blob claude reads from `--mcp-config <path>`.
|
||||
/// `agent_binary` is the path (or PATH-resolvable name) of the `hive-ag3nt`
|
||||
/// executable; `socket` is the hyperhive per-agent socket bind-mounted into
|
||||
/// the container (forwarded to the child as `--socket <path>`).
|
||||
/// the container (forwarded to the child as `--socket <path>`). Merges in
|
||||
/// any extra MCP servers declared via `hyperhive.extraMcpServers` in the
|
||||
/// agent's NixOS config.
|
||||
#[must_use]
|
||||
pub fn render_claude_config(agent_binary: &str, socket: &std::path::Path) -> String {
|
||||
let config = serde_json::json!({
|
||||
"mcpServers": {
|
||||
SERVER_NAME: {
|
||||
"command": agent_binary,
|
||||
"args": ["--socket", socket.display().to_string(), "mcp"],
|
||||
"env": {}
|
||||
}
|
||||
let mut servers = serde_json::Map::new();
|
||||
servers.insert(
|
||||
SERVER_NAME.to_owned(),
|
||||
serde_json::json!({
|
||||
"command": agent_binary,
|
||||
"args": ["--socket", socket.display().to_string(), "mcp"],
|
||||
"env": {}
|
||||
}),
|
||||
);
|
||||
for (name, spec) in load_extra_mcp() {
|
||||
if name == SERVER_NAME {
|
||||
tracing::warn!(
|
||||
"extra MCP server name `{SERVER_NAME}` collides with the built-in surface; ignoring",
|
||||
);
|
||||
continue;
|
||||
}
|
||||
});
|
||||
servers.insert(
|
||||
name,
|
||||
serde_json::json!({
|
||||
"command": spec.command,
|
||||
"args": spec.args,
|
||||
"env": spec.env,
|
||||
}),
|
||||
);
|
||||
}
|
||||
let config = serde_json::json!({ "mcpServers": servers });
|
||||
serde_json::to_string_pretty(&config).unwrap_or_else(|_| "{}".into())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue