-
Notifications
You must be signed in to change notification settings - Fork 489
Closed
Description
Bug Description
The JSON editor for MCP servers exports configuration as an object keyed by server name (losing server IDs), but the backend requires IDs to locate servers in storage. After editing and saving JSON, the backend cannot find servers because the IDs are missing.
Impact
- Severity: High - Data integrity issue affecting server persistence
- User Experience: Editing via JSON breaks server functionality
- Affected Operations: "Edit JSON" feature, backend server lookups
- Data Loss Risk: Server IDs regenerated on every JSON edit → breaks references
Affected Code
File: apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts
Affected Functions:
- Lines 606-639:
handleOpenGlobalJsonEdit- exports to JSON - Lines 641-828:
handleSaveGlobalJsonEdit- imports from JSON
Current Behavior (Incorrect)
Export (Object Format - Loses IDs)
const handleOpenGlobalJsonEdit = () => {
const exportData: Record<string, Record<string, unknown>> = {};
for (const server of mcpServers) {
const serverConfig = { type: server.type, command: server.command, ... };
exportData[server.name] = serverConfig; // ❌ Uses name as key, loses ID
}
setGlobalJsonValue(JSON.stringify({ mcpServers: exportData }, null, 2));
};Exported JSON (Current - BAD):
{
"mcpServers": {
"shadcn": {
"type": "stdio",
"command": "cmd",
"args": ["/c", "npx", "shadcn@latest", "mcp"]
}
}
}❌ Problem: Server ID mcp-1767024860547-90lvecvle is lost!
Import (No ID Handling)
const handleSaveGlobalJsonEdit = async () => {
const parsed = JSON.parse(globalJsonValue);
const servers = parsed.mcpServers || parsed;
// Uses name to match existing servers, but backend needs ID
const existing = existingByName.get(name);
if (existing) {
updateMCPServer(existing.id, serverData); // ✅ Has ID (if matched by name)
} else {
addMCPServer(serverData); // ❌ Creates NEW ID (even if editing existing)
}
};Result: Backend calls like POST /api/mcp/${serverId}/test fail because IDs don't match storage.
Expected Behavior
- Export: Include server IDs in JSON format
- Import: Preserve IDs when editing existing servers
- Backward Compatibility: Support legacy object format (Claude Desktop compatible)
Proposed Solution
1. Change Export Format to Array with IDs
const handleOpenGlobalJsonEdit = () => {
const serversArray = mcpServers.map((server) => ({
id: server.id, // ✅ Preserve ID
name: server.name, // ✅ Preserve name
type: server.type || 'stdio',
// ... other fields
}));
setGlobalJsonValue(JSON.stringify({ mcpServers: serversArray }, null, 2));
};New Export Format (Array - GOOD):
{
"mcpServers": [
{
"id": "mcp-1767024860547-90lvecvle",
"name": "shadcn",
"type": "stdio",
"command": "cmd",
"args": ["/c", "npx", "shadcn@latest", "mcp"]
}
]
}✅ IDs preserved!
2. Support Both Formats on Import
const handleSaveGlobalJsonEdit = async () => {
const parsed = JSON.parse(globalJsonValue);
const servers = parsed.mcpServers || parsed;
if (Array.isArray(servers)) {
// ✅ New format: array with IDs
await handleSaveGlobalJsonArray(servers);
} else if (typeof servers === 'object' && servers !== null) {
// ✅ Legacy format: object (Claude Desktop compatible)
await handleSaveGlobalJsonObject(servers);
} else {
toast.error('Invalid format');
}
};3. Match by ID First, Then Name
const handleSaveGlobalJsonArray = async (serversArray: unknown[]) => {
for (const config of serversArray) {
const id = serverConfig.id as string | undefined;
const name = serverConfig.name as string;
// ✅ Try to match by ID first, then by name
const existing = id ? existingById.get(id) : existingByName.get(name);
if (existing) {
updateMCPServer(existing.id, serverData); // Uses original ID
} else {
addMCPServer(serverData); // Creates new server with new ID
}
}
};Testing
Before Fix:
- Add MCP server (gets ID
mcp-123) - Open JSON editor → ID not visible
- Edit and save → New ID generated (
mcp-456) - Backend test fails: "Server mcp-123 not found"
After Fix:
- Add MCP server (gets ID
mcp-123) - Open JSON editor → ID visible in array format
- Edit and save → Same ID preserved (
mcp-123) - Backend test succeeds: Server found with correct ID
- Legacy object format still imports correctly (Claude Desktop compatibility)
Dependencies
- Requires: Race condition fix (see related issue) for correct sync timing
- Requires: HTTP error handling fix (see related issue) for proper error messages
- Enables: Reliable backend operations (test, execute, delete)
Additional Context
- Breaking Change: No - supports both formats
- Claude Desktop Compatibility: Yes - object format still supported for import
- Default Format: Array (new format with IDs)
- Migration: Automatic - next JSON edit converts to array format
- Performance: Minimal impact (~1-2ms for array iteration)
Example: Full Working Configuration
{
"mcpServers": [
{
"id": "mcp-1767024860547-90lvecvle",
"name": "shadcn",
"type": "stdio",
"description": "Shadcn UI components",
"command": "cmd",
"args": ["/c", "npx", "shadcn@latest", "mcp"],
"enabled": true
},
{
"id": "mcp-1767024918008-pvxmpgulq",
"name": "playwright",
"type": "stdio",
"description": "Playwright testing",
"command": "cmd",
"args": ["/c", "npx", "@playwright/mcp@latest"],
"enabled": true
},
{
"id": "mcp-1767025842728-oknc6liiu",
"name": "context7",
"type": "http",
"description": "Context7 AI assistant",
"url": "https://mcp.context7.com/mcp",
"headers": {
"CONTEXT7_API_KEY": "ctx7sk-xxx"
},
"enabled": true
}
]
}coderabbitai
Metadata
Metadata
Assignees
Labels
No labels