refactor(#2455): split hive-agent-mcp into its own bin crate
This commit is contained in:
parent
59dd3a0aa7
commit
e7f8254788
15 changed files with 334 additions and 100 deletions
58
hive-agent-mcp/src/send_allow.rs
Normal file
58
hive-agent-mcp/src/send_allow.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
//! Per-agent `send` allow-list enforcement. Driven by the operator config at
|
||||
//! `/etc/hyperhive/send-allow.json` (written by the NixOS harness module); the
|
||||
//! MCP server calls [`check_send_allowed`] before forwarding a `send` tool call
|
||||
//! so a blocked recipient surfaces as a claude-readable tool result rather than
|
||||
//! a silent drop.
|
||||
|
||||
/// Where the NixOS module writes the per-agent send allow-list (see
|
||||
/// `nix/templates/harness/`). Empty list = unrestricted (the
|
||||
/// default). Non-empty list constrains `mcp__hyperhive__send`'s `to`
|
||||
/// field; the manager is always implicitly permitted regardless of
|
||||
/// the list contents.
|
||||
const SEND_ALLOW_PATH: &str = "/etc/hyperhive/send-allow.json";
|
||||
|
||||
/// Enforce the per-agent send allow-list. Returns `Ok` when the
|
||||
/// recipient is permitted (no list configured, `<parent>` sentinel
|
||||
/// always allowed, or `to` is in the list); returns `Err(refusal)`
|
||||
/// with a claude-readable string when blocked — the harness surfaces
|
||||
/// the refusal as the tool result so claude knows the message didn't
|
||||
/// land and can react (e.g. route via `<parent>` instead).
|
||||
pub fn check_send_allowed(to: &str) -> Result<(), String> {
|
||||
if to == hive_sh4re::PARENT_RECIPIENT {
|
||||
// Always allow `<parent>` — the allow-list constrains peer
|
||||
// chatter, not the structural reporting line; the operator
|
||||
// can rewire who the parent IS via `set_parent` without
|
||||
// having to remember to update the per-agent allow-list.
|
||||
// The broker resolves the sentinel to the real parent label
|
||||
// on the host side per topology.json (falls back to `operator`
|
||||
// for root agents).
|
||||
return Ok(());
|
||||
}
|
||||
let Ok(raw) = std::fs::read_to_string(SEND_ALLOW_PATH) else {
|
||||
return Ok(()); // file missing → no policy configured → unrestricted
|
||||
};
|
||||
let allow: Vec<String> = match serde_json::from_str(&raw) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
path = SEND_ALLOW_PATH,
|
||||
error = ?e,
|
||||
"send allow-list parse failed; falling back to unrestricted",
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
if allow.is_empty() {
|
||||
return Ok(()); // empty list = unrestricted (back-compat)
|
||||
}
|
||||
if allow.iter().any(|n| n == to) {
|
||||
return Ok(());
|
||||
}
|
||||
Err(format!(
|
||||
"send refused: recipient '{to}' not in hyperhive.allowedRecipients \
|
||||
(configured in agent.nix). Allowed: {allow:?}. Your structural \
|
||||
parent is always reachable — route through `send(to: \"{}\", …)` \
|
||||
if you need to reach someone outside the allow-list.",
|
||||
hive_sh4re::PARENT_RECIPIENT
|
||||
))
|
||||
}
|
||||
Loading…
Reference in a new issue