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.
- package/dist/src/cli/commands/add-ide.js +7 -30
- package/dist/src/cli/commands/doctor.js +26 -1
- package/dist/src/cli/commands/sync.js +6 -1
- package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +80 -6
- package/dist/src/cli/doctor/checks/scripts-checks.js +1 -1
- package/dist/src/cli/mcp/fraim-mcp-latest-launcher.js +113 -13
- package/dist/src/cli/mcp/mcp-server-registry.js +14 -8
- package/dist/src/cli/setup/mcp-config-generator.js +42 -1
- package/package.json +1 -1
|
@@ -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
|
|
148
|
-
|
|
149
|
-
const
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
872
|
-
|
|
873
|
-
|
|
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',
|
|
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
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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:
|
|
133
|
-
args: [
|
|
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
|
-
|
|
21
|
-
|
|
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
|
-
|
|
30
|
-
|
|
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)) {
|