From e9d1b76d0ad84569c889be265dd270d44b54fbc7 Mon Sep 17 00:00:00 2001 From: Xuan-Son Nguyen Date: Thu, 25 Jun 2026 16:36:40 +0200 Subject: [PATCH] server: use status code 403 for disabled features (#24970) * server: use status code 403 for disabled features * cont * fix test case --- tools/server/server.cpp | 20 ++++++++++++++++++++ tools/server/tests/unit/test_proxy.py | 2 +- tools/ui/src/lib/stores/tools.svelte.ts | 9 ++++++--- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/tools/server/server.cpp b/tools/server/server.cpp index 680590871f..0a1947faf5 100644 --- a/tools/server/server.cpp +++ b/tools/server/server.cpp @@ -242,6 +242,19 @@ int llama_server(int argc, char ** argv) { // Google Cloud Platform (Vertex AI) compat ctx_http.register_gcp_compat(); + // return 403 for disabled features + server_http_context::handler_t res_403 = [](const server_http_req &) { + auto res = std::make_unique(); + res->status = 403; + res->data = safe_json_to_str({ + {"error", { + {"message", "this feature is disabled"}, + {"type", "feature_disabled"}, + }} + }); + return res; + }; + // CORS proxy (EXPERIMENTAL, only used by the Web UI for MCP) if (params.ui_mcp_proxy) { SRV_WRN("%s", "-----------------\n"); @@ -250,7 +263,11 @@ int llama_server(int argc, char ** argv) { SRV_WRN("%s", "-----------------\n"); ctx_http.get ("/cors-proxy", ex_wrapper(proxy_handler_get)); ctx_http.post("/cors-proxy", ex_wrapper(proxy_handler_post)); + } else { + ctx_http.get ("/cors-proxy", ex_wrapper(res_403)); + ctx_http.post("/cors-proxy", ex_wrapper(res_403)); } + // EXPERIMENTAL built-in tools if (!params.server_tools.empty()) { try { @@ -265,6 +282,9 @@ int llama_server(int argc, char ** argv) { SRV_WRN("%s", "-----------------\n"); ctx_http.get ("/tools", ex_wrapper(tools.handle_get)); ctx_http.post("/tools", ex_wrapper(tools.handle_post)); + } else { + ctx_http.get ("/tools", ex_wrapper(res_403)); + ctx_http.post("/tools", ex_wrapper(res_403)); } // diff --git a/tools/server/tests/unit/test_proxy.py b/tools/server/tests/unit/test_proxy.py index 3b86d80473..0fed536e59 100644 --- a/tools/server/tests/unit/test_proxy.py +++ b/tools/server/tests/unit/test_proxy.py @@ -16,7 +16,7 @@ def test_mcp_no_proxy(): server.start() res = server.make_request("GET", "/cors-proxy") - assert res.status_code == 404 + assert res.status_code == 403 def test_mcp_proxy(): diff --git a/tools/ui/src/lib/stores/tools.svelte.ts b/tools/ui/src/lib/stores/tools.svelte.ts index 9f0101a82e..a637819885 100644 --- a/tools/ui/src/lib/stores/tools.svelte.ts +++ b/tools/ui/src/lib/stores/tools.svelte.ts @@ -392,11 +392,14 @@ class ToolsStore { } catch (err) { const errorMessage = err instanceof Error ? err.message : String(err); this._error = errorMessage; - // 404 from /tools means the server was started without --tools - if (errorMessage.includes('404') || errorMessage.toLowerCase().includes('not found')) { + // 403 from /tools means the server was started without --tools + // TODO: check status code instead of relying on message + if (errorMessage.includes('this feature is disabled')) { this._toolsEndpointUnreachable = true; + console.info('[ToolsStore] Built-in tools are disabled on the server'); + } else { + console.error('[ToolsStore] Failed to fetch built-in tools:', err); } - console.error('[ToolsStore] Failed to fetch built-in tools:', err); } finally { this._loading = false; }