imcp 0.0.12 → 0.0.14

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 (81) 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/clients/ClientInstaller.js +58 -40
  14. package/dist/core/installers/requirements/PipInstaller.js +10 -5
  15. package/dist/core/onboard/FeedOnboardService.d.ts +35 -0
  16. package/dist/core/onboard/FeedOnboardService.js +137 -0
  17. package/dist/core/types.d.ts +6 -1
  18. package/dist/core/validators/FeedValidator.d.ts +13 -0
  19. package/dist/core/validators/FeedValidator.js +27 -0
  20. package/dist/services/ServerService.d.ts +5 -0
  21. package/dist/services/ServerService.js +15 -0
  22. package/dist/utils/githubAuth.js +0 -10
  23. package/dist/utils/githubUtils.d.ts +16 -0
  24. package/dist/utils/githubUtils.js +55 -39
  25. package/dist/web/contract/serverContract.d.ts +64 -0
  26. package/dist/web/contract/serverContract.js +2 -0
  27. package/dist/web/public/css/detailsWidget.css +157 -32
  28. package/dist/web/public/css/onboard.css +44 -0
  29. package/dist/web/public/css/serverDetails.css +35 -19
  30. package/dist/web/public/index.html +16 -10
  31. package/dist/web/public/js/detailsWidget.js +43 -40
  32. package/dist/web/public/js/modal/index.js +58 -0
  33. package/dist/web/public/js/modal/installHandler.js +227 -0
  34. package/dist/web/public/js/modal/installModal.js +163 -0
  35. package/dist/web/public/js/modal/installation.js +281 -0
  36. package/dist/web/public/js/modal/loadingModal.js +52 -0
  37. package/dist/web/public/js/modal/loadingUI.js +74 -0
  38. package/dist/web/public/js/modal/messageQueue.js +112 -0
  39. package/dist/web/public/js/modal/modalSetup.js +512 -0
  40. package/dist/web/public/js/modal/modalUI.js +214 -0
  41. package/dist/web/public/js/modal/modalUtils.js +49 -0
  42. package/dist/web/public/js/modal/version.js +20 -0
  43. package/dist/web/public/js/modal/versionUtils.js +20 -0
  44. package/dist/web/public/js/modal.js +25 -1041
  45. package/dist/web/public/js/onboard/formProcessor.js +309 -0
  46. package/dist/web/public/js/onboard/index.js +131 -0
  47. package/dist/web/public/js/onboard/state.js +32 -0
  48. package/dist/web/public/js/onboard/templates.js +375 -0
  49. package/dist/web/public/js/onboard/uiHandlers.js +196 -0
  50. package/dist/web/public/js/serverCategoryDetails.js +211 -123
  51. package/dist/web/public/onboard.html +150 -0
  52. package/dist/web/server.js +25 -0
  53. package/package.json +3 -4
  54. package/src/core/ConfigurationProvider.ts +37 -29
  55. package/src/core/InstallationService.ts +176 -62
  56. package/src/core/MCPManager.ts +4 -0
  57. package/src/core/RequirementService.ts +12 -8
  58. package/src/core/ServerSchemaLoader.ts +48 -0
  59. package/src/core/ServerSchemaProvider.ts +137 -0
  60. package/src/core/constants.ts +4 -1
  61. package/src/core/installers/clients/ClientInstaller.ts +66 -49
  62. package/src/core/installers/requirements/PipInstaller.ts +10 -5
  63. package/src/core/types.ts +6 -1
  64. package/src/services/ServerService.ts +15 -0
  65. package/src/utils/githubAuth.ts +14 -27
  66. package/src/utils/githubUtils.ts +84 -47
  67. package/src/web/public/css/detailsWidget.css +235 -0
  68. package/src/web/public/css/serverDetails.css +126 -0
  69. package/src/web/public/index.html +16 -10
  70. package/src/web/public/js/detailsWidget.js +264 -0
  71. package/src/web/public/js/modal/index.js +58 -0
  72. package/src/web/public/js/modal/installModal.js +163 -0
  73. package/src/web/public/js/modal/installation.js +281 -0
  74. package/src/web/public/js/modal/loadingModal.js +52 -0
  75. package/src/web/public/js/modal/messageQueue.js +112 -0
  76. package/src/web/public/js/modal/modalSetup.js +512 -0
  77. package/src/web/public/js/modal/modalUtils.js +49 -0
  78. package/src/web/public/js/modal/versionUtils.js +20 -0
  79. package/src/web/public/js/modal.js +25 -1041
  80. package/src/web/public/js/serverCategoryDetails.js +211 -123
  81. package/src/web/server.ts +31 -0
@@ -276,6 +276,7 @@ export class ClientInstaller {
276
276
  // --- Start of new logic ---
277
277
  // Clone the base installation configuration to avoid modifying the original serverConfig
278
278
  const installConfig = JSON.parse(JSON.stringify(serverConfig.installation));
279
+ installConfig.mode = serverConfig.mode;
279
280
  const pythonEnv = options.settings?.pythonEnv;
280
281
  let pythonDir = null;
281
282
  // 1. Determine which args to use and resolve npm paths
@@ -499,39 +500,48 @@ export class ClientInstaller {
499
500
  if (!settings.mcpServers) {
500
501
  settings.mcpServers = {};
501
502
  }
502
- // Special handling for Windows when command is npx for Cline and MSROO clients
503
- // Use a copy to avoid modifying the passed installConfig directly if needed elsewhere
504
- const serverConfigForClient = { ...installConfig };
505
- if (process.platform === 'win32' &&
506
- serverConfigForClient.command === 'npx' &&
507
- (clientName === 'Cline' || clientName === 'MSRooCode' || clientName === 'MSROO')) {
508
- // Update command to cmd
509
- serverConfigForClient.command = 'cmd';
510
- // Add /c and npx at the beginning of args
511
- serverConfigForClient.args = ['/c', 'npx', ...serverConfigForClient.args];
512
- // Add APPDATA environment variable pointing to npm directory
513
- if (!serverConfigForClient.env) {
514
- serverConfigForClient.env = {};
503
+ if (installConfig.mode === 'stdio') {
504
+ // Special handling for Windows when command is npx for Cline and MSROO clients
505
+ // Use a copy to avoid modifying the passed installConfig directly if needed elsewhere
506
+ const serverConfigForClient = { ...installConfig };
507
+ if (process.platform === 'win32' &&
508
+ serverConfigForClient.command === 'npx' &&
509
+ (clientName === 'Cline' || clientName === 'MSRooCode' || clientName === 'MSROO')) {
510
+ // Update command to cmd
511
+ serverConfigForClient.command = 'cmd';
512
+ // Add /c and npx at the beginning of args
513
+ serverConfigForClient.args = ['/c', 'npx', ...serverConfigForClient.args];
514
+ // Add APPDATA environment variable pointing to npm directory
515
+ if (!serverConfigForClient.env) {
516
+ serverConfigForClient.env = {};
517
+ }
518
+ // Dynamically get npm path and set APPDATA to it
519
+ const npmPath = await this.getNpmPath();
520
+ serverConfigForClient.env['APPDATA'] = npmPath;
521
+ Logger.debug(`Windows npx fix: command=${serverConfigForClient.command}, args=${serverConfigForClient.args.join(' ')}, env=${JSON.stringify(serverConfigForClient.env)}`);
522
+ }
523
+ // Convert backslashes to forward slashes in args paths
524
+ if (serverConfigForClient.args) {
525
+ serverConfigForClient.args = serverConfigForClient.args.map((arg) => typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg);
515
526
  }
516
- // Dynamically get npm path and set APPDATA to it
517
- const npmPath = await this.getNpmPath();
518
- serverConfigForClient.env['APPDATA'] = npmPath;
519
- Logger.debug(`Windows npx fix: command=${serverConfigForClient.command}, args=${serverConfigForClient.args.join(' ')}, env=${JSON.stringify(serverConfigForClient.env)}`);
527
+ // Add or update the server configuration
528
+ settings.mcpServers[serverName] = {
529
+ command: serverConfigForClient.command,
530
+ args: serverConfigForClient.args,
531
+ env: serverConfigForClient.env,
532
+ autoApprove: [],
533
+ disabled: false,
534
+ alwaysAllow: []
535
+ };
536
+ Logger.debug(`Updating ${settingPath} for ${serverName}: ${JSON.stringify(settings.mcpServers[serverName])}`);
520
537
  }
521
- // Convert backslashes to forward slashes in args paths
522
- if (serverConfigForClient.args) {
523
- serverConfigForClient.args = serverConfigForClient.args.map((arg) => typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg);
538
+ else if (installConfig.mode === 'sse') {
539
+ // Handle SSE mode for Cline and MSRoo clients
540
+ settings.mcpServers[serverName] = {
541
+ type: 'sse',
542
+ url: installConfig.url
543
+ };
524
544
  }
525
- // Add or update the server configuration
526
- settings.mcpServers[serverName] = {
527
- command: serverConfigForClient.command,
528
- args: serverConfigForClient.args,
529
- env: serverConfigForClient.env,
530
- autoApprove: [],
531
- disabled: false,
532
- alwaysAllow: []
533
- };
534
- Logger.debug(`Updating ${settingPath} for ${serverName}: ${JSON.stringify(settings.mcpServers[serverName])}`);
535
545
  // Write the updated settings back to the file
536
546
  await writeJsonFile(settingPath, settings);
537
547
  }
@@ -551,17 +561,25 @@ export class ClientInstaller {
551
561
  }
552
562
  // Use a copy to avoid modifying the passed installConfig directly
553
563
  const serverConfigForClient = { ...installConfig };
554
- // Convert backslashes to forward slashes in args paths
555
- if (serverConfigForClient.args) {
556
- serverConfigForClient.args = serverConfigForClient.args.map((arg) => typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg);
564
+ if (installConfig.mode === 'stdio') {
565
+ if (serverConfigForClient.args) {
566
+ serverConfigForClient.args = serverConfigForClient.args.map((arg) => typeof arg === 'string' ? arg.replace(/\\/g, '/') : arg);
567
+ }
568
+ // Add or update the server configuration
569
+ settings.mcp.servers[serverName] = {
570
+ command: serverConfigForClient.command,
571
+ args: serverConfigForClient.args,
572
+ env: serverConfigForClient.env
573
+ };
574
+ Logger.debug(`Updating ${settingPath} for ${serverName}: ${JSON.stringify(settings.mcp.servers[serverName])}`);
575
+ }
576
+ else if (installConfig.mode === 'sse') {
577
+ // Handle SSE mode for Github Copilot
578
+ settings.mcp.servers[serverName] = {
579
+ type: 'sse',
580
+ url: installConfig.url
581
+ };
557
582
  }
558
- // Add or update the server configuration
559
- settings.mcp.servers[serverName] = {
560
- command: serverConfigForClient.command,
561
- args: serverConfigForClient.args,
562
- env: serverConfigForClient.env
563
- };
564
- Logger.debug(`Updating ${settingPath} for ${serverName}: ${JSON.stringify(settings.mcp.servers[serverName])}`);
565
583
  // Write the updated settings back to the file
566
584
  await writeJsonFile(settingPath, settings);
567
585
  }
@@ -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
  }
@@ -0,0 +1,35 @@
1
+ import { FeedConfiguration } from '../types.js';
2
+ /**
3
+ * Service for handling feed onboarding operations
4
+ */
5
+ export declare class FeedOnboardService {
6
+ private tempDir;
7
+ private repoDir;
8
+ constructor();
9
+ /**
10
+ * Onboard a new feed configuration
11
+ * @param config Feed configuration to onboard
12
+ */
13
+ onboardFeed(config: FeedConfiguration): Promise<void>;
14
+ /**
15
+ * Fork the repository using gh CLI
16
+ */
17
+ private forkRepo;
18
+ /**
19
+ * Clone the forked repository
20
+ */
21
+ private cloneRepo;
22
+ /**
23
+ * Get GitHub username using gh CLI
24
+ */
25
+ private getGitHubUsername;
26
+ /**
27
+ * Save feed configuration to file
28
+ */
29
+ private saveFeedConfig;
30
+ /**
31
+ * Create pull request using gh CLI
32
+ */
33
+ private createPullRequest;
34
+ }
35
+ export declare const feedOnboardService: FeedOnboardService;
@@ -0,0 +1,137 @@
1
+ import { exec } from 'child_process';
2
+ import { promisify } from 'util';
3
+ import fs from 'fs/promises';
4
+ import path from 'path';
5
+ import { GITHUB_REPO, SETTINGS_DIR } from '../constants.js';
6
+ import { feedValidator } from '../validators/FeedValidator.js';
7
+ import { Logger } from '../../utils/logger.js';
8
+ const execAsync = promisify(exec);
9
+ /**
10
+ * Service for handling feed onboarding operations
11
+ */
12
+ export class FeedOnboardService {
13
+ tempDir;
14
+ repoDir;
15
+ constructor() {
16
+ this.tempDir = path.join(SETTINGS_DIR, 'temp');
17
+ this.repoDir = path.join(this.tempDir, 'imcp-feed');
18
+ }
19
+ /**
20
+ * Onboard a new feed configuration
21
+ * @param config Feed configuration to onboard
22
+ */
23
+ async onboardFeed(config) {
24
+ try {
25
+ // Validate feed configuration
26
+ feedValidator.validate(config);
27
+ // Create temp directory
28
+ await fs.mkdir(this.tempDir, { recursive: true });
29
+ // Fork the repo
30
+ await this.forkRepo();
31
+ // Clone the forked repo
32
+ await this.cloneRepo();
33
+ // Save feed configuration
34
+ await this.saveFeedConfig(config);
35
+ // Create branch, push changes and create PR
36
+ await this.createPullRequest(config);
37
+ Logger.log(`Successfully onboarded feed: ${config.name}`);
38
+ }
39
+ catch (error) {
40
+ Logger.error('Feed onboarding failed:', error);
41
+ throw error;
42
+ }
43
+ finally {
44
+ // Cleanup temp directory
45
+ await fs.rm(this.tempDir, { recursive: true, force: true });
46
+ }
47
+ }
48
+ /**
49
+ * Fork the repository using gh CLI
50
+ */
51
+ async forkRepo() {
52
+ try {
53
+ await execAsync(`gh repo fork ${GITHUB_REPO.repoName} --clone=false`);
54
+ Logger.debug('Successfully forked repository');
55
+ }
56
+ catch (error) {
57
+ throw new Error(`Failed to fork repository: ${error instanceof Error ? error.message : String(error)}`);
58
+ }
59
+ }
60
+ /**
61
+ * Clone the forked repository
62
+ */
63
+ async cloneRepo() {
64
+ try {
65
+ // Clean up existing repo directory
66
+ await fs.rm(this.repoDir, { recursive: true, force: true });
67
+ const username = await this.getGitHubUsername();
68
+ const repoUrl = `https://github.com/${username}/${GITHUB_REPO.repoName.split('/')[1]}.git`;
69
+ // Ensure temp dir exists
70
+ await fs.mkdir(this.tempDir, { recursive: true });
71
+ // Clone the repo into imcp-feed subdirectory
72
+ await execAsync(`git clone ${repoUrl} ${this.repoDir}`);
73
+ Logger.debug('Successfully cloned repository');
74
+ }
75
+ catch (error) {
76
+ throw new Error(`Failed to clone repository: ${error instanceof Error ? error.message : String(error)}`);
77
+ }
78
+ }
79
+ /**
80
+ * Get GitHub username using gh CLI
81
+ */
82
+ async getGitHubUsername() {
83
+ try {
84
+ const { stdout } = await execAsync('gh api user -q .login');
85
+ return stdout.trim();
86
+ }
87
+ catch (error) {
88
+ throw new Error(`Failed to get GitHub username: ${error instanceof Error ? error.message : String(error)}`);
89
+ }
90
+ }
91
+ /**
92
+ * Save feed configuration to file
93
+ */
94
+ async saveFeedConfig(config) {
95
+ try {
96
+ const feedsDir = path.join(this.repoDir, GITHUB_REPO.feedsPath);
97
+ await fs.mkdir(feedsDir, { recursive: true });
98
+ const configPath = path.join(feedsDir, `${config.name}.json`);
99
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2));
100
+ Logger.debug(`Saved feed configuration to ${configPath}`);
101
+ }
102
+ catch (error) {
103
+ throw new Error(`Failed to save feed configuration: ${error instanceof Error ? error.message : String(error)}`);
104
+ }
105
+ }
106
+ /**
107
+ * Create pull request using gh CLI
108
+ */
109
+ async createPullRequest(config) {
110
+ try {
111
+ process.chdir(this.repoDir);
112
+ // Ensure we're up to date with upstream main
113
+ await execAsync(`git remote add upstream ${GITHUB_REPO.url}`);
114
+ await execAsync('git fetch upstream');
115
+ await execAsync('git reset --hard upstream/main');
116
+ // Create and checkout new branch
117
+ const branchName = `feed/${config.name}`;
118
+ await execAsync(`git checkout -b ${branchName}`);
119
+ // Stage and commit changes
120
+ await execAsync('git add .');
121
+ await execAsync(`git commit -m "Add feed configuration for ${config.name}"`);
122
+ // Push to origin
123
+ await execAsync(`git push -f origin ${branchName}`);
124
+ const title = `Add feed configuration for ${config.name}`;
125
+ const body = `Add new feed configuration:\n\n- Name: ${config.name}\n- Display Name: ${config.displayName}\n- Description: ${config.description}`;
126
+ const username = await this.getGitHubUsername();
127
+ await execAsync(`gh pr create --title "${title}" --body "${body}" --repo ${GITHUB_REPO.repoName} --base main --head ${username}:${branchName}`);
128
+ Logger.debug('Successfully created pull request');
129
+ }
130
+ catch (error) {
131
+ throw new Error(`Failed to create pull request: ${error instanceof Error ? error.message : String(error)}`);
132
+ }
133
+ }
134
+ }
135
+ // Export singleton instance
136
+ export const feedOnboardService = new FeedOnboardService();
137
+ //# sourceMappingURL=FeedOnboardService.js.map
@@ -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>;
@@ -84,6 +85,7 @@ export interface InstallationConfig {
84
85
  command: string;
85
86
  args: string[];
86
87
  env?: Record<string, EnvVariableConfig>;
88
+ url?: string;
87
89
  }
88
90
  export interface DependencyConfig {
89
91
  requirements?: Array<{
@@ -98,8 +100,10 @@ export interface DependencyConfig {
98
100
  export interface McpConfig {
99
101
  name: string;
100
102
  description: string;
101
- mode: 'stdio' | 'http';
103
+ mode: 'stdio' | 'sse';
102
104
  dependencies?: DependencyConfig;
105
+ schemas?: string;
106
+ repository?: string;
103
107
  installation: InstallationConfig;
104
108
  }
105
109
  export interface RegistryConfig {
@@ -128,6 +132,7 @@ export interface FeedConfiguration {
128
132
  name: string;
129
133
  displayName: string;
130
134
  description: string;
135
+ repository?: string;
131
136
  requirements: RequirementConfig[];
132
137
  mcpServers: McpConfig[];
133
138
  }
@@ -0,0 +1,13 @@
1
+ import { FeedConfiguration } from "../types.js";
2
+ /**
3
+ * Validates feed configurations to ensure they meet required criteria
4
+ */
5
+ export declare class FeedValidator {
6
+ /**
7
+ * Validates a feed configuration
8
+ * @param config The feed configuration to validate
9
+ * @returns true if valid, throws error if invalid
10
+ */
11
+ validate(config: FeedConfiguration): boolean;
12
+ }
13
+ export declare const feedValidator: FeedValidator;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Validates feed configurations to ensure they meet required criteria
3
+ */
4
+ export class FeedValidator {
5
+ /**
6
+ * Validates a feed configuration
7
+ * @param config The feed configuration to validate
8
+ * @returns true if valid, throws error if invalid
9
+ */
10
+ validate(config) {
11
+ try {
12
+ // For now return true as requested
13
+ return true;
14
+ // TODO: Implement full validation logic:
15
+ // - Required fields (name, displayName, etc)
16
+ // - Server configuration validation
17
+ // - Requirements validation
18
+ // - Repository validation
19
+ }
20
+ catch (error) {
21
+ throw new Error(`Feed validation failed: ${error instanceof Error ? error.message : String(error)}`);
22
+ }
23
+ }
24
+ }
25
+ // Export singleton instance
26
+ export const feedValidator = new FeedValidator();
27
+ //# sourceMappingURL=FeedValidator.js.map
@@ -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.
@@ -1,6 +1,7 @@
1
1
  import path from 'path';
2
2
  import { fileURLToPath } from 'url';
3
3
  import { Logger } from '../utils/logger.js';
4
+ import { getServerSchemaProvider } from '../core/ServerSchemaProvider.js';
4
5
  import { mcpManager } from '../core/MCPManager.js';
5
6
  import { UPDATE_CHECK_INTERVAL_MS } from '../core/constants.js';
6
7
  import { updateCheckTracker } from '../utils/UpdateCheckTracker.js';
@@ -96,6 +97,20 @@ export class ServerService {
96
97
  }
97
98
  }
98
99
  }
100
+ /**
101
+ * Gets the schema for a specific server in a category
102
+ */
103
+ async getServerSchema(categoryName, serverName) {
104
+ try {
105
+ const provider = await getServerSchemaProvider();
106
+ const serverMcpConfig = await mcpManager.getServerMcpConfig(categoryName, serverName);
107
+ return await provider.getSchema(categoryName, serverMcpConfig?.schemas || `${serverName}.json`);
108
+ }
109
+ catch (error) {
110
+ Logger.error(`Failed to get schema for server ${serverName} in category ${categoryName}:`, error);
111
+ throw error;
112
+ }
113
+ }
99
114
  /**
100
115
  * Installs a specific mcp tool for a server.
101
116
  * TODO: This might require enhancing MCPManager to handle category-specific installs.
@@ -29,16 +29,6 @@ class GithubAuthError extends Error {
29
29
  export async function checkGithubAuth() {
30
30
  Logger.debug('Starting GitHub authentication check');
31
31
  try {
32
- // Check if git is installed
33
- if (!await isToolInstalled('git')) {
34
- Logger.log('Installing required Git...');
35
- await installCLI('git');
36
- // Verify git was installed correctly, with retry mechanism
37
- if (!await isToolInstalled('git')) {
38
- throw new Error('Failed to install Git. Please install it manually and try again.');
39
- }
40
- Logger.debug('Git installed successfully and verified');
41
- }
42
32
  // Check if gh CLI is installed
43
33
  if (!await isToolInstalled('gh')) {
44
34
  Logger.log('Installing required GitHub CLI...');
@@ -1,4 +1,19 @@
1
1
  import { RegistryConfig, RequirementConfig } from '../core/types.js';
2
+ interface DownloadGithubReleaseResult {
3
+ version: string;
4
+ downloadPath: string;
5
+ }
6
+ /**
7
+ * Downloads a GitHub release asset
8
+ * @param repo GitHub repository in format owner/repo
9
+ * @param version Version to download, can be "latest"
10
+ * @param assetsName Assets name pattern (optional, but either assetsName or assetName must be provided)
11
+ * @param assetName Asset name pattern (optional, but either assetsName or assetName must be provided)
12
+ * @param isFolder Whether to treat the downloaded asset as a folder (default: false)
13
+ * @param targetDirectory Target directory for downloads (default: SETTINGS_DIR/downloads)
14
+ * @returns Object containing version and download path
15
+ */
16
+ export declare function downloadGithubRelease(repo: string, version: string, assetsName?: string, assetName?: string, isFolder?: boolean, targetDirectory?: string): Promise<DownloadGithubReleaseResult>;
2
17
  /**
3
18
  * Helper to handle GitHub release downloads
4
19
  * @param requirement The requirement configuration
@@ -9,3 +24,4 @@ export declare function handleGitHubRelease(requirement: RequirementConfig, regi
9
24
  resolvedVersion: string;
10
25
  resolvedPath: string;
11
26
  }>;
27
+ export {};
@@ -3,78 +3,94 @@ import util from 'util';
3
3
  import fs from 'fs/promises';
4
4
  import path from 'path';
5
5
  import { extractZipFile } from './clientUtils.js';
6
- import { Logger } from './logger.js';
7
6
  import { SETTINGS_DIR } from '../core/constants.js';
8
7
  const execAsync = util.promisify(exec);
9
8
  /**
10
- * Helper to handle GitHub release downloads
11
- * @param requirement The requirement configuration
12
- * @param registry The GitHub release registry configuration
13
- * @returns The path to the downloaded file
9
+ * Downloads a GitHub release asset
10
+ * @param repo GitHub repository in format owner/repo
11
+ * @param version Version to download, can be "latest"
12
+ * @param assetsName Assets name pattern (optional, but either assetsName or assetName must be provided)
13
+ * @param assetName Asset name pattern (optional, but either assetsName or assetName must be provided)
14
+ * @param isFolder Whether to treat the downloaded asset as a folder (default: false)
15
+ * @param targetDirectory Target directory for downloads (default: SETTINGS_DIR/downloads)
16
+ * @returns Object containing version and download path
14
17
  */
15
- export async function handleGitHubRelease(requirement, registry) {
16
- if (!registry) {
17
- throw new Error('GitHub release registry configuration is required');
18
+ export async function downloadGithubRelease(repo, version, assetsName, assetName, isFolder = false, targetDirectory) {
19
+ if (!repo) {
20
+ throw new Error('GitHub repository is required');
18
21
  }
19
- const downloadsDir = path.join(SETTINGS_DIR, 'downloads');
20
- await fs.mkdir(downloadsDir, { recursive: true });
21
- const { repository, assetsName, assetName } = registry;
22
- if (!repository) {
23
- throw new Error('GitHub repository is required for GitHub release downloads');
22
+ if (!assetsName && !assetName) {
23
+ throw new Error('Either assetsName or assetName must be specified');
24
24
  }
25
- let version = requirement.version;
26
- let resolvedAssetName = assetName || '';
27
- let resolvedAssetsName = assetsName || '';
28
- const { stdout } = await execAsync(`gh release view --repo ${repository} --json tagName --jq .tagName`);
25
+ const downloadsDir = targetDirectory || path.join(SETTINGS_DIR, 'downloads');
26
+ await fs.mkdir(downloadsDir, { recursive: true });
27
+ // Get latest version if needed
28
+ const { stdout } = await execAsync(`gh release view --repo ${repo} --json tagName --jq .tagName`);
29
29
  const latestTag = stdout.trim();
30
30
  let latestVersion = latestTag;
31
31
  const tagWithVPrefix = latestVersion.startsWith('v');
32
32
  if (tagWithVPrefix)
33
33
  latestVersion = latestVersion.substring(1); // Remove 'v' prefix if present
34
- version = version.includes("latest") ? latestVersion : version;
34
+ const resolvedVersion = version.includes("latest") ? latestVersion : version;
35
+ // Resolve asset names
36
+ let resolvedAssetsName = '';
37
+ let resolvedAssetName = '';
35
38
  if (assetsName) {
36
- resolvedAssetsName = assetsName.replace('${latest}', version).replace('${version}', version);
39
+ resolvedAssetsName = assetsName.replace('${latest}', resolvedVersion).replace('${version}', resolvedVersion);
37
40
  }
38
41
  if (assetName) {
39
- resolvedAssetName = assetName.replace('${latest}', version).replace('${version}', version);
42
+ resolvedAssetName = assetName.replace('${latest}', resolvedVersion).replace('${version}', resolvedVersion);
40
43
  }
41
- Logger.debug(`Downloading ${requirement.name} from GitHub release ${repository} version ${version}`);
42
- Logger.debug(`ResolvedAssetsName} ${resolvedAssetName}; ResolvedAsetName} ${resolvedAssetName}`);
43
- const pattern = resolvedAssetsName ? resolvedAssetsName : resolvedAssetName;
44
- Logger.debug(`Resolved pattern: ${pattern}`);
45
- if (!pattern) {
46
- throw new Error('Either assetsName or assetName must be specified for GitHub release downloads');
44
+ // Validate zip requirement for isFolder
45
+ const pattern = resolvedAssetsName || resolvedAssetName;
46
+ if (isFolder && (!resolvedAssetsName || !resolvedAssetsName.endsWith('.zip'))) {
47
+ throw new Error('When isFolder is true, assetsName must be provided and end with .zip');
47
48
  }
48
49
  // Download the release asset
49
50
  const downloadPath = path.join(downloadsDir, path.basename(pattern));
50
51
  if (!await fileExists(downloadPath)) {
51
- await execAsync(`gh release download ${tagWithVPrefix ? `v${version}` : version} --repo ${repository} --pattern "${pattern}" -O "${downloadPath}"`);
52
+ await execAsync(`gh release download ${tagWithVPrefix ? `v${resolvedVersion}` : resolvedVersion} --repo ${repo} --pattern "${pattern}" -O "${downloadPath}"`);
52
53
  }
53
- // Handle zip file extraction if the downloaded file is a zip
54
- if (downloadPath.endsWith('.zip')) {
54
+ // Handle zip extraction if needed
55
+ if (isFolder && downloadPath.endsWith('.zip')) {
55
56
  const extractDir = path.join(downloadsDir, path.basename(pattern, '.zip'));
56
57
  await fs.mkdir(extractDir, { recursive: true });
57
- // Extract the zip file
58
58
  await extractZipFile(downloadPath, { dir: extractDir });
59
- let assetPath = '';
60
59
  // If resolvedAssetName is specified, look for it in the extracted directory
61
60
  if (resolvedAssetName) {
62
- assetPath = path.join(extractDir, resolvedAssetName);
61
+ const assetPath = path.join(extractDir, resolvedAssetName);
63
62
  try {
64
63
  await fs.access(assetPath);
65
- return { resolvedVersion: version, resolvedPath: assetPath };
64
+ return { version: resolvedVersion, downloadPath: assetPath };
66
65
  }
67
66
  catch (error) {
68
67
  throw new Error(`Asset ${resolvedAssetName} not found in extracted directory ${extractDir}`);
69
68
  }
70
69
  }
71
- else {
72
- assetPath = path.join(extractDir, path.basename(pattern, '.zip') + '.tgz');
73
- }
74
- // If no specific asset is required, return the extraction directory
75
- return { resolvedVersion: version, resolvedPath: extractDir };
70
+ return { version: resolvedVersion, downloadPath: extractDir };
71
+ }
72
+ return { version: resolvedVersion, downloadPath };
73
+ }
74
+ /**
75
+ * Helper to handle GitHub release downloads
76
+ * @param requirement The requirement configuration
77
+ * @param registry The GitHub release registry configuration
78
+ * @returns The path to the downloaded file
79
+ */
80
+ export async function handleGitHubRelease(requirement, registry) {
81
+ if (!registry) {
82
+ throw new Error('GitHub release registry configuration is required');
83
+ }
84
+ const { repository, assetsName, assetName } = registry;
85
+ if (!repository) {
86
+ throw new Error('GitHub repository is required for GitHub release downloads');
76
87
  }
77
- return { resolvedVersion: version, resolvedPath: downloadPath };
88
+ const isZipAsset = assetsName?.endsWith('.zip') || false;
89
+ const result = await downloadGithubRelease(repository, requirement.version, assetsName, assetName, isZipAsset);
90
+ return {
91
+ resolvedVersion: result.version,
92
+ resolvedPath: result.downloadPath
93
+ };
78
94
  }
79
95
  async function fileExists(filePath) {
80
96
  try {