mcp: SocketReply + format_{ack,recv,status} helpers — dedupe tool wrappers

This commit is contained in:
müde 2026-05-15 16:35:18 +02:00
parent edf42b7e93
commit 392a448656

View file

@ -27,6 +27,79 @@ use rmcp::{
use crate::client;
/// Wire-protocol-agnostic view of a hyperhive socket response. Both
/// `AgentResponse` and `ManagerResponse` convert into this so the tool
/// formatters can be shared between `AgentServer` and `ManagerServer`.
#[derive(Debug)]
pub enum SocketReply {
Ok,
Err(String),
Message { from: String, body: String },
Empty,
Status(u64),
}
impl From<hive_sh4re::AgentResponse> for SocketReply {
fn from(r: hive_sh4re::AgentResponse) -> Self {
match r {
hive_sh4re::AgentResponse::Ok => Self::Ok,
hive_sh4re::AgentResponse::Err { message } => Self::Err(message),
hive_sh4re::AgentResponse::Message { from, body } => Self::Message { from, body },
hive_sh4re::AgentResponse::Empty => Self::Empty,
hive_sh4re::AgentResponse::Status { unread } => Self::Status(unread),
}
}
}
impl From<hive_sh4re::ManagerResponse> for SocketReply {
fn from(r: hive_sh4re::ManagerResponse) -> Self {
match r {
hive_sh4re::ManagerResponse::Ok => Self::Ok,
hive_sh4re::ManagerResponse::Err { message } => Self::Err(message),
hive_sh4re::ManagerResponse::Message { from, body } => Self::Message { from, body },
hive_sh4re::ManagerResponse::Empty => Self::Empty,
hive_sh4re::ManagerResponse::Status { unread } => Self::Status(unread),
}
}
}
/// Format helper for "send-like" tools (anything that expects an `Ok`).
/// `tool` and `ok_msg` only appear in the result string; they don't change
/// behavior.
pub fn format_ack(
resp: Result<SocketReply, anyhow::Error>,
tool: &str,
ok_msg: String,
) -> String {
match resp {
Ok(SocketReply::Ok) => ok_msg,
Ok(SocketReply::Err(m)) => format!("{tool} failed: {m}"),
Ok(other) => format!("{tool} unexpected response: {other:?}"),
Err(e) => format!("{tool} transport error: {e:#}"),
}
}
/// Format helper for `recv` tools: `Message` → from + body block;
/// `Empty` → marker; anything else surfaces as an error.
pub fn format_recv(resp: Result<SocketReply, anyhow::Error>) -> String {
match resp {
Ok(SocketReply::Message { from, body }) => format!("from: {from}\n\n{body}"),
Ok(SocketReply::Empty) => "(empty)".into(),
Ok(SocketReply::Err(m)) => format!("recv failed: {m}"),
Ok(other) => format!("recv unexpected response: {other:?}"),
Err(e) => format!("recv transport error: {e:#}"),
}
}
/// Format helper for the status peek used in the status line.
pub fn format_status(resp: Result<SocketReply, anyhow::Error>) -> String {
match resp {
Ok(SocketReply::Status(unread)) => format!("{unread} unread message(s) in inbox"),
Ok(other) => format!("status: unexpected response {other:?}"),
Err(e) => format!("status: transport error: {e:#}"),
}
}
/// Common envelope around every MCP tool handler: pre-log → run → append
/// a status line → post-log. Free function so both `AgentServer` and
/// `ManagerServer` use the same shape; the per-server `status_line`
@ -77,18 +150,13 @@ impl AgentServer {
/// note rather than failing the whole tool call when the socket
/// hiccups.
async fn status_line(&self) -> String {
match client::request::<_, hive_sh4re::AgentResponse>(
let resp = client::request::<_, hive_sh4re::AgentResponse>(
&self.socket,
&hive_sh4re::AgentRequest::Status,
)
.await
{
Ok(hive_sh4re::AgentResponse::Status { unread }) => {
format!("{unread} unread message(s) in inbox")
}
Ok(other) => format!("status: unexpected response {other:?}"),
Err(e) => format!("status: transport error: {e:#}"),
}
.map(SocketReply::from);
format_status(resp)
}
}
@ -102,16 +170,16 @@ impl AgentServer {
let log = format!("{args:?}");
let to = args.to.clone();
run_tool_envelope("send", log, self.status_line(), async move {
let req = hive_sh4re::AgentRequest::Send {
to: args.to,
body: args.body,
};
match client::request::<_, hive_sh4re::AgentResponse>(&self.socket, &req).await {
Ok(hive_sh4re::AgentResponse::Ok) => format!("sent to {to}"),
Ok(hive_sh4re::AgentResponse::Err { message }) => format!("send failed: {message}"),
Ok(other) => format!("send unexpected response: {other:?}"),
Err(e) => format!("send transport error: {e:#}"),
}
let resp = client::request::<_, hive_sh4re::AgentResponse>(
&self.socket,
&hive_sh4re::AgentRequest::Send {
to: args.to,
body: args.body,
},
)
.await
.map(SocketReply::from);
format_ack(resp, "send", format!("sent to {to}"))
})
.await
}
@ -120,19 +188,15 @@ impl AgentServer {
description = "Pop one message from this agent's inbox. Returns the sender and body, \
or an empty marker if nothing is waiting."
)]
async fn recv(&self, Parameters(args): Parameters<RecvArgs>) -> String {
let log = format!("{args:?}");
run_tool_envelope("recv", log, self.status_line(), async move {
let req = hive_sh4re::AgentRequest::Recv;
match client::request::<_, hive_sh4re::AgentResponse>(&self.socket, &req).await {
Ok(hive_sh4re::AgentResponse::Message { from, body }) => {
format!("from: {from}\n\n{body}")
}
Ok(hive_sh4re::AgentResponse::Empty) => "(empty)".into(),
Ok(hive_sh4re::AgentResponse::Err { message }) => format!("recv failed: {message}"),
Ok(other) => format!("recv unexpected response: {other:?}"),
Err(e) => format!("recv transport error: {e:#}"),
}
async fn recv(&self, Parameters(_args): Parameters<RecvArgs>) -> String {
run_tool_envelope("recv", String::new(), self.status_line(), async move {
let resp = client::request::<_, hive_sh4re::AgentResponse>(
&self.socket,
&hive_sh4re::AgentRequest::Recv,
)
.await
.map(SocketReply::from);
format_recv(resp)
})
.await
}
@ -199,18 +263,21 @@ impl ManagerServer {
}
async fn status_line(&self) -> String {
match client::request::<_, hive_sh4re::ManagerResponse>(
let resp = client::request::<_, hive_sh4re::ManagerResponse>(
&self.socket,
&hive_sh4re::ManagerRequest::Status,
)
.await
{
Ok(hive_sh4re::ManagerResponse::Status { unread }) => {
format!("{unread} unread message(s) in inbox")
}
Ok(other) => format!("status: unexpected response {other:?}"),
Err(e) => format!("status: transport error: {e:#}"),
}
.map(SocketReply::from);
format_status(resp)
}
/// Helper: issue any `ManagerRequest`, convert the reply through
/// `SocketReply`. Manager tools that just need an `Ok` ack share this.
async fn dispatch(&self, req: hive_sh4re::ManagerRequest) -> Result<SocketReply, anyhow::Error> {
client::request::<_, hive_sh4re::ManagerResponse>(&self.socket, &req)
.await
.map(SocketReply::from)
}
}
@ -224,18 +291,13 @@ impl ManagerServer {
let log = format!("{args:?}");
let to = args.to.clone();
run_tool_envelope("send", log, self.status_line(), async move {
let req = hive_sh4re::ManagerRequest::Send {
to: args.to,
body: args.body,
};
match client::request::<_, hive_sh4re::ManagerResponse>(&self.socket, &req).await {
Ok(hive_sh4re::ManagerResponse::Ok) => format!("sent to {to}"),
Ok(hive_sh4re::ManagerResponse::Err { message }) => {
format!("send failed: {message}")
}
Ok(other) => format!("send unexpected response: {other:?}"),
Err(e) => format!("send transport error: {e:#}"),
}
let resp = self
.dispatch(hive_sh4re::ManagerRequest::Send {
to: args.to,
body: args.body,
})
.await;
format_ack(resp, "send", format!("sent to {to}"))
})
.await
}
@ -244,21 +306,10 @@ impl ManagerServer {
description = "Pop one message from the manager inbox. Returns sender + body, or \
empty."
)]
async fn recv(&self, Parameters(args): Parameters<RecvArgs>) -> String {
let log = format!("{args:?}");
run_tool_envelope("recv", log, self.status_line(), async move {
let req = hive_sh4re::ManagerRequest::Recv;
match client::request::<_, hive_sh4re::ManagerResponse>(&self.socket, &req).await {
Ok(hive_sh4re::ManagerResponse::Message { from, body }) => {
format!("from: {from}\n\n{body}")
}
Ok(hive_sh4re::ManagerResponse::Empty) => "(empty)".into(),
Ok(hive_sh4re::ManagerResponse::Err { message }) => {
format!("recv failed: {message}")
}
Ok(other) => format!("recv unexpected response: {other:?}"),
Err(e) => format!("recv transport error: {e:#}"),
}
async fn recv(&self, Parameters(_args): Parameters<RecvArgs>) -> String {
run_tool_envelope("recv", String::new(), self.status_line(), async move {
let resp = self.dispatch(hive_sh4re::ManagerRequest::Recv).await;
format_recv(resp)
})
.await
}
@ -271,15 +322,14 @@ impl ManagerServer {
let log = format!("{args:?}");
let name = args.name.clone();
run_tool_envelope("request_spawn", log, self.status_line(), async move {
let req = hive_sh4re::ManagerRequest::RequestSpawn { name: args.name };
match client::request::<_, hive_sh4re::ManagerResponse>(&self.socket, &req).await {
Ok(hive_sh4re::ManagerResponse::Ok) => format!("spawn approval queued for {name}"),
Ok(hive_sh4re::ManagerResponse::Err { message }) => {
format!("request_spawn failed: {message}")
}
Ok(other) => format!("request_spawn unexpected response: {other:?}"),
Err(e) => format!("request_spawn transport error: {e:#}"),
}
let resp = self
.dispatch(hive_sh4re::ManagerRequest::RequestSpawn { name: args.name })
.await;
format_ack(
resp,
"request_spawn",
format!("spawn approval queued for {name}"),
)
})
.await
}
@ -292,15 +342,10 @@ impl ManagerServer {
let log = format!("{args:?}");
let name = args.name.clone();
run_tool_envelope("kill", log, self.status_line(), async move {
let req = hive_sh4re::ManagerRequest::Kill { name: args.name };
match client::request::<_, hive_sh4re::ManagerResponse>(&self.socket, &req).await {
Ok(hive_sh4re::ManagerResponse::Ok) => format!("killed {name}"),
Ok(hive_sh4re::ManagerResponse::Err { message }) => {
format!("kill failed: {message}")
}
Ok(other) => format!("kill unexpected response: {other:?}"),
Err(e) => format!("kill transport error: {e:#}"),
}
let resp = self
.dispatch(hive_sh4re::ManagerRequest::Kill { name: args.name })
.await;
format_ack(resp, "kill", format!("killed {name}"))
})
.await
}
@ -322,20 +367,17 @@ impl ManagerServer {
log,
self.status_line(),
async move {
let req = hive_sh4re::ManagerRequest::RequestApplyCommit {
agent: args.agent,
commit_ref: args.commit_ref,
};
match client::request::<_, hive_sh4re::ManagerResponse>(&self.socket, &req).await {
Ok(hive_sh4re::ManagerResponse::Ok) => {
format!("apply approval queued for {agent} @ {commit_ref}")
}
Ok(hive_sh4re::ManagerResponse::Err { message }) => {
format!("request_apply_commit failed: {message}")
}
Ok(other) => format!("request_apply_commit unexpected response: {other:?}"),
Err(e) => format!("request_apply_commit transport error: {e:#}"),
}
let resp = self
.dispatch(hive_sh4re::ManagerRequest::RequestApplyCommit {
agent: args.agent,
commit_ref: args.commit_ref,
})
.await;
format_ack(
resp,
"request_apply_commit",
format!("apply approval queued for {agent} @ {commit_ref}"),
)
},
)
.await