fa-mcp-sdk 0.4.13 → 0.4.16

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 (36) hide show
  1. package/cli-template/CLAUDE.md +1 -1
  2. package/cli-template/FA-MCP-SDK-DOC/00-FA-MCP-SDK-index.md +1 -1
  3. package/cli-template/FA-MCP-SDK-DOC/01-getting-started.md +1 -0
  4. package/cli-template/FA-MCP-SDK-DOC/03-configuration.md +5 -0
  5. package/cli-template/FA-MCP-SDK-DOC/04-authentication.md +19 -0
  6. package/cli-template/FA-MCP-SDK-DOC/08-agent-tester-and-headless-api.md +311 -8
  7. package/cli-template/package.json +1 -1
  8. package/config/_local.yaml +15 -3
  9. package/config/custom-environment-variables.yaml +3 -0
  10. package/config/default.yaml +10 -1
  11. package/config/local.yaml +112 -0
  12. package/dist/core/_types_/config.d.ts +12 -2
  13. package/dist/core/_types_/config.d.ts.map +1 -1
  14. package/dist/core/agent-tester/agent-tester-router.d.ts.map +1 -1
  15. package/dist/core/agent-tester/agent-tester-router.js +39 -1
  16. package/dist/core/agent-tester/agent-tester-router.js.map +1 -1
  17. package/dist/core/auth/agent-tester-auth.d.ts +42 -0
  18. package/dist/core/auth/agent-tester-auth.d.ts.map +1 -0
  19. package/dist/core/auth/agent-tester-auth.js +166 -0
  20. package/dist/core/auth/agent-tester-auth.js.map +1 -0
  21. package/dist/core/auth/middleware.d.ts.map +1 -1
  22. package/dist/core/auth/middleware.js +4 -0
  23. package/dist/core/auth/middleware.js.map +1 -1
  24. package/dist/core/bootstrap/init-config.d.ts.map +1 -1
  25. package/dist/core/bootstrap/init-config.js +3 -0
  26. package/dist/core/bootstrap/init-config.js.map +1 -1
  27. package/dist/core/web/home-api.d.ts.map +1 -1
  28. package/dist/core/web/home-api.js +7 -1
  29. package/dist/core/web/home-api.js.map +1 -1
  30. package/dist/core/web/server-http.d.ts.map +1 -1
  31. package/dist/core/web/server-http.js +6 -6
  32. package/dist/core/web/server-http.js.map +1 -1
  33. package/dist/core/web/static/agent-tester/index.html +94 -47
  34. package/dist/core/web/static/agent-tester/script.js +192 -21
  35. package/dist/core/web/static/agent-tester/styles.css +156 -0
  36. package/package.json +1 -1
@@ -1,6 +1,165 @@
1
1
  const API_BASE = '/agent-tester';
2
2
  const trim = (s) => String(s || '').trim();
3
3
 
4
+ /**
5
+ * Wrapper around fetch that always includes credentials (session cookie).
6
+ */
7
+ function apiFetch (url, options = {}) {
8
+ return fetch(url, { ...options, credentials: 'include' });
9
+ }
10
+
11
+ /**
12
+ * Auth manager — handles login overlay when agentTester.useAuth is enabled.
13
+ */
14
+ class AuthManager {
15
+ constructor () {
16
+ this._authenticated = false;
17
+ this._authRequired = false;
18
+ }
19
+
20
+ /** Check auth status and show login if needed. Returns true if app can proceed. */
21
+ async init () {
22
+ try {
23
+ const resp = await apiFetch(`${API_BASE}/api/auth/status`);
24
+ const status = await resp.json();
25
+
26
+ if (!status.authRequired) {
27
+ return true;
28
+ }
29
+
30
+ this._authRequired = true;
31
+
32
+ if (status.authenticated) {
33
+ this._authenticated = true;
34
+ this._showLogoutButton();
35
+ return true;
36
+ }
37
+
38
+ this._showLoginOverlay(status.methods || []);
39
+ return false; // block app init until authenticated
40
+ } catch (e) {
41
+ console.warn('Auth status check failed, proceeding without auth:', e);
42
+ return true;
43
+ }
44
+ }
45
+
46
+ _showLoginOverlay (methods) {
47
+ const overlay = document.getElementById('authOverlay');
48
+ const appEl = document.querySelector('.app');
49
+ overlay.style.display = 'flex';
50
+ appEl.style.display = 'none';
51
+
52
+ const hasToken = methods.includes('token');
53
+ const hasBasic = methods.includes('basic');
54
+
55
+ const tokenForm = document.getElementById('authTokenForm');
56
+ const basicForm = document.getElementById('authBasicForm');
57
+ const tabs = document.getElementById('authTabs');
58
+
59
+ if (hasToken && hasBasic) {
60
+ tabs.style.display = 'flex';
61
+ tokenForm.style.display = 'flex';
62
+ basicForm.style.display = 'none';
63
+ this._bindTabs();
64
+ } else if (hasBasic) {
65
+ tokenForm.style.display = 'none';
66
+ basicForm.style.display = 'flex';
67
+ } else {
68
+ tokenForm.style.display = 'flex';
69
+ basicForm.style.display = 'none';
70
+ }
71
+
72
+ tokenForm.addEventListener('submit', (e) => {
73
+ e.preventDefault();
74
+ const token = document.getElementById('authToken').value.trim();
75
+ if (token) { this._login({ token }); }
76
+ });
77
+
78
+ basicForm.addEventListener('submit', (e) => {
79
+ e.preventDefault();
80
+ const username = document.getElementById('authUsername').value.trim();
81
+ const password = document.getElementById('authPassword').value;
82
+ if (username && password) { this._login({ username, password }); }
83
+ });
84
+ }
85
+
86
+ _bindTabs () {
87
+ const tabs = document.querySelectorAll('.auth-tab');
88
+ const tokenForm = document.getElementById('authTokenForm');
89
+ const basicForm = document.getElementById('authBasicForm');
90
+
91
+ tabs.forEach((tab) => {
92
+ tab.addEventListener('click', () => {
93
+ tabs.forEach((t) => t.classList.remove('active'));
94
+ tab.classList.add('active');
95
+ if (tab.dataset.tab === 'token') {
96
+ tokenForm.style.display = 'flex';
97
+ basicForm.style.display = 'none';
98
+ } else {
99
+ tokenForm.style.display = 'none';
100
+ basicForm.style.display = 'flex';
101
+ }
102
+ this._hideError();
103
+ });
104
+ });
105
+ }
106
+
107
+ async _login (credentials) {
108
+ this._hideError();
109
+ try {
110
+ const resp = await apiFetch(`${API_BASE}/api/auth/login`, {
111
+ method: 'POST',
112
+ headers: { 'Content-Type': 'application/json' },
113
+ body: JSON.stringify(credentials),
114
+ });
115
+
116
+ if (!resp.ok) {
117
+ const err = await resp.json();
118
+ this._showError(err.error || 'Authentication failed');
119
+ return;
120
+ }
121
+
122
+ this._authenticated = true;
123
+
124
+ // Hide overlay, show app
125
+ document.getElementById('authOverlay').style.display = 'none';
126
+ document.querySelector('.app').style.display = 'flex';
127
+ this._showLogoutButton();
128
+
129
+ // Initialize the main app after successful login
130
+ window.mcpAgentTester = new McpAgentTester();
131
+ } catch (_e) {
132
+ this._showError('Connection error');
133
+ }
134
+ }
135
+
136
+ _showLogoutButton () {
137
+ const btn = document.getElementById('logoutBtn');
138
+ if (btn) {
139
+ btn.style.display = '';
140
+ btn.addEventListener('click', () => this._logout());
141
+ }
142
+ }
143
+
144
+ async _logout () {
145
+ try {
146
+ await apiFetch(`${API_BASE}/api/auth/logout`, { method: 'POST' });
147
+ } catch { /* ignore */ }
148
+ location.reload();
149
+ }
150
+
151
+ _showError (msg) {
152
+ const el = document.getElementById('authError');
153
+ el.textContent = msg;
154
+ el.style.display = 'block';
155
+ }
156
+
157
+ _hideError () {
158
+ const el = document.getElementById('authError');
159
+ el.style.display = 'none';
160
+ }
161
+ }
162
+
4
163
  class McpAgentTester {
5
164
  constructor () {
6
165
  this.currentSessionId = null;
@@ -118,6 +277,7 @@ class McpAgentTester {
118
277
  const select = document.createElement('select');
119
278
  select.className = 'format-toggle';
120
279
  select.dataset.messageId = messageId;
280
+ select.setAttribute('data-testid', 'at-message-format-toggle');
121
281
 
122
282
  const options = ['MD', 'HTML'];
123
283
  const currentFormat = this.messageFormats[messageId] || 'MD';
@@ -391,7 +551,7 @@ class McpAgentTester {
391
551
  this.showLoading('Auto-connecting to MCP server...');
392
552
 
393
553
  try {
394
- const response = await fetch(`${API_BASE}/api/mcp/connect`, {
554
+ const response = await apiFetch(`${API_BASE}/api/mcp/connect`, {
395
555
  method: 'POST',
396
556
  headers: { 'Content-Type': 'application/json' },
397
557
  body: JSON.stringify(connectionData),
@@ -441,7 +601,7 @@ class McpAgentTester {
441
601
 
442
602
  async loadDefaultConfig () {
443
603
  try {
444
- const response = await fetch(`${API_BASE}/api/config`);
604
+ const response = await apiFetch(`${API_BASE}/api/config`);
445
605
  const config = await response.json();
446
606
  this.defaultMcpUrl = config.defaultMcpUrl || null;
447
607
  this.authEnabled = !!config.authEnabled;
@@ -475,7 +635,7 @@ class McpAgentTester {
475
635
  this.showLoading('Connecting to MCP server...');
476
636
 
477
637
  try {
478
- const response = await fetch(`${API_BASE}/api/mcp/connect`, {
638
+ const response = await apiFetch(`${API_BASE}/api/mcp/connect`, {
479
639
  method: 'POST',
480
640
  headers: { 'Content-Type': 'application/json' },
481
641
  body: JSON.stringify(connectionData),
@@ -555,7 +715,7 @@ class McpAgentTester {
555
715
  this.showLoading('Checking used headers...');
556
716
 
557
717
  try {
558
- const response = await fetch(`${API_BASE}/api/mcp/used-headers?url=${encodeURIComponent(url)}`, {
718
+ const response = await apiFetch(`${API_BASE}/api/mcp/used-headers?url=${encodeURIComponent(url)}`, {
559
719
  method: 'GET',
560
720
  headers: { 'Accept': 'application/json' },
561
721
  });
@@ -605,6 +765,7 @@ class McpAgentTester {
605
765
  const tooltipAttr = hasDesc ? ` data-tooltip="${header.description.replace(/"/g, '"')}"` : '';
606
766
  const inputClass = isRequired ? 'header-value used-header' : 'header-value';
607
767
 
768
+ headerGroup.setAttribute('data-testid', `at-header-row-${header.name}`);
608
769
  headerGroup.innerHTML = `
609
770
  <span class="${nameClass}"${tooltipAttr}>${header.name}</span>
610
771
  <input
@@ -614,6 +775,7 @@ class McpAgentTester {
614
775
  placeholder="${header.name}"
615
776
  data-header-name="${header.name}"
616
777
  data-required="${isRequired}"
778
+ data-testid="at-header-input-${header.name}"
617
779
  value="${savedValue.replace(/"/g, '&quot;')}"
618
780
  >
619
781
  `;
@@ -725,7 +887,7 @@ class McpAgentTester {
725
887
  }
726
888
  const headers = this.getHeadersFromForm();
727
889
  try {
728
- const resp = await fetch(`${API_BASE}/api/mcp/headers`, {
890
+ const resp = await apiFetch(`${API_BASE}/api/mcp/headers`, {
729
891
  method: 'POST',
730
892
  headers: { 'Content-Type': 'application/json' },
731
893
  body: JSON.stringify({ serverName: this.currentServer.name, headers }),
@@ -777,7 +939,7 @@ class McpAgentTester {
777
939
  if (savedHeaders['Authorization']) {return;}
778
940
 
779
941
  try {
780
- const response = await fetch(`${API_BASE}/api/auth-token`);
942
+ const response = await apiFetch(`${API_BASE}/api/auth-token`);
781
943
  if (!response.ok) {return;}
782
944
 
783
945
  const data = await response.json();
@@ -804,7 +966,7 @@ class McpAgentTester {
804
966
  this.stopAuthRefresh();
805
967
  this._authRefreshInterval = setInterval(async () => {
806
968
  try {
807
- const response = await fetch(`${API_BASE}/api/auth-token/refresh`, { method: 'POST' });
969
+ const response = await apiFetch(`${API_BASE}/api/auth-token/refresh`, { method: 'POST' });
808
970
  if (!response.ok) {return;}
809
971
 
810
972
  const data = await response.json();
@@ -848,7 +1010,7 @@ class McpAgentTester {
848
1010
 
849
1011
  async loadCurrentServer () {
850
1012
  try {
851
- const response = await fetch(`${API_BASE}/api/mcp/servers`);
1013
+ const response = await apiFetch(`${API_BASE}/api/mcp/servers`);
852
1014
  const servers = await response.json();
853
1015
 
854
1016
  if (servers && servers.length > 0) {
@@ -880,15 +1042,15 @@ class McpAgentTester {
880
1042
 
881
1043
  if (server.isConnected) {
882
1044
  this.connectedServersContainer.innerHTML = `
883
- <div class="server-status-row">
884
- <span class="server-status connected">${toolCount} tools <span class="material-icons-round">check_circle</span> connected</span>
885
- <button type="button" class="btn btn-danger disconnect-btn"><span class="material-icons-round">link_off</span>Disconnect</button>
1045
+ <div class="server-status-row" data-testid="at-server-status-row">
1046
+ <span class="server-status connected" data-testid="at-server-status-connected">${toolCount} tools <span class="material-icons-round">check_circle</span> connected</span>
1047
+ <button type="button" class="btn btn-danger disconnect-btn" data-testid="at-disconnect-btn"><span class="material-icons-round">link_off</span>Disconnect</button>
886
1048
  </div>`;
887
1049
  } else {
888
1050
  this.connectedServersContainer.innerHTML = `
889
- <div class="server-status-row">
890
- <span class="server-status disconnected"><span class="material-icons-round">cancel</span>Disconnected</span>
891
- <button type="button" class="btn btn-secondary reconnect-btn"><span class="material-icons-round">refresh</span>Reconnect</button>
1051
+ <div class="server-status-row" data-testid="at-server-status-row">
1052
+ <span class="server-status disconnected" data-testid="at-server-status-disconnected"><span class="material-icons-round">cancel</span>Disconnected</span>
1053
+ <button type="button" class="btn btn-secondary reconnect-btn" data-testid="at-reconnect-btn"><span class="material-icons-round">refresh</span>Reconnect</button>
892
1054
  </div>`;
893
1055
  }
894
1056
 
@@ -909,7 +1071,7 @@ class McpAgentTester {
909
1071
  this.stopAuthRefresh();
910
1072
 
911
1073
  try {
912
- const response = await fetch(`${API_BASE}/api/mcp/disconnect/${this.currentServer.name}`, { method: 'POST' });
1074
+ const response = await apiFetch(`${API_BASE}/api/mcp/disconnect/${this.currentServer.name}`, { method: 'POST' });
913
1075
 
914
1076
  if (response.ok) {
915
1077
  this.showToast(`Disconnected from ${this.currentServer.name}`, 'success');
@@ -947,7 +1109,7 @@ class McpAgentTester {
947
1109
  this.showLoading('Reconnecting to MCP server...');
948
1110
 
949
1111
  try {
950
- const response = await fetch(`${API_BASE}/api/mcp/connect`, {
1112
+ const response = await apiFetch(`${API_BASE}/api/mcp/connect`, {
951
1113
  method: 'POST',
952
1114
  headers: { 'Content-Type': 'application/json' },
953
1115
  body: JSON.stringify(connectionData),
@@ -1045,7 +1207,7 @@ class McpAgentTester {
1045
1207
  modelConfig: modelConfig,
1046
1208
  };
1047
1209
 
1048
- const response = await fetch(`${API_BASE}/api/chat/message`, {
1210
+ const response = await apiFetch(`${API_BASE}/api/chat/message`, {
1049
1211
  method: 'POST',
1050
1212
  headers: { 'Content-Type': 'application/json' },
1051
1213
  body: JSON.stringify(requestData),
@@ -1075,6 +1237,7 @@ class McpAgentTester {
1075
1237
  const messageDiv = document.createElement('div');
1076
1238
  messageDiv.className = `message ${sender}`;
1077
1239
  messageDiv.dataset.messageId = messageId;
1240
+ messageDiv.setAttribute('data-testid', `at-message-${sender}`);
1078
1241
 
1079
1242
  if (metadata.error) {
1080
1243
  messageDiv.classList.add('error');
@@ -1098,6 +1261,7 @@ class McpAgentTester {
1098
1261
  const messageText = document.createElement('div');
1099
1262
  messageText.className = 'message-text';
1100
1263
  messageText.dataset.messageId = messageId;
1264
+ messageText.setAttribute('data-testid', `at-message-text-${sender}`);
1101
1265
 
1102
1266
  if (sender === 'assistant' && !metadata.error) {
1103
1267
  const format = this.messageFormats[messageId];
@@ -1186,6 +1350,7 @@ class McpAgentTester {
1186
1350
  showToast (message, type = 'info') {
1187
1351
  const toast = document.createElement('div');
1188
1352
  toast.className = `toast ${type}`;
1353
+ toast.setAttribute('data-testid', `at-toast-${type}`);
1189
1354
 
1190
1355
  const icon = {
1191
1356
  'success': 'check_circle',
@@ -1419,11 +1584,12 @@ class McpAgentTester {
1419
1584
  savedUrls.forEach(url => {
1420
1585
  const item = document.createElement('div');
1421
1586
  item.className = 'dropdown-item';
1587
+ item.setAttribute('data-testid', 'at-saved-url-item');
1422
1588
 
1423
1589
  item.innerHTML = `
1424
1590
  <div class="url-item">
1425
- <span class="url-text" title="${url}">${url}</span>
1426
- <button class="delete-btn" title="Delete URL">
1591
+ <span class="url-text" title="${url}" data-testid="at-saved-url-text">${url}</span>
1592
+ <button class="delete-btn" title="Delete URL" data-testid="at-saved-url-delete">
1427
1593
  <span class="material-icons-round" style="font-size: 16px;">close</span>
1428
1594
  </button>
1429
1595
  </div>
@@ -1498,6 +1664,11 @@ class McpAgentTester {
1498
1664
  }
1499
1665
 
1500
1666
  // Initialize the app when DOM is loaded
1501
- document.addEventListener('DOMContentLoaded', () => {
1502
- window.mcpAgentTester = new McpAgentTester();
1667
+ document.addEventListener('DOMContentLoaded', async () => {
1668
+ const authManager = new AuthManager();
1669
+ const canProceed = await authManager.init();
1670
+ if (canProceed) {
1671
+ window.mcpAgentTester = new McpAgentTester();
1672
+ }
1673
+ // If !canProceed, AuthManager shows login overlay and creates McpAgentTester after successful login
1503
1674
  });
@@ -1488,3 +1488,159 @@ body {
1488
1488
  .btn-primary:hover {
1489
1489
  opacity: 0.9;
1490
1490
  }
1491
+
1492
+ /* ============================================
1493
+ Auth Overlay (Login Screen)
1494
+ ============================================ */
1495
+
1496
+ .auth-overlay {
1497
+ position: fixed;
1498
+ inset: 0;
1499
+ z-index: 10000;
1500
+ display: flex;
1501
+ align-items: center;
1502
+ justify-content: center;
1503
+ background: var(--bg-overlay);
1504
+ backdrop-filter: blur(4px);
1505
+ }
1506
+
1507
+ .auth-card {
1508
+ background: var(--bg-surface);
1509
+ border: 1px solid var(--border);
1510
+ border-radius: var(--radius-xl);
1511
+ box-shadow: var(--shadow-lg);
1512
+ padding: 40px 36px 32px;
1513
+ width: 100%;
1514
+ max-width: 380px;
1515
+ animation: authFadeIn 0.25s ease-out;
1516
+ }
1517
+
1518
+ @keyframes authFadeIn {
1519
+ from { opacity: 0; transform: translateY(12px); }
1520
+ to { opacity: 1; transform: translateY(0); }
1521
+ }
1522
+
1523
+ .auth-header {
1524
+ text-align: center;
1525
+ margin-bottom: 28px;
1526
+ }
1527
+
1528
+ .auth-lock-icon {
1529
+ font-size: 40px;
1530
+ color: var(--primary);
1531
+ margin-bottom: 8px;
1532
+ }
1533
+
1534
+ .auth-header h2 {
1535
+ margin: 0;
1536
+ font-size: 1.4rem;
1537
+ font-weight: 700;
1538
+ color: var(--text);
1539
+ }
1540
+
1541
+ .auth-subtitle {
1542
+ margin: 4px 0 0;
1543
+ font-size: 0.85rem;
1544
+ color: var(--text-secondary);
1545
+ }
1546
+
1547
+ .auth-tabs {
1548
+ display: flex;
1549
+ gap: 0;
1550
+ margin-bottom: 20px;
1551
+ border-bottom: 1px solid var(--border);
1552
+ }
1553
+
1554
+ .auth-tab {
1555
+ flex: 1;
1556
+ padding: 10px 0;
1557
+ background: none;
1558
+ border: none;
1559
+ border-bottom: 2px solid transparent;
1560
+ color: var(--text-secondary);
1561
+ font-size: 0.85rem;
1562
+ font-weight: 600;
1563
+ cursor: pointer;
1564
+ transition: color 0.15s, border-color 0.15s;
1565
+ }
1566
+
1567
+ .auth-tab:hover {
1568
+ color: var(--text);
1569
+ }
1570
+
1571
+ .auth-tab.active {
1572
+ color: var(--primary);
1573
+ border-bottom-color: var(--primary);
1574
+ }
1575
+
1576
+ .auth-form {
1577
+ display: flex;
1578
+ flex-direction: column;
1579
+ gap: 16px;
1580
+ }
1581
+
1582
+ .auth-form .form-group {
1583
+ display: flex;
1584
+ flex-direction: column;
1585
+ gap: 6px;
1586
+ }
1587
+
1588
+ .auth-form label {
1589
+ font-size: 0.8rem;
1590
+ font-weight: 600;
1591
+ color: var(--text-secondary);
1592
+ }
1593
+
1594
+ .auth-form input {
1595
+ padding: 10px 14px;
1596
+ border: 1px solid var(--border-input);
1597
+ border-radius: var(--radius);
1598
+ background: var(--bg-input);
1599
+ color: var(--text);
1600
+ font-size: 0.9rem;
1601
+ font-family: inherit;
1602
+ transition: border-color 0.15s, box-shadow 0.15s;
1603
+ }
1604
+
1605
+ .auth-form input:focus {
1606
+ outline: none;
1607
+ border-color: var(--border-focus);
1608
+ box-shadow: 0 0 0 3px var(--primary-glow);
1609
+ }
1610
+
1611
+ .auth-submit {
1612
+ display: flex;
1613
+ align-items: center;
1614
+ justify-content: center;
1615
+ gap: 8px;
1616
+ width: 100%;
1617
+ padding: 12px;
1618
+ margin-top: 4px;
1619
+ background: var(--primary);
1620
+ color: var(--text-on-primary);
1621
+ border: none;
1622
+ border-radius: var(--radius);
1623
+ font-size: 0.9rem;
1624
+ font-weight: 600;
1625
+ cursor: pointer;
1626
+ transition: background 0.15s;
1627
+ }
1628
+
1629
+ .auth-submit:hover {
1630
+ background: var(--primary-hover);
1631
+ }
1632
+
1633
+ .auth-submit .material-icons-round {
1634
+ font-size: 18px;
1635
+ }
1636
+
1637
+ .auth-error {
1638
+ margin-top: 12px;
1639
+ padding: 10px 14px;
1640
+ background: var(--error-bg);
1641
+ border: 1px solid var(--error-border);
1642
+ border-radius: var(--radius);
1643
+ color: var(--error);
1644
+ font-size: 0.8rem;
1645
+ text-align: center;
1646
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fa-mcp-sdk",
3
3
  "productName": "FA MCP SDK",
4
- "version": "0.4.13",
4
+ "version": "0.4.16",
5
5
  "description": "Core infrastructure and templates for building Model Context Protocol (MCP) servers with TypeScript",
6
6
  "type": "module",
7
7
  "main": "dist/core/index.js",