diff --git a/tools/server/server-tools.cpp b/tools/server/server-tools.cpp
index eacfbf0f7..d5e696434 100644
--- a/tools/server/server-tools.cpp
+++ b/tools/server/server-tools.cpp
@@ -74,6 +74,7 @@ json server_tool::to_json() const {
{"permissions", json{
{"write", permission_write}
}},
+ {"uses_cwd", uses_cwd},
{"definition", get_definition()},
};
}
@@ -763,6 +764,7 @@ struct server_tool_read_file : server_tool {
server_tool_read_file() {
name = "read_file";
display_name = "Read file";
+ uses_cwd = true;
permission_write = false;
}
@@ -851,6 +853,7 @@ struct server_tool_file_glob_search : server_tool {
server_tool_file_glob_search() {
name = "file_glob_search";
display_name = "File search";
+ uses_cwd = true;
permission_write = false;
}
@@ -965,6 +968,7 @@ struct server_tool_grep_search : server_tool {
server_tool_grep_search() {
name = "grep_search";
display_name = "Grep search";
+ uses_cwd = true;
permission_write = false;
}
@@ -1117,6 +1121,7 @@ struct server_tool_exec_shell_command : server_tool {
server_tool_exec_shell_command() {
name = "exec_shell_command";
display_name = "Execute shell command";
+ uses_cwd = true;
permission_write = true;
support_stream = true;
}
@@ -1195,6 +1200,7 @@ struct server_tool_write_file : server_tool {
server_tool_write_file() {
name = "write_file";
display_name = "Write file";
+ uses_cwd = true;
permission_write = true;
}
@@ -1237,6 +1243,7 @@ struct server_tool_edit_file : server_tool {
server_tool_edit_file() {
name = "edit_file";
display_name = "Edit file";
+ uses_cwd = true;
permission_write = true;
}
@@ -1625,6 +1632,7 @@ struct server_tool_get_info : server_tool {
server_tool_get_info() {
name = "get_info";
display_name = "Get Runtime Info";
+ uses_cwd = true;
permission_write = false;
}
diff --git a/tools/server/server-tools.h b/tools/server/server-tools.h
index 7f70e6767..ede303181 100644
--- a/tools/server/server-tools.h
+++ b/tools/server/server-tools.h
@@ -14,6 +14,7 @@ struct server_tool {
std::string display_name;
bool permission_write = false;
bool support_stream = false; // if true, output can be streamed
+ bool uses_cwd = false; // if true, the tool resolves paths and runs against the working directory
virtual ~server_tool() = default;
virtual json get_definition() const = 0;
diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatForm.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatForm.svelte
index 1df125708..2d70c302c 100644
--- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatForm.svelte
+++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatForm.svelte
@@ -156,7 +156,7 @@
focusInput: refocusInput,
getShowModelSelector: () => showModelSelector,
hasPrompts: () => mcpStore.hasPromptsCapability(conversationsStore.getAllMcpServerOverrides()),
- hasBuiltinTools: () => toolsStore.builtinTools.length > 0,
+ hasCwdTools: () => toolsStore.hasEnabledCwdTools,
getCwd: () => cwd,
getServerHome: () => toolsStore.serverHome ?? null,
openModelSelector: () => chatFormActionsRef?.openModelSelector(),
@@ -651,7 +651,7 @@
- {#if toolsStore.builtinTools.length > 0}
+ {#if toolsStore.hasEnabledCwdTools}
boolean;
/** Gates `/cwd`. */
- hasBuiltinTools: () => boolean;
+ hasCwdTools: () => boolean;
}
/**
@@ -32,7 +32,7 @@ export function getChatCommands(options: ChatCommandsOptions): ChatFormCommand[]
description: SET_WORKING_DIRECTORY_LABEL,
keywords: ['current working directory'],
action: ChatFormCommandAction.CWD,
- disabled: !options.hasBuiltinTools()
+ disabled: !options.hasCwdTools()
},
{
name: 'model',
diff --git a/tools/ui/src/lib/hooks/use-chat-form-pickers.svelte.ts b/tools/ui/src/lib/hooks/use-chat-form-pickers.svelte.ts
index f3bc5f732..4860f16fb 100644
--- a/tools/ui/src/lib/hooks/use-chat-form-pickers.svelte.ts
+++ b/tools/ui/src/lib/hooks/use-chat-form-pickers.svelte.ts
@@ -24,7 +24,7 @@ export interface UseChatFormPickersOptions {
/** Gates `/prompt`. */
hasPrompts: () => boolean;
/** Gates `/cwd`. */
- hasBuiltinTools: () => boolean;
+ hasCwdTools: () => boolean;
getCwd: () => string | null;
/** Mention search fallback scope. */
getServerHome: () => string | null;
@@ -63,7 +63,7 @@ export function useChatFormPickers(opts: UseChatFormPickersOptions) {
getChatCommands({
showModelSelector: opts.getShowModelSelector(),
hasPrompts: opts.hasPrompts,
- hasBuiltinTools: opts.hasBuiltinTools
+ hasCwdTools: opts.hasCwdTools
})
);
diff --git a/tools/ui/src/lib/stores/tools.svelte.ts b/tools/ui/src/lib/stores/tools.svelte.ts
index 5136101e7..4114ef756 100644
--- a/tools/ui/src/lib/stores/tools.svelte.ts
+++ b/tools/ui/src/lib/stores/tools.svelte.ts
@@ -27,6 +27,9 @@ class ToolsStore {
private _loading = $state(false);
private _error = $state(null);
private _disabledTools = $state(new SvelteSet());
+ // builtin tools that resolve their paths against the working directory,
+ // as declared by the server in its `/tools` listing
+ private _cwdAwareTools = $state(new SvelteSet());
private _toolsEndpointUnreachable = $state(false);
private _serverHome = $state(undefined);
@@ -476,6 +479,21 @@ class ToolsStore {
return this.getEnabledToolsForLLM().length > 0;
}
+ /**
+ * Check if a working directory is worth setting: at least one builtin tool
+ * that reads it is both served and left enabled by the user.
+ */
+ get hasEnabledCwdTools(): boolean {
+ return this._builtinTools.some((def) => {
+ const name = def.function.name;
+
+ return (
+ this._cwdAwareTools.has(name) &&
+ !this._disabledTools.has(this.toolKey(ToolSource.BUILTIN, name))
+ );
+ });
+ }
+
async fetchBuiltinTools(): Promise {
if (this._loading) return;
@@ -486,6 +504,9 @@ class ToolsStore {
try {
const toolInfos = await ToolsService.list();
this._builtinTools = toolInfos.map((info) => info.definition);
+ this._cwdAwareTools = new SvelteSet(
+ toolInfos.filter((info) => info.uses_cwd).map((info) => info.tool)
+ );
} catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err);
this._error = errorMessage;
diff --git a/tools/ui/src/lib/types/mcp.d.ts b/tools/ui/src/lib/types/mcp.d.ts
index b567c20c9..2b5937913 100644
--- a/tools/ui/src/lib/types/mcp.d.ts
+++ b/tools/ui/src/lib/types/mcp.d.ts
@@ -292,6 +292,7 @@ export interface ServerBuiltinToolInfo {
permissions: {
write: boolean;
};
+ uses_cwd: boolean;
definition: OpenAIToolDefinition;
}
diff --git a/tools/ui/tests/client/components/ChatFormPickersHarness.svelte b/tools/ui/tests/client/components/ChatFormPickersHarness.svelte
index 76b2e9fe3..8e5e56c20 100644
--- a/tools/ui/tests/client/components/ChatFormPickersHarness.svelte
+++ b/tools/ui/tests/client/components/ChatFormPickersHarness.svelte
@@ -21,7 +21,7 @@
focusInput: () => {},
getShowModelSelector: () => true,
hasPrompts: () => true,
- hasBuiltinTools: () => true,
+ hasCwdTools: () => true,
getCwd: () => null,
getServerHome: () => null,
openModelSelector: () => {