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.
Files changed (46) hide show
  1. package/dist/cli/commands/serve.js +2 -1
  2. package/dist/core/installers/clients/BaseClientInstaller.d.ts +25 -2
  3. package/dist/core/installers/clients/BaseClientInstaller.js +121 -0
  4. package/dist/core/installers/clients/ClineInstaller.d.ts +1 -6
  5. package/dist/core/installers/clients/ClineInstaller.js +1 -94
  6. package/dist/core/installers/clients/GithubCopilotInstaller.d.ts +1 -6
  7. package/dist/core/installers/clients/GithubCopilotInstaller.js +1 -94
  8. package/dist/core/installers/clients/MSRooCodeInstaller.d.ts +1 -5
  9. package/dist/core/installers/clients/MSRooCodeInstaller.js +1 -94
  10. package/dist/core/loaders/ConfigurationLoader.d.ts +4 -1
  11. package/dist/core/loaders/ConfigurationLoader.js +24 -3
  12. package/dist/core/loaders/ConfigurationProvider.d.ts +4 -1
  13. package/dist/core/loaders/ConfigurationProvider.js +13 -4
  14. package/dist/core/loaders/ServerSchemaLoader.d.ts +15 -4
  15. package/dist/core/loaders/ServerSchemaLoader.js +86 -20
  16. package/dist/core/loaders/ServerSchemaProvider.d.ts +2 -5
  17. package/dist/core/loaders/ServerSchemaProvider.js +32 -62
  18. package/dist/core/metadatas/types.d.ts +3 -2
  19. package/dist/core/onboard/FeedOnboardService.d.ts +14 -7
  20. package/dist/core/onboard/FeedOnboardService.js +214 -129
  21. package/dist/core/onboard/OnboardProcessor.d.ts +7 -1
  22. package/dist/core/onboard/OnboardProcessor.js +52 -8
  23. package/dist/core/onboard/OnboardStatus.d.ts +6 -1
  24. package/dist/core/onboard/OnboardStatusManager.d.ts +70 -24
  25. package/dist/core/onboard/OnboardStatusManager.js +230 -46
  26. package/dist/core/validators/FeedValidator.d.ts +7 -2
  27. package/dist/core/validators/FeedValidator.js +61 -7
  28. package/dist/core/validators/StdioServerValidator.js +84 -32
  29. package/dist/services/MCPManager.d.ts +2 -1
  30. package/dist/services/MCPManager.js +5 -1
  31. package/dist/services/ServerService.js +2 -2
  32. package/dist/utils/logger.d.ts +2 -0
  33. package/dist/utils/logger.js +10 -0
  34. package/dist/web/public/js/modal/installation.js +1 -1
  35. package/dist/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +41 -9
  36. package/dist/web/public/js/onboard/formProcessor.js +200 -34
  37. package/dist/web/public/js/onboard/index.js +2 -2
  38. package/dist/web/public/js/onboard/publishHandler.js +30 -22
  39. package/dist/web/public/js/onboard/templates.js +34 -40
  40. package/dist/web/public/js/onboard/uiHandlers.js +175 -84
  41. package/dist/web/public/js/onboard/validationHandlers.js +147 -64
  42. package/dist/web/public/js/serverCategoryDetails.js +19 -4
  43. package/dist/web/public/js/serverCategoryList.js +13 -1
  44. package/dist/web/public/onboard.html +1 -1
  45. package/dist/web/server.js +19 -6
  46. package/package.json +1 -1
@@ -1,15 +1,10 @@
1
1
  import { configProvider } from '../loaders/ConfigurationProvider.js';
2
+ import { ServerSchemaProvider } from '../loaders/ServerSchemaProvider.js';
2
3
  import { feedValidator } from '../validators/FeedValidator.js';
3
4
  import { Logger } from '../../utils/logger.js';
4
5
  import { OnboardingProcessStatus } from './OnboardStatus.js';
5
6
  import { onboardStatusManager } from './OnboardStatusManager.js';
6
7
  import { onboardProcessor } from './OnboardProcessor.js';
7
- const NON_COMPLETED_ONBOARDING_STATUSES = [
8
- OnboardingProcessStatus.PENDING,
9
- OnboardingProcessStatus.VALIDATING,
10
- OnboardingProcessStatus.VALIDATED,
11
- OnboardingProcessStatus.PR_CREATING,
12
- ];
13
8
  /**
14
9
  * Service for handling feed onboarding operations
15
10
  */
@@ -18,15 +13,49 @@ export class FeedOnboardService {
18
13
  constructor() {
19
14
  // No shared tempDir or repoDir at instance level
20
15
  }
16
+ /**
17
+ * Creates an operation ID that combines feed name and operation type.
18
+ * @param feedName The name of the feed.
19
+ * @param operationType The type of operation.
20
+ * @returns Combined operation ID string.
21
+ */
22
+ createOperationId(feedName, operationType) {
23
+ return `${feedName}_${operationType}`;
24
+ }
21
25
  /**
22
26
  * Onboard a new feed configuration
23
27
  * @param config Feed configuration to onboard
24
28
  */
25
29
  async onboardFeed(config, forExistingCategory) {
26
30
  // Perform static validation first
27
- await this.validateStaticConfig(config, forExistingCategory);
28
- const operationStatus = await this._initiateOperation(config, 'FULL_ONBOARDING', forExistingCategory);
29
- return { ...operationStatus, feedConfiguration: config };
31
+ const serverList = await this.validateStaticConfig(config, forExistingCategory);
32
+ // Check for an existing, succeeded VALIDATION_ONLY operation with an identical configuration.
33
+ const existingSucceededValidation = await onboardStatusManager.findSucceededOperation(config.name, 'VALIDATION_ONLY', config);
34
+ if (!existingSucceededValidation) {
35
+ // If no matching successful validation is found, instruct user to validate first.
36
+ Logger.log(`Feed ${config.name} has not been successfully validated with the current configuration. Publishing requires prior successful validation.`);
37
+ // Return a specific status/message indicating validation is required.
38
+ // This is not a FAILED state for an operation, but a prerequisite not met.
39
+ // We'll use a custom message and potentially a specific status if we define one for "PREREQUISITE_FAILED" or similar.
40
+ // For now, let's return a FAILED status for the attempted FULL_ONBOARDING initiation.
41
+ // A more robust solution might involve a different return type or error code.
42
+ const onboardingId = this.createOperationId(config.name, 'FULL_ONBOARDING'); // Hypothetical ID
43
+ return {
44
+ onboardingId: onboardingId,
45
+ status: OnboardingProcessStatus.FAILED, // Or a new status like 'VALIDATION_REQUIRED'
46
+ message: 'The feed configuration has not been validated or has changed. Please validate the configuration before publishing.',
47
+ lastQueried: new Date().toISOString(),
48
+ feedConfiguration: config
49
+ };
50
+ }
51
+ Logger.log(`Found existing successful validation for feed: ${config.name}. Proceeding with full onboarding.`);
52
+ // If validation exists and matches, proceed to initiate FULL_ONBOARDING.
53
+ // _initiateOperation will handle checks for existing non-completed or successfully completed FULL_ONBOARDING operations.
54
+ const operationStatus = await this._initiateOperation(config, 'FULL_ONBOARDING', serverList, forExistingCategory);
55
+ return {
56
+ ...operationStatus,
57
+ feedConfiguration: config
58
+ };
30
59
  }
31
60
  /**
32
61
  * Validate a feed configuration without performing full onboarding
@@ -35,8 +64,8 @@ export class FeedOnboardService {
35
64
  */
36
65
  async validateFeed(config, forExistingCategory) {
37
66
  // Perform static validation first
38
- await this.validateStaticConfig(config, forExistingCategory);
39
- const operationStatus = await this._initiateOperation(config, 'VALIDATION_ONLY', forExistingCategory);
67
+ const serverList = await this.validateStaticConfig(config, forExistingCategory);
68
+ const operationStatus = await this._initiateOperation(config, 'VALIDATION_ONLY', serverList, forExistingCategory);
40
69
  return { ...operationStatus, feedConfiguration: config };
41
70
  }
42
71
  /**
@@ -45,13 +74,13 @@ export class FeedOnboardService {
45
74
  * If an existing operation is found, its status is returned. Otherwise, a new operation is created and started.
46
75
  * @param config The feed configuration.
47
76
  * @param operationType The type of operation to initiate (FULL_ONBOARDING or VALIDATION_ONLY).
77
+ * @param serverList The list of server names to process.
48
78
  * @returns A promise that resolves to the operation status.
49
79
  */
50
- async _initiateOperation(config, operationType, forExistingCategory) {
51
- let existingOperation = await this._findExistingNonCompletedOperation(config.name, operationType);
80
+ async _initiateOperation(config, operationType, serverList, forExistingCategory) {
81
+ // First, check for existing non-completed operations
82
+ let existingOperation = await onboardStatusManager._findExistingNonCompletedOperation(config.name, operationType);
52
83
  if (existingOperation) {
53
- // OnboardStatus has a lastUpdated field (ISO string)
54
- // This field is updated by OnboardStatusManager.updateStatus
55
84
  const fiveMinutesInMs = 5 * 60 * 1000;
56
85
  const lastUpdateTimestamp = existingOperation.lastUpdated ? new Date(existingOperation.lastUpdated).getTime() : 0;
57
86
  const currentTime = new Date().getTime();
@@ -61,39 +90,52 @@ export class FeedOnboardService {
61
90
  }
62
91
  else {
63
92
  Logger.log(`[${existingOperation.onboardingId}] Found existing non-completed ${operationType} operation for feed: ${config.name}. Returning its status.`);
93
+ const lastStep = existingOperation.steps && existingOperation.steps.length > 0 ? existingOperation.steps[existingOperation.steps.length - 1].stepName : 'N/A';
64
94
  return {
65
95
  onboardingId: existingOperation.onboardingId,
66
96
  status: existingOperation.status,
67
- message: `An ${operationType} process for this feed (${existingOperation.onboardingId}) is already in status: ${existingOperation.status}. Current step: ${existingOperation.currentStep || 'N/A'}`,
97
+ message: `An ${operationType} process for this feed (${existingOperation.onboardingId}) is already in status: ${existingOperation.status}. Last step: ${lastStep}`,
68
98
  lastQueried: new Date().toISOString(),
69
99
  };
70
100
  }
71
101
  }
72
- // serverName is removed
102
+ // Then, check for successful operations with matching configuration
103
+ const succeededOperation = await onboardStatusManager.findSucceededOperation(config.name, operationType, config);
104
+ if (succeededOperation) {
105
+ Logger.log(`[${succeededOperation.onboardingId}] Found existing successful ${operationType} operation for feed: ${config.name} with matching configuration.`);
106
+ const operationStatus = {
107
+ onboardingId: succeededOperation.onboardingId,
108
+ status: succeededOperation.status,
109
+ message: `A successful ${operationType} already exists for this feed with identical configuration.`,
110
+ lastQueried: new Date().toISOString(),
111
+ };
112
+ if (config) {
113
+ operationStatus.feedConfiguration = config;
114
+ }
115
+ return operationStatus;
116
+ }
117
+ // If no existing or matching operations found, create a new one
118
+ const onboardingId = this.createOperationId(config.name, operationType);
73
119
  const initialStatus = await onboardStatusManager.createInitialStatus(config.name, operationType);
74
- const onboardingId = initialStatus.onboardingId;
75
- await onboardStatusManager.saveFeedConfiguration(onboardingId, config);
120
+ // Use config.name as categoryName for saveFeedConfiguration
121
+ await onboardStatusManager.saveFeedConfiguration(config.name, operationType, config);
76
122
  if (operationType === 'FULL_ONBOARDING') {
77
- this.processFullOnboarding(onboardingId, config).catch(async (error) => {
123
+ // For FULL_ONBOARDING, we assume all servers defined in the config are to be processed for PR creation.
124
+ // The serverList from validateStaticConfig primarily influences validation.
125
+ // If specific servers need to be excluded from the PR, this logic might need adjustment.
126
+ this.processFullOnboarding(onboardingId, config, operationType, serverList).catch(async (error) => {
78
127
  Logger.error(`[${onboardingId}] Full feed onboarding process failed:`, error);
79
- await onboardStatusManager.updateStatus(onboardingId, {
80
- status: OnboardingProcessStatus.FAILED,
81
- errorMessage: error instanceof Error ? error.message : String(error),
82
- currentStep: 'Failed during full onboarding process',
83
- prInfo: undefined,
84
- });
128
+ await onboardStatusManager.recordStep(config.name, operationType, 'Failed during full onboarding process', undefined, // serverName
129
+ OnboardingProcessStatus.FAILED, error instanceof Error ? error.message : String(error));
85
130
  });
86
131
  }
87
132
  else if (operationType === 'VALIDATION_ONLY') {
88
- this.processValidationOnly(onboardingId, config).catch(async (error) => {
133
+ this.processValidationOnly(onboardingId, config, operationType, serverList).catch(async (error) => {
89
134
  Logger.error(`[${onboardingId}] Feed validation process failed:`, error);
90
- await onboardStatusManager.updateStatus(onboardingId, {
91
- status: OnboardingProcessStatus.FAILED,
92
- errorMessage: error instanceof Error ? error.message : String(error),
93
- currentStep: 'Failed during validation process',
94
- validationStatus: error.validationStatus || { isValid: false, message: error instanceof Error ? error.message : String(error) },
95
- prInfo: undefined,
96
- });
135
+ await onboardStatusManager.recordStep(config.name, operationType, 'Failed during validation process', undefined, // serverName
136
+ OnboardingProcessStatus.FAILED, error instanceof Error ? error.message : String(error)
137
+ // TODO: Consider how to best store/pass 'validationStatus' if it's still needed at this specific failure point
138
+ );
97
139
  });
98
140
  }
99
141
  return {
@@ -103,23 +145,11 @@ export class FeedOnboardService {
103
145
  lastQueried: new Date().toISOString(),
104
146
  };
105
147
  }
106
- /**
107
- * Finds an existing non-completed operation for a given feed name, server name, and operation type.
108
- * @param feedName The name of the feed.
109
- * @param operationType The type of operation.
110
- * @returns A promise that resolves to the OnboardStatus of the existing operation, or undefined if not found.
111
- */
112
- async _findExistingNonCompletedOperation(feedName, operationType) {
113
- const allStatuses = await onboardStatusManager.getAllStatuses();
114
- return Object.values(allStatuses).find((status) => status.feedName === feedName &&
115
- // status.serverName === serverName && // serverName is removed
116
- status.operationType === operationType &&
117
- NON_COMPLETED_ONBOARDING_STATUSES.includes(status.status));
118
- }
119
148
  /**
120
149
  * Performs static validation of the feed configuration.
121
150
  * @param config The feed configuration to validate.
122
151
  * @param forExistingCategory Whether this is for an existing category.
152
+ * @returns A promise that resolves to a list of server names to be processed.
123
153
  * @throws Error if validation fails.
124
154
  */
125
155
  async validateStaticConfig(config, forExistingCategory) {
@@ -138,88 +168,111 @@ export class FeedOnboardService {
138
168
  }
139
169
  // --- End of moved static validation ---
140
170
  const existingConfig = await configProvider.getFeedConfiguration(config.name);
171
+ // Note: operationType validation is handled at the MCP server level
172
+ let serverListToProcess;
141
173
  if (forExistingCategory) {
142
174
  if (!existingConfig) {
143
175
  throw new Error(`Cannot update non-existent category: ${config.name}. Please ensure the category name is correct or create it as a new category.`);
144
176
  }
145
- // Original logic from existing validateStaticConfig:
146
- // This implies that for an existing category, the payload must define mcpServers,
147
- // and at least one of them must be new.
148
- // If config.mcpServers is empty, newServersInPayload.length will be 0, leading to an error.
177
+ // For existing categories, mcpServers list must be present in the payload.
178
+ if (!config.mcpServers || config.mcpServers.length === 0) {
179
+ throw new Error('For an existing category, "mcpServers" must be provided and non-empty in the payload.');
180
+ }
149
181
  const existingServerNames = new Set(existingConfig.mcpServers.map((s) => s.name));
150
- const newServersInPayload = config.mcpServers.filter((s) => !existingServerNames.has(s.name));
151
- if (newServersInPayload.length === 0) {
152
- // This error triggers if config.mcpServers is empty, or if all servers in config.mcpServers already exist.
153
- throw new Error('No new servers provided for the existing category. Ensure the "mcpServers" list in your payload contains at least one server not already in this category, or that the list is not empty if you intend to add servers.');
182
+ // Servers to process are those that are new OR existing ones marked as adhoc in the payload.
183
+ const serversToProcessInPayload = config.mcpServers.filter(payloadServer => {
184
+ const isNew = !existingServerNames.has(payloadServer.name);
185
+ const isAdhocInPayload = payloadServer.systemTags?.adhoc === 'true';
186
+ return isNew || (existingServerNames.has(payloadServer.name) && isAdhocInPayload);
187
+ });
188
+ if (serversToProcessInPayload.length === 0) {
189
+ // If no new or editable (adhoc) servers are found in the payload for an existing category, it's an error.
190
+ throw new Error(`No new or adhoc (editable) servers provided for the existing category "${config.name}". Ensure the "mcpServers" list in your payload contains at least one server not already in this category or an existing server marked with adhoc:true.`);
154
191
  }
192
+ serverListToProcess = serversToProcessInPayload.map(s => s.name);
155
193
  }
156
194
  else { // For new categories
157
195
  if (existingConfig) {
158
- throw new Error(`Category "${config.name}" already exists. To modify an existing category, please indicate that it is for an existing category.`);
196
+ // If it's not for an existing category, but the category name already exists, this is an error.
197
+ // Adhoc tags on servers are not relevant here as we are creating a new category.
198
+ throw new Error(`Category "${config.name}" already exists. To modify an existing category, please set 'forExistingCategory' to true.`);
159
199
  }
200
+ // For new categories, mcpServers list must be present and non-empty.
160
201
  if (!config.mcpServers || config.mcpServers.length === 0) {
161
202
  throw new Error('Server configuration ("mcpServers") must be provided and non-empty for new categories.');
162
203
  }
204
+ // For new categories, all servers in the payload are considered.
205
+ serverListToProcess = config.mcpServers.map(s => s.name);
163
206
  }
207
+ // Remove systemTags from individual servers
208
+ if (config.mcpServers && Array.isArray(config.mcpServers)) {
209
+ config.mcpServers.forEach(server => {
210
+ if (server.systemTags) {
211
+ delete server.systemTags;
212
+ }
213
+ });
214
+ }
215
+ // Remove systemTags and PullRequest from the main config object
216
+ if (config.systemTags) {
217
+ delete config.systemTags;
218
+ }
219
+ if (config.PullRequest) {
220
+ delete config.PullRequest;
221
+ }
222
+ return serverListToProcess;
164
223
  }
165
224
  /**
166
225
  * Validates the feed configuration.
167
226
  * Updates the onboarding status to VALIDATING, then to VALIDATED or FAILED based on the validation result.
168
227
  * @param onboardingId The ID of the onboarding process.
169
228
  * @param config The feed configuration to validate.
229
+ * @param operationType The type of operation this validation is for.
230
+ * @param serverList The list of server names to validate.
170
231
  * @throws Error if validation fails, to be caught by the calling process.
171
232
  */
172
- async _validateFeedConfiguration(onboardingId, config) {
173
- await onboardStatusManager.updateStatus(onboardingId, { status: OnboardingProcessStatus.VALIDATING, currentStep: 'Validating feed configuration' });
233
+ async _validateFeedConfiguration(onboardingId, config, operationType, serverList) {
234
+ await onboardStatusManager.recordStep(config.name, operationType, 'Validating feed configuration', undefined, OnboardingProcessStatus.VALIDATING);
174
235
  let overallValidationStatus = { isValid: true, serverResults: [] };
175
236
  try {
176
- feedValidator.validate(config);
177
- // Then, validate each MCP server
178
- for (const server of config.mcpServers) {
237
+ await feedValidator.validate(config, config.name, operationType);
238
+ // Then, validate each MCP server specified in the serverList
239
+ for (const serverConfig of config.mcpServers) {
240
+ if (!serverList.includes(serverConfig.name)) {
241
+ Logger.log(`[${onboardingId}] Skipping validation for server ${serverConfig.name} as it's not in the specified list for this operation.`);
242
+ // Optionally, record a skipped status or add to serverResults as skipped
243
+ overallValidationStatus.serverResults?.push({ serverName: serverConfig.name, isValid: true, message: "Skipped (existed)" });
244
+ continue;
245
+ }
179
246
  try {
180
- await feedValidator.validateServer(server, config);
181
- overallValidationStatus.serverResults?.push({ serverName: server.name, isValid: true });
182
- await onboardStatusManager.updateStatus(onboardingId, {
183
- currentStep: `Validated server: ${server.name}`,
184
- validationStatus: overallValidationStatus // Update with intermediate results
185
- });
247
+ await feedValidator.validateServer(serverConfig, config, config.name, operationType);
248
+ overallValidationStatus.serverResults?.push({ serverName: serverConfig.name, isValid: true });
249
+ await onboardStatusManager.recordStep(config.name, operationType, `Validated server: ${serverConfig.name}`, serverConfig.name, OnboardingProcessStatus.VALIDATING);
250
+ await onboardStatusManager.updateStatus(config.name, operationType, { validationStatus: overallValidationStatus });
186
251
  }
187
252
  catch (serverValidationError) {
188
253
  overallValidationStatus.isValid = false;
189
254
  overallValidationStatus.serverResults?.push({
190
- serverName: server.name,
255
+ serverName: serverConfig.name,
191
256
  isValid: false,
192
257
  message: serverValidationError.message || String(serverValidationError)
193
258
  });
194
- // Update status immediately upon first server validation failure
195
- await onboardStatusManager.updateStatus(onboardingId, {
196
- status: OnboardingProcessStatus.FAILED,
197
- currentStep: `Validation failed for server: ${server.name}`,
198
- errorMessage: `Server ${server.name}: ${serverValidationError.message || String(serverValidationError)}`,
199
- validationStatus: overallValidationStatus
200
- });
259
+ await onboardStatusManager.recordStep(config.name, operationType, `Validation failed for server: ${serverConfig.name}`, serverConfig.name, OnboardingProcessStatus.FAILED, `Server ${serverConfig.name}: ${serverValidationError.message || String(serverValidationError)}`);
260
+ await onboardStatusManager.updateStatus(config.name, operationType, { validationStatus: overallValidationStatus });
201
261
  throw serverValidationError; // Propagate the error to fail the whole validation
202
262
  }
203
263
  }
204
- // If all servers validated successfully
205
- await onboardStatusManager.updateStatus(onboardingId, {
206
- status: OnboardingProcessStatus.VALIDATED,
207
- currentStep: 'All servers in feed configuration validated successfully',
208
- validationStatus: overallValidationStatus
209
- });
264
+ // If all *processed* servers validated successfully
265
+ await onboardStatusManager.recordStep(config.name, operationType, 'All servers in feed configuration validated successfully', undefined, OnboardingProcessStatus.VALIDATED);
266
+ await onboardStatusManager.updateStatus(config.name, operationType, { validationStatus: overallValidationStatus });
210
267
  }
211
268
  catch (error) {
212
269
  const validationError = error; // Assuming error might have validation details
213
- // This block will now primarily catch errors from the main feedValidator.validate(config)
214
- // or if a server validation error was re-thrown and not caught by the loop's catch.
215
- await onboardStatusManager.updateStatus(onboardingId, {
216
- status: OnboardingProcessStatus.FAILED,
217
- currentStep: 'Feed configuration validation failed',
218
- errorMessage: validationError.message || String(error),
270
+ await onboardStatusManager.recordStep(config.name, operationType, 'Feed configuration validation failed', undefined, OnboardingProcessStatus.FAILED, validationError.message || String(error));
271
+ await onboardStatusManager.updateStatus(config.name, operationType, {
219
272
  validationStatus: {
220
273
  isValid: false,
221
274
  message: validationError.message || String(error),
222
- serverResults: overallValidationStatus.serverResults // Include any server results gathered so far
275
+ serverResults: overallValidationStatus.serverResults // Ensure this is up-to-date
223
276
  }
224
277
  });
225
278
  throw error; // Re-throw to be caught by the calling process
@@ -231,30 +284,33 @@ export class FeedOnboardService {
231
284
  * If validation fails, the error is handled by _validateFeedConfiguration and the calling _initiateOperation method.
232
285
  * @param onboardingId The ID of the onboarding process.
233
286
  * @param config The feed configuration.
287
+ * @param operationType The type of operation (should be 'VALIDATION_ONLY').
288
+ * @param serverList The list of server names to validate.
234
289
  */
235
- async processValidationOnly(onboardingId, config, forExistingCategory) {
290
+ async processValidationOnly(onboardingId, config, operationType, serverList) {
236
291
  try {
237
- await this._validateFeedConfiguration(onboardingId, config);
238
- // _validateFeedConfiguration now handles setting VALIDATED or FAILED.
239
- // If it completes without throwing, it means validation was successful.
240
- const finalStatus = await onboardStatusManager.getStatus(onboardingId);
292
+ // Pass operationType and serverList to _validateFeedConfiguration
293
+ await this._validateFeedConfiguration(onboardingId, config, operationType, serverList);
294
+ // Use config.name and operationType for getStatus
295
+ const finalStatus = await onboardStatusManager.getStatus(config.name, operationType);
241
296
  const result = {
242
- validationStatus: finalStatus?.validationStatus || { isValid: true, serverResults: [] }, // Get the detailed status
297
+ validationStatus: finalStatus?.validationStatus || { isValid: true, serverResults: [] },
243
298
  feedConfiguration: config
244
299
  };
245
- await onboardStatusManager.updateStatus(onboardingId, {
246
- status: OnboardingProcessStatus.SUCCEEDED, // Mark as SUCCEEDED for VALIDATION_ONLY
247
- currentStep: 'Feed configuration validated successfully',
248
- errorMessage: undefined,
249
- prInfo: undefined,
250
- result: result
300
+ // Use config.name and operationType for recordStep and updateStatus
301
+ await onboardStatusManager.recordStep(config.name, operationType, 'Feed configuration validated successfully', undefined, // serverName
302
+ OnboardingProcessStatus.SUCCEEDED);
303
+ await onboardStatusManager.updateStatus(config.name, operationType, {
304
+ errorMessage: undefined, // Clear any previous error
305
+ prInfo: undefined, // Clear PR info for validation-only
306
+ result: result, // Set the result
307
+ validationStatus: result.validationStatus // Ensure validationStatus is also updated on success
251
308
  });
252
309
  Logger.log(`[${onboardingId}] Successfully validated feed: ${config.name}`);
253
310
  }
254
311
  catch (error) {
255
- // Error handling is done within _validateFeedConfiguration and by the caller of processValidationOnly
256
- Logger.error(`[${onboardingId}] Feed validation process failed for ${config.name}:`, error);
257
- // No need to re-throw here as the caller's catch block will handle it.
312
+ // Error is already logged and status updated by _validateFeedConfiguration or its callers
313
+ Logger.error(`[${onboardingId}] Feed validation process failed for ${config.name} (already handled):`, error);
258
314
  }
259
315
  }
260
316
  /**
@@ -263,47 +319,76 @@ export class FeedOnboardService {
263
319
  * Manages status updates throughout the process and handles cleanup of temporary directories.
264
320
  * @param onboardingId The ID of the onboarding process.
265
321
  * @param config The feed configuration.
322
+ * @param operationType The type of operation (should be 'FULL_ONBOARDING').
266
323
  * @throws Error if any step of the full onboarding process fails.
267
324
  */
268
- async processFullOnboarding(onboardingId, config, forExistingCategory) {
325
+ async processFullOnboarding(onboardingId, config, operationType, serverList) {
269
326
  const { tempDir, repoDir } = onboardProcessor.createDirectories(onboardingId);
270
327
  try {
271
- await this._validateFeedConfiguration(onboardingId, config);
272
- await onboardStatusManager.updateStatus(onboardingId, { status: OnboardingProcessStatus.PR_CREATING, currentStep: 'Forking repository' });
328
+ // Validation is now a prerequisite and assumed to be done if this process is initiated.
329
+ // The status should be PENDING or a similar initial state from _initiateOperation.
330
+ // We directly proceed to PR creation steps.
331
+ // The first step recorded here will likely move status from PENDING to PR_CREATING.
332
+ await onboardStatusManager.recordStep(config.name, operationType, 'Starting PR creation process (validation confirmed)', undefined, OnboardingProcessStatus.PR_CREATING);
333
+ await onboardStatusManager.recordStep(config.name, operationType, 'Forking repository', undefined, OnboardingProcessStatus.PR_CREATING);
273
334
  await onboardProcessor.forkRepo(onboardingId, repoDir);
274
- await onboardStatusManager.updateStatus(onboardingId, { status: OnboardingProcessStatus.PR_CREATING, currentStep: 'Cloning repository' });
335
+ await onboardStatusManager.recordStep(config.name, operationType, 'Cloning repository', undefined, OnboardingProcessStatus.PR_CREATING);
275
336
  await onboardProcessor.cloneRepo(onboardingId, tempDir, repoDir);
276
- await onboardStatusManager.updateStatus(onboardingId, { status: OnboardingProcessStatus.PR_CREATING, currentStep: 'Setting up branch' });
337
+ await onboardStatusManager.recordStep(config.name, operationType, 'Setting up branch', undefined, OnboardingProcessStatus.PR_CREATING);
277
338
  const branchName = await onboardProcessor.setupBranch(onboardingId, config, repoDir);
278
- await onboardStatusManager.updateStatus(onboardingId, { status: OnboardingProcessStatus.PR_CREATING, currentStep: 'Saving feed configuration to repository' });
279
- await onboardProcessor.saveFeedConfigToRepo(onboardingId, config, repoDir);
280
- await onboardStatusManager.updateStatus(onboardingId, { status: OnboardingProcessStatus.PR_CREATING, currentStep: 'Creating pull request' });
339
+ await onboardStatusManager.recordStep(config.name, operationType, 'Saving feed configuration to repository', undefined, OnboardingProcessStatus.PR_CREATING);
340
+ const { feedFilePath, categorySchemasPath } = await onboardProcessor.saveFeedConfigToRepo(onboardingId, config, repoDir, serverList);
341
+ await onboardStatusManager.recordStep(config.name, operationType, 'Creating pull request', undefined, OnboardingProcessStatus.PR_CREATING);
281
342
  const prInfo = await onboardProcessor.createPullRequest(onboardingId, config, repoDir, branchName);
282
- await onboardStatusManager.updateStatus(onboardingId, {
283
- status: OnboardingProcessStatus.SUCCEEDED,
284
- currentStep: 'Successfully onboarded',
285
- errorMessage: undefined,
343
+ // Step 3: Finalize with SUCCEEDED status for PR creation
344
+ await onboardStatusManager.recordStep(config.name, operationType, prInfo.prExists ? 'Successfully updated to existing pull request' : 'Successfully created pull request', undefined, OnboardingProcessStatus.SUCCEEDED);
345
+ await onboardStatusManager.updateStatus(config.name, operationType, {
346
+ errorMessage: undefined, // Clear any previous errors
286
347
  prInfo: prInfo,
287
- result: prInfo.url
348
+ result: prInfo.url // Store PR URL as the result
288
349
  });
289
- Logger.log(`[${onboardingId}] Successfully onboarded feed: ${config.name}`);
350
+ Logger.log(`[${onboardingId}] Successfully created/updated PR for feed: ${config.name}. PR URL: ${prInfo.url}`);
351
+ // After successful PR, re-initialize providers
352
+ try {
353
+ await onboardStatusManager.recordStep(config.name, operationType, 'Reloading server schemas', undefined, OnboardingProcessStatus.SUCCEEDED);
354
+ Logger.log(`[${onboardingId}] Initializing ServerSchemaProvider with new schemas at ${categorySchemasPath}`);
355
+ await ServerSchemaProvider.getInstance().initialize(categorySchemasPath);
356
+ Logger.log(`[${onboardingId}] ServerSchemaProvider re-initialized.`);
357
+ await onboardStatusManager.recordStep(config.name, operationType, 'Reloading configurations', undefined, OnboardingProcessStatus.SUCCEEDED);
358
+ Logger.log(`[${onboardingId}] Initializing ConfigurationProvider with new feed file at ${feedFilePath}`);
359
+ await configProvider.initialize(feedFilePath, { prLink: prInfo.url, adhocServers: serverList }); // configProvider is already an instance
360
+ Logger.log(`[${onboardingId}] ConfigurationProvider re-initialized.`);
361
+ Logger.log(`[${onboardingId}] Successfully completed full onboarding for feed: ${config.name}`);
362
+ }
363
+ catch (reinitError) {
364
+ Logger.warn(`[${onboardingId}] Failed to re-initialize providers after PR creation for feed ${config.name}:`);
365
+ Logger.warn(`Skipping re-initialization of providers due to error: ${reinitError}`);
366
+ }
290
367
  }
291
368
  catch (error) {
292
- Logger.error(`[${onboardingId}] Full feed onboarding failed:`, error);
369
+ Logger.error(`[${onboardingId}] Full feed onboarding process failed:`, error);
293
370
  const errorMessage = error instanceof Error ? error.message : String(error);
294
- let currentStep = 'Failed during full onboarding process';
295
- if (error instanceof Error && 'step' in error) {
296
- currentStep = `Failed at step: ${error.step}`;
371
+ // Check if status is already FAILED (e.g., by _validateFeedConfiguration)
372
+ // to avoid overwriting a more specific error message or step from validation.
373
+ const currentStatus = await onboardStatusManager.getStatus(config.name, operationType);
374
+ if (currentStatus?.status !== OnboardingProcessStatus.FAILED) {
375
+ let stepName = 'Failed during full onboarding process';
376
+ if (error instanceof Error && 'step' in error && typeof error.step === 'string') {
377
+ stepName = `Failed at step: ${error.step}`;
378
+ }
379
+ else if (currentStatus?.steps && currentStatus.steps.length > 0) {
380
+ // If not already failed by validation, use the last known good step before this error
381
+ stepName = `Failed after step: ${currentStatus.steps[currentStatus.steps.length - 1].stepName}`;
382
+ }
383
+ await onboardStatusManager.recordStep(config.name, operationType, stepName, undefined, // serverName
384
+ OnboardingProcessStatus.FAILED, errorMessage);
297
385
  }
298
- else if ((await onboardStatusManager.getStatus(onboardingId))?.currentStep?.includes('validation failed')) {
299
- throw error;
386
+ else {
387
+ // If already FAILED, the error and step were likely recorded by _validateFeedConfiguration.
388
+ // Log the current error context if it's different or provides more info.
389
+ Logger.log(`[${onboardingId}] Process was already marked as FAILED. Additional context - Current error: ${errorMessage}. Initial error from status: ${currentStatus.errorMessage}`);
300
390
  }
301
- await onboardStatusManager.updateStatus(onboardingId, {
302
- status: OnboardingProcessStatus.FAILED,
303
- errorMessage: errorMessage,
304
- currentStep: currentStep,
305
- });
306
- throw error;
391
+ throw error; // Re-throw to ensure the promise from processFullOnboarding is rejected.
307
392
  }
308
393
  }
309
394
  }
@@ -33,9 +33,14 @@ export declare class OnboardProcessor {
33
33
  * @param onboardingId The ID of the onboarding process.
34
34
  * @param config The feed configuration to save.
35
35
  * @param repoDir The root directory of the cloned repository.
36
+ * @param serverList The list of MCP server names to process.
37
+ * @returns A promise that resolves to an object containing the path to the saved feed file and the category-specific schemas directory.
36
38
  * @throws An error if saving the configuration fails, with a 'step' property set to 'saveFeedConfigToRepo'.
37
39
  */
38
- saveFeedConfigToRepo(onboardingId: string, config: FeedConfiguration, repoDir: string): Promise<void>;
40
+ saveFeedConfigToRepo(onboardingId: string, config: FeedConfiguration, repoDir: string, serverList: string[]): Promise<{
41
+ feedFilePath: string;
42
+ categorySchemasPath: string;
43
+ }>;
39
44
  /**
40
45
  * Creates a pull request on GitHub for the newly added feed configuration.
41
46
  * @param onboardingId The ID of the onboarding process.
@@ -65,6 +70,7 @@ export declare class OnboardProcessor {
65
70
  createPullRequest(onboardingId: string, config: FeedConfiguration, repoDir: string, branchName: string): Promise<{
66
71
  url: string;
67
72
  branchName: string;
73
+ prExists: boolean;
68
74
  }>;
69
75
  /**
70
76
  * Creates necessary directories for the onboarding process