From 8c1c2610ee6f6e4134eb12cda09b796f9e217480 Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Sun, 26 Jul 2026 00:33:24 +0200 Subject: [PATCH] Preserve malformed JSON configuration files --- .../architecture/agentConfigurationManager.md | 1 + src/test/agentConfigurationManager.test.ts | 30 ++++++++- src/utils/agentConfigurationManager.ts | 65 ++++++++++++------- 3 files changed, 71 insertions(+), 25 deletions(-) diff --git a/docs/architecture/agentConfigurationManager.md b/docs/architecture/agentConfigurationManager.md index 8cd43c2..7afab33 100644 --- a/docs/architecture/agentConfigurationManager.md +++ b/docs/architecture/agentConfigurationManager.md @@ -13,6 +13,7 @@ For AI agents to use DebugMCP, they need MCP server configuration in their setti - Detect supported AI agents and their config file paths - Show post-install popup for agent selection - Write MCP server configuration to agent settings files +- Preserve existing JSON configuration files when they cannot be parsed - Handle cross-platform config path differences (Windows, macOS, Linux) - Track whether onboarding popup has been shown diff --git a/src/test/agentConfigurationManager.test.ts b/src/test/agentConfigurationManager.test.ts index e272d70..901f7da 100644 --- a/src/test/agentConfigurationManager.test.ts +++ b/src/test/agentConfigurationManager.test.ts @@ -1,7 +1,35 @@ // Copyright (c) Microsoft Corporation. import * as assert from 'assert'; -import { upsertCodexDebugMCPConfig } from '../utils/agentConfigurationManager'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { + upsertCodexDebugMCPConfig, + upsertJsonDebugMCPConfigFile +} from '../utils/agentConfigurationManager'; + +suite('AgentConfigurationManager JSON configuration', () => { + test('upsertJsonDebugMCPConfigFile should preserve an existing file if it contains malformed JSON', async () => { + const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'debugmcp-agent-config-')); + const configPath = path.join(tempDir, 'mcp.json'); + const malformedConfig = '{\n "servers": {\n "other": true,\n'; + await fs.promises.writeFile(configPath, malformedConfig, 'utf8'); + + try { + await assert.rejects( + upsertJsonDebugMCPConfigFile(configPath, 'servers', { + type: 'streamableHttp', + url: 'http://localhost:3001/mcp' + }), + SyntaxError + ); + assert.strictEqual(await fs.promises.readFile(configPath, 'utf8'), malformedConfig); + } finally { + await fs.promises.rm(tempDir, { recursive: true, force: true }); + } + }); +}); suite('AgentConfigurationManager Codex TOML configuration', () => { const mcpServerUrl = 'http://localhost:3001/mcp'; diff --git a/src/utils/agentConfigurationManager.ts b/src/utils/agentConfigurationManager.ts index af23a90..ba1c161 100644 --- a/src/utils/agentConfigurationManager.ts +++ b/src/utils/agentConfigurationManager.ts @@ -33,6 +33,26 @@ export interface MCPServerConfig { tools?: string[]; } +export async function upsertJsonDebugMCPConfigFile( + configPath: string, + fieldName: string, + debugMCPConfig: MCPServerConfig +): Promise { + let config: any = {}; + + if (fs.existsSync(configPath)) { + const configContent = await fs.promises.readFile(configPath, 'utf8'); + config = JSON.parse(configContent); + } + + if (!config[fieldName]) { + config[fieldName] = {}; + } + + config[fieldName].debugmcp = debugMCPConfig; + await fs.promises.writeFile(configPath, JSON.stringify(config, null, 2), 'utf8'); +} + export function upsertCodexDebugMCPConfig(configContent: string, mcpServerUrl: string): string { const normalizedConfigContent = configContent.replace(/\r\n/g, '\n'); const lines = normalizedConfigContent.split('\n'); @@ -528,34 +548,31 @@ export class AgentConfigurationManager { return { success: true, skillPath }; } - let config: any = {}; - - // Read existing config if it exists - if (fs.existsSync(agent.configPath)) { - const configContent = await fs.promises.readFile(agent.configPath, 'utf8'); - try { - config = JSON.parse(configContent); - } catch (parseError) { - console.warn(`Failed to parse existing config for ${agent.name}, creating new config`); - config = {}; + const fieldName = agent.mcpServerFieldName; + try { + await upsertJsonDebugMCPConfigFile( + agent.configPath, + fieldName, + this.getDebugMCPConfig(agent) + ); + } catch (error) { + if (!(error instanceof SyntaxError)) { + throw error; } - } - // Ensure the correct MCP servers object exists for this agent - const fieldName = agent.mcpServerFieldName; - if (!config[fieldName]) { - config[fieldName] = {}; - } + console.error(`Failed to parse existing config for ${agent.name}:`, error); + const openConfigButton = 'Open Config'; + const result = await vscode.window.showErrorMessage( + `Failed to configure DebugMCP for ${agent.displayName}: the existing configuration contains invalid JSON.`, + openConfigButton + ); - // Add or update DebugMCP configuration with current settings - config[fieldName].debugmcp = this.getDebugMCPConfig(agent); + if (result === openConfigButton) { + await vscode.commands.executeCommand('vscode.open', vscode.Uri.file(agent.configPath)); + } - // Write the updated config back to file - await fs.promises.writeFile( - agent.configPath, - JSON.stringify(config, null, 2), - 'utf8' - ); + return { success: false, skillPath: null }; + } console.log(`Successfully added DebugMCP configuration to ${agent.name}`); const skillPath = await this.installDebugMCPSkill();