imcp 0.0.4 → 0.0.5
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/README.md +3 -4
- package/dist/cli/commands/install.js +2 -0
- package/dist/cli/commands/list.js +1 -0
- package/dist/cli/index.js +1 -2
- package/dist/core/ConfigurationLoader.d.ts +32 -0
- package/dist/core/ConfigurationLoader.js +213 -0
- package/dist/core/ConfigurationProvider.d.ts +2 -3
- package/dist/core/ConfigurationProvider.js +13 -182
- package/dist/core/InstallationService.d.ts +8 -0
- package/dist/core/InstallationService.js +124 -96
- package/dist/core/RequirementService.d.ts +1 -1
- package/dist/core/RequirementService.js +5 -9
- package/dist/core/constants.js +14 -1
- package/dist/core/installers/BaseInstaller.d.ts +5 -4
- package/dist/core/installers/BaseInstaller.js +17 -28
- package/dist/core/installers/ClientInstaller.js +134 -43
- package/dist/core/installers/CommandInstaller.d.ts +1 -0
- package/dist/core/installers/CommandInstaller.js +3 -0
- package/dist/core/installers/GeneralInstaller.d.ts +1 -0
- package/dist/core/installers/GeneralInstaller.js +3 -0
- package/dist/core/installers/InstallerFactory.d.ts +9 -7
- package/dist/core/installers/InstallerFactory.js +10 -8
- package/dist/core/installers/NpmInstaller.d.ts +1 -0
- package/dist/core/installers/NpmInstaller.js +3 -0
- package/dist/core/installers/PipInstaller.d.ts +6 -3
- package/dist/core/installers/PipInstaller.js +21 -8
- package/dist/core/installers/RequirementInstaller.d.ts +4 -3
- package/dist/core/installers/clients/ClientInstaller.d.ts +23 -0
- package/dist/core/installers/clients/ClientInstaller.js +573 -0
- package/dist/core/installers/clients/ExtensionInstaller.d.ts +26 -0
- package/dist/core/installers/clients/ExtensionInstaller.js +149 -0
- package/dist/core/installers/index.d.ts +8 -6
- package/dist/core/installers/index.js +8 -6
- package/dist/core/installers/requirements/BaseInstaller.d.ts +59 -0
- package/dist/core/installers/requirements/BaseInstaller.js +168 -0
- package/dist/core/installers/requirements/CommandInstaller.d.ts +37 -0
- package/dist/core/installers/requirements/CommandInstaller.js +173 -0
- package/dist/core/installers/requirements/GeneralInstaller.d.ts +33 -0
- package/dist/core/installers/requirements/GeneralInstaller.js +86 -0
- package/dist/core/installers/requirements/InstallerFactory.d.ts +54 -0
- package/dist/core/installers/requirements/InstallerFactory.js +97 -0
- package/dist/core/installers/requirements/NpmInstaller.d.ts +26 -0
- package/dist/core/installers/requirements/NpmInstaller.js +128 -0
- package/dist/core/installers/requirements/PipInstaller.d.ts +28 -0
- package/dist/core/installers/requirements/PipInstaller.js +128 -0
- package/{src/core/installers/RequirementInstaller.ts → dist/core/installers/requirements/RequirementInstaller.d.ts} +33 -38
- package/dist/core/installers/requirements/RequirementInstaller.js +3 -0
- package/dist/core/types.d.ts +4 -1
- package/dist/services/ServerService.js +1 -1
- package/dist/utils/githubUtils.d.ts +11 -0
- package/dist/utils/githubUtils.js +88 -0
- package/dist/utils/osUtils.d.ts +17 -0
- package/dist/utils/osUtils.js +184 -0
- package/dist/web/public/css/modal.css +97 -3
- package/dist/web/public/index.html +21 -2
- package/dist/web/public/js/modal.js +177 -28
- package/dist/web/public/js/serverCategoryDetails.js +12 -10
- package/dist/web/public/js/serverCategoryList.js +20 -5
- package/dist/web/public/modal.html +27 -13
- package/package.json +1 -1
- package/src/cli/commands/install.ts +4 -2
- package/src/cli/commands/list.ts +1 -0
- package/src/cli/index.ts +1 -1
- package/src/core/ConfigurationLoader.ts +251 -0
- package/src/core/ConfigurationProvider.ts +13 -195
- package/src/core/InstallationService.ts +140 -106
- package/src/core/RequirementService.ts +5 -10
- package/src/core/constants.ts +15 -1
- package/src/core/installers/{ClientInstaller.ts → clients/ClientInstaller.ts} +157 -51
- package/src/core/installers/clients/ExtensionInstaller.ts +162 -0
- package/src/core/installers/index.ts +9 -7
- package/src/core/installers/{BaseInstaller.ts → requirements/BaseInstaller.ts} +10 -118
- package/src/core/installers/{CommandInstaller.ts → requirements/CommandInstaller.ts} +7 -3
- package/src/core/installers/{GeneralInstaller.ts → requirements/GeneralInstaller.ts} +6 -2
- package/src/core/installers/{InstallerFactory.ts → requirements/InstallerFactory.ts} +11 -9
- package/src/core/installers/{NpmInstaller.ts → requirements/NpmInstaller.ts} +7 -4
- package/src/core/installers/{PipInstaller.ts → requirements/PipInstaller.ts} +26 -10
- package/src/core/installers/requirements/RequirementInstaller.ts +41 -0
- package/src/core/types.ts +4 -1
- package/src/services/ServerService.ts +1 -1
- package/src/utils/githubUtils.ts +103 -0
- package/src/utils/osUtils.ts +206 -15
- package/src/web/public/css/modal.css +97 -3
- package/src/web/public/index.html +21 -2
- package/src/web/public/js/modal.js +177 -28
- package/src/web/public/js/serverCategoryDetails.js +12 -10
- package/src/web/public/js/serverCategoryList.js +20 -5
- package/src/web/public/modal.html +27 -13
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { GetBrowserPath } from '../../utils/osUtils.js';
|
|
1
2
|
import { ConfigurationProvider } from '../ConfigurationProvider.js';
|
|
2
3
|
import { SUPPORTED_CLIENTS } from '../constants.js';
|
|
3
4
|
import { resolveNpmModulePath, readJsonFile, writeJsonFile } from '../../utils/clientUtils.js';
|
|
4
5
|
import { exec } from 'child_process';
|
|
5
6
|
import { promisify } from 'util';
|
|
6
7
|
import { Logger } from '../../utils/logger.js';
|
|
8
|
+
import { getPythonPackagePath, getSystemPythonPackageDirectory } from '../../utils/osUtils.js';
|
|
9
|
+
const execAsync = promisify(exec); // Moved promisify here for reuse
|
|
7
10
|
export class ClientInstaller {
|
|
8
11
|
categoryName;
|
|
9
12
|
serverName;
|
|
@@ -21,7 +24,6 @@ export class ClientInstaller {
|
|
|
21
24
|
return `install-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
22
25
|
}
|
|
23
26
|
async getNpmPath() {
|
|
24
|
-
const execAsync = promisify(exec);
|
|
25
27
|
try {
|
|
26
28
|
// Execute the get-command npm command to find the npm path
|
|
27
29
|
const { stdout } = await execAsync('powershell -Command "get-command npm | Select-Object -ExpandProperty Source"');
|
|
@@ -41,7 +43,6 @@ export class ClientInstaller {
|
|
|
41
43
|
* @returns True if the command is available, false otherwise
|
|
42
44
|
*/
|
|
43
45
|
async isCommandAvailable(command) {
|
|
44
|
-
const execAsync = promisify(exec);
|
|
45
46
|
try {
|
|
46
47
|
if (process.platform === 'win32') {
|
|
47
48
|
// Windows-specific command check
|
|
@@ -78,7 +79,8 @@ export class ClientInstaller {
|
|
|
78
79
|
return false;
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
|
-
|
|
82
|
+
// Modified to accept ServerInstallOptions
|
|
83
|
+
async installClient(clientName, options) {
|
|
82
84
|
// Check if client is supported
|
|
83
85
|
if (!SUPPORTED_CLIENTS[clientName]) {
|
|
84
86
|
return {
|
|
@@ -101,11 +103,13 @@ export class ClientInstaller {
|
|
|
101
103
|
// Update server status with initial client installation status
|
|
102
104
|
await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, initialStatus);
|
|
103
105
|
// Start the asynchronous installation process without awaiting it
|
|
104
|
-
|
|
106
|
+
// Pass options down
|
|
107
|
+
this.processInstallation(clientName, operationId, options);
|
|
105
108
|
// Return the initial status immediately
|
|
106
109
|
return initialStatus;
|
|
107
110
|
}
|
|
108
|
-
|
|
111
|
+
// Modified to accept ServerInstallOptions
|
|
112
|
+
async processInstallation(clientName, operationId, options) {
|
|
109
113
|
try {
|
|
110
114
|
// Check requirements before installation
|
|
111
115
|
let requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
|
|
@@ -179,8 +183,8 @@ export class ClientInstaller {
|
|
|
179
183
|
return;
|
|
180
184
|
}
|
|
181
185
|
try {
|
|
182
|
-
// Install client-specific configuration
|
|
183
|
-
const result = await this.installClientConfig(clientName,
|
|
186
|
+
// Install client-specific configuration, passing options down
|
|
187
|
+
const result = await this.installClientConfig(clientName, options, serverConfig, feedConfiguration);
|
|
184
188
|
const finalStatus = {
|
|
185
189
|
status: result.success ? 'completed' : 'failed',
|
|
186
190
|
type: 'install',
|
|
@@ -190,6 +194,9 @@ export class ClientInstaller {
|
|
|
190
194
|
error: result.success ? undefined : result.message
|
|
191
195
|
};
|
|
192
196
|
await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, finalStatus);
|
|
197
|
+
if (result.success) {
|
|
198
|
+
await this.configProvider.reloadClientMCPSettings();
|
|
199
|
+
}
|
|
193
200
|
}
|
|
194
201
|
catch (error) {
|
|
195
202
|
const errorStatus = {
|
|
@@ -215,7 +222,9 @@ export class ClientInstaller {
|
|
|
215
222
|
await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, errorStatus);
|
|
216
223
|
}
|
|
217
224
|
}
|
|
218
|
-
|
|
225
|
+
// Modified to accept ServerInstallOptions
|
|
226
|
+
async installClientConfig(clientName, options, // Use options directly
|
|
227
|
+
serverConfig, feedConfig) {
|
|
219
228
|
try {
|
|
220
229
|
if (!SUPPORTED_CLIENTS[clientName]) {
|
|
221
230
|
Logger.debug(`Client ${clientName} is not supported.`);
|
|
@@ -233,7 +242,6 @@ export class ClientInstaller {
|
|
|
233
242
|
const isVSCodeInsidersInstalled = await this.isCommandAvailable('code-insiders');
|
|
234
243
|
Logger.debug(isVSCodeInstalled ? 'VS Code detected on system' : 'VS Code not detected on system');
|
|
235
244
|
Logger.debug(isVSCodeInsidersInstalled ? 'VS Code Insiders detected on system' : 'VS Code Insiders not detected on system');
|
|
236
|
-
Logger.debug(`VS Code Insiders installed: ${isVSCodeInsidersInstalled}`);
|
|
237
245
|
// If neither is installed, we can't proceed
|
|
238
246
|
if (!isVSCodeInstalled && !isVSCodeInsidersInstalled) {
|
|
239
247
|
Logger.debug('No VS Code installations detected on system. Cannot update settings.');
|
|
@@ -242,25 +250,95 @@ export class ClientInstaller {
|
|
|
242
250
|
message: `Neither VS Code nor VS Code Insiders are installed on this system. Cannot update settings for client: ${clientName}. Please install VS Code or VS Code Insiders and try again.`
|
|
243
251
|
};
|
|
244
252
|
}
|
|
245
|
-
//
|
|
253
|
+
// --- Start of new logic ---
|
|
254
|
+
// Clone the base installation configuration to avoid modifying the original serverConfig
|
|
246
255
|
const installConfig = JSON.parse(JSON.stringify(serverConfig.installation));
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
//
|
|
250
|
-
|
|
251
|
-
if (
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
256
|
+
const pythonEnv = options.settings?.pythonEnv;
|
|
257
|
+
let pythonDir = null;
|
|
258
|
+
// 1. Determine which args to use and resolve npm paths
|
|
259
|
+
let finalArgs = [];
|
|
260
|
+
if (options.args && options.args.length > 0) {
|
|
261
|
+
Logger.debug(`Using args from ServerInstallOptions: ${options.args.join(' ')}`);
|
|
262
|
+
finalArgs = options.args.map(arg => resolveNpmModulePath(arg));
|
|
263
|
+
}
|
|
264
|
+
else if (installConfig.args && installConfig.args.length > 0) {
|
|
265
|
+
Logger.debug(`Using args from serverConfig.installation: ${installConfig.args.join(' ')}`);
|
|
266
|
+
finalArgs = installConfig.args.map((arg) => resolveNpmModulePath(arg));
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
Logger.debug('No args found in options or serverConfig.installation.');
|
|
270
|
+
finalArgs = [];
|
|
271
|
+
}
|
|
272
|
+
// 2. Handle pythonEnv settings
|
|
273
|
+
if (pythonEnv) {
|
|
274
|
+
// 2.1 If pythonEnv is set
|
|
275
|
+
Logger.debug(`Python environment specified: ${pythonEnv}`);
|
|
276
|
+
pythonDir = getPythonPackagePath(pythonEnv);
|
|
277
|
+
// 2.1.1 Replace ${PYTHON_PACKAGE} in args
|
|
278
|
+
if (pythonDir) {
|
|
279
|
+
finalArgs = finalArgs.map(arg => arg.includes('${PYTHON_PACKAGE}') ? arg.replace('${PYTHON_PACKAGE}', pythonDir) : arg);
|
|
280
|
+
Logger.debug(`Args after PYTHON_PACKAGE replacement (using pythonEnv): ${finalArgs.join(' ')}`);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
Logger.debug(`Could not determine directory for pythonEnv: ${pythonEnv}`);
|
|
284
|
+
}
|
|
285
|
+
// 2.1.2 Replace command if it's 'python'
|
|
286
|
+
if (installConfig.command === 'python') {
|
|
287
|
+
Logger.debug(`Replacing command 'python' with specified pythonEnv: ${pythonEnv}`);
|
|
288
|
+
installConfig.command = pythonEnv;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
// 2.2 If pythonEnv is not set
|
|
293
|
+
Logger.debug('No Python environment specified in settings.');
|
|
294
|
+
// 2.2.1 Replace ${PYTHON_PACKAGE} with system python directory if needed
|
|
295
|
+
if (finalArgs.some(arg => arg.includes('${PYTHON_PACKAGE}'))) {
|
|
296
|
+
Logger.debug('Attempting to find system Python directory for ${PYTHON_PACKAGE} replacement.');
|
|
297
|
+
pythonDir = await getSystemPythonPackageDirectory();
|
|
298
|
+
if (pythonDir) {
|
|
299
|
+
finalArgs = finalArgs.map(arg => arg.includes('${PYTHON_PACKAGE}') ? arg.replace('${PYTHON_PACKAGE}', pythonDir) : arg);
|
|
300
|
+
Logger.debug(`Args after PYTHON_PACKAGE replacement (using system python): ${finalArgs.join(' ')}`);
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
Logger.debug('Could not find system Python directory. ${PYTHON_PACKAGE} replacement skipped.');
|
|
304
|
+
// Optionally, remove or handle the arg containing ${PYTHON_PACKAGE} if Python is required
|
|
305
|
+
// finalArgs = finalArgs.filter(arg => !arg.includes('${PYTHON_PACKAGE}'));
|
|
257
306
|
}
|
|
258
307
|
}
|
|
259
308
|
}
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
|
|
309
|
+
// Update installConfig with potentially modified args
|
|
310
|
+
installConfig.args = finalArgs;
|
|
311
|
+
// 3. Handle environment variables (merge default, serverConfig, and options.env)
|
|
312
|
+
const baseEnv = serverConfig.installation.env || {};
|
|
313
|
+
const defaultEnv = {};
|
|
314
|
+
for (const [key, config] of Object.entries(baseEnv)) {
|
|
315
|
+
const envConfig = config; // Type assertion
|
|
316
|
+
if (envConfig.Default) {
|
|
317
|
+
defaultEnv[key] = envConfig.Default;
|
|
318
|
+
}
|
|
263
319
|
}
|
|
320
|
+
// Merge: options.env overrides defaultEnv
|
|
321
|
+
installConfig.env = { ...defaultEnv, ...(options.env || {}) };
|
|
322
|
+
// Replace ${BROWSER_PATH} with actual browser path
|
|
323
|
+
const replacements = Object.entries(installConfig.env)
|
|
324
|
+
.filter(([_, value]) => typeof value === 'string' && value.includes('${BROWSER_PATH}'))
|
|
325
|
+
.map(async ([key, value]) => {
|
|
326
|
+
try {
|
|
327
|
+
const browserPath = await GetBrowserPath();
|
|
328
|
+
return [key, value.replace('${BROWSER_PATH}', browserPath)];
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
Logger.error(`Failed to get system browser path for env var ${key}:`, error);
|
|
332
|
+
return [key, value];
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
// Wait for all replacements to complete
|
|
336
|
+
const replacedValues = await Promise.all(replacements);
|
|
337
|
+
replacedValues.forEach(([key, value]) => {
|
|
338
|
+
installConfig.env[key] = value;
|
|
339
|
+
});
|
|
340
|
+
Logger.debug(`Final environment variables: ${JSON.stringify(installConfig.env)}`);
|
|
341
|
+
// --- End of new logic ---
|
|
264
342
|
// Keep track of success for both installations
|
|
265
343
|
let regularSuccess = false;
|
|
266
344
|
let insidersSuccess = false;
|
|
@@ -271,6 +349,7 @@ export class ClientInstaller {
|
|
|
271
349
|
if (isVSCodeInstalled) {
|
|
272
350
|
try {
|
|
273
351
|
Logger.debug(`Updating VS Code settings for ${clientName} at path: ${regularSettingPath}`);
|
|
352
|
+
// Pass the modified installConfig
|
|
274
353
|
await this.updateClineOrMSRooSettings(regularSettingPath, this.serverName, installConfig, clientName);
|
|
275
354
|
regularSuccess = true;
|
|
276
355
|
Logger.debug(`Settings successfully updated to ${regularSettingPath} for ${clientName} (VS Code)`);
|
|
@@ -285,6 +364,7 @@ export class ClientInstaller {
|
|
|
285
364
|
if (isVSCodeInsidersInstalled) {
|
|
286
365
|
try {
|
|
287
366
|
Logger.debug(`Updating VS Code Insiders settings for ${clientName} at path: ${insidersSettingPath}`);
|
|
367
|
+
// Pass the modified installConfig
|
|
288
368
|
await this.updateClineOrMSRooSettings(insidersSettingPath, this.serverName, installConfig, clientName);
|
|
289
369
|
insidersSuccess = true;
|
|
290
370
|
Logger.debug(`Settings successfully updated to ${insidersSettingPath} for ${clientName} (VS Code Insiders)`);
|
|
@@ -301,6 +381,7 @@ export class ClientInstaller {
|
|
|
301
381
|
if (isVSCodeInstalled) {
|
|
302
382
|
try {
|
|
303
383
|
Logger.debug(`Updating VS Code settings for ${clientName} at path: ${regularSettingPath}`);
|
|
384
|
+
// Pass the modified installConfig
|
|
304
385
|
await this.updateGithubCopilotSettings(regularSettingPath, this.serverName, installConfig);
|
|
305
386
|
regularSuccess = true;
|
|
306
387
|
Logger.debug(`Settings successfully updated to ${regularSettingPath} for ${clientName} (VS Code)`);
|
|
@@ -315,6 +396,7 @@ export class ClientInstaller {
|
|
|
315
396
|
if (isVSCodeInsidersInstalled) {
|
|
316
397
|
try {
|
|
317
398
|
Logger.debug(`Updating VS Code Insiders settings for ${clientName} at path: ${insidersSettingPath}`);
|
|
399
|
+
// Pass the modified installConfig
|
|
318
400
|
await this.updateGithubCopilotSettings(insidersSettingPath, this.serverName, installConfig);
|
|
319
401
|
insidersSuccess = true;
|
|
320
402
|
Logger.debug(`Settings successfully updated to ${insidersSettingPath} for ${clientName} (VS Code Insiders)`);
|
|
@@ -386,7 +468,8 @@ export class ClientInstaller {
|
|
|
386
468
|
};
|
|
387
469
|
}
|
|
388
470
|
}
|
|
389
|
-
async updateClineOrMSRooSettings(settingPath, serverName, installConfig,
|
|
471
|
+
async updateClineOrMSRooSettings(settingPath, serverName, installConfig, // Use the processed installConfig
|
|
472
|
+
clientName) {
|
|
390
473
|
// Read the Cline/MSRoo settings file
|
|
391
474
|
const settings = await readJsonFile(settingPath, true);
|
|
392
475
|
// Initialize mcpServers section if it doesn't exist
|
|
@@ -394,39 +477,43 @@ export class ClientInstaller {
|
|
|
394
477
|
settings.mcpServers = {};
|
|
395
478
|
}
|
|
396
479
|
// Special handling for Windows when command is npx for Cline and MSROO clients
|
|
397
|
-
|
|
480
|
+
// Use a copy to avoid modifying the passed installConfig directly if needed elsewhere
|
|
481
|
+
const serverConfigForClient = { ...installConfig };
|
|
398
482
|
if (process.platform === 'win32' &&
|
|
399
|
-
|
|
483
|
+
serverConfigForClient.command === 'npx' &&
|
|
400
484
|
(clientName === 'Cline' || clientName === 'MSRooCode' || clientName === 'MSROO')) {
|
|
401
485
|
// Update command to cmd
|
|
402
|
-
|
|
486
|
+
serverConfigForClient.command = 'cmd';
|
|
403
487
|
// Add /c and npx at the beginning of args
|
|
404
|
-
|
|
488
|
+
serverConfigForClient.args = ['/c', 'npx', ...serverConfigForClient.args];
|
|
405
489
|
// Add APPDATA environment variable pointing to npm directory
|
|
406
|
-
if (!
|
|
407
|
-
|
|
490
|
+
if (!serverConfigForClient.env) {
|
|
491
|
+
serverConfigForClient.env = {};
|
|
408
492
|
}
|
|
409
493
|
// Dynamically get npm path and set APPDATA to it
|
|
410
494
|
const npmPath = await this.getNpmPath();
|
|
411
|
-
|
|
495
|
+
serverConfigForClient.env['APPDATA'] = npmPath;
|
|
496
|
+
Logger.debug(`Windows npx fix: command=${serverConfigForClient.command}, args=${serverConfigForClient.args.join(' ')}, env=${JSON.stringify(serverConfigForClient.env)}`);
|
|
412
497
|
}
|
|
413
498
|
// Convert backslashes to forward slashes in args paths
|
|
414
|
-
if (
|
|
415
|
-
|
|
499
|
+
if (serverConfigForClient.args) {
|
|
500
|
+
serverConfigForClient.args = serverConfigForClient.args.map((arg) => typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg);
|
|
416
501
|
}
|
|
417
502
|
// Add or update the server configuration
|
|
418
503
|
settings.mcpServers[serverName] = {
|
|
419
|
-
command:
|
|
420
|
-
args:
|
|
421
|
-
env:
|
|
504
|
+
command: serverConfigForClient.command,
|
|
505
|
+
args: serverConfigForClient.args,
|
|
506
|
+
env: serverConfigForClient.env,
|
|
422
507
|
autoApprove: [],
|
|
423
508
|
disabled: false,
|
|
424
509
|
alwaysAllow: []
|
|
425
510
|
};
|
|
511
|
+
Logger.debug(`Updating ${settingPath} for ${serverName}: ${JSON.stringify(settings.mcpServers[serverName])}`);
|
|
426
512
|
// Write the updated settings back to the file
|
|
427
513
|
await writeJsonFile(settingPath, settings);
|
|
428
514
|
}
|
|
429
|
-
async updateGithubCopilotSettings(settingPath, serverName, installConfig
|
|
515
|
+
async updateGithubCopilotSettings(settingPath, serverName, installConfig // Use the processed installConfig
|
|
516
|
+
) {
|
|
430
517
|
// Read the VS Code settings.json file
|
|
431
518
|
const settings = await readJsonFile(settingPath, true);
|
|
432
519
|
// Initialize the mcp section if it doesn't exist
|
|
@@ -439,24 +526,28 @@ export class ClientInstaller {
|
|
|
439
526
|
if (!settings.mcp.servers) {
|
|
440
527
|
settings.mcp.servers = {};
|
|
441
528
|
}
|
|
529
|
+
// Use a copy to avoid modifying the passed installConfig directly
|
|
530
|
+
const serverConfigForClient = { ...installConfig };
|
|
442
531
|
// Convert backslashes to forward slashes in args paths
|
|
443
|
-
if (
|
|
444
|
-
|
|
532
|
+
if (serverConfigForClient.args) {
|
|
533
|
+
serverConfigForClient.args = serverConfigForClient.args.map((arg) => typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg);
|
|
445
534
|
}
|
|
446
535
|
// Add or update the server configuration
|
|
447
536
|
settings.mcp.servers[serverName] = {
|
|
448
|
-
command:
|
|
449
|
-
args:
|
|
450
|
-
env:
|
|
537
|
+
command: serverConfigForClient.command,
|
|
538
|
+
args: serverConfigForClient.args,
|
|
539
|
+
env: serverConfigForClient.env
|
|
451
540
|
};
|
|
541
|
+
Logger.debug(`Updating ${settingPath} for ${serverName}: ${JSON.stringify(settings.mcp.servers[serverName])}`);
|
|
452
542
|
// Write the updated settings back to the file
|
|
453
543
|
await writeJsonFile(settingPath, settings);
|
|
454
544
|
}
|
|
455
545
|
async install(options) {
|
|
456
546
|
const initialStatuses = [];
|
|
457
547
|
// Start installation for each client asynchronously and collect initial statuses
|
|
548
|
+
// Pass options down to installClient
|
|
458
549
|
const installPromises = this.clients.map(async (clientName) => {
|
|
459
|
-
const initialStatus = await this.installClient(clientName, options
|
|
550
|
+
const initialStatus = await this.installClient(clientName, options);
|
|
460
551
|
initialStatuses.push(initialStatus);
|
|
461
552
|
return initialStatus;
|
|
462
553
|
});
|
|
@@ -15,6 +15,7 @@ export declare class CommandInstaller extends BaseInstaller {
|
|
|
15
15
|
* @returns True if this installer can handle the requirement
|
|
16
16
|
*/
|
|
17
17
|
canHandle(requirement: RequirementConfig): boolean;
|
|
18
|
+
supportCheckUpdates(): boolean;
|
|
18
19
|
/**
|
|
19
20
|
* Get the mapped package ID for a command
|
|
20
21
|
* @param commandName The command name to map
|
|
@@ -22,6 +22,9 @@ export class CommandInstaller extends BaseInstaller {
|
|
|
22
22
|
canHandle(requirement) {
|
|
23
23
|
return requirement.type === 'command';
|
|
24
24
|
}
|
|
25
|
+
supportCheckUpdates() {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
25
28
|
/**
|
|
26
29
|
* Get the mapped package ID for a command
|
|
27
30
|
* @param commandName The command name to map
|
|
@@ -11,6 +11,7 @@ export declare class GeneralInstaller extends BaseInstaller {
|
|
|
11
11
|
* @returns True if this installer can handle the requirement
|
|
12
12
|
*/
|
|
13
13
|
canHandle(requirement: RequirementConfig): boolean;
|
|
14
|
+
supportCheckUpdates(): boolean;
|
|
14
15
|
/**
|
|
15
16
|
* Check if the requirement is already installed
|
|
16
17
|
* For general installers, we can't easily check if something is installed
|
|
@@ -12,6 +12,9 @@ export class GeneralInstaller extends BaseInstaller {
|
|
|
12
12
|
canHandle(requirement) {
|
|
13
13
|
return requirement.type === 'other';
|
|
14
14
|
}
|
|
15
|
+
supportCheckUpdates() {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
15
18
|
/**
|
|
16
19
|
* Check if the requirement is already installed
|
|
17
20
|
* For general installers, we can't easily check if something is installed
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RequirementConfig, RequirementStatus } from '../types.js';
|
|
1
|
+
import { RequirementConfig, RequirementStatus, ServerInstallOptions } from '../types.js';
|
|
2
2
|
import { RequirementInstaller } from './RequirementInstaller.js';
|
|
3
3
|
import { exec } from 'child_process';
|
|
4
4
|
/**
|
|
@@ -34,15 +34,17 @@ export declare class InstallerFactory {
|
|
|
34
34
|
/**
|
|
35
35
|
* Install a requirement using the appropriate installer
|
|
36
36
|
* @param requirement The requirement to install
|
|
37
|
+
* @param options Installation options including python environment
|
|
37
38
|
* @returns The installation status
|
|
38
39
|
*/
|
|
39
|
-
install(requirement: RequirementConfig): Promise<RequirementStatus>;
|
|
40
|
+
install(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
|
|
40
41
|
/**
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
* Check the installation status of a requirement
|
|
43
|
+
* @param requirement The requirement to check
|
|
44
|
+
* @param options Installation options including python environment
|
|
45
|
+
* @returns The installation status
|
|
46
|
+
*/
|
|
47
|
+
checkInstallation(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
|
|
46
48
|
}
|
|
47
49
|
/**
|
|
48
50
|
* Create a new InstallerFactory with default settings
|
|
@@ -50,9 +50,10 @@ export class InstallerFactory {
|
|
|
50
50
|
/**
|
|
51
51
|
* Install a requirement using the appropriate installer
|
|
52
52
|
* @param requirement The requirement to install
|
|
53
|
+
* @param options Installation options including python environment
|
|
53
54
|
* @returns The installation status
|
|
54
55
|
*/
|
|
55
|
-
async install(requirement) {
|
|
56
|
+
async install(requirement, options) {
|
|
56
57
|
const installer = this.getInstaller(requirement);
|
|
57
58
|
if (!installer) {
|
|
58
59
|
return {
|
|
@@ -63,14 +64,15 @@ export class InstallerFactory {
|
|
|
63
64
|
inProgress: false
|
|
64
65
|
};
|
|
65
66
|
}
|
|
66
|
-
return await installer.install(requirement);
|
|
67
|
+
return await installer.install(requirement, options);
|
|
67
68
|
}
|
|
68
69
|
/**
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
* Check the installation status of a requirement
|
|
71
|
+
* @param requirement The requirement to check
|
|
72
|
+
* @param options Installation options including python environment
|
|
73
|
+
* @returns The installation status
|
|
74
|
+
*/
|
|
75
|
+
async checkInstallation(requirement, options) {
|
|
74
76
|
const installer = this.getInstaller(requirement);
|
|
75
77
|
if (!installer) {
|
|
76
78
|
return {
|
|
@@ -81,7 +83,7 @@ export class InstallerFactory {
|
|
|
81
83
|
inProgress: false
|
|
82
84
|
};
|
|
83
85
|
}
|
|
84
|
-
return await installer.checkInstallation(requirement);
|
|
86
|
+
return await installer.checkInstallation(requirement, options);
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
/**
|
|
@@ -10,6 +10,7 @@ export declare class NpmInstaller extends BaseInstaller {
|
|
|
10
10
|
* @returns True if this installer can handle the requirement
|
|
11
11
|
*/
|
|
12
12
|
canHandle(requirement: RequirementConfig): boolean;
|
|
13
|
+
supportCheckUpdates(): boolean;
|
|
13
14
|
/**
|
|
14
15
|
* Check if the NPM package is already installed
|
|
15
16
|
* @param requirement The requirement to check
|
|
@@ -12,6 +12,9 @@ export class NpmInstaller extends BaseInstaller {
|
|
|
12
12
|
canHandle(requirement) {
|
|
13
13
|
return requirement.type === 'npm';
|
|
14
14
|
}
|
|
15
|
+
supportCheckUpdates() {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
15
18
|
/**
|
|
16
19
|
* Check if the NPM package is already installed
|
|
17
20
|
* @param requirement The requirement to check
|
|
@@ -1,25 +1,28 @@
|
|
|
1
|
-
import { RequirementConfig, RequirementStatus } from '../types.js';
|
|
1
|
+
import { RequirementConfig, RequirementStatus, ServerInstallOptions } from '../types.js';
|
|
2
2
|
import { BaseInstaller } from './BaseInstaller.js';
|
|
3
3
|
/**
|
|
4
4
|
* Installer implementation for Python packages using pip
|
|
5
5
|
*/
|
|
6
6
|
export declare class PipInstaller extends BaseInstaller {
|
|
7
|
+
private getPythonCommand;
|
|
8
|
+
private getPipCommand;
|
|
7
9
|
/**
|
|
8
10
|
* Check if this installer can handle the given requirement type
|
|
9
11
|
* @param requirement The requirement to check
|
|
10
12
|
* @returns True if this installer can handle the requirement
|
|
11
13
|
*/
|
|
12
14
|
canHandle(requirement: RequirementConfig): boolean;
|
|
15
|
+
supportCheckUpdates(): boolean;
|
|
13
16
|
/**
|
|
14
17
|
* Check if the Python package is already installed
|
|
15
18
|
* @param requirement The requirement to check
|
|
16
19
|
* @returns The status of the requirement
|
|
17
20
|
*/
|
|
18
|
-
checkInstallation(requirement: RequirementConfig): Promise<RequirementStatus>;
|
|
21
|
+
checkInstallation(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
|
|
19
22
|
/**
|
|
20
23
|
* Install the Python package
|
|
21
24
|
* @param requirement The requirement to install
|
|
22
25
|
* @returns The status of the installation
|
|
23
26
|
*/
|
|
24
|
-
install(requirement: RequirementConfig): Promise<RequirementStatus>;
|
|
27
|
+
install(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
|
|
25
28
|
}
|
|
@@ -3,6 +3,13 @@ import { BaseInstaller } from './BaseInstaller.js';
|
|
|
3
3
|
* Installer implementation for Python packages using pip
|
|
4
4
|
*/
|
|
5
5
|
export class PipInstaller extends BaseInstaller {
|
|
6
|
+
getPythonCommand(options) {
|
|
7
|
+
return options?.settings?.pythonEnv || 'python';
|
|
8
|
+
}
|
|
9
|
+
getPipCommand(options) {
|
|
10
|
+
const pythonCmd = this.getPythonCommand(options);
|
|
11
|
+
return `${pythonCmd} -m pip`;
|
|
12
|
+
}
|
|
6
13
|
/**
|
|
7
14
|
* Check if this installer can handle the given requirement type
|
|
8
15
|
* @param requirement The requirement to check
|
|
@@ -11,14 +18,19 @@ export class PipInstaller extends BaseInstaller {
|
|
|
11
18
|
canHandle(requirement) {
|
|
12
19
|
return requirement.type === 'pip';
|
|
13
20
|
}
|
|
21
|
+
supportCheckUpdates() {
|
|
22
|
+
/// temporarily disabling update check for pip as not able to get which pip of python is being used
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
14
25
|
/**
|
|
15
26
|
* Check if the Python package is already installed
|
|
16
27
|
* @param requirement The requirement to check
|
|
17
28
|
* @returns The status of the requirement
|
|
18
29
|
*/
|
|
19
|
-
async checkInstallation(requirement) {
|
|
30
|
+
async checkInstallation(requirement, options) {
|
|
20
31
|
try {
|
|
21
|
-
const
|
|
32
|
+
const pipCmd = this.getPipCommand(options);
|
|
33
|
+
const { stdout, stderr } = await this.execPromise(`${pipCmd} show ${requirement.name}`);
|
|
22
34
|
// If we get an output and no error, the package is installed
|
|
23
35
|
const installed = stdout.includes(requirement.name);
|
|
24
36
|
const versionMatch = stdout.match(/Version: (.+)/);
|
|
@@ -46,16 +58,17 @@ export class PipInstaller extends BaseInstaller {
|
|
|
46
58
|
* @param requirement The requirement to install
|
|
47
59
|
* @returns The status of the installation
|
|
48
60
|
*/
|
|
49
|
-
async install(requirement) {
|
|
61
|
+
async install(requirement, options) {
|
|
50
62
|
try {
|
|
51
|
-
const status = await this.checkInstallation(requirement);
|
|
63
|
+
const status = await this.checkInstallation(requirement, options);
|
|
52
64
|
if (status.installed) {
|
|
53
65
|
return status;
|
|
54
66
|
}
|
|
67
|
+
const pipCmd = this.getPipCommand(options);
|
|
55
68
|
// If no registry is specified, use standard pip installation
|
|
56
69
|
if (!requirement.registry) {
|
|
57
70
|
// Standard pip installation
|
|
58
|
-
const { stderr } = await this.execPromise(
|
|
71
|
+
const { stderr } = await this.execPromise(`${pipCmd} install ${requirement.name}==${requirement.version}`);
|
|
59
72
|
if (stderr && stderr.toLowerCase().includes('error')) {
|
|
60
73
|
throw new Error(stderr);
|
|
61
74
|
}
|
|
@@ -67,7 +80,7 @@ export class PipInstaller extends BaseInstaller {
|
|
|
67
80
|
const result = await this.handleGitHubRelease(requirement, requirement.registry.githubRelease);
|
|
68
81
|
packageSource = result.resolvedPath;
|
|
69
82
|
// Install from the downloaded wheel or tar.gz file
|
|
70
|
-
const { stderr } = await this.execPromise(
|
|
83
|
+
const { stderr } = await this.execPromise(`${pipCmd} install "${packageSource}"`);
|
|
71
84
|
if (stderr && stderr.toLowerCase().includes('error')) {
|
|
72
85
|
throw new Error(stderr);
|
|
73
86
|
}
|
|
@@ -75,7 +88,7 @@ export class PipInstaller extends BaseInstaller {
|
|
|
75
88
|
else if (requirement.registry.artifacts) {
|
|
76
89
|
const registryUrl = requirement.registry.artifacts.registryUrl;
|
|
77
90
|
// Install using the custom index URL
|
|
78
|
-
const { stderr } = await this.execPromise(
|
|
91
|
+
const { stderr } = await this.execPromise(`${pipCmd} install ${requirement.name}==${requirement.version} --index-url ${registryUrl}`);
|
|
79
92
|
if (stderr && stderr.toLowerCase().includes('error')) {
|
|
80
93
|
throw new Error(stderr);
|
|
81
94
|
}
|
|
@@ -83,7 +96,7 @@ export class PipInstaller extends BaseInstaller {
|
|
|
83
96
|
else if (requirement.registry.local) {
|
|
84
97
|
packageSource = await this.handleLocalRegistry(requirement, requirement.registry.local);
|
|
85
98
|
// Install from the local path
|
|
86
|
-
const { stderr } = await this.execPromise(
|
|
99
|
+
const { stderr } = await this.execPromise(`${pipCmd} install "${packageSource}"`);
|
|
87
100
|
if (stderr && stderr.toLowerCase().includes('error')) {
|
|
88
101
|
throw new Error(stderr);
|
|
89
102
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RequirementConfig, RequirementStatus } from '../types.js';
|
|
1
|
+
import { RequirementConfig, RequirementStatus, ServerInstallOptions } from '../types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Interface for requirement installers.
|
|
4
4
|
* Implementations should handle specific requirement types.
|
|
@@ -10,18 +10,19 @@ export interface RequirementInstaller {
|
|
|
10
10
|
* @returns True if this installer can handle the requirement
|
|
11
11
|
*/
|
|
12
12
|
canHandle(requirement: RequirementConfig): boolean;
|
|
13
|
+
supportCheckUpdates(): boolean;
|
|
13
14
|
/**
|
|
14
15
|
* Install the requirement
|
|
15
16
|
* @param requirement The requirement to install
|
|
16
17
|
* @returns The status of the installation
|
|
17
18
|
*/
|
|
18
|
-
install(requirement: RequirementConfig): Promise<RequirementStatus>;
|
|
19
|
+
install(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
|
|
19
20
|
/**
|
|
20
21
|
* Check if the requirement is already installed
|
|
21
22
|
* @param requirement The requirement to check
|
|
22
23
|
* @returns The status of the requirement
|
|
23
24
|
*/
|
|
24
|
-
checkInstallation(requirement: RequirementConfig): Promise<RequirementStatus>;
|
|
25
|
+
checkInstallation(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
|
|
25
26
|
/**
|
|
26
27
|
* Check if updates are available for the requirement
|
|
27
28
|
* @param requirement The requirement to check
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ServerOperationResult, ServerInstallOptions } from '../../types.js';
|
|
2
|
+
export declare class ClientInstaller {
|
|
3
|
+
private categoryName;
|
|
4
|
+
private serverName;
|
|
5
|
+
private clients;
|
|
6
|
+
private configProvider;
|
|
7
|
+
private operationStatuses;
|
|
8
|
+
constructor(categoryName: string, serverName: string, clients: string[]);
|
|
9
|
+
private generateOperationId;
|
|
10
|
+
private getNpmPath;
|
|
11
|
+
/**
|
|
12
|
+
* Check if a command is available on the system
|
|
13
|
+
* @param command The command to check
|
|
14
|
+
* @returns True if the command is available, false otherwise
|
|
15
|
+
*/
|
|
16
|
+
private isCommandAvailable;
|
|
17
|
+
private installClient;
|
|
18
|
+
private processInstallation;
|
|
19
|
+
private installClientConfig;
|
|
20
|
+
private updateClineOrMSRooSettings;
|
|
21
|
+
private updateGithubCopilotSettings;
|
|
22
|
+
install(options: ServerInstallOptions): Promise<ServerOperationResult>;
|
|
23
|
+
}
|