From 498ce7a51a5785d614bea197fe78893474d7241a Mon Sep 17 00:00:00 2001 From: Shivakumar Date: Wed, 26 Aug 2026 20:52:09 +0530 Subject: [PATCH] mcp --http done: Arc-shared AgentflareMcp across sessions, loopback-only default, 244 tests green. Follow-ups: per-process agent identity conflates multi-agent handoff; add run_http concurrent test. Agentflare-Agent: claude-code_2-1-251_agent Agentflare-Branch: HEAD Agentflare-Session: 42b8c2f2-a60f-4b46-9fab-fb3d3370d30f --- Cargo.toml | 2 +- src/cli/mcp.rs | 27 +++++++++++++++++++++++++-- src/mcp_server.rs | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a9afc4df..f23dc2a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,7 @@ toml = { version = "0.8" } serde_json = "1" dirs = "6" chrono = "0.4" -rmcp = { version = "1.8.0", features = ["server", "transport-io"] } +rmcp = { version = "1.8.0", features = ["server", "transport-io", "transport-streamable-http-server"] } tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros", "io-util", "io-std", "sync", "net", "time"] } ureq = { version = "2", features = ["json"] } sha2 = "0.10" diff --git a/src/cli/mcp.rs b/src/cli/mcp.rs index 77a06e97..0020627e 100644 --- a/src/cli/mcp.rs +++ b/src/cli/mcp.rs @@ -1,11 +1,34 @@ use clap::Args; -/// Run the agentflare MCP server over stdio (for editor/agent MCP clients). +/// Run the agentflare MCP server (stdio by default; `--http` serves many +/// concurrent agent sessions from one persistent process over Streamable HTTP). #[derive(Args)] -pub struct McpArgs; +pub struct McpArgs { + /// Serve over Streamable HTTP/SSE instead of stdio, so one agentflare + /// process backs many concurrent agent sessions instead of one per session. + #[arg(long)] + pub http: bool, + /// TCP port for `--http`. Default 35274 ("FLARE" shifted one up from the + /// dashboard's 35273); 0 = auto-assign. + #[arg(long, default_value = "35274")] + pub port: u16, + /// Interface to bind for `--http`. Loopback only for now: rmcp's default + /// Host allowlist rejects remote clients, and there is no auth yet. + #[arg(long, default_value = "127.0.0.1")] + pub host: String, +} impl McpArgs { pub fn run(self) { + if self.http { + let runtime = tokio::runtime::Runtime::new() + .expect("failed to build tokio runtime for mcp server"); + if let Err(e) = runtime.block_on(crate::mcp_server::run_http(&self.host, self.port)) { + crate::ui::error(&format!("agentflare mcp: {e}")); + std::process::exit(1); + } + return; + } let runtime = tokio::runtime::Builder::new_current_thread() .enable_all() .build() diff --git a/src/mcp_server.rs b/src/mcp_server.rs index 4de0fbb2..5e598f3b 100644 --- a/src/mcp_server.rs +++ b/src/mcp_server.rs @@ -1798,6 +1798,41 @@ pub async fn run() -> Result<(), Box> { Ok(()) } +/// Serve the MCP server over Streamable HTTP/SSE so one persistent agentflare +/// process backs many concurrent agent sessions, instead of one cold-started +/// OS process per connecting agent (the stdio default). +pub async fn run_http( + host: &str, + port: u16, +) -> Result<(), Box> { + use rmcp::transport::streamable_http_server::{ + StreamableHttpServerConfig, StreamableHttpService, session::local::LocalSessionManager, + }; + + // One server instance shared across every session: the point of HTTP mode + // is that a single process (and its lazily-opened skills/gateway registries + // and backend/store DB connections) backs many concurrent agent sessions, + // instead of cold-starting one per connection. The factory clones the Arc, + // so each rmcp session routes into the same `AgentflareMcp`. + let shared = std::sync::Arc::new(AgentflareMcp::from_env()); + + let service: StreamableHttpService, LocalSessionManager> = + StreamableHttpService::new( + { + let shared = shared.clone(); + move || Ok(shared.clone()) + }, + std::sync::Arc::new(LocalSessionManager::default()), + StreamableHttpServerConfig::default(), + ); + let app = axum::Router::new().nest_service("/mcp", service); + let listener = tokio::net::TcpListener::bind((host, port)).await?; + let addr = listener.local_addr()?; + eprintln!("agentflare MCP listening on http://{addr}/mcp (streamable HTTP)"); + axum::serve(listener, app).await?; + Ok(()) +} + #[cfg(test)] mod optimize_instructions_tests { use super::build_optimize_instructions;