agent-discover 1.3.8 → 1.4.0

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 (40) hide show
  1. package/CHANGELOG.md +44 -1
  2. package/README.md +34 -8
  3. package/agent-desk-plugin.json +10 -3
  4. package/dist/context.d.ts +2 -0
  5. package/dist/context.d.ts.map +1 -1
  6. package/dist/context.js +15 -0
  7. package/dist/context.js.map +1 -1
  8. package/dist/domain/log.d.ts +7 -3
  9. package/dist/domain/log.d.ts.map +1 -1
  10. package/dist/domain/log.js +15 -5
  11. package/dist/domain/log.js.map +1 -1
  12. package/dist/domain/presets.d.ts +30 -0
  13. package/dist/domain/presets.d.ts.map +1 -0
  14. package/dist/domain/presets.js +76 -0
  15. package/dist/domain/presets.js.map +1 -0
  16. package/dist/domain/proxy.d.ts +153 -0
  17. package/dist/domain/proxy.d.ts.map +1 -1
  18. package/dist/domain/proxy.js +396 -3
  19. package/dist/domain/proxy.js.map +1 -1
  20. package/dist/domain/sampling.d.ts +9 -0
  21. package/dist/domain/sampling.d.ts.map +1 -0
  22. package/dist/domain/sampling.js +68 -0
  23. package/dist/domain/sampling.js.map +1 -0
  24. package/dist/storage/database.js +21 -0
  25. package/dist/storage/database.js.map +1 -1
  26. package/dist/transport/rest.d.ts.map +1 -1
  27. package/dist/transport/rest.js +349 -0
  28. package/dist/transport/rest.js.map +1 -1
  29. package/dist/transport/ws.d.ts.map +1 -1
  30. package/dist/transport/ws.js +32 -0
  31. package/dist/transport/ws.js.map +1 -1
  32. package/dist/ui/app.js +16 -0
  33. package/dist/ui/index.html +3 -0
  34. package/dist/ui/markdown.js +102 -0
  35. package/dist/ui/schema-form.js +393 -0
  36. package/dist/ui/styles.css +724 -0
  37. package/dist/ui/tester-window.html +116 -0
  38. package/dist/ui/tester-window.js +153 -0
  39. package/dist/ui/tester.js +1412 -0
  40. package/package.json +1 -1
@@ -0,0 +1,1412 @@
1
+ /* eslint-disable */
2
+ (function () {
3
+ 'use strict';
4
+ var AD = (window.AD = window.AD || {});
5
+
6
+ var state = {};
7
+ var eventBuffers = {};
8
+ var presetsCache = {};
9
+ var elicitationModalOpen = null;
10
+ var PRESET_KEY = 'agent-discover-tester-presets-v1';
11
+ var PRESET_MIGRATED_KEY = 'agent-discover-presets-migrated-v2';
12
+
13
+ function esc(s) {
14
+ return String(s == null ? '' : s)
15
+ .replace(/&/g, '&')
16
+ .replace(/</g, '&lt;')
17
+ .replace(/>/g, '&gt;')
18
+ .replace(/"/g, '&quot;');
19
+ }
20
+
21
+ function getState(serverId) {
22
+ if (!state[serverId]) {
23
+ state[serverId] = {
24
+ subtab: 'tools',
25
+ tools: null,
26
+ resources: null,
27
+ resourceCursor: null,
28
+ prompts: null,
29
+ info: null,
30
+ selectedTool: null,
31
+ selectedPrompt: null,
32
+ selectedResource: null,
33
+ result: null,
34
+ resultMode: 'pretty',
35
+ pingRtt: null,
36
+ loggingLevel: 'info',
37
+ loading: false,
38
+ error: null,
39
+ floating: false,
40
+ handle: null,
41
+ serverName: null,
42
+ };
43
+ }
44
+ return state[serverId];
45
+ }
46
+
47
+ function baseUrl(serverId, handle) {
48
+ return handle ? '/api/transient/' + encodeURIComponent(handle) : '/api/servers/' + serverId;
49
+ }
50
+
51
+ function doFetch(url, opts) {
52
+ return AD._fetch(url, opts).then(function (r) {
53
+ return r.json().then(function (body) {
54
+ if (!r.ok) throw new Error(body && body.error ? body.error : 'HTTP ' + r.status);
55
+ return body;
56
+ });
57
+ });
58
+ }
59
+
60
+ AD.renderTesterShell = function (server) {
61
+ var s = getState(server.id);
62
+ s.serverName = server.name;
63
+ var capsKnown = s.info && s.info !== 'error' && s.info.capabilities;
64
+ var caps = capsKnown ? s.info.capabilities : null;
65
+ // When capabilities are not yet known, show all tabs optimistically.
66
+ // The server will 400 on unsupported methods and the UI will surface it.
67
+ var hasResources = caps ? !!caps.resources : true;
68
+ var hasPrompts = caps ? !!caps.prompts : true;
69
+ return (
70
+ '<div class="tester-shell" data-tester-id="' +
71
+ server.id +
72
+ '">' +
73
+ '<div class="tester-tabs">' +
74
+ tabBtn(server.id, 'tools', 'Tools', true) +
75
+ tabBtn(server.id, 'info', 'Info', true) +
76
+ tabBtn(
77
+ server.id,
78
+ 'resources',
79
+ 'Resources',
80
+ hasResources,
81
+ !hasResources ? 'Server does not advertise resources' : '',
82
+ ) +
83
+ tabBtn(
84
+ server.id,
85
+ 'prompts',
86
+ 'Prompts',
87
+ hasPrompts,
88
+ !hasPrompts ? 'Server does not advertise prompts' : '',
89
+ ) +
90
+ tabBtn(server.id, 'events', 'Events', true) +
91
+ tabBtn(server.id, 'export', 'Export', true) +
92
+ tabBtn(server.id, 'diagnostics', 'Diagnostics', true) +
93
+ '<button class="tester-popout" data-action="tester-popout" data-id="' +
94
+ server.id +
95
+ '" title="Open in floating panel"><span class="material-symbols-outlined" style="font-size:14px">open_in_new</span></button>' +
96
+ '</div>' +
97
+ '<div class="tester-body" data-tester-body="' +
98
+ server.id +
99
+ '">' +
100
+ '<div class="hint">Expand to load...</div>' +
101
+ '</div>' +
102
+ '</div>'
103
+ );
104
+ };
105
+
106
+ function tabBtn(serverId, name, label, enabled, title) {
107
+ var st = getState(serverId);
108
+ return (
109
+ '<button class="tester-tab' +
110
+ (st.subtab === name ? ' active' : '') +
111
+ (enabled ? '' : ' disabled') +
112
+ '"' +
113
+ (enabled
114
+ ? ' data-action="tester-subtab" data-id="' + serverId + '" data-subtab="' + name + '"'
115
+ : ' disabled') +
116
+ (title ? ' title="' + esc(title) + '"' : '') +
117
+ '>' +
118
+ esc(label) +
119
+ '</button>'
120
+ );
121
+ }
122
+
123
+ AD.openTesterFor = function (serverId) {
124
+ var s = getState(serverId);
125
+ renderBody(serverId);
126
+ if (!s.info) loadInfo(serverId);
127
+ };
128
+
129
+ function renderBody(serverId) {
130
+ var bodies = document.querySelectorAll('[data-tester-body="' + serverId + '"]');
131
+ if (!bodies.length) return;
132
+ var s = getState(serverId);
133
+ var html;
134
+ switch (s.subtab) {
135
+ case 'tools':
136
+ html = renderToolsTab(serverId);
137
+ break;
138
+ case 'info':
139
+ html = renderInfoTab(serverId);
140
+ break;
141
+ case 'resources':
142
+ html = renderResourcesTab(serverId);
143
+ break;
144
+ case 'prompts':
145
+ html = renderPromptsTab(serverId);
146
+ break;
147
+ case 'events':
148
+ html = renderEventsTab(serverId);
149
+ break;
150
+ case 'export':
151
+ html = renderExportTab(serverId);
152
+ break;
153
+ case 'diagnostics':
154
+ html = renderDiagnosticsTab(serverId);
155
+ break;
156
+ default:
157
+ html = '<div class="hint">Unknown tab</div>';
158
+ }
159
+ for (var i = 0; i < bodies.length; i++) {
160
+ bodies[i].innerHTML = html;
161
+ }
162
+ syncTabHighlight(serverId);
163
+ bindBody(serverId);
164
+ }
165
+
166
+ function syncTabHighlight(serverId) {
167
+ var s = getState(serverId);
168
+ var shells = document.querySelectorAll('[data-tester-id="' + serverId + '"]');
169
+ shells.forEach(function (shell) {
170
+ shell.querySelectorAll('.tester-tab').forEach(function (b) {
171
+ if (b.getAttribute('data-subtab') === s.subtab) b.classList.add('active');
172
+ else b.classList.remove('active');
173
+ });
174
+ });
175
+ }
176
+
177
+ // ---------------------------------------------------------------------------
178
+ // Tools tab
179
+ // ---------------------------------------------------------------------------
180
+
181
+ function renderToolsTab(serverId) {
182
+ var s = getState(serverId);
183
+ if (s.tools == null) {
184
+ loadTools(serverId);
185
+ return '<div class="hint">Loading tools...</div>';
186
+ }
187
+ if (s.tools.length === 0) return '<div class="hint">This server exposes no tools.</div>';
188
+ var list = s.tools
189
+ .map(function (t) {
190
+ return (
191
+ '<button class="tester-listitem' +
192
+ (s.selectedTool === t.name ? ' active' : '') +
193
+ '" data-action="tester-select-tool" data-id="' +
194
+ serverId +
195
+ '" data-name="' +
196
+ esc(t.name) +
197
+ '">' +
198
+ '<div class="tester-listitem-name">' +
199
+ esc(t.name) +
200
+ '</div>' +
201
+ (t.description
202
+ ? '<div class="tester-listitem-desc">' + esc(t.description) + '</div>'
203
+ : '') +
204
+ '</button>'
205
+ );
206
+ })
207
+ .join('');
208
+ var panel = s.selectedTool
209
+ ? renderToolForm(serverId)
210
+ : '<div class="hint">Select a tool to call.</div>';
211
+ return (
212
+ '<div class="tester-split">' +
213
+ '<div class="tester-list">' +
214
+ list +
215
+ '</div>' +
216
+ '<div class="tester-detail">' +
217
+ panel +
218
+ '</div>' +
219
+ '</div>'
220
+ );
221
+ }
222
+
223
+ function renderToolForm(serverId) {
224
+ var s = getState(serverId);
225
+ var tool = s.tools.find(function (t) {
226
+ return t.name === s.selectedTool;
227
+ });
228
+ if (!tool) return '<div class="hint">Tool disappeared.</div>';
229
+ var presets = loadPresets(s.serverName, 'tool', tool.name);
230
+ var presetList = Object.keys(presets);
231
+ var presetOpts =
232
+ '<option value="">Preset…</option>' +
233
+ presetList
234
+ .map(function (k) {
235
+ return '<option value="' + esc(k) + '">' + esc(k) + '</option>';
236
+ })
237
+ .join('');
238
+ return (
239
+ '<div class="tester-form">' +
240
+ '<div class="tester-form-head"><strong>' +
241
+ esc(tool.name) +
242
+ '</strong>' +
243
+ (tool.description ? '<p class="tester-desc">' + esc(tool.description) + '</p>' : '') +
244
+ '</div>' +
245
+ '<div class="tester-form-fields" data-form-for="' +
246
+ esc(tool.name) +
247
+ '"></div>' +
248
+ '<div class="tester-form-actions">' +
249
+ '<button class="btn-activate" data-action="tester-call" data-id="' +
250
+ serverId +
251
+ '">Call tool</button>' +
252
+ '<select class="tester-preset-sel" data-action="tester-preset-load" data-id="' +
253
+ serverId +
254
+ '">' +
255
+ presetOpts +
256
+ '</select>' +
257
+ '<button class="btn-health" data-action="tester-preset-save" data-id="' +
258
+ serverId +
259
+ '">Save as preset</button>' +
260
+ '</div>' +
261
+ renderResult(serverId) +
262
+ '</div>'
263
+ );
264
+ }
265
+
266
+ // ---------------------------------------------------------------------------
267
+ // Info tab
268
+ // ---------------------------------------------------------------------------
269
+
270
+ function renderInfoTab(serverId) {
271
+ var s = getState(serverId);
272
+ if (s.info == null) {
273
+ loadInfo(serverId);
274
+ return '<div class="hint">Loading server info...</div>';
275
+ }
276
+ if (s.info === 'error')
277
+ return '<div class="hint error">' + esc(s.error || 'Failed to load info') + '</div>';
278
+ var caps = s.info.capabilities || {};
279
+ var capRows = Object.keys(caps)
280
+ .map(function (k) {
281
+ return (
282
+ '<div class="tester-cap-row"><span class="tester-cap-name">' +
283
+ esc(k) +
284
+ '</span><span class="tester-cap-val">' +
285
+ esc(JSON.stringify(caps[k])) +
286
+ '</span></div>'
287
+ );
288
+ })
289
+ .join('');
290
+ return (
291
+ '<div class="tester-info">' +
292
+ '<div class="tester-info-grid">' +
293
+ '<div><span class="tester-info-k">Name</span><span class="tester-info-v">' +
294
+ esc(s.info.name) +
295
+ '</span></div>' +
296
+ '<div><span class="tester-info-k">Version</span><span class="tester-info-v">' +
297
+ esc(s.info.version) +
298
+ '</span></div>' +
299
+ '</div>' +
300
+ (s.info.instructions
301
+ ? '<div class="tester-info-instr"><h4>Instructions</h4><div class="md">' +
302
+ (AD.renderMarkdown ? AD.renderMarkdown(s.info.instructions) : esc(s.info.instructions)) +
303
+ '</div></div>'
304
+ : '') +
305
+ '<h4>Capabilities</h4>' +
306
+ '<div class="tester-caps">' +
307
+ (capRows || '<div class="hint">(none)</div>') +
308
+ '</div>' +
309
+ '</div>'
310
+ );
311
+ }
312
+
313
+ // ---------------------------------------------------------------------------
314
+ // Resources tab
315
+ // ---------------------------------------------------------------------------
316
+
317
+ function renderResourcesTab(serverId) {
318
+ var s = getState(serverId);
319
+ if (s.resources == null) {
320
+ loadResources(serverId);
321
+ return '<div class="hint">Loading resources...</div>';
322
+ }
323
+ var list = s.resources
324
+ .map(function (r) {
325
+ return (
326
+ '<button class="tester-listitem' +
327
+ (s.selectedResource === r.uri ? ' active' : '') +
328
+ '" data-action="tester-select-resource" data-id="' +
329
+ serverId +
330
+ '" data-uri="' +
331
+ esc(r.uri) +
332
+ '">' +
333
+ '<div class="tester-listitem-name">' +
334
+ esc(r.name || r.uri) +
335
+ '</div>' +
336
+ '<div class="tester-listitem-desc">' +
337
+ esc(r.uri) +
338
+ (r.mimeType ? ' — ' + esc(r.mimeType) : '') +
339
+ '</div>' +
340
+ '</button>'
341
+ );
342
+ })
343
+ .join('');
344
+ var more = s.resourceCursor
345
+ ? '<button class="btn-health" data-action="tester-resources-more" data-id="' +
346
+ serverId +
347
+ '">Load more</button>'
348
+ : '';
349
+ var detail = s.selectedResource
350
+ ? renderResourceDetail(serverId)
351
+ : '<div class="hint">Pick a resource to read. You can also subscribe for live updates.</div>';
352
+ return (
353
+ '<div class="tester-split">' +
354
+ '<div class="tester-list">' +
355
+ (list || '<div class="hint">No resources.</div>') +
356
+ more +
357
+ '</div>' +
358
+ '<div class="tester-detail">' +
359
+ detail +
360
+ '</div>' +
361
+ '</div>'
362
+ );
363
+ }
364
+
365
+ function renderResourceDetail(serverId) {
366
+ var s = getState(serverId);
367
+ var contents = s.resourceContents;
368
+ var body;
369
+ if (!contents) body = '<div class="hint">Click Read to load.</div>';
370
+ else {
371
+ body =
372
+ '<div class="tester-result-raw"><pre>' +
373
+ esc(JSON.stringify(contents, null, 2)) +
374
+ '</pre></div>';
375
+ }
376
+ return (
377
+ '<div class="tester-form">' +
378
+ '<div class="tester-form-head"><strong>' +
379
+ esc(s.selectedResource) +
380
+ '</strong></div>' +
381
+ '<div class="tester-form-actions">' +
382
+ '<button class="btn-activate" data-action="tester-resource-read" data-id="' +
383
+ serverId +
384
+ '">Read</button>' +
385
+ '<button class="btn-health" data-action="tester-resource-subscribe" data-id="' +
386
+ serverId +
387
+ '">Subscribe</button>' +
388
+ '<button class="btn-delete" data-action="tester-resource-unsubscribe" data-id="' +
389
+ serverId +
390
+ '">Unsubscribe</button>' +
391
+ '</div>' +
392
+ body +
393
+ '</div>'
394
+ );
395
+ }
396
+
397
+ // ---------------------------------------------------------------------------
398
+ // Prompts tab
399
+ // ---------------------------------------------------------------------------
400
+
401
+ function renderPromptsTab(serverId) {
402
+ var s = getState(serverId);
403
+ if (s.prompts == null) {
404
+ loadPrompts(serverId);
405
+ return '<div class="hint">Loading prompts...</div>';
406
+ }
407
+ var list = s.prompts
408
+ .map(function (p) {
409
+ return (
410
+ '<button class="tester-listitem' +
411
+ (s.selectedPrompt === p.name ? ' active' : '') +
412
+ '" data-action="tester-select-prompt" data-id="' +
413
+ serverId +
414
+ '" data-name="' +
415
+ esc(p.name) +
416
+ '">' +
417
+ '<div class="tester-listitem-name">' +
418
+ esc(p.name) +
419
+ '</div>' +
420
+ (p.description
421
+ ? '<div class="tester-listitem-desc">' + esc(p.description) + '</div>'
422
+ : '') +
423
+ '</button>'
424
+ );
425
+ })
426
+ .join('');
427
+ var detail = s.selectedPrompt
428
+ ? renderPromptForm(serverId)
429
+ : '<div class="hint">Pick a prompt.</div>';
430
+ return (
431
+ '<div class="tester-split">' +
432
+ '<div class="tester-list">' +
433
+ (list || '<div class="hint">No prompts.</div>') +
434
+ '</div>' +
435
+ '<div class="tester-detail">' +
436
+ detail +
437
+ '</div>' +
438
+ '</div>'
439
+ );
440
+ }
441
+
442
+ function renderPromptForm(serverId) {
443
+ var s = getState(serverId);
444
+ var prompt = s.prompts.find(function (p) {
445
+ return p.name === s.selectedPrompt;
446
+ });
447
+ if (!prompt) return '<div class="hint">Prompt missing.</div>';
448
+ var argsRows = (prompt.arguments || [])
449
+ .map(function (a) {
450
+ return (
451
+ '<div class="sf-field" data-type="string" data-path="' +
452
+ esc(a.name) +
453
+ '">' +
454
+ '<label>' +
455
+ esc(a.name) +
456
+ (a.required ? ' <span class="sf-req">*</span>' : '') +
457
+ '</label>' +
458
+ '<input type="text" class="sf-input" data-path="' +
459
+ esc(a.name) +
460
+ '" />' +
461
+ (a.description ? '<div class="sf-desc">' + esc(a.description) + '</div>' : '') +
462
+ '</div>'
463
+ );
464
+ })
465
+ .join('');
466
+ var msgs = s.promptMessages;
467
+ var msgRender = '';
468
+ if (msgs) {
469
+ msgRender =
470
+ '<div class="tester-messages">' +
471
+ msgs
472
+ .map(function (m) {
473
+ var body = '';
474
+ if (m.content && m.content.type === 'text')
475
+ body = AD.renderMarkdown ? AD.renderMarkdown(m.content.text) : esc(m.content.text);
476
+ else body = '<pre>' + esc(JSON.stringify(m.content, null, 2)) + '</pre>';
477
+ return (
478
+ '<div class="tester-message tester-message-' +
479
+ esc(m.role) +
480
+ '"><div class="tester-msg-role">' +
481
+ esc(m.role) +
482
+ '</div><div class="tester-msg-body">' +
483
+ body +
484
+ '</div></div>'
485
+ );
486
+ })
487
+ .join('') +
488
+ '</div>';
489
+ }
490
+ return (
491
+ '<div class="tester-form">' +
492
+ '<div class="tester-form-head"><strong>' +
493
+ esc(prompt.name) +
494
+ '</strong>' +
495
+ (prompt.description ? '<p class="tester-desc">' + esc(prompt.description) + '</p>' : '') +
496
+ '</div>' +
497
+ '<div class="tester-prompt-fields">' +
498
+ (argsRows || '<div class="hint">No arguments.</div>') +
499
+ '</div>' +
500
+ '<div class="tester-form-actions">' +
501
+ '<button class="btn-activate" data-action="tester-prompt-get" data-id="' +
502
+ serverId +
503
+ '">Get prompt</button>' +
504
+ '</div>' +
505
+ msgRender +
506
+ '</div>'
507
+ );
508
+ }
509
+
510
+ // ---------------------------------------------------------------------------
511
+ // Events / Export / Diagnostics
512
+ // ---------------------------------------------------------------------------
513
+
514
+ function renderEventsTab(serverId) {
515
+ var s = getState(serverId);
516
+ var buf = eventBuffers[s.serverName] || [];
517
+ if (buf.length === 0)
518
+ return '<div class="hint">No events yet. Server notifications and progress updates will appear here.</div>';
519
+ var rows = buf
520
+ .slice(0, 100)
521
+ .map(function (e) {
522
+ return (
523
+ '<div class="tester-event tester-event-' +
524
+ esc(e.type) +
525
+ '">' +
526
+ '<span class="tester-event-ts">' +
527
+ esc((e.ts || '').substr(11, 8)) +
528
+ '</span>' +
529
+ '<span class="tester-event-kind">' +
530
+ esc(e.type) +
531
+ '</span>' +
532
+ '<span class="tester-event-method">' +
533
+ esc(e.method || (e.payload && e.payload.token) || '') +
534
+ '</span>' +
535
+ '<pre class="tester-event-payload">' +
536
+ esc(JSON.stringify(e.params || e.payload || {}, null, 2)) +
537
+ '</pre>' +
538
+ '</div>'
539
+ );
540
+ })
541
+ .join('');
542
+ return '<div class="tester-events">' + rows + '</div>';
543
+ }
544
+
545
+ function renderExportTab(serverId) {
546
+ var s = getState(serverId);
547
+ if (!s.exportFormat) s.exportFormat = 'mcp-json';
548
+ var fmt = s.exportFormat;
549
+ if (!s.exportBody || s.exportBody.format !== fmt) {
550
+ exportConfig(serverId, fmt);
551
+ }
552
+ var formats = [
553
+ {
554
+ id: 'mcp-json',
555
+ label: 'mcp.json',
556
+ hint: 'Generic MCP client config — Claude Desktop, Claude Code, Cursor, Windsurf.',
557
+ },
558
+ {
559
+ id: 'agent-discover',
560
+ label: 'agent-discover',
561
+ hint: 'Declarative setup file (AGENT_DISCOVER_SETUP_FILE).',
562
+ },
563
+ ];
564
+ var seg = formats
565
+ .map(function (f) {
566
+ return (
567
+ '<button class="tester-seg' +
568
+ (fmt === f.id ? ' active' : '') +
569
+ '" data-action="tester-export" data-id="' +
570
+ serverId +
571
+ '" data-format="' +
572
+ f.id +
573
+ '">' +
574
+ esc(f.label) +
575
+ '</button>'
576
+ );
577
+ })
578
+ .join('');
579
+ var hint = formats.find(function (f) {
580
+ return f.id === fmt;
581
+ });
582
+ var body =
583
+ s.exportBody && s.exportBody.format === fmt
584
+ ? '<div class="tester-result-raw"><pre>' +
585
+ (AD.highlightJson
586
+ ? AD.highlightJson(s.exportBody.config)
587
+ : esc(JSON.stringify(s.exportBody.config, null, 2))) +
588
+ '</pre></div>'
589
+ : '<div class="hint">Loading…</div>';
590
+ return (
591
+ '<div class="tester-export">' +
592
+ '<div class="tester-export-head">' +
593
+ '<div class="tester-segmented">' +
594
+ seg +
595
+ '</div>' +
596
+ '<button class="tester-copy-btn" data-action="tester-export-copy" data-id="' +
597
+ serverId +
598
+ '" title="Copy to clipboard">' +
599
+ '<span class="material-symbols-outlined" style="font-size:14px">content_copy</span>' +
600
+ ' Copy' +
601
+ '</button>' +
602
+ '</div>' +
603
+ (hint ? '<div class="tester-export-hint">' + esc(hint.hint) + '</div>' : '') +
604
+ body +
605
+ '</div>'
606
+ );
607
+ }
608
+
609
+ function renderDiagnosticsTab(serverId) {
610
+ var s = getState(serverId);
611
+ var rtt =
612
+ s.pingRtt != null
613
+ ? '<strong>' + esc(s.pingRtt) + ' ms</strong>'
614
+ : '<span class="hint">not pinged yet</span>';
615
+ return (
616
+ '<div class="tester-diagnostics">' +
617
+ '<div class="tester-form-row"><label>Last ping</label>' +
618
+ rtt +
619
+ ' <button class="btn-activate" data-action="tester-ping" data-id="' +
620
+ serverId +
621
+ '">Ping</button></div>' +
622
+ '<div class="tester-form-row"><label>Logging level</label>' +
623
+ '<select data-action="noop" data-tester-logging="' +
624
+ serverId +
625
+ '">' +
626
+ ['debug', 'info', 'notice', 'warning', 'error', 'critical', 'alert', 'emergency']
627
+ .map(function (l) {
628
+ return (
629
+ '<option value="' +
630
+ l +
631
+ '"' +
632
+ (l === s.loggingLevel ? ' selected' : '') +
633
+ '>' +
634
+ l +
635
+ '</option>'
636
+ );
637
+ })
638
+ .join('') +
639
+ '</select> <button class="btn-activate" data-action="tester-set-logging" data-id="' +
640
+ serverId +
641
+ '">Apply</button></div>' +
642
+ '</div>'
643
+ );
644
+ }
645
+
646
+ // ---------------------------------------------------------------------------
647
+ // Result pane
648
+ // ---------------------------------------------------------------------------
649
+
650
+ function renderResult(serverId) {
651
+ var s = getState(serverId);
652
+ if (s.loading) return '<div class="tester-result loading">Calling…</div>';
653
+ if (s.error) return '<div class="tester-result error">' + esc(s.error) + '</div>';
654
+ if (!s.result) return '';
655
+ var mode = s.resultMode || 'pretty';
656
+ var pretty = renderPrettyResult(s.result);
657
+ var raw =
658
+ '<pre class="tester-result-raw">' +
659
+ (AD.highlightJson ? AD.highlightJson(s.result) : esc(JSON.stringify(s.result, null, 2))) +
660
+ '</pre>';
661
+ var curl = renderCurl(serverId, s.selectedTool, s.lastArgs);
662
+ var body = mode === 'raw' ? raw : mode === 'curl' ? curl : pretty;
663
+ var pill = s.result.isError
664
+ ? '<span class="tester-pill fail">error</span>'
665
+ : '<span class="tester-pill ok">ok</span>';
666
+ var latency =
667
+ s.lastLatency != null
668
+ ? '<span class="tester-latency">' + esc(s.lastLatency) + ' ms</span>'
669
+ : '';
670
+ return (
671
+ '<div class="tester-result">' +
672
+ '<div class="tester-result-tabs">' +
673
+ '<button class="' +
674
+ (mode === 'pretty' ? 'active' : '') +
675
+ '" data-action="tester-result-mode" data-id="' +
676
+ serverId +
677
+ '" data-mode="pretty">Pretty</button>' +
678
+ '<button class="' +
679
+ (mode === 'raw' ? 'active' : '') +
680
+ '" data-action="tester-result-mode" data-id="' +
681
+ serverId +
682
+ '" data-mode="raw">Raw</button>' +
683
+ '<button class="' +
684
+ (mode === 'curl' ? 'active' : '') +
685
+ '" data-action="tester-result-mode" data-id="' +
686
+ serverId +
687
+ '" data-mode="curl">cURL</button>' +
688
+ pill +
689
+ latency +
690
+ '</div>' +
691
+ '<div class="tester-result-body">' +
692
+ body +
693
+ '</div>' +
694
+ '</div>'
695
+ );
696
+ }
697
+
698
+ function renderPrettyResult(result) {
699
+ if (!result.content || !Array.isArray(result.content)) {
700
+ return '<pre>' + esc(JSON.stringify(result, null, 2)) + '</pre>';
701
+ }
702
+ return result.content
703
+ .map(function (c) {
704
+ if (c.type === 'text')
705
+ return (
706
+ '<div class="tester-content-text">' +
707
+ (AD.renderMarkdown ? AD.renderMarkdown(c.text) : esc(c.text)) +
708
+ '</div>'
709
+ );
710
+ if (c.type === 'image')
711
+ return (
712
+ '<img class="tester-content-img" src="data:' +
713
+ esc(c.mimeType) +
714
+ ';base64,' +
715
+ esc(c.data) +
716
+ '"/>'
717
+ );
718
+ if (c.type === 'audio')
719
+ return (
720
+ '<audio class="tester-content-audio" controls src="data:' +
721
+ esc(c.mimeType) +
722
+ ';base64,' +
723
+ esc(c.data) +
724
+ '"></audio>'
725
+ );
726
+ if (c.type === 'resource')
727
+ return (
728
+ '<div class="tester-content-resource">Resource: <code>' +
729
+ esc(c.resource && c.resource.uri) +
730
+ '</code></div>'
731
+ );
732
+ if (c.type === 'resource_link')
733
+ return (
734
+ '<div class="tester-content-resource">Link: <a href="' +
735
+ esc(c.uri) +
736
+ '" target="_blank">' +
737
+ esc(c.name || c.uri) +
738
+ '</a></div>'
739
+ );
740
+ return '<pre>' + esc(JSON.stringify(c, null, 2)) + '</pre>';
741
+ })
742
+ .join('');
743
+ }
744
+
745
+ function renderCurl(serverId, tool, args) {
746
+ var url = (location.origin || '') + '/api/servers/' + serverId + '/call';
747
+ var body = JSON.stringify({ tool: tool, args: args || {} });
748
+ return (
749
+ '<pre class="tester-curl">curl -X POST ' +
750
+ esc(url) +
751
+ " \\\n -H 'Content-Type: application/json' \\\n -d '" +
752
+ esc(body) +
753
+ "'</pre>"
754
+ );
755
+ }
756
+
757
+ // ---------------------------------------------------------------------------
758
+ // Data loads
759
+ // ---------------------------------------------------------------------------
760
+
761
+ function loadInfo(serverId) {
762
+ var s = getState(serverId);
763
+ doFetch(baseUrl(serverId, s.handle) + '/info')
764
+ .then(function (body) {
765
+ s.info =
766
+ body && body.capabilities ? body : { name: s.serverName, version: '', capabilities: {} };
767
+ renderBody(serverId);
768
+ })
769
+ .catch(function (err) {
770
+ s.info = 'error';
771
+ s.error = err.message;
772
+ renderBody(serverId);
773
+ });
774
+ }
775
+
776
+ function loadTools(serverId) {
777
+ var s = getState(serverId);
778
+ doFetch(baseUrl(serverId, s.handle) + '/tools')
779
+ .then(function (body) {
780
+ s.tools = body.tools || [];
781
+ renderBody(serverId);
782
+ })
783
+ .catch(function (err) {
784
+ s.tools = [];
785
+ s.error = err.message;
786
+ renderBody(serverId);
787
+ });
788
+ }
789
+
790
+ function loadResources(serverId, append) {
791
+ var s = getState(serverId);
792
+ var url =
793
+ baseUrl(serverId, s.handle) +
794
+ '/resources' +
795
+ (s.resourceCursor && append ? '?cursor=' + encodeURIComponent(s.resourceCursor) : '');
796
+ doFetch(url)
797
+ .then(function (body) {
798
+ var incoming = body.resources || [];
799
+ s.resources = append ? (s.resources || []).concat(incoming) : incoming;
800
+ s.resourceCursor = body.nextCursor || null;
801
+ renderBody(serverId);
802
+ })
803
+ .catch(function (err) {
804
+ s.resources = [];
805
+ s.error = err.message;
806
+ renderBody(serverId);
807
+ });
808
+ }
809
+
810
+ function loadPrompts(serverId) {
811
+ var s = getState(serverId);
812
+ doFetch(baseUrl(serverId, s.handle) + '/prompts')
813
+ .then(function (body) {
814
+ s.prompts = body.prompts || [];
815
+ renderBody(serverId);
816
+ })
817
+ .catch(function (err) {
818
+ s.prompts = [];
819
+ s.error = err.message;
820
+ renderBody(serverId);
821
+ });
822
+ }
823
+
824
+ // ---------------------------------------------------------------------------
825
+ // Presets (localStorage)
826
+ // ---------------------------------------------------------------------------
827
+
828
+ function presetCacheKey(serverName, kind, targetName) {
829
+ return (serverName || '') + '::' + kind + '::' + (targetName || '');
830
+ }
831
+
832
+ function loadPresets(serverName, kind, targetName) {
833
+ var key = presetCacheKey(serverName, kind, targetName);
834
+ if (presetsCache[key]) return presetsCache[key];
835
+ // Kick an async fetch; return empty until it resolves.
836
+ fetchPresets(serverName, kind, targetName);
837
+ return {};
838
+ }
839
+
840
+ function fetchPresets(serverName, kind, targetName) {
841
+ var key = presetCacheKey(serverName, kind, targetName);
842
+ var url =
843
+ '/api/presets?server=' +
844
+ encodeURIComponent(serverName || '') +
845
+ '&kind=' +
846
+ encodeURIComponent(kind) +
847
+ '&target=' +
848
+ encodeURIComponent(targetName || '');
849
+ AD._fetch(url)
850
+ .then(function (r) {
851
+ return r.json();
852
+ })
853
+ .then(function (body) {
854
+ var map = {};
855
+ (body.entries || []).forEach(function (e) {
856
+ map[e.preset_name] = { id: e.id, payload: e.payload };
857
+ });
858
+ presetsCache[key] = map;
859
+ // Re-render any tester whose tool form shows this server.
860
+ Object.keys(state).forEach(function (serverId) {
861
+ var s = state[serverId];
862
+ if (s.serverName === serverName && s.selectedTool === targetName && s.subtab === 'tools')
863
+ renderBody(serverId);
864
+ });
865
+ })
866
+ .catch(function () {
867
+ presetsCache[key] = {};
868
+ });
869
+ }
870
+
871
+ function savePresetRemote(serverName, kind, targetName, presetName, payload) {
872
+ return AD._fetch('/api/presets', {
873
+ method: 'POST',
874
+ headers: { 'Content-Type': 'application/json' },
875
+ body: JSON.stringify({
876
+ server: serverName,
877
+ kind: kind,
878
+ target: targetName,
879
+ preset: presetName,
880
+ payload: payload,
881
+ }),
882
+ })
883
+ .then(function (r) {
884
+ return r.json();
885
+ })
886
+ .then(function (body) {
887
+ if (body && body.id != null) {
888
+ var key = presetCacheKey(serverName, kind, targetName);
889
+ if (!presetsCache[key]) presetsCache[key] = {};
890
+ presetsCache[key][presetName] = { id: body.id, payload: payload };
891
+ }
892
+ return body;
893
+ });
894
+ }
895
+
896
+ function migrateLocalStoragePresets() {
897
+ try {
898
+ if (localStorage.getItem(PRESET_MIGRATED_KEY)) return;
899
+ var raw = localStorage.getItem(PRESET_KEY);
900
+ if (!raw) {
901
+ localStorage.setItem(PRESET_MIGRATED_KEY, '1');
902
+ return;
903
+ }
904
+ var parsed = JSON.parse(raw);
905
+ var uploads = [];
906
+ Object.keys(parsed || {}).forEach(function (groupKey) {
907
+ var parts = groupKey.split('::');
908
+ if (parts.length < 3) return;
909
+ var serverName = parts[0];
910
+ var kind = parts[1];
911
+ var targetName = parts[2];
912
+ if (kind !== 'tool' && kind !== 'prompt') return;
913
+ var group = parsed[groupKey] || {};
914
+ Object.keys(group).forEach(function (presetName) {
915
+ uploads.push({
916
+ server: serverName,
917
+ kind: kind,
918
+ target: targetName,
919
+ preset: presetName,
920
+ payload: group[presetName],
921
+ });
922
+ });
923
+ });
924
+ if (uploads.length === 0) {
925
+ localStorage.setItem(PRESET_MIGRATED_KEY, '1');
926
+ return;
927
+ }
928
+ Promise.all(
929
+ uploads.map(function (u) {
930
+ return AD._fetch('/api/presets', {
931
+ method: 'POST',
932
+ headers: { 'Content-Type': 'application/json' },
933
+ body: JSON.stringify(u),
934
+ }).catch(function () {
935
+ /* best-effort */
936
+ });
937
+ }),
938
+ ).then(function () {
939
+ localStorage.setItem(PRESET_MIGRATED_KEY, '1');
940
+ localStorage.removeItem(PRESET_KEY);
941
+ });
942
+ } catch (e) {
943
+ /* ignore */
944
+ }
945
+ }
946
+
947
+ migrateLocalStoragePresets();
948
+
949
+ // ---------------------------------------------------------------------------
950
+ // Bindings
951
+ // ---------------------------------------------------------------------------
952
+
953
+ function bindBody(serverId) {
954
+ var s = getState(serverId);
955
+ if (s.subtab === 'tools' && s.selectedTool) {
956
+ var tool = s.tools.find(function (t) {
957
+ return t.name === s.selectedTool;
958
+ });
959
+ if (tool) {
960
+ var containers = document.querySelectorAll('[data-form-for="' + cssEsc(tool.name) + '"]');
961
+ containers.forEach(function (container) {
962
+ if (AD.renderSchemaForm) {
963
+ AD.renderSchemaForm(
964
+ tool.inputSchema || { type: 'object', properties: {} },
965
+ container,
966
+ s.lastArgs || null,
967
+ );
968
+ }
969
+ });
970
+ }
971
+ }
972
+ }
973
+
974
+ function scopeFor(btn) {
975
+ return (btn && btn.closest && btn.closest('.tester-shell')) || document;
976
+ }
977
+
978
+ function cssEsc(n) {
979
+ return String(n).replace(/"/g, '\\"');
980
+ }
981
+
982
+ // Delegated handler
983
+ document.addEventListener('click', function (e) {
984
+ var btn = e.target.closest && e.target.closest('[data-action]');
985
+ if (!btn) return;
986
+ var action = btn.getAttribute('data-action');
987
+ if (!action || action.indexOf('tester-') !== 0) return;
988
+ var rawId = btn.getAttribute('data-id');
989
+ var id = /^-?\d+$/.test(rawId || '') ? parseInt(rawId, 10) : rawId;
990
+ if (id == null || id === '') return;
991
+ e.preventDefault();
992
+ handleAction(action, id, btn);
993
+ });
994
+
995
+ function handleAction(action, id, btn) {
996
+ var s = getState(id);
997
+ switch (action) {
998
+ case 'tester-subtab':
999
+ s.subtab = btn.getAttribute('data-subtab');
1000
+ renderBody(id);
1001
+ break;
1002
+ case 'tester-select-tool':
1003
+ s.selectedTool = btn.getAttribute('data-name');
1004
+ s.result = null;
1005
+ s.error = null;
1006
+ renderBody(id);
1007
+ break;
1008
+ case 'tester-call':
1009
+ callTool(id, btn);
1010
+ break;
1011
+ case 'tester-result-mode':
1012
+ s.resultMode = btn.getAttribute('data-mode');
1013
+ renderBody(id);
1014
+ break;
1015
+ case 'tester-select-resource':
1016
+ s.selectedResource = btn.getAttribute('data-uri');
1017
+ s.resourceContents = null;
1018
+ renderBody(id);
1019
+ break;
1020
+ case 'tester-resource-read':
1021
+ resourceRead(id);
1022
+ break;
1023
+ case 'tester-resource-subscribe':
1024
+ resourceSubscribe(id, 'subscribe');
1025
+ break;
1026
+ case 'tester-resource-unsubscribe':
1027
+ resourceSubscribe(id, 'unsubscribe');
1028
+ break;
1029
+ case 'tester-resources-more':
1030
+ loadResources(id, true);
1031
+ break;
1032
+ case 'tester-select-prompt':
1033
+ s.selectedPrompt = btn.getAttribute('data-name');
1034
+ s.promptMessages = null;
1035
+ renderBody(id);
1036
+ break;
1037
+ case 'tester-prompt-get':
1038
+ promptGet(id, btn);
1039
+ break;
1040
+ case 'tester-preset-save':
1041
+ presetSave(id);
1042
+ break;
1043
+ case 'tester-preset-load':
1044
+ /* handled via change, but button also triggers no-op */
1045
+ break;
1046
+ case 'tester-export':
1047
+ exportConfig(id, btn.getAttribute('data-format'));
1048
+ break;
1049
+ case 'tester-export-copy':
1050
+ copyExport(id);
1051
+ break;
1052
+ case 'tester-ping':
1053
+ ping(id);
1054
+ break;
1055
+ case 'tester-set-logging':
1056
+ setLogging(id, btn);
1057
+ break;
1058
+ case 'tester-popout':
1059
+ popOut(id);
1060
+ break;
1061
+ }
1062
+ }
1063
+
1064
+ // preset-load is a <select> change, not a click
1065
+ document.addEventListener('change', function (e) {
1066
+ var el = e.target;
1067
+ if (!el.getAttribute) return;
1068
+ var action = el.getAttribute('data-action');
1069
+ var id = parseInt(el.getAttribute('data-id'), 10);
1070
+ if (action === 'tester-preset-load' && !isNaN(id)) {
1071
+ var name = el.value;
1072
+ if (!name) return;
1073
+ var s = getState(id);
1074
+ var presets = loadPresets(s.serverName, 'tool', s.selectedTool);
1075
+ var entry = presets[name];
1076
+ s.lastArgs = entry && entry.payload ? entry.payload : {};
1077
+ renderBody(id);
1078
+ }
1079
+ });
1080
+
1081
+ function callTool(serverId, btn) {
1082
+ var s = getState(serverId);
1083
+ if (!s.selectedTool) return;
1084
+ var tool = s.tools.find(function (t) {
1085
+ return t.name === s.selectedTool;
1086
+ });
1087
+ if (!tool) return;
1088
+ var scope = scopeFor(btn);
1089
+ var container = scope.querySelector('[data-form-for="' + cssEsc(tool.name) + '"]');
1090
+ var args = {};
1091
+ try {
1092
+ args = AD.collectSchemaForm(container) || {};
1093
+ } catch (err) {
1094
+ s.error = err.message;
1095
+ renderBody(serverId);
1096
+ return;
1097
+ }
1098
+ s.loading = true;
1099
+ s.error = null;
1100
+ s.lastArgs = args;
1101
+ renderBody(serverId);
1102
+ var start = Date.now();
1103
+ doFetch(baseUrl(serverId, s.handle) + '/call', {
1104
+ method: 'POST',
1105
+ headers: { 'Content-Type': 'application/json' },
1106
+ body: JSON.stringify({ tool: tool.name, args: args }),
1107
+ })
1108
+ .then(function (body) {
1109
+ s.loading = false;
1110
+ s.result = body;
1111
+ s.lastLatency = Date.now() - start;
1112
+ renderBody(serverId);
1113
+ })
1114
+ .catch(function (err) {
1115
+ s.loading = false;
1116
+ s.error = err.message;
1117
+ renderBody(serverId);
1118
+ });
1119
+ }
1120
+
1121
+ function resourceRead(serverId) {
1122
+ var s = getState(serverId);
1123
+ if (!s.selectedResource) return;
1124
+ doFetch(baseUrl(serverId, s.handle) + '/resource/read', {
1125
+ method: 'POST',
1126
+ headers: { 'Content-Type': 'application/json' },
1127
+ body: JSON.stringify({ uri: s.selectedResource }),
1128
+ })
1129
+ .then(function (body) {
1130
+ s.resourceContents = body.contents;
1131
+ renderBody(serverId);
1132
+ })
1133
+ .catch(function (err) {
1134
+ s.error = err.message;
1135
+ renderBody(serverId);
1136
+ });
1137
+ }
1138
+
1139
+ function resourceSubscribe(serverId, op) {
1140
+ var s = getState(serverId);
1141
+ if (!s.selectedResource) return;
1142
+ doFetch(baseUrl(serverId, s.handle) + '/resource/' + op, {
1143
+ method: 'POST',
1144
+ headers: { 'Content-Type': 'application/json' },
1145
+ body: JSON.stringify({ uri: s.selectedResource }),
1146
+ }).catch(function (err) {
1147
+ s.error = err.message;
1148
+ renderBody(serverId);
1149
+ });
1150
+ }
1151
+
1152
+ function promptGet(serverId, btn) {
1153
+ var s = getState(serverId);
1154
+ if (!s.selectedPrompt) return;
1155
+ var scope = scopeFor(btn);
1156
+ var container = scope.querySelector('.tester-prompt-fields');
1157
+ var args = {};
1158
+ if (container) {
1159
+ try {
1160
+ var inputs = container.querySelectorAll('[data-path]');
1161
+ for (var i = 0; i < inputs.length; i++) {
1162
+ var p = inputs[i].getAttribute('data-path');
1163
+ if (!p) continue;
1164
+ var v = inputs[i].value;
1165
+ if (v) args[p] = v;
1166
+ }
1167
+ } catch (err) {
1168
+ s.error = err.message;
1169
+ renderBody(serverId);
1170
+ return;
1171
+ }
1172
+ }
1173
+ doFetch(baseUrl(serverId, s.handle) + '/prompt/get', {
1174
+ method: 'POST',
1175
+ headers: { 'Content-Type': 'application/json' },
1176
+ body: JSON.stringify({ name: s.selectedPrompt, arguments: args }),
1177
+ })
1178
+ .then(function (body) {
1179
+ s.promptMessages = body.messages || [];
1180
+ renderBody(serverId);
1181
+ })
1182
+ .catch(function (err) {
1183
+ s.error = err.message;
1184
+ renderBody(serverId);
1185
+ });
1186
+ }
1187
+
1188
+ function ping(serverId) {
1189
+ var s = getState(serverId);
1190
+ doFetch(baseUrl(serverId, s.handle) + '/ping', {
1191
+ method: 'POST',
1192
+ headers: { 'Content-Type': 'application/json' },
1193
+ body: '{}',
1194
+ })
1195
+ .then(function (body) {
1196
+ s.pingRtt = body.rtt_ms;
1197
+ renderBody(serverId);
1198
+ })
1199
+ .catch(function (err) {
1200
+ s.error = err.message;
1201
+ renderBody(serverId);
1202
+ });
1203
+ }
1204
+
1205
+ function setLogging(serverId, btn) {
1206
+ var s = getState(serverId);
1207
+ var scope = scopeFor(btn);
1208
+ var sel = scope.querySelector('[data-tester-logging="' + serverId + '"]');
1209
+ if (!sel) return;
1210
+ var level = sel.value;
1211
+ doFetch(baseUrl(serverId, s.handle) + '/logging-level', {
1212
+ method: 'POST',
1213
+ headers: { 'Content-Type': 'application/json' },
1214
+ body: JSON.stringify({ level: level }),
1215
+ })
1216
+ .then(function () {
1217
+ s.loggingLevel = level;
1218
+ renderBody(serverId);
1219
+ })
1220
+ .catch(function (err) {
1221
+ s.error = err.message;
1222
+ renderBody(serverId);
1223
+ });
1224
+ }
1225
+
1226
+ function exportConfig(serverId, format) {
1227
+ var s = getState(serverId);
1228
+ s.exportFormat = format;
1229
+ // Mark loading so renderExportTab shows the hint, not stale JSON
1230
+ s.exportBody = null;
1231
+ doFetch(baseUrl(serverId, s.handle) + '/export?format=' + encodeURIComponent(format))
1232
+ .then(function (body) {
1233
+ s.exportBody = body;
1234
+ renderBody(serverId);
1235
+ })
1236
+ .catch(function (err) {
1237
+ s.error = err.message;
1238
+ renderBody(serverId);
1239
+ });
1240
+ }
1241
+
1242
+ function copyExport(serverId) {
1243
+ var s = getState(serverId);
1244
+ if (!s.exportBody) return;
1245
+ var txt = JSON.stringify(s.exportBody.config, null, 2);
1246
+ if (navigator.clipboard) {
1247
+ navigator.clipboard.writeText(txt).then(function () {
1248
+ flashCopyButton(serverId);
1249
+ });
1250
+ }
1251
+ }
1252
+
1253
+ function flashCopyButton(serverId) {
1254
+ var btn = document.querySelector(
1255
+ '.tester-copy-btn[data-action="tester-export-copy"][data-id="' + serverId + '"]',
1256
+ );
1257
+ if (!btn) return;
1258
+ btn.classList.add('copied');
1259
+ var orig = btn.innerHTML;
1260
+ btn.innerHTML =
1261
+ '<span class="material-symbols-outlined" style="font-size:14px">check</span> Copied';
1262
+ setTimeout(function () {
1263
+ btn.classList.remove('copied');
1264
+ btn.innerHTML = orig;
1265
+ }, 1200);
1266
+ }
1267
+
1268
+ function presetSave(serverId) {
1269
+ var s = getState(serverId);
1270
+ if (!s.selectedTool) return;
1271
+ var name = prompt('Preset name:');
1272
+ if (!name) return;
1273
+ savePresetRemote(s.serverName, 'tool', s.selectedTool, name, s.lastArgs || {}).then(
1274
+ function () {
1275
+ renderBody(serverId);
1276
+ },
1277
+ );
1278
+ }
1279
+
1280
+ function popOut(serverId) {
1281
+ var url = '/tester/' + encodeURIComponent(serverId);
1282
+ var win = window.open(
1283
+ url,
1284
+ 'mcp-tester-' + serverId,
1285
+ 'width=1000,height=720,resizable=yes,scrollbars=yes,menubar=no,toolbar=no,location=no',
1286
+ );
1287
+ if (!win) {
1288
+ alert(
1289
+ 'Popup blocked. Allow popups for this site and try again (the tester opens in its own window).',
1290
+ );
1291
+ }
1292
+ }
1293
+
1294
+ // Exposed for tester-window.js bootstrap to associate a non-numeric render
1295
+ // key with a transient handle so baseUrl resolution still works.
1296
+ AD._setTesterHandle = function (serverId, handle) {
1297
+ var s = getState(serverId);
1298
+ s.handle = handle;
1299
+ };
1300
+
1301
+ // ---------------------------------------------------------------------------
1302
+ // WS event ingestion
1303
+ // ---------------------------------------------------------------------------
1304
+
1305
+ AD.onTesterEvent = function (msg) {
1306
+ var name = msg.serverName || '';
1307
+ if (!eventBuffers[name]) eventBuffers[name] = [];
1308
+ eventBuffers[name].unshift(msg);
1309
+ if (eventBuffers[name].length > 500) eventBuffers[name].length = 500;
1310
+ // Refresh any tester whose events tab is open for this server
1311
+ Object.keys(state).forEach(function (serverId) {
1312
+ var s = state[serverId];
1313
+ if (s.serverName === name && s.subtab === 'events') renderBody(serverId);
1314
+ });
1315
+ };
1316
+
1317
+ AD.onTesterLogEntry = function (entry) {
1318
+ if (!entry || entry.kind !== 'resource-read') return;
1319
+ /* placeholder hook for future per-resource refresh */
1320
+ };
1321
+
1322
+ // ---------------------------------------------------------------------------
1323
+ // Auto-load on section expand
1324
+ // ---------------------------------------------------------------------------
1325
+
1326
+ document.addEventListener('click', function (e) {
1327
+ var btn = e.target.closest && e.target.closest('[data-action="toggle-section"]');
1328
+ if (!btn) return;
1329
+ if (btn.getAttribute('data-section') !== 'tester') return;
1330
+ var id = parseInt(btn.getAttribute('data-id'), 10);
1331
+ setTimeout(function () {
1332
+ AD.openTesterFor(id);
1333
+ }, 20);
1334
+ });
1335
+
1336
+ // ---------------------------------------------------------------------------
1337
+ // Elicitation modal — server → human round-trip (transient connections only)
1338
+ // ---------------------------------------------------------------------------
1339
+
1340
+ AD.onElicitationRequest = function (msg) {
1341
+ showElicitationModal({
1342
+ id: msg.id,
1343
+ serverName: msg.serverName,
1344
+ message: msg.message,
1345
+ requestedSchema: msg.requestedSchema || { type: 'object', properties: {} },
1346
+ });
1347
+ };
1348
+
1349
+ function showElicitationModal(req) {
1350
+ hideElicitationModal();
1351
+ var backdrop = document.createElement('div');
1352
+ backdrop.className = 'tester-elicit-backdrop';
1353
+ backdrop.innerHTML =
1354
+ '<div class="tester-elicit-modal">' +
1355
+ '<div class="tester-elicit-head">' +
1356
+ '<span class="material-symbols-outlined">chat</span>' +
1357
+ '<strong>Server request</strong>' +
1358
+ '<span class="tester-pill">' +
1359
+ esc(req.serverName) +
1360
+ '</span>' +
1361
+ '</div>' +
1362
+ '<div class="tester-elicit-message">' +
1363
+ (AD.renderMarkdown ? AD.renderMarkdown(req.message) : esc(req.message)) +
1364
+ '</div>' +
1365
+ '<div class="tester-elicit-form"></div>' +
1366
+ '<div class="tester-elicit-actions">' +
1367
+ '<button class="btn-activate" data-elicit="accept">Accept</button>' +
1368
+ '<button class="btn-health" data-elicit="decline">Decline</button>' +
1369
+ '<button class="btn-delete" data-elicit="cancel">Cancel</button>' +
1370
+ '</div></div>';
1371
+ document.body.appendChild(backdrop);
1372
+ var formEl = backdrop.querySelector('.tester-elicit-form');
1373
+ if (formEl && AD.renderSchemaForm) {
1374
+ AD.renderSchemaForm(req.requestedSchema, formEl, null);
1375
+ }
1376
+ var handleClick = function (e) {
1377
+ var btn = e.target.closest && e.target.closest('[data-elicit]');
1378
+ if (!btn) return;
1379
+ e.preventDefault();
1380
+ var action = btn.getAttribute('data-elicit');
1381
+ var payload = { action: action };
1382
+ if (action === 'accept' && formEl && AD.collectSchemaForm) {
1383
+ try {
1384
+ payload.content = AD.collectSchemaForm(formEl);
1385
+ } catch (err) {
1386
+ alert('Form invalid: ' + err.message);
1387
+ return;
1388
+ }
1389
+ }
1390
+ AD._fetch('/api/elicitations/' + encodeURIComponent(req.id) + '/respond', {
1391
+ method: 'POST',
1392
+ headers: { 'Content-Type': 'application/json' },
1393
+ body: JSON.stringify(payload),
1394
+ })
1395
+ .then(function () {
1396
+ hideElicitationModal();
1397
+ })
1398
+ .catch(function (err) {
1399
+ alert('Elicitation reply failed: ' + err.message);
1400
+ });
1401
+ };
1402
+ backdrop.addEventListener('click', handleClick);
1403
+ elicitationModalOpen = { backdrop: backdrop, handler: handleClick, req: req };
1404
+ }
1405
+
1406
+ function hideElicitationModal() {
1407
+ if (!elicitationModalOpen) return;
1408
+ elicitationModalOpen.backdrop.removeEventListener('click', elicitationModalOpen.handler);
1409
+ elicitationModalOpen.backdrop.remove();
1410
+ elicitationModalOpen = null;
1411
+ }
1412
+ })();