From 6edf1117531dc899cc3878a4f6e57e27f671b4fc Mon Sep 17 00:00:00 2001 From: Ben Younes Date: Sat, 6 Jun 2026 18:17:46 +0200 Subject: [PATCH] feat(mcp): add --host flag for MCP HTTP transport bind address (#254) Add a --host CLI flag to 'obscura mcp --http' so the server can bind to addresses other than 127.0.0.1 (e.g. 0.0.0.0 for Docker Compose sidecar deployments). The default remains 127.0.0.1 for security. Closes #253 --- crates/obscura-cli/src/main.rs | 7 +++++-- crates/obscura-mcp/src/http.rs | 6 +++--- crates/obscura-mcp/tests/cors_preflight.rs | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/obscura-cli/src/main.rs b/crates/obscura-cli/src/main.rs index c57bc11..2f49684 100644 --- a/crates/obscura-cli/src/main.rs +++ b/crates/obscura-cli/src/main.rs @@ -150,6 +150,9 @@ enum Command { #[arg(long)] http: bool, + #[arg(long, default_value = "127.0.0.1")] + host: String, + #[arg(long, default_value_t = 3000)] port: u16, @@ -318,10 +321,10 @@ async fn main() -> anyhow::Result<()> { Some(Command::Scrape { urls, eval, concurrency, format, timeout, quiet }) => { run_parallel_scrape(urls, eval, concurrency.get(), &format, timeout, quiet, global_proxy).await?; } - Some(Command::Mcp { http, port, proxy, user_agent, stealth }) => { + Some(Command::Mcp { http, host, port, proxy, user_agent, stealth }) => { let mcp_proxy = merge_proxy(global_proxy.clone(), proxy); if http { - obscura_mcp::http::run(port, mcp_proxy, user_agent, stealth).await?; + obscura_mcp::http::run(host, port, mcp_proxy, user_agent, stealth).await?; } else { obscura_mcp::run(mcp_proxy, user_agent, stealth).await?; } diff --git a/crates/obscura-mcp/src/http.rs b/crates/obscura-mcp/src/http.rs index 289d5f2..ff6121b 100644 --- a/crates/obscura-mcp/src/http.rs +++ b/crates/obscura-mcp/src/http.rs @@ -10,10 +10,10 @@ use crate::{dispatch, BrowserState}; /// Connections are handled sequentially on the current thread — the browser /// session (including the V8 runtime) is single-threaded and `!Send`, so we /// never need to move state across threads. -pub async fn run(port: u16, proxy: Option, user_agent: Option, stealth: bool) -> Result<()> { - let addr = std::net::SocketAddr::from(([127, 0, 0, 1], port)); +pub async fn run(host: String, port: u16, proxy: Option, user_agent: Option, stealth: bool) -> Result<()> { + let addr: std::net::SocketAddr = format!("{}:{}", host, port).parse()?; let listener = TcpListener::bind(&addr).await?; - tracing::info!("MCP HTTP server on http://127.0.0.1:{}/mcp", port); + tracing::info!("MCP HTTP server on http://{}:{}/mcp", host, port); let mut state = BrowserState::new(proxy, user_agent, stealth); diff --git a/crates/obscura-mcp/tests/cors_preflight.rs b/crates/obscura-mcp/tests/cors_preflight.rs index d2aed25..b874439 100644 --- a/crates/obscura-mcp/tests/cors_preflight.rs +++ b/crates/obscura-mcp/tests/cors_preflight.rs @@ -27,7 +27,7 @@ async fn options_preflight_lists_required_browser_headers() { // end of the test. `current_thread` + LocalSet is required because the // browser state is `!Send` (Page holds V8 handles). let server = local.spawn_local(async move { - let _ = obscura_mcp::http::run(port, None, None, false).await; + let _ = obscura_mcp::http::run("127.0.0.1".to_string(), port, None, None, false).await; }); local.run_until(async {