imcp 0.0.11 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/uninstall.js +14 -6
- package/dist/core/ConfigurationProvider.d.ts +3 -1
- package/dist/core/ConfigurationProvider.js +85 -25
- package/dist/core/InstallationService.d.ts +17 -0
- package/dist/core/InstallationService.js +127 -61
- package/dist/core/MCPManager.d.ts +1 -0
- package/dist/core/MCPManager.js +30 -5
- package/dist/core/RequirementService.d.ts +4 -4
- package/dist/core/RequirementService.js +11 -7
- package/dist/core/ServerSchemaLoader.js +2 -2
- package/dist/core/ServerSchemaProvider.d.ts +1 -1
- package/dist/core/ServerSchemaProvider.js +15 -10
- package/dist/core/constants.d.ts +14 -1
- package/dist/core/constants.js +4 -1
- package/dist/core/installers/clients/ExtensionInstaller.js +3 -0
- package/dist/core/installers/requirements/PipInstaller.js +10 -5
- package/dist/core/types.d.ts +10 -0
- package/dist/services/ServerService.d.ts +12 -1
- package/dist/services/ServerService.js +39 -9
- package/dist/utils/githubAuth.js +0 -10
- package/dist/utils/githubUtils.d.ts +16 -0
- package/dist/utils/githubUtils.js +55 -39
- package/dist/utils/osUtils.js +1 -1
- package/dist/web/public/css/detailsWidget.css +189 -57
- package/dist/web/public/css/modal.css +42 -0
- package/dist/web/public/css/serverDetails.css +35 -18
- package/dist/web/public/index.html +2 -0
- package/dist/web/public/js/detailsWidget.js +175 -60
- package/dist/web/public/js/modal.js +93 -29
- package/dist/web/public/js/notifications.js +34 -35
- package/dist/web/public/js/serverCategoryDetails.js +182 -120
- package/dist/web/server.js +38 -2
- package/package.json +3 -4
- package/src/cli/commands/uninstall.ts +16 -6
- package/src/core/ConfigurationProvider.ts +102 -25
- package/src/core/InstallationService.ts +176 -62
- package/src/core/MCPManager.ts +36 -5
- package/src/core/RequirementService.ts +12 -8
- package/src/core/ServerSchemaLoader.ts +48 -0
- package/src/core/ServerSchemaProvider.ts +137 -0
- package/src/core/constants.ts +16 -3
- package/src/core/installers/clients/ExtensionInstaller.ts +3 -0
- package/src/core/installers/requirements/PipInstaller.ts +10 -5
- package/src/core/types.ts +11 -1
- package/src/services/ServerService.ts +41 -8
- package/src/utils/githubAuth.ts +14 -27
- package/src/utils/githubUtils.ts +84 -47
- package/src/utils/osUtils.ts +1 -1
- package/src/web/public/css/detailsWidget.css +235 -0
- package/src/web/public/css/modal.css +42 -0
- package/src/web/public/css/serverDetails.css +126 -0
- package/src/web/public/index.html +2 -0
- package/src/web/public/js/detailsWidget.js +264 -0
- package/src/web/public/js/modal.js +93 -29
- package/src/web/public/js/notifications.js +34 -35
- package/src/web/public/js/serverCategoryDetails.js +182 -120
- package/src/web/server.ts +52 -3
|
@@ -3,19 +3,27 @@ import { serverService } from '../../services/ServerService.js';
|
|
|
3
3
|
export function createUninstallCommand() {
|
|
4
4
|
return new Command('uninstall')
|
|
5
5
|
.description('Uninstall specific MCP servers')
|
|
6
|
-
.requiredOption('--category <category>', '
|
|
7
|
-
.
|
|
6
|
+
.requiredOption('--category <category>', 'Server category')
|
|
7
|
+
.requiredOption('--names <names>', 'Server names (semicolon separated)')
|
|
8
|
+
.requiredOption('--targets <targets>', 'Target clients to uninstall from (semicolon separated)')
|
|
9
|
+
.option('--remove-data', 'Remove all associated data', true // Change default to true to ensure cleanup happens
|
|
10
|
+
)
|
|
8
11
|
.action(async (options) => {
|
|
9
12
|
try {
|
|
10
13
|
const serverNames = options.names.split(';').map((name) => name.trim());
|
|
11
|
-
|
|
14
|
+
const validNames = await serverService.validateServerName(options.category, serverNames);
|
|
15
|
+
if (!validNames) {
|
|
12
16
|
console.error('Invalid server names provided');
|
|
13
17
|
process.exit(1);
|
|
14
18
|
}
|
|
15
19
|
console.log(`Uninstalling servers: ${serverNames.join(', ')}`);
|
|
16
|
-
const results = await Promise.all(serverNames.map((serverName) =>
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
const results = await Promise.all(serverNames.map((serverName) => {
|
|
21
|
+
const targets = options.targets ? options.targets.split(';').map((t) => t.trim()) : [];
|
|
22
|
+
return serverService.uninstallMcpServer(options.category, serverName, {
|
|
23
|
+
removeData: options.removeData,
|
|
24
|
+
targets: targets
|
|
25
|
+
});
|
|
26
|
+
}));
|
|
19
27
|
const { success, messages } = serverService.formatOperationResults(results);
|
|
20
28
|
messages.forEach(message => {
|
|
21
29
|
if (success) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MCPServerCategory, FeedConfiguration, InstallationStatus, RequirementStatus, MCPServerStatus, OperationStatus } from './types.js';
|
|
1
|
+
import { MCPServerCategory, FeedConfiguration, InstallationStatus, RequirementStatus, MCPServerStatus, OperationStatus, McpConfig } from './types.js';
|
|
2
2
|
export declare class ConfigurationProvider {
|
|
3
3
|
private static instance;
|
|
4
4
|
private configPath;
|
|
@@ -14,6 +14,7 @@ export declare class ConfigurationProvider {
|
|
|
14
14
|
getServerCategory(categoryName: string): Promise<MCPServerCategory | undefined>;
|
|
15
15
|
getClientMcpSettings(): Promise<Record<string, Record<string, any>> | undefined>;
|
|
16
16
|
getFeedConfiguration(categoryName: string): Promise<FeedConfiguration | undefined>;
|
|
17
|
+
getServerMcpConfig(categoryName: string, serverName: string): Promise<McpConfig | undefined>;
|
|
17
18
|
getInstallationStatus(categoryName: string): Promise<InstallationStatus | undefined>;
|
|
18
19
|
getServerStatus(categoryName: string, serverName: string): Promise<MCPServerStatus | undefined>;
|
|
19
20
|
getRequirementStatus(categoryName: string, requirementName: string): Promise<RequirementStatus | undefined>;
|
|
@@ -28,5 +29,6 @@ export declare class ConfigurationProvider {
|
|
|
28
29
|
private loadFeedsIntoConfiguration;
|
|
29
30
|
private loadClientMCPSettings;
|
|
30
31
|
reloadClientMCPSettings(): Promise<void>;
|
|
32
|
+
removeServerFromClientMCPSettings(serverName: string, target?: string): Promise<void>;
|
|
31
33
|
}
|
|
32
34
|
export declare const configProvider: ConfigurationProvider;
|
|
@@ -3,7 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import { exec } from 'child_process';
|
|
4
4
|
import { promisify } from 'util';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
-
import { GITHUB_REPO, LOCAL_FEEDS_DIR, SETTINGS_DIR } from './constants.js';
|
|
6
|
+
import { GITHUB_REPO, LOCAL_FEEDS_DIR, SETTINGS_DIR, SUPPORTED_CLIENTS } from './constants.js';
|
|
7
7
|
import { Logger } from '../utils/logger.js';
|
|
8
8
|
import { checkGithubAuth } from '../utils/githubAuth.js';
|
|
9
9
|
import { ConfigurationLoader } from './ConfigurationLoader.js';
|
|
@@ -94,6 +94,11 @@ export class ConfigurationProvider {
|
|
|
94
94
|
return this.configuration.feeds[categoryName];
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
|
+
async getServerMcpConfig(categoryName, serverName) {
|
|
98
|
+
return await this.withLock(async () => {
|
|
99
|
+
return this.configuration.feeds[categoryName]?.mcpServers.find(s => s.name === serverName);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
97
102
|
async getInstallationStatus(categoryName) {
|
|
98
103
|
return await this.withLock(async () => {
|
|
99
104
|
// Inline getServerCategory logic
|
|
@@ -241,35 +246,26 @@ export class ConfigurationProvider {
|
|
|
241
246
|
});
|
|
242
247
|
await fs.mkdir(LOCAL_FEEDS_DIR, { recursive: true });
|
|
243
248
|
await fs.mkdir(this.tempDir, { recursive: true });
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
await fs.rm(this.tempDir, { recursive: true, force: true });
|
|
257
|
-
const { stdout, stderr } = await execAsync(`git clone ${GITHUB_REPO.url} ${this.tempDir}`);
|
|
258
|
-
Logger.debug({
|
|
259
|
-
action: 'git_clone',
|
|
260
|
-
stderr,
|
|
261
|
-
stdout,
|
|
262
|
-
url: GITHUB_REPO.url
|
|
263
|
-
});
|
|
264
|
-
}
|
|
249
|
+
// Clean up temp directory
|
|
250
|
+
await fs.rm(this.tempDir, { recursive: true, force: true });
|
|
251
|
+
// Download latest release
|
|
252
|
+
Logger.debug('Downloading latest release...');
|
|
253
|
+
const { downloadGithubRelease } = await import('../utils/githubUtils.js');
|
|
254
|
+
const { version, downloadPath } = await downloadGithubRelease(GITHUB_REPO.repoName, 'latest', GITHUB_REPO.feedAssetsName, undefined, true, this.tempDir);
|
|
255
|
+
Logger.debug({
|
|
256
|
+
action: 'download_release',
|
|
257
|
+
downloadPath,
|
|
258
|
+
version,
|
|
259
|
+
repoName: GITHUB_REPO.repoName,
|
|
260
|
+
});
|
|
265
261
|
Logger.debug('Updating local feeds...');
|
|
266
262
|
await fs.rm(LOCAL_FEEDS_DIR, { recursive: true, force: true });
|
|
267
|
-
const sourceFeedsDir =
|
|
263
|
+
const sourceFeedsDir = downloadPath;
|
|
268
264
|
try {
|
|
269
|
-
await fs.access(
|
|
265
|
+
await fs.access(downloadPath);
|
|
270
266
|
}
|
|
271
267
|
catch (err) {
|
|
272
|
-
throw new Error(`Could not find feeds directory in
|
|
268
|
+
throw new Error(`Could not find feeds directory in downloaded path: ${sourceFeedsDir}`);
|
|
273
269
|
}
|
|
274
270
|
await fs.cp(sourceFeedsDir, LOCAL_FEEDS_DIR, { recursive: true, force: true });
|
|
275
271
|
Logger.log('Successfully updated local feeds');
|
|
@@ -294,6 +290,70 @@ export class ConfigurationProvider {
|
|
|
294
290
|
await this.loadClientMCPSettings();
|
|
295
291
|
});
|
|
296
292
|
}
|
|
293
|
+
async removeServerFromClientMCPSettings(serverName, target) {
|
|
294
|
+
return await this.withLock(async () => {
|
|
295
|
+
// Load utils in async context to avoid circular dependencies
|
|
296
|
+
const { readJsonFile, writeJsonFile } = await import('../utils/clientUtils.js');
|
|
297
|
+
// Filter clients if target is specified
|
|
298
|
+
const clientEntries = Object.entries(SUPPORTED_CLIENTS);
|
|
299
|
+
const targetClients = target
|
|
300
|
+
? clientEntries.filter(([clientName]) => clientName === target)
|
|
301
|
+
: clientEntries;
|
|
302
|
+
for (const [clientName, clientSettings] of targetClients) {
|
|
303
|
+
const settingPath = process.env.CODE_INSIDERS
|
|
304
|
+
? clientSettings.codeInsiderSettingPath
|
|
305
|
+
: clientSettings.codeSettingPath;
|
|
306
|
+
try {
|
|
307
|
+
const content = await readJsonFile(settingPath, true);
|
|
308
|
+
let modified = false;
|
|
309
|
+
// Handle GitHub Copilot's different structure
|
|
310
|
+
if (clientName === 'GithubCopilot' && content.mcp?.servers?.[serverName]) {
|
|
311
|
+
delete content.mcp.servers[serverName];
|
|
312
|
+
modified = true;
|
|
313
|
+
}
|
|
314
|
+
else if (content.mcpServers?.[serverName]) {
|
|
315
|
+
delete content.mcpServers[serverName];
|
|
316
|
+
modified = true;
|
|
317
|
+
}
|
|
318
|
+
// Only write if we actually modified the content
|
|
319
|
+
if (modified) {
|
|
320
|
+
await writeJsonFile(settingPath, content);
|
|
321
|
+
Logger.debug(`Removed server ${serverName} from client ${clientName} settings`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
Logger.error(`Failed to remove server ${serverName} from client ${clientName} settings:`, error);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
// Also update our in-memory configuration
|
|
329
|
+
if (this.configuration.clientMCPSettings) {
|
|
330
|
+
if (target) {
|
|
331
|
+
// Only update settings for the target client
|
|
332
|
+
const clientSettings = this.configuration.clientMCPSettings[target];
|
|
333
|
+
if (clientSettings) {
|
|
334
|
+
if (clientSettings.mcpServers?.[serverName]) {
|
|
335
|
+
delete clientSettings.mcpServers[serverName];
|
|
336
|
+
}
|
|
337
|
+
if (clientSettings.servers?.[serverName]) { // For GitHub Copilot
|
|
338
|
+
delete clientSettings.servers[serverName];
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
// Update all clients if no target specified
|
|
344
|
+
for (const clientSettings of Object.values(this.configuration.clientMCPSettings)) {
|
|
345
|
+
if (clientSettings.mcpServers?.[serverName]) {
|
|
346
|
+
delete clientSettings.mcpServers[serverName];
|
|
347
|
+
}
|
|
348
|
+
if (clientSettings.servers?.[serverName]) { // For GitHub Copilot
|
|
349
|
+
delete clientSettings.servers[serverName];
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
await this.saveConfiguration();
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
}
|
|
297
357
|
}
|
|
298
358
|
// Export a singleton instance
|
|
299
359
|
export const configProvider = ConfigurationProvider.getInstance();
|
|
@@ -30,4 +30,21 @@ export declare class InstallationService {
|
|
|
30
30
|
* @returns A failure result if requirements check fails, null if requirements are satisfied
|
|
31
31
|
*/
|
|
32
32
|
private checkAndInstallRequirements;
|
|
33
|
+
/**
|
|
34
|
+
* Installs requirements in background without blocking the main thread
|
|
35
|
+
* Requirements with the same order are installed in parallel
|
|
36
|
+
*/
|
|
37
|
+
private installRequirementsInBackground;
|
|
38
|
+
/**
|
|
39
|
+
* Helper to update requirement status for failure case
|
|
40
|
+
*/
|
|
41
|
+
private updateRequirementFailureStatus;
|
|
42
|
+
/**
|
|
43
|
+
* Helper to update requirement status for in-progress case
|
|
44
|
+
*/
|
|
45
|
+
private updateRequirementProgressStatus;
|
|
46
|
+
/**
|
|
47
|
+
* Helper to update requirement status for completion case
|
|
48
|
+
*/
|
|
49
|
+
private updateRequirementCompletionStatus;
|
|
33
50
|
}
|
|
@@ -34,7 +34,7 @@ export class InstallationService {
|
|
|
34
34
|
// Fire off requirement updates in the background without awaiting completion
|
|
35
35
|
if (options.requirements && options.requirements.length > 0) {
|
|
36
36
|
// Start the process but don't await it - it will run in the background
|
|
37
|
-
this.processRequirementUpdates(categoryName, serverName, options
|
|
37
|
+
this.processRequirementUpdates(categoryName, serverName, options)
|
|
38
38
|
.catch(error => {
|
|
39
39
|
console.error(`Error in background requirement updates: ${error instanceof Error ? error.message : String(error)}`);
|
|
40
40
|
});
|
|
@@ -70,7 +70,7 @@ export class InstallationService {
|
|
|
70
70
|
* @param serverName The server name
|
|
71
71
|
* @param requirements The requirements to update
|
|
72
72
|
*/
|
|
73
|
-
async processRequirementUpdates(categoryName, serverName,
|
|
73
|
+
async processRequirementUpdates(categoryName, serverName, options) {
|
|
74
74
|
// Use UpdateCheckTracker to prevent concurrent updates
|
|
75
75
|
const updateCheckTracker = await import('../utils/UpdateCheckTracker.js').then(m => m.updateCheckTracker);
|
|
76
76
|
const operationKey = `requirement-updates-${categoryName}-${serverName}`;
|
|
@@ -90,7 +90,7 @@ export class InstallationService {
|
|
|
90
90
|
// Import the RequirementService
|
|
91
91
|
const { requirementService } = await import('./RequirementService.js');
|
|
92
92
|
// Create an array of promises to update all requirements in parallel
|
|
93
|
-
const updatePromises = requirements
|
|
93
|
+
const updatePromises = options.requirements?.map(async (reqToUpdate) => {
|
|
94
94
|
try {
|
|
95
95
|
// Find the full requirement config
|
|
96
96
|
const reqConfig = feedConfig.requirements?.find((r) => r.name === reqToUpdate.name);
|
|
@@ -123,8 +123,15 @@ export class InstallationService {
|
|
|
123
123
|
...reqConfig,
|
|
124
124
|
version: reqToUpdate.version
|
|
125
125
|
};
|
|
126
|
-
//
|
|
127
|
-
|
|
126
|
+
// For pip requirements, check if we have a stored pythonEnv
|
|
127
|
+
if (updatedReqConfig.type === 'pip' && currentStatus.pythonEnv && !options?.settings?.pythonEnv) {
|
|
128
|
+
options = {
|
|
129
|
+
...options,
|
|
130
|
+
settings: { ...options?.settings, pythonEnv: currentStatus.pythonEnv }
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
// Update the requirement with options for pip environment
|
|
134
|
+
const updatedStatus = await requirementService.updateRequirement(updatedReqConfig, reqToUpdate.version, options);
|
|
128
135
|
// Update requirement status
|
|
129
136
|
await configProvider.updateRequirementStatus(categoryName, reqToUpdate.name, {
|
|
130
137
|
...updatedStatus,
|
|
@@ -162,8 +169,10 @@ export class InstallationService {
|
|
|
162
169
|
});
|
|
163
170
|
}
|
|
164
171
|
});
|
|
165
|
-
// Wait for all updates to complete in parallel
|
|
166
|
-
|
|
172
|
+
// Wait for all updates to complete in parallel if there are any
|
|
173
|
+
if (updatePromises) {
|
|
174
|
+
await Promise.all(updatePromises);
|
|
175
|
+
}
|
|
167
176
|
}
|
|
168
177
|
finally {
|
|
169
178
|
// Always release the lock when done, even if there was an error
|
|
@@ -226,62 +235,119 @@ export class InstallationService {
|
|
|
226
235
|
const orderB = b.order ?? Infinity;
|
|
227
236
|
return orderA - orderB;
|
|
228
237
|
});
|
|
229
|
-
//
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
238
|
+
// Start requirements installation in background
|
|
239
|
+
this.installRequirementsInBackground(categoryName, sortedRequirements, options)
|
|
240
|
+
.catch(error => {
|
|
241
|
+
Logger.error(`Error in background requirement installations: ${error instanceof Error ? error.message : String(error)}`);
|
|
242
|
+
});
|
|
243
|
+
// Return immediately while installation continues in background
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Installs requirements in background without blocking the main thread
|
|
248
|
+
* Requirements with the same order are installed in parallel
|
|
249
|
+
*/
|
|
250
|
+
async installRequirementsInBackground(categoryName, sortedRequirements, options) {
|
|
251
|
+
const configProvider = ConfigurationProvider.getInstance();
|
|
252
|
+
const requirementGroups = sortedRequirements.reduce((groups, req) => {
|
|
253
|
+
const order = req.order ?? Infinity;
|
|
254
|
+
if (!groups[order]) {
|
|
255
|
+
groups[order] = [];
|
|
256
|
+
}
|
|
257
|
+
groups[order].push(req);
|
|
258
|
+
return groups;
|
|
259
|
+
}, {});
|
|
260
|
+
// Process each group in sequence, but requirements within group in parallel
|
|
261
|
+
const orderKeys = Object.keys(requirementGroups).map(Number).sort((a, b) => a - b);
|
|
262
|
+
for (const order of orderKeys) {
|
|
263
|
+
const group = requirementGroups[order];
|
|
264
|
+
await Promise.all(group.map(async (requirement) => {
|
|
265
|
+
try {
|
|
266
|
+
const feeds = await configProvider.getFeedConfiguration(categoryName);
|
|
267
|
+
const requirementConfig = feeds?.requirements?.find((r) => r.name === requirement.name) || {
|
|
241
268
|
name: requirement.name,
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
error: `No installer found for requirement type: ${requirementConfig.type}`,
|
|
245
|
-
operationStatus: {
|
|
246
|
-
status: 'failed',
|
|
247
|
-
type: 'install',
|
|
248
|
-
target: 'requirement',
|
|
249
|
-
message: `No installer found for requirement type: ${requirementConfig.type}`,
|
|
250
|
-
operationId: this.generateOperationId()
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
const operationStatus = {
|
|
256
|
-
status: 'pending',
|
|
257
|
-
type: 'install',
|
|
258
|
-
target: 'requirement',
|
|
259
|
-
message: `Installing requirement: ${requirement.name}`,
|
|
260
|
-
operationId: this.generateOperationId()
|
|
261
|
-
};
|
|
262
|
-
await configProvider.updateRequirementStatus(categoryName, requirement.name, {
|
|
263
|
-
name: requirement.name,
|
|
264
|
-
type: requirementConfig.type,
|
|
265
|
-
installed: false,
|
|
266
|
-
inProgress: true,
|
|
267
|
-
operationStatus
|
|
268
|
-
});
|
|
269
|
-
return installer.install(requirementConfig, options).then(async (installStatus) => {
|
|
270
|
-
const status = {
|
|
271
|
-
...installStatus,
|
|
272
|
-
operationStatus: {
|
|
273
|
-
status: installStatus.installed ? 'completed' : 'failed',
|
|
274
|
-
type: 'install',
|
|
275
|
-
target: 'requirement',
|
|
276
|
-
message: installStatus.installed ? `Requirement ${requirement.name} installed successfully` : `Failed to install ${requirement.name}`,
|
|
277
|
-
operationId: operationStatus.operationId
|
|
278
|
-
}
|
|
269
|
+
version: requirement.version,
|
|
270
|
+
type: 'npm'
|
|
279
271
|
};
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
272
|
+
// For pip requirements, check if we need to use stored pythonEnv
|
|
273
|
+
const currentStatus = await configProvider.getRequirementStatus(categoryName, requirement.name);
|
|
274
|
+
if (requirementConfig.type === 'pip' && currentStatus?.pythonEnv && !options?.settings?.pythonEnv) {
|
|
275
|
+
options = {
|
|
276
|
+
...options,
|
|
277
|
+
settings: { ...options?.settings, pythonEnv: currentStatus.pythonEnv }
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
const installer = this.installerFactory.getInstaller(requirementConfig);
|
|
281
|
+
if (!installer) {
|
|
282
|
+
await this.updateRequirementFailureStatus(categoryName, requirement.name, requirementConfig.type, `No installer found for requirement type: ${requirementConfig.type}`);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const operationId = this.generateOperationId();
|
|
286
|
+
await this.updateRequirementProgressStatus(categoryName, requirement.name, requirementConfig.type, operationId);
|
|
287
|
+
const installStatus = await installer.install(requirementConfig, options);
|
|
288
|
+
await this.updateRequirementCompletionStatus(categoryName, requirement.name, installStatus, operationId);
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
await this.updateRequirementFailureStatus(categoryName, requirement.name, 'unknown', error instanceof Error ? error.message : String(error));
|
|
292
|
+
}
|
|
293
|
+
}));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Helper to update requirement status for failure case
|
|
298
|
+
*/
|
|
299
|
+
async updateRequirementFailureStatus(categoryName, requirementName, requirementType, errorMessage) {
|
|
300
|
+
const configProvider = ConfigurationProvider.getInstance();
|
|
301
|
+
await configProvider.updateRequirementStatus(categoryName, requirementName, {
|
|
302
|
+
name: requirementName,
|
|
303
|
+
type: requirementType,
|
|
304
|
+
installed: false,
|
|
305
|
+
error: errorMessage,
|
|
306
|
+
operationStatus: {
|
|
307
|
+
status: 'failed',
|
|
308
|
+
type: 'install',
|
|
309
|
+
target: 'requirement',
|
|
310
|
+
message: `Error installing requirement: ${errorMessage}`,
|
|
311
|
+
operationId: this.generateOperationId()
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Helper to update requirement status for in-progress case
|
|
317
|
+
*/
|
|
318
|
+
async updateRequirementProgressStatus(categoryName, requirementName, requirementType, operationId) {
|
|
319
|
+
const configProvider = ConfigurationProvider.getInstance();
|
|
320
|
+
await configProvider.updateRequirementStatus(categoryName, requirementName, {
|
|
321
|
+
name: requirementName,
|
|
322
|
+
type: requirementType,
|
|
323
|
+
installed: false,
|
|
324
|
+
inProgress: true,
|
|
325
|
+
operationStatus: {
|
|
326
|
+
status: 'in-progress',
|
|
327
|
+
type: 'install',
|
|
328
|
+
target: 'requirement',
|
|
329
|
+
message: `Installing requirement: ${requirementName}`,
|
|
330
|
+
operationId
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Helper to update requirement status for completion case
|
|
336
|
+
*/
|
|
337
|
+
async updateRequirementCompletionStatus(categoryName, requirementName, installStatus, operationId) {
|
|
338
|
+
const configProvider = ConfigurationProvider.getInstance();
|
|
339
|
+
await configProvider.updateRequirementStatus(categoryName, requirementName, {
|
|
340
|
+
...installStatus,
|
|
341
|
+
operationStatus: {
|
|
342
|
+
status: installStatus.installed ? 'completed' : 'failed',
|
|
343
|
+
type: 'install',
|
|
344
|
+
target: 'requirement',
|
|
345
|
+
message: installStatus.installed
|
|
346
|
+
? `Requirement ${requirementName} installed successfully`
|
|
347
|
+
: `Failed to install ${requirementName}`,
|
|
348
|
+
operationId
|
|
349
|
+
}
|
|
350
|
+
});
|
|
285
351
|
}
|
|
286
352
|
}
|
|
287
353
|
// Export a singleton instance (optional)
|
|
@@ -8,6 +8,7 @@ export declare class MCPManager extends EventEmitter {
|
|
|
8
8
|
initialize(feedFile?: string): Promise<void>;
|
|
9
9
|
listServerCategories(options?: ServerCategoryListOptions): Promise<MCPServerCategory[]>;
|
|
10
10
|
getFeedConfiguration(categoryName: string): Promise<import("./types.js").FeedConfiguration | undefined>;
|
|
11
|
+
getServerMcpConfig(categoryName: string, serverName: string): Promise<import("./types.js").McpConfig | undefined>;
|
|
11
12
|
installServer(categoryName: string, serverName: string, requestOptions?: ServerInstallOptions): Promise<ServerOperationResult>;
|
|
12
13
|
uninstallServer(categoryName: string, serverName: string, options?: ServerUninstallOptions): Promise<ServerOperationResult>;
|
|
13
14
|
updateRequirement(categoryName: string, serverName: string, requirementName: string, updateVersion: string): Promise<ServerOperationResult>;
|
package/dist/core/MCPManager.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
2
|
import { ConfigurationProvider } from './ConfigurationProvider.js';
|
|
3
3
|
import { InstallationService } from './InstallationService.js';
|
|
4
|
-
import { MCPEvent
|
|
4
|
+
import { MCPEvent } from './types.js';
|
|
5
5
|
export class MCPManager extends EventEmitter {
|
|
6
6
|
installationService;
|
|
7
7
|
configProvider;
|
|
@@ -32,6 +32,9 @@ export class MCPManager extends EventEmitter {
|
|
|
32
32
|
async getFeedConfiguration(categoryName) {
|
|
33
33
|
return this.configProvider.getFeedConfiguration(categoryName);
|
|
34
34
|
}
|
|
35
|
+
async getServerMcpConfig(categoryName, serverName) {
|
|
36
|
+
return this.configProvider.getServerMcpConfig(categoryName, serverName);
|
|
37
|
+
}
|
|
35
38
|
async installServer(categoryName, serverName, requestOptions = {}) {
|
|
36
39
|
try {
|
|
37
40
|
const server = await this.configProvider.getServerCategory(categoryName);
|
|
@@ -66,12 +69,34 @@ export class MCPManager extends EventEmitter {
|
|
|
66
69
|
message: `Server category ${categoryName} is not onboarded`,
|
|
67
70
|
};
|
|
68
71
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
const { targets = [], removeData = false } = options;
|
|
73
|
+
// Clear installation status for specified targets
|
|
74
|
+
const currentStatus = serverCategory.installationStatus || {
|
|
75
|
+
requirementsStatus: {},
|
|
76
|
+
serversStatus: {},
|
|
77
|
+
lastUpdated: new Date().toISOString()
|
|
78
|
+
};
|
|
79
|
+
const serversStatus = currentStatus.serversStatus || {};
|
|
80
|
+
const serverStatus = serversStatus[serverName] || { installedStatus: {}, name: serverName };
|
|
81
|
+
// Only reset installedStatus for specified targets
|
|
82
|
+
for (const target of targets) {
|
|
83
|
+
if (serverStatus.installedStatus) {
|
|
84
|
+
delete serverStatus.installedStatus[target];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (removeData) {
|
|
88
|
+
for (const target of targets) {
|
|
89
|
+
await this.configProvider.removeServerFromClientMCPSettings(serverName, target);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Update server status
|
|
93
|
+
serversStatus[serverName] = serverStatus;
|
|
94
|
+
// Update status keeping requirements
|
|
95
|
+
await this.configProvider.updateInstallationStatus(categoryName, currentStatus.requirementsStatus || {}, serversStatus);
|
|
96
|
+
this.emit(MCPEvent.SERVER_UNINSTALLED, { serverName, targets });
|
|
72
97
|
return {
|
|
73
98
|
success: true,
|
|
74
|
-
message: `Successfully uninstalled ${serverName}`,
|
|
99
|
+
message: `Successfully uninstalled ${serverName} from ${targets.join(', ')}`,
|
|
75
100
|
};
|
|
76
101
|
}
|
|
77
102
|
catch (error) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RequirementConfig, RequirementStatus } from './types.js';
|
|
1
|
+
import { RequirementConfig, RequirementStatus, ServerInstallOptions } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Service responsible for managing requirements installation and status
|
|
4
4
|
*/
|
|
@@ -16,13 +16,13 @@ export declare class RequirementService {
|
|
|
16
16
|
* @param requirement The requirement to install
|
|
17
17
|
* @returns The installation status
|
|
18
18
|
*/
|
|
19
|
-
installRequirement(requirement: RequirementConfig): Promise<RequirementStatus>;
|
|
19
|
+
installRequirement(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
|
|
20
20
|
/**
|
|
21
21
|
* Check the installation status of a requirement
|
|
22
22
|
* @param requirement The requirement to check
|
|
23
23
|
* @returns The installation status
|
|
24
24
|
*/
|
|
25
|
-
checkRequirementStatus(requirement: RequirementConfig): Promise<RequirementStatus>;
|
|
25
|
+
checkRequirementStatus(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
|
|
26
26
|
/**
|
|
27
27
|
* Check if updates are available for a requirement
|
|
28
28
|
* @param requirement The requirement to check for updates
|
|
@@ -35,7 +35,7 @@ export declare class RequirementService {
|
|
|
35
35
|
* @param updateVersion The version to update to
|
|
36
36
|
* @returns The updated requirement status
|
|
37
37
|
*/
|
|
38
|
-
updateRequirement(requirement: RequirementConfig, updateVersion: string): Promise<RequirementStatus>;
|
|
38
|
+
updateRequirement(requirement: RequirementConfig, updateVersion: string, options?: ServerInstallOptions): Promise<RequirementStatus>;
|
|
39
39
|
/**
|
|
40
40
|
* Install multiple requirements
|
|
41
41
|
* @param requirements The requirements to install
|
|
@@ -23,22 +23,22 @@ export class RequirementService {
|
|
|
23
23
|
* @param requirement The requirement to install
|
|
24
24
|
* @returns The installation status
|
|
25
25
|
*/
|
|
26
|
-
async installRequirement(requirement) {
|
|
26
|
+
async installRequirement(requirement, options) {
|
|
27
27
|
// Validate requirement
|
|
28
28
|
this.validateRequirement(requirement);
|
|
29
29
|
// Install the requirement
|
|
30
|
-
return await this.installerFactory.install(requirement);
|
|
30
|
+
return await this.installerFactory.install(requirement, options);
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
33
|
* Check the installation status of a requirement
|
|
34
34
|
* @param requirement The requirement to check
|
|
35
35
|
* @returns The installation status
|
|
36
36
|
*/
|
|
37
|
-
async checkRequirementStatus(requirement) {
|
|
37
|
+
async checkRequirementStatus(requirement, options) {
|
|
38
38
|
// Validate requirement
|
|
39
39
|
this.validateRequirement(requirement);
|
|
40
40
|
// Check the installation status
|
|
41
|
-
return await this.installerFactory.checkInstallation(requirement);
|
|
41
|
+
return await this.installerFactory.checkInstallation(requirement, options);
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
44
|
* Check if updates are available for a requirement
|
|
@@ -54,7 +54,11 @@ export class RequirementService {
|
|
|
54
54
|
if (!installer || !installer.supportCheckUpdates()) {
|
|
55
55
|
return currentStatus;
|
|
56
56
|
}
|
|
57
|
-
|
|
57
|
+
// Pass pythonEnv from currentStatus if it exists for pip packages
|
|
58
|
+
const options = requirement.type === 'pip' && currentStatus.pythonEnv
|
|
59
|
+
? { settings: { pythonEnv: currentStatus.pythonEnv } }
|
|
60
|
+
: undefined;
|
|
61
|
+
const status = await this.checkRequirementStatus(requirement, options);
|
|
58
62
|
return await installer.checkForUpdates(requirement, status);
|
|
59
63
|
}
|
|
60
64
|
/**
|
|
@@ -63,7 +67,7 @@ export class RequirementService {
|
|
|
63
67
|
* @param updateVersion The version to update to
|
|
64
68
|
* @returns The updated requirement status
|
|
65
69
|
*/
|
|
66
|
-
async updateRequirement(requirement, updateVersion) {
|
|
70
|
+
async updateRequirement(requirement, updateVersion, options) {
|
|
67
71
|
// Validate requirement
|
|
68
72
|
this.validateRequirement(requirement);
|
|
69
73
|
// Create an updated requirement with the new version
|
|
@@ -72,7 +76,7 @@ export class RequirementService {
|
|
|
72
76
|
version: updateVersion
|
|
73
77
|
};
|
|
74
78
|
// Install the updated version
|
|
75
|
-
return await this.
|
|
79
|
+
return await this.installerFactory.install(updatedRequirement, options);
|
|
76
80
|
}
|
|
77
81
|
/**
|
|
78
82
|
* Install multiple requirements
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import {
|
|
3
|
+
import { LOCAL_FEEDS_SCHEMA_DIR } from './constants.js';
|
|
4
4
|
import { Logger } from '../utils/logger.js';
|
|
5
5
|
export class ServerSchemaLoader {
|
|
6
6
|
/**
|
|
@@ -8,7 +8,7 @@ export class ServerSchemaLoader {
|
|
|
8
8
|
*/
|
|
9
9
|
static async loadSchema(categoryName, serverName) {
|
|
10
10
|
try {
|
|
11
|
-
const schemaPath = path.join(
|
|
11
|
+
const schemaPath = path.join(LOCAL_FEEDS_SCHEMA_DIR, categoryName, `${serverName}.json`);
|
|
12
12
|
const content = await fs.readFile(schemaPath, 'utf8');
|
|
13
13
|
const schema = JSON.parse(content);
|
|
14
14
|
// Validate schema structure
|
|
@@ -11,7 +11,7 @@ export declare class ServerSchemaProvider {
|
|
|
11
11
|
initialize(): Promise<void>;
|
|
12
12
|
private loadSchema;
|
|
13
13
|
private loadAllSchemas;
|
|
14
|
-
getSchema(categoryName: string,
|
|
14
|
+
getSchema(categoryName: string, schemaFileName: string): Promise<ServerSchema | undefined>;
|
|
15
15
|
reloadSchemas(): Promise<void>;
|
|
16
16
|
}
|
|
17
17
|
export declare function getServerSchemaProvider(): Promise<ServerSchemaProvider>;
|