imcp 0.0.19 → 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 (183) hide show
  1. package/.roo/rules-code/rules.md +88 -0
  2. package/dist/cli/index.js +1 -45
  3. package/dist/core/installers/clients/BaseClientInstaller.d.ts +1 -5
  4. package/dist/core/installers/clients/BaseClientInstaller.js +40 -38
  5. package/dist/core/installers/clients/ClientInstaller.d.ts +9 -9
  6. package/dist/core/installers/clients/ClientInstaller.js +105 -99
  7. package/dist/core/installers/requirements/BaseInstaller.d.ts +9 -1
  8. package/dist/core/installers/requirements/CommandInstaller.d.ts +9 -1
  9. package/dist/core/installers/requirements/CommandInstaller.js +46 -12
  10. package/dist/core/installers/requirements/GeneralInstaller.d.ts +11 -1
  11. package/dist/core/installers/requirements/GeneralInstaller.js +46 -10
  12. package/dist/core/installers/requirements/InstallerFactory.d.ts +3 -1
  13. package/dist/core/installers/requirements/InstallerFactory.js +3 -2
  14. package/dist/core/installers/requirements/NpmInstaller.d.ts +4 -2
  15. package/dist/core/installers/requirements/NpmInstaller.js +38 -22
  16. package/dist/core/installers/requirements/PipInstaller.d.ts +3 -1
  17. package/dist/core/installers/requirements/PipInstaller.js +58 -36
  18. package/dist/core/installers/requirements/RequirementInstaller.d.ts +4 -1
  19. package/dist/core/loaders/InstallOperationManager.d.ts +115 -0
  20. package/dist/core/loaders/InstallOperationManager.js +311 -0
  21. package/dist/core/loaders/SystemSettingsManager.d.ts +54 -0
  22. package/dist/core/loaders/SystemSettingsManager.js +257 -0
  23. package/dist/core/metadatas/constants.d.ts +7 -0
  24. package/dist/core/metadatas/constants.js +7 -0
  25. package/dist/core/metadatas/recordingConstants.d.ts +44 -0
  26. package/dist/core/metadatas/recordingConstants.js +45 -0
  27. package/dist/core/metadatas/types.d.ts +21 -0
  28. package/dist/core/onboard/FeedOnboardService.d.ts +7 -3
  29. package/dist/core/onboard/FeedOnboardService.js +52 -5
  30. package/dist/core/onboard/InstallOperationManager.d.ts +23 -0
  31. package/dist/core/onboard/InstallOperationManager.js +144 -0
  32. package/dist/core/onboard/OnboardStatusManager.js +2 -1
  33. package/dist/core/validators/StdioServerValidator.js +4 -3
  34. package/dist/services/InstallationService.d.ts +2 -37
  35. package/dist/services/InstallationService.js +45 -313
  36. package/dist/services/MCPManager.d.ts +1 -1
  37. package/dist/services/MCPManager.js +53 -47
  38. package/dist/services/RequirementService.d.ts +85 -12
  39. package/dist/services/RequirementService.js +488 -49
  40. package/dist/services/ServerService.d.ts +0 -6
  41. package/dist/services/ServerService.js +0 -74
  42. package/dist/services/TelemetryService.d.ts +15 -0
  43. package/dist/services/TelemetryService.js +54 -0
  44. package/dist/utils/adoUtils.js +6 -3
  45. package/dist/utils/githubAuth.js +65 -0
  46. package/dist/utils/logger.d.ts +16 -0
  47. package/dist/utils/logger.js +78 -1
  48. package/dist/utils/macroExpressionUtils.js +3 -25
  49. package/dist/utils/osUtils.d.ts +22 -1
  50. package/dist/utils/osUtils.js +92 -1
  51. package/dist/utils/versionUtils.d.ts +20 -0
  52. package/dist/utils/versionUtils.js +76 -0
  53. package/dist/web/public/css/modal.css +292 -1
  54. package/dist/web/public/css/serverCategoryList.css +120 -0
  55. package/dist/web/public/css/serverDetails.css +14 -1
  56. package/dist/web/public/index.html +126 -21
  57. package/dist/web/public/js/flights/flights.js +1 -1
  58. package/dist/web/public/js/modal/index.js +8 -14
  59. package/dist/web/public/js/modal/installModal.js +3 -4
  60. package/dist/web/public/js/modal/installation.js +122 -137
  61. package/dist/web/public/js/modal/loadingModal.js +155 -25
  62. package/dist/web/public/js/modal/messageQueue.js +45 -101
  63. package/dist/web/public/js/modal/modalSetup.js +125 -43
  64. package/dist/web/public/js/modal/modalUtils.js +0 -12
  65. package/dist/web/public/js/modal.js +23 -10
  66. package/dist/web/public/js/onboard/formProcessor.js +18 -11
  67. package/dist/web/public/js/onboard/publishHandler.js +35 -3
  68. package/dist/web/public/js/onboard/templates.js +5 -1
  69. package/dist/web/public/js/onboard/uiHandlers.js +266 -39
  70. package/dist/web/public/js/onboard/validationHandlers.js +71 -39
  71. package/dist/web/public/js/serverCategoryDetails.js +60 -11
  72. package/dist/web/public/js/serverCategoryList.js +93 -9
  73. package/dist/web/public/js/settings.js +314 -0
  74. package/dist/web/public/onboard.html +2 -2
  75. package/dist/web/public/settings.html +135 -0
  76. package/dist/web/public/styles.css +32 -0
  77. package/dist/web/server.js +93 -1
  78. package/{src/web/public/js/onboard → docs}/ONBOARDING_PAGE_DESIGN.md +15 -125
  79. package/docs/Telemetry.md +136 -0
  80. package/memory-bank/activeContext.md +26 -0
  81. package/memory-bank/decisionLog.md +91 -0
  82. package/memory-bank/productContext.md +41 -0
  83. package/memory-bank/progress.md +35 -0
  84. package/memory-bank/systemPatterns.md +10 -0
  85. package/package.json +2 -1
  86. package/src/cli/index.ts +1 -48
  87. package/src/core/installers/clients/BaseClientInstaller.ts +64 -50
  88. package/src/core/installers/clients/ClientInstaller.ts +130 -130
  89. package/src/core/installers/requirements/BaseInstaller.ts +9 -1
  90. package/src/core/installers/requirements/CommandInstaller.ts +47 -13
  91. package/src/core/installers/requirements/GeneralInstaller.ts +48 -10
  92. package/src/core/installers/requirements/InstallerFactory.ts +4 -3
  93. package/src/core/installers/requirements/NpmInstaller.ts +90 -68
  94. package/src/core/installers/requirements/PipInstaller.ts +81 -55
  95. package/src/core/installers/requirements/RequirementInstaller.ts +4 -3
  96. package/src/core/loaders/InstallOperationManager.ts +367 -0
  97. package/src/core/loaders/SystemSettingsManager.ts +278 -0
  98. package/src/core/metadatas/constants.ts +9 -0
  99. package/src/core/metadatas/recordingConstants.ts +62 -0
  100. package/src/core/metadatas/types.ts +23 -0
  101. package/src/core/onboard/FeedOnboardService.ts +59 -5
  102. package/src/core/onboard/OnboardStatusManager.ts +2 -1
  103. package/src/core/validators/StdioServerValidator.ts +4 -3
  104. package/src/services/InstallationService.ts +54 -399
  105. package/src/services/MCPManager.ts +61 -64
  106. package/src/services/RequirementService.ts +564 -67
  107. package/src/services/ServerService.ts +0 -90
  108. package/src/services/TelemetryService.ts +59 -0
  109. package/src/utils/adoUtils.ts +6 -4
  110. package/src/utils/githubAuth.ts +84 -1
  111. package/src/utils/logger.ts +83 -1
  112. package/src/utils/macroExpressionUtils.ts +4 -21
  113. package/src/utils/osUtils.ts +92 -1
  114. package/src/utils/versionUtils.ts +98 -13
  115. package/src/web/public/css/modal.css +292 -1
  116. package/src/web/public/css/serverCategoryList.css +120 -0
  117. package/src/web/public/css/serverDetails.css +14 -1
  118. package/src/web/public/index.html +126 -21
  119. package/src/web/public/js/flights/flights.js +1 -1
  120. package/src/web/public/js/modal/index.js +8 -14
  121. package/src/web/public/js/modal/installModal.js +3 -4
  122. package/src/web/public/js/modal/installation.js +122 -137
  123. package/src/web/public/js/modal/loadingModal.js +155 -25
  124. package/src/web/public/js/modal/modalSetup.js +125 -43
  125. package/src/web/public/js/modal/modalUtils.js +0 -12
  126. package/src/web/public/js/modal.js +23 -10
  127. package/src/web/public/js/onboard/formProcessor.js +18 -11
  128. package/src/web/public/js/onboard/publishHandler.js +35 -3
  129. package/src/web/public/js/onboard/templates.js +5 -1
  130. package/src/web/public/js/onboard/uiHandlers.js +266 -39
  131. package/src/web/public/js/onboard/validationHandlers.js +71 -39
  132. package/src/web/public/js/serverCategoryDetails.js +60 -11
  133. package/src/web/public/js/serverCategoryList.js +93 -9
  134. package/src/web/public/js/settings.js +314 -0
  135. package/src/web/public/onboard.html +2 -2
  136. package/src/web/public/settings.html +135 -0
  137. package/src/web/public/styles.css +32 -0
  138. package/src/web/server.ts +96 -1
  139. package/dist/cli/commands/start.d.ts +0 -2
  140. package/dist/cli/commands/start.js +0 -32
  141. package/dist/cli/commands/sync.d.ts +0 -2
  142. package/dist/cli/commands/sync.js +0 -17
  143. package/dist/core/ConfigurationLoader.d.ts +0 -32
  144. package/dist/core/ConfigurationLoader.js +0 -236
  145. package/dist/core/ConfigurationProvider.d.ts +0 -35
  146. package/dist/core/ConfigurationProvider.js +0 -375
  147. package/dist/core/InstallationService.d.ts +0 -50
  148. package/dist/core/InstallationService.js +0 -350
  149. package/dist/core/MCPManager.d.ts +0 -28
  150. package/dist/core/MCPManager.js +0 -188
  151. package/dist/core/RequirementService.d.ts +0 -40
  152. package/dist/core/RequirementService.js +0 -110
  153. package/dist/core/ServerSchemaLoader.d.ts +0 -11
  154. package/dist/core/ServerSchemaLoader.js +0 -43
  155. package/dist/core/ServerSchemaProvider.d.ts +0 -17
  156. package/dist/core/ServerSchemaProvider.js +0 -120
  157. package/dist/core/constants.d.ts +0 -47
  158. package/dist/core/constants.js +0 -94
  159. package/dist/core/installers/BaseInstaller.d.ts +0 -74
  160. package/dist/core/installers/BaseInstaller.js +0 -253
  161. package/dist/core/installers/ClientInstaller.d.ts +0 -23
  162. package/dist/core/installers/ClientInstaller.js +0 -564
  163. package/dist/core/installers/CommandInstaller.d.ts +0 -37
  164. package/dist/core/installers/CommandInstaller.js +0 -173
  165. package/dist/core/installers/GeneralInstaller.d.ts +0 -33
  166. package/dist/core/installers/GeneralInstaller.js +0 -85
  167. package/dist/core/installers/InstallerFactory.d.ts +0 -54
  168. package/dist/core/installers/InstallerFactory.js +0 -97
  169. package/dist/core/installers/NpmInstaller.d.ts +0 -26
  170. package/dist/core/installers/NpmInstaller.js +0 -127
  171. package/dist/core/installers/PipInstaller.d.ts +0 -28
  172. package/dist/core/installers/PipInstaller.js +0 -127
  173. package/dist/core/installers/RequirementInstaller.d.ts +0 -33
  174. package/dist/core/installers/RequirementInstaller.js +0 -3
  175. package/dist/core/types.d.ts +0 -166
  176. package/dist/core/types.js +0 -16
  177. package/dist/services/InstallRequestValidator.d.ts +0 -21
  178. package/dist/services/InstallRequestValidator.js +0 -99
  179. package/dist/web/public/js/modal/installHandler.js +0 -227
  180. package/dist/web/public/js/modal/loadingUI.js +0 -74
  181. package/dist/web/public/js/modal/modalUI.js +0 -214
  182. package/dist/web/public/js/modal/version.js +0 -20
  183. package/src/web/public/js/modal/messageQueue.js +0 -112
@@ -0,0 +1,35 @@
1
+ # Progress Log
2
+
3
+ - [YYYY-MM-DD HH:MM:SS] - Initializing Memory Bank.
4
+
5
+ - [2025-05-16 09:22:36] - Completed: Made "IMCP Server Manager" title clickable in `src/web/public/index.html`.
6
+ - [2025-05-16 13:28:10] - Completed: Refactored requirement management from `InstallationService` to `RequirementService`. Fixed `reqConfig` scoping and type fallbacks in `RequirementService.processRequirementUpdates`.
7
+
8
+ [2025-05-16 16:07:34] - Completed: Redesign installation operation status and polling mechanism.
9
+ - Added `InstallOperationDetails` type to `src/core/metadatas/types.ts`.
10
+ - Created `InstallOperationManager` in `src/core/loaders/InstallOperationManager.ts`.
11
+ - Added API endpoint for installation status in `src/web/server.ts`.
12
+ - Integrated `recordStep` in `InstallationService`, `RequirementService`, and `BaseClientInstaller`.
13
+
14
+ [2025-05-16 16:50:21] - Enhanced installation status:
15
+ - Added `overallStatus` to `InstallOperationDetails` in `src/core/metadatas/types.ts`.
16
+ - Updated `InstallOperationManager` in `src/core/loaders/InstallOperationManager.ts` to manage `overallStatus`.
17
+ - Modified frontend polling in `src/web/public/js/modal/installation.js` to use the new API `/api/categories/:categoryName/servers/:serverName/installation/status` and display detailed steps.
18
+
19
+ [2025-05-17 00:26:16] - Began refactoring InstallOperationManager to be instance-based per category/server, with new file structure for status. Memory bank updated to reflect architectural and pattern changes.
20
+
21
+ [2025-05-17 15:02:44] - Completed: Refactored `RequirementService.ts` to use instance-based `InstallOperationManager` and its `recording`/`recordingAsync` methods. This involved updating `processRequirementUpdates`, `checkAndInstallRequirements`, and `installRequirementsInBackground`.
22
+
23
+ [2025-05-18 11:33:23] - Completed: Standardized step recording names by creating `src/core/metadatas/recordingConstants.ts`. This centralizes static step names and documents dynamic patterns for installation/onboarding operations.
24
+
25
+ [2025-05-18 13:21:30] - Completed: Implemented main modal refresh logic to replace page reload.
26
+ - `loadingModal.js`: Dispatches `refreshMainModalContent` event on close.
27
+ - `installation.js`: Stores `categoryName` and `serverName` in `localStorage`.
28
+ - `modal.js`: Listens for event, retrieves context from `localStorage`, and calls `showInstallModal` with both `categoryName` and `serverName`.
29
+ - This resolves "Server configuration not found" errors and improves UX.
30
+ [2025-05-17 15:26:15] - Refactored all requirement installer classes and factory to support InstallOperationManager step recording. All install methods now accept a recorder and log critical steps.
31
+
32
+ [2025-05-19 00:14:13] - Completed: Updated settings page (`src/web/public/settings.html` and `src/web/public/js/settings.js`):
33
+ - Removed "NPM Global Package Path" display.
34
+ - Added prompt with a question icon for User Configurations, displayed on the same line as the section title.
35
+ - Implemented secret input type for keys containing "key" in User Configurations, with an eye icon button *inside* the value input field to toggle visibility (maintaining original input box sizes).
@@ -0,0 +1,10 @@
1
+ # System Patterns
2
+
3
+ - [YYYY-MM-DD HH:MM:SS] - Initializing Memory Bank.
4
+
5
+ [2025-05-18 13:21:30] - Implemented an event-driven modal refresh pattern for the installation UI.
6
+ - Pattern: Instead of page reloads, the loading modal (`loadingModal.js`) dispatches a custom DOM event (`refreshMainModalContent`) upon closing.
7
+ - Context Persistence: The initiating action (e.g., in `installation.js`) stores necessary context (like `lastSelectedCategory` and `lastSelectedServerName`) in `localStorage`.
8
+ - Event Handling: The main modal controller (`modal.js`) listens for this event, retrieves context from `localStorage`, and re-initializes/refreshes its content (`showInstallModal`) with the persisted context.
9
+ - Benefit: This decouples the loading modal from the main modal, allows for non-disruptive UI updates, and maintains user context effectively.
10
+ [2025-05-17 00:26:06] - Changed InstallOperationManager from singleton/static to instance-based, with per-category/server file storage pattern: Settings_Dir/InstallOperationStatus/CategoryName/ServerName.json. This introduces a more granular, object-oriented persistence and management pattern for install operation status.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imcp",
3
- "version": "0.0.19",
3
+ "version": "0.1.2",
4
4
  "description": "Node.js SDK for Model Context Protocol (MCP)",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -25,6 +25,7 @@
25
25
  "author": "",
26
26
  "license": "MIT",
27
27
  "dependencies": {
28
+ "applicationinsights": "^2.9.3",
28
29
  "axios": "^1.6.2",
29
30
  "commander": "^11.1.0",
30
31
  "express": "^4.18.2",
package/src/cli/index.ts CHANGED
@@ -8,19 +8,9 @@ import { createUninstallCommand } from './commands/uninstall.js';
8
8
  import { createPullCommand } from './commands/pull.js';
9
9
  import { mcpManager } from '../services/MCPManager.js';
10
10
  import { Logger } from '../utils/logger.js';
11
- import axios from 'axios';
12
- import path from 'path';
13
- import { fileURLToPath } from 'url';
14
- import fs from 'fs';
15
- import { compareVersions } from '../utils/versionUtils.js';
11
+ import { checkForUpdates } from '../utils/versionUtils.js';
16
12
 
17
13
  // Custom error interface for Commander.js errors
18
- // ANSI color codes
19
- const COLORS = {
20
- reset: '\x1b[0m',
21
- yellow: '\x1b[33m'
22
- };
23
-
24
14
  interface CommanderError extends Error {
25
15
  code?: string;
26
16
  }
@@ -81,43 +71,6 @@ process.on('unhandledRejection', (error: unknown) => {
81
71
  process.exit(1);
82
72
  });
83
73
 
84
- /**
85
- * Check if there's a newer version of the package available
86
- */
87
-
88
- async function checkForUpdates(): Promise<void> {
89
- try {
90
- // Get the current package version
91
- const __filename = fileURLToPath(import.meta.url);
92
- const __dirname = path.dirname(__filename);
93
- const packagePath = path.resolve(__dirname, '../../package.json');
94
- const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
95
- const currentVersion = packageJson.name && packageJson.version ? packageJson.version : '0.0.0';
96
- const packageName = packageJson.name || 'imcp';
97
-
98
- try {
99
- // Get the latest version from npm registry (only for published packages)
100
- const npmResponse = await axios.get(`https://registry.npmjs.org/${packageName}`);
101
-
102
- if (npmResponse.data && npmResponse.data['dist-tags'] && npmResponse.data['dist-tags'].latest) {
103
- const latestVersion = npmResponse.data['dist-tags'].latest;
104
-
105
- // Compare versions properly to ensure we're only notifying for newer versions
106
- if (latestVersion && compareVersions(latestVersion, currentVersion) > 0) {
107
- console.log(`${COLORS.yellow}Update available for ${packageName}: ${currentVersion} → ${latestVersion}${COLORS.reset}`);
108
- console.log(`${COLORS.yellow}Run \`npm install -g ${packageName}@latest\` to update${COLORS.reset}`);
109
- }
110
- }
111
- } catch (npmError) {
112
- // Log the npm error
113
- Logger.debug(`Failed to check npm registry: ${npmError instanceof Error ? npmError.message : String(npmError)}`);
114
- }
115
- } catch (error) {
116
- // Silently fail - don't interrupt the command if update check fails
117
- Logger.debug(`Failed to check for updates: ${error instanceof Error ? error.message : String(error)}`);
118
- }
119
- }
120
-
121
74
  // Start the CLI
122
75
  main().catch((error: unknown) => {
123
76
  if (error instanceof Error) {
@@ -1,7 +1,7 @@
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 {
@@ -13,6 +13,8 @@ import {
13
13
  MACRO_EXPRESSIONS,
14
14
  MacroResolverFunctions
15
15
  } from '../../../utils/macroExpressionUtils.js';
16
+ import { InstallOperationManager } from '../../loaders/InstallOperationManager.js';
17
+ import * as RecordingConstants from '../../metadatas/recordingConstants.js';
16
18
 
17
19
  const execAsync = promisify(exec);
18
20
 
@@ -123,7 +125,7 @@ export abstract class BaseClientInstaller {
123
125
  */
124
126
  protected async handleWindowsNpx(config: any): Promise<any> {
125
127
  if (process.platform === 'win32' && config.command === 'npx') {
126
- const npmPath = await this.getNpmPath();
128
+ const npmPath = await getNpmExecutablePath();
127
129
  return {
128
130
  ...config,
129
131
  command: 'cmd',
@@ -179,19 +181,6 @@ export abstract class BaseClientInstaller {
179
181
  };
180
182
  }
181
183
 
182
- /**
183
- * Get the NPM path on Windows
184
- */
185
- private async getNpmPath(): Promise<string> {
186
- try {
187
- const { stdout } = await execAsync('powershell -Command "get-command npm | Select-Object -ExpandProperty Source"');
188
- return stdout.trim().replace(/\\npm\.cmd$/, '');
189
- } catch (error) {
190
- Logger.error('Error getting npm path:', error);
191
- return 'C:\\Program Files\\nodejs';
192
- }
193
- }
194
-
195
184
  /**
196
185
  * Checks if VS Code or VS Code Insiders is installed and installs the client extension.
197
186
  * @param operationId The operation ID for tracking.
@@ -276,48 +265,73 @@ export abstract class BaseClientInstaller {
276
265
  * @param serverConfig Server configuration
277
266
  * @param options Installation options including environment variables and arguments
278
267
  */
279
- async install(serverConfig: McpConfig, options: ServerInstallOptions): Promise<OperationStatus> {
268
+ async install(serverConfig: McpConfig, options: ServerInstallOptions, categoryName?: string): Promise<OperationStatus> {
280
269
  const operationId = this.generateOperationId();
281
270
 
282
- try {
283
- const vsCodeCheckResult = await this.checkVSCodeAndInstallExtension(operationId);
284
- if (vsCodeCheckResult) {
285
- return vsCodeCheckResult;
286
- }
271
+ const recorder = InstallOperationManager.getInstance(categoryName || serverConfig.name, serverConfig.name);
272
+ return await recorder.recording(
273
+ async () => {
274
+ await recorder.recording(
275
+ () => this.checkVSCodeAndInstallExtension(operationId), {
276
+ stepName: RecordingConstants.STEP_CHECK_VSCODE_AND_INSTALL_EXTENSION,
277
+ onResult: (result) => result?.status !== 'failed'
278
+ }
279
+ )
287
280
 
288
- const installConfig = await this.setupInstallConfig(serverConfig, options);
289
- if (serverConfig.mode) {
290
- installConfig.mode = serverConfig.mode;
291
- }
281
+ const installConfig = await recorder.recording(
282
+ () => this.setupInstallConfig(serverConfig, options), {
283
+ stepName: RecordingConstants.STEP_SETUP_INSTALLATION_CONFIG
284
+ });
292
285
 
293
- // Update VS Code settings
294
- const results = await this.updateVSCodeSettings(serverConfig.name, installConfig);
286
+ if (serverConfig.mode) {
287
+ installConfig.mode = serverConfig.mode;
288
+ }
295
289
 
296
- // Determine overall success
297
- const anySuccess = results.some(r => r.success);
298
- const successPaths = results.filter(r => r.success).map(r => r.path);
299
- const errors = results.filter(r => !r.success).map(r => r.error);
290
+ const results = await recorder.recording(
291
+ () => this.updateVSCodeSettings(serverConfig.name, installConfig), {
292
+ stepName: RecordingConstants.STEP_UPDATE_VSCODE_SETTINGS,
293
+ onResult: (result) => result?.some(r => r.success)
294
+ }
295
+ );
300
296
 
301
- return {
302
- status: anySuccess ? 'completed' : 'failed',
303
- type: 'install',
304
- target: 'server',
305
- message: anySuccess
297
+ // Determine overall success
298
+ const anySuccess = results.some(r => r.success);
299
+ const successPaths = results.filter(r => r.success).map(r => r.path);
300
+ const errors = results.filter(r => !r.success).map(r => r.error);
301
+
302
+ const finalMessage = anySuccess
306
303
  ? `Successfully installed ${this.clientName} client. Updated settings in: ${successPaths.join(', ')}`
307
- : `Failed to install ${this.clientName} client. Errors: ${errors.join('; ')}`,
308
- operationId,
309
- error: anySuccess ? undefined : errors.join('; ')
310
- };
311
- } catch (error) {
312
- return {
313
- status: 'failed',
314
- type: 'install',
315
- target: 'server',
316
- message: `Unexpected error installing ${this.clientName} client: ${error instanceof Error ? error.message : String(error)}`,
317
- operationId,
318
- error: error instanceof Error ? error.message : String(error)
319
- };
320
- }
304
+ : `Failed to install ${this.clientName} client. Errors: ${errors.join('; ')}`;
305
+
306
+ return {
307
+ status: anySuccess ? 'completed' : 'failed',
308
+ type: 'install',
309
+ target: 'server',
310
+ message: finalMessage,
311
+ operationId,
312
+ error: anySuccess ? undefined : errors.join('; ')
313
+ };
314
+ },
315
+ {
316
+ stepName: RecordingConstants.STEP_INSTALLATION,
317
+ onResult: (result) => result?.status !== 'failed',
318
+ endMessage: (result) => (result as OperationStatus)?.message,
319
+ onError: (error) => {
320
+ const errorMsg = `Unexpected error installing ${this.clientName} client: ${error instanceof Error ? error.message : String(error)}`;
321
+ return {
322
+ result: {
323
+ status: 'failed',
324
+ type: 'install',
325
+ target: 'server',
326
+ message: errorMsg,
327
+ operationId,
328
+ error: error instanceof Error ? error.message : String(error)
329
+ }, message: errorMsg
330
+ };
331
+ }
332
+ }
333
+ );
334
+
321
335
  }
322
336
 
323
337
  /**
@@ -9,6 +9,8 @@ import {
9
9
  } from '../../metadatas/types.js';
10
10
  import { Logger } from '../../../utils/logger.js';
11
11
  import { ClientInstallerFactory } from './ClientInstallerFactory.js';
12
+ import { InstallOperationManager } from '../../loaders/InstallOperationManager.js';
13
+ import { stat } from 'fs';
12
14
 
13
15
  /**
14
16
  * Main client installer class that orchestrates client installation process
@@ -26,56 +28,27 @@ export class ClientInstaller {
26
28
  }
27
29
 
28
30
  /**
29
- * Generate a unique operation ID for tracking installations
30
- */
31
- private generateOperationId(): string {
32
- return `install-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
33
- }
34
-
35
- /**
36
- * Check if server requirements are ready
37
- * Waits for requirements to be ready with timeout
31
+ * Install all specified clients
38
32
  */
39
- private async checkRequirements(operationId: string, clientName: string, options: ServerInstallOptions): Promise<boolean> {
40
- let requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
41
-
42
- if (!requirementsReady) {
43
- const pendingStatus: OperationStatus = {
44
- status: 'pending',
45
- type: 'install',
46
- target: 'server',
47
- message: `Waiting for requirements to be ready for client: ${clientName}`,
48
- operationId
49
- };
50
-
51
- await this.configProvider.updateServerOperationStatus(
52
- this.categoryName,
53
- this.serverName,
54
- clientName,
55
- pendingStatus
56
- );
57
-
58
- // Set up periodic checking with timeout
59
- const startTime = Date.now();
60
- const timeoutMs = 5 * 60 * 1000; // 5 minutes
61
- const intervalMs = 5 * 1000; // 5 seconds
62
-
63
- while (!requirementsReady && (Date.now() - startTime) < timeoutMs) {
64
- await new Promise(resolve => setTimeout(resolve, intervalMs));
65
- requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
66
- }
67
- }
33
+ async install(options: ServerInstallOptions): Promise<ServerOperationResult> {
34
+ const initialStatuses: OperationStatus[] = [];
68
35
 
69
- var requirementsStatus: RequirementStatus[] = await this.configProvider.GetServerRequirementStatus(this.categoryName, this.serverName);
36
+ // Start installation for each client asynchronously
37
+ const installPromises = this.clients.map(async (clientName) => {
38
+ const status = await this.installClient(clientName, options);
39
+ initialStatuses.push(status);
40
+ return status;
41
+ });
70
42
 
71
- // Find first non-empty npmPath from requirements status
72
- const npmPathRequirement = requirementsStatus.find(status => status.npmPath && status.npmPath.length > 0);
73
- if (npmPathRequirement && npmPathRequirement.npmPath) {
74
- options.settings = options.settings || {};
75
- options.settings.npmPath = npmPathRequirement.npmPath;
76
- }
43
+ // Wait for all installations to complete
44
+ await Promise.all(installPromises);
77
45
 
78
- return requirementsReady;
46
+ // Return result
47
+ return {
48
+ success: true,
49
+ message: 'Client installations completed',
50
+ status: initialStatuses
51
+ };
79
52
  }
80
53
 
81
54
  /**
@@ -104,7 +77,41 @@ export class ClientInstaller {
104
77
  operationId
105
78
  };
106
79
 
107
- this.processInstallation(clientName, operationId, options);
80
+ // Async installation process
81
+ this.processInstallation(clientName, operationId, options)
82
+ .then((status) => {
83
+ if (status.status === 'completed' || status.status === 'failed') {
84
+ InstallOperationManager
85
+ .getInstance(this.categoryName, this.serverName)
86
+ .markOverallStatus(status.status)
87
+ }
88
+
89
+ this.configProvider.updateServerOperationStatus(
90
+ this.categoryName,
91
+ this.serverName,
92
+ clientName,
93
+ status
94
+ );
95
+ })
96
+ .catch((error) => {
97
+ this.configProvider.updateServerOperationStatus(
98
+ this.categoryName,
99
+ this.serverName,
100
+ clientName,
101
+ {
102
+ status: 'failed',
103
+ type: 'install',
104
+ target: 'server',
105
+ message: `Error installing client ${clientName}: ${error instanceof Error ? error.message : String(error)}`,
106
+ operationId,
107
+ error: error instanceof Error ? error.message : String(error)
108
+ }
109
+ );
110
+ Logger.error(`Error installing client ${clientName}: ${error instanceof Error ? error.message : String(error)}`);
111
+ InstallOperationManager
112
+ .getInstance(this.categoryName, this.serverName)
113
+ .markOverallStatus('failed', error)
114
+ });
108
115
 
109
116
  // Update server status with initial client installation status
110
117
  await this.configProvider.updateServerOperationStatus(
@@ -116,107 +123,100 @@ export class ClientInstaller {
116
123
  return initialStatus;
117
124
  }
118
125
 
119
- private async processInstallation(clientName: string, operationId: string, options: ServerInstallOptions): Promise<void> {
120
- try {
121
- // Check requirements
122
- const requirementsReady = await this.checkRequirements(operationId, clientName, options);
123
- if (!requirementsReady) {
124
- const failedStatus: OperationStatus = {
125
- status: 'failed',
126
- type: 'install',
127
- target: 'server',
128
- message: `Requirements not ready for client: ${clientName} after timeout`,
129
- operationId
130
- };
131
-
132
- await this.configProvider.updateServerOperationStatus(
133
- this.categoryName,
134
- this.serverName,
135
- clientName,
136
- failedStatus
137
- );
138
- return;
139
- }
126
+ private async processInstallation(clientName: string, operationId: string, options: ServerInstallOptions): Promise<OperationStatus> {
127
+ const requirementsReady = await this.checkRequirements(operationId, clientName, options);
128
+ if (!requirementsReady) {
129
+ const failedStatus: OperationStatus = {
130
+ status: 'failed',
131
+ type: 'install',
132
+ target: 'server',
133
+ message: `Requirements not ready for client: ${clientName} after timeout`,
134
+ operationId
135
+ };
136
+ return failedStatus;
137
+ }
140
138
 
141
- // Create client-specific installer
142
- const installer = ClientInstallerFactory.getInstaller(clientName);
139
+ // Create client-specific installer
140
+ const installer = ClientInstallerFactory.getInstaller(clientName);
143
141
 
144
- if (!installer) {
145
- throw new Error(`Failed to create installer for client: ${clientName}`);
146
- }
142
+ if (!installer) {
143
+ throw new Error(`Failed to create installer for client: ${clientName}`);
144
+ }
147
145
 
148
- const serverConfig = await this.configProvider.getServerMcpConfig(this.categoryName, this.serverName);
149
- if (!serverConfig) {
150
- throw new Error(`Server configuration not found for category: ${this.categoryName}, server: ${this.serverName}`);
151
- }
146
+ const serverConfig = await this.configProvider.getServerMcpConfig(this.categoryName, this.serverName);
147
+ if (!serverConfig) {
148
+ throw new Error(`Server configuration not found for category: ${this.categoryName}, server: ${this.serverName}`);
149
+ }
152
150
 
153
- // If we've reached here, requirements are ready - update status to in-progress
154
- const inProgressStatus: OperationStatus = {
155
- status: 'in-progress',
156
- type: 'install',
157
- target: 'server',
158
- message: `Installing client: ${clientName}`,
159
- operationId: operationId
160
- };
151
+ // If we've reached here, requirements are ready - update status to in-progress
152
+ const inProgressStatus: OperationStatus = {
153
+ status: 'in-progress',
154
+ type: 'install',
155
+ target: 'server',
156
+ message: `Installing client: ${clientName}`,
157
+ operationId: operationId
158
+ };
161
159
 
162
- await this.configProvider.updateServerOperationStatus(
163
- this.categoryName,
164
- this.serverName,
165
- clientName,
166
- inProgressStatus
167
- );
168
- // Install client
169
- const status = await installer.install(serverConfig, options);
170
- await this.configProvider.updateServerOperationStatus(
171
- this.categoryName,
172
- this.serverName,
173
- clientName,
174
- status
175
- );
160
+ await this.configProvider.updateServerOperationStatus(
161
+ this.categoryName,
162
+ this.serverName,
163
+ clientName,
164
+ inProgressStatus
165
+ );
166
+ // Install client
167
+ return await installer.install(serverConfig, options, this.categoryName);
168
+ }
176
169
 
177
- if (status.status === 'completed') {
178
- await this.configProvider.reloadClientMCPSettings();
179
- }
180
- } catch (error) {
181
- const errorStatus: OperationStatus = {
182
- status: 'failed',
170
+ /**
171
+ * Generate a unique operation ID for tracking installations
172
+ */
173
+ private generateOperationId(): string {
174
+ return `install-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
175
+ }
176
+
177
+ /**
178
+ * Check if server requirements are ready
179
+ * Waits for requirements to be ready with timeout
180
+ */
181
+ private async checkRequirements(operationId: string, clientName: string, options: ServerInstallOptions): Promise<boolean> {
182
+ let requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
183
+
184
+ if (!requirementsReady) {
185
+ const pendingStatus: OperationStatus = {
186
+ status: 'pending',
183
187
  type: 'install',
184
188
  target: 'server',
185
- message: `Error installing client ${clientName}: ${error instanceof Error ? error.message : String(error)}`,
186
- operationId,
187
- error: error instanceof Error ? error.message : String(error)
189
+ message: `Waiting for requirements to be ready for client: ${clientName}`,
190
+ operationId
188
191
  };
189
192
 
190
193
  await this.configProvider.updateServerOperationStatus(
191
194
  this.categoryName,
192
195
  this.serverName,
193
196
  clientName,
194
- errorStatus
197
+ pendingStatus
195
198
  );
196
- }
197
- }
198
199
 
199
- /**
200
- * Install all specified clients
201
- */
202
- async install(options: ServerInstallOptions): Promise<ServerOperationResult> {
203
- const initialStatuses: OperationStatus[] = [];
200
+ // Set up periodic checking with timeout
201
+ const startTime = Date.now();
202
+ const timeoutMs = 5 * 60 * 1000; // 5 minutes
203
+ const intervalMs = 5 * 1000; // 5 seconds
204
204
 
205
- // Start installation for each client asynchronously
206
- const installPromises = this.clients.map(async (clientName) => {
207
- const status = await this.installClient(clientName, options);
208
- initialStatuses.push(status);
209
- return status;
210
- });
205
+ while (!requirementsReady && (Date.now() - startTime) < timeoutMs) {
206
+ await new Promise(resolve => setTimeout(resolve, intervalMs));
207
+ requirementsReady = await this.configProvider.isRequirementsReady(this.categoryName, this.serverName);
208
+ }
209
+ }
211
210
 
212
- // Wait for all installations to complete
213
- await Promise.all(installPromises);
211
+ var requirementsStatus: RequirementStatus[] = await this.configProvider.GetServerRequirementStatus(this.categoryName, this.serverName);
214
212
 
215
- // Return result
216
- return {
217
- success: true,
218
- message: 'Client installations completed',
219
- status: initialStatuses
220
- };
213
+ // Find first non-empty npmPath from requirements status
214
+ const npmPathRequirement = requirementsStatus.find(status => status.npmPath && status.npmPath.length > 0);
215
+ if (npmPathRequirement && npmPathRequirement.npmPath) {
216
+ options.settings = options.settings || {};
217
+ options.settings.npmPath = npmPathRequirement.npmPath;
218
+ }
219
+
220
+ return requirementsReady;
221
221
  }
222
222
  }
@@ -3,6 +3,7 @@ import path from 'path';
3
3
  import { SETTINGS_DIR } from '../../metadatas/constants.js';
4
4
  import { RequirementInstaller } from './RequirementInstaller.js';
5
5
  import { Logger } from '../../../utils/logger.js';
6
+ import { InstallOperationManager } from '../../loaders/InstallOperationManager.js';
6
7
 
7
8
  /**
8
9
  * Abstract base class with common functionality for all requirement installers
@@ -18,7 +19,14 @@ export abstract class BaseInstaller implements RequirementInstaller {
18
19
 
19
20
  abstract canHandle(requirement: RequirementConfig): boolean;
20
21
  abstract supportCheckUpdates(): boolean;
21
- abstract install(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
22
+ /**
23
+ * Install the requirement
24
+ * @param requirement The requirement to install
25
+ * @param options Optional install options
26
+ * @param recorder Optional InstallOperationManager for recording steps
27
+ * @returns The status of the installation
28
+ */
29
+ abstract install(requirement: RequirementConfig, recorder: InstallOperationManager, options?: ServerInstallOptions): Promise<RequirementStatus>;
22
30
  abstract checkInstallation(requirement: RequirementConfig, options?: ServerInstallOptions): Promise<RequirementStatus>;
23
31
  /**
24
32
  * Get the latest version available for the requirement.