From 041bbb106d54d93554aaa733e034ebf83dd49ea8 Mon Sep 17 00:00:00 2001 From: myukitty Date: Sat, 15 Aug 2026 09:42:04 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=AA=B2=20BUG:=20deduplicate=20and=20v?= =?UTF-8?q?alidate=20MCP=20server=20list=20input?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/settings/mcpServerList.ts | 6 +++++- test/mcpServerList.test.ts | 28 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/features/settings/mcpServerList.ts b/src/features/settings/mcpServerList.ts index 36c5934..2aa6823 100644 --- a/src/features/settings/mcpServerList.ts +++ b/src/features/settings/mcpServerList.ts @@ -14,7 +14,11 @@ export function parseServerLabel(label: string): string | null { } export function addServer(servers: string[], server: string): string[] { - return [...servers, server]; + const trimmed = server.trim(); + if (!trimmed || servers.some((existing) => existing.trim() === trimmed)) { + return [...servers]; + } + return [...servers, trimmed]; } export function removeServer(servers: string[], server: string): string[] { diff --git a/test/mcpServerList.test.ts b/test/mcpServerList.test.ts index 16b58f2..dddcc76 100644 --- a/test/mcpServerList.test.ts +++ b/test/mcpServerList.test.ts @@ -28,6 +28,34 @@ test("addServer appends without mutating the input", () => { assert.deepEqual(original, ["a"]); }); +test("addServer trims whitespace from the new command", () => { + const original = ["a"]; + const next = addServer(original, " b "); + assert.deepEqual(next, ["a", "b"]); + assert.deepEqual(original, ["a"]); +}); + +test("addServer ignores empty or whitespace-only commands", () => { + const original = ["a"]; + assert.deepEqual(addServer(original, ""), ["a"]); + assert.deepEqual(addServer(original, " "), ["a"]); + assert.deepEqual(original, ["a"]); +}); + +test("addServer ignores duplicate commands", () => { + const original = ["a", "b"]; + assert.deepEqual(addServer(original, "a"), ["a", "b"]); + assert.deepEqual(addServer(original, " a "), ["a", "b"]); + assert.deepEqual(original, ["a", "b"]); +}); + +test("addServer ignores duplicate commands when existing entries contain whitespace", () => { + const original = [" a ", "b"]; + assert.deepEqual(addServer(original, "a"), [" a ", "b"]); + assert.deepEqual(addServer(original, " a "), [" a ", "b"]); + assert.deepEqual(original, [" a ", "b"]); +}); + test("removeServer drops a matching entry without mutating the input", () => { const original = ["a", "b", "c"]; const next = removeServer(original, "b"); From 6bbc77edbcedec8ca7a117a9a15fccdcf9cb5026 Mon Sep 17 00:00:00 2001 From: Fernando Celmer Date: Sat, 15 Aug 2026 17:20:16 -0300 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=AA=B2=20BUG:=20Trim=20server=20names?= =?UTF-8?q?=20in=20removeServer=20to=20match=20addServer's=20normalization?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit addServer trims whitespace before storing, but removeServer compared with strict equality — any legacy or existing whitespace-padded entry (including the " a " case this PR's own tests explicitly leave in storage) could never be removed through the UI. removeServer now trims both the stored entries and the incoming name before comparing, symmetric with addServer. --- src/features/settings/mcpServerList.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/features/settings/mcpServerList.ts b/src/features/settings/mcpServerList.ts index 2aa6823..924a206 100644 --- a/src/features/settings/mcpServerList.ts +++ b/src/features/settings/mcpServerList.ts @@ -22,5 +22,6 @@ export function addServer(servers: string[], server: string): string[] { } export function removeServer(servers: string[], server: string): string[] { - return servers.filter((s) => s !== server); + const trimmed = server.trim(); + return servers.filter((s) => s.trim() !== trimmed); } From a325bb6aa4a316910b91c503a275fd0fefbf4db6 Mon Sep 17 00:00:00 2001 From: Fernando Celmer Date: Sat, 15 Aug 2026 17:20:16 -0300 Subject: [PATCH 3/3] =?UTF-8?q?=E2=9D=A4=EF=B8=8F=20TEST:=20Cover=20remove?= =?UTF-8?q?Server's=20trim=20symmetry=20and=20the=20add=E2=86=92remove=20r?= =?UTF-8?q?ound-trip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/mcpServerList.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/mcpServerList.test.ts b/test/mcpServerList.test.ts index dddcc76..0259d23 100644 --- a/test/mcpServerList.test.ts +++ b/test/mcpServerList.test.ts @@ -66,3 +66,14 @@ test("removeServer drops a matching entry without mutating the input", () => { test("removeServer is a no-op when the command isn't found", () => { assert.deepEqual(removeServer(["a"], "missing"), ["a"]); }); + +test("removeServer trims both sides so legacy whitespace-padded entries can be removed", () => { + assert.deepEqual(removeServer([" a ", "b"], "a"), ["b"]); + assert.deepEqual(removeServer(["a", "b"], " a "), ["b"]); +}); + +test("a server added via addServer can be removed via removeServer", () => { + const after = addServer(["a"], " b "); + assert.deepEqual(after, ["a", "b"]); + assert.deepEqual(removeServer(after, "b"), ["a"]); +});