remove the get_host_journal MCP tool and its capability

swarm-logs covers every host-tier unit this tool could reach, so the
second, capability-gated path into host journald earns nothing and is
removed outright rather than disabled behind a flag.

Removed end to end: the MCP tool definition + handler, the
GetHostJournal/HostJournal wire variants, hive-c0re's
dispatch_host_journal handler, the ReadHostJournal capability, and the
harness-side capability->--allowedTools gate. get_host_journal was the
only capability that mapped to an MCP tool, so allowed_capability_tools
could only ever return an empty vec; it goes too rather than linger as a
function that provably does nothing.

capabilities::has_cap/caps_for stay: #4624 gave ManageRootAgent's
bind-mount enforcement (hive-c0re/src/lifecycle/host_config.rs) a
second caller of has_cap, so they're no longer callerless once this
lands on top of it.

hive-sh4re's journal module (JournalPriority) had no consumer outside
this tool and is deleted.

An existing capabilities.json still naming read_host_journal does not
error: capabilities::prune_unknown drops unrecognised names with a
warn!, and an agent left with no capabilities has its entry removed. No
migration step is needed.

Untouched: hive-c0re/src/dashboard/journal.rs's
read_host_journal_response, which matches the name but is the private
helper behind the operator-only GET /api/journal-host dashboard route
and carries no capability check.
This commit is contained in:
atlas 2026-09-21 19:20:05 +02:00
commit 11097ed336
18 changed files with 21 additions and 401 deletions

View file

@ -30,37 +30,6 @@ pub const DEFAULT_MCP_HTTP_PORT: u16 = 8790;
/// drift.
pub use hive_sh4re::permissions::{builtin_tools_arg, effective_tool_groups};
/// `HIVE_CAPABILITIES` env var injected by `meta::render_flake` when the
/// operator grants capabilities to this agent. Comma-separated
/// `hive_sh4re::permissions::Capability` `snake_case` names. Absent = no extra capabilities.
const CAPABILITIES_ENV: &str = "HIVE_CAPABILITIES";
/// Returns the MCP tool names (without `mcp__hyperhive__` prefix) that are
/// unlocked by the agent's current capability set. These are added to the
/// `--allowedTools` list so claude can call them without prompting, and
/// hive-c0re performs a second server-side capability check before executing.
fn allowed_capability_tools() -> Vec<String> {
let raw = match std::env::var(CAPABILITIES_ENV) {
Ok(v) if !v.trim().is_empty() => v,
_ => return vec![],
};
let mut tools = Vec::new();
for token in raw.split(',') {
let t = token.trim().to_ascii_lowercase();
match t.as_str() {
"read_host_journal" => tools.push("get_host_journal".to_owned()),
// manage_root_agent exposes no MCP tool at all: it is a
// host-side grant, enforced by hive-c0re when it computes the
// container's bind mounts. Nothing for the harness to register.
"manage_root_agent" => {}
unknown => {
tracing::warn!(capability = %unknown, "unrecognised capability in HIVE_CAPABILITIES — skipped");
}
}
}
tools
}
/// Tool group an extra (out-of-process) MCP server is gated behind, if any.
///
/// Most `services.hyperhive.agent.extraMcpServers` entries are ungated — available whenever
@ -161,13 +130,6 @@ pub fn allowed_tools_arg() -> String {
.map(ToOwned::to_owned)
.collect();
all.extend(allowed_mcp_tools(&groups));
// Capability-gated MCP tools: added to --allowedTools when HIVE_CAPABILITIES
// includes the corresponding capability. hive-c0re performs a second
// server-side check, so this is a usability gate (no annoying prompts),
// not the security boundary.
for tool in allowed_capability_tools() {
all.push(format!("mcp__{SERVER_NAME}__{tool}"));
}
all.join(",")
}

View file

@ -501,7 +501,6 @@ fn tool_icon(name: &str) -> &'static str {
"mcp__matrix__list_rooms"
| "mcp__matrix__list_room_members"
| "mcp__matrix__list_invites" => "📋",
"mcp__hyperhive__get_host_journal" => "📜",
"mcp__matrix__read_room" | "Read" => "📖",
"mcp__matrix__mark_read" => "👁️",
"mcp__bash__kill" => "🛑",
@ -645,25 +644,6 @@ fn fmt_hyperhive_tool(name: &str, short: &str, input: &Value) -> String {
.map_or_else(|| "?".to_owned(), |n| n.to_string());
format!("{short}{up_to}")
}
"mcp__hyperhive__get_host_journal" => {
let mut parts = Vec::new();
if let Some(c) = input.get("container").and_then(Value::as_str) {
parts.push(c.to_owned());
} else if let Some(u) = input.get("unit").and_then(Value::as_str) {
parts.push(u.to_owned());
}
if let Some(g) = input.get("grep").and_then(Value::as_str) {
parts.push(format!("/{g}/"));
}
if let Some(l) = input.get("lines").and_then(Value::as_u64) {
parts.push(format!("{l}L"));
}
if parts.is_empty() {
format!("{short}()")
} else {
format!("{short} {}", parts.join(" · "))
}
}
"mcp__hyperhive__remind" => fmt_hyperhive_remind(short, input),
"mcp__hyperhive__list_schedules"
| "mcp__hyperhive__cancel_schedule"