imcp 0.0.10 → 0.0.12
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/README.md +4 -0
- package/dist/cli/commands/serve.js +2 -1
- package/dist/cli/commands/uninstall.js +14 -6
- package/dist/core/ConfigurationLoader.d.ts +1 -1
- package/dist/core/ConfigurationLoader.js +25 -4
- package/dist/core/ConfigurationProvider.d.ts +2 -1
- package/dist/core/ConfigurationProvider.js +69 -5
- package/dist/core/MCPManager.d.ts +1 -1
- package/dist/core/MCPManager.js +28 -6
- package/dist/core/ServerSchemaLoader.d.ts +11 -0
- package/dist/core/ServerSchemaLoader.js +43 -0
- package/dist/core/ServerSchemaProvider.d.ts +17 -0
- package/dist/core/ServerSchemaProvider.js +115 -0
- package/dist/core/constants.d.ts +11 -1
- package/dist/core/installers/clients/ExtensionInstaller.js +3 -0
- package/dist/core/types.d.ts +6 -0
- package/dist/services/ServerService.d.ts +7 -1
- package/dist/services/ServerService.js +24 -9
- package/dist/utils/osUtils.js +1 -1
- package/dist/web/public/css/detailsWidget.css +110 -0
- package/dist/web/public/css/modal.css +42 -0
- package/dist/web/public/css/serverDetails.css +42 -30
- package/dist/web/public/js/detailsWidget.js +225 -12
- package/dist/web/public/js/modal.js +93 -29
- package/dist/web/public/js/notifications.js +34 -35
- package/dist/web/server.js +13 -2
- package/package.json +1 -1
- package/src/cli/commands/serve.ts +4 -3
- package/src/cli/commands/uninstall.ts +16 -6
- package/src/core/ConfigurationLoader.ts +25 -4
- package/src/core/ConfigurationProvider.ts +75 -6
- package/src/core/MCPManager.ts +34 -9
- package/src/core/constants.ts +12 -2
- package/src/core/installers/clients/ExtensionInstaller.ts +3 -0
- package/src/core/types.ts +7 -1
- package/src/services/ServerService.ts +26 -8
- package/src/utils/osUtils.ts +1 -1
- package/src/web/public/css/modal.css +42 -0
- package/src/web/public/js/modal.js +93 -29
- package/src/web/public/js/notifications.js +34 -35
- package/src/web/server.ts +21 -3
|
@@ -141,14 +141,16 @@ function _appendInstallLoadingMessage(message) {
|
|
|
141
141
|
/**
|
|
142
142
|
* Display the installation loading modal and prepare it for messages.
|
|
143
143
|
*/
|
|
144
|
-
function showInstallLoadingModal() {
|
|
144
|
+
function showInstallLoadingModal(operation = 'Installing') {
|
|
145
145
|
const loadingModal = document.getElementById('installLoadingModal');
|
|
146
146
|
const loadingMsg = document.getElementById('installLoadingMessage');
|
|
147
|
-
|
|
147
|
+
const loadingTitle = document.querySelector('.loading-title');
|
|
148
|
+
if (loadingModal && loadingMsg && loadingTitle) {
|
|
148
149
|
loadingModal.style.display = 'block';
|
|
149
150
|
loadingMsg.innerHTML = '';
|
|
151
|
+
loadingTitle.textContent = `${operation}...`;
|
|
150
152
|
} else {
|
|
151
|
-
console.error('[LoadingModal] Required elements not found: installLoadingModal or
|
|
153
|
+
console.error('[LoadingModal] Required elements not found: installLoadingModal, installLoadingMessage, or loading-title');
|
|
152
154
|
}
|
|
153
155
|
}
|
|
154
156
|
|
|
@@ -330,17 +332,55 @@ async function showInstallModal(categoryName, serverName, callback) {
|
|
|
330
332
|
// Add elements to client info
|
|
331
333
|
clientInfo.appendChild(clientName);
|
|
332
334
|
|
|
333
|
-
// Add
|
|
335
|
+
// Add elements to client item
|
|
334
336
|
clientItem.appendChild(clientInfo);
|
|
335
337
|
|
|
338
|
+
// Status container for badge and uninstall button
|
|
339
|
+
const statusContainer = document.createElement('div');
|
|
340
|
+
statusContainer.className = 'status-container';
|
|
341
|
+
|
|
336
342
|
// Status badge - only show if we have status text
|
|
337
343
|
if (statusText) {
|
|
338
344
|
const statusBadge = document.createElement('span');
|
|
339
345
|
statusBadge.className = `status-badge ${statusClass}`;
|
|
340
346
|
statusBadge.textContent = statusText;
|
|
341
|
-
|
|
347
|
+
statusContainer.appendChild(statusBadge);
|
|
348
|
+
|
|
349
|
+
// Add uninstall button right after status badge if installed
|
|
350
|
+
if (operationStatus.status === 'completed' && operationStatus.type === 'install') {
|
|
351
|
+
const uninstallBtn = document.createElement('button');
|
|
352
|
+
uninstallBtn.className = 'uninstall-btn text-red-600 hover:text-red-800 ml-2';
|
|
353
|
+
uninstallBtn.innerHTML = '<i class="bx bx-trash"></i>';
|
|
354
|
+
uninstallBtn.title = 'Uninstall from this client';
|
|
355
|
+
uninstallBtn.onclick = async (e) => {
|
|
356
|
+
e.stopPropagation(); // Prevent item selection
|
|
357
|
+
e.preventDefault(); // Prevent form submission
|
|
358
|
+
const confirmed = await showConfirm('Uninstall Confirmation', `Are you sure you want to uninstall ${serverName} from ${target}?`);
|
|
359
|
+
if (confirmed) {
|
|
360
|
+
// Add target to selectedClients for uninstallation
|
|
361
|
+
window.selectedClients = [target];
|
|
362
|
+
showInstallLoadingModal('Uninstalling');
|
|
363
|
+
const serverList = {
|
|
364
|
+
[serverName]: {
|
|
365
|
+
removeData: true // Include removal of associated data
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
try {
|
|
369
|
+
delayedAppendInstallLoadingMessage(`Uninstalling ${serverName} from ${target}...`);
|
|
370
|
+
await uninstallTools(categoryName, serverList, [target]);
|
|
371
|
+
} catch (error) {
|
|
372
|
+
delayedAppendInstallLoadingMessage(`Error: ${error.message}`);
|
|
373
|
+
throw error; // Re-throw to trigger error handling in uninstallTools
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return false; // Prevent form submission
|
|
377
|
+
};
|
|
378
|
+
statusContainer.appendChild(uninstallBtn);
|
|
379
|
+
}
|
|
342
380
|
}
|
|
343
381
|
|
|
382
|
+
clientItem.appendChild(statusContainer);
|
|
383
|
+
|
|
344
384
|
// Add client item to target div
|
|
345
385
|
targetDiv.appendChild(clientItem);
|
|
346
386
|
});
|
|
@@ -641,34 +681,40 @@ async function showInstallModal(categoryName, serverName, callback) {
|
|
|
641
681
|
});
|
|
642
682
|
});
|
|
643
683
|
|
|
644
|
-
// Get selected clients
|
|
645
|
-
const selectedTargets = window.selectedClients.length > 0 ?
|
|
646
|
-
window.selectedClients :
|
|
647
|
-
Array.from(document.querySelectorAll('.client-item.selected'))
|
|
648
|
-
.map(item => item.dataset.target);
|
|
649
|
-
|
|
650
684
|
// Check if we have any requirements selected for update
|
|
651
685
|
const hasRequirementsToUpdate = requirementsToUpdate.length > 0;
|
|
652
686
|
|
|
653
|
-
// Only
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
687
|
+
// Only proceed if this isn't an uninstall operation
|
|
688
|
+
const uninstallBtn = document.querySelector('.uninstall-btn');
|
|
689
|
+
if (!uninstallBtn || !uninstallBtn.matches(':active')) {
|
|
690
|
+
// Get selected clients
|
|
691
|
+
const selectedTargets = window.selectedClients.length > 0 ?
|
|
692
|
+
window.selectedClients :
|
|
693
|
+
Array.from(document.querySelectorAll('.client-item.selected'))
|
|
694
|
+
.map(item => item.dataset.target);
|
|
695
|
+
|
|
696
|
+
console.log('Selected targets:', selectedTargets);
|
|
697
|
+
console.log('Requirements to update:', requirementsToUpdate);
|
|
698
|
+
if (selectedTargets.length === 0 && !hasRequirementsToUpdate) {
|
|
699
|
+
showToast('Please select at least one client to configure.', 'error');
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
window.selectedClients = selectedTargets;
|
|
657
703
|
}
|
|
658
704
|
|
|
659
705
|
// Call install function with selected targets
|
|
660
706
|
// Find installing message for the first selected target
|
|
661
707
|
let installingMessage = "Starting installation...";
|
|
662
708
|
const serverStatus = serverStatuses[serverName] || { installedStatus: {} };
|
|
663
|
-
if (
|
|
664
|
-
const target =
|
|
709
|
+
if (window.selectedClients.length > 0) {
|
|
710
|
+
const target = window.selectedClients[0];
|
|
665
711
|
const msg = serverStatus.installedStatus?.[target]?.message;
|
|
666
712
|
if (msg) installingMessage = msg;
|
|
667
713
|
}
|
|
668
714
|
|
|
669
715
|
// Add requirements to update to serverInstallOptions if any
|
|
670
716
|
const serverInstallOptions = {
|
|
671
|
-
targetClients:
|
|
717
|
+
targetClients: window.selectedClients,
|
|
672
718
|
env: envVars,
|
|
673
719
|
args: args,
|
|
674
720
|
settings: pythonEnv ? { pythonEnv } : undefined
|
|
@@ -679,7 +725,9 @@ async function showInstallModal(categoryName, serverName, callback) {
|
|
|
679
725
|
serverInstallOptions.requirements = requirementsToUpdate;
|
|
680
726
|
}
|
|
681
727
|
|
|
682
|
-
|
|
728
|
+
// For installation, use the selectedTargets from the validation above
|
|
729
|
+
const targetsToUse = document.querySelector('.uninstall-btn:hover') ? [] : window.selectedClients;
|
|
730
|
+
handleBulkClientInstall(categoryName, serverName, targetsToUse, envVars, installingMessage, serverData, serverInstallOptions);
|
|
683
731
|
};
|
|
684
732
|
|
|
685
733
|
} catch (error) {
|
|
@@ -893,23 +941,35 @@ async function pollInstallStatus(categoryName, serverName, targets, interval = 2
|
|
|
893
941
|
|
|
894
942
|
// Function to handle client uninstallation for multiple targets
|
|
895
943
|
async function uninstallTools(categoryName, serverList, targets) {
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
944
|
+
// Get selected targets from window.selectedClients or the provided targets
|
|
945
|
+
const selectedTargets = window.selectedClients || (Array.isArray(targets) ? targets : [targets]);
|
|
946
|
+
|
|
947
|
+
// Validate selected targets
|
|
948
|
+
if (!selectedTargets || selectedTargets.length === 0) {
|
|
949
|
+
showToast('Please select at least one client to uninstall.', 'error');
|
|
902
950
|
return;
|
|
903
951
|
}
|
|
904
952
|
|
|
905
953
|
try {
|
|
954
|
+
delayedAppendInstallLoadingMessage('Starting uninstallation...');
|
|
955
|
+
|
|
956
|
+
// Ensure serverList is an object where each key is a server name
|
|
957
|
+
if (Array.isArray(serverList)) {
|
|
958
|
+
const formattedServerList = {};
|
|
959
|
+
serverList.forEach(server => {
|
|
960
|
+
formattedServerList[server] = { removeData: true };
|
|
961
|
+
});
|
|
962
|
+
serverList = formattedServerList;
|
|
963
|
+
}
|
|
964
|
+
|
|
906
965
|
const response = await fetch(`/api/categories/${categoryName}/uninstall`, {
|
|
907
966
|
method: 'POST',
|
|
908
967
|
headers: { 'Content-Type': 'application/json' },
|
|
909
968
|
body: JSON.stringify({
|
|
910
|
-
|
|
969
|
+
serverList: serverList,
|
|
911
970
|
options: {
|
|
912
|
-
targets:
|
|
971
|
+
targets: selectedTargets,
|
|
972
|
+
removeData: true
|
|
913
973
|
}
|
|
914
974
|
})
|
|
915
975
|
});
|
|
@@ -924,10 +984,14 @@ async function uninstallTools(categoryName, serverList, targets) {
|
|
|
924
984
|
throw new Error(result.error || 'Uninstallation failed');
|
|
925
985
|
}
|
|
926
986
|
|
|
927
|
-
|
|
928
|
-
|
|
987
|
+
// Add completion message and trigger completion UI
|
|
988
|
+
delayedAppendInstallLoadingMessage(`Successfully uninstalled from ${selectedTargets.join(', ')}`, true);
|
|
989
|
+
|
|
990
|
+
// Clear selected clients after successful uninstall
|
|
991
|
+
window.selectedClients = [];
|
|
929
992
|
} catch (error) {
|
|
930
993
|
console.error('Error uninstalling tools:', error);
|
|
994
|
+
delayedAppendInstallLoadingMessage(`Error: ${error.message}`, true);
|
|
931
995
|
showToast(`Error uninstalling tools: ${error.message}`, 'error');
|
|
932
996
|
}
|
|
933
997
|
}
|
|
@@ -30,28 +30,26 @@ function showToast(message, type = 'success') {
|
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
// Function to show a
|
|
34
|
-
function showConfirm(message) {
|
|
33
|
+
// Function to show a confirmation dialog using custom modal system
|
|
34
|
+
function showConfirm(title, message) {
|
|
35
35
|
return new Promise((resolve) => {
|
|
36
36
|
const modalId = `confirm-modal-${Date.now()}`;
|
|
37
37
|
const modalHtml = `
|
|
38
|
-
<div
|
|
39
|
-
<div class="modal-
|
|
40
|
-
<div class="modal-
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
</button>
|
|
54
|
-
</div>
|
|
38
|
+
<div id="${modalId}" class="modal">
|
|
39
|
+
<div class="modal-content" style="max-width: 400px; margin: 15% auto;">
|
|
40
|
+
<div class="modal-header">
|
|
41
|
+
<h3 class="text-lg font-semibold text-gray-700">${title}</h3>
|
|
42
|
+
</div>
|
|
43
|
+
<div style="margin: 1rem 0;">
|
|
44
|
+
${message}
|
|
45
|
+
</div>
|
|
46
|
+
<div style="display: flex; justify-content: flex-end; gap: 0.5rem; margin-top: 1rem;">
|
|
47
|
+
<button type="button" class="cancel-button px-4 py-2 text-gray-600 hover:text-gray-800 font-medium rounded-lg hover:bg-gray-100 transition-colors">
|
|
48
|
+
Cancel
|
|
49
|
+
</button>
|
|
50
|
+
<button type="button" class="confirm-button submit-button">
|
|
51
|
+
Confirm
|
|
52
|
+
</button>
|
|
55
53
|
</div>
|
|
56
54
|
</div>
|
|
57
55
|
</div>
|
|
@@ -60,29 +58,30 @@ function showConfirm(message) {
|
|
|
60
58
|
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
|
61
59
|
|
|
62
60
|
const modalElement = document.getElementById(modalId);
|
|
63
|
-
|
|
61
|
+
modalElement.style.display = 'block';
|
|
64
62
|
|
|
65
63
|
// Handle confirm button click
|
|
66
|
-
modalElement.querySelector('.confirm-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
spinner.classList.remove('d-none');
|
|
71
|
-
|
|
72
|
-
// Add small delay to show the loading state
|
|
73
|
-
setTimeout(() => {
|
|
74
|
-
modal.hide();
|
|
75
|
-
resolve(true);
|
|
76
|
-
}, 300);
|
|
64
|
+
modalElement.querySelector('.confirm-button').addEventListener('click', () => {
|
|
65
|
+
modalElement.style.display = 'none';
|
|
66
|
+
modalElement.remove();
|
|
67
|
+
resolve(true);
|
|
77
68
|
});
|
|
78
69
|
|
|
79
|
-
// Handle
|
|
80
|
-
modalElement.
|
|
70
|
+
// Handle cancel button click
|
|
71
|
+
modalElement.querySelector('.cancel-button').addEventListener('click', () => {
|
|
72
|
+
modalElement.style.display = 'none';
|
|
81
73
|
modalElement.remove();
|
|
82
74
|
resolve(false);
|
|
83
75
|
});
|
|
84
|
-
|
|
85
|
-
modal
|
|
76
|
+
|
|
77
|
+
// Handle click outside modal
|
|
78
|
+
modalElement.addEventListener('click', (event) => {
|
|
79
|
+
if (event.target === modalElement) {
|
|
80
|
+
modalElement.style.display = 'none';
|
|
81
|
+
modalElement.remove();
|
|
82
|
+
resolve(false);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
86
85
|
});
|
|
87
86
|
}
|
|
88
87
|
|
package/dist/web/server.js
CHANGED
|
@@ -110,13 +110,24 @@ app.post('/api/categories/:categoryName/uninstall', async (req, res) => {
|
|
|
110
110
|
try {
|
|
111
111
|
const { categoryName } = req.params;
|
|
112
112
|
const { serverList } = req.body;
|
|
113
|
-
if (!
|
|
113
|
+
if (!serverList || Object.keys(serverList).length === 0) {
|
|
114
114
|
return res.status(400).json({
|
|
115
115
|
success: false,
|
|
116
116
|
error: 'Invalid tool list provided'
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
|
-
const
|
|
119
|
+
const { options } = req.body;
|
|
120
|
+
if (!options?.targets || options.targets.length === 0) {
|
|
121
|
+
return res.status(400).json({
|
|
122
|
+
success: false,
|
|
123
|
+
error: 'No target clients specified'
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const results = await Promise.all(Object.entries(serverList).map(([serverName, serverOptions]) => serverService.uninstallMcpServer(categoryName, serverName, {
|
|
127
|
+
...serverOptions,
|
|
128
|
+
targets: options.targets,
|
|
129
|
+
removeData: options.removeData ?? serverOptions.removeData
|
|
130
|
+
})));
|
|
120
131
|
const { success, messages } = serverService.formatOperationResults(results);
|
|
121
132
|
const response = {
|
|
122
133
|
success,
|
package/package.json
CHANGED
|
@@ -8,21 +8,22 @@ export function createServeCommand(): Command {
|
|
|
8
8
|
return new Command('serve')
|
|
9
9
|
.description('Serve local web interface')
|
|
10
10
|
.option('-p, --port <port>', 'Port to run the server on', '3000')
|
|
11
|
+
.option('-f, --feed-file <filepath>', 'Path to a custom feed configuration file')
|
|
11
12
|
.action(async (options) => {
|
|
12
13
|
try {
|
|
13
14
|
// Sync feeds before start the local UI
|
|
14
15
|
await mcpManager.syncFeeds();
|
|
15
16
|
|
|
16
17
|
// Ensure MCP manager is initialized before starting the web server
|
|
17
|
-
await mcpManager.initialize();
|
|
18
|
-
|
|
18
|
+
await mcpManager.initialize(options.feedFile);
|
|
19
|
+
|
|
19
20
|
const port = parseInt(options.port, 10);
|
|
20
21
|
if (isNaN(port) || port < 1 || port > 65535) {
|
|
21
22
|
throw new Error('Invalid port number');
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
await startWebServer(port);
|
|
25
|
-
|
|
26
|
+
|
|
26
27
|
// The server is running, keep the process alive
|
|
27
28
|
process.on('SIGINT', () => {
|
|
28
29
|
console.log('\nShutting down server...');
|
|
@@ -6,19 +6,27 @@ export function createUninstallCommand(): Command {
|
|
|
6
6
|
.description('Uninstall specific MCP servers')
|
|
7
7
|
.requiredOption(
|
|
8
8
|
'--category <category>',
|
|
9
|
+
'Server category'
|
|
10
|
+
)
|
|
11
|
+
.requiredOption(
|
|
9
12
|
'--names <names>',
|
|
10
13
|
'Server names (semicolon separated)'
|
|
11
14
|
)
|
|
15
|
+
.requiredOption(
|
|
16
|
+
'--targets <targets>',
|
|
17
|
+
'Target clients to uninstall from (semicolon separated)'
|
|
18
|
+
)
|
|
12
19
|
.option(
|
|
13
20
|
'--remove-data',
|
|
14
21
|
'Remove all associated data',
|
|
15
|
-
|
|
22
|
+
true // Change default to true to ensure cleanup happens
|
|
16
23
|
)
|
|
17
24
|
.action(async (options) => {
|
|
18
25
|
try {
|
|
19
26
|
const serverNames = options.names.split(';').map((name: string) => name.trim());
|
|
20
27
|
|
|
21
|
-
|
|
28
|
+
const validNames = await serverService.validateServerName(options.category, serverNames);
|
|
29
|
+
if (!validNames) {
|
|
22
30
|
console.error('Invalid server names provided');
|
|
23
31
|
process.exit(1);
|
|
24
32
|
}
|
|
@@ -26,11 +34,13 @@ export function createUninstallCommand(): Command {
|
|
|
26
34
|
console.log(`Uninstalling servers: ${serverNames.join(', ')}`);
|
|
27
35
|
|
|
28
36
|
const results = await Promise.all(
|
|
29
|
-
serverNames.map((serverName: string) =>
|
|
30
|
-
|
|
37
|
+
serverNames.map((serverName: string) => {
|
|
38
|
+
const targets = options.targets ? options.targets.split(';').map((t: string) => t.trim()) : [];
|
|
39
|
+
return serverService.uninstallMcpServer(options.category, serverName, {
|
|
31
40
|
removeData: options.removeData,
|
|
32
|
-
|
|
33
|
-
|
|
41
|
+
targets: targets
|
|
42
|
+
});
|
|
43
|
+
})
|
|
34
44
|
);
|
|
35
45
|
|
|
36
46
|
const { success, messages } = serverService.formatOperationResults(results);
|
|
@@ -177,25 +177,46 @@ export class ConfigurationLoader {
|
|
|
177
177
|
/**
|
|
178
178
|
* Loads feed configurations into the MCP configuration
|
|
179
179
|
*/
|
|
180
|
-
static async loadFeedsIntoConfiguration(configuration: MCPConfiguration): Promise<MCPConfiguration> {
|
|
180
|
+
static async loadFeedsIntoConfiguration(configuration: MCPConfiguration, feedFile?: string): Promise<MCPConfiguration> {
|
|
181
181
|
try {
|
|
182
182
|
await fs.mkdir(LOCAL_FEEDS_DIR, { recursive: true });
|
|
183
|
+
const feeds: Record<string, FeedConfiguration> = {};
|
|
184
|
+
|
|
185
|
+
// Load provided feed file if specified
|
|
186
|
+
if (feedFile) {
|
|
187
|
+
try {
|
|
188
|
+
const content = await fs.readFile(feedFile, 'utf8');
|
|
189
|
+
const config = JSON.parse(content) as FeedConfiguration;
|
|
190
|
+
if (config && config.name) {
|
|
191
|
+
feeds[config.name] = config;
|
|
192
|
+
console.log(`Loaded feed configuration from provided file: ${feedFile}`);
|
|
193
|
+
}
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.log(`Error loading feed configuration from provided file ${feedFile}:`, error);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Load feeds from LOCAL_FEEDS_DIR
|
|
183
200
|
const files = await fs.readdir(LOCAL_FEEDS_DIR);
|
|
184
201
|
const jsonFiles = files.filter(file => file.endsWith('.json'));
|
|
185
202
|
|
|
186
|
-
if (jsonFiles.length === 0) {
|
|
203
|
+
if (jsonFiles.length === 0 && !feedFile) {
|
|
187
204
|
console.log(`No feed configuration files found in ${LOCAL_FEEDS_DIR}`);
|
|
188
205
|
return configuration;
|
|
189
206
|
}
|
|
190
207
|
|
|
191
|
-
const feeds: Record<string, FeedConfiguration> = {};
|
|
192
208
|
for (const file of jsonFiles) {
|
|
193
209
|
try {
|
|
194
210
|
const filePath = path.join(LOCAL_FEEDS_DIR, file);
|
|
195
211
|
const content = await fs.readFile(filePath, 'utf8');
|
|
196
212
|
const config = JSON.parse(content) as FeedConfiguration;
|
|
197
213
|
if (config && config.name) {
|
|
198
|
-
|
|
214
|
+
// If feed exists from provided file, skip the local one
|
|
215
|
+
if (!feeds[config.name]) {
|
|
216
|
+
feeds[config.name] = config;
|
|
217
|
+
} else {
|
|
218
|
+
console.log(`Skipping local feed ${config.name} as it was provided via --feed-file`);
|
|
219
|
+
}
|
|
199
220
|
}
|
|
200
221
|
} catch (error) {
|
|
201
222
|
console.warn(`Error loading feed configuration from ${file}:`, error);
|
|
@@ -3,7 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import { exec } from 'child_process';
|
|
4
4
|
import { promisify } from 'util';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
-
import { GITHUB_REPO, LOCAL_FEEDS_DIR, SETTINGS_DIR } from './constants.js';
|
|
6
|
+
import { GITHUB_REPO, LOCAL_FEEDS_DIR, SETTINGS_DIR, SUPPORTED_CLIENTS } from './constants.js';
|
|
7
7
|
import { Logger } from '../utils/logger.js';
|
|
8
8
|
import { checkGithubAuth } from '../utils/githubAuth.js';
|
|
9
9
|
import {
|
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
InstallationStatus,
|
|
14
14
|
RequirementStatus,
|
|
15
15
|
MCPServerStatus,
|
|
16
|
-
OperationStatus
|
|
16
|
+
OperationStatus,
|
|
17
|
+
ClientSettings
|
|
17
18
|
} from './types.js';
|
|
18
19
|
import { ConfigurationLoader } from './ConfigurationLoader.js';
|
|
19
20
|
|
|
@@ -57,7 +58,7 @@ export class ConfigurationProvider {
|
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
async initialize(): Promise<void> {
|
|
61
|
+
async initialize(feedFile?: string): Promise<void> {
|
|
61
62
|
await this.withLock(async () => {
|
|
62
63
|
const configDir = path.dirname(this.configPath);
|
|
63
64
|
await fs.mkdir(configDir, { recursive: true });
|
|
@@ -75,7 +76,7 @@ export class ConfigurationProvider {
|
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
// Always load feeds and client settings, whether file existed or not
|
|
78
|
-
await this.loadFeedsIntoConfiguration();
|
|
79
|
+
await this.loadFeedsIntoConfiguration(feedFile);
|
|
79
80
|
await this.loadClientMCPSettings();
|
|
80
81
|
} catch (error) {
|
|
81
82
|
Logger.error('Error during initialization', error);
|
|
@@ -340,8 +341,8 @@ export class ConfigurationProvider {
|
|
|
340
341
|
});
|
|
341
342
|
}
|
|
342
343
|
|
|
343
|
-
private async loadFeedsIntoConfiguration(): Promise<void> {
|
|
344
|
-
this.configuration = await ConfigurationLoader.loadFeedsIntoConfiguration(this.configuration);
|
|
344
|
+
private async loadFeedsIntoConfiguration(feedFile?: string): Promise<void> {
|
|
345
|
+
this.configuration = await ConfigurationLoader.loadFeedsIntoConfiguration(this.configuration, feedFile);
|
|
345
346
|
await this.saveConfiguration();
|
|
346
347
|
}
|
|
347
348
|
|
|
@@ -356,6 +357,74 @@ export class ConfigurationProvider {
|
|
|
356
357
|
await this.loadClientMCPSettings();
|
|
357
358
|
});
|
|
358
359
|
}
|
|
360
|
+
|
|
361
|
+
async removeServerFromClientMCPSettings(serverName: string, target?: string): Promise<void> {
|
|
362
|
+
return await this.withLock(async () => {
|
|
363
|
+
// Load utils in async context to avoid circular dependencies
|
|
364
|
+
const { readJsonFile, writeJsonFile } = await import('../utils/clientUtils.js');
|
|
365
|
+
|
|
366
|
+
// Filter clients if target is specified
|
|
367
|
+
const clientEntries = Object.entries(SUPPORTED_CLIENTS as Record<string, ClientSettings>);
|
|
368
|
+
const targetClients = target
|
|
369
|
+
? clientEntries.filter(([clientName]) => clientName === target)
|
|
370
|
+
: clientEntries;
|
|
371
|
+
|
|
372
|
+
for (const [clientName, clientSettings] of targetClients) {
|
|
373
|
+
const settingPath = process.env.CODE_INSIDERS
|
|
374
|
+
? clientSettings.codeInsiderSettingPath
|
|
375
|
+
: clientSettings.codeSettingPath;
|
|
376
|
+
|
|
377
|
+
try {
|
|
378
|
+
const content = await readJsonFile(settingPath, true);
|
|
379
|
+
let modified = false;
|
|
380
|
+
|
|
381
|
+
// Handle GitHub Copilot's different structure
|
|
382
|
+
if (clientName === 'GithubCopilot' && content.mcp?.servers?.[serverName]) {
|
|
383
|
+
delete content.mcp.servers[serverName];
|
|
384
|
+
modified = true;
|
|
385
|
+
} else if (content.mcpServers?.[serverName]) {
|
|
386
|
+
delete content.mcpServers[serverName];
|
|
387
|
+
modified = true;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Only write if we actually modified the content
|
|
391
|
+
if (modified) {
|
|
392
|
+
await writeJsonFile(settingPath, content);
|
|
393
|
+
Logger.debug(`Removed server ${serverName} from client ${clientName} settings`);
|
|
394
|
+
}
|
|
395
|
+
} catch (error) {
|
|
396
|
+
Logger.error(`Failed to remove server ${serverName} from client ${clientName} settings:`, error);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Also update our in-memory configuration
|
|
401
|
+
if (this.configuration.clientMCPSettings) {
|
|
402
|
+
if (target) {
|
|
403
|
+
// Only update settings for the target client
|
|
404
|
+
const clientSettings = this.configuration.clientMCPSettings[target];
|
|
405
|
+
if (clientSettings) {
|
|
406
|
+
if (clientSettings.mcpServers?.[serverName]) {
|
|
407
|
+
delete clientSettings.mcpServers[serverName];
|
|
408
|
+
}
|
|
409
|
+
if (clientSettings.servers?.[serverName]) { // For GitHub Copilot
|
|
410
|
+
delete clientSettings.servers[serverName];
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
} else {
|
|
414
|
+
// Update all clients if no target specified
|
|
415
|
+
for (const clientSettings of Object.values(this.configuration.clientMCPSettings)) {
|
|
416
|
+
if (clientSettings.mcpServers?.[serverName]) {
|
|
417
|
+
delete clientSettings.mcpServers[serverName];
|
|
418
|
+
}
|
|
419
|
+
if (clientSettings.servers?.[serverName]) { // For GitHub Copilot
|
|
420
|
+
delete clientSettings.servers[serverName];
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
await this.saveConfiguration();
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
}
|
|
359
428
|
}
|
|
360
429
|
|
|
361
430
|
// Export a singleton instance
|
package/src/core/MCPManager.ts
CHANGED
|
@@ -9,8 +9,7 @@ import {
|
|
|
9
9
|
ServerCategoryListOptions,
|
|
10
10
|
ServerOperationResult,
|
|
11
11
|
ServerUninstallOptions,
|
|
12
|
-
InstallationStatus
|
|
13
|
-
UpdateRequirementOptions
|
|
12
|
+
InstallationStatus
|
|
14
13
|
} from './types.js';
|
|
15
14
|
import path from 'path';
|
|
16
15
|
|
|
@@ -28,9 +27,9 @@ export class MCPManager extends EventEmitter {
|
|
|
28
27
|
await this.configProvider.syncFeeds();
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
async initialize(): Promise<void> {
|
|
30
|
+
async initialize(feedFile?: string): Promise<void> {
|
|
32
31
|
try {
|
|
33
|
-
await this.configProvider.initialize();
|
|
32
|
+
await this.configProvider.initialize(feedFile);
|
|
34
33
|
} catch (error) {
|
|
35
34
|
console.error("Error during MCPManager initialization:", error);
|
|
36
35
|
throw error;
|
|
@@ -95,18 +94,44 @@ export class MCPManager extends EventEmitter {
|
|
|
95
94
|
};
|
|
96
95
|
}
|
|
97
96
|
|
|
98
|
-
|
|
97
|
+
const { targets = [], removeData = false } = options;
|
|
98
|
+
|
|
99
|
+
// Clear installation status for specified targets
|
|
100
|
+
const currentStatus: InstallationStatus = serverCategory.installationStatus || {
|
|
101
|
+
requirementsStatus: {},
|
|
102
|
+
serversStatus: {},
|
|
103
|
+
lastUpdated: new Date().toISOString()
|
|
104
|
+
};
|
|
105
|
+
const serversStatus = currentStatus.serversStatus || {};
|
|
106
|
+
const serverStatus = serversStatus[serverName] || { installedStatus: {}, name: serverName };
|
|
107
|
+
|
|
108
|
+
// Only reset installedStatus for specified targets
|
|
109
|
+
for (const target of targets) {
|
|
110
|
+
if (serverStatus.installedStatus) {
|
|
111
|
+
delete serverStatus.installedStatus[target];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (removeData) {
|
|
115
|
+
for (const target of targets) {
|
|
116
|
+
await this.configProvider.removeServerFromClientMCPSettings(serverName, target);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Update server status
|
|
121
|
+
serversStatus[serverName] = serverStatus;
|
|
122
|
+
|
|
123
|
+
// Update status keeping requirements
|
|
99
124
|
await this.configProvider.updateInstallationStatus(
|
|
100
125
|
categoryName,
|
|
101
|
-
{},
|
|
102
|
-
|
|
126
|
+
currentStatus.requirementsStatus || {},
|
|
127
|
+
serversStatus
|
|
103
128
|
);
|
|
104
129
|
|
|
105
|
-
this.emit(MCPEvent.SERVER_UNINSTALLED, { serverName });
|
|
130
|
+
this.emit(MCPEvent.SERVER_UNINSTALLED, { serverName, targets });
|
|
106
131
|
|
|
107
132
|
return {
|
|
108
133
|
success: true,
|
|
109
|
-
message: `Successfully uninstalled ${serverName}`,
|
|
134
|
+
message: `Successfully uninstalled ${serverName} from ${targets.join(', ')}`,
|
|
110
135
|
};
|
|
111
136
|
} catch (error) {
|
|
112
137
|
return {
|