From bdbec5c14edd91ccc7627a6713adca1a4e4a18b3 Mon Sep 17 00:00:00 2001 From: Glean Code Writer Date: Sat, 11 Apr 2026 00:12:33 +0000 Subject: [PATCH] Fix duplicate server entries caused by hyphenated serverName in mcp-config.json A bad merge introduced entries with "glean-default" (hyphen) into mcp-config.json. The buildGleanServerName function only recognizes the "glean_" (underscore) prefix, so "glean-default" was transformed into "glean_glean-default" in host configs like claude_desktop_config.json. The dedup logic also failed to match "glean-default" against "glean_default" since it used exact string comparison. Fix by normalizing hyphens to underscores in serverName: - Add a Zod transform on McpServerEntrySchema so all parsed entries are normalized automatically (affects both readMcpConfig and writeConfig) - Normalize incoming serverName in writeConfig before writing Generated by Glean Code Writer --- src/config-writer.spec.ts | 57 +++++++++++++++++++++++++++++++++++++++ src/config-writer.ts | 2 +- src/config.ts | 5 +++- 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/config-writer.spec.ts b/src/config-writer.spec.ts index 5380ba3..5aff312 100644 --- a/src/config-writer.spec.ts +++ b/src/config-writer.spec.ts @@ -320,4 +320,61 @@ describe('writeConfig', () => { expect(mcp).toHaveLength(3) expect(mcp.map((e: { serverName: string }) => e.serverName)).toEqual(['server_a', 'server_b', 'server_c']) }) + + it('normalizes hyphens to underscores in serverName', () => { + writeConfig({ + serverName: 'glean-default', + serverUrl: 'https://example.com/mcp/default', + autoUpdate: false, + binaryUrlPrefix: 'https://example.com/binaries', + outputDir, + }) + + const mcp = JSON.parse(readFileSync(join(outputDir, 'mcp-config.json'), 'utf-8')) + expect(mcp).toHaveLength(1) + expect(mcp[0].serverName).toBe('glean_default') + }) + + it('deduplicates hyphenated serverName against existing underscored entry', () => { + writeConfig({ + serverName: 'glean_default', + serverUrl: 'https://example.com/mcp/default', + autoUpdate: false, + binaryUrlPrefix: 'https://example.com/binaries', + outputDir, + }) + + writeConfig({ + serverName: 'glean-default', + serverUrl: 'https://other.example.com/mcp/default', + autoUpdate: false, + binaryUrlPrefix: 'https://example.com/binaries', + outputDir, + }) + + const mcp = JSON.parse(readFileSync(join(outputDir, 'mcp-config.json'), 'utf-8')) + expect(mcp).toHaveLength(1) + expect(mcp[0].serverName).toBe('glean_default') + }) + + it('skips new entry when existing hyphenated entry normalizes to same name', () => { + const mcpPath = join(outputDir, 'mcp-config.json') + writeFileSync( + mcpPath, + JSON.stringify([{ serverName: 'glean-default', url: 'https://example.com/mcp/default' }]) + '\n', + ) + + writeConfig({ + serverName: 'glean_default', + serverUrl: 'https://other.example.com/mcp/default', + autoUpdate: false, + binaryUrlPrefix: 'https://example.com/binaries', + outputDir, + }) + + // The existing entry is recognized as a duplicate after normalization, + // so no new entry is appended + const mcp = JSON.parse(readFileSync(mcpPath, 'utf-8')) + expect(mcp).toHaveLength(1) + }) }) diff --git a/src/config-writer.ts b/src/config-writer.ts index bcc65e8..aaca304 100644 --- a/src/config-writer.ts +++ b/src/config-writer.ts @@ -41,7 +41,7 @@ export function writeConfig(options: WriteConfigOptions): void { const outputDir = options.outputDir ?? getDefaultConfigDir() mkdirSync(outputDir, { recursive: true }) - const newEntry = { serverName: options.serverName, url: options.serverUrl } + const newEntry = { serverName: options.serverName.replace(/-/g, '_'), url: options.serverUrl } McpConfigSchema.parse([newEntry]) const mdmData: Record = { diff --git a/src/config.ts b/src/config.ts index 156ef05..f85e99c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -7,7 +7,10 @@ import { getDefaultMcpConfigPath, getDefaultMdmConfigPath } from './platform.js' const SEMVER_PATTERN = /^v?\d+\.\d+\.\d+$/ const McpServerEntrySchema = z.object({ - serverName: z.string().min(1), + serverName: z + .string() + .min(1) + .transform((name) => name.replace(/-/g, '_')), url: z.string().min(1), })