fa-mcp-sdk 0.4.76 → 0.4.77

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 (198) hide show
  1. package/README.md +319 -314
  2. package/bin/fa-mcp.js +85 -68
  3. package/cli-template/.claude/agents/javascript-pro.md +276 -276
  4. package/cli-template/.claude/settings.json +50 -50
  5. package/cli-template/.claude/skills/upgrade-guide/SKILL.md +2 -1
  6. package/cli-template/.oxfmtrc.json +41 -0
  7. package/cli-template/.oxlintrc.json +120 -0
  8. package/cli-template/CLAUDE.md +358 -355
  9. package/cli-template/FA-MCP-SDK-DOC/00-FA-MCP-SDK-index.md +132 -132
  10. package/cli-template/FA-MCP-SDK-DOC/01-getting-started.md +146 -146
  11. package/cli-template/FA-MCP-SDK-DOC/02-1-tools-and-api.md +431 -431
  12. package/cli-template/FA-MCP-SDK-DOC/02-2-prompts-and-resources.md +201 -201
  13. package/cli-template/FA-MCP-SDK-DOC/03-configuration.md +384 -384
  14. package/cli-template/FA-MCP-SDK-DOC/04-authentication.md +412 -412
  15. package/cli-template/FA-MCP-SDK-DOC/05-ad-authorization.md +196 -196
  16. package/cli-template/FA-MCP-SDK-DOC/06-utilities.md +163 -163
  17. package/cli-template/FA-MCP-SDK-DOC/07-testing-and-operations.md +127 -127
  18. package/cli-template/jest.config.js +27 -30
  19. package/cli-template/package.json +10 -5
  20. package/cli-template/prompt-example-new-MCP.md +101 -101
  21. package/cli-template/readme-docs/SKILLS.md +1 -1
  22. package/cli-template/tsconfig.json +58 -58
  23. package/cli-template/update.cjs +41 -38
  24. package/config/custom-environment-variables.yaml +63 -63
  25. package/config/development.yaml +4 -4
  26. package/config/production.yaml +4 -4
  27. package/config/test.yaml +26 -26
  28. package/dist/core/_types_/TNtlm.d.ts.map +1 -1
  29. package/dist/core/_types_/active-directory-config.d.ts.map +1 -1
  30. package/dist/core/_types_/config.d.ts.map +1 -1
  31. package/dist/core/_types_/types.d.ts.map +1 -1
  32. package/dist/core/ad/group-checker.d.ts.map +1 -1
  33. package/dist/core/ad/group-checker.js.map +1 -1
  34. package/dist/core/agent-tester/agent-tester-router.d.ts.map +1 -1
  35. package/dist/core/agent-tester/agent-tester-router.js +6 -6
  36. package/dist/core/agent-tester/agent-tester-router.js.map +1 -1
  37. package/dist/core/agent-tester/check-llm.d.ts.map +1 -1
  38. package/dist/core/agent-tester/check-llm.js.map +1 -1
  39. package/dist/core/agent-tester/services/SummaryMemory.d.ts.map +1 -1
  40. package/dist/core/agent-tester/services/SummaryMemory.js +3 -9
  41. package/dist/core/agent-tester/services/SummaryMemory.js.map +1 -1
  42. package/dist/core/agent-tester/services/TesterAgentService.d.ts.map +1 -1
  43. package/dist/core/agent-tester/services/TesterAgentService.js +25 -27
  44. package/dist/core/agent-tester/services/TesterAgentService.js.map +1 -1
  45. package/dist/core/agent-tester/services/TesterMcpClientService.d.ts.map +1 -1
  46. package/dist/core/agent-tester/services/TesterMcpClientService.js +26 -25
  47. package/dist/core/agent-tester/services/TesterMcpClientService.js.map +1 -1
  48. package/dist/core/auth/admin-auth.d.ts.map +1 -1
  49. package/dist/core/auth/admin-auth.js +5 -5
  50. package/dist/core/auth/admin-auth.js.map +1 -1
  51. package/dist/core/auth/agent-tester-auth.d.ts.map +1 -1
  52. package/dist/core/auth/agent-tester-auth.js +1 -6
  53. package/dist/core/auth/agent-tester-auth.js.map +1 -1
  54. package/dist/core/auth/basic.d.ts.map +1 -1
  55. package/dist/core/auth/basic.js.map +1 -1
  56. package/dist/core/auth/ip-check.d.ts.map +1 -1
  57. package/dist/core/auth/ip-check.js +1 -1
  58. package/dist/core/auth/ip-check.js.map +1 -1
  59. package/dist/core/auth/jwt.d.ts.map +1 -1
  60. package/dist/core/auth/jwt.js +1 -1
  61. package/dist/core/auth/jwt.js.map +1 -1
  62. package/dist/core/auth/middleware.d.ts.map +1 -1
  63. package/dist/core/auth/middleware.js +9 -6
  64. package/dist/core/auth/middleware.js.map +1 -1
  65. package/dist/core/auth/multi-auth.d.ts.map +1 -1
  66. package/dist/core/auth/multi-auth.js +6 -6
  67. package/dist/core/auth/multi-auth.js.map +1 -1
  68. package/dist/core/auth/revocation.d.ts.map +1 -1
  69. package/dist/core/auth/revocation.js +2 -6
  70. package/dist/core/auth/revocation.js.map +1 -1
  71. package/dist/core/auth/token-generator/ntlm/ntlm-auth-options.d.ts.map +1 -1
  72. package/dist/core/auth/token-generator/ntlm/ntlm-auth-options.js +2 -2
  73. package/dist/core/auth/token-generator/ntlm/ntlm-auth-options.js.map +1 -1
  74. package/dist/core/auth/token-generator/ntlm/ntlm-domain-config.js +1 -1
  75. package/dist/core/auth/token-generator/ntlm/ntlm-domain-config.js.map +1 -1
  76. package/dist/core/auth/token-generator/ntlm/ntlm-integration.d.ts.map +1 -1
  77. package/dist/core/auth/token-generator/ntlm/ntlm-integration.js +4 -2
  78. package/dist/core/auth/token-generator/ntlm/ntlm-integration.js.map +1 -1
  79. package/dist/core/auth/token-generator/server.d.ts.map +1 -1
  80. package/dist/core/auth/token-generator/server.js.map +1 -1
  81. package/dist/core/bootstrap/init-config.d.ts.map +1 -1
  82. package/dist/core/bootstrap/init-config.js +2 -2
  83. package/dist/core/bootstrap/init-config.js.map +1 -1
  84. package/dist/core/bootstrap/startup-info.d.ts.map +1 -1
  85. package/dist/core/bootstrap/startup-info.js +3 -7
  86. package/dist/core/bootstrap/startup-info.js.map +1 -1
  87. package/dist/core/cache/cache.d.ts.map +1 -1
  88. package/dist/core/cache/cache.js +2 -2
  89. package/dist/core/cache/cache.js.map +1 -1
  90. package/dist/core/consul/deregister.d.ts.map +1 -1
  91. package/dist/core/consul/deregister.js.map +1 -1
  92. package/dist/core/consul/get-consul-api.d.ts.map +1 -1
  93. package/dist/core/consul/get-consul-api.js +1 -2
  94. package/dist/core/consul/get-consul-api.js.map +1 -1
  95. package/dist/core/db/pg-db.d.ts.map +1 -1
  96. package/dist/core/db/pg-db.js +3 -3
  97. package/dist/core/db/pg-db.js.map +1 -1
  98. package/dist/core/debug.d.ts.map +1 -1
  99. package/dist/core/debug.js.map +1 -1
  100. package/dist/core/errors/BaseMcpError.d.ts.map +1 -1
  101. package/dist/core/errors/BaseMcpError.js.map +1 -1
  102. package/dist/core/errors/ValidationError.d.ts.map +1 -1
  103. package/dist/core/errors/ValidationError.js.map +1 -1
  104. package/dist/core/errors/errors.d.ts.map +1 -1
  105. package/dist/core/errors/errors.js +1 -1
  106. package/dist/core/errors/errors.js.map +1 -1
  107. package/dist/core/index.d.ts +6 -6
  108. package/dist/core/index.d.ts.map +1 -1
  109. package/dist/core/index.js +5 -5
  110. package/dist/core/index.js.map +1 -1
  111. package/dist/core/init-mcp-server.d.ts.map +1 -1
  112. package/dist/core/init-mcp-server.js.map +1 -1
  113. package/dist/core/logger.d.ts.map +1 -1
  114. package/dist/core/logger.js +1 -1
  115. package/dist/core/logger.js.map +1 -1
  116. package/dist/core/mcp/create-mcp-server.d.ts.map +1 -1
  117. package/dist/core/mcp/create-mcp-server.js +1 -1
  118. package/dist/core/mcp/create-mcp-server.js.map +1 -1
  119. package/dist/core/mcp/prompts.d.ts.map +1 -1
  120. package/dist/core/mcp/prompts.js.map +1 -1
  121. package/dist/core/mcp/readme-assembler.d.ts.map +1 -1
  122. package/dist/core/mcp/readme-assembler.js +3 -1
  123. package/dist/core/mcp/readme-assembler.js.map +1 -1
  124. package/dist/core/mcp/resources.d.ts.map +1 -1
  125. package/dist/core/mcp/resources.js.map +1 -1
  126. package/dist/core/mcp/server-stdio.d.ts.map +1 -1
  127. package/dist/core/utils/formatToolResult.d.ts.map +1 -1
  128. package/dist/core/utils/formatToolResult.js.map +1 -1
  129. package/dist/core/utils/port-checker.d.ts.map +1 -1
  130. package/dist/core/utils/port-checker.js.map +1 -1
  131. package/dist/core/utils/rate-limit.d.ts.map +1 -1
  132. package/dist/core/utils/rate-limit.js +2 -8
  133. package/dist/core/utils/rate-limit.js.map +1 -1
  134. package/dist/core/utils/testing/BaseMcpClient.d.ts.map +1 -1
  135. package/dist/core/utils/testing/BaseMcpClient.js.map +1 -1
  136. package/dist/core/utils/testing/McpHttpClient.d.ts.map +1 -1
  137. package/dist/core/utils/testing/McpHttpClient.js +2 -2
  138. package/dist/core/utils/testing/McpHttpClient.js.map +1 -1
  139. package/dist/core/utils/testing/McpSseClient.d.ts.map +1 -1
  140. package/dist/core/utils/testing/McpSseClient.js +3 -8
  141. package/dist/core/utils/testing/McpSseClient.js.map +1 -1
  142. package/dist/core/utils/testing/McpStdioClient.d.ts.map +1 -1
  143. package/dist/core/utils/testing/McpStdioClient.js.map +1 -1
  144. package/dist/core/utils/testing/McpStreamableHttpClient.d.ts.map +1 -1
  145. package/dist/core/utils/testing/McpStreamableHttpClient.js +7 -8
  146. package/dist/core/utils/testing/McpStreamableHttpClient.js.map +1 -1
  147. package/dist/core/utils/utils.d.ts.map +1 -1
  148. package/dist/core/utils/utils.js +3 -5
  149. package/dist/core/utils/utils.js.map +1 -1
  150. package/dist/core/web/admin-router.d.ts.map +1 -1
  151. package/dist/core/web/admin-router.js +3 -3
  152. package/dist/core/web/admin-router.js.map +1 -1
  153. package/dist/core/web/cors.d.ts.map +1 -1
  154. package/dist/core/web/cors.js.map +1 -1
  155. package/dist/core/web/favicon-svg.d.ts.map +1 -1
  156. package/dist/core/web/favicon-svg.js +1 -5
  157. package/dist/core/web/favicon-svg.js.map +1 -1
  158. package/dist/core/web/home-api.d.ts.map +1 -1
  159. package/dist/core/web/home-api.js +7 -8
  160. package/dist/core/web/home-api.js.map +1 -1
  161. package/dist/core/web/openapi.d.ts.map +1 -1
  162. package/dist/core/web/openapi.js +1 -3
  163. package/dist/core/web/openapi.js.map +1 -1
  164. package/dist/core/web/server-http.d.ts.map +1 -1
  165. package/dist/core/web/server-http.js +4 -4
  166. package/dist/core/web/server-http.js.map +1 -1
  167. package/dist/core/web/static/agent-tester/index.html +323 -323
  168. package/dist/core/web/static/agent-tester/script.js +311 -200
  169. package/dist/core/web/static/agent-tester/styles.css +1840 -1840
  170. package/dist/core/web/static/home/index.html +220 -220
  171. package/dist/core/web/static/home/script.js +72 -43
  172. package/dist/core/web/static/styles.css +927 -927
  173. package/dist/core/web/static/token-gen/index.html +136 -136
  174. package/dist/core/web/static/token-gen/script.js +58 -56
  175. package/dist/core/web/svg-icons.d.ts.map +1 -1
  176. package/dist/core/web/svg-icons.js +1 -5
  177. package/dist/core/web/svg-icons.js.map +1 -1
  178. package/package.json +10 -5
  179. package/{cli-template/.claude/hooks/eslint-fix.cjs → scripts/cc-hook-oxlint-oxfmt-fix.cjs} +109 -100
  180. package/scripts/generate-jwt.js +5 -9
  181. package/scripts/kill-port.js +5 -2
  182. package/scripts/npm/run.js +1 -2
  183. package/scripts/remove-nul.js +1 -1
  184. package/scripts/update-sdk.js +36 -14
  185. package/src/template/api/router.ts +3 -3
  186. package/src/template/prompts/agent-brief.ts +0 -1
  187. package/src/template/start.ts +3 -8
  188. package/src/template/tools/handle-tool-call.ts +3 -3
  189. package/src/template/tools/tools.ts +3 -7
  190. package/src/tests/jest-simple-reporter.js +1 -1
  191. package/src/tests/mcp/sse/mcp-sse-client-handling.md +111 -111
  192. package/src/tests/mcp/sse/test-sse-npm-package.js +2 -3
  193. package/src/tests/mcp/test-cases.js +6 -7
  194. package/src/tests/mcp/test-http.js +2 -2
  195. package/src/tests/mcp/test-sse.js +9 -7
  196. package/src/tests/mcp/test-stdio.js +12 -8
  197. package/src/tests/utils.ts +4 -3
  198. package/cli-template/eslint.config.js +0 -27
@@ -30,7 +30,7 @@ const LLM_DEFAULTS = {
30
30
  /**
31
31
  * Wrapper around fetch that always includes credentials (session cookie).
32
32
  */
33
- function apiFetch (url, options = {}) {
33
+ function apiFetch(url, options = {}) {
34
34
  return fetch(url, { ...options, credentials: 'include' });
35
35
  }
36
36
 
@@ -38,13 +38,13 @@ function apiFetch (url, options = {}) {
38
38
  * Auth manager — handles login overlay when agentTester.useAuth is enabled.
39
39
  */
40
40
  class AuthManager {
41
- constructor () {
41
+ constructor() {
42
42
  this._authenticated = false;
43
43
  this._authRequired = false;
44
44
  }
45
45
 
46
46
  /** Check auth status and show login if needed. Returns true if app can proceed. */
47
- async init () {
47
+ async init() {
48
48
  try {
49
49
  const resp = await apiFetch(`${API_BASE}/api/auth/status`);
50
50
  const status = await resp.json();
@@ -69,7 +69,7 @@ class AuthManager {
69
69
  }
70
70
  }
71
71
 
72
- _showLoginOverlay (methods) {
72
+ _showLoginOverlay(methods) {
73
73
  const overlay = document.getElementById('authOverlay');
74
74
  const appEl = document.querySelector('.app');
75
75
  overlay.style.display = 'flex';
@@ -98,18 +98,22 @@ class AuthManager {
98
98
  tokenForm.addEventListener('submit', (e) => {
99
99
  e.preventDefault();
100
100
  const token = document.getElementById('authToken').value.trim();
101
- if (token) { this._login({ token }); }
101
+ if (token) {
102
+ this._login({ token });
103
+ }
102
104
  });
103
105
 
104
106
  basicForm.addEventListener('submit', (e) => {
105
107
  e.preventDefault();
106
108
  const username = document.getElementById('authUsername').value.trim();
107
109
  const password = document.getElementById('authPassword').value;
108
- if (username && password) { this._login({ username, password }); }
110
+ if (username && password) {
111
+ this._login({ username, password });
112
+ }
109
113
  });
110
114
  }
111
115
 
112
- _bindTabs () {
116
+ _bindTabs() {
113
117
  const tabs = document.querySelectorAll('.auth-tab');
114
118
  const tokenForm = document.getElementById('authTokenForm');
115
119
  const basicForm = document.getElementById('authBasicForm');
@@ -130,7 +134,7 @@ class AuthManager {
130
134
  });
131
135
  }
132
136
 
133
- async _login (credentials) {
137
+ async _login(credentials) {
134
138
  this._hideError();
135
139
  try {
136
140
  const resp = await apiFetch(`${API_BASE}/api/auth/login`, {
@@ -159,7 +163,7 @@ class AuthManager {
159
163
  }
160
164
  }
161
165
 
162
- _showLogoutButton () {
166
+ _showLogoutButton() {
163
167
  const btn = document.getElementById('logoutBtn');
164
168
  if (btn) {
165
169
  btn.style.display = '';
@@ -167,27 +171,29 @@ class AuthManager {
167
171
  }
168
172
  }
169
173
 
170
- async _logout () {
174
+ async _logout() {
171
175
  try {
172
176
  await apiFetch(`${API_BASE}/api/auth/logout`, { method: 'POST' });
173
- } catch { /* ignore */ }
177
+ } catch {
178
+ /* ignore */
179
+ }
174
180
  location.reload();
175
181
  }
176
182
 
177
- _showError (msg) {
183
+ _showError(msg) {
178
184
  const el = document.getElementById('authError');
179
185
  el.textContent = msg;
180
186
  el.style.display = 'block';
181
187
  }
182
188
 
183
- _hideError () {
189
+ _hideError() {
184
190
  const el = document.getElementById('authError');
185
191
  el.style.display = 'none';
186
192
  }
187
193
  }
188
194
 
189
195
  class McpAgentTester {
190
- constructor () {
196
+ constructor() {
191
197
  this.currentSessionId = null;
192
198
  this.currentServer = null;
193
199
  this.currentSystemPrompt = '';
@@ -223,22 +229,46 @@ class McpAgentTester {
223
229
  console.log('MCP Agent Tester initialized');
224
230
  }
225
231
 
226
- sanitizeHtml (html) {
227
- const allowedTags = [
228
- 'p', 'br', 'strong', 'b', 'em', 'i', 'u', 'code', 'pre',
229
- 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
230
- 'ul', 'ol', 'li', 'blockquote', 'a', 'span', 'div',
231
- 'table', 'thead', 'tbody', 'tr', 'th', 'td',
232
- ];
232
+ sanitizeHtml(html) {
233
+ const allowedTags = new Set([
234
+ 'p',
235
+ 'br',
236
+ 'strong',
237
+ 'b',
238
+ 'em',
239
+ 'i',
240
+ 'u',
241
+ 'code',
242
+ 'pre',
243
+ 'h1',
244
+ 'h2',
245
+ 'h3',
246
+ 'h4',
247
+ 'h5',
248
+ 'h6',
249
+ 'ul',
250
+ 'ol',
251
+ 'li',
252
+ 'blockquote',
253
+ 'a',
254
+ 'span',
255
+ 'div',
256
+ 'table',
257
+ 'thead',
258
+ 'tbody',
259
+ 'tr',
260
+ 'th',
261
+ 'td',
262
+ ]);
233
263
 
234
264
  const allowedAttributes = {
235
- 'a': ['href', 'title', 'target'],
236
- 'th': ['colspan', 'rowspan'],
237
- 'td': ['colspan', 'rowspan'],
238
- 'code': ['class'],
239
- 'pre': ['class'],
240
- 'span': ['class'],
241
- 'div': ['class'],
265
+ a: ['href', 'title', 'target'],
266
+ th: ['colspan', 'rowspan'],
267
+ td: ['colspan', 'rowspan'],
268
+ code: ['class'],
269
+ pre: ['class'],
270
+ span: ['class'],
271
+ div: ['class'],
242
272
  };
243
273
 
244
274
  const tempDiv = document.createElement('div');
@@ -255,7 +285,8 @@ class McpAgentTester {
255
285
 
256
286
  const tagName = node.tagName.toLowerCase();
257
287
 
258
- if (!allowedTags.includes(tagName)) {
288
+ if (!allowedTags.has(tagName)) {
289
+ // noinspection UnnecessaryLocalVariableJS
259
290
  const textNode = document.createTextNode(node.textContent || '');
260
291
  return textNode;
261
292
  }
@@ -263,7 +294,7 @@ class McpAgentTester {
263
294
  const cleanedElement = document.createElement(tagName);
264
295
 
265
296
  const allowedAttrs = allowedAttributes[tagName] || [];
266
- allowedAttrs.forEach(attr => {
297
+ allowedAttrs.forEach((attr) => {
267
298
  if (node.hasAttribute(attr)) {
268
299
  const value = node.getAttribute(attr);
269
300
  if (attr === 'href') {
@@ -281,7 +312,7 @@ class McpAgentTester {
281
312
  }
282
313
  });
283
314
 
284
- Array.from(node.childNodes).forEach(child => {
315
+ Array.from(node.childNodes).forEach((child) => {
285
316
  const cleanedChild = cleanNode(child);
286
317
  if (cleanedChild) {
287
318
  cleanedElement.appendChild(cleanedChild);
@@ -291,15 +322,17 @@ class McpAgentTester {
291
322
  return cleanedElement;
292
323
  };
293
324
 
294
- const cleanedNodes = Array.from(tempDiv.childNodes).map(cleanNode).filter(node => node !== null);
325
+ const cleanedNodes = Array.from(tempDiv.childNodes)
326
+ .map(cleanNode)
327
+ .filter((node) => node !== null);
295
328
 
296
329
  const finalDiv = document.createElement('div');
297
- cleanedNodes.forEach(node => finalDiv.appendChild(node));
330
+ cleanedNodes.forEach((node) => finalDiv.appendChild(node));
298
331
 
299
332
  return finalDiv.innerHTML.trim();
300
333
  }
301
334
 
302
- createFormatToggle (messageId) {
335
+ createFormatToggle(messageId) {
303
336
  const toggleContainer = document.createElement('div');
304
337
  toggleContainer.className = 'format-toggle-container';
305
338
 
@@ -311,7 +344,7 @@ class McpAgentTester {
311
344
  const options = ['MD', 'HTML'];
312
345
  const currentFormat = this.messageFormats[messageId] || 'MD';
313
346
 
314
- options.forEach(opt => {
347
+ options.forEach((opt) => {
315
348
  const option = document.createElement('option');
316
349
  option.value = opt;
317
350
  option.textContent = opt;
@@ -329,7 +362,7 @@ class McpAgentTester {
329
362
  return toggleContainer;
330
363
  }
331
364
 
332
- onFormatChange (messageId, format) {
365
+ onFormatChange(messageId, format) {
333
366
  this.messageFormats[messageId] = format;
334
367
  const originalText = this.messageTexts[messageId];
335
368
  const messageText = document.querySelector(`.message-text[data-message-id="${messageId}"]`);
@@ -338,7 +371,7 @@ class McpAgentTester {
338
371
  }
339
372
  }
340
373
 
341
- handleDefaultFormatChange () {
374
+ handleDefaultFormatChange() {
342
375
  const { value } = this.defaultFormatSelect;
343
376
  this.defaultDisplayFormat = value;
344
377
  localStorage.setItem('agentTesterDefaultFormat', value);
@@ -347,7 +380,7 @@ class McpAgentTester {
347
380
  }
348
381
  }
349
382
 
350
- renderMessageContent (element, text, format) {
383
+ renderMessageContent(element, text, format) {
351
384
  if (format === 'HTML') {
352
385
  element.innerHTML = this.sanitizeHtml(text).trim();
353
386
  element.classList.add('html-content');
@@ -359,7 +392,7 @@ class McpAgentTester {
359
392
  }
360
393
  }
361
394
 
362
- initializeElements () {
395
+ initializeElements() {
363
396
  this.sidebar = document.getElementById('sidebar');
364
397
  this.sidebarToggle = document.getElementById('sidebarToggle');
365
398
  this.sidebarToggleMobile = document.getElementById('sidebarToggleMobile');
@@ -422,7 +455,7 @@ class McpAgentTester {
422
455
  this.defaultFormatSelect = document.getElementById('defaultDisplayFormat');
423
456
  }
424
457
 
425
- bindEvents () {
458
+ bindEvents() {
426
459
  if (this.sidebarToggle) {
427
460
  this.sidebarToggle.addEventListener('click', () => this.toggleSidebar());
428
461
  }
@@ -464,7 +497,9 @@ class McpAgentTester {
464
497
  this.llmModelDropdownToggle.addEventListener('click', (e) => this.toggleLlmModelDropdown(e));
465
498
  this.renderLlmModelDropdown();
466
499
  this.llmModal.addEventListener('click', (e) => {
467
- if (e.target === this.llmModal) { this.closeLlmModal(); }
500
+ if (e.target === this.llmModal) {
501
+ this.closeLlmModal();
502
+ }
468
503
  });
469
504
  document.addEventListener('keydown', (e) => {
470
505
  if (e.key === 'Escape' && this.llmModal.style.display === 'flex') {
@@ -472,13 +507,15 @@ class McpAgentTester {
472
507
  }
473
508
  });
474
509
 
475
- document.querySelectorAll('.btn-enlarge').forEach(btn => {
510
+ document.querySelectorAll('.btn-enlarge').forEach((btn) => {
476
511
  btn.addEventListener('click', () => this.openPromptModal(btn.dataset.target));
477
512
  });
478
513
  document.getElementById('promptModalClose').addEventListener('click', () => this.closePromptModal());
479
514
  document.getElementById('promptModalSave').addEventListener('click', () => this.savePromptModal());
480
515
  document.getElementById('promptModal').addEventListener('click', (e) => {
481
- if (e.target === e.currentTarget) {this.closePromptModal();}
516
+ if (e.target === e.currentTarget) {
517
+ this.closePromptModal();
518
+ }
482
519
  });
483
520
 
484
521
  this.messageInput.addEventListener('input', () => this.handleInputChange());
@@ -487,10 +524,7 @@ class McpAgentTester {
487
524
  this.clearChatBtn.addEventListener('click', () => this.clearChat());
488
525
 
489
526
  document.addEventListener('click', (e) => {
490
- if (window.innerWidth <= 768 &&
491
- !this.sidebar.contains(e.target) &&
492
- !this.sidebarToggleMobile.contains(e.target) &&
493
- this.sidebar.classList.contains('open')) {
527
+ if (window.innerWidth <= 768 && !this.sidebar.contains(e.target) && !this.sidebarToggleMobile.contains(e.target) && this.sidebar.classList.contains('open')) {
494
528
  this.toggleSidebar();
495
529
  }
496
530
  });
@@ -502,24 +536,23 @@ class McpAgentTester {
502
536
  });
503
537
  }
504
538
 
505
- initTheme () {
539
+ initTheme() {
506
540
  const saved = localStorage.getItem('mcpAgentTheme');
507
541
  let theme = saved;
508
542
  if (!theme) {
509
- theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
510
- ? 'dark' : 'light';
543
+ theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
511
544
  }
512
545
  this.applyTheme(theme);
513
546
  }
514
547
 
515
- toggleTheme () {
548
+ toggleTheme() {
516
549
  const current = document.documentElement.getAttribute('data-theme') || 'light';
517
550
  const next = current === 'dark' ? 'light' : 'dark';
518
551
  this.applyTheme(next);
519
552
  localStorage.setItem('mcpAgentTheme', next);
520
553
  }
521
554
 
522
- applyTheme (theme) {
555
+ applyTheme(theme) {
523
556
  document.documentElement.setAttribute('data-theme', theme);
524
557
  if (this.themeToggle) {
525
558
  const icon = this.themeToggle.querySelector('.material-icons-round');
@@ -529,7 +562,7 @@ class McpAgentTester {
529
562
  }
530
563
  }
531
564
 
532
- openPromptModal (targetId) {
565
+ openPromptModal(targetId) {
533
566
  this._promptModalTarget = document.getElementById(targetId);
534
567
  const modal = document.getElementById('promptModal');
535
568
  const textarea = document.getElementById('promptModalTextarea');
@@ -540,7 +573,7 @@ class McpAgentTester {
540
573
  textarea.focus();
541
574
  }
542
575
 
543
- closePromptModal () {
576
+ closePromptModal() {
544
577
  const modal = document.getElementById('promptModal');
545
578
  const textarea = document.getElementById('promptModalTextarea');
546
579
  const saveBtn = document.getElementById('promptModalSave');
@@ -553,7 +586,7 @@ class McpAgentTester {
553
586
  this._promptModalTarget = null;
554
587
  }
555
588
 
556
- savePromptModal () {
589
+ savePromptModal() {
557
590
  if (this._promptModalTarget) {
558
591
  this._promptModalTarget.value = document.getElementById('promptModalTextarea').value;
559
592
  this.saveFormValuesToStorage();
@@ -561,18 +594,18 @@ class McpAgentTester {
561
594
  this.closePromptModal();
562
595
  }
563
596
 
564
- setupAutoResize () {
597
+ setupAutoResize() {
565
598
  this.messageInput.addEventListener('input', function () {
566
599
  this.style.height = 'auto';
567
600
  this.style.height = Math.min(this.scrollHeight, 120) + 'px';
568
601
  });
569
602
  }
570
603
 
571
- toggleSidebar () {
604
+ toggleSidebar() {
572
605
  this.sidebar.classList.toggle('open');
573
606
  }
574
607
 
575
- async loadInitialData () {
608
+ async loadInitialData() {
576
609
  try {
577
610
  this.loadFormValuesFromStorage();
578
611
  this.loadFormValuesFromURL();
@@ -596,9 +629,11 @@ class McpAgentTester {
596
629
  }
597
630
  }
598
631
 
599
- async autoConnect () {
632
+ async autoConnect() {
600
633
  const serverUrl = this.serverUrlInput.value.trim();
601
- if (!serverUrl) {return;}
634
+ if (!serverUrl) {
635
+ return;
636
+ }
602
637
 
603
638
  const transport = this.transportSelect.value;
604
639
  const serverName = this.generateServerName(serverUrl);
@@ -663,7 +698,7 @@ class McpAgentTester {
663
698
  }
664
699
  }
665
700
 
666
- async loadDefaultConfig () {
701
+ async loadDefaultConfig() {
667
702
  try {
668
703
  const response = await apiFetch(`${API_BASE}/api/config`);
669
704
  const config = await response.json();
@@ -682,7 +717,7 @@ class McpAgentTester {
682
717
  }
683
718
  }
684
719
 
685
- async handleMcpConnection (event) {
720
+ async handleMcpConnection(event) {
686
721
  event.preventDefault();
687
722
 
688
723
  const serverUrl = this.serverUrlInput.value.trim();
@@ -745,7 +780,6 @@ class McpAgentTester {
745
780
  } else {
746
781
  this.showToast('Failed to connect: ' + result.error, 'error');
747
782
  }
748
-
749
783
  } catch (error) {
750
784
  console.error('Connection error:', error);
751
785
  this.showToast('Connection failed: ' + error.message, 'error');
@@ -754,7 +788,7 @@ class McpAgentTester {
754
788
  }
755
789
  }
756
790
 
757
- generateServerName (url) {
791
+ generateServerName(url) {
758
792
  try {
759
793
  const parsedUrl = new URL(url);
760
794
  const { hostname, port } = parsedUrl;
@@ -771,7 +805,7 @@ class McpAgentTester {
771
805
  }
772
806
  }
773
807
 
774
- async checkRequiredHeaders () {
808
+ async checkRequiredHeaders() {
775
809
  const url = this.serverUrlInput.value.trim();
776
810
 
777
811
  if (!url) {
@@ -784,7 +818,7 @@ class McpAgentTester {
784
818
  try {
785
819
  const response = await apiFetch(`${API_BASE}/api/mcp/used-headers?url=${encodeURIComponent(url)}`, {
786
820
  method: 'GET',
787
- headers: { 'Accept': 'application/json' },
821
+ headers: { Accept: 'application/json' },
788
822
  });
789
823
 
790
824
  if (response.ok) {
@@ -794,7 +828,7 @@ class McpAgentTester {
794
828
  await this.autoFillAuthHeader();
795
829
 
796
830
  if (this.usedHeaders.length > 0) {
797
- const reqCount = this.usedHeaders.filter(h => !h.isOptional).length;
831
+ const reqCount = this.usedHeaders.filter((h) => !h.isOptional).length;
798
832
  this.showToast(`Found ${this.usedHeaders.length} headers (${reqCount} used)`, 'success');
799
833
  this.headersSection.style.display = 'block';
800
834
  } else {
@@ -806,7 +840,6 @@ class McpAgentTester {
806
840
  this.headersSection.style.display = 'none';
807
841
  this.usedHeaders = [];
808
842
  }
809
-
810
843
  } catch (error) {
811
844
  console.log('Headers check failed:', error);
812
845
  this.showToast('Headers endpoint not available - proceeding without additional headers', 'info');
@@ -817,11 +850,11 @@ class McpAgentTester {
817
850
  }
818
851
  }
819
852
 
820
- renderHeaderInputs () {
853
+ renderHeaderInputs() {
821
854
  this.dynamicHeaders.innerHTML = '';
822
855
  const savedHeaders = this.loadHeaderValuesFromStorage();
823
856
 
824
- this.usedHeaders.forEach(header => {
857
+ this.usedHeaders.forEach((header) => {
825
858
  const headerGroup = document.createElement('div');
826
859
  headerGroup.className = 'header-row';
827
860
 
@@ -886,31 +919,31 @@ class McpAgentTester {
886
919
  this.mcpConfig.headers = this.getHeadersFromForm();
887
920
  }
888
921
 
889
- showHeaderTooltip (e, text) {
922
+ showHeaderTooltip(e, text) {
890
923
  const tip = document.getElementById('headerTooltip');
891
924
  tip._sourceEl = e.target;
892
925
  tip.textContent = text;
893
926
  const rect = e.target.getBoundingClientRect();
894
927
  tip.style.left = rect.left + 'px';
895
- tip.style.top = (rect.top - 4) + 'px';
928
+ tip.style.top = rect.top - 4 + 'px';
896
929
  tip.style.transform = 'translateY(-100%)';
897
930
  tip.classList.add('visible');
898
931
  }
899
932
 
900
- hideHeaderTooltip () {
933
+ hideHeaderTooltip() {
901
934
  const tip = document.getElementById('headerTooltip');
902
935
  tip.classList.remove('visible');
903
936
  tip._sourceEl = null;
904
937
  }
905
938
 
906
- copyToClipboard (text) {
939
+ copyToClipboard(text) {
907
940
  if (navigator.clipboard && navigator.clipboard.writeText) {
908
941
  return navigator.clipboard.writeText(text).catch(() => this._fallbackCopy(text));
909
942
  }
910
943
  return this._fallbackCopy(text);
911
944
  }
912
945
 
913
- _fallbackCopy (text) {
946
+ _fallbackCopy(text) {
914
947
  const ta = document.createElement('textarea');
915
948
  ta.value = text;
916
949
  ta.style.position = 'fixed';
@@ -922,7 +955,7 @@ class McpAgentTester {
922
955
  return Promise.resolve();
923
956
  }
924
957
 
925
- updateHeaderBorder (inputEl) {
958
+ updateHeaderBorder(inputEl) {
926
959
  if (inputEl.dataset.required === 'true') {
927
960
  if (inputEl.value.trim()) {
928
961
  inputEl.classList.remove('empty-required');
@@ -932,12 +965,12 @@ class McpAgentTester {
932
965
  }
933
966
  }
934
967
 
935
- getHeaderStorageKey () {
968
+ getHeaderStorageKey() {
936
969
  const url = this.serverUrlInput.value.trim();
937
970
  return `mcpHeaderValues_${url}`;
938
971
  }
939
972
 
940
- saveHeaderValuesToStorage () {
973
+ saveHeaderValuesToStorage() {
941
974
  const headers = this.getHeadersFromForm();
942
975
  const key = this.getHeaderStorageKey();
943
976
  try {
@@ -947,7 +980,7 @@ class McpAgentTester {
947
980
  }
948
981
  }
949
982
 
950
- loadHeaderValuesFromStorage () {
983
+ loadHeaderValuesFromStorage() {
951
984
  const key = this.getHeaderStorageKey();
952
985
  try {
953
986
  const stored = localStorage.getItem(key);
@@ -958,18 +991,18 @@ class McpAgentTester {
958
991
  }
959
992
  }
960
993
 
961
- scheduleHeadersUpdate () {
994
+ scheduleHeadersUpdate() {
962
995
  this.mcpConfig.headers = this.getHeadersFromForm();
963
996
 
964
997
  if (this._headersUpdateTimer) {
965
998
  clearTimeout(this._headersUpdateTimer);
966
999
  }
967
1000
  this._headersUpdateTimer = setTimeout(() => {
968
- this.applyHeadersUpdate().catch(err => console.warn('Apply headers failed:', err));
1001
+ this.applyHeadersUpdate().catch((err) => console.warn('Apply headers failed:', err));
969
1002
  }, 600);
970
1003
  }
971
1004
 
972
- async applyHeadersUpdate () {
1005
+ async applyHeadersUpdate() {
973
1006
  if (!this.currentServer || !this.currentServer.name) {
974
1007
  return;
975
1008
  }
@@ -995,14 +1028,14 @@ class McpAgentTester {
995
1028
  }
996
1029
  }
997
1030
 
998
- getHeadersFromForm () {
1031
+ getHeadersFromForm() {
999
1032
  const headers = {};
1000
1033
 
1001
1034
  if (this.usedHeaders.length === 0) {
1002
1035
  return headers;
1003
1036
  }
1004
1037
 
1005
- this.usedHeaders.forEach(header => {
1038
+ this.usedHeaders.forEach((header) => {
1006
1039
  const input = document.getElementById(`header_${header.name}`);
1007
1040
  if (input && input.value.trim()) {
1008
1041
  headers[header.name] = input.value.trim();
@@ -1012,10 +1045,14 @@ class McpAgentTester {
1012
1045
  return headers;
1013
1046
  }
1014
1047
 
1015
- isOwnService () {
1016
- if (!this.defaultMcpUrl) { return false; }
1048
+ isOwnService() {
1049
+ if (!this.defaultMcpUrl) {
1050
+ return false;
1051
+ }
1017
1052
  const current = this.serverUrlInput?.value?.trim();
1018
- if (!current) { return false; }
1053
+ if (!current) {
1054
+ return false;
1055
+ }
1019
1056
  const norm = (s) => {
1020
1057
  try {
1021
1058
  const u = new URL(s, window.location.origin);
@@ -1033,24 +1070,32 @@ class McpAgentTester {
1033
1070
  return norm(current) === norm(this.defaultMcpUrl);
1034
1071
  }
1035
1072
 
1036
- async autoFillAuthHeader () {
1037
- if (!this.authEnabled) {return;}
1073
+ async autoFillAuthHeader() {
1074
+ if (!this.authEnabled) {
1075
+ return;
1076
+ }
1038
1077
 
1039
- const hasAuthHeader = this.usedHeaders.some(h => h.name === 'Authorization');
1040
- if (!hasAuthHeader) {return;}
1078
+ const hasAuthHeader = this.usedHeaders.some((h) => h.name === 'Authorization');
1079
+ if (!hasAuthHeader) {
1080
+ return;
1081
+ }
1041
1082
 
1042
1083
  const savedHeaders = this.loadHeaderValuesFromStorage();
1043
1084
 
1044
1085
  try {
1045
1086
  const response = await apiFetch(`${API_BASE}/api/auth-token`);
1046
- if (!response.ok) {return;}
1087
+ if (!response.ok) {
1088
+ return;
1089
+ }
1047
1090
 
1048
1091
  const data = await response.json();
1049
1092
  this._currentAuthType = data.authType;
1050
1093
 
1051
1094
  // For non-JWT auth, keep user's saved value if any.
1052
1095
  // JWT must always be refreshed (short-lived, regenerated on page reload).
1053
- if (data.authType !== 'jwtToken' && savedHeaders['Authorization']) {return;}
1096
+ if (data.authType !== 'jwtToken' && savedHeaders['Authorization']) {
1097
+ return;
1098
+ }
1054
1099
 
1055
1100
  const input = document.getElementById('header_Authorization');
1056
1101
  if (input) {
@@ -1071,21 +1116,27 @@ class McpAgentTester {
1071
1116
 
1072
1117
  // Compute the delay (ms) until the next refresh: ~1/3 of TTL minus a 60s safety window.
1073
1118
  // Math.max(30, ...) clamps against negative or too-short delays when ttl/3 - 60 <= 30.
1074
- _refreshDelayMs (ttlSec) {
1119
+ _refreshDelayMs(ttlSec) {
1075
1120
  const ttl = Number(ttlSec);
1076
- if (!Number.isFinite(ttl) || ttl <= 0) { return 3 * 60 * 1000; }
1121
+ if (!Number.isFinite(ttl) || ttl <= 0) {
1122
+ return 3 * 60 * 1000;
1123
+ }
1077
1124
  return Math.max(30, ttl / 3 - 60) * 1000;
1078
1125
  }
1079
1126
 
1080
- startAuthRefresh (ttlSec) {
1127
+ startAuthRefresh(ttlSec) {
1081
1128
  this.stopAuthRefresh();
1082
- if (ttlSec) { this._authTtlSec = Number(ttlSec); }
1129
+ if (ttlSec) {
1130
+ this._authTtlSec = Number(ttlSec);
1131
+ }
1083
1132
  this._scheduleNextRefresh(this._refreshDelayMs(this._authTtlSec));
1084
1133
  this._attachAuthVisibilityListeners();
1085
1134
  }
1086
1135
 
1087
- _scheduleNextRefresh (delayMs) {
1088
- if (this._authRefreshTimer) { clearTimeout(this._authRefreshTimer); }
1136
+ _scheduleNextRefresh(delayMs) {
1137
+ if (this._authRefreshTimer) {
1138
+ clearTimeout(this._authRefreshTimer);
1139
+ }
1089
1140
  this._authRefreshTimer = setTimeout(() => {
1090
1141
  this._authRefreshTimer = null;
1091
1142
  this._doRefreshAuthToken().finally(() => {
@@ -1098,14 +1149,20 @@ class McpAgentTester {
1098
1149
  }, delayMs);
1099
1150
  }
1100
1151
 
1101
- async _doRefreshAuthToken () {
1102
- if (this._authRefreshInFlight) { return; }
1152
+ async _doRefreshAuthToken() {
1153
+ if (this._authRefreshInFlight) {
1154
+ return;
1155
+ }
1103
1156
  this._authRefreshInFlight = true;
1104
1157
  try {
1105
1158
  const response = await apiFetch(`${API_BASE}/api/auth-token/refresh`, { method: 'POST' });
1106
- if (!response.ok) { return; }
1159
+ if (!response.ok) {
1160
+ return;
1161
+ }
1107
1162
  const data = await response.json();
1108
- if (data.ttlSec) { this._authTtlSec = Number(data.ttlSec); }
1163
+ if (data.ttlSec) {
1164
+ this._authTtlSec = Number(data.ttlSec);
1165
+ }
1109
1166
  const input = document.getElementById('header_Authorization');
1110
1167
  if (input) {
1111
1168
  input.value = data.token;
@@ -1119,14 +1176,22 @@ class McpAgentTester {
1119
1176
  }
1120
1177
  }
1121
1178
 
1122
- _attachAuthVisibilityListeners () {
1123
- if (this._authVisibilityListenerAttached) { return; }
1179
+ _attachAuthVisibilityListeners() {
1180
+ if (this._authVisibilityListenerAttached) {
1181
+ return;
1182
+ }
1124
1183
  this._authVisibilityListenerAttached = true;
1125
1184
  const handler = () => {
1126
1185
  // Background-tab throttling can starve setTimeout — refresh eagerly when the user comes back.
1127
- if (document.visibilityState !== 'visible') { return; }
1128
- if (this._currentAuthType !== 'jwtToken') { return; }
1129
- if (!this.isOwnService()) { return; }
1186
+ if (document.visibilityState !== 'visible') {
1187
+ return;
1188
+ }
1189
+ if (this._currentAuthType !== 'jwtToken') {
1190
+ return;
1191
+ }
1192
+ if (!this.isOwnService()) {
1193
+ return;
1194
+ }
1130
1195
  this._doRefreshAuthToken().finally(() => {
1131
1196
  if (this._authTtlSec) {
1132
1197
  this._scheduleNextRefresh(this._refreshDelayMs(this._authTtlSec));
@@ -1137,14 +1202,14 @@ class McpAgentTester {
1137
1202
  window.addEventListener('focus', handler);
1138
1203
  }
1139
1204
 
1140
- stopAuthRefresh () {
1205
+ stopAuthRefresh() {
1141
1206
  if (this._authRefreshTimer) {
1142
1207
  clearTimeout(this._authRefreshTimer);
1143
1208
  this._authRefreshTimer = null;
1144
1209
  }
1145
1210
  }
1146
1211
 
1147
- resetConnectionForm () {
1212
+ resetConnectionForm() {
1148
1213
  this.stopAuthRefresh();
1149
1214
  this.mcpConnectionForm.reset();
1150
1215
  this.serverUrlInput.value = '';
@@ -1165,7 +1230,7 @@ class McpAgentTester {
1165
1230
  localStorage.removeItem('mcpAgentFormValues');
1166
1231
  }
1167
1232
 
1168
- async loadCurrentServer () {
1233
+ async loadCurrentServer() {
1169
1234
  try {
1170
1235
  const response = await apiFetch(`${API_BASE}/api/mcp/servers`);
1171
1236
  const servers = await response.json();
@@ -1179,7 +1244,6 @@ class McpAgentTester {
1179
1244
  this.updateConnectionStatus();
1180
1245
  this.renderServerInfo();
1181
1246
  }
1182
-
1183
1247
  } catch (error) {
1184
1248
  console.error('Error loading current server:', error);
1185
1249
  this.currentServer = null;
@@ -1188,7 +1252,7 @@ class McpAgentTester {
1188
1252
  }
1189
1253
  }
1190
1254
 
1191
- renderServerInfo () {
1255
+ renderServerInfo() {
1192
1256
  if (!this.currentServer) {
1193
1257
  this.connectedServersContainer.innerHTML = '';
1194
1258
  return;
@@ -1220,7 +1284,7 @@ class McpAgentTester {
1220
1284
  });
1221
1285
  }
1222
1286
 
1223
- async disconnectServer () {
1287
+ async disconnectServer() {
1224
1288
  if (!this.currentServer) {
1225
1289
  return;
1226
1290
  }
@@ -1246,14 +1310,13 @@ class McpAgentTester {
1246
1310
  } else {
1247
1311
  this.showToast('Failed to disconnect', 'error');
1248
1312
  }
1249
-
1250
1313
  } catch (error) {
1251
1314
  console.error('Disconnect error:', error);
1252
1315
  this.showToast('Disconnect failed: ' + error.message, 'error');
1253
1316
  }
1254
1317
  }
1255
1318
 
1256
- async handleReconnect () {
1319
+ async handleReconnect() {
1257
1320
  if (!this.currentServer) {
1258
1321
  return;
1259
1322
  }
@@ -1297,8 +1360,10 @@ class McpAgentTester {
1297
1360
  }
1298
1361
  }
1299
1362
 
1300
- updateConnectionStatus () {
1301
- if (!this.connectionStatus) {return;}
1363
+ updateConnectionStatus() {
1364
+ if (!this.connectionStatus) {
1365
+ return;
1366
+ }
1302
1367
  if (this.currentServer && this.currentServer.isConnected) {
1303
1368
  this.connectionStatus.textContent = `Connected to ${this.currentServer.name}`;
1304
1369
  this.connectionStatus.classList.add('connected');
@@ -1308,7 +1373,7 @@ class McpAgentTester {
1308
1373
  }
1309
1374
  }
1310
1375
 
1311
- handleInputChange () {
1376
+ handleInputChange() {
1312
1377
  const { length } = this.messageInput.value;
1313
1378
  this.charCount.textContent = `${length}/40000`;
1314
1379
 
@@ -1324,16 +1389,18 @@ class McpAgentTester {
1324
1389
  }
1325
1390
  }
1326
1391
 
1327
- handleKeyDown (event) {
1392
+ handleKeyDown(event) {
1328
1393
  if (event.key === 'Enter' && !event.shiftKey) {
1329
1394
  event.preventDefault();
1330
1395
  this.sendMessage();
1331
1396
  }
1332
1397
  }
1333
1398
 
1334
- async sendMessage () {
1399
+ async sendMessage() {
1335
1400
  const message = this.messageInput.value.trim();
1336
- if (!message) {return;}
1401
+ if (!message) {
1402
+ return;
1403
+ }
1337
1404
 
1338
1405
  if (!this.validateLlmSettings()) {
1339
1406
  return;
@@ -1357,12 +1424,14 @@ class McpAgentTester {
1357
1424
  customPrompt: trim(this.customPromptTextarea.value) || undefined,
1358
1425
  model: modelConfig.model,
1359
1426
  useStreaming: false,
1360
- mcpConfig: this.mcpConfig.url ? {
1361
- url: this.mcpConfig.url,
1362
- transport: this.mcpConfig.transport,
1363
- headers: this.mcpConfig.headers,
1364
- name: this.mcpConfig.name,
1365
- } : undefined,
1427
+ mcpConfig: this.mcpConfig.url
1428
+ ? {
1429
+ url: this.mcpConfig.url,
1430
+ transport: this.mcpConfig.transport,
1431
+ headers: this.mcpConfig.headers,
1432
+ name: this.mcpConfig.name,
1433
+ }
1434
+ : undefined,
1366
1435
  modelConfig: modelConfig,
1367
1436
  };
1368
1437
 
@@ -1381,7 +1450,6 @@ class McpAgentTester {
1381
1450
  this.currentSessionId = result.sessionId;
1382
1451
 
1383
1452
  this.addMessage(result.message, 'assistant', result.metadata);
1384
-
1385
1453
  } catch (error) {
1386
1454
  console.error('Send message error:', error);
1387
1455
  this.addMessage(`Error: ${error.message}`, 'assistant', { error: true });
@@ -1391,7 +1459,7 @@ class McpAgentTester {
1391
1459
  }
1392
1460
  }
1393
1461
 
1394
- addMessage (text, sender, metadata = {}) {
1462
+ addMessage(text, sender, metadata = {}) {
1395
1463
  const messageId = Date.now() + '_' + Math.random().toString(36).substr(2, 9);
1396
1464
  const messageDiv = document.createElement('div');
1397
1465
  messageDiv.className = `message ${sender}`;
@@ -1471,15 +1539,15 @@ class McpAgentTester {
1471
1539
  this.scrollToBottom();
1472
1540
  }
1473
1541
 
1474
- showTypingIndicator () {
1542
+ showTypingIndicator() {
1475
1543
  this.typingIndicator.classList.add('visible');
1476
1544
  }
1477
1545
 
1478
- hideTypingIndicator () {
1546
+ hideTypingIndicator() {
1479
1547
  this.typingIndicator.classList.remove('visible');
1480
1548
  }
1481
1549
 
1482
- clearChat () {
1550
+ clearChat() {
1483
1551
  const welcomeMessage = this.chatMessages.querySelector('.message.welcome');
1484
1552
  this.chatMessages.innerHTML = '';
1485
1553
  if (welcomeMessage) {
@@ -1491,32 +1559,33 @@ class McpAgentTester {
1491
1559
  this.showToast('Chat cleared', 'success');
1492
1560
  }
1493
1561
 
1494
- scrollToBottom () {
1562
+ scrollToBottom() {
1495
1563
  setTimeout(() => {
1496
1564
  this.chatMessages.scrollTop = this.chatMessages.scrollHeight;
1497
1565
  }, 100);
1498
1566
  }
1499
1567
 
1500
- showLoading (message = 'Loading...') {
1568
+ showLoading(message = 'Loading...') {
1501
1569
  this.loadingOverlay.querySelector('span').textContent = message;
1502
1570
  this.loadingOverlay.style.display = 'flex';
1503
1571
  }
1504
1572
 
1505
- hideLoading () {
1573
+ hideLoading() {
1506
1574
  this.loadingOverlay.style.display = 'none';
1507
1575
  }
1508
1576
 
1509
- showToast (message, type = 'info') {
1577
+ showToast(message, type = 'info') {
1510
1578
  const toast = document.createElement('div');
1511
1579
  toast.className = `toast ${type}`;
1512
1580
  toast.setAttribute('data-testid', `at-toast-${type}`);
1513
1581
 
1514
- const icon = {
1515
- 'success': 'check_circle',
1516
- 'error': 'error',
1517
- 'warning': 'warning',
1518
- 'info': 'info',
1519
- }[type] || 'info';
1582
+ const icon =
1583
+ {
1584
+ success: 'check_circle',
1585
+ error: 'error',
1586
+ warning: 'warning',
1587
+ info: 'info',
1588
+ }[type] || 'info';
1520
1589
 
1521
1590
  toast.innerHTML = `
1522
1591
  <span class="material-icons-round">${icon}</span>
@@ -1538,11 +1607,13 @@ class McpAgentTester {
1538
1607
  });
1539
1608
  }
1540
1609
 
1541
- initLlmSettings () {
1610
+ initLlmSettings() {
1542
1611
  let stored = {};
1543
1612
  try {
1544
1613
  stored = JSON.parse(localStorage.getItem(LLM_LS_KEY) || '{}');
1545
- } catch { stored = {}; }
1614
+ } catch {
1615
+ stored = {};
1616
+ }
1546
1617
 
1547
1618
  const merged = { ...LLM_DEFAULTS, ...stored };
1548
1619
  const cfg = this.llmDefaults || {};
@@ -1559,13 +1630,15 @@ class McpAgentTester {
1559
1630
 
1560
1631
  this.llmSettings = merged;
1561
1632
 
1562
- if (touched) { this.saveLlmSettings(); }
1633
+ if (touched) {
1634
+ this.saveLlmSettings();
1635
+ }
1563
1636
 
1564
1637
  this.migrateLegacyLlmSettings();
1565
1638
  this.renderModelDisplay();
1566
1639
  }
1567
1640
 
1568
- migrateLegacyLlmSettings () {
1641
+ migrateLegacyLlmSettings() {
1569
1642
  try {
1570
1643
  const legacy = JSON.parse(localStorage.getItem('mcpAgentFormValues') || '{}');
1571
1644
  let dirty = false;
@@ -1581,12 +1654,18 @@ class McpAgentTester {
1581
1654
  };
1582
1655
  for (const [from, to] of Object.entries(map)) {
1583
1656
  const raw = legacy[from];
1584
- if (raw == null || raw === '') { continue; }
1657
+ if (raw == null || raw === '') {
1658
+ continue;
1659
+ }
1585
1660
  const current = this.llmSettings[to];
1586
1661
  const isEmpty = current == null || current === '' || current === LLM_DEFAULTS[to];
1587
- if (!isEmpty) { continue; }
1662
+ if (!isEmpty) {
1663
+ continue;
1664
+ }
1588
1665
  const v = numericFields.has(to) ? Number(raw) : raw;
1589
- if (numericFields.has(to) && Number.isNaN(v)) { continue; }
1666
+ if (numericFields.has(to) && Number.isNaN(v)) {
1667
+ continue;
1668
+ }
1590
1669
  this.llmSettings[to] = v;
1591
1670
  dirty = true;
1592
1671
  }
@@ -1599,10 +1678,12 @@ class McpAgentTester {
1599
1678
  this.saveLlmSettings();
1600
1679
  this.renderModelDisplay();
1601
1680
  }
1602
- } catch { /* ignore */ }
1681
+ } catch {
1682
+ /* ignore */
1683
+ }
1603
1684
  }
1604
1685
 
1605
- saveLlmSettings () {
1686
+ saveLlmSettings() {
1606
1687
  try {
1607
1688
  localStorage.setItem(LLM_LS_KEY, JSON.stringify(this.llmSettings));
1608
1689
  } catch (e) {
@@ -1610,13 +1691,13 @@ class McpAgentTester {
1610
1691
  }
1611
1692
  }
1612
1693
 
1613
- renderModelDisplay () {
1694
+ renderModelDisplay() {
1614
1695
  const name = trim(this.llmSettings.model) || '—';
1615
1696
  this.modelDisplay.textContent = name;
1616
1697
  this.apiKeyWarning.style.display = this.llmSettings.apiKey ? 'none' : 'block';
1617
1698
  }
1618
1699
 
1619
- openLlmModal () {
1700
+ openLlmModal() {
1620
1701
  const s = this.llmSettings;
1621
1702
  this.llmBaseUrl.value = s.baseURL || '';
1622
1703
  this.llmApiKey.value = s.apiKey || '';
@@ -1629,17 +1710,19 @@ class McpAgentTester {
1629
1710
  // Reset API key visibility to hidden on open
1630
1711
  this.llmApiKey.type = 'password';
1631
1712
  const icon = this.llmApiKeyToggle.querySelector('.material-icons-round');
1632
- if (icon) { icon.textContent = 'visibility'; }
1713
+ if (icon) {
1714
+ icon.textContent = 'visibility';
1715
+ }
1633
1716
 
1634
1717
  this.llmModal.style.display = 'flex';
1635
1718
  }
1636
1719
 
1637
- closeLlmModal () {
1720
+ closeLlmModal() {
1638
1721
  this.closeLlmModelDropdown();
1639
1722
  this.llmModal.style.display = 'none';
1640
1723
  }
1641
1724
 
1642
- saveLlmModal () {
1725
+ saveLlmModal() {
1643
1726
  const baseURL = trim(this.llmBaseUrl.value);
1644
1727
  const apiKey = trim(this.llmApiKey.value);
1645
1728
  const model = trim(this.llmModelName.value);
@@ -1649,13 +1732,23 @@ class McpAgentTester {
1649
1732
  const toolResultLimitChars = parseInt(this.llmLimitChars.value, 10);
1650
1733
 
1651
1734
  const missing = [];
1652
- if (!model) { missing.push('Model Name'); }
1735
+ if (!model) {
1736
+ missing.push('Model Name');
1737
+ }
1653
1738
  // baseURL is optional (OpenAI default) — empty means use provider default
1654
1739
  // apiKey intentionally not required here — its absence triggers the red warning instead
1655
- if (Number.isNaN(temperature)) { missing.push('Temperature'); }
1656
- if (!maxTokens) { missing.push('Max Tokens'); }
1657
- if (!maxTurns) { missing.push('Max Turns'); }
1658
- if (!toolResultLimitChars) { missing.push('Limit (chars)'); }
1740
+ if (Number.isNaN(temperature)) {
1741
+ missing.push('Temperature');
1742
+ }
1743
+ if (!maxTokens) {
1744
+ missing.push('Max Tokens');
1745
+ }
1746
+ if (!maxTurns) {
1747
+ missing.push('Max Turns');
1748
+ }
1749
+ if (!toolResultLimitChars) {
1750
+ missing.push('Limit (chars)');
1751
+ }
1659
1752
 
1660
1753
  if (missing.length) {
1661
1754
  this.showToast(`Missing required fields: ${missing.join(', ')}`, 'error');
@@ -1669,18 +1762,22 @@ class McpAgentTester {
1669
1762
  this.showToast('LLM settings saved', 'success');
1670
1763
  }
1671
1764
 
1672
- toggleApiKeyVisibility () {
1765
+ toggleApiKeyVisibility() {
1673
1766
  const icon = this.llmApiKeyToggle.querySelector('.material-icons-round');
1674
1767
  if (this.llmApiKey.type === 'password') {
1675
1768
  this.llmApiKey.type = 'text';
1676
- if (icon) { icon.textContent = 'visibility_off'; }
1769
+ if (icon) {
1770
+ icon.textContent = 'visibility_off';
1771
+ }
1677
1772
  } else {
1678
1773
  this.llmApiKey.type = 'password';
1679
- if (icon) { icon.textContent = 'visibility'; }
1774
+ if (icon) {
1775
+ icon.textContent = 'visibility';
1776
+ }
1680
1777
  }
1681
1778
  }
1682
1779
 
1683
- renderLlmModelDropdown () {
1780
+ renderLlmModelDropdown() {
1684
1781
  this.llmModelDropdownList.innerHTML = '';
1685
1782
  LLM_PRESET_MODELS.forEach((name) => {
1686
1783
  const item = document.createElement('div');
@@ -1696,7 +1793,7 @@ class McpAgentTester {
1696
1793
  });
1697
1794
  }
1698
1795
 
1699
- toggleLlmModelDropdown (e) {
1796
+ toggleLlmModelDropdown(e) {
1700
1797
  e.preventDefault();
1701
1798
  e.stopPropagation();
1702
1799
  const visible = this.llmModelDropdownList.style.display !== 'none';
@@ -1707,7 +1804,7 @@ class McpAgentTester {
1707
1804
  }
1708
1805
  }
1709
1806
 
1710
- openLlmModelDropdown () {
1807
+ openLlmModelDropdown() {
1711
1808
  this.llmModelDropdownList.style.display = 'block';
1712
1809
  this.llmModelDropdownToggle.classList.add('active');
1713
1810
  // Close on outside click (one-shot)
@@ -1722,17 +1819,21 @@ class McpAgentTester {
1722
1819
  }, 0);
1723
1820
  }
1724
1821
 
1725
- closeLlmModelDropdown () {
1822
+ closeLlmModelDropdown() {
1726
1823
  this.llmModelDropdownList.style.display = 'none';
1727
1824
  this.llmModelDropdownToggle.classList.remove('active');
1728
1825
  }
1729
1826
 
1730
- validateLlmSettings () {
1827
+ validateLlmSettings() {
1731
1828
  const s = this.llmSettings;
1732
1829
  const missing = [];
1733
1830
  // baseURL is optional — empty means use provider default (OpenAI)
1734
- if (!s.apiKey) { missing.push('API Key'); }
1735
- if (!s.model) { missing.push('Model Name'); }
1831
+ if (!s.apiKey) {
1832
+ missing.push('API Key');
1833
+ }
1834
+ if (!s.model) {
1835
+ missing.push('Model Name');
1836
+ }
1736
1837
  if (missing.length) {
1737
1838
  this.showToast(`Cannot send message — missing: ${missing.join(', ')}. Open LLM Settings.`, 'error');
1738
1839
  return false;
@@ -1740,7 +1841,7 @@ class McpAgentTester {
1740
1841
  return true;
1741
1842
  }
1742
1843
 
1743
- getModelConfig () {
1844
+ getModelConfig() {
1744
1845
  const s = this.llmSettings;
1745
1846
  return {
1746
1847
  baseURL: s.baseURL,
@@ -1753,7 +1854,7 @@ class McpAgentTester {
1753
1854
  };
1754
1855
  }
1755
1856
 
1756
- handleServerUrlChange () {
1857
+ handleServerUrlChange() {
1757
1858
  this.stopAuthRefresh();
1758
1859
  let url = this.serverUrlInput.value.trim();
1759
1860
 
@@ -1781,7 +1882,7 @@ class McpAgentTester {
1781
1882
  this.saveFormValuesToStorage();
1782
1883
  }
1783
1884
 
1784
- resetAgentPrompt () {
1885
+ resetAgentPrompt() {
1785
1886
  if (this.originalAgentPrompt) {
1786
1887
  this.systemPromptTextarea.value = this.originalAgentPrompt;
1787
1888
  this.currentSystemPrompt = this.originalAgentPrompt;
@@ -1790,8 +1891,10 @@ class McpAgentTester {
1790
1891
  }
1791
1892
  }
1792
1893
 
1793
- viewOriginalPrompt () {
1794
- if (!this.originalAgentPrompt) { return; }
1894
+ viewOriginalPrompt() {
1895
+ if (!this.originalAgentPrompt) {
1896
+ return;
1897
+ }
1795
1898
  const modal = document.getElementById('promptModal');
1796
1899
  const textarea = document.getElementById('promptModalTextarea');
1797
1900
  const title = document.getElementById('promptModalTitle');
@@ -1806,12 +1909,12 @@ class McpAgentTester {
1806
1909
  textarea.focus();
1807
1910
  }
1808
1911
 
1809
- updateResetPromptButton () {
1912
+ updateResetPromptButton() {
1810
1913
  this.btnResetAgentPrompt.style.display = this.originalAgentPrompt ? '' : 'none';
1811
1914
  this.updatePromptModifiedState();
1812
1915
  }
1813
1916
 
1814
- updatePromptModifiedState () {
1917
+ updatePromptModifiedState() {
1815
1918
  const hasOriginal = !!this.originalAgentPrompt;
1816
1919
  const isModified = hasOriginal && this.systemPromptTextarea.value.trim() !== this.originalAgentPrompt.trim();
1817
1920
  this.promptModifiedBadge.style.display = isModified ? '' : 'none';
@@ -1823,7 +1926,7 @@ class McpAgentTester {
1823
1926
  }
1824
1927
  }
1825
1928
 
1826
- saveFormValuesToStorage () {
1929
+ saveFormValuesToStorage() {
1827
1930
  const formData = {
1828
1931
  serverUrl: this.serverUrlInput.value,
1829
1932
  transport: this.transportSelect.value,
@@ -1833,7 +1936,7 @@ class McpAgentTester {
1833
1936
  localStorage.setItem('mcpAgentFormValues', JSON.stringify(formData));
1834
1937
  }
1835
1938
 
1836
- loadFormValuesFromURL () {
1939
+ loadFormValuesFromURL() {
1837
1940
  try {
1838
1941
  const params = new URLSearchParams(window.location.search);
1839
1942
  const serverUrl = params.get('serverUrl');
@@ -1850,22 +1953,30 @@ class McpAgentTester {
1850
1953
  }
1851
1954
  }
1852
1955
 
1853
- loadFormValuesFromStorage () {
1956
+ loadFormValuesFromStorage() {
1854
1957
  try {
1855
1958
  const stored = localStorage.getItem('mcpAgentFormValues');
1856
1959
  if (stored) {
1857
1960
  const formData = JSON.parse(stored);
1858
- if (formData.serverUrl) {this.serverUrlInput.value = formData.serverUrl;}
1859
- if (formData.transport) {this.transportSelect.value = formData.transport;}
1860
- if (formData.agentPrompt) {this.systemPromptTextarea.value = trim(formData.agentPrompt);}
1861
- if (formData.customPrompt) {this.customPromptTextarea.value = trim(formData.customPrompt);}
1961
+ if (formData.serverUrl) {
1962
+ this.serverUrlInput.value = formData.serverUrl;
1963
+ }
1964
+ if (formData.transport) {
1965
+ this.transportSelect.value = formData.transport;
1966
+ }
1967
+ if (formData.agentPrompt) {
1968
+ this.systemPromptTextarea.value = trim(formData.agentPrompt);
1969
+ }
1970
+ if (formData.customPrompt) {
1971
+ this.customPromptTextarea.value = trim(formData.customPrompt);
1972
+ }
1862
1973
  }
1863
1974
  } catch (error) {
1864
1975
  console.error('Error loading form values from storage:', error);
1865
1976
  }
1866
1977
  }
1867
1978
 
1868
- getSavedUrls () {
1979
+ getSavedUrls() {
1869
1980
  try {
1870
1981
  const saved = localStorage.getItem('mcpSavedUrls');
1871
1982
  return saved ? JSON.parse(saved) : [];
@@ -1875,7 +1986,7 @@ class McpAgentTester {
1875
1986
  }
1876
1987
  }
1877
1988
 
1878
- saveSavedUrls (urls) {
1989
+ saveSavedUrls(urls) {
1879
1990
  try {
1880
1991
  localStorage.setItem('mcpSavedUrls', JSON.stringify(urls));
1881
1992
  } catch (error) {
@@ -1883,7 +1994,7 @@ class McpAgentTester {
1883
1994
  }
1884
1995
  }
1885
1996
 
1886
- addUrlToSaved (url) {
1997
+ addUrlToSaved(url) {
1887
1998
  if (!url || url.trim() === '') {
1888
1999
  return;
1889
2000
  }
@@ -1891,7 +2002,7 @@ class McpAgentTester {
1891
2002
  url = url.trim();
1892
2003
  let savedUrls = this.getSavedUrls();
1893
2004
 
1894
- savedUrls = savedUrls.filter(savedUrl => savedUrl !== url);
2005
+ savedUrls = savedUrls.filter((savedUrl) => savedUrl !== url);
1895
2006
 
1896
2007
  savedUrls.unshift(url);
1897
2008
 
@@ -1901,14 +2012,14 @@ class McpAgentTester {
1901
2012
  this.renderSavedUrls();
1902
2013
  }
1903
2014
 
1904
- removeUrlFromSaved (url) {
2015
+ removeUrlFromSaved(url) {
1905
2016
  let savedUrls = this.getSavedUrls();
1906
- savedUrls = savedUrls.filter(savedUrl => savedUrl !== url);
2017
+ savedUrls = savedUrls.filter((savedUrl) => savedUrl !== url);
1907
2018
  this.saveSavedUrls(savedUrls);
1908
2019
  this.renderSavedUrls();
1909
2020
  }
1910
2021
 
1911
- renderSavedUrls () {
2022
+ renderSavedUrls() {
1912
2023
  const savedUrls = this.getSavedUrls();
1913
2024
  this.savedUrlsList.innerHTML = '';
1914
2025
 
@@ -1920,7 +2031,7 @@ class McpAgentTester {
1920
2031
  return;
1921
2032
  }
1922
2033
 
1923
- savedUrls.forEach(url => {
2034
+ savedUrls.forEach((url) => {
1924
2035
  const item = document.createElement('div');
1925
2036
  item.className = 'dropdown-item';
1926
2037
  item.setAttribute('data-testid', 'at-saved-url-item');
@@ -1947,14 +2058,14 @@ class McpAgentTester {
1947
2058
  });
1948
2059
  }
1949
2060
 
1950
- selectUrl (url) {
2061
+ selectUrl(url) {
1951
2062
  this.serverUrlInput.value = url;
1952
2063
  this.handleServerUrlChange();
1953
2064
  this.closeUrlDropdown();
1954
2065
  this.autoConnect();
1955
2066
  }
1956
2067
 
1957
- toggleUrlDropdown (e) {
2068
+ toggleUrlDropdown(e) {
1958
2069
  e.preventDefault();
1959
2070
  e.stopPropagation();
1960
2071
 
@@ -1967,7 +2078,7 @@ class McpAgentTester {
1967
2078
  }
1968
2079
  }
1969
2080
 
1970
- openUrlDropdown () {
2081
+ openUrlDropdown() {
1971
2082
  this.renderSavedUrls();
1972
2083
  this.serverUrlDropdownList.style.display = 'block';
1973
2084
  this.serverUrlDropdown.classList.add('active');
@@ -1980,12 +2091,12 @@ class McpAgentTester {
1980
2091
  }
1981
2092
  }
1982
2093
 
1983
- closeUrlDropdown () {
2094
+ closeUrlDropdown() {
1984
2095
  this.serverUrlDropdownList.style.display = 'none';
1985
2096
  this.serverUrlDropdown.classList.remove('active');
1986
2097
  }
1987
2098
 
1988
- addCurrentUrlToSaved () {
2099
+ addCurrentUrlToSaved() {
1989
2100
  const currentUrl = this.serverUrlInput.value.trim();
1990
2101
  if (currentUrl) {
1991
2102
  this.addUrlToSaved(currentUrl);
@@ -1994,7 +2105,7 @@ class McpAgentTester {
1994
2105
  }
1995
2106
  }
1996
2107
 
1997
- handleClickOutside (e) {
2108
+ handleClickOutside(e) {
1998
2109
  const container = e.target.closest('.custom-select-container');
1999
2110
  if (!container) {
2000
2111
  this.closeUrlDropdown();