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 {
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 => {
serverMap.set(server.name, server);
});
@ -639,9 +640,9 @@ function getUserShell(): string {
// Execute command in user's login shell
async function execInLoginShell(command: string): Promise<string> {
const userShell = getUserShell();
const { stdout } = await execAsync(command, {
shell: `${userShell} -l -c`
});
// Wrap command to execute in login shell
const wrappedCommand = `${userShell} -l -c '${command.replace(/'/g, "'\\''")}'`;
const { stdout } = await execAsync(wrappedCommand);
return stdout;
}

View file

@ -973,11 +973,16 @@ function renderMcpServers() {
removeBtn?.addEventListener("click", async (e) => {
e.stopPropagation();
if (confirm(`Remove MCP server "${server.name}"?`)) {
// Optimistically remove from UI
mcpServers = mcpServers.filter(s => s.name !== server.name);
renderMcpServers();
try {
await ipcRenderer.invoke("remove-mcp-server", server.name);
await refreshMcpServers();
} catch (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 (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 {
await ipcRenderer.invoke("remove-mcp-server", serverName);
mcpDetailsModal?.classList.add("hidden");
await refreshMcpServers();
} catch (error) {
alert(`Failed to remove server: ${error}`);
// Refresh to restore correct state on error
await refreshMcpServers();
}
}
});
// Listen for MCP polling started event
ipcRenderer.on("mcp-polling-started", (_event, sessionId: string) => {
console.log(`[MCP UI] Polling started for session ${sessionId}, active: ${activeSessionId}`);
if (sessionId === activeSessionId) {
const addMcpServerBtn = document.getElementById("add-mcp-server");
if (addMcpServerBtn) {
console.log("[MCP UI] Setting add button to loading state");
addMcpServerBtn.innerHTML = '<span class="loading-spinner"></span>';
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
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
if (sessionId === activeSessionId) {
mcpServers = servers;
renderMcpServers();
// Restore add button
console.log("[MCP UI] Restoring add button from loading state");
const addMcpServerBtn = document.getElementById("add-mcp-server");
if (addMcpServerBtn) {
addMcpServerBtn.innerHTML = '+';