diff --git a/hive-agent-mcp/src/mcp/mod.rs b/hive-agent-mcp/src/mcp/mod.rs index ee94c465..4ccbc384 100644 --- a/hive-agent-mcp/src/mcp/mod.rs +++ b/hive-agent-mcp/src/mcp/mod.rs @@ -1020,7 +1020,16 @@ 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 mut session_manager = LocalSessionManager::default(); + // rmcp's default session idle timeout is 5 minutes, meant to reap zombie + // connections lost to an HTTP/2 RST_STREAM — far too short for a claude + // turn that goes several minutes between MCP calls to this daemon (a long + // Read/Edit stretch, a background-task wait). That trips a client-visible + // "MCP server ... session expired" mid-turn; self-recovering on the next + // call, but confusing. Give it real headroom instead of turning it off + // outright. + session_manager.session_config.keep_alive = Some(std::time::Duration::from_hours(24)); + let session_manager = std::sync::Arc::new(session_manager); let service = StreamableHttpService::new( move || Ok(AgentServer::new(socket.clone())), session_manager, diff --git a/hive-bash-mcp/src/mcp.rs b/hive-bash-mcp/src/mcp.rs index 940b7480..5c159409 100644 --- a/hive-bash-mcp/src/mcp.rs +++ b/hive-bash-mcp/src/mcp.rs @@ -272,7 +272,15 @@ pub async fn serve_http(addr: std::net::SocketAddr) -> anyhow::Result<()> { use rmcp::transport::streamable_http_server::{ StreamableHttpServerConfig, StreamableHttpService, session::local::LocalSessionManager, }; - let session_manager = std::sync::Arc::new(LocalSessionManager::default()); + let mut session_manager = LocalSessionManager::default(); + // rmcp's default session idle timeout is 5 minutes, meant to reap zombie + // connections lost to an HTTP/2 RST_STREAM — far too short for a claude + // turn that goes several minutes between bash-MCP calls (a long Read/Edit + // stretch, a background-task wait). That trips a client-visible "MCP + // server ... session expired" mid-turn; self-recovering on the next call, + // but confusing. Give it real headroom instead of turning it off outright. + session_manager.session_config.keep_alive = Some(std::time::Duration::from_hours(24)); + let session_manager = std::sync::Arc::new(session_manager); let service = StreamableHttpService::new( || Ok(BashMcp), session_manager, diff --git a/hive-matrix-mcp/src/mcp.rs b/hive-matrix-mcp/src/mcp.rs index 76d7824b..302b2c2e 100644 --- a/hive-matrix-mcp/src/mcp.rs +++ b/hive-matrix-mcp/src/mcp.rs @@ -570,7 +570,16 @@ pub async fn serve_http(addr: std::net::SocketAddr, registry: Arc) -> use rmcp::transport::streamable_http_server::{ StreamableHttpServerConfig, StreamableHttpService, session::local::LocalSessionManager, }; - let session_manager = std::sync::Arc::new(LocalSessionManager::default()); + let mut session_manager = LocalSessionManager::default(); + // rmcp's default session idle timeout is 5 minutes, meant to reap zombie + // connections lost to an HTTP/2 RST_STREAM — far too short for a claude + // turn that goes several minutes between matrix-MCP calls (a long + // Read/Edit stretch, a background-task wait). That trips a client-visible + // "MCP server ... session expired" mid-turn; self-recovering on the next + // call, but confusing. Give it real headroom instead of turning it off + // outright. + session_manager.session_config.keep_alive = Some(std::time::Duration::from_hours(24)); + let session_manager = std::sync::Arc::new(session_manager); let mcp_registry = registry.clone(); let service = StreamableHttpService::new( move || {