diff --git a/Cargo.lock b/Cargo.lock index ad94e4ad..228e132f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -460,17 +460,6 @@ dependencies = [ "cpufeatures 0.2.17", ] -[[package]] -name = "chacha20" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "rand_core 0.10.1", -] - [[package]] name = "chacha20poly1305" version = "0.10.1" @@ -478,7 +467,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", - "chacha20 0.9.1", + "chacha20", "cipher 0.4.4", "poly1305", "zeroize", @@ -1204,7 +1193,6 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", - "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -2840,17 +2828,6 @@ dependencies = [ "rand_core 0.9.5", ] -[[package]] -name = "rand" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" -dependencies = [ - "chacha20 0.10.0", - "getrandom 0.4.2", - "rand_core 0.10.1", -] - [[package]] name = "rand_chacha" version = "0.3.1" @@ -2889,12 +2866,6 @@ dependencies = [ "getrandom 0.3.4", ] -[[package]] -name = "rand_core" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" - [[package]] name = "rand_xoshiro" version = "0.7.0" @@ -3043,27 +3014,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0810a9f717d9828f475fe1f629f4c305c8464b7f496c3a854b58d29e65f4058e" dependencies = [ "async-trait", - "bytes", "chrono", "futures", - "http", - "http-body", - "http-body-util", "pastey", "pin-project-lite", - "rand 0.10.1", "rmcp-macros", "schemars", "serde", "serde_json", - "sse-stream", "thiserror 2.0.18", "tokio", - "tokio-stream", "tokio-util", - "tower-service", "tracing", - "uuid", ] [[package]] @@ -3666,19 +3628,6 @@ dependencies = [ "der", ] -[[package]] -name = "sse-stream" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3962b63f038885f15bce2c6e02c0e7925c072f1ac86bb60fd44c5c6b762fb72" -dependencies = [ - "bytes", - "futures-util", - "http-body", - "http-body-util", - "pin-project-lite", -] - [[package]] name = "stable_deref_trait" version = "1.2.1" diff --git a/Cargo.toml b/Cargo.toml index 44680642..3fdc0642 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,6 @@ rmcp = { version = "1.7", default-features = false, features = [ "server", "macros", "transport-io", - "transport-streamable-http-server", ] } rusqlite = { version = "0.37" } schemars = "1.0" diff --git a/hive-ag3nt/src/bin/hive.rs b/hive-ag3nt/src/bin/hive.rs index 2a67da85..e31b26e7 100644 --- a/hive-ag3nt/src/bin/hive.rs +++ b/hive-ag3nt/src/bin/hive.rs @@ -40,18 +40,10 @@ enum Cmd { #[arg(long, default_value_t = 1000)] poll_ms: u64, }, - /// Run the MCP server. Default is stdio — spawned by `claude` via + /// Run the MCP server on stdio. Spawned by `claude` via /// `--mcp-config`; tools dispatch through `/run/hive/mcp.sock` back - /// into the hyperhive broker. Pass `--http ` to instead run a - /// long-lived streamable-http listener (persistent daemon) that - /// claude reconnects to each turn, avoiding the per-turn stdio - /// re-registration race. - Mcp { - /// Serve over streamable-http on this address (e.g. - /// `127.0.0.1:8790`) instead of stdio. Bind loopback only. - #[arg(long)] - http: Option, - }, + /// into the hyperhive broker. + Mcp, /// Inject a wake-up event into this harness's inbox so the next /// turn fires with the given body. Intended for extra MCP servers /// / helpers (matrix bridge, scraper, webhook listener, etc.) that @@ -78,10 +70,7 @@ async fn main() -> Result<()> { match cli.cmd { Cmd::Serve { poll_ms } => serve_main::(&cli.socket, poll_ms).await, - Cmd::Mcp { http } => match http { - Some(addr) => mcp::serve_http(cli.socket, addr).await, - None => mcp::serve_agent_stdio(cli.socket).await, - }, + Cmd::Mcp => mcp::serve_agent_stdio(cli.socket).await, Cmd::Wake { from, body } => wake::(&cli.socket, from, body).await, } } diff --git a/hive-ag3nt/src/mcp.rs b/hive-ag3nt/src/mcp.rs index da8dac22..610e26c3 100644 --- a/hive-ag3nt/src/mcp.rs +++ b/hive-ag3nt/src/mcp.rs @@ -1549,41 +1549,6 @@ pub async fn serve_agent_stdio(socket: PathBuf) -> Result<()> { serve_stdio(socket).await } -/// Run the MCP server over HTTP (rmcp streamable-http transport) on `addr`. -/// -/// Unlike [`serve_stdio`] — a fresh stdio child claude respawns every turn — -/// this is meant to run as a long-lived in-container daemon. claude reconnects -/// to the stable URL each turn instead of respawning and re-registering a stdio -/// subprocess, which removes the per-turn MCP registration race that can strand -/// an agent when the async `initialize`/`tools/list` loses to claude's first -/// tool call. `socket` is the hyperhive control socket every tool call dials -/// fresh (the handler holds only the path), so a host-side hive-c0re restart is -/// transparent — the next call just reconnects. -/// -/// Binds loopback only in practice; the default `allowed_hosts` -/// (`localhost`/`127.0.0.1`/`::1`) rejects Host headers from anywhere else. -/// -/// # Errors -/// -/// Returns an error if the listener cannot bind `addr` or the HTTP server -/// exits with a fatal error. -pub async fn serve_http(socket: PathBuf, addr: std::net::SocketAddr) -> Result<()> { - use rmcp::transport::streamable_http_server::{ - StreamableHttpServerConfig, StreamableHttpService, session::local::LocalSessionManager, - }; - let session_manager = std::sync::Arc::new(LocalSessionManager::default()); - let service = StreamableHttpService::new( - move || Ok(AgentServer::new(socket.clone())), - session_manager, - StreamableHttpServerConfig::default(), - ); - let app = axum::Router::new().nest_service("/mcp", service); - let listener = tokio::net::TcpListener::bind(addr).await?; - tracing::info!(%addr, "serving hyperhive MCP over streamable-http at /mcp"); - axum::serve(listener, app).await?; - Ok(()) -} - // ----------------------------------------------------------------------------- // Privileged tool arg types (lifecycle, approvals, scheduling, diagnostics) // ----------------------------------------------------------------------------- @@ -2212,27 +2177,14 @@ fn load_extra_mcp() -> std::collections::BTreeMap { #[must_use] pub fn render_claude_config(agent_binary: &str, socket: &std::path::Path) -> String { let mut servers = serde_json::Map::new(); - // When the harness is configured to run the built-in server as a - // persistent streamable-http daemon (loopback port in - // `HYPERHIVE_MCP_HTTP_PORT`), point claude at the stable URL instead of - // respawning a fresh stdio child each turn. The URL survives the per-turn - // claude re-spawn, so there is no per-turn re-registration race for the - // hyperhive surface. Extra servers (matrix/bash) stay stdio bridges. - let hyperhive_entry = match std::env::var("HYPERHIVE_MCP_HTTP_PORT") - .ok() - .and_then(|p| p.trim().parse::().ok()) - { - Some(port) => serde_json::json!({ - "type": "http", - "url": format!("http://127.0.0.1:{port}/mcp"), - }), - None => serde_json::json!({ + servers.insert( + SERVER_NAME.to_owned(), + serde_json::json!({ "command": agent_binary, "args": ["--socket", socket.display().to_string(), "mcp"], "env": {} }), - }; - servers.insert(SERVER_NAME.to_owned(), hyperhive_entry); + ); // Auto-inject HYPERHIVE_STATE_DIR so extra MCP servers can resolve the // agent's durable state dir without the agent author hard-coding it. // User-supplied env takes precedence — we only fill in the missing key. diff --git a/nix/templates/harness-base.nix b/nix/templates/harness-base.nix index eb921254..f6c7c01a 100644 --- a/nix/templates/harness-base.nix +++ b/nix/templates/harness-base.nix @@ -828,34 +828,6 @@ in ''; }; - options.hyperhive.mcp.httpPort = lib.mkOption { - type = lib.types.nullOr lib.types.port; - default = null; - example = 8790; - description = '' - Serve the built-in hyperhive MCP surface as a persistent - streamable-http daemon on this loopback port instead of the - default per-turn stdio child. - - When null (the default) claude spawns a fresh `hive … mcp` stdio - subprocess every turn — the shape that carries the per-turn MCP - re-registration race (a resumed turn can emit its first tool call - before that turn's async `initialize`/`tools-list` completes, - stranding the agent with `No such tool`). When set, a long-lived - `hive-mcp-http` systemd unit runs `hive mcp --http 127.0.0.1:` - and `render_claude_config` points claude at the stable - `http://127.0.0.1:/mcp` URL, which survives the per-turn - claude re-spawn (and a host-side hive-c0re restart — each tool call - dials the control socket fresh). Extra MCP servers (matrix/bash) - stay stdio bridges regardless. - - Bound loopback-only; the rmcp streamable-http transport's default - `allowed_hosts` (`localhost` / `127.0.0.1` / `::1`) rejects Host - headers from anywhere else, so no auth token is required for a - container-local endpoint. - ''; - }; - config = { warnings = lib.optional (config.hyperhive.allowedBashPatterns != [ ]) '' hyperhive.allowedBashPatterns is deprecated and has no effect. @@ -1737,33 +1709,6 @@ in }; }; - # Persistent streamable-http MCP daemon for the built-in hyperhive - # surface. Only wired when `hyperhive.mcp.httpPort` is set; otherwise - # the surface stays the default per-turn stdio child (rendered by - # `render_claude_config`). Long-lived so claude reconnects to the - # stable URL each turn instead of respawning + re-registering a stdio - # subprocess (the per-turn MCP registration race). It dials the - # control socket (`/run/hive/mcp.sock`, the `hive` binary default) - # fresh on every tool call, so a host-side hive-c0re restart is - # transparent. `before = hive-ag3nt` so the URL is already listening - # by the time the harness renders the first turn's config; the - # harness/claude also reconnect on their own, so ordering is a - # latency nicety not a hard correctness dep. - systemd.services.hive-mcp-http = lib.mkIf (config.hyperhive.mcp.httpPort != null) { - description = "persistent streamable-http MCP daemon for the hyperhive surface"; - wantedBy = [ "multi-user.target" ]; - before = [ "hive-ag3nt.service" ]; - environment.RUST_LOG = "info"; - serviceConfig = { - ExecStart = "${pkgs.hyperhive}/bin/hive mcp --http 127.0.0.1:${toString config.hyperhive.mcp.httpPort}"; - SyslogIdentifier = "hive-mcp-http"; - Restart = "on-failure"; - RestartSec = 3; - User = userName; - Group = userName; - }; - }; - # Re-fire the daemon when the matrix token appears (hive-c0re # provisions it after agent containers come up). Without this # the daemon would exit 0 silently on first boot and the MCP @@ -1857,13 +1802,6 @@ in # bind-mounts and gateway upstream config stay in sync. HIVE_WEB_SOCKET = "/run/hive-agent/${userName}/web.sock"; } - // lib.optionalAttrs (config.hyperhive.mcp.httpPort != null) { - # Presence tells `render_claude_config` to point claude at the - # persistent `hive-mcp-http` daemon's loopback URL instead of a - # per-turn stdio child. Kept in sync with the `hive-mcp-http` - # unit's `--http` port above via the same option. - HYPERHIVE_MCP_HTTP_PORT = toString config.hyperhive.mcp.httpPort; - } // lib.optionalAttrs config.hyperhive.gui.enable { # Tells the harness which fixed VNC port weston bound, and (by # its presence) that gui is enabled — the harness `/screen/ws`