fa-mcp-sdk 0.4.5 → 0.4.6

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 (153) hide show
  1. package/bin/fa-mcp.js +1040 -1039
  2. package/cli-template/eslint.config.js +16 -136
  3. package/cli-template/package.json +3 -8
  4. package/cli-template/tsconfig.json +1 -0
  5. package/dist/core/_types_/active-directory-config.d.ts.map +1 -1
  6. package/dist/core/_types_/config.d.ts +1 -1
  7. package/dist/core/_types_/config.d.ts.map +1 -1
  8. package/dist/core/_types_/types.d.ts.map +1 -1
  9. package/dist/core/ad/group-checker.d.ts.map +1 -1
  10. package/dist/core/ad/group-checker.js.map +1 -1
  11. package/dist/core/agent-tester/agent-tester-router.d.ts.map +1 -1
  12. package/dist/core/agent-tester/agent-tester-router.js +8 -8
  13. package/dist/core/agent-tester/agent-tester-router.js.map +1 -1
  14. package/dist/core/agent-tester/check-llm.d.ts.map +1 -1
  15. package/dist/core/agent-tester/check-llm.js +1 -1
  16. package/dist/core/agent-tester/check-llm.js.map +1 -1
  17. package/dist/core/agent-tester/services/TesterAgentService.d.ts.map +1 -1
  18. package/dist/core/agent-tester/services/TesterAgentService.js +53 -53
  19. package/dist/core/agent-tester/services/TesterAgentService.js.map +1 -1
  20. package/dist/core/agent-tester/services/TesterMcpClientService.d.ts.map +1 -1
  21. package/dist/core/agent-tester/services/TesterMcpClientService.js +2 -2
  22. package/dist/core/agent-tester/services/TesterMcpClientService.js.map +1 -1
  23. package/dist/core/auth/admin-auth.d.ts.map +1 -1
  24. package/dist/core/auth/admin-auth.js +3 -3
  25. package/dist/core/auth/admin-auth.js.map +1 -1
  26. package/dist/core/auth/basic.d.ts.map +1 -1
  27. package/dist/core/auth/basic.js.map +1 -1
  28. package/dist/core/auth/jwt.d.ts.map +1 -1
  29. package/dist/core/auth/jwt.js +6 -16
  30. package/dist/core/auth/jwt.js.map +1 -1
  31. package/dist/core/auth/middleware.d.ts.map +1 -1
  32. package/dist/core/auth/middleware.js +3 -2
  33. package/dist/core/auth/middleware.js.map +1 -1
  34. package/dist/core/auth/multi-auth.d.ts +0 -3
  35. package/dist/core/auth/multi-auth.d.ts.map +1 -1
  36. package/dist/core/auth/multi-auth.js +10 -7
  37. package/dist/core/auth/multi-auth.js.map +1 -1
  38. package/dist/core/auth/permanent.d.ts.map +1 -1
  39. package/dist/core/auth/permanent.js +1 -1
  40. package/dist/core/auth/permanent.js.map +1 -1
  41. package/dist/core/auth/token-generator/ntlm/ntlm-auth-options.d.ts.map +1 -1
  42. package/dist/core/auth/token-generator/ntlm/ntlm-auth-options.js +2 -2
  43. package/dist/core/auth/token-generator/ntlm/ntlm-auth-options.js.map +1 -1
  44. package/dist/core/auth/token-generator/ntlm/ntlm-domain-config.d.ts.map +1 -1
  45. package/dist/core/auth/token-generator/ntlm/ntlm-domain-config.js +1 -1
  46. package/dist/core/auth/token-generator/ntlm/ntlm-domain-config.js.map +1 -1
  47. package/dist/core/auth/token-generator/ntlm/ntlm-integration.d.ts.map +1 -1
  48. package/dist/core/auth/token-generator/ntlm/ntlm-integration.js +1 -1
  49. package/dist/core/auth/token-generator/ntlm/ntlm-integration.js.map +1 -1
  50. package/dist/core/auth/token-generator/ntlm/ntlm-templates.d.ts.map +1 -1
  51. package/dist/core/auth/token-generator/ntlm/ntlm-templates.js +222 -221
  52. package/dist/core/auth/token-generator/ntlm/ntlm-templates.js.map +1 -1
  53. package/dist/core/auth/token-generator/server.d.ts.map +1 -1
  54. package/dist/core/auth/token-generator/server.js +8 -8
  55. package/dist/core/auth/token-generator/server.js.map +1 -1
  56. package/dist/core/bootstrap/init-config.d.ts.map +1 -1
  57. package/dist/core/bootstrap/init-config.js +4 -4
  58. package/dist/core/bootstrap/init-config.js.map +1 -1
  59. package/dist/core/bootstrap/startup-info.d.ts.map +1 -1
  60. package/dist/core/bootstrap/startup-info.js +4 -4
  61. package/dist/core/bootstrap/startup-info.js.map +1 -1
  62. package/dist/core/cache/cache.d.ts.map +1 -1
  63. package/dist/core/cache/cache.js +3 -3
  64. package/dist/core/cache/cache.js.map +1 -1
  65. package/dist/core/consul/access-points-updater.d.ts.map +1 -1
  66. package/dist/core/consul/access-points-updater.js +3 -3
  67. package/dist/core/consul/access-points-updater.js.map +1 -1
  68. package/dist/core/consul/deregister.d.ts.map +1 -1
  69. package/dist/core/consul/deregister.js +1 -1
  70. package/dist/core/consul/deregister.js.map +1 -1
  71. package/dist/core/consul/get-consul-api.d.ts.map +1 -1
  72. package/dist/core/consul/get-consul-api.js +3 -3
  73. package/dist/core/consul/get-consul-api.js.map +1 -1
  74. package/dist/core/db/pg-db.d.ts +1 -1
  75. package/dist/core/db/pg-db.d.ts.map +1 -1
  76. package/dist/core/db/pg-db.js +2 -2
  77. package/dist/core/db/pg-db.js.map +1 -1
  78. package/dist/core/debug.js +1 -1
  79. package/dist/core/debug.js.map +1 -1
  80. package/dist/core/init-mcp-server.d.ts.map +1 -1
  81. package/dist/core/init-mcp-server.js +9 -9
  82. package/dist/core/init-mcp-server.js.map +1 -1
  83. package/dist/core/logger.d.ts.map +1 -1
  84. package/dist/core/logger.js +3 -3
  85. package/dist/core/logger.js.map +1 -1
  86. package/dist/core/mcp/create-mcp-server.d.ts.map +1 -1
  87. package/dist/core/mcp/create-mcp-server.js +1 -1
  88. package/dist/core/mcp/create-mcp-server.js.map +1 -1
  89. package/dist/core/mcp/prompts.d.ts.map +1 -1
  90. package/dist/core/mcp/prompts.js +1 -3
  91. package/dist/core/mcp/prompts.js.map +1 -1
  92. package/dist/core/mcp/resources.d.ts.map +1 -1
  93. package/dist/core/mcp/resources.js +8 -10
  94. package/dist/core/mcp/resources.js.map +1 -1
  95. package/dist/core/mcp/server-stdio.d.ts.map +1 -1
  96. package/dist/core/mcp/server-stdio.js.map +1 -1
  97. package/dist/core/utils/formatToolResult.d.ts.map +1 -1
  98. package/dist/core/utils/formatToolResult.js +1 -3
  99. package/dist/core/utils/formatToolResult.js.map +1 -1
  100. package/dist/core/utils/port-checker.d.ts.map +1 -1
  101. package/dist/core/utils/port-checker.js +1 -1
  102. package/dist/core/utils/port-checker.js.map +1 -1
  103. package/dist/core/utils/rate-limit.js +2 -2
  104. package/dist/core/utils/testing/McpSseClient.d.ts.map +1 -1
  105. package/dist/core/utils/testing/McpSseClient.js.map +1 -1
  106. package/dist/core/utils/testing/McpStdioClient.d.ts.map +1 -1
  107. package/dist/core/utils/testing/McpStdioClient.js.map +1 -1
  108. package/dist/core/utils/utils.d.ts.map +1 -1
  109. package/dist/core/utils/utils.js.map +1 -1
  110. package/dist/core/web/admin-router.d.ts.map +1 -1
  111. package/dist/core/web/admin-router.js +4 -4
  112. package/dist/core/web/admin-router.js.map +1 -1
  113. package/dist/core/web/cors.d.ts.map +1 -1
  114. package/dist/core/web/cors.js.map +1 -1
  115. package/dist/core/web/favicon-svg.d.ts.map +1 -1
  116. package/dist/core/web/favicon-svg.js.map +1 -1
  117. package/dist/core/web/home-api.d.ts.map +1 -1
  118. package/dist/core/web/home-api.js +4 -4
  119. package/dist/core/web/home-api.js.map +1 -1
  120. package/dist/core/web/openapi.d.ts.map +1 -1
  121. package/dist/core/web/openapi.js.map +1 -1
  122. package/dist/core/web/server-http.d.ts.map +1 -1
  123. package/dist/core/web/server-http.js +20 -22
  124. package/dist/core/web/server-http.js.map +1 -1
  125. package/dist/core/web/static/agent-tester/script.js +1503 -1513
  126. package/dist/core/web/static/home/script.js +646 -646
  127. package/dist/core/web/static/token-gen/script.js +561 -561
  128. package/dist/core/web/svg-icons.d.ts.map +1 -1
  129. package/dist/core/web/svg-icons.js +1 -1
  130. package/dist/core/web/svg-icons.js.map +1 -1
  131. package/package.json +2 -6
  132. package/scripts/copy-static.js +31 -31
  133. package/scripts/kill-port.js +107 -107
  134. package/scripts/npm/patch_node_modules.js +8 -8
  135. package/scripts/npm/run.js +31 -31
  136. package/scripts/remove-nul.js +53 -53
  137. package/scripts/update-doc.js +18 -18
  138. package/src/template/_types_/custom-config.ts +83 -83
  139. package/src/template/api/router.ts +86 -89
  140. package/src/template/custom-resources.ts +11 -11
  141. package/src/template/prompts/agent-brief.ts +8 -8
  142. package/src/template/prompts/agent-prompt.ts +10 -10
  143. package/src/template/prompts/custom-prompts.ts +12 -12
  144. package/src/template/start.ts +71 -72
  145. package/src/template/tools/handle-tool-call.ts +57 -56
  146. package/src/template/tools/tools.ts +89 -88
  147. package/src/tests/jest-simple-reporter.js +10 -10
  148. package/src/tests/mcp/sse/test-sse-npm-package.js +96 -96
  149. package/src/tests/mcp/test-cases.js +143 -143
  150. package/src/tests/mcp/test-http.js +76 -75
  151. package/src/tests/mcp/test-sse.js +80 -79
  152. package/src/tests/mcp/test-stdio.js +83 -81
  153. package/src/tests/utils.ts +157 -156
@@ -1,646 +1,646 @@
1
- // Store data globally
2
- let toolsData = [];
3
- let resourcesData = [];
4
- let promptsData = [];
5
-
6
- // Set primary color CSS variable
7
- function setPrimaryColor (color) {
8
- if (color) {
9
- document.documentElement.style.setProperty('--primary-color', color);
10
- }
11
- }
12
-
13
- // Set favicon dynamically
14
- function setFavicon (svgContent) {
15
- if (!svgContent) {return;}
16
-
17
- const encoded = encodeURIComponent(svgContent)
18
- .replace(/'/g, '%27')
19
- .replace(/"/g, '%22');
20
-
21
- const link = document.querySelector('link[rel="icon"]') || document.createElement('link');
22
- link.rel = 'icon';
23
- link.type = 'image/svg+xml';
24
- link.href = 'data:image/svg+xml,' + encoded;
25
-
26
- if (!document.querySelector('link[rel="icon"]')) {
27
- document.head.appendChild(link);
28
- }
29
- }
30
-
31
- // Set header icon dynamically
32
- function setHeaderIcon (svgContent) {
33
- const iconContainer = document.getElementById('serviceIcon');
34
- if (iconContainer && svgContent) {
35
- iconContainer.innerHTML = svgContent;
36
- }
37
- }
38
-
39
- // Render page info
40
- function renderPageInfo (data) {
41
- // Service title
42
- const titleEl = document.getElementById('serviceTitle');
43
- if (titleEl) {
44
- titleEl.innerHTML = '<span class="MCPServer">MCP Server</span> ' + data.serviceTitle;
45
- }
46
-
47
- // Document title
48
- document.title = data.serviceTitle + ' MCP Server';
49
-
50
- // Description
51
- const descEl = document.getElementById('serviceDescription');
52
- if (descEl) {descEl.textContent = data.description;}
53
-
54
- // Version
55
- const versionEl = document.getElementById('serviceVersion');
56
- if (versionEl) {versionEl.textContent = data.version;}
57
-
58
- // Uptime
59
- const uptimeEl = document.getElementById('serviceUptime');
60
- if (uptimeEl) {uptimeEl.textContent = data.uptime;}
61
-
62
- // Tools count
63
- const toolsEl = document.getElementById('toolsCount');
64
- if (toolsEl) {toolsEl.textContent = data.toolsCount + ' available';}
65
-
66
- // Resources count
67
- const resourcesEl = document.getElementById('resourcesCount');
68
- if (resourcesEl) {resourcesEl.textContent = data.resourcesCount + ' available';}
69
-
70
- // Prompts count
71
- const promptsEl = document.getElementById('promptsCount');
72
- if (promptsEl) {promptsEl.textContent = data.promptsCount + ' available';}
73
-
74
- // Database info
75
- const dbSection = document.getElementById('dbSection');
76
- if (dbSection) {
77
- if (data.db) {
78
- dbSection.style.display = 'block';
79
- const dbValue = document.getElementById('dbValue');
80
- const dbStatus = document.getElementById('dbStatus');
81
- if (dbValue) {dbValue.textContent = data.db.connection + ' • ';}
82
- if (dbStatus) {
83
- dbStatus.textContent = data.db.status;
84
- dbStatus.className = 'value ' + data.db.status;
85
- }
86
- } else {
87
- dbSection.style.display = 'none';
88
- }
89
- }
90
-
91
- // OpenAPI info
92
- const swaggerSection = document.getElementById('swaggerSection');
93
- if (swaggerSection) {
94
- swaggerSection.style.display = data.openAPI ? 'block' : 'none';
95
- }
96
-
97
- // Auth info
98
- const mcpAuthEl = document.getElementById('mcpAuthValue');
99
- if (mcpAuthEl) {mcpAuthEl.textContent = data.mcpAuth || 'disabled';}
100
-
101
- const adminAuthEl = document.getElementById('adminAuthValue');
102
- if (adminAuthEl) {adminAuthEl.textContent = data.adminAuth || 'disabled';}
103
-
104
- // Consul info
105
- const consulSection = document.getElementById('consulSection');
106
- if (consulSection) {
107
- if (data.consul && data.consul.id) {
108
- consulSection.style.display = 'block';
109
- const consulLink = document.getElementById('consulLink');
110
- if (consulLink) {
111
- consulLink.href = data.consul.url;
112
- consulLink.textContent = data.consul.id;
113
- }
114
- } else {
115
- consulSection.style.display = 'none';
116
- }
117
- }
118
-
119
- // Footer
120
- const footerContent = document.getElementById('footerContent');
121
- if (footerContent && data.footer) {
122
- footerContent.innerHTML = data.footer;
123
- }
124
- }
125
-
126
- // Load page data from API
127
- async function loadPageData () {
128
- try {
129
- const response = await fetch('/api/home-info');
130
- if (!response.ok) {
131
- throw new Error('HTTP ' + response.status);
132
- }
133
-
134
- const data = await response.json();
135
-
136
- // Set theme color
137
- setPrimaryColor(data.primaryColor);
138
-
139
- // Set favicon and header icon (logo)
140
- setFavicon(data.logoSvg);
141
- setHeaderIcon(data.logoSvg);
142
-
143
- // Store MCP data
144
- toolsData = data.tools || [];
145
- resourcesData = data.resources || [];
146
- promptsData = data.prompts || [];
147
-
148
- // Render page info
149
- renderPageInfo(data);
150
-
151
- } catch (error) {
152
- console.error('Error loading page data:', error);
153
- }
154
- }
155
-
156
- // eslint-disable-next-line unused-imports/no-unused-vars
157
- function openModal (sectionName) {
158
- const modal = document.getElementById(sectionName + '-modal');
159
- const tableBody = document.getElementById(sectionName + '-table').querySelector('tbody');
160
-
161
- // Show loading state
162
- tableBody.innerHTML = '<tr><td colspan="100%" class="loading-cell"><div class="loading-spinner"></div> Loading...</td></tr>';
163
- modal.style.display = 'flex';
164
-
165
- // Load data with small delay to show loading animation
166
- setTimeout(function () {
167
- loadTableData(sectionName);
168
- }, 300);
169
- }
170
-
171
- function closeModal (sectionName) {
172
- const modal = document.getElementById(sectionName + '-modal');
173
- modal.style.display = 'none';
174
- }
175
-
176
- function loadTableData (sectionName) {
177
- const tableBody = document.getElementById(sectionName + '-table').querySelector('tbody');
178
- let data, html;
179
-
180
- switch (sectionName) {
181
- case 'tools':
182
- data = toolsData;
183
- html = generateToolsTableRows(data);
184
- break;
185
- case 'resources':
186
- data = resourcesData;
187
- html = generateResourcesTableRows(data);
188
- break;
189
- case 'prompts':
190
- data = promptsData;
191
- html = generatePromptsTableRows(data);
192
- break;
193
- }
194
-
195
- tableBody.innerHTML = html;
196
- }
197
-
198
- function generateToolsTableRows (tools) {
199
- if (!tools || tools.length === 0) {
200
- return '<tr><td colspan="3" class="loading-cell">No tools available</td></tr>';
201
- }
202
- return tools.map((tool, index) =>
203
- `<tr>
204
- <td><code>${tool.name}</code></td>
205
- <td>${tool.annotations?.title || tool.description}</td>
206
- <td>
207
- <a class="detail-link" id="tools-toggle-${index}" onclick="toggleDetails('tools', ${index})">details</a>
208
- </td>
209
- </tr>
210
- <tr id="tools-detail-${index}" class="detail-row" style="display: none;">
211
- <td colspan="3">
212
- <div class="detail-content">
213
- <div class="loading-spinner" style="display: none;"></div>
214
- <pre class="json-content" style="display: none;"></pre>
215
- </div>
216
- </td>
217
- </tr>`
218
- ).join('');
219
- }
220
-
221
- function generateResourcesTableRows (resources) {
222
- if (!resources || resources.length === 0) {
223
- return '<tr><td colspan="5" class="loading-cell">No resources available</td></tr>';
224
- }
225
- return resources.map((resource, index) =>
226
- `<tr>
227
- <td><code>${resource.uri}</code></td>
228
- <td>${resource.name}</td>
229
- <td>${resource.description}</td>
230
- <td><code>${resource.mimeType}</code></td>
231
- <td>
232
- <a class="detail-link" id="resources-toggle-details-${index}" onclick="toggleResourceDetails('resources', ${index}, 'details')">details</a>
233
- /
234
- <a class="detail-link" id="resources-toggle-resource-${index}" onclick="toggleResourceDetails('resources', ${index}, 'resource')">resource</a>
235
- </td>
236
- </tr>
237
- <tr id="resources-detail-${index}" class="detail-row" style="display: none;">
238
- <td colspan="5">
239
- <div class="detail-content">
240
- <div class="loading-spinner"></div>
241
- <pre class="json-content" style="display: none;"></pre>
242
- <div class="resource-content" style="display: none;"></div>
243
- </div>
244
- </td>
245
- </tr>`
246
- ).join('');
247
- }
248
-
249
- function generatePromptsTableRows (prompts) {
250
- if (!prompts || prompts.length === 0) {
251
- return '<tr><td colspan="2" class="loading-cell">No prompts available</td></tr>';
252
- }
253
- return prompts.map((prompt, index) =>
254
- `<tr>
255
- <td><code>${prompt.name}</code></td>
256
- <td>
257
- <a class="detail-link" id="prompts-toggle-details-${index}" onclick="togglePromptDetails('prompts', ${index}, 'details')">details</a>
258
- /
259
- <a class="detail-link" id="prompts-toggle-prompt-${index}" onclick="togglePromptDetails('prompts', ${index}, 'prompt')">prompt</a>
260
- </td>
261
- </tr>
262
- <tr id="prompts-detail-${index}" class="detail-row" style="display: none;">
263
- <td colspan="2">
264
- <div class="detail-content">
265
- <div class="loading-spinner"></div>
266
- <pre class="json-content" style="display: none;"></pre>
267
- <div class="prompt-content" style="display: none;"></div>
268
- </div>
269
- </td>
270
- </tr>`
271
- ).join('');
272
- }
273
-
274
- // eslint-disable-next-line unused-imports/no-unused-vars
275
- function toggleDetails (sectionName, index) {
276
- const detailRow = document.getElementById(sectionName + '-detail-' + index);
277
- const toggleLink = document.getElementById(sectionName + '-toggle-' + index);
278
- const loadingSpinner = detailRow.querySelector('.loading-spinner');
279
- const jsonContent = detailRow.querySelector('.json-content');
280
-
281
- if (detailRow.style.display === 'none') {
282
- // Show the detail row with loading state
283
- detailRow.style.display = 'table-row';
284
- toggleLink.textContent = 'hide';
285
- loadingSpinner.style.display = 'block';
286
- jsonContent.style.display = 'none';
287
-
288
- // Simulate loading delay and show content
289
- setTimeout(() => {
290
- let data;
291
- let textContent;
292
- switch (sectionName) {
293
- case 'tools':
294
- data = {
295
- name: toolsData[index].name,
296
- description: toolsData[index].description,
297
- inputSchema: toolsData[index].inputSchema,
298
- annotations: toolsData[index].annotations
299
- };
300
- textContent = JSON.stringify(data, null, 2);
301
- break;
302
- case 'resources':
303
- data = resourcesData[index].content || resourcesData[index];
304
- textContent = JSON.stringify(data, null, 2);
305
- break;
306
- case 'prompts':
307
- data = promptsData[index];
308
- textContent = JSON.stringify(data, null, 2);
309
- break;
310
- }
311
-
312
- loadingSpinner.style.display = 'none';
313
- jsonContent.style.display = 'block';
314
- jsonContent.textContent = textContent;
315
- addCopyButton(jsonContent);
316
- }, 500);
317
- } else {
318
- // Hide the detail row
319
- detailRow.style.display = 'none';
320
- toggleLink.textContent = 'details';
321
- }
322
- }
323
-
324
- // Handle prompt details and prompt content display
325
- // eslint-disable-next-line unused-imports/no-unused-vars
326
- async function togglePromptDetails (sectionName, index, displayType) {
327
- const detailRow = document.getElementById(sectionName + '-detail-' + index);
328
- const toggleLinkDetails = document.getElementById(sectionName + '-toggle-details-' + index);
329
- const toggleLinkPrompt = document.getElementById(sectionName + '-toggle-prompt-' + index);
330
- const loadingSpinner = detailRow.querySelector('.loading-spinner');
331
- const jsonContent = detailRow.querySelector('.json-content');
332
- const promptContent = detailRow.querySelector('.prompt-content');
333
-
334
- const isCurrentlyHidden = detailRow.style.display === 'none';
335
- const currentToggleLink = displayType === 'details' ? toggleLinkDetails : toggleLinkPrompt;
336
- const otherToggleLink = displayType === 'details' ? toggleLinkPrompt : toggleLinkDetails;
337
-
338
- if (isCurrentlyHidden || currentToggleLink.textContent === displayType) {
339
- // Show the detail row with loading state
340
- detailRow.style.display = 'table-row';
341
- currentToggleLink.textContent = 'hide';
342
- otherToggleLink.textContent = displayType === 'details' ? 'prompt' : 'details';
343
- loadingSpinner.style.display = 'block';
344
- jsonContent.style.display = 'none';
345
- promptContent.style.display = 'none';
346
-
347
- if (displayType === 'details') {
348
- // Show JSON details
349
- setTimeout(() => {
350
- const data = promptsData[index];
351
- const textContent = JSON.stringify(data, null, 2);
352
- loadingSpinner.style.display = 'none';
353
- jsonContent.style.display = 'block';
354
- jsonContent.textContent = textContent;
355
- addCopyButton(jsonContent);
356
- }, 300);
357
- } else {
358
- // Fetch and show prompt content
359
- try {
360
- const promptName = promptsData[index].name;
361
- const response = await fetch('/mcp', {
362
- method: 'POST',
363
- headers: { 'Content-Type': 'application/json' },
364
- body: JSON.stringify({
365
- jsonrpc: '2.0',
366
- id: Date.now(),
367
- method: 'prompts/get',
368
- params: { name: promptName }
369
- })
370
- });
371
-
372
- if (!response.ok) {
373
- let errorData = '';
374
- try {
375
- errorData = await response.text();
376
- } catch {
377
- //
378
- }
379
- errorData = [response.statusText || '', errorData].join('. ');
380
- throw new Error('HTTP ' + response.status + (errorData ? ': ' + errorData : ''));
381
- }
382
-
383
- const result = await response.json();
384
- const messages = result.result?.messages || [];
385
- let promptText = '';
386
-
387
- messages.forEach((msg, i) => {
388
- if (i > 0) {promptText += '\n\n---\n\n';}
389
- promptText += 'Role: ' + msg.role + '\n\n';
390
- if (typeof msg.content === 'string') {
391
- promptText += msg.content;
392
- } else if (msg.content?.text) {
393
- promptText += msg.content.text;
394
- } else {
395
- promptText += JSON.stringify(msg.content, null, 2);
396
- }
397
- });
398
-
399
- loadingSpinner.style.display = 'none';
400
- promptContent.style.display = 'block';
401
- promptContent.innerHTML = '<pre class="json-content">' + escapeHtml(promptText) + '</pre>';
402
- addCopyButton(promptContent.querySelector('.json-content'));
403
- } catch (error) {
404
- loadingSpinner.style.display = 'none';
405
- promptContent.style.display = 'block';
406
- promptContent.innerHTML = '<div class="error-message">Failed to load prompt: ' + escapeHtml(error.message) + '</div>';
407
- }
408
- }
409
- } else {
410
- // Hide the detail row
411
- detailRow.style.display = 'none';
412
- toggleLinkDetails.textContent = 'details';
413
- toggleLinkPrompt.textContent = 'prompt';
414
- }
415
- }
416
-
417
- // Handle resource details and resource content display
418
- // eslint-disable-next-line unused-imports/no-unused-vars
419
- async function toggleResourceDetails (sectionName, index, displayType) {
420
- const detailRow = document.getElementById(sectionName + '-detail-' + index);
421
- const toggleLinkDetails = document.getElementById(sectionName + '-toggle-details-' + index);
422
- const toggleLinkResource = document.getElementById(sectionName + '-toggle-resource-' + index);
423
- const loadingSpinner = detailRow.querySelector('.loading-spinner');
424
- const jsonContent = detailRow.querySelector('.json-content');
425
- const resourceContent = detailRow.querySelector('.resource-content');
426
-
427
- const isCurrentlyHidden = detailRow.style.display === 'none';
428
- const currentToggleLink = displayType === 'details' ? toggleLinkDetails : toggleLinkResource;
429
- const otherToggleLink = displayType === 'details' ? toggleLinkResource : toggleLinkDetails;
430
-
431
- if (isCurrentlyHidden || currentToggleLink.textContent === displayType) {
432
- // Show the detail row with loading state
433
- detailRow.style.display = 'table-row';
434
- currentToggleLink.textContent = 'hide';
435
- otherToggleLink.textContent = displayType === 'details' ? 'resource' : 'details';
436
- loadingSpinner.style.display = 'block';
437
- jsonContent.style.display = 'none';
438
- resourceContent.style.display = 'none';
439
-
440
- if (displayType === 'details') {
441
- // Show JSON details
442
- setTimeout(() => {
443
- const data = resourcesData[index];
444
- const textContent = JSON.stringify(data, null, 2);
445
- loadingSpinner.style.display = 'none';
446
- jsonContent.style.display = 'block';
447
- jsonContent.textContent = textContent;
448
- addCopyButton(jsonContent);
449
- }, 300);
450
- } else {
451
- // Fetch and show resource content
452
- try {
453
- const resourceUri = resourcesData[index].uri;
454
- const response = await fetch('/mcp', {
455
- method: 'POST',
456
- headers: { 'Content-Type': 'application/json' },
457
- body: JSON.stringify({
458
- jsonrpc: '2.0',
459
- id: Date.now(),
460
- method: 'resources/read',
461
- params: { uri: resourceUri }
462
- })
463
- });
464
-
465
- if (!response.ok) {
466
- let errorData = '';
467
- try {
468
- errorData = await response.text();
469
- } catch {
470
- //
471
- }
472
- errorData = [response.statusText || '', errorData].join('. ');
473
- throw new Error('HTTP ' + response.status + (errorData ? ': ' + errorData : ''));
474
- }
475
-
476
- const result = await response.json();
477
- const contents = result.result?.contents || [];
478
- let resourceText = '';
479
-
480
- contents.forEach((content, i) => {
481
- if (i > 0) {resourceText += '\n\n---\n\n';}
482
- resourceText += 'URI: ' + content.uri + '\n';
483
- resourceText += 'MIME Type: ' + content.mimeType + '\n\n';
484
-
485
- if (content.text) {
486
- let processedText = content.text;
487
-
488
- // Handle JSON content more intelligently
489
- if (content.mimeType === 'application/json') {
490
- if (typeof processedText !== 'string') {
491
- processedText = JSON.stringify(processedText, null, 2);
492
- }
493
- }
494
- resourceText += processedText;
495
- } else if (content.blob) {
496
- resourceText += '[Binary content: ' + content.blob.length + ' bytes]';
497
- } else {
498
- resourceText += JSON.stringify(content, null, 2);
499
- }
500
- });
501
-
502
- loadingSpinner.style.display = 'none';
503
- resourceContent.style.display = 'block';
504
- resourceContent.innerHTML = '<pre class="json-content">' + escapeHtml(resourceText) + '</pre>';
505
- addCopyButton(resourceContent.querySelector('.json-content'));
506
- } catch (error) {
507
- loadingSpinner.style.display = 'none';
508
- resourceContent.style.display = 'block';
509
- resourceContent.innerHTML = '<div class="error-message">Failed to load resource: ' + escapeHtml(error.message) + '</div>';
510
- }
511
- }
512
- } else {
513
- // Hide the detail row
514
- detailRow.style.display = 'none';
515
- toggleLinkDetails.textContent = 'details';
516
- toggleLinkResource.textContent = 'resource';
517
- }
518
- }
519
-
520
- // Health Check Modal
521
- // eslint-disable-next-line unused-imports/no-unused-vars
522
- async function openHealthCheckModal () {
523
- const modal = document.getElementById('health-modal');
524
- const loading = document.getElementById('health-loading');
525
- const result = document.getElementById('health-result');
526
- const error = document.getElementById('health-error');
527
-
528
- // Show modal with loading state
529
- modal.style.display = 'flex';
530
- loading.style.display = 'block';
531
- result.style.display = 'none';
532
- error.style.display = 'none';
533
-
534
- try {
535
- const response = await fetch('/health');
536
-
537
- if (!response.ok) {
538
- let errorData = '';
539
- try {
540
- errorData = await response.text();
541
- } catch {
542
- //
543
- }
544
- errorData = [response.statusText || '', errorData].join('. ');
545
- throw new Error('HTTP ' + response.status + (errorData ? ': ' + errorData : ''));
546
- }
547
-
548
- const data = await response.json();
549
-
550
- // Hide loading and show result
551
- loading.style.display = 'none';
552
- result.style.display = 'block';
553
- result.textContent = JSON.stringify(data, null, 2);
554
- addCopyButton(result);
555
- } catch (err) {
556
- // Hide loading and show error
557
- loading.style.display = 'none';
558
- error.style.display = 'block';
559
- error.textContent = 'Error: ' + (err.message || 'Failed to fetch health check data');
560
- }
561
- }
562
-
563
- // Escape HTML to prevent XSS
564
- function escapeHtml (text) {
565
- const div = document.createElement('div');
566
- div.textContent = text;
567
- return div.innerHTML;
568
- }
569
-
570
- // Copy to clipboard functionality
571
- function addCopyButton (contentElement) {
572
- if (!contentElement || contentElement.hasAttribute('data-copy-added')) {
573
- return;
574
- }
575
-
576
- contentElement.setAttribute('data-copy-added', 'true');
577
-
578
- const copyButton = document.createElement('button');
579
- copyButton.className = 'copy-button';
580
- copyButton.innerHTML = '📋';
581
- copyButton.title = 'Copy to clipboard';
582
- copyButton.setAttribute('aria-label', 'Copy to clipboard');
583
-
584
- const notification = document.createElement('div');
585
- notification.className = 'copy-notification';
586
- notification.textContent = 'Copied';
587
-
588
- contentElement.appendChild(copyButton);
589
- contentElement.appendChild(notification);
590
-
591
- copyButton.addEventListener('click', async function () {
592
- let textToCopy = contentElement.textContent || contentElement.innerText;
593
- textToCopy = textToCopy.replace(/📋Copied/, '');
594
- try {
595
- await navigator.clipboard.writeText(textToCopy);
596
-
597
- // Show notification
598
- notification.classList.add('show');
599
-
600
- // Hide notification after 1 second
601
- setTimeout(() => {
602
- notification.classList.remove('show');
603
- }, 1000);
604
-
605
- } catch {
606
- // Fallback for browsers that don't support clipboard API
607
- const textArea = document.createElement('textarea');
608
- textArea.value = textToCopy;
609
- textArea.style.position = 'fixed';
610
- textArea.style.opacity = '0';
611
- document.body.appendChild(textArea);
612
- textArea.focus();
613
- textArea.select();
614
-
615
- try {
616
- document.execCommand('copy');
617
-
618
- // Show notification
619
- notification.classList.add('show');
620
-
621
- // Hide notification after 1 second
622
- setTimeout(() => {
623
- notification.classList.remove('show');
624
- }, 1000);
625
- } catch (fallbackErr) {
626
- console.error('Failed to copy text:', fallbackErr);
627
- }
628
-
629
- document.body.removeChild(textArea);
630
- }
631
- });
632
- }
633
-
634
- // Close modal when clicking outside
635
- document.addEventListener('click', function (event) {
636
- if (event.target.classList.contains('modal-overlay')) {
637
- const modalId = event.target.id;
638
- const sectionName = modalId.replace('-modal', '');
639
- closeModal(sectionName);
640
- }
641
- });
642
-
643
- // Initialize on page load
644
- document.addEventListener('DOMContentLoaded', function () {
645
- loadPageData();
646
- });
1
+ // Store data globally
2
+ let toolsData = [];
3
+ let resourcesData = [];
4
+ let promptsData = [];
5
+
6
+ // Set primary color CSS variable
7
+ function setPrimaryColor (color) {
8
+ if (color) {
9
+ document.documentElement.style.setProperty('--primary-color', color);
10
+ }
11
+ }
12
+
13
+ // Set favicon dynamically
14
+ function setFavicon (svgContent) {
15
+ if (!svgContent) {return;}
16
+
17
+ const encoded = encodeURIComponent(svgContent)
18
+ .replace(/'/g, '%27')
19
+ .replace(/"/g, '%22');
20
+
21
+ const link = document.querySelector('link[rel="icon"]') || document.createElement('link');
22
+ link.rel = 'icon';
23
+ link.type = 'image/svg+xml';
24
+ link.href = 'data:image/svg+xml,' + encoded;
25
+
26
+ if (!document.querySelector('link[rel="icon"]')) {
27
+ document.head.appendChild(link);
28
+ }
29
+ }
30
+
31
+ // Set header icon dynamically
32
+ function setHeaderIcon (svgContent) {
33
+ const iconContainer = document.getElementById('serviceIcon');
34
+ if (iconContainer && svgContent) {
35
+ iconContainer.innerHTML = svgContent;
36
+ }
37
+ }
38
+
39
+ // Render page info
40
+ function renderPageInfo (data) {
41
+ // Service title
42
+ const titleEl = document.getElementById('serviceTitle');
43
+ if (titleEl) {
44
+ titleEl.innerHTML = '<span class="MCPServer">MCP Server</span> ' + data.serviceTitle;
45
+ }
46
+
47
+ // Document title
48
+ document.title = data.serviceTitle + ' MCP Server';
49
+
50
+ // Description
51
+ const descEl = document.getElementById('serviceDescription');
52
+ if (descEl) {descEl.textContent = data.description;}
53
+
54
+ // Version
55
+ const versionEl = document.getElementById('serviceVersion');
56
+ if (versionEl) {versionEl.textContent = data.version;}
57
+
58
+ // Uptime
59
+ const uptimeEl = document.getElementById('serviceUptime');
60
+ if (uptimeEl) {uptimeEl.textContent = data.uptime;}
61
+
62
+ // Tools count
63
+ const toolsEl = document.getElementById('toolsCount');
64
+ if (toolsEl) {toolsEl.textContent = data.toolsCount + ' available';}
65
+
66
+ // Resources count
67
+ const resourcesEl = document.getElementById('resourcesCount');
68
+ if (resourcesEl) {resourcesEl.textContent = data.resourcesCount + ' available';}
69
+
70
+ // Prompts count
71
+ const promptsEl = document.getElementById('promptsCount');
72
+ if (promptsEl) {promptsEl.textContent = data.promptsCount + ' available';}
73
+
74
+ // Database info
75
+ const dbSection = document.getElementById('dbSection');
76
+ if (dbSection) {
77
+ if (data.db) {
78
+ dbSection.style.display = 'block';
79
+ const dbValue = document.getElementById('dbValue');
80
+ const dbStatus = document.getElementById('dbStatus');
81
+ if (dbValue) {dbValue.textContent = data.db.connection + ' • ';}
82
+ if (dbStatus) {
83
+ dbStatus.textContent = data.db.status;
84
+ dbStatus.className = 'value ' + data.db.status;
85
+ }
86
+ } else {
87
+ dbSection.style.display = 'none';
88
+ }
89
+ }
90
+
91
+ // OpenAPI info
92
+ const swaggerSection = document.getElementById('swaggerSection');
93
+ if (swaggerSection) {
94
+ swaggerSection.style.display = data.openAPI ? 'block' : 'none';
95
+ }
96
+
97
+ // Auth info
98
+ const mcpAuthEl = document.getElementById('mcpAuthValue');
99
+ if (mcpAuthEl) {mcpAuthEl.textContent = data.mcpAuth || 'disabled';}
100
+
101
+ const adminAuthEl = document.getElementById('adminAuthValue');
102
+ if (adminAuthEl) {adminAuthEl.textContent = data.adminAuth || 'disabled';}
103
+
104
+ // Consul info
105
+ const consulSection = document.getElementById('consulSection');
106
+ if (consulSection) {
107
+ if (data.consul && data.consul.id) {
108
+ consulSection.style.display = 'block';
109
+ const consulLink = document.getElementById('consulLink');
110
+ if (consulLink) {
111
+ consulLink.href = data.consul.url;
112
+ consulLink.textContent = data.consul.id;
113
+ }
114
+ } else {
115
+ consulSection.style.display = 'none';
116
+ }
117
+ }
118
+
119
+ // Footer
120
+ const footerContent = document.getElementById('footerContent');
121
+ if (footerContent && data.footer) {
122
+ footerContent.innerHTML = data.footer;
123
+ }
124
+ }
125
+
126
+ // Load page data from API
127
+ async function loadPageData () {
128
+ try {
129
+ const response = await fetch('/api/home-info');
130
+ if (!response.ok) {
131
+ throw new Error('HTTP ' + response.status);
132
+ }
133
+
134
+ const data = await response.json();
135
+
136
+ // Set theme color
137
+ setPrimaryColor(data.primaryColor);
138
+
139
+ // Set favicon and header icon (logo)
140
+ setFavicon(data.logoSvg);
141
+ setHeaderIcon(data.logoSvg);
142
+
143
+ // Store MCP data
144
+ toolsData = data.tools || [];
145
+ resourcesData = data.resources || [];
146
+ promptsData = data.prompts || [];
147
+
148
+ // Render page info
149
+ renderPageInfo(data);
150
+
151
+ } catch (error) {
152
+ console.error('Error loading page data:', error);
153
+ }
154
+ }
155
+
156
+ // eslint-disable-next-line unused-imports/no-unused-vars
157
+ function openModal (sectionName) {
158
+ const modal = document.getElementById(sectionName + '-modal');
159
+ const tableBody = document.getElementById(sectionName + '-table').querySelector('tbody');
160
+
161
+ // Show loading state
162
+ tableBody.innerHTML = '<tr><td colspan="100%" class="loading-cell"><div class="loading-spinner"></div> Loading...</td></tr>';
163
+ modal.style.display = 'flex';
164
+
165
+ // Load data with small delay to show loading animation
166
+ setTimeout(function () {
167
+ loadTableData(sectionName);
168
+ }, 300);
169
+ }
170
+
171
+ function closeModal (sectionName) {
172
+ const modal = document.getElementById(sectionName + '-modal');
173
+ modal.style.display = 'none';
174
+ }
175
+
176
+ function loadTableData (sectionName) {
177
+ const tableBody = document.getElementById(sectionName + '-table').querySelector('tbody');
178
+ let data, html;
179
+
180
+ switch (sectionName) {
181
+ case 'tools':
182
+ data = toolsData;
183
+ html = generateToolsTableRows(data);
184
+ break;
185
+ case 'resources':
186
+ data = resourcesData;
187
+ html = generateResourcesTableRows(data);
188
+ break;
189
+ case 'prompts':
190
+ data = promptsData;
191
+ html = generatePromptsTableRows(data);
192
+ break;
193
+ }
194
+
195
+ tableBody.innerHTML = html;
196
+ }
197
+
198
+ function generateToolsTableRows (tools) {
199
+ if (!tools || tools.length === 0) {
200
+ return '<tr><td colspan="3" class="loading-cell">No tools available</td></tr>';
201
+ }
202
+ return tools.map((tool, index) =>
203
+ `<tr>
204
+ <td><code>${tool.name}</code></td>
205
+ <td>${tool.annotations?.title || tool.description}</td>
206
+ <td>
207
+ <a class="detail-link" id="tools-toggle-${index}" onclick="toggleDetails('tools', ${index})">details</a>
208
+ </td>
209
+ </tr>
210
+ <tr id="tools-detail-${index}" class="detail-row" style="display: none;">
211
+ <td colspan="3">
212
+ <div class="detail-content">
213
+ <div class="loading-spinner" style="display: none;"></div>
214
+ <pre class="json-content" style="display: none;"></pre>
215
+ </div>
216
+ </td>
217
+ </tr>`,
218
+ ).join('');
219
+ }
220
+
221
+ function generateResourcesTableRows (resources) {
222
+ if (!resources || resources.length === 0) {
223
+ return '<tr><td colspan="5" class="loading-cell">No resources available</td></tr>';
224
+ }
225
+ return resources.map((resource, index) =>
226
+ `<tr>
227
+ <td><code>${resource.uri}</code></td>
228
+ <td>${resource.name}</td>
229
+ <td>${resource.description}</td>
230
+ <td><code>${resource.mimeType}</code></td>
231
+ <td>
232
+ <a class="detail-link" id="resources-toggle-details-${index}" onclick="toggleResourceDetails('resources', ${index}, 'details')">details</a>
233
+ /
234
+ <a class="detail-link" id="resources-toggle-resource-${index}" onclick="toggleResourceDetails('resources', ${index}, 'resource')">resource</a>
235
+ </td>
236
+ </tr>
237
+ <tr id="resources-detail-${index}" class="detail-row" style="display: none;">
238
+ <td colspan="5">
239
+ <div class="detail-content">
240
+ <div class="loading-spinner"></div>
241
+ <pre class="json-content" style="display: none;"></pre>
242
+ <div class="resource-content" style="display: none;"></div>
243
+ </div>
244
+ </td>
245
+ </tr>`,
246
+ ).join('');
247
+ }
248
+
249
+ function generatePromptsTableRows (prompts) {
250
+ if (!prompts || prompts.length === 0) {
251
+ return '<tr><td colspan="2" class="loading-cell">No prompts available</td></tr>';
252
+ }
253
+ return prompts.map((prompt, index) =>
254
+ `<tr>
255
+ <td><code>${prompt.name}</code></td>
256
+ <td>
257
+ <a class="detail-link" id="prompts-toggle-details-${index}" onclick="togglePromptDetails('prompts', ${index}, 'details')">details</a>
258
+ /
259
+ <a class="detail-link" id="prompts-toggle-prompt-${index}" onclick="togglePromptDetails('prompts', ${index}, 'prompt')">prompt</a>
260
+ </td>
261
+ </tr>
262
+ <tr id="prompts-detail-${index}" class="detail-row" style="display: none;">
263
+ <td colspan="2">
264
+ <div class="detail-content">
265
+ <div class="loading-spinner"></div>
266
+ <pre class="json-content" style="display: none;"></pre>
267
+ <div class="prompt-content" style="display: none;"></div>
268
+ </div>
269
+ </td>
270
+ </tr>`,
271
+ ).join('');
272
+ }
273
+
274
+ // eslint-disable-next-line unused-imports/no-unused-vars
275
+ function toggleDetails (sectionName, index) {
276
+ const detailRow = document.getElementById(sectionName + '-detail-' + index);
277
+ const toggleLink = document.getElementById(sectionName + '-toggle-' + index);
278
+ const loadingSpinner = detailRow.querySelector('.loading-spinner');
279
+ const jsonContent = detailRow.querySelector('.json-content');
280
+
281
+ if (detailRow.style.display === 'none') {
282
+ // Show the detail row with loading state
283
+ detailRow.style.display = 'table-row';
284
+ toggleLink.textContent = 'hide';
285
+ loadingSpinner.style.display = 'block';
286
+ jsonContent.style.display = 'none';
287
+
288
+ // Simulate loading delay and show content
289
+ setTimeout(() => {
290
+ let data;
291
+ let textContent;
292
+ switch (sectionName) {
293
+ case 'tools':
294
+ data = {
295
+ name: toolsData[index].name,
296
+ description: toolsData[index].description,
297
+ inputSchema: toolsData[index].inputSchema,
298
+ annotations: toolsData[index].annotations,
299
+ };
300
+ textContent = JSON.stringify(data, null, 2);
301
+ break;
302
+ case 'resources':
303
+ data = resourcesData[index].content || resourcesData[index];
304
+ textContent = JSON.stringify(data, null, 2);
305
+ break;
306
+ case 'prompts':
307
+ data = promptsData[index];
308
+ textContent = JSON.stringify(data, null, 2);
309
+ break;
310
+ }
311
+
312
+ loadingSpinner.style.display = 'none';
313
+ jsonContent.style.display = 'block';
314
+ jsonContent.textContent = textContent;
315
+ addCopyButton(jsonContent);
316
+ }, 500);
317
+ } else {
318
+ // Hide the detail row
319
+ detailRow.style.display = 'none';
320
+ toggleLink.textContent = 'details';
321
+ }
322
+ }
323
+
324
+ // Handle prompt details and prompt content display
325
+ // eslint-disable-next-line unused-imports/no-unused-vars
326
+ async function togglePromptDetails (sectionName, index, displayType) {
327
+ const detailRow = document.getElementById(sectionName + '-detail-' + index);
328
+ const toggleLinkDetails = document.getElementById(sectionName + '-toggle-details-' + index);
329
+ const toggleLinkPrompt = document.getElementById(sectionName + '-toggle-prompt-' + index);
330
+ const loadingSpinner = detailRow.querySelector('.loading-spinner');
331
+ const jsonContent = detailRow.querySelector('.json-content');
332
+ const promptContent = detailRow.querySelector('.prompt-content');
333
+
334
+ const isCurrentlyHidden = detailRow.style.display === 'none';
335
+ const currentToggleLink = displayType === 'details' ? toggleLinkDetails : toggleLinkPrompt;
336
+ const otherToggleLink = displayType === 'details' ? toggleLinkPrompt : toggleLinkDetails;
337
+
338
+ if (isCurrentlyHidden || currentToggleLink.textContent === displayType) {
339
+ // Show the detail row with loading state
340
+ detailRow.style.display = 'table-row';
341
+ currentToggleLink.textContent = 'hide';
342
+ otherToggleLink.textContent = displayType === 'details' ? 'prompt' : 'details';
343
+ loadingSpinner.style.display = 'block';
344
+ jsonContent.style.display = 'none';
345
+ promptContent.style.display = 'none';
346
+
347
+ if (displayType === 'details') {
348
+ // Show JSON details
349
+ setTimeout(() => {
350
+ const data = promptsData[index];
351
+ const textContent = JSON.stringify(data, null, 2);
352
+ loadingSpinner.style.display = 'none';
353
+ jsonContent.style.display = 'block';
354
+ jsonContent.textContent = textContent;
355
+ addCopyButton(jsonContent);
356
+ }, 300);
357
+ } else {
358
+ // Fetch and show prompt content
359
+ try {
360
+ const promptName = promptsData[index].name;
361
+ const response = await fetch('/mcp', {
362
+ method: 'POST',
363
+ headers: { 'Content-Type': 'application/json' },
364
+ body: JSON.stringify({
365
+ jsonrpc: '2.0',
366
+ id: Date.now(),
367
+ method: 'prompts/get',
368
+ params: { name: promptName },
369
+ }),
370
+ });
371
+
372
+ if (!response.ok) {
373
+ let errorData = '';
374
+ try {
375
+ errorData = await response.text();
376
+ } catch {
377
+ //
378
+ }
379
+ errorData = [response.statusText || '', errorData].join('. ');
380
+ throw new Error('HTTP ' + response.status + (errorData ? ': ' + errorData : ''));
381
+ }
382
+
383
+ const result = await response.json();
384
+ const messages = result.result?.messages || [];
385
+ let promptText = '';
386
+
387
+ messages.forEach((msg, i) => {
388
+ if (i > 0) {promptText += '\n\n---\n\n';}
389
+ promptText += 'Role: ' + msg.role + '\n\n';
390
+ if (typeof msg.content === 'string') {
391
+ promptText += msg.content;
392
+ } else if (msg.content?.text) {
393
+ promptText += msg.content.text;
394
+ } else {
395
+ promptText += JSON.stringify(msg.content, null, 2);
396
+ }
397
+ });
398
+
399
+ loadingSpinner.style.display = 'none';
400
+ promptContent.style.display = 'block';
401
+ promptContent.innerHTML = '<pre class="json-content">' + escapeHtml(promptText) + '</pre>';
402
+ addCopyButton(promptContent.querySelector('.json-content'));
403
+ } catch (error) {
404
+ loadingSpinner.style.display = 'none';
405
+ promptContent.style.display = 'block';
406
+ promptContent.innerHTML = '<div class="error-message">Failed to load prompt: ' + escapeHtml(error.message) + '</div>';
407
+ }
408
+ }
409
+ } else {
410
+ // Hide the detail row
411
+ detailRow.style.display = 'none';
412
+ toggleLinkDetails.textContent = 'details';
413
+ toggleLinkPrompt.textContent = 'prompt';
414
+ }
415
+ }
416
+
417
+ // Handle resource details and resource content display
418
+ // eslint-disable-next-line unused-imports/no-unused-vars
419
+ async function toggleResourceDetails (sectionName, index, displayType) {
420
+ const detailRow = document.getElementById(sectionName + '-detail-' + index);
421
+ const toggleLinkDetails = document.getElementById(sectionName + '-toggle-details-' + index);
422
+ const toggleLinkResource = document.getElementById(sectionName + '-toggle-resource-' + index);
423
+ const loadingSpinner = detailRow.querySelector('.loading-spinner');
424
+ const jsonContent = detailRow.querySelector('.json-content');
425
+ const resourceContent = detailRow.querySelector('.resource-content');
426
+
427
+ const isCurrentlyHidden = detailRow.style.display === 'none';
428
+ const currentToggleLink = displayType === 'details' ? toggleLinkDetails : toggleLinkResource;
429
+ const otherToggleLink = displayType === 'details' ? toggleLinkResource : toggleLinkDetails;
430
+
431
+ if (isCurrentlyHidden || currentToggleLink.textContent === displayType) {
432
+ // Show the detail row with loading state
433
+ detailRow.style.display = 'table-row';
434
+ currentToggleLink.textContent = 'hide';
435
+ otherToggleLink.textContent = displayType === 'details' ? 'resource' : 'details';
436
+ loadingSpinner.style.display = 'block';
437
+ jsonContent.style.display = 'none';
438
+ resourceContent.style.display = 'none';
439
+
440
+ if (displayType === 'details') {
441
+ // Show JSON details
442
+ setTimeout(() => {
443
+ const data = resourcesData[index];
444
+ const textContent = JSON.stringify(data, null, 2);
445
+ loadingSpinner.style.display = 'none';
446
+ jsonContent.style.display = 'block';
447
+ jsonContent.textContent = textContent;
448
+ addCopyButton(jsonContent);
449
+ }, 300);
450
+ } else {
451
+ // Fetch and show resource content
452
+ try {
453
+ const resourceUri = resourcesData[index].uri;
454
+ const response = await fetch('/mcp', {
455
+ method: 'POST',
456
+ headers: { 'Content-Type': 'application/json' },
457
+ body: JSON.stringify({
458
+ jsonrpc: '2.0',
459
+ id: Date.now(),
460
+ method: 'resources/read',
461
+ params: { uri: resourceUri },
462
+ }),
463
+ });
464
+
465
+ if (!response.ok) {
466
+ let errorData = '';
467
+ try {
468
+ errorData = await response.text();
469
+ } catch {
470
+ //
471
+ }
472
+ errorData = [response.statusText || '', errorData].join('. ');
473
+ throw new Error('HTTP ' + response.status + (errorData ? ': ' + errorData : ''));
474
+ }
475
+
476
+ const result = await response.json();
477
+ const contents = result.result?.contents || [];
478
+ let resourceText = '';
479
+
480
+ contents.forEach((content, i) => {
481
+ if (i > 0) {resourceText += '\n\n---\n\n';}
482
+ resourceText += 'URI: ' + content.uri + '\n';
483
+ resourceText += 'MIME Type: ' + content.mimeType + '\n\n';
484
+
485
+ if (content.text) {
486
+ let processedText = content.text;
487
+
488
+ // Handle JSON content more intelligently
489
+ if (content.mimeType === 'application/json') {
490
+ if (typeof processedText !== 'string') {
491
+ processedText = JSON.stringify(processedText, null, 2);
492
+ }
493
+ }
494
+ resourceText += processedText;
495
+ } else if (content.blob) {
496
+ resourceText += '[Binary content: ' + content.blob.length + ' bytes]';
497
+ } else {
498
+ resourceText += JSON.stringify(content, null, 2);
499
+ }
500
+ });
501
+
502
+ loadingSpinner.style.display = 'none';
503
+ resourceContent.style.display = 'block';
504
+ resourceContent.innerHTML = '<pre class="json-content">' + escapeHtml(resourceText) + '</pre>';
505
+ addCopyButton(resourceContent.querySelector('.json-content'));
506
+ } catch (error) {
507
+ loadingSpinner.style.display = 'none';
508
+ resourceContent.style.display = 'block';
509
+ resourceContent.innerHTML = '<div class="error-message">Failed to load resource: ' + escapeHtml(error.message) + '</div>';
510
+ }
511
+ }
512
+ } else {
513
+ // Hide the detail row
514
+ detailRow.style.display = 'none';
515
+ toggleLinkDetails.textContent = 'details';
516
+ toggleLinkResource.textContent = 'resource';
517
+ }
518
+ }
519
+
520
+ // Health Check Modal
521
+ // eslint-disable-next-line unused-imports/no-unused-vars
522
+ async function openHealthCheckModal () {
523
+ const modal = document.getElementById('health-modal');
524
+ const loading = document.getElementById('health-loading');
525
+ const result = document.getElementById('health-result');
526
+ const error = document.getElementById('health-error');
527
+
528
+ // Show modal with loading state
529
+ modal.style.display = 'flex';
530
+ loading.style.display = 'block';
531
+ result.style.display = 'none';
532
+ error.style.display = 'none';
533
+
534
+ try {
535
+ const response = await fetch('/health');
536
+
537
+ if (!response.ok) {
538
+ let errorData = '';
539
+ try {
540
+ errorData = await response.text();
541
+ } catch {
542
+ //
543
+ }
544
+ errorData = [response.statusText || '', errorData].join('. ');
545
+ throw new Error('HTTP ' + response.status + (errorData ? ': ' + errorData : ''));
546
+ }
547
+
548
+ const data = await response.json();
549
+
550
+ // Hide loading and show result
551
+ loading.style.display = 'none';
552
+ result.style.display = 'block';
553
+ result.textContent = JSON.stringify(data, null, 2);
554
+ addCopyButton(result);
555
+ } catch (err) {
556
+ // Hide loading and show error
557
+ loading.style.display = 'none';
558
+ error.style.display = 'block';
559
+ error.textContent = 'Error: ' + (err.message || 'Failed to fetch health check data');
560
+ }
561
+ }
562
+
563
+ // Escape HTML to prevent XSS
564
+ function escapeHtml (text) {
565
+ const div = document.createElement('div');
566
+ div.textContent = text;
567
+ return div.innerHTML;
568
+ }
569
+
570
+ // Copy to clipboard functionality
571
+ function addCopyButton (contentElement) {
572
+ if (!contentElement || contentElement.hasAttribute('data-copy-added')) {
573
+ return;
574
+ }
575
+
576
+ contentElement.setAttribute('data-copy-added', 'true');
577
+
578
+ const copyButton = document.createElement('button');
579
+ copyButton.className = 'copy-button';
580
+ copyButton.innerHTML = '📋';
581
+ copyButton.title = 'Copy to clipboard';
582
+ copyButton.setAttribute('aria-label', 'Copy to clipboard');
583
+
584
+ const notification = document.createElement('div');
585
+ notification.className = 'copy-notification';
586
+ notification.textContent = 'Copied';
587
+
588
+ contentElement.appendChild(copyButton);
589
+ contentElement.appendChild(notification);
590
+
591
+ copyButton.addEventListener('click', async function () {
592
+ let textToCopy = contentElement.textContent || contentElement.innerText;
593
+ textToCopy = textToCopy.replace(/📋Copied/, '');
594
+ try {
595
+ await navigator.clipboard.writeText(textToCopy);
596
+
597
+ // Show notification
598
+ notification.classList.add('show');
599
+
600
+ // Hide notification after 1 second
601
+ setTimeout(() => {
602
+ notification.classList.remove('show');
603
+ }, 1000);
604
+
605
+ } catch {
606
+ // Fallback for browsers that don't support clipboard API
607
+ const textArea = document.createElement('textarea');
608
+ textArea.value = textToCopy;
609
+ textArea.style.position = 'fixed';
610
+ textArea.style.opacity = '0';
611
+ document.body.appendChild(textArea);
612
+ textArea.focus();
613
+ textArea.select();
614
+
615
+ try {
616
+ document.execCommand('copy');
617
+
618
+ // Show notification
619
+ notification.classList.add('show');
620
+
621
+ // Hide notification after 1 second
622
+ setTimeout(() => {
623
+ notification.classList.remove('show');
624
+ }, 1000);
625
+ } catch (fallbackErr) {
626
+ console.error('Failed to copy text:', fallbackErr);
627
+ }
628
+
629
+ document.body.removeChild(textArea);
630
+ }
631
+ });
632
+ }
633
+
634
+ // Close modal when clicking outside
635
+ document.addEventListener('click', function (event) {
636
+ if (event.target.classList.contains('modal-overlay')) {
637
+ const modalId = event.target.id;
638
+ const sectionName = modalId.replace('-modal', '');
639
+ closeModal(sectionName);
640
+ }
641
+ });
642
+
643
+ // Initialize on page load
644
+ document.addEventListener('DOMContentLoaded', function () {
645
+ loadPageData();
646
+ });