Skip to content

🤖 feat: add 'Manage MCP servers' modal on chat creation#3364

Open
Neppkun wants to merge 1 commit into
coder:mainfrom
Neppkun:manage_mcp_on_chat_creation
Open

🤖 feat: add 'Manage MCP servers' modal on chat creation#3364
Neppkun wants to merge 1 commit into
coder:mainfrom
Neppkun:manage_mcp_on_chat_creation

Conversation

@Neppkun
Copy link
Copy Markdown

@Neppkun Neppkun commented May 22, 2026

Summary

Replaces the "Add MCP server" button in the MCP Servers box on the project's new-chat page with "Manage MCP servers", which opens a modal where the user can toggle each MCP server on/off for the workspace this chat will create and add new MCP servers — without detouring to global Settings.

Demo

https://owo.whats-th.is/3DqYvnr.mp4

Background

Today the button on the creation page only links into the global Settings → MCP panel, which is awkward when the user knows up-front they want a different set of servers for the next workspace (e.g. opt into a heavyweight server only for one chat, or shut a noisy one off for a single task). The backend already supports per-workspace overrides at <workspace>/.mux/mcp.local.jsonc via api.workspace.mcp.{get,set} (the WorkspaceMCPOverrides schema in src/common/orpc/schemas/mcp.ts), and the existing WorkspaceMCPModal exposes that for already-created workspaces. This PR brings the same affordance to the moment of creation by staging the overrides client-side and applying them right after api.workspace.create returns.

Implementation

Creation-page UX

ProjectMCPOverview now:

  • Renders "Manage MCP servers" (with a SlidersHorizontal icon).
  • Computes the enabled count via a new shared effectiveEnabledServerNames helper, so the count reflects staged overrides.
  • Shows "(modified for this chat)" next to the count when any override is staged.
  • Opens a new WorkspaceMCPModal instance in mode="draft".

Dual-mode WorkspaceMCPModal

The existing modal becomes a discriminated-union dual-mode component:

  • mode: "workspace" (default, unchanged for WorkspaceMenuBar / WorkspaceActionsMenuContent): still loads via api.workspace.mcp.get, persists via api.workspace.mcp.set, still shows the per-server tool-allowlist UI.
  • mode: "draft" (new): takes initialOverrides + onSave(overrides) callback. Skips the live get/save (the parent stages the result). Hides the tool-allowlist UI (out of scope here — workspace-mode keeps it). Embeds the new MCPAddServerForm inline so the user can add a server without leaving the modal.

Staged-then-applied flow

ProjectPage          stagedOverridesByDraft: Map<draftKey, WorkspaceMCPOverrides>
  │
  ▼ stagedOverrides + onStagedOverridesChange
ProjectMCPOverview ──► WorkspaceMCPModal (mode="draft")
  │
  ▼ stagedMcpOverrides (new prop)
ChatInput → useCreationWorkspace
  │
  ▼ after createResult.success
api.workspace.mcp.set({ workspaceId: metadata.id, overrides })   // fire-and-forget

The mcp.set call is intentionally fire-and-forget: remote runtimes (SSH/Coder) can make the override write slow, and the user expects their first prompt to start streaming immediately. On failure we console.warn; the workspace simply uses project defaults until the user re-applies via the post-creation modal.

The staged map is keyed by pendingDraftId ?? "__pending__" (matches ChatInput's draft scoping) and is reset on projectPath change so drafts can't leak across projects.

Refactors

  • MCPAddServerForm (new component): extracted verbatim from MCPSettingsSection so the new modal and Settings → MCP share one Add-server surface (transport policy clamp, headers editor, Test, OAuth callout, post-add remote test, etc.).
  • MCPOAuth.tsx (new): OAuth helpers + MCPOAuthRequiredCallout moved out of MCPSettingsSection so MCPAddServerForm can use them without creating a circular import.
  • workspaceMcpEffective.ts (new shared helper, 14 unit tests): centralizes the three-way precedence rule (enabledServers > disabledServers > project disabled) so the modal toggles and the overview count can never disagree. Also exports toggleServerOverride and hasAnyOverride.
  • MCPSettingsSection.tsx: dropped from 1614 → 882 lines after pulling out the shared form/helpers. No behavior change for Settings users.

Validation

  • make typecheck — green
  • make lint — green
  • New helper: bun test src/common/utils/workspaceMcpEffective.test.ts — 14/14 pass (3-way rule, toggle preserves allowlist + doesn't dup, hasAnyOverride edge cases)
  • Manual smoke against make dev-server-sandbox: button copy/icon, staged-modified count, save persists via api.workspace.mcp.set after creation (verified by reopening the post-creation WorkspaceMCPModal), add-server flow inside the modal, Settings → MCP unchanged for stdio + http + OAuth servers.

Risks

Moderate, contained mostly to the creation/MCP surface:

  • WorkspaceMCPModal refactor: the existing workspace-mode call sites (WorkspaceMenuBar, WorkspaceActionsMenuContent) keep the same prop shape; the new optional mode discriminator defaults to "workspace" so untouched call sites behave identically. Tool-allowlist UI is only hidden in draft mode.
  • MCPSettingsSection extraction: largest mechanical risk — the inline <details>Add server</details> JSX, headers editor wiring, OAuth callout, and post-add remote test were moved to MCPAddServerForm byte-for-byte. Settings now consumes that component; no public oRPC calls changed.
  • useCreationWorkspace post-create call: gated behind hasAnyOverride(stagedMcpOverrides) so creation paths that don't touch the new UI emit zero extra IPC. The call is void-ed so it cannot reject into the workspace-creation try/catch.
  • Per-draft state: scoped both by projectPath (reset effect) and pendingDraftId ?? "__pending__" to mirror existing draft semantics.

Pains

  • The Storybook story file for WorkspaceMCPModal types its meta against the component's discriminated-union prop type; switching that union forced a narrow cast (WorkspaceMCPModal as ComponentType<WorkspaceMCPModalWorkspaceProps>) plus an exported workspace-mode props alias so the stories' StoryObj<typeof meta> doesn't resolve to never. Documented inline with a comment.

Generated with mux • Model: anthropic:claude-opus-4-7 • Thinking: high • Cost: $20.69

Replace the 'Add MCP server' button on the project's new-chat page with
'Manage MCP servers', opening a draft-mode WorkspaceMCPModal that lets
the user stage per-workspace enable/disable overrides and add new
servers before the workspace exists. Staged overrides are applied via
api.workspace.mcp.set immediately after workspace creation
(fire-and-forget so the first prompt is never blocked on remote
runtimes).

Refactors:
- Extract MCPAddServerForm and MCPOAuth helpers out of MCPSettingsSection
  so the new modal and Settings → MCP share one Add-server surface.
- Introduce shared workspaceMcpEffective helper (three-way precedence
  rule + toggle/hasAnyOverride) with 14 unit tests so the modal toggles
  and the overview count always agree.
- WorkspaceMCPModal becomes a discriminated-union dual-mode component
  ('workspace' = unchanged behavior, 'draft' = no live get/set, hides
  tool-allowlist UI, embeds MCPAddServerForm).
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.

1 participant