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
@@ -43,13 +43,62 @@ let pollingIntervalId = null;
43
43
  * @param {HTMLElement} contentElement - The HTML element to update.
44
44
  */
45
45
  export function updateOperationDisplay(data, contentElement) { // Renamed from updateValidationDisplay
46
- const { status, message, validationStatus, prInfo, errorMessage: operationErrorMessage, operationType } = data;
47
- let htmlContent = `<p><strong>Overall Status:</strong> <span class="${status === 'COMPLETED' || status === 'succeeded' ? 'text-green-600' : (status === 'FAILED' ? 'text-red-600' : 'text-yellow-600')}">${status} ${(status === 'IN_PROGRESS' || status === 'PENDING' || status === 'VALIDATING' || status === 'PUBLISHING') ? "<i class='bx bx-loader-alt bx-spin ml-2'></i>" : ""}</span></p>`;
46
+ const { status, message, validationStatus, prInfo, errorMessage: operationErrorMessage, operationType, steps } = data; // Added steps
47
+ const upperStatus = typeof status === 'string' ? status.toUpperCase() : ''; // Overall status
48
+ const isInProgressOverall = upperStatus === 'PENDING' || upperStatus === 'VALIDATING' || upperStatus === 'PRCREATING' || upperStatus === 'IN_PROGRESS' || upperStatus === 'PUBLISHING';
49
+
50
+ let htmlContent = `<p><strong>Overall Status:</strong> <span class="${upperStatus === 'COMPLETED' || upperStatus === 'SUCCEEDED' ? 'text-green-600' : (upperStatus === 'FAILED' ? 'text-red-600' : 'text-yellow-600')}">${status} ${isInProgressOverall ? "<i class='bx bx-loader-alt bx-spin ml-2'></i>" : ""}</span></p>`;
48
51
  if (operationType) htmlContent += `<p><strong>Operation Type:</strong> ${operationType}</p>`;
52
+ // Show message if provided, especially useful for direct success/failure from API.
49
53
  if (message) htmlContent += `<p><strong>Message:</strong> ${message}</p>`;
50
- if (operationErrorMessage) htmlContent += `<p class="text-red-500"><strong>Error:</strong> ${operationErrorMessage}</p>`;
54
+ if (operationErrorMessage) htmlContent += `<p class="text-red-500"><strong>Overall Error:</strong> ${operationErrorMessage}</p>`;
55
+
56
+ // Display Steps
57
+ if (Array.isArray(steps) && steps.length > 0) {
58
+ const progressListId = `progress-steps-list-${contentElement.id}`; // Unique ID for the steps list
59
+ // Ensure the toggle icon reflects the initial state (expanded, so 'bx-chevron-up')
60
+ // The UL itself won't have 'hidden' class initially.
61
+ htmlContent += `
62
+ <div class="progress-section-container mt-4">
63
+ <h4 class="font-semibold mb-2 text-gray-700 cursor-pointer flex items-center progress-section-toggle" data-target-id="${progressListId}">
64
+ Progress
65
+ <i class='bx bx-chevron-up toggle-icon-progress ml-2 text-gray-500'></i>
66
+ </h4>
67
+ <ul id="${progressListId}" class="list-none space-y-2 border-l-2 border-blue-500 pl-4 py-2">`; // Added py-2 for padding
68
+ steps.forEach((step, index) => {
69
+ const stepTimestamp = new Date(step.timestamp).toLocaleString();
70
+ let stepStatusIcon = '';
71
+ let stepTextColor = 'text-gray-700'; // Default text color
72
+
73
+ const isLastStep = index === steps.length - 1;
74
+
75
+ if (step.errorMessage || step.status === 'failed') {
76
+ stepStatusIcon = "<i class='bx bx-x-circle text-red-500 mr-2'></i>";
77
+ stepTextColor = 'text-red-600';
78
+ } else if (isLastStep && isInProgressOverall) {
79
+ // Current active step if overall process is still running
80
+ stepStatusIcon = "<i class='bx bx-loader-alt bx-spin text-blue-500 mr-2'></i>";
81
+ stepTextColor = 'text-blue-700';
82
+ } else {
83
+ // Completed, non-failed step
84
+ stepStatusIcon = "<i class='bx bx-check-circle text-green-500 mr-2'></i>";
85
+ stepTextColor = 'text-gray-700'; // Keep default gray for completed steps, icon is green
86
+ }
87
+
88
+ htmlContent += `<li class="text-sm ${stepTextColor}">`;
89
+ htmlContent += `${stepStatusIcon}<strong>${step.stepName}</strong>`;
90
+ if (step.serverName) htmlContent += ` (Server: ${step.serverName})`;
91
+ htmlContent += `<span class="text-xs text-gray-500 ml-2">- ${stepTimestamp}</span>`;
92
+ if (step.errorMessage) {
93
+ htmlContent += `<p class="text-xs text-red-400 pl-6">Error: ${step.errorMessage}</p>`;
94
+ }
95
+ htmlContent += `</li>`;
96
+ });
97
+ htmlContent += '</ul>';
98
+ }
99
+
51
100
  if (validationStatus) {
52
- htmlContent += '<h4 class="font-semibold mt-3 mb-1">Detailed Validation:</h4>';
101
+ htmlContent += '<h4 class="font-semibold mt-4 mb-2 text-gray-700">Detailed Validation Results:</h4>';
53
102
  htmlContent += '<ul class="list-disc list-inside space-y-1">';
54
103
  let hasDetailedItems = false;
55
104
  // Check for serverResults array first
@@ -98,8 +147,36 @@ export function updateOperationDisplay(data, contentElement) { // Renamed from u
98
147
  htmlContent += `<p class="mt-3"><strong>PR Info:</strong> <a href="${prInfo.url}" target="_blank" class="text-blue-600 hover:underline">${prInfo.url}</a></p>`;
99
148
  }
100
149
  contentElement.innerHTML = htmlContent;
150
+ // After updating content, ensure the toggle listener is active for the new/updated progress section
151
+ if (Array.isArray(steps) && steps.length > 0) {
152
+ ensureProgressToggleListener(contentElement);
153
+ }
101
154
  }
102
155
 
156
+ /**
157
+ * Ensures that a click listener is attached to the statusContentElement
158
+ * to handle toggling the visibility of the progress steps list.
159
+ * This listener is attached only once.
160
+ * @param {HTMLElement} statusContentElement - The parent element where progress is displayed.
161
+ */
162
+ export function ensureProgressToggleListener(statusContentElement) {
163
+ if (statusContentElement && !statusContentElement.dataset.progressToggleListenerAttached) {
164
+ statusContentElement.addEventListener('click', (event) => {
165
+ const toggleHeader = event.target.closest('.progress-section-toggle');
166
+ if (toggleHeader) {
167
+ const targetId = toggleHeader.dataset.targetId;
168
+ const iconElement = toggleHeader.querySelector('.toggle-icon-progress');
169
+ // Assuming toggleSectionContent is imported from uiHandlers.js and is in scope
170
+ if (targetId && iconElement && typeof toggleSectionContent === 'function') {
171
+ toggleSectionContent(targetId, iconElement);
172
+ }
173
+ }
174
+ });
175
+ statusContentElement.dataset.progressToggleListenerAttached = 'true';
176
+ }
177
+ }
178
+
179
+
103
180
  /**
104
181
  * Polls the server for operation status.
105
182
  * @param {string} categoryName - The name of the category.
@@ -107,71 +184,74 @@ export function updateOperationDisplay(data, contentElement) { // Renamed from u
107
184
  * @param {string} validateBtnId - The ID of the validate button.
108
185
  * @param {string} publishBtnId - The ID of the publish button.
109
186
  * @param {string} operationInitiator - 'validate' or 'publish' to restore correct button text.
187
+ * @param {string} operationType - The type of operation to poll for (e.g., 'VALIDATION_ONLY', 'FULL_ONBOARDING').
188
+ * @returns {Promise<boolean>} - Promise resolving to true if polling should continue, false otherwise.
110
189
  */
111
- export async function pollOperationStatus(categoryName, contentId, validateBtnId, publishBtnId, operationInitiator) {
190
+ export async function pollOperationStatus(categoryName, contentId, validateBtnId, publishBtnId, operationInitiator, operationType) {
112
191
  const statusContentElement = document.getElementById(contentId);
192
+ ensureProgressToggleListener(statusContentElement); // Ensure listener is active during polling updates
193
+
113
194
  const validateButton = document.getElementById(validateBtnId);
114
195
  const publishButton = document.getElementById(publishBtnId);
115
196
 
116
- // Helper function to update button states based on operation status
117
- const updateButtonStates = (isCompleted = false) => {
118
- if (validateButton && publishButton) {
119
- if (isCompleted) {
120
- // On completion, enable both buttons and restore their original state
121
- validateButton.disabled = false;
122
- validateButton.innerHTML = "<i class='bx bx-check-shield mr-2'></i>Validate";
123
- validateButton.classList.remove('opacity-50');
124
- publishButton.disabled = false;
125
- publishButton.innerHTML = "<i class='bx bx-cloud-upload mr-2'></i>Publish";
126
- publishButton.classList.remove('opacity-50');
127
- } else {
128
- // During operation, handle buttons based on which operation is running
129
- if (operationInitiator === 'validate') {
130
- validateButton.disabled = true;
131
- validateButton.innerHTML = "<i class='bx bx-loader-alt bx-spin mr-2'></i>Validating...";
132
- publishButton.disabled = true;
133
- publishButton.innerHTML = "<i class='bx bx-cloud-upload mr-2'></i>Publish";
134
- publishButton.classList.add('opacity-50');
135
- } else if (operationInitiator === 'publish') {
136
- publishButton.disabled = true;
137
- publishButton.innerHTML = "<i class='bx bx-loader-alt bx-spin mr-2'></i>Publishing...";
138
- validateButton.disabled = true;
139
- validateButton.innerHTML = "<i class='bx bx-check-shield mr-2'></i>Validate";
140
- validateButton.classList.add('opacity-50');
141
- }
142
- }
143
- }
144
- };
145
-
146
- try {
147
- const response = await fetch(`/api/categories/${categoryName}/onboard/status`);
148
- const result = await response.json();
149
-
150
- if (result.success && result.data) {
197
+ // Helper function to update button states based on operation status
198
+ const updateButtonStates = (isCompleted = false) => {
199
+ if (validateButton && publishButton) {
200
+ if (isCompleted) {
201
+ // On completion, enable both buttons and restore their original state
202
+ validateButton.disabled = false;
203
+ validateButton.innerHTML = "<i class='bx bx-check-shield mr-2'></i>Validate";
204
+ validateButton.classList.remove('opacity-50');
205
+ publishButton.disabled = false;
206
+ publishButton.innerHTML = "<i class='bx bx-cloud-upload mr-2'></i>Publish";
207
+ publishButton.classList.remove('opacity-50');
208
+ } else {
209
+ // During operation, handle buttons based on which operation is running
210
+ if (operationInitiator === 'validate') {
211
+ validateButton.disabled = true;
212
+ validateButton.innerHTML = "<i class='bx bx-loader-alt bx-spin mr-2'></i>Validating...";
213
+ publishButton.disabled = true;
214
+ publishButton.innerHTML = "<i class='bx bx-cloud-upload mr-2'></i>Publish";
215
+ publishButton.classList.add('opacity-50');
216
+ } else if (operationInitiator === 'publish') {
217
+ publishButton.disabled = true;
218
+ publishButton.innerHTML = "<i class='bx bx-loader-alt bx-spin mr-2'></i>Publishing...";
219
+ validateButton.disabled = true;
220
+ validateButton.innerHTML = "<i class='bx bx-check-shield mr-2'></i>Validate";
221
+ validateButton.classList.add('opacity-50');
222
+ }
223
+ }
224
+ }
225
+ };
226
+
227
+ try {
228
+ // Add operationType as a query parameter
229
+ const response = await fetch(`/api/categories/${categoryName}/onboard/status?operationType=${encodeURIComponent(operationType)}`);
230
+ const result = await response.json();
231
+
232
+ if (result.success && result.data) {
151
233
  updateOperationDisplay(result.data, statusContentElement);
152
234
 
153
235
  const currentStatus = result.data.status ? String(result.data.status).toUpperCase() : ''; // Ensure uppercase for comparison
154
236
  // Use string literals for status comparison, include 'succeeded'
155
237
  if (currentStatus === 'COMPLETED' || currentStatus === 'FAILED' || currentStatus === 'SUCCEEDED') {
156
- clearInterval(pollingIntervalId);
157
- pollingIntervalId = null;
158
238
  updateButtonStates(true);
239
+ return false; // Stop polling
159
240
  } else {
160
241
  // Operation is still in progress
161
242
  updateButtonStates(false);
243
+ return true; // Continue polling
162
244
  }
163
245
  } else {
164
246
  statusContentElement.innerHTML = `<p class="text-red-500">Error polling status: ${result.error || 'Unknown error'}</p>`;
165
- clearInterval(pollingIntervalId);
166
- pollingIntervalId = null;
167
247
  updateButtonStates(true);
248
+ return false; // Stop polling
168
249
  }
169
250
  } catch (error) {
170
251
  console.error('Error polling operation status:', error);
171
252
  statusContentElement.innerHTML = `<p class="text-red-500">An error occurred while polling status. Please check the console.</p>`;
172
- clearInterval(pollingIntervalId);
173
- pollingIntervalId = null;
174
253
  updateButtonStates(true);
254
+ return false; // Stop polling
175
255
  }
176
256
  }
177
257
 
@@ -186,6 +266,8 @@ export async function handleValidation(event, activeTab, currentSelectedCategory
186
266
  const { panelId, contentId, formId, validateButtonId, publishButtonId } = getElementIdsByTab(activeTab);
187
267
 
188
268
  const statusContentElement = document.getElementById(contentId);
269
+ ensureProgressToggleListener(statusContentElement); // Ensure listener is active for initial display
270
+
189
271
  const onboardForm = document.getElementById(formId);
190
272
  const validateButton = document.getElementById(validateButtonId);
191
273
  const publishButton = document.getElementById(publishButtonId);
@@ -266,21 +348,11 @@ export async function handleValidation(event, activeTab, currentSelectedCategory
266
348
  if (forExistingCategoryTab && currentSelectedCategoryData) {
267
349
  finalFeedConfiguration = JSON.parse(JSON.stringify(currentSelectedCategoryData)); // Deep clone
268
350
 
269
- // Merge MCP Servers
270
- finalFeedConfiguration.mcpServers = (finalFeedConfiguration.mcpServers || []).concat(newServersData.mcpServers || []);
271
-
272
- // Merge Requirements (ensuring uniqueness)
273
- const existingReqKeys = new Set((finalFeedConfiguration.requirements || []).map(r => `${r.type}|${r.name}|${r.version}`));
274
- (newServersData.requirements || []).forEach(newReq => {
275
- const reqKey = `${newReq.type}|${newReq.name}|${newReq.version}`;
276
- if (!existingReqKeys.has(reqKey)) {
277
- if (!finalFeedConfiguration.requirements) {
278
- finalFeedConfiguration.requirements = [];
279
- }
280
- finalFeedConfiguration.requirements.push(newReq);
281
- existingReqKeys.add(reqKey);
282
- }
283
- });
351
+ // Replace MCP Servers and Requirements with the current state from the form (newServersData).
352
+ // newServersData, generated by getFormData, already contains the correct, complete list
353
+ // of servers (including original, adhoc, and newly added ones) and their derived global requirements.
354
+ finalFeedConfiguration.mcpServers = newServersData.mcpServers || [];
355
+ finalFeedConfiguration.requirements = newServersData.requirements || [];
284
356
  } else {
285
357
  finalFeedConfiguration = newServersData;
286
358
  }
@@ -305,12 +377,23 @@ export async function handleValidation(event, activeTab, currentSelectedCategory
305
377
 
306
378
  if (result.success && result.data) {
307
379
  updateOperationDisplay(result.data, statusContentElement);
308
- const categoryName = result.data.onboardingId; // This is the category name used for polling
380
+ // The onboardingId from the response is the combined ID (categoryName_operationType).
381
+ // We need the categoryName for polling, which we already have as a parameter.
382
+ // The operationType is also passed to pollOperationStatus.
383
+ const categoryNameForPolling = finalFeedConfiguration.name; // Use the original category name
384
+ const operationTypeForPolling = 'VALIDATION_ONLY'; // Validation always initiates 'VALIDATION_ONLY'
309
385
  const initialStatus = result.data.status;
310
386
 
311
387
  // Use string literals for status comparison, include 'succeeded'
312
- if (categoryName && initialStatus !== 'COMPLETED' && initialStatus !== 'FAILED' && initialStatus !== 'succeeded') {
313
- pollingIntervalId = setInterval(() => pollOperationStatus(categoryName, contentId, validateButtonId, publishButtonId, 'validate'), POLLING_INTERVAL);
388
+ const upperInitialStatus = typeof initialStatus === 'string' ? initialStatus.toUpperCase() : '';
389
+ if (categoryNameForPolling && upperInitialStatus !== 'COMPLETED' && upperInitialStatus !== 'FAILED' && upperInitialStatus !== 'SUCCEEDED') {
390
+ pollingIntervalId = setInterval(async () => {
391
+ const shouldContinue = await pollOperationStatus(categoryNameForPolling, contentId, validateButtonId, publishButtonId, 'validate', operationTypeForPolling);
392
+ if (!shouldContinue) {
393
+ clearInterval(pollingIntervalId);
394
+ pollingIntervalId = null;
395
+ }
396
+ }, POLLING_INTERVAL);
314
397
  } else {
315
398
  validateButton.disabled = false;
316
399
  validateButton.innerHTML = "<i class='bx bx-check-shield mr-2'></i>Validate";
@@ -63,9 +63,19 @@ async function showServerDetails(serverName, retryCount = 0) {
63
63
  throw new Error('Server data not found after retries');
64
64
  }
65
65
 
66
+ let pullRequestHtml = '';
67
+ if (server.feedConfiguration?.PullRequest) {
68
+ pullRequestHtml = `
69
+ <p class="mb-1 text-sm">
70
+ <span class="font-semibold">Pull Request:</span>
71
+ <a href="${server.feedConfiguration.PullRequest}" target="_blank" class="text-blue-600 hover:underline">${server.feedConfiguration.PullRequest}</a>
72
+ </p>`;
73
+ }
74
+
66
75
  detailsDiv.innerHTML = `
67
76
  <h3 class="text-xl font-semibold mb-2 text-gray-800">${server.displayName || server.name}</h3>
68
- <p class="mb-4"><span class="font-semibold">Description:</span> ${server.description || 'N/A'}</p>
77
+ <p class="mb-1"><span class="font-semibold">Description:</span> ${server.description || 'N/A'}</p>
78
+ ${pullRequestHtml}
69
79
  <div id="toolMcpsList" class="mt-4">
70
80
  <div class="animate-pulse flex space-x-4">
71
81
  <div class="flex-1 space-y-4 py-1">
@@ -180,8 +190,13 @@ async function renderServersList(serverCategory) {
180
190
  <div class="server-item-content" data-server-name="${mcpServer.name}">
181
191
  <div class="server-item-info" style="width: 100%; box-sizing: border-box;">
182
192
  <div class="server-item-header">
183
- <div class="flex items-center">
184
- <h5 class="font-semibold text-gray-800">${mcpServer.displayName || mcpServer.name}</h5>
193
+ <div class="flex items-center flex-wrap">
194
+ <h5 class="font-semibold text-gray-800 mr-2">${mcpServer.displayName || mcpServer.name}</h5>
195
+ ${mcpServer.systemTags && Object.keys(mcpServer.systemTags).length > 0 ? `
196
+ <div class="flex flex-wrap gap-1 items-center">
197
+ ${Object.entries(mcpServer.systemTags).map(([key, value]) => `<span class="text-xs bg-purple-100 text-purple-700 px-2 py-0.5 rounded-full">${key}: ${value}</span>`).join('')}
198
+ </div>
199
+ ` : ''}
185
200
  ${mcpServer.repository || serverCategory.feedConfiguration?.repository ? (() => {
186
201
  const repoUrl = mcpServer.repository || serverCategory.feedConfiguration?.repository;
187
202
  const isGithub = repoUrl.toLowerCase().includes('github.com');
@@ -201,7 +216,7 @@ async function renderServersList(serverCategory) {
201
216
  </a>`
202
217
  })() : ''}
203
218
  </div>
204
- <p class="text-sm text-gray-600 mb-1">${mcpServer.description || 'No description'}</p>
219
+ <p class="text-sm text-gray-600 mb-1 mt-1">${mcpServer.description || 'No description'}</p>
205
220
  </div>
206
221
  <div class="flex flex-col mt-3">
207
222
  <span class="text-xs font-semibold mb-2">Client Status:</span>
@@ -72,12 +72,24 @@ function renderServerCategoryList(servers) {
72
72
  }
73
73
  }
74
74
 
75
+ let systemTagsHtml = '';
76
+ if (server.feedConfiguration?.systemTags && Object.keys(server.feedConfiguration.systemTags).length > 0) {
77
+ systemTagsHtml += '<div class="flex flex-wrap gap-1 ml-2">'; // Added ml-2 for spacing
78
+ for (const [key, value] of Object.entries(server.feedConfiguration.systemTags)) {
79
+ systemTagsHtml += `<span class="text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded-full">${key}: ${value}</span>`;
80
+ }
81
+ systemTagsHtml += '</div>';
82
+ }
83
+
75
84
  return `
76
85
  <div class="server-item border border-gray-200 p-3 rounded hover:bg-gray-50 cursor-pointer transition duration-150 ease-in-out"
77
86
  onclick="navigateToCategory('${server.name}')"
78
87
  data-server-name="${server.name}">
79
88
  <h3 class="font-semibold text-gray-800">${server.displayName || server.name}</h3>
80
- <p class="text-sm text-gray-500">${statusHtml}</p>
89
+ <div class="text-sm text-gray-500 flex items-center mt-1">
90
+ ${statusHtml}
91
+ ${systemTagsHtml}
92
+ </div>
81
93
  </div>
82
94
  `;
83
95
  }).join('');
@@ -126,7 +126,7 @@
126
126
  <i class='bx bx-server mr-2 text-blue-600'></i>
127
127
  MCP Servers
128
128
  </h2>
129
- <button type="button" onclick="addServer()"
129
+ <button type="button" id="addServerBtnNewCategory"
130
130
  class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 flex items-center text-sm">
131
131
  <i class='bx bx-plus mr-2'></i>
132
132
  Add MCP Server
@@ -205,25 +205,38 @@ app.post('/api/categories/onboard/validate', async (req, res) => {
205
205
  app.get('/api/categories/:categoryName/onboard/status', async (req, res) => {
206
206
  try {
207
207
  const { categoryName } = req.params;
208
- // categoryName is now the identifier for the operation status
209
- const status = await onboardStatusManager.getStatus(categoryName);
208
+ const { operationType } = req.query;
209
+ if (!operationType) {
210
+ return res.status(400).json({
211
+ success: false,
212
+ error: 'operationType query parameter is required.'
213
+ });
214
+ }
215
+ // Validate operationType if necessary (e.g., check against known OperationType values)
216
+ // For now, we'll assume it's a valid OperationType string.
217
+ const validOperationType = operationType;
218
+ const status = await onboardStatusManager.getStatus(categoryName, validOperationType);
210
219
  if (!status) {
211
220
  return res.status(404).json({
212
221
  success: false,
213
- error: `No active operation found for category ${categoryName}`
222
+ error: `No active operation found for category ${categoryName} with operation type ${validOperationType}`
214
223
  });
215
224
  }
216
225
  // Construct the response data based on the retrieved OnboardStatus
226
+ const lastStepName = status.steps && status.steps.length > 0 ? status.steps[status.steps.length - 1].stepName : undefined;
217
227
  const responseData = {
218
- onboardingId: status.onboardingId, // This will be the categoryName
228
+ onboardingId: status.onboardingId, // This is categoryName_operationType
219
229
  status: status.status,
220
- message: status.currentStep || status.errorMessage || 'Processing...',
230
+ message: lastStepName || status.errorMessage || 'Processing...',
221
231
  lastQueried: new Date().toISOString(),
232
+ steps: status.steps, // Include the steps array in the response
222
233
  ...(status.validationStatus && { validationStatus: status.validationStatus }),
223
234
  ...(status.prInfo && { prInfo: status.prInfo }),
224
235
  ...(status.result && { result: status.result }),
225
236
  ...(status.errorMessage && { errorMessage: status.errorMessage }),
226
- ...(status.operationType && { operationType: status.operationType }),
237
+ operationType: status.operationType, // Always include operationType from the status object
238
+ // Attempt to include feedConfiguration if available, especially for SUCCEEDED VALIDATION_ONLY
239
+ ...(status.operationType === 'VALIDATION_ONLY' && status.status === OnboardingProcessStatus.SUCCEEDED && status.result?.feedConfiguration && { feedConfiguration: status.result.feedConfiguration }),
227
240
  };
228
241
  const response = {
229
242
  success: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imcp",
3
- "version": "0.0.17",
3
+ "version": "0.0.18",
4
4
  "description": "Node.js SDK for Model Context Protocol (MCP)",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",