From f6b80cf02ed015d0721875e2a4094571e90a3134 Mon Sep 17 00:00:00 2001 From: iris Date: Tue, 2 Jun 2026 11:23:39 +0200 Subject: [PATCH] feat(#1065): web_tools tool group gates WebFetch/WebSearch built-ins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the earlier capability-based approach (closed #1069) with a ToolGroup — capabilities are for privileged system access, web egress is a tool permission. Add ToolGroup::WebTools to hive-sh4re: - tools() returns &[] (no MCP tools gated) - builtin_tools() returns &["WebFetch", "WebSearch"] — new method on ToolGroup - Present in ALL and as_str() → "web_tools" In hive-ag3nt/mcp.rs: - allowed_tools_arg() now iterates group.builtin_tools() to prepend any group-gated built-ins alongside the base ALLOWED_BUILTIN_TOOLS set - builtin_tools_arg_for_flavor(flavor) replaces builtin_tools_arg() so the flavor-correct effective groups are used when building --tools - builtin_tools_arg() kept as a flavor=Agent convenience alias - turn.rs updated to call builtin_tools_arg_for_flavor(files.flavor) so manager sessions also see web tools when web_tools is in their groups The dashboard T00L GR0UPS table gains a web_tools column automatically (ToolGroup::ALL drives the columns). --- hive-ag3nt/src/mcp.rs | 45 ++++++++++++++++++++++++++++++++---------- hive-ag3nt/src/turn.rs | 2 +- hive-sh4re/src/lib.rs | 22 +++++++++++++++++++++ 3 files changed, 58 insertions(+), 11 deletions(-) diff --git a/hive-ag3nt/src/mcp.rs b/hive-ag3nt/src/mcp.rs index 9d181d63..61c07458 100644 --- a/hive-ag3nt/src/mcp.rs +++ b/hive-ag3nt/src/mcp.rs @@ -1895,9 +1895,10 @@ impl ServerHandler for ManagerServer {} /// tools as `mcp____` (e.g. `mcp__hyperhive__send`). pub const SERVER_NAME: &str = "hyperhive"; -/// Built-in claude tools the turn loop enables via `--tools`. Anything not -/// in this list literally doesn't exist in the session (claude won't even -/// try to call it). Web egress (`WebFetch`/`WebSearch`) and nested agents +/// Built-in claude tools always present in every session. Anything not +/// in this list (or added by `extra_builtin_tools`) literally doesn't +/// exist in the session. Web egress (`WebFetch`/`WebSearch`) are +/// tool-group-gated (`web_tools`) — off by default. Nested agents /// (`Task`) are intentionally omitted. `Bash` is disallowed — shell /// execution goes through `mcp__hyperhive__bash_run` (background tasks /// with structured output) instead of a raw interactive shell. `TodoWrite` @@ -2035,13 +2036,19 @@ pub fn allowed_mcp_tools(groups: &[hive_sh4re::ToolGroup]) -> Vec { /// both the built-ins and the MCP surface. #[must_use] pub fn allowed_tools_arg(flavor: Flavor) -> String { - let mut all: Vec = ALLOWED_BUILTIN_TOOLS - .iter() - .map(|s| (*s).to_owned()) - .collect(); let groups = effective_tool_groups(flavor); + // Base built-ins always present. + let mut all: Vec = ALLOWED_BUILTIN_TOOLS.iter().map(|s| (*s).to_owned()).collect(); + // Extra built-ins gated by tool groups (e.g. WebFetch/WebSearch via web_tools). + for group in &groups { + for tool in group.builtin_tools() { + if !all.iter().any(|t| t == *tool) { + all.push((*tool).to_owned()); + } + } + } all.extend(allowed_mcp_tools(&groups)); - // Capability-gated tools: added to --allowedTools when HIVE_CAPABILITIES + // 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. @@ -2052,10 +2059,28 @@ pub fn allowed_tools_arg(flavor: Flavor) -> String { } /// Built-in tools list for `--tools` (which built-ins exist in this -/// session). Same as `ALLOWED_BUILTIN_TOOLS` but joined comma-separated. +/// session). Base set plus any group-gated built-ins (e.g. +/// `WebFetch`/`WebSearch` when the `web_tools` group is active). #[must_use] pub fn builtin_tools_arg() -> String { - ALLOWED_BUILTIN_TOOLS.join(",") + builtin_tools_arg_for_flavor(Flavor::Agent) +} + +/// Flavor-aware variant used by `turn.rs` via `builtin_tools_arg`. Reads +/// the effective tool groups for `flavor` so `--tools` matches what +/// `--allowedTools` includes for the same session. +#[must_use] +pub fn builtin_tools_arg_for_flavor(flavor: Flavor) -> String { + let groups = effective_tool_groups(flavor); + let mut tools: Vec<&str> = ALLOWED_BUILTIN_TOOLS.to_vec(); + for group in &groups { + for t in group.builtin_tools() { + if !tools.contains(t) { + tools.push(t); + } + } + } + tools.join(",") } /// Where the NixOS module writes the per-agent extra-MCP spec (see diff --git a/hive-ag3nt/src/turn.rs b/hive-ag3nt/src/turn.rs index d0b0d25c..88057c69 100644 --- a/hive-ag3nt/src/turn.rs +++ b/hive-ag3nt/src/turn.rs @@ -672,7 +672,7 @@ async fn run_claude(prompt: &str, files: &TurnFiles, bus: &Bus) -> Result<(bool, .arg(&files.mcp_config) .arg("--strict-mcp-config") .arg("--tools") - .arg(mcp::builtin_tools_arg()) + .arg(mcp::builtin_tools_arg_for_flavor(files.flavor)) .arg("--allowedTools") .arg(mcp::allowed_tools_arg(files.flavor)); let mut child = cmd diff --git a/hive-sh4re/src/lib.rs b/hive-sh4re/src/lib.rs index c611093d..87ab7caf 100644 --- a/hive-sh4re/src/lib.rs +++ b/hive-sh4re/src/lib.rs @@ -774,10 +774,18 @@ pub enum ToolGroup { Diagnostics, /// `bash_run`, `bash_status` Execution, + /// Claude built-in web egress tools: `WebFetch` (retrieve a URL) and + /// `WebSearch` (search the web). Both are omitted from `--tools` by + /// default; adding this group to an agent enables them in the session + /// and in `--allowedTools` so they run without a confirmation prompt. + /// Does not gate any MCP tools — `tools()` returns `&[]`. + WebTools, } impl ToolGroup { /// The MCP tool names (without the `mcp__hyperhive__` prefix) in this group. + /// Returns `&[]` for `WebTools` — it enables Claude built-in tools, + /// not MCP tools; see `builtin_tools()`. #[must_use] pub fn tools(self) -> &'static [&'static str] { match self { @@ -804,6 +812,18 @@ impl ToolGroup { ], Self::Diagnostics => &["get_logs"], Self::Execution => &["bash_run", "bash_status"], + Self::WebTools => &[], + } + } + + /// The Claude built-in tool names enabled by this group. Only + /// `WebTools` returns a non-empty slice; all other groups return `&[]` + /// (they control MCP tools via `tools()` instead). + #[must_use] + pub fn builtin_tools(self) -> &'static [&'static str] { + match self { + Self::WebTools => &["WebFetch", "WebSearch"], + _ => &[], } } @@ -837,6 +857,7 @@ impl ToolGroup { Self::Scheduling, Self::Diagnostics, Self::Execution, + Self::WebTools, ]; /// The `snake_case` wire name for this group (matches `serde(rename_all = @@ -852,6 +873,7 @@ impl ToolGroup { Self::Scheduling => "scheduling", Self::Diagnostics => "diagnostics", Self::Execution => "execution", + Self::WebTools => "web_tools", } } }