imcp 0.1.1 → 0.1.2

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 (111) hide show
  1. package/dist/cli/index.js +1 -45
  2. package/dist/core/installers/clients/BaseClientInstaller.d.ts +1 -5
  3. package/dist/core/installers/clients/BaseClientInstaller.js +40 -38
  4. package/dist/core/installers/clients/ClientInstaller.d.ts +9 -9
  5. package/dist/core/installers/clients/ClientInstaller.js +105 -99
  6. package/dist/core/installers/requirements/BaseInstaller.d.ts +9 -1
  7. package/dist/core/installers/requirements/CommandInstaller.d.ts +9 -1
  8. package/dist/core/installers/requirements/CommandInstaller.js +46 -12
  9. package/dist/core/installers/requirements/GeneralInstaller.d.ts +11 -1
  10. package/dist/core/installers/requirements/GeneralInstaller.js +46 -10
  11. package/dist/core/installers/requirements/InstallerFactory.d.ts +3 -1
  12. package/dist/core/installers/requirements/InstallerFactory.js +3 -2
  13. package/dist/core/installers/requirements/NpmInstaller.d.ts +4 -2
  14. package/dist/core/installers/requirements/NpmInstaller.js +38 -22
  15. package/dist/core/installers/requirements/PipInstaller.d.ts +3 -1
  16. package/dist/core/installers/requirements/PipInstaller.js +58 -36
  17. package/dist/core/installers/requirements/RequirementInstaller.d.ts +4 -1
  18. package/dist/core/loaders/InstallOperationManager.d.ts +115 -0
  19. package/dist/core/loaders/InstallOperationManager.js +311 -0
  20. package/dist/core/loaders/SystemSettingsManager.d.ts +54 -0
  21. package/dist/core/loaders/SystemSettingsManager.js +257 -0
  22. package/dist/core/metadatas/recordingConstants.d.ts +44 -0
  23. package/dist/core/metadatas/recordingConstants.js +45 -0
  24. package/dist/core/metadatas/types.d.ts +21 -0
  25. package/dist/core/onboard/InstallOperationManager.d.ts +23 -0
  26. package/dist/core/onboard/InstallOperationManager.js +144 -0
  27. package/dist/core/onboard/OnboardStatusManager.js +2 -1
  28. package/dist/core/validators/StdioServerValidator.js +4 -3
  29. package/dist/services/InstallationService.d.ts +2 -37
  30. package/dist/services/InstallationService.js +45 -313
  31. package/dist/services/MCPManager.d.ts +1 -1
  32. package/dist/services/MCPManager.js +4 -58
  33. package/dist/services/RequirementService.d.ts +85 -12
  34. package/dist/services/RequirementService.js +488 -49
  35. package/dist/services/ServerService.d.ts +0 -6
  36. package/dist/services/ServerService.js +0 -74
  37. package/dist/utils/adoUtils.js +6 -3
  38. package/dist/utils/logger.js +1 -1
  39. package/dist/utils/macroExpressionUtils.js +3 -25
  40. package/dist/utils/osUtils.d.ts +22 -1
  41. package/dist/utils/osUtils.js +92 -1
  42. package/dist/utils/versionUtils.d.ts +20 -1
  43. package/dist/utils/versionUtils.js +51 -4
  44. package/dist/web/public/css/modal.css +292 -1
  45. package/dist/web/public/css/serverDetails.css +14 -1
  46. package/dist/web/public/index.html +122 -20
  47. package/dist/web/public/js/flights/flights.js +1 -0
  48. package/dist/web/public/js/modal/index.js +8 -14
  49. package/dist/web/public/js/modal/installModal.js +3 -4
  50. package/dist/web/public/js/modal/installation.js +122 -137
  51. package/dist/web/public/js/modal/loadingModal.js +155 -25
  52. package/dist/web/public/js/modal/messageQueue.js +45 -101
  53. package/dist/web/public/js/modal/modalSetup.js +125 -43
  54. package/dist/web/public/js/modal/modalUtils.js +0 -12
  55. package/dist/web/public/js/modal.js +23 -10
  56. package/dist/web/public/js/onboard/publishHandler.js +22 -20
  57. package/dist/web/public/js/serverCategoryDetails.js +60 -11
  58. package/dist/web/public/js/serverCategoryList.js +2 -2
  59. package/dist/web/public/js/settings.js +314 -0
  60. package/dist/web/public/settings.html +135 -0
  61. package/dist/web/public/styles.css +32 -0
  62. package/dist/web/server.js +82 -0
  63. package/memory-bank/activeContext.md +13 -1
  64. package/memory-bank/decisionLog.md +63 -0
  65. package/memory-bank/progress.md +30 -0
  66. package/memory-bank/systemPatterns.md +7 -0
  67. package/package.json +1 -1
  68. package/src/cli/index.ts +1 -48
  69. package/src/core/installers/clients/BaseClientInstaller.ts +64 -50
  70. package/src/core/installers/clients/ClientInstaller.ts +130 -130
  71. package/src/core/installers/requirements/BaseInstaller.ts +9 -1
  72. package/src/core/installers/requirements/CommandInstaller.ts +47 -13
  73. package/src/core/installers/requirements/GeneralInstaller.ts +48 -10
  74. package/src/core/installers/requirements/InstallerFactory.ts +4 -3
  75. package/src/core/installers/requirements/NpmInstaller.ts +90 -68
  76. package/src/core/installers/requirements/PipInstaller.ts +81 -55
  77. package/src/core/installers/requirements/RequirementInstaller.ts +4 -3
  78. package/src/core/loaders/InstallOperationManager.ts +367 -0
  79. package/src/core/loaders/SystemSettingsManager.ts +278 -0
  80. package/src/core/metadatas/recordingConstants.ts +62 -0
  81. package/src/core/metadatas/types.ts +23 -0
  82. package/src/core/onboard/OnboardStatusManager.ts +2 -1
  83. package/src/core/validators/StdioServerValidator.ts +4 -3
  84. package/src/services/InstallationService.ts +54 -399
  85. package/src/services/MCPManager.ts +4 -77
  86. package/src/services/RequirementService.ts +564 -67
  87. package/src/services/ServerService.ts +0 -90
  88. package/src/utils/adoUtils.ts +6 -4
  89. package/src/utils/logger.ts +1 -1
  90. package/src/utils/macroExpressionUtils.ts +4 -21
  91. package/src/utils/osUtils.ts +92 -1
  92. package/src/utils/versionUtils.ts +71 -19
  93. package/src/web/public/css/modal.css +292 -1
  94. package/src/web/public/css/serverDetails.css +14 -1
  95. package/src/web/public/index.html +122 -20
  96. package/src/web/public/js/flights/flights.js +1 -1
  97. package/src/web/public/js/modal/index.js +8 -14
  98. package/src/web/public/js/modal/installModal.js +3 -4
  99. package/src/web/public/js/modal/installation.js +122 -137
  100. package/src/web/public/js/modal/loadingModal.js +155 -25
  101. package/src/web/public/js/modal/modalSetup.js +125 -43
  102. package/src/web/public/js/modal/modalUtils.js +0 -12
  103. package/src/web/public/js/modal.js +23 -10
  104. package/src/web/public/js/onboard/publishHandler.js +22 -20
  105. package/src/web/public/js/serverCategoryDetails.js +60 -11
  106. package/src/web/public/js/serverCategoryList.js +2 -2
  107. package/src/web/public/js/settings.js +314 -0
  108. package/src/web/public/settings.html +135 -0
  109. package/src/web/public/styles.css +32 -0
  110. package/src/web/server.ts +85 -0
  111. package/src/web/public/js/modal/messageQueue.js +0 -112
package/dist/cli/index.js CHANGED
@@ -2,17 +2,7 @@
2
2
  import { Command } from 'commander';
3
3
  import { createServeCommand } from './commands/serve.js';
4
4
  import { Logger } from '../utils/logger.js';
5
- import axios from 'axios';
6
- import path from 'path';
7
- import { fileURLToPath } from 'url';
8
- import fs from 'fs';
9
- import { compareVersions } from '../utils/versionUtils.js';
10
- // Custom error interface for Commander.js errors
11
- // ANSI color codes
12
- const COLORS = {
13
- reset: '\x1b[0m',
14
- yellow: '\x1b[33m'
15
- };
5
+ import { checkForUpdates } from '../utils/versionUtils.js';
16
6
  async function main() {
17
7
  // Initialize the MCP manager
18
8
  // await mcpManager.initialize();
@@ -64,40 +54,6 @@ process.on('unhandledRejection', (error) => {
64
54
  }
65
55
  process.exit(1);
66
56
  });
67
- /**
68
- * Check if there's a newer version of the package available
69
- */
70
- async function checkForUpdates() {
71
- try {
72
- // Get the current package version
73
- const __filename = fileURLToPath(import.meta.url);
74
- const __dirname = path.dirname(__filename);
75
- const packagePath = path.resolve(__dirname, '../../package.json');
76
- const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
77
- const currentVersion = packageJson.name && packageJson.version ? packageJson.version : '0.0.0';
78
- const packageName = packageJson.name || 'imcp';
79
- try {
80
- // Get the latest version from npm registry (only for published packages)
81
- const npmResponse = await axios.get(`https://registry.npmjs.org/${packageName}`);
82
- if (npmResponse.data && npmResponse.data['dist-tags'] && npmResponse.data['dist-tags'].latest) {
83
- const latestVersion = npmResponse.data['dist-tags'].latest;
84
- // Compare versions properly to ensure we're only notifying for newer versions
85
- if (latestVersion && compareVersions(latestVersion, currentVersion) > 0) {
86
- console.log(`${COLORS.yellow}Update available for ${packageName}: ${currentVersion} → ${latestVersion}${COLORS.reset}`);
87
- console.log(`${COLORS.yellow}Run \`npm install -g ${packageName}@latest\` to update${COLORS.reset}`);
88
- }
89
- }
90
- }
91
- catch (npmError) {
92
- // Log the npm error
93
- Logger.debug(`Failed to check npm registry: ${npmError instanceof Error ? npmError.message : String(npmError)}`);
94
- }
95
- }
96
- catch (error) {
97
- // Silently fail - don't interrupt the command if update check fails
98
- Logger.debug(`Failed to check for updates: ${error instanceof Error ? error.message : String(error)}`);
99
- }
100
- }
101
57
  // Start the CLI
102
58
  main().catch((error) => {
103
59
  if (error instanceof Error) {
@@ -40,10 +40,6 @@ export declare abstract class BaseClientInstaller {
40
40
  * Override in child classes to provide custom SSE configuration
41
41
  */
42
42
  protected handleSseMode(settings: any, serverName: string, installConfig: any): void;
43
- /**
44
- * Get the NPM path on Windows
45
- */
46
- private getNpmPath;
47
43
  /**
48
44
  * Checks if VS Code or VS Code Insiders is installed and installs the client extension.
49
45
  * @param operationId The operation ID for tracking.
@@ -66,7 +62,7 @@ export declare abstract class BaseClientInstaller {
66
62
  * @param serverConfig Server configuration
67
63
  * @param options Installation options including environment variables and arguments
68
64
  */
69
- install(serverConfig: McpConfig, options: ServerInstallOptions): Promise<OperationStatus>;
65
+ install(serverConfig: McpConfig, options: ServerInstallOptions, categoryName?: string): Promise<OperationStatus>;
70
66
  /**
71
67
  * Abstract method that must be implemented by client-specific installers
72
68
  */
@@ -1,10 +1,12 @@
1
1
  import { Logger } from '../../../utils/logger.js';
2
2
  import { exec } from 'child_process';
3
3
  import { promisify } from 'util';
4
- import { isCommandAvailable } from '../../../utils/osUtils.js';
4
+ import { isCommandAvailable, getNpmExecutablePath } from '../../../utils/osUtils.js';
5
5
  import { ExtensionInstaller } from './ExtensionInstaller.js';
6
6
  import { SUPPORTED_CLIENTS } from '../../metadatas/constants.js';
7
7
  import { MACRO_EXPRESSIONS, MacroResolverFunctions } from '../../../utils/macroExpressionUtils.js';
8
+ import { InstallOperationManager } from '../../loaders/InstallOperationManager.js';
9
+ import * as RecordingConstants from '../../metadatas/recordingConstants.js';
8
10
  const execAsync = promisify(exec);
9
11
  /**
10
12
  * Base class for client installers with shared functionality
@@ -96,7 +98,7 @@ export class BaseClientInstaller {
96
98
  */
97
99
  async handleWindowsNpx(config) {
98
100
  if (process.platform === 'win32' && config.command === 'npx') {
99
- const npmPath = await this.getNpmPath();
101
+ const npmPath = await getNpmExecutablePath();
100
102
  return {
101
103
  ...config,
102
104
  command: 'cmd',
@@ -146,19 +148,6 @@ export class BaseClientInstaller {
146
148
  url: installConfig.url
147
149
  };
148
150
  }
149
- /**
150
- * Get the NPM path on Windows
151
- */
152
- async getNpmPath() {
153
- try {
154
- const { stdout } = await execAsync('powershell -Command "get-command npm | Select-Object -ExpandProperty Source"');
155
- return stdout.trim().replace(/\\npm\.cmd$/, '');
156
- }
157
- catch (error) {
158
- Logger.error('Error getting npm path:', error);
159
- return 'C:\\Program Files\\nodejs';
160
- }
161
- }
162
151
  /**
163
152
  * Checks if VS Code or VS Code Insiders is installed and installs the client extension.
164
153
  * @param operationId The operation ID for tracking.
@@ -238,44 +227,57 @@ export class BaseClientInstaller {
238
227
  * @param serverConfig Server configuration
239
228
  * @param options Installation options including environment variables and arguments
240
229
  */
241
- async install(serverConfig, options) {
230
+ async install(serverConfig, options, categoryName) {
242
231
  const operationId = this.generateOperationId();
243
- try {
244
- const vsCodeCheckResult = await this.checkVSCodeAndInstallExtension(operationId);
245
- if (vsCodeCheckResult) {
246
- return vsCodeCheckResult;
247
- }
248
- const installConfig = await this.setupInstallConfig(serverConfig, options);
232
+ const recorder = InstallOperationManager.getInstance(categoryName || serverConfig.name, serverConfig.name);
233
+ return await recorder.recording(async () => {
234
+ await recorder.recording(() => this.checkVSCodeAndInstallExtension(operationId), {
235
+ stepName: RecordingConstants.STEP_CHECK_VSCODE_AND_INSTALL_EXTENSION,
236
+ onResult: (result) => result?.status !== 'failed'
237
+ });
238
+ const installConfig = await recorder.recording(() => this.setupInstallConfig(serverConfig, options), {
239
+ stepName: RecordingConstants.STEP_SETUP_INSTALLATION_CONFIG
240
+ });
249
241
  if (serverConfig.mode) {
250
242
  installConfig.mode = serverConfig.mode;
251
243
  }
252
- // Update VS Code settings
253
- const results = await this.updateVSCodeSettings(serverConfig.name, installConfig);
244
+ const results = await recorder.recording(() => this.updateVSCodeSettings(serverConfig.name, installConfig), {
245
+ stepName: RecordingConstants.STEP_UPDATE_VSCODE_SETTINGS,
246
+ onResult: (result) => result?.some(r => r.success)
247
+ });
254
248
  // Determine overall success
255
249
  const anySuccess = results.some(r => r.success);
256
250
  const successPaths = results.filter(r => r.success).map(r => r.path);
257
251
  const errors = results.filter(r => !r.success).map(r => r.error);
252
+ const finalMessage = anySuccess
253
+ ? `Successfully installed ${this.clientName} client. Updated settings in: ${successPaths.join(', ')}`
254
+ : `Failed to install ${this.clientName} client. Errors: ${errors.join('; ')}`;
258
255
  return {
259
256
  status: anySuccess ? 'completed' : 'failed',
260
257
  type: 'install',
261
258
  target: 'server',
262
- message: anySuccess
263
- ? `Successfully installed ${this.clientName} client. Updated settings in: ${successPaths.join(', ')}`
264
- : `Failed to install ${this.clientName} client. Errors: ${errors.join('; ')}`,
259
+ message: finalMessage,
265
260
  operationId,
266
261
  error: anySuccess ? undefined : errors.join('; ')
267
262
  };
268
- }
269
- catch (error) {
270
- return {
271
- status: 'failed',
272
- type: 'install',
273
- target: 'server',
274
- message: `Unexpected error installing ${this.clientName} client: ${error instanceof Error ? error.message : String(error)}`,
275
- operationId,
276
- error: error instanceof Error ? error.message : String(error)
277
- };
278
- }
263
+ }, {
264
+ stepName: RecordingConstants.STEP_INSTALLATION,
265
+ onResult: (result) => result?.status !== 'failed',
266
+ endMessage: (result) => result?.message,
267
+ onError: (error) => {
268
+ const errorMsg = `Unexpected error installing ${this.clientName} client: ${error instanceof Error ? error.message : String(error)}`;
269
+ return {
270
+ result: {
271
+ status: 'failed',
272
+ type: 'install',
273
+ target: 'server',
274
+ message: errorMsg,
275
+ operationId,
276
+ error: error instanceof Error ? error.message : String(error)
277
+ }, message: errorMsg
278
+ };
279
+ }
280
+ });
279
281
  }
280
282
  }
281
283
  //# sourceMappingURL=BaseClientInstaller.js.map
@@ -10,21 +10,21 @@ export declare class ClientInstaller {
10
10
  private configProvider;
11
11
  constructor(categoryName: string, serverName: string, clients: string[]);
12
12
  /**
13
- * Generate a unique operation ID for tracking installations
14
- */
15
- private generateOperationId;
16
- /**
17
- * Check if server requirements are ready
18
- * Waits for requirements to be ready with timeout
13
+ * Install all specified clients
19
14
  */
20
- private checkRequirements;
15
+ install(options: ServerInstallOptions): Promise<ServerOperationResult>;
21
16
  /**
22
17
  * Install client with requirements checking
23
18
  */
24
19
  private installClient;
25
20
  private processInstallation;
26
21
  /**
27
- * Install all specified clients
22
+ * Generate a unique operation ID for tracking installations
23
+ */
24
+ private generateOperationId;
25
+ /**
26
+ * Check if server requirements are ready
27
+ * Waits for requirements to be ready with timeout
28
28
  */
29
- install(options: ServerInstallOptions): Promise<ServerOperationResult>;
29
+ private checkRequirements;
30
30
  }
@@ -1,5 +1,7 @@
1
1
  import { ConfigurationProvider } from '../../loaders/ConfigurationProvider.js';
2
+ import { Logger } from '../../../utils/logger.js';
2
3
  import { ClientInstallerFactory } from './ClientInstallerFactory.js';
4
+ import { InstallOperationManager } from '../../loaders/InstallOperationManager.js';
3
5
  /**
4
6
  * Main client installer class that orchestrates client installation process
5
7
  * Handles requirements checking and delegates to specific client installers
@@ -16,43 +18,24 @@ export class ClientInstaller {
16
18
  this.configProvider = ConfigurationProvider.getInstance();
17
19
  }
18
20
  /**
19
- * Generate a unique operation ID for tracking installations
20
- */
21
- generateOperationId() {
22
- return `install-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
23
- }
24
- /**
25
- * Check if server requirements are ready
26
- * Waits for requirements to be ready with timeout
21
+ * Install all specified clients
27
22
  */
28
- async checkRequirements(operationId, clientName, options) {
29
- let requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
30
- if (!requirementsReady) {
31
- const pendingStatus = {
32
- status: 'pending',
33
- type: 'install',
34
- target: 'server',
35
- message: `Waiting for requirements to be ready for client: ${clientName}`,
36
- operationId
37
- };
38
- await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, pendingStatus);
39
- // Set up periodic checking with timeout
40
- const startTime = Date.now();
41
- const timeoutMs = 5 * 60 * 1000; // 5 minutes
42
- const intervalMs = 5 * 1000; // 5 seconds
43
- while (!requirementsReady && (Date.now() - startTime) < timeoutMs) {
44
- await new Promise(resolve => setTimeout(resolve, intervalMs));
45
- requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
46
- }
47
- }
48
- var requirementsStatus = await this.configProvider.GetServerRequirementStatus(this.categoryName, this.serverName);
49
- // Find first non-empty npmPath from requirements status
50
- const npmPathRequirement = requirementsStatus.find(status => status.npmPath && status.npmPath.length > 0);
51
- if (npmPathRequirement && npmPathRequirement.npmPath) {
52
- options.settings = options.settings || {};
53
- options.settings.npmPath = npmPathRequirement.npmPath;
54
- }
55
- return requirementsReady;
23
+ async install(options) {
24
+ const initialStatuses = [];
25
+ // Start installation for each client asynchronously
26
+ const installPromises = this.clients.map(async (clientName) => {
27
+ const status = await this.installClient(clientName, options);
28
+ initialStatuses.push(status);
29
+ return status;
30
+ });
31
+ // Wait for all installations to complete
32
+ await Promise.all(installPromises);
33
+ // Return result
34
+ return {
35
+ success: true,
36
+ message: 'Client installations completed',
37
+ status: initialStatuses
38
+ };
56
39
  }
57
40
  /**
58
41
  * Install client with requirements checking
@@ -77,82 +60,105 @@ export class ClientInstaller {
77
60
  message: `Initializing installation for client: ${clientName}`,
78
61
  operationId
79
62
  };
80
- this.processInstallation(clientName, operationId, options);
63
+ // Async installation process
64
+ this.processInstallation(clientName, operationId, options)
65
+ .then((status) => {
66
+ if (status.status === 'completed' || status.status === 'failed') {
67
+ InstallOperationManager
68
+ .getInstance(this.categoryName, this.serverName)
69
+ .markOverallStatus(status.status);
70
+ }
71
+ this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, status);
72
+ })
73
+ .catch((error) => {
74
+ this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, {
75
+ status: 'failed',
76
+ type: 'install',
77
+ target: 'server',
78
+ message: `Error installing client ${clientName}: ${error instanceof Error ? error.message : String(error)}`,
79
+ operationId,
80
+ error: error instanceof Error ? error.message : String(error)
81
+ });
82
+ Logger.error(`Error installing client ${clientName}: ${error instanceof Error ? error.message : String(error)}`);
83
+ InstallOperationManager
84
+ .getInstance(this.categoryName, this.serverName)
85
+ .markOverallStatus('failed', error);
86
+ });
81
87
  // Update server status with initial client installation status
82
88
  await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, initialStatus);
83
89
  return initialStatus;
84
90
  }
85
91
  async processInstallation(clientName, operationId, options) {
86
- try {
87
- // Check requirements
88
- const requirementsReady = await this.checkRequirements(operationId, clientName, options);
89
- if (!requirementsReady) {
90
- const failedStatus = {
91
- status: 'failed',
92
- type: 'install',
93
- target: 'server',
94
- message: `Requirements not ready for client: ${clientName} after timeout`,
95
- operationId
96
- };
97
- await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, failedStatus);
98
- return;
99
- }
100
- // Create client-specific installer
101
- const installer = ClientInstallerFactory.getInstaller(clientName);
102
- if (!installer) {
103
- throw new Error(`Failed to create installer for client: ${clientName}`);
104
- }
105
- const serverConfig = await this.configProvider.getServerMcpConfig(this.categoryName, this.serverName);
106
- if (!serverConfig) {
107
- throw new Error(`Server configuration not found for category: ${this.categoryName}, server: ${this.serverName}`);
108
- }
109
- // If we've reached here, requirements are ready - update status to in-progress
110
- const inProgressStatus = {
111
- status: 'in-progress',
112
- type: 'install',
113
- target: 'server',
114
- message: `Installing client: ${clientName}`,
115
- operationId: operationId
116
- };
117
- await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, inProgressStatus);
118
- // Install client
119
- const status = await installer.install(serverConfig, options);
120
- await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, status);
121
- if (status.status === 'completed') {
122
- await this.configProvider.reloadClientMCPSettings();
123
- }
124
- }
125
- catch (error) {
126
- const errorStatus = {
92
+ const requirementsReady = await this.checkRequirements(operationId, clientName, options);
93
+ if (!requirementsReady) {
94
+ const failedStatus = {
127
95
  status: 'failed',
128
96
  type: 'install',
129
97
  target: 'server',
130
- message: `Error installing client ${clientName}: ${error instanceof Error ? error.message : String(error)}`,
131
- operationId,
132
- error: error instanceof Error ? error.message : String(error)
98
+ message: `Requirements not ready for client: ${clientName} after timeout`,
99
+ operationId
133
100
  };
134
- await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, errorStatus);
101
+ return failedStatus;
102
+ }
103
+ // Create client-specific installer
104
+ const installer = ClientInstallerFactory.getInstaller(clientName);
105
+ if (!installer) {
106
+ throw new Error(`Failed to create installer for client: ${clientName}`);
135
107
  }
108
+ const serverConfig = await this.configProvider.getServerMcpConfig(this.categoryName, this.serverName);
109
+ if (!serverConfig) {
110
+ throw new Error(`Server configuration not found for category: ${this.categoryName}, server: ${this.serverName}`);
111
+ }
112
+ // If we've reached here, requirements are ready - update status to in-progress
113
+ const inProgressStatus = {
114
+ status: 'in-progress',
115
+ type: 'install',
116
+ target: 'server',
117
+ message: `Installing client: ${clientName}`,
118
+ operationId: operationId
119
+ };
120
+ await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, inProgressStatus);
121
+ // Install client
122
+ return await installer.install(serverConfig, options, this.categoryName);
136
123
  }
137
124
  /**
138
- * Install all specified clients
125
+ * Generate a unique operation ID for tracking installations
126
+ */
127
+ generateOperationId() {
128
+ return `install-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
129
+ }
130
+ /**
131
+ * Check if server requirements are ready
132
+ * Waits for requirements to be ready with timeout
139
133
  */
140
- async install(options) {
141
- const initialStatuses = [];
142
- // Start installation for each client asynchronously
143
- const installPromises = this.clients.map(async (clientName) => {
144
- const status = await this.installClient(clientName, options);
145
- initialStatuses.push(status);
146
- return status;
147
- });
148
- // Wait for all installations to complete
149
- await Promise.all(installPromises);
150
- // Return result
151
- return {
152
- success: true,
153
- message: 'Client installations completed',
154
- status: initialStatuses
155
- };
134
+ async checkRequirements(operationId, clientName, options) {
135
+ let requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
136
+ if (!requirementsReady) {
137
+ const pendingStatus = {
138
+ status: 'pending',
139
+ type: 'install',
140
+ target: 'server',
141
+ message: `Waiting for requirements to be ready for client: ${clientName}`,
142
+ operationId
143
+ };
144
+ await this.configProvider.updateServerOperationStatus(this.categoryName, this.serverName, clientName, pendingStatus);
145
+ // Set up periodic checking with timeout
146
+ const startTime = Date.now();
147
+ const timeoutMs = 5 * 60 * 1000; // 5 minutes
148
+ const intervalMs = 5 * 1000; // 5 seconds
149
+ while (!requirementsReady && (Date.now() - startTime) < timeoutMs) {
150
+ await new Promise(resolve => setTimeout(resolve, intervalMs));
151
+ requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
152
+ }
153
+ }
154
+ var requirementsStatus = await this.configProvider.GetServerRequirementStatus(this.categoryName, this.serverName);
155
+ // Find first non-empty npmPath from requirements status
156
+ const npmPathRequirement = requirementsStatus.find(status => status.npmPath && status.npmPath.length > 0);
157
+ if (npmPathRequirement && npmPathRequirement.npmPath) {
158
+ options.settings = options.settings || {};
159
+ options.settings.npmPath = npmPathRequirement.npmPath;
160
+ }
161
+ return requirementsReady;
156
162
  }
157
163
  }
158
164
  //# sourceMappingURL=ClientInstaller.js.map
@@ -1,5 +1,6 @@
1
1
  import { RequirementConfig, RequirementStatus, ServerInstallOptions } from '../../metadatas/types.js';
2
2
  import { RequirementInstaller } from './RequirementInstaller.js';
3
+ import { InstallOperationManager } from '../../loaders/InstallOperationManager.js';
3
4
  /**
4
5
  * Abstract base class with common functionality for all requirement installers
5
6
  */
@@ -15,7 +16,14 @@ export declare abstract class BaseInstaller implements RequirementInstaller {
15
16
  }>);
16
17
  abstract canHandle(requirement: RequirementConfig): boolean;
17
18
  abstract supportCheckUpdates(): boolean;
18
- abstract install(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
19
+ /**
20
+ * Install the requirement
21
+ * @param requirement The requirement to install
22
+ * @param options Optional install options
23
+ * @param recorder Optional InstallOperationManager for recording steps
24
+ * @returns The status of the installation
25
+ */
26
+ abstract install(requirement: RequirementConfig, recorder: InstallOperationManager, options?: ServerInstallOptions): Promise<RequirementStatus>;
19
27
  abstract checkInstallation(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
20
28
  /**
21
29
  * Get the latest version available for the requirement.
@@ -1,5 +1,6 @@
1
1
  import { RequirementConfig, RequirementStatus, ServerInstallOptions } from '../../metadatas/types.js';
2
2
  import { BaseInstaller } from './BaseInstaller.js';
3
+ import { InstallOperationManager } from '../../loaders/InstallOperationManager.js';
3
4
  /**
4
5
  * Installer implementation for command-line tools
5
6
  */
@@ -38,5 +39,12 @@ export declare class CommandInstaller extends BaseInstaller {
38
39
  * @param requirement The requirement to install
39
40
  * @returns The status of the installation
40
41
  */
41
- install(requirement: RequirementConfig): Promise<RequirementStatus>;
42
+ /**
43
+ * Install the command
44
+ * @param requirement The requirement to install
45
+ * @param options Optional install options
46
+ * @param recorder Optional InstallOperationManager for recording steps
47
+ * @returns The status of the installation
48
+ */
49
+ install(requirement: RequirementConfig, recorder: InstallOperationManager, options?: ServerInstallOptions): Promise<RequirementStatus>;
42
50
  }
@@ -2,6 +2,7 @@ import { OSType } from '../../metadatas/types.js';
2
2
  import { BaseInstaller } from './BaseInstaller.js';
3
3
  import { getOSType, refreshPathEnv } from '../../../utils/osUtils.js';
4
4
  import { Logger } from '../../../utils/logger.js';
5
+ import * as RecordingConstants from '../../metadatas/recordingConstants.js';
5
6
  /**
6
7
  * Installer implementation for command-line tools
7
8
  */
@@ -122,8 +123,15 @@ export class CommandInstaller extends BaseInstaller {
122
123
  * @param requirement The requirement to install
123
124
  * @returns The status of the installation
124
125
  */
125
- async install(requirement) {
126
- try {
126
+ /**
127
+ * Install the command
128
+ * @param requirement The requirement to install
129
+ * @param options Optional install options
130
+ * @param recorder Optional InstallOperationManager for recording steps
131
+ * @returns The status of the installation
132
+ */
133
+ async install(requirement, recorder, options) {
134
+ const doInstall = async () => {
127
135
  const status = await this.checkInstallation(requirement);
128
136
  if (status.installed) {
129
137
  return status;
@@ -132,20 +140,20 @@ export class CommandInstaller extends BaseInstaller {
132
140
  const osType = getOSType();
133
141
  let installCommand;
134
142
  if (osType === OSType.Windows) {
135
- // Windows installation using winget
136
143
  installCommand = `winget install --id ${packageId}`;
137
144
  if (requirement.version && requirement.version !== 'latest') {
138
145
  installCommand += ` --version ${requirement.version}`;
139
146
  }
140
147
  }
141
148
  else if (osType === OSType.MacOS) {
142
- // macOS installation using Homebrew
143
149
  installCommand = `brew install ${packageId}`;
144
150
  if (requirement.version && requirement.version !== 'latest') {
145
151
  installCommand += `@${requirement.version}`;
146
152
  }
147
153
  }
148
154
  else {
155
+ if (recorder)
156
+ await recorder.recordStep('CommandInstaller:UnsupportedOS', 'failed', `Unsupported OS for ${requirement.name}`);
149
157
  throw new Error(`Unsupported operating system for installing ${requirement.name}`);
150
158
  }
151
159
  // Execute the installation command
@@ -165,15 +173,41 @@ export class CommandInstaller extends BaseInstaller {
165
173
  version: updatedStatus.version || requirement.version,
166
174
  inProgress: false
167
175
  };
176
+ };
177
+ if (recorder) {
178
+ return recorder.recording(doInstall, {
179
+ stepName: RecordingConstants.STEP_COMMAND_INSTALLER_INSTALL,
180
+ inProgressMessage: `Installing command: ${requirement.name}`,
181
+ endMessage: (result) => result.installed
182
+ ? `Install completed for ${requirement.name}`
183
+ : `Install failed for ${requirement.name}`,
184
+ onError: (error) => {
185
+ return {
186
+ result: {
187
+ name: requirement.name,
188
+ type: 'command',
189
+ installed: false,
190
+ error: error instanceof Error ? error.message : String(error),
191
+ inProgress: false,
192
+ },
193
+ message: error instanceof Error ? error.message : String(error),
194
+ };
195
+ },
196
+ });
168
197
  }
169
- catch (error) {
170
- return {
171
- name: requirement.name,
172
- type: 'command',
173
- installed: false,
174
- error: error instanceof Error ? error.message : String(error),
175
- inProgress: false
176
- };
198
+ else {
199
+ try {
200
+ return await doInstall();
201
+ }
202
+ catch (error) {
203
+ return {
204
+ name: requirement.name,
205
+ type: 'command',
206
+ installed: false,
207
+ error: error instanceof Error ? error.message : String(error),
208
+ inProgress: false,
209
+ };
210
+ }
177
211
  }
178
212
  }
179
213
  }
@@ -1,5 +1,6 @@
1
1
  import { RequirementConfig, RequirementStatus, ServerInstallOptions } from '../../metadatas/types.js';
2
2
  import { BaseInstaller } from './BaseInstaller.js';
3
+ import { InstallOperationManager } from '../../loaders/InstallOperationManager.js';
3
4
  /**
4
5
  * Installer implementation for general requirements (type 'other')
5
6
  * This installer handles requirements that don't fit into specific package manager categories
@@ -34,5 +35,14 @@ export declare class GeneralInstaller extends BaseInstaller {
34
35
  * @param requirement The requirement to install
35
36
  * @returns The status of the installation, including the install path in updateInfo
36
37
  */
37
- install(requirement: RequirementConfig): Promise<RequirementStatus>;
38
+ /**
39
+ * Install the general requirement
40
+ * For type 'other', this doesn't actually install anything, but downloads
41
+ * or locates the asset and returns the path for the caller to use
42
+ * @param requirement The requirement to install
43
+ * @param options Optional install options
44
+ * @param recorder Optional InstallOperationManager for recording steps
45
+ * @returns The status of the installation, including the install path in updateInfo
46
+ */
47
+ install(requirement: RequirementConfig, recorder: InstallOperationManager, options?: ServerInstallOptions): Promise<RequirementStatus>;
38
48
  }