From 6ca0eec1075053d4e97b35dfbf8ededcdcd1791a Mon Sep 17 00:00:00 2001 From: Hengfei Yang Date: Mon, 17 Aug 2026 11:44:34 +0000 Subject: [PATCH] fix: MCP tool calls return 404 when ZO_BASE_URI is set (#13844) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #13845 ## What changed The MCP shared HTTP client (used to execute tool calls against the local REST API) now includes `ZO_BASE_URI` in its base URL: `http://localhost:{port}{base_uri}` instead of `http://localhost:{port}`. ## Why When `ZO_BASE_URI` is set (e.g. `/abc`), all HTTP routes are mounted under that prefix. The MCP endpoint itself was reachable (`POST /abc/api/default/mcp` → 200), but every tool execution failed: the internal client sent requests to the unprefixed path and got a 404. ``` INFO rmcp_openapi::http_client: Final URL: http://localhost:5080/api/default/streams INFO config::axum::middlewares::access_log: 127.0.0.1 "GET /api/default/streams HTTP/1.1" 404 ... ``` ## Notes for reviewers - `cfg.common.base_uri` is already normalized at config load to have no trailing slash (empty when unset), so the empty-prefix behavior is unchanged. - The join semantics are safe for path-prefixed base URLs: rmcp-openapi's `with_base_url` appends a trailing slash and `build_url` strips the tool path's leading slash before `Url::join`, covered by the crate's `test_build_url_with_base_url_containing_path`. So `/abc` + `/api/{org}/streams` → `/abc/api/{org}/streams`. - The other `localhost:5080` occurrence in `tools.rs` is a test-only mock client; the OAuth protected-resource metadata handler already includes `base_uri`. - `cargo check -p openobserve-mcp` and `cargo test -p openobserve-mcp` pass. --- src/mcp/src/tools.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/mcp/src/tools.rs b/src/mcp/src/tools.rs index 6fb529567a..0b53c85452 100644 --- a/src/mcp/src/tools.rs +++ b/src/mcp/src/tools.rs @@ -360,8 +360,12 @@ pub fn init_mcp_tools(api: &OpenApi) -> Result<()> { let spec = rmcp_openapi::Spec::from_value(api_json)?; let zo_config = config::get_config(); - let base_url = url::Url::parse(&format!("http://localhost:{}", zo_config.http.port)) - .map_err(|e| anyhow::anyhow!("Invalid base URL: {e}"))?; + // Include ZO_BASE_URI so tool calls hit the routes actually mounted under it + let base_url = url::Url::parse(&format!( + "http://localhost:{}{}", + zo_config.http.port, zo_config.common.base_uri + )) + .map_err(|e| anyhow::anyhow!("Invalid base URL: {e}"))?; // Set default headers including x-o2-mcp for MCP-initiated calls let mut default_headers = reqwest::header::HeaderMap::new();