server: use status code 403 for disabled features (#24970)

* server: use status code 403 for disabled features

* cont

* fix test case
This commit is contained in:
Xuan-Son Nguyen 2026-06-25 16:36:40 +02:00 committed by GitHub
parent 099bf06952
commit e9d1b76d0a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 27 additions and 4 deletions

View File

@ -242,6 +242,19 @@ int llama_server(int argc, char ** argv) {
// Google Cloud Platform (Vertex AI) compat // Google Cloud Platform (Vertex AI) compat
ctx_http.register_gcp_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<server_http_res>();
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) // CORS proxy (EXPERIMENTAL, only used by the Web UI for MCP)
if (params.ui_mcp_proxy) { if (params.ui_mcp_proxy) {
SRV_WRN("%s", "-----------------\n"); SRV_WRN("%s", "-----------------\n");
@ -250,7 +263,11 @@ int llama_server(int argc, char ** argv) {
SRV_WRN("%s", "-----------------\n"); SRV_WRN("%s", "-----------------\n");
ctx_http.get ("/cors-proxy", ex_wrapper(proxy_handler_get)); ctx_http.get ("/cors-proxy", ex_wrapper(proxy_handler_get));
ctx_http.post("/cors-proxy", ex_wrapper(proxy_handler_post)); 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 // EXPERIMENTAL built-in tools
if (!params.server_tools.empty()) { if (!params.server_tools.empty()) {
try { try {
@ -265,6 +282,9 @@ int llama_server(int argc, char ** argv) {
SRV_WRN("%s", "-----------------\n"); SRV_WRN("%s", "-----------------\n");
ctx_http.get ("/tools", ex_wrapper(tools.handle_get)); ctx_http.get ("/tools", ex_wrapper(tools.handle_get));
ctx_http.post("/tools", ex_wrapper(tools.handle_post)); 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));
} }
// //

View File

@ -16,7 +16,7 @@ def test_mcp_no_proxy():
server.start() server.start()
res = server.make_request("GET", "/cors-proxy") res = server.make_request("GET", "/cors-proxy")
assert res.status_code == 404 assert res.status_code == 403
def test_mcp_proxy(): def test_mcp_proxy():

View File

@ -392,11 +392,14 @@ class ToolsStore {
} catch (err) { } catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err); const errorMessage = err instanceof Error ? err.message : String(err);
this._error = errorMessage; this._error = errorMessage;
// 404 from /tools means the server was started without --tools // 403 from /tools means the server was started without --tools
if (errorMessage.includes('404') || errorMessage.toLowerCase().includes('not found')) { // TODO: check status code instead of relying on message
if (errorMessage.includes('this feature is disabled')) {
this._toolsEndpointUnreachable = true; 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 { } finally {
this._loading = false; this._loading = false;
} }