diff --git a/tools/ui/src/lib/hooks/use-tools-panel.svelte.ts b/tools/ui/src/lib/hooks/use-tools-panel.svelte.ts index 21deed32d3..cb361aad8a 100644 --- a/tools/ui/src/lib/hooks/use-tools-panel.svelte.ts +++ b/tools/ui/src/lib/hooks/use-tools-panel.svelte.ts @@ -47,7 +47,7 @@ export function useToolsPanel(): UseToolsPanelReturn { if (toolsStore.toolGroups.length > 0) return null; - // Tools endpoint is unreachable (404) — server started without --tools + // Tools endpoint unreachable (403) — server started without tools if (toolsStore.isToolsEndpointUnreachable) { return `To enable Server Tools you need to run llama-server with ${CLI_FLAGS.TOOLS} all or ${CLI_FLAGS.TOOLS} flag. To see MCP Tools you need to add / enable MCP Server(s).`; } diff --git a/tools/ui/src/lib/stores/agentic/index.svelte.ts b/tools/ui/src/lib/stores/agentic/index.svelte.ts index 50db1be0cc..121ee7739c 100644 --- a/tools/ui/src/lib/stores/agentic/index.svelte.ts +++ b/tools/ui/src/lib/stores/agentic/index.svelte.ts @@ -315,8 +315,14 @@ class AgenticStore { // Clear any pending permissions/continue requests for this conversation when starting a new flow this.gates.clear(conversationId); - // Ensure server tools are fetched before checking if agentic is enabled - if (toolsStore.serverTools.length === 0 && !toolsStore.loading) { + // Ensure server tools are fetched before checking if agentic is enabled. + // A disabled /tools endpoint stays disabled for the life of the server, + // so the tools panel is the only place that probes it again. + if ( + toolsStore.serverTools.length === 0 && + !toolsStore.loading && + !toolsStore.isToolsEndpointUnreachable + ) { await toolsStore.fetchServerTools(); } diff --git a/tools/ui/src/lib/stores/tools.svelte.ts b/tools/ui/src/lib/stores/tools.svelte.ts index db05e3cd55..1d4133408b 100644 --- a/tools/ui/src/lib/stores/tools.svelte.ts +++ b/tools/ui/src/lib/stores/tools.svelte.ts @@ -32,7 +32,7 @@ import { mcpStore } from '$lib/stores/mcp/index.svelte'; import { modelsStore } from '$lib/stores/models/index.svelte'; import { settingsStore } from '$lib/stores/settings/index.svelte'; import type { OpenAIToolDefinition, ToolEntry, ToolGroup } from '$lib/types'; -import { buildSandboxToolDefinition } from '$lib/utils'; +import { ApiError, buildSandboxToolDefinition } from '$lib/utils'; import { SvelteMap, SvelteSet } from 'svelte/reactivity'; /** Stable selection identity for a tool, shared by the disabled set and the permission store */ @@ -246,13 +246,10 @@ class ToolsStore { toolInfos.filter((info) => info.uses_cwd).map((info) => info.tool) ); } catch (err) { - const errorMessage = err instanceof Error ? err.message : String(err); - - this._error = errorMessage; + this._error = err instanceof Error ? err.message : String(err); // 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')) { + if (err instanceof ApiError && err.status === 403) { this._toolsEndpointUnreachable = true; console.info('[ToolsStore] Server tools are disabled on the server'); } else {