From 30dc7ebd711ef2e1e060dfcda141bce8e6ff1537 Mon Sep 17 00:00:00 2001 From: Ethan <39577870+ethanndickson@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:34:02 +0800 Subject: [PATCH] fix(site/src/pages/AgentsPage): persist empty MCP selection (#28238) Removing the final optional MCP server from an existing chat produced an empty selection, but the message request omitted `mcp_server_ids`. The API interprets an omitted field as preserving the current selection. Send the selected MCP server IDs for every message, including an empty array. Add a Storybook interaction test that removes the final MCP server and verifies the request contains `mcp_server_ids: []`.
Manual verification on a local dev instance Setup: `./scripts/develop.sh`, an Anthropic provider with `claude-haiku-4-5`, and a local test MCP server registered with availability `default_on`. With this branch, chat `75285d9f`: 1. The new chat showed the MCP chip selected. 2. Sent a message, then removed the chip with the X control. 3. Sent a second message, then reloaded the page. 4. No MCP chip appeared, and the picker toggle stayed off. 5. `GET /api/experimental/chats/{id}` returned `mcp_server_ids: []`. With the one-line change reverted, chat `751ac191` repeated the same flow. The chip returned as selected after the reload, and the API returned `mcp_server_ids: ["b63a2a3a-..."]`. Not covered: `force_on` servers, plan mode interaction, and queued messages during streaming.
Generated by Coder Agents. --- .../AgentsPage/AgentChatPage.stories.tsx | 55 ++++++++++++++++++- site/src/pages/AgentsPage/AgentChatPage.tsx | 5 +- 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/site/src/pages/AgentsPage/AgentChatPage.stories.tsx b/site/src/pages/AgentsPage/AgentChatPage.stories.tsx index cc6b3784b6..d124f03114 100644 --- a/site/src/pages/AgentsPage/AgentChatPage.stories.tsx +++ b/site/src/pages/AgentsPage/AgentChatPage.stories.tsx @@ -26,6 +26,7 @@ import { MockChat, MockChatMessage, MockChatQueuedMessage, + MockMCPServerConfig, } from "#/testHelpers/chatEntities"; import { MockChatModelConfig } from "#/testHelpers/chatModels"; import { @@ -255,7 +256,10 @@ const buildChatAuthorizationQuery = ( const buildQueries = ( chat: TypesGen.Chat, messagesData: TypesGen.ChatMessagesResponse, - opts?: { diffUrl?: string }, + opts?: { + diffUrl?: string; + mcpServers?: readonly TypesGen.MCPServerConfig[]; + }, ) => { const diffStatus: TypesGen.ChatDiffStatus = { chat_id: CHAT_ID, @@ -301,7 +305,7 @@ const buildQueries = ( }, { key: chatModelsKey, data: mockModelCatalog }, { key: chatModelConfigs().queryKey, data: mockModelConfigs }, - { key: mcpServersKey, data: [] }, + { key: mcpServersKey, data: opts?.mcpServers ?? [] }, buildChatAuthorizationQuery(chat, { canShareChat: { action: "share", @@ -3217,6 +3221,53 @@ export const SendResponseAfterChatSwitch: Story = { }, }; +export const RemoveLastMCPServer: Story = { + parameters: { + queries: buildQueries( + { + id: CHAT_ID, + ...baseChatFields, + title: "Remove last MCP server", + status: "waiting", + mcp_server_ids: [MockMCPServerConfig.id], + }, + { messages: [], queued_messages: [], has_more: false }, + { + diffUrl: undefined, + mcpServers: [MockMCPServerConfig], + }, + ), + }, + beforeEach: () => { + spyOn(API.experimental, "getUserSkills").mockResolvedValue([]); + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const sendSpy = spyOn( + API.experimental, + "createChatMessage", + ).mockResolvedValue({ queued: false }); + + await userEvent.click( + await canvas.findByRole("button", { name: "Remove MCP Server" }), + ); + const editor = await canvas.findByTestId("chat-message-input"); + await userEvent.click(editor); + await userEvent.type(editor, "Send without MCP tools"); + await userEvent.keyboard("{Enter}"); + + await waitFor(() => { + expect(sendSpy).toHaveBeenCalledTimes(1); + }); + expect(sendSpy).toHaveBeenCalledWith( + CHAT_ID, + expect.objectContaining({ + mcp_server_ids: [], + }), + ); + }, +}; + /** * The send flow renders the durable user row once the server accepts the * prompt, before the assistant turn produces any output. diff --git a/site/src/pages/AgentsPage/AgentChatPage.tsx b/site/src/pages/AgentsPage/AgentChatPage.tsx index da472f7d64..efe81b3a18 100644 --- a/site/src/pages/AgentsPage/AgentChatPage.tsx +++ b/site/src/pages/AgentsPage/AgentChatPage.tsx @@ -1799,10 +1799,7 @@ const AgentChatPage: FC = () => { content, model_config_id: selectedModelConfigID, reasoning_effort: effectiveReasoningEffort, - mcp_server_ids: - effectiveMCPServerIds.length > 0 - ? [...effectiveMCPServerIds] - : undefined, + mcp_server_ids: [...effectiveMCPServerIds], ...(planModeSwitch !== undefined ? { plan_mode: