@softtechai/quickmcp 1.0.8

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 (163) hide show
  1. package/README.md +553 -0
  2. package/dist/client/MCPClient.d.ts +24 -0
  3. package/dist/client/MCPClient.d.ts.map +1 -0
  4. package/dist/client/MCPClient.js +211 -0
  5. package/dist/client/MCPClient.js.map +1 -0
  6. package/dist/client/MCPClientUnified.d.ts +31 -0
  7. package/dist/client/MCPClientUnified.d.ts.map +1 -0
  8. package/dist/client/MCPClientUnified.js +275 -0
  9. package/dist/client/MCPClientUnified.js.map +1 -0
  10. package/dist/client/MCPTestRunner.d.ts +44 -0
  11. package/dist/client/MCPTestRunner.d.ts.map +1 -0
  12. package/dist/client/MCPTestRunner.js +220 -0
  13. package/dist/client/MCPTestRunner.js.map +1 -0
  14. package/dist/client/MCPTestRunnerUnified.d.ts +48 -0
  15. package/dist/client/MCPTestRunnerUnified.d.ts.map +1 -0
  16. package/dist/client/MCPTestRunnerUnified.js +183 -0
  17. package/dist/client/MCPTestRunnerUnified.js.map +1 -0
  18. package/dist/database/json-manager.d.ts +55 -0
  19. package/dist/database/json-manager.d.ts.map +1 -0
  20. package/dist/database/json-manager.js +128 -0
  21. package/dist/database/json-manager.js.map +1 -0
  22. package/dist/database/sqlite-manager.d.ts +53 -0
  23. package/dist/database/sqlite-manager.d.ts.map +1 -0
  24. package/dist/database/sqlite-manager.js +193 -0
  25. package/dist/database/sqlite-manager.js.map +1 -0
  26. package/dist/dynamic-mcp-executor.d.ts +14 -0
  27. package/dist/dynamic-mcp-executor.d.ts.map +1 -0
  28. package/dist/dynamic-mcp-executor.js +274 -0
  29. package/dist/dynamic-mcp-executor.js.map +1 -0
  30. package/dist/generators/MCPServerGenerator-new.d.ts +37 -0
  31. package/dist/generators/MCPServerGenerator-new.d.ts.map +1 -0
  32. package/dist/generators/MCPServerGenerator-new.js +287 -0
  33. package/dist/generators/MCPServerGenerator-new.js.map +1 -0
  34. package/dist/generators/MCPServerGenerator.d.ts +42 -0
  35. package/dist/generators/MCPServerGenerator.d.ts.map +1 -0
  36. package/dist/generators/MCPServerGenerator.js +494 -0
  37. package/dist/generators/MCPServerGenerator.js.map +1 -0
  38. package/dist/generators/database/sqlite-manager.d.ts +52 -0
  39. package/dist/generators/database/sqlite-manager.js +143 -0
  40. package/dist/generators/generators/MCPServerGenerator.d.ts +37 -0
  41. package/dist/generators/generators/MCPServerGenerator.js +396 -0
  42. package/dist/index.d.ts +7 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +23 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/integrated-mcp-server-new.d.ts +12 -0
  47. package/dist/integrated-mcp-server-new.d.ts.map +1 -0
  48. package/dist/integrated-mcp-server-new.js +253 -0
  49. package/dist/integrated-mcp-server-new.js.map +1 -0
  50. package/dist/integrated-mcp-server.d.ts +25 -0
  51. package/dist/integrated-mcp-server.d.ts.map +1 -0
  52. package/dist/integrated-mcp-server.js +541 -0
  53. package/dist/integrated-mcp-server.js.map +1 -0
  54. package/dist/mcp-inspector-server.d.ts +3 -0
  55. package/dist/mcp-inspector-server.d.ts.map +1 -0
  56. package/dist/mcp-inspector-server.js +119 -0
  57. package/dist/mcp-inspector-server.js.map +1 -0
  58. package/dist/mcp-sdk-server.d.ts +3 -0
  59. package/dist/mcp-sdk-server.d.ts.map +1 -0
  60. package/dist/mcp-sdk-server.js +90 -0
  61. package/dist/mcp-sdk-server.js.map +1 -0
  62. package/dist/mcp-server.d.ts +3 -0
  63. package/dist/mcp-server.d.ts.map +1 -0
  64. package/dist/mcp-server.js +300 -0
  65. package/dist/mcp-server.js.map +1 -0
  66. package/dist/parsers/CsvParser.d.ts +7 -0
  67. package/dist/parsers/CsvParser.d.ts.map +1 -0
  68. package/dist/parsers/CsvParser.js +98 -0
  69. package/dist/parsers/CsvParser.js.map +1 -0
  70. package/dist/parsers/DatabaseParser.d.ts +18 -0
  71. package/dist/parsers/DatabaseParser.d.ts.map +1 -0
  72. package/dist/parsers/DatabaseParser.js +372 -0
  73. package/dist/parsers/DatabaseParser.js.map +1 -0
  74. package/dist/parsers/ExcelParser.d.ts +8 -0
  75. package/dist/parsers/ExcelParser.d.ts.map +1 -0
  76. package/dist/parsers/ExcelParser.js +119 -0
  77. package/dist/parsers/ExcelParser.js.map +1 -0
  78. package/dist/parsers/index.d.ts +13 -0
  79. package/dist/parsers/index.d.ts.map +1 -0
  80. package/dist/parsers/index.js +88 -0
  81. package/dist/parsers/index.js.map +1 -0
  82. package/dist/parsers/parsers/ExcelParser.js +118 -0
  83. package/dist/parsers/types/index.js +2 -0
  84. package/dist/quickmcp-unified-bridge.d.ts +13 -0
  85. package/dist/quickmcp-unified-bridge.d.ts.map +1 -0
  86. package/dist/quickmcp-unified-bridge.js +176 -0
  87. package/dist/quickmcp-unified-bridge.js.map +1 -0
  88. package/dist/server/ServerManager.d.ts +37 -0
  89. package/dist/server/ServerManager.d.ts.map +1 -0
  90. package/dist/server/ServerManager.js +376 -0
  91. package/dist/server/ServerManager.js.map +1 -0
  92. package/dist/sqlite-manager.js +145 -0
  93. package/dist/start-new-server.d.ts +3 -0
  94. package/dist/start-new-server.d.ts.map +1 -0
  95. package/dist/start-new-server.js +10 -0
  96. package/dist/start-new-server.js.map +1 -0
  97. package/dist/test-app.d.ts +2 -0
  98. package/dist/test-app.d.ts.map +1 -0
  99. package/dist/test-app.js +119 -0
  100. package/dist/test-app.js.map +1 -0
  101. package/dist/test-new-architecture.d.ts +3 -0
  102. package/dist/test-new-architecture.d.ts.map +1 -0
  103. package/dist/test-new-architecture.js +72 -0
  104. package/dist/test-new-architecture.js.map +1 -0
  105. package/dist/transport/base-transport.d.ts +21 -0
  106. package/dist/transport/base-transport.d.ts.map +1 -0
  107. package/dist/transport/base-transport.js +16 -0
  108. package/dist/transport/base-transport.js.map +1 -0
  109. package/dist/transport/index.d.ts +10 -0
  110. package/dist/transport/index.d.ts.map +1 -0
  111. package/dist/transport/index.js +12 -0
  112. package/dist/transport/index.js.map +1 -0
  113. package/dist/transport/sse-transport.d.ts +13 -0
  114. package/dist/transport/sse-transport.d.ts.map +1 -0
  115. package/dist/transport/sse-transport.js +106 -0
  116. package/dist/transport/sse-transport.js.map +1 -0
  117. package/dist/transport/stdio-transport.d.ts +8 -0
  118. package/dist/transport/stdio-transport.d.ts.map +1 -0
  119. package/dist/transport/stdio-transport.js +53 -0
  120. package/dist/transport/stdio-transport.js.map +1 -0
  121. package/dist/transport/streamable-http-transport.d.ts +15 -0
  122. package/dist/transport/streamable-http-transport.d.ts.map +1 -0
  123. package/dist/transport/streamable-http-transport.js +151 -0
  124. package/dist/transport/streamable-http-transport.js.map +1 -0
  125. package/dist/types/index.d.ts +64 -0
  126. package/dist/types/index.d.ts.map +1 -0
  127. package/dist/types/index.js +3 -0
  128. package/dist/types/index.js.map +1 -0
  129. package/dist/web/client/MCPClient.js +348 -0
  130. package/dist/web/client/MCPTestRunner.js +317 -0
  131. package/dist/web/database/json-manager.js +124 -0
  132. package/dist/web/database/sqlite-manager.js +146 -0
  133. package/dist/web/dynamic-mcp-executor.js +443 -0
  134. package/dist/web/generators/MCPServerGenerator-new.js +284 -0
  135. package/dist/web/generators/MCPServerGenerator.js +566 -0
  136. package/dist/web/integrated-mcp-server-new.js +394 -0
  137. package/dist/web/parsers/CsvParser.js +144 -0
  138. package/dist/web/parsers/DatabaseParser.js +637 -0
  139. package/dist/web/parsers/ExcelParser.js +180 -0
  140. package/dist/web/parsers/index.js +152 -0
  141. package/dist/web/server.d.ts +3 -0
  142. package/dist/web/server.d.ts.map +1 -0
  143. package/dist/web/server.js +790 -0
  144. package/dist/web/server.js.map +1 -0
  145. package/dist/web/types/index.js +2 -0
  146. package/dist/web/web/server.js +860 -0
  147. package/package.json +68 -0
  148. package/quickmcp-direct-stdio.js +328 -0
  149. package/src/web/public/app.js +1795 -0
  150. package/src/web/public/database-tables.html +711 -0
  151. package/src/web/public/how-to-use.html +571 -0
  152. package/src/web/public/how-to-use.js +255 -0
  153. package/src/web/public/images/1-claude-quickmcp-stdio.png +0 -0
  154. package/src/web/public/images/2-claude-tools.png +0 -0
  155. package/src/web/public/images/3-claude-developer-settings.png +0 -0
  156. package/src/web/public/images/4-claude-config.png +0 -0
  157. package/src/web/public/images/5-claude-config-edit.png +0 -0
  158. package/src/web/public/index.html +626 -0
  159. package/src/web/public/manage-servers.html +198 -0
  160. package/src/web/public/modern-styles.css +946 -0
  161. package/src/web/public/shared-styles.css +2091 -0
  162. package/src/web/public/shared.js +93 -0
  163. package/src/web/public/test-servers.html +302 -0
@@ -0,0 +1,1795 @@
1
+ let currentParsedData = null;
2
+ let currentDataSource = null;
3
+ let currentWizardStep = 1;
4
+
5
+ // Initialize the app
6
+ document.addEventListener('DOMContentLoaded', function() {
7
+ setupEventListeners();
8
+ setupFileUpload();
9
+ setupRouting();
10
+ handleInitialRoute();
11
+ });
12
+
13
+ // Setup event listeners
14
+ function setupEventListeners() {
15
+ // Sidebar controls
16
+ document.getElementById('openSidebar')?.addEventListener('click', openSidebar);
17
+ document.getElementById('closeSidebar')?.addEventListener('click', closeSidebar);
18
+ document.getElementById('sidebarOverlay')?.addEventListener('click', closeSidebar);
19
+
20
+ // Navigation
21
+ document.querySelectorAll('.nav-item').forEach(item => {
22
+ item.addEventListener('click', (e) => {
23
+ const tabName = item.getAttribute('data-tab');
24
+ // Only prevent default for tab navigation, allow normal URL navigation
25
+ if (tabName) {
26
+ e.preventDefault();
27
+ switchTab(tabName);
28
+ }
29
+ // If no data-tab, allow normal link navigation to href
30
+ });
31
+ });
32
+
33
+ // Data source type selection
34
+ document.querySelectorAll('input[name="dataSourceType"]').forEach(radio => {
35
+ radio.addEventListener('change', toggleDataSourceFields);
36
+ });
37
+
38
+ // Database type change
39
+ document.getElementById('dbType')?.addEventListener('change', updateDefaultPort);
40
+
41
+ // Database field changes to update navigation
42
+ document.getElementById('dbType')?.addEventListener('change', updateWizardNavigation);
43
+ document.getElementById('dbHost')?.addEventListener('input', updateWizardNavigation);
44
+ document.getElementById('dbPort')?.addEventListener('input', updateWizardNavigation);
45
+ document.getElementById('dbName')?.addEventListener('input', updateWizardNavigation);
46
+ document.getElementById('dbUser')?.addEventListener('input', updateWizardNavigation);
47
+ document.getElementById('dbPassword')?.addEventListener('input', updateWizardNavigation);
48
+
49
+
50
+ // Generate button
51
+ document.getElementById('generateBtn')?.addEventListener('click', generateServer);
52
+
53
+ // Server name validation
54
+ document.getElementById('serverName')?.addEventListener('input', checkServerName);
55
+
56
+ // Test buttons
57
+ document.getElementById('runQuickTestBtn')?.addEventListener('click', () => runAutoTests(false));
58
+ document.getElementById('runFullTestBtn')?.addEventListener('click', () => runAutoTests(true));
59
+ document.getElementById('runAutoTestsBtn')?.addEventListener('click', runAutoTests);
60
+ document.getElementById('runCustomTestBtn')?.addEventListener('click', runCustomTest);
61
+
62
+ // Test type change handler
63
+ document.getElementById('testType')?.addEventListener('change', handleTestTypeChange);
64
+
65
+ // Test server select change handler for loading tools
66
+ document.getElementById('testServerSelect')?.addEventListener('change', handleTestServerChange);
67
+
68
+ // Wizard navigation
69
+ document.getElementById('next-to-step-2')?.addEventListener('click', handleNextToStep2);
70
+ document.getElementById('back-to-step-1')?.addEventListener('click', () => goToWizardStep(1));
71
+ document.getElementById('next-to-step-3')?.addEventListener('click', () => goToWizardStep(3));
72
+ document.getElementById('back-to-step-2')?.addEventListener('click', () => goToWizardStep(2));
73
+ }
74
+
75
+ // Sidebar functions
76
+ function openSidebar() {
77
+ const sidebar = document.getElementById('sidebar');
78
+ const overlay = document.getElementById('sidebarOverlay');
79
+
80
+ sidebar.classList.remove('-translate-x-full');
81
+ overlay.classList.remove('opacity-0', 'invisible');
82
+ }
83
+
84
+ function closeSidebar() {
85
+ const sidebar = document.getElementById('sidebar');
86
+ const overlay = document.getElementById('sidebarOverlay');
87
+
88
+ sidebar.classList.add('-translate-x-full');
89
+ overlay.classList.add('opacity-0', 'invisible');
90
+ }
91
+
92
+ // Setup URL routing
93
+ function setupRouting() {
94
+ window.addEventListener('popstate', function(event) {
95
+ handleRoute();
96
+ });
97
+ }
98
+
99
+ // Handle initial route
100
+ function handleInitialRoute() {
101
+ handleRoute();
102
+ }
103
+
104
+ // Route handler
105
+ function handleRoute() {
106
+ const path = window.location.pathname;
107
+ let tabName = 'generate'; // default tab
108
+
109
+ if (path === '/manage-servers') {
110
+ tabName = 'manage';
111
+ } else if (path === '/test-servers') {
112
+ tabName = 'test';
113
+ }
114
+
115
+ switchTabByRoute(tabName);
116
+ }
117
+
118
+ // Switch tab and update URL
119
+ function switchTabByRoute(tabName) {
120
+ // Hide all tab contents
121
+ const tabs = document.querySelectorAll('.tab-content');
122
+ tabs.forEach(tab => tab.classList.add('hidden'));
123
+
124
+ // Remove active state from all nav items
125
+ const navItems = document.querySelectorAll('.nav-item');
126
+ navItems.forEach(item => {
127
+ item.classList.remove('active', 'bg-gradient-to-r', 'from-blue-50', 'to-cyan-50', 'text-blue-700', 'border', 'border-blue-200');
128
+ item.classList.add('text-gray-700', 'hover:bg-gray-50');
129
+ });
130
+
131
+ // Show selected tab
132
+ const selectedTab = document.getElementById(`${tabName}-tab`);
133
+ if (selectedTab) {
134
+ selectedTab.classList.remove('hidden');
135
+ }
136
+
137
+ // Set active nav item
138
+ const activeItem = document.querySelector(`[data-tab="${tabName}"]`);
139
+ if (activeItem) {
140
+ activeItem.classList.remove('text-gray-700', 'hover:bg-gray-50');
141
+ activeItem.classList.add('active', 'bg-gradient-to-r', 'from-blue-50', 'to-cyan-50', 'text-blue-700', 'border', 'border-blue-200');
142
+ }
143
+
144
+ // Update page title
145
+ const pageTitle = document.getElementById('pageTitle');
146
+ const pageSubtitle = pageTitle?.nextElementSibling;
147
+
148
+ if (pageTitle) {
149
+ switch(tabName) {
150
+ case 'generate':
151
+ pageTitle.textContent = 'Generate Server';
152
+ if (pageSubtitle) pageSubtitle.textContent = 'Create powerful MCP servers from your data';
153
+ break;
154
+ case 'manage':
155
+ pageTitle.textContent = 'Manage Servers';
156
+ if (pageSubtitle) pageSubtitle.textContent = 'Manage and deploy your created MCP servers';
157
+ break;
158
+ case 'test':
159
+ pageTitle.textContent = 'Test Servers';
160
+ if (pageSubtitle) pageSubtitle.textContent = 'Run automated tests or create custom test scenarios';
161
+ break;
162
+ }
163
+ }
164
+
165
+ // Load data for specific tabs
166
+ if (tabName === 'manage') {
167
+ loadServers();
168
+ } else if (tabName === 'test') {
169
+ loadTestServers();
170
+ }
171
+ }
172
+
173
+ // Tab management with URL update
174
+ function switchTab(tabName) {
175
+ // Update URL
176
+ let newPath = '/';
177
+ if (tabName === 'manage') {
178
+ newPath = '/manage-servers';
179
+ } else if (tabName === 'test') {
180
+ newPath = '/test-servers';
181
+ }
182
+
183
+ // Update URL without triggering popstate
184
+ window.history.pushState(null, '', newPath);
185
+
186
+ // Switch the tab
187
+ switchTabByRoute(tabName);
188
+
189
+ // Close sidebar on mobile
190
+ if (window.innerWidth < 1024) {
191
+ closeSidebar();
192
+ }
193
+ }
194
+
195
+ // File upload setup
196
+ function setupFileUpload() {
197
+ const fileUpload = document.getElementById('fileUpload');
198
+ const fileInput = document.getElementById('fileInput');
199
+
200
+ if (!fileUpload || !fileInput) return;
201
+
202
+ fileUpload.addEventListener('click', () => fileInput.click());
203
+
204
+ fileUpload.addEventListener('dragover', (e) => {
205
+ e.preventDefault();
206
+ fileUpload.classList.add('border-blue-400', 'bg-blue-50');
207
+ });
208
+
209
+ fileUpload.addEventListener('dragleave', () => {
210
+ fileUpload.classList.remove('border-blue-400', 'bg-blue-50');
211
+ });
212
+
213
+ fileUpload.addEventListener('drop', (e) => {
214
+ e.preventDefault();
215
+ fileUpload.classList.remove('border-blue-400', 'bg-blue-50');
216
+
217
+ const files = e.dataTransfer.files;
218
+ if (files.length > 0) {
219
+ fileInput.files = files;
220
+ updateFileUploadDisplay(files[0].name);
221
+ }
222
+ });
223
+
224
+ fileInput.addEventListener('change', (e) => {
225
+ if (e.target.files.length > 0) {
226
+ updateFileUploadDisplay(e.target.files[0].name);
227
+ }
228
+ });
229
+ }
230
+
231
+ function updateFileUploadDisplay(fileName) {
232
+ const fileUpload = document.getElementById('fileUpload');
233
+ if (fileUpload) {
234
+ fileUpload.innerHTML = `
235
+ <div class="space-y-4">
236
+ <div class="w-16 h-16 mx-auto bg-green-50 rounded-xl flex items-center justify-center">
237
+ <i class="fas fa-check text-2xl text-green-500"></i>
238
+ </div>
239
+ <div>
240
+ <p class="text-lg font-medium text-gray-900">File Selected</p>
241
+ <p class="text-sm text-gray-500">${fileName}</p>
242
+ </div>
243
+ <button type="button" onclick="resetFileUpload()" class="text-xs text-blue-500 hover:text-blue-600">Choose different file</button>
244
+ </div>
245
+ `;
246
+ }
247
+ }
248
+
249
+ function resetFileUpload() {
250
+ const fileUpload = document.getElementById('fileUpload');
251
+ const fileInput = document.getElementById('fileInput');
252
+
253
+ if (fileInput) fileInput.value = '';
254
+
255
+ if (fileUpload) {
256
+ fileUpload.innerHTML = `
257
+ <div class="space-y-4">
258
+ <div class="w-16 h-16 mx-auto bg-blue-50 rounded-xl flex items-center justify-center group-hover:bg-blue-100 transition-colors">
259
+ <i class="fas fa-cloud-upload-alt text-2xl text-blue-500"></i>
260
+ </div>
261
+ <div>
262
+ <p class="text-lg font-medium text-gray-900">Drop your file here</p>
263
+ <p class="text-sm text-gray-500">or click to browse files</p>
264
+ </div>
265
+ <p class="text-xs text-gray-400">Supports CSV, Excel files up to 10MB</p>
266
+ </div>
267
+ `;
268
+ }
269
+ }
270
+
271
+
272
+ // Update default port based on database type
273
+ function updateDefaultPort() {
274
+ const dbType = document.getElementById('dbType')?.value;
275
+ const dbPort = document.getElementById('dbPort');
276
+
277
+ const defaultPorts = {
278
+ 'mysql': 3306,
279
+ 'postgresql': 5432,
280
+ 'sqlite': '',
281
+ 'mssql': 1433
282
+ };
283
+
284
+ if (dbPort) {
285
+ dbPort.placeholder = defaultPorts[dbType] || '';
286
+ if (!dbPort.value && defaultPorts[dbType]) {
287
+ dbPort.value = defaultPorts[dbType];
288
+ }
289
+ }
290
+ }
291
+
292
+
293
+ // Display data preview
294
+ function displayDataPreview(parsedData) {
295
+ const preview = document.getElementById('data-preview');
296
+ if (!preview) return;
297
+
298
+ let html = '<div class="space-y-4">';
299
+
300
+ // Header with instructions
301
+ html += `
302
+ <div class="bg-blue-50 border border-blue-200 rounded-xl p-4 mb-6">
303
+ <div class="flex items-start space-x-3">
304
+ <i class="fas fa-info-circle text-blue-500 mt-1"></i>
305
+ <div>
306
+ <h3 class="font-semibold text-blue-900 mb-1">Configure Your MCP Server</h3>
307
+ <p class="text-blue-800 text-sm">Select which tables to include and choose which tools to generate for each table. All tools are enabled by default.</p>
308
+ </div>
309
+ </div>
310
+ </div>
311
+ `;
312
+
313
+ parsedData.forEach((data, index) => {
314
+ const tableName = data.tableName || `Table ${index + 1}`;
315
+ const panelId = `table-panel-${index}`;
316
+ const cleanTableName = tableName.replace(/[^a-zA-Z0-9]/g, '_');
317
+
318
+ // Check if table has numeric columns
319
+ const numericColumns = data.headers.filter(header => {
320
+ const dataType = data.metadata.dataTypes[header]?.toLowerCase() || '';
321
+ return dataType.includes('int') || dataType.includes('float') || dataType.includes('decimal') ||
322
+ dataType.includes('numeric') || dataType.includes('real') || dataType.includes('double') ||
323
+ dataType === 'number';
324
+ });
325
+
326
+ html += `
327
+ <div class="bg-white rounded-xl border-2 border-gray-200 shadow-sm overflow-hidden mb-4 table-selection-panel">
328
+ <!-- Table Header with Selection -->
329
+ <div class="bg-gradient-to-r from-gray-50 to-gray-100 p-4 border-b border-gray-200">
330
+ <div class="flex items-center justify-between">
331
+ <div class="flex items-center space-x-3">
332
+ <label class="flex items-center space-x-2 cursor-pointer">
333
+ <input type="checkbox"
334
+ id="table-select-${index}"
335
+ class="w-5 h-5 text-blue-600 border-2 border-gray-300 rounded focus:ring-2 focus:ring-blue-500"
336
+ checked
337
+ onchange="toggleTableSelection(${index})">
338
+ <div>
339
+ <h4 class="font-semibold text-gray-900 text-lg">${tableName}</h4>
340
+ <p class="text-sm text-gray-600">${data.metadata.rowCount} rows, ${data.metadata.columnCount} columns</p>
341
+ </div>
342
+ </label>
343
+ </div>
344
+ <button class="text-gray-400 hover:text-gray-600 transition-colors" onclick="toggleTableDetails('${panelId}')">
345
+ <i id="${panelId}-icon" class="fas fa-chevron-down transition-transform"></i>
346
+ </button>
347
+ </div>
348
+ </div>
349
+
350
+ <!-- Tool Selection Panel -->
351
+ <div id="table-tools-${index}" class="bg-blue-50 p-4 border-b border-gray-200">
352
+ <h5 class="font-medium text-gray-900 mb-3">
353
+ <i class="fas fa-tools mr-2 text-blue-500"></i>
354
+ Select Tools to Generate
355
+ </h5>
356
+ <div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-3">
357
+ <!-- Basic CRUD Tools -->
358
+ <label class="flex items-center space-x-2 cursor-pointer p-2 rounded-lg hover:bg-white transition-colors">
359
+ <input type="checkbox"
360
+ id="tool-get-${index}"
361
+ class="w-4 h-4 text-blue-600 border border-gray-300 rounded focus:ring-2 focus:ring-blue-500"
362
+ checked>
363
+ <span class="text-sm font-medium text-gray-700">GET</span>
364
+ </label>
365
+
366
+ <label class="flex items-center space-x-2 cursor-pointer p-2 rounded-lg hover:bg-white transition-colors">
367
+ <input type="checkbox"
368
+ id="tool-create-${index}"
369
+ class="w-4 h-4 text-green-600 border border-gray-300 rounded focus:ring-2 focus:ring-green-500"
370
+ checked>
371
+ <span class="text-sm font-medium text-gray-700">CREATE</span>
372
+ </label>
373
+
374
+ <label class="flex items-center space-x-2 cursor-pointer p-2 rounded-lg hover:bg-white transition-colors">
375
+ <input type="checkbox"
376
+ id="tool-update-${index}"
377
+ class="w-4 h-4 text-yellow-600 border border-gray-300 rounded focus:ring-2 focus:ring-yellow-500"
378
+ checked>
379
+ <span class="text-sm font-medium text-gray-700">UPDATE</span>
380
+ </label>
381
+
382
+ <label class="flex items-center space-x-2 cursor-pointer p-2 rounded-lg hover:bg-white transition-colors">
383
+ <input type="checkbox"
384
+ id="tool-delete-${index}"
385
+ class="w-4 h-4 text-red-600 border border-gray-300 rounded focus:ring-2 focus:ring-red-500"
386
+ checked>
387
+ <span class="text-sm font-medium text-gray-700">DELETE</span>
388
+ </label>
389
+
390
+ <label class="flex items-center space-x-2 cursor-pointer p-2 rounded-lg hover:bg-white transition-colors">
391
+ <input type="checkbox"
392
+ id="tool-count-${index}"
393
+ class="w-4 h-4 text-purple-600 border border-gray-300 rounded focus:ring-2 focus:ring-purple-500"
394
+ checked>
395
+ <span class="text-sm font-medium text-gray-700">COUNT</span>
396
+ </label>
397
+
398
+ <!-- Aggregate Tools (only if numeric columns exist) -->
399
+ ${numericColumns.length > 0 ? `
400
+ <label class="flex items-center space-x-2 cursor-pointer p-2 rounded-lg hover:bg-white transition-colors">
401
+ <input type="checkbox"
402
+ id="tool-min-${index}"
403
+ class="w-4 h-4 text-indigo-600 border border-gray-300 rounded focus:ring-2 focus:ring-indigo-500"
404
+ checked>
405
+ <span class="text-sm font-medium text-gray-700">MIN</span>
406
+ </label>
407
+
408
+ <label class="flex items-center space-x-2 cursor-pointer p-2 rounded-lg hover:bg-white transition-colors">
409
+ <input type="checkbox"
410
+ id="tool-max-${index}"
411
+ class="w-4 h-4 text-pink-600 border border-gray-300 rounded focus:ring-2 focus:ring-pink-500"
412
+ checked>
413
+ <span class="text-sm font-medium text-gray-700">MAX</span>
414
+ </label>
415
+
416
+ <label class="flex items-center space-x-2 cursor-pointer p-2 rounded-lg hover:bg-white transition-colors">
417
+ <input type="checkbox"
418
+ id="tool-sum-${index}"
419
+ class="w-4 h-4 text-teal-600 border border-gray-300 rounded focus:ring-2 focus:ring-teal-500"
420
+ checked>
421
+ <span class="text-sm font-medium text-gray-700">SUM</span>
422
+ </label>
423
+
424
+ <label class="flex items-center space-x-2 cursor-pointer p-2 rounded-lg hover:bg-white transition-colors">
425
+ <input type="checkbox"
426
+ id="tool-avg-${index}"
427
+ class="w-4 h-4 text-orange-600 border border-gray-300 rounded focus:ring-2 focus:ring-orange-500"
428
+ checked>
429
+ <span class="text-sm font-medium text-gray-700">AVG</span>
430
+ </label>
431
+ ` : ''}
432
+ </div>
433
+ ${numericColumns.length > 0 ? `
434
+ <div class="mt-3 p-3 bg-green-50 border border-green-200 rounded-lg">
435
+ <p class="text-sm text-green-800">
436
+ <i class="fas fa-calculator mr-2"></i>
437
+ <strong>Aggregate tools available:</strong> This table has ${numericColumns.length} numeric column(s):
438
+ <span class="font-mono">${numericColumns.join(', ')}</span>
439
+ </p>
440
+ </div>
441
+ ` : ''}
442
+
443
+ <!-- Quick Actions -->
444
+ <div class="mt-4 flex items-center space-x-4">
445
+ <button onclick="selectAllTools(${index})" class="text-sm text-blue-600 hover:text-blue-800 font-medium">
446
+ <i class="fas fa-check-square mr-1"></i>Select All
447
+ </button>
448
+ <button onclick="deselectAllTools(${index})" class="text-sm text-gray-600 hover:text-gray-800 font-medium">
449
+ <i class="fas fa-square mr-1"></i>Deselect All
450
+ </button>
451
+ <button onclick="selectOnlyBasicTools(${index})" class="text-sm text-green-600 hover:text-green-800 font-medium">
452
+ <i class="fas fa-check mr-1"></i>Basic Only
453
+ </button>
454
+ </div>
455
+ </div>
456
+
457
+ <!-- Table Data Preview (Collapsible) -->
458
+ <div id="${panelId}" class="p-4 hidden">
459
+ <div class="overflow-x-auto">
460
+ <table class="min-w-full text-sm">
461
+ <thead>
462
+ <tr class="bg-gray-100">`;
463
+
464
+ data.headers.forEach(header => {
465
+ const dataType = data.metadata.dataTypes[header];
466
+ html += `<th class="px-3 py-2 text-left font-medium text-gray-700">${header} <span class="text-xs text-gray-500">(${dataType})</span></th>`;
467
+ });
468
+ html += '</tr></thead><tbody>';
469
+
470
+ data.rows.slice(0, 5).forEach((row, rowIndex) => {
471
+ html += `<tr class="${rowIndex % 2 === 0 ? 'bg-white' : 'bg-gray-50'}">`;
472
+ row.forEach(cell => {
473
+ html += `<td class="px-3 py-2 text-gray-900">${cell || ''}</td>`;
474
+ });
475
+ html += '</tr>';
476
+ });
477
+
478
+ if (data.rows.length > 5) {
479
+ html += `<tr><td colspan="${data.headers.length}" class="px-3 py-2 text-center text-gray-500 italic">... and ${data.rows.length - 5} more rows</td></tr>`;
480
+ }
481
+
482
+ html += `</tbody></table>
483
+ </div>
484
+ </div>
485
+ </div>`;
486
+ });
487
+
488
+ html += '</div>';
489
+ preview.innerHTML = html;
490
+ }
491
+
492
+ // Toggle table details panel
493
+ function toggleTableDetails(panelId) {
494
+ const panel = document.getElementById(panelId);
495
+ const icon = document.getElementById(`${panelId}-icon`);
496
+
497
+ if (panel?.classList.contains('hidden')) {
498
+ panel.classList.remove('hidden');
499
+ icon?.classList.add('rotate-180');
500
+ } else {
501
+ panel?.classList.add('hidden');
502
+ icon?.classList.remove('rotate-180');
503
+ }
504
+ }
505
+
506
+ // Legacy function for backward compatibility
507
+ function togglePanel(panelId) {
508
+ toggleTableDetails(panelId);
509
+ }
510
+
511
+ // Toggle table selection
512
+ function toggleTableSelection(tableIndex) {
513
+ const checkbox = document.getElementById(`table-select-${tableIndex}`);
514
+ const toolsPanel = document.getElementById(`table-tools-${tableIndex}`);
515
+
516
+ if (checkbox?.checked) {
517
+ toolsPanel?.classList.remove('opacity-50');
518
+ toolsPanel?.querySelectorAll('input[type="checkbox"]').forEach(input => {
519
+ input.disabled = false;
520
+ });
521
+ } else {
522
+ toolsPanel?.classList.add('opacity-50');
523
+ toolsPanel?.querySelectorAll('input[type="checkbox"]').forEach(input => {
524
+ input.disabled = true;
525
+ });
526
+ }
527
+ }
528
+
529
+ // Tool selection helper functions
530
+ function selectAllTools(tableIndex) {
531
+ const toolInputs = document.querySelectorAll(`#table-tools-${tableIndex} input[type="checkbox"]`);
532
+ toolInputs.forEach(input => {
533
+ if (!input.disabled) input.checked = true;
534
+ });
535
+ }
536
+
537
+ function deselectAllTools(tableIndex) {
538
+ const toolInputs = document.querySelectorAll(`#table-tools-${tableIndex} input[type="checkbox"]`);
539
+ toolInputs.forEach(input => {
540
+ if (!input.disabled) input.checked = false;
541
+ });
542
+ }
543
+
544
+ function selectOnlyBasicTools(tableIndex) {
545
+ // First deselect all
546
+ deselectAllTools(tableIndex);
547
+
548
+ // Then select only basic CRUD tools
549
+ const basicTools = ['get', 'create', 'update', 'delete'];
550
+ basicTools.forEach(tool => {
551
+ const input = document.getElementById(`tool-${tool}-${tableIndex}`);
552
+ if (input && !input.disabled) input.checked = true;
553
+ });
554
+ }
555
+
556
+ // Get selected tables and their tools configuration
557
+ function getSelectedTablesAndTools() {
558
+ const selectedTables = [];
559
+
560
+ // Find all table selection checkboxes
561
+ document.querySelectorAll('[id^="table-select-"]').forEach((checkbox, index) => {
562
+ if (checkbox.checked) {
563
+ const toolsConfig = {
564
+ get: document.getElementById(`tool-get-${index}`)?.checked || false,
565
+ create: document.getElementById(`tool-create-${index}`)?.checked || false,
566
+ update: document.getElementById(`tool-update-${index}`)?.checked || false,
567
+ delete: document.getElementById(`tool-delete-${index}`)?.checked || false,
568
+ count: document.getElementById(`tool-count-${index}`)?.checked || false,
569
+ min: document.getElementById(`tool-min-${index}`)?.checked || false,
570
+ max: document.getElementById(`tool-max-${index}`)?.checked || false,
571
+ sum: document.getElementById(`tool-sum-${index}`)?.checked || false,
572
+ avg: document.getElementById(`tool-avg-${index}`)?.checked || false
573
+ };
574
+
575
+ selectedTables.push({
576
+ index: index,
577
+ tools: toolsConfig
578
+ });
579
+ }
580
+ });
581
+
582
+ return selectedTables;
583
+ }
584
+
585
+ // Generate server
586
+ async function generateServer() {
587
+ const name = document.getElementById('serverName')?.value;
588
+ const description = document.getElementById('serverDescription')?.value;
589
+ const version = document.getElementById('serverVersion')?.value;
590
+
591
+ if (!name) {
592
+ showError('generate-error', 'Please provide server name');
593
+ return;
594
+ }
595
+
596
+ if (!currentDataSource || !currentParsedData) {
597
+ showError('generate-error', 'Please parse a data source first');
598
+ return;
599
+ }
600
+
601
+ // Get selected tables and their tool configurations
602
+ const selectedTablesConfig = getSelectedTablesAndTools();
603
+
604
+ if (selectedTablesConfig.length === 0) {
605
+ showError('generate-error', 'Please select at least one table to generate server for');
606
+ return;
607
+ }
608
+
609
+ console.log('🔍 Selected tables and tools:', selectedTablesConfig);
610
+
611
+ const loading = document.getElementById('generate-loading');
612
+ const successDiv = document.getElementById('generate-success');
613
+ const errorDiv = document.getElementById('generate-error');
614
+ const generateBtn = document.getElementById('generateBtn');
615
+
616
+ loading?.classList.remove('hidden');
617
+ successDiv?.classList.add('hidden');
618
+ errorDiv?.classList.add('hidden');
619
+ if (generateBtn) generateBtn.disabled = true;
620
+
621
+ try {
622
+ const response = await fetch('/api/generate', {
623
+ method: 'POST',
624
+ headers: {
625
+ 'Content-Type': 'application/json'
626
+ },
627
+ body: JSON.stringify({
628
+ name,
629
+ description: description || '',
630
+ version: version || '1.0.0',
631
+ dataSource: currentDataSource,
632
+ selectedTables: selectedTablesConfig,
633
+ parsedData: currentParsedData
634
+ })
635
+ });
636
+
637
+ const result = await response.json();
638
+
639
+ if (result.success) {
640
+ showSuccessModal(name, result.data);
641
+ resetForm();
642
+ } else {
643
+ throw new Error(result.error);
644
+ }
645
+ } catch (error) {
646
+ showError('generate-error', error.message);
647
+ } finally {
648
+ loading?.classList.add('hidden');
649
+ if (generateBtn) generateBtn.disabled = false;
650
+ }
651
+ }
652
+
653
+ function resetForm() {
654
+ document.getElementById('serverName').value = '';
655
+ document.getElementById('serverDescription').value = '';
656
+ document.getElementById('serverVersion').value = '1.0.0';
657
+
658
+ // Reset data source selection
659
+ document.querySelectorAll('input[name="dataSourceType"]').forEach(radio => {
660
+ radio.checked = false;
661
+ });
662
+
663
+ document.getElementById('file-upload-section')?.classList.add('hidden');
664
+ document.getElementById('database-section')?.classList.add('hidden');
665
+
666
+ resetFileUpload();
667
+
668
+ currentParsedData = null;
669
+ currentDataSource = null;
670
+ currentWizardStep = 1;
671
+
672
+ // Reset wizard to step 1
673
+ goToWizardStep(1);
674
+
675
+ const generateBtn = document.getElementById('generateBtn');
676
+ if (generateBtn) generateBtn.disabled = true;
677
+
678
+ const nextBtn = document.getElementById('next-to-step-2');
679
+ if (nextBtn) nextBtn.disabled = true;
680
+ }
681
+
682
+ // Load servers list
683
+ async function loadServers() {
684
+ try {
685
+ const response = await fetch('/api/servers');
686
+ const result = await response.json();
687
+
688
+ if (result.success) {
689
+ displayServers(result.data);
690
+ } else {
691
+ displayServers([]);
692
+ }
693
+ } catch (error) {
694
+ console.error('Failed to load servers:', error);
695
+ displayServers([]);
696
+ }
697
+ }
698
+
699
+ // Display servers
700
+ function displayServers(servers) {
701
+ const serverList = document.getElementById('server-list');
702
+ if (!serverList) return;
703
+
704
+ if (!servers || servers.length === 0) {
705
+ serverList.innerHTML = `
706
+ <div class="col-span-full">
707
+ <div class="text-center py-12">
708
+ <div class="w-24 h-24 mx-auto bg-gray-100 rounded-2xl flex items-center justify-center mb-4">
709
+ <i class="fas fa-server text-3xl text-gray-400"></i>
710
+ </div>
711
+ <h3 class="text-xl font-semibold text-gray-900 mb-2">No Servers Generated Yet</h3>
712
+ <p class="text-gray-600 mb-6">Create your first MCP server by uploading data or connecting to a database.</p>
713
+ <button class="bg-gradient-to-r from-blue-500 to-cyan-500 text-white px-6 py-3 rounded-xl font-medium hover:from-blue-600 hover:to-cyan-600 transition-all duration-200 transform hover:scale-[1.02]" onclick="window.location.href='/'">
714
+ <i class="fas fa-rocket mr-2"></i>
715
+ Generate Your First Server
716
+ </button>
717
+ </div>
718
+ </div>
719
+ `;
720
+ return;
721
+ }
722
+
723
+ let html = '';
724
+ servers.forEach(server => {
725
+ html += `
726
+ <div class="bg-white rounded-2xl shadow-lg border border-gray-200/50 overflow-hidden hover:shadow-xl transition-all duration-200 transform hover:scale-[1.02]">
727
+ <div class="bg-gradient-to-r from-blue-50 to-cyan-50 p-6 border-b border-gray-200/50">
728
+ <div class="flex items-center justify-between">
729
+ <div>
730
+ <h3 class="text-lg font-semibold text-gray-900">${server.name}</h3>
731
+ <p class="text-sm text-gray-600">${server.description}</p>
732
+ </div>
733
+ <div class="w-10 h-10 bg-gradient-to-r from-blue-500 to-cyan-500 rounded-xl flex items-center justify-center">
734
+ <i class="fas fa-server text-white"></i>
735
+ </div>
736
+ </div>
737
+ </div>
738
+
739
+ <div class="p-6">
740
+ <div class="grid grid-cols-2 gap-4 mb-6">
741
+ <div class="text-center">
742
+ <div class="text-2xl font-bold text-blue-600">${server.toolsCount}</div>
743
+ <div class="text-sm text-gray-500">Tools</div>
744
+ </div>
745
+ <div class="text-center">
746
+ <div class="text-2xl font-bold text-green-600">${server.resourcesCount}</div>
747
+ <div class="text-sm text-gray-500">Resources</div>
748
+ </div>
749
+ </div>
750
+
751
+ <div class="flex flex-wrap gap-2 mb-6">
752
+ <span class="bg-blue-100 text-blue-800 text-xs font-medium px-2.5 py-0.5 rounded-full">v${server.version}</span>
753
+ <span class="bg-purple-100 text-purple-800 text-xs font-medium px-2.5 py-0.5 rounded-full">${server.promptsCount} prompts</span>
754
+ </div>
755
+
756
+ <div class="space-y-2">
757
+ <button class="w-full bg-gradient-to-r from-blue-500 to-cyan-500 text-white px-4 py-2 rounded-lg font-medium hover:from-blue-600 hover:to-cyan-600 transition-all duration-200" onclick="viewServer('${server.id}')">
758
+ <i class="fas fa-eye mr-2"></i>
759
+ View Details
760
+ </button>
761
+
762
+ <div class="grid grid-cols-3 gap-2">
763
+ <button class="bg-gray-100 text-gray-700 px-3 py-2 rounded-lg text-sm font-medium hover:bg-gray-200 transition-all duration-200" onclick="testServer('${server.id}')">
764
+ <i class="fas fa-vial text-xs mr-1"></i>
765
+ Test
766
+ </button>
767
+ <button class="bg-gray-100 text-gray-700 px-3 py-2 rounded-lg text-sm font-medium hover:bg-gray-200 transition-all duration-200" onclick="exportServer('${server.id}')">
768
+ <i class="fas fa-download text-xs mr-1"></i>
769
+ Export
770
+ </button>
771
+ <button class="bg-red-100 text-red-700 px-3 py-2 rounded-lg text-sm font-medium hover:bg-red-200 transition-all duration-200" onclick="deleteServer('${server.id}')">
772
+ <i class="fas fa-trash text-xs mr-1"></i>
773
+ Delete
774
+ </button>
775
+ </div>
776
+ </div>
777
+ </div>
778
+ </div>
779
+ `;
780
+ });
781
+
782
+ serverList.innerHTML = html;
783
+ }
784
+
785
+ // Load test servers
786
+ async function loadTestServers() {
787
+ try {
788
+ const response = await fetch('/api/servers');
789
+ const result = await response.json();
790
+
791
+ const select = document.getElementById('testServerSelect');
792
+ if (!select) return;
793
+
794
+ if (result.success && result.data.length > 0) {
795
+ select.innerHTML = '<option value="">Select a server to test</option>';
796
+ result.data.forEach(server => {
797
+ select.innerHTML += `<option value="${server.id}">${server.name}</option>`;
798
+ });
799
+ } else {
800
+ select.innerHTML = '<option value="">No servers available - Generate a server first</option>';
801
+ }
802
+ } catch (error) {
803
+ console.error('Failed to load test servers:', error);
804
+ const select = document.getElementById('testServerSelect');
805
+ if (select) {
806
+ select.innerHTML = '<option value="">Error loading servers</option>';
807
+ }
808
+ }
809
+ }
810
+
811
+ // Handle test type change - switch between input and dropdown for test name
812
+ function handleTestTypeChange() {
813
+ const testType = document.getElementById('testType')?.value;
814
+ const container = document.getElementById('testNameContainer');
815
+ const serverId = document.getElementById('testServerSelect')?.value;
816
+
817
+ if (!container) return;
818
+
819
+ if (testType === 'tools/call' && serverId) {
820
+ // Load tools dropdown
821
+ loadToolsDropdown(serverId);
822
+ } else {
823
+ // Show regular input
824
+ container.innerHTML = '<input type="text" id="testName" placeholder="Enter test name" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-transparent">';
825
+ }
826
+ }
827
+
828
+ // Handle test server change - reload tools if Tool Call is selected
829
+ async function handleTestServerChange() {
830
+ const testType = document.getElementById('testType')?.value;
831
+ const serverId = document.getElementById('testServerSelect')?.value;
832
+
833
+ if (testType === 'tools/call' && serverId) {
834
+ await loadToolsDropdown(serverId);
835
+ }
836
+ }
837
+
838
+ // Load tools dropdown for custom test
839
+ async function loadToolsDropdown(serverId) {
840
+ const container = document.getElementById('testNameContainer');
841
+ if (!container) return;
842
+
843
+ try {
844
+ const response = await fetch(`/api/servers/${serverId}`);
845
+ const result = await response.json();
846
+
847
+ if (result.success && result.data) {
848
+ const tools = result.data.config.tools || [];
849
+
850
+ // Create dropdown with tools
851
+ let dropdownHTML = '<select id="testName" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-transparent" onchange="updateParametersExample()">';
852
+ dropdownHTML += '<option value="">Select a tool to test</option>';
853
+
854
+ tools.forEach(tool => {
855
+ // Store tool data in data attributes for parameter generation
856
+ dropdownHTML += `<option value="${tool.name}" data-schema='${JSON.stringify(tool.inputSchema || {})}'>${tool.name} - ${tool.description}</option>`;
857
+ });
858
+
859
+ dropdownHTML += '</select>';
860
+ container.innerHTML = dropdownHTML;
861
+
862
+ // Store tools data globally for parameter generation
863
+ window.testTools = tools;
864
+ }
865
+ } catch (error) {
866
+ console.error('Failed to load tools:', error);
867
+ container.innerHTML = '<input type="text" id="testName" placeholder="Error loading tools" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-orange-500 focus:border-transparent">';
868
+ }
869
+ }
870
+
871
+ // Update parameters example when tool is selected
872
+ function updateParametersExample() {
873
+ const select = document.getElementById('testName');
874
+ const paramsTextarea = document.getElementById('testParams');
875
+
876
+ if (!select || !paramsTextarea) return;
877
+
878
+ const selectedOption = select.options[select.selectedIndex];
879
+ if (!selectedOption || !selectedOption.value) {
880
+ paramsTextarea.placeholder = '{"param1": "value1"}';
881
+ return;
882
+ }
883
+
884
+ try {
885
+ const schema = JSON.parse(selectedOption.getAttribute('data-schema') || '{}');
886
+ const exampleParams = {};
887
+
888
+ // Generate example parameters based on schema
889
+ if (schema.properties) {
890
+ for (const [key, prop] of Object.entries(schema.properties)) {
891
+ if (key === 'limit') {
892
+ exampleParams[key] = 10;
893
+ } else if (key === 'offset') {
894
+ exampleParams[key] = 0;
895
+ } else if (prop.type === 'string') {
896
+ exampleParams[key] = prop.description || "example_value";
897
+ } else if (prop.type === 'number' || prop.type === 'integer') {
898
+ exampleParams[key] = 0;
899
+ } else if (prop.type === 'boolean') {
900
+ exampleParams[key] = false;
901
+ } else {
902
+ exampleParams[key] = "value";
903
+ }
904
+ }
905
+ }
906
+
907
+ // Set the example in the textarea
908
+ if (Object.keys(exampleParams).length > 0) {
909
+ paramsTextarea.value = JSON.stringify(exampleParams, null, 2);
910
+ } else {
911
+ paramsTextarea.value = '{}';
912
+ }
913
+ } catch (error) {
914
+ console.error('Error generating parameter example:', error);
915
+ paramsTextarea.value = '{}';
916
+ }
917
+ }
918
+
919
+ // Test functions
920
+ async function runAutoTests(runAll = false) {
921
+ const serverId = document.getElementById('testServerSelect')?.value;
922
+ if (!serverId) {
923
+ showError('test-error', 'Please select a server to test');
924
+ return;
925
+ }
926
+
927
+ const loading = document.getElementById('test-loading');
928
+ const loadingText = document.getElementById('loadingText');
929
+ const resultsDiv = document.getElementById('test-results');
930
+ const noResults = document.getElementById('no-results');
931
+ const errorDiv = document.getElementById('test-error');
932
+
933
+ // Update button states
934
+ const quickBtn = document.getElementById('runQuickTestBtn');
935
+ const fullBtn = document.getElementById('runFullTestBtn');
936
+ const fullBtnIcon = document.getElementById('fullTestIcon');
937
+ const fullBtnText = document.getElementById('fullTestText');
938
+
939
+ if (runAll) {
940
+ // Show loading spinner in button
941
+ fullBtnIcon.className = 'fas fa-spinner fa-spin mr-2';
942
+ fullBtnText.textContent = 'Testing All Tools...';
943
+ fullBtn.disabled = true;
944
+ quickBtn.disabled = true;
945
+ loadingText.textContent = 'Running all tests... This may take a while.';
946
+ } else {
947
+ quickBtn.disabled = true;
948
+ fullBtn.disabled = true;
949
+ loadingText.textContent = 'Running quick tests...';
950
+ }
951
+
952
+ loading?.classList.remove('hidden');
953
+ resultsDiv?.classList.add('hidden');
954
+ noResults?.classList.add('hidden');
955
+ errorDiv?.classList.add('hidden');
956
+
957
+ try {
958
+ const response = await fetch(`/api/servers/${serverId}/test`, {
959
+ method: 'POST',
960
+ headers: {
961
+ 'Content-Type': 'application/json'
962
+ },
963
+ body: JSON.stringify({ runAll: runAll })
964
+ });
965
+
966
+ const result = await response.json();
967
+
968
+ if (result.success) {
969
+ displayTestResults(result.data);
970
+ } else {
971
+ throw new Error(result.error);
972
+ }
973
+ } catch (error) {
974
+ showError('test-error', error.message);
975
+ noResults?.classList.remove('hidden');
976
+ } finally {
977
+ loading?.classList.add('hidden');
978
+
979
+ // Reset button states
980
+ if (quickBtn) quickBtn.disabled = false;
981
+ if (fullBtn) {
982
+ fullBtn.disabled = false;
983
+ fullBtnIcon.className = 'fas fa-play-circle mr-2';
984
+ fullBtnText.textContent = 'Run Auto Tests';
985
+ }
986
+ }
987
+ }
988
+
989
+ async function runCustomTest() {
990
+ const serverId = document.getElementById('testServerSelect')?.value;
991
+ const testType = document.getElementById('testType')?.value;
992
+ const testName = document.getElementById('testName')?.value;
993
+ const testParams = document.getElementById('testParams')?.value;
994
+
995
+ if (!serverId || !testName) {
996
+ showError('test-error', 'Please select a server and enter test name');
997
+ return;
998
+ }
999
+
1000
+ let params = {};
1001
+ if (testParams) {
1002
+ try {
1003
+ params = JSON.parse(testParams);
1004
+ } catch (error) {
1005
+ showError('test-error', 'Invalid JSON in parameters');
1006
+ return;
1007
+ }
1008
+ }
1009
+
1010
+ const loading = document.getElementById('test-loading');
1011
+ const resultsDiv = document.getElementById('test-results');
1012
+ const noResults = document.getElementById('no-results');
1013
+ const errorDiv = document.getElementById('test-error');
1014
+
1015
+ loading?.classList.remove('hidden');
1016
+ resultsDiv?.classList.add('hidden');
1017
+ noResults?.classList.add('hidden');
1018
+ errorDiv?.classList.add('hidden');
1019
+
1020
+ try {
1021
+ const response = await fetch(`/api/servers/${serverId}/test`, {
1022
+ method: 'POST',
1023
+ headers: {
1024
+ 'Content-Type': 'application/json'
1025
+ },
1026
+ body: JSON.stringify({
1027
+ testType: testType,
1028
+ toolName: testName,
1029
+ parameters: params
1030
+ })
1031
+ });
1032
+
1033
+ const result = await response.json();
1034
+
1035
+ if (result.success) {
1036
+ displaySingleTestResult(result.data);
1037
+ } else {
1038
+ throw new Error(result.error);
1039
+ }
1040
+ } catch (error) {
1041
+ showError('test-error', error.message);
1042
+ noResults?.classList.remove('hidden');
1043
+ } finally {
1044
+ loading?.classList.add('hidden');
1045
+ }
1046
+ }
1047
+
1048
+ // Display test results
1049
+ function displayTestResults(testData) {
1050
+ const resultsDiv = document.getElementById('test-results');
1051
+ const noResults = document.getElementById('no-results');
1052
+
1053
+ if (!resultsDiv) return;
1054
+
1055
+ // Check if this is the new format from SQLite-based test
1056
+ if (testData.serverName && testData.results) {
1057
+ // Count success and failed tests
1058
+ const successCount = testData.results.filter(r => r.status === 'success').length;
1059
+ const failedCount = testData.results.filter(r => r.status === 'error').length;
1060
+
1061
+ let output = `=== Test Results for ${testData.serverName} ===\n`;
1062
+ output += `Total Tools: ${testData.toolsCount}\n`;
1063
+ output += `Tests Run: ${testData.testsRun}\n`;
1064
+ output += `✅ Success: ${successCount} | ❌ Failed: ${failedCount}\n\n`;
1065
+
1066
+ testData.results.forEach(result => {
1067
+ const status = result.status === 'success' ? '✅ PASS' : '❌ FAIL';
1068
+ output += `${status} ${result.tool}\n`;
1069
+ output += ` Description: ${result.description}\n`;
1070
+ if (result.parameters && Object.keys(result.parameters).length > 0) {
1071
+ output += ` Parameters: ${JSON.stringify(result.parameters)}\n`;
1072
+ }
1073
+ if (result.status === 'success') {
1074
+ output += ` Result: ${result.result}\n`;
1075
+ if (result.rowCount !== undefined) {
1076
+ output += ` Rows: ${result.rowCount}\n`;
1077
+ }
1078
+ } else if (result.error) {
1079
+ output += ` Error: ${result.error}\n`;
1080
+ }
1081
+ output += '\n';
1082
+ });
1083
+
1084
+ resultsDiv.textContent = output;
1085
+ resultsDiv.classList.remove('hidden');
1086
+ noResults?.classList.add('hidden');
1087
+ } else if (testData.testSuite) {
1088
+ // Old format for compatibility
1089
+ let output = `=== Test Suite: ${testData.testSuite.name} ===\n`;
1090
+ output += `Description: ${testData.testSuite.description}\n`;
1091
+ output += `Duration: ${testData.duration}ms\n`;
1092
+ output += `Results: ${testData.passedTests}/${testData.totalTests} tests passed\n\n`;
1093
+
1094
+ testData.results.forEach(testResult => {
1095
+ const status = testResult.passed ? '✅ PASS' : '❌ FAIL';
1096
+ output += `${status} ${testResult.testCase.name} (${testResult.duration}ms)\n`;
1097
+
1098
+ if (!testResult.passed) {
1099
+ output += ` Error: ${testResult.error || testResult.response.error || 'Test assertion failed'}\n`;
1100
+ }
1101
+
1102
+ if (testResult.response && testResult.response.data) {
1103
+ const dataPreview = JSON.stringify(testResult.response.data, null, 2).slice(0, 200);
1104
+ output += ` Response: ${dataPreview}${dataPreview.length >= 200 ? '...' : ''}\n`;
1105
+ }
1106
+
1107
+ output += '\n';
1108
+ });
1109
+
1110
+ resultsDiv.textContent = output;
1111
+ resultsDiv.classList.remove('hidden');
1112
+ noResults?.classList.add('hidden');
1113
+ } else {
1114
+ // Fallback for unknown format - just display the raw JSON
1115
+ resultsDiv.textContent = JSON.stringify(testData, null, 2);
1116
+ resultsDiv.classList.remove('hidden');
1117
+ noResults?.classList.add('hidden');
1118
+ }
1119
+ }
1120
+
1121
+ function displaySingleTestResult(testData) {
1122
+ const resultsDiv = document.getElementById('test-results');
1123
+ const noResults = document.getElementById('no-results');
1124
+
1125
+ if (!resultsDiv) return;
1126
+
1127
+ // Check if this is the new format from custom tool test
1128
+ if (testData && testData.tool) {
1129
+ let output = `=== Custom Test Result ===\n`;
1130
+ output += `Tool: ${testData.tool}\n`;
1131
+ output += `Status: ${testData.status === 'success' ? '✅ PASS' : '❌ FAIL'}\n`;
1132
+ if (testData.description) {
1133
+ output += `Description: ${testData.description}\n`;
1134
+ }
1135
+ if (testData.parameters && Object.keys(testData.parameters).length > 0) {
1136
+ output += `Parameters: ${JSON.stringify(testData.parameters)}\n`;
1137
+ }
1138
+ output += '\n';
1139
+
1140
+ if (testData.status === 'success') {
1141
+ if (testData.result) {
1142
+ output += `Result: ${typeof testData.result === 'object' ? JSON.stringify(testData.result, null, 2) : testData.result}\n`;
1143
+ }
1144
+ if (testData.rowCount !== undefined) {
1145
+ output += `Rows: ${testData.rowCount}\n`;
1146
+ }
1147
+ } else if (testData.error) {
1148
+ output += `Error: ${testData.error}\n`;
1149
+ }
1150
+
1151
+ resultsDiv.textContent = output;
1152
+ resultsDiv.classList.remove('hidden');
1153
+ noResults?.classList.add('hidden');
1154
+ } else if (testData && testData.testCase) {
1155
+ // Old format for compatibility
1156
+ let output = `=== Custom Test Result ===\n`;
1157
+ output += `Test: ${testData.testCase.name}\n`;
1158
+ output += `Duration: ${testData.duration}ms\n`;
1159
+ output += `Status: ${testData.passed ? '✅ PASS' : '❌ FAIL'}\n\n`;
1160
+
1161
+ if (!testData.passed) {
1162
+ output += `Error: ${testData.error || testData.response?.error || 'Test assertion failed'}\n\n`;
1163
+ }
1164
+
1165
+ if (testData.response?.data) {
1166
+ output += `Response:\n${JSON.stringify(testData.response.data, null, 2)}\n`;
1167
+ }
1168
+
1169
+ resultsDiv.textContent = output;
1170
+ resultsDiv.classList.remove('hidden');
1171
+ noResults?.classList.add('hidden');
1172
+ } else {
1173
+ // Fallback for unknown format
1174
+ resultsDiv.textContent = JSON.stringify(testData, null, 2);
1175
+ resultsDiv.classList.remove('hidden');
1176
+ noResults?.classList.add('hidden');
1177
+ }
1178
+ }
1179
+
1180
+ // Server management functions
1181
+ async function viewServer(serverId) {
1182
+ console.log('🔍 viewServer called with serverId:', serverId);
1183
+
1184
+ try {
1185
+ console.log('🔍 Fetching server details from:', `/api/servers/${serverId}`);
1186
+ const response = await fetch(`/api/servers/${serverId}`);
1187
+ console.log('🔍 Response status:', response.status);
1188
+
1189
+ const result = await response.json();
1190
+ console.log('🔍 Response result:', result);
1191
+
1192
+ if (result.success) {
1193
+ console.log('🔍 Server data structure:', result.data);
1194
+ console.log('🔍 Config tools:', result.data?.config?.tools);
1195
+ console.log('🔍 Config resources:', result.data?.config?.resources);
1196
+ showServerDetailsModal(result.data);
1197
+ } else {
1198
+ console.error('❌ Failed to load server details:', result.error);
1199
+ alert('Failed to load server details: ' + result.error);
1200
+ }
1201
+ } catch (error) {
1202
+ console.error('❌ Error loading server details:', error);
1203
+ alert('Error loading server details: ' + error.message);
1204
+ }
1205
+ }
1206
+
1207
+ function showServerDetailsModal(serverData) {
1208
+ console.log('🔍 showServerDetailsModal called with:', serverData);
1209
+
1210
+ // Safely extract data with defaults
1211
+ const config = serverData?.config || {};
1212
+ const tools = config.tools || [];
1213
+ const resources = config.resources || [];
1214
+ const serverName = config.name || 'Unknown Server';
1215
+ const serverDescription = config.description || 'No description available';
1216
+
1217
+ console.log('🔍 Modal data:', { tools: tools.length, resources: resources.length, serverName });
1218
+
1219
+ const modalHtml = `
1220
+ <div id="server-details-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
1221
+ <div class="bg-white rounded-2xl shadow-xl max-w-4xl w-full mx-4 max-h-[90vh] overflow-y-auto">
1222
+ <div class="p-6 border-b border-gray-200">
1223
+ <div class="flex items-center justify-between">
1224
+ <h2 class="text-2xl font-bold text-gray-900">${serverName}</h2>
1225
+ <button onclick="closeServerDetailsModal()" class="text-gray-400 hover:text-gray-600">
1226
+ <i class="fas fa-times text-xl"></i>
1227
+ </button>
1228
+ </div>
1229
+ <p class="text-gray-600 mt-2">${serverDescription}</p>
1230
+ </div>
1231
+
1232
+ <div class="p-6">
1233
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
1234
+ <!-- Tools Section -->
1235
+ <div>
1236
+ <h3 class="text-lg font-semibold text-gray-900 mb-4">
1237
+ <i class="fas fa-tools mr-2 text-blue-500"></i>
1238
+ Tools (${tools.length})
1239
+ </h3>
1240
+ <div class="space-y-3 max-h-60 overflow-y-auto">
1241
+ ${tools.length > 0 ? tools.map(tool => `
1242
+ <div class="bg-gray-50 rounded-lg p-3">
1243
+ <div class="font-medium text-gray-900">${tool.name || 'Unnamed Tool'}</div>
1244
+ <div class="text-sm text-gray-600 mt-1">${tool.description || 'No description'}</div>
1245
+ <div class="text-xs text-gray-500 mt-1">Operation: ${tool.operation || 'Unknown'}</div>
1246
+ </div>
1247
+ `).join('') : '<div class="text-gray-500 text-sm">No tools available</div>'}
1248
+ </div>
1249
+ </div>
1250
+
1251
+ <!-- Resources Section -->
1252
+ <div>
1253
+ <h3 class="text-lg font-semibold text-gray-900 mb-4">
1254
+ <i class="fas fa-database mr-2 text-green-500"></i>
1255
+ Resources (${resources.length})
1256
+ </h3>
1257
+ <div class="space-y-3 max-h-60 overflow-y-auto">
1258
+ ${resources.length > 0 ? resources.map(resource => `
1259
+ <div class="bg-gray-50 rounded-lg p-3">
1260
+ <div class="font-medium text-gray-900">${resource.name || 'Unnamed Resource'}</div>
1261
+ <div class="text-sm text-gray-600 mt-1">${resource.description || 'No description'}</div>
1262
+ <div class="text-xs text-gray-500 mt-1 font-mono">${resource.uri_template || resource.uri || 'No URI'}</div>
1263
+ </div>
1264
+ `).join('') : '<div class="text-gray-500 text-sm">No resources available</div>'}
1265
+ </div>
1266
+ </div>
1267
+ </div>
1268
+
1269
+ <div class="mt-6 pt-6 border-t border-gray-200">
1270
+ <div class="flex flex-wrap gap-4">
1271
+ <button onclick="testServer('${serverData.id || serverData.config?.id || 'unknown'}')" class="bg-blue-500 text-white px-4 py-2 rounded-lg font-medium hover:bg-blue-600 transition-all duration-200">
1272
+ <i class="fas fa-vial mr-2"></i>
1273
+ Test Server
1274
+ </button>
1275
+ <button onclick="exportServer('${serverData.id || serverData.config?.id || 'unknown'}')" class="bg-green-500 text-white px-4 py-2 rounded-lg font-medium hover:bg-green-600 transition-all duration-200">
1276
+ <i class="fas fa-download mr-2"></i>
1277
+ Export Server
1278
+ </button>
1279
+ <button onclick="deleteServer('${serverData.id || serverData.config?.id || 'unknown'}')" class="bg-red-500 text-white px-4 py-2 rounded-lg font-medium hover:bg-red-600 transition-all duration-200">
1280
+ <i class="fas fa-trash mr-2"></i>
1281
+ Delete Server
1282
+ </button>
1283
+ </div>
1284
+ </div>
1285
+ </div>
1286
+ </div>
1287
+ </div>
1288
+ `;
1289
+
1290
+ // Add modal to body
1291
+ document.body.insertAdjacentHTML('beforeend', modalHtml);
1292
+ console.log('🔍 Modal added to DOM');
1293
+ }
1294
+
1295
+ function closeServerDetailsModal() {
1296
+ const modal = document.getElementById('server-details-modal');
1297
+ if (modal) {
1298
+ modal.remove();
1299
+ }
1300
+ }
1301
+
1302
+ function testServer(serverId) {
1303
+ // Close modal if open
1304
+ closeServerDetailsModal();
1305
+
1306
+ switchTab('test');
1307
+ setTimeout(() => {
1308
+ const select = document.getElementById('testServerSelect');
1309
+ if (select) select.value = serverId;
1310
+ }, 100);
1311
+ }
1312
+
1313
+ async function exportServer(serverId) {
1314
+ try {
1315
+ const response = await fetch(`/api/servers/${serverId}/export`);
1316
+ const result = await response.json();
1317
+
1318
+ if (result.success) {
1319
+ // Create download link
1320
+ const link = document.createElement('a');
1321
+ link.href = result.data.downloadUrl;
1322
+ link.download = result.data.filename;
1323
+ link.click();
1324
+ }
1325
+ } catch (error) {
1326
+ console.error('Export failed:', error);
1327
+ }
1328
+ }
1329
+
1330
+ async function deleteServer(serverId) {
1331
+ // Get server name for modal display
1332
+ const serverName = await getServerName(serverId);
1333
+ showDeleteConfirmModal(serverId, serverName || 'Unknown Server');
1334
+ }
1335
+
1336
+ async function getServerName(serverId) {
1337
+ try {
1338
+ const response = await fetch(`/api/servers/${serverId}`);
1339
+ const result = await response.json();
1340
+ return result.success ? result.data.config.name : null;
1341
+ } catch (error) {
1342
+ return null;
1343
+ }
1344
+ }
1345
+
1346
+ function showDeleteConfirmModal(serverId, serverName) {
1347
+ const modalHtml = `
1348
+ <div id="delete-confirm-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
1349
+ <div class="bg-white rounded-2xl shadow-xl max-w-md w-full mx-4">
1350
+ <div class="p-6">
1351
+ <div class="flex items-center justify-center w-16 h-16 mx-auto bg-red-100 rounded-full mb-4">
1352
+ <i class="fas fa-trash text-2xl text-red-600"></i>
1353
+ </div>
1354
+
1355
+ <h3 class="text-lg font-semibold text-gray-900 text-center mb-2">Delete Server</h3>
1356
+ <p class="text-gray-600 text-center mb-6">
1357
+ Are you sure you want to delete <strong>"${serverName}"</strong>?
1358
+ <br><br>
1359
+ This action cannot be undone and will permanently remove all server files and configurations.
1360
+ </p>
1361
+
1362
+ <div class="flex gap-3">
1363
+ <button onclick="closeDeleteConfirmModal()" class="flex-1 bg-gray-100 text-gray-700 px-4 py-2 rounded-lg font-medium hover:bg-gray-200 transition-all duration-200">
1364
+ Cancel
1365
+ </button>
1366
+ <button onclick="confirmDeleteServer('${serverId}')" class="flex-1 bg-red-500 text-white px-4 py-2 rounded-lg font-medium hover:bg-red-600 transition-all duration-200">
1367
+ <i class="fas fa-trash mr-2"></i>
1368
+ Delete
1369
+ </button>
1370
+ </div>
1371
+ </div>
1372
+ </div>
1373
+ </div>
1374
+ `;
1375
+
1376
+ document.body.insertAdjacentHTML('beforeend', modalHtml);
1377
+ }
1378
+
1379
+ function closeDeleteConfirmModal() {
1380
+ const modal = document.getElementById('delete-confirm-modal');
1381
+ if (modal) {
1382
+ modal.remove();
1383
+ }
1384
+ }
1385
+
1386
+ async function confirmDeleteServer(serverId) {
1387
+ // Close confirmation modal
1388
+ closeDeleteConfirmModal();
1389
+
1390
+ // Show loading modal
1391
+ showDeleteLoadingModal();
1392
+
1393
+ try {
1394
+ const response = await fetch(`/api/servers/${serverId}`, {
1395
+ method: 'DELETE'
1396
+ });
1397
+
1398
+ const result = await response.json();
1399
+
1400
+ // Close loading modal
1401
+ closeDeleteLoadingModal();
1402
+
1403
+ if (result.success) {
1404
+ // Close server details modal if open
1405
+ closeServerDetailsModal();
1406
+
1407
+ // Show success modal
1408
+ showDeleteSuccessModal();
1409
+
1410
+ // Reload server lists
1411
+ loadServers();
1412
+ loadTestServers();
1413
+ } else {
1414
+ throw new Error(result.error);
1415
+ }
1416
+ } catch (error) {
1417
+ closeDeleteLoadingModal();
1418
+ showDeleteErrorModal(error.message);
1419
+ console.error('Delete failed:', error);
1420
+ }
1421
+ }
1422
+
1423
+ function showDeleteLoadingModal() {
1424
+ const modalHtml = `
1425
+ <div id="delete-loading-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
1426
+ <div class="bg-white rounded-2xl shadow-xl max-w-md w-full mx-4">
1427
+ <div class="p-6 text-center">
1428
+ <div class="animate-spin rounded-full h-16 w-16 border-b-2 border-red-500 mx-auto mb-4"></div>
1429
+ <h3 class="text-lg font-semibold text-gray-900 mb-2">Deleting Server</h3>
1430
+ <p class="text-gray-600">Please wait while we remove the server and all its files...</p>
1431
+ </div>
1432
+ </div>
1433
+ </div>
1434
+ `;
1435
+
1436
+ document.body.insertAdjacentHTML('beforeend', modalHtml);
1437
+ }
1438
+
1439
+ function closeDeleteLoadingModal() {
1440
+ const modal = document.getElementById('delete-loading-modal');
1441
+ if (modal) {
1442
+ modal.remove();
1443
+ }
1444
+ }
1445
+
1446
+ function showDeleteSuccessModal() {
1447
+ const modalHtml = `
1448
+ <div id="delete-success-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
1449
+ <div class="bg-white rounded-2xl shadow-xl max-w-md w-full mx-4">
1450
+ <div class="p-6">
1451
+ <div class="flex items-center justify-center w-16 h-16 mx-auto bg-green-100 rounded-full mb-4">
1452
+ <i class="fas fa-check text-2xl text-green-600"></i>
1453
+ </div>
1454
+
1455
+ <h3 class="text-lg font-semibold text-gray-900 text-center mb-2">Server Deleted Successfully</h3>
1456
+ <p class="text-gray-600 text-center mb-6">
1457
+ The server has been permanently removed from your system.
1458
+ </p>
1459
+
1460
+ <button onclick="closeDeleteSuccessModal()" class="w-full bg-green-500 text-white px-4 py-2 rounded-lg font-medium hover:bg-green-600 transition-all duration-200">
1461
+ <i class="fas fa-check mr-2"></i>
1462
+ OK
1463
+ </button>
1464
+ </div>
1465
+ </div>
1466
+ </div>
1467
+ `;
1468
+
1469
+ document.body.insertAdjacentHTML('beforeend', modalHtml);
1470
+ }
1471
+
1472
+ function closeDeleteSuccessModal() {
1473
+ const modal = document.getElementById('delete-success-modal');
1474
+ if (modal) {
1475
+ modal.remove();
1476
+ }
1477
+ }
1478
+
1479
+ function showDeleteErrorModal(errorMessage) {
1480
+ const modalHtml = `
1481
+ <div id="delete-error-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
1482
+ <div class="bg-white rounded-2xl shadow-xl max-w-md w-full mx-4">
1483
+ <div class="p-6">
1484
+ <div class="flex items-center justify-center w-16 h-16 mx-auto bg-red-100 rounded-full mb-4">
1485
+ <i class="fas fa-exclamation-triangle text-2xl text-red-600"></i>
1486
+ </div>
1487
+
1488
+ <h3 class="text-lg font-semibold text-gray-900 text-center mb-2">Delete Failed</h3>
1489
+ <p class="text-gray-600 text-center mb-6">
1490
+ Failed to delete the server: ${errorMessage}
1491
+ </p>
1492
+
1493
+ <button onclick="closeDeleteErrorModal()" class="w-full bg-red-500 text-white px-4 py-2 rounded-lg font-medium hover:bg-red-600 transition-all duration-200">
1494
+ <i class="fas fa-times mr-2"></i>
1495
+ Close
1496
+ </button>
1497
+ </div>
1498
+ </div>
1499
+ </div>
1500
+ `;
1501
+
1502
+ document.body.insertAdjacentHTML('beforeend', modalHtml);
1503
+ }
1504
+
1505
+ function closeDeleteErrorModal() {
1506
+ const modal = document.getElementById('delete-error-modal');
1507
+ if (modal) {
1508
+ modal.remove();
1509
+ }
1510
+ }
1511
+
1512
+ // Success Modal Functions
1513
+ function showSuccessModal(serverName, serverData) {
1514
+ const modal = document.getElementById("success-modal");
1515
+ const messageElement = document.getElementById("success-message");
1516
+
1517
+ let message = `Your MCP server "${serverName}" has been successfully generated and is ready to use.`;
1518
+
1519
+ if (serverData) {
1520
+ message += ` Generated ${serverData.toolsCount || 0} tools, ${serverData.resourcesCount || 0} resources, and ${serverData.promptsCount || 0} prompts.`;
1521
+ }
1522
+
1523
+ if (messageElement) messageElement.textContent = message;
1524
+
1525
+ if (modal) {
1526
+ modal.classList.remove('opacity-0', 'invisible');
1527
+ modal.querySelector('.bg-white').classList.remove('scale-95');
1528
+ }
1529
+ }
1530
+
1531
+ function closeSuccessModal() {
1532
+ const modal = document.getElementById("success-modal");
1533
+ if (modal) {
1534
+ modal.classList.add('opacity-0', 'invisible');
1535
+ modal.querySelector('.bg-white').classList.add('scale-95');
1536
+ }
1537
+ }
1538
+
1539
+ function goToManageServers() {
1540
+ closeSuccessModal();
1541
+ switchTab('manage');
1542
+ }
1543
+
1544
+ // Server name validation
1545
+ let nameCheckTimeout;
1546
+
1547
+ async function checkServerName() {
1548
+ const nameInput = document.getElementById('serverName');
1549
+ const validationDiv = document.getElementById('name-validation');
1550
+ const serverName = nameInput?.value.trim();
1551
+
1552
+ // Clear previous timeout
1553
+ if (nameCheckTimeout) {
1554
+ clearTimeout(nameCheckTimeout);
1555
+ }
1556
+
1557
+ // Hide validation if empty
1558
+ if (!serverName) {
1559
+ if (validationDiv) validationDiv.style.display = 'none';
1560
+ if (nameInput) nameInput.classList.remove('border-green-300', 'border-red-300');
1561
+ return;
1562
+ }
1563
+
1564
+ // Debounce API calls
1565
+ nameCheckTimeout = setTimeout(async () => {
1566
+ try {
1567
+ const response = await fetch(`/api/servers/check-name/${encodeURIComponent(serverName)}`);
1568
+ const result = await response.json();
1569
+
1570
+ if (result.success && validationDiv && nameInput) {
1571
+ validationDiv.style.display = 'block';
1572
+ if (result.available) {
1573
+ validationDiv.textContent = '✓ Server name is available';
1574
+ validationDiv.className = 'mt-2 text-sm text-green-600';
1575
+ nameInput.classList.remove('border-red-300');
1576
+ nameInput.classList.add('border-green-300');
1577
+ } else {
1578
+ validationDiv.textContent = '✗ Server name already exists';
1579
+ validationDiv.className = 'mt-2 text-sm text-red-600';
1580
+ nameInput.classList.remove('border-green-300');
1581
+ nameInput.classList.add('border-red-300');
1582
+ }
1583
+ }
1584
+ } catch (error) {
1585
+ console.error('Error checking server name:', error);
1586
+ }
1587
+ }, 500);
1588
+ }
1589
+
1590
+ // Handle next to step 2 - parse data first
1591
+ async function handleNextToStep2() {
1592
+ const selectedType = document.querySelector('input[name="dataSourceType"]:checked')?.value;
1593
+
1594
+ if (!selectedType) {
1595
+ showError('parse-error', 'Please select a data source type');
1596
+ return;
1597
+ }
1598
+
1599
+ // If we already have parsed data, just go to step 2
1600
+ if (currentParsedData) {
1601
+ goToWizardStep(2);
1602
+ return;
1603
+ }
1604
+
1605
+ const loading = document.getElementById('parse-loading');
1606
+ const errorDiv = document.getElementById('parse-error');
1607
+ const nextBtn = document.getElementById('next-to-step-2');
1608
+
1609
+ loading?.classList.remove('hidden');
1610
+ errorDiv?.classList.add('hidden');
1611
+ if (nextBtn) nextBtn.disabled = true;
1612
+
1613
+ try {
1614
+ const formData = new FormData();
1615
+ formData.append('type', selectedType);
1616
+
1617
+ if (selectedType === 'csv' || selectedType === 'excel') {
1618
+ const fileInput = document.getElementById('fileInput');
1619
+ if (!fileInput?.files[0]) {
1620
+ throw new Error('Please select a file');
1621
+ }
1622
+ formData.append('file', fileInput.files[0]);
1623
+ } else if (selectedType === 'database') {
1624
+ const connection = {
1625
+ type: document.getElementById('dbType')?.value,
1626
+ host: document.getElementById('dbHost')?.value,
1627
+ port: parseInt(document.getElementById('dbPort')?.value),
1628
+ database: document.getElementById('dbName')?.value,
1629
+ username: document.getElementById('dbUser')?.value,
1630
+ password: document.getElementById('dbPassword')?.value
1631
+ };
1632
+ formData.append('connection', JSON.stringify(connection));
1633
+ }
1634
+
1635
+ const response = await fetch('/api/parse', {
1636
+ method: 'POST',
1637
+ body: formData
1638
+ });
1639
+
1640
+ const result = await response.json();
1641
+
1642
+ if (result.success) {
1643
+ currentParsedData = result.data.parsedData;
1644
+ currentDataSource = result.data.dataSource;
1645
+ displayDataPreview(result.data.parsedData);
1646
+
1647
+ // Go to step 2 after successful parse
1648
+ goToWizardStep(2);
1649
+ } else {
1650
+ throw new Error(result.error);
1651
+ }
1652
+ } catch (error) {
1653
+ showError('parse-error', error.message);
1654
+ } finally {
1655
+ loading?.classList.add('hidden');
1656
+ if (nextBtn) nextBtn.disabled = false;
1657
+ }
1658
+ }
1659
+
1660
+ // Wizard Navigation Functions
1661
+ function goToWizardStep(stepNumber) {
1662
+ // Hide all steps
1663
+ document.querySelectorAll('.wizard-step').forEach(step => {
1664
+ step.classList.add('hidden');
1665
+ });
1666
+
1667
+ // Show target step
1668
+ const targetStep = document.getElementById(`wizard-step-${stepNumber}`);
1669
+ if (targetStep) {
1670
+ targetStep.classList.remove('hidden');
1671
+ }
1672
+
1673
+ // Update progress indicators
1674
+ updateWizardProgress(stepNumber);
1675
+
1676
+ currentWizardStep = stepNumber;
1677
+
1678
+ // Enable/disable navigation based on step and data state
1679
+ updateWizardNavigation();
1680
+ }
1681
+
1682
+ function updateWizardProgress(activeStep) {
1683
+ // Reset all step indicators
1684
+ for (let i = 1; i <= 3; i++) {
1685
+ const indicator = document.getElementById(`step-${i}-indicator`);
1686
+ const stepText = indicator?.parentElement.nextElementSibling.querySelector('p');
1687
+
1688
+ if (i < activeStep) {
1689
+ // Completed step
1690
+ indicator?.classList.remove('bg-gray-300', 'text-gray-600', 'bg-blue-500');
1691
+ indicator?.classList.add('bg-green-500', 'text-white');
1692
+ stepText?.classList.remove('text-gray-500', 'text-blue-600');
1693
+ stepText?.classList.add('text-green-600');
1694
+ } else if (i === activeStep) {
1695
+ // Current step
1696
+ indicator?.classList.remove('bg-gray-300', 'text-gray-600', 'bg-green-500');
1697
+ indicator?.classList.add('bg-blue-500', 'text-white');
1698
+ stepText?.classList.remove('text-gray-500', 'text-green-600');
1699
+ stepText?.classList.add('text-blue-600');
1700
+ } else {
1701
+ // Future step
1702
+ indicator?.classList.remove('bg-blue-500', 'bg-green-500', 'text-white');
1703
+ indicator?.classList.add('bg-gray-300', 'text-gray-600');
1704
+ stepText?.classList.remove('text-blue-600', 'text-green-600');
1705
+ stepText?.classList.add('text-gray-500');
1706
+ }
1707
+ }
1708
+
1709
+ // Update progress bars
1710
+ const progress12 = document.getElementById('progress-1-2');
1711
+ const progress23 = document.getElementById('progress-2-3');
1712
+
1713
+ if (activeStep >= 2) {
1714
+ progress12?.classList.remove('bg-gray-200');
1715
+ progress12?.classList.add('bg-green-500');
1716
+ } else {
1717
+ progress12?.classList.remove('bg-green-500');
1718
+ progress12?.classList.add('bg-gray-200');
1719
+ }
1720
+
1721
+ if (activeStep >= 3) {
1722
+ progress23?.classList.remove('bg-gray-200');
1723
+ progress23?.classList.add('bg-green-500');
1724
+ } else {
1725
+ progress23?.classList.remove('bg-green-500');
1726
+ progress23?.classList.add('bg-gray-200');
1727
+ }
1728
+ }
1729
+
1730
+ function updateWizardNavigation() {
1731
+ const nextToStep2 = document.getElementById('next-to-step-2');
1732
+
1733
+ // Only enable step 2 if data source is configured and parsed, or if database connection is ready
1734
+ if (nextToStep2) {
1735
+ const hasDataSource = document.querySelector('input[name="dataSourceType"]:checked');
1736
+ const selectedType = hasDataSource?.value;
1737
+ const hasParsedData = currentParsedData !== null;
1738
+
1739
+ let canProceed = false;
1740
+
1741
+ if (selectedType === 'csv' || selectedType === 'excel') {
1742
+ // For file uploads, need parsed data
1743
+ canProceed = hasParsedData;
1744
+ } else if (selectedType === 'database') {
1745
+ // For database, check if all required fields are filled
1746
+ const dbType = document.getElementById('dbType')?.value;
1747
+ const dbHost = document.getElementById('dbHost')?.value;
1748
+ const dbName = document.getElementById('dbName')?.value;
1749
+ const dbUser = document.getElementById('dbUser')?.value;
1750
+ const dbPassword = document.getElementById('dbPassword')?.value;
1751
+
1752
+ canProceed = dbType && dbHost && dbName && dbUser && dbPassword;
1753
+ }
1754
+
1755
+ nextToStep2.disabled = !hasDataSource || !canProceed;
1756
+ }
1757
+ }
1758
+
1759
+ // Toggle data source fields (updated to enable navigation)
1760
+ function toggleDataSourceFields() {
1761
+ const selectedType = document.querySelector('input[name="dataSourceType"]:checked')?.value;
1762
+ const fileSection = document.getElementById('file-upload-section');
1763
+ const dbSection = document.getElementById('database-section');
1764
+
1765
+ // Hide all sections first
1766
+ fileSection?.classList.add('hidden');
1767
+ dbSection?.classList.add('hidden');
1768
+
1769
+ if (selectedType === 'csv' || selectedType === 'excel') {
1770
+ fileSection?.classList.remove('hidden');
1771
+ } else if (selectedType === 'database') {
1772
+ dbSection?.classList.remove('hidden');
1773
+ updateDefaultPort();
1774
+ }
1775
+
1776
+ // Update wizard navigation state
1777
+ updateWizardNavigation();
1778
+ }
1779
+
1780
+ // Utility functions
1781
+ function showError(elementId, message) {
1782
+ const errorDiv = document.getElementById(elementId);
1783
+ if (errorDiv) {
1784
+ errorDiv.textContent = message;
1785
+ errorDiv.classList.remove('hidden');
1786
+ }
1787
+ }
1788
+
1789
+ function showSuccess(elementId, message) {
1790
+ const successDiv = document.getElementById(elementId);
1791
+ if (successDiv) {
1792
+ successDiv.textContent = message;
1793
+ successDiv.classList.remove('hidden');
1794
+ }
1795
+ }