imcp 0.0.16 → 0.0.17

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 (135) hide show
  1. package/dist/cli/commands/install.js +2 -2
  2. package/dist/cli/commands/list.js +2 -2
  3. package/dist/cli/commands/serve.js +1 -1
  4. package/dist/core/RequirementService.d.ts +0 -12
  5. package/dist/core/RequirementService.js +0 -24
  6. package/dist/core/installers/clients/BaseClientInstaller.d.ts +1 -1
  7. package/dist/core/installers/clients/ClientInstaller.d.ts +1 -1
  8. package/dist/core/installers/clients/ClientInstaller.js +1 -1
  9. package/dist/core/installers/clients/ClientInstallerFactory.js +1 -1
  10. package/dist/core/installers/clients/ClineInstaller.d.ts +1 -1
  11. package/dist/core/installers/clients/ClineInstaller.js +1 -1
  12. package/dist/core/installers/clients/ExtensionInstaller.js +1 -1
  13. package/dist/core/installers/clients/GithubCopilotInstaller.d.ts +1 -1
  14. package/dist/core/installers/clients/GithubCopilotInstaller.js +1 -1
  15. package/dist/core/installers/clients/MSRooCodeInstaller.d.ts +1 -1
  16. package/dist/core/installers/clients/MSRooCodeInstaller.js +1 -1
  17. package/dist/core/installers/requirements/BaseInstaller.d.ts +1 -1
  18. package/dist/core/installers/requirements/BaseInstaller.js +1 -1
  19. package/dist/core/installers/requirements/CommandInstaller.d.ts +1 -1
  20. package/dist/core/installers/requirements/CommandInstaller.js +1 -1
  21. package/dist/core/installers/requirements/GeneralInstaller.d.ts +1 -1
  22. package/dist/core/installers/requirements/InstallerFactory.d.ts +1 -1
  23. package/dist/core/installers/requirements/NpmInstaller.d.ts +1 -1
  24. package/dist/core/installers/requirements/NpmInstaller.js +1 -1
  25. package/dist/core/installers/requirements/PipInstaller.d.ts +1 -1
  26. package/dist/core/installers/requirements/RequirementInstaller.d.ts +1 -1
  27. package/dist/core/loaders/ConfigurationLoader.d.ts +32 -0
  28. package/dist/core/loaders/ConfigurationLoader.js +236 -0
  29. package/dist/core/loaders/ConfigurationProvider.d.ts +35 -0
  30. package/dist/core/loaders/ConfigurationProvider.js +375 -0
  31. package/dist/core/loaders/ServerSchemaLoader.d.ts +11 -0
  32. package/{src/core/ServerSchemaLoader.ts → dist/core/loaders/ServerSchemaLoader.js} +43 -48
  33. package/dist/core/loaders/ServerSchemaProvider.d.ts +17 -0
  34. package/{src/core/ServerSchemaProvider.ts → dist/core/loaders/ServerSchemaProvider.js} +120 -137
  35. package/dist/core/metadatas/constants.d.ts +47 -0
  36. package/dist/core/metadatas/constants.js +94 -0
  37. package/dist/core/metadatas/types.d.ts +166 -0
  38. package/dist/core/metadatas/types.js +16 -0
  39. package/dist/core/onboard/FeedOnboardService.d.ts +1 -1
  40. package/dist/core/onboard/FeedOnboardService.js +1 -1
  41. package/dist/core/onboard/OnboardProcessor.d.ts +1 -1
  42. package/dist/core/onboard/OnboardProcessor.js +1 -1
  43. package/dist/core/onboard/OnboardStatus.d.ts +1 -1
  44. package/dist/core/onboard/OnboardStatusManager.d.ts +1 -1
  45. package/dist/core/onboard/OnboardStatusManager.js +1 -1
  46. package/dist/core/validators/FeedValidator.d.ts +1 -1
  47. package/dist/core/validators/IServerValidator.d.ts +1 -1
  48. package/dist/core/validators/SSEServerValidator.d.ts +1 -1
  49. package/dist/core/validators/ServerValidatorFactory.d.ts +1 -1
  50. package/dist/core/validators/StdioServerValidator.d.ts +1 -1
  51. package/dist/core/validators/StdioServerValidator.js +1 -1
  52. package/dist/index.d.ts +3 -3
  53. package/dist/index.js +3 -3
  54. package/dist/services/InstallationService.d.ts +50 -0
  55. package/dist/services/InstallationService.js +350 -0
  56. package/dist/services/MCPManager.d.ts +28 -0
  57. package/dist/services/MCPManager.js +188 -0
  58. package/dist/services/RequirementService.d.ts +40 -0
  59. package/dist/services/RequirementService.js +110 -0
  60. package/dist/services/ServerService.d.ts +2 -2
  61. package/dist/services/ServerService.js +5 -5
  62. package/dist/utils/adoUtils.d.ts +2 -2
  63. package/dist/utils/adoUtils.js +1 -1
  64. package/dist/utils/feedUtils.js +1 -1
  65. package/dist/utils/githubUtils.d.ts +1 -1
  66. package/dist/utils/githubUtils.js +1 -1
  67. package/dist/utils/logger.js +1 -1
  68. package/dist/utils/macroExpressionUtils.d.ts +1 -1
  69. package/dist/utils/osUtils.d.ts +1 -1
  70. package/dist/utils/osUtils.js +1 -1
  71. package/dist/web/contract/serverContract.d.ts +1 -1
  72. package/dist/web/public/index.html +1 -3
  73. package/dist/web/public/js/api.js +2 -80
  74. package/dist/web/server.js +2 -2
  75. package/package.json +1 -1
  76. package/src/cli/commands/install.ts +3 -3
  77. package/src/cli/commands/list.ts +2 -2
  78. package/src/cli/commands/serve.ts +3 -2
  79. package/src/cli/index.ts +1 -1
  80. package/src/core/installers/clients/BaseClientInstaller.ts +134 -3
  81. package/src/core/installers/clients/ClientInstaller.ts +3 -3
  82. package/src/core/installers/clients/ClientInstallerFactory.ts +1 -1
  83. package/src/core/installers/clients/ClineInstaller.ts +1 -101
  84. package/src/core/installers/clients/ExtensionInstaller.ts +1 -1
  85. package/src/core/installers/clients/GithubCopilotInstaller.ts +1 -101
  86. package/src/core/installers/clients/MSRooCodeInstaller.ts +1 -102
  87. package/src/core/installers/requirements/BaseInstaller.ts +2 -2
  88. package/src/core/installers/requirements/CommandInstaller.ts +1 -1
  89. package/src/core/installers/requirements/GeneralInstaller.ts +1 -1
  90. package/src/core/installers/requirements/InstallerFactory.ts +1 -1
  91. package/src/core/installers/requirements/NpmInstaller.ts +12 -12
  92. package/src/core/installers/requirements/PipInstaller.ts +1 -1
  93. package/src/core/installers/requirements/RequirementInstaller.ts +1 -1
  94. package/src/core/{ConfigurationLoader.ts → loaders/ConfigurationLoader.ts} +31 -7
  95. package/src/core/{ConfigurationProvider.ts → loaders/ConfigurationProvider.ts} +18 -10
  96. package/src/core/loaders/ServerSchemaLoader.ts +117 -0
  97. package/src/core/loaders/ServerSchemaProvider.ts +99 -0
  98. package/src/core/{types.ts → metadatas/types.ts} +3 -2
  99. package/src/core/onboard/FeedOnboardService.ts +270 -146
  100. package/src/core/onboard/OnboardProcessor.ts +60 -11
  101. package/src/core/onboard/OnboardStatus.ts +7 -2
  102. package/src/core/onboard/OnboardStatusManager.ts +270 -43
  103. package/src/core/validators/FeedValidator.ts +65 -9
  104. package/src/core/validators/IServerValidator.ts +1 -1
  105. package/src/core/validators/SSEServerValidator.ts +2 -2
  106. package/src/core/validators/ServerValidatorFactory.ts +1 -1
  107. package/src/core/validators/StdioServerValidator.ts +86 -34
  108. package/src/index.ts +3 -3
  109. package/src/{core → services}/InstallationService.ts +5 -5
  110. package/src/{core → services}/MCPManager.ts +10 -5
  111. package/src/{core → services}/RequirementService.ts +2 -31
  112. package/src/services/ServerService.ts +7 -7
  113. package/src/utils/adoUtils.ts +3 -3
  114. package/src/utils/feedUtils.ts +2 -2
  115. package/src/utils/githubUtils.ts +2 -2
  116. package/src/utils/logger.ts +13 -1
  117. package/src/utils/macroExpressionUtils.ts +1 -1
  118. package/src/utils/osUtils.ts +4 -4
  119. package/src/web/contract/serverContract.ts +2 -2
  120. package/src/web/public/index.html +1 -3
  121. package/src/web/public/js/api.js +2 -80
  122. package/src/web/public/js/modal/installation.js +1 -1
  123. package/src/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +41 -9
  124. package/src/web/public/js/onboard/formProcessor.js +200 -34
  125. package/src/web/public/js/onboard/index.js +2 -2
  126. package/src/web/public/js/onboard/publishHandler.js +30 -22
  127. package/src/web/public/js/onboard/templates.js +34 -40
  128. package/src/web/public/js/onboard/uiHandlers.js +175 -84
  129. package/src/web/public/js/onboard/validationHandlers.js +147 -64
  130. package/src/web/public/js/serverCategoryDetails.js +19 -4
  131. package/src/web/public/js/serverCategoryList.js +13 -1
  132. package/src/web/public/onboard.html +1 -1
  133. package/src/web/server.ts +30 -14
  134. package/src/services/InstallRequestValidator.ts +0 -112
  135. /package/src/core/{constants.ts → metadatas/constants.ts} +0 -0
@@ -167,7 +167,7 @@
167
167
 
168
168
  <!-- Module scripts -->
169
169
  <script type="module">
170
- import { fetchServerCategories, handleInstallServer, installRequirement, upgradeRequirement, uninstallTool } from './js/api.js';
170
+ import { fetchServerCategories, handleInstallServer, uninstallTool } from './js/api.js';
171
171
  import { setupSearch } from './js/serverCategoryList.js';
172
172
  import { showServerDetails } from './js/serverCategoryDetails.js';
173
173
  import { showInstallModal, closeModal, setupModalOutsideClick } from './js/modal.js';
@@ -182,8 +182,6 @@
182
182
  // If other parts of the app call window.showOnboardModal, they'll need updating too.
183
183
  window.closeModal = closeModal;
184
184
  window.handleInstallServer = handleInstallServer;
185
- window.installRequirement = installRequirement;
186
- window.upgradeRequirement = upgradeRequirement;
187
185
  window.uninstallTool = uninstallTool;
188
186
 
189
187
  // Initialize
@@ -42,7 +42,7 @@ async function fetchServerCategories() {
42
42
  async function handleInstallServer(event, categoryName, serverName, callback, options = {}) {
43
43
  event.preventDefault(); // Prevent page reload
44
44
  console.log("Handling install for:", categoryName, serverName);
45
-
45
+
46
46
  // Get selected targets from the form if not provided in options
47
47
  const selectedTargets = options.targets || [...document.querySelectorAll('input[name="targets"]:checked')]
48
48
  .map(cb => cb.value);
@@ -71,7 +71,7 @@ async function handleInstallServer(event, categoryName, serverName, callback, op
71
71
  const response = await fetch(`/api/categories/${categoryName}/install`, {
72
72
  method: 'POST',
73
73
  headers: { 'Content-Type': 'application/json' },
74
- body: JSON.stringify({serverList: serverList})
74
+ body: JSON.stringify({ serverList: serverList })
75
75
  });
76
76
 
77
77
  if (!response.ok) {
@@ -90,82 +90,6 @@ async function handleInstallServer(event, categoryName, serverName, callback, op
90
90
  }
91
91
  }
92
92
 
93
- // Install requirement handler
94
- async function installRequirement(reqName) {
95
- const detailsDiv = document.getElementById('serverCategoryDetails');
96
- const serverTitle = detailsDiv.querySelector('h3');
97
- if (!serverTitle) {
98
- showToast('No server selected.', 'error');
99
- return;
100
- }
101
- let serverName = serverTitle.textContent;
102
- if (serverName.includes(' for ')) {
103
- serverName = serverName.split(' for ')[0];
104
- }
105
- const confirmed = await showConfirm(`Do you want to install requirement '${reqName}' for ${serverName}?`);
106
- if (!confirmed) {
107
- return;
108
- }
109
-
110
- const btn = Array.from(document.querySelectorAll('button')).find(b => b.textContent.trim() === 'Install' && b.onclick?.toString().includes(reqName));
111
- if (btn) btn.disabled = true;
112
-
113
- try {
114
- const response = await fetch(`/api/categories/${serverName}/install-requirement`, {
115
- method: 'POST',
116
- headers: { 'Content-Type': 'application/json' },
117
- body: JSON.stringify({ requirementName: reqName })
118
- });
119
- if (!response.ok) {
120
- const errorData = await response.text();
121
- throw new Error(`Installation failed: ${errorData || response.statusText}`);
122
- }
123
- showToast(`Requirement '${reqName}' installation initiated successfully!`, 'success');
124
- setTimeout(() => {
125
- fetchServerCategories();
126
- window.showServerDetails(serverName);
127
- }, 1000);
128
- } catch (error) {
129
- console.error('Error installing requirement:', error);
130
- showToast(`Error installing requirement: ${error.message}. Please check console.`, 'error');
131
- } finally {
132
- if (btn) btn.disabled = false;
133
- }
134
- }
135
-
136
- // Handle upgrade/uninstall actions
137
- async function upgradeRequirement(name) {
138
- const serverTitle = document.querySelector('#serverCategoryDetails h3')?.textContent;
139
- if (!serverTitle) {
140
- showToast('No server selected.', 'error');
141
- return;
142
- }
143
-
144
- const upgradeConfirmed = await showConfirm(`Do you want to upgrade requirement '${name}' for ${serverTitle}?`);
145
- if (!upgradeConfirmed) {
146
- return;
147
- }
148
-
149
- try {
150
- const response = await fetch(`/api/categories/${serverTitle}/upgrade-requirement`, {
151
- method: 'POST',
152
- headers: { 'Content-Type': 'application/json' },
153
- body: JSON.stringify({ requirementName: name })
154
- });
155
-
156
- if (!response.ok) {
157
- const errorData = await response.text();
158
- throw new Error(`Upgrade failed: ${errorData || response.statusText}`);
159
- }
160
-
161
- showToast(`Requirement '${name}' upgrade initiated successfully!`, 'success');
162
- fetchServerCategories();
163
- window.showServerDetails(serverTitle);
164
- } catch (error) {
165
- console.error('Error upgrading requirement:', error);
166
- showToast(`Error upgrading requirement: ${error.message}`, 'error');
167
- }
168
- }
169
93
 
170
94
  async function uninstallTool(name) {
171
95
  const serverTitle = document.querySelector('#serverCategoryDetails h3')?.textContent;
@@ -204,7 +128,5 @@ export {
204
128
  allServerCategoriesData,
205
129
  fetchServerCategories,
206
130
  handleInstallServer,
207
- installRequirement,
208
- upgradeRequirement,
209
131
  uninstallTool
210
132
  };
@@ -175,7 +175,7 @@ async function pollInstallStatus(categoryName, serverName, targets, interval = 2
175
175
  delayedAppendInstallLoadingMessage(`${target}: ${msg}`);
176
176
  lastMessages[target] = msg;
177
177
  }
178
- if (status !== "completed") {
178
+ if (status !== "completed" && status !== "failed") {
179
179
  allTargetsCompleted = false;
180
180
  }
181
181
  }
@@ -168,9 +168,9 @@ A toggle switch allows users to switch between the standard form view and a raw
168
168
  - Calls `getFormData()` (from `formProcessor.js`) to get the current form data as `FeedConfiguration`.
169
169
  - Sends an initial POST request to `/api/categories/onboard` with the `FeedConfiguration` object and an `isUpdate` flag.
170
170
  - The "Publish" button shows a spinning loader icon and its text changes to "Publishing...". Both "Validate" and "Publish" buttons are disabled.
171
- - **Polling for Status**: If the initial publish request is successful and the operation is not immediately completed or failed, `handlePublish` initiates polling using the shared `pollOperationStatus` function (from `validationHandlers.js`). This function periodically calls `GET /api/categories/:categoryName/onboard/status`.
172
- - **Displaying Results**: The shared `updateOperationDisplay` function (from `validationHandlers.js`) formats and displays the results from both the initial publish call and subsequent status polls in the relevant status panel. It shows overall status, messages, PR info (if available), and detailed validation results if included in the publish response.
173
- - **Completion/Failure**: Polling stops when the status is 'COMPLETED', 'FAILED', or 'succeeded'. Upon completion or failure, the "Publish" button reverts to its original state, and both "Validate" and "Publish" buttons are re-enabled. Success and error messages are displayed using `showToast()`.
171
+ - **Polling for Status**: If the initial publish request is successful and the operation is not immediately completed or failed, `handlePublish` initiates polling. The shared `pollOperationStatus` function (from `validationHandlers.js`) is called, which periodically queries `GET /api/categories/:categoryName/onboard/status?operationType=FULL_ONBOARDING`. `pollOperationStatus` now returns a boolean to manage polling continuation.
172
+ - **Displaying Results**: The shared `updateOperationDisplay` function (from `validationHandlers.js`) formats and displays detailed progress, including individual steps, from both the initial publish call and subsequent status polls.
173
+ - **Completion/Failure**: Polling stops based on the status returned by `pollOperationStatus`. Upon completion or failure, buttons are reset, and `showToast()` displays messages. For detailed information on recent changes to polling and status display, see Section 9: "Enhanced Operation Status Polling and Display".
174
174
  - **Validation (`handleValidation` in `validationHandlers.js`)**:
175
175
  - **Validation (`handleValidation` in `validationHandlers.js`)**:
176
176
  - The `validationStatusPanel` (unique per tab) is located under the "MCP Servers" section (or equivalent) and above the main action buttons ("Validate", "Publish"). It is a foldable panel.
@@ -179,14 +179,14 @@ A toggle switch allows users to switch between the standard form view and a raw
179
179
  - Sends an initial POST request to `/api/categories/onboard/validate`.
180
180
  - The "Validate" button shows a spinning loader icon and its text changes to "Validating...". Both "Validate" and "Publish" buttons are disabled.
181
181
  - **Polling for Status**:
182
- - If the initial validation request is successful and the operation is not immediately completed or failed, `handleValidation` initiates polling using the shared `pollOperationStatus` function (from `validationHandlers.js`).
183
- - This function uses the `onboardingId` (category name) from the validation response to periodically call `GET /api/categories/:categoryName/onboard/status`.
182
+ - If the initial validation request is successful and the operation is not immediately completed or failed, `handleValidation` initiates polling.
183
+ - The shared `pollOperationStatus` function (from `validationHandlers.js`) is called, which periodically queries `GET /api/categories/:categoryName/onboard/status?operationType=VALIDATION_ONLY` (using the category name from the form). `pollOperationStatus` now returns a boolean to manage polling continuation.
184
184
  - **Displaying Results**:
185
- - The shared `updateOperationDisplay` function (from `validationHandlers.js`) formats and displays the results from both the initial validation call and subsequent status polls in the relevant status panel.
186
- - It handles different structures of the `validationStatus` object in the response, including displaying overall status, messages, and detailed results for each server if `validationStatus.serverResults` is present.
185
+ - The shared `updateOperationDisplay` function (from `validationHandlers.js`) formats and displays detailed progress, including individual steps, from both the initial validation call and subsequent status polls.
186
+ - It handles different structures of the `validationStatus` object and overall operation status.
187
187
  - **Completion/Failure**:
188
- - Polling stops when the status is 'COMPLETED', 'FAILED', or 'succeeded'.
189
- - Upon completion or failure, the "Validate" button reverts to its original state ("Validate" text and icon), and both "Validate" and "Publish" buttons are re-enabled.
188
+ - Polling stops based on the status returned by `pollOperationStatus`.
189
+ - Upon completion or failure, buttons are reset. For detailed information on recent changes to polling and status display, see Section 9: "Enhanced Operation Status Polling and Display".
190
190
  - **Copy JSON (`copyJsonToClipboard` in `uiHandlers.js`)**: Copies the content of `jsonEditorTextarea` to the clipboard. Uses `showToast()` for feedback.
191
191
  - **Global Function Exposure**: Necessary UI handler functions (like `addServer`, `removeServer`, `toggleSectionContent`, etc.) are attached to the `window` object so they can be called from `onclick` attributes in the HTML templates.
192
192
  - **Custom Notifications**: Standard browser `alert()` calls have been replaced with a custom toast notification system (`showToast()` from `../notifications.js`). This system requires an `.alert-container` div in the host HTML (`onboard.html`) and uses CSS from `css/notifications.css` for styling. The `notifications.js` module handles the creation, display, and dismissal (manual and automatic) of these toasts without relying on Bootstrap's JavaScript.
@@ -290,6 +290,38 @@ This section documents significant fixes and behavior changes implemented recent
290
290
  - **Outcome:** The "Publish" and "Validate" buttons now reliably revert to their default active states when an operation (validation or publish) concludes with a failure, regardless of the casing of the status string from the backend or whether the failure occurs immediately or during polling.
291
291
  - **Overall Outcome:** These changes improve the reliability of data submission and provide more accurate UI feedback for asynchronous operations on the "Create Server in Existing Category" tab.
292
292
 
293
+ - **Enhanced Operation Status Polling and Display (May 2025 - Current Session):**
294
+ - **Context:** Significant improvements were made to the handling of asynchronous operations (validation and publishing), including more detailed progress display, robust polling, and clearer API interactions.
295
+ - **Issues Addressed & Solutions:**
296
+ 1. **Server-Side API (`server.ts` - `/api/categories/:categoryName/onboard/status`):**
297
+ - The endpoint now requires an `operationType` query parameter (e.g., `VALIDATION_ONLY`, `FULL_ONBOARDING`).
298
+ - The response payload (`OperationStatus`) was enhanced to include:
299
+ - `steps`: An array of objects detailing each step of the operation (name, serverName, timestamp, status, errorMessage).
300
+ - `operationType`: Reflects the type of operation being tracked, taken from the status object.
301
+ - `feedConfiguration`: Conditionally included for successful `VALIDATION_ONLY` operations if present in the result.
302
+ - The `onboardingId` in the response is `categoryName_operationType`.
303
+ - The `message` field in the response now prioritizes the last step's name or an overall error message.
304
+ 2. **Client-Side Polling Logic (`validationHandlers.js` - `pollOperationStatus`):**
305
+ - The function now accepts an `operationType` parameter, which is passed to the API.
306
+ - It returns a boolean promise (`Promise<boolean>`) indicating whether polling should continue, allowing the caller (`handleValidation` or `handlePublish`) to manage `clearInterval`.
307
+ - Calls `ensureProgressToggleListener` to ensure the UI for displaying progress steps is interactive.
308
+ - Status comparisons (`COMPLETED`, `FAILED`, `SUCCEEDED`) are now case-insensitive.
309
+ 3. **Operation Initiation (`publishHandler.js` - `handlePublish`, `validationHandlers.js` - `handleValidation`):**
310
+ - When initiating polling, these functions now pass the correct `operationType` (`FULL_ONBOARDING` for publish, `VALIDATION_ONLY` for validate) and use the original `finalFeedConfiguration.name` as the `categoryName` for the polling request.
311
+ - They now manage `clearInterval` based on the boolean returned by `pollOperationStatus`.
312
+ - Both handlers call `ensureProgressToggleListener` at the beginning to prepare the UI for status updates.
313
+ - `handlePublish` includes improved error handling for the initial publish request, ensuring UI elements are correctly updated even if the initial call fails or returns a terminal status.
314
+ 4. **UI Display (`validationHandlers.js` - `updateOperationDisplay`):**
315
+ - A new collapsible "Progress" section is displayed, showing detailed `steps` from the API response. Each step includes:
316
+ - A status icon (error, in-progress spinner, success).
317
+ - The step name, server name (if applicable), and timestamp.
318
+ - An error message for the step, if any.
319
+ - The visibility of the "Progress" section is managed by `ensureProgressToggleListener` (which attaches a click handler to the section header) and the `toggleSectionContent` utility.
320
+ - The `operationType` is now displayed.
321
+ - The logic for displaying the overall status spinner has been updated to cover more in-progress states (e.g., `PENDING`, `VALIDATING`, `PRCREATING`, `PUBLISHING`) and uses case-insensitive comparisons.
322
+ 5. **Progress Section Toggling (`validationHandlers.js` - `ensureProgressToggleListener`):**
323
+ - A new exported utility function, `ensureProgressToggleListener`, was added. It attaches a click event listener to the status content element. This listener delegates to `toggleSectionContent` (from `uiHandlers.js`) when the progress section header is clicked, allowing users to expand or collapse the detailed list of progress steps. The listener is attached only once per status content element.
324
+ - **Outcome:** These changes provide users with more granular and interactive feedback on long-running operations, improve the robustness and accuracy of status polling, and align client-server communication for tracking asynchronous onboarding tasks.
293
325
  ---
294
326
 
295
327
  ## 10. Server Validation System
@@ -172,14 +172,26 @@ export async function submitForm(event, activeTab, currentSelectedCategoryData =
172
172
  showToast('No existing category selected or category data is missing.', 'error');
173
173
  return;
174
174
  }
175
- const newServersData = formDataToFeedConfiguration(formElement, true, currentSelectedCategoryData);
176
175
 
177
- feedConfiguration = JSON.parse(JSON.stringify(currentSelectedCategoryData)); // Deep clone
176
+ // Get the complete state of servers and their requirements from the current form.
177
+ // The `true` flag for `forExistingCategoryTab` tells formDataToFeedConfiguration
178
+ // to only process server data and not category-level fields from this form.
179
+ // It should return ALL servers currently in this form, including modified adhoc and new ones.
180
+ const formDerivedData = formDataToFeedConfiguration(formElement, true, currentSelectedCategoryData);
178
181
 
179
- feedConfiguration.mcpServers = (feedConfiguration.mcpServers || []).concat(newServersData.mcpServers || []);
182
+ // Start with a deep clone of the original category data (for name, description, etc.)
183
+ feedConfiguration = JSON.parse(JSON.stringify(currentSelectedCategoryData));
180
184
 
185
+ // Replace the mcpServers list entirely with what was parsed from the form.
186
+ // This ensures that the list reflects the current state of the form (original read-only, modified adhoc, new).
187
+ feedConfiguration.mcpServers = formDerivedData.mcpServers || [];
188
+
189
+ // Merge requirements: start with original requirements, then add any new ones from the form.
190
+ // `formDerivedData.requirements` should ideally only contain requirements introduced by
191
+ // servers in the current form that are not already in `currentSelectedCategoryData.requirements`.
192
+ // The existing logic for merging requirements seems okay if `formDerivedData.requirements` is correctly populated.
181
193
  const existingReqKeys = new Set((feedConfiguration.requirements || []).map(r => `${r.type}|${r.name}|${r.version}`));
182
- (newServersData.requirements || []).forEach(newReq => {
194
+ (formDerivedData.requirements || []).forEach(newReq => {
183
195
  const reqKey = `${newReq.type}|${newReq.name}|${newReq.version}`;
184
196
  if (!existingReqKeys.has(reqKey)) {
185
197
  feedConfiguration.requirements.push(newReq);
@@ -193,7 +205,7 @@ export async function submitForm(event, activeTab, currentSelectedCategoryData =
193
205
  }
194
206
 
195
207
  try {
196
- const response = await fetch('/api/categories/onboard', {
208
+ const response = await fetch('/api/categories/onboard', {
197
209
  method: 'POST',
198
210
  headers: { 'Content-Type': 'application/json' },
199
211
  body: JSON.stringify({
@@ -208,7 +220,7 @@ export async function submitForm(event, activeTab, currentSelectedCategoryData =
208
220
  }
209
221
 
210
222
  const result = await response.json();
211
- console.log('Form submitted successfully:', result);
223
+ // console.log('Form submitted successfully:', result);
212
224
  showToast('Form submitted successfully! See console for operation details.', 'success');
213
225
  } catch (error) {
214
226
  console.error('Error submitting form:', error);
@@ -327,6 +339,15 @@ export function formDataToFeedConfiguration(formElement, forExistingCategoryTab
327
339
  mcpServers: []
328
340
  };
329
341
 
342
+ let serversListId;
343
+ if (formElement.id === 'onboardForm') {
344
+ serversListId = 'serversList';
345
+ } else if (formElement.id === 'onboardServerForm') {
346
+ serversListId = 'existingCategoryServersList';
347
+ } else {
348
+ console.warn('[formDataToFeedConfiguration] Could not determine serversListId from formElement.id:', formElement.id);
349
+ }
350
+
330
351
  if (!forExistingCategoryTab) {
331
352
  feedConfiguration.name = currentFormData.get('name') || '';
332
353
  feedConfiguration.displayName = currentFormData.get('displayName') || '';
@@ -420,8 +441,13 @@ export function formDataToFeedConfiguration(formElement, forExistingCategoryTab
420
441
  }
421
442
  }
422
443
 
423
- serverDataMap.forEach(serverRaw => {
444
+ // console.log('[formDataToFeedConfiguration] serverDataMap keys before processing:', Array.from(serverDataMap.keys())); // DEBUG
445
+ // console.log('[formDataToFeedConfiguration] serverDataMap size:', serverDataMap.size); // DEBUG
446
+
447
+ // Process servers found in the form data (new/adhoc or edited original servers)
448
+ serverDataMap.forEach((serverRaw, serverIndex) => {
424
449
  let mcpServer;
450
+ // ... (existing logic to build mcpServer from serverRaw)
425
451
  if (serverRaw.mode === 'sse') {
426
452
  mcpServer = {
427
453
  name: serverRaw.name,
@@ -436,7 +462,7 @@ export function formDataToFeedConfiguration(formElement, forExistingCategoryTab
436
462
  requirements: []
437
463
  }
438
464
  };
439
- } else {
465
+ } else { // stdio or other modes
440
466
  mcpServer = {
441
467
  name: serverRaw.name,
442
468
  description: serverRaw.description,
@@ -446,7 +472,7 @@ export function formDataToFeedConfiguration(formElement, forExistingCategoryTab
446
472
  installation: {
447
473
  command: serverRaw.installation.command,
448
474
  args: serverRaw.installation.args || [],
449
- env: {}
475
+ env: {} // Initialize env
450
476
  },
451
477
  dependencies: {
452
478
  requirements: []
@@ -466,7 +492,7 @@ export function formDataToFeedConfiguration(formElement, forExistingCategoryTab
466
492
  if (Object.keys(envVars).length > 0) {
467
493
  mcpServer.installation.env = envVars;
468
494
  } else {
469
- delete mcpServer.installation.env;
495
+ delete mcpServer.installation.env; // Clean up if no env vars
470
496
  }
471
497
  }
472
498
 
@@ -500,6 +526,7 @@ export function formDataToFeedConfiguration(formElement, forExistingCategoryTab
500
526
  assetName: reqRaw.registry.local.assetName,
501
527
  };
502
528
  }
529
+ // Clean up empty registry objects
503
530
  if (fullRequirementConfig.registry.githubRelease && !fullRequirementConfig.registry.githubRelease.repository) delete fullRequirementConfig.registry.githubRelease;
504
531
  if (fullRequirementConfig.registry.artifacts && !fullRequirementConfig.registry.artifacts.registryUrl) delete fullRequirementConfig.registry.artifacts;
505
532
  if (fullRequirementConfig.registry.local && !fullRequirementConfig.registry.local.localPath) delete fullRequirementConfig.registry.local;
@@ -514,17 +541,103 @@ export function formDataToFeedConfiguration(formElement, forExistingCategoryTab
514
541
 
515
542
  const reqKey = `${reqRaw.type}|${reqRaw.name}|${reqRaw.version}`;
516
543
  if (!globalRequirementsMap.has(reqKey)) {
517
- const { order, ...reqConfigForGlobal } = fullRequirementConfig;
544
+ const { order, ...reqConfigForGlobal } = fullRequirementConfig; // Exclude order for global list
518
545
  globalRequirementsMap.set(reqKey, reqConfigForGlobal);
519
546
  }
520
547
  });
521
548
  if (mcpServer.dependencies.requirements.length === 0) {
522
- delete mcpServer.dependencies;
549
+ delete mcpServer.dependencies; // Clean up if no requirements
550
+ }
551
+
552
+ // Attempt to retrieve and add systemTags from the DOM element,
553
+ // ONLY if we are in the 'Create Server in Existing Category' tab context.
554
+ // For 'Create New Category' tab, systemTags should not be assigned from DOM.
555
+ if (formElement.id === 'onboardServerForm' && serversListId) {
556
+ const selector = `#${serversListId} .server-item[data-index="${serverIndex}"]`;
557
+ const serverItemElement = document.querySelector(selector);
558
+ if (serverItemElement && serverItemElement.dataset.systemTags) {
559
+ try {
560
+ mcpServer.systemTags = JSON.parse(serverItemElement.dataset.systemTags);
561
+ } catch (e) {
562
+ console.error(`[formDataToFeedConfiguration] Error parsing systemTags for server index ${serverIndex} ('${mcpServer.name}') on ${formElement.id}:`, e, serverItemElement.dataset.systemTags);
563
+ mcpServer.systemTags = { parseError: true };
564
+ }
565
+ }
566
+ // If no dataset.systemTags or not onboardServerForm, mcpServer.systemTags remains undefined.
567
+ } else {
568
+ // Ensure systemTags is not carried over if not in the correct context or not present in dataset
569
+ delete mcpServer.systemTags;
523
570
  }
524
571
 
525
572
  feedConfiguration.mcpServers.push(mcpServer);
526
573
  });
527
574
 
575
+
576
+ // If processing for an existing category, ensure original non-adhoc servers are included
577
+ // if they weren't picked up by the form (e.g., because they were read-only and disabled).
578
+ if (forExistingCategoryTab && baseCategoryData && Array.isArray(baseCategoryData.mcpServers)) {
579
+ baseCategoryData.mcpServers.forEach(originalServer => {
580
+ // Check if this original server (by name) is already in our processed list.
581
+ // We use name as the primary identifier for existing servers.
582
+ const isAlreadyProcessed = feedConfiguration.mcpServers.some(
583
+ processedServer => processedServer.name === originalServer.name
584
+ );
585
+
586
+ if (!isAlreadyProcessed && (!originalServer.systemTags || originalServer.systemTags.adhoc !== 'true')) {
587
+ // This original server was not in the form data (likely read-only) and is not adhoc. Add it.
588
+ // Ensure its requirements are also added to the global list if not already present.
589
+ feedConfiguration.mcpServers.push(JSON.parse(JSON.stringify(originalServer))); // Add a clone
590
+
591
+ if (originalServer.dependencies && Array.isArray(originalServer.dependencies.requirements)) {
592
+ originalServer.dependencies.requirements.forEach(req => {
593
+ // We need the full requirement definition for the global map.
594
+ // This might require looking up the full definition from baseCategoryData.requirements
595
+ // if originalServer.dependencies.requirements only has name/version/order.
596
+ // For simplicity here, we assume baseCategoryData.requirements contains full definitions.
597
+ const originalGlobalReq = baseCategoryData.requirements?.find(
598
+ gReq => gReq.name === req.name && gReq.type && gReq.version === req.version // Type might not be in server's dep list
599
+ );
600
+
601
+ if (originalGlobalReq) {
602
+ const reqKey = `${originalGlobalReq.type}|${originalGlobalReq.name}|${originalGlobalReq.version}`;
603
+ if (!globalRequirementsMap.has(reqKey)) {
604
+ globalRequirementsMap.set(reqKey, JSON.parse(JSON.stringify(originalGlobalReq)));
605
+ }
606
+ } else {
607
+ // Fallback if full definition not found, add what we have, though type might be missing.
608
+ // This part might need refinement based on actual structure of baseCategoryData.requirements
609
+ // and how server-specific dependencies link to global ones.
610
+ // The current server-specific req usually has name, version, order. Type is global.
611
+ // We need to find the type from the global requirements list.
612
+ // This logic assumes that if a server has a dependency, its full definition (including type)
613
+ // must exist in the global `baseCategoryData.requirements`.
614
+
615
+ // Let's find the type from baseCategoryData.requirements based on name and version
616
+ const matchingGlobalReqForType = baseCategoryData.requirements?.find(
617
+ gReq => gReq.name === req.name && gReq.version === req.version
618
+ );
619
+ if (matchingGlobalReqForType && matchingGlobalReqForType.type) {
620
+ const reqKey = `${matchingGlobalReqForType.type}|${req.name}|${req.version}`;
621
+ if (!globalRequirementsMap.has(reqKey)) {
622
+ // Construct a basic global requirement if not found, though ideally it should exist.
623
+ globalRequirementsMap.set(reqKey, {
624
+ name: req.name,
625
+ version: req.version,
626
+ type: matchingGlobalReqForType.type
627
+ // Other fields like alias, registry would be missing here if not in matchingGlobalReqForType
628
+ });
629
+ }
630
+ } else {
631
+ console.warn(`Could not find full global requirement definition (or type) for ${req.name} v${req.version} from original server ${originalServer.name}`);
632
+ }
633
+ }
634
+ });
635
+ }
636
+ }
637
+ });
638
+ }
639
+
640
+
528
641
  feedConfiguration.requirements = Array.from(globalRequirementsMap.values());
529
642
 
530
643
  // Validate the entire configuration
@@ -668,47 +781,95 @@ export function populateForm(feedConfig, formId = 'onboardForm', renderServersAs
668
781
  clearEnvCountersForTab(serversListId);
669
782
  clearServerRequirementCountersForTab(serversListId);
670
783
 
671
- (feedConfig.mcpServers || []).forEach((server) => {
784
+ (feedConfig.mcpServers || []).forEach((serverData) => { // Renamed 'server' to 'serverData' for clarity
672
785
  // Get the correct current index for this tab before adding the server
673
786
  const currentServerIndex = getServerCounter(serversListId);
674
787
  // window.addServer will increment the counter for serversListId internally
675
- window.addServer(serversListId, renderServersAsReadOnly, server);
676
-
788
+ // It also returns the server item, but we'll query it again for safety after DOM manipulation.
789
+ window.addServer(serversListId, renderServersAsReadOnly, serverData);
790
+
791
+ // After addServer, the server item should exist in the DOM.
792
+ const serverItem = currentForm.querySelector(`#${serversListId} .server-item[data-index="${currentServerIndex}"]`);
793
+
794
+ if (serverItem) {
795
+ if (formId === 'onboardServerForm') {
796
+ if (serverData.systemTags?.adhoc === "true") {
797
+ // Case 1: JSON data explicitly marks it as adhoc. Respect this.
798
+ serverItem.dataset.systemTags = JSON.stringify(serverData.systemTags);
799
+ // console.log(`[populateForm] Server ${serverData.name || currentServerIndex} in ${formId} retains adhoc status from JSON.`);
800
+ } else {
801
+ // Case 2: JSON data does NOT explicitly mark it adhoc.
802
+ // Check if it was an original server from the category.
803
+ // state.originalServerNamesForFormPopulation is set when toggling from JSON to Form view for an existing category.
804
+ if (state.originalServerNamesForFormPopulation && serverData.name && state.originalServerNamesForFormPopulation.has(serverData.name)) {
805
+ // It's an original server from the category, now treated as adhoc because it passed through JSON view.
806
+ serverItem.dataset.systemTags = JSON.stringify({ adhoc: "true" });
807
+ // console.log(`[populateForm] Original server ${serverData.name} in ${formId} marked as adhoc after JSON view.`);
808
+ } else {
809
+ // It's a new server (not in original list) and JSON didn't mark it adhoc.
810
+ // Or, it's an original server but its JSON representation explicitly removed/lacked adhoc tag.
811
+ // Ensure it's NOT adhoc.
812
+ // If serverData.systemTags exists but doesn't have adhoc:true, preserve those other tags.
813
+ if (serverData.systemTags && Object.keys(serverData.systemTags).length > 0) {
814
+ const newTags = { ...serverData.systemTags };
815
+ delete newTags.adhoc; // Ensure adhoc is not true
816
+ if (Object.keys(newTags).length > 0) {
817
+ serverItem.dataset.systemTags = JSON.stringify(newTags);
818
+ } else {
819
+ delete serverItem.dataset.systemTags;
820
+ }
821
+ } else {
822
+ delete serverItem.dataset.systemTags;
823
+ }
824
+ // console.log(`[populateForm] Server ${serverData.name || currentServerIndex} in ${formId} is NOT marked adhoc.`);
825
+ }
826
+ }
827
+ } else {
828
+ // For 'onboardForm' (Create Category tab), or if serverData had systemTags not making it adhoc.
829
+ // If serverData has systemTags, reflect them. Otherwise, ensure no systemTags.
830
+ if (serverData.systemTags) {
831
+ serverItem.dataset.systemTags = JSON.stringify(serverData.systemTags);
832
+ } else {
833
+ delete serverItem.dataset.systemTags;
834
+ }
835
+ }
836
+ }
837
+ // Continue with populating fields using serverData
677
838
  const serverNameInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].name"]`);
678
- if (serverNameInput) serverNameInput.value = server.name || '';
839
+ if (serverNameInput) serverNameInput.value = serverData.name || '';
679
840
 
680
841
  const modeInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].mode"]`);
681
842
  if (modeInput) {
682
- modeInput.value = server.mode || 'stdio';
843
+ modeInput.value = serverData.mode || 'stdio';
683
844
  if (typeof window.renderInstallationConfig === 'function') {
684
- window.renderInstallationConfig(currentServerIndex, serversListId, server.mode || 'stdio', renderServersAsReadOnly, server.installation);
845
+ window.renderInstallationConfig(currentServerIndex, serversListId, serverData.mode || 'stdio', renderServersAsReadOnly, serverData.installation);
685
846
  }
686
847
  }
687
848
  const descInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].description"]`);
688
- if (descInput) descInput.value = server.description || '';
849
+ if (descInput) descInput.value = serverData.description || '';
689
850
 
690
- if (server.schemas) {
851
+ if (serverData.schemas) {
691
852
  const schemaPathEl = document.getElementById(`schema-path-${currentServerIndex}`);
692
- if (schemaPathEl) schemaPathEl.value = server.schemas;
853
+ if (schemaPathEl) schemaPathEl.value = serverData.schemas;
693
854
  }
694
- if (server.repository) {
855
+ if (serverData.repository) {
695
856
  const repoInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].repository"]`);
696
- if (repoInput) repoInput.value = server.repository;
857
+ if (repoInput) repoInput.value = serverData.repository;
697
858
  }
698
859
 
699
- if (server.installation) {
700
- if (server.mode === 'sse') {
860
+ if (serverData.installation) {
861
+ if (serverData.mode === 'sse') {
701
862
  const urlInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].installation.url"]`);
702
- if (urlInput) urlInput.value = server.installation.url || '';
863
+ if (urlInput) urlInput.value = serverData.installation.url || '';
703
864
  } else {
704
865
  const cmdInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].installation.command"]`);
705
- if (cmdInput) cmdInput.value = server.installation.command || '';
706
- if (server.installation.args && Array.isArray(server.installation.args)) {
866
+ if (cmdInput) cmdInput.value = serverData.installation.command || '';
867
+ if (serverData.installation.args && Array.isArray(serverData.installation.args)) {
707
868
  const argsInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].installation.args"]`);
708
- if (argsInput) argsInput.value = server.installation.args.join(', ');
869
+ if (argsInput) argsInput.value = serverData.installation.args.join(', ');
709
870
  }
710
- if (server.installation.env) {
711
- Object.entries(server.installation.env).forEach(([envName, envConfig]) => {
871
+ if (serverData.installation.env) {
872
+ Object.entries(serverData.installation.env).forEach(([envName, envConfig]) => {
712
873
  const currentEnvIndex = window.addEnvVariable(currentServerIndex, serversListId, renderServersAsReadOnly);
713
874
 
714
875
  const nameInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].installation.env[${currentEnvIndex}].name"]`);
@@ -730,8 +891,12 @@ export function populateForm(feedConfig, formId = 'onboardForm', renderServersAs
730
891
  }
731
892
  }
732
893
 
733
- if (server.dependencies && server.dependencies.requirements) {
734
- server.dependencies.requirements.forEach((depReq) => {
894
+ if (serverData.dependencies && serverData.dependencies.requirements) {
895
+ // Determine if this server's requirements should be effectively read-only
896
+ const serverIsEffectivelyReadOnlyForReqs = renderServersAsReadOnly && !(serverData.systemTags?.adhoc === 'true');
897
+ // console.log(`[populateForm] Server: ${serverData.name}, Adhoc: ${serverData.systemTags?.adhoc === 'true'}, renderServersAsReadOnly: ${renderServersAsReadOnly}, Calculated serverIsEffectivelyReadOnlyForReqs: ${serverIsEffectivelyReadOnlyForReqs}`); // DEBUG
898
+
899
+ serverData.dependencies.requirements.forEach((depReq) => {
735
900
  // Find requirement by name only, as requested.
736
901
  const fullReq = (feedConfig.requirements || []).find(r => r.name === depReq.name);
737
902
  if (!fullReq) {
@@ -739,7 +904,8 @@ export function populateForm(feedConfig, formId = 'onboardForm', renderServersAs
739
904
  return;
740
905
  }
741
906
 
742
- const currentReqIndex = window.addServerRequirement(currentServerIndex, serversListId, renderServersAsReadOnly);
907
+ // Pass serverIsEffectivelyReadOnlyForReqs to addServerRequirement
908
+ const currentReqIndex = window.addServerRequirement(currentServerIndex, serversListId, serverIsEffectivelyReadOnlyForReqs);
743
909
 
744
910
  currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].name"]`).value = fullReq.name || '';
745
911
  currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].type"]`).value = fullReq.type || '';
@@ -293,11 +293,11 @@ document.addEventListener('DOMContentLoaded', async () => {
293
293
  // addRequirementBtn.addEventListener('click', addRequirement); // addRequirement from uiHandlers.js
294
294
  // }
295
295
 
296
- const addServerBtnNewCategory = document.getElementById('addServerBtn'); // For "Create New Category" tab
296
+ const addServerBtnNewCategory = document.getElementById('addServerBtnNewCategory'); // For "Create New Category" tab
297
297
  if (addServerBtnNewCategory) {
298
298
  // addServer in uiHandlers defaults serversListId to 'serversList', which is correct for this button.
299
299
  // It also defaults isReadOnly to false.
300
- addServerBtnNewCategory.addEventListener('click', () => addServer());
300
+ addServerBtnNewCategory.addEventListener('click', () => window.addServer('serversList', false, null));
301
301
  }
302
302
 
303
303
  const addServerBtnExistingCategory = document.getElementById('addServerToExistingCategoryBtn'); // For "Create Server in Existing Category" tab