imcp 0.0.17 → 0.0.18
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/commands/serve.js +2 -1
- package/dist/core/installers/clients/BaseClientInstaller.d.ts +25 -2
- package/dist/core/installers/clients/BaseClientInstaller.js +121 -0
- package/dist/core/installers/clients/ClineInstaller.d.ts +1 -6
- package/dist/core/installers/clients/ClineInstaller.js +1 -94
- package/dist/core/installers/clients/GithubCopilotInstaller.d.ts +1 -6
- package/dist/core/installers/clients/GithubCopilotInstaller.js +1 -94
- package/dist/core/installers/clients/MSRooCodeInstaller.d.ts +1 -5
- package/dist/core/installers/clients/MSRooCodeInstaller.js +1 -94
- package/dist/core/loaders/ConfigurationLoader.d.ts +4 -1
- package/dist/core/loaders/ConfigurationLoader.js +24 -3
- package/dist/core/loaders/ConfigurationProvider.d.ts +4 -1
- package/dist/core/loaders/ConfigurationProvider.js +13 -4
- package/dist/core/loaders/ServerSchemaLoader.d.ts +15 -4
- package/dist/core/loaders/ServerSchemaLoader.js +86 -20
- package/dist/core/loaders/ServerSchemaProvider.d.ts +2 -5
- package/dist/core/loaders/ServerSchemaProvider.js +32 -62
- package/dist/core/metadatas/types.d.ts +3 -2
- package/dist/core/onboard/FeedOnboardService.d.ts +14 -7
- package/dist/core/onboard/FeedOnboardService.js +214 -129
- package/dist/core/onboard/OnboardProcessor.d.ts +7 -1
- package/dist/core/onboard/OnboardProcessor.js +52 -8
- package/dist/core/onboard/OnboardStatus.d.ts +6 -1
- package/dist/core/onboard/OnboardStatusManager.d.ts +70 -24
- package/dist/core/onboard/OnboardStatusManager.js +230 -46
- package/dist/core/validators/FeedValidator.d.ts +7 -2
- package/dist/core/validators/FeedValidator.js +61 -7
- package/dist/core/validators/StdioServerValidator.js +84 -32
- package/dist/services/MCPManager.d.ts +2 -1
- package/dist/services/MCPManager.js +5 -1
- package/dist/services/ServerService.js +2 -2
- package/dist/utils/logger.d.ts +2 -0
- package/dist/utils/logger.js +10 -0
- package/dist/web/public/js/modal/installation.js +1 -1
- package/dist/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +41 -9
- package/dist/web/public/js/onboard/formProcessor.js +200 -34
- package/dist/web/public/js/onboard/index.js +2 -2
- package/dist/web/public/js/onboard/publishHandler.js +30 -22
- package/dist/web/public/js/onboard/templates.js +34 -40
- package/dist/web/public/js/onboard/uiHandlers.js +175 -84
- package/dist/web/public/js/onboard/validationHandlers.js +147 -64
- package/dist/web/public/js/serverCategoryDetails.js +19 -4
- package/dist/web/public/js/serverCategoryList.js +13 -1
- package/dist/web/public/onboard.html +1 -1
- package/dist/web/server.js +19 -6
- package/package.json +1 -1
|
@@ -102,15 +102,57 @@ export class OnboardProcessor {
|
|
|
102
102
|
* @param onboardingId The ID of the onboarding process.
|
|
103
103
|
* @param config The feed configuration to save.
|
|
104
104
|
* @param repoDir The root directory of the cloned repository.
|
|
105
|
+
* @param serverList The list of MCP server names to process.
|
|
106
|
+
* @returns A promise that resolves to an object containing the path to the saved feed file and the category-specific schemas directory.
|
|
105
107
|
* @throws An error if saving the configuration fails, with a 'step' property set to 'saveFeedConfigToRepo'.
|
|
106
108
|
*/
|
|
107
|
-
async saveFeedConfigToRepo(onboardingId, config, repoDir) {
|
|
109
|
+
async saveFeedConfigToRepo(onboardingId, config, repoDir, serverList) {
|
|
108
110
|
try {
|
|
109
111
|
const feedsDir = path.join(repoDir, GITHUB_REPO.feedsPath);
|
|
110
112
|
await fs.mkdir(feedsDir, { recursive: true });
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
const categoryName = config.name; // Feed name is the category name
|
|
114
|
+
const categorySchemasPath = path.join(feedsDir, 'schemas', categoryName);
|
|
115
|
+
await fs.mkdir(categorySchemasPath, { recursive: true }); // Ensure category schema directory exists
|
|
116
|
+
// Process schemas for each MCP server
|
|
117
|
+
// Create a mutable copy of mcpServers to update schema paths
|
|
118
|
+
const updatedMcpServers = [];
|
|
119
|
+
for (const server of config.mcpServers) {
|
|
120
|
+
if (!serverList.includes(server.name))
|
|
121
|
+
continue;
|
|
122
|
+
let updatedServer = { ...server }; // Shallow copy server config
|
|
123
|
+
if (updatedServer.schemas && typeof updatedServer.schemas === 'string' && updatedServer.schemas.trim() !== '') {
|
|
124
|
+
const originalSchemaPath = updatedServer.schemas;
|
|
125
|
+
const serverName = updatedServer.name;
|
|
126
|
+
const newSchemaFileName = `${serverName}.json`;
|
|
127
|
+
// Schemas are now directly under categorySchemasPath
|
|
128
|
+
const newSchemaPathInRepo = path.join(categorySchemasPath, newSchemaFileName);
|
|
129
|
+
try {
|
|
130
|
+
// Read content from the original schema path
|
|
131
|
+
const schemaContent = await fs.readFile(originalSchemaPath, 'utf-8');
|
|
132
|
+
// Write content to the new schema file in the repo
|
|
133
|
+
await fs.writeFile(newSchemaPathInRepo, schemaContent);
|
|
134
|
+
Logger.debug(`[${onboardingId}] Copied schema for server '${serverName}' from '${originalSchemaPath}' to '${newSchemaPathInRepo}'`);
|
|
135
|
+
// Update the schemas property to the new filename, as per instruction "rename schemas in McpServer as serverName].json"
|
|
136
|
+
updatedServer.schemas = newSchemaFileName;
|
|
137
|
+
}
|
|
138
|
+
catch (schemaError) {
|
|
139
|
+
const errorMsg = `Error processing schema for server '${serverName}' (source: ${originalSchemaPath}): ${schemaError instanceof Error ? schemaError.message : String(schemaError)}`;
|
|
140
|
+
Logger.error(`[${onboardingId}] ${errorMsg}`);
|
|
141
|
+
// Propagate the error to fail the onboarding step
|
|
142
|
+
throw new Error(errorMsg);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
updatedMcpServers.push(updatedServer);
|
|
146
|
+
}
|
|
147
|
+
// Create a new config object with the updated mcpServers
|
|
148
|
+
const processedConfig = {
|
|
149
|
+
...config,
|
|
150
|
+
mcpServers: updatedMcpServers
|
|
151
|
+
};
|
|
152
|
+
const feedFilePath = path.join(feedsDir, `${config.name}.json`);
|
|
153
|
+
await fs.writeFile(feedFilePath, JSON.stringify(processedConfig, null, 2));
|
|
154
|
+
Logger.debug(`[${onboardingId}] Saved feed configuration (with processed schemas) to ${feedFilePath}`);
|
|
155
|
+
return { feedFilePath, categorySchemasPath };
|
|
114
156
|
}
|
|
115
157
|
catch (error) {
|
|
116
158
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -149,7 +191,7 @@ export class OnboardProcessor {
|
|
|
149
191
|
await execAsync('git fetch upstream');
|
|
150
192
|
Logger.debug(`[${onboardingId}] Fetched from upstream.`);
|
|
151
193
|
try {
|
|
152
|
-
await execAsync('git checkout main');
|
|
194
|
+
await execAsync('git checkout main --force');
|
|
153
195
|
Logger.debug(`[${onboardingId}] Switched to existing local branch 'main'.`);
|
|
154
196
|
}
|
|
155
197
|
catch (checkoutMainError) {
|
|
@@ -159,7 +201,7 @@ export class OnboardProcessor {
|
|
|
159
201
|
errStdErr.includes('pathspec \'main\' did not match any file(s) known to git')) {
|
|
160
202
|
Logger.debug(`[${onboardingId}] Local branch 'main' not found or invalid. Attempting to create it from 'upstream/main'.`);
|
|
161
203
|
try {
|
|
162
|
-
await execAsync('git checkout -b main upstream/main');
|
|
204
|
+
await execAsync('git checkout -b main --force upstream/main');
|
|
163
205
|
Logger.debug(`[${onboardingId}] Created and switched to local branch 'main' from 'upstream/main'.`);
|
|
164
206
|
}
|
|
165
207
|
catch (createMainError) {
|
|
@@ -237,16 +279,18 @@ export class OnboardProcessor {
|
|
|
237
279
|
const body = `Add new feed configuration:\n\n- Name: ${config.name}\n- Display Name: ${config.displayName}\n- Description: ${config.description}`;
|
|
238
280
|
const username = await this.getGitHubUsername();
|
|
239
281
|
const explicitHeadRef = `${username}:${branchName}`;
|
|
240
|
-
const prCheckCommand = `gh pr list --repo ${GITHUB_REPO.repoName} --head
|
|
282
|
+
const prCheckCommand = `gh pr list --repo ${GITHUB_REPO.repoName} --head ${branchName} --author ${username} --state open --json url --limit 1`;
|
|
241
283
|
Logger.debug(`[${onboardingId}] Checking for existing PR with command: ${prCheckCommand}`);
|
|
242
284
|
const { stdout: existingPrJson } = await execAsync(prCheckCommand);
|
|
243
285
|
let prUrlToReturn;
|
|
244
286
|
const createPRCommand = `gh pr create --repo ${GITHUB_REPO.repoName} --base main --head ${explicitHeadRef} --title "${title}" --body "${body}"`;
|
|
287
|
+
let prExists = false;
|
|
245
288
|
if (existingPrJson && existingPrJson.trim() !== '[]' && existingPrJson.trim() !== '') {
|
|
246
289
|
try {
|
|
247
290
|
const prs = JSON.parse(existingPrJson);
|
|
248
291
|
if (prs.length > 0 && prs[0].url) {
|
|
249
292
|
prUrlToReturn = prs[0].url;
|
|
293
|
+
prExists = true;
|
|
250
294
|
Logger.log(`[${onboardingId}] Found existing open PR for ${explicitHeadRef} on ${GITHUB_REPO.repoName}: ${prUrlToReturn}`);
|
|
251
295
|
}
|
|
252
296
|
else {
|
|
@@ -267,7 +311,7 @@ export class OnboardProcessor {
|
|
|
267
311
|
prUrlToReturn = prUrl.trim();
|
|
268
312
|
}
|
|
269
313
|
Logger.debug(`[${onboardingId}] Pull request operation completed. PR URL: ${prUrlToReturn}`);
|
|
270
|
-
return { url: prUrlToReturn, branchName };
|
|
314
|
+
return { url: prUrlToReturn, branchName: branchName, prExists: prExists };
|
|
271
315
|
}
|
|
272
316
|
catch (error) {
|
|
273
317
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -16,7 +16,12 @@ export interface ServerValidationResult {
|
|
|
16
16
|
export interface OnboardStatus {
|
|
17
17
|
onboardingId: string;
|
|
18
18
|
status: OnboardingProcessStatus;
|
|
19
|
-
|
|
19
|
+
steps?: {
|
|
20
|
+
stepName: string;
|
|
21
|
+
timestamp: string;
|
|
22
|
+
status?: OnboardingProcessStatus;
|
|
23
|
+
errorMessage?: string;
|
|
24
|
+
}[];
|
|
20
25
|
validationStatus?: {
|
|
21
26
|
isValid: boolean;
|
|
22
27
|
message?: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FeedConfiguration } from '../metadatas/types.js';
|
|
2
|
-
import { OnboardStatus, OperationType } from './OnboardStatus.js';
|
|
2
|
+
import { OnboardStatus, OnboardingProcessStatus, OperationType } from './OnboardStatus.js';
|
|
3
3
|
export declare class OnboardStatusManager {
|
|
4
4
|
private static instance;
|
|
5
5
|
private activeCategoryOperations;
|
|
@@ -15,43 +15,89 @@ export declare class OnboardStatusManager {
|
|
|
15
15
|
* @param categoryName The name of the category (acting as operationId).
|
|
16
16
|
* @param config The FeedConfiguration to save.
|
|
17
17
|
*/
|
|
18
|
-
saveFeedConfiguration(categoryName: string, config: FeedConfiguration): Promise<void>;
|
|
18
|
+
saveFeedConfiguration(categoryName: string, operationType: OperationType, config: FeedConfiguration): Promise<void>;
|
|
19
19
|
/**
|
|
20
20
|
* Retrieves the feed configuration associated with a category's active operation.
|
|
21
21
|
* @param categoryName The name of the category (acting as operationId).
|
|
22
22
|
* @returns A promise that resolves to the FeedConfiguration, or undefined if not found.
|
|
23
23
|
*/
|
|
24
|
-
getFeedConfiguration(categoryName: string): Promise<FeedConfiguration | undefined>;
|
|
24
|
+
getFeedConfiguration(categoryName: string, operationType: OperationType): Promise<FeedConfiguration | undefined>;
|
|
25
25
|
/**
|
|
26
|
-
* Retrieves the status of a
|
|
27
|
-
* @param categoryName The name of the category
|
|
28
|
-
* @
|
|
26
|
+
* Retrieves the status of a specific operation for a category.
|
|
27
|
+
* @param categoryName The name of the category.
|
|
28
|
+
* @param operationType The type of operation.
|
|
29
|
+
* @returns A promise that resolves to the OnboardStatus, or undefined if no such operation or category.
|
|
29
30
|
*/
|
|
30
|
-
getStatus(categoryName: string): Promise<OnboardStatus | undefined>;
|
|
31
|
+
getStatus(categoryName: string, operationType: OperationType): Promise<OnboardStatus | undefined>;
|
|
31
32
|
/**
|
|
32
|
-
* Retrieves all active category operation statuses.
|
|
33
|
-
* @returns A promise that resolves to a record of all active OnboardStatus objects
|
|
33
|
+
* Retrieves all active category operation statuses, structured by category and then by operation type.
|
|
34
|
+
* @returns A promise that resolves to a record of all active OnboardStatus objects.
|
|
34
35
|
*/
|
|
35
|
-
getAllStatuses(): Promise<Record<string, OnboardStatus
|
|
36
|
+
getAllStatuses(): Promise<Record<string, Partial<Record<OperationType, OnboardStatus>>>>;
|
|
36
37
|
/**
|
|
37
|
-
* Updates the status of a
|
|
38
|
-
* If
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* @param
|
|
42
|
-
* @param
|
|
38
|
+
* Updates the status of a specific operation for a category.
|
|
39
|
+
* If a `stepName` is provided in `updates`, it will also add a new step.
|
|
40
|
+
* @param categoryName The name of the category.
|
|
41
|
+
* @param operationType The type of operation.
|
|
42
|
+
* @param updates Partial OnboardStatus object with fields to update. Can include `stepName` to record a new step.
|
|
43
|
+
* @param stepName Optional. If provided, a new step with this name will be recorded.
|
|
44
|
+
* @param stepStatus Optional. Status for the new step, if `stepName` is provided.
|
|
45
|
+
* @param errorMessage Optional. Error message for the new step, if `stepName` is provided.
|
|
43
46
|
* @returns A promise that resolves to the updated OnboardStatus.
|
|
44
47
|
*/
|
|
45
|
-
updateStatus(categoryName: string, updates: Partial<OnboardStatus
|
|
48
|
+
updateStatus(categoryName: string, operationType: OperationType, updates: Partial<Omit<OnboardStatus, 'steps'>>, // Exclude steps from direct updates here
|
|
49
|
+
stepName?: string, stepStatus?: OnboardingProcessStatus, errorMessage?: string): Promise<OnboardStatus>;
|
|
46
50
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* @param categoryName The name of the category (
|
|
50
|
-
* @param operationType The type of operation
|
|
51
|
-
* @param
|
|
52
|
-
*
|
|
53
|
-
* @
|
|
51
|
+
* A more specific method to record a step, which can also update the overall status.
|
|
52
|
+
* This is intended to replace the local `updateStatus` functions in validators/services.
|
|
53
|
+
* @param categoryName The name of the category (e.g., feed name).
|
|
54
|
+
* @param operationType The type of operation.
|
|
55
|
+
* @param stepName The description of the current step.
|
|
56
|
+
* @param serverName Optional server context for this step.
|
|
57
|
+
* @param newStatus Optional overall status to set for the operation.
|
|
58
|
+
* @param errorMessage Optional error message if this step resulted in a failure.
|
|
59
|
+
* @returns A promise that resolves to the updated OnboardStatus.
|
|
54
60
|
*/
|
|
61
|
+
recordStep(categoryName: string, operationType: OperationType, stepName: string, serverName?: string, // Added serverName to be part of the step if relevant
|
|
62
|
+
newStatus?: OnboardingProcessStatus, errorMessage?: string): Promise<OnboardStatus>;
|
|
63
|
+
/**
|
|
64
|
+
* Creates an operation ID that combines category name and operation type.
|
|
65
|
+
* @param categoryName The name of the category.
|
|
66
|
+
* @param operationType The type of operation.
|
|
67
|
+
* @returns Combined operation ID string.
|
|
68
|
+
*/
|
|
69
|
+
private createOperationId;
|
|
70
|
+
/**
|
|
71
|
+
* Creates an initial status for a new category-wide operation.
|
|
72
|
+
* The operation is identified by the categoryName.
|
|
73
|
+
* @param categoryName The name of the category (also used as feedName and operationId).
|
|
74
|
+
* @param operationType The type of operation (e.g., FULL_ONBOARDING, VALIDATION_ONLY).
|
|
75
|
+
* @param serverName Optional: if the operation has a specific server context (e.g. validating a single new server in an existing category).
|
|
76
|
+
* However, the primary operation tracking is still by categoryName.
|
|
77
|
+
* @returns A promise that resolves to the initial OnboardStatus.
|
|
78
|
+
*/
|
|
55
79
|
createInitialStatus(categoryName: string, operationType: OperationType, serverName?: string): Promise<OnboardStatus>;
|
|
80
|
+
/**
|
|
81
|
+
* Finds a succeeded operation for a given feed name and operation type that has a matching feed configuration.
|
|
82
|
+
* @param feedName The name of the feed.
|
|
83
|
+
* @param operationType The type of operation.
|
|
84
|
+
* @param currentConfig The current feed configuration to compare against.
|
|
85
|
+
* @returns A promise that resolves to the OnboardStatus of the succeeded operation, or undefined if not found.
|
|
86
|
+
*/
|
|
87
|
+
findSucceededOperation(feedName: string, operationType: OperationType, currentConfig: FeedConfiguration): Promise<OnboardStatus | undefined>;
|
|
88
|
+
/**
|
|
89
|
+
* Compares two feed configurations for equality.
|
|
90
|
+
* @param config1 First feed configuration.
|
|
91
|
+
* @param config2 Second feed configuration.
|
|
92
|
+
* @returns True if the configurations are identical, false otherwise.
|
|
93
|
+
*/
|
|
94
|
+
private isFeedConfigurationEqual;
|
|
95
|
+
/**
|
|
96
|
+
* Finds an existing non-completed operation for a given feed name, server name, and operation type.
|
|
97
|
+
* @param feedName The name of the feed.
|
|
98
|
+
* @param operationType The type of operation.
|
|
99
|
+
* @returns A promise that resolves to the OnboardStatus of the existing operation, or undefined if not found.
|
|
100
|
+
*/
|
|
101
|
+
_findExistingNonCompletedOperation(feedName: string, operationType: OperationType): Promise<OnboardStatus | undefined>;
|
|
56
102
|
}
|
|
57
103
|
export declare const onboardStatusManager: OnboardStatusManager;
|
|
@@ -4,8 +4,14 @@ import { SETTINGS_DIR } from '../metadatas/constants.js';
|
|
|
4
4
|
import { OnboardingProcessStatus } from './OnboardStatus.js';
|
|
5
5
|
import { Logger } from '../../utils/logger.js';
|
|
6
6
|
const ONBOARD_STATUS_DIR = path.join(SETTINGS_DIR, 'onboard');
|
|
7
|
-
const CATEGORY_OPERATIONS_STATUS_FILE = path.join(ONBOARD_STATUS_DIR, '
|
|
7
|
+
const CATEGORY_OPERATIONS_STATUS_FILE = path.join(ONBOARD_STATUS_DIR, 'OnboardStatus.json');
|
|
8
8
|
const FEED_CONFIG_DIR = path.join(ONBOARD_STATUS_DIR, 'feed_configs'); // Staging for feed configs during operation
|
|
9
|
+
const NON_COMPLETED_ONBOARDING_STATUSES = [
|
|
10
|
+
OnboardingProcessStatus.PENDING,
|
|
11
|
+
OnboardingProcessStatus.VALIDATING,
|
|
12
|
+
OnboardingProcessStatus.VALIDATED,
|
|
13
|
+
OnboardingProcessStatus.PR_CREATING,
|
|
14
|
+
];
|
|
9
15
|
export class OnboardStatusManager {
|
|
10
16
|
static instance;
|
|
11
17
|
activeCategoryOperations = {};
|
|
@@ -61,12 +67,13 @@ export class OnboardStatusManager {
|
|
|
61
67
|
* @param categoryName The name of the category (acting as operationId).
|
|
62
68
|
* @param config The FeedConfiguration to save.
|
|
63
69
|
*/
|
|
64
|
-
async saveFeedConfiguration(categoryName, config) {
|
|
70
|
+
async saveFeedConfiguration(categoryName, operationType, config) {
|
|
65
71
|
await this.withLock(async () => {
|
|
66
72
|
try {
|
|
67
73
|
await fs.mkdir(FEED_CONFIG_DIR, { recursive: true });
|
|
68
74
|
// Suffix `_feed.json` to distinguish from potential status files if names overlap
|
|
69
|
-
const
|
|
75
|
+
const operationId = this.createOperationId(categoryName, operationType);
|
|
76
|
+
const configPath = path.join(FEED_CONFIG_DIR, `${operationId}_feed.json`);
|
|
70
77
|
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
71
78
|
Logger.debug(`Saved feed configuration for category ${categoryName} to ${configPath}`);
|
|
72
79
|
}
|
|
@@ -81,10 +88,11 @@ export class OnboardStatusManager {
|
|
|
81
88
|
* @param categoryName The name of the category (acting as operationId).
|
|
82
89
|
* @returns A promise that resolves to the FeedConfiguration, or undefined if not found.
|
|
83
90
|
*/
|
|
84
|
-
async getFeedConfiguration(categoryName) {
|
|
91
|
+
async getFeedConfiguration(categoryName, operationType) {
|
|
85
92
|
return await this.withLock(async () => {
|
|
86
93
|
try {
|
|
87
|
-
const
|
|
94
|
+
const operationId = this.createOperationId(categoryName, operationType);
|
|
95
|
+
const configPath = path.join(FEED_CONFIG_DIR, `${operationId}_feed.json`);
|
|
88
96
|
const data = await fs.readFile(configPath, 'utf-8');
|
|
89
97
|
return JSON.parse(data);
|
|
90
98
|
}
|
|
@@ -99,76 +107,252 @@ export class OnboardStatusManager {
|
|
|
99
107
|
});
|
|
100
108
|
}
|
|
101
109
|
/**
|
|
102
|
-
* Retrieves the status of a
|
|
103
|
-
* @param categoryName The name of the category
|
|
104
|
-
* @
|
|
110
|
+
* Retrieves the status of a specific operation for a category.
|
|
111
|
+
* @param categoryName The name of the category.
|
|
112
|
+
* @param operationType The type of operation.
|
|
113
|
+
* @returns A promise that resolves to the OnboardStatus, or undefined if no such operation or category.
|
|
105
114
|
*/
|
|
106
|
-
async getStatus(categoryName) {
|
|
107
|
-
await this.loadStatuses(); // Ensure latest statuses are loaded
|
|
108
|
-
return this.activeCategoryOperations[categoryName];
|
|
115
|
+
async getStatus(categoryName, operationType) {
|
|
116
|
+
await this.loadStatuses(); // Ensure latest statuses are loaded
|
|
117
|
+
return this.activeCategoryOperations[categoryName]?.[operationType];
|
|
109
118
|
}
|
|
110
119
|
/**
|
|
111
|
-
* Retrieves all active category operation statuses.
|
|
112
|
-
* @returns A promise that resolves to a record of all active OnboardStatus objects
|
|
120
|
+
* Retrieves all active category operation statuses, structured by category and then by operation type.
|
|
121
|
+
* @returns A promise that resolves to a record of all active OnboardStatus objects.
|
|
113
122
|
*/
|
|
114
123
|
async getAllStatuses() {
|
|
115
124
|
await this.loadStatuses();
|
|
116
125
|
return this.activeCategoryOperations;
|
|
117
126
|
}
|
|
118
127
|
/**
|
|
119
|
-
* Updates the status of a
|
|
120
|
-
* If
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* @param
|
|
124
|
-
* @param
|
|
128
|
+
* Updates the status of a specific operation for a category.
|
|
129
|
+
* If a `stepName` is provided in `updates`, it will also add a new step.
|
|
130
|
+
* @param categoryName The name of the category.
|
|
131
|
+
* @param operationType The type of operation.
|
|
132
|
+
* @param updates Partial OnboardStatus object with fields to update. Can include `stepName` to record a new step.
|
|
133
|
+
* @param stepName Optional. If provided, a new step with this name will be recorded.
|
|
134
|
+
* @param stepStatus Optional. Status for the new step, if `stepName` is provided.
|
|
135
|
+
* @param errorMessage Optional. Error message for the new step, if `stepName` is provided.
|
|
125
136
|
* @returns A promise that resolves to the updated OnboardStatus.
|
|
126
137
|
*/
|
|
127
|
-
async updateStatus(categoryName, updates
|
|
138
|
+
async updateStatus(categoryName, operationType, updates, // Exclude steps from direct updates here
|
|
139
|
+
stepName, stepStatus, errorMessage) {
|
|
128
140
|
return await this.withLock(async () => {
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
141
|
+
const operationId = this.createOperationId(categoryName, operationType);
|
|
142
|
+
const categoryOps = this.activeCategoryOperations[categoryName] || {};
|
|
143
|
+
let existingStatus = categoryOps[operationType];
|
|
144
|
+
if (!existingStatus) {
|
|
145
|
+
// If no status exists, create an initial one.
|
|
146
|
+
// This might happen if updateStatus is called before createInitialStatus,
|
|
147
|
+
// though ideally createInitialStatus should be the entry point.
|
|
148
|
+
existingStatus = {
|
|
149
|
+
onboardingId: operationId,
|
|
150
|
+
feedName: categoryName,
|
|
151
|
+
status: updates.status || OnboardingProcessStatus.PENDING,
|
|
152
|
+
lastUpdated: new Date().toISOString(),
|
|
153
|
+
operationType: operationType,
|
|
154
|
+
steps: [],
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
// Apply general updates
|
|
158
|
+
let updatedStatus = {
|
|
138
159
|
...existingStatus,
|
|
139
160
|
...updates,
|
|
161
|
+
onboardingId: operationId,
|
|
162
|
+
feedName: categoryName,
|
|
163
|
+
operationType: operationType,
|
|
140
164
|
lastUpdated: new Date().toISOString(),
|
|
141
165
|
};
|
|
142
|
-
|
|
166
|
+
// If stepName is provided, add it as a new step
|
|
167
|
+
if (stepName) {
|
|
168
|
+
const newStep = {
|
|
169
|
+
stepName,
|
|
170
|
+
timestamp: new Date().toISOString(),
|
|
171
|
+
status: stepStatus ?? updates.status ?? existingStatus.status,
|
|
172
|
+
errorMessage: errorMessage ?? updates.errorMessage,
|
|
173
|
+
};
|
|
174
|
+
updatedStatus.steps = [...(existingStatus.steps || []), newStep];
|
|
175
|
+
// If the step has a specific status, it might also update the overall status
|
|
176
|
+
if (newStep.status && newStep.status !== updatedStatus.status) {
|
|
177
|
+
updatedStatus.status = newStep.status;
|
|
178
|
+
}
|
|
179
|
+
if (newStep.errorMessage && !updatedStatus.errorMessage) {
|
|
180
|
+
updatedStatus.errorMessage = newStep.errorMessage;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
categoryOps[operationType] = updatedStatus;
|
|
184
|
+
this.activeCategoryOperations[categoryName] = categoryOps;
|
|
143
185
|
await this.saveStatuses();
|
|
144
186
|
return updatedStatus;
|
|
145
187
|
});
|
|
146
188
|
}
|
|
147
189
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* @param categoryName The name of the category (
|
|
151
|
-
* @param operationType The type of operation
|
|
152
|
-
* @param
|
|
153
|
-
*
|
|
154
|
-
* @
|
|
190
|
+
* A more specific method to record a step, which can also update the overall status.
|
|
191
|
+
* This is intended to replace the local `updateStatus` functions in validators/services.
|
|
192
|
+
* @param categoryName The name of the category (e.g., feed name).
|
|
193
|
+
* @param operationType The type of operation.
|
|
194
|
+
* @param stepName The description of the current step.
|
|
195
|
+
* @param serverName Optional server context for this step.
|
|
196
|
+
* @param newStatus Optional overall status to set for the operation.
|
|
197
|
+
* @param errorMessage Optional error message if this step resulted in a failure.
|
|
198
|
+
* @returns A promise that resolves to the updated OnboardStatus.
|
|
155
199
|
*/
|
|
200
|
+
async recordStep(categoryName, operationType, stepName, serverName, // Added serverName to be part of the step if relevant
|
|
201
|
+
newStatus, errorMessage) {
|
|
202
|
+
return await this.withLock(async () => {
|
|
203
|
+
const operationId = this.createOperationId(categoryName, operationType);
|
|
204
|
+
const categoryOps = this.activeCategoryOperations[categoryName] || {};
|
|
205
|
+
let currentStatus = categoryOps[operationType];
|
|
206
|
+
if (!currentStatus) {
|
|
207
|
+
currentStatus = await this.createInitialStatus(categoryName, operationType, serverName);
|
|
208
|
+
}
|
|
209
|
+
const stepToAdd = {
|
|
210
|
+
stepName,
|
|
211
|
+
timestamp: new Date().toISOString(),
|
|
212
|
+
status: newStatus ?? (errorMessage ? OnboardingProcessStatus.FAILED : currentStatus.status), // Step inherits current or becomes FAILED
|
|
213
|
+
errorMessage: errorMessage,
|
|
214
|
+
...(serverName && { serverName }), // Include serverName in the step if provided
|
|
215
|
+
};
|
|
216
|
+
const updatedSteps = [...(currentStatus.steps || []), stepToAdd];
|
|
217
|
+
const statusUpdatePayload = {
|
|
218
|
+
steps: updatedSteps,
|
|
219
|
+
lastUpdated: new Date().toISOString(),
|
|
220
|
+
};
|
|
221
|
+
if (newStatus) {
|
|
222
|
+
statusUpdatePayload.status = newStatus;
|
|
223
|
+
}
|
|
224
|
+
else if (errorMessage && currentStatus.status !== OnboardingProcessStatus.FAILED) {
|
|
225
|
+
// If an error occurs and we're not already FAILED, mark as FAILED.
|
|
226
|
+
statusUpdatePayload.status = OnboardingProcessStatus.FAILED;
|
|
227
|
+
}
|
|
228
|
+
if (errorMessage && !currentStatus.errorMessage) { // Store the first error message
|
|
229
|
+
statusUpdatePayload.errorMessage = errorMessage;
|
|
230
|
+
}
|
|
231
|
+
if (serverName && !currentStatus.serverName) { // Store serverName if not already set at top level
|
|
232
|
+
statusUpdatePayload.serverName = serverName;
|
|
233
|
+
}
|
|
234
|
+
const updatedStatus = {
|
|
235
|
+
...currentStatus,
|
|
236
|
+
...statusUpdatePayload,
|
|
237
|
+
};
|
|
238
|
+
categoryOps[operationType] = updatedStatus;
|
|
239
|
+
this.activeCategoryOperations[categoryName] = categoryOps;
|
|
240
|
+
await this.saveStatuses();
|
|
241
|
+
return updatedStatus;
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Creates an operation ID that combines category name and operation type.
|
|
246
|
+
* @param categoryName The name of the category.
|
|
247
|
+
* @param operationType The type of operation.
|
|
248
|
+
* @returns Combined operation ID string.
|
|
249
|
+
*/
|
|
250
|
+
createOperationId(categoryName, operationType) {
|
|
251
|
+
return `${categoryName}_${operationType}`;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Creates an initial status for a new category-wide operation.
|
|
255
|
+
* The operation is identified by the categoryName.
|
|
256
|
+
* @param categoryName The name of the category (also used as feedName and operationId).
|
|
257
|
+
* @param operationType The type of operation (e.g., FULL_ONBOARDING, VALIDATION_ONLY).
|
|
258
|
+
* @param serverName Optional: if the operation has a specific server context (e.g. validating a single new server in an existing category).
|
|
259
|
+
* However, the primary operation tracking is still by categoryName.
|
|
260
|
+
* @returns A promise that resolves to the initial OnboardStatus.
|
|
261
|
+
*/
|
|
156
262
|
async createInitialStatus(categoryName, operationType, serverName) {
|
|
157
|
-
//
|
|
158
|
-
const operationId = categoryName;
|
|
263
|
+
// Create operation ID that includes both category and operation type
|
|
264
|
+
const operationId = this.createOperationId(categoryName, operationType);
|
|
159
265
|
const initialStatus = {
|
|
160
|
-
onboardingId: operationId,
|
|
161
|
-
feedName: categoryName,
|
|
162
|
-
serverName,
|
|
266
|
+
onboardingId: operationId,
|
|
267
|
+
feedName: categoryName,
|
|
268
|
+
serverName,
|
|
163
269
|
status: OnboardingProcessStatus.PENDING,
|
|
164
|
-
|
|
165
|
-
lastUpdated: new Date().toISOString(),
|
|
270
|
+
steps: [{ stepName: 'Initiated', timestamp: new Date().toISOString() }],
|
|
271
|
+
lastUpdated: new Date().toISOString(),
|
|
166
272
|
operationType,
|
|
167
273
|
errorMessage: undefined,
|
|
168
274
|
prInfo: undefined
|
|
169
275
|
};
|
|
170
|
-
//
|
|
171
|
-
|
|
276
|
+
// Save directly without using updateStatus, into the new structure
|
|
277
|
+
await this.withLock(async () => {
|
|
278
|
+
const categoryOps = this.activeCategoryOperations[categoryName] || {};
|
|
279
|
+
categoryOps[operationType] = initialStatus;
|
|
280
|
+
this.activeCategoryOperations[categoryName] = categoryOps;
|
|
281
|
+
await this.saveStatuses();
|
|
282
|
+
});
|
|
283
|
+
return initialStatus;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Finds a succeeded operation for a given feed name and operation type that has a matching feed configuration.
|
|
287
|
+
* @param feedName The name of the feed.
|
|
288
|
+
* @param operationType The type of operation.
|
|
289
|
+
* @param currentConfig The current feed configuration to compare against.
|
|
290
|
+
* @returns A promise that resolves to the OnboardStatus of the succeeded operation, or undefined if not found.
|
|
291
|
+
*/
|
|
292
|
+
async findSucceededOperation(feedName, operationType, currentConfig) {
|
|
293
|
+
return await this.withLock(async () => {
|
|
294
|
+
for (const categoryName in this.activeCategoryOperations) {
|
|
295
|
+
const operations = this.activeCategoryOperations[categoryName];
|
|
296
|
+
if (operations) {
|
|
297
|
+
const specificOperationStatus = operations[operationType];
|
|
298
|
+
if (specificOperationStatus &&
|
|
299
|
+
specificOperationStatus.feedName === feedName &&
|
|
300
|
+
specificOperationStatus.status === OnboardingProcessStatus.SUCCEEDED) {
|
|
301
|
+
// The FeedConfiguration is stored in the result of the SUCCEEDED operation
|
|
302
|
+
const savedConfig = specificOperationStatus.result?.feedConfiguration;
|
|
303
|
+
if (savedConfig && this.isFeedConfigurationEqual(savedConfig, currentConfig)) {
|
|
304
|
+
return specificOperationStatus;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return undefined;
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Compares two feed configurations for equality.
|
|
314
|
+
* @param config1 First feed configuration.
|
|
315
|
+
* @param config2 Second feed configuration.
|
|
316
|
+
* @returns True if the configurations are identical, false otherwise.
|
|
317
|
+
*/
|
|
318
|
+
isFeedConfigurationEqual(config1, config2) {
|
|
319
|
+
// Compare basic properties
|
|
320
|
+
if (config1.name !== config2.name)
|
|
321
|
+
return false;
|
|
322
|
+
// Compare arrays with specific order handling
|
|
323
|
+
const compareArrays = (arr1, arr2, sortFn) => {
|
|
324
|
+
if (arr1.length !== arr2.length)
|
|
325
|
+
return false;
|
|
326
|
+
const sorted1 = sortFn ? [...arr1].sort(sortFn) : arr1;
|
|
327
|
+
const sorted2 = sortFn ? [...arr2].sort(sortFn) : arr2;
|
|
328
|
+
return JSON.stringify(sorted1) === JSON.stringify(sorted2);
|
|
329
|
+
};
|
|
330
|
+
// Compare requirements array
|
|
331
|
+
if (!compareArrays(config1.requirements || [], config2.requirements || []))
|
|
332
|
+
return false;
|
|
333
|
+
// Compare MCP servers with name-based sorting
|
|
334
|
+
const sortByName = (a, b) => (a.name || '').localeCompare(b.name || '');
|
|
335
|
+
if (!compareArrays(config1.mcpServers || [], config2.mcpServers || [], sortByName))
|
|
336
|
+
return false;
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Finds an existing non-completed operation for a given feed name, server name, and operation type.
|
|
341
|
+
* @param feedName The name of the feed.
|
|
342
|
+
* @param operationType The type of operation.
|
|
343
|
+
* @returns A promise that resolves to the OnboardStatus of the existing operation, or undefined if not found.
|
|
344
|
+
*/
|
|
345
|
+
async _findExistingNonCompletedOperation(feedName, operationType) {
|
|
346
|
+
const allCategoryStatuses = await this.getAllStatuses();
|
|
347
|
+
// feedName is the categoryName
|
|
348
|
+
const categoryOperations = allCategoryStatuses[feedName];
|
|
349
|
+
if (categoryOperations) {
|
|
350
|
+
const specificOperationStatus = categoryOperations[operationType];
|
|
351
|
+
if (specificOperationStatus && NON_COMPLETED_ONBOARDING_STATUSES.includes(specificOperationStatus.status)) {
|
|
352
|
+
return specificOperationStatus;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return undefined;
|
|
172
356
|
}
|
|
173
357
|
}
|
|
174
358
|
// Export singleton instance
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FeedConfiguration, McpConfig } from "../metadatas/types.js";
|
|
2
|
+
import { OperationType } from "../onboard/OnboardStatus.js";
|
|
2
3
|
/**
|
|
3
4
|
* Validates feed configurations to ensure they meet required criteria
|
|
4
5
|
*/
|
|
@@ -6,15 +7,19 @@ export declare class FeedValidator {
|
|
|
6
7
|
/**
|
|
7
8
|
* Validates a feed configuration
|
|
8
9
|
* @param config The feed configuration to validate
|
|
10
|
+
* @param categoryName The name of the category (feed name) for status updates
|
|
11
|
+
* @param operationType The type of operation for status updates
|
|
9
12
|
* @returns true if valid, throws error if invalid
|
|
10
13
|
*/
|
|
11
|
-
validate(config: FeedConfiguration): boolean
|
|
14
|
+
validate(config: FeedConfiguration, categoryName: string, operationType: OperationType): Promise<boolean>;
|
|
12
15
|
/**
|
|
13
16
|
* Validates a single MCP server configuration using the appropriate validator
|
|
14
17
|
* @param server The MCP server configuration to validate
|
|
15
18
|
* @param config The feed configuration containing shared requirements
|
|
19
|
+
* @param categoryName The name of the category (feed name) for status updates
|
|
20
|
+
* @param operationType The type of operation for status updates
|
|
16
21
|
* @returns true if valid, throws error if invalid
|
|
17
22
|
*/
|
|
18
|
-
validateServer(server: McpConfig, config: FeedConfiguration): Promise<boolean>;
|
|
23
|
+
validateServer(server: McpConfig, config: FeedConfiguration, categoryName: string, operationType: OperationType): Promise<boolean>;
|
|
19
24
|
}
|
|
20
25
|
export declare const feedValidator: FeedValidator;
|