ft-scout 9.0.8 → 9.0.9
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.
- package/.firebase/hosting.d2Vi.cache +7 -7
- package/README.md +2 -2
- package/bin/src/commands/agent.d.ts.map +1 -1
- package/bin/src/commands/agent.js +26 -13
- package/bin/src/commands/agent.js.map +1 -1
- package/bin/src/engine/agentEngine.d.ts.map +1 -1
- package/bin/src/engine/agentEngine.js +58 -21
- package/bin/src/engine/agentEngine.js.map +1 -1
- package/bin/src/index.js +4 -4
- package/bin/src/mcp/client.js +1 -1
- package/bin/src/mcp/manager.d.ts.map +1 -1
- package/bin/src/mcp/manager.js +117 -4
- package/bin/src/mcp/manager.js.map +1 -1
- package/bin/src/mcp/persistence.d.ts.map +1 -1
- package/bin/src/mcp/persistence.js +15 -0
- package/bin/src/mcp/persistence.js.map +1 -1
- package/bin/src/mcp/types.d.ts.map +1 -1
- package/bin/src/server/server.d.ts.map +1 -1
- package/bin/src/server/server.js +18 -12
- package/bin/src/server/server.js.map +1 -1
- package/bin/src/utils/admin.d.ts.map +1 -1
- package/bin/src/utils/admin.js +5 -11
- package/bin/src/utils/admin.js.map +1 -1
- package/bin/src/utils/branding.js +1 -1
- package/bin/src/utils/session.d.ts.map +1 -1
- package/bin/src/utils/session.js +8 -7
- package/bin/src/utils/session.js.map +1 -1
- package/package.json +1 -1
- package/web/app.js +274 -52
- package/web/styles.css +2 -2
package/web/app.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* ==========================================================================
|
|
2
|
-
FrontTerrain Scout Multi-Page Application Engine v9.0.
|
|
2
|
+
FrontTerrain Scout Multi-Page Application Engine v9.0.9
|
|
3
3
|
========================================================================== */
|
|
4
4
|
|
|
5
5
|
const FIREBASE_CONFIG = {
|
|
@@ -220,14 +220,28 @@ async function loadDynamicPricing() {
|
|
|
220
220
|
|
|
221
221
|
try {
|
|
222
222
|
const adminToken = sessionStorage.getItem('scout_admin_token');
|
|
223
|
-
const userEmail = (state.user?.email || '').toLowerCase();
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
223
|
+
const userEmail = (state.user?.email || '').trim().toLowerCase();
|
|
224
|
+
const AUTHORIZED_ADMIN_EMAILS = [
|
|
225
|
+
'rishishandilya40@gmail.com',
|
|
226
|
+
'rishi.shandilya@frontterrain.com',
|
|
227
|
+
'company@frontterrain.com'
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
if (adminToken && AUTHORIZED_ADMIN_EMAILS.includes(userEmail)) {
|
|
231
|
+
if (state.serverConnected) {
|
|
232
|
+
const checkRes = await fetch('/api/admin/check', {
|
|
233
|
+
headers: { 'Authorization': `Bearer ${adminToken}` }
|
|
234
|
+
});
|
|
235
|
+
if (checkRes.ok) {
|
|
236
|
+
const checkData = await checkRes.json();
|
|
237
|
+
if (checkData.authorized) {
|
|
238
|
+
showAdminNavigationLink();
|
|
239
|
+
} else {
|
|
240
|
+
sessionStorage.removeItem('scout_admin_token');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
showAdminNavigationLink();
|
|
231
245
|
}
|
|
232
246
|
}
|
|
233
247
|
} catch { }
|
|
@@ -249,7 +263,13 @@ function showAdminNavigationLink() {
|
|
|
249
263
|
|
|
250
264
|
// Highlight admin identity in sidebar user footer
|
|
251
265
|
const sidebarTier = document.getElementById('sidebar-user-tier');
|
|
252
|
-
|
|
266
|
+
const userEmail = (state.user?.email || '').trim().toLowerCase();
|
|
267
|
+
const AUTHORIZED_ADMIN_EMAILS = [
|
|
268
|
+
'rishishandilya40@gmail.com',
|
|
269
|
+
'rishi.shandilya@frontterrain.com',
|
|
270
|
+
'company@frontterrain.com'
|
|
271
|
+
];
|
|
272
|
+
if (sidebarTier && sessionStorage.getItem('scout_admin_token') && AUTHORIZED_ADMIN_EMAILS.includes(userEmail)) {
|
|
253
273
|
sidebarTier.innerHTML = '<span style="color: #e15b37; font-weight: 700;"><i class="fa-solid fa-shield-halved"></i> Master Admin</span>';
|
|
254
274
|
}
|
|
255
275
|
|
|
@@ -1057,7 +1077,7 @@ function initTerminalSimulator() {
|
|
|
1057
1077
|
`;
|
|
1058
1078
|
} else if (lower.includes('status')) {
|
|
1059
1079
|
response.innerHTML = `
|
|
1060
|
-
<span class="term-green">✔ Scout CLI Engine: v9.0.
|
|
1080
|
+
<span class="term-green">✔ Scout CLI Engine: v9.0.9 Online</span><br>
|
|
1061
1081
|
↳ User: <strong>${escapeHtml(state.user ? state.user.name : 'Guest Session')}</strong><br>
|
|
1062
1082
|
↳ Active Subscription: <strong>${escapeHtml(TIER_DETAILS[state.currentTier].name)}</strong><br>
|
|
1063
1083
|
↳ Credit Quota: <strong>${state.byokEnabled ? 'BYOK Mode (Unlimited)' : `${state.creditsUsed} / ${state.creditsTotal} Credits Used`}</strong><br>
|
|
@@ -1304,7 +1324,7 @@ function renderTiersUI() {
|
|
|
1304
1324
|
getEl('price-pro-val').innerText = proUSD;
|
|
1305
1325
|
}
|
|
1306
1326
|
}
|
|
1307
|
-
if (getEl('price-pro-period')) getEl('price-pro-period').innerText = isProOffer ? `/ month (${p.pro?.offerBadge || 'PROMO OFFER'})` : (isAnnual ? '(billed annually)' : '/ month');
|
|
1327
|
+
if (getEl('price-pro-period')) getEl('price-pro-period').innerText = isProOffer ? `/ month (${p.pro?.offerBadge || 'PROMO OFFER'})` : (isAnnual ? '/month(billed annually)' : '/ month');
|
|
1308
1328
|
const proCurrency = document.querySelector('#tier-card-pro .price-currency');
|
|
1309
1329
|
if (proCurrency) proCurrency.innerText = isProOffer ? '' : '$';
|
|
1310
1330
|
|
|
@@ -1315,7 +1335,7 @@ function renderTiersUI() {
|
|
|
1315
1335
|
getEl('price-ent-val').innerText = entUSD;
|
|
1316
1336
|
}
|
|
1317
1337
|
}
|
|
1318
|
-
if (getEl('price-ent-period')) getEl('price-ent-period').innerText = isEntOffer ? `/ month (${p.enterprise?.offerBadge || 'PROMO OFFER'})` : (isAnnual ? '(billed annually)' : '/ month');
|
|
1338
|
+
if (getEl('price-ent-period')) getEl('price-ent-period').innerText = isEntOffer ? `/ month (${p.enterprise?.offerBadge || 'PROMO OFFER'})` : (isAnnual ? '/month(billed annually)' : '/ month');
|
|
1319
1339
|
const entCurrency = document.querySelector('#tier-card-enterprise .price-currency');
|
|
1320
1340
|
if (entCurrency) entCurrency.innerText = isEntOffer ? '' : '$';
|
|
1321
1341
|
|
|
@@ -1367,8 +1387,8 @@ async function checkServerStatusAndSession() {
|
|
|
1367
1387
|
let connected = false;
|
|
1368
1388
|
let data = null;
|
|
1369
1389
|
|
|
1370
|
-
// Only attempt
|
|
1371
|
-
const isLocalhost = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
|
|
1390
|
+
// Only attempt direct relative path if running locally or via file protocol
|
|
1391
|
+
const isLocalhost = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' || window.location.protocol === 'file:';
|
|
1372
1392
|
|
|
1373
1393
|
if (isLocalhost) {
|
|
1374
1394
|
try {
|
|
@@ -1381,15 +1401,22 @@ async function checkServerStatusAndSession() {
|
|
|
1381
1401
|
}
|
|
1382
1402
|
}
|
|
1383
1403
|
} catch { }
|
|
1404
|
+
}
|
|
1384
1405
|
|
|
1385
|
-
|
|
1406
|
+
if (!connected) {
|
|
1407
|
+
const candidateHosts = ['http://localhost:9120', 'http://127.0.0.1:9120', 'http://localhost:9121', 'http://localhost:9122'];
|
|
1408
|
+
for (const host of candidateHosts) {
|
|
1386
1409
|
try {
|
|
1387
|
-
const
|
|
1410
|
+
const controller = new AbortController();
|
|
1411
|
+
const timeoutId = setTimeout(() => controller.abort(), 1200);
|
|
1412
|
+
const res = await fetch(`${host}/api/status`, { method: 'GET', signal: controller.signal });
|
|
1413
|
+
clearTimeout(timeoutId);
|
|
1388
1414
|
if (res.ok) {
|
|
1389
1415
|
data = await res.json();
|
|
1390
1416
|
if (data && data.status === 'ok') {
|
|
1391
|
-
state.apiBaseUrl =
|
|
1417
|
+
state.apiBaseUrl = host;
|
|
1392
1418
|
connected = true;
|
|
1419
|
+
break;
|
|
1393
1420
|
}
|
|
1394
1421
|
}
|
|
1395
1422
|
} catch { }
|
|
@@ -1475,8 +1502,46 @@ async function handleSignIn(e) {
|
|
|
1475
1502
|
if (!email || !password) return;
|
|
1476
1503
|
|
|
1477
1504
|
// Check Master Admin Credentials
|
|
1478
|
-
const
|
|
1505
|
+
const AUTHORIZED_ADMIN_EMAILS = [
|
|
1506
|
+
'rishishandilya40@gmail.com',
|
|
1507
|
+
'rishi.shandilya@frontterrain.com',
|
|
1508
|
+
'company@frontterrain.com'
|
|
1509
|
+
];
|
|
1510
|
+
const isMasterAdminAuth = AUTHORIZED_ADMIN_EMAILS.includes(email.toLowerCase().trim()) && password === 'scout_admin_2026';
|
|
1479
1511
|
if (isMasterAdminAuth) {
|
|
1512
|
+
if (state.serverConnected) {
|
|
1513
|
+
try {
|
|
1514
|
+
const adminLoginRes = await fetch(`${state.apiBaseUrl}/api/admin/login`, {
|
|
1515
|
+
method: 'POST',
|
|
1516
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1517
|
+
body: JSON.stringify({ email: email.trim(), passkey: password }),
|
|
1518
|
+
});
|
|
1519
|
+
if (adminLoginRes.ok) {
|
|
1520
|
+
const adminData = await adminLoginRes.json();
|
|
1521
|
+
if (adminData.success && adminData.token) {
|
|
1522
|
+
state.token = adminData.token;
|
|
1523
|
+
state.refreshToken = 'rt_' + Date.now().toString(36);
|
|
1524
|
+
state.user = {
|
|
1525
|
+
id: 'usr_admin_001',
|
|
1526
|
+
email: email,
|
|
1527
|
+
name: 'Scout Administrator',
|
|
1528
|
+
createdAt: new Date().toISOString(),
|
|
1529
|
+
};
|
|
1530
|
+
state.currentTier = 'pro';
|
|
1531
|
+
state.creditsTotal = 1000;
|
|
1532
|
+
state.creditsUsed = 0;
|
|
1533
|
+
sessionStorage.setItem('scout_admin_token', adminData.token);
|
|
1534
|
+
saveSessionToStorage();
|
|
1535
|
+
updateUserUI();
|
|
1536
|
+
showToast('Authenticated as Scout Administrator! Redirecting to Control Center...', 'success');
|
|
1537
|
+
setTimeout(() => { window.location.href = '/admin'; }, 400);
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
} catch { }
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
// Standalone fallback
|
|
1480
1545
|
state.token = 'scout_admin_token_' + Date.now().toString(36);
|
|
1481
1546
|
state.refreshToken = 'rt_' + Date.now().toString(36);
|
|
1482
1547
|
state.user = {
|
|
@@ -1784,6 +1849,11 @@ function updateUserUI() {
|
|
|
1784
1849
|
inceptionBadge.style.display = 'none';
|
|
1785
1850
|
}
|
|
1786
1851
|
}
|
|
1852
|
+
|
|
1853
|
+
// Refresh MCP servers scoped to active user
|
|
1854
|
+
if (typeof loadMcpData === 'function') {
|
|
1855
|
+
loadMcpData();
|
|
1856
|
+
}
|
|
1787
1857
|
}
|
|
1788
1858
|
|
|
1789
1859
|
async function handleLogout() {
|
|
@@ -1801,6 +1871,11 @@ async function handleLogout() {
|
|
|
1801
1871
|
localStorage.removeItem('scout_auth_token');
|
|
1802
1872
|
localStorage.removeItem('scout_refresh_token');
|
|
1803
1873
|
|
|
1874
|
+
if (typeof mcpState !== 'undefined') {
|
|
1875
|
+
mcpState.servers = [];
|
|
1876
|
+
if (typeof renderMcpServersList === 'function') renderMcpServersList();
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1804
1879
|
showToast('Signed out of Scout session.', 'info');
|
|
1805
1880
|
window.location.href = '/auth';
|
|
1806
1881
|
}
|
|
@@ -3145,8 +3220,25 @@ function initWorkstationRouter() {
|
|
|
3145
3220
|
// Model Context Protocol (MCP) Ecosystem Controller
|
|
3146
3221
|
// ==========================================================================
|
|
3147
3222
|
|
|
3223
|
+
function getMcpApiBase() {
|
|
3224
|
+
return state.apiBaseUrl || '';
|
|
3225
|
+
}
|
|
3226
|
+
|
|
3227
|
+
function getAccountMcpStorageKey() {
|
|
3228
|
+
const email = (state.user?.email || '').trim().toLowerCase();
|
|
3229
|
+
return email ? `scout_mcp_servers_${email}` : 'scout_cached_mcp_servers';
|
|
3230
|
+
}
|
|
3231
|
+
|
|
3148
3232
|
const mcpState = {
|
|
3149
|
-
servers:
|
|
3233
|
+
servers: (() => {
|
|
3234
|
+
try {
|
|
3235
|
+
const key = (state.user?.email) ? `scout_mcp_servers_${state.user.email.toLowerCase()}` : 'scout_cached_mcp_servers';
|
|
3236
|
+
const cached = localStorage.getItem(key) || localStorage.getItem('scout_cached_mcp_servers');
|
|
3237
|
+
return cached ? JSON.parse(cached) : [];
|
|
3238
|
+
} catch {
|
|
3239
|
+
return [];
|
|
3240
|
+
}
|
|
3241
|
+
})(),
|
|
3150
3242
|
activity: [],
|
|
3151
3243
|
health: {},
|
|
3152
3244
|
selectedServer: null,
|
|
@@ -3154,21 +3246,69 @@ const mcpState = {
|
|
|
3154
3246
|
};
|
|
3155
3247
|
|
|
3156
3248
|
async function loadMcpData() {
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
fetch('/api/mcp/servers').then(r => r.json()).catch(() => ({ success: false, servers: [] })),
|
|
3160
|
-
fetch('/api/mcp/health').then(r => r.json()).catch(() => ({ success: false, health: {} })),
|
|
3161
|
-
fetch('/api/mcp/activity?limit=30').then(r => r.json()).catch(() => ({ success: false, activity: [] }))
|
|
3162
|
-
]);
|
|
3249
|
+
const userEmail = (state.user?.email || '').trim().toLowerCase();
|
|
3250
|
+
const storageKey = getAccountMcpStorageKey();
|
|
3163
3251
|
|
|
3164
|
-
|
|
3165
|
-
|
|
3252
|
+
// 1. Immediately display cached servers for this account
|
|
3253
|
+
try {
|
|
3254
|
+
const cached = localStorage.getItem(storageKey);
|
|
3255
|
+
if (cached) {
|
|
3256
|
+
const parsed = JSON.parse(cached);
|
|
3257
|
+
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
3258
|
+
mcpState.servers = parsed;
|
|
3259
|
+
renderMcpKpis();
|
|
3260
|
+
renderMcpServersList();
|
|
3261
|
+
}
|
|
3166
3262
|
}
|
|
3167
|
-
|
|
3168
|
-
|
|
3263
|
+
} catch { }
|
|
3264
|
+
|
|
3265
|
+
// 2. Update account badge in header
|
|
3266
|
+
const badgeEl = getEl('mcp-account-badge');
|
|
3267
|
+
if (badgeEl) {
|
|
3268
|
+
if (state.user?.email) {
|
|
3269
|
+
badgeEl.style.display = 'inline-flex';
|
|
3270
|
+
badgeEl.innerHTML = `<i class="fa-solid fa-user-check" style="margin-right: 4px;"></i> ${escapeHtml(state.user.email)}`;
|
|
3271
|
+
} else {
|
|
3272
|
+
badgeEl.style.display = 'none';
|
|
3169
3273
|
}
|
|
3170
|
-
|
|
3171
|
-
|
|
3274
|
+
}
|
|
3275
|
+
|
|
3276
|
+
try {
|
|
3277
|
+
const apiBase = getMcpApiBase();
|
|
3278
|
+
const canFetchServer = state.serverConnected || window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' || (apiBase && apiBase.length > 0);
|
|
3279
|
+
|
|
3280
|
+
if (canFetchServer) {
|
|
3281
|
+
const queryParams = userEmail ? `?user=${encodeURIComponent(userEmail)}` : '';
|
|
3282
|
+
const [serversRes, healthRes, activityRes] = await Promise.all([
|
|
3283
|
+
fetch(`${apiBase}/api/mcp/servers${queryParams}`).then(r => r.json()).catch(() => ({ success: false, servers: [] })),
|
|
3284
|
+
fetch(`${apiBase}/api/mcp/health`).then(r => r.json()).catch(() => ({ success: false, health: {} })),
|
|
3285
|
+
fetch(`${apiBase}/api/mcp/activity?limit=30`).then(r => r.json()).catch(() => ({ success: false, activity: [] }))
|
|
3286
|
+
]);
|
|
3287
|
+
|
|
3288
|
+
if (serversRes.success && Array.isArray(serversRes.servers)) {
|
|
3289
|
+
mcpState.servers = serversRes.servers;
|
|
3290
|
+
try {
|
|
3291
|
+
localStorage.setItem(storageKey, JSON.stringify(serversRes.servers));
|
|
3292
|
+
localStorage.setItem('scout_cached_mcp_servers', JSON.stringify(serversRes.servers));
|
|
3293
|
+
} catch { }
|
|
3294
|
+
}
|
|
3295
|
+
if (healthRes.success && healthRes.health) {
|
|
3296
|
+
mcpState.health = healthRes.health;
|
|
3297
|
+
}
|
|
3298
|
+
if (activityRes.success && Array.isArray(activityRes.activity)) {
|
|
3299
|
+
mcpState.activity = activityRes.activity;
|
|
3300
|
+
}
|
|
3301
|
+
} else {
|
|
3302
|
+
// Standalone Cloud Web Mode (e.g. on scout.frontterrain.com without local server online)
|
|
3303
|
+
try {
|
|
3304
|
+
const stored = localStorage.getItem(storageKey);
|
|
3305
|
+
if (stored) {
|
|
3306
|
+
mcpState.servers = JSON.parse(stored);
|
|
3307
|
+
} else if (!userEmail) {
|
|
3308
|
+
const fallback = localStorage.getItem('scout_cached_mcp_servers');
|
|
3309
|
+
if (fallback) mcpState.servers = JSON.parse(fallback);
|
|
3310
|
+
}
|
|
3311
|
+
} catch { }
|
|
3172
3312
|
}
|
|
3173
3313
|
|
|
3174
3314
|
renderMcpKpis();
|
|
@@ -3221,6 +3361,32 @@ function renderMcpServersList() {
|
|
|
3221
3361
|
if (!container) return;
|
|
3222
3362
|
|
|
3223
3363
|
if (mcpState.servers.length === 0) {
|
|
3364
|
+
if (!state.serverConnected) {
|
|
3365
|
+
container.innerHTML = `
|
|
3366
|
+
<div style="padding: 36px 24px; text-align: center; border: 1px dashed rgba(0, 229, 255, 0.3); border-radius: 12px; background: rgba(0, 229, 255, 0.03); margin: 16px;">
|
|
3367
|
+
<div style="width: 52px; height: 52px; border-radius: 12px; background: rgba(0, 229, 255, 0.12); border: 1px solid rgba(0, 229, 255, 0.25); color: #00e5ff; display: inline-flex; align-items: center; justify-content: center; font-size: 22px; margin-bottom: 14px;">
|
|
3368
|
+
<i class="fa-solid fa-cubes-stacked"></i>
|
|
3369
|
+
</div>
|
|
3370
|
+
<h4 style="font-size: 16px; font-weight: 700; color: #fff; margin: 0 0 8px 0;">No External MCP Servers Configured</h4>
|
|
3371
|
+
<p style="font-size: 13px; color: var(--scout-text-secondary); max-width: 520px; margin: 0 auto 16px auto; line-height: 1.5;">
|
|
3372
|
+
Connect external Model Context Protocol (MCP) servers to grant autonomous tools to Scout under <strong>${escapeHtml(state.user?.email || 'this account')}</strong>.
|
|
3373
|
+
</p>
|
|
3374
|
+
<div style="display: flex; justify-content: center; gap: 10px; flex-wrap: wrap; margin-bottom: 14px;">
|
|
3375
|
+
<button type="button" class="btn btn-sm btn-primary" onclick="openAddMcpModal()">
|
|
3376
|
+
<i class="fa-solid fa-plus"></i> Connect First MCP Server
|
|
3377
|
+
</button>
|
|
3378
|
+
<button type="button" class="btn btn-sm btn-outline" onclick="checkServerStatusAndSession().then(loadMcpData)">
|
|
3379
|
+
<i class="fa-solid fa-satellite-dish"></i> Sync with Local CLI Server
|
|
3380
|
+
</button>
|
|
3381
|
+
</div>
|
|
3382
|
+
<div style="font-size: 11px; color: var(--scout-text-dim);">
|
|
3383
|
+
<i class="fa-solid fa-terminal" style="color: #00e5ff;"></i> Tip: Run <code>scout dashboard</code> in your terminal for real-time 2-way CLI sync.
|
|
3384
|
+
</div>
|
|
3385
|
+
</div>
|
|
3386
|
+
`;
|
|
3387
|
+
return;
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3224
3390
|
container.innerHTML = `
|
|
3225
3391
|
<div style="padding: 48px 24px; text-align: center;">
|
|
3226
3392
|
<div style="width: 56px; height: 56px; border-radius: 14px; background: rgba(0, 229, 255, 0.08); border: 1px solid rgba(0, 229, 255, 0.2); color: #00e5ff; display: inline-flex; align-items: center; justify-content: center; font-size: 24px; margin-bottom: 16px;">
|
|
@@ -3454,6 +3620,9 @@ async function handleAddMcpServerSubmit(e) {
|
|
|
3454
3620
|
}
|
|
3455
3621
|
|
|
3456
3622
|
try {
|
|
3623
|
+
const userEmail = (state.user?.email || '').trim().toLowerCase();
|
|
3624
|
+
const userId = state.user?.id || state.user?.uid || undefined;
|
|
3625
|
+
|
|
3457
3626
|
const payload = {
|
|
3458
3627
|
name,
|
|
3459
3628
|
transport,
|
|
@@ -3463,6 +3632,8 @@ async function handleAddMcpServerSubmit(e) {
|
|
|
3463
3632
|
authType,
|
|
3464
3633
|
headerKey: authType === 'apiKey' ? headerKey : undefined,
|
|
3465
3634
|
token: authType !== 'none' ? token : undefined,
|
|
3635
|
+
userId,
|
|
3636
|
+
userEmail: userEmail || undefined,
|
|
3466
3637
|
permissions: {
|
|
3467
3638
|
autoApproveSafe,
|
|
3468
3639
|
requireConfirmationForDestructive,
|
|
@@ -3470,20 +3641,51 @@ async function handleAddMcpServerSubmit(e) {
|
|
|
3470
3641
|
autoConnect,
|
|
3471
3642
|
};
|
|
3472
3643
|
|
|
3473
|
-
const
|
|
3474
|
-
|
|
3475
|
-
headers: { 'Content-Type': 'application/json' },
|
|
3476
|
-
body: JSON.stringify(payload)
|
|
3477
|
-
});
|
|
3644
|
+
const apiBase = getMcpApiBase();
|
|
3645
|
+
const canCallApi = state.serverConnected || window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' || (apiBase && apiBase.length > 0);
|
|
3478
3646
|
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3647
|
+
if (canCallApi) {
|
|
3648
|
+
const res = await fetch(`${apiBase}/api/mcp/servers`, {
|
|
3649
|
+
method: 'POST',
|
|
3650
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3651
|
+
body: JSON.stringify(payload)
|
|
3652
|
+
});
|
|
3653
|
+
|
|
3654
|
+
const data = await res.json();
|
|
3655
|
+
if (data.success) {
|
|
3656
|
+
showToast(`Connected to MCP server "${name}"! Discovered ${data.server?.tools?.length || 0} tools.`, 'success');
|
|
3657
|
+
closeAddMcpModal();
|
|
3658
|
+
getEl('form-add-mcp')?.reset();
|
|
3659
|
+
await loadMcpData();
|
|
3660
|
+
} else {
|
|
3661
|
+
showToast(data.error || 'Failed to add MCP server', 'error');
|
|
3662
|
+
}
|
|
3663
|
+
} else {
|
|
3664
|
+
// Standalone Cloud Web Mode fallback on scout.frontterrain.com
|
|
3665
|
+
const newServer = {
|
|
3666
|
+
id: `mcp_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`,
|
|
3667
|
+
name,
|
|
3668
|
+
transport,
|
|
3669
|
+
endpoint: transport === 'http' ? endpoint : undefined,
|
|
3670
|
+
command: transport === 'stdio' ? command : undefined,
|
|
3671
|
+
args: transport === 'stdio' ? args : undefined,
|
|
3672
|
+
authType,
|
|
3673
|
+
headerKey: authType === 'apiKey' ? headerKey : undefined,
|
|
3674
|
+
status: transport === 'http' ? 'connected' : 'disconnected',
|
|
3675
|
+
userId,
|
|
3676
|
+
userEmail: userEmail || 'default',
|
|
3677
|
+
tools: [],
|
|
3678
|
+
createdAt: new Date().toISOString()
|
|
3679
|
+
};
|
|
3680
|
+
mcpState.servers.push(newServer);
|
|
3681
|
+
const storageKey = getAccountMcpStorageKey();
|
|
3682
|
+
localStorage.setItem(storageKey, JSON.stringify(mcpState.servers));
|
|
3683
|
+
localStorage.setItem('scout_cached_mcp_servers', JSON.stringify(mcpState.servers));
|
|
3684
|
+
showToast(`MCP server "${name}" saved for ${state.user?.email || 'your account'}.`, 'success');
|
|
3482
3685
|
closeAddMcpModal();
|
|
3483
3686
|
getEl('form-add-mcp')?.reset();
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
showToast(data.error || 'Failed to add MCP server', 'error');
|
|
3687
|
+
renderMcpKpis();
|
|
3688
|
+
renderMcpServersList();
|
|
3487
3689
|
}
|
|
3488
3690
|
} catch (err) {
|
|
3489
3691
|
showToast(err.message || 'Error connecting MCP server', 'error');
|
|
@@ -3560,7 +3762,7 @@ function closeInspectMcpModal() {
|
|
|
3560
3762
|
async function connectMcpServer(serverId) {
|
|
3561
3763
|
try {
|
|
3562
3764
|
showToast('Connecting to MCP server...', 'info');
|
|
3563
|
-
const res = await fetch(
|
|
3765
|
+
const res = await fetch(`${getMcpApiBase()}/api/mcp/servers/${encodeURIComponent(serverId)}/connect`, { method: 'POST' });
|
|
3564
3766
|
const data = await res.json();
|
|
3565
3767
|
if (data.success) {
|
|
3566
3768
|
showToast(`Connected! Discovered ${data.server.tools?.length || 0} tools.`, 'success');
|
|
@@ -3575,7 +3777,7 @@ async function connectMcpServer(serverId) {
|
|
|
3575
3777
|
|
|
3576
3778
|
async function disconnectMcpServer(serverId) {
|
|
3577
3779
|
try {
|
|
3578
|
-
const res = await fetch(
|
|
3780
|
+
const res = await fetch(`${getMcpApiBase()}/api/mcp/servers/${encodeURIComponent(serverId)}/disconnect`, { method: 'POST' });
|
|
3579
3781
|
const data = await res.json();
|
|
3580
3782
|
if (data.success) {
|
|
3581
3783
|
showToast('MCP server disconnected.', 'info');
|
|
@@ -3593,14 +3795,29 @@ async function deleteMcpServer(serverId) {
|
|
|
3593
3795
|
return;
|
|
3594
3796
|
}
|
|
3595
3797
|
try {
|
|
3596
|
-
const
|
|
3597
|
-
const
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
await
|
|
3798
|
+
const apiBase = getMcpApiBase();
|
|
3799
|
+
const canCallApi = state.serverConnected || window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' || (apiBase && apiBase.length > 0);
|
|
3800
|
+
|
|
3801
|
+
if (canCallApi) {
|
|
3802
|
+
const res = await fetch(`${apiBase}/api/mcp/servers/${encodeURIComponent(serverId)}`, { method: 'DELETE' });
|
|
3803
|
+
const data = await res.json();
|
|
3804
|
+
if (data.success) {
|
|
3805
|
+
showToast('MCP server removed.', 'info');
|
|
3806
|
+
} else {
|
|
3807
|
+
showToast(data.error || 'Failed to delete server', 'error');
|
|
3808
|
+
}
|
|
3601
3809
|
} else {
|
|
3602
|
-
showToast(
|
|
3810
|
+
showToast('MCP server removed from your account.', 'info');
|
|
3603
3811
|
}
|
|
3812
|
+
|
|
3813
|
+
mcpState.servers = mcpState.servers.filter(s => s.id !== serverId);
|
|
3814
|
+
const storageKey = getAccountMcpStorageKey();
|
|
3815
|
+
try {
|
|
3816
|
+
localStorage.setItem(storageKey, JSON.stringify(mcpState.servers));
|
|
3817
|
+
localStorage.setItem('scout_cached_mcp_servers', JSON.stringify(mcpState.servers));
|
|
3818
|
+
} catch { }
|
|
3819
|
+
renderMcpKpis();
|
|
3820
|
+
renderMcpServersList();
|
|
3604
3821
|
} catch (err) {
|
|
3605
3822
|
showToast(err.message || 'Error deleting server', 'error');
|
|
3606
3823
|
}
|
|
@@ -3608,7 +3825,7 @@ async function deleteMcpServer(serverId) {
|
|
|
3608
3825
|
|
|
3609
3826
|
async function toggleMcpTool(serverId, toolName, enabled) {
|
|
3610
3827
|
try {
|
|
3611
|
-
const res = await fetch(
|
|
3828
|
+
const res = await fetch(`${getMcpApiBase()}/api/mcp/servers/${encodeURIComponent(serverId)}/tools/${encodeURIComponent(toolName)}/toggle`, {
|
|
3612
3829
|
method: 'POST',
|
|
3613
3830
|
headers: { 'Content-Type': 'application/json' },
|
|
3614
3831
|
body: JSON.stringify({ enabled })
|
|
@@ -3631,6 +3848,11 @@ async function toggleMcpTool(serverId, toolName, enabled) {
|
|
|
3631
3848
|
}
|
|
3632
3849
|
}
|
|
3633
3850
|
|
|
3851
|
+
// Auto-sync when window regains focus (e.g., returning from CLI terminal)
|
|
3852
|
+
window.addEventListener('focus', () => {
|
|
3853
|
+
loadMcpData();
|
|
3854
|
+
});
|
|
3855
|
+
|
|
3634
3856
|
// Global exports for inline HTML event handlers
|
|
3635
3857
|
window.loadMcpData = loadMcpData;
|
|
3636
3858
|
window.openAddMcpModal = openAddMcpModal;
|
package/web/styles.css
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* ==========================================================================
|
|
2
|
-
FrontTerrain Scout Design System & Visual Engine v9.0.
|
|
2
|
+
FrontTerrain Scout Design System & Visual Engine v9.0.9
|
|
3
3
|
Inspired by HappyRobot, Linear.app & Vercel aesthetics
|
|
4
4
|
========================================================================== */
|
|
5
5
|
|
|
@@ -1055,7 +1055,7 @@ h1, h2, h3, h4, h5, h6 {
|
|
|
1055
1055
|
.footer-links a:hover { color: var(--hr-rust); }
|
|
1056
1056
|
|
|
1057
1057
|
/* ==========================================================================
|
|
1058
|
-
Visual Perception & Automation Engine v9.0.
|
|
1058
|
+
Visual Perception & Automation Engine v9.0.9
|
|
1059
1059
|
========================================================================== */
|
|
1060
1060
|
.vision-banner {
|
|
1061
1061
|
margin-bottom: 28px;
|