mirror of
https://github.com/coder/coder.git
synced 2026-09-21 20:51:01 +08:00
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: []`.
<details>
<summary>Manual verification on a local dev instance</summary>
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.
</details>
Generated by Coder Agents.
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user