Fix MCP server removal and execAsync shell invocation

- Fix execInLoginShell to properly wrap commands instead of malformed shell option
- Fix serverMap accumulating deleted servers by clearing on each poll
- Add optimistic UI removal for instant feedback when removing servers
- Add refresh after add operation with loading spinner
- Remove recently removed servers tracking (unnecessary with proper serverMap clear)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Amrit Subramanian 2025-10-08 00:11:22 -04:00
parent 45f2aaefa3
commit ead8191b22
2 changed files with 20 additions and 11 deletions

View file

@ -150,7 +150,8 @@ function spawnMcpPoller(sessionId: string, projectDir: string) {
try { try {
const servers = parseMcpOutput(outputBuffer); const servers = parseMcpOutput(outputBuffer);
// Merge servers into the map (upsert by name) // Clear and replace the server map with current results
serverMap.clear();
servers.forEach(server => { servers.forEach(server => {
serverMap.set(server.name, server); serverMap.set(server.name, server);
}); });
@ -639,9 +640,9 @@ function getUserShell(): string {
// Execute command in user's login shell // Execute command in user's login shell
async function execInLoginShell(command: string): Promise<string> { async function execInLoginShell(command: string): Promise<string> {
const userShell = getUserShell(); const userShell = getUserShell();
const { stdout } = await execAsync(command, { // Wrap command to execute in login shell
shell: `${userShell} -l -c` const wrappedCommand = `${userShell} -l -c '${command.replace(/'/g, "'\\''")}'`;
}); const { stdout } = await execAsync(wrappedCommand);
return stdout; return stdout;
} }

View file

@ -973,11 +973,16 @@ function renderMcpServers() {
removeBtn?.addEventListener("click", async (e) => { removeBtn?.addEventListener("click", async (e) => {
e.stopPropagation(); e.stopPropagation();
if (confirm(`Remove MCP server "${server.name}"?`)) { if (confirm(`Remove MCP server "${server.name}"?`)) {
// Optimistically remove from UI
mcpServers = mcpServers.filter(s => s.name !== server.name);
renderMcpServers();
try { try {
await ipcRenderer.invoke("remove-mcp-server", server.name); await ipcRenderer.invoke("remove-mcp-server", server.name);
await refreshMcpServers();
} catch (error) { } catch (error) {
alert(`Failed to remove server: ${error}`); alert(`Failed to remove server: ${error}`);
// Refresh to restore correct state on error
await refreshMcpServers();
} }
} }
}); });
@ -1193,23 +1198,28 @@ removeMcpDetailsBtn?.addEventListener("click", async () => {
if (!serverName) return; if (!serverName) return;
if (confirm(`Remove MCP server "${serverName}"?`)) { if (confirm(`Remove MCP server "${serverName}"?`)) {
// Close modal immediately
mcpDetailsModal?.classList.add("hidden");
// Optimistically remove from UI
mcpServers = mcpServers.filter(s => s.name !== serverName);
renderMcpServers();
try { try {
await ipcRenderer.invoke("remove-mcp-server", serverName); await ipcRenderer.invoke("remove-mcp-server", serverName);
mcpDetailsModal?.classList.add("hidden");
await refreshMcpServers();
} catch (error) { } catch (error) {
alert(`Failed to remove server: ${error}`); alert(`Failed to remove server: ${error}`);
// Refresh to restore correct state on error
await refreshMcpServers();
} }
} }
}); });
// Listen for MCP polling started event // Listen for MCP polling started event
ipcRenderer.on("mcp-polling-started", (_event, sessionId: string) => { ipcRenderer.on("mcp-polling-started", (_event, sessionId: string) => {
console.log(`[MCP UI] Polling started for session ${sessionId}, active: ${activeSessionId}`);
if (sessionId === activeSessionId) { if (sessionId === activeSessionId) {
const addMcpServerBtn = document.getElementById("add-mcp-server"); const addMcpServerBtn = document.getElementById("add-mcp-server");
if (addMcpServerBtn) { if (addMcpServerBtn) {
console.log("[MCP UI] Setting add button to loading state");
addMcpServerBtn.innerHTML = '<span class="loading-spinner"></span>'; addMcpServerBtn.innerHTML = '<span class="loading-spinner"></span>';
addMcpServerBtn.classList.add("pointer-events-none"); addMcpServerBtn.classList.add("pointer-events-none");
} }
@ -1218,14 +1228,12 @@ ipcRenderer.on("mcp-polling-started", (_event, sessionId: string) => {
// Listen for MCP server updates from main process // Listen for MCP server updates from main process
ipcRenderer.on("mcp-servers-updated", (_event, sessionId: string, servers: McpServer[]) => { ipcRenderer.on("mcp-servers-updated", (_event, sessionId: string, servers: McpServer[]) => {
console.log(`[MCP UI] Received update for session ${sessionId}, active: ${activeSessionId}`);
// Only update if this is for the active session // Only update if this is for the active session
if (sessionId === activeSessionId) { if (sessionId === activeSessionId) {
mcpServers = servers; mcpServers = servers;
renderMcpServers(); renderMcpServers();
// Restore add button // Restore add button
console.log("[MCP UI] Restoring add button from loading state");
const addMcpServerBtn = document.getElementById("add-mcp-server"); const addMcpServerBtn = document.getElementById("add-mcp-server");
if (addMcpServerBtn) { if (addMcpServerBtn) {
addMcpServerBtn.innerHTML = '+'; addMcpServerBtn.innerHTML = '+';