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.
Files changed (57) hide show
  1. package/dist/cli/commands/uninstall.js +14 -6
  2. package/dist/core/ConfigurationProvider.d.ts +3 -1
  3. package/dist/core/ConfigurationProvider.js +85 -25
  4. package/dist/core/InstallationService.d.ts +17 -0
  5. package/dist/core/InstallationService.js +127 -61
  6. package/dist/core/MCPManager.d.ts +1 -0
  7. package/dist/core/MCPManager.js +30 -5
  8. package/dist/core/RequirementService.d.ts +4 -4
  9. package/dist/core/RequirementService.js +11 -7
  10. package/dist/core/ServerSchemaLoader.js +2 -2
  11. package/dist/core/ServerSchemaProvider.d.ts +1 -1
  12. package/dist/core/ServerSchemaProvider.js +15 -10
  13. package/dist/core/constants.d.ts +14 -1
  14. package/dist/core/constants.js +4 -1
  15. package/dist/core/installers/clients/ExtensionInstaller.js +3 -0
  16. package/dist/core/installers/requirements/PipInstaller.js +10 -5
  17. package/dist/core/types.d.ts +10 -0
  18. package/dist/services/ServerService.d.ts +12 -1
  19. package/dist/services/ServerService.js +39 -9
  20. package/dist/utils/githubAuth.js +0 -10
  21. package/dist/utils/githubUtils.d.ts +16 -0
  22. package/dist/utils/githubUtils.js +55 -39
  23. package/dist/utils/osUtils.js +1 -1
  24. package/dist/web/public/css/detailsWidget.css +189 -57
  25. package/dist/web/public/css/modal.css +42 -0
  26. package/dist/web/public/css/serverDetails.css +35 -18
  27. package/dist/web/public/index.html +2 -0
  28. package/dist/web/public/js/detailsWidget.js +175 -60
  29. package/dist/web/public/js/modal.js +93 -29
  30. package/dist/web/public/js/notifications.js +34 -35
  31. package/dist/web/public/js/serverCategoryDetails.js +182 -120
  32. package/dist/web/server.js +38 -2
  33. package/package.json +3 -4
  34. package/src/cli/commands/uninstall.ts +16 -6
  35. package/src/core/ConfigurationProvider.ts +102 -25
  36. package/src/core/InstallationService.ts +176 -62
  37. package/src/core/MCPManager.ts +36 -5
  38. package/src/core/RequirementService.ts +12 -8
  39. package/src/core/ServerSchemaLoader.ts +48 -0
  40. package/src/core/ServerSchemaProvider.ts +137 -0
  41. package/src/core/constants.ts +16 -3
  42. package/src/core/installers/clients/ExtensionInstaller.ts +3 -0
  43. package/src/core/installers/requirements/PipInstaller.ts +10 -5
  44. package/src/core/types.ts +11 -1
  45. package/src/services/ServerService.ts +41 -8
  46. package/src/utils/githubAuth.ts +14 -27
  47. package/src/utils/githubUtils.ts +84 -47
  48. package/src/utils/osUtils.ts +1 -1
  49. package/src/web/public/css/detailsWidget.css +235 -0
  50. package/src/web/public/css/modal.css +42 -0
  51. package/src/web/public/css/serverDetails.css +126 -0
  52. package/src/web/public/index.html +2 -0
  53. package/src/web/public/js/detailsWidget.js +264 -0
  54. package/src/web/public/js/modal.js +93 -29
  55. package/src/web/public/js/notifications.js +34 -35
  56. package/src/web/public/js/serverCategoryDetails.js +182 -120
  57. 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>', '--names <names>', 'Server names (semicolon separated)')
7
- .option('--remove-data', 'Remove all associated data', false)
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
- if (!serverService.validateServerName(options.category, serverNames)) {
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) => serverService.uninstallMcpServer(options.category, serverName, {
17
- removeData: options.removeData,
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
- try {
245
- await fs.access(path.join(this.tempDir, '.git'));
246
- Logger.debug('Found existing repository, updating...');
247
- const { stdout, stderr } = await execAsync('git pull', { cwd: this.tempDir });
248
- Logger.debug({
249
- action: 'git_pull',
250
- stderr,
251
- stdout
252
- });
253
- }
254
- catch (err) {
255
- Logger.debug('No existing repository found, cloning...');
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 = path.join(this.tempDir, GITHUB_REPO.feedsPath);
263
+ const sourceFeedsDir = downloadPath;
268
264
  try {
269
- await fs.access(sourceFeedsDir);
265
+ await fs.access(downloadPath);
270
266
  }
271
267
  catch (err) {
272
- throw new Error(`Could not find feeds directory in cloned repository: ${sourceFeedsDir}`);
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.requirements)
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, requirements) {
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.map(async (reqToUpdate) => {
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
- // Update the requirement
127
- const updatedStatus = await requirementService.updateRequirement(updatedReqConfig, reqToUpdate.version);
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
- await Promise.all(updatePromises);
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
- // Chain installations in sequence while keeping them non-blocking
230
- await sortedRequirements.reduce((chain, requirement) => {
231
- return chain.then(async () => {
232
- const feeds = await configProvider.getFeedConfiguration(categoryName);
233
- const requirementConfig = feeds?.requirements?.find((r) => r.name === requirement.name) || {
234
- name: requirement.name,
235
- version: requirement.version,
236
- type: 'npm'
237
- };
238
- const installer = this.installerFactory.getInstaller(requirementConfig);
239
- if (!installer) {
240
- await configProvider.updateRequirementStatus(categoryName, requirement.name, {
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
- type: requirementConfig.type,
243
- installed: false,
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
- await configProvider.updateRequirementStatus(categoryName, requirement.name, status);
281
- });
282
- });
283
- }, Promise.resolve());
284
- return null;
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>;
@@ -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, } from './types.js';
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
- // Clear installation status
70
- await this.configProvider.updateInstallationStatus(categoryName, {}, {});
71
- this.emit(MCPEvent.SERVER_UNINSTALLED, { serverName });
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
- const status = await this.checkRequirementStatus(requirement);
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.installRequirement(updatedRequirement);
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 { LOCAL_FEEDS_DIR } from './constants.js';
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(LOCAL_FEEDS_DIR, categoryName, `${serverName}.json`);
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, serverName: string): Promise<ServerSchema | undefined>;
14
+ getSchema(categoryName: string, schemaFileName: string): Promise<ServerSchema | undefined>;
15
15
  reloadSchemas(): Promise<void>;
16
16
  }
17
17
  export declare function getServerSchemaProvider(): Promise<ServerSchemaProvider>;