Polish MCP server removal flow
Use the shared inline confirmation helper for configured MCP server removal and keep the delete icon neutral until confirmation.\n\nApply confirmed removals immediately, refresh server status from the apply response, guard against stale/double actions, and document the new MCP manager behavior.
Alessandro committed
Jun 11, 2026 at 03:04 UTC
da4fb47d0b8cade0d1ce0c1b6784dee3aed5dde2
3 files changed
+36
-10
webui/components/settings/AGENTS.md
+1
@@ -16,6 +16,7 @@
16
- Do not store secrets in localStorage, URLs, or console output.
17
- Preserve Store Gating and modal footer conventions in settings components.
18
- MCP manager tool toggles write `disabled_tools` into the draft JSON and require Apply before changing the running MCP tool set.
19
+- Confirmed MCP server removals apply immediately and refresh server status; other MCP manager draft edits still require Apply.
20
21
## Work Guidance
22
webui/components/settings/mcp/client/mcp-servers-store.js
+29
-7
@@ -865,15 +865,33 @@ const model = {
865
requestAnimationFrame(() => globalThis.scrollModal?.("mcp-add-server"));
866
},
867
868
- removeConfigServer(name) {
868
+ async removeConfigServer(name) {
869
try {
870
const config = this.getConfigObject();
871
+ const normalized = normalizeName(name);
872
+ let removed = false;
873
+
874
if (Array.isArray(config.mcpServers)) {
872
- config.mcpServers = config.mcpServers.filter((server) => normalizeName(server?.name || "") !== normalizeName(name));
873
- } else {
874
- delete config.mcpServers[name];
875
+ const nextServers = config.mcpServers.filter((server) => normalizeName(server?.name || "") !== normalized);
876
+ removed = nextServers.length !== config.mcpServers.length;
877
+ config.mcpServers = nextServers;
878
+ } else if (config.mcpServers && typeof config.mcpServers === "object") {
879
+ const key = Object.keys(config.mcpServers).find((serverName) => normalizeName(serverName) === normalized);
880
+ if (key) {
881
+ delete config.mcpServers[key];
882
+ removed = true;
883
+ }
884
}
885
+
886
+ if (!removed) {
887
+ void toastFrontendWarning("MCP server is no longer in this config.", "MCP Servers");
888
+ await this.loadStatus({ silent: true });
889
+ return;
890
+ }
891
+
892
this.setEditorValue(stringifyConfig(config));
893
+ if (normalizeName(this.serverForm.name) === normalized) this.resetForm();
894
+ await this.applyNow({ successMessage: "MCP server removed" });
895
} catch (error) {
896
void toastFrontendError(`Failed to remove MCP server: ${error.message || error}`, "MCP Servers");
897
}
@@ -995,7 +1013,7 @@ const model = {
1013
this.statusCheck = false;
1014
},
1015
998
- async applyNow() {
1016
+ async applyNow(options = {}) {
1017
if (this.applying) return;
1018
try {
1019
const formatted = stringifyConfig(this.getConfigObject());
@@ -1012,9 +1030,13 @@ const model = {
1030
this.setScopeConfigJson(resp.mcp_servers || this.getEditorValue());
1031
this.servers = resp.status || [];
1032
this.servers.sort((a, b) => String(a.name || "").localeCompare(String(b.name || "")));
1015
- void toastFrontendSuccess("MCP servers applied", "MCP Servers");
1033
+ if (options.successMessage !== false) {
1034
+ void toastFrontendSuccess(options.successMessage || "MCP servers applied", "MCP Servers");
1035
+ }
1036
await sleep(100);
1017
- if (globalThis.scrollModal) globalThis.scrollModal("mcp-servers-status");
1037
+ if (options.scrollToStatus !== false && globalThis.scrollModal) {
1038
+ globalThis.scrollModal("mcp-servers-status");
1039
+ }
1040
} catch (error) {
1041
console.error("Failed to apply MCP servers:", error);
1042
void toastFrontendError(`Failed to apply MCP servers: ${error.message || error}`, "MCP Servers");
webui/components/settings/mcp/client/mcp-servers.html
+6
-3
@@ -94,7 +94,9 @@
94
</label>
95
<span class="plugin-status-text" x-text="entry.config.disabled ? 'OFF' : 'ON'"></span>
96
</div>
97
- <button type="button" class="mcp-icon-button danger" title="Remove" @click="$store.mcpServersStore.removeConfigServer(entry.name)">
97
+ <button type="button" class="mcp-icon-button" title="Remove"
98
+ :disabled="$store.mcpServersStore.applying"
99
+ @click="$confirmClick($event, () => $store.mcpServersStore.removeConfigServer(entry.name))">
100
<span class="material-symbols-outlined" aria-hidden="true">delete</span>
101
</button>
102
</div>
@@ -704,8 +706,9 @@
706
cursor: pointer;
707
}
708
707
- .mcp-icon-button.danger {
708
- color: var(--color-error-text);
709
+ .mcp-icon-button:disabled {
710
+ opacity: 0.55;
711
+ cursor: not-allowed;
712
}
713
714
.mcp-raw-toolbar {