imcp 0.0.17 → 0.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/serve.js +2 -1
- package/dist/core/installers/clients/BaseClientInstaller.d.ts +25 -2
- package/dist/core/installers/clients/BaseClientInstaller.js +121 -0
- package/dist/core/installers/clients/ClineInstaller.d.ts +1 -6
- package/dist/core/installers/clients/ClineInstaller.js +1 -94
- package/dist/core/installers/clients/GithubCopilotInstaller.d.ts +1 -6
- package/dist/core/installers/clients/GithubCopilotInstaller.js +1 -94
- package/dist/core/installers/clients/MSRooCodeInstaller.d.ts +1 -5
- package/dist/core/installers/clients/MSRooCodeInstaller.js +1 -94
- package/dist/core/loaders/ConfigurationLoader.d.ts +4 -1
- package/dist/core/loaders/ConfigurationLoader.js +24 -3
- package/dist/core/loaders/ConfigurationProvider.d.ts +4 -1
- package/dist/core/loaders/ConfigurationProvider.js +13 -4
- package/dist/core/loaders/ServerSchemaLoader.d.ts +15 -4
- package/dist/core/loaders/ServerSchemaLoader.js +86 -20
- package/dist/core/loaders/ServerSchemaProvider.d.ts +2 -5
- package/dist/core/loaders/ServerSchemaProvider.js +32 -62
- package/dist/core/metadatas/types.d.ts +3 -2
- package/dist/core/onboard/FeedOnboardService.d.ts +14 -7
- package/dist/core/onboard/FeedOnboardService.js +214 -129
- package/dist/core/onboard/OnboardProcessor.d.ts +7 -1
- package/dist/core/onboard/OnboardProcessor.js +52 -8
- package/dist/core/onboard/OnboardStatus.d.ts +6 -1
- package/dist/core/onboard/OnboardStatusManager.d.ts +70 -24
- package/dist/core/onboard/OnboardStatusManager.js +230 -46
- package/dist/core/validators/FeedValidator.d.ts +7 -2
- package/dist/core/validators/FeedValidator.js +61 -7
- package/dist/core/validators/StdioServerValidator.js +84 -32
- package/dist/services/MCPManager.d.ts +2 -1
- package/dist/services/MCPManager.js +5 -1
- package/dist/services/ServerService.js +2 -2
- package/dist/utils/logger.d.ts +2 -0
- package/dist/utils/logger.js +10 -0
- package/dist/web/public/js/modal/installation.js +1 -1
- package/dist/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +41 -9
- package/dist/web/public/js/onboard/formProcessor.js +200 -34
- package/dist/web/public/js/onboard/index.js +2 -2
- package/dist/web/public/js/onboard/publishHandler.js +30 -22
- package/dist/web/public/js/onboard/templates.js +34 -40
- package/dist/web/public/js/onboard/uiHandlers.js +175 -84
- package/dist/web/public/js/onboard/validationHandlers.js +147 -64
- package/dist/web/public/js/serverCategoryDetails.js +19 -4
- package/dist/web/public/js/serverCategoryList.js +13 -1
- package/dist/web/public/onboard.html +1 -1
- package/dist/web/server.js +19 -6
- package/package.json +1 -1
|
@@ -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
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
251
|
-
|
|
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
|
-
|
|
254
|
-
|
|
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
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
373
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
600
|
-
(
|
|
601
|
-
|
|
602
|
-
|
|
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
|