imcp 0.0.16 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/install.js +2 -2
- package/dist/cli/commands/list.js +2 -2
- package/dist/cli/commands/serve.js +1 -1
- package/dist/core/RequirementService.d.ts +0 -12
- package/dist/core/RequirementService.js +0 -24
- package/dist/core/installers/clients/BaseClientInstaller.d.ts +1 -1
- package/dist/core/installers/clients/ClientInstaller.d.ts +1 -1
- package/dist/core/installers/clients/ClientInstaller.js +1 -1
- package/dist/core/installers/clients/ClientInstallerFactory.js +1 -1
- package/dist/core/installers/clients/ClineInstaller.d.ts +1 -1
- package/dist/core/installers/clients/ClineInstaller.js +1 -1
- package/dist/core/installers/clients/ExtensionInstaller.js +1 -1
- package/dist/core/installers/clients/GithubCopilotInstaller.d.ts +1 -1
- package/dist/core/installers/clients/GithubCopilotInstaller.js +1 -1
- package/dist/core/installers/clients/MSRooCodeInstaller.d.ts +1 -1
- package/dist/core/installers/clients/MSRooCodeInstaller.js +1 -1
- package/dist/core/installers/requirements/BaseInstaller.d.ts +1 -1
- package/dist/core/installers/requirements/BaseInstaller.js +1 -1
- package/dist/core/installers/requirements/CommandInstaller.d.ts +1 -1
- package/dist/core/installers/requirements/CommandInstaller.js +1 -1
- package/dist/core/installers/requirements/GeneralInstaller.d.ts +1 -1
- package/dist/core/installers/requirements/InstallerFactory.d.ts +1 -1
- package/dist/core/installers/requirements/NpmInstaller.d.ts +1 -1
- package/dist/core/installers/requirements/NpmInstaller.js +1 -1
- package/dist/core/installers/requirements/PipInstaller.d.ts +1 -1
- package/dist/core/installers/requirements/RequirementInstaller.d.ts +1 -1
- package/dist/core/loaders/ConfigurationLoader.d.ts +32 -0
- package/dist/core/loaders/ConfigurationLoader.js +236 -0
- package/dist/core/loaders/ConfigurationProvider.d.ts +35 -0
- package/dist/core/loaders/ConfigurationProvider.js +375 -0
- package/dist/core/loaders/ServerSchemaLoader.d.ts +11 -0
- package/{src/core/ServerSchemaLoader.ts → dist/core/loaders/ServerSchemaLoader.js} +43 -48
- package/dist/core/loaders/ServerSchemaProvider.d.ts +17 -0
- package/{src/core/ServerSchemaProvider.ts → dist/core/loaders/ServerSchemaProvider.js} +120 -137
- package/dist/core/metadatas/constants.d.ts +47 -0
- package/dist/core/metadatas/constants.js +94 -0
- package/dist/core/metadatas/types.d.ts +166 -0
- package/dist/core/metadatas/types.js +16 -0
- package/dist/core/onboard/FeedOnboardService.d.ts +1 -1
- package/dist/core/onboard/FeedOnboardService.js +1 -1
- package/dist/core/onboard/OnboardProcessor.d.ts +1 -1
- package/dist/core/onboard/OnboardProcessor.js +1 -1
- package/dist/core/onboard/OnboardStatus.d.ts +1 -1
- package/dist/core/onboard/OnboardStatusManager.d.ts +1 -1
- package/dist/core/onboard/OnboardStatusManager.js +1 -1
- package/dist/core/validators/FeedValidator.d.ts +1 -1
- package/dist/core/validators/IServerValidator.d.ts +1 -1
- package/dist/core/validators/SSEServerValidator.d.ts +1 -1
- package/dist/core/validators/ServerValidatorFactory.d.ts +1 -1
- package/dist/core/validators/StdioServerValidator.d.ts +1 -1
- package/dist/core/validators/StdioServerValidator.js +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/services/InstallationService.d.ts +50 -0
- package/dist/services/InstallationService.js +350 -0
- package/dist/services/MCPManager.d.ts +28 -0
- package/dist/services/MCPManager.js +188 -0
- package/dist/services/RequirementService.d.ts +40 -0
- package/dist/services/RequirementService.js +110 -0
- package/dist/services/ServerService.d.ts +2 -2
- package/dist/services/ServerService.js +5 -5
- package/dist/utils/adoUtils.d.ts +2 -2
- package/dist/utils/adoUtils.js +1 -1
- package/dist/utils/feedUtils.js +1 -1
- package/dist/utils/githubUtils.d.ts +1 -1
- package/dist/utils/githubUtils.js +1 -1
- package/dist/utils/logger.js +1 -1
- package/dist/utils/macroExpressionUtils.d.ts +1 -1
- package/dist/utils/osUtils.d.ts +1 -1
- package/dist/utils/osUtils.js +1 -1
- package/dist/web/contract/serverContract.d.ts +1 -1
- package/dist/web/public/index.html +1 -3
- package/dist/web/public/js/api.js +2 -80
- package/dist/web/server.js +2 -2
- package/package.json +1 -1
- package/src/cli/commands/install.ts +3 -3
- package/src/cli/commands/list.ts +2 -2
- package/src/cli/commands/serve.ts +3 -2
- package/src/cli/index.ts +1 -1
- package/src/core/installers/clients/BaseClientInstaller.ts +134 -3
- package/src/core/installers/clients/ClientInstaller.ts +3 -3
- package/src/core/installers/clients/ClientInstallerFactory.ts +1 -1
- package/src/core/installers/clients/ClineInstaller.ts +1 -101
- package/src/core/installers/clients/ExtensionInstaller.ts +1 -1
- package/src/core/installers/clients/GithubCopilotInstaller.ts +1 -101
- package/src/core/installers/clients/MSRooCodeInstaller.ts +1 -102
- package/src/core/installers/requirements/BaseInstaller.ts +2 -2
- package/src/core/installers/requirements/CommandInstaller.ts +1 -1
- package/src/core/installers/requirements/GeneralInstaller.ts +1 -1
- package/src/core/installers/requirements/InstallerFactory.ts +1 -1
- package/src/core/installers/requirements/NpmInstaller.ts +12 -12
- package/src/core/installers/requirements/PipInstaller.ts +1 -1
- package/src/core/installers/requirements/RequirementInstaller.ts +1 -1
- package/src/core/{ConfigurationLoader.ts → loaders/ConfigurationLoader.ts} +31 -7
- package/src/core/{ConfigurationProvider.ts → loaders/ConfigurationProvider.ts} +18 -10
- package/src/core/loaders/ServerSchemaLoader.ts +117 -0
- package/src/core/loaders/ServerSchemaProvider.ts +99 -0
- package/src/core/{types.ts → metadatas/types.ts} +3 -2
- package/src/core/onboard/FeedOnboardService.ts +270 -146
- package/src/core/onboard/OnboardProcessor.ts +60 -11
- package/src/core/onboard/OnboardStatus.ts +7 -2
- package/src/core/onboard/OnboardStatusManager.ts +270 -43
- package/src/core/validators/FeedValidator.ts +65 -9
- package/src/core/validators/IServerValidator.ts +1 -1
- package/src/core/validators/SSEServerValidator.ts +2 -2
- package/src/core/validators/ServerValidatorFactory.ts +1 -1
- package/src/core/validators/StdioServerValidator.ts +86 -34
- package/src/index.ts +3 -3
- package/src/{core → services}/InstallationService.ts +5 -5
- package/src/{core → services}/MCPManager.ts +10 -5
- package/src/{core → services}/RequirementService.ts +2 -31
- package/src/services/ServerService.ts +7 -7
- package/src/utils/adoUtils.ts +3 -3
- package/src/utils/feedUtils.ts +2 -2
- package/src/utils/githubUtils.ts +2 -2
- package/src/utils/logger.ts +13 -1
- package/src/utils/macroExpressionUtils.ts +1 -1
- package/src/utils/osUtils.ts +4 -4
- package/src/web/contract/serverContract.ts +2 -2
- package/src/web/public/index.html +1 -3
- package/src/web/public/js/api.js +2 -80
- package/src/web/public/js/modal/installation.js +1 -1
- package/src/web/public/js/onboard/ONBOARDING_PAGE_DESIGN.md +41 -9
- package/src/web/public/js/onboard/formProcessor.js +200 -34
- package/src/web/public/js/onboard/index.js +2 -2
- package/src/web/public/js/onboard/publishHandler.js +30 -22
- package/src/web/public/js/onboard/templates.js +34 -40
- package/src/web/public/js/onboard/uiHandlers.js +175 -84
- package/src/web/public/js/onboard/validationHandlers.js +147 -64
- package/src/web/public/js/serverCategoryDetails.js +19 -4
- package/src/web/public/js/serverCategoryList.js +13 -1
- package/src/web/public/onboard.html +1 -1
- package/src/web/server.ts +30 -14
- package/src/services/InstallRequestValidator.ts +0 -112
- /package/src/core/{constants.ts → metadatas/constants.ts} +0 -0
|
@@ -43,13 +43,62 @@ let pollingIntervalId = null;
|
|
|
43
43
|
* @param {HTMLElement} contentElement - The HTML element to update.
|
|
44
44
|
*/
|
|
45
45
|
export function updateOperationDisplay(data, contentElement) { // Renamed from updateValidationDisplay
|
|
46
|
-
const { status, message, validationStatus, prInfo, errorMessage: operationErrorMessage, operationType } = data;
|
|
47
|
-
|
|
46
|
+
const { status, message, validationStatus, prInfo, errorMessage: operationErrorMessage, operationType, steps } = data; // Added steps
|
|
47
|
+
const upperStatus = typeof status === 'string' ? status.toUpperCase() : ''; // Overall status
|
|
48
|
+
const isInProgressOverall = upperStatus === 'PENDING' || upperStatus === 'VALIDATING' || upperStatus === 'PRCREATING' || upperStatus === 'IN_PROGRESS' || upperStatus === 'PUBLISHING';
|
|
49
|
+
|
|
50
|
+
let htmlContent = `<p><strong>Overall Status:</strong> <span class="${upperStatus === 'COMPLETED' || upperStatus === 'SUCCEEDED' ? 'text-green-600' : (upperStatus === 'FAILED' ? 'text-red-600' : 'text-yellow-600')}">${status} ${isInProgressOverall ? "<i class='bx bx-loader-alt bx-spin ml-2'></i>" : ""}</span></p>`;
|
|
48
51
|
if (operationType) htmlContent += `<p><strong>Operation Type:</strong> ${operationType}</p>`;
|
|
52
|
+
// Show message if provided, especially useful for direct success/failure from API.
|
|
49
53
|
if (message) htmlContent += `<p><strong>Message:</strong> ${message}</p>`;
|
|
50
|
-
if (operationErrorMessage) htmlContent += `<p class="text-red-500"><strong>Error:</strong> ${operationErrorMessage}</p>`;
|
|
54
|
+
if (operationErrorMessage) htmlContent += `<p class="text-red-500"><strong>Overall Error:</strong> ${operationErrorMessage}</p>`;
|
|
55
|
+
|
|
56
|
+
// Display Steps
|
|
57
|
+
if (Array.isArray(steps) && steps.length > 0) {
|
|
58
|
+
const progressListId = `progress-steps-list-${contentElement.id}`; // Unique ID for the steps list
|
|
59
|
+
// Ensure the toggle icon reflects the initial state (expanded, so 'bx-chevron-up')
|
|
60
|
+
// The UL itself won't have 'hidden' class initially.
|
|
61
|
+
htmlContent += `
|
|
62
|
+
<div class="progress-section-container mt-4">
|
|
63
|
+
<h4 class="font-semibold mb-2 text-gray-700 cursor-pointer flex items-center progress-section-toggle" data-target-id="${progressListId}">
|
|
64
|
+
Progress
|
|
65
|
+
<i class='bx bx-chevron-up toggle-icon-progress ml-2 text-gray-500'></i>
|
|
66
|
+
</h4>
|
|
67
|
+
<ul id="${progressListId}" class="list-none space-y-2 border-l-2 border-blue-500 pl-4 py-2">`; // Added py-2 for padding
|
|
68
|
+
steps.forEach((step, index) => {
|
|
69
|
+
const stepTimestamp = new Date(step.timestamp).toLocaleString();
|
|
70
|
+
let stepStatusIcon = '';
|
|
71
|
+
let stepTextColor = 'text-gray-700'; // Default text color
|
|
72
|
+
|
|
73
|
+
const isLastStep = index === steps.length - 1;
|
|
74
|
+
|
|
75
|
+
if (step.errorMessage || step.status === 'failed') {
|
|
76
|
+
stepStatusIcon = "<i class='bx bx-x-circle text-red-500 mr-2'></i>";
|
|
77
|
+
stepTextColor = 'text-red-600';
|
|
78
|
+
} else if (isLastStep && isInProgressOverall) {
|
|
79
|
+
// Current active step if overall process is still running
|
|
80
|
+
stepStatusIcon = "<i class='bx bx-loader-alt bx-spin text-blue-500 mr-2'></i>";
|
|
81
|
+
stepTextColor = 'text-blue-700';
|
|
82
|
+
} else {
|
|
83
|
+
// Completed, non-failed step
|
|
84
|
+
stepStatusIcon = "<i class='bx bx-check-circle text-green-500 mr-2'></i>";
|
|
85
|
+
stepTextColor = 'text-gray-700'; // Keep default gray for completed steps, icon is green
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
htmlContent += `<li class="text-sm ${stepTextColor}">`;
|
|
89
|
+
htmlContent += `${stepStatusIcon}<strong>${step.stepName}</strong>`;
|
|
90
|
+
if (step.serverName) htmlContent += ` (Server: ${step.serverName})`;
|
|
91
|
+
htmlContent += `<span class="text-xs text-gray-500 ml-2">- ${stepTimestamp}</span>`;
|
|
92
|
+
if (step.errorMessage) {
|
|
93
|
+
htmlContent += `<p class="text-xs text-red-400 pl-6">Error: ${step.errorMessage}</p>`;
|
|
94
|
+
}
|
|
95
|
+
htmlContent += `</li>`;
|
|
96
|
+
});
|
|
97
|
+
htmlContent += '</ul>';
|
|
98
|
+
}
|
|
99
|
+
|
|
51
100
|
if (validationStatus) {
|
|
52
|
-
htmlContent += '<h4 class="font-semibold mt-
|
|
101
|
+
htmlContent += '<h4 class="font-semibold mt-4 mb-2 text-gray-700">Detailed Validation Results:</h4>';
|
|
53
102
|
htmlContent += '<ul class="list-disc list-inside space-y-1">';
|
|
54
103
|
let hasDetailedItems = false;
|
|
55
104
|
// Check for serverResults array first
|
|
@@ -98,8 +147,36 @@ export function updateOperationDisplay(data, contentElement) { // Renamed from u
|
|
|
98
147
|
htmlContent += `<p class="mt-3"><strong>PR Info:</strong> <a href="${prInfo.url}" target="_blank" class="text-blue-600 hover:underline">${prInfo.url}</a></p>`;
|
|
99
148
|
}
|
|
100
149
|
contentElement.innerHTML = htmlContent;
|
|
150
|
+
// After updating content, ensure the toggle listener is active for the new/updated progress section
|
|
151
|
+
if (Array.isArray(steps) && steps.length > 0) {
|
|
152
|
+
ensureProgressToggleListener(contentElement);
|
|
153
|
+
}
|
|
101
154
|
}
|
|
102
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Ensures that a click listener is attached to the statusContentElement
|
|
158
|
+
* to handle toggling the visibility of the progress steps list.
|
|
159
|
+
* This listener is attached only once.
|
|
160
|
+
* @param {HTMLElement} statusContentElement - The parent element where progress is displayed.
|
|
161
|
+
*/
|
|
162
|
+
export function ensureProgressToggleListener(statusContentElement) {
|
|
163
|
+
if (statusContentElement && !statusContentElement.dataset.progressToggleListenerAttached) {
|
|
164
|
+
statusContentElement.addEventListener('click', (event) => {
|
|
165
|
+
const toggleHeader = event.target.closest('.progress-section-toggle');
|
|
166
|
+
if (toggleHeader) {
|
|
167
|
+
const targetId = toggleHeader.dataset.targetId;
|
|
168
|
+
const iconElement = toggleHeader.querySelector('.toggle-icon-progress');
|
|
169
|
+
// Assuming toggleSectionContent is imported from uiHandlers.js and is in scope
|
|
170
|
+
if (targetId && iconElement && typeof toggleSectionContent === 'function') {
|
|
171
|
+
toggleSectionContent(targetId, iconElement);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
statusContentElement.dataset.progressToggleListenerAttached = 'true';
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
103
180
|
/**
|
|
104
181
|
* Polls the server for operation status.
|
|
105
182
|
* @param {string} categoryName - The name of the category.
|
|
@@ -107,71 +184,74 @@ export function updateOperationDisplay(data, contentElement) { // Renamed from u
|
|
|
107
184
|
* @param {string} validateBtnId - The ID of the validate button.
|
|
108
185
|
* @param {string} publishBtnId - The ID of the publish button.
|
|
109
186
|
* @param {string} operationInitiator - 'validate' or 'publish' to restore correct button text.
|
|
187
|
+
* @param {string} operationType - The type of operation to poll for (e.g., 'VALIDATION_ONLY', 'FULL_ONBOARDING').
|
|
188
|
+
* @returns {Promise<boolean>} - Promise resolving to true if polling should continue, false otherwise.
|
|
110
189
|
*/
|
|
111
|
-
export async function pollOperationStatus(categoryName, contentId, validateBtnId, publishBtnId, operationInitiator) {
|
|
190
|
+
export async function pollOperationStatus(categoryName, contentId, validateBtnId, publishBtnId, operationInitiator, operationType) {
|
|
112
191
|
const statusContentElement = document.getElementById(contentId);
|
|
192
|
+
ensureProgressToggleListener(statusContentElement); // Ensure listener is active during polling updates
|
|
193
|
+
|
|
113
194
|
const validateButton = document.getElementById(validateBtnId);
|
|
114
195
|
const publishButton = document.getElementById(publishBtnId);
|
|
115
196
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
197
|
+
// Helper function to update button states based on operation status
|
|
198
|
+
const updateButtonStates = (isCompleted = false) => {
|
|
199
|
+
if (validateButton && publishButton) {
|
|
200
|
+
if (isCompleted) {
|
|
201
|
+
// On completion, enable both buttons and restore their original state
|
|
202
|
+
validateButton.disabled = false;
|
|
203
|
+
validateButton.innerHTML = "<i class='bx bx-check-shield mr-2'></i>Validate";
|
|
204
|
+
validateButton.classList.remove('opacity-50');
|
|
205
|
+
publishButton.disabled = false;
|
|
206
|
+
publishButton.innerHTML = "<i class='bx bx-cloud-upload mr-2'></i>Publish";
|
|
207
|
+
publishButton.classList.remove('opacity-50');
|
|
208
|
+
} else {
|
|
209
|
+
// During operation, handle buttons based on which operation is running
|
|
210
|
+
if (operationInitiator === 'validate') {
|
|
211
|
+
validateButton.disabled = true;
|
|
212
|
+
validateButton.innerHTML = "<i class='bx bx-loader-alt bx-spin mr-2'></i>Validating...";
|
|
213
|
+
publishButton.disabled = true;
|
|
214
|
+
publishButton.innerHTML = "<i class='bx bx-cloud-upload mr-2'></i>Publish";
|
|
215
|
+
publishButton.classList.add('opacity-50');
|
|
216
|
+
} else if (operationInitiator === 'publish') {
|
|
217
|
+
publishButton.disabled = true;
|
|
218
|
+
publishButton.innerHTML = "<i class='bx bx-loader-alt bx-spin mr-2'></i>Publishing...";
|
|
219
|
+
validateButton.disabled = true;
|
|
220
|
+
validateButton.innerHTML = "<i class='bx bx-check-shield mr-2'></i>Validate";
|
|
221
|
+
validateButton.classList.add('opacity-50');
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
// Add operationType as a query parameter
|
|
229
|
+
const response = await fetch(`/api/categories/${categoryName}/onboard/status?operationType=${encodeURIComponent(operationType)}`);
|
|
230
|
+
const result = await response.json();
|
|
231
|
+
|
|
232
|
+
if (result.success && result.data) {
|
|
151
233
|
updateOperationDisplay(result.data, statusContentElement);
|
|
152
234
|
|
|
153
235
|
const currentStatus = result.data.status ? String(result.data.status).toUpperCase() : ''; // Ensure uppercase for comparison
|
|
154
236
|
// Use string literals for status comparison, include 'succeeded'
|
|
155
237
|
if (currentStatus === 'COMPLETED' || currentStatus === 'FAILED' || currentStatus === 'SUCCEEDED') {
|
|
156
|
-
clearInterval(pollingIntervalId);
|
|
157
|
-
pollingIntervalId = null;
|
|
158
238
|
updateButtonStates(true);
|
|
239
|
+
return false; // Stop polling
|
|
159
240
|
} else {
|
|
160
241
|
// Operation is still in progress
|
|
161
242
|
updateButtonStates(false);
|
|
243
|
+
return true; // Continue polling
|
|
162
244
|
}
|
|
163
245
|
} else {
|
|
164
246
|
statusContentElement.innerHTML = `<p class="text-red-500">Error polling status: ${result.error || 'Unknown error'}</p>`;
|
|
165
|
-
clearInterval(pollingIntervalId);
|
|
166
|
-
pollingIntervalId = null;
|
|
167
247
|
updateButtonStates(true);
|
|
248
|
+
return false; // Stop polling
|
|
168
249
|
}
|
|
169
250
|
} catch (error) {
|
|
170
251
|
console.error('Error polling operation status:', error);
|
|
171
252
|
statusContentElement.innerHTML = `<p class="text-red-500">An error occurred while polling status. Please check the console.</p>`;
|
|
172
|
-
clearInterval(pollingIntervalId);
|
|
173
|
-
pollingIntervalId = null;
|
|
174
253
|
updateButtonStates(true);
|
|
254
|
+
return false; // Stop polling
|
|
175
255
|
}
|
|
176
256
|
}
|
|
177
257
|
|
|
@@ -186,6 +266,8 @@ export async function handleValidation(event, activeTab, currentSelectedCategory
|
|
|
186
266
|
const { panelId, contentId, formId, validateButtonId, publishButtonId } = getElementIdsByTab(activeTab);
|
|
187
267
|
|
|
188
268
|
const statusContentElement = document.getElementById(contentId);
|
|
269
|
+
ensureProgressToggleListener(statusContentElement); // Ensure listener is active for initial display
|
|
270
|
+
|
|
189
271
|
const onboardForm = document.getElementById(formId);
|
|
190
272
|
const validateButton = document.getElementById(validateButtonId);
|
|
191
273
|
const publishButton = document.getElementById(publishButtonId);
|
|
@@ -266,21 +348,11 @@ export async function handleValidation(event, activeTab, currentSelectedCategory
|
|
|
266
348
|
if (forExistingCategoryTab && currentSelectedCategoryData) {
|
|
267
349
|
finalFeedConfiguration = JSON.parse(JSON.stringify(currentSelectedCategoryData)); // Deep clone
|
|
268
350
|
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
(newServersData.requirements || []).forEach(newReq => {
|
|
275
|
-
const reqKey = `${newReq.type}|${newReq.name}|${newReq.version}`;
|
|
276
|
-
if (!existingReqKeys.has(reqKey)) {
|
|
277
|
-
if (!finalFeedConfiguration.requirements) {
|
|
278
|
-
finalFeedConfiguration.requirements = [];
|
|
279
|
-
}
|
|
280
|
-
finalFeedConfiguration.requirements.push(newReq);
|
|
281
|
-
existingReqKeys.add(reqKey);
|
|
282
|
-
}
|
|
283
|
-
});
|
|
351
|
+
// Replace MCP Servers and Requirements with the current state from the form (newServersData).
|
|
352
|
+
// newServersData, generated by getFormData, already contains the correct, complete list
|
|
353
|
+
// of servers (including original, adhoc, and newly added ones) and their derived global requirements.
|
|
354
|
+
finalFeedConfiguration.mcpServers = newServersData.mcpServers || [];
|
|
355
|
+
finalFeedConfiguration.requirements = newServersData.requirements || [];
|
|
284
356
|
} else {
|
|
285
357
|
finalFeedConfiguration = newServersData;
|
|
286
358
|
}
|
|
@@ -305,12 +377,23 @@ export async function handleValidation(event, activeTab, currentSelectedCategory
|
|
|
305
377
|
|
|
306
378
|
if (result.success && result.data) {
|
|
307
379
|
updateOperationDisplay(result.data, statusContentElement);
|
|
308
|
-
|
|
380
|
+
// The onboardingId from the response is the combined ID (categoryName_operationType).
|
|
381
|
+
// We need the categoryName for polling, which we already have as a parameter.
|
|
382
|
+
// The operationType is also passed to pollOperationStatus.
|
|
383
|
+
const categoryNameForPolling = finalFeedConfiguration.name; // Use the original category name
|
|
384
|
+
const operationTypeForPolling = 'VALIDATION_ONLY'; // Validation always initiates 'VALIDATION_ONLY'
|
|
309
385
|
const initialStatus = result.data.status;
|
|
310
386
|
|
|
311
387
|
// Use string literals for status comparison, include 'succeeded'
|
|
312
|
-
|
|
313
|
-
|
|
388
|
+
const upperInitialStatus = typeof initialStatus === 'string' ? initialStatus.toUpperCase() : '';
|
|
389
|
+
if (categoryNameForPolling && upperInitialStatus !== 'COMPLETED' && upperInitialStatus !== 'FAILED' && upperInitialStatus !== 'SUCCEEDED') {
|
|
390
|
+
pollingIntervalId = setInterval(async () => {
|
|
391
|
+
const shouldContinue = await pollOperationStatus(categoryNameForPolling, contentId, validateButtonId, publishButtonId, 'validate', operationTypeForPolling);
|
|
392
|
+
if (!shouldContinue) {
|
|
393
|
+
clearInterval(pollingIntervalId);
|
|
394
|
+
pollingIntervalId = null;
|
|
395
|
+
}
|
|
396
|
+
}, POLLING_INTERVAL);
|
|
314
397
|
} else {
|
|
315
398
|
validateButton.disabled = false;
|
|
316
399
|
validateButton.innerHTML = "<i class='bx bx-check-shield mr-2'></i>Validate";
|
|
@@ -63,9 +63,19 @@ async function showServerDetails(serverName, retryCount = 0) {
|
|
|
63
63
|
throw new Error('Server data not found after retries');
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
let pullRequestHtml = '';
|
|
67
|
+
if (server.feedConfiguration?.PullRequest) {
|
|
68
|
+
pullRequestHtml = `
|
|
69
|
+
<p class="mb-1 text-sm">
|
|
70
|
+
<span class="font-semibold">Pull Request:</span>
|
|
71
|
+
<a href="${server.feedConfiguration.PullRequest}" target="_blank" class="text-blue-600 hover:underline">${server.feedConfiguration.PullRequest}</a>
|
|
72
|
+
</p>`;
|
|
73
|
+
}
|
|
74
|
+
|
|
66
75
|
detailsDiv.innerHTML = `
|
|
67
76
|
<h3 class="text-xl font-semibold mb-2 text-gray-800">${server.displayName || server.name}</h3>
|
|
68
|
-
<p class="mb-
|
|
77
|
+
<p class="mb-1"><span class="font-semibold">Description:</span> ${server.description || 'N/A'}</p>
|
|
78
|
+
${pullRequestHtml}
|
|
69
79
|
<div id="toolMcpsList" class="mt-4">
|
|
70
80
|
<div class="animate-pulse flex space-x-4">
|
|
71
81
|
<div class="flex-1 space-y-4 py-1">
|
|
@@ -180,8 +190,13 @@ async function renderServersList(serverCategory) {
|
|
|
180
190
|
<div class="server-item-content" data-server-name="${mcpServer.name}">
|
|
181
191
|
<div class="server-item-info" style="width: 100%; box-sizing: border-box;">
|
|
182
192
|
<div class="server-item-header">
|
|
183
|
-
<div class="flex items-center">
|
|
184
|
-
<h5 class="font-semibold text-gray-800">${mcpServer.displayName || mcpServer.name}</h5>
|
|
193
|
+
<div class="flex items-center flex-wrap">
|
|
194
|
+
<h5 class="font-semibold text-gray-800 mr-2">${mcpServer.displayName || mcpServer.name}</h5>
|
|
195
|
+
${mcpServer.systemTags && Object.keys(mcpServer.systemTags).length > 0 ? `
|
|
196
|
+
<div class="flex flex-wrap gap-1 items-center">
|
|
197
|
+
${Object.entries(mcpServer.systemTags).map(([key, value]) => `<span class="text-xs bg-purple-100 text-purple-700 px-2 py-0.5 rounded-full">${key}: ${value}</span>`).join('')}
|
|
198
|
+
</div>
|
|
199
|
+
` : ''}
|
|
185
200
|
${mcpServer.repository || serverCategory.feedConfiguration?.repository ? (() => {
|
|
186
201
|
const repoUrl = mcpServer.repository || serverCategory.feedConfiguration?.repository;
|
|
187
202
|
const isGithub = repoUrl.toLowerCase().includes('github.com');
|
|
@@ -201,7 +216,7 @@ async function renderServersList(serverCategory) {
|
|
|
201
216
|
</a>`
|
|
202
217
|
})() : ''}
|
|
203
218
|
</div>
|
|
204
|
-
<p class="text-sm text-gray-600 mb-1">${mcpServer.description || 'No description'}</p>
|
|
219
|
+
<p class="text-sm text-gray-600 mb-1 mt-1">${mcpServer.description || 'No description'}</p>
|
|
205
220
|
</div>
|
|
206
221
|
<div class="flex flex-col mt-3">
|
|
207
222
|
<span class="text-xs font-semibold mb-2">Client Status:</span>
|
|
@@ -72,12 +72,24 @@ function renderServerCategoryList(servers) {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
let systemTagsHtml = '';
|
|
76
|
+
if (server.feedConfiguration?.systemTags && Object.keys(server.feedConfiguration.systemTags).length > 0) {
|
|
77
|
+
systemTagsHtml += '<div class="flex flex-wrap gap-1 ml-2">'; // Added ml-2 for spacing
|
|
78
|
+
for (const [key, value] of Object.entries(server.feedConfiguration.systemTags)) {
|
|
79
|
+
systemTagsHtml += `<span class="text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded-full">${key}: ${value}</span>`;
|
|
80
|
+
}
|
|
81
|
+
systemTagsHtml += '</div>';
|
|
82
|
+
}
|
|
83
|
+
|
|
75
84
|
return `
|
|
76
85
|
<div class="server-item border border-gray-200 p-3 rounded hover:bg-gray-50 cursor-pointer transition duration-150 ease-in-out"
|
|
77
86
|
onclick="navigateToCategory('${server.name}')"
|
|
78
87
|
data-server-name="${server.name}">
|
|
79
88
|
<h3 class="font-semibold text-gray-800">${server.displayName || server.name}</h3>
|
|
80
|
-
<
|
|
89
|
+
<div class="text-sm text-gray-500 flex items-center mt-1">
|
|
90
|
+
${statusHtml}
|
|
91
|
+
${systemTagsHtml}
|
|
92
|
+
</div>
|
|
81
93
|
</div>
|
|
82
94
|
`;
|
|
83
95
|
}).join('');
|
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
<i class='bx bx-server mr-2 text-blue-600'></i>
|
|
127
127
|
MCP Servers
|
|
128
128
|
</h2>
|
|
129
|
-
<button type="button"
|
|
129
|
+
<button type="button" id="addServerBtnNewCategory"
|
|
130
130
|
class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 flex items-center text-sm">
|
|
131
131
|
<i class='bx bx-plus mr-2'></i>
|
|
132
132
|
Add MCP Server
|
package/src/web/server.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import express, { Request, Response } from 'express';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
-
import { ServerSchema } from '../core/ServerSchemaProvider.js';
|
|
4
|
+
import { ServerSchema } from '../core/loaders/ServerSchemaProvider.js';
|
|
5
5
|
import {
|
|
6
6
|
RequirementConfig,
|
|
7
7
|
FeedConfiguration,
|
|
8
8
|
MCPServerCategory,
|
|
9
9
|
McpConfig,
|
|
10
10
|
EnvVariableConfig
|
|
11
|
-
} from '../core/types.js';
|
|
11
|
+
} from '../core/metadatas/types.js';
|
|
12
12
|
import {
|
|
13
13
|
ApiResponse,
|
|
14
14
|
ListQueryParams,
|
|
@@ -20,13 +20,13 @@ import {
|
|
|
20
20
|
RequirementType
|
|
21
21
|
} from './contract/serverContract.js';
|
|
22
22
|
|
|
23
|
-
import { OperationStatus, OnboardingProcessStatus } from '../core/onboard/OnboardStatus.js';
|
|
24
|
-
import { SUPPORTED_CLIENT_NAMES } from '../core/constants.js';
|
|
23
|
+
import { OperationStatus, OnboardingProcessStatus, OperationType } from '../core/onboard/OnboardStatus.js';
|
|
24
|
+
import { SUPPORTED_CLIENT_NAMES } from '../core/metadatas/constants.js';
|
|
25
25
|
import { serverService } from '../services/ServerService.js';
|
|
26
26
|
import { feedOnboardService } from '../core/onboard/FeedOnboardService.js';
|
|
27
27
|
import { openBrowser } from '../utils/osUtils.js';
|
|
28
28
|
import { Logger } from '../utils/logger.js';
|
|
29
|
-
import { configProvider } from '../core/ConfigurationProvider.js';
|
|
29
|
+
import { configProvider } from '../core/loaders/ConfigurationProvider.js';
|
|
30
30
|
import { onboardStatusManager } from '../core/onboard/OnboardStatusManager.js';
|
|
31
31
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
32
32
|
const app = express();
|
|
@@ -253,33 +253,49 @@ app.post('/api/categories/onboard/validate', async (req: Request<{}, {}, Onboard
|
|
|
253
253
|
});
|
|
254
254
|
|
|
255
255
|
// Get category onboarding/validation operation status
|
|
256
|
-
app.get('/api/categories/:categoryName/onboard/status', async (req: Request<{ categoryName: string }>, res: Response) => {
|
|
256
|
+
app.get('/api/categories/:categoryName/onboard/status', async (req: Request<{ categoryName: string }, {}, {}, { operationType?: string }>, res: Response) => {
|
|
257
257
|
try {
|
|
258
258
|
const { categoryName } = req.params;
|
|
259
|
-
|
|
260
|
-
|
|
259
|
+
const { operationType } = req.query;
|
|
260
|
+
|
|
261
|
+
if (!operationType) {
|
|
262
|
+
return res.status(400).json({
|
|
263
|
+
success: false,
|
|
264
|
+
error: 'operationType query parameter is required.'
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Validate operationType if necessary (e.g., check against known OperationType values)
|
|
269
|
+
// For now, we'll assume it's a valid OperationType string.
|
|
270
|
+
const validOperationType = operationType as OperationType;
|
|
271
|
+
|
|
272
|
+
const status = await onboardStatusManager.getStatus(categoryName, validOperationType);
|
|
261
273
|
|
|
262
274
|
if (!status) {
|
|
263
275
|
return res.status(404).json({
|
|
264
276
|
success: false,
|
|
265
|
-
error: `No active operation found for category ${categoryName}`
|
|
277
|
+
error: `No active operation found for category ${categoryName} with operation type ${validOperationType}`
|
|
266
278
|
});
|
|
267
279
|
}
|
|
268
280
|
|
|
269
281
|
// Construct the response data based on the retrieved OnboardStatus
|
|
270
|
-
const
|
|
271
|
-
|
|
282
|
+
const lastStepName = status.steps && status.steps.length > 0 ? status.steps[status.steps.length - 1].stepName : undefined;
|
|
283
|
+
const responseData: OperationStatus & { steps?: any[], feedConfiguration?: FeedConfiguration } = { // Added steps and feedConfiguration to type
|
|
284
|
+
onboardingId: status.onboardingId, // This is categoryName_operationType
|
|
272
285
|
status: status.status,
|
|
273
|
-
message:
|
|
286
|
+
message: lastStepName || status.errorMessage || 'Processing...',
|
|
274
287
|
lastQueried: new Date().toISOString(),
|
|
288
|
+
steps: status.steps, // Include the steps array in the response
|
|
275
289
|
...(status.validationStatus && { validationStatus: status.validationStatus }),
|
|
276
290
|
...(status.prInfo && { prInfo: status.prInfo }),
|
|
277
291
|
...(status.result && { result: status.result }),
|
|
278
292
|
...(status.errorMessage && { errorMessage: status.errorMessage }),
|
|
279
|
-
|
|
293
|
+
operationType: status.operationType, // Always include operationType from the status object
|
|
294
|
+
// Attempt to include feedConfiguration if available, especially for SUCCEEDED VALIDATION_ONLY
|
|
295
|
+
...(status.operationType === 'VALIDATION_ONLY' && status.status === OnboardingProcessStatus.SUCCEEDED && status.result?.feedConfiguration && { feedConfiguration: status.result.feedConfiguration }),
|
|
280
296
|
};
|
|
281
297
|
|
|
282
|
-
const response: ApiResponse<
|
|
298
|
+
const response: ApiResponse<typeof responseData> = {
|
|
283
299
|
success: true,
|
|
284
300
|
data: responseData
|
|
285
301
|
};
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { FeedConfiguration, EnvVariableConfig, ServerInstallOptions } from '../core/types.js';
|
|
4
|
-
import { InstallServersRequestBody } from '../web/contract/serverContract.js'; // Assuming InstallRequestBody is defined here
|
|
5
|
-
import { SUPPORTED_CLIENT_NAMES } from '../core/constants.js';
|
|
6
|
-
|
|
7
|
-
export class InstallRequestValidator {
|
|
8
|
-
private serverFeedConfigs: Map<string, FeedConfiguration> = new Map();
|
|
9
|
-
private feedDirectory: string;
|
|
10
|
-
|
|
11
|
-
constructor(feedDirectory: string = path.join(__dirname, '../feeds')) {
|
|
12
|
-
this.feedDirectory = feedDirectory;
|
|
13
|
-
// Consider loading configs lazily or providing an explicit load method
|
|
14
|
-
// For now, let's assume pre-loading or loading on demand in validate
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
private async loadServerFeedConfiguration(serverName: string): Promise<FeedConfiguration | undefined> {
|
|
18
|
-
if (this.serverFeedConfigs.has(serverName)) {
|
|
19
|
-
return this.serverFeedConfigs.get(serverName);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const filePath = path.join(this.feedDirectory, `${serverName}.json`);
|
|
23
|
-
try {
|
|
24
|
-
const fileContent = await fs.readFile(filePath, 'utf-8');
|
|
25
|
-
const config: FeedConfiguration = JSON.parse(fileContent);
|
|
26
|
-
// Basic validation of the loaded config structure could be added here
|
|
27
|
-
if (config && config.name === serverName) {
|
|
28
|
-
this.serverFeedConfigs.set(serverName, config);
|
|
29
|
-
return config;
|
|
30
|
-
}
|
|
31
|
-
console.error(`Configuration name mismatch in ${filePath}`);
|
|
32
|
-
return undefined;
|
|
33
|
-
} catch (error) {
|
|
34
|
-
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
35
|
-
console.warn(`Server feed configuration not found for: ${serverName} at ${filePath}`);
|
|
36
|
-
} else {
|
|
37
|
-
console.error(`Error loading server feed configuration for ${serverName}:`, error);
|
|
38
|
-
}
|
|
39
|
-
return undefined;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Validates the install request body for a specific server.
|
|
45
|
-
* @param serverName The name of the server to validate against.
|
|
46
|
-
* @param requestBody The installation request body.
|
|
47
|
-
* @returns An array of error messages, or an empty array if validation passes.
|
|
48
|
-
*/
|
|
49
|
-
async validate(serverName: string, requestBody: ServerInstallOptions): Promise<string[]> {
|
|
50
|
-
const errors: string[] = [];
|
|
51
|
-
const config = await this.loadServerFeedConfiguration(serverName);
|
|
52
|
-
|
|
53
|
-
if (!config) {
|
|
54
|
-
errors.push(`Server configuration feed not found for '${serverName}'.`);
|
|
55
|
-
// Cannot perform further validation without the config
|
|
56
|
-
return errors;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// 1.2 Validate required environment variables
|
|
60
|
-
config.mcpServers.forEach(mcp => {
|
|
61
|
-
if (mcp.installation?.env) {
|
|
62
|
-
Object.entries(mcp.installation.env).forEach(([envVar, envConfig]) => {
|
|
63
|
-
if (envConfig.Required) {
|
|
64
|
-
if (!requestBody.env || !(envVar in requestBody.env) || !requestBody.env[envVar]) {
|
|
65
|
-
errors.push(`Missing required environment variable '${envVar}' for server '${serverName}' (category: ${mcp.name}).`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
// 1.3 Validate target clients
|
|
73
|
-
if (!requestBody.targetClients || requestBody.targetClients.length === 0) {
|
|
74
|
-
errors.push(`Request body must include a non-empty 'targetClients' array for server '${serverName}'.`);
|
|
75
|
-
} else {
|
|
76
|
-
requestBody.targetClients.forEach((client: string) => {
|
|
77
|
-
if (!SUPPORTED_CLIENT_NAMES.includes(client)) {
|
|
78
|
-
errors.push(`Unsupported target client '${client}' specified for server '${serverName}'. Supported clients are: ${SUPPORTED_CLIENT_NAMES.join(', ')}.`);
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Add other validation rules as needed
|
|
84
|
-
|
|
85
|
-
return errors;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Validates the install request body for multiple servers.
|
|
90
|
-
* @param requestBody The installation request body containing multiple server names.
|
|
91
|
-
* @returns An object mapping server names to their validation errors. Empty arrays indicate success.
|
|
92
|
-
*/
|
|
93
|
-
async validateMultiple(requestBody: InstallServersRequestBody): Promise<Record<string, string[]>> {
|
|
94
|
-
const results: Record<string, string[]> = {};
|
|
95
|
-
if (!requestBody.serverList || Object.keys(requestBody.serverList).length === 0) {
|
|
96
|
-
return { '_global': ['Request body must include a non-empty \'serverCategoryList\' record.'] };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
for (const serverName of Object.keys(requestBody.serverList)) {
|
|
100
|
-
results[serverName] = await this.validate(serverName, requestBody.serverList[serverName]);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return results;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Helper __dirname for ES modules
|
|
108
|
-
import { fileURLToPath } from 'url';
|
|
109
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
110
|
-
|
|
111
|
-
// Export a singleton instance (optional, depends on usage pattern)
|
|
112
|
-
// export const installRequestValidator = new InstallRequestValidator();
|
|
File without changes
|