remove flavor from AgentServer — dumb dispatcher, tool groups gate access

This commit is contained in:
damocles 2026-06-04 00:14:02 +02:00 committed by mara
commit 7261992fda

View file

@ -8,13 +8,11 @@
//! broker-routed protocol. Unaffected by this module.
//! - **MCP stdio** owned by this module — what claude actually speaks.
//!
//! One `AgentServer { socket, flavor }` struct handles both flavors:
//! - `Flavor::Agent` — restricted send allow-list, no manager-only tools.
//! - `Flavor::Manager` — manager-only tools unlocked via `require_manager()`.
//! One `AgentServer { socket }` struct shared by agent and manager roles.
//! `ManagerServer` is a backward-compat type alias for `AgentServer`.
//!
//! All tools live in one `#[tool_router] impl AgentServer`. Both flavors go
//! through the same `run_tool_envelope` helper so logging stays uniform.
//! Tool access is gated upstream by `--allowedTools` (derived from the
//! agent's `ToolGroup` config); the server itself is a dumb dispatcher.
//! All tools go through the same `run_tool_envelope` helper.
use std::future::Future;
use std::path::PathBuf;
@ -578,12 +576,9 @@ pub struct RemindArgs {
/// `dispatch` call covers both sockets — the only real difference is which
/// socket path is used and which tools the flavor enables.
///
/// Manager-only tools return a clear error when called from an agent context.
/// In practice the `--allowedTools` gate prevents that — this is belt-and-suspenders.
#[derive(Debug, Clone)]
pub struct AgentServer {
socket: PathBuf,
flavor: Flavor,
}
/// Backward-compat alias — callers that reference `ManagerServer` by name still compile.
@ -591,8 +586,8 @@ pub type ManagerServer = AgentServer;
impl AgentServer {
#[must_use]
pub fn new(socket: PathBuf, flavor: Flavor) -> Self {
Self { socket, flavor }
pub fn new(socket: PathBuf) -> Self {
Self { socket }
}
/// Issue any `Request` through the retry-aware client and pull
@ -610,16 +605,6 @@ impl AgentServer {
Err(e) => (Err(e), 0),
}
}
/// Returns an error string when called from a non-manager flavor.
/// Manager-only tool methods call this first and early-return on Some.
fn require_manager(&self) -> Option<String> {
if !matches!(self.flavor, Flavor::Manager) {
Some("error: this tool is only available to manager agents".to_owned())
} else {
None
}
}
}
// IMPORTANT: when adding a new `#[tool]` fn to this impl, also add
@ -635,12 +620,10 @@ impl AgentServer {
async fn send(&self, Parameters(args): Parameters<SendArgs>) -> String {
let log = format!("{args:?}");
let to = args.to.clone();
// Agent flavor: check per-agent allow-list (hyperhive.allowedRecipients).
// Manager flavor: unrestricted send.
if matches!(self.flavor, Flavor::Agent) {
if let Err(refusal) = check_send_allowed(&to) {
return run_tool_envelope("send", log, async move { refusal }).await;
}
// Check per-agent allow-list (hyperhive.allowedRecipients). When no
// policy file is present (e.g. manager containers) the check is a no-op.
if let Err(refusal) = check_send_allowed(&to) {
return run_tool_envelope("send", log, async move { refusal }).await;
}
run_tool_envelope("send", log, async move {
let (resp, retries) = self
@ -1214,11 +1197,6 @@ impl AgentServer {
.await
}
// -------------------------------------------------------------------------
// Manager-only tools — guarded by require_manager(). The `--allowedTools`
// gate prevents agents from ever calling these in practice; the guard is
// belt-and-suspenders in case the gate is misconfigured.
// -------------------------------------------------------------------------
#[tool(
description = "Fetch recent journal log lines for a sub-agent container. Useful \
@ -1228,9 +1206,6 @@ impl AgentServer {
`lines` defaults to 50 (max capped at 500 on the host side)."
)]
async fn get_logs(&self, Parameters(args): Parameters<GetLogsArgs>) -> String {
if let Some(e) = self.require_manager() {
return e;
}
let log = format!("{args:?}");
let agent = args.agent.clone();
run_tool_envelope("get_logs", log, async move {
@ -1270,9 +1245,6 @@ impl AgentServer {
&self,
Parameters(args): Parameters<UpdateMetaInputsArgs>,
) -> String {
if let Some(e) = self.require_manager() {
return e;
}
let log = format!("{args:?}");
run_tool_envelope("request_update_meta_inputs", log, async move {
let label = if args.inputs.is_empty() {
@ -1316,9 +1288,6 @@ impl AgentServer {
&self,
Parameters(args): Parameters<RequestSchedulePromptArgs>,
) -> String {
if let Some(e) = self.require_manager() {
return e;
}
let log = format!("{args:?}");
run_tool_envelope("request_schedule_prompt", log, async move {
let target_count = args.targets.len();
@ -1355,9 +1324,6 @@ impl AgentServer {
owned by a sub-agent in your subtree per topology.json."
)]
async fn fire_schedule_now(&self, Parameters(args): Parameters<FireScheduleNowArgs>) -> String {
if let Some(e) = self.require_manager() {
return e;
}
let log = format!("{args:?}");
run_tool_envelope("fire_schedule_now", log, async move {
let id = args.id;
@ -1381,9 +1347,6 @@ impl AgentServer {
owner is one of its sub-agents per topology.json. Other owners are refused."
)]
async fn cancel_schedule(&self, Parameters(args): Parameters<CancelScheduleArgs>) -> String {
if let Some(e) = self.require_manager() {
return e;
}
let log = format!("{args:?}");
run_tool_envelope("cancel_schedule", log, async move {
let id = args.id;
@ -1416,9 +1379,6 @@ impl AgentServer {
Refuses cancelled schedules (the row's terminal submit a fresh one)."
)]
async fn edit_schedule(&self, Parameters(args): Parameters<EditScheduleArgs>) -> String {
if let Some(e) = self.require_manager() {
return e;
}
let log = format!("{args:?}");
run_tool_envelope("edit_schedule", log, async move {
let id = args.id;
@ -1449,9 +1409,6 @@ impl AgentServer {
the swarm is going to be woken up about next."
)]
async fn list_schedules(&self) -> String {
if let Some(e) = self.require_manager() {
return e;
}
run_tool_envelope("list_schedules", String::new(), async move {
let (resp, retries) = self
.dispatch(hive_sh4re::Request::ListSchedules)
@ -1482,8 +1439,8 @@ impl ServerHandler for AgentServer {}
///
/// Returns an error if the MCP server fails to initialize or the transport
/// encounters a fatal error.
pub async fn serve_stdio(socket: PathBuf, flavor: Flavor) -> Result<()> {
let server = AgentServer::new(socket, flavor);
pub async fn serve_stdio(socket: PathBuf) -> Result<()> {
let server = AgentServer::new(socket);
let service = server.serve(stdio()).await?;
service.waiting().await?;
Ok(())
@ -1491,15 +1448,12 @@ pub async fn serve_stdio(socket: PathBuf, flavor: Flavor) -> Result<()> {
/// Convenience wrapper: run the agent MCP server over stdio.
pub async fn serve_agent_stdio(socket: PathBuf) -> Result<()> {
serve_stdio(socket, Flavor::Agent).await
serve_stdio(socket).await
}
/// Convenience wrapper: run the manager MCP server over stdio.
pub async fn serve_manager_stdio(socket: PathBuf) -> Result<()> {
let server = AgentServer::new(socket, Flavor::Manager);
let service = server.serve(stdio()).await?;
service.waiting().await?;
Ok(())
serve_stdio(socket).await
}
// -----------------------------------------------------------------------------