Skip to content

Commit 45e45ad

Browse files
Merge pull request #728 from Kiln-AI/dchiang/KIL-130/archive-tool-server
Add ability to archive tool server instead of deleting
2 parents 250f5b0 + 07b5cc4 commit 45e45ad

File tree

11 files changed

+394
-38
lines changed

11 files changed

+394
-38
lines changed

app/desktop/studio_server/test_tool_api.py

Lines changed: 101 additions & 7 deletions
Large diffs are not rendered by default.

app/desktop/studio_server/tool_api.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ class ExternalToolServerCreationRequest(BaseModel):
6363
server_url: str
6464
headers: Dict[str, str] = Field(default_factory=dict)
6565
secret_header_keys: List[str] = Field(default_factory=list)
66+
is_archived: bool
6667

6768

6869
class LocalToolServerCreationRequest(BaseModel):
@@ -72,6 +73,7 @@ class LocalToolServerCreationRequest(BaseModel):
7273
args: List[str]
7374
env_vars: Dict[str, str] = Field(default_factory=dict)
7475
secret_env_var_keys: List[str] = Field(default_factory=list)
76+
is_archived: bool
7577

7678

7779
class KilnTaskToolServerCreationRequest(BaseModel):
@@ -247,6 +249,9 @@ async def get_available_tools(
247249
task_tools = []
248250
mcp_tool_sets = []
249251
for server in project.external_tool_servers(readonly=True):
252+
if server.properties.get("is_archived", False):
253+
continue
254+
250255
server_tools = []
251256
match server.type:
252257
case ToolServerType.remote_mcp | ToolServerType.local_mcp:
@@ -256,14 +261,13 @@ async def get_available_tools(
256261
# Skip the tool when we can't connect to the server
257262
continue
258263
case ToolServerType.kiln_task:
259-
if not server.properties.get("is_archived", False):
260-
task_tools.append(
261-
ToolApiDescription(
262-
id=build_kiln_task_tool_id(server.id),
263-
name=server.properties.get("name") or "",
264-
description=server.properties.get("description") or "",
265-
)
264+
task_tools.append(
265+
ToolApiDescription(
266+
id=build_kiln_task_tool_id(server.id),
267+
name=server.properties.get("name") or "",
268+
description=server.properties.get("description") or "",
266269
)
270+
)
267271
case _:
268272
raise_exhaustive_enum_error(server.type)
269273

@@ -342,6 +346,9 @@ async def get_available_tool_servers(
342346
is_archived=tool.properties.get("is_archived", False),
343347
)
344348
)
349+
350+
# Sort the result and put archived tools at the end
351+
results.sort(key=lambda x: x.is_archived)
345352
return results
346353

347354
@app.get("/api/projects/{project_id}/kiln_task_tools")
@@ -492,6 +499,7 @@ def _remote_tool_server_properties(
492499
"server_url": tool_data.server_url,
493500
"headers": tool_data.headers,
494501
"secret_header_keys": tool_data.secret_header_keys,
502+
"is_archived": tool_data.is_archived,
495503
}
496504

497505
@app.post("/api/projects/{project_id}/connect_local_mcp")
@@ -551,6 +559,7 @@ def _local_tool_server_properties(
551559
"args": tool_data.args,
552560
"env_vars": tool_data.env_vars,
553561
"secret_env_var_keys": tool_data.secret_env_var_keys,
562+
"is_archived": tool_data.is_archived,
554563
}
555564

556565
def _validate_kiln_task_tool_task_and_run_config(

app/web_ui/src/lib/api_schema.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3246,6 +3246,8 @@ export interface components {
32463246
};
32473247
/** Secret Header Keys */
32483248
secret_header_keys?: string[];
3249+
/** Is Archived */
3250+
is_archived: boolean;
32493251
};
32503252
/** ExtractionProgress */
32513253
ExtractionProgress: {
@@ -3776,6 +3778,8 @@ export interface components {
37763778
};
37773779
/** Secret Env Var Keys */
37783780
secret_env_var_keys?: string[];
3781+
/** Is Archived */
3782+
is_archived: boolean;
37793783
};
37803784
/** LocalToolServerCreationRequest */
37813785
LocalToolServerCreationRequest: {
@@ -3793,6 +3797,8 @@ export interface components {
37933797
};
37943798
/** Secret Env Var Keys */
37953799
secret_env_var_keys?: string[];
3800+
/** Is Archived */
3801+
is_archived: boolean;
37963802
};
37973803
/** LogMessage */
37983804
LogMessage: {
@@ -4340,6 +4346,8 @@ export interface components {
43404346
};
43414347
/** Secret Header Keys */
43424348
secret_header_keys?: string[];
4349+
/** Is Archived */
4350+
is_archived: boolean;
43434351
};
43444352
/** RepairRunPost */
43454353
RepairRunPost: {

app/web_ui/src/routes/(app)/settings/manage_tools/[project_id]/add_tools/local_mcp/edit_local_tool.svelte

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
let env_vars: McpServerKeyValuePair[] = []
2727
let description = ""
2828
let installation_instruction = ""
29+
let is_archived = false
2930
// Form state
3031
let error: KilnError | null = null
3132
let submitting = false
@@ -70,6 +71,9 @@
7071
7172
// We should fix the server type, so it's not "never" and we don't need a cast
7273
const props = editing_tool_server.properties as Record<string, unknown>
74+
if (typeof props.is_archived === "boolean") {
75+
is_archived = props.is_archived
76+
}
7377
7478
if (props.command && typeof props.command === "string") {
7579
command = props.command
@@ -181,6 +185,7 @@
181185
args: args.trim() ? args.trim().split(/\s+/) : [], // Split into argv list; empty -> []
182186
env_vars: envVarsData.envVarsObj,
183187
secret_env_var_keys: envVarsData.secret_env_var_keys,
188+
is_archived: is_archived,
184189
}
185190
186191
let server_id: string | null | undefined = undefined

app/web_ui/src/routes/(app)/settings/manage_tools/[project_id]/add_tools/remote_mcp/edit_remote_tool.svelte

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
let name = ""
2222
let server_url = ""
2323
let description = ""
24+
let is_archived = false
2425
2526
let headers: McpServerKeyValuePair[] = []
2627
@@ -58,6 +59,9 @@
5859
5960
// We should fix the server type, so it's not "never" and we don't need a cast
6061
const props = editing_tool_server.properties as Record<string, unknown>
62+
if (typeof props.is_archived === "boolean") {
63+
is_archived = props.is_archived
64+
}
6165
6266
if (props.server_url && typeof props.server_url === "string") {
6367
server_url = props.server_url
@@ -150,6 +154,7 @@
150154
headers: headersData.headersObj,
151155
secret_header_keys: headersData.secret_header_keys,
152156
description: description || null,
157+
is_archived: is_archived,
153158
}
154159
155160
let server_id: string | null | undefined = undefined

app/web_ui/src/routes/(app)/settings/manage_tools/[project_id]/tool_servers/[tool_server_id]/+page.svelte

Lines changed: 126 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,19 @@
1212
isToolType,
1313
} from "$lib/types"
1414
import { toolServerTypeToString } from "$lib/utils/formatters"
15-
import DeleteDialog from "$lib/ui/delete_dialog.svelte"
1615
import type { UiProperty } from "$lib/ui/property_list"
17-
import { uncache_available_tools } from "$lib/stores"
16+
import { load_available_tools } from "$lib/stores"
17+
import Warning from "$lib/ui/warning.svelte"
1818
1919
$: project_id = $page.params.project_id
2020
$: tool_server_id = $page.params.tool_server_id
21+
$: is_archived = tool_server?.properties?.is_archived ?? false
2122
2223
let tool_server: ExternalToolServerApiDescription | null = null
2324
let loading = true
24-
let error: KilnError | null = null
25-
let delete_dialog: DeleteDialog | null = null
26-
$: delete_url = `/api/projects/${project_id}/tool_servers/${tool_server_id}`
25+
let loading_error: KilnError | null = null
26+
let archive_error: KilnError | null = null
27+
let unarchive_error: KilnError | null = null
2728
2829
onMount(async () => {
2930
await fetch_tool_server()
@@ -32,7 +33,7 @@
3233
async function fetch_tool_server() {
3334
try {
3435
loading = true
35-
error = null
36+
loading_error = null
3637
3738
if (!project_id) {
3839
throw new Error("No project ID provided")
@@ -61,7 +62,7 @@
6162
6263
tool_server = data as ExternalToolServerApiDescription
6364
} catch (err) {
64-
error = createKilnError(err)
65+
loading_error = createKilnError(err)
6566
} finally {
6667
loading = false
6768
}
@@ -249,11 +250,95 @@
249250
return args
250251
}
251252
252-
function afterDelete() {
253-
// Delete the project_id from the available_tools, so next reload it loads the updated list.
254-
uncache_available_tools(project_id)
253+
async function archive() {
254+
update_archive(true)
255+
}
255256
256-
goto(`/settings/manage_tools/${project_id}`)
257+
async function unarchive() {
258+
update_archive(false)
259+
}
260+
261+
async function update_archive(is_archived: boolean) {
262+
if (!tool_server) {
263+
return
264+
}
265+
266+
try {
267+
archive_error = null
268+
unarchive_error = null
269+
270+
switch (tool_server.type) {
271+
case "remote_mcp": {
272+
toolIsType(tool_server, tool_server.type)
273+
await client.PATCH(
274+
"/api/projects/{project_id}/edit_remote_mcp/{tool_server_id}",
275+
{
276+
params: {
277+
path: {
278+
project_id,
279+
tool_server_id,
280+
},
281+
},
282+
body: {
283+
name: tool_server.name,
284+
description: tool_server.description ?? null,
285+
server_url: tool_server.properties.server_url,
286+
headers: tool_server.properties.headers || {},
287+
secret_header_keys:
288+
tool_server.properties.secret_header_keys || [],
289+
is_archived: is_archived,
290+
},
291+
},
292+
)
293+
break
294+
}
295+
case "local_mcp": {
296+
toolIsType(tool_server, tool_server.type)
297+
await client.PATCH(
298+
"/api/projects/{project_id}/edit_local_mcp/{tool_server_id}",
299+
{
300+
params: {
301+
path: {
302+
project_id,
303+
tool_server_id,
304+
},
305+
},
306+
body: {
307+
name: tool_server.name,
308+
description: tool_server.description ?? null,
309+
command: tool_server.properties.command,
310+
args: tool_server.properties.args || [],
311+
env_vars: tool_server.properties.env_vars || {},
312+
secret_env_var_keys:
313+
tool_server.properties.secret_env_var_keys || [],
314+
is_archived: is_archived,
315+
},
316+
},
317+
)
318+
break
319+
}
320+
case "kiln_task": {
321+
// Kiln task tools is handled by /settings/manage_tools/[project_id]/kiln_task/[tool_server_id] page
322+
break
323+
}
324+
default: {
325+
const exhaustiveCheck: never = tool_server.type
326+
console.warn(`Unhandled toolType: ${exhaustiveCheck}`)
327+
break
328+
}
329+
}
330+
} catch (e) {
331+
if (is_archived) {
332+
archive_error = createKilnError(e)
333+
} else {
334+
unarchive_error = createKilnError(e)
335+
}
336+
} finally {
337+
fetch_tool_server()
338+
if (project_id) {
339+
load_available_tools(project_id, true)
340+
}
341+
}
257342
}
258343
</script>
259344

@@ -277,22 +362,48 @@
277362
href: `/settings/manage_tools/${project_id}/edit_tool_server/${tool_server?.id}`,
278363
},
279364
{
280-
icon: "/images/delete.svg",
281-
handler: () => delete_dialog?.show(),
365+
label: is_archived ? "Unarchive" : "Archive",
366+
handler: is_archived ? unarchive : archive,
282367
},
283368
]}
284369
>
370+
{#if archive_error}
371+
<Warning
372+
warning_message={archive_error.getMessage() ||
373+
"An unknown error occurred"}
374+
large_icon={true}
375+
warning_color="error"
376+
outline={true}
377+
/>
378+
{/if}
379+
{#if unarchive_error}
380+
<Warning
381+
warning_message={unarchive_error.getMessage() ||
382+
"An unknown error occurred"}
383+
large_icon={true}
384+
warning_color="error"
385+
outline={true}
386+
/>
387+
{/if}
388+
{#if is_archived}
389+
<Warning
390+
warning_message="This tool server is archived. You may unarchive it to use it again."
391+
large_icon={true}
392+
warning_color="warning"
393+
outline={true}
394+
/>
395+
{/if}
285396
{#if loading}
286397
<div class="w-full min-h-[50vh] flex justify-center items-center">
287398
<div class="loading loading-spinner loading-lg"></div>
288399
</div>
289-
{:else if error}
400+
{:else if loading_error}
290401
<div
291402
class="w-full min-h-[50vh] flex flex-col justify-center items-center gap-2"
292403
>
293404
<div class="font-medium">Error Loading Tool</div>
294405
<div class="text-error text-sm">
295-
{error.getMessage() || "An unknown error occurred"}
406+
{loading_error.getMessage() || "An unknown error occurred"}
296407
</div>
297408
<button class="btn btn-primary mt-4" on:click={goBack}>
298409
Back to Tools
@@ -418,10 +529,3 @@
418529
{/if}
419530
</AppPage>
420531
</div>
421-
422-
<DeleteDialog
423-
name="Tool Server"
424-
bind:this={delete_dialog}
425-
{delete_url}
426-
after_delete={afterDelete}
427-
/>

0 commit comments

Comments
 (0)