diff --git a/CLAUDE.md b/CLAUDE.md index a2a0875..7337044 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -156,6 +156,20 @@ docs/damocles-migration.md options for moving damocles onto hyperhive marks them `failed` with note `"agent state dir missing"` so they fall out of `pending`. They stay in sqlite for audit. +## Agent MCP surface + +The harness ships an embedded MCP server (rmcp 1.7) that claude can launch +via `--mcp-config`. Subcommand: `hive-ag3nt mcp`. Tools: +- `send(to, body)` — message a peer or the operator. +- `recv()` — drain one inbox message. + +Both translate to `AgentRequest::Send`/`Recv` against the agent's own +`/run/hive/mcp.sock` (the existing hyperhive socket). The MCP surface is +just claude's view of that socket — same authority, friendlier protocol. + +Manager will get its own subcommand later with `request_spawn`, `kill`, +`request_apply_commit` added to the TOOLS list. + ## Manager (hm1nd) is hive-c0re-managed The manager container runs through the **same lifecycle as sub-agents** — diff --git a/Cargo.lock b/Cargo.lock index 8776902..4286126 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "1.0.0" @@ -67,12 +76,29 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "axum" version = "0.8.9" @@ -131,6 +157,12 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + [[package]] name = "bytes" version = "1.11.1" @@ -153,6 +185,18 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + [[package]] name = "clap" version = "4.6.1" @@ -199,6 +243,52 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "errno" version = "0.3.14" @@ -242,6 +332,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.32" @@ -249,6 +354,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -257,6 +363,34 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.32" @@ -275,8 +409,13 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "slab", ] @@ -313,6 +452,8 @@ dependencies = [ "axum", "clap", "hive-sh4re", + "rmcp", + "schemars", "serde", "serde_json", "tokio", @@ -425,6 +566,36 @@ dependencies = [ "tower-service", ] +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -437,6 +608,18 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "js-sys" +version = "0.3.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -513,6 +696,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -525,6 +717,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "pastey" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5a797f0e07bdf071d15742978fc3128ec6c22891c31a3a931513263904c982a" + [[package]] name = "percent-encoding" version = "2.3.2" @@ -561,6 +759,26 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "regex-automata" version = "0.4.14" @@ -578,6 +796,40 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "rmcp" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0810a9f717d9828f475fe1f629f4c305c8464b7f496c3a854b58d29e65f4058e" +dependencies = [ + "async-trait", + "chrono", + "futures", + "pastey", + "pin-project-lite", + "rmcp-macros", + "schemars", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "rmcp-macros" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aefac48c364756e97f04c0401ba3231e8607882c7c1d92da0437dc16307904d" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "serde_json", + "syn", +] + [[package]] name = "rusqlite" version = "0.37.0" @@ -592,12 +844,44 @@ dependencies = [ "smallvec", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "ryu" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "chrono", + "dyn-clone", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + [[package]] name = "serde" version = "1.0.228" @@ -628,6 +912,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_json" version = "1.0.149" @@ -740,6 +1035,26 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.9" @@ -921,12 +1236,110 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasm-bindgen" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.61.2" diff --git a/Cargo.toml b/Cargo.toml index 947b050..87a8235 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,13 @@ anyhow = "1" axum = "0.8" clap = { version = "4", features = ["derive"] } hive-sh4re = { path = "hive-sh4re" } +rmcp = { version = "1.7", default-features = false, features = [ + "server", + "macros", + "transport-io", +] } rusqlite = { version = "0.37", features = ["bundled"] } +schemars = "1.0" serde = { version = "1", features = ["derive"] } serde_json = "1" similar = "2" diff --git a/hive-ag3nt/Cargo.toml b/hive-ag3nt/Cargo.toml index 09ae091..ae1af3e 100644 --- a/hive-ag3nt/Cargo.toml +++ b/hive-ag3nt/Cargo.toml @@ -11,6 +11,8 @@ anyhow.workspace = true axum.workspace = true clap.workspace = true hive-sh4re.workspace = true +rmcp.workspace = true +schemars.workspace = true serde.workspace = true serde_json.workspace = true tokio.workspace = true diff --git a/hive-ag3nt/src/bin/hive-ag3nt.rs b/hive-ag3nt/src/bin/hive-ag3nt.rs index 0dd9d5c..423bcc3 100644 --- a/hive-ag3nt/src/bin/hive-ag3nt.rs +++ b/hive-ag3nt/src/bin/hive-ag3nt.rs @@ -5,7 +5,7 @@ use std::time::Duration; use anyhow::{Result, bail}; use clap::{Parser, Subcommand}; use hive_ag3nt::login::{self, LoginState}; -use hive_ag3nt::{DEFAULT_SOCKET, DEFAULT_WEB_PORT, client, web_ui}; +use hive_ag3nt::{DEFAULT_SOCKET, DEFAULT_WEB_PORT, client, mcp, web_ui}; use hive_sh4re::{AgentRequest, AgentResponse}; use tokio::process::Command; @@ -33,6 +33,10 @@ enum Cmd { Send { to: String, body: String }, /// Pop one message from the inbox. Recv, + /// Run the agent's MCP server on stdio. Spawned by `claude` via + /// `--mcp-config`; tools dispatch through `/run/hive/mcp.sock` back into + /// the hyperhive broker. + Mcp, } #[tokio::main] @@ -88,6 +92,7 @@ async fn main() -> Result<()> { render(&resp)?; check(&resp) } + Cmd::Mcp => mcp::serve_stdio(cli.socket).await, } } diff --git a/hive-ag3nt/src/lib.rs b/hive-ag3nt/src/lib.rs index b601724..4b7a62e 100644 --- a/hive-ag3nt/src/lib.rs +++ b/hive-ag3nt/src/lib.rs @@ -4,6 +4,7 @@ pub mod client; pub mod login; pub mod login_session; +pub mod mcp; pub mod web_ui; /// Default socket path inside the container — bind-mounted by `hive-c0re`. diff --git a/hive-ag3nt/src/mcp.rs b/hive-ag3nt/src/mcp.rs new file mode 100644 index 0000000..ff9bb8f --- /dev/null +++ b/hive-ag3nt/src/mcp.rs @@ -0,0 +1,103 @@ +//! Embedded MCP server. Claude Code (running inside the agent container) +//! launches this as a stdio child via `--mcp-config`; tool calls land here +//! and are translated to `AgentRequest::Send`/`Recv` against hyperhive's +//! own per-agent unix socket at `/run/hive/mcp.sock`. +//! +//! Two protocols, two surfaces: +//! - **hyperhive socket** at `/run/hive/mcp.sock` — JSON-line, our +//! broker-routed Send/Recv. Unaffected by this module. +//! - **MCP stdio** owned by this module — what claude actually speaks. +//! +//! The agent surface today is intentionally tiny (send/recv); the manager +//! surface (Phase 8 follow-up) will add `request_spawn`, `request_kill`, +//! `request_apply_commit`. + +use std::path::PathBuf; + +use anyhow::Result; +use rmcp::{ + ServerHandler, ServiceExt, + handler::server::wrapper::Parameters, + schemars, tool, tool_handler, tool_router, + transport::stdio, +}; + +use crate::client; + +#[derive(Debug, serde::Deserialize, schemars::JsonSchema)] +pub struct SendArgs { + /// Logical agent name to deliver the message to (e.g. `"manager"`, + /// `"alice"`, or the literal `"operator"` for the dashboard's T4LK box). + pub to: String, + /// Message body. Plain text; the broker doesn't parse it. + pub body: String, +} + +#[derive(Debug, serde::Deserialize, schemars::JsonSchema)] +pub struct RecvArgs {} + +/// Per-agent tool surface. Holds the socket path so each tool call doesn't +/// re-derive it; the socket itself is the per-container `/run/hive/mcp.sock`. +#[derive(Debug, Clone)] +pub struct AgentServer { + socket: PathBuf, +} + +impl AgentServer { + #[must_use] + pub fn new(socket: PathBuf) -> Self { + Self { socket } + } +} + +#[tool_router] +impl AgentServer { + #[tool( + description = "Send a message to another hyperhive agent (or to the operator). \ + Use this to talk to peers or to surface output for the human at the dashboard." + )] + async fn send(&self, Parameters(args): Parameters) -> String { + let req = hive_sh4re::AgentRequest::Send { + to: args.to.clone(), + body: args.body, + }; + match client::request::<_, hive_sh4re::AgentResponse>(&self.socket, &req).await { + Ok(hive_sh4re::AgentResponse::Ok) => format!("sent to {}", args.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:#}"), + } + } + + #[tool( + 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(_): Parameters) -> String { + 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:#}"), + } + } +} + +#[tool_handler( + instructions = "You are a hyperhive agent. Use `send` to talk to peers (by their logical \ + name) or to the operator (recipient `operator`). Use `recv` to drain your inbox one \ + message at a time." +)] +impl ServerHandler for AgentServer {} + +/// Run the MCP server over stdio. Returns when the client disconnects. +pub async fn serve_stdio(socket: PathBuf) -> Result<()> { + let server = AgentServer::new(socket); + let service = server.serve(stdio()).await?; + service.waiting().await?; + Ok(()) +}