feat(#2038): streamable-http mcp transport for hyperhive server
This commit is contained in:
parent
621ee66133
commit
ecba548787
4 changed files with 103 additions and 5 deletions
|
|
@ -40,10 +40,18 @@ enum Cmd {
|
|||
#[arg(long, default_value_t = 1000)]
|
||||
poll_ms: u64,
|
||||
},
|
||||
/// Run the MCP server on stdio. Spawned by `claude` via
|
||||
/// Run the MCP server. Default is stdio — spawned by `claude` via
|
||||
/// `--mcp-config`; tools dispatch through `/run/hive/mcp.sock` back
|
||||
/// into the hyperhive broker.
|
||||
Mcp,
|
||||
/// into the hyperhive broker. Pass `--http <addr>` 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<std::net::SocketAddr>,
|
||||
},
|
||||
/// 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
|
||||
|
|
@ -70,7 +78,10 @@ async fn main() -> Result<()> {
|
|||
|
||||
match cli.cmd {
|
||||
Cmd::Serve { poll_ms } => serve_main::<AgentSurface>(&cli.socket, poll_ms).await,
|
||||
Cmd::Mcp => mcp::serve_agent_stdio(cli.socket).await,
|
||||
Cmd::Mcp { http } => match http {
|
||||
Some(addr) => mcp::serve_http(cli.socket, addr).await,
|
||||
None => mcp::serve_agent_stdio(cli.socket).await,
|
||||
},
|
||||
Cmd::Wake { from, body } => wake::<AgentSurface>(&cli.socket, from, body).await,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1549,6 +1549,41 @@ 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)
|
||||
// -----------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Reference in a new issue