Skip to content

Conversation

@chiang-daniel
Copy link
Contributor

@chiang-daniel chiang-daniel commented Oct 14, 2025

What does this PR do?

Previously users can only delete a tool server, which is problematic when a tool server is used in many of the run-configs. This PR adds the ability to archive a tool server so user could easily re-enable one.

image

Checklists

  • Tests have been run locally and passed
  • New tests have been added to any work in /lib

Summary by CodeRabbit

  • New Features

    • Archive/unarchive tool servers (remote, local, MCP, Kiln task) instead of deleting.
    • Create/edit forms include an "archived" flag; listings hide archived servers and sort them toward the end.
    • UI shows archive status, contextual warnings, and updates available tools after changes.
  • Refactor

    • Settings page replaces Delete with Archive/Unarchive actions and dynamic labels.
    • Separate loading, archive, and unarchive error handling with automatic refresh.
  • Tests

    • Added tests covering archived-state handling and migration of older records.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 14, 2025

Walkthrough

Adds an is_archived boolean across datamodels, API request/response models, web UI forms/pages, and tests; replaces delete flows with archive/unarchive UI actions; and introduces a before-model validator to backfill missing is_archived in ExternalToolServer properties.

Changes

Cohort / File(s) Summary of Changes
Studio server API models & logic
app/desktop/studio_server/tool_api.py
Added is_archived: bool to creation request types and server descriptions; propagate/serialize is_archived on create/edit; sort/filter available-tool servers to place/exclude archived entries; adjust Kiln task inclusion rules.
Studio server tests
app/desktop/studio_server/test_tool_api.py
Updated fixtures and assertions to include is_archived across create/list/get/edit/archive flows; test payloads and mocks updated to include is_archived.
Web UI schema
app/web_ui/src/lib/api_schema.d.ts
Added is_archived: boolean to ExternalToolServerCreationRequest, LocalServerProperties, LocalToolServerCreationRequest, and RemoteServerProperties.
Web UI add/edit remote/local MCP
app/web_ui/src/routes/(app)/settings/manage_tools/[project_id]/add_tools/local_mcp/edit_local_tool.svelte, app/web_ui/src/routes/(app)/settings/manage_tools/[project_id]/add_tools/remote_mcp/edit_remote_tool.svelte
Added is_archived form state (default false), initialize from editing_tool_server.properties.is_archived when editing, and include it in create/edit request payloads.
Web UI tool server page
app/web_ui/src/routes/(app)/settings/manage_tools/[project_id]/tool_servers/[tool_server_id]/+page.svelte
Replaced delete dialog with archive/unarchive actions; added is_archived reactive flag, separate archive/unarchive error handling, PATCH logic per tool type, refetch on completion, and archive-state UI messaging.
Core datamodel
libs/core/kiln_ai/datamodel/external_tool_server.py
Added is_archived: bool to LocalServerProperties and RemoteServerProperties; added @model_validator(mode="before") upgrade_old_properties to populate missing is_archived defaulting to False.
Core datamodel tests
libs/core/kiln_ai/datamodel/test_external_tool_server.py
Added is_archived to fixtures; added tests verifying upgrade_old_properties adds default is_archived and preserves existing values; updated serializations/assertions.
Core tools tests
libs/core/kiln_ai/tools/test_mcp_server_tool.py, libs/core/kiln_ai/tools/test_mcp_session_manager.py, libs/core/kiln_ai/tools/test_tool_registry.py
Appended is_archived: False to mocked ExternalToolServer.properties across many tests; no control-flow changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant UI as Tool Server Page
  participant API as Studio Server API
  participant Model as ExternalToolServer

  User->>UI: Click "Archive" / "Unarchive"
  UI->>API: PATCH /tool_servers/{id} { "is_archived": true|false }
  API->>Model: Persist/update properties
  Note over Model: @model_validator(mode="before") ensures properties.has(is_archived) (default false)
  Model-->>API: Updated server object
  API-->>UI: 200 OK
  UI->>API: GET /tool_servers/{id} && GET /available_tools
  API->>API: Build available tools list
  alt Exclude archived
    API->>API: Filter out servers where is_archived == true
  end
  API-->>UI: Refreshed data
  UI-->>User: Show updated archive state
Loading
sequenceDiagram
  autonumber
  participant Client as Web UI Form
  participant API as Studio Server API

  Client->>Client: fill form (name, desc, ..., is_archived)
  Client->>API: POST/PUT with is_archived in payload
  API->>API: Validate/serialize payload (is_archived -> properties)
  API-->>Client: 201/200 created/updated
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • scosman
  • sfierro

Poem

A rabbit nudges code to sleep, a tiny flag to hide or keep.
Tucked tools rest beneath the sod, archived neat like buried plod.
Hop—restore with whiskered cheer, and watch the tests all reappear. 🥕🐇

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description Check ⚠️ Warning The PR description includes a comprehensive "What does this PR do?" section that clearly explains the feature (ability to archive tool servers instead of deleting them) with supporting visual evidence (screenshot). Both checklist items are marked as completed. However, the description is missing two required sections from the template: the "Related Issues" section for linking related issues, and critically, the "Contributor License Agreement" section that requires the author to confirm their acceptance of the CLA with their GitHub username. While the main descriptive content is strong, the absence of the CLA confirmation represents a gap in the required template structure. The author should add the missing sections to the PR description. Specifically, they need to: (1) add a "## Related Issues" section that links to any related GitHub issues (such as KIL-130 mentioned in the PR objectives), and (2) add the "## Contributor License Agreement" section with a statement confirming they have read and agree to the CLA, including their GitHub username (@chiang-daniel). The "What does this PR do?" and Checklists sections are well-completed and do not require changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly and concisely describes the primary change of adding the ability to archive a tool server instead of deleting it, accurately reflecting the main focus of the pull request.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dchiang/KIL-130/archive-tool-server

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Oct 14, 2025

📊 Coverage Report

Overall Coverage: 92%

Diff: origin/main...HEAD

  • app/desktop/studio_server/tool_api.py (100%)
  • libs/core/kiln_ai/datamodel/external_tool_server.py (100%)

Summary

  • Total: 15 lines
  • Missing: 0 lines
  • Coverage: 100%

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
libs/core/kiln_ai/datamodel/external_tool_server.py (1)

28-34: Mark is_archived as NotRequired[bool] to match runtime behavior.

The TypedDicts declare is_archived: bool without NotRequired, which means it's type-checked as a required field (due to total=True). However, the upgrade_old_properties validator backfills this field when missing, making it effectively optional at runtime. This inconsistency can cause type-checking errors when creating these properties without is_archived and may confuse developers about whether the field is required.

Apply this diff to align the type declarations with the runtime behavior:

 class LocalServerProperties(TypedDict, total=True):
     command: str
     args: NotRequired[list[str]]
     env_vars: NotRequired[dict[str, str]]
     secret_env_var_keys: NotRequired[list[str]]
-    is_archived: bool
+    is_archived: NotRequired[bool]


 class RemoteServerProperties(TypedDict, total=True):
     server_url: str
     headers: NotRequired[dict[str, str]]
     secret_header_keys: NotRequired[list[str]]
-    is_archived: bool
+    is_archived: NotRequired[bool]

Also applies to: 36-41

app/desktop/studio_server/tool_api.py (1)

60-85: Make is_archived optional with default False to avoid breaking clients

Requiring is_archived in all creation/edit payloads is a breaking change. Default it to False in all three request models.

 class ExternalToolServerCreationRequest(BaseModel):
   name: str
   description: str | None = None
   server_url: str
-  headers: Dict[str, str] = Field(default_factory=dict)
-  secret_header_keys: List[str] = Field(default_factory=list)
-  is_archived: bool
+  headers: Dict[str, str] = Field(default_factory=dict)
+  secret_header_keys: List[str] = Field(default_factory=list)
+  is_archived: bool = Field(default=False)

 class LocalToolServerCreationRequest(BaseModel):
   name: str
   description: str | None = None
   command: str
   args: List[str]
   env_vars: Dict[str, str] = Field(default_factory=dict)
-  secret_env_var_keys: List[str] = Field(default_factory=list)
-  is_archived: bool
+  secret_env_var_keys: List[str] = Field(default_factory=list)
+  is_archived: bool = Field(default=False)

 class KilnTaskToolServerCreationRequest(BaseModel):
   name: str
   description: str
   task_id: str
   run_config_id: str
-  is_archived: bool
+  is_archived: bool = Field(default=False)

As per coding guidelines (Pydantic v2, strong typing).

🧹 Nitpick comments (6)
app/web_ui/src/lib/api_schema.d.ts (1)

4331-4332: LGTM: archived state added to RemoteServerProperties

Matches Local/KilnTask properties. Optional: add a description on the backend model so OpenAPI surfaces helpful docs for this field here too.

app/desktop/studio_server/test_tool_api.py (1)

3566-3660: Add coverage for archived MCP servers in /available_tools

You test exclusion for RAG and Kiln Task. Add a case where an MCP server has is_archived: True and assert it’s excluded from MCP tool sets.

libs/core/kiln_ai/datamodel/test_external_tool_server.py (1)

1185-1189: Minor naming/docstring mismatch

Docstring says “upgrade_properties” but the validator is “upgrade_old_properties”. Consider aligning naming for clarity.

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

253-341: Prevent double-submit and await refresh after archive/unarchive

Add a simple guard and await data refresh to avoid racey UI states and accidental double clicks.

@@
-  async function archive() {
-    update_archive(true)
-  }
+  async function archive() {
+    await update_archive(true)
+  }
@@
-  async function unarchive() {
-    update_archive(false)
-  }
+  async function unarchive() {
+    await update_archive(false)
+  }
@@
-  async function update_archive(is_archived: boolean) {
-    if (!tool_server) {
-      return
-    }
+  let archiving = false
+  async function update_archive(is_archived: boolean) {
+    if (!tool_server || archiving) return
+    archiving = true
@@
-    } catch (e) {
+    } catch (e) {
       if (is_archived) {
         archive_error = createKilnError(e)
       } else {
         unarchive_error = createKilnError(e)
       }
-    } finally {
-      fetch_tool_server()
-      if (project_id) {
-        load_available_tools(project_id, true)
-      }
-    }
+    } finally {
+      await fetch_tool_server()
+      if (project_id) {
+        await Promise.resolve(load_available_tools(project_id, true))
+      }
+      archiving = false
+    }

365-367: Optional: disable button while archiving

If AppPage supports it, pass a disabled/isBusy flag tied to archiving to prevent repeated clicks.


422-455: Simplify type guards for readability (optional)

Instead of tool_server.type === "remote_mcp" && isToolType(tool_server, tool_server.type), consider isToolType(tool_server, "remote_mcp") (and similarly for local_mcp).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7772b79 and c44044d.

📒 Files selected for processing (11)
  • app/desktop/studio_server/test_tool_api.py (70 hunks)
  • app/desktop/studio_server/tool_api.py (6 hunks)
  • app/web_ui/src/lib/api_schema.d.ts (4 hunks)
  • app/web_ui/src/routes/(app)/settings/manage_tools/[project_id]/add_tools/local_mcp/edit_local_tool.svelte (1 hunks)
  • app/web_ui/src/routes/(app)/settings/manage_tools/[project_id]/add_tools/remote_mcp/edit_remote_tool.svelte (1 hunks)
  • app/web_ui/src/routes/(app)/settings/manage_tools/[project_id]/tool_servers/[tool_server_id]/+page.svelte (5 hunks)
  • libs/core/kiln_ai/datamodel/external_tool_server.py (2 hunks)
  • libs/core/kiln_ai/datamodel/test_external_tool_server.py (24 hunks)
  • libs/core/kiln_ai/tools/test_mcp_server_tool.py (14 hunks)
  • libs/core/kiln_ai/tools/test_mcp_session_manager.py (15 hunks)
  • libs/core/kiln_ai/tools/test_tool_registry.py (5 hunks)
🧰 Additional context used
📓 Path-based instructions (10)
app/web_ui/**/*.{ts,svelte}

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

app/web_ui/**/*.{ts,svelte}: Write frontend web code in TypeScript; do not add plain .js sources
From the web UI, make backend calls only to our FastAPI servers; do not call external services directly
Use TypeScript types and interfaces to maintain strong typing in the frontend

Files:

  • app/web_ui/src/routes/(app)/settings/manage_tools/[project_id]/add_tools/local_mcp/edit_local_tool.svelte
  • app/web_ui/src/routes/(app)/settings/manage_tools/[project_id]/add_tools/remote_mcp/edit_remote_tool.svelte
  • app/web_ui/src/lib/api_schema.d.ts
  • app/web_ui/src/routes/(app)/settings/manage_tools/[project_id]/tool_servers/[tool_server_id]/+page.svelte
app/web_ui/**/*.svelte

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

app/web_ui/**/*.svelte: Author components for Svelte v4 compatibility (do not use Svelte v5-only APIs)
Prefer Tailwind CSS and DaisyUI utility/classes for styling in Svelte components

Files:

  • app/web_ui/src/routes/(app)/settings/manage_tools/[project_id]/add_tools/local_mcp/edit_local_tool.svelte
  • app/web_ui/src/routes/(app)/settings/manage_tools/[project_id]/add_tools/remote_mcp/edit_remote_tool.svelte
  • app/web_ui/src/routes/(app)/settings/manage_tools/[project_id]/tool_servers/[tool_server_id]/+page.svelte
app/web_ui/**/*.{ts,tsx,svelte}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript for frontend code (including <script lang="ts"> in .svelte files)

Files:

  • app/web_ui/src/routes/(app)/settings/manage_tools/[project_id]/add_tools/local_mcp/edit_local_tool.svelte
  • app/web_ui/src/routes/(app)/settings/manage_tools/[project_id]/add_tools/remote_mcp/edit_remote_tool.svelte
  • app/web_ui/src/lib/api_schema.d.ts
  • app/web_ui/src/routes/(app)/settings/manage_tools/[project_id]/tool_servers/[tool_server_id]/+page.svelte
libs/{core,server}/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

Use Python 3.10+ for library and server code under libs/core and libs/server

Files:

  • libs/core/kiln_ai/tools/test_mcp_session_manager.py
  • libs/core/kiln_ai/datamodel/test_external_tool_server.py
  • libs/core/kiln_ai/tools/test_mcp_server_tool.py
  • libs/core/kiln_ai/tools/test_tool_registry.py
  • libs/core/kiln_ai/datamodel/external_tool_server.py
{libs,app/desktop}/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

{libs,app/desktop}/**/*.py: Use Pydantic v2 (not v1) in Python code that models/validates data
Use explicit type hints for functions, classes, and public APIs in Python code

Files:

  • libs/core/kiln_ai/tools/test_mcp_session_manager.py
  • libs/core/kiln_ai/datamodel/test_external_tool_server.py
  • app/desktop/studio_server/tool_api.py
  • libs/core/kiln_ai/tools/test_mcp_server_tool.py
  • app/desktop/studio_server/test_tool_api.py
  • libs/core/kiln_ai/tools/test_tool_registry.py
  • libs/core/kiln_ai/datamodel/external_tool_server.py
libs/core/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Python code in libs/core must be compatible with Python 3.10+

Files:

  • libs/core/kiln_ai/tools/test_mcp_session_manager.py
  • libs/core/kiln_ai/datamodel/test_external_tool_server.py
  • libs/core/kiln_ai/tools/test_mcp_server_tool.py
  • libs/core/kiln_ai/tools/test_tool_registry.py
  • libs/core/kiln_ai/datamodel/external_tool_server.py
{libs/core,libs/server,app/desktop}/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

{libs/core,libs/server,app/desktop}/**/*.py: Use Pydantic v2 APIs (e.g., BaseModel v2, model_validate/model_dump) and avoid v1-only patterns
Maintain strong typing in Python code (add type hints and run type checking)

Files:

  • libs/core/kiln_ai/tools/test_mcp_session_manager.py
  • libs/core/kiln_ai/datamodel/test_external_tool_server.py
  • app/desktop/studio_server/tool_api.py
  • libs/core/kiln_ai/tools/test_mcp_server_tool.py
  • app/desktop/studio_server/test_tool_api.py
  • libs/core/kiln_ai/tools/test_tool_registry.py
  • libs/core/kiln_ai/datamodel/external_tool_server.py
app/desktop/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

Use Python 3.13 for all desktop app Python code, including studio_server

Python code in app/desktop must target Python 3.13

Files:

  • app/desktop/studio_server/tool_api.py
  • app/desktop/studio_server/test_tool_api.py
{libs/server,app/desktop/studio_server}/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/project.mdc)

{libs/server,app/desktop/studio_server}/**/*.py: Implement REST APIs using FastAPI (no alternative web frameworks) in server code
Use asyncio-based patterns for async operations in backend servers

Files:

  • app/desktop/studio_server/tool_api.py
  • app/desktop/studio_server/test_tool_api.py
app/desktop/studio_server/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Extend the core server with FastAPI endpoints in app/desktop/studio_server

Files:

  • app/desktop/studio_server/tool_api.py
  • app/desktop/studio_server/test_tool_api.py
🧬 Code graph analysis (3)
libs/core/kiln_ai/datamodel/test_external_tool_server.py (1)
libs/core/kiln_ai/datamodel/external_tool_server.py (2)
  • ToolServerType (18-25)
  • ExternalToolServer (51-464)
app/desktop/studio_server/tool_api.py (2)
app/web_ui/src/lib/types.ts (1)
  • ToolApiDescription (79-79)
libs/core/kiln_ai/datamodel/tool_id.py (1)
  • build_kiln_task_tool_id (127-129)
libs/core/kiln_ai/datamodel/external_tool_server.py (1)
app/web_ui/src/lib/types.ts (1)
  • RemoteServerProperties (84-85)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Generate Coverage Report
🔇 Additional comments (11)
libs/core/kiln_ai/datamodel/external_tool_server.py (1)

217-227: Good backwards compatibility approach.

The validator correctly backfills is_archived when missing, enabling smooth migration for existing data. The mode="before" ensures it runs before other validators that might expect this field, and the None check prevents errors when properties is absent.

Note: This validator makes is_archived effectively optional, so consider marking it as NotRequired[bool] in the TypedDict definitions (see separate comment) for type-checking consistency.

app/web_ui/src/lib/api_schema.d.ts (3)

3763-3764: LGTM: archived state included in LocalServerProperties

Consistent with KilnTaskServerProperties. Ensure the tool-server detail view reads archived state from properties.is_archived since ExternalToolServerApiDescription has no top-level field.


3230-3232: UI request bodies include is_archived — no change needed
All connect/edit flows set is_archived (default false on create, passed through on update).


3782-3783: is_archived included in both connect_local_mcp and edit_local_mcp calls
All POST and PATCH payloads in edit_local_tool.svelte and +page.svelte include an is_archived property.

app/desktop/studio_server/tool_api.py (2)

248-271: Filtering archived servers and always including active Kiln tasks looks good

  • Skipping archived servers in available_tools is correct.
  • Always appending KilnTask tools after the archive check is consistent.

495-501: Correct propagation of archive state to server properties

Including is_archived in properties for remote/local MCP and Kiln task servers is correct and consistent with listing/description usage.

Also applies to: 555-561, 593-601

libs/core/kiln_ai/tools/test_mcp_server_tool.py (1)

31-35: Test fixtures updated consistently with is_archived

Looks good and consistent across test cases.

Also applies to: 64-68, 94-98, 120-124, 174-178, 200-204, 236-239, 370-373, 420-423

libs/core/kiln_ai/tools/test_tool_registry.py (1)

92-98: Test fixtures aligned with archive flag

Consistent additions; no further changes needed.

Also applies to: 124-130, 165-169, 616-620, 741-743

libs/core/kiln_ai/tools/test_mcp_session_manager.py (1)

35-36: Consistent inclusion of is_archived across fixtures — LGTM

The additions keep tests aligned with the new model shape and backfill validator. No issues.

Would we want MCPSessionManager to actively prevent creating sessions for archived servers (defense-in-depth)? If yes, I can add a small test asserting a ValueError when properties.is_archived is True.

Also applies to: 56-57, 562-563, 621-622, 667-668, 711-713, 770-771, 855-856, 949-951, 989-990, 1004-1005, 1150-1151, 1221-1222, 1524-1525, 1550-1551

libs/core/kiln_ai/datamodel/test_external_tool_server.py (1)

32-35: is_archived defaults/backfill in test data — LGTM

Fixtures and assertions uniformly include is_archived. Backfill coverage looks good.

Also applies to: 57-58, 66-68, 88-90, 98-99, 123-124, 167-168, 187-188, 219-220, 247-248, 278-279, 295-296, 360-361, 388-389, 546-547, 571-572, 617-618, 670-671, 765-766, 792-793, 1066-1067, 1013-1014, 1029-1030

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

261-292: Confirm backend accepts is_archived in edit_remote_mcp/edit_local_mcp PATCH bodies

Confirmed — both endpoints accept request models with is_archived and apply tool_data.is_archived to saved properties; API schema and tests also include is_archived.

@chiang-daniel chiang-daniel merged commit 45e45ad into main Oct 17, 2025
17 checks passed
@chiang-daniel chiang-daniel deleted the dchiang/KIL-130/archive-tool-server branch October 17, 2025 17:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants