Run MCP add/remove/get commands in poller PTY instead of execAsync
- Fixes issue where execAsync spawns non-login shell without claude in PATH - All MCP operations now run in the session's MCP poller PTY - Adds executeInMcpPoller helper to run commands and wait for completion - Uses prompt detection to know when command finishes - 10 second timeout for safety 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
7c344ff0f3
commit
84e43f656c
60
main.ts
60
main.ts
|
|
@ -631,23 +631,55 @@ async function listMcpServers() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addMcpServer(name: string, config: any) {
|
// Helper to execute command in MCP poller PTY and wait for completion
|
||||||
|
function executeInMcpPoller(sessionId: string, command: string): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const mcpPoller = mcpPollerPtyProcesses.get(sessionId);
|
||||||
|
if (!mcpPoller) {
|
||||||
|
reject(new Error("No active MCP poller for this session"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = "";
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
dataHandler && mcpPoller.off("data", dataHandler);
|
||||||
|
reject(new Error("Command timeout"));
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
|
const dataHandler = (data: string) => {
|
||||||
|
output += data;
|
||||||
|
// Check if command completed (prompt returned)
|
||||||
|
if (data.includes("% ") || data.includes("$ ") || data.includes("➜ ")) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
mcpPoller.off("data", dataHandler);
|
||||||
|
resolve(output);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
mcpPoller.on("data", dataHandler);
|
||||||
|
mcpPoller.write(command + "\r");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addMcpServer(sessionId: string, name: string, config: any) {
|
||||||
// Use add-json to support full configuration including env vars, headers, etc.
|
// Use add-json to support full configuration including env vars, headers, etc.
|
||||||
const jsonConfig = JSON.stringify(config);
|
const jsonConfig = JSON.stringify(config).replace(/'/g, "'\\''"); // Escape single quotes for shell
|
||||||
await execAsync(`claude mcp add-json --scope user "${name}" '${jsonConfig}'`);
|
const command = `claude mcp add-json --scope user "${name}" '${jsonConfig}'`;
|
||||||
|
await executeInMcpPoller(sessionId, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeMcpServer(name: string) {
|
async function removeMcpServer(sessionId: string, name: string) {
|
||||||
await execAsync(`claude mcp remove "${name}"`);
|
const command = `claude mcp remove "${name}"`;
|
||||||
|
await executeInMcpPoller(sessionId, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getMcpServerDetails(name: string) {
|
async function getMcpServerDetails(sessionId: string, name: string) {
|
||||||
try {
|
try {
|
||||||
const { stdout } = await execAsync(`claude mcp get "${name}"`);
|
const output = await executeInMcpPoller(sessionId, `claude mcp get "${name}"`);
|
||||||
|
|
||||||
// Parse the output to extract details
|
// Parse the output to extract details
|
||||||
const details: any = { name };
|
const details: any = { name };
|
||||||
const lines = stdout.split("\n");
|
const lines = output.split("\n");
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const trimmed = line.trim();
|
const trimmed = line.trim();
|
||||||
|
|
@ -688,27 +720,27 @@ ipcMain.handle("list-mcp-servers", async (_event, sessionId: string) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle("add-mcp-server", async (_event, name: string, config: any) => {
|
ipcMain.handle("add-mcp-server", async (_event, sessionId: string, name: string, config: any) => {
|
||||||
try {
|
try {
|
||||||
await addMcpServer(name, config);
|
await addMcpServer(sessionId, name, config);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error adding MCP server:", error);
|
console.error("Error adding MCP server:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle("remove-mcp-server", async (_event, name: string) => {
|
ipcMain.handle("remove-mcp-server", async (_event, sessionId: string, name: string) => {
|
||||||
try {
|
try {
|
||||||
await removeMcpServer(name);
|
await removeMcpServer(sessionId, name);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error removing MCP server:", error);
|
console.error("Error removing MCP server:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle("get-mcp-server-details", async (_event, name: string) => {
|
ipcMain.handle("get-mcp-server-details", async (_event, sessionId: string, name: string) => {
|
||||||
try {
|
try {
|
||||||
return await getMcpServerDetails(name);
|
return await getMcpServerDetails(sessionId, name);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error getting MCP server details:", error);
|
console.error("Error getting MCP server details:", error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|
|
||||||
|
|
@ -949,7 +949,7 @@ function renderMcpServers() {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (confirm(`Remove MCP server "${server.name}"?`)) {
|
if (confirm(`Remove MCP server "${server.name}"?`)) {
|
||||||
try {
|
try {
|
||||||
await ipcRenderer.invoke("remove-mcp-server", server.name);
|
await ipcRenderer.invoke("remove-mcp-server", activeSessionId, server.name);
|
||||||
await loadMcpServers();
|
await loadMcpServers();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert(`Failed to remove server: ${error}`);
|
alert(`Failed to remove server: ${error}`);
|
||||||
|
|
@ -978,7 +978,7 @@ async function showMcpServerDetails(name: string) {
|
||||||
detailsModal?.classList.remove("hidden");
|
detailsModal?.classList.remove("hidden");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const details = await ipcRenderer.invoke("get-mcp-server-details", name);
|
const details = await ipcRenderer.invoke("get-mcp-server-details", activeSessionId, name);
|
||||||
|
|
||||||
if (detailsContent) {
|
if (detailsContent) {
|
||||||
let html = "";
|
let html = "";
|
||||||
|
|
@ -1139,7 +1139,7 @@ addMcpBtn?.addEventListener("click", async () => {
|
||||||
addMcpBtn.classList.add("opacity-50", "cursor-not-allowed");
|
addMcpBtn.classList.add("opacity-50", "cursor-not-allowed");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await ipcRenderer.invoke("add-mcp-server", name, config);
|
await ipcRenderer.invoke("add-mcp-server", activeSessionId, name, config);
|
||||||
await loadMcpServers();
|
await loadMcpServers();
|
||||||
mcpModal?.classList.add("hidden");
|
mcpModal?.classList.add("hidden");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -1168,7 +1168,7 @@ removeMcpDetailsBtn?.addEventListener("click", async () => {
|
||||||
|
|
||||||
if (confirm(`Remove MCP server "${serverName}"?`)) {
|
if (confirm(`Remove MCP server "${serverName}"?`)) {
|
||||||
try {
|
try {
|
||||||
await ipcRenderer.invoke("remove-mcp-server", serverName);
|
await ipcRenderer.invoke("remove-mcp-server", activeSessionId, serverName);
|
||||||
mcpDetailsModal?.classList.add("hidden");
|
mcpDetailsModal?.classList.add("hidden");
|
||||||
await loadMcpServers();
|
await loadMcpServers();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue