From fedfbcd25599e840944a179d6d64ea91273e35b5 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Wed, 18 Mar 2026 10:26:02 -0400 Subject: [PATCH] feat(mcporter): add .gsd/mcp.json per-project MCP config support (#1141) --- src/resources/extensions/mcporter/index.ts | 41 ++++++++++++++-------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/src/resources/extensions/mcporter/index.ts b/src/resources/extensions/mcporter/index.ts index f5d2657b3..58edce7cd 100644 --- a/src/resources/extensions/mcporter/index.ts +++ b/src/resources/extensions/mcporter/index.ts @@ -102,22 +102,34 @@ async function runMcporter( } /** - * Read .mcp.json from the project root (cwd) and return servers not already - * discovered by mcporter. This bridges the gap where mcporter doesn't scan - * the standard .mcp.json config used by Claude Code, Cursor, etc. + * Read MCP server configs from project-level files (.mcp.json and .gsd/mcp.json) + * and return servers not already discovered by mcporter. + * + * Search order: + * 1. .mcp.json (Claude Code / Cursor standard) + * 2. .gsd/mcp.json (GSD-specific per-project config, #716) + * + * Project config overrides global — servers found here take precedence over + * identically-named servers from mcporter's global discovery. */ function readProjectMcpJson(knownNames: Set): McpServer[] { const servers: McpServer[] = []; - try { - const mcpJsonPath = join(process.cwd(), ".mcp.json"); - if (!existsSync(mcpJsonPath)) return servers; - const raw = readFileSync(mcpJsonPath, "utf-8"); - const data = JSON.parse(raw) as Record; - const mcpServers = (data.mcpServers ?? data.servers) as Record> | undefined; - if (!mcpServers || typeof mcpServers !== "object") return servers; + const configPaths = [ + join(process.cwd(), ".mcp.json"), + join(process.cwd(), ".gsd", "mcp.json"), + ]; - for (const [name, config] of Object.entries(mcpServers)) { - if (knownNames.has(name)) continue; // Already discovered by mcporter + for (const mcpJsonPath of configPaths) { + try { + if (!existsSync(mcpJsonPath)) continue; + const raw = readFileSync(mcpJsonPath, "utf-8"); + const data = JSON.parse(raw) as Record; + const mcpServers = (data.mcpServers ?? data.servers) as Record> | undefined; + if (!mcpServers || typeof mcpServers !== "object") continue; + + for (const [name, config] of Object.entries(mcpServers)) { + if (knownNames.has(name)) continue; // Already discovered + knownNames.add(name); // Prevent duplicates across config files const transport = (config.type as string) ?? (config.command ? "stdio" : config.url ? "http" : "unknown"); servers.push({ name, @@ -126,8 +138,9 @@ function readProjectMcpJson(knownNames: Set): McpServer[] { tools: [], // Tools unknown until mcp_discover is called }); } - } catch { - // Non-fatal — .mcp.json may not exist or be malformed + } catch { + // Non-fatal — config file may not exist or be malformed + } } return servers; }