imcp 0.0.12 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/core/ConfigurationProvider.d.ts +2 -1
  2. package/dist/core/ConfigurationProvider.js +20 -24
  3. package/dist/core/InstallationService.d.ts +17 -0
  4. package/dist/core/InstallationService.js +127 -61
  5. package/dist/core/MCPManager.d.ts +1 -0
  6. package/dist/core/MCPManager.js +3 -0
  7. package/dist/core/RequirementService.d.ts +4 -4
  8. package/dist/core/RequirementService.js +11 -7
  9. package/dist/core/ServerSchemaProvider.d.ts +1 -1
  10. package/dist/core/ServerSchemaProvider.js +15 -10
  11. package/dist/core/constants.d.ts +3 -0
  12. package/dist/core/constants.js +4 -1
  13. package/dist/core/installers/clients/ClientInstaller.js +58 -40
  14. package/dist/core/installers/requirements/PipInstaller.js +10 -5
  15. package/dist/core/onboard/FeedOnboardService.d.ts +35 -0
  16. package/dist/core/onboard/FeedOnboardService.js +137 -0
  17. package/dist/core/types.d.ts +6 -1
  18. package/dist/core/validators/FeedValidator.d.ts +13 -0
  19. package/dist/core/validators/FeedValidator.js +27 -0
  20. package/dist/services/ServerService.d.ts +5 -0
  21. package/dist/services/ServerService.js +15 -0
  22. package/dist/utils/githubAuth.js +0 -10
  23. package/dist/utils/githubUtils.d.ts +16 -0
  24. package/dist/utils/githubUtils.js +55 -39
  25. package/dist/web/contract/serverContract.d.ts +64 -0
  26. package/dist/web/contract/serverContract.js +2 -0
  27. package/dist/web/public/css/detailsWidget.css +157 -32
  28. package/dist/web/public/css/onboard.css +44 -0
  29. package/dist/web/public/css/serverDetails.css +35 -19
  30. package/dist/web/public/index.html +16 -10
  31. package/dist/web/public/js/detailsWidget.js +43 -40
  32. package/dist/web/public/js/modal/index.js +58 -0
  33. package/dist/web/public/js/modal/installHandler.js +227 -0
  34. package/dist/web/public/js/modal/installModal.js +163 -0
  35. package/dist/web/public/js/modal/installation.js +281 -0
  36. package/dist/web/public/js/modal/loadingModal.js +52 -0
  37. package/dist/web/public/js/modal/loadingUI.js +74 -0
  38. package/dist/web/public/js/modal/messageQueue.js +112 -0
  39. package/dist/web/public/js/modal/modalSetup.js +512 -0
  40. package/dist/web/public/js/modal/modalUI.js +214 -0
  41. package/dist/web/public/js/modal/modalUtils.js +49 -0
  42. package/dist/web/public/js/modal/version.js +20 -0
  43. package/dist/web/public/js/modal/versionUtils.js +20 -0
  44. package/dist/web/public/js/modal.js +25 -1041
  45. package/dist/web/public/js/onboard/formProcessor.js +309 -0
  46. package/dist/web/public/js/onboard/index.js +131 -0
  47. package/dist/web/public/js/onboard/state.js +32 -0
  48. package/dist/web/public/js/onboard/templates.js +375 -0
  49. package/dist/web/public/js/onboard/uiHandlers.js +196 -0
  50. package/dist/web/public/js/serverCategoryDetails.js +211 -123
  51. package/dist/web/public/onboard.html +150 -0
  52. package/dist/web/server.js +25 -0
  53. package/package.json +3 -4
  54. package/src/core/ConfigurationProvider.ts +37 -29
  55. package/src/core/InstallationService.ts +176 -62
  56. package/src/core/MCPManager.ts +4 -0
  57. package/src/core/RequirementService.ts +12 -8
  58. package/src/core/ServerSchemaLoader.ts +48 -0
  59. package/src/core/ServerSchemaProvider.ts +137 -0
  60. package/src/core/constants.ts +4 -1
  61. package/src/core/installers/clients/ClientInstaller.ts +66 -49
  62. package/src/core/installers/requirements/PipInstaller.ts +10 -5
  63. package/src/core/types.ts +6 -1
  64. package/src/services/ServerService.ts +15 -0
  65. package/src/utils/githubAuth.ts +14 -27
  66. package/src/utils/githubUtils.ts +84 -47
  67. package/src/web/public/css/detailsWidget.css +235 -0
  68. package/src/web/public/css/serverDetails.css +126 -0
  69. package/src/web/public/index.html +16 -10
  70. package/src/web/public/js/detailsWidget.js +264 -0
  71. package/src/web/public/js/modal/index.js +58 -0
  72. package/src/web/public/js/modal/installModal.js +163 -0
  73. package/src/web/public/js/modal/installation.js +281 -0
  74. package/src/web/public/js/modal/loadingModal.js +52 -0
  75. package/src/web/public/js/modal/messageQueue.js +112 -0
  76. package/src/web/public/js/modal/modalSetup.js +512 -0
  77. package/src/web/public/js/modal/modalUtils.js +49 -0
  78. package/src/web/public/js/modal/versionUtils.js +20 -0
  79. package/src/web/public/js/modal.js +25 -1041
  80. package/src/web/public/js/serverCategoryDetails.js +211 -123
  81. package/src/web/server.ts +31 -0
@@ -1,143 +1,242 @@
1
1
  import { allServerCategoriesData, fetchServerCategories } from './api.js';
2
2
  import { showInstallModal } from './modal.js';
3
3
  import { showToast, showConfirm } from './notifications.js';
4
+ import { DetailsWidget } from './detailsWidget.js';
4
5
 
5
6
  const REFRESH_INTERVAL = 2000; // 2 seconds
6
7
  let refreshTimer = null;
8
+ let activeDetailsWidget = null;
9
+ const MAX_RETRIES = 3;
10
+ const RETRY_DELAY = 1000;
7
11
 
8
12
  // Start refresh timer for installation status
9
13
  function startRefreshTimer(serverName) {
10
- // Clear existing timer if any
11
14
  if (refreshTimer) {
12
15
  clearInterval(refreshTimer);
13
16
  }
14
17
 
15
- // Setup new refresh timer
16
18
  refreshTimer = setInterval(async () => {
17
- const server = allServerCategoriesData.find(s => s.name === serverName);
18
- if (!server?.installationStatus?.serversStatus) return;
19
-
20
- // Check if any installation is pending
21
- const hasPendingInstallation = Object.values(server.installationStatus.serversStatus).some(serverStatus =>
22
- Object.values(serverStatus.installedStatus || {}).some(status =>
23
- status.status === 'pending' || status.status === 'in-progress'
24
- )
25
- );
26
-
27
- if (hasPendingInstallation) {
28
- await fetchServerCategories(); // Refresh data
29
- await showServerDetails(serverName); // Update UI
30
- } else {
31
- clearInterval(refreshTimer); // Stop refreshing if no pending installations
19
+ try {
20
+ const server = allServerCategoriesData.find(s => s.name === serverName);
21
+ if (!server?.installationStatus?.serversStatus) return;
22
+
23
+ const hasPendingInstallation = Object.values(server.installationStatus.serversStatus).some(serverStatus =>
24
+ Object.values(serverStatus.installedStatus || {}).some(status =>
25
+ status.status === 'pending' || status.status === 'in-progress'
26
+ )
27
+ );
28
+
29
+ if (hasPendingInstallation) {
30
+ await fetchServerCategories();
31
+ await showServerDetails(serverName);
32
+ } else {
33
+ clearInterval(refreshTimer);
34
+ }
35
+ } catch (error) {
36
+ console.error('Error in refresh timer:', error);
37
+ clearInterval(refreshTimer);
32
38
  }
33
39
  }, REFRESH_INTERVAL);
34
40
  }
35
41
 
36
- // Show server details
37
- async function showServerDetails(serverName) {
42
+ // Show server details with retry mechanism
43
+ async function showServerDetails(serverName, retryCount = 0) {
38
44
  console.log("Showing details for:", serverName);
39
- // Save the selected category in localStorage
40
- localStorage.setItem('lastSelectedCategory', serverName);
41
- const server = allServerCategoriesData.find(s => s.name === serverName);
42
- const detailsDiv = document.getElementById('serverCategoryDetails');
43
-
44
- if (!server) {
45
- detailsDiv.innerHTML = '<p class="text-red-500">Error: Server data not found.</p>';
46
- return;
47
- }
45
+ try {
46
+ localStorage.setItem('lastSelectedCategory', serverName);
48
47
 
49
- // Highlight selected server
50
- document.querySelectorAll('.server-item').forEach(item => {
51
- item.classList.remove('bg-blue-100', 'border-blue-300');
52
- if (item.dataset.serverName === serverName) {
53
- item.classList.add('bg-blue-100', 'border-blue-300');
48
+ // If server data is not available, attempt to fetch it
49
+ if (allServerCategoriesData.length === 0) {
50
+ await fetchServerCategories();
54
51
  }
55
- });
56
-
57
- // Initial render with loading state
58
- detailsDiv.innerHTML = `
59
- <h3 class="text-xl font-semibold mb-2 text-gray-800">${server.displayName || server.name}</h3>
60
- <p class="mb-4"><span class="font-semibold">Description:</span> ${server.description || 'N/A'}</p>
61
- <div id="toolMcpsList" class="mt-4">
62
- <div class="animate-pulse flex space-x-4">
63
- <div class="flex-1 space-y-4 py-1">
64
- <div class="h-4 bg-gray-200 rounded w-3/4"></div>
65
- <div class="space-y-2">
66
- <div class="h-4 bg-gray-200 rounded"></div>
67
- <div class="h-4 bg-gray-200 rounded w-5/6"></div>
52
+
53
+ const server = allServerCategoriesData.find(s => s.name === serverName);
54
+ const detailsDiv = document.getElementById('serverCategoryDetails');
55
+
56
+ if (!server) {
57
+ if (retryCount < MAX_RETRIES) {
58
+ console.log(`Server data not found, retrying (${retryCount + 1}/${MAX_RETRIES})...`);
59
+ await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
60
+ await fetchServerCategories(); // Refresh the data
61
+ return showServerDetails(serverName, retryCount + 1);
62
+ }
63
+ throw new Error('Server data not found after retries');
64
+ }
65
+
66
+ detailsDiv.innerHTML = `
67
+ <h3 class="text-xl font-semibold mb-2 text-gray-800">${server.displayName || server.name}</h3>
68
+ <p class="mb-4"><span class="font-semibold">Description:</span> ${server.description || 'N/A'}</p>
69
+ <div id="toolMcpsList" class="mt-4">
70
+ <div class="animate-pulse flex space-x-4">
71
+ <div class="flex-1 space-y-4 py-1">
72
+ <div class="h-4 bg-gray-200 rounded w-3/4"></div>
73
+ <div class="space-y-2">
74
+ <div class="h-4 bg-gray-200 rounded"></div>
75
+ <div class="h-4 bg-gray-200 rounded w-5/6"></div>
76
+ </div>
68
77
  </div>
69
78
  </div>
70
79
  </div>
71
- </div>
72
- `;
80
+ `;
73
81
 
74
- // Asynchronously load and render the servers list
75
- try {
76
82
  const serversListHtml = await renderServersList(server);
77
- document.getElementById('toolMcpsList').innerHTML = serversListHtml;
83
+ const toolMcpsList = document.getElementById('toolMcpsList');
84
+ if (!toolMcpsList) {
85
+ throw new Error('toolMcpsList element not found');
86
+ }
87
+
88
+ toolMcpsList.innerHTML = serversListHtml;
89
+
90
+ // Setup server items click handlers
91
+ const serverItems = document.querySelectorAll('.server-item-content');
92
+ console.log(`Found ${serverItems.length} server items`);
93
+
94
+ serverItems.forEach(item => {
95
+ const mcpServerName = item.dataset.serverName;
96
+ console.log(`Setting up click handler for server: ${mcpServerName}`);
97
+
98
+ const mcpServer = server.feedConfiguration?.mcpServers?.find(
99
+ s => s.name === mcpServerName
100
+ );
101
+
102
+ if (mcpServer) {
103
+ const detailsWidget = new DetailsWidget(item);
104
+
105
+ // Initially set description
106
+ detailsWidget.setContent(mcpServer.description);
107
+
108
+ // Fetch and display schema when expanded
109
+ const fetchSchema = async () => {
110
+ try {
111
+ const response = await fetch(`/api/categories/${server.name}/servers/${mcpServerName}/schema`);
112
+ if (!response.ok) return;
113
+
114
+ const result = await response.json();
115
+ if (result.success && result.data) {
116
+ detailsWidget.setContent(result.data);
117
+ }
118
+ } catch (error) {
119
+ console.error('Error fetching schema:', error);
120
+ // Silently fail - don't show error in UI
121
+ }
122
+ };
123
+
124
+ item.addEventListener('click', async (event) => {
125
+ if (!event.target.closest('button')) {
126
+ if (detailsWidget === activeDetailsWidget) {
127
+ // Only toggle if clicking the currently active item
128
+ detailsWidget.toggle();
129
+ if (!detailsWidget.isVisible()) {
130
+ activeDetailsWidget = null;
131
+ }
132
+ } else {
133
+ // Expand the clicked item without collapsing others
134
+ detailsWidget.expand();
135
+ activeDetailsWidget = detailsWidget;
136
+ await fetchSchema();
137
+ }
138
+ }
139
+ });
140
+ }
141
+ });
142
+
143
+ startRefreshTimer(serverName);
78
144
  } catch (error) {
79
- console.error('Error rendering servers list:', error);
80
- document.getElementById('toolMcpsList').innerHTML = '<p class="text-red-500">Error loading server details.</p>';
145
+ console.error('Error in showServerDetails:', error);
146
+ const detailsDiv = document.getElementById('serverCategoryDetails');
147
+ if (detailsDiv) {
148
+ detailsDiv.innerHTML = `<p class="text-red-500">Error loading server details: ${error.message}</p>`;
149
+ }
81
150
  showToast(`Error loading server details: ${error.message}`, 'error');
82
151
  }
83
-
84
- // Start monitoring installation status
85
- startRefreshTimer(serverName);
86
152
  }
87
153
 
88
- // Render tools list
89
154
  async function renderServersList(serverCategory) {
90
- // Fetch available targets
91
- const targetResponse = await fetch('/api/targets');
92
- const targetData = await targetResponse.json();
93
- const availableTargets = targetData.success ? targetData.data : [];
94
- if (!serverCategory.feedConfiguration?.mcpServers) {
95
- return '<p class="text-gray-500">No MCP Servers found for this server.</p>';
96
- }
155
+ try {
156
+ const targetResponse = await fetch('/api/targets');
157
+ const targetData = await targetResponse.json();
158
+ const availableTargets = targetData.success ? targetData.data : [];
97
159
 
98
- const mcpServers = serverCategory.feedConfiguration.mcpServers;
99
- if (mcpServers.length === 0) {
100
- return '<p class="text-gray-500">No MCP Servers found for this server.</p>';
101
- }
160
+ if (!serverCategory.feedConfiguration?.mcpServers) {
161
+ return '<p class="text-gray-500">No MCP Servers found for this server.</p>';
162
+ }
163
+
164
+ const mcpServers = serverCategory.feedConfiguration.mcpServers;
165
+ if (mcpServers.length === 0) {
166
+ return '<p class="text-gray-500">No MCP Servers found for this server.</p>';
167
+ }
102
168
 
103
- let toolsHtml = `
104
- <div class="flex justify-between items-center mb-4">
105
- <h2 class="text-lg font-semibold text-gray-600">MCP Servers</h2>
106
- </div>`;
107
-
108
- mcpServers.forEach(mcpServer => {
109
- const isInstalled = mcpServer.installed;
110
- const envRequirements = mcpServer.installation?.['env:'] || mcpServer.installation?.env || {};
111
- const hasEnvRequirements = Object.keys(envRequirements).length > 0;
112
-
113
- toolsHtml += `
114
- <div class="border border-gray-200 p-3 rounded mb-2 flex justify-between items-center hover:bg-gray-50 transition-all duration-150 ${!isInstalled ? 'bg-white' : 'bg-gray-50'}"
115
- id="tool-item-${mcpServer.name}">
116
- <div class="flex items-center flex-1">
117
- <div>
118
- <h5 class="font-semibold text-gray-800">${mcpServer.displayName || mcpServer.name}</h5>
119
- <p class="text-sm text-gray-600 mb-1">${mcpServer.description || 'No description'}</p>
120
- <div class="flex flex-col">
169
+ let toolsHtml = `
170
+ <div class="flex justify-between items-center mb-4">
171
+ <h2 class="text-lg font-semibold text-gray-600">MCP Servers</h2>
172
+ </div>`;
173
+
174
+ mcpServers.forEach(mcpServer => {
175
+ const isInstalled = mcpServer.installed;
176
+ const envRequirements = mcpServer.installation?.['env:'] || mcpServer.installation?.env || {};
177
+ const hasEnvRequirements = Object.keys(envRequirements).length > 0;
178
+
179
+ toolsHtml += `
180
+ <div class="server-item-content" data-server-name="${mcpServer.name}">
181
+ <div class="server-item-info" style="width: 100%; box-sizing: border-box;">
182
+ <div class="server-item-header">
183
+ <div class="flex items-center">
184
+ <h5 class="font-semibold text-gray-800">${mcpServer.displayName || mcpServer.name}</h5>
185
+ ${mcpServer.repository || serverCategory.feedConfiguration?.repository ? (() => {
186
+ const repoUrl = mcpServer.repository || serverCategory.feedConfiguration?.repository;
187
+ const isGithub = repoUrl.toLowerCase().includes('github.com');
188
+ return `
189
+ <a href="${repoUrl}" target="_blank" class="ml-2 flex items-center">
190
+ <span class="text-xs px-2 py-1 bg-gray-100 rounded-md flex items-center text-gray-700 hover:bg-gray-200 transition-colors duration-200">
191
+ ${isGithub ? `
192
+ <svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
193
+ <path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd"/>
194
+ </svg>
195
+ GitHub` : `
196
+ <svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
197
+ <path d="M21.721 12.752a9.711 9.711 0 00-.945-5.003 1.743 1.743 0 01-1.339-1.647c0-.522.236-1.01.62-1.341a9.707 9.707 0 00-3.62-2.087 1.744 1.744 0 01-2.113-1.334A9.721 9.721 0 0012 1.016 9.721 9.721 0 009.676 1.3 1.744 1.744 0 017.562 2.634a9.707 9.707 0 00-3.62 2.087 1.744 1.744 0 00-.048 2.32 9.711 9.711 0 00-.945 5.003 9.712 9.712 0 00.945 5.003 1.744 1.744 0 01.048 2.32 9.707 9.707 0 003.62 2.087 1.744 1.744 0 012.114 1.334A9.721 9.721 0 0012 22.982a9.721 9.721 0 002.324-.284 1.744 1.744 0 012.114-1.334 9.707 9.707 0 003.62-2.087 1.744 1.744 0 01.048-2.32 9.711 9.711 0 00.945-5.003z"/>
198
+ </svg>
199
+ Website`}
200
+ </span>
201
+ </a>`
202
+ })() : ''}
203
+ </div>
204
+ <p class="text-sm text-gray-600 mb-1">${mcpServer.description || 'No description'}</p>
205
+ </div>
206
+ <div class="flex flex-col mt-3">
121
207
  <span class="text-xs font-semibold mb-2">Client Status:</span>
122
208
  <div class="flex flex-wrap gap-2">
123
209
  ${availableTargets.map(client => {
124
- const status = (serverCategory.installationStatus?.serversStatus[mcpServer.name]?.installedStatus || {})[client];
125
- const isInstalled = status?.status === 'completed';
126
- return `
210
+ const status = (serverCategory.installationStatus?.serversStatus[mcpServer.name]?.installedStatus || {})[client];
211
+ const isInstalled = status?.status === 'completed';
212
+ return `
127
213
  <span class="text-xs flex items-center ${isInstalled ? 'text-green-600 bg-green-50' : 'text-gray-600 bg-gray-50'} px-3 py-1 rounded-full shadow">
128
214
  <svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
129
215
  ${isInstalled
130
- ? '<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path>'
131
- : '<path d="M7 3.5A1.5 1.5 0 018.5 2h3.879a1.5 1.5 0 011.06.44l3.122 3.12A1.5 1.5 0 0117 6.622V12.5a1.5 1.5 0 01-1.5 1.5h-1v-3.379a1.5 1.5 0 00-.44-1.06L10.94 6.44A1.5 1.5 0 009.879 6H7V3.5z M6 6h2.879A1.5 1.5 0 0110 7.5v1.379a1.5 1.5 0 01-.44 1.06L6.44 13.06A1.5 1.5 0 015.379 13H4.5A1.5 1.5 0 013 11.5V7.5A1.5 1.5 0 014.5 6H6z"></path>'
132
- }
216
+ ? '<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path>'
217
+ : '<path d="M7 3.5A1.5 1.5 0 018.5 2h3.879a1.5 1.5 0 011.06.44l3.122 3.12A1.5 1.5 0 0117 6.622V12.5a1.5 1.5 0 01-1.5 1.5h-1v-3.379a1.5 1.5 0 00-.44-1.06L10.94 6.44A1.5 1.5 0 009.879 6H7V3.5z M6 6h2.879A1.5 1.5 0 0110 7.5v1.379a1.5 1.5 0 01-.44 1.06L6.44 13.06A1.5 1.5 0 015.379 13H4.5A1.5 1.5 0 013 11.5V7.5A1.5 1.5 0 014.5 6H6z"></path>'
218
+ }
133
219
  </svg>
134
220
  ${client}
135
221
  </span>
136
222
  `;
137
- }).join('')}
223
+ }).join('')}
138
224
  </div>
139
225
  </div>
140
- ${hasEnvRequirements ? `<div class="mt-1 text-xs text-blue-600">
226
+ <div class="action-buttons">
227
+ ${!isInstalled ? `
228
+ <button onclick="event.stopPropagation(); window.showInstallModal('${serverCategory.name}', '${mcpServer.name}')"
229
+ class="bg-blue-500 hover:bg-blue-700 text-white text-xs font-bold py-2 px-4 rounded-full shadow-sm transition duration-150 ease-in-out">
230
+ Setup
231
+ </button>
232
+ ` : `
233
+ <button onclick="event.stopPropagation(); window.uninstallTools('${serverCategory.name}', ['${mcpServer.name}'])"
234
+ class="bg-red-500 hover:bg-red-700 text-white text-sm font-bold py-2 px-4 rounded-full shadow-sm transition duration-150 ease-in-out">
235
+ Uninstall
236
+ </button>
237
+ `}
238
+ </div>
239
+ ${hasEnvRequirements ? `<div class="mt-3 text-xs text-blue-600">
141
240
  <svg class="w-3 h-3 inline mr-1" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
142
241
  <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path>
143
242
  </svg>
@@ -145,34 +244,21 @@ async function renderServersList(serverCategory) {
145
244
  </div>` : ''}
146
245
  </div>
147
246
  </div>
148
- ${!isInstalled ? `
149
- <button onclick="event.stopPropagation(); window.showInstallModal('${serverCategory.name}', '${mcpServer.name}')"
150
- class="bg-blue-500 hover:bg-blue-700 text-white text-sm font-bold py-1 px-3 rounded-full shadow-sm transition duration-150 ease-in-out">
151
- Install
152
- </button>
153
- ` : `
154
- <div class="flex items-center gap-2">
155
- <button onclick="event.stopPropagation(); window.uninstallTools('${serverCategory.name}', ['${mcpServer.name}'])"
156
- class="bg-red-500 hover:bg-red-700 text-white text-sm font-bold py-1 px-3 rounded transition duration-150 ease-in-out">
157
- Uninstall
158
- </button>
159
- </div>
160
- `}
161
- </div>
162
- `;
163
- });
247
+ `;
248
+ });
164
249
 
165
- return toolsHtml;
250
+ return toolsHtml;
251
+ } catch (error) {
252
+ console.error('Error in renderServersList:', error);
253
+ throw error;
254
+ }
166
255
  }
167
256
 
168
- // Uninstall tools
169
257
  window.uninstallTools = async function (serverName, toolList) {
170
- const confirmed = await showConfirm(`Are you sure you want to uninstall ${toolList.length} tool(s)?`);
171
- if (!confirmed) {
172
- return;
173
- }
174
-
175
258
  try {
259
+ const confirmed = await showConfirm(`Are you sure you want to uninstall ${toolList.length} tool(s)?`);
260
+ if (!confirmed) return;
261
+
176
262
  const response = await fetch(`/api/categories/${serverName}/uninstall`, {
177
263
  method: 'POST',
178
264
  headers: { 'Content-Type': 'application/json' },
@@ -190,23 +276,25 @@ window.uninstallTools = async function (serverName, toolList) {
190
276
  }
191
277
 
192
278
  showToast('Tools uninstalled successfully!', 'success');
193
- await fetchServerCategories(); // Refresh data
194
- await showServerDetails(serverName); // Refresh UI
279
+ await fetchServerCategories();
280
+ await showServerDetails(serverName);
195
281
  } catch (error) {
196
282
  console.error('Error uninstalling tools:', error);
197
283
  showToast(`Error uninstalling tools: ${error.message}`, 'error');
198
284
  }
199
285
  }
200
286
 
201
- // Make showServerDetails available in window scope and handle async
202
287
  window.showServerDetails = async function (serverName) {
203
288
  try {
204
289
  await showServerDetails(serverName);
205
290
  } catch (error) {
206
- console.error('Error showing server details:', error);
207
- document.getElementById('serverCategoryDetails').innerHTML = '<p class="text-red-500">Error loading server details.</p>';
291
+ console.error('Error in window.showServerDetails:', error);
292
+ const detailsDiv = document.getElementById('serverCategoryDetails');
293
+ if (detailsDiv) {
294
+ detailsDiv.innerHTML = `<p class="text-red-500">Error loading server details: ${error.message}</p>`;
295
+ }
208
296
  showToast(`Error loading server details: ${error.message}`, 'error');
209
297
  }
210
298
  };
211
299
 
212
- export { showServerDetails };
300
+ export { showServerDetails };
package/src/web/server.ts CHANGED
@@ -1,6 +1,8 @@
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';
5
+
4
6
  import { SUPPORTED_CLIENT_NAMES } from '../core/constants.js';
5
7
  import { serverService } from '../services/ServerService.js';
6
8
  import { openBrowser } from '../utils/osUtils.js';
@@ -90,6 +92,35 @@ app.get('/api/categories', async (req: Request<{}, {}, {}, ListQueryParams>, res
90
92
  error: message
91
93
  });
92
94
  }
95
+
96
+ // Get server schema
97
+ app.get('/api/categories/:categoryName/servers/:serverName/schema', async (req: Request<{ categoryName: string; serverName: string }>, res: Response) => {
98
+ try {
99
+ const { categoryName, serverName } = req.params;
100
+ const schema = await serverService.getServerSchema(categoryName, serverName);
101
+
102
+ if (!schema) {
103
+ return res.status(404).json({
104
+ success: false,
105
+ error: `Schema not found for server ${serverName} in category ${categoryName}`
106
+ });
107
+ }
108
+
109
+ const response: ApiResponse<ServerSchema> = {
110
+ success: true,
111
+ data: schema
112
+ };
113
+
114
+ res.json(response);
115
+ } catch (error) {
116
+ const message = error instanceof Error ? error.message : 'Unknown error';
117
+ res.status(500).json({
118
+ success: false,
119
+ error: `Failed to get schema for server ${req.params.serverName} in category ${req.params.categoryName}: ${message}`
120
+ });
121
+ }
122
+ });
123
+
93
124
  });
94
125
 
95
126
  // Get categories data (including feed configuration)