imcp 0.0.3 → 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.
Files changed (93) hide show
  1. package/README.md +5 -6
  2. package/dist/cli/commands/install.js +2 -0
  3. package/dist/cli/commands/list.js +1 -0
  4. package/dist/cli/index.js +1 -2
  5. package/dist/core/ConfigurationLoader.d.ts +32 -0
  6. package/dist/core/ConfigurationLoader.js +213 -0
  7. package/dist/core/ConfigurationProvider.d.ts +2 -3
  8. package/dist/core/ConfigurationProvider.js +13 -182
  9. package/dist/core/InstallationService.d.ts +8 -0
  10. package/dist/core/InstallationService.js +124 -96
  11. package/dist/core/RequirementService.d.ts +1 -1
  12. package/dist/core/RequirementService.js +5 -9
  13. package/dist/core/constants.js +14 -1
  14. package/dist/core/installers/BaseInstaller.d.ts +5 -4
  15. package/dist/core/installers/BaseInstaller.js +17 -28
  16. package/dist/core/installers/ClientInstaller.js +159 -39
  17. package/dist/core/installers/CommandInstaller.d.ts +1 -0
  18. package/dist/core/installers/CommandInstaller.js +3 -0
  19. package/dist/core/installers/GeneralInstaller.d.ts +1 -0
  20. package/dist/core/installers/GeneralInstaller.js +3 -0
  21. package/dist/core/installers/InstallerFactory.d.ts +9 -7
  22. package/dist/core/installers/InstallerFactory.js +10 -8
  23. package/dist/core/installers/NpmInstaller.d.ts +1 -0
  24. package/dist/core/installers/NpmInstaller.js +3 -0
  25. package/dist/core/installers/PipInstaller.d.ts +6 -3
  26. package/dist/core/installers/PipInstaller.js +21 -8
  27. package/dist/core/installers/RequirementInstaller.d.ts +4 -3
  28. package/dist/core/installers/clients/ClientInstaller.d.ts +23 -0
  29. package/dist/core/installers/clients/ClientInstaller.js +573 -0
  30. package/dist/core/installers/clients/ExtensionInstaller.d.ts +26 -0
  31. package/dist/core/installers/clients/ExtensionInstaller.js +149 -0
  32. package/dist/core/installers/index.d.ts +8 -6
  33. package/dist/core/installers/index.js +8 -6
  34. package/dist/core/installers/requirements/BaseInstaller.d.ts +59 -0
  35. package/dist/core/installers/requirements/BaseInstaller.js +168 -0
  36. package/dist/core/installers/requirements/CommandInstaller.d.ts +37 -0
  37. package/dist/core/installers/requirements/CommandInstaller.js +173 -0
  38. package/dist/core/installers/requirements/GeneralInstaller.d.ts +33 -0
  39. package/dist/core/installers/requirements/GeneralInstaller.js +86 -0
  40. package/dist/core/installers/requirements/InstallerFactory.d.ts +54 -0
  41. package/dist/core/installers/requirements/InstallerFactory.js +97 -0
  42. package/dist/core/installers/requirements/NpmInstaller.d.ts +26 -0
  43. package/dist/core/installers/requirements/NpmInstaller.js +128 -0
  44. package/dist/core/installers/requirements/PipInstaller.d.ts +28 -0
  45. package/dist/core/installers/requirements/PipInstaller.js +128 -0
  46. package/{src/core/installers/RequirementInstaller.ts → dist/core/installers/requirements/RequirementInstaller.d.ts} +33 -38
  47. package/dist/core/installers/requirements/RequirementInstaller.js +3 -0
  48. package/dist/core/types.d.ts +4 -1
  49. package/dist/services/ServerService.js +1 -1
  50. package/dist/utils/clientUtils.d.ts +0 -6
  51. package/dist/utils/clientUtils.js +3 -2
  52. package/dist/utils/githubUtils.d.ts +11 -0
  53. package/dist/utils/githubUtils.js +88 -0
  54. package/dist/utils/osUtils.d.ts +17 -0
  55. package/dist/utils/osUtils.js +184 -0
  56. package/dist/web/public/css/modal.css +97 -3
  57. package/dist/web/public/index.html +21 -2
  58. package/dist/web/public/js/modal.js +177 -28
  59. package/dist/web/public/js/serverCategoryDetails.js +12 -10
  60. package/dist/web/public/js/serverCategoryList.js +20 -5
  61. package/dist/web/public/modal.html +27 -13
  62. package/dist/web/server.js +1 -1
  63. package/package.json +2 -1
  64. package/src/cli/commands/install.ts +4 -2
  65. package/src/cli/commands/list.ts +1 -0
  66. package/src/cli/index.ts +1 -1
  67. package/src/core/ConfigurationLoader.ts +251 -0
  68. package/src/core/ConfigurationProvider.ts +13 -195
  69. package/src/core/InstallationService.ts +140 -106
  70. package/src/core/RequirementService.ts +5 -10
  71. package/src/core/constants.ts +15 -1
  72. package/src/core/installers/{ClientInstaller.ts → clients/ClientInstaller.ts} +185 -46
  73. package/src/core/installers/clients/ExtensionInstaller.ts +162 -0
  74. package/src/core/installers/index.ts +9 -7
  75. package/src/core/installers/{BaseInstaller.ts → requirements/BaseInstaller.ts} +10 -118
  76. package/src/core/installers/{CommandInstaller.ts → requirements/CommandInstaller.ts} +7 -3
  77. package/src/core/installers/{GeneralInstaller.ts → requirements/GeneralInstaller.ts} +6 -2
  78. package/src/core/installers/{InstallerFactory.ts → requirements/InstallerFactory.ts} +11 -9
  79. package/src/core/installers/{NpmInstaller.ts → requirements/NpmInstaller.ts} +7 -4
  80. package/src/core/installers/{PipInstaller.ts → requirements/PipInstaller.ts} +26 -10
  81. package/src/core/installers/requirements/RequirementInstaller.ts +41 -0
  82. package/src/core/types.ts +4 -1
  83. package/src/services/ServerService.ts +1 -1
  84. package/src/utils/clientUtils.ts +4 -2
  85. package/src/utils/githubUtils.ts +103 -0
  86. package/src/utils/osUtils.ts +206 -15
  87. package/src/web/public/css/modal.css +97 -3
  88. package/src/web/public/index.html +21 -2
  89. package/src/web/public/js/modal.js +177 -28
  90. package/src/web/public/js/serverCategoryDetails.js +12 -10
  91. package/src/web/public/js/serverCategoryList.js +20 -5
  92. package/src/web/public/modal.html +27 -13
  93. package/src/web/server.ts +1 -1
@@ -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,12 +43,18 @@ 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
48
49
  await execAsync(`where ${command}`);
49
50
  }
51
+ else if (process.platform === 'darwin' && (command === 'code' || command === 'code-insiders')) {
52
+ // macOS-specific VS Code check
53
+ const vscodePath = command === 'code' ?
54
+ '/Applications/Visual Studio Code.app' :
55
+ '/Applications/Visual Studio Code - Insiders.app';
56
+ await execAsync(`test -d "${vscodePath}"`);
57
+ }
50
58
  else {
51
59
  // Unix-like systems
52
60
  await execAsync(`which ${command}`);
@@ -54,10 +62,25 @@ export class ClientInstaller {
54
62
  return true;
55
63
  }
56
64
  catch (error) {
65
+ if (process.platform === 'darwin' && (command === 'code' || command === 'code-insiders')) {
66
+ // Try checking in ~/Applications as well for macOS
67
+ try {
68
+ const homedir = process.env.HOME;
69
+ const vscodePath = command === 'code' ?
70
+ `${homedir}/Applications/Visual Studio Code.app` :
71
+ `${homedir}/Applications/Visual Studio Code - Insiders.app`;
72
+ await execAsync(`test -d "${vscodePath}"`);
73
+ return true;
74
+ }
75
+ catch (error) {
76
+ return false;
77
+ }
78
+ }
57
79
  return false;
58
80
  }
59
81
  }
60
- async installClient(clientName, env) {
82
+ // Modified to accept ServerInstallOptions
83
+ async installClient(clientName, options) {
61
84
  // Check if client is supported
62
85
  if (!SUPPORTED_CLIENTS[clientName]) {
63
86
  return {
@@ -80,11 +103,13 @@ export class ClientInstaller {
80
103
  // Update server status with initial client installation status
81
104
  await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, initialStatus);
82
105
  // Start the asynchronous installation process without awaiting it
83
- this.processInstallation(clientName, operationId, env);
106
+ // Pass options down
107
+ this.processInstallation(clientName, operationId, options);
84
108
  // Return the initial status immediately
85
109
  return initialStatus;
86
110
  }
87
- async processInstallation(clientName, operationId, env) {
111
+ // Modified to accept ServerInstallOptions
112
+ async processInstallation(clientName, operationId, options) {
88
113
  try {
89
114
  // Check requirements before installation
90
115
  let requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
@@ -158,8 +183,8 @@ export class ClientInstaller {
158
183
  return;
159
184
  }
160
185
  try {
161
- // Install client-specific configuration
162
- const result = await this.installClientConfig(clientName, env || {}, serverConfig, feedConfiguration);
186
+ // Install client-specific configuration, passing options down
187
+ const result = await this.installClientConfig(clientName, options, serverConfig, feedConfiguration);
163
188
  const finalStatus = {
164
189
  status: result.success ? 'completed' : 'failed',
165
190
  type: 'install',
@@ -169,6 +194,9 @@ export class ClientInstaller {
169
194
  error: result.success ? undefined : result.message
170
195
  };
171
196
  await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, finalStatus);
197
+ if (result.success) {
198
+ await this.configProvider.reloadClientMCPSettings();
199
+ }
172
200
  }
173
201
  catch (error) {
174
202
  const errorStatus = {
@@ -194,7 +222,9 @@ export class ClientInstaller {
194
222
  await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, errorStatus);
195
223
  }
196
224
  }
197
- async installClientConfig(clientName, env, serverConfig, feedConfig) {
225
+ // Modified to accept ServerInstallOptions
226
+ async installClientConfig(clientName, options, // Use options directly
227
+ serverConfig, feedConfig) {
198
228
  try {
199
229
  if (!SUPPORTED_CLIENTS[clientName]) {
200
230
  Logger.debug(`Client ${clientName} is not supported.`);
@@ -212,7 +242,6 @@ export class ClientInstaller {
212
242
  const isVSCodeInsidersInstalled = await this.isCommandAvailable('code-insiders');
213
243
  Logger.debug(isVSCodeInstalled ? 'VS Code detected on system' : 'VS Code not detected on system');
214
244
  Logger.debug(isVSCodeInsidersInstalled ? 'VS Code Insiders detected on system' : 'VS Code Insiders not detected on system');
215
- Logger.debug(`VS Code Insiders installed: ${isVSCodeInsidersInstalled}`);
216
245
  // If neither is installed, we can't proceed
217
246
  if (!isVSCodeInstalled && !isVSCodeInsidersInstalled) {
218
247
  Logger.debug('No VS Code installations detected on system. Cannot update settings.');
@@ -221,25 +250,95 @@ export class ClientInstaller {
221
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.`
222
251
  };
223
252
  }
224
- // Clone the installation configuration to make modifications
253
+ // --- Start of new logic ---
254
+ // Clone the base installation configuration to avoid modifying the original serverConfig
225
255
  const installConfig = JSON.parse(JSON.stringify(serverConfig.installation));
226
- // Replace template variables in args
227
- installConfig.args = installConfig.args.map((arg) => resolveNpmModulePath(arg));
228
- // Add environment variables from options
229
- installConfig.env = {};
230
- if (serverConfig.installation.env) {
231
- // Add default env variables from config
232
- for (const [key, config] of Object.entries(serverConfig.installation.env)) {
233
- const envConfig = config; // Type assertion for dynamic access
234
- if (envConfig.Default) {
235
- installConfig.env[key] = envConfig.Default;
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}'));
236
306
  }
237
307
  }
238
308
  }
239
- // Override with provided env variables
240
- if (env) {
241
- Object.assign(installConfig.env, env);
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
+ }
242
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 ---
243
342
  // Keep track of success for both installations
244
343
  let regularSuccess = false;
245
344
  let insidersSuccess = false;
@@ -250,6 +349,7 @@ export class ClientInstaller {
250
349
  if (isVSCodeInstalled) {
251
350
  try {
252
351
  Logger.debug(`Updating VS Code settings for ${clientName} at path: ${regularSettingPath}`);
352
+ // Pass the modified installConfig
253
353
  await this.updateClineOrMSRooSettings(regularSettingPath, this.serverName, installConfig, clientName);
254
354
  regularSuccess = true;
255
355
  Logger.debug(`Settings successfully updated to ${regularSettingPath} for ${clientName} (VS Code)`);
@@ -264,6 +364,7 @@ export class ClientInstaller {
264
364
  if (isVSCodeInsidersInstalled) {
265
365
  try {
266
366
  Logger.debug(`Updating VS Code Insiders settings for ${clientName} at path: ${insidersSettingPath}`);
367
+ // Pass the modified installConfig
267
368
  await this.updateClineOrMSRooSettings(insidersSettingPath, this.serverName, installConfig, clientName);
268
369
  insidersSuccess = true;
269
370
  Logger.debug(`Settings successfully updated to ${insidersSettingPath} for ${clientName} (VS Code Insiders)`);
@@ -280,6 +381,7 @@ export class ClientInstaller {
280
381
  if (isVSCodeInstalled) {
281
382
  try {
282
383
  Logger.debug(`Updating VS Code settings for ${clientName} at path: ${regularSettingPath}`);
384
+ // Pass the modified installConfig
283
385
  await this.updateGithubCopilotSettings(regularSettingPath, this.serverName, installConfig);
284
386
  regularSuccess = true;
285
387
  Logger.debug(`Settings successfully updated to ${regularSettingPath} for ${clientName} (VS Code)`);
@@ -294,6 +396,7 @@ export class ClientInstaller {
294
396
  if (isVSCodeInsidersInstalled) {
295
397
  try {
296
398
  Logger.debug(`Updating VS Code Insiders settings for ${clientName} at path: ${insidersSettingPath}`);
399
+ // Pass the modified installConfig
297
400
  await this.updateGithubCopilotSettings(insidersSettingPath, this.serverName, installConfig);
298
401
  insidersSuccess = true;
299
402
  Logger.debug(`Settings successfully updated to ${insidersSettingPath} for ${clientName} (VS Code Insiders)`);
@@ -365,7 +468,8 @@ export class ClientInstaller {
365
468
  };
366
469
  }
367
470
  }
368
- async updateClineOrMSRooSettings(settingPath, serverName, installConfig, clientName) {
471
+ async updateClineOrMSRooSettings(settingPath, serverName, installConfig, // Use the processed installConfig
472
+ clientName) {
369
473
  // Read the Cline/MSRoo settings file
370
474
  const settings = await readJsonFile(settingPath, true);
371
475
  // Initialize mcpServers section if it doesn't exist
@@ -373,35 +477,43 @@ export class ClientInstaller {
373
477
  settings.mcpServers = {};
374
478
  }
375
479
  // Special handling for Windows when command is npx for Cline and MSROO clients
376
- const serverConfig = { ...installConfig };
480
+ // Use a copy to avoid modifying the passed installConfig directly if needed elsewhere
481
+ const serverConfigForClient = { ...installConfig };
377
482
  if (process.platform === 'win32' &&
378
- serverConfig.command === 'npx' &&
483
+ serverConfigForClient.command === 'npx' &&
379
484
  (clientName === 'Cline' || clientName === 'MSRooCode' || clientName === 'MSROO')) {
380
485
  // Update command to cmd
381
- serverConfig.command = 'cmd';
486
+ serverConfigForClient.command = 'cmd';
382
487
  // Add /c and npx at the beginning of args
383
- serverConfig.args = ['/c', 'npx', ...serverConfig.args];
488
+ serverConfigForClient.args = ['/c', 'npx', ...serverConfigForClient.args];
384
489
  // Add APPDATA environment variable pointing to npm directory
385
- if (!serverConfig.env) {
386
- serverConfig.env = {};
490
+ if (!serverConfigForClient.env) {
491
+ serverConfigForClient.env = {};
387
492
  }
388
493
  // Dynamically get npm path and set APPDATA to it
389
494
  const npmPath = await this.getNpmPath();
390
- serverConfig.env['APPDATA'] = npmPath;
495
+ serverConfigForClient.env['APPDATA'] = npmPath;
496
+ Logger.debug(`Windows npx fix: command=${serverConfigForClient.command}, args=${serverConfigForClient.args.join(' ')}, env=${JSON.stringify(serverConfigForClient.env)}`);
497
+ }
498
+ // Convert backslashes to forward slashes in args paths
499
+ if (serverConfigForClient.args) {
500
+ serverConfigForClient.args = serverConfigForClient.args.map((arg) => typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg);
391
501
  }
392
502
  // Add or update the server configuration
393
503
  settings.mcpServers[serverName] = {
394
- command: serverConfig.command,
395
- args: serverConfig.args,
396
- env: serverConfig.env,
504
+ command: serverConfigForClient.command,
505
+ args: serverConfigForClient.args,
506
+ env: serverConfigForClient.env,
397
507
  autoApprove: [],
398
508
  disabled: false,
399
509
  alwaysAllow: []
400
510
  };
511
+ Logger.debug(`Updating ${settingPath} for ${serverName}: ${JSON.stringify(settings.mcpServers[serverName])}`);
401
512
  // Write the updated settings back to the file
402
513
  await writeJsonFile(settingPath, settings);
403
514
  }
404
- async updateGithubCopilotSettings(settingPath, serverName, installConfig) {
515
+ async updateGithubCopilotSettings(settingPath, serverName, installConfig // Use the processed installConfig
516
+ ) {
405
517
  // Read the VS Code settings.json file
406
518
  const settings = await readJsonFile(settingPath, true);
407
519
  // Initialize the mcp section if it doesn't exist
@@ -414,20 +526,28 @@ export class ClientInstaller {
414
526
  if (!settings.mcp.servers) {
415
527
  settings.mcp.servers = {};
416
528
  }
529
+ // Use a copy to avoid modifying the passed installConfig directly
530
+ const serverConfigForClient = { ...installConfig };
531
+ // Convert backslashes to forward slashes in args paths
532
+ if (serverConfigForClient.args) {
533
+ serverConfigForClient.args = serverConfigForClient.args.map((arg) => typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg);
534
+ }
417
535
  // Add or update the server configuration
418
536
  settings.mcp.servers[serverName] = {
419
- command: installConfig.command,
420
- args: installConfig.args,
421
- env: installConfig.env
537
+ command: serverConfigForClient.command,
538
+ args: serverConfigForClient.args,
539
+ env: serverConfigForClient.env
422
540
  };
541
+ Logger.debug(`Updating ${settingPath} for ${serverName}: ${JSON.stringify(settings.mcp.servers[serverName])}`);
423
542
  // Write the updated settings back to the file
424
543
  await writeJsonFile(settingPath, settings);
425
544
  }
426
545
  async install(options) {
427
546
  const initialStatuses = [];
428
547
  // Start installation for each client asynchronously and collect initial statuses
548
+ // Pass options down to installClient
429
549
  const installPromises = this.clients.map(async (clientName) => {
430
- const initialStatus = await this.installClient(clientName, options.env || {});
550
+ const initialStatus = await this.installClient(clientName, options);
431
551
  initialStatuses.push(initialStatus);
432
552
  return initialStatus;
433
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
- * Check the installation status of a requirement
42
- * @param requirement The requirement to check
43
- * @returns The installation status
44
- */
45
- checkInstallation(requirement: RequirementConfig): Promise<RequirementStatus>;
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
- * Check the installation status of a requirement
70
- * @param requirement The requirement to check
71
- * @returns The installation status
72
- */
73
- async checkInstallation(requirement) {
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 { stdout, stderr } = await this.execPromise(`pip show ${requirement.name}`);
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(`pip install ${requirement.name}==${requirement.version}`);
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(`pip install "${packageSource}"`);
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(`pip install ${requirement.name}==${requirement.version} --index-url ${registryUrl}`);
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(`pip install "${packageSource}"`);
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