fa-mcp-sdk 0.2.144 → 0.2.174

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