From ef94b777d1783bad0f4a6ef529bfc352b7c2ac92 Mon Sep 17 00:00:00 2001 From: a7m-1st Date: Fri, 12 Sep 2025 15:22:52 +0300 Subject: [PATCH 01/10] enhance: update mcp config to support frontend fields --- electron/main/utils/mcpConfig.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/electron/main/utils/mcpConfig.ts b/electron/main/utils/mcpConfig.ts index ca351f5c0..cf08775f5 100644 --- a/electron/main/utils/mcpConfig.ts +++ b/electron/main/utils/mcpConfig.ts @@ -8,6 +8,7 @@ const MCP_CONFIG_PATH = path.join(MCP_CONFIG_DIR, 'mcp.json'); type McpServerConfig = { command: string; args: string[]; + description?: string; env?: Record; } | { url: string; @@ -17,7 +18,7 @@ type McpServersConfig = { [name: string]: McpServerConfig; }; -type ConfigFile = { +export type ConfigFile = { mcpServers: McpServersConfig; }; From 024673ca63ec2dcb3a6ecc70d1e6be48f3ad42af Mon Sep 17 00:00:00 2001 From: a7m-1st Date: Fri, 12 Sep 2025 15:23:36 +0300 Subject: [PATCH 02/10] fix: loading section --- src/pages/Setting/MCP.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Setting/MCP.tsx b/src/pages/Setting/MCP.tsx index 23bf13732..34010d016 100644 --- a/src/pages/Setting/MCP.tsx +++ b/src/pages/Setting/MCP.tsx @@ -335,13 +335,13 @@ export default function SettingMCP() { {!isLoading && !error && items.length === 0 && (
No MCP servers
)} - + />} Date: Fri, 12 Sep 2025 15:24:35 +0300 Subject: [PATCH 03/10] fix: duplicate mcp server names --- src/pages/Setting/MCP.tsx | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/pages/Setting/MCP.tsx b/src/pages/Setting/MCP.tsx index 34010d016..40ab2b188 100644 --- a/src/pages/Setting/MCP.tsx +++ b/src/pages/Setting/MCP.tsx @@ -20,6 +20,7 @@ import { getProxyBaseURL } from "@/lib"; import { useAuthStore } from "@/store/authStore"; import { toast } from "sonner"; +import { ConfigFile } from "electron/main/utils/mcpConfig"; export default function SettingMCP() { const navigate = useNavigate(); @@ -236,9 +237,27 @@ export default function SettingMCP() { setInstalling(true); try { if (addType === "local") { - let data; + let data:ConfigFile; try { data = JSON.parse(localJson); + + // validate mcpServers structure + if (!data.mcpServers || typeof data.mcpServers !== "object") { + throw new Error("Invalid mcpServers"); + } + + // check for name conflicts with existing items + const serverNames = Object.keys(data.mcpServers); + const conflict = serverNames.find((name) => + items.some((d) => d.mcp_name === name) + ); + if (conflict) { + toast.error(`MCP server "${conflict}" already exists`, { + closeButton: true, + }); + setInstalling(false); + return; + } } catch (e) { toast.error("Invalid JSON", { closeButton: true }); setInstalling(false); From 6cd10fed292d5f14c3e63f3af50a2856ff0ba96f Mon Sep 17 00:00:00 2001 From: a7m-1st Date: Fri, 12 Sep 2025 15:26:47 +0300 Subject: [PATCH 04/10] fix: update mcp.json & disable name editing to avoid duplicates --- src/pages/Setting/MCP.tsx | 17 ++++++++++++++--- .../Setting/components/MCPConfigDialog.tsx | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/pages/Setting/MCP.tsx b/src/pages/Setting/MCP.tsx index 40ab2b188..f8c126646 100644 --- a/src/pages/Setting/MCP.tsx +++ b/src/pages/Setting/MCP.tsx @@ -196,13 +196,24 @@ export default function SettingMCP() { setSaving(true); setErrorMsg(null); try { - await proxyFetchPut(`/api/mcp/users/${showConfig.id}`, { + const mcpData = { mcp_name: configForm.mcp_name, mcp_desc: configForm.mcp_desc, command: configForm.command, args: arrayToArgsJson(configForm.argsArr), env: configForm.env, - }); + } + await proxyFetchPut(`/api/mcp/users/${showConfig.id}`, mcpData); + + if (window.ipcRenderer) { + window.ipcRenderer.invoke("mcp-update", mcpData.mcp_name, { + description: configForm.mcp_desc, + command: configForm.command, + args: arrayToArgsJson(configForm.argsArr), + env: configForm.env, + }) + } + setShowConfig(null); fetchList(); } catch (err: any) { @@ -236,7 +247,7 @@ export default function SettingMCP() { const handleInstall = async () => { setInstalling(true); try { - if (addType === "local") { + if (addType === "local") { let data:ConfigFile; try { data = JSON.parse(localJson); diff --git a/src/pages/Setting/components/MCPConfigDialog.tsx b/src/pages/Setting/components/MCPConfigDialog.tsx index d58a36e77..32b56fb02 100644 --- a/src/pages/Setting/components/MCPConfigDialog.tsx +++ b/src/pages/Setting/components/MCPConfigDialog.tsx @@ -34,7 +34,7 @@ export default function MCPConfigDialog({ open, form, mcp, onChange, onSave, onC
- onChange({ ...form, mcp_name: e.target.value })} disabled={loading} /> + onChange({ ...form, mcp_name: e.target.value })} disabled readOnly />
From 4879ca4a0c62579fe5113ce25e58ebbd25c35a7f Mon Sep 17 00:00:00 2001 From: a7m-1st Date: Fri, 12 Sep 2025 17:04:59 +0300 Subject: [PATCH 05/10] refactor: spacing --- src/pages/Setting/MCP.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Setting/MCP.tsx b/src/pages/Setting/MCP.tsx index f8c126646..ae917bbb3 100644 --- a/src/pages/Setting/MCP.tsx +++ b/src/pages/Setting/MCP.tsx @@ -247,7 +247,7 @@ export default function SettingMCP() { const handleInstall = async () => { setInstalling(true); try { - if (addType === "local") { + if (addType === "local") { let data:ConfigFile; try { data = JSON.parse(localJson); From 9c5f7412f460a922c5b48db3ad2c6a550db9f670 Mon Sep 17 00:00:00 2001 From: a7m-1st Date: Mon, 15 Sep 2025 15:45:40 +0300 Subject: [PATCH 06/10] chore: map with unique keys & correct default config --- src/pages/Setting/MCP.tsx | 7 +------ src/pages/Setting/components/MCPList.tsx | 4 ++-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/pages/Setting/MCP.tsx b/src/pages/Setting/MCP.tsx index ae917bbb3..c92a8d981 100644 --- a/src/pages/Setting/MCP.tsx +++ b/src/pages/Setting/MCP.tsx @@ -289,12 +289,7 @@ export default function SettingMCP() { } setShowAdd(false); setLocalJson(`{ - "mcp_id": 0, - "mcp_name": "", - "mcp_desc": "", - "command": "", - "args": "", - "env": {} + "mcpServers": {} }`); setRemoteName(""); setRemoteUrl(""); diff --git a/src/pages/Setting/components/MCPList.tsx b/src/pages/Setting/components/MCPList.tsx index 498109e62..04199e261 100644 --- a/src/pages/Setting/components/MCPList.tsx +++ b/src/pages/Setting/components/MCPList.tsx @@ -12,9 +12,9 @@ interface MCPListProps { export default function MCPList({ items, onSetting, onDelete, onSwitch, switchLoading }: MCPListProps) { return (
- {items.map(item => ( + {items.map((item, index) => ( Date: Mon, 15 Sep 2025 15:49:42 +0300 Subject: [PATCH 07/10] fix: parse args if string or array from server & handle edge cases --- electron/main/index.ts | 18 ++++++ electron/main/utils/mcpConfig.ts | 56 ++++++++++++++++++- .../Setting/components/MCPConfigDialog.tsx | 2 +- src/pages/Setting/components/utils.ts | 28 +++++++++- 4 files changed, 100 insertions(+), 4 deletions(-) diff --git a/electron/main/index.ts b/electron/main/index.ts index af7152835..822d8171e 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -516,6 +516,15 @@ function registerIpcHandlers() { // ==================== MCP manage handler ==================== ipcMain.handle('mcp-install', async (event, name, mcp) => { + // Convert args from JSON string to array if needed + if (mcp.args && typeof mcp.args === 'string') { + try { + mcp.args = JSON.parse(mcp.args); + } catch (e) { + // If parsing fails, split by comma as fallback + mcp.args = mcp.args.split(',').map((arg: string) => arg.trim()).filter((arg: string) => arg !== ''); + } + } addMcp(name, mcp); return { success: true }; }); @@ -526,6 +535,15 @@ function registerIpcHandlers() { }); ipcMain.handle('mcp-update', async (event, name, mcp) => { + // Convert args from JSON string to array if needed + if (mcp.args && typeof mcp.args === 'string') { + try { + mcp.args = JSON.parse(mcp.args); + } catch (e) { + // If parsing fails, split by comma as fallback + mcp.args = mcp.args.split(',').map((arg: string) => arg.trim()).filter((arg: string) => arg !== ''); + } + } updateMcp(name, mcp); return { success: true }; }); diff --git a/electron/main/utils/mcpConfig.ts b/electron/main/utils/mcpConfig.ts index cf08775f5..b3400cae9 100644 --- a/electron/main/utils/mcpConfig.ts +++ b/electron/main/utils/mcpConfig.ts @@ -43,6 +43,28 @@ export function readMcpConfig(): ConfigFile { if (!parsed.mcpServers || typeof parsed.mcpServers !== 'object') { return getDefaultConfig(); } + + // Normalize args field - ensure it's always an array + Object.keys(parsed.mcpServers).forEach(serverName => { + const server = parsed.mcpServers[serverName]; + if (server.args) { + const args = server.args as any; + if (typeof args === 'string') { + try { + // Try to parse as JSON string first + server.args = JSON.parse(args); + } catch (e) { + // If parsing fails, split by comma as fallback + server.args = args.split(',').map((arg: string) => arg.trim()).filter((arg: string) => arg !== ''); + } + } + // Ensure it's always an array of strings + if (Array.isArray(server.args)) { + server.args = server.args.map((arg: any) => String(arg)); + } + } + }); + return parsed; } catch (e) { return getDefaultConfig(); @@ -59,7 +81,22 @@ export function writeMcpConfig(config: ConfigFile): void { export function addMcp(name: string, mcp: McpServerConfig): void { const config = readMcpConfig(); if (!config.mcpServers[name]) { - config.mcpServers[name] = mcp; + // Ensure args is an array before adding + const normalizedMcp = { ...mcp }; + if ('args' in normalizedMcp && normalizedMcp.args) { + const args = normalizedMcp.args as any; + if (typeof args === 'string') { + try { + normalizedMcp.args = JSON.parse(args); + } catch (e) { + normalizedMcp.args = args.split(',').map((arg: string) => arg.trim()).filter((arg: string) => arg !== ''); + } + } + if (Array.isArray(normalizedMcp.args)) { + normalizedMcp.args = normalizedMcp.args.map((arg: any) => String(arg)); + } + } + config.mcpServers[name] = normalizedMcp; writeMcpConfig(config); } } @@ -75,6 +112,21 @@ export function removeMcp(name: string): void { export function updateMcp(name: string, mcp: McpServerConfig): void { const config = readMcpConfig(); - config.mcpServers[name] = mcp; + // Ensure args is an array before updating + const normalizedMcp = { ...mcp }; + if ('args' in normalizedMcp && normalizedMcp.args) { + const args = normalizedMcp.args as any; + if (typeof args === 'string') { + try { + normalizedMcp.args = JSON.parse(args); + } catch (e) { + normalizedMcp.args = args.split(',').map((arg: string) => arg.trim()).filter((arg: string) => arg !== ''); + } + } + if (Array.isArray(normalizedMcp.args)) { + normalizedMcp.args = normalizedMcp.args.map((arg: any) => String(arg)); + } + } + config.mcpServers[name] = normalizedMcp; writeMcpConfig(config); } \ No newline at end of file diff --git a/src/pages/Setting/components/MCPConfigDialog.tsx b/src/pages/Setting/components/MCPConfigDialog.tsx index 32b56fb02..4440f5231 100644 --- a/src/pages/Setting/components/MCPConfigDialog.tsx +++ b/src/pages/Setting/components/MCPConfigDialog.tsx @@ -45,7 +45,7 @@ export default function MCPConfigDialog({ open, form, mcp, onChange, onSave, onC onChange({ ...form, command: e.target.value })} disabled={loading} />
- +