ft-scout 9.0.7 → 9.0.8
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 +8 -8
- package/AUTONOMOUS_FEATURES.md +1 -1
- package/README.md +113 -16
- package/bin/src/commands/agent.js +2 -2
- package/bin/src/commands/agent.js.map +1 -1
- package/bin/src/commands/audit.js +1 -1
- package/bin/src/commands/audit.js.map +1 -1
- package/bin/src/commands/dashboard.js +1 -1
- package/bin/src/commands/dashboard.js.map +1 -1
- package/bin/src/commands/mcp.d.ts.map +1 -0
- package/bin/src/commands/mcp.js +393 -0
- package/bin/src/commands/mcp.js.map +1 -0
- package/bin/src/commands/owners.js +1 -1
- package/bin/src/commands/owners.js.map +1 -1
- package/bin/src/discovery/messages.js +2 -2
- package/bin/src/discovery/messages.js.map +1 -1
- package/bin/src/discovery/signals.js +3 -3
- package/bin/src/discovery/signals.js.map +1 -1
- package/bin/src/engine/agentEngine.d.ts.map +1 -1
- package/bin/src/engine/agentEngine.js +53 -11
- package/bin/src/engine/agentEngine.js.map +1 -1
- package/bin/src/engine/runtime/agentRuntime.d.ts.map +1 -1
- package/bin/src/engine/runtime/agentRuntime.js +40 -3
- package/bin/src/engine/runtime/agentRuntime.js.map +1 -1
- package/bin/src/engine/toolRegistry.d.ts.map +1 -1
- package/bin/src/engine/toolRegistry.js +13 -0
- package/bin/src/engine/toolRegistry.js.map +1 -1
- package/bin/src/engine/verifier.js +2 -2
- package/bin/src/engine/verifier.js.map +1 -1
- package/bin/src/index.js +85 -7
- package/bin/src/index.js.map +1 -1
- package/bin/src/mcp/client.d.ts.map +1 -0
- package/bin/src/mcp/client.js +127 -0
- package/bin/src/mcp/client.js.map +1 -0
- package/bin/src/mcp/discovery.d.ts.map +1 -0
- package/bin/src/mcp/discovery.js +91 -0
- package/bin/src/mcp/discovery.js.map +1 -0
- package/bin/src/mcp/health.d.ts.map +1 -0
- package/bin/src/mcp/health.js +44 -0
- package/bin/src/mcp/health.js.map +1 -0
- package/bin/src/mcp/index.d.ts.map +1 -0
- package/bin/src/mcp/index.js +15 -0
- package/bin/src/mcp/index.js.map +1 -0
- package/bin/src/mcp/manager.d.ts.map +1 -0
- package/bin/src/mcp/manager.js +535 -0
- package/bin/src/mcp/manager.js.map +1 -0
- package/bin/src/mcp/permissions.d.ts.map +1 -0
- package/bin/src/mcp/permissions.js +50 -0
- package/bin/src/mcp/permissions.js.map +1 -0
- package/bin/src/mcp/persistence.d.ts.map +1 -0
- package/bin/src/mcp/persistence.js +156 -0
- package/bin/src/mcp/persistence.js.map +1 -0
- package/bin/src/mcp/recovery.d.ts.map +1 -0
- package/bin/src/mcp/recovery.js +75 -0
- package/bin/src/mcp/recovery.js.map +1 -0
- package/bin/src/mcp/telemetry.d.ts.map +1 -0
- package/bin/src/mcp/telemetry.js +82 -0
- package/bin/src/mcp/telemetry.js.map +1 -0
- package/bin/src/mcp/toolRouter.d.ts.map +1 -0
- package/bin/src/mcp/toolRouter.js +127 -0
- package/bin/src/mcp/toolRouter.js.map +1 -0
- package/bin/src/mcp/transport.d.ts.map +1 -0
- package/bin/src/mcp/transport.js +328 -0
- package/bin/src/mcp/transport.js.map +1 -0
- package/bin/src/mcp/types.d.ts.map +1 -0
- package/bin/src/mcp/types.js +6 -0
- package/bin/src/mcp/types.js.map +1 -0
- package/bin/src/server/server.d.ts.map +1 -1
- package/bin/src/server/server.js +207 -0
- package/bin/src/server/server.js.map +1 -1
- package/bin/src/utils/branding.js +1 -1
- package/bin/src/utils/email.js +4 -4
- package/package.json +1 -1
- package/web/app.js +1255 -59
- package/web/styles.css +1474 -15
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scout MCP Ecosystem - Persistence & Encrypted Credential Store
|
|
3
|
+
* Manages MCP server definitions and securely encrypts secrets at rest using AES-256-GCM.
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
import crypto from 'crypto';
|
|
9
|
+
const HOME_FT_DIR = path.join(os.homedir(), '.ft');
|
|
10
|
+
const SERVERS_FILE = path.join(HOME_FT_DIR, 'mcp_servers.json');
|
|
11
|
+
const CREDENTIALS_FILE = path.join(HOME_FT_DIR, 'mcp_credentials.enc');
|
|
12
|
+
// Derive machine-specific key for local AES-256-GCM encryption
|
|
13
|
+
function getEncryptionKey() {
|
|
14
|
+
const machineSalt = 'scout_mcp_secure_v1_encryption_salt';
|
|
15
|
+
const machineId = `${os.hostname()}_${os.userInfo().username}_${os.platform()}`;
|
|
16
|
+
return crypto.pbkdf2Sync(machineId, machineSalt, 100000, 32, 'sha256');
|
|
17
|
+
}
|
|
18
|
+
function ensureDir(dirPath) {
|
|
19
|
+
if (!fs.existsSync(dirPath)) {
|
|
20
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Encrypt a plaintext string using AES-256-GCM
|
|
25
|
+
*/
|
|
26
|
+
export function encryptSecret(plainText) {
|
|
27
|
+
const key = getEncryptionKey();
|
|
28
|
+
const iv = crypto.randomBytes(12);
|
|
29
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
|
|
30
|
+
let encrypted = cipher.update(plainText, 'utf8', 'hex');
|
|
31
|
+
encrypted += cipher.final('hex');
|
|
32
|
+
const authTag = cipher.getAuthTag();
|
|
33
|
+
return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Decrypt an AES-256-GCM encrypted payload string
|
|
37
|
+
*/
|
|
38
|
+
export function decryptSecret(encryptedPayload) {
|
|
39
|
+
try {
|
|
40
|
+
const parts = encryptedPayload.split(':');
|
|
41
|
+
if (parts.length !== 3)
|
|
42
|
+
return '';
|
|
43
|
+
const ivHex = parts[0];
|
|
44
|
+
const authTagHex = parts[1];
|
|
45
|
+
const encryptedHex = parts[2];
|
|
46
|
+
if (!ivHex || !authTagHex || !encryptedHex)
|
|
47
|
+
return '';
|
|
48
|
+
const key = getEncryptionKey();
|
|
49
|
+
const iv = Buffer.from(ivHex, 'hex');
|
|
50
|
+
const authTag = Buffer.from(authTagHex, 'hex');
|
|
51
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
|
|
52
|
+
decipher.setAuthTag(authTag);
|
|
53
|
+
let decrypted = decipher.update(encryptedHex, 'hex', 'utf8');
|
|
54
|
+
decrypted += decipher.final('utf8');
|
|
55
|
+
return decrypted;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return '';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function loadCredentialsMap() {
|
|
62
|
+
if (!fs.existsSync(CREDENTIALS_FILE))
|
|
63
|
+
return {};
|
|
64
|
+
try {
|
|
65
|
+
const raw = fs.readFileSync(CREDENTIALS_FILE, 'utf-8');
|
|
66
|
+
return JSON.parse(raw);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return {};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function saveCredentialsMap(map) {
|
|
73
|
+
ensureDir(HOME_FT_DIR);
|
|
74
|
+
fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(map, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Save an encrypted secret for an MCP server
|
|
78
|
+
*/
|
|
79
|
+
export function saveServerSecret(serverId, secret) {
|
|
80
|
+
if (!secret)
|
|
81
|
+
return;
|
|
82
|
+
const map = loadCredentialsMap();
|
|
83
|
+
map[serverId] = encryptSecret(secret);
|
|
84
|
+
saveCredentialsMap(map);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Retrieve decrypted secret for an MCP server
|
|
88
|
+
*/
|
|
89
|
+
export function getServerSecret(serverId) {
|
|
90
|
+
const map = loadCredentialsMap();
|
|
91
|
+
const enc = map[serverId];
|
|
92
|
+
if (!enc)
|
|
93
|
+
return null;
|
|
94
|
+
const dec = decryptSecret(enc);
|
|
95
|
+
return dec || null;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Delete a stored secret for an MCP server
|
|
99
|
+
*/
|
|
100
|
+
export function deleteServerSecret(serverId) {
|
|
101
|
+
const map = loadCredentialsMap();
|
|
102
|
+
if (map[serverId]) {
|
|
103
|
+
delete map[serverId];
|
|
104
|
+
saveCredentialsMap(map);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Check if a server has an encrypted secret stored
|
|
109
|
+
*/
|
|
110
|
+
export function hasServerSecret(serverId) {
|
|
111
|
+
const map = loadCredentialsMap();
|
|
112
|
+
return Boolean(map[serverId]);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Load all configured MCP servers from disk
|
|
116
|
+
*/
|
|
117
|
+
export function loadMCPServers() {
|
|
118
|
+
if (!fs.existsSync(SERVERS_FILE))
|
|
119
|
+
return [];
|
|
120
|
+
try {
|
|
121
|
+
const raw = fs.readFileSync(SERVERS_FILE, 'utf-8');
|
|
122
|
+
const list = JSON.parse(raw);
|
|
123
|
+
if (!Array.isArray(list))
|
|
124
|
+
return [];
|
|
125
|
+
return list.map((server) => {
|
|
126
|
+
server.hasSecret = hasServerSecret(server.id);
|
|
127
|
+
return server;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Save MCP servers list to disk
|
|
136
|
+
*/
|
|
137
|
+
export function saveMCPServers(servers) {
|
|
138
|
+
ensureDir(HOME_FT_DIR);
|
|
139
|
+
// Sanitize before writing: ensure no secrets are stored in mcp_servers.json
|
|
140
|
+
const cleaned = servers.map((s) => sanitizeServerConfig(s));
|
|
141
|
+
fs.writeFileSync(SERVERS_FILE, JSON.stringify(cleaned, null, 2), 'utf-8');
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Return a copy of MCPServerConfig safe for client/UI/CLI display (no secrets)
|
|
145
|
+
*/
|
|
146
|
+
export function sanitizeServerConfig(server) {
|
|
147
|
+
const clone = JSON.parse(JSON.stringify(server));
|
|
148
|
+
delete clone.token;
|
|
149
|
+
delete clone.key;
|
|
150
|
+
delete clone.secret;
|
|
151
|
+
delete clone.password;
|
|
152
|
+
delete clone.apiKey;
|
|
153
|
+
clone.hasSecret = hasServerSecret(server.id) || Boolean(server.hasSecret);
|
|
154
|
+
return clone;
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=persistence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistence.js","sourceRoot":"","sources":["persistence.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery.d.ts","sourceRoot":"","sources":["recovery.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,gBAAgB,GACtB,iBAAiB,GACjB,SAAS,GACT,wBAAwB,GACxB,gBAAgB,GAChB,cAAc,GACd,kBAAkB,GAClB,gBAAgB,CAAC;AAEvB,MAAM,WAAW,kBAAkB;IAC/B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,WAAW;IACpB;;OAEG;IACH,OAAc,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,GAAE,OAAe,GAAG,kBAAkB,CAuE5G;CACJ"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scout MCP Ecosystem - Failure Recovery & Error Classification
|
|
3
|
+
* Implements bounded recovery, error classification, and user-friendly error formatting.
|
|
4
|
+
*/
|
|
5
|
+
export class MCPRecovery {
|
|
6
|
+
/**
|
|
7
|
+
* Classify an error encountered during MCP communication or tool execution
|
|
8
|
+
*/
|
|
9
|
+
static classifyError(err, serverName, isDestructive = false) {
|
|
10
|
+
const msg = String(err?.message || err || '');
|
|
11
|
+
const lower = msg.toLowerCase();
|
|
12
|
+
if (lower.includes('econnrefused') || lower.includes('failed to fetch') || lower.includes('connection lost') || lower.includes('exited with code') || lower.includes('disconnected')) {
|
|
13
|
+
return {
|
|
14
|
+
category: 'CONNECTION_LOST',
|
|
15
|
+
isRecoverable: true,
|
|
16
|
+
canRetry: !isDestructive,
|
|
17
|
+
userFriendlyMessage: `${serverName} connection was dropped. Scout can attempt to reconnect.`,
|
|
18
|
+
originalError: msg,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (lower.includes('timed out') || lower.includes('timeout') || lower.includes('aborterror')) {
|
|
22
|
+
return {
|
|
23
|
+
category: 'TIMEOUT',
|
|
24
|
+
isRecoverable: true,
|
|
25
|
+
canRetry: !isDestructive,
|
|
26
|
+
userFriendlyMessage: `${serverName} took too long to respond. The operation timed out.`,
|
|
27
|
+
originalError: msg,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (lower.includes('unauthorized') || lower.includes('401') || lower.includes('403') || lower.includes('forbidden') || lower.includes('auth')) {
|
|
31
|
+
return {
|
|
32
|
+
category: 'AUTHENTICATION_EXPIRED',
|
|
33
|
+
isRecoverable: false,
|
|
34
|
+
canRetry: false,
|
|
35
|
+
userFriendlyMessage: `Authentication for ${serverName} is expired or invalid. Please update credentials in Connections.`,
|
|
36
|
+
originalError: msg,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (lower.includes('429') || lower.includes('rate limit') || lower.includes('quota')) {
|
|
40
|
+
return {
|
|
41
|
+
category: 'RATE_LIMITED',
|
|
42
|
+
isRecoverable: true,
|
|
43
|
+
canRetry: false,
|
|
44
|
+
userFriendlyMessage: `${serverName} rate limit exceeded. Please wait a moment before trying again.`,
|
|
45
|
+
originalError: msg,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (lower.includes('method not found') || lower.includes('tool not found') || lower.includes('-32601')) {
|
|
49
|
+
return {
|
|
50
|
+
category: 'TOOL_NOT_FOUND',
|
|
51
|
+
isRecoverable: false,
|
|
52
|
+
canRetry: false,
|
|
53
|
+
userFriendlyMessage: `Requested tool is no longer available on ${serverName}. Discovered tools will be refreshed.`,
|
|
54
|
+
originalError: msg,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (lower.includes('invalid params') || lower.includes('validation') || lower.includes('-32602')) {
|
|
58
|
+
return {
|
|
59
|
+
category: 'VALIDATION_ERROR',
|
|
60
|
+
isRecoverable: false,
|
|
61
|
+
canRetry: false,
|
|
62
|
+
userFriendlyMessage: `Invalid parameters supplied for ${serverName} tool call.`,
|
|
63
|
+
originalError: msg,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
category: 'INTERNAL_ERROR',
|
|
68
|
+
isRecoverable: false,
|
|
69
|
+
canRetry: !isDestructive,
|
|
70
|
+
userFriendlyMessage: `An error occurred while executing an operation on ${serverName}.`,
|
|
71
|
+
originalError: msg,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=recovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery.js","sourceRoot":"","sources":["recovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["telemetry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAYlE;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,GAAG,GAAG,GAAG,CAqB3C;AAED,qBAAa,YAAY;IACrB,OAAc,YAAY,CAAC,KAAK,GAAE,MAAW,GAAG,gBAAgB,EAAE,CASjE;IAED,OAAc,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAUvD;IAED,OAAc,YAAY,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAatD;CACJ"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scout MCP Ecosystem - Telemetry & Activity Logger
|
|
3
|
+
* Records observable tool executions and logs without capturing secrets or internal reasoning.
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
const HOME_FT_DIR = path.join(os.homedir(), '.ft');
|
|
9
|
+
const ACTIVITY_FILE = path.join(HOME_FT_DIR, 'mcp_activity.json');
|
|
10
|
+
const MAX_LOG_ENTRIES = 200;
|
|
11
|
+
function ensureDir(dirPath) {
|
|
12
|
+
if (!fs.existsSync(dirPath)) {
|
|
13
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Sanitize an object to remove sensitive keys (auth, token, key, secret, password)
|
|
18
|
+
*/
|
|
19
|
+
export function sanitizeData(data) {
|
|
20
|
+
if (!data)
|
|
21
|
+
return data;
|
|
22
|
+
if (typeof data !== 'object')
|
|
23
|
+
return data;
|
|
24
|
+
if (Array.isArray(data)) {
|
|
25
|
+
return data.map((item) => sanitizeData(item));
|
|
26
|
+
}
|
|
27
|
+
const cleaned = {};
|
|
28
|
+
const sensitiveKeys = ['authorization', 'token', 'key', 'secret', 'password', 'apikey', 'credential'];
|
|
29
|
+
for (const [k, v] of Object.entries(data)) {
|
|
30
|
+
if (sensitiveKeys.some((s) => k.toLowerCase().includes(s))) {
|
|
31
|
+
cleaned[k] = '[REDACTED]';
|
|
32
|
+
}
|
|
33
|
+
else if (typeof v === 'string' && v.length > 2000) {
|
|
34
|
+
cleaned[k] = `${v.slice(0, 2000)}... [truncated]`;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
cleaned[k] = sanitizeData(v);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return cleaned;
|
|
41
|
+
}
|
|
42
|
+
export class MCPTelemetry {
|
|
43
|
+
static loadActivity(limit = 50) {
|
|
44
|
+
if (!fs.existsSync(ACTIVITY_FILE))
|
|
45
|
+
return [];
|
|
46
|
+
try {
|
|
47
|
+
const raw = fs.readFileSync(ACTIVITY_FILE, 'utf-8');
|
|
48
|
+
const list = JSON.parse(raw);
|
|
49
|
+
return Array.isArray(list) ? list.slice(0, limit) : [];
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
static recordEvent(event) {
|
|
56
|
+
ensureDir(HOME_FT_DIR);
|
|
57
|
+
let list = this.loadActivity(MAX_LOG_ENTRIES);
|
|
58
|
+
list.unshift(event);
|
|
59
|
+
if (list.length > MAX_LOG_ENTRIES) {
|
|
60
|
+
list = list.slice(0, MAX_LOG_ENTRIES);
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
fs.writeFileSync(ACTIVITY_FILE, JSON.stringify(list, null, 2), 'utf-8');
|
|
64
|
+
}
|
|
65
|
+
catch { }
|
|
66
|
+
}
|
|
67
|
+
static recordResult(result) {
|
|
68
|
+
const event = {
|
|
69
|
+
id: result.invocationId,
|
|
70
|
+
timestamp: result.completedAt || new Date().toISOString(),
|
|
71
|
+
serverId: result.serverId,
|
|
72
|
+
serverName: result.serverName,
|
|
73
|
+
toolName: result.toolName,
|
|
74
|
+
status: result.status,
|
|
75
|
+
summary: result.outputSummary || result.inputSummary,
|
|
76
|
+
durationMs: result.executionDurationMs,
|
|
77
|
+
error: result.error,
|
|
78
|
+
};
|
|
79
|
+
this.recordEvent(event);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=telemetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["telemetry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolRouter.d.ts","sourceRoot":"","sources":["toolRouter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGrE,MAAM,WAAW,mBAAmB;IAChC,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,mBAAmB,EAAE,MAAM,CAAC;CAC/B;AAED,qBAAa,aAAa;IACtB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAM;IAElD;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAoDjC;;OAEG;IACH,OAAc,UAAU,CACpB,QAAQ,EAAE,MAAM,EAChB,gBAAgB,EAAE,eAAe,EAAE,GACpC,mBAAmB,CA4ErB;CACJ"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scout MCP Ecosystem - Intelligent Capability & Tool Router
|
|
3
|
+
* Selects only relevant MCP capabilities and tools for a given user goal,
|
|
4
|
+
* preventing context-window bloating and reasoning degradation.
|
|
5
|
+
*/
|
|
6
|
+
import { MCPPermissionManager } from './permissions.js';
|
|
7
|
+
export class MCPToolRouter {
|
|
8
|
+
static MAX_ROUTED_MCP_TOOLS = 10;
|
|
9
|
+
/**
|
|
10
|
+
* Compute a relevance score for a tool based on user goal and context
|
|
11
|
+
*/
|
|
12
|
+
static scoreToolRelevance(tool, server, goalTokens, rawGoalLower) {
|
|
13
|
+
let score = 0;
|
|
14
|
+
const toolNameLower = tool.originalName.toLowerCase();
|
|
15
|
+
const serverNameLower = server.name.toLowerCase();
|
|
16
|
+
const descLower = tool.description.toLowerCase();
|
|
17
|
+
// 1. Direct explicit mention of server name in user request (e.g. "use higgsfield to...", "via motion")
|
|
18
|
+
if (rawGoalLower.includes(serverNameLower)) {
|
|
19
|
+
score += 100;
|
|
20
|
+
}
|
|
21
|
+
// 2. Direct mention of tool name
|
|
22
|
+
if (rawGoalLower.includes(toolNameLower)) {
|
|
23
|
+
score += 80;
|
|
24
|
+
}
|
|
25
|
+
// 3. Domain / Capability affinity rules
|
|
26
|
+
const domainClusters = {
|
|
27
|
+
media_generation: ['video', 'image', 'picture', 'photo', 'audio', 'voice', 'sound', 'render', 'generate', 'animat', 'frame', 'visual', 'ad', 'banner', 'higgsfield'],
|
|
28
|
+
research_analytics: ['competitor', 'research', 'trend', 'analytics', 'market', 'creative', 'metrics', 'motion', 'benchmark', 'intelligence'],
|
|
29
|
+
version_control: ['github', 'gitlab', 'git', 'issue', 'pr', 'pull request', 'commit', 'branch', 'repo', 'repository'],
|
|
30
|
+
design_ui: ['figma', 'sketch', 'mockup', 'design', 'frame', 'canvas', 'component', 'prototype', 'wireframe', 'palette'],
|
|
31
|
+
communication: ['slack', 'discord', 'message', 'chat', 'channel', 'notify', 'alert', 'post', 'webhook'],
|
|
32
|
+
knowledge_docs: ['notion', 'docs', 'wiki', 'page', 'database', 'notes', 'workspace'],
|
|
33
|
+
};
|
|
34
|
+
for (const [_domain, keywords] of Object.entries(domainClusters)) {
|
|
35
|
+
const goalHasDomain = keywords.some((k) => rawGoalLower.includes(k));
|
|
36
|
+
if (goalHasDomain) {
|
|
37
|
+
const toolHasDomain = keywords.some((k) => toolNameLower.includes(k) || descLower.includes(k) || serverNameLower.includes(k));
|
|
38
|
+
if (toolHasDomain) {
|
|
39
|
+
score += 25;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// 4. Token-level keyword matching
|
|
44
|
+
for (const token of goalTokens) {
|
|
45
|
+
if (token.length < 3)
|
|
46
|
+
continue;
|
|
47
|
+
if (toolNameLower.includes(token))
|
|
48
|
+
score += 15;
|
|
49
|
+
if (descLower.includes(token))
|
|
50
|
+
score += 5;
|
|
51
|
+
if (serverNameLower.includes(token))
|
|
52
|
+
score += 10;
|
|
53
|
+
}
|
|
54
|
+
return score;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Route user goal to the most relevant MCP tools across all connected servers
|
|
58
|
+
*/
|
|
59
|
+
static routeTools(userGoal, connectedServers) {
|
|
60
|
+
const rawGoalLower = userGoal.toLowerCase().trim();
|
|
61
|
+
const goalTokens = rawGoalLower
|
|
62
|
+
.replace(/[^a-zA-Z0-9_\s]/g, ' ')
|
|
63
|
+
.split(/\s+/)
|
|
64
|
+
.filter((t) => t.length >= 3);
|
|
65
|
+
// Gather all permitted tools across all connected & enabled servers
|
|
66
|
+
const candidateTools = [];
|
|
67
|
+
for (const server of connectedServers) {
|
|
68
|
+
if (server.status !== 'connected' || server.permissions.enabled === false) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
for (const tool of server.tools) {
|
|
72
|
+
const perm = MCPPermissionManager.checkToolAccess(server, tool);
|
|
73
|
+
if (!perm.allowed)
|
|
74
|
+
continue;
|
|
75
|
+
const score = this.scoreToolRelevance(tool, server, goalTokens, rawGoalLower);
|
|
76
|
+
candidateTools.push({ tool, server, score });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Total permitted tools count
|
|
80
|
+
const totalToolsCount = candidateTools.length;
|
|
81
|
+
// If total tools are small (e.g. <= 8), provide all permitted tools without aggressive filtering
|
|
82
|
+
let selected;
|
|
83
|
+
if (totalToolsCount <= 8) {
|
|
84
|
+
selected = candidateTools;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
// Sort by relevance score descending
|
|
88
|
+
candidateTools.sort((a, b) => b.score - a.score);
|
|
89
|
+
// Filter tools with positive relevance, or take top tools if there's general affinity
|
|
90
|
+
const positiveMatches = candidateTools.filter((c) => c.score > 0);
|
|
91
|
+
if (positiveMatches.length > 0) {
|
|
92
|
+
selected = positiveMatches.slice(0, this.MAX_ROUTED_MCP_TOOLS);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
// If no specific keyword matched, include up to 5 representative tools (one per active server)
|
|
96
|
+
const seenServers = new Set();
|
|
97
|
+
const diverseSelection = [];
|
|
98
|
+
for (const item of candidateTools) {
|
|
99
|
+
if (!seenServers.has(item.server.id) && diverseSelection.length < 5) {
|
|
100
|
+
seenServers.add(item.server.id);
|
|
101
|
+
diverseSelection.push(item);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
selected = diverseSelection;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const tools = selected.map((s) => s.tool);
|
|
108
|
+
const activeServerSet = new Set(selected.map((s) => s.server.name));
|
|
109
|
+
const activeServers = Array.from(activeServerSet);
|
|
110
|
+
// Build concise capability representation for system prompt
|
|
111
|
+
const serverSummaries = [];
|
|
112
|
+
for (const sName of activeServers) {
|
|
113
|
+
const serverTools = tools.filter((t) => t.serverName === sName);
|
|
114
|
+
const toolSummary = serverTools.map((t) => t.originalName).join(', ');
|
|
115
|
+
serverSummaries.push(`• ${sName} (${serverTools.length} tools): ${toolSummary}`);
|
|
116
|
+
}
|
|
117
|
+
const capabilitiesSummary = serverSummaries.length > 0
|
|
118
|
+
? `AVAILABLE EXTERNAL MCP CAPABILITIES:\n${serverSummaries.join('\n')}`
|
|
119
|
+
: '';
|
|
120
|
+
return {
|
|
121
|
+
tools,
|
|
122
|
+
activeServers,
|
|
123
|
+
capabilitiesSummary,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=toolRouter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolRouter.js","sourceRoot":"","sources":["toolRouter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAQxD,MAAM,OAAO,aAAa;IACd,MAAM,CAAU,oBAAoB,GAAG,EAAE,CAAC;IAElD;;OAEG;IACK,MAAM,CAAC,kBAAkB,CAC7B,IAAuB,EACvB,MAAuB,EACvB,UAAoB,EACpB,YAAoB;QAEpB,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,MAAM,aAAa,GAAG,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC;QACtD,MAAM,eAAe,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QAClD,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC;QAEjD,wGAAwG;QACxG,IAAI,YAAY,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;YACzC,KAAK,IAAI,GAAG,CAAC;QACjB,CAAC;QAED,iCAAiC;QACjC,IAAI,YAAY,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;YACvC,KAAK,IAAI,EAAE,CAAC;QAChB,CAAC;QAED,wCAAwC;QACxC,MAAM,cAAc,GAA6B;YAC7C,gBAAgB,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,CAAC;YACpK,kBAAkB,EAAE,CAAC,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,CAAC;YAC5I,eAAe,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,CAAC;YACrH,SAAS,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,CAAC;YACvH,aAAa,EAAE,CAAC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC;YACvG,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,CAAC;SACvF,CAAC;QAEF,KAAK,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;YAC/D,MAAM,aAAa,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;YACrE,IAAI,aAAa,EAAE,CAAC;gBAChB,MAAM,aAAa,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC9H,IAAI,aAAa,EAAE,CAAC;oBAChB,KAAK,IAAI,EAAE,CAAC;gBAChB,CAAC;YACL,CAAC;QACL,CAAC;QAED,kCAAkC;QAClC,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;YAC7B,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;gBAAE,SAAS;YAC/B,IAAI,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAAE,KAAK,IAAI,EAAE,CAAC;YAC/C,IAAI,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAAE,KAAK,IAAI,CAAC,CAAC;YAC1C,IAAI,eAAe,CAAC,QAAQ,CAAC,KAAK,CAAC;gBAAE,KAAK,IAAI,EAAE,CAAC;QACrD,CAAC;QAED,OAAO,KAAK,CAAC;IACjB,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,UAAU,CACpB,QAAgB,EAChB,gBAAmC;QAEnC,MAAM,YAAY,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;QACnD,MAAM,UAAU,GAAG,YAAY;aAC1B,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC;aAChC,KAAK,CAAC,KAAK,CAAC;aACZ,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;QAElC,oEAAoE;QACpE,MAAM,cAAc,GAA+E,EAAE,CAAC;QAEtG,KAAK,MAAM,MAAM,IAAI,gBAAgB,EAAE,CAAC;YACpC,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,WAAW,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;gBACxE,SAAS;YACb,CAAC;YAED,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;gBAC9B,MAAM,IAAI,GAAG,oBAAoB,CAAC,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBAChE,IAAI,CAAC,IAAI,CAAC,OAAO;oBAAE,SAAS;gBAE5B,MAAM,KAAK,GAAG,IAAI,CAAC,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC;gBAC9E,cAAc,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;YACjD,CAAC;QACL,CAAC;QAED,8BAA8B;QAC9B,MAAM,eAAe,GAAG,cAAc,CAAC,MAAM,CAAC;QAE9C,iGAAiG;QACjG,IAAI,QAAqE,CAAC;QAE1E,IAAI,eAAe,IAAI,CAAC,EAAE,CAAC;YACvB,QAAQ,GAAG,cAAc,CAAC;QAC9B,CAAC;aAAM,CAAC;YACJ,qCAAqC;YACrC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;YAEjD,sFAAsF;YACtF,MAAM,eAAe,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;YAElE,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC7B,QAAQ,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;YACnE,CAAC;iBAAM,CAAC;gBACJ,+FAA+F;gBAC/F,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;gBACtC,MAAM,gBAAgB,GAA0B,EAAE,CAAC;gBACnD,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;oBAChC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBAClE,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;wBAChC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBAChC,CAAC;gBACL,CAAC;gBACD,QAAQ,GAAG,gBAAgB,CAAC;YAChC,CAAC;QACL,CAAC;QAED,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,eAAe,GAAG,IAAI,GAAG,CAAS,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5E,MAAM,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAElD,4DAA4D;QAC5D,MAAM,eAAe,GAAa,EAAE,CAAC;QACrC,KAAK,MAAM,KAAK,IAAI,aAAa,EAAE,CAAC;YAChC,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,KAAK,CAAC,CAAC;YAChE,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtE,eAAe,CAAC,IAAI,CAAC,KAAK,KAAK,KAAK,WAAW,CAAC,MAAM,YAAY,WAAW,EAAE,CAAC,CAAC;QACrF,CAAC;QAED,MAAM,mBAAmB,GAAG,eAAe,CAAC,MAAM,GAAG,CAAC;YAClD,CAAC,CAAC,yCAAyC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YACvE,CAAC,CAAC,EAAE,CAAC;QAET,OAAO;YACH,KAAK;YACL,aAAa;YACb,mBAAmB;SACtB,CAAC;IACN,CAAC;CACJ"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["transport.ts"],"names":[],"mappings":"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"}
|