fix: remove button for mcp server install

This commit is contained in:
2026-02-28 22:09:34 +01:00
parent 61b6c904f4
commit aedd013d88
10 changed files with 116 additions and 3 deletions

View File

@@ -1601,6 +1601,12 @@ export function registerIpcHandlers(bundle: EngineBundle): void {
return engine.addToConfig(agentId as import('../engine/MCPAgentConfigEngine').MCPAgentId); return engine.addToConfig(agentId as import('../engine/MCPAgentConfigEngine').MCPAgentId);
}); });
safeHandle('mcp:removeFromAgentConfig', async (_event: unknown, agentId: string) => {
const { MCPAgentConfigEngine } = await import('../engine/MCPAgentConfigEngine');
const engine = new MCPAgentConfigEngine(buildMcpAgentConfigOptions(bundle));
return engine.removeFromConfig(agentId as import('../engine/MCPAgentConfigEngine').MCPAgentId);
});
safeHandle('mcp:isConfigured', async (_event: unknown, agentId: string) => { safeHandle('mcp:isConfigured', async (_event: unknown, agentId: string) => {
const { MCPAgentConfigEngine } = await import('../engine/MCPAgentConfigEngine'); const { MCPAgentConfigEngine } = await import('../engine/MCPAgentConfigEngine');
const engine = new MCPAgentConfigEngine(buildMcpAgentConfigOptions(bundle)); const engine = new MCPAgentConfigEngine(buildMcpAgentConfigOptions(bundle));

View File

@@ -392,6 +392,7 @@ export const electronAPI: ElectronAPI = {
mcp: { mcp: {
getAgents: () => ipcRenderer.invoke('mcp:getAgents'), getAgents: () => ipcRenderer.invoke('mcp:getAgents'),
addToAgentConfig: (agentId: string) => ipcRenderer.invoke('mcp:addToAgentConfig', agentId), addToAgentConfig: (agentId: string) => ipcRenderer.invoke('mcp:addToAgentConfig', agentId),
removeFromAgentConfig: (agentId: string) => ipcRenderer.invoke('mcp:removeFromAgentConfig', agentId),
isConfigured: (agentId: string) => ipcRenderer.invoke('mcp:isConfigured', agentId), isConfigured: (agentId: string) => ipcRenderer.invoke('mcp:isConfigured', agentId),
getPort: () => ipcRenderer.invoke('mcp:getPort'), getPort: () => ipcRenderer.invoke('mcp:getPort'),
}, },

View File

@@ -848,6 +848,7 @@ export interface ElectronAPI {
mcp: { mcp: {
getAgents: () => Promise<Array<{ id: string; label: string }>>; getAgents: () => Promise<Array<{ id: string; label: string }>>;
addToAgentConfig: (agentId: string) => Promise<{ success: boolean; configPath: string; error?: string }>; addToAgentConfig: (agentId: string) => Promise<{ success: boolean; configPath: string; error?: string }>;
removeFromAgentConfig: (agentId: string) => Promise<{ success: boolean; configPath: string; error?: string }>;
isConfigured: (agentId: string) => Promise<boolean>; isConfigured: (agentId: string) => Promise<boolean>;
getPort: () => Promise<number | null>; getPort: () => Promise<number | null>;
}; };

View File

@@ -138,7 +138,7 @@ const MCPStatusBadge: React.FC = () => {
); );
}; };
/** Button to add bDS MCP server to an agent's config. Shows "Configured" if already present. */ /** Button to add/remove bDS MCP server to/from an agent's config. */
const MCPAgentButton: React.FC<{ agentId: string; agentLabel: string }> = ({ agentId, agentLabel }) => { const MCPAgentButton: React.FC<{ agentId: string; agentLabel: string }> = ({ agentId, agentLabel }) => {
const { t } = useI18n(); const { t } = useI18n();
const [configured, setConfigured] = React.useState(false); const [configured, setConfigured] = React.useState(false);
@@ -149,7 +149,30 @@ const MCPAgentButton: React.FC<{ agentId: string; agentLabel: string }> = ({ age
}, [agentId]); }, [agentId]);
if (configured) { if (configured) {
return <span className="badge badge-success">{t('settings.mcp.alreadyConfigured')}</span>; return (
<button
className="secondary danger"
disabled={loading}
onClick={async () => {
setLoading(true);
try {
const result = await window.electronAPI?.mcp?.removeFromAgentConfig(agentId);
if (result?.success) {
showToast.success(t('settings.toast.mcpConfigRemoveSuccess', { agent: agentLabel }));
setConfigured(false);
} else {
showToast.error(t('settings.toast.mcpConfigRemoveFailed', { agent: agentLabel, error: result?.error ?? 'Unknown error' }));
}
} catch {
showToast.error(t('settings.toast.mcpConfigRemoveFailed', { agent: agentLabel, error: 'Unexpected error' }));
} finally {
setLoading(false);
}
}}
>
{t('settings.mcp.removeFromAgent', { agent: agentLabel })}
</button>
);
} }
return ( return (

View File

@@ -988,8 +988,11 @@
"settings.mcp.agentsTitle": "Agenten-Konfiguration", "settings.mcp.agentsTitle": "Agenten-Konfiguration",
"settings.mcp.agentsDescription": "Fügen Sie den bDS MCP-Server zur Konfiguration Ihres Programmieragenten hinzu. Vorhandene Einstellungen bleiben erhalten.", "settings.mcp.agentsDescription": "Fügen Sie den bDS MCP-Server zur Konfiguration Ihres Programmieragenten hinzu. Vorhandene Einstellungen bleiben erhalten.",
"settings.mcp.addToAgent": "Zu {agent} hinzufügen", "settings.mcp.addToAgent": "Zu {agent} hinzufügen",
"settings.mcp.removeFromAgent": "Aus {agent} entfernen",
"settings.mcp.alreadyConfigured": "Konfiguriert", "settings.mcp.alreadyConfigured": "Konfiguriert",
"settings.toast.mcpConfigSuccess": "bDS MCP-Server zur {agent}-Konfiguration hinzugefügt", "settings.toast.mcpConfigSuccess": "bDS MCP-Server zur {agent}-Konfiguration hinzugefügt",
"settings.toast.mcpConfigRemoveSuccess": "bDS MCP-Server aus der {agent}-Konfiguration entfernt",
"settings.toast.mcpConfigFailed": "Konfiguration von {agent} fehlgeschlagen: {error}", "settings.toast.mcpConfigFailed": "Konfiguration von {agent} fehlgeschlagen: {error}",
"settings.toast.mcpConfigRemoveFailed": "Entfernen aus {agent} fehlgeschlagen: {error}",
"settings.toast.mcpConfigPath": "Konfiguration geschrieben nach {path}" "settings.toast.mcpConfigPath": "Konfiguration geschrieben nach {path}"
} }

View File

@@ -988,8 +988,11 @@
"settings.mcp.agentsTitle": "Agent Configuration", "settings.mcp.agentsTitle": "Agent Configuration",
"settings.mcp.agentsDescription": "Add the bDS MCP server to your coding agent's configuration. Existing settings are preserved.", "settings.mcp.agentsDescription": "Add the bDS MCP server to your coding agent's configuration. Existing settings are preserved.",
"settings.mcp.addToAgent": "Add to {agent}", "settings.mcp.addToAgent": "Add to {agent}",
"settings.mcp.removeFromAgent": "Remove from {agent}",
"settings.mcp.alreadyConfigured": "Configured", "settings.mcp.alreadyConfigured": "Configured",
"settings.toast.mcpConfigSuccess": "bDS MCP server added to {agent} configuration", "settings.toast.mcpConfigSuccess": "bDS MCP server added to {agent} configuration",
"settings.toast.mcpConfigRemoveSuccess": "bDS MCP server removed from {agent} configuration",
"settings.toast.mcpConfigFailed": "Failed to configure {agent}: {error}", "settings.toast.mcpConfigFailed": "Failed to configure {agent}: {error}",
"settings.toast.mcpConfigRemoveFailed": "Failed to remove from {agent}: {error}",
"settings.toast.mcpConfigPath": "Config written to {path}" "settings.toast.mcpConfigPath": "Config written to {path}"
} }

View File

@@ -988,8 +988,11 @@
"settings.mcp.agentsTitle": "Configuración de agentes", "settings.mcp.agentsTitle": "Configuración de agentes",
"settings.mcp.agentsDescription": "Añada el servidor MCP de bDS a la configuración de su agente de programación. Las configuraciones existentes se conservan.", "settings.mcp.agentsDescription": "Añada el servidor MCP de bDS a la configuración de su agente de programación. Las configuraciones existentes se conservan.",
"settings.mcp.addToAgent": "Añadir a {agent}", "settings.mcp.addToAgent": "Añadir a {agent}",
"settings.mcp.removeFromAgent": "Eliminar de {agent}",
"settings.mcp.alreadyConfigured": "Configurado", "settings.mcp.alreadyConfigured": "Configurado",
"settings.toast.mcpConfigSuccess": "Servidor MCP de bDS añadido a la configuración de {agent}", "settings.toast.mcpConfigSuccess": "Servidor MCP de bDS añadido a la configuración de {agent}",
"settings.toast.mcpConfigRemoveSuccess": "Servidor MCP de bDS eliminado de la configuración de {agent}",
"settings.toast.mcpConfigFailed": "Error al configurar {agent}: {error}", "settings.toast.mcpConfigFailed": "Error al configurar {agent}: {error}",
"settings.toast.mcpConfigRemoveFailed": "Error al eliminar de {agent}: {error}",
"settings.toast.mcpConfigPath": "Configuración escrita en {path}" "settings.toast.mcpConfigPath": "Configuración escrita en {path}"
} }

View File

@@ -986,8 +986,11 @@
"settings.mcp.agentsTitle": "Configuration des agents", "settings.mcp.agentsTitle": "Configuration des agents",
"settings.mcp.agentsDescription": "Ajoutez le serveur MCP bDS à la configuration de votre agent de programmation. Les paramètres existants sont préservés.", "settings.mcp.agentsDescription": "Ajoutez le serveur MCP bDS à la configuration de votre agent de programmation. Les paramètres existants sont préservés.",
"settings.mcp.addToAgent": "Ajouter à {agent}", "settings.mcp.addToAgent": "Ajouter à {agent}",
"settings.mcp.removeFromAgent": "Retirer de {agent}",
"settings.mcp.alreadyConfigured": "Configuré", "settings.mcp.alreadyConfigured": "Configuré",
"settings.toast.mcpConfigSuccess": "Serveur MCP bDS ajouté à la configuration de {agent}", "settings.toast.mcpConfigSuccess": "Serveur MCP bDS ajouté à la configuration de {agent}",
"settings.toast.mcpConfigRemoveSuccess": "Serveur MCP bDS retiré de la configuration de {agent}",
"settings.toast.mcpConfigFailed": "Échec de la configuration de {agent}: {error}", "settings.toast.mcpConfigFailed": "Échec de la configuration de {agent}: {error}",
"settings.toast.mcpConfigRemoveFailed": "Échec du retrait de {agent}: {error}",
"settings.toast.mcpConfigPath": "Configuration écrite dans {path}" "settings.toast.mcpConfigPath": "Configuration écrite dans {path}"
} }

View File

@@ -986,8 +986,11 @@
"settings.mcp.agentsTitle": "Configurazione agenti", "settings.mcp.agentsTitle": "Configurazione agenti",
"settings.mcp.agentsDescription": "Aggiungi il server MCP bDS alla configurazione del tuo agente di programmazione. Le impostazioni esistenti vengono preservate.", "settings.mcp.agentsDescription": "Aggiungi il server MCP bDS alla configurazione del tuo agente di programmazione. Le impostazioni esistenti vengono preservate.",
"settings.mcp.addToAgent": "Aggiungi a {agent}", "settings.mcp.addToAgent": "Aggiungi a {agent}",
"settings.mcp.removeFromAgent": "Rimuovi da {agent}",
"settings.mcp.alreadyConfigured": "Configurato", "settings.mcp.alreadyConfigured": "Configurato",
"settings.toast.mcpConfigSuccess": "Server MCP bDS aggiunto alla configurazione di {agent}", "settings.toast.mcpConfigSuccess": "Server MCP bDS aggiunto alla configurazione di {agent}",
"settings.toast.mcpConfigRemoveSuccess": "Server MCP bDS rimosso dalla configurazione di {agent}",
"settings.toast.mcpConfigFailed": "Configurazione di {agent} non riuscita: {error}", "settings.toast.mcpConfigFailed": "Configurazione di {agent} non riuscita: {error}",
"settings.toast.mcpConfigRemoveFailed": "Rimozione da {agent} non riuscita: {error}",
"settings.toast.mcpConfigPath": "Configurazione scritta in {path}" "settings.toast.mcpConfigPath": "Configurazione scritta in {path}"
} }

View File

@@ -1,9 +1,76 @@
import React from 'react'; import React from 'react';
import { describe, it, expect, beforeEach, vi } from 'vitest'; import { describe, it, expect, beforeEach, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react'; import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
import { SettingsView } from '../../../src/renderer/components/SettingsView/SettingsView'; import { SettingsView } from '../../../src/renderer/components/SettingsView/SettingsView';
import { useAppStore } from '../../../src/renderer/store'; import { useAppStore } from '../../../src/renderer/store';
describe('MCPAgentButton uninstall', () => {
beforeEach(() => {
vi.clearAllMocks();
useAppStore.setState({
activeProject: {
id: 'p1',
name: 'Test',
slug: 'test',
isActive: true,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
gitDiffPreferences: { wordWrap: true, viewStyle: 'inline', hideUnchangedRegions: false },
});
(window as any).electronAPI = {
...(window as any).electronAPI,
app: { getDefaultProjectPath: vi.fn().mockResolvedValue('/repo') },
meta: {
getCategories: vi.fn().mockResolvedValue(['article']),
getProjectMetadata: vi.fn().mockResolvedValue({
maxPostsPerPage: 75,
publicUrl: 'https://example.com',
categorySettings: { article: { renderInLists: true, showTitle: true } },
}),
updateProjectMetadata: vi.fn().mockResolvedValue({}),
},
chat: {
getSystemPrompt: vi.fn().mockResolvedValue({ success: true, prompt: '' }),
getApiKey: vi.fn().mockResolvedValue({ hasKey: false, maskedKey: '' }),
getAvailableModels: vi.fn().mockResolvedValue({ success: true, models: [], selectedModel: '' }),
},
templates: { getEnabledByKind: vi.fn().mockResolvedValue([]) },
projects: { update: vi.fn().mockResolvedValue({}) },
mcp: {
getAgents: vi.fn().mockResolvedValue([
{ id: 'claude-code', label: 'Claude Code' },
]),
addToAgentConfig: vi.fn().mockResolvedValue({ success: true, configPath: '/p' }),
removeFromAgentConfig: vi.fn().mockResolvedValue({ success: true, configPath: '/p' }),
isConfigured: vi.fn().mockResolvedValue(true),
getPort: vi.fn().mockResolvedValue(4124),
},
};
});
it('shows an uninstall button when agent is already configured', async () => {
render(<SettingsView />);
const btn = await screen.findByRole('button', { name: /remove from claude code/i });
expect(btn).toBeInTheDocument();
});
it('calls removeFromAgentConfig and shows add button after removal', async () => {
render(<SettingsView />);
const btn = await screen.findByRole('button', { name: /remove from claude code/i });
await act(async () => {
fireEvent.click(btn);
});
expect((window as any).electronAPI.mcp.removeFromAgentConfig).toHaveBeenCalledWith('claude-code');
const addBtn = await screen.findByRole('button', { name: /add to claude code/i });
expect(addBtn).toBeInTheDocument();
});
});
describe('SettingsView Diff Preferences', () => { describe('SettingsView Diff Preferences', () => {
let updateProjectMock: ReturnType<typeof vi.fn>; let updateProjectMock: ReturnType<typeof vi.fn>;