imcp 0.0.14 → 0.0.16
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/core/ConfigurationProvider.d.ts +1 -0
- package/dist/core/ConfigurationProvider.js +15 -0
- package/dist/core/InstallationService.js +2 -7
- package/dist/core/MCPManager.d.ts +11 -2
- package/dist/core/MCPManager.js +24 -1
- package/dist/core/RequirementService.js +2 -8
- package/dist/core/installers/clients/BaseClientInstaller.d.ts +51 -0
- package/dist/core/installers/clients/BaseClientInstaller.js +160 -0
- package/dist/core/installers/clients/ClientInstaller.d.ts +16 -9
- package/dist/core/installers/clients/ClientInstaller.js +80 -527
- package/dist/core/installers/clients/ClientInstallerFactory.d.ts +20 -0
- package/dist/core/installers/clients/ClientInstallerFactory.js +37 -0
- package/dist/core/installers/clients/ClineInstaller.d.ts +18 -0
- package/dist/core/installers/clients/ClineInstaller.js +124 -0
- package/dist/core/installers/clients/GithubCopilotInstaller.d.ts +34 -0
- package/dist/core/installers/clients/GithubCopilotInstaller.js +162 -0
- package/dist/core/installers/clients/MSRooCodeInstaller.d.ts +15 -0
- package/dist/core/installers/clients/MSRooCodeInstaller.js +122 -0
- package/dist/core/installers/requirements/BaseInstaller.d.ts +11 -34
- package/dist/core/installers/requirements/BaseInstaller.js +5 -116
- package/dist/core/installers/requirements/CommandInstaller.d.ts +6 -1
- package/dist/core/installers/requirements/CommandInstaller.js +7 -0
- package/dist/core/installers/requirements/GeneralInstaller.d.ts +6 -1
- package/dist/core/installers/requirements/GeneralInstaller.js +9 -4
- package/dist/core/installers/requirements/NpmInstaller.d.ts +46 -7
- package/dist/core/installers/requirements/NpmInstaller.js +150 -58
- package/dist/core/installers/requirements/PipInstaller.d.ts +9 -0
- package/dist/core/installers/requirements/PipInstaller.js +66 -28
- package/dist/core/onboard/FeedOnboardService.d.ts +50 -13
- package/dist/core/onboard/FeedOnboardService.js +263 -88
- package/dist/core/onboard/OnboardProcessor.d.ts +79 -0
- package/dist/core/onboard/OnboardProcessor.js +290 -0
- package/dist/core/onboard/OnboardStatus.d.ts +49 -0
- package/dist/core/onboard/OnboardStatus.js +10 -0
- package/dist/core/onboard/OnboardStatusManager.d.ts +57 -0
- package/dist/core/onboard/OnboardStatusManager.js +176 -0
- package/dist/core/types.d.ts +4 -5
- package/dist/core/validators/FeedValidator.d.ts +8 -1
- package/dist/core/validators/FeedValidator.js +60 -7
- package/dist/core/validators/IServerValidator.d.ts +19 -0
- package/dist/core/validators/IServerValidator.js +2 -0
- package/dist/core/validators/SSEServerValidator.d.ts +15 -0
- package/dist/core/validators/SSEServerValidator.js +39 -0
- package/dist/core/validators/ServerValidatorFactory.d.ts +24 -0
- package/dist/core/validators/ServerValidatorFactory.js +45 -0
- package/dist/core/validators/StdioServerValidator.d.ts +46 -0
- package/dist/core/validators/StdioServerValidator.js +229 -0
- package/dist/services/InstallRequestValidator.d.ts +1 -1
- package/dist/services/ServerService.d.ts +9 -6
- package/dist/services/ServerService.js +18 -7
- package/dist/utils/adoUtils.d.ts +29 -0
- package/dist/utils/adoUtils.js +252 -0
- package/dist/utils/clientUtils.d.ts +0 -7
- package/dist/utils/clientUtils.js +0 -42
- package/dist/utils/githubUtils.d.ts +10 -0
- package/dist/utils/githubUtils.js +22 -0
- package/dist/utils/macroExpressionUtils.d.ts +38 -0
- package/dist/utils/macroExpressionUtils.js +116 -0
- package/dist/utils/osUtils.d.ts +4 -20
- package/dist/utils/osUtils.js +78 -23
- package/dist/web/contract/serverContract.d.ts +3 -1
- package/dist/web/public/css/notifications.css +48 -17
- package/dist/web/public/css/onboard.css +66 -3
- package/dist/web/public/index.html +84 -16
- package/dist/web/public/js/api.js +3 -6
- package/dist/web/public/js/flights/flights.js +127 -0
- package/dist/web/public/js/modal/installation.js +5 -5
- package/dist/web/public/js/modal/modalSetup.js +3 -2
- package/dist/web/public/js/notifications.js +66 -27
- package/dist/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
- package/dist/web/public/js/onboard/formProcessor.js +810 -255
- package/dist/web/public/js/onboard/index.js +328 -85
- package/dist/web/public/js/onboard/publishHandler.js +132 -0
- package/dist/web/public/js/onboard/state.js +61 -17
- package/dist/web/public/js/onboard/templates.js +217 -249
- package/dist/web/public/js/onboard/uiHandlers.js +679 -117
- package/dist/web/public/js/onboard/validationHandlers.js +378 -0
- package/dist/web/public/js/serverCategoryList.js +15 -2
- package/dist/web/public/onboard.html +191 -45
- package/dist/web/public/styles.css +91 -1
- package/dist/web/server.d.ts +0 -10
- package/dist/web/server.js +131 -22
- package/package.json +2 -2
- package/src/core/ConfigurationProvider.ts +15 -0
- package/src/core/InstallationService.ts +2 -7
- package/src/core/MCPManager.ts +26 -1
- package/src/core/RequirementService.ts +2 -9
- package/src/core/installers/clients/BaseClientInstaller.ts +196 -0
- package/src/core/installers/clients/ClientInstaller.ts +97 -608
- package/src/core/installers/clients/ClientInstallerFactory.ts +43 -0
- package/src/core/installers/clients/ClineInstaller.ts +135 -0
- package/src/core/installers/clients/GithubCopilotInstaller.ts +179 -0
- package/src/core/installers/clients/MSRooCodeInstaller.ts +133 -0
- package/src/core/installers/requirements/BaseInstaller.ts +13 -136
- package/src/core/installers/requirements/CommandInstaller.ts +9 -1
- package/src/core/installers/requirements/GeneralInstaller.ts +11 -4
- package/src/core/installers/requirements/NpmInstaller.ts +178 -61
- package/src/core/installers/requirements/PipInstaller.ts +68 -29
- package/src/core/onboard/FeedOnboardService.ts +346 -0
- package/src/core/onboard/OnboardProcessor.ts +305 -0
- package/src/core/onboard/OnboardStatus.ts +55 -0
- package/src/core/onboard/OnboardStatusManager.ts +188 -0
- package/src/core/types.ts +4 -5
- package/src/core/validators/FeedValidator.ts +79 -0
- package/src/core/validators/IServerValidator.ts +21 -0
- package/src/core/validators/SSEServerValidator.ts +43 -0
- package/src/core/validators/ServerValidatorFactory.ts +51 -0
- package/src/core/validators/StdioServerValidator.ts +259 -0
- package/src/services/InstallRequestValidator.ts +1 -1
- package/src/services/ServerService.ts +22 -7
- package/src/utils/adoUtils.ts +291 -0
- package/src/utils/clientUtils.ts +0 -44
- package/src/utils/githubUtils.ts +24 -0
- package/src/utils/macroExpressionUtils.ts +121 -0
- package/src/utils/osUtils.ts +89 -24
- package/src/web/contract/serverContract.ts +74 -0
- package/src/web/public/css/notifications.css +48 -17
- package/src/web/public/css/onboard.css +107 -0
- package/src/web/public/index.html +84 -16
- package/src/web/public/js/api.js +3 -6
- package/src/web/public/js/flights/flights.js +127 -0
- package/src/web/public/js/modal/installation.js +5 -5
- package/src/web/public/js/modal/modalSetup.js +3 -2
- package/src/web/public/js/notifications.js +66 -27
- package/src/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +338 -0
- package/src/web/public/js/onboard/formProcessor.js +864 -0
- package/src/web/public/js/onboard/index.js +374 -0
- package/src/web/public/js/onboard/publishHandler.js +132 -0
- package/src/web/public/js/onboard/state.js +76 -0
- package/src/web/public/js/onboard/templates.js +343 -0
- package/src/web/public/js/onboard/uiHandlers.js +758 -0
- package/src/web/public/js/onboard/validationHandlers.js +378 -0
- package/src/web/public/js/serverCategoryList.js +15 -2
- package/src/web/public/onboard.html +296 -0
- package/src/web/public/styles.css +91 -1
- package/src/web/server.ts +167 -58
|
@@ -0,0 +1,864 @@
|
|
|
1
|
+
// Form data processing and API calls
|
|
2
|
+
import { showToast } from '../notifications.js';
|
|
3
|
+
import { state, setServerCounter, getServerCounter, clearEnvCountersForTab, clearServerRequirementCountersForTab } from './state.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Adds real-time validation to form inputs.
|
|
7
|
+
* @param {string} formId - The ID of the form to add validation to.
|
|
8
|
+
*/
|
|
9
|
+
function setupRealTimeValidation(formId) {
|
|
10
|
+
const form = document.getElementById(formId);
|
|
11
|
+
if (!form) return;
|
|
12
|
+
|
|
13
|
+
// Helper function to show validation message
|
|
14
|
+
const showValidationMessage = (element, message, isError = true) => {
|
|
15
|
+
let messageDiv = element.nextElementSibling;
|
|
16
|
+
if (!messageDiv || !messageDiv.classList.contains('validation-message')) {
|
|
17
|
+
messageDiv = document.createElement('div');
|
|
18
|
+
messageDiv.className = `validation-message text-xs mt-1 ${isError ? 'text-red-500' : 'text-green-500'}`;
|
|
19
|
+
element.insertAdjacentElement('afterend', messageDiv);
|
|
20
|
+
}
|
|
21
|
+
messageDiv.textContent = message;
|
|
22
|
+
messageDiv.className = `validation-message text-xs mt-1 ${isError ? 'text-red-500' : 'text-green-500'}`;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Name input validation (category or server)
|
|
26
|
+
const nameInput = form.querySelector('input[name="name"]');
|
|
27
|
+
if (nameInput) {
|
|
28
|
+
nameInput.addEventListener('input', (e) => {
|
|
29
|
+
const value = e.target.value.trim();
|
|
30
|
+
if (!value) {
|
|
31
|
+
showValidationMessage(e.target, 'Name is required');
|
|
32
|
+
} else if (!/^[a-zA-Z0-9-_]+$/.test(value)) {
|
|
33
|
+
showValidationMessage(e.target, 'Only alphanumeric characters, hyphens, and underscores allowed');
|
|
34
|
+
} else if (value.length > 50) {
|
|
35
|
+
showValidationMessage(e.target, 'Must not exceed 50 characters');
|
|
36
|
+
} else {
|
|
37
|
+
showValidationMessage(e.target, 'Valid name', false);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Display name validation
|
|
43
|
+
const displayNameInput = form.querySelector('input[name="displayName"]');
|
|
44
|
+
if (displayNameInput) {
|
|
45
|
+
displayNameInput.addEventListener('input', (e) => {
|
|
46
|
+
const value = e.target.value.trim();
|
|
47
|
+
if (!value) {
|
|
48
|
+
showValidationMessage(e.target, 'Display name is required');
|
|
49
|
+
} else if (value.length > 100) {
|
|
50
|
+
showValidationMessage(e.target, 'Must not exceed 100 characters');
|
|
51
|
+
} else {
|
|
52
|
+
showValidationMessage(e.target, 'Valid display name', false);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Description validation
|
|
58
|
+
const descriptionInput = form.querySelector('textarea[name="description"]');
|
|
59
|
+
if (descriptionInput) {
|
|
60
|
+
descriptionInput.addEventListener('input', (e) => {
|
|
61
|
+
const value = e.target.value.trim();
|
|
62
|
+
if (value.length > 500) {
|
|
63
|
+
showValidationMessage(e.target, 'Must not exceed 500 characters');
|
|
64
|
+
} else if (value.length === 0) {
|
|
65
|
+
showValidationMessage(e.target, 'Description is required');
|
|
66
|
+
} else {
|
|
67
|
+
showValidationMessage(e.target, 'Valid description', false);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Repository URL validation
|
|
73
|
+
const repoInput = form.querySelector('input[name="repository"]');
|
|
74
|
+
if (repoInput) {
|
|
75
|
+
repoInput.addEventListener('input', (e) => {
|
|
76
|
+
const value = e.target.value.trim();
|
|
77
|
+
if (value) {
|
|
78
|
+
try {
|
|
79
|
+
new URL(value);
|
|
80
|
+
showValidationMessage(e.target, 'Valid URL', false);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
showValidationMessage(e.target, 'Invalid URL format');
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
e.target.nextElementSibling?.remove(); // Remove validation message if empty
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Server mode validation
|
|
91
|
+
form.addEventListener('change', (e) => {
|
|
92
|
+
if (e.target.name?.match(/servers\[\d+\]\.mode/)) {
|
|
93
|
+
const serverIndex = e.target.name.match(/servers\[(\d+)\]/)[1];
|
|
94
|
+
const commandInput = form.querySelector(`[name="servers[${serverIndex}].installation.command"]`);
|
|
95
|
+
const urlInput = form.querySelector(`[name="servers[${serverIndex}].installation.url"]`);
|
|
96
|
+
const argsInput = form.querySelector(`[name="servers[${serverIndex}].installation.args"]`);
|
|
97
|
+
|
|
98
|
+
if (e.target.value === 'stdio') {
|
|
99
|
+
if (commandInput) {
|
|
100
|
+
commandInput.required = true;
|
|
101
|
+
showValidationMessage(commandInput, 'Command is required for stdio mode');
|
|
102
|
+
}
|
|
103
|
+
if (urlInput) {
|
|
104
|
+
urlInput.required = false;
|
|
105
|
+
urlInput.value = '';
|
|
106
|
+
urlInput.nextElementSibling?.remove();
|
|
107
|
+
}
|
|
108
|
+
} else if (e.target.value === 'sse') {
|
|
109
|
+
if (urlInput) {
|
|
110
|
+
urlInput.required = true;
|
|
111
|
+
showValidationMessage(urlInput, 'URL is required for sse mode');
|
|
112
|
+
}
|
|
113
|
+
if (commandInput) {
|
|
114
|
+
commandInput.required = false;
|
|
115
|
+
commandInput.value = '';
|
|
116
|
+
commandInput.nextElementSibling?.remove();
|
|
117
|
+
}
|
|
118
|
+
if (argsInput) {
|
|
119
|
+
argsInput.value = '';
|
|
120
|
+
argsInput.nextElementSibling?.remove();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Environment variable validation
|
|
127
|
+
form.addEventListener('input', (e) => {
|
|
128
|
+
if (e.target.name?.includes('.installation.env[')) {
|
|
129
|
+
const matches = e.target.name.match(/servers\[(\d+)\]\.installation\.env\[(\d+)\]\.(name|default)/);
|
|
130
|
+
if (matches) {
|
|
131
|
+
const [, serverIndex, envIndex, field] = matches;
|
|
132
|
+
if (field === 'name') {
|
|
133
|
+
const value = e.target.value.trim();
|
|
134
|
+
if (!value) {
|
|
135
|
+
showValidationMessage(e.target, 'Environment variable name is required');
|
|
136
|
+
} else if (!/^[A-Z_][A-Z0-9_]*$/.test(value)) {
|
|
137
|
+
showValidationMessage(e.target, 'Must be uppercase with only letters, numbers, and underscores');
|
|
138
|
+
} else if (value.length > 50) {
|
|
139
|
+
showValidationMessage(e.target, 'Must not exceed 50 characters');
|
|
140
|
+
} else {
|
|
141
|
+
showValidationMessage(e.target, 'Valid name', false);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Submits the form data for onboarding or updating a server category.
|
|
151
|
+
* @param {Event} event - The form submission event.
|
|
152
|
+
* @param {string} activeTab - Identifier for the active tab ('create-category' or 'create-server').
|
|
153
|
+
* @param {object|null} currentSelectedCategoryData - Full FeedConfiguration of the selected category if activeTab is 'create-server'.
|
|
154
|
+
*/
|
|
155
|
+
export async function submitForm(event, activeTab, currentSelectedCategoryData = null) {
|
|
156
|
+
event.preventDefault();
|
|
157
|
+
const formElement = event.target;
|
|
158
|
+
let feedConfiguration;
|
|
159
|
+
let isUpdateOperation = false;
|
|
160
|
+
|
|
161
|
+
if (activeTab === 'create-category') {
|
|
162
|
+
try {
|
|
163
|
+
feedConfiguration = formDataToFeedConfiguration(formElement);
|
|
164
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
165
|
+
isUpdateOperation = urlParams.get('action') === 'edit' && urlParams.get('category') === feedConfiguration.name;
|
|
166
|
+
} catch (validationError) {
|
|
167
|
+
console.error('Validation error:', validationError);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
} else if (activeTab === 'create-server') {
|
|
171
|
+
if (!currentSelectedCategoryData || !currentSelectedCategoryData.name) {
|
|
172
|
+
showToast('No existing category selected or category data is missing.', 'error');
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const newServersData = formDataToFeedConfiguration(formElement, true, currentSelectedCategoryData);
|
|
176
|
+
|
|
177
|
+
feedConfiguration = JSON.parse(JSON.stringify(currentSelectedCategoryData)); // Deep clone
|
|
178
|
+
|
|
179
|
+
feedConfiguration.mcpServers = (feedConfiguration.mcpServers || []).concat(newServersData.mcpServers || []);
|
|
180
|
+
|
|
181
|
+
const existingReqKeys = new Set((feedConfiguration.requirements || []).map(r => `${r.type}|${r.name}|${r.version}`));
|
|
182
|
+
(newServersData.requirements || []).forEach(newReq => {
|
|
183
|
+
const reqKey = `${newReq.type}|${newReq.name}|${newReq.version}`;
|
|
184
|
+
if (!existingReqKeys.has(reqKey)) {
|
|
185
|
+
feedConfiguration.requirements.push(newReq);
|
|
186
|
+
existingReqKeys.add(reqKey);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
isUpdateOperation = true;
|
|
190
|
+
} else {
|
|
191
|
+
showToast('Invalid tab context for submission.', 'error');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const response = await fetch('/api/categories/onboard', {
|
|
197
|
+
method: 'POST',
|
|
198
|
+
headers: { 'Content-Type': 'application/json' },
|
|
199
|
+
body: JSON.stringify({
|
|
200
|
+
categoryData: feedConfiguration,
|
|
201
|
+
isUpdate: isUpdateOperation
|
|
202
|
+
})
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
if (!response.ok) {
|
|
206
|
+
const errorData = await response.json().catch(() => ({ error: 'Failed to parse error response' }));
|
|
207
|
+
throw new Error(`HTTP error! status: ${response.status}, message: ${errorData.error || response.statusText}`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const result = await response.json();
|
|
211
|
+
console.log('Form submitted successfully:', result);
|
|
212
|
+
showToast('Form submitted successfully! See console for operation details.', 'success');
|
|
213
|
+
} catch (error) {
|
|
214
|
+
console.error('Error submitting form:', error);
|
|
215
|
+
showToast(`Error submitting form: ${error.message}. Please try again.`, 'error');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Retrieves form data from a given HTMLFormElement.
|
|
221
|
+
* @param {HTMLFormElement} formElement - The form element to process.
|
|
222
|
+
* @param {boolean} [forExistingCategoryTab=false] - True if processing for the "Create Server in Existing Category" tab.
|
|
223
|
+
* @param {object|null} [baseCategoryData=null] - The base FeedConfiguration of the selected category (used if forExistingCategoryTab is true).
|
|
224
|
+
* @returns {object} The FeedConfiguration object (or partial for new servers if forExistingCategoryTab is true).
|
|
225
|
+
*/
|
|
226
|
+
export function getFormData(formElement, forExistingCategoryTab = false, baseCategoryData = null) {
|
|
227
|
+
return formDataToFeedConfiguration(formElement, forExistingCategoryTab, baseCategoryData);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Converts form data to a FeedConfiguration object.
|
|
232
|
+
* @param {HTMLFormElement} formElement - The form element.
|
|
233
|
+
* @param {boolean} [forExistingCategoryTab=false] - True if processing for the "Create Server in Existing Category" tab.
|
|
234
|
+
* @param {object|null} [baseCategoryData=null] - The base FeedConfiguration of the selected category.
|
|
235
|
+
* @returns {object} The FeedConfiguration object. If forExistingCategoryTab is true, this will be a partial
|
|
236
|
+
* FeedConfiguration containing only newly added servers and their requirements.
|
|
237
|
+
*/
|
|
238
|
+
function validateServerConfig(serverConfig) {
|
|
239
|
+
const errors = [];
|
|
240
|
+
|
|
241
|
+
// Validate required fields
|
|
242
|
+
if (!serverConfig.name) {
|
|
243
|
+
errors.push('Server name is required');
|
|
244
|
+
} else if (!/^[a-zA-Z0-9-_]+$/.test(serverConfig.name)) {
|
|
245
|
+
errors.push('Server name must only contain alphanumeric characters, hyphens, and underscores');
|
|
246
|
+
} else if (serverConfig.name.length > 50) {
|
|
247
|
+
errors.push('Server name must not exceed 50 characters');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!serverConfig.description || serverConfig.description.trim() === '') {
|
|
251
|
+
errors.push('Server description is required');
|
|
252
|
+
} else if (serverConfig.description.length > 200) {
|
|
253
|
+
errors.push('Server description must not exceed 200 characters');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (!serverConfig.mode) {
|
|
257
|
+
errors.push('Server mode is required');
|
|
258
|
+
} else if (!['stdio', 'sse'].includes(serverConfig.mode)) {
|
|
259
|
+
errors.push('Server mode must be either "stdio" or "sse"');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Validate installation config based on mode
|
|
263
|
+
if (serverConfig.mode === 'stdio') {
|
|
264
|
+
if (!serverConfig.installation?.command) {
|
|
265
|
+
errors.push('Command is required for stdio server');
|
|
266
|
+
} else if (!/^[a-zA-Z0-9-_.\\/]+$/.test(serverConfig.installation.command)) {
|
|
267
|
+
errors.push('Command must only contain alphanumeric characters, hyphens, underscores, dots, and slashes');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Validate arguments format if present
|
|
271
|
+
if (serverConfig.installation?.args?.length > 0) {
|
|
272
|
+
serverConfig.installation.args.forEach((arg, index) => {
|
|
273
|
+
if (!/^[a-zA-Z0-9-_./\\]+$/.test(arg)) {
|
|
274
|
+
errors.push(`Argument ${index + 1} must only contain alphanumeric characters, hyphens, underscores, dots, and slashes`);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
} else if (serverConfig.mode === 'sse') {
|
|
279
|
+
if (!serverConfig.installation?.url) {
|
|
280
|
+
errors.push('URL is required for sse server');
|
|
281
|
+
} else {
|
|
282
|
+
try {
|
|
283
|
+
new URL(serverConfig.installation.url);
|
|
284
|
+
} catch (e) {
|
|
285
|
+
errors.push('Invalid URL format');
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Validate env variables if present
|
|
291
|
+
if (serverConfig.installation?.env) {
|
|
292
|
+
Object.entries(serverConfig.installation.env).forEach(([name, config]) => {
|
|
293
|
+
if (!name) {
|
|
294
|
+
errors.push('Environment variable name is required');
|
|
295
|
+
} else if (!/^[A-Z_][A-Z0-9_]*$/.test(name)) {
|
|
296
|
+
errors.push(`Environment variable name "${name}" must be uppercase with only letters, numbers, and underscores`);
|
|
297
|
+
} else if (name.length > 50) {
|
|
298
|
+
errors.push(`Environment variable name "${name}" must not exceed 50 characters`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (config.Required && !config.Default) {
|
|
302
|
+
errors.push(`Required environment variable "${name}" should have a default value`);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (config.Description && config.Description.length > 200) {
|
|
306
|
+
errors.push(`Description for environment variable "${name}" must not exceed 200 characters`);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Validate schemas if present
|
|
312
|
+
if (serverConfig.schemas) {
|
|
313
|
+
try {
|
|
314
|
+
JSON.parse(serverConfig.schemas);
|
|
315
|
+
} catch (e) {
|
|
316
|
+
errors.push('Invalid JSON format in schemas');
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return errors;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function formDataToFeedConfiguration(formElement, forExistingCategoryTab = false, baseCategoryData = null) {
|
|
324
|
+
const currentFormData = new FormData(formElement);
|
|
325
|
+
const feedConfiguration = {
|
|
326
|
+
requirements: [],
|
|
327
|
+
mcpServers: []
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
if (!forExistingCategoryTab) {
|
|
331
|
+
feedConfiguration.name = currentFormData.get('name') || '';
|
|
332
|
+
feedConfiguration.displayName = currentFormData.get('displayName') || '';
|
|
333
|
+
feedConfiguration.description = currentFormData.get('description') || '';
|
|
334
|
+
feedConfiguration.repository = currentFormData.get('repository') || undefined;
|
|
335
|
+
} else if (baseCategoryData) {
|
|
336
|
+
feedConfiguration.name = baseCategoryData.name;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const serverDataMap = new Map();
|
|
340
|
+
const globalRequirementsMap = new Map();
|
|
341
|
+
|
|
342
|
+
for (const [key, value] of currentFormData.entries()) {
|
|
343
|
+
if (forExistingCategoryTab && !key.startsWith('servers[')) {
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const serverMatchKey = key.match(/^servers\[(\d+)\]\.(.+)$/);
|
|
348
|
+
if (serverMatchKey) {
|
|
349
|
+
let serverIndexInForm = parseInt(serverMatchKey[1], 10);
|
|
350
|
+
const fieldPathFromMatch = serverMatchKey[2];
|
|
351
|
+
let actualServerDataIndex = serverIndexInForm;
|
|
352
|
+
|
|
353
|
+
if (!serverDataMap.has(actualServerDataIndex)) {
|
|
354
|
+
serverDataMap.set(actualServerDataIndex, {
|
|
355
|
+
installation: { env: new Map() },
|
|
356
|
+
dependencies: { requirements: new Map() },
|
|
357
|
+
isNew: true // Assuming all servers processed this way are new additions to the form
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
const currentServerData = serverDataMap.get(actualServerDataIndex);
|
|
361
|
+
|
|
362
|
+
if (fieldPathFromMatch === 'name') currentServerData.name = value;
|
|
363
|
+
else if (fieldPathFromMatch === 'description') currentServerData.description = value;
|
|
364
|
+
else if (fieldPathFromMatch === 'mode') currentServerData.mode = value;
|
|
365
|
+
else if (fieldPathFromMatch === 'repository') currentServerData.repository = value || undefined;
|
|
366
|
+
else if (fieldPathFromMatch === 'schemas') currentServerData.schemas = value || undefined;
|
|
367
|
+
else if (fieldPathFromMatch === 'installation.command') currentServerData.installation.command = value;
|
|
368
|
+
else if (fieldPathFromMatch === 'installation.args') {
|
|
369
|
+
currentServerData.installation.args = value ? value.split(',').map(arg => arg.trim()).filter(arg => arg) : [];
|
|
370
|
+
}
|
|
371
|
+
else if (fieldPathFromMatch === 'installation.url') {
|
|
372
|
+
currentServerData.installation.url = value;
|
|
373
|
+
}
|
|
374
|
+
else if (fieldPathFromMatch.startsWith('installation.env[')) {
|
|
375
|
+
const envMatch = fieldPathFromMatch.match(/^installation\.env\[(\d+)\]\.(name|default|required|description)$/);
|
|
376
|
+
if (envMatch) {
|
|
377
|
+
const envIndex = parseInt(envMatch[1], 10);
|
|
378
|
+
const envField = envMatch[2];
|
|
379
|
+
if (!currentServerData.installation.env.has(envIndex)) {
|
|
380
|
+
currentServerData.installation.env.set(envIndex, {});
|
|
381
|
+
}
|
|
382
|
+
const currentEnv = currentServerData.installation.env.get(envIndex);
|
|
383
|
+
if (envField === 'required') {
|
|
384
|
+
currentEnv[envField] = currentFormData.get(key) === 'on';
|
|
385
|
+
} else {
|
|
386
|
+
currentEnv[envField] = value || undefined;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
else if (fieldPathFromMatch.startsWith('requirements[')) {
|
|
391
|
+
const reqMatch = fieldPathFromMatch.match(/^requirements\[(\d+)\]\.(.+)$/);
|
|
392
|
+
if (reqMatch) {
|
|
393
|
+
const reqIndex = parseInt(reqMatch[1], 10);
|
|
394
|
+
const reqFieldPath = reqMatch[2];
|
|
395
|
+
|
|
396
|
+
if (!currentServerData.dependencies.requirements.has(reqIndex)) {
|
|
397
|
+
currentServerData.dependencies.requirements.set(reqIndex, { registry: {} });
|
|
398
|
+
}
|
|
399
|
+
const currentReq = currentServerData.dependencies.requirements.get(reqIndex);
|
|
400
|
+
|
|
401
|
+
if (reqFieldPath === 'name') currentReq.name = value;
|
|
402
|
+
else if (reqFieldPath === 'type') currentReq.type = value;
|
|
403
|
+
else if (reqFieldPath === 'version') currentReq.version = value;
|
|
404
|
+
else if (reqFieldPath === 'order') currentReq.order = value ? parseInt(value, 10) : undefined;
|
|
405
|
+
else if (reqFieldPath === 'alias') currentReq.alias = value || undefined;
|
|
406
|
+
else if (reqFieldPath === 'registryType') {
|
|
407
|
+
currentReq.registryType = value;
|
|
408
|
+
} else if (reqFieldPath.startsWith('registry.githubRelease.')) {
|
|
409
|
+
currentReq.registry.githubRelease = currentReq.registry.githubRelease || {};
|
|
410
|
+
currentReq.registry.githubRelease[reqFieldPath.substring('registry.githubRelease.'.length)] = value || undefined;
|
|
411
|
+
} else if (reqFieldPath.startsWith('registry.artifacts.')) {
|
|
412
|
+
currentReq.registry.artifacts = currentReq.registry.artifacts || {};
|
|
413
|
+
currentReq.registry.artifacts[reqFieldPath.substring('registry.artifacts.'.length)] = value || undefined;
|
|
414
|
+
} else if (reqFieldPath.startsWith('registry.local.')) {
|
|
415
|
+
currentReq.registry.local = currentReq.registry.local || {};
|
|
416
|
+
currentReq.registry.local[reqFieldPath.substring('registry.local.'.length)] = value || undefined;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
serverDataMap.forEach(serverRaw => {
|
|
424
|
+
let mcpServer;
|
|
425
|
+
if (serverRaw.mode === 'sse') {
|
|
426
|
+
mcpServer = {
|
|
427
|
+
name: serverRaw.name,
|
|
428
|
+
description: serverRaw.description,
|
|
429
|
+
mode: serverRaw.mode,
|
|
430
|
+
schemas: serverRaw.schemas,
|
|
431
|
+
repository: serverRaw.repository,
|
|
432
|
+
installation: {
|
|
433
|
+
url: serverRaw.installation.url || ''
|
|
434
|
+
},
|
|
435
|
+
dependencies: {
|
|
436
|
+
requirements: []
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
} else {
|
|
440
|
+
mcpServer = {
|
|
441
|
+
name: serverRaw.name,
|
|
442
|
+
description: serverRaw.description,
|
|
443
|
+
mode: serverRaw.mode,
|
|
444
|
+
schemas: serverRaw.schemas,
|
|
445
|
+
repository: serverRaw.repository,
|
|
446
|
+
installation: {
|
|
447
|
+
command: serverRaw.installation.command,
|
|
448
|
+
args: serverRaw.installation.args || [],
|
|
449
|
+
env: {}
|
|
450
|
+
},
|
|
451
|
+
dependencies: {
|
|
452
|
+
requirements: []
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
const envVars = {};
|
|
457
|
+
serverRaw.installation.env.forEach(env => {
|
|
458
|
+
if (env.name) {
|
|
459
|
+
envVars[env.name] = {
|
|
460
|
+
Required: env.required || false,
|
|
461
|
+
Description: env.description || '',
|
|
462
|
+
Default: env.default || undefined
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
if (Object.keys(envVars).length > 0) {
|
|
467
|
+
mcpServer.installation.env = envVars;
|
|
468
|
+
} else {
|
|
469
|
+
delete mcpServer.installation.env;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
serverRaw.dependencies.requirements.forEach(reqRaw => {
|
|
474
|
+
if (!reqRaw.name || !reqRaw.type || !reqRaw.version) return;
|
|
475
|
+
|
|
476
|
+
const fullRequirementConfig = {
|
|
477
|
+
name: reqRaw.name,
|
|
478
|
+
type: reqRaw.type,
|
|
479
|
+
version: reqRaw.version,
|
|
480
|
+
alias: reqRaw.alias,
|
|
481
|
+
order: reqRaw.order,
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
if (reqRaw.registryType && reqRaw.registryType !== 'public') {
|
|
485
|
+
fullRequirementConfig.registry = {};
|
|
486
|
+
if (reqRaw.registryType === 'github' && reqRaw.registry.githubRelease) {
|
|
487
|
+
fullRequirementConfig.registry.githubRelease = {
|
|
488
|
+
repository: reqRaw.registry.githubRelease.repository,
|
|
489
|
+
assetsName: reqRaw.registry.githubRelease.assetsName,
|
|
490
|
+
assetName: reqRaw.registry.githubRelease.assetName,
|
|
491
|
+
};
|
|
492
|
+
} else if (reqRaw.registryType === 'artifacts' && reqRaw.registry.artifacts) {
|
|
493
|
+
fullRequirementConfig.registry.artifacts = {
|
|
494
|
+
registryUrl: reqRaw.registry.artifacts.registryUrl,
|
|
495
|
+
registryName: reqRaw.registry.artifacts.registryName,
|
|
496
|
+
};
|
|
497
|
+
} else if (reqRaw.registryType === 'local' && reqRaw.registry.local) {
|
|
498
|
+
fullRequirementConfig.registry.local = {
|
|
499
|
+
localPath: reqRaw.registry.local.localPath,
|
|
500
|
+
assetName: reqRaw.registry.local.assetName,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
if (fullRequirementConfig.registry.githubRelease && !fullRequirementConfig.registry.githubRelease.repository) delete fullRequirementConfig.registry.githubRelease;
|
|
504
|
+
if (fullRequirementConfig.registry.artifacts && !fullRequirementConfig.registry.artifacts.registryUrl) delete fullRequirementConfig.registry.artifacts;
|
|
505
|
+
if (fullRequirementConfig.registry.local && !fullRequirementConfig.registry.local.localPath) delete fullRequirementConfig.registry.local;
|
|
506
|
+
if (Object.keys(fullRequirementConfig.registry).length === 0) delete fullRequirementConfig.registry;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
mcpServer.dependencies.requirements.push({
|
|
510
|
+
name: reqRaw.name,
|
|
511
|
+
version: reqRaw.version,
|
|
512
|
+
order: reqRaw.order
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
const reqKey = `${reqRaw.type}|${reqRaw.name}|${reqRaw.version}`;
|
|
516
|
+
if (!globalRequirementsMap.has(reqKey)) {
|
|
517
|
+
const { order, ...reqConfigForGlobal } = fullRequirementConfig;
|
|
518
|
+
globalRequirementsMap.set(reqKey, reqConfigForGlobal);
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
if (mcpServer.dependencies.requirements.length === 0) {
|
|
522
|
+
delete mcpServer.dependencies;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
feedConfiguration.mcpServers.push(mcpServer);
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
feedConfiguration.requirements = Array.from(globalRequirementsMap.values());
|
|
529
|
+
|
|
530
|
+
// Validate the entire configuration
|
|
531
|
+
const errors = [];
|
|
532
|
+
|
|
533
|
+
// Validate basic category information
|
|
534
|
+
if (!forExistingCategoryTab) {
|
|
535
|
+
if (!feedConfiguration.name) {
|
|
536
|
+
errors.push('Category name is required');
|
|
537
|
+
} else if (!/^[a-zA-Z0-9-_]+$/.test(feedConfiguration.name)) {
|
|
538
|
+
errors.push('Category name must only contain alphanumeric characters, hyphens, and underscores');
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (!feedConfiguration.displayName) {
|
|
542
|
+
errors.push('Display name is required');
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Validate each server configuration
|
|
547
|
+
feedConfiguration.mcpServers.forEach((server, index) => {
|
|
548
|
+
const serverErrors = validateServerConfig(server);
|
|
549
|
+
if (serverErrors.length > 0) {
|
|
550
|
+
errors.push(`Server ${index + 1} (${server.name || 'unnamed'}): ${serverErrors.join(', ')}`);
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
// Validate requirements
|
|
555
|
+
feedConfiguration.requirements.forEach((req, index) => {
|
|
556
|
+
if (!req.name || !req.type || !req.version) {
|
|
557
|
+
errors.push(`Requirement ${index + 1}: name, type, and version are required`);
|
|
558
|
+
}
|
|
559
|
+
if (req.registry) {
|
|
560
|
+
const registryType = Object.keys(req.registry)[0];
|
|
561
|
+
if (!['githubRelease', 'artifacts', 'local'].includes(registryType)) {
|
|
562
|
+
errors.push(`Requirement ${index + 1}: Invalid registry type ${registryType}`);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
if (errors.length > 0) {
|
|
568
|
+
// Remove previous error messages
|
|
569
|
+
formElement.querySelectorAll('.validation-error-message').forEach(el => el.remove());
|
|
570
|
+
|
|
571
|
+
// Map field names to input selectors
|
|
572
|
+
const fieldMap = {
|
|
573
|
+
'Category name is required': 'input[name="name"]',
|
|
574
|
+
'Category name must only contain alphanumeric characters, hyphens, and underscores': 'input[name="name"]',
|
|
575
|
+
'Category name must not exceed 50 characters': 'input[name="name"]',
|
|
576
|
+
'Display name is required': 'input[name="displayName"]',
|
|
577
|
+
'Display name must not exceed 100 characters': 'input[name="displayName"]',
|
|
578
|
+
'Description must not exceed 500 characters': 'textarea[name="description"]',
|
|
579
|
+
'Repository URL must be a valid URL': 'input[name="repository"]'
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
// Show error messages below relevant fields
|
|
583
|
+
errors.forEach(errorMsg => {
|
|
584
|
+
let selector = null;
|
|
585
|
+
if (errorMsg.includes('Category name')) selector = 'input[name="name"]';
|
|
586
|
+
else if (errorMsg.includes('Display name')) selector = 'input[name="displayName"]';
|
|
587
|
+
// Add more mappings as needed
|
|
588
|
+
|
|
589
|
+
if (selector) {
|
|
590
|
+
const input = formElement.querySelector(selector);
|
|
591
|
+
if (input) {
|
|
592
|
+
const errorDiv = document.createElement('div');
|
|
593
|
+
errorDiv.className = 'validation-error-message text-red-500 text-xs mt-1';
|
|
594
|
+
errorDiv.textContent = errorMsg;
|
|
595
|
+
input.insertAdjacentElement('afterend', errorDiv);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
return feedConfiguration;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Populates the form with data from a FeedConfiguration object.
|
|
606
|
+
* @param {object} feedConfig - The FeedConfiguration object.
|
|
607
|
+
* @param {string} [formId='onboardForm'] - The ID of the form to populate.
|
|
608
|
+
* @param {boolean} [renderServersAsReadOnly=false] - If true, renders servers from feedConfig as read-only.
|
|
609
|
+
* This is used for the "Create Server in Existing Category" tab.
|
|
610
|
+
* @param {string|null} [targetServersListId=null] - The ID of the servers list container. Defaults based on formId.
|
|
611
|
+
*/
|
|
612
|
+
export function populateForm(feedConfig, formId = 'onboardForm', renderServersAsReadOnly = false, targetServersListId = null) {
|
|
613
|
+
const currentForm = document.getElementById(formId);
|
|
614
|
+
if (!currentForm) {
|
|
615
|
+
console.error(`populateForm: Form with ID "${formId}" not found.`);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const serversListId = targetServersListId || (formId === 'onboardForm' ? 'serversList' : 'existingCategoryServersList');
|
|
620
|
+
|
|
621
|
+
resetOnboardFormDynamicContent(formId, serversListId); // This also clears the target server list's innerHTML
|
|
622
|
+
|
|
623
|
+
// Setup real-time validation
|
|
624
|
+
setupRealTimeValidation(formId);
|
|
625
|
+
|
|
626
|
+
if (!feedConfig) return;
|
|
627
|
+
|
|
628
|
+
if (formId === 'onboardForm') {
|
|
629
|
+
currentForm.querySelector('[name="name"]').value = feedConfig.name || '';
|
|
630
|
+
currentForm.querySelector('[name="displayName"]').value = feedConfig.displayName || '';
|
|
631
|
+
currentForm.querySelector('[name="description"]').value = feedConfig.description || '';
|
|
632
|
+
if (feedConfig.repository) {
|
|
633
|
+
currentForm.querySelector('[name="repository"]').value = feedConfig.repository;
|
|
634
|
+
}
|
|
635
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
636
|
+
if (urlParams.get('action') === 'edit' && urlParams.get('category') === feedConfig.name) {
|
|
637
|
+
const nameInput = currentForm.querySelector('[name="name"]');
|
|
638
|
+
if (nameInput) {
|
|
639
|
+
nameInput.readOnly = true;
|
|
640
|
+
nameInput.classList.add('bg-gray-100', 'cursor-not-allowed');
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
} else if (formId === 'onboardServerForm' && feedConfig) {
|
|
644
|
+
// Populate basic category information for the "Create Server in Existing Category" tab
|
|
645
|
+
// These fields are read-only in this tab, but their values need to be set from feedConfig.
|
|
646
|
+
currentForm.querySelector('input[name="name"]').value = feedConfig.name || '';
|
|
647
|
+
currentForm.querySelector('input[name="displayName"]').value = feedConfig.displayName || '';
|
|
648
|
+
currentForm.querySelector('textarea[name="description"]').value = feedConfig.description || '';
|
|
649
|
+
currentForm.querySelector('input[name="repository"]').value = feedConfig.repository || '';
|
|
650
|
+
|
|
651
|
+
// Ensure the containers for basic info and MCP servers are visible,
|
|
652
|
+
// as resetOnboardFormDynamicContent might have hidden them.
|
|
653
|
+
const basicInfoContainer = document.getElementById('existingCategoryBasicInfoContainer');
|
|
654
|
+
if (basicInfoContainer) {
|
|
655
|
+
basicInfoContainer.classList.remove('hidden');
|
|
656
|
+
}
|
|
657
|
+
const mcpServersContainer = document.getElementById('existingCategoryMcpServersContainer');
|
|
658
|
+
if (mcpServersContainer) {
|
|
659
|
+
// Visibility of mcpServersContainer is also handled by whether servers exist,
|
|
660
|
+
// but ensuring it's not hidden here is a good measure after reset.
|
|
661
|
+
mcpServersContainer.classList.remove('hidden');
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Reset tab-specific server counters before populating a list of servers from data.
|
|
666
|
+
// This ensures that servers rendered by this populateForm call start their numbering fresh for this tab.
|
|
667
|
+
setServerCounter(serversListId, 0);
|
|
668
|
+
clearEnvCountersForTab(serversListId);
|
|
669
|
+
clearServerRequirementCountersForTab(serversListId);
|
|
670
|
+
|
|
671
|
+
(feedConfig.mcpServers || []).forEach((server) => {
|
|
672
|
+
// Get the correct current index for this tab before adding the server
|
|
673
|
+
const currentServerIndex = getServerCounter(serversListId);
|
|
674
|
+
// window.addServer will increment the counter for serversListId internally
|
|
675
|
+
window.addServer(serversListId, renderServersAsReadOnly, server);
|
|
676
|
+
|
|
677
|
+
const serverNameInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].name"]`);
|
|
678
|
+
if (serverNameInput) serverNameInput.value = server.name || '';
|
|
679
|
+
|
|
680
|
+
const modeInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].mode"]`);
|
|
681
|
+
if (modeInput) {
|
|
682
|
+
modeInput.value = server.mode || 'stdio';
|
|
683
|
+
if (typeof window.renderInstallationConfig === 'function') {
|
|
684
|
+
window.renderInstallationConfig(currentServerIndex, serversListId, server.mode || 'stdio', renderServersAsReadOnly, server.installation);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
const descInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].description"]`);
|
|
688
|
+
if (descInput) descInput.value = server.description || '';
|
|
689
|
+
|
|
690
|
+
if (server.schemas) {
|
|
691
|
+
const schemaPathEl = document.getElementById(`schema-path-${currentServerIndex}`);
|
|
692
|
+
if (schemaPathEl) schemaPathEl.value = server.schemas;
|
|
693
|
+
}
|
|
694
|
+
if (server.repository) {
|
|
695
|
+
const repoInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].repository"]`);
|
|
696
|
+
if (repoInput) repoInput.value = server.repository;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
if (server.installation) {
|
|
700
|
+
if (server.mode === 'sse') {
|
|
701
|
+
const urlInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].installation.url"]`);
|
|
702
|
+
if (urlInput) urlInput.value = server.installation.url || '';
|
|
703
|
+
} else {
|
|
704
|
+
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)) {
|
|
707
|
+
const argsInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].installation.args"]`);
|
|
708
|
+
if (argsInput) argsInput.value = server.installation.args.join(', ');
|
|
709
|
+
}
|
|
710
|
+
if (server.installation.env) {
|
|
711
|
+
Object.entries(server.installation.env).forEach(([envName, envConfig]) => {
|
|
712
|
+
const currentEnvIndex = window.addEnvVariable(currentServerIndex, serversListId, renderServersAsReadOnly);
|
|
713
|
+
|
|
714
|
+
const nameInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].installation.env[${currentEnvIndex}].name"]`);
|
|
715
|
+
if (nameInput) nameInput.value = envName;
|
|
716
|
+
if (envConfig.Default) {
|
|
717
|
+
const defInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].installation.env[${currentEnvIndex}].default"]`);
|
|
718
|
+
if (defInput) defInput.value = envConfig.Default;
|
|
719
|
+
}
|
|
720
|
+
if (envConfig.Required) {
|
|
721
|
+
const reqInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].installation.env[${currentEnvIndex}].required"]`);
|
|
722
|
+
if (reqInput) reqInput.checked = true;
|
|
723
|
+
}
|
|
724
|
+
if (envConfig.Description) {
|
|
725
|
+
const descInput = currentForm.querySelector(`[name="servers[${currentServerIndex}].installation.env[${currentEnvIndex}].description"]`);
|
|
726
|
+
if (descInput) descInput.value = envConfig.Description;
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
if (server.dependencies && server.dependencies.requirements) {
|
|
734
|
+
server.dependencies.requirements.forEach((depReq) => {
|
|
735
|
+
// Find requirement by name only, as requested.
|
|
736
|
+
const fullReq = (feedConfig.requirements || []).find(r => r.name === depReq.name);
|
|
737
|
+
if (!fullReq) {
|
|
738
|
+
console.warn(`Could not find full requirement config for dependency name: ${depReq.name} (version ${depReq.version} specified by server, but lookup is by name only). Skipping.`);
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const currentReqIndex = window.addServerRequirement(currentServerIndex, serversListId, renderServersAsReadOnly);
|
|
743
|
+
|
|
744
|
+
currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].name"]`).value = fullReq.name || '';
|
|
745
|
+
currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].type"]`).value = fullReq.type || '';
|
|
746
|
+
currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].version"]`).value = fullReq.version || '';
|
|
747
|
+
|
|
748
|
+
if (depReq.order !== undefined) {
|
|
749
|
+
currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].order"]`).value = depReq.order;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
if (fullReq.type === 'command' && fullReq.alias) {
|
|
753
|
+
currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].alias"]`).value = fullReq.alias;
|
|
754
|
+
window.toggleServerAliasField(currentServerIndex, currentReqIndex, serversListId);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
let registryType = 'public';
|
|
758
|
+
if (fullReq.registry) {
|
|
759
|
+
if (fullReq.registry.githubRelease) registryType = 'github';
|
|
760
|
+
else if (fullReq.registry.artifacts) registryType = 'artifacts';
|
|
761
|
+
else if (fullReq.registry.local) registryType = 'local';
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
const registrySelect = currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].registryType"]`);
|
|
765
|
+
if (registrySelect) {
|
|
766
|
+
registrySelect.value = registryType;
|
|
767
|
+
// Pass serversListId to ensure context is correct for toggling UI elements
|
|
768
|
+
window.toggleServerRegistryConfig(currentServerIndex, currentReqIndex, serversListId);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
if (registryType === 'github' && fullReq.registry.githubRelease) {
|
|
772
|
+
const gh = fullReq.registry.githubRelease;
|
|
773
|
+
if (gh.repository) currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].registry.githubRelease.repository"]`).value = gh.repository;
|
|
774
|
+
if (gh.assetsName) currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].registry.githubRelease.assetsName"]`).value = gh.assetsName;
|
|
775
|
+
if (gh.assetName) currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].registry.githubRelease.assetName"]`).value = gh.assetName;
|
|
776
|
+
} else if (registryType === 'artifacts' && fullReq.registry.artifacts) {
|
|
777
|
+
const art = fullReq.registry.artifacts;
|
|
778
|
+
if (art.registryUrl) currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].registry.artifacts.registryUrl"]`).value = art.registryUrl;
|
|
779
|
+
if (art.registryName) currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].registry.artifacts.registryName"]`).value = art.registryName;
|
|
780
|
+
} else if (registryType === 'local' && fullReq.registry.local) {
|
|
781
|
+
const loc = fullReq.registry.local;
|
|
782
|
+
if (loc.localPath) currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].registry.local.localPath"]`).value = loc.localPath;
|
|
783
|
+
if (loc.assetName) currentForm.querySelector(`[name="servers[${currentServerIndex}].requirements[${currentReqIndex}].registry.local.assetName"]`).value = loc.assetName;
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Resets the dynamic parts of the onboarding form, specifically the servers list.
|
|
792
|
+
* Also resets server-related counters in the state.
|
|
793
|
+
* This function is called before populating the form with new data (e.g., when loading an existing category
|
|
794
|
+
* or switching from JSON view to form view).
|
|
795
|
+
* @param {string} formId - The ID of the form whose dynamic content is to be reset.
|
|
796
|
+
* @param {string} serversListId - The ID of the server list container to clear.
|
|
797
|
+
*/
|
|
798
|
+
export function resetOnboardFormDynamicContent(formId = 'onboardForm', serversListId = 'serversList') {
|
|
799
|
+
const formToReset = document.getElementById(formId);
|
|
800
|
+
if (formToReset) {
|
|
801
|
+
// formToReset.reset(); // This resets the entire form, including the category dropdown.
|
|
802
|
+
// We want to avoid resetting the dropdown when a category is selected.
|
|
803
|
+
// Other parts of this function handle clearing specific dynamic content.
|
|
804
|
+
if (formId !== 'onboardServerForm') { // Only reset other forms, not the one with the category select that triggers loading
|
|
805
|
+
formToReset.reset();
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
const serversListContainer = document.getElementById(serversListId);
|
|
810
|
+
if (serversListContainer) {
|
|
811
|
+
serversListContainer.innerHTML = '';
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
setServerCounter(0);
|
|
815
|
+
if (state && state.envCounters && typeof state.envCounters.clear === 'function') {
|
|
816
|
+
state.envCounters.clear();
|
|
817
|
+
}
|
|
818
|
+
if (state && state.serverRequirementCounters && typeof state.serverRequirementCounters.clear === 'function') {
|
|
819
|
+
state.serverRequirementCounters.clear();
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Determine the correct validation panel and content IDs based on formId
|
|
823
|
+
let validationPanelIdToReset;
|
|
824
|
+
let validationContentIdToReset;
|
|
825
|
+
|
|
826
|
+
if (formId === 'onboardForm') {
|
|
827
|
+
validationPanelIdToReset = 'validationStatusPanelNewCategory';
|
|
828
|
+
validationContentIdToReset = 'validationStatusContentNewCategory';
|
|
829
|
+
} else if (formId === 'onboardServerForm') {
|
|
830
|
+
validationPanelIdToReset = 'validationStatusPanelExistingCategory';
|
|
831
|
+
validationContentIdToReset = 'validationStatusContentExistingCategoryTab';
|
|
832
|
+
}
|
|
833
|
+
// else if other forms have validation panels, add conditions here
|
|
834
|
+
|
|
835
|
+
if (validationPanelIdToReset) {
|
|
836
|
+
const validationPanel = document.getElementById(validationPanelIdToReset);
|
|
837
|
+
if (validationPanel) {
|
|
838
|
+
validationPanel.classList.add('hidden');
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
if (validationContentIdToReset) {
|
|
842
|
+
const validationContent = document.getElementById(validationContentIdToReset);
|
|
843
|
+
if (validationContent) {
|
|
844
|
+
validationContent.innerHTML = '';
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
if (formId === 'onboardServerForm') {
|
|
849
|
+
// const existingCategorySelect = document.getElementById('existingCategorySelect');
|
|
850
|
+
// if (existingCategorySelect) existingCategorySelect.value = ''; // This was incorrectly resetting the dropdown
|
|
851
|
+
|
|
852
|
+
const basicInfoContainer = document.getElementById('existingCategoryBasicInfoContainer');
|
|
853
|
+
if (basicInfoContainer) basicInfoContainer.classList.add('hidden');
|
|
854
|
+
|
|
855
|
+
const mcpServersContainer = document.getElementById('existingCategoryMcpServersContainer');
|
|
856
|
+
if (mcpServersContainer) mcpServersContainer.classList.add('hidden');
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// Setup real-time validation after reset
|
|
860
|
+
setupRealTimeValidation(formId);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Export setupRealTimeValidation for external use if needed
|
|
864
|
+
export { setupRealTimeValidation };
|