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.
- package/dist/cli/index.js +1 -45
- package/dist/core/installers/clients/BaseClientInstaller.d.ts +1 -5
- package/dist/core/installers/clients/BaseClientInstaller.js +40 -38
- package/dist/core/installers/clients/ClientInstaller.d.ts +9 -9
- package/dist/core/installers/clients/ClientInstaller.js +105 -99
- package/dist/core/installers/requirements/BaseInstaller.d.ts +9 -1
- package/dist/core/installers/requirements/CommandInstaller.d.ts +9 -1
- package/dist/core/installers/requirements/CommandInstaller.js +46 -12
- package/dist/core/installers/requirements/GeneralInstaller.d.ts +11 -1
- package/dist/core/installers/requirements/GeneralInstaller.js +46 -10
- package/dist/core/installers/requirements/InstallerFactory.d.ts +3 -1
- package/dist/core/installers/requirements/InstallerFactory.js +3 -2
- package/dist/core/installers/requirements/NpmInstaller.d.ts +4 -2
- package/dist/core/installers/requirements/NpmInstaller.js +38 -22
- package/dist/core/installers/requirements/PipInstaller.d.ts +3 -1
- package/dist/core/installers/requirements/PipInstaller.js +58 -36
- package/dist/core/installers/requirements/RequirementInstaller.d.ts +4 -1
- package/dist/core/loaders/InstallOperationManager.d.ts +115 -0
- package/dist/core/loaders/InstallOperationManager.js +311 -0
- package/dist/core/loaders/SystemSettingsManager.d.ts +54 -0
- package/dist/core/loaders/SystemSettingsManager.js +257 -0
- package/dist/core/metadatas/recordingConstants.d.ts +44 -0
- package/dist/core/metadatas/recordingConstants.js +45 -0
- package/dist/core/metadatas/types.d.ts +21 -0
- package/dist/core/onboard/InstallOperationManager.d.ts +23 -0
- package/dist/core/onboard/InstallOperationManager.js +144 -0
- package/dist/core/onboard/OnboardStatusManager.js +2 -1
- package/dist/core/validators/StdioServerValidator.js +4 -3
- package/dist/services/InstallationService.d.ts +2 -37
- package/dist/services/InstallationService.js +45 -313
- package/dist/services/MCPManager.d.ts +1 -1
- package/dist/services/MCPManager.js +4 -58
- package/dist/services/RequirementService.d.ts +85 -12
- package/dist/services/RequirementService.js +488 -49
- package/dist/services/ServerService.d.ts +0 -6
- package/dist/services/ServerService.js +0 -74
- package/dist/utils/adoUtils.js +6 -3
- package/dist/utils/logger.js +1 -1
- package/dist/utils/macroExpressionUtils.js +3 -25
- package/dist/utils/osUtils.d.ts +22 -1
- package/dist/utils/osUtils.js +92 -1
- package/dist/utils/versionUtils.d.ts +20 -1
- package/dist/utils/versionUtils.js +51 -4
- package/dist/web/public/css/modal.css +292 -1
- package/dist/web/public/css/serverDetails.css +14 -1
- package/dist/web/public/index.html +122 -20
- package/dist/web/public/js/flights/flights.js +1 -0
- package/dist/web/public/js/modal/index.js +8 -14
- package/dist/web/public/js/modal/installModal.js +3 -4
- package/dist/web/public/js/modal/installation.js +122 -137
- package/dist/web/public/js/modal/loadingModal.js +155 -25
- package/dist/web/public/js/modal/messageQueue.js +45 -101
- package/dist/web/public/js/modal/modalSetup.js +125 -43
- package/dist/web/public/js/modal/modalUtils.js +0 -12
- package/dist/web/public/js/modal.js +23 -10
- package/dist/web/public/js/onboard/publishHandler.js +22 -20
- package/dist/web/public/js/serverCategoryDetails.js +60 -11
- package/dist/web/public/js/serverCategoryList.js +2 -2
- package/dist/web/public/js/settings.js +314 -0
- package/dist/web/public/settings.html +135 -0
- package/dist/web/public/styles.css +32 -0
- package/dist/web/server.js +82 -0
- package/memory-bank/activeContext.md +13 -1
- package/memory-bank/decisionLog.md +63 -0
- package/memory-bank/progress.md +30 -0
- package/memory-bank/systemPatterns.md +7 -0
- package/package.json +1 -1
- package/src/cli/index.ts +1 -48
- package/src/core/installers/clients/BaseClientInstaller.ts +64 -50
- package/src/core/installers/clients/ClientInstaller.ts +130 -130
- package/src/core/installers/requirements/BaseInstaller.ts +9 -1
- package/src/core/installers/requirements/CommandInstaller.ts +47 -13
- package/src/core/installers/requirements/GeneralInstaller.ts +48 -10
- package/src/core/installers/requirements/InstallerFactory.ts +4 -3
- package/src/core/installers/requirements/NpmInstaller.ts +90 -68
- package/src/core/installers/requirements/PipInstaller.ts +81 -55
- package/src/core/installers/requirements/RequirementInstaller.ts +4 -3
- package/src/core/loaders/InstallOperationManager.ts +367 -0
- package/src/core/loaders/SystemSettingsManager.ts +278 -0
- package/src/core/metadatas/recordingConstants.ts +62 -0
- package/src/core/metadatas/types.ts +23 -0
- package/src/core/onboard/OnboardStatusManager.ts +2 -1
- package/src/core/validators/StdioServerValidator.ts +4 -3
- package/src/services/InstallationService.ts +54 -399
- package/src/services/MCPManager.ts +4 -77
- package/src/services/RequirementService.ts +564 -67
- package/src/services/ServerService.ts +0 -90
- package/src/utils/adoUtils.ts +6 -4
- package/src/utils/logger.ts +1 -1
- package/src/utils/macroExpressionUtils.ts +4 -21
- package/src/utils/osUtils.ts +92 -1
- package/src/utils/versionUtils.ts +71 -19
- package/src/web/public/css/modal.css +292 -1
- package/src/web/public/css/serverDetails.css +14 -1
- package/src/web/public/index.html +122 -20
- package/src/web/public/js/flights/flights.js +1 -1
- package/src/web/public/js/modal/index.js +8 -14
- package/src/web/public/js/modal/installModal.js +3 -4
- package/src/web/public/js/modal/installation.js +122 -137
- package/src/web/public/js/modal/loadingModal.js +155 -25
- package/src/web/public/js/modal/modalSetup.js +125 -43
- package/src/web/public/js/modal/modalUtils.js +0 -12
- package/src/web/public/js/modal.js +23 -10
- package/src/web/public/js/onboard/publishHandler.js +22 -20
- package/src/web/public/js/serverCategoryDetails.js +60 -11
- package/src/web/public/js/serverCategoryList.js +5 -5
- package/src/web/public/js/settings.js +314 -0
- package/src/web/public/settings.html +135 -0
- package/src/web/public/styles.css +32 -0
- package/src/web/server.ts +85 -0
- 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:
|
|
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
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
60
|
-
|
|
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:
|
|
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
|
-
|
|
78
|
-
|
|
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
|
-
//
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
285
|
-
|
|
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();
|