fix(mcp): keep hidden apps' flags when a batch paste overwrites a server

The drawer's apps state covers every MCP app with hidden ones set to
false, and buildEntry spread it over the existing server's apps, so
overwriting from a batch paste turned the server off in apps hidden from
the sidebar and removed it from their config. Only let the visible
checkboxes override the stored flags.
This commit is contained in:
Jason committed 2026-10-03 23:29:00 +08:00
1 parent 3239b36672
commit 3795a3fe34
2 files changed
+48 -3

No files matched your search

+8 -3
View File
@@ -322,16 +322,21 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
base?: McpServer,
metaDraft?: MetaDraft,
): McpServer => {
// 只用「写入到」里能看到的勾选覆盖原值:隐藏只影响界面,覆盖已有服务器时隐藏
// 应用的开关原样带回,否则会被当成关掉、从那些应用的配置里删掉;不支持 MCP 的
// 应用(OpenClaw 等)沿用原值,新建时为 false
const visibleApps = Object.fromEntries(
visibleAppIds.map((app) => [app, apps[app]]),
);
const entry: McpServer = {
...(base ? { ...base } : {}),
id,
name: id,
server: spec,
// 不支持 MCP 的应用(OpenClaw 等)沿用原值,新建时为 false
apps: {
openclaw: false,
...(base?.apps ?? {}),
...apps,
...(base?.apps ?? appsOf(undefined, [])),
...visibleApps,
} as McpServer["apps"],
};
if (!metaDraft) {
+40
View File
@@ -185,6 +185,46 @@ describe("McpFormModal (drawer)", () => {
expect(mocks.upsert.mock.calls[0][0]).toMatchObject({ id: "linear" });
});
it("keeps hidden apps' flags when overwriting an existing server from a batch", async () => {
// Hermes 从侧栏隐藏了:覆盖 figma 时它在 Hermes 的开关要原样带回,不能被当成关掉
renderForm({
visibleAppIds: ["claude", "codex"],
existingServers: {
figma: {
id: "figma",
name: "figma",
server: { type: "http", url: "http://127.0.0.1:3845/mcp" },
apps: { claude: false, hermes: true } as McpServer["apps"],
},
},
});
fireEvent.change(screen.getByLabelText("mcpPage.drawer.paste"), {
target: {
value: JSON.stringify({
mcpServers: {
linear: { type: "http", url: "https://mcp.linear.app/mcp" },
figma: { type: "http", url: "http://127.0.0.1:3845/v2" },
},
}),
},
});
await userEvent.click(
screen.getByRole("button", { name: "mcpPage.drawer.batchOverwrite" }),
);
await userEvent.click(
screen.getByRole("button", { name: "mcpPage.drawer.addManyReplace" }),
);
await waitFor(() => expect(mocks.upsert).toHaveBeenCalledTimes(2));
const figma = mocks.upsert.mock.calls
.map((call) => call[0])
.find((entry) => entry.id === "figma");
expect(figma.apps).toMatchObject({
claude: true,
codex: true,
hermes: true,
});
});
it("masks secrets in the JSON tab and keeps them on save", async () => {
const server: McpServer = {
id: "serena",