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.
- package/README.md +5 -6
- 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 +159 -39
- 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/clientUtils.d.ts +0 -6
- package/dist/utils/clientUtils.js +3 -2
- 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/dist/web/server.js +1 -1
- package/package.json +2 -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} +185 -46
- 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/clientUtils.ts +4 -2
- 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
- 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
|
-
|
|
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
|
-
|
|
106
|
+
// Pass options down
|
|
107
|
+
this.processInstallation(clientName, operationId, options);
|
|
84
108
|
// Return the initial status immediately
|
|
85
109
|
return initialStatus;
|
|
86
110
|
}
|
|
87
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
//
|
|
229
|
-
|
|
230
|
-
if (
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
//
|
|
240
|
-
|
|
241
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
483
|
+
serverConfigForClient.command === 'npx' &&
|
|
379
484
|
(clientName === 'Cline' || clientName === 'MSRooCode' || clientName === 'MSROO')) {
|
|
380
485
|
// Update command to cmd
|
|
381
|
-
|
|
486
|
+
serverConfigForClient.command = 'cmd';
|
|
382
487
|
// Add /c and npx at the beginning of args
|
|
383
|
-
|
|
488
|
+
serverConfigForClient.args = ['/c', 'npx', ...serverConfigForClient.args];
|
|
384
489
|
// Add APPDATA environment variable pointing to npm directory
|
|
385
|
-
if (!
|
|
386
|
-
|
|
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
|
-
|
|
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:
|
|
395
|
-
args:
|
|
396
|
-
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:
|
|
420
|
-
args:
|
|
421
|
-
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
|
|
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
|
-
|
|
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
|