imcp 0.0.12 → 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 (44) hide show
  1. package/dist/core/ConfigurationProvider.d.ts +2 -1
  2. package/dist/core/ConfigurationProvider.js +20 -24
  3. package/dist/core/InstallationService.d.ts +17 -0
  4. package/dist/core/InstallationService.js +127 -61
  5. package/dist/core/MCPManager.d.ts +1 -0
  6. package/dist/core/MCPManager.js +3 -0
  7. package/dist/core/RequirementService.d.ts +4 -4
  8. package/dist/core/RequirementService.js +11 -7
  9. package/dist/core/ServerSchemaProvider.d.ts +1 -1
  10. package/dist/core/ServerSchemaProvider.js +15 -10
  11. package/dist/core/constants.d.ts +3 -0
  12. package/dist/core/constants.js +4 -1
  13. package/dist/core/installers/requirements/PipInstaller.js +10 -5
  14. package/dist/core/types.d.ts +4 -0
  15. package/dist/services/ServerService.d.ts +5 -0
  16. package/dist/services/ServerService.js +15 -0
  17. package/dist/utils/githubAuth.js +0 -10
  18. package/dist/utils/githubUtils.d.ts +16 -0
  19. package/dist/utils/githubUtils.js +55 -39
  20. package/dist/web/public/css/detailsWidget.css +157 -32
  21. package/dist/web/public/css/serverDetails.css +35 -19
  22. package/dist/web/public/index.html +2 -0
  23. package/dist/web/public/js/detailsWidget.js +43 -40
  24. package/dist/web/public/js/serverCategoryDetails.js +182 -120
  25. package/dist/web/server.js +25 -0
  26. package/package.json +3 -4
  27. package/src/core/ConfigurationProvider.ts +37 -29
  28. package/src/core/InstallationService.ts +176 -62
  29. package/src/core/MCPManager.ts +4 -0
  30. package/src/core/RequirementService.ts +12 -8
  31. package/src/core/ServerSchemaLoader.ts +48 -0
  32. package/src/core/ServerSchemaProvider.ts +137 -0
  33. package/src/core/constants.ts +4 -1
  34. package/src/core/installers/requirements/PipInstaller.ts +10 -5
  35. package/src/core/types.ts +4 -0
  36. package/src/services/ServerService.ts +15 -0
  37. package/src/utils/githubAuth.ts +14 -27
  38. package/src/utils/githubUtils.ts +84 -47
  39. package/src/web/public/css/detailsWidget.css +235 -0
  40. package/src/web/public/css/serverDetails.css +126 -0
  41. package/src/web/public/index.html +2 -0
  42. package/src/web/public/js/detailsWidget.js +264 -0
  43. package/src/web/public/js/serverCategoryDetails.js +182 -120
  44. package/src/web/server.ts +31 -0
@@ -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>;
@@ -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');
@@ -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>;
@@ -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);
@@ -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
@@ -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>;
@@ -42,9 +42,9 @@ export class ServerSchemaProvider {
42
42
  }
43
43
  });
44
44
  }
45
- async loadSchema(categoryName, serverName) {
45
+ async loadSchema(categoryName, schemaFileName) {
46
46
  try {
47
- const schemaPath = path.join(LOCAL_FEEDS_SCHEMA_DIR, categoryName, `${serverName}.json`);
47
+ const schemaPath = path.join(LOCAL_FEEDS_SCHEMA_DIR, categoryName, schemaFileName);
48
48
  const content = await fs.readFile(schemaPath, 'utf8');
49
49
  const schema = JSON.parse(content);
50
50
  return {
@@ -53,10 +53,10 @@ export class ServerSchemaProvider {
53
53
  }
54
54
  catch (error) {
55
55
  if (error.code === 'ENOENT') {
56
- Logger.debug(`No schema file found for server ${serverName} in category ${categoryName}`);
56
+ Logger.debug(`No schema file found for ${schemaFileName} in category ${categoryName}`);
57
57
  return undefined;
58
58
  }
59
- Logger.error(`Error loading schema for server ${serverName} in category ${categoryName}:`, error);
59
+ Logger.error(`Error loading schema ${schemaFileName} in category ${categoryName}:`, error);
60
60
  throw error;
61
61
  }
62
62
  }
@@ -71,15 +71,15 @@ export class ServerSchemaProvider {
71
71
  const serverSchemas = new Map();
72
72
  for (const file of serverFiles) {
73
73
  if (file.endsWith('.json')) {
74
- const serverName = path.basename(file, '.json');
75
74
  try {
76
- const schema = await this.loadSchema(categoryDir.name, serverName);
75
+ const schema = await this.loadSchema(categoryDir.name, file);
77
76
  if (schema) {
78
- serverSchemas.set(serverName, schema);
77
+ // Store with the complete file name for direct lookup
78
+ serverSchemas.set(file, schema);
79
79
  }
80
80
  }
81
81
  catch (error) {
82
- Logger.error(`Error loading schema for server ${serverName} in category ${categoryDir.name}:`, error);
82
+ Logger.error(`Error loading schema for file ${file} in category ${categoryDir.name}:`, error);
83
83
  }
84
84
  }
85
85
  }
@@ -89,13 +89,18 @@ export class ServerSchemaProvider {
89
89
  }
90
90
  }
91
91
  }
92
- async getSchema(categoryName, serverName) {
92
+ async getSchema(categoryName, schemaFileName) {
93
93
  return await this.withLock(async () => {
94
94
  const categorySchemas = this.schemaMap.get(categoryName);
95
95
  if (!categorySchemas) {
96
+ Logger.debug(`No schemas found for category ${categoryName}`);
96
97
  return undefined;
97
98
  }
98
- return categorySchemas.get(serverName);
99
+ const schema = categorySchemas.get(schemaFileName);
100
+ if (!schema) {
101
+ Logger.debug(`Schema ${schemaFileName} not found in category ${categoryName}`);
102
+ }
103
+ return schema;
99
104
  });
100
105
  }
101
106
  async reloadSchemas() {
@@ -6,7 +6,9 @@
6
6
  */
7
7
  export declare const GITHUB_REPO: {
8
8
  url: string;
9
+ repoName: string;
9
10
  feedsPath: string;
11
+ feedAssetsName: string;
10
12
  };
11
13
  /**
12
14
  * Local settings directory path based on OS
@@ -16,6 +18,7 @@ export declare const SETTINGS_DIR: string;
16
18
  * Local feeds directory path
17
19
  */
18
20
  export declare const LOCAL_FEEDS_DIR: string;
21
+ export declare const LOCAL_FEEDS_SCHEMA_DIR: string;
19
22
  /**
20
23
  * Supported client configurations.
21
24
  * Key: Client name (e.g., 'vscode')
@@ -8,7 +8,9 @@ import path from 'path';
8
8
  */
9
9
  export const GITHUB_REPO = {
10
10
  url: 'https://github.com/ai-microsoft/imcp-feed.git',
11
- feedsPath: 'feeds'
11
+ repoName: 'ai-microsoft/imcp-feed',
12
+ feedsPath: 'feeds',
13
+ feedAssetsName: 'imcp-feeds-${latest}.zip',
12
14
  };
13
15
  /**
14
16
  * Local settings directory path based on OS
@@ -25,6 +27,7 @@ export const SETTINGS_DIR = (() => {
25
27
  * Local feeds directory path
26
28
  */
27
29
  export const LOCAL_FEEDS_DIR = path.join(SETTINGS_DIR, 'feeds');
30
+ export const LOCAL_FEEDS_SCHEMA_DIR = path.join(LOCAL_FEEDS_DIR, 'schemas');
28
31
  const CODE_STRORAGE_DIR = (() => {
29
32
  switch (process.platform) {
30
33
  case 'win32':
@@ -21,7 +21,7 @@ export class PipInstaller extends BaseInstaller {
21
21
  }
22
22
  supportCheckUpdates() {
23
23
  /// temporarily disabling update check for pip as not able to get which pip of python is being used
24
- return false;
24
+ return true;
25
25
  }
26
26
  /**
27
27
  * Check if the Python package is already installed
@@ -41,7 +41,8 @@ export class PipInstaller extends BaseInstaller {
41
41
  type: 'pip',
42
42
  installed,
43
43
  version: installedVersion,
44
- inProgress: false
44
+ inProgress: false,
45
+ pythonEnv: this.getPythonCommand(options) // Preserve pythonEnv in status check too
45
46
  };
46
47
  }
47
48
  catch (error) {
@@ -50,7 +51,8 @@ export class PipInstaller extends BaseInstaller {
50
51
  type: 'pip',
51
52
  installed: false,
52
53
  error: error instanceof Error ? error.message : String(error),
53
- inProgress: false
54
+ inProgress: false,
55
+ pythonEnv: this.getPythonCommand(options) // Preserve pythonEnv even in error case
54
56
  };
55
57
  }
56
58
  }
@@ -106,12 +108,14 @@ export class PipInstaller extends BaseInstaller {
106
108
  throw new Error('Invalid registry configuration');
107
109
  }
108
110
  }
111
+ // Store the pythonEnv in the status for future use
109
112
  return {
110
113
  name: requirement.name,
111
114
  type: 'pip',
112
115
  installed: true,
113
116
  version: requirement.version,
114
- inProgress: false
117
+ inProgress: false,
118
+ pythonEnv: this.getPythonCommand(options) // Store the python env
115
119
  };
116
120
  }
117
121
  catch (error) {
@@ -120,7 +124,8 @@ export class PipInstaller extends BaseInstaller {
120
124
  type: 'pip',
121
125
  installed: false,
122
126
  error: error instanceof Error ? error.message : String(error),
123
- inProgress: false
127
+ inProgress: false,
128
+ pythonEnv: this.getPythonCommand(options) // Store the python env
124
129
  };
125
130
  }
126
131
  }
@@ -16,6 +16,7 @@ export interface RequirementStatus {
16
16
  };
17
17
  lastCheckTime?: string;
18
18
  operationStatus?: OperationStatus;
19
+ pythonEnv?: string;
19
20
  }
20
21
  export interface MCPServerStatus {
21
22
  installedStatus: Record<string, OperationStatus>;
@@ -100,6 +101,8 @@ export interface McpConfig {
100
101
  description: string;
101
102
  mode: 'stdio' | 'http';
102
103
  dependencies?: DependencyConfig;
104
+ schemas?: string;
105
+ repository?: string;
103
106
  installation: InstallationConfig;
104
107
  }
105
108
  export interface RegistryConfig {
@@ -128,6 +131,7 @@ export interface FeedConfiguration {
128
131
  name: string;
129
132
  displayName: string;
130
133
  description: string;
134
+ repository?: string;
131
135
  requirements: RequirementConfig[];
132
136
  mcpServers: McpConfig[];
133
137
  }
@@ -1,3 +1,4 @@
1
+ import { ServerSchema } from '../core/ServerSchemaProvider.js';
1
2
  import { MCPServerCategory, ServerInstallOptions, ServerCategoryListOptions, ServerOperationResult, ServerUninstallOptions } from '../core/types.js';
2
3
  /**
3
4
  * ServerService provides a unified interface for server management operations.
@@ -18,6 +19,10 @@ export declare class ServerService {
18
19
  * @private
19
20
  */
20
21
  private checkRequirementsForUpdate;
22
+ /**
23
+ * Gets the schema for a specific server in a category
24
+ */
25
+ getServerSchema(categoryName: string, serverName: string): Promise<ServerSchema | undefined>;
21
26
  /**
22
27
  * Installs a specific mcp tool for a server.
23
28
  * TODO: This might require enhancing MCPManager to handle category-specific installs.