fraim 2.0.273 → 2.0.274

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -144,41 +144,18 @@ const configureIDEMCP = async (ide, fraimKey) => {
144
144
  else {
145
145
  // For JSON configs - intelligent merging
146
146
  const newConfig = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, {});
147
- const newMCPServers = newConfig[serversKey] || newConfig.mcpServers || {};
148
- // Merge MCP servers intelligently
149
- const mergedMCPServers = { ...existingMCPServers };
150
- const addedServers = [];
151
- const updatedServers = [];
152
- const skippedServers = [];
153
- const alwaysUpdateServers = new Set(['fraim']);
154
- for (const [serverName, serverConfig] of Object.entries(newMCPServers)) {
155
- if (!existingMCPServers[serverName]) {
156
- mergedMCPServers[serverName] = serverConfig;
157
- addedServers.push(serverName);
158
- }
159
- else if (alwaysUpdateServers.has(serverName)) {
160
- mergedMCPServers[serverName] = serverConfig;
161
- updatedServers.push(serverName);
162
- }
163
- else {
164
- skippedServers.push(serverName);
165
- }
166
- }
167
- // Merge with existing config
168
- const mergedConfig = {
169
- ...existingConfig,
170
- ...newConfig,
171
- [serversKey]: mergedMCPServers
172
- };
147
+ const { getAllMCPServerIds } = await Promise.resolve().then(() => __importStar(require('../mcp/mcp-server-registry')));
148
+ const baseServerIds = getAllMCPServerIds();
149
+ const mergeResult = (0, mcp_config_generator_1.mergeJsonMCPServers)(existingConfig, newConfig, serversKey, baseServerIds);
173
150
  // Write updated config
174
- fs_1.default.writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2));
175
- addedServers.forEach(server => {
151
+ fs_1.default.writeFileSync(configPath, JSON.stringify(mergeResult.config, null, 2));
152
+ mergeResult.addedServers.forEach(server => {
176
153
  console.log(chalk_1.default.green(` ✅ Added ${server} MCP server`));
177
154
  });
178
- updatedServers.forEach(server => {
155
+ mergeResult.replacedServers.forEach(server => {
179
156
  console.log(chalk_1.default.blue(` Updated ${server} MCP server`));
180
157
  });
181
- skippedServers.forEach(server => {
158
+ mergeResult.skippedServers.forEach(server => {
182
159
  console.log(chalk_1.default.gray(` ⏭️ Skipped ${server} (already exists)`));
183
160
  });
184
161
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.doctorCommand = void 0;
4
4
  exports.getAllChecks = getAllChecks;
5
+ exports.runFixMcpRepair = runFixMcpRepair;
5
6
  const commander_1 = require("commander");
6
7
  const fs_1 = require("fs");
7
8
  const path_1 = require("path");
@@ -14,6 +15,7 @@ const workflow_checks_1 = require("../doctor/checks/workflow-checks");
14
15
  const ide_config_checks_1 = require("../doctor/checks/ide-config-checks");
15
16
  const mcp_connectivity_checks_1 = require("../doctor/checks/mcp-connectivity-checks");
16
17
  const scripts_checks_1 = require("../doctor/checks/scripts-checks");
18
+ const add_ide_1 = require("./add-ide");
17
19
  // Read version from package.json
18
20
  const getFramVersion = () => {
19
21
  try {
@@ -59,19 +61,38 @@ function getAllChecks() {
59
61
  ...(0, scripts_checks_1.getScriptsChecks)()
60
62
  ];
61
63
  }
64
+ async function runFixMcpRepair(options, repair = add_ide_1.runAddIDE) {
65
+ if (!options.fixMcp) {
66
+ return;
67
+ }
68
+ if (!options.json) {
69
+ await repair({ all: true });
70
+ return;
71
+ }
72
+ const originalLog = console.log;
73
+ try {
74
+ console.log = () => undefined;
75
+ await repair({ all: true });
76
+ }
77
+ finally {
78
+ console.log = originalLog;
79
+ }
80
+ }
62
81
  exports.doctorCommand = new commander_1.Command('doctor')
63
82
  .description('Validate FRAIM installation and configuration')
64
83
  .option('--test-mcp', 'Test only MCP server connectivity')
65
84
  .option('--test-config', 'Validate only configuration files')
66
85
  .option('--test-jobs', 'Check only job status')
86
+ .option('--fix-mcp', 'Repair FRAIM-owned MCP server entries in detected IDE configs, then validate MCP connectivity')
67
87
  .option('--verbose', 'Show detailed output including successful checks')
68
88
  .option('--json', 'Output results as JSON')
69
89
  .action(async (cmdOptions) => {
70
90
  const startTime = Date.now();
71
91
  const options = {
72
- testMcp: cmdOptions.testMcp,
92
+ testMcp: cmdOptions.testMcp || cmdOptions.fixMcp,
73
93
  testConfig: cmdOptions.testConfig,
74
94
  testJobs: cmdOptions.testJobs,
95
+ fixMcp: cmdOptions.fixMcp,
75
96
  verbose: cmdOptions.verbose,
76
97
  json: cmdOptions.json
77
98
  };
@@ -81,6 +102,7 @@ exports.doctorCommand = new commander_1.Command('doctor')
81
102
  testMcp: options.testMcp || false,
82
103
  testConfig: options.testConfig || false,
83
104
  testJobs: options.testJobs || false,
105
+ fixMcp: options.fixMcp || false,
84
106
  verbose: options.verbose || false,
85
107
  json: options.json || false
86
108
  }
@@ -94,11 +116,14 @@ exports.doctorCommand = new commander_1.Command('doctor')
94
116
  trackMetric('doctor.flags.test_config', 1);
95
117
  if (options.testJobs)
96
118
  trackMetric('doctor.flags.test_jobs', 1);
119
+ if (options.fixMcp)
120
+ trackMetric('doctor.flags.fix_mcp', 1);
97
121
  if (options.verbose)
98
122
  trackMetric('doctor.flags.verbose', 1);
99
123
  if (options.json)
100
124
  trackMetric('doctor.flags.json', 1);
101
125
  try {
126
+ await runFixMcpRepair(options);
102
127
  // Collect all checks
103
128
  const checks = getAllChecks();
104
129
  // Run checks
@@ -283,13 +283,18 @@ const runSync = async (options) => {
283
283
  console.error(chalk_1.default.red('Local sync requires a FRAIM project directory (fraim/ must exist).'));
284
284
  failSync(failHard, 'Local sync requires a project directory.');
285
285
  }
286
+ const localRegistryFiles = await (0, remote_sync_1.fetchRegistryFiles)(localUrl, 'local-dev');
286
287
  const result = await syncFromRemote({
287
288
  remoteUrl: localUrl,
288
289
  apiKey: 'local-dev',
289
290
  projectRoot,
290
- skipUpdates: true
291
+ skipUpdates: true,
292
+ registryFiles: localRegistryFiles
291
293
  });
292
294
  if (result.success) {
295
+ // Local-dev sync bypasses Layer 1 (machine-level sync) below, but scripts
296
+ // still need to land in ~/.fraim/scripts/ or the reported count is a lie.
297
+ await (0, remote_sync_1.syncScriptsToUserDir)(localRegistryFiles);
293
298
  console.log(chalk_1.default.green(`Successfully synced ${result.employeeJobsSynced} ai-employee jobs, ${result.managerJobsSynced} ai-manager jobs, ${result.skillsSynced} skills, ${result.rulesSynced} rules, ${result.scriptsSynced} scripts, and ${result.docsSynced} docs from local server`));
294
299
  const fraimDir = (0, project_fraim_paths_1.getWorkspaceFraimDir)(projectRoot);
295
300
  removeLegacyVersionFromConfig(fraimDir);
@@ -55,7 +55,6 @@ const axios_1 = __importDefault(require("axios"));
55
55
  const toml = __importStar(require("toml"));
56
56
  const ide_detector_1 = require("../../setup/ide-detector");
57
57
  const fraim_mcp_latest_launcher_1 = require("../../mcp/fraim-mcp-latest-launcher");
58
- const command_resolution_1 = require("../../mcp/command-resolution");
59
58
  const fraim_mcp_diagnostics_1 = require("./fraim-mcp-diagnostics");
60
59
  // Cache the npm major version so execFileSync is called at most once per process.
61
60
  // Without caching, each IDE config check calls diagnoseFraimMcpLaunchPlan which calls
@@ -123,6 +122,58 @@ function getNpmMajorVersion() {
123
122
  function diagnoseFraimMcpLaunchPlan(fraimServer, platform = process.platform, npmMajorVersion = getNpmMajorVersion()) {
124
123
  const command = String(fraimServer?.command || '');
125
124
  const args = Array.isArray(fraimServer?.args) ? fraimServer.args.map(String) : [];
125
+ const fraimMcpShimName = platform === 'win32' ? 'fraim-mcp.cmd' : 'fraim-mcp.sh';
126
+ const fraimMcpShimPath = (0, fraim_mcp_latest_launcher_1.getFraimMcpShimPath)();
127
+ const fraimNpxShimPath = (0, fraim_mcp_latest_launcher_1.getFraimNpxShimPath)();
128
+ const looksLikeStableFraimShim = command
129
+ && path_1.default.basename(command).toLowerCase() === fraimMcpShimName
130
+ && path_1.default.basename(path_1.default.dirname(command)).toLowerCase() === 'bin';
131
+ const usesStableFraimShim = looksLikeStableFraimShim
132
+ && path_1.default.resolve(command) === path_1.default.resolve(fraimMcpShimPath);
133
+ if (usesStableFraimShim) {
134
+ const missingStableShim = [command, fraimNpxShimPath].find((value) => !fs_1.default.existsSync(value));
135
+ if (missingStableShim) {
136
+ return {
137
+ status: 'error',
138
+ message: 'FRAIM MCP config references a stable shim that is missing on disk',
139
+ suggestion: 'Run: fraim doctor --fix-mcp or fraim add-ide to repair local MCP config',
140
+ details: {
141
+ launchPlanSource: 'stale-persisted-path',
142
+ phase: 'preflight',
143
+ command,
144
+ args,
145
+ stalePath: missingStableShim,
146
+ expectedCommand: fraimMcpShimPath,
147
+ companionNpxShim: fraimNpxShimPath
148
+ }
149
+ };
150
+ }
151
+ return {
152
+ status: 'passed',
153
+ message: 'FRAIM MCP config uses the stable FRAIM shim',
154
+ details: {
155
+ launchPlanSource: 'fraim-stable-shim',
156
+ command,
157
+ expectedCommand: fraimMcpShimPath,
158
+ companionNpxShim: fraimNpxShimPath
159
+ }
160
+ };
161
+ }
162
+ if (looksLikeStableFraimShim) {
163
+ return {
164
+ status: 'error',
165
+ message: 'FRAIM MCP config references a stable shim outside the expected FRAIM user bin directory',
166
+ suggestion: 'Run: fraim doctor --fix-mcp or fraim add-ide to repair local MCP config',
167
+ details: {
168
+ launchPlanSource: 'unexpected-stable-shim-path',
169
+ phase: 'preflight',
170
+ command,
171
+ args,
172
+ expectedCommand: fraimMcpShimPath,
173
+ companionNpxShim: fraimNpxShimPath
174
+ }
175
+ };
176
+ }
126
177
  const usesDirectLatest = command.toLowerCase().includes('npx')
127
178
  && args.some((arg) => arg === 'fraim@latest');
128
179
  if (usesDirectLatest) {
@@ -152,6 +203,30 @@ function diagnoseFraimMcpLaunchPlan(fraimServer, platform = process.platform, np
152
203
  }
153
204
  };
154
205
  }
206
+ const absolutePathValues = [command, ...args]
207
+ .filter((value) => value && path_1.default.isAbsolute(value));
208
+ const stalePath = absolutePathValues.find((value) => {
209
+ const lower = value.toLowerCase();
210
+ return lower.includes(`${path_1.default.sep}temp${path_1.default.sep}`)
211
+ || lower.includes(`${path_1.default.sep}tmp${path_1.default.sep}`)
212
+ || lower.includes('fraim-deleted-launcher')
213
+ || !fs_1.default.existsSync(value);
214
+ });
215
+ if (stalePath) {
216
+ return {
217
+ status: 'error',
218
+ message: 'FRAIM MCP config persists a stale runtime or temporary launcher path',
219
+ suggestion: 'Run: fraim doctor --fix-mcp or fraim add-ide to repair local MCP config',
220
+ details: {
221
+ launchPlanSource: 'stale-persisted-path',
222
+ phase: 'preflight',
223
+ command,
224
+ args,
225
+ stalePath,
226
+ expectedCommand: fraimMcpShimPath
227
+ }
228
+ };
229
+ }
155
230
  return null;
156
231
  }
157
232
  /**
@@ -868,10 +943,9 @@ async function testStdioMCPServer(serverName, command, args) {
868
943
  * Exported separately so callers can obtain just the runtime checks if needed.
869
944
  */
870
945
  function getStdioMCPRuntimeChecks() {
871
- const npx = (0, command_resolution_1.resolveManagedCommand)('npx');
872
- // For the fraim MCP server we use the latest launcher so the test
873
- // exercises the same path that IDE configs use.
874
- const fraimLauncherPath = (0, fraim_mcp_latest_launcher_1.getFraimMcpLatestLauncherPath)();
946
+ (0, fraim_mcp_latest_launcher_1.ensureFraimMcpLatestLauncher)();
947
+ const npx = (0, fraim_mcp_latest_launcher_1.getFraimNpxShimPath)();
948
+ const fraimMcp = (0, fraim_mcp_latest_launcher_1.getFraimMcpShimPath)();
875
949
  return [
876
950
  {
877
951
  name: 'git stdio runtime check',
@@ -889,7 +963,7 @@ function getStdioMCPRuntimeChecks() {
889
963
  name: 'fraim stdio runtime check',
890
964
  category: 'mcpConnectivity',
891
965
  critical: false,
892
- run: () => testStdioMCPServer('fraim', process.execPath, [fraimLauncherPath])
966
+ run: () => testStdioMCPServer('fraim', fraimMcp, [])
893
967
  }
894
968
  ];
895
969
  }
@@ -168,7 +168,7 @@ function checkPythonAvailability() {
168
168
  }
169
169
  if (process.platform === 'win32') {
170
170
  // Primary probe: py launcher (not subject to App Execution Alias)
171
- const pyResult = (0, child_process_1.spawnSync)('py', ['--version'], { timeout: 1500, encoding: 'utf8' });
171
+ const pyResult = (0, child_process_1.spawnSync)('py --version', { timeout: 1500, encoding: 'utf8', shell: true });
172
172
  if (pyResult.status === 0) {
173
173
  const version = (pyResult.stdout || pyResult.stderr || '').trim();
174
174
  return {
@@ -4,6 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getFraimMcpLatestLauncherPath = getFraimMcpLatestLauncherPath;
7
+ exports.getFraimMcpShimPath = getFraimMcpShimPath;
8
+ exports.getFraimNpxShimPath = getFraimNpxShimPath;
7
9
  exports.ensureFraimMcpLatestLauncher = ensureFraimMcpLatestLauncher;
8
10
  const fs_1 = __importDefault(require("fs"));
9
11
  const os_1 = __importDefault(require("os"));
@@ -113,24 +115,122 @@ process.exit(status);
113
115
  function getFraimMcpLatestLauncherPath() {
114
116
  return path_1.default.join(process.env.FRAIM_USER_DIR || path_1.default.join(os_1.default.homedir(), '.fraim'), 'bin', 'fraim-mcp-latest.js');
115
117
  }
118
+ function getFraimMcpShimPath() {
119
+ return path_1.default.join(process.env.FRAIM_USER_DIR || path_1.default.join(os_1.default.homedir(), '.fraim'), 'bin', process.platform === 'win32' ? 'fraim-mcp.cmd' : 'fraim-mcp.sh');
120
+ }
121
+ function getFraimNpxShimPath() {
122
+ return path_1.default.join(process.env.FRAIM_USER_DIR || path_1.default.join(os_1.default.homedir(), '.fraim'), 'bin', process.platform === 'win32' ? 'fraim-npx.cmd' : 'fraim-npx.sh');
123
+ }
124
+ const windowsFraimMcpShimSource = `@echo off
125
+ setlocal
126
+ for %%I in ("%~dp0..") do set "FRAIM_USER_DIR_VALUE=%%~fI"
127
+ where node >nul 2>nul
128
+ if not errorlevel 1 (
129
+ node "%FRAIM_USER_DIR_VALUE%\\bin\\fraim-mcp-latest.js" %*
130
+ exit /b %ERRORLEVEL%
131
+ )
132
+ if exist "%FRAIM_USER_DIR_VALUE%\\node\\node.exe" (
133
+ set "PATH=%FRAIM_USER_DIR_VALUE%\\node;%PATH%"
134
+ "%FRAIM_USER_DIR_VALUE%\\node\\node.exe" "%FRAIM_USER_DIR_VALUE%\\bin\\fraim-mcp-latest.js" %*
135
+ exit /b %ERRORLEVEL%
136
+ )
137
+ for /f "delims=" %%D in ('dir /b /ad /o-n "%FRAIM_USER_DIR_VALUE%\\node\\node-v*" 2^>nul') do (
138
+ if exist "%FRAIM_USER_DIR_VALUE%\\node\\%%D\\node.exe" (
139
+ set "PATH=%FRAIM_USER_DIR_VALUE%\\node\\%%D;%PATH%"
140
+ "%FRAIM_USER_DIR_VALUE%\\node\\%%D\\node.exe" "%FRAIM_USER_DIR_VALUE%\\bin\\fraim-mcp-latest.js" %*
141
+ exit /b %ERRORLEVEL%
142
+ )
143
+ )
144
+ echo [fraim-mcp] Could not find node on PATH or in "%FRAIM_USER_DIR_VALUE%\\node" 1>&2
145
+ exit /b 1
146
+ `;
147
+ const windowsFraimNpxShimSource = `@echo off
148
+ setlocal
149
+ for %%I in ("%~dp0..") do set "FRAIM_USER_DIR_VALUE=%%~fI"
150
+ where npx >nul 2>nul
151
+ if not errorlevel 1 (
152
+ npx %*
153
+ exit /b %ERRORLEVEL%
154
+ )
155
+ if exist "%FRAIM_USER_DIR_VALUE%\\node\\npx.cmd" (
156
+ "%FRAIM_USER_DIR_VALUE%\\node\\npx.cmd" %*
157
+ exit /b %ERRORLEVEL%
158
+ )
159
+ for /f "delims=" %%D in ('dir /b /ad /o-n "%FRAIM_USER_DIR_VALUE%\\node\\node-v*" 2^>nul') do (
160
+ if exist "%FRAIM_USER_DIR_VALUE%\\node\\%%D\\npx.cmd" (
161
+ "%FRAIM_USER_DIR_VALUE%\\node\\%%D\\npx.cmd" %*
162
+ exit /b %ERRORLEVEL%
163
+ )
164
+ )
165
+ echo [fraim-npx] Could not find npx on PATH or in "%FRAIM_USER_DIR_VALUE%\\node" 1>&2
166
+ exit /b 1
167
+ `;
168
+ const posixFraimMcpShimSource = `#!/usr/bin/env sh
169
+ set -eu
170
+ SHIM_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
171
+ FRAIM_USER_DIR_VALUE=$(dirname "$SHIM_DIR")
172
+ LAUNCHER="$FRAIM_USER_DIR_VALUE/bin/fraim-mcp-latest.js"
173
+
174
+ if command -v node >/dev/null 2>&1; then
175
+ exec node "$LAUNCHER" "$@"
176
+ fi
177
+ if [ -x "$FRAIM_USER_DIR_VALUE/node/bin/node" ]; then
178
+ PATH="$FRAIM_USER_DIR_VALUE/node/bin:$PATH"
179
+ export PATH
180
+ exec "$FRAIM_USER_DIR_VALUE/node/bin/node" "$LAUNCHER" "$@"
181
+ fi
182
+ if [ -x "$FRAIM_USER_DIR_VALUE/node/node" ]; then
183
+ PATH="$FRAIM_USER_DIR_VALUE/node:$PATH"
184
+ export PATH
185
+ exec "$FRAIM_USER_DIR_VALUE/node/node" "$LAUNCHER" "$@"
186
+ fi
187
+
188
+ echo "[fraim-mcp] Could not find node on PATH or in $FRAIM_USER_DIR_VALUE/node" >&2
189
+ exit 1
190
+ `;
191
+ const posixFraimNpxShimSource = `#!/usr/bin/env sh
192
+ set -eu
193
+ SHIM_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
194
+ FRAIM_USER_DIR_VALUE=$(dirname "$SHIM_DIR")
195
+
196
+ if command -v npx >/dev/null 2>&1; then
197
+ exec npx "$@"
198
+ fi
199
+ if [ -x "$FRAIM_USER_DIR_VALUE/node/bin/npx" ]; then
200
+ exec "$FRAIM_USER_DIR_VALUE/node/bin/npx" "$@"
201
+ fi
202
+ if [ -x "$FRAIM_USER_DIR_VALUE/node/npx" ]; then
203
+ exec "$FRAIM_USER_DIR_VALUE/node/npx" "$@"
204
+ fi
205
+
206
+ echo "[fraim-npx] Could not find npx on PATH or in $FRAIM_USER_DIR_VALUE/node" >&2
207
+ exit 1
208
+ `;
209
+ const writeFileIfChanged = (filePath, content, mode) => {
210
+ if (!fs_1.default.existsSync(filePath) || fs_1.default.readFileSync(filePath, 'utf8') !== content) {
211
+ fs_1.default.writeFileSync(filePath, content, 'utf8');
212
+ }
213
+ if (mode !== undefined) {
214
+ try {
215
+ fs_1.default.chmodSync(filePath, mode);
216
+ }
217
+ catch {
218
+ // Best effort only. Some filesystems do not support POSIX modes.
219
+ }
220
+ }
221
+ };
116
222
  function ensureFraimMcpLatestLauncher() {
117
223
  const launcherPath = getFraimMcpLatestLauncherPath();
118
224
  const launcherDir = path_1.default.dirname(launcherPath);
119
225
  fs_1.default.mkdirSync(launcherDir, { recursive: true });
120
- if (!fs_1.default.existsSync(launcherPath) || fs_1.default.readFileSync(launcherPath, 'utf8') !== launcherSource) {
121
- fs_1.default.writeFileSync(launcherPath, launcherSource, 'utf8');
122
- if (process.platform !== 'win32') {
123
- try {
124
- fs_1.default.chmodSync(launcherPath, 0o755);
125
- }
126
- catch {
127
- // Best effort only. IDE configs invoke this through node, so chmod is not required.
128
- }
129
- }
130
- }
226
+ writeFileIfChanged(launcherPath, launcherSource, process.platform === 'win32' ? undefined : 0o755);
227
+ const fraimMcpShimPath = getFraimMcpShimPath();
228
+ const fraimNpxShimPath = getFraimNpxShimPath();
229
+ writeFileIfChanged(fraimMcpShimPath, process.platform === 'win32' ? windowsFraimMcpShimSource : posixFraimMcpShimSource, process.platform === 'win32' ? undefined : 0o755);
230
+ writeFileIfChanged(fraimNpxShimPath, process.platform === 'win32' ? windowsFraimNpxShimSource : posixFraimNpxShimSource, process.platform === 'win32' ? undefined : 0o755);
131
231
  return {
132
- command: process.execPath,
133
- args: [launcherPath],
232
+ command: fraimMcpShimPath,
233
+ args: [],
134
234
  path: launcherPath
135
235
  };
136
236
  }
@@ -16,19 +16,25 @@ exports.BASE_MCP_SERVERS = [
16
16
  id: 'git',
17
17
  name: 'Git',
18
18
  description: 'Git repository operations (commit, branch, merge, etc.)',
19
- buildServer: () => ({
20
- command: (0, command_resolution_1.resolveManagedCommand)('npx'),
21
- args: ['-y', '@cyanheads/git-mcp-server']
22
- })
19
+ buildServer: () => {
20
+ (0, fraim_mcp_latest_launcher_1.ensureFraimMcpLatestLauncher)();
21
+ return {
22
+ command: (0, fraim_mcp_latest_launcher_1.getFraimNpxShimPath)(),
23
+ args: ['-y', '@cyanheads/git-mcp-server']
24
+ };
25
+ }
23
26
  },
24
27
  {
25
28
  id: 'playwright',
26
29
  name: 'Playwright',
27
30
  description: 'Browser automation and testing',
28
- buildServer: () => ({
29
- command: (0, command_resolution_1.resolveManagedCommand)('npx'),
30
- args: ['-y', '@playwright/mcp']
31
- })
31
+ buildServer: () => {
32
+ (0, fraim_mcp_latest_launcher_1.ensureFraimMcpLatestLauncher)();
33
+ return {
34
+ command: (0, fraim_mcp_latest_launcher_1.getFraimNpxShimPath)(),
35
+ args: ['-y', '@playwright/mcp']
36
+ };
37
+ }
32
38
  },
33
39
  {
34
40
  id: 'fraim',
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.generateMCPConfig = exports.generateWindsurfMCPServers = exports.generateCopilotCliMCPServers = exports.generateGeminiCliMCPServers = exports.generateVSCodeMCPServers = exports.generateGrokMCPServers = exports.generateCodexMCPServers = exports.generateKiroMCPServers = exports.generateClaudeCodeMCPServers = exports.generateClaudeMCPServers = exports.generateStandardMCPServers = exports.mergeTomlMCPServers = exports.extractTomlMcpServerBlock = void 0;
3
+ exports.generateMCPConfig = exports.generateWindsurfMCPServers = exports.generateCopilotCliMCPServers = exports.generateGeminiCliMCPServers = exports.generateVSCodeMCPServers = exports.generateGrokMCPServers = exports.generateCodexMCPServers = exports.generateKiroMCPServers = exports.generateClaudeCodeMCPServers = exports.generateClaudeMCPServers = exports.generateStandardMCPServers = exports.mergeJsonMCPServers = exports.mergeTomlMCPServers = exports.extractTomlMcpServerBlock = void 0;
4
4
  const mcp_server_builder_1 = require("../mcp/mcp-server-builder");
5
5
  const ide_formats_1 = require("../mcp/ide-formats");
6
6
  const normalizeTokens = (tokenInput) => {
@@ -91,6 +91,47 @@ const mergeTomlMCPServers = (existingContent, generatedContent, servers) => {
91
91
  };
92
92
  };
93
93
  exports.mergeTomlMCPServers = mergeTomlMCPServers;
94
+ const mergeJsonMCPServers = (existingConfig, generatedConfig, serversKey, servers) => {
95
+ const existingServers = existingConfig?.[serversKey] || {};
96
+ const generatedServers = generatedConfig?.[serversKey] || generatedConfig?.mcpServers || {};
97
+ const mergedServers = { ...existingServers };
98
+ const addedServers = [];
99
+ const replacedServers = [];
100
+ const skippedServers = [];
101
+ for (const server of servers) {
102
+ if (!Object.prototype.hasOwnProperty.call(generatedServers, server)) {
103
+ skippedServers.push(server);
104
+ continue;
105
+ }
106
+ const generatedServer = generatedServers[server];
107
+ const existingServer = existingServers[server];
108
+ if (!existingServer) {
109
+ mergedServers[server] = generatedServer;
110
+ addedServers.push(server);
111
+ continue;
112
+ }
113
+ if (JSON.stringify(existingServer) === JSON.stringify(generatedServer)) {
114
+ skippedServers.push(server);
115
+ continue;
116
+ }
117
+ mergedServers[server] = generatedServer;
118
+ replacedServers.push(server);
119
+ }
120
+ return {
121
+ config: {
122
+ ...existingConfig,
123
+ ...generatedConfig,
124
+ [serversKey]: {
125
+ ...(generatedConfig?.[serversKey] || {}),
126
+ ...mergedServers
127
+ }
128
+ },
129
+ addedServers,
130
+ replacedServers,
131
+ skippedServers
132
+ };
133
+ };
134
+ exports.mergeJsonMCPServers = mergeJsonMCPServers;
94
135
  // Helper function to add all provider servers with their configs
95
136
  const addProviderServers = async (builder, tokens, providerConfigs) => {
96
137
  for (const [providerId, token] of Object.entries(tokens)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim",
3
- "version": "2.0.273",
3
+ "version": "2.0.274",
4
4
  "description": "FRAIM core CLI and MCP package.",
5
5
  "main": "index.js",
6
6
  "bin": {