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
@@ -54,19 +54,61 @@ function reindexServers(serversListId = 'serversList') {
54
54
 
55
55
  const titleElement = serverItem.querySelector('h3');
56
56
  if (titleElement) {
57
+ const originalTextContent = titleElement.textContent || '';
57
58
  const baseTitle = `MCP Server #${newServerIndex + 1}`;
58
- const readOnlySuffix = titleElement.textContent.includes('(Read-only)') ? ' (Read-only)' : '';
59
- titleElement.textContent = `${baseTitle}${readOnlySuffix}`;
59
+ let suffix = '';
60
+ if (originalTextContent.includes('(Adhoc - Editable)')) {
61
+ suffix = ' <span class="text-sm text-blue-600 ml-1">(Adhoc - Editable)</span>';
62
+ } else if (originalTextContent.includes('(Read-only)')) {
63
+ suffix = ' (Read-only)';
64
+ }
65
+ // Use innerHTML to preserve the span if it's an adhoc server
66
+ titleElement.innerHTML = `${baseTitle}${suffix}`;
60
67
  }
61
68
 
62
69
  // Update onclick handlers
70
+ // Ensure the server header toggle onclick is correctly re-indexed for its ID parameters
71
+ const headerToggle = serverItem.querySelector(`#${serversListId}-server-header-${oldServerIndex}`);
72
+ if (headerToggle) {
73
+ headerToggle.id = `${serversListId}-server-header-${newServerIndex}`;
74
+ const oldContentId = `${serversListId}-server-content-${oldServerIndex}`;
75
+ const newContentId = `${serversListId}-server-content-${newServerIndex}`;
76
+ let onclickAttr = headerToggle.getAttribute('onclick');
77
+ if (onclickAttr) {
78
+ onclickAttr = onclickAttr.replace(new RegExp(oldContentId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newContentId);
79
+ headerToggle.setAttribute('onclick', onclickAttr);
80
+ }
81
+ let onkeydownAttr = headerToggle.getAttribute('onkeydown');
82
+ if (onkeydownAttr) {
83
+ onkeydownAttr = onkeydownAttr.replace(new RegExp(oldContentId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), newContentId);
84
+ headerToggle.setAttribute('onkeydown', onkeydownAttr);
85
+ }
86
+ headerToggle.setAttribute('aria-controls', newContentId);
87
+ }
88
+ // Update title ID for aria-labelledby
89
+ const titleForAria = serverItem.querySelector(`#${serversListId}-server-title-${oldServerIndex}`);
90
+ if (titleForAria) {
91
+ titleForAria.id = `${serversListId}-server-title-${newServerIndex}`;
92
+ }
93
+ const contentRegion = serverItem.querySelector(`#${serversListId}-server-content-${oldServerIndex}`);
94
+ if (contentRegion) {
95
+ contentRegion.setAttribute('aria-labelledby', `${serversListId}-server-title-${newServerIndex}`);
96
+ }
97
+
98
+
63
99
  serverItem.querySelectorAll('[onclick]').forEach(element => {
64
100
  const onclickAttr = element.getAttribute('onclick');
65
101
  if (!onclickAttr) return;
66
102
 
103
+ // Skip the header toggle as it's handled above to be more precise with ID replacement
104
+ if (element.id === `${serversListId}-server-header-${newServerIndex}`) return;
105
+
67
106
  const updatedOnclick = onclickAttr
68
107
  .replace(new RegExp(`\\((\\s*)${oldServerIndex}(\\s*[,\\)])`, 'g'), `($1${newServerIndex}$2`)
69
- .replace(new RegExp(`(${serversListId}-(?:server|installation|env-vars)-content-${oldServerIndex})`, 'g'), `$1${newServerIndex}`);
108
+ // More specific replacement for content IDs to avoid accidental replacements
109
+ .replace(new RegExp(`${serversListId}-server-deps-content-${oldServerIndex}`.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), `${serversListId}-server-deps-content-${newServerIndex}`)
110
+ .replace(new RegExp(`${serversListId}-installation-content-${oldServerIndex}`.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), `${serversListId}-installation-content-${newServerIndex}`)
111
+ .replace(new RegExp(`${serversListId}-env-vars-content-${oldServerIndex}`.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), `${serversListId}-env-vars-content-${newServerIndex}`);
70
112
 
71
113
  element.setAttribute('onclick', updatedOnclick);
72
114
  });
@@ -137,13 +179,18 @@ export function addServer(serversListId = 'serversList', isContextGenerallyReadO
137
179
 
138
180
  const newServerIndex = getServerCounter(serversListId);
139
181
  let actualReadOnlyForThisServer;
182
+ const isServerAdhoc = serverData?.systemTags?.adhoc === 'true';
140
183
 
141
- if (!isContextGenerallyReadOnly) {
184
+ if (isServerAdhoc) {
185
+ // Adhoc servers are always editable, regardless of the general context.
186
+ actualReadOnlyForThisServer = false;
187
+ } else if (!isContextGenerallyReadOnly) {
142
188
  // Context is not generally read-only (e.g., creating a brand new category,
143
189
  // or user clicked "Add Server" button when no specific read-only category context applies).
144
190
  actualReadOnlyForThisServer = false;
145
191
  } else {
146
- // Context IS generally read-only (e.g., an existing category was selected, or we are populating from JSON for such a category).
192
+ // Context IS generally read-only AND the server is NOT adhoc.
193
+ // Determine read-only status based on whether it's an original server or a newly added one.
147
194
  if (state.originalServerNamesForFormPopulation) {
148
195
  // We are in the process of toggling from JSON view back to Form view for an existing category.
149
196
  // state.originalServerNamesForFormPopulation contains names of servers that were part of the category *before* any UI/JSON edits.
@@ -152,7 +199,6 @@ export function addServer(serversListId = 'serversList', isContextGenerallyReadO
152
199
  actualReadOnlyForThisServer = true;
153
200
  } else {
154
201
  // This serverData (from JSON) is new (wasn't in originalServerNamesForFormPopulation). It should be editable.
155
- // This also covers cases where serverData might not have a name yet if added directly in JSON.
156
202
  actualReadOnlyForThisServer = false;
157
203
  }
158
204
  } else {
@@ -160,7 +206,7 @@ export function addServer(serversListId = 'serversList', isContextGenerallyReadO
160
206
  // 1. Initial population of an existing category's servers (serverData will be provided).
161
207
  // 2. User clicked the "Add Server" button while an existing category context is active (serverData will be null).
162
208
  if (serverData) {
163
- // Case 1: Initial population of an existing server from category data. Should be read-only.
209
+ // Case 1: Initial population of an existing server from category data. Should be read-only (unless it was adhoc, handled above).
164
210
  actualReadOnlyForThisServer = true;
165
211
  } else {
166
212
  // Case 2: User clicked "Add Server" button. The new server item should be editable.
@@ -168,8 +214,10 @@ export function addServer(serversListId = 'serversList', isContextGenerallyReadO
168
214
  }
169
215
  }
170
216
  }
171
-
172
- container.insertAdjacentHTML('beforeend', serverTemplate(newServerIndex, actualReadOnlyForThisServer, serverData, serversListId));
217
+ // `isContextGenerallyReadOnly`: Influences initial template state for some UI elements (e.g., button visibility).
218
+ // `actualReadOnlyForThisServer`: Determines if fields should be disabled and is passed to setupReadOnlyState.
219
+ // `serverData`: Used by the template for initial adhoc title span, and passed to setupReadOnlyState for more checks.
220
+ container.insertAdjacentHTML('beforeend', serverTemplate(newServerIndex, isContextGenerallyReadOnly, serverData, serversListId));
173
221
 
174
222
  setEnvCounter(serversListId, newServerIndex, 0);
175
223
  setServerRequirementCounter(serversListId, newServerIndex, 0);
@@ -177,8 +225,20 @@ export function addServer(serversListId = 'serversList', isContextGenerallyReadO
177
225
 
178
226
  const serverItem = container.querySelector(`.server-item[data-index="${newServerIndex}"]`);
179
227
  if (serverItem) {
228
+ if (serverData && serverData.systemTags) { // Case 1: Populating from existing serverData that has systemTags
229
+ serverItem.dataset.systemTags = JSON.stringify(serverData.systemTags);
230
+ } else if (serverData && !serverData.systemTags) { // Case 3: Populating from serverData that *lacks* systemTags
231
+ // Ensure no stale adhoc tag if the DOM element was somehow reused and had it previously.
232
+ delete serverItem.dataset.systemTags;
233
+ } else if (!serverData) { // Case 2: Adding a brand new server via "Add Server" button click (serverData is null)
234
+ // A brand new server is NOT adhoc by default. It becomes adhoc if modified (e.g. via JSON).
235
+ // This fulfills part of Req 3: newly added one in create-server tab should not have systemTags.
236
+ // And implicitly part of Req 2: new server in create-category tab also won't have systemTags.
237
+ delete serverItem.dataset.systemTags;
238
+ }
180
239
  setupServerMode(serverItem, newServerIndex, serversListId, actualReadOnlyForThisServer, serverData);
181
- setupReadOnlyState(serverItem, actualReadOnlyForThisServer, serversListId, newServerIndex);
240
+ // Pass serverData to setupReadOnlyState so it can accurately determine adhoc status for title.
241
+ setupReadOnlyState(serverItem, actualReadOnlyForThisServer, serverData, serversListId, newServerIndex);
182
242
 
183
243
  // Default expand Package Dependencies, Startup Configuration, and Environment Variables sections
184
244
  const sections = [
@@ -247,15 +307,56 @@ function setupServerMode(serverItem, serverIndex, serversListId, isReadOnly, ser
247
307
  renderInstallationConfig(serverIndex, serversListId, modeSelect.value, isReadOnly, serverData?.installation);
248
308
  }
249
309
 
250
- function setupReadOnlyState(serverItem, isReadOnly, serversListId, serverIndex) {
251
- if (isReadOnly) {
310
+ // `isEffectivelyReadOnly` determines if the fields within this server item should be disabled.
311
+ // `serverDataFromPopulation` is the original server data object passed during population, used to check initial adhoc status.
312
+ function setupReadOnlyState(serverItem, isEffectivelyReadOnly, serverDataFromPopulation, serversListId, serverIndex) {
313
+ const titleElement = serverItem.querySelector(`#${serversListId}-server-title-${serverIndex}`);
314
+ const baseTitle = `MCP Server #${serverIndex + 1}`;
315
+ let isAdhocForTitle = serverDataFromPopulation?.systemTags?.adhoc === 'true';
316
+
317
+ // Double-check adhoc status from dataset if serverDataFromPopulation doesn't indicate it
318
+ // (e.g. if it became adhoc after JSON edit and re-population)
319
+ if (!isAdhocForTitle && serverItem.dataset.systemTags) {
320
+ try {
321
+ const tags = JSON.parse(serverItem.dataset.systemTags);
322
+ if (tags.adhoc === "true") {
323
+ isAdhocForTitle = true;
324
+ }
325
+ } catch (e) { /* ignore parsing error for title determination */ }
326
+ }
327
+
328
+ if (isEffectivelyReadOnly) {
329
+ if (titleElement) {
330
+ if (!isAdhocForTitle) {
331
+ titleElement.innerHTML = `${baseTitle} (Read-only)`;
332
+ } else {
333
+ // For adhoc servers, the template is responsible for the "(Adhoc - Editable)" span.
334
+ // If it's somehow missing and it's adhoc, ensure it's present.
335
+ // This situation should be rare if template logic is correct.
336
+ if (!titleElement.querySelector('span.text-blue-600')) {
337
+ titleElement.innerHTML = `${baseTitle} <span class="text-sm text-blue-600 ml-1">(Adhoc - Editable)</span>`;
338
+ } else {
339
+ // Ensure base title is correct if adhoc span is already there
340
+ const adhocSpanHTML = titleElement.querySelector('span.text-blue-600').outerHTML;
341
+ titleElement.innerHTML = `${baseTitle} ${adhocSpanHTML}`;
342
+ }
343
+ }
344
+ }
345
+ // Disable all input fields within this specific server item
252
346
  serverItem.querySelectorAll('input, select, textarea').forEach(el => {
253
- el.disabled = true;
254
- el.classList.add('bg-gray-100', 'cursor-not-allowed', 'opacity-70');
347
+ // Check if the element is part of a sub-item (like env var or requirement)
348
+ // These sub-items have their own read-only logic handled by their add functions.
349
+ // We only want to disable the main server fields here.
350
+ if (!el.closest('.env-var-item') && !el.closest('.server-requirement-item')) {
351
+ el.disabled = true;
352
+ el.classList.add('bg-gray-100', 'cursor-not-allowed', 'opacity-70');
353
+ }
255
354
  });
256
355
 
356
+ // Hide all action buttons (Add Dependency, Add Env Var, Remove Server) for this server if it's read-only.
257
357
  serverItem.querySelectorAll('.action-button-in-server').forEach(btn => {
258
- btn.style.display = 'none';
358
+ btn.style.display = 'none';
359
+ btn.classList.add('hidden');
259
360
  });
260
361
 
261
362
  // Expand server content and all key sections
@@ -275,18 +376,42 @@ function setupReadOnlyState(serverItem, isReadOnly, serversListId, serverIndex)
275
376
  iconElement.classList.add('bxs-chevron-up');
276
377
  }
277
378
  });
278
- } else {
379
+ } else { // Server is effectively editable
380
+ if (titleElement) {
381
+ // Server is editable. Template handles the adhoc span.
382
+ // We just ensure the base title is correct.
383
+ // If adhoc, the template adds the span. If not adhoc, it's just the base title.
384
+ if (isAdhocForTitle) {
385
+ // Check if the adhoc span is already there from the template.
386
+ // If not (e.g. server became adhoc after initial render and this is a re-evaluation), add it.
387
+ if (!titleElement.querySelector('span.text-blue-600')) {
388
+ titleElement.innerHTML = `${baseTitle} <span class="text-sm text-blue-600 ml-1">(Adhoc - Editable)</span>`;
389
+ } else {
390
+ // Ensure base title is correct if adhoc span is already there
391
+ const adhocSpanHTML = titleElement.querySelector('span.text-blue-600').outerHTML;
392
+ titleElement.innerHTML = `${baseTitle} ${adhocSpanHTML}`;
393
+ }
394
+ } else {
395
+ // Editable and not adhoc, just the base title.
396
+ titleElement.innerHTML = baseTitle;
397
+ }
398
+ }
279
399
  setTimeout(() => {
400
+ // Enable main server fields
280
401
  serverItem.querySelectorAll('input, select, textarea').forEach(el => {
281
- el.disabled = false;
282
- el.removeAttribute('readonly');
283
- el.classList.remove('bg-gray-100', 'cursor-not-allowed', 'opacity-70');
402
+ if (!el.closest('.env-var-item') && !el.closest('.server-requirement-item')) {
403
+ el.disabled = false;
404
+ el.removeAttribute('readonly'); // Ensure readonly is also removed
405
+ el.classList.remove('bg-gray-100', 'cursor-not-allowed', 'opacity-70');
406
+ }
284
407
  });
285
408
 
409
+ // Show ALL action buttons for this server (Add Dependency, Add Env Var, Remove Server)
410
+ // as the server is determined to be effectively editable.
286
411
  serverItem.querySelectorAll('.action-button-in-server').forEach(btn => {
287
- btn.style.display = '';
288
- btn.disabled = false;
289
- btn.classList.remove('hidden', 'opacity-50', 'cursor-not-allowed');
412
+ btn.style.display = 'flex'; // Or 'inline-flex' or whatever its default visible display is
413
+ btn.disabled = false;
414
+ btn.classList.remove('hidden', 'opacity-50', 'cursor-not-allowed');
290
415
  });
291
416
  }, 0);
292
417
  }
@@ -361,24 +486,19 @@ export function removeServer(serverIndexToRemove, serversListId = 'serversList')
361
486
  }
362
487
  }
363
488
 
364
- export function addEnvVariable(serverIndex, serversListId = 'serversList', isReadOnly = false) {
489
+ // `isServerEffectivelyReadOnly` is passed to determine if the new env var item itself should be read-only.
490
+ // This would be true if the parent server is non-adhoc and in a read-only category.
491
+ export function addEnvVariable(serverIndex, serversListId = 'serversList', isServerEffectivelyReadOnly = false) {
365
492
  const container = document.querySelector(`#${serversListId} .server-item[data-index="${serverIndex}"] #envVarsContainer_${serverIndex}`);
366
493
  if (!container) return -1;
367
494
 
368
495
  const envIndex = getEnvCounter(serversListId, serverIndex);
369
- container.insertAdjacentHTML('beforeend', envVariableTemplate(serverIndex, envIndex, isReadOnly, serversListId));
496
+ // Pass `isServerEffectivelyReadOnly` to the template for the new env var item.
497
+ container.insertAdjacentHTML('beforeend', envVariableTemplate(serverIndex, envIndex, isServerEffectivelyReadOnly, serversListId));
370
498
  setEnvCounter(serversListId, serverIndex, envIndex + 1);
371
499
 
372
- if (isReadOnly) {
373
- const envItem = container.querySelector(`.env-var-item[data-env-index="${envIndex}"]`);
374
- if (envItem) {
375
- envItem.querySelectorAll('input, select, textarea').forEach(el => {
376
- el.disabled = true;
377
- el.classList.add('bg-gray-100', 'cursor-not-allowed', 'opacity-70');
378
- });
379
- envItem.querySelectorAll('.action-button-in-env').forEach(btn => btn.style.display = 'none');
380
- }
381
- }
500
+ // The template itself handles disabling fields if isServerEffectivelyReadOnly is true.
501
+ // No need for additional logic here to disable fields of the newly added item.
382
502
 
383
503
  return envIndex;
384
504
  }
@@ -388,24 +508,18 @@ export function removeEnvVariable(serverIndex, envIndex, serversListId = 'server
388
508
  if (item) item.remove();
389
509
  }
390
510
 
391
- export function addServerRequirement(serverIndex, serversListId = 'serversList', isReadOnly = false) {
511
+ // `isServerEffectivelyReadOnly` is passed to determine if the new requirement item itself should be read-only.
512
+ export function addServerRequirement(serverIndex, serversListId = 'serversList', isServerEffectivelyReadOnly = false) {
513
+ console.log(`[addServerRequirement] ServerIndex: ${serverIndex}, isServerEffectivelyReadOnly received: ${isServerEffectivelyReadOnly}`); // DEBUG
392
514
  const container = document.querySelector(`#${serversListId} .server-item[data-index="${serverIndex}"] #server-requirements-list-${serverIndex}`);
393
515
  if (!container) return -1;
394
516
 
395
517
  const reqIndex = getServerRequirementCounter(serversListId, serverIndex);
396
- container.insertAdjacentHTML('beforeend', serverRequirementTemplate(serverIndex, reqIndex, isReadOnly, serversListId));
518
+ // Pass `isServerEffectivelyReadOnly` to the template for the new requirement item.
519
+ container.insertAdjacentHTML('beforeend', serverRequirementTemplate(serverIndex, reqIndex, isServerEffectivelyReadOnly, serversListId));
397
520
  setServerRequirementCounter(serversListId, serverIndex, reqIndex + 1);
398
521
 
399
- if (isReadOnly) {
400
- const reqItem = container.querySelector(`.server-requirement-item[data-req-index="${reqIndex}"]`);
401
- if (reqItem) {
402
- reqItem.querySelectorAll('input, select, textarea').forEach(el => {
403
- el.disabled = true;
404
- el.classList.add('bg-gray-100', 'cursor-not-allowed', 'opacity-70');
405
- });
406
- reqItem.querySelectorAll('.action-button-in-req').forEach(btn => btn.style.display = 'none');
407
- }
408
- }
522
+ // The template itself handles disabling fields if isServerEffectivelyReadOnly is true.
409
523
 
410
524
  toggleServerAliasField(serverIndex, reqIndex, serversListId);
411
525
  toggleServerRegistryConfig(serverIndex, reqIndex, serversListId);
@@ -444,33 +558,6 @@ export function toggleServerRegistryConfig(serverIndex, reqIndex, serversListId
444
558
  });
445
559
  }
446
560
 
447
- export async function browseLocalSchema(serverIndex, serversListId = 'serversList') {
448
- const schemaPath = document.querySelector(`#${serversListId} .server-item[data-index="${serverIndex}"] #schema-path-${serverIndex}`);
449
- if (!schemaPath) return;
450
-
451
- try {
452
- if ('showOpenFilePicker' in window) {
453
- const [fileHandle] = await window.showOpenFilePicker({
454
- types: [{ description: 'JSON Files', accept: { 'application/json': ['.json'] } }]
455
- });
456
- const file = await fileHandle.getFile();
457
- schemaPath.value = file.name;
458
- } else {
459
- const input = document.createElement('input');
460
- input.type = 'file';
461
- input.accept = '.json';
462
- input.onchange = (e) => {
463
- if (e.target.files.length > 0) {
464
- schemaPath.value = e.target.files[0].name;
465
- }
466
- };
467
- input.click();
468
- }
469
- } catch (err) {
470
- console.error('Error browsing for schema file:', err);
471
- }
472
- }
473
-
474
561
  export function toggleSectionContent(contentId, iconElement, toggleElement = null) {
475
562
  const contentElement = document.getElementById(contentId);
476
563
  if (!contentElement) return;
@@ -588,23 +675,28 @@ function handleFormView(elements, currentFormId, currentServersListId, baseCateg
588
675
 
589
676
  function getFeedConfiguration(activeForm, baseCategoryData, isExistingCategoryContext) {
590
677
  if (!isExistingCategoryContext || !baseCategoryData) {
678
+ // If not in an existing category context, or no base data, just get current form data.
591
679
  return activeForm ? getFormData(activeForm) : {};
592
680
  }
593
681
 
682
+ // In an existing category context, get the data from the current form.
683
+ // `true` for forExistingCategoryTab ensures getFormData processes only server-related fields
684
+ // and correctly handles adhoc/new servers.
594
685
  const newData = getFormData(activeForm, true, baseCategoryData);
686
+
687
+ // Start with a deep clone of the original category data (for name, description, etc.)
595
688
  const merged = JSON.parse(JSON.stringify(baseCategoryData));
596
689
 
597
- merged.mcpServers = (merged.mcpServers || []).concat(newData.mcpServers || []);
690
+ // Replace mcpServers with the current state from the form (newData).
691
+ // This ensures deletions and modifications in the form are accurately reflected.
692
+ // newData.mcpServers will include original read-only servers (if any),
693
+ // modified adhoc servers, and newly added servers, all with their current state.
694
+ merged.mcpServers = newData.mcpServers || [];
598
695
 
599
- const existingReqs = new Set(merged.requirements?.map(r => `${r.type}|${r.name}|${r.version}`) || []);
600
- (newData.requirements || []).forEach(req => {
601
- const key = `${req.type}|${req.name}|${req.version}`;
602
- if (!existingReqs.has(key)) {
603
- merged.requirements = merged.requirements || [];
604
- merged.requirements.push(req);
605
- existingReqs.add(key);
606
- }
607
- });
696
+ // Requirements should also be derived from the current state of servers in the form.
697
+ // formDataToFeedConfiguration (which produces newData) calculates global requirements
698
+ // based on the servers it finds. So, newData.requirements should be the correct set.
699
+ merged.requirements = newData.requirements || [];
608
700
 
609
701
  return merged;
610
702
  }
@@ -749,7 +841,6 @@ Object.entries({
749
841
  removeServerRequirement,
750
842
  toggleServerAliasField,
751
843
  toggleServerRegistryConfig,
752
- browseLocalSchema,
753
844
  renderInstallationConfig,
754
845
  toggleSectionContent,
755
846
  copyJsonToClipboard