hive-runtime, hive-agent: MCP permission only for kind other
A permission request counted as an MCP tool call whenever its title looked like `<server>_<tool>`, whatever its kind, and acp_permits allowed MCP calls before looking at the kind. So an `execute` request titled e.g. `hyperhive_x`, or a `fetch` without web_tools, was allowed. MCP tool calls come with kind `other` (opencode's toToolKind maps every tool it doesn't name, MCP tools included, to "other"). The runtime now sets PermissionAsk::mcp_server only for kind `other`, and acp_permits allows an MCP server's tool only under the `other` arm. Refs #4391
This commit is contained in:
parent
868fc789d1
commit
bed72ce280
3 changed files with 49 additions and 10 deletions
|
|
@ -34,9 +34,10 @@ pub struct PermissionAsk<'a> {
|
|||
/// The tool call's ACP `kind` (`read`, `edit`, `execute`, `fetch`, …;
|
||||
/// `other` when the agent gives none).
|
||||
pub kind: &'a str,
|
||||
/// The MCP server, of those handed to the session, whose tool this is:
|
||||
/// the tool call's title names it as `<server>_<tool>`,
|
||||
/// `<server>__<tool>` or `mcp__<server>__<tool>`.
|
||||
/// For a `kind` of `other`: the MCP server, of those handed to the
|
||||
/// session, whose tool this is — the tool call's title names it as
|
||||
/// `<server>_<tool>`, `<server>__<tool>` or `mcp__<server>__<tool>`.
|
||||
/// `None` for every other kind.
|
||||
pub mcp_server: Option<&'a str>,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -239,9 +239,14 @@ pub(super) fn permission_outcome(
|
|||
) -> Value {
|
||||
let tool_call = params.get("toolCall");
|
||||
let field = |k: &str| tool_call.and_then(|t| t.get(k)).and_then(Value::as_str);
|
||||
let kind = field("kind").unwrap_or("other");
|
||||
let ask = PermissionAsk {
|
||||
kind: field("kind").unwrap_or("other"),
|
||||
mcp_server: field("title").and_then(|title| mcp_server_of(title, servers)),
|
||||
kind,
|
||||
// MCP tool calls are kind `other`; a title alone must not lift another
|
||||
// kind (`execute`, `fetch`) into one.
|
||||
mcp_server: (kind == "other")
|
||||
.then(|| field("title").and_then(|title| mcp_server_of(title, servers)))
|
||||
.flatten(),
|
||||
};
|
||||
let wanted = if permit(&ask) {
|
||||
["allow_once", "allow_always"]
|
||||
|
|
@ -334,6 +339,26 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
/// A title shaped like `<server>_<tool>` marks an MCP call only on kind
|
||||
/// `other`, which is what an agent gives an MCP tool call.
|
||||
#[test]
|
||||
fn an_mcp_looking_title_on_execute_or_fetch_is_not_an_mcp_call() {
|
||||
let policy: PermissionPolicy = Arc::new(|ask: &PermissionAsk<'_>| ask.mcp_server.is_some());
|
||||
for kind in ["execute", "fetch"] {
|
||||
assert_eq!(
|
||||
permission_outcome(&request(kind, "hyperhive_send"), &policy, &servers())["outcome"]
|
||||
["optionId"],
|
||||
"reject",
|
||||
"{kind}"
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
permission_outcome(&request("other", "hyperhive_send"), &policy, &servers())["outcome"]
|
||||
["optionId"],
|
||||
"once"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_matching_option_cancels() {
|
||||
let req = json!({ "toolCall": { "kind": "execute" },
|
||||
|
|
|
|||
Loading…
Reference in a new issue