imcp 0.1.1 → 0.1.3

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 +5 -5
  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
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Step name constants for InstallOperationManager and step recording.
3
+ *
4
+ * This file defines all static step names and documents dynamic step name patterns
5
+ * used throughout the installation and onboarding process.
6
+ *
7
+ * Dynamic step names are documented as template string patterns.
8
+ */
9
+
10
+ /** Step for processing all requirement updates in a batch operation. */
11
+ export const STEP_PROCESS_REQUIREMENT_UPDATES = 'Processing all requirement updates';
12
+
13
+ /** Step for checking the status of a specific requirement. */
14
+ export const STEP_CHECKING_REQUIREMENT_STATUS = 'Checking the status of requirement';
15
+
16
+ /** Step for installing requirements in the background process. */
17
+ export const STEP_INSTALLING_REQUIREMENTS_IN_BACKGROUND = 'Installing requirements in the background';
18
+
19
+ /** Step for checking and installing all requirements as needed. */
20
+ export const STEP_CHECK_AND_INSTALL_REQUIREMENTS = 'Checking and installing all requirements';
21
+
22
+ /** Step for running the install logic in the CommandInstaller. */
23
+ export const STEP_COMMAND_INSTALLER_INSTALL = 'Running install in CommandInstaller';
24
+
25
+ /** Step for running the install logic in the GeneralInstaller. */
26
+ export const STEP_GENERAL_INSTALLER_INSTALL = 'Running install in GeneralInstaller';
27
+
28
+ /** Step for executing the actual installation command (npm, pip, etc.). */
29
+ export const STEP_INSTALLATION_COMMAND_EXECUTION = 'Executing installation command for requirement';
30
+
31
+ /** Step for running the install logic in the PipInstaller. */
32
+ export const STEP_PIP_INSTALLER_INSTALL = 'Running install in PipInstaller';
33
+
34
+ /** Step for processing requirement updates in the RequirementService. */
35
+ export const STEP_PROCESS_REQUIREMENT_UPDATES_SERVICE = 'Processing requirement updates in RequirementService';
36
+
37
+ /** Step for checking if the server is ready after installation. */
38
+ export const STEP_CHECK_SERVER_READINESS = 'Checking server readiness after installation';
39
+
40
+ /** Step for running the install logic in the NpmInstaller. */
41
+ export const STEP_NPM_INSTALLER_INSTALL = 'Running install in NpmInstaller';
42
+
43
+ /** Prefix for steps that update a specific requirement. */
44
+ export const STEP_INSTALL_REQUIREMENT_PREFIX = 'Updating requirement:';
45
+
46
+ /** Prefix for steps that execute an installation command for a requirement. */
47
+ export const STEP_INSTALL_COMMAND_PREFIX = 'Executing installation command for:';
48
+
49
+ /** Step for checking and installing the VS Code extension for the client. */
50
+ export const STEP_CHECK_VSCODE_AND_INSTALL_EXTENSION = 'Checking and installing VS Code extension for client';
51
+
52
+ /** Step for setting up the installation configuration (env, args, etc.). */
53
+ export const STEP_SETUP_INSTALLATION_CONFIG = 'Setting up installation configuration';
54
+
55
+ /** Step for updating VS Code settings for the client/server. */
56
+ export const STEP_UPDATE_VSCODE_SETTINGS = 'Updating VS Code settings for client/server';
57
+
58
+ /** Step for the overall installation process of a client or server. */
59
+ export const STEP_INSTALLATION = 'Running overall installation process';
60
+
61
+ /** Step for marking the initiation of an onboarding or installation process. */
62
+ export const STEP_INITIATED = 'Initiating onboarding or installation process';
@@ -176,4 +176,27 @@ export interface MCPEventData {
176
176
  [MCPEvent.SERVER_STARTED]: { server: MCPServerCategory };
177
177
  [MCPEvent.SERVER_STOPPED]: { serverName: string };
178
178
  [MCPEvent.CONFIG_CHANGED]: { configuration: MCPConfiguration };
179
+ }
180
+
181
+ export interface InstallOperationStep {
182
+ name: string;
183
+ status: 'pending' | 'in-progress' | 'completed' | 'failed' | 'canceled';
184
+ message?: string;
185
+ isCritical?: boolean;
186
+ timestamp: string;
187
+ }
188
+
189
+ export interface InstallOperationDetails {
190
+ currentStep: string;
191
+ steps: InstallOperationStep[];
192
+ lastUpdated: string;
193
+ error?: string;
194
+ overallStatus: 'pending' | 'in-progress' | 'completed' | 'failed';
195
+ }
196
+ export interface SystemSettings {
197
+ pythonEnvs?: Record<string, string>;
198
+ nodePath?: string;
199
+ browserPath?: string;
200
+ systemEnvironments?: Record<string, string>;
201
+ userConfigurations?: Record<string, string>;
179
202
  }
@@ -5,6 +5,7 @@ import { FeedConfiguration, McpConfig } from '../metadatas/types.js';
5
5
  import { OnboardStatus, OnboardingProcessStatus, OperationType } from './OnboardStatus.js';
6
6
  import { Logger } from '../../utils/logger.js';
7
7
 
8
+ import * as RecordingConstants from '../metadatas/recordingConstants.js';
8
9
  const ONBOARD_STATUS_DIR = path.join(SETTINGS_DIR, 'onboard');
9
10
  const CATEGORY_OPERATIONS_STATUS_FILE = path.join(ONBOARD_STATUS_DIR, 'OnboardStatus.json');
10
11
  const FEED_CONFIG_DIR = path.join(ONBOARD_STATUS_DIR, 'feed_configs'); // Staging for feed configs during operation
@@ -306,7 +307,7 @@ export class OnboardStatusManager {
306
307
  feedName: categoryName,
307
308
  serverName,
308
309
  status: OnboardingProcessStatus.PENDING,
309
- steps: [{ stepName: 'Initiated', timestamp: new Date().toISOString() }],
310
+ steps: [{ stepName: RecordingConstants.STEP_INITIATED, timestamp: new Date().toISOString() }],
310
311
  lastUpdated: new Date().toISOString(),
311
312
  operationType,
312
313
  errorMessage: undefined,
@@ -8,6 +8,7 @@ import { MACRO_EXPRESSIONS, resolveNpmModulePath } from "../../utils/macroExpres
8
8
  import { getSystemPythonPackageDirectory } from "../../utils/osUtils.js";
9
9
  import { SETTINGS_DIR } from "../metadatas/constants.js";
10
10
  import path from "path";
11
+ import { InstallOperationManager } from "../loaders/InstallOperationManager.js";
11
12
 
12
13
  const execPromise = util.promisify(exec);
13
14
 
@@ -57,7 +58,7 @@ export class StdioServerValidator implements IServerValidator {
57
58
  * @param requirement The requirement config to validate and install
58
59
  * @returns true if requirement is successfully installed/validated
59
60
  */
60
- private async validateRequirement(requirement: RequirementConfig): Promise<boolean> {
61
+ private async validateRequirement(requirement: RequirementConfig, recorder: InstallOperationManager): Promise<boolean> {
61
62
  try {
62
63
  Logger.debug(`Validating/installing requirement: ${requirement.name}`);
63
64
  const installer = this.installerFactory.getInstaller(requirement);
@@ -69,7 +70,7 @@ export class StdioServerValidator implements IServerValidator {
69
70
  }
70
71
 
71
72
  const targetDir = this._getRequirementFolderPath(requirement);
72
- const status = await installer.install(requirement, { settings: { folderName: targetDir } });
73
+ const status = await installer.install(requirement, recorder, { settings: { folderName: targetDir } });
73
74
  if (!status.installed) {
74
75
  const msg = `Failed to install requirement ${requirement.name}: ${status.error || 'Unknown error'}`;
75
76
  Logger.error(msg);
@@ -288,7 +289,7 @@ export class StdioServerValidator implements IServerValidator {
288
289
  type: 'npm' // Default to npm if not specified
289
290
  };
290
291
 
291
- const isValid = await this.validateRequirement(reqConfig);
292
+ const isValid = await this.validateRequirement(reqConfig, InstallOperationManager.getInstance(config.name, server.name));
292
293
  if (!isValid) {
293
294
  throw new Error(`Dependency validation failed for: ${req.name}`);
294
295
  }
@@ -1,72 +1,78 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
3
- import { fileURLToPath } from 'url';
4
- import { exec } from 'child_process';
5
- import util from 'util';
6
1
  import {
7
2
  ServerInstallOptions,
8
- ServerOperationResult,
9
- FeedConfiguration,
10
- RequirementConfig,
11
- OperationStatus,
12
- RequirementStatus,
13
- McpConfig
3
+ ServerOperationResult
14
4
  } from '../core/metadatas/types.js';
15
- import { RequirementInstaller, InstallerFactory, createInstallerFactory } from '../core/installers/index.js';
16
- import { SUPPORTED_CLIENTS } from '../core/metadatas/constants.js';
17
5
  import { ClientInstaller } from '../core/installers/clients/ClientInstaller.js';
18
6
  import { ConfigurationProvider } from '../core/loaders/ConfigurationProvider.js';
19
7
  import { Logger } from '../utils/logger.js';
20
-
21
- const execPromise = util.promisify(exec);
22
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ import { requirementService } from './RequirementService.js';
9
+ import { InstallOperationManager } from '../core/loaders/InstallOperationManager.js';
10
+ import * as RecordingConstants from '../core/metadatas/recordingConstants.js';
23
11
 
24
12
  /**
25
13
  * Handles the actual installation process for an MCP server.
26
14
  */
27
15
  export class InstallationService {
28
- private activeInstallations: Map<string, OperationStatus> = new Map();
29
- private installerFactory: InstallerFactory;
30
16
 
31
17
  constructor() {
32
- this.installerFactory = createInstallerFactory();
33
- }
34
-
35
- private generateOperationId(): string {
36
- return `install-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
18
+ // Constructor is now empty after removing installerFactory initialization
37
19
  }
38
20
 
39
21
  /**
40
- * Installs a server based on the provided options and feed configuration.
22
+ * Installs a server based on the provided options.
23
+ * @param categoryName The category name of the server.
41
24
  * @param serverName The name of the server to install.
42
25
  * @param options The installation options.
43
26
  * @returns A result object indicating success or failure.
44
27
  */
45
28
  async install(categoryName: string, serverName: string, options: ServerInstallOptions): Promise<ServerOperationResult> {
29
+ // Reset any previous operation status for this server before starting a new one.
30
+ const recoder = await InstallOperationManager.getInstance(categoryName, serverName).resetOperation();
46
31
  const configProvider = ConfigurationProvider.getInstance();
47
- const clients = options.targetClients || Object.keys(SUPPORTED_CLIENTS);
32
+
33
+ const clients = options.targetClients || [];
48
34
 
49
35
  // Process updates for requirements if specified in options
50
- // Fire off requirement updates in the background without awaiting completion
51
36
  if (options.requirements && options.requirements.length > 0) {
52
- // Start the process but don't await it - it will run in the background
53
- this.processRequirementUpdates(categoryName, serverName, options)
54
- .catch(error => {
55
- console.error(`Error in background requirement updates: ${error instanceof Error ? error.message : String(error)}`);
56
- });
37
+ recoder.recordingAsync(
38
+ () => requirementService.processRequirementUpdates(categoryName, serverName, options),
39
+ {
40
+ stepName: RecordingConstants.STEP_PROCESS_REQUIREMENT_UPDATES_SERVICE,
41
+ onError: (error) => {
42
+ const errorMsg = `Error in background requirement updates: ${error instanceof Error ? error.message : String(error)}`;
43
+ Logger.error(errorMsg);
44
+ return errorMsg;
45
+ },
46
+ onComplete: () => {
47
+ if (clients.length === 0) recoder.markOverallStatus('completed', 'Requirement updates completed.');
48
+ }
49
+ }
50
+ );
57
51
  }
58
52
 
59
- // Check if server is already ready
60
- const isReady = await configProvider.isServerReady(categoryName, serverName, clients);
53
+ if (!clients || clients.length === 0) {
54
+ const message = 'No clients specified for installation.';
55
+ return { success: true, message };
56
+ }
57
+
58
+ // Check if the server is already installed and ready
59
+ const readyMessage = 'Server and clients are already installed and ready';
60
+ const isReady = await recoder.recording(
61
+ () => configProvider.isServerReady(categoryName, serverName, clients),
62
+ {
63
+ stepName: RecordingConstants.STEP_CHECK_SERVER_READINESS,
64
+ inProgressMessage: 'Checking if server is already ready.',
65
+ endMessage: (ready: boolean) => ready ? 'Server and clients are already installed and ready' : 'Server is not ready. Proceeding with installation.',
66
+ }
67
+ )
61
68
  if (isReady) {
62
69
  return {
63
- success: true,
64
- message: 'Server and clients are already installed and ready',
70
+ success: true, message: readyMessage,
65
71
  status: [{
66
72
  status: 'completed',
67
73
  type: 'install',
68
74
  target: 'server',
69
- message: 'Server and clients are already installed and ready'
75
+ message: readyMessage,
70
76
  }]
71
77
  };
72
78
  }
@@ -74,374 +80,23 @@ export class InstallationService {
74
80
  // Create new ClientInstaller instance for handling installation
75
81
  const clientInstaller = new ClientInstaller(categoryName, serverName, clients);
76
82
 
77
- const requirementsResult = await this.checkAndInstallRequirements(categoryName, serverName, options);
78
- if (requirementsResult) {
79
- return requirementsResult;
80
- }
81
-
82
- // Process client installation regardless of requirements state
83
- // Each client installer will check requirements before actual installation
84
- return await clientInstaller.install(options);
85
- }
86
-
87
- /**
88
- * Process requirement updates specified in serverInstallOptions
89
- * All updates are processed in parallel for maximum efficiency
90
- * @param categoryName The category name
91
- * @param serverName The server name
92
- * @param requirements The requirements to update
93
- */
94
- private async processRequirementUpdates(categoryName: string, serverName: string, options: ServerInstallOptions): Promise<void> {
95
- // Use UpdateCheckTracker to prevent concurrent updates
96
- const updateCheckTracker = await import('../utils/UpdateCheckTracker.js').then(m => m.updateCheckTracker);
97
- const operationKey = `requirement-updates-${categoryName}-${serverName}`;
98
-
99
- // Check if there's already an update operation in progress for this server
100
- const canProceed = await updateCheckTracker.startOperation(operationKey);
101
- if (!canProceed) {
102
- console.log(`Requirement updates for ${categoryName}/${serverName} already in progress, skipping`);
103
- return;
104
- }
105
-
106
- try {
107
- const configProvider = ConfigurationProvider.getInstance();
108
- const feedConfig = await configProvider.getFeedConfiguration(categoryName);
109
-
110
- if (!feedConfig) {
111
- console.error(`Feed configuration not found for category: ${categoryName}`);
112
- return;
113
- }
114
-
115
- // Import the RequirementService
116
- const { requirementService } = await import('./RequirementService.js');
117
-
118
- // Create an array of promises to update all requirements in parallel
119
- const updatePromises = options.requirements?.map(async (reqToUpdate) => {
120
- try {
121
- // Find the full requirement config
122
- const reqConfig = feedConfig.requirements?.find((r: RequirementConfig) => r.name === reqToUpdate.name);
123
-
124
- if (!reqConfig) {
125
- console.error(`Requirement configuration not found for: ${reqToUpdate.name}`);
126
- return;
127
- }
128
-
129
- // Get current status
130
- const currentStatus = await configProvider.getRequirementStatus(categoryName, reqToUpdate.name);
131
-
132
- if (!currentStatus) {
133
- console.error(`No current status found for requirement: ${reqToUpdate.name}`);
134
- return;
135
- }
136
-
137
- // Update requirement status to indicate update in progress
138
- await configProvider.updateRequirementStatus(categoryName, reqToUpdate.name, {
139
- ...currentStatus,
140
- name: reqToUpdate.name,
141
- type: currentStatus.type || 'unknown',
142
- installed: currentStatus.installed || false,
143
- inProgress: true,
144
- operationStatus: {
145
- status: 'in-progress',
146
- type: 'update',
147
- target: 'requirement',
148
- message: `Updating ${reqToUpdate.name} from ${currentStatus.version || 'unknown'} to ${reqToUpdate.version}`
149
- }
150
- });
151
-
152
-
153
- // For pip requirements, check if we have a stored pythonEnv
154
- if (reqConfig.type === 'pip' && currentStatus.pythonEnv && !options?.settings?.pythonEnv) {
155
- options = {
156
- ...options,
157
- settings: { ...options?.settings, pythonEnv: currentStatus.pythonEnv }
158
- };
159
- }
160
-
161
- // Update the requirement with options for pip environment
162
- const updatedStatus = await requirementService.updateRequirement(reqConfig, reqToUpdate.version, options);
163
-
164
- // Update requirement status
165
- await configProvider.updateRequirementStatus(categoryName, reqToUpdate.name, {
166
- ...updatedStatus,
167
- name: reqToUpdate.name,
168
- type: updatedStatus.type || currentStatus.type || 'unknown',
169
- installed: updatedStatus.installed,
170
- inProgress: false,
171
- operationStatus: {
172
- status: updatedStatus.installed ? 'completed' : 'failed',
173
- type: 'update',
174
- target: 'requirement',
175
- message: updatedStatus.installed
176
- ? `Successfully updated ${reqToUpdate.name} to version ${reqToUpdate.version}`
177
- : `Failed to update ${reqToUpdate.name} to version ${reqToUpdate.version}`
178
- },
179
- availableUpdate: updatedStatus.installed ? undefined : currentStatus.availableUpdate
180
- });
181
-
182
- console.log(`Requirement ${reqToUpdate.name} updated to version ${reqToUpdate.version}`);
183
- } catch (error) {
184
- console.error(`Error updating requirement ${reqToUpdate.name}:`, error);
185
-
186
- // Update status to indicate failure
187
- await configProvider.updateRequirementStatus(categoryName, reqToUpdate.name, {
188
- name: reqToUpdate.name,
189
- type: 'unknown',
190
- installed: false,
191
- inProgress: false,
192
- error: error instanceof Error ? error.message : String(error),
193
- operationStatus: {
194
- status: 'failed',
195
- type: 'update',
196
- target: 'requirement',
197
- message: `Error updating requirement: ${error instanceof Error ? error.message : String(error)}`
198
- }
199
- });
200
- }
201
- });
202
-
203
- // Wait for all updates to complete in parallel if there are any
204
- if (updatePromises) {
205
- await Promise.all(updatePromises);
206
- }
207
- } finally {
208
- // Always release the lock when done, even if there was an error
209
- await updateCheckTracker.endOperation(operationKey);
210
- }
211
- }
212
-
213
- /**
214
- * Checks and installs requirements for a server if needed
215
- * @param categoryName The category name
216
- * @param serverName The server name
217
- * @param options The installation options
218
- * @returns A failure result if requirements check fails, null if requirements are satisfied
219
- */
220
- private async checkAndInstallRequirements(categoryName: string, serverName: string, options: ServerInstallOptions): Promise<ServerOperationResult | null> {
221
- const configProvider = ConfigurationProvider.getInstance();
222
-
223
- // Get feed configuration to get requirements
224
- const feedConfig = await configProvider.getFeedConfiguration(categoryName);
225
- if (!feedConfig) {
226
- return {
227
- success: false,
228
- message: 'Feed configuration not found',
229
- status: [{
230
- status: 'failed',
231
- type: 'install',
232
- target: 'server',
233
- message: 'Feed configuration not found'
234
- }]
235
- };
236
- }
237
-
238
- // Find server config and verify requirements
239
- const serverConfig = feedConfig.mcpServers.find((s: McpConfig) => s.name === serverName);
240
- if (!serverConfig?.dependencies?.requirements) {
241
- Logger.debug(`No requirements for ${serverName}`);
242
- return null;
243
- }
83
+ // Check and install requirements using RequirementService
84
+ const requirementsResult = await requirementService.checkAndInstallRequirements(categoryName, serverName, options);
244
85
 
245
- // Check all requirements installation status
246
- const requirementStatuses = await Promise.all(
247
- serverConfig.dependencies.requirements.map(async (req) => {
248
- const reqConfig = feedConfig.requirements?.find((r: RequirementConfig) => r.name === req.name) || {
249
- name: req.name,
250
- version: req.version,
251
- type: 'npm'
252
- };
253
- return await this.installerFactory.checkInstallation(reqConfig, options);
86
+ // trigger a backend requirement check
87
+ await requirementService.checkServerRequirementForUpdateAsync(categoryName, serverName)
88
+ .then(() => {
89
+ Logger.info(`Requirement check for ${categoryName}:${serverName} completed successfully.`);
254
90
  })
255
- );
256
-
257
- // If all requirements are installed and ready, no need to proceed with installation
258
- if (requirementStatuses.every(status => status.installed)) {
259
- // Check if requirements are ready via ConfigurationProvider
260
- const requirementsReady = await configProvider.isRequirementsReady(categoryName, serverName);
261
-
262
- // Update requirement status if not ready
263
- if (!requirementsReady) {
264
- for (const status of requirementStatuses) {
265
- await configProvider.updateRequirementStatus(categoryName, status.name, status);
266
- }
267
- }
268
- return null;
269
- }
270
-
271
- // Sort requirements by order for installation
272
- const sortedRequirements = [...serverConfig.dependencies.requirements].sort((a, b) => {
273
- const orderA = a.order ?? Infinity;
274
- const orderB = b.order ?? Infinity;
275
- return orderA - orderB;
276
- });
277
-
278
- // Start requirements installation in background
279
- this.installRequirementsInBackground(categoryName, sortedRequirements, options)
280
- .catch(error => {
281
- Logger.error(`Error in background requirement installations: ${error instanceof Error ? error.message : String(error)}`);
91
+ .catch((error) => {
92
+ Logger.error(`Requirement check for ${categoryName}:${serverName} failed: ${error instanceof Error ? error.message : String(error)}`);
282
93
  });
283
94
 
284
- // Return immediately while installation continues in background
285
- return null;
286
- }
287
-
288
- /**
289
- * Installs requirements in background without blocking the main thread
290
- * Requirements with the same order are installed in parallel
291
- */
292
- private async installRequirementsInBackground(
293
- categoryName: string,
294
- sortedRequirements: Array<{ name: string; version: string; order?: number }>,
295
- options: ServerInstallOptions
296
- ): Promise<void> {
297
- const configProvider = ConfigurationProvider.getInstance();
298
-
299
- // Group requirements by order
300
- type RequirementType = { name: string; version: string; order?: number };
301
- const requirementGroups = sortedRequirements.reduce<Record<number, RequirementType[]>>((groups, req) => {
302
- const order = req.order ?? Infinity;
303
- if (!groups[order]) {
304
- groups[order] = [];
305
- }
306
- groups[order].push(req);
307
- return groups;
308
- }, {});
309
-
310
- // Process each group in sequence, but requirements within group in parallel
311
- const orderKeys = Object.keys(requirementGroups).map(Number).sort((a, b) => a - b);
312
- for (const order of orderKeys) {
313
- const group = requirementGroups[order];
314
-
315
- await Promise.all(group.map(async requirement => {
316
- try {
317
- const feeds = await configProvider.getFeedConfiguration(categoryName);
318
- const requirementConfig = feeds?.requirements?.find((r: RequirementConfig) => r.name === requirement.name) || {
319
- name: requirement.name,
320
- version: requirement.version,
321
- type: 'npm'
322
- };
323
-
324
- // For pip requirements, check if we need to use stored pythonEnv
325
- const currentStatus = await configProvider.getRequirementStatus(categoryName, requirement.name);
326
- if (requirementConfig.type === 'pip' && currentStatus?.pythonEnv && !options?.settings?.pythonEnv) {
327
- options = {
328
- ...options,
329
- settings: { ...options?.settings, pythonEnv: currentStatus.pythonEnv }
330
- };
331
- }
332
-
333
- const installer = this.installerFactory.getInstaller(requirementConfig);
334
- if (!installer) {
335
- await this.updateRequirementFailureStatus(
336
- categoryName,
337
- requirement.name,
338
- requirementConfig.type,
339
- `No installer found for requirement type: ${requirementConfig.type}`
340
- );
341
- return;
342
- }
343
-
344
- const operationId = this.generateOperationId();
345
- await this.updateRequirementProgressStatus(
346
- categoryName,
347
- requirement.name,
348
- requirementConfig.type,
349
- operationId
350
- );
351
-
352
- const installStatus = await installer.install(requirementConfig, options);
353
- await this.updateRequirementCompletionStatus(
354
- categoryName,
355
- requirement.name,
356
- installStatus,
357
- operationId
358
- );
359
- } catch (error) {
360
- await this.updateRequirementFailureStatus(
361
- categoryName,
362
- requirement.name,
363
- 'unknown',
364
- error instanceof Error ? error.message : String(error)
365
- );
366
- }
367
- }));
95
+ if (requirementsResult && !requirementsResult.success) {
96
+ await recoder.recordStep('RequirementInstallationCheck', 'failed', requirementsResult.error?.message || requirementsResult.message || 'Requirement installation failed.');
97
+ return requirementsResult;
368
98
  }
369
- }
370
-
371
- /**
372
- * Helper to update requirement status for failure case
373
- */
374
- private async updateRequirementFailureStatus(
375
- categoryName: string,
376
- requirementName: string,
377
- requirementType: string,
378
- errorMessage: string
379
- ): Promise<void> {
380
- const configProvider = ConfigurationProvider.getInstance();
381
- await configProvider.updateRequirementStatus(categoryName, requirementName, {
382
- name: requirementName,
383
- type: requirementType,
384
- installed: false,
385
- error: errorMessage,
386
- operationStatus: {
387
- status: 'failed',
388
- type: 'install',
389
- target: 'requirement',
390
- message: `Error installing requirement: ${errorMessage}`,
391
- operationId: this.generateOperationId()
392
- }
393
- });
394
- }
395
99
 
396
- /**
397
- * Helper to update requirement status for in-progress case
398
- */
399
- private async updateRequirementProgressStatus(
400
- categoryName: string,
401
- requirementName: string,
402
- requirementType: string,
403
- operationId: string
404
- ): Promise<void> {
405
- const configProvider = ConfigurationProvider.getInstance();
406
- await configProvider.updateRequirementStatus(categoryName, requirementName, {
407
- name: requirementName,
408
- type: requirementType,
409
- installed: false,
410
- inProgress: true,
411
- operationStatus: {
412
- status: 'in-progress',
413
- type: 'install',
414
- target: 'requirement',
415
- message: `Installing requirement: ${requirementName}`,
416
- operationId
417
- }
418
- });
419
- }
420
-
421
- /**
422
- * Helper to update requirement status for completion case
423
- */
424
- private async updateRequirementCompletionStatus(
425
- categoryName: string,
426
- requirementName: string,
427
- installStatus: RequirementStatus,
428
- operationId: string
429
- ): Promise<void> {
430
- const configProvider = ConfigurationProvider.getInstance();
431
- await configProvider.updateRequirementStatus(categoryName, requirementName, {
432
- ...installStatus,
433
- operationStatus: {
434
- status: installStatus.installed ? 'completed' : 'failed',
435
- type: 'install',
436
- target: 'requirement',
437
- message: installStatus.installed
438
- ? `Requirement ${requirementName} installed successfully`
439
- : `Failed to install ${requirementName}`,
440
- operationId
441
- }
442
- });
100
+ return await clientInstaller.install(options);
443
101
  }
444
102
  }
445
-
446
- // Export a singleton instance (optional)
447
- // export const installationService = new InstallationService();