fullcourtdefense-cli 1.14.7 → 1.14.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/dist/commands/discover.js +109 -8
- package/dist/commands/knownMcpServers.js +45 -0
- package/dist/version.json +1 -1
- package/package.json +2 -1
|
@@ -39,6 +39,7 @@ const crypto = __importStar(require("crypto"));
|
|
|
39
39
|
const fs = __importStar(require("fs"));
|
|
40
40
|
const os = __importStar(require("os"));
|
|
41
41
|
const path = __importStar(require("path"));
|
|
42
|
+
const child_process_1 = require("child_process");
|
|
42
43
|
const config_1 = require("../config");
|
|
43
44
|
const discoverProxy_1 = require("./discoverProxy");
|
|
44
45
|
const discoverSchedule_1 = require("./discoverSchedule");
|
|
@@ -227,7 +228,7 @@ const RISK_RULES = [
|
|
|
227
228
|
{ test: /stripe|payment|refund|payout|invoice|billing/i, level: 'critical', tag: 'payments' },
|
|
228
229
|
{ test: /filesystem|file[-_]?system|\bfs\b|read[-_]?file|write[-_]?file/i, level: 'high', tag: 'filesystem' },
|
|
229
230
|
{ test: /github|gitlab|bitbucket/i, level: 'high', tag: 'source-control' },
|
|
230
|
-
{ test: /aws|gcp|azure|cloud|kubernetes|k8s|terraform/i, level: 'high', tag: 'cloud-infra' },
|
|
231
|
+
{ test: /aws|gcp|azure|cloud|kubernetes|k8s|terraform|docker|container|podman/i, level: 'high', tag: 'cloud-infra' },
|
|
231
232
|
{ test: /slack|email|gmail|smtp|sendgrid|twilio|notification/i, level: 'high', tag: 'messaging' },
|
|
232
233
|
{ test: /browser|puppeteer|playwright|fetch|http[-_]?request|web[-_]?search/i, level: 'medium', tag: 'web/network' },
|
|
233
234
|
{ test: /drive|gdrive|dropbox|s3|storage|notion|confluence/i, level: 'medium', tag: 'data-store' },
|
|
@@ -361,7 +362,94 @@ async function mcpJsonRpc(endpoint, method, params, timeoutMs, sessionId) {
|
|
|
361
362
|
clearTimeout(timer);
|
|
362
363
|
}
|
|
363
364
|
}
|
|
364
|
-
|
|
365
|
+
function extractJsonRpcMessages(chunk) {
|
|
366
|
+
const messages = [];
|
|
367
|
+
let buffer = chunk;
|
|
368
|
+
while (true) {
|
|
369
|
+
const headerEnd = buffer.indexOf('\r\n\r\n');
|
|
370
|
+
if (headerEnd < 0)
|
|
371
|
+
break;
|
|
372
|
+
const header = buffer.slice(0, headerEnd);
|
|
373
|
+
const match = header.match(/Content-Length:\s*(\d+)/i);
|
|
374
|
+
if (!match)
|
|
375
|
+
break;
|
|
376
|
+
const length = Number(match[1]);
|
|
377
|
+
const bodyStart = headerEnd + 4;
|
|
378
|
+
const bodyEnd = bodyStart + length;
|
|
379
|
+
if (buffer.length < bodyEnd)
|
|
380
|
+
break;
|
|
381
|
+
try {
|
|
382
|
+
messages.push(JSON.parse(buffer.slice(bodyStart, bodyEnd)));
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
// Ignore malformed frames from noisy local servers.
|
|
386
|
+
}
|
|
387
|
+
buffer = buffer.slice(bodyEnd);
|
|
388
|
+
}
|
|
389
|
+
for (const line of chunk.split(/\r?\n/)) {
|
|
390
|
+
const trimmed = line.trim();
|
|
391
|
+
if (!trimmed.startsWith('{'))
|
|
392
|
+
continue;
|
|
393
|
+
try {
|
|
394
|
+
messages.push(JSON.parse(trimmed));
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
// Ignore logs or partial lines.
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return messages;
|
|
401
|
+
}
|
|
402
|
+
function normalizeMcpToolList(tools) {
|
|
403
|
+
if (!Array.isArray(tools))
|
|
404
|
+
return [];
|
|
405
|
+
return tools
|
|
406
|
+
.map((t) => (t && typeof t.name === 'string'
|
|
407
|
+
? { name: t.name, description: typeof t.description === 'string' ? t.description : undefined }
|
|
408
|
+
: null))
|
|
409
|
+
.filter(Boolean);
|
|
410
|
+
}
|
|
411
|
+
async function probeStdioMcpTools(command, args = [], timeoutMs = 8000) {
|
|
412
|
+
return new Promise(resolve => {
|
|
413
|
+
const child = (0, child_process_1.spawn)(command, args, {
|
|
414
|
+
cwd: process.cwd(),
|
|
415
|
+
env: process.env,
|
|
416
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
417
|
+
windowsHide: true,
|
|
418
|
+
});
|
|
419
|
+
let done = false;
|
|
420
|
+
const finish = (tools = []) => {
|
|
421
|
+
if (done)
|
|
422
|
+
return;
|
|
423
|
+
done = true;
|
|
424
|
+
clearTimeout(timer);
|
|
425
|
+
child.kill();
|
|
426
|
+
resolve(tools);
|
|
427
|
+
};
|
|
428
|
+
const timer = setTimeout(() => finish([]), timeoutMs);
|
|
429
|
+
child.on('error', () => finish([]));
|
|
430
|
+
child.on('exit', () => finish([]));
|
|
431
|
+
child.stdout.setEncoding('utf8');
|
|
432
|
+
child.stdout.on('data', chunk => {
|
|
433
|
+
for (const message of extractJsonRpcMessages(String(chunk))) {
|
|
434
|
+
if (message?.id !== 2)
|
|
435
|
+
continue;
|
|
436
|
+
const tools = normalizeMcpToolList(message?.result?.tools);
|
|
437
|
+
finish(tools);
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
const send = (id, method, params = {}) => {
|
|
441
|
+
child.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n');
|
|
442
|
+
};
|
|
443
|
+
send(1, 'initialize', {
|
|
444
|
+
protocolVersion: '2024-11-05',
|
|
445
|
+
capabilities: {},
|
|
446
|
+
clientInfo: { name: 'fullcourtdefense-discover', version: '1.0.0' },
|
|
447
|
+
});
|
|
448
|
+
child.stdin.write(JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized', params: {} }) + '\n');
|
|
449
|
+
send(2, 'tools/list');
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
/** Best-effort live tools/list for HTTP/SSE MCP servers. */
|
|
365
453
|
async function probeHttpMcpTools(url, timeoutMs = 8000) {
|
|
366
454
|
const normalized = url.replace(/\/$/, '');
|
|
367
455
|
const candidates = [...new Set([
|
|
@@ -384,9 +472,7 @@ async function probeHttpMcpTools(url, timeoutMs = 8000) {
|
|
|
384
472
|
const listed = await mcpJsonRpc(endpoint, 'tools/list', {}, timeoutMs, sid);
|
|
385
473
|
const tools = listed.json?.result?.tools;
|
|
386
474
|
if (Array.isArray(tools) && tools.length > 0) {
|
|
387
|
-
return tools
|
|
388
|
-
.map((t) => (t && typeof t.name === 'string' ? { name: t.name, description: typeof t.description === 'string' ? t.description : undefined } : null))
|
|
389
|
-
.filter(Boolean);
|
|
475
|
+
return normalizeMcpToolList(tools);
|
|
390
476
|
}
|
|
391
477
|
}
|
|
392
478
|
catch {
|
|
@@ -398,8 +484,23 @@ async function probeHttpMcpTools(url, timeoutMs = 8000) {
|
|
|
398
484
|
async function enrichWithDeepProbe(servers, silent) {
|
|
399
485
|
for (const server of servers) {
|
|
400
486
|
if (server.transport !== 'http' && server.transport !== 'sse') {
|
|
401
|
-
if (
|
|
402
|
-
|
|
487
|
+
if (server.transport === 'stdio' && server.command) {
|
|
488
|
+
const downstream = (0, discoverProxy_1.isFcdGatewayServer)(server) ? (0, discoverProxy_1.extractGatewayDownstream)(server) : null;
|
|
489
|
+
const command = downstream?.command || server.command;
|
|
490
|
+
const args = downstream?.args || server.args || [];
|
|
491
|
+
const liveTools = await probeStdioMcpTools(command, args);
|
|
492
|
+
if (liveTools.length > 0) {
|
|
493
|
+
const byName = new Map(server.tools.map(t => [t.name, t]));
|
|
494
|
+
for (const tool of liveTools)
|
|
495
|
+
byName.set(tool.name, tool);
|
|
496
|
+
server.tools = [...byName.values()];
|
|
497
|
+
server.probeMode = 'deep';
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
server.warnings.push('Deep probe could not list tools from stdio server (server may require auth or failed to start).');
|
|
501
|
+
}
|
|
502
|
+
else if (!silent && server.transport === 'stdio') {
|
|
503
|
+
server.warnings.push('Deep probe skipped for stdio server without a command.');
|
|
403
504
|
}
|
|
404
505
|
continue;
|
|
405
506
|
}
|
|
@@ -678,7 +779,7 @@ async function discoverCommand(args, config) {
|
|
|
678
779
|
clientCoverage = applyProxyClassification(found, scanned, cwd);
|
|
679
780
|
if (deep && found.length > 0) {
|
|
680
781
|
if (!silent)
|
|
681
|
-
console.log(`${COLOR.gray}Running deep probe (HTTP/SSE tools/list)…${COLOR.reset}`);
|
|
782
|
+
console.log(`${COLOR.gray}Running deep probe (HTTP/SSE/stdio tools/list)…${COLOR.reset}`);
|
|
682
783
|
await enrichWithDeepProbe(found, silent);
|
|
683
784
|
}
|
|
684
785
|
}
|
|
@@ -39,6 +39,51 @@ const KNOWN = [
|
|
|
39
39
|
recommendGateway: true,
|
|
40
40
|
},
|
|
41
41
|
},
|
|
42
|
+
{
|
|
43
|
+
match: /docker|container|podman/i,
|
|
44
|
+
info: {
|
|
45
|
+
title: 'Container runtime',
|
|
46
|
+
description: 'Controls local containers or registry access from the AI client.',
|
|
47
|
+
category: 'other',
|
|
48
|
+
recommendGateway: true,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
match: /kubernetes|k8s/i,
|
|
53
|
+
info: {
|
|
54
|
+
title: 'Kubernetes',
|
|
55
|
+
description: 'Controls Kubernetes resources from the AI client.',
|
|
56
|
+
category: 'other',
|
|
57
|
+
recommendGateway: true,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
match: /aws|gcp|azure|terraform|cloud/i,
|
|
62
|
+
info: {
|
|
63
|
+
title: 'Cloud infrastructure',
|
|
64
|
+
description: 'Controls cloud infrastructure or deployment resources.',
|
|
65
|
+
category: 'other',
|
|
66
|
+
recommendGateway: true,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
match: /slack|gmail|email|sendgrid|twilio/i,
|
|
71
|
+
info: {
|
|
72
|
+
title: 'Messaging',
|
|
73
|
+
description: 'Can send messages or notifications through external services.',
|
|
74
|
+
category: 'other',
|
|
75
|
+
recommendGateway: true,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
match: /memory/i,
|
|
80
|
+
info: {
|
|
81
|
+
title: 'Memory',
|
|
82
|
+
description: 'Stores local agent memory; review content, but this is usually lower risk than shell, database, or cloud tools.',
|
|
83
|
+
category: 'other',
|
|
84
|
+
recommendGateway: false,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
42
87
|
{
|
|
43
88
|
match: /agentguard-gateway|fullcourtdefense-gateway|fcd-gateway/i,
|
|
44
89
|
info: {
|
package/dist/version.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fullcourtdefense-cli",
|
|
3
|
-
"version": "1.14.
|
|
3
|
+
"version": "1.14.9",
|
|
4
4
|
"description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"test:secret-posture": "npm run build && node scripts/test-secret-posture-fixtures.js",
|
|
24
24
|
"test:agent-file-posture": "npm run build && node scripts/test-agent-file-posture.js",
|
|
25
25
|
"test:realworld-posture": "npm run build && node scripts/test-realworld-posture-fixtures.js",
|
|
26
|
+
"test:discover-stdio-mcp": "npm run build && node scripts/test-discover-stdio-mcp-tools.js",
|
|
26
27
|
"test:posture-blast": "npm run build && node scripts/test-posture-blast-radius.js",
|
|
27
28
|
"test:ping-e2e": "npm run build && node scripts/test-ping-e2e.js",
|
|
28
29
|
"prepublishOnly": "npm run build"
|