From e53d1530ffa10ddc41e428ff84ac3329d8182f32 Mon Sep 17 00:00:00 2001 From: Alex Newman Date: Thu, 21 May 2026 01:48:50 -0700 Subject: [PATCH] fix(mcp): drop root .mcp.json so plugin's mcp-search isn't duplicated (#2411) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The repo shipped both a root-level .mcp.json and plugin/.mcp.json with identical mcp-search launchers — kept in sync by a build-time guard and a test. The root file was a holdover from when devs working inside the repo could load mem-search without installing the plugin. With the plugin universally installed, every plugin user now sees `/doctor` warn: Plugin (claude-mem @ plugin:claude-mem:mcp-search): MCP server "mcp-search" skipped — same command/URL as already-configured "mcp-search" …because Claude Code dedupes by command and skips the plugin's namespaced registration. The duplicate is functionally harmless but suppresses the canonical `plugin:claude-mem:mcp-search` entry. This removes the root .mcp.json entirely and re-points everything that referenced it at the bundled plugin copy: - .mcp.json: deleted - .codex-plugin/plugin.json: mcpServers → ./plugin/.mcp.json - package.json: drop .mcp.json from files - scripts/build-hooks.js: drop root-file requirement + sync check - scripts/sync-marketplace.cjs: drop syncManagedFiles entry - src/npx-cli/commands/install.ts: drop from allowedTopLevelEntries - tests/infrastructure/plugin-distribution.test.ts: drop two tests enforcing the now-removed root file Co-authored-by: Claude Opus 4.7 (1M context) --- .codex-plugin/plugin.json | 2 +- .mcp.json | 12 ------------ package.json | 1 - scripts/build-hooks.js | 5 ----- scripts/sync-marketplace.cjs | 4 +--- src/npx-cli/commands/install.ts | 1 - tests/infrastructure/plugin-distribution.test.ts | 9 +-------- 7 files changed, 3 insertions(+), 31 deletions(-) delete mode 100644 .mcp.json diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 3464b061..3cb106f3 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -23,7 +23,7 @@ "nodejs" ], "skills": "./plugin/skills/", - "mcpServers": "./.mcp.json", + "mcpServers": "./plugin/.mcp.json", "hooks": "./plugin/hooks/codex-hooks.json", "interface": { "displayName": "claude-mem", diff --git a/.mcp.json b/.mcp.json deleted file mode 100644 index 4111f045..00000000 --- a/.mcp.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "mcpServers": { - "mcp-search": { - "type": "stdio", - "command": "sh", - "args": [ - "-c", - "_C=\"${CLAUDE_CONFIG_DIR:-$HOME/.claude}\"; _E=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; _P=$({ [ -n \"$_E\" ] && printf '%s\\n' \"$_E\"; printf '%s\\n' \"$PWD/plugin\" \"$PWD\"; ls -dt \"$HOME/.codex/plugins/cache/claude-mem-local/claude-mem\"/[0-9]*/ \"$HOME/.codex/plugins/cache/thedotmack/claude-mem\"/[0-9]*/ \"$_C/plugins/cache/thedotmack/claude-mem\"/[0-9]*/ 2>/dev/null; printf '%s\\n' \"$_C/plugins/marketplaces/thedotmack/plugin\"; } | while IFS= read -r _R; do [ -d \"$_R/plugin/scripts\" ] && _Q=\"$_R/plugin\" || _Q=\"$_R\"; [ -f \"$_Q/scripts/mcp-server.cjs\" ] && { printf '%s\\n' \"$_Q\"; break; }; done); [ -n \"$_P\" ] || { echo \"claude-mem: mcp server not found\" >&2; exit 1; }; exec node \"$_P/scripts/mcp-server.cjs\"" - ] - } - } -} diff --git a/package.json b/package.json index 79402860..7e046ce7 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,6 @@ "dist", ".agents/plugins/marketplace.json", ".codex-plugin", - ".mcp.json", "plugin/.claude-plugin", "plugin/.codex-plugin", "plugin/.mcp.json", diff --git a/scripts/build-hooks.js b/scripts/build-hooks.js index 1e1558f8..36d5b007 100644 --- a/scripts/build-hooks.js +++ b/scripts/build-hooks.js @@ -414,7 +414,6 @@ async function buildHooks() { 'plugin/.codex-plugin/plugin.json', 'plugin/.mcp.json', '.codex-plugin/plugin.json', - '.mcp.json', '.agents/plugins/marketplace.json', ]; for (const filePath of requiredDistributionFiles) { @@ -433,11 +432,7 @@ async function buildHooks() { if (claudeMemMarketplaceEntry?.source?.path !== './plugin') { throw new Error('.agents/plugins/marketplace.json must point claude-mem source.path at ./plugin so Codex loads the bundled plugin root'); } - const rootMcp = JSON.parse(fs.readFileSync('.mcp.json', 'utf-8')); const bundledMcp = JSON.parse(fs.readFileSync('plugin/.mcp.json', 'utf-8')); - if (JSON.stringify(rootMcp.mcpServers?.['mcp-search']) !== JSON.stringify(bundledMcp.mcpServers?.['mcp-search'])) { - throw new Error('.mcp.json and plugin/.mcp.json mcp-search launchers must stay in sync'); - } const mcpSearchCommand = bundledMcp.mcpServers?.['mcp-search']?.args?.join(' ') ?? ''; if (!mcpSearchCommand.includes('.codex/plugins/cache/claude-mem-local/claude-mem')) { throw new Error('plugin/.mcp.json mcp-search launcher must include Codex cache fallback for hosts that do not inject PLUGIN_ROOT'); diff --git a/scripts/sync-marketplace.cjs b/scripts/sync-marketplace.cjs index 86fc79f2..fa531a83 100644 --- a/scripts/sync-marketplace.cjs +++ b/scripts/sync-marketplace.cjs @@ -34,9 +34,7 @@ function getGitignoreExcludes(basePath) { const gitignorePath = path.join(basePath, '.gitignore'); if (!existsSync(gitignorePath)) return ''; - const syncManagedFiles = new Set([ - '.mcp.json', - ]); + const syncManagedFiles = new Set(); const lines = readFileSync(gitignorePath, 'utf-8').split('\n'); return lines diff --git a/src/npx-cli/commands/install.ts b/src/npx-cli/commands/install.ts index cc4d2667..36d0248d 100644 --- a/src/npx-cli/commands/install.ts +++ b/src/npx-cli/commands/install.ts @@ -527,7 +527,6 @@ function copyPluginToMarketplace(): void { const allowedTopLevelEntries = [ '.agents', '.codex-plugin', - '.mcp.json', 'plugin', 'package.json', 'package-lock.json', diff --git a/tests/infrastructure/plugin-distribution.test.ts b/tests/infrastructure/plugin-distribution.test.ts index d3ae3fdb..dd53168e 100644 --- a/tests/infrastructure/plugin-distribution.test.ts +++ b/tests/infrastructure/plugin-distribution.test.ts @@ -90,13 +90,6 @@ describe('Plugin Distribution - Codex Marketplace', () => { expect(command).toContain('plugins/cache/thedotmack/claude-mem'); expect(command).toContain('claude-mem: mcp server not found'); }); - - it('keeps root and bundled MCP launchers in sync', () => { - const rootMcp = JSON.parse(readFileSync(path.join(projectRoot, '.mcp.json'), 'utf-8')); - const bundledMcp = JSON.parse(readFileSync(path.join(projectRoot, 'plugin/.mcp.json'), 'utf-8')); - - expect(rootMcp.mcpServers['mcp-search']).toEqual(bundledMcp.mcpServers['mcp-search']); - }); }); describe('Plugin Distribution - hooks.json Integrity', () => { @@ -134,7 +127,7 @@ describe('Plugin Distribution - hooks.json Integrity', () => { describe('Plugin Distribution - Startup Root Resolution', () => { it('MCP startup commands should have config-dir based non-empty fallbacks', () => { - for (const relativePath of ['.mcp.json', 'plugin/.mcp.json']) { + for (const relativePath of ['plugin/.mcp.json']) { const command = mcpStartupCommandFrom(relativePath); expect(command).toContain('${CLAUDE_CONFIG_DIR:-$HOME/.claude}');