feat(#1065): web_tools tool group gates WebFetch/WebSearch built-ins
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).
This commit is contained in:
parent
cd6cbf9997
commit
f6b80cf02e
3 changed files with 58 additions and 11 deletions
|
|
@ -1895,9 +1895,10 @@ impl ServerHandler for ManagerServer {}
|
||||||
/// tools as `mcp__<this>__<tool>` (e.g. `mcp__hyperhive__send`).
|
/// tools as `mcp__<this>__<tool>` (e.g. `mcp__hyperhive__send`).
|
||||||
pub const SERVER_NAME: &str = "hyperhive";
|
pub const SERVER_NAME: &str = "hyperhive";
|
||||||
|
|
||||||
/// Built-in claude tools the turn loop enables via `--tools`. Anything not
|
/// Built-in claude tools always present in every session. Anything not
|
||||||
/// in this list literally doesn't exist in the session (claude won't even
|
/// in this list (or added by `extra_builtin_tools`) literally doesn't
|
||||||
/// try to call it). Web egress (`WebFetch`/`WebSearch`) and nested agents
|
/// 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
|
/// (`Task`) are intentionally omitted. `Bash` is disallowed — shell
|
||||||
/// execution goes through `mcp__hyperhive__bash_run` (background tasks
|
/// execution goes through `mcp__hyperhive__bash_run` (background tasks
|
||||||
/// with structured output) instead of a raw interactive shell. `TodoWrite`
|
/// with structured output) instead of a raw interactive shell. `TodoWrite`
|
||||||
|
|
@ -2035,13 +2036,19 @@ pub fn allowed_mcp_tools(groups: &[hive_sh4re::ToolGroup]) -> Vec<String> {
|
||||||
/// both the built-ins and the MCP surface.
|
/// both the built-ins and the MCP surface.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn allowed_tools_arg(flavor: Flavor) -> String {
|
pub fn allowed_tools_arg(flavor: Flavor) -> String {
|
||||||
let mut all: Vec<String> = ALLOWED_BUILTIN_TOOLS
|
|
||||||
.iter()
|
|
||||||
.map(|s| (*s).to_owned())
|
|
||||||
.collect();
|
|
||||||
let groups = effective_tool_groups(flavor);
|
let groups = effective_tool_groups(flavor);
|
||||||
|
// Base built-ins always present.
|
||||||
|
let mut all: Vec<String> = 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));
|
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
|
// includes the corresponding capability. hive-c0re performs a second
|
||||||
// server-side check, so this is a usability gate (no annoying prompts),
|
// server-side check, so this is a usability gate (no annoying prompts),
|
||||||
// not the security boundary.
|
// 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
|
/// 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]
|
#[must_use]
|
||||||
pub fn builtin_tools_arg() -> String {
|
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
|
/// Where the NixOS module writes the per-agent extra-MCP spec (see
|
||||||
|
|
|
||||||
|
|
@ -672,7 +672,7 @@ async fn run_claude(prompt: &str, files: &TurnFiles, bus: &Bus) -> Result<(bool,
|
||||||
.arg(&files.mcp_config)
|
.arg(&files.mcp_config)
|
||||||
.arg("--strict-mcp-config")
|
.arg("--strict-mcp-config")
|
||||||
.arg("--tools")
|
.arg("--tools")
|
||||||
.arg(mcp::builtin_tools_arg())
|
.arg(mcp::builtin_tools_arg_for_flavor(files.flavor))
|
||||||
.arg("--allowedTools")
|
.arg("--allowedTools")
|
||||||
.arg(mcp::allowed_tools_arg(files.flavor));
|
.arg(mcp::allowed_tools_arg(files.flavor));
|
||||||
let mut child = cmd
|
let mut child = cmd
|
||||||
|
|
|
||||||
|
|
@ -774,10 +774,18 @@ pub enum ToolGroup {
|
||||||
Diagnostics,
|
Diagnostics,
|
||||||
/// `bash_run`, `bash_status`
|
/// `bash_run`, `bash_status`
|
||||||
Execution,
|
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 {
|
impl ToolGroup {
|
||||||
/// The MCP tool names (without the `mcp__hyperhive__` prefix) in this group.
|
/// 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]
|
#[must_use]
|
||||||
pub fn tools(self) -> &'static [&'static str] {
|
pub fn tools(self) -> &'static [&'static str] {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -804,6 +812,18 @@ impl ToolGroup {
|
||||||
],
|
],
|
||||||
Self::Diagnostics => &["get_logs"],
|
Self::Diagnostics => &["get_logs"],
|
||||||
Self::Execution => &["bash_run", "bash_status"],
|
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::Scheduling,
|
||||||
Self::Diagnostics,
|
Self::Diagnostics,
|
||||||
Self::Execution,
|
Self::Execution,
|
||||||
|
Self::WebTools,
|
||||||
];
|
];
|
||||||
|
|
||||||
/// The `snake_case` wire name for this group (matches `serde(rename_all =
|
/// The `snake_case` wire name for this group (matches `serde(rename_all =
|
||||||
|
|
@ -852,6 +873,7 @@ impl ToolGroup {
|
||||||
Self::Scheduling => "scheduling",
|
Self::Scheduling => "scheduling",
|
||||||
Self::Diagnostics => "diagnostics",
|
Self::Diagnostics => "diagnostics",
|
||||||
Self::Execution => "execution",
|
Self::Execution => "execution",
|
||||||
|
Self::WebTools => "web_tools",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue