fullcourtdefense-cli 1.2.0 → 1.3.1
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.d.ts +8 -0
- package/dist/commands/discover.js +130 -127
- package/dist/commands/discoverPaths.d.ts +18 -0
- package/dist/commands/discoverPaths.js +194 -0
- package/dist/commands/knownMcpServers.d.ts +8 -0
- package/dist/commands/knownMcpServers.js +68 -0
- package/dist/commands/mcpGateway.d.ts +2 -0
- package/dist/commands/mcpGateway.js +20 -81
- package/dist/index.js +3 -1
- package/package.json +1 -1
|
@@ -23,3 +23,11 @@ export interface DesktopDiscoveryHost {
|
|
|
23
23
|
probeMode: 'config' | 'deep';
|
|
24
24
|
}
|
|
25
25
|
export declare function discoverCommand(args: DiscoverArgs, config: BotGuardConfig): Promise<void>;
|
|
26
|
+
/** Upload-only discover — used after install-mcp-gateway --upload. */
|
|
27
|
+
export declare function runDiscoverUpload(args: {
|
|
28
|
+
apiKey?: string;
|
|
29
|
+
apiUrl?: string;
|
|
30
|
+
userEmail?: string;
|
|
31
|
+
silent?: boolean;
|
|
32
|
+
connectorName?: string;
|
|
33
|
+
}, config: BotGuardConfig): Promise<void>;
|
|
@@ -34,12 +34,14 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.discoverCommand = discoverCommand;
|
|
37
|
+
exports.runDiscoverUpload = runDiscoverUpload;
|
|
37
38
|
const crypto = __importStar(require("crypto"));
|
|
38
39
|
const fs = __importStar(require("fs"));
|
|
39
40
|
const os = __importStar(require("os"));
|
|
40
|
-
const path = __importStar(require("path"));
|
|
41
41
|
const discoverProxy_1 = require("./discoverProxy");
|
|
42
42
|
const discoverSchedule_1 = require("./discoverSchedule");
|
|
43
|
+
const discoverPaths_1 = require("./discoverPaths");
|
|
44
|
+
const knownMcpServers_1 = require("./knownMcpServers");
|
|
43
45
|
const DEFAULT_API_URL = 'https://api.fullcourtdefense.ai';
|
|
44
46
|
const SECRET_VALUE = /sk-[A-Za-z0-9_-]{12,}|ghp_[A-Za-z0-9_]{20,}|xox[baprs]-[A-Za-z0-9-]{20,}|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{20,}/;
|
|
45
47
|
const SECRET_KEY = /key|token|secret|password|credential|api[_-]?key/i;
|
|
@@ -58,68 +60,9 @@ function buildHostMetadata(userEmail, probeMode = 'config') {
|
|
|
58
60
|
probeMode,
|
|
59
61
|
};
|
|
60
62
|
}
|
|
61
|
-
/** Where MCP client configs live
|
|
63
|
+
/** Where MCP client configs live — see discoverPaths.ts for the canonical list. */
|
|
62
64
|
function candidateConfigPaths(cwd, extra) {
|
|
63
|
-
|
|
64
|
-
const appData = process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
|
|
65
|
-
const localAppData = process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
|
|
66
|
-
const xdg = process.env.XDG_CONFIG_HOME || path.join(home, '.config');
|
|
67
|
-
const list = [
|
|
68
|
-
// Cursor
|
|
69
|
-
{ path: path.join(home, '.cursor', 'mcp.json'), source: 'Cursor (global)' },
|
|
70
|
-
{ path: path.join(cwd, '.cursor', 'mcp.json'), source: 'Cursor (project)' },
|
|
71
|
-
// Cursor managed (enterprise)
|
|
72
|
-
{ path: path.join(appData, 'Cursor', 'managed-mcp.json'), source: 'Cursor (managed, win)' },
|
|
73
|
-
{ path: path.join(home, 'Library', 'Application Support', 'Cursor', 'managed-mcp.json'), source: 'Cursor (managed, mac)' },
|
|
74
|
-
{ path: '/etc/cursor/managed-mcp.json', source: 'Cursor (managed, linux)' },
|
|
75
|
-
// Claude Desktop
|
|
76
|
-
{ path: path.join(appData, 'Claude', 'claude_desktop_config.json'), source: 'Claude Desktop (win)' },
|
|
77
|
-
{ path: path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'), source: 'Claude Desktop (mac)' },
|
|
78
|
-
{ path: path.join(xdg, 'Claude', 'claude_desktop_config.json'), source: 'Claude Desktop (linux)' },
|
|
79
|
-
// Claude Code
|
|
80
|
-
{ path: path.join(home, '.claude.json'), source: 'Claude Code' },
|
|
81
|
-
{ path: path.join(home, '.claude', 'settings.json'), source: 'Claude Code (global)' },
|
|
82
|
-
{ path: path.join(cwd, '.claude', 'settings.json'), source: 'Claude Code (project)' },
|
|
83
|
-
{ path: path.join(cwd, '.mcp.json'), source: 'Claude Code (.mcp.json project)' },
|
|
84
|
-
// Gemini CLI
|
|
85
|
-
{ path: path.join(home, '.gemini', 'settings.json'), source: 'Gemini CLI' },
|
|
86
|
-
{ path: path.join(xdg, 'gemini', 'settings.json'), source: 'Gemini CLI (xdg)' },
|
|
87
|
-
// OpenAI Codex
|
|
88
|
-
{ path: path.join(home, '.codex', 'mcp.json'), source: 'Codex' },
|
|
89
|
-
{ path: path.join(xdg, 'codex', 'mcp.json'), source: 'Codex (xdg)' },
|
|
90
|
-
// VS Code (project + user settings)
|
|
91
|
-
{ path: path.join(cwd, '.vscode', 'mcp.json'), source: 'VS Code (project)' },
|
|
92
|
-
{ path: path.join(appData, 'Code', 'User', 'settings.json'), source: 'VS Code (user, win)' },
|
|
93
|
-
{ path: path.join(home, 'Library', 'Application Support', 'Code', 'User', 'settings.json'), source: 'VS Code (user, mac)' },
|
|
94
|
-
{ path: path.join(xdg, 'Code', 'User', 'settings.json'), source: 'VS Code (user, linux)' },
|
|
95
|
-
// Windsurf
|
|
96
|
-
{ path: path.join(home, '.codeium', 'windsurf', 'mcp_config.json'), source: 'Windsurf' },
|
|
97
|
-
// Kiro / generic
|
|
98
|
-
{ path: path.join(home, '.kiro', 'mcp.json'), source: 'Kiro' },
|
|
99
|
-
// Generic / repo-root
|
|
100
|
-
{ path: path.join(cwd, '.mcp.json'), source: 'Repo (.mcp.json)' },
|
|
101
|
-
{ path: path.join(cwd, 'mcp.json'), source: 'Repo (mcp.json)' },
|
|
102
|
-
];
|
|
103
|
-
if (process.platform === 'win32') {
|
|
104
|
-
list.push({ path: path.join(process.env.ProgramData || 'C:\\ProgramData', 'Cursor', 'managed-mcp.json'), source: 'Cursor (managed, program data)' });
|
|
105
|
-
const packagesDir = path.join(localAppData, 'Packages');
|
|
106
|
-
try {
|
|
107
|
-
for (const packageName of fs.readdirSync(packagesDir)) {
|
|
108
|
-
if (!/^Claude_/i.test(packageName))
|
|
109
|
-
continue;
|
|
110
|
-
list.push({
|
|
111
|
-
path: path.join(packagesDir, packageName, 'LocalCache', 'Roaming', 'Claude', 'claude_desktop_config.json'),
|
|
112
|
-
source: 'Claude Desktop (win MSIX)',
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
// Missing Packages directory is normal on non-Store installs.
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
if (extra)
|
|
121
|
-
list.push({ path: path.resolve(extra), source: 'Custom path' });
|
|
122
|
-
return list;
|
|
65
|
+
return (0, discoverPaths_1.discoverScanTargets)(cwd, extra);
|
|
123
66
|
}
|
|
124
67
|
/** MCP servers can be declared under several keys across clients. */
|
|
125
68
|
function extractServerMap(parsed) {
|
|
@@ -160,6 +103,55 @@ function scoreRisk(s) {
|
|
|
160
103
|
}
|
|
161
104
|
return { level, tags };
|
|
162
105
|
}
|
|
106
|
+
function pushDiscoveredServer(out, name, cfgRaw, source, configPath) {
|
|
107
|
+
const cfg = (cfgRaw || {});
|
|
108
|
+
const command = typeof cfg.command === 'string' ? cfg.command : undefined;
|
|
109
|
+
const args = Array.isArray(cfg.args) ? cfg.args.map(String) : undefined;
|
|
110
|
+
const url = typeof cfg.url === 'string' ? cfg.url : (typeof cfg.serverUrl === 'string' ? cfg.serverUrl : undefined);
|
|
111
|
+
const env = (cfg.env && typeof cfg.env === 'object') ? cfg.env : {};
|
|
112
|
+
const envKeys = Object.keys(env);
|
|
113
|
+
const transport = url
|
|
114
|
+
? (/\/sse(\b|$)/.test(url) ? 'sse' : 'http')
|
|
115
|
+
: command ? 'stdio' : 'unknown';
|
|
116
|
+
const tools = Array.isArray(cfg.tools)
|
|
117
|
+
? cfg.tools
|
|
118
|
+
.map((t) => {
|
|
119
|
+
if (typeof t === 'string')
|
|
120
|
+
return { name: t };
|
|
121
|
+
if (t && typeof t.name === 'string')
|
|
122
|
+
return { name: t.name, description: typeof t.description === 'string' ? t.description : undefined };
|
|
123
|
+
return null;
|
|
124
|
+
})
|
|
125
|
+
.filter(Boolean)
|
|
126
|
+
: [];
|
|
127
|
+
const { level, tags } = scoreRisk({ serverName: name, command, args, url });
|
|
128
|
+
const warnings = [];
|
|
129
|
+
const blob = `${command || ''} ${(args || []).join(' ')} ${JSON.stringify(env)}`;
|
|
130
|
+
if (SECRET_VALUE.test(blob))
|
|
131
|
+
warnings.push('Hardcoded secret/API token detected in config (rotate + use env var).');
|
|
132
|
+
for (const [k, v] of Object.entries(env)) {
|
|
133
|
+
const val = String(v ?? '');
|
|
134
|
+
if (SECRET_KEY.test(k) && val && !/^\$\{?[A-Za-z0-9_]+\}?$/.test(val)) {
|
|
135
|
+
warnings.push(`Env "${k}" appears to contain a literal secret value.`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
out.push({
|
|
139
|
+
serverName: name,
|
|
140
|
+
command,
|
|
141
|
+
args,
|
|
142
|
+
url,
|
|
143
|
+
transport,
|
|
144
|
+
envKeys,
|
|
145
|
+
tools,
|
|
146
|
+
source,
|
|
147
|
+
configPath,
|
|
148
|
+
riskLevel: level,
|
|
149
|
+
riskTags: tags,
|
|
150
|
+
warnings,
|
|
151
|
+
probeMode: 'config',
|
|
152
|
+
proxyStatus: 'unknown',
|
|
153
|
+
});
|
|
154
|
+
}
|
|
163
155
|
function parseConfigFile(filePath, source) {
|
|
164
156
|
let raw;
|
|
165
157
|
try {
|
|
@@ -175,56 +167,21 @@ function parseConfigFile(filePath, source) {
|
|
|
175
167
|
catch {
|
|
176
168
|
return [];
|
|
177
169
|
}
|
|
178
|
-
const servers = extractServerMap(parsed);
|
|
179
170
|
const out = [];
|
|
180
|
-
for (const [name, cfgRaw] of Object.entries(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
.map((t) => {
|
|
193
|
-
if (typeof t === 'string')
|
|
194
|
-
return { name: t };
|
|
195
|
-
if (t && typeof t.name === 'string')
|
|
196
|
-
return { name: t.name, description: typeof t.description === 'string' ? t.description : undefined };
|
|
197
|
-
return null;
|
|
198
|
-
})
|
|
199
|
-
.filter(Boolean)
|
|
200
|
-
: [];
|
|
201
|
-
const { level, tags } = scoreRisk({ serverName: name, command, args, url });
|
|
202
|
-
const warnings = [];
|
|
203
|
-
const blob = `${command || ''} ${(args || []).join(' ')} ${JSON.stringify(env)}`;
|
|
204
|
-
if (SECRET_VALUE.test(blob))
|
|
205
|
-
warnings.push('Hardcoded secret/API token detected in config (rotate + use env var).');
|
|
206
|
-
for (const [k, v] of Object.entries(env)) {
|
|
207
|
-
const val = String(v ?? '');
|
|
208
|
-
if (SECRET_KEY.test(k) && val && !/^\$\{?[A-Za-z0-9_]+\}?$/.test(val)) {
|
|
209
|
-
warnings.push(`Env "${k}" appears to contain a literal secret value.`);
|
|
171
|
+
for (const [name, cfgRaw] of Object.entries(extractServerMap(parsed))) {
|
|
172
|
+
pushDiscoveredServer(out, name, cfgRaw, source, filePath);
|
|
173
|
+
}
|
|
174
|
+
// Claude Code stores MCP per-project inside ~/.claude.json
|
|
175
|
+
if (/claude code/i.test(source) && parsed?.projects && typeof parsed.projects === 'object') {
|
|
176
|
+
for (const [projectPath, projectCfg] of Object.entries(parsed.projects)) {
|
|
177
|
+
const projectServers = projectCfg?.mcpServers;
|
|
178
|
+
if (!projectServers || typeof projectServers !== 'object')
|
|
179
|
+
continue;
|
|
180
|
+
const projectLabel = projectPath.split(/[/\\]/).filter(Boolean).slice(-1)[0] || projectPath;
|
|
181
|
+
for (const [name, cfgRaw] of Object.entries(projectServers)) {
|
|
182
|
+
pushDiscoveredServer(out, name, cfgRaw, `${source} · ${projectLabel}`, filePath);
|
|
210
183
|
}
|
|
211
184
|
}
|
|
212
|
-
out.push({
|
|
213
|
-
serverName: name,
|
|
214
|
-
command,
|
|
215
|
-
args,
|
|
216
|
-
url,
|
|
217
|
-
transport,
|
|
218
|
-
envKeys,
|
|
219
|
-
tools,
|
|
220
|
-
source,
|
|
221
|
-
configPath: filePath,
|
|
222
|
-
riskLevel: level,
|
|
223
|
-
riskTags: tags,
|
|
224
|
-
warnings,
|
|
225
|
-
probeMode: 'config',
|
|
226
|
-
proxyStatus: 'unknown',
|
|
227
|
-
});
|
|
228
185
|
}
|
|
229
186
|
return out;
|
|
230
187
|
}
|
|
@@ -313,13 +270,14 @@ async function enrichWithDeepProbe(servers, silent) {
|
|
|
313
270
|
server.probeMode = 'deep';
|
|
314
271
|
}
|
|
315
272
|
}
|
|
316
|
-
/**
|
|
273
|
+
/** Dedupe within the same config file + server name only (keep separate entries per client config path). */
|
|
317
274
|
function dedupe(servers) {
|
|
318
|
-
const
|
|
275
|
+
const byKey = new Map();
|
|
319
276
|
for (const s of servers) {
|
|
320
|
-
const
|
|
277
|
+
const key = `${s.configPath.toLowerCase()}::${s.serverName.toLowerCase()}`;
|
|
278
|
+
const existing = byKey.get(key);
|
|
321
279
|
if (!existing) {
|
|
322
|
-
|
|
280
|
+
byKey.set(key, { ...s });
|
|
323
281
|
continue;
|
|
324
282
|
}
|
|
325
283
|
if (!existing.source.includes(s.source))
|
|
@@ -334,7 +292,7 @@ function dedupe(servers) {
|
|
|
334
292
|
if (s.probeMode === 'deep')
|
|
335
293
|
existing.probeMode = 'deep';
|
|
336
294
|
}
|
|
337
|
-
return [...
|
|
295
|
+
return [...byKey.values()];
|
|
338
296
|
}
|
|
339
297
|
const COLOR = {
|
|
340
298
|
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
@@ -353,6 +311,16 @@ function applyProxyClassification(servers, scanned, cwd) {
|
|
|
353
311
|
if (server.proxyStatus === 'direct' && server.riskLevel !== 'low') {
|
|
354
312
|
server.warnings.push('Direct MCP — not proxied through Full Court Defense gateway.');
|
|
355
313
|
}
|
|
314
|
+
const info = (0, knownMcpServers_1.knownMcpServerInfo)(server.serverName, server.command);
|
|
315
|
+
if (info) {
|
|
316
|
+
server.serverKind = info.category;
|
|
317
|
+
server.serverDescription = info.description;
|
|
318
|
+
}
|
|
319
|
+
if (server.proxyStatus === 'direct') {
|
|
320
|
+
const hint = (0, knownMcpServers_1.directServerFixHint)(server.serverName);
|
|
321
|
+
if (hint && !server.warnings.includes(hint))
|
|
322
|
+
server.warnings.push(hint);
|
|
323
|
+
}
|
|
356
324
|
}
|
|
357
325
|
return (0, discoverProxy_1.buildClientCoverage)(scanned, servers, cwd);
|
|
358
326
|
}
|
|
@@ -383,6 +351,8 @@ async function upload(servers, host, clientCoverage, apiUrl, apiKey, connectorNa
|
|
|
383
351
|
probeMode: s.probeMode,
|
|
384
352
|
proxyStatus: s.proxyStatus,
|
|
385
353
|
wrapsServerName: s.wrapsServerName,
|
|
354
|
+
serverKind: s.serverKind,
|
|
355
|
+
serverDescription: s.serverDescription,
|
|
386
356
|
tools: s.tools,
|
|
387
357
|
})),
|
|
388
358
|
};
|
|
@@ -427,10 +397,10 @@ async function discoverCommand(args, config) {
|
|
|
427
397
|
const scanned = [];
|
|
428
398
|
let found = [];
|
|
429
399
|
for (const c of candidates) {
|
|
430
|
-
if (!fs.existsSync(c.path))
|
|
431
|
-
continue;
|
|
432
400
|
scanned.push({ path: c.path, source: c.source });
|
|
433
|
-
|
|
401
|
+
if (fs.existsSync(c.path)) {
|
|
402
|
+
found.push(...parseConfigFile(c.path, c.source));
|
|
403
|
+
}
|
|
434
404
|
}
|
|
435
405
|
found = dedupe(found);
|
|
436
406
|
const clientCoverage = applyProxyClassification(found, scanned, cwd);
|
|
@@ -455,7 +425,7 @@ async function discoverCommand(args, config) {
|
|
|
455
425
|
const apiUrl = args.apiUrl || config.apiUrl || DEFAULT_API_URL;
|
|
456
426
|
if (!apiKey)
|
|
457
427
|
process.exit(1);
|
|
458
|
-
if (
|
|
428
|
+
if (scanned.length === 0)
|
|
459
429
|
return;
|
|
460
430
|
await upload(found, host, clientCoverage, apiUrl, apiKey, args.connectorName);
|
|
461
431
|
}
|
|
@@ -469,16 +439,12 @@ async function discoverCommand(args, config) {
|
|
|
469
439
|
console.log(`${COLOR.gray}Looked in Cursor, Claude Desktop/Code, VS Code, Windsurf, Codex, Gemini CLI, and repo-root configs.${COLOR.reset}`);
|
|
470
440
|
return;
|
|
471
441
|
}
|
|
472
|
-
console.log(`${COLOR.bold}
|
|
473
|
-
for (const s of scanned)
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
if (found.length === 0) {
|
|
477
|
-
console.log(`${COLOR.yellow}Config files exist but declare no MCP servers.${COLOR.reset}`);
|
|
478
|
-
return;
|
|
442
|
+
console.log(`${COLOR.bold}AI clients checked (${scanned.length}):${COLOR.reset}`);
|
|
443
|
+
for (const s of scanned) {
|
|
444
|
+
const exists = fs.existsSync(s.path);
|
|
445
|
+
console.log(` ${COLOR.dim}•${COLOR.reset} ${s.source} → ${s.path}${exists ? '' : ` ${COLOR.yellow}(not created yet)${COLOR.reset}`}`);
|
|
479
446
|
}
|
|
480
|
-
|
|
481
|
-
found.sort((a, b) => order[a.riskLevel] - order[b.riskLevel]);
|
|
447
|
+
console.log('');
|
|
482
448
|
if (clientCoverage.length > 0) {
|
|
483
449
|
console.log(`${COLOR.bold}Client coverage:${COLOR.reset}`);
|
|
484
450
|
for (const row of clientCoverage) {
|
|
@@ -487,15 +453,40 @@ async function discoverCommand(args, config) {
|
|
|
487
453
|
? (row.cursorHooksInstalled ? `${COLOR.green}hooks ✓ (${row.cursorHookEvents.join(', ')})${COLOR.reset}` : `${COLOR.yellow}no hooks${COLOR.reset}`)
|
|
488
454
|
: '';
|
|
489
455
|
console.log(` ${COLOR.dim}•${COLOR.reset} ${row.client} — ${row.mcpServerCount} MCP · ${gateway}${hooks ? ` · ${hooks}` : ''}`);
|
|
456
|
+
if (row.clientKey === 'claude_desktop' && !row.mcpGatewayInstalled && row.mcpServerCount === 0) {
|
|
457
|
+
console.log(` ${COLOR.gray}Install: fullcourtdefense install-claude-desktop-mcp-gateway --shield-id ... --mcp-command npm --mcp-args "run mcp"${COLOR.reset}`);
|
|
458
|
+
}
|
|
490
459
|
}
|
|
491
460
|
console.log('');
|
|
492
461
|
}
|
|
462
|
+
if (found.length === 0) {
|
|
463
|
+
console.log(`${COLOR.yellow}No MCP servers configured in these clients yet.${COLOR.reset}`);
|
|
464
|
+
console.log(`${COLOR.gray}Install gateway everywhere: fullcourtdefense install-mcp-gateway --clients all --shield-id ... --shield-key ... --mcp-command npm --mcp-args "run mcp"${COLOR.reset}`);
|
|
465
|
+
if (args.upload === 'true') {
|
|
466
|
+
const apiKey = args.apiKey || config.apiKey || process.env.FULLCOURTDEFENSE_API_KEY;
|
|
467
|
+
const apiUrl = args.apiUrl || config.apiUrl || DEFAULT_API_URL;
|
|
468
|
+
if (!apiKey) {
|
|
469
|
+
console.error(`\n${COLOR.red}--upload requires an API key.${COLOR.reset}`);
|
|
470
|
+
process.exit(1);
|
|
471
|
+
}
|
|
472
|
+
console.log('');
|
|
473
|
+
await upload(found, host, clientCoverage, apiUrl, apiKey, args.connectorName);
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
console.log(`${COLOR.gray}Run with --upload to push client coverage into AI Inventory.${COLOR.reset}`);
|
|
477
|
+
}
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const order = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
481
|
+
found.sort((a, b) => order[a.riskLevel] - order[b.riskLevel]);
|
|
493
482
|
console.log(`${COLOR.bold}${found.length} MCP server(s) discovered:${COLOR.reset}\n`);
|
|
494
483
|
for (const s of found) {
|
|
495
484
|
const col = riskColor(s.riskLevel);
|
|
496
485
|
const target = s.command ? `${s.command} ${(s.args || []).join(' ')}`.trim() : (s.url || 'unknown');
|
|
497
486
|
const proxyCol = s.proxyStatus === 'direct' ? COLOR.red : s.proxyStatus === 'proxied' || s.proxyStatus === 'fcd_gateway' ? COLOR.green : COLOR.yellow;
|
|
498
487
|
console.log(` ${col}${COLOR.bold}[${s.riskLevel.toUpperCase()}]${COLOR.reset} ${COLOR.bold}${s.serverName}${COLOR.reset} ${COLOR.gray}(${s.transport}${s.probeMode === 'deep' ? ', deep' : ''})${COLOR.reset} ${proxyCol}${proxyStatusLabel(s.proxyStatus)}${COLOR.reset}`);
|
|
488
|
+
if (s.serverDescription)
|
|
489
|
+
console.log(` ${COLOR.gray}about:${COLOR.reset} ${s.serverDescription}`);
|
|
499
490
|
console.log(` ${COLOR.gray}target:${COLOR.reset} ${target}`);
|
|
500
491
|
if (s.riskTags.length)
|
|
501
492
|
console.log(` ${COLOR.gray}tags:${COLOR.reset} ${s.riskTags.join(', ')}`);
|
|
@@ -527,3 +518,15 @@ async function discoverCommand(args, config) {
|
|
|
527
518
|
console.log(`${COLOR.gray}Add --schedule daily for automatic daily upload (MDM/fleet).${COLOR.reset}`);
|
|
528
519
|
}
|
|
529
520
|
}
|
|
521
|
+
/** Upload-only discover — used after install-mcp-gateway --upload. */
|
|
522
|
+
async function runDiscoverUpload(args, config) {
|
|
523
|
+
await discoverCommand({
|
|
524
|
+
type: 'mcp',
|
|
525
|
+
upload: 'true',
|
|
526
|
+
silent: args.silent ? 'true' : undefined,
|
|
527
|
+
apiKey: args.apiKey,
|
|
528
|
+
apiUrl: args.apiUrl,
|
|
529
|
+
userEmail: args.userEmail,
|
|
530
|
+
connectorName: args.connectorName,
|
|
531
|
+
}, config);
|
|
532
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ConfigPathCandidate {
|
|
2
|
+
path: string;
|
|
3
|
+
source: string;
|
|
4
|
+
clientKey: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ClaudeDesktopConfigCandidate {
|
|
7
|
+
file: string;
|
|
8
|
+
source: string;
|
|
9
|
+
exists: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function claudeDesktopMsixCandidates(): ClaudeDesktopConfigCandidate[];
|
|
12
|
+
export declare function claudeDesktopConfigCandidates(): ClaudeDesktopConfigCandidate[];
|
|
13
|
+
export declare function claudeDesktopLikelyInstalled(): boolean;
|
|
14
|
+
export declare function claudeDesktopInstallTargets(configPath?: string): ClaudeDesktopConfigCandidate[];
|
|
15
|
+
/** All MCP client config locations discover / install should check. */
|
|
16
|
+
export declare function candidateConfigPaths(cwd: string, extra?: string): ConfigPathCandidate[];
|
|
17
|
+
/** Include config paths for apps that exist on disk even when the MCP file is not created yet. */
|
|
18
|
+
export declare function discoverScanTargets(cwd: string, extra?: string): ConfigPathCandidate[];
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.claudeDesktopMsixCandidates = claudeDesktopMsixCandidates;
|
|
37
|
+
exports.claudeDesktopConfigCandidates = claudeDesktopConfigCandidates;
|
|
38
|
+
exports.claudeDesktopLikelyInstalled = claudeDesktopLikelyInstalled;
|
|
39
|
+
exports.claudeDesktopInstallTargets = claudeDesktopInstallTargets;
|
|
40
|
+
exports.candidateConfigPaths = candidateConfigPaths;
|
|
41
|
+
exports.discoverScanTargets = discoverScanTargets;
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const os = __importStar(require("os"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
function claudeDesktopDocumentedPath() {
|
|
46
|
+
const home = os.homedir();
|
|
47
|
+
if (process.platform === 'win32') {
|
|
48
|
+
const appData = process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
|
|
49
|
+
return path.join(appData, 'Claude', 'claude_desktop_config.json');
|
|
50
|
+
}
|
|
51
|
+
if (process.platform === 'darwin') {
|
|
52
|
+
return path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
53
|
+
}
|
|
54
|
+
const xdg = process.env.XDG_CONFIG_HOME || path.join(home, '.config');
|
|
55
|
+
return path.join(xdg, 'Claude', 'claude_desktop_config.json');
|
|
56
|
+
}
|
|
57
|
+
function claudeDesktopMsixCandidates() {
|
|
58
|
+
if (process.platform !== 'win32')
|
|
59
|
+
return [];
|
|
60
|
+
const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
61
|
+
const packagesDir = path.join(localAppData, 'Packages');
|
|
62
|
+
try {
|
|
63
|
+
return fs.readdirSync(packagesDir)
|
|
64
|
+
.filter(name => /^Claude_/i.test(name))
|
|
65
|
+
.map(name => ({
|
|
66
|
+
file: path.join(packagesDir, name, 'LocalCache', 'Roaming', 'Claude', 'claude_desktop_config.json'),
|
|
67
|
+
source: 'Claude Desktop (win MSIX)',
|
|
68
|
+
exists: fs.existsSync(path.join(packagesDir, name, 'LocalCache', 'Roaming', 'Claude', 'claude_desktop_config.json')),
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function claudeDesktopConfigCandidates() {
|
|
76
|
+
const documented = claudeDesktopDocumentedPath();
|
|
77
|
+
const normal = {
|
|
78
|
+
file: documented,
|
|
79
|
+
source: process.platform === 'win32'
|
|
80
|
+
? 'Claude Desktop (win)'
|
|
81
|
+
: process.platform === 'darwin'
|
|
82
|
+
? 'Claude Desktop (mac)'
|
|
83
|
+
: 'Claude Desktop (linux)',
|
|
84
|
+
exists: fs.existsSync(documented),
|
|
85
|
+
};
|
|
86
|
+
const seen = new Set();
|
|
87
|
+
return [...claudeDesktopMsixCandidates(), normal].filter(candidate => {
|
|
88
|
+
const key = path.resolve(candidate.file).toLowerCase();
|
|
89
|
+
if (seen.has(key))
|
|
90
|
+
return false;
|
|
91
|
+
seen.add(key);
|
|
92
|
+
return true;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function claudeDesktopLikelyInstalled() {
|
|
96
|
+
if (claudeDesktopConfigCandidates().some(candidate => candidate.exists))
|
|
97
|
+
return true;
|
|
98
|
+
if (claudeDesktopMsixCandidates().length > 0)
|
|
99
|
+
return true;
|
|
100
|
+
if (process.platform === 'darwin' && fs.existsSync('/Applications/Claude.app'))
|
|
101
|
+
return true;
|
|
102
|
+
if (process.platform === 'win32') {
|
|
103
|
+
const pf = process.env.ProgramFiles || 'C:\\Program Files';
|
|
104
|
+
if (fs.existsSync(path.join(pf, 'Claude', 'Claude.exe')))
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
return fs.existsSync(path.dirname(claudeDesktopDocumentedPath()));
|
|
108
|
+
}
|
|
109
|
+
function claudeDesktopInstallTargets(configPath) {
|
|
110
|
+
if (configPath) {
|
|
111
|
+
const file = path.resolve(configPath);
|
|
112
|
+
return [{ file, source: 'Claude Desktop (custom)', exists: fs.existsSync(file) }];
|
|
113
|
+
}
|
|
114
|
+
const candidates = claudeDesktopConfigCandidates();
|
|
115
|
+
const existing = candidates.filter(candidate => candidate.exists);
|
|
116
|
+
if (existing.length > 0)
|
|
117
|
+
return existing;
|
|
118
|
+
const msix = candidates.find(candidate => candidate.source.includes('MSIX'));
|
|
119
|
+
if (msix)
|
|
120
|
+
return [msix];
|
|
121
|
+
return [candidates[candidates.length - 1] || {
|
|
122
|
+
file: claudeDesktopDocumentedPath(),
|
|
123
|
+
source: 'Claude Desktop',
|
|
124
|
+
exists: false,
|
|
125
|
+
}];
|
|
126
|
+
}
|
|
127
|
+
/** All MCP client config locations discover / install should check. */
|
|
128
|
+
function candidateConfigPaths(cwd, extra) {
|
|
129
|
+
const home = os.homedir();
|
|
130
|
+
const appData = process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
|
|
131
|
+
const xdg = process.env.XDG_CONFIG_HOME || path.join(home, '.config');
|
|
132
|
+
const list = [
|
|
133
|
+
{ path: path.join(home, '.cursor', 'mcp.json'), source: 'Cursor (global)', clientKey: 'cursor' },
|
|
134
|
+
{ path: path.join(cwd, '.cursor', 'mcp.json'), source: 'Cursor (project)', clientKey: 'cursor' },
|
|
135
|
+
{ path: path.join(appData, 'Cursor', 'managed-mcp.json'), source: 'Cursor (managed, win)', clientKey: 'cursor' },
|
|
136
|
+
{ path: path.join(home, 'Library', 'Application Support', 'Cursor', 'managed-mcp.json'), source: 'Cursor (managed, mac)', clientKey: 'cursor' },
|
|
137
|
+
{ path: '/etc/cursor/managed-mcp.json', source: 'Cursor (managed, linux)', clientKey: 'cursor' },
|
|
138
|
+
{ path: path.join(home, '.claude.json'), source: 'Claude Code', clientKey: 'claude_code' },
|
|
139
|
+
{ path: path.join(home, '.claude', 'settings.json'), source: 'Claude Code (global)', clientKey: 'claude_code' },
|
|
140
|
+
{ path: path.join(cwd, '.claude', 'settings.json'), source: 'Claude Code (project)', clientKey: 'claude_code' },
|
|
141
|
+
{ path: path.join(cwd, '.mcp.json'), source: 'Claude Code (.mcp.json project)', clientKey: 'claude_code' },
|
|
142
|
+
{ path: path.join(home, '.gemini', 'settings.json'), source: 'Gemini CLI', clientKey: 'gemini_cli' },
|
|
143
|
+
{ path: path.join(xdg, 'gemini', 'settings.json'), source: 'Gemini CLI (xdg)', clientKey: 'gemini_cli' },
|
|
144
|
+
{ path: path.join(home, '.codex', 'mcp.json'), source: 'Codex', clientKey: 'codex' },
|
|
145
|
+
{ path: path.join(xdg, 'codex', 'mcp.json'), source: 'Codex (xdg)', clientKey: 'codex' },
|
|
146
|
+
{ path: path.join(cwd, '.vscode', 'mcp.json'), source: 'VS Code (project)', clientKey: 'vscode' },
|
|
147
|
+
{ path: path.join(appData, 'Code', 'User', 'settings.json'), source: 'VS Code (user, win)', clientKey: 'vscode' },
|
|
148
|
+
{ path: path.join(home, 'Library', 'Application Support', 'Code', 'User', 'settings.json'), source: 'VS Code (user, mac)', clientKey: 'vscode' },
|
|
149
|
+
{ path: path.join(xdg, 'Code', 'User', 'settings.json'), source: 'VS Code (user, linux)', clientKey: 'vscode' },
|
|
150
|
+
{ path: path.join(home, '.codeium', 'windsurf', 'mcp_config.json'), source: 'Windsurf', clientKey: 'windsurf' },
|
|
151
|
+
{ path: path.join(home, '.kiro', 'mcp.json'), source: 'Kiro', clientKey: 'kiro' },
|
|
152
|
+
{ path: path.join(cwd, '.mcp.json'), source: 'Repo (.mcp.json)', clientKey: 'other' },
|
|
153
|
+
{ path: path.join(cwd, 'mcp.json'), source: 'Repo (mcp.json)', clientKey: 'other' },
|
|
154
|
+
];
|
|
155
|
+
for (const candidate of claudeDesktopConfigCandidates()) {
|
|
156
|
+
list.push({ path: candidate.file, source: candidate.source, clientKey: 'claude_desktop' });
|
|
157
|
+
}
|
|
158
|
+
if (process.platform === 'win32') {
|
|
159
|
+
list.push({
|
|
160
|
+
path: path.join(process.env.ProgramData || 'C:\\ProgramData', 'Cursor', 'managed-mcp.json'),
|
|
161
|
+
source: 'Cursor (managed, program data)',
|
|
162
|
+
clientKey: 'cursor',
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
if (extra) {
|
|
166
|
+
list.push({ path: path.resolve(extra), source: 'Custom path', clientKey: 'other' });
|
|
167
|
+
}
|
|
168
|
+
const seen = new Set();
|
|
169
|
+
return list.filter(item => {
|
|
170
|
+
const key = path.resolve(item.path).toLowerCase();
|
|
171
|
+
if (seen.has(key))
|
|
172
|
+
return false;
|
|
173
|
+
seen.add(key);
|
|
174
|
+
return true;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/** Include config paths for apps that exist on disk even when the MCP file is not created yet. */
|
|
178
|
+
function discoverScanTargets(cwd, extra) {
|
|
179
|
+
const candidates = candidateConfigPaths(cwd, extra);
|
|
180
|
+
const existing = candidates.filter(item => fs.existsSync(item.path));
|
|
181
|
+
const implicit = [];
|
|
182
|
+
if (claudeDesktopLikelyInstalled()) {
|
|
183
|
+
for (const target of claudeDesktopInstallTargets()) {
|
|
184
|
+
if (!existing.some(item => path.resolve(item.path).toLowerCase() === path.resolve(target.file).toLowerCase())) {
|
|
185
|
+
implicit.push({ path: target.file, source: target.source, clientKey: 'claude_desktop' });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const cursorGlobal = path.join(os.homedir(), '.cursor', 'mcp.json');
|
|
190
|
+
if (fs.existsSync(path.join(os.homedir(), '.cursor')) && !existing.some(item => path.resolve(item.path).toLowerCase() === cursorGlobal.toLowerCase())) {
|
|
191
|
+
implicit.push({ path: cursorGlobal, source: 'Cursor (global)', clientKey: 'cursor' });
|
|
192
|
+
}
|
|
193
|
+
return [...existing, ...implicit];
|
|
194
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface KnownMcpServerInfo {
|
|
2
|
+
title: string;
|
|
3
|
+
description: string;
|
|
4
|
+
category: 'browser' | 'filesystem' | 'database' | 'gateway' | 'other';
|
|
5
|
+
recommendGateway: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function knownMcpServerInfo(serverName: string, command?: string): KnownMcpServerInfo | undefined;
|
|
8
|
+
export declare function directServerFixHint(serverName: string): string | undefined;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.knownMcpServerInfo = knownMcpServerInfo;
|
|
4
|
+
exports.directServerFixHint = directServerFixHint;
|
|
5
|
+
const KNOWN = [
|
|
6
|
+
{
|
|
7
|
+
match: /playwright/i,
|
|
8
|
+
info: {
|
|
9
|
+
title: 'Playwright',
|
|
10
|
+
description: 'Browser automation — AI can open sites, click, and act as the logged-in user.',
|
|
11
|
+
category: 'browser',
|
|
12
|
+
recommendGateway: true,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
match: /puppeteer|selenium|browser/i,
|
|
17
|
+
info: {
|
|
18
|
+
title: 'Browser automation',
|
|
19
|
+
description: 'Controls a web browser on the developer machine.',
|
|
20
|
+
category: 'browser',
|
|
21
|
+
recommendGateway: true,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
match: /filesystem|server-filesystem/i,
|
|
26
|
+
info: {
|
|
27
|
+
title: 'Filesystem',
|
|
28
|
+
description: 'Reads and writes local files — high exfiltration risk.',
|
|
29
|
+
category: 'filesystem',
|
|
30
|
+
recommendGateway: true,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
match: /postgres|mysql|sqlite|database|supabase/i,
|
|
35
|
+
info: {
|
|
36
|
+
title: 'Database',
|
|
37
|
+
description: 'Queries databases directly from the AI client.',
|
|
38
|
+
category: 'database',
|
|
39
|
+
recommendGateway: true,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
match: /agentguard-gateway|fullcourtdefense-gateway|fcd-gateway/i,
|
|
44
|
+
info: {
|
|
45
|
+
title: 'FCD MCP gateway',
|
|
46
|
+
description: 'Full Court Defense proxy — tool calls checked by Shield and Action Policies.',
|
|
47
|
+
category: 'gateway',
|
|
48
|
+
recommendGateway: false,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
function knownMcpServerInfo(serverName, command) {
|
|
53
|
+
const hay = `${serverName} ${command || ''}`;
|
|
54
|
+
for (const entry of KNOWN) {
|
|
55
|
+
if (entry.match.test(hay))
|
|
56
|
+
return entry.info;
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
function directServerFixHint(serverName) {
|
|
61
|
+
const info = knownMcpServerInfo(serverName);
|
|
62
|
+
if (!info?.recommendGateway)
|
|
63
|
+
return undefined;
|
|
64
|
+
if (/playwright/i.test(serverName)) {
|
|
65
|
+
return 'Remove Playwright from mcp.json or route it through: fullcourtdefense install-mcp-gateway --clients all --mcp-command ...';
|
|
66
|
+
}
|
|
67
|
+
return 'Route through FCD gateway: fullcourtdefense install-mcp-gateway --clients all --mcp-command npm --mcp-args "run mcp"';
|
|
68
|
+
}
|
|
@@ -34,6 +34,8 @@ export interface InstallMcpGatewayArgs extends McpGatewayArgs {
|
|
|
34
34
|
cursorProject?: string;
|
|
35
35
|
claudeCodeScope?: string;
|
|
36
36
|
serverName?: string;
|
|
37
|
+
upload?: string;
|
|
38
|
+
apiKey?: string;
|
|
37
39
|
}
|
|
38
40
|
export declare function mcpGatewayCommand(args: McpGatewayArgs, config: BotGuardConfig): Promise<void>;
|
|
39
41
|
export declare function installCursorMcpGatewayCommand(args: InstallCursorMcpGatewayArgs, config: BotGuardConfig): Promise<void>;
|
|
@@ -42,6 +42,8 @@ const fs = __importStar(require("fs"));
|
|
|
42
42
|
const os = __importStar(require("os"));
|
|
43
43
|
const path = __importStar(require("path"));
|
|
44
44
|
const child_process_1 = require("child_process");
|
|
45
|
+
const discoverPaths_1 = require("./discoverPaths");
|
|
46
|
+
const discover_1 = require("./discover");
|
|
45
47
|
const DEFAULT_API_URL = 'https://api.fullcourtdefense.ai';
|
|
46
48
|
const MANAGED_SERVER_NAME = 'agentguard-gateway';
|
|
47
49
|
function parseArgsList(value) {
|
|
@@ -672,83 +674,6 @@ function writeMcpServerConfig(file, serverName, nodeExe, commandArgs) {
|
|
|
672
674
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
673
675
|
fs.writeFileSync(file, `${JSON.stringify(json, null, 2)}\n`, 'utf8');
|
|
674
676
|
}
|
|
675
|
-
function dedupePaths(candidates) {
|
|
676
|
-
const seen = new Set();
|
|
677
|
-
return candidates.filter(candidate => {
|
|
678
|
-
const key = path.resolve(candidate.file).toLowerCase();
|
|
679
|
-
if (seen.has(key))
|
|
680
|
-
return false;
|
|
681
|
-
seen.add(key);
|
|
682
|
-
return true;
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
function claudeDesktopMcpPath() {
|
|
686
|
-
const home = os.homedir();
|
|
687
|
-
if (process.platform === 'win32') {
|
|
688
|
-
const appData = process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
|
|
689
|
-
return path.join(appData, 'Claude', 'claude_desktop_config.json');
|
|
690
|
-
}
|
|
691
|
-
if (process.platform === 'darwin') {
|
|
692
|
-
return path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
693
|
-
}
|
|
694
|
-
const xdg = process.env.XDG_CONFIG_HOME || path.join(home, '.config');
|
|
695
|
-
return path.join(xdg, 'Claude', 'claude_desktop_config.json');
|
|
696
|
-
}
|
|
697
|
-
function claudeDesktopMsixCandidates() {
|
|
698
|
-
if (process.platform !== 'win32')
|
|
699
|
-
return [];
|
|
700
|
-
const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
701
|
-
const packagesDir = path.join(localAppData, 'Packages');
|
|
702
|
-
let packageNames = [];
|
|
703
|
-
try {
|
|
704
|
-
packageNames = fs.readdirSync(packagesDir)
|
|
705
|
-
.filter(name => /^Claude_/i.test(name));
|
|
706
|
-
}
|
|
707
|
-
catch {
|
|
708
|
-
return [];
|
|
709
|
-
}
|
|
710
|
-
return packageNames.map(name => {
|
|
711
|
-
const file = path.join(packagesDir, name, 'LocalCache', 'Roaming', 'Claude', 'claude_desktop_config.json');
|
|
712
|
-
return {
|
|
713
|
-
file,
|
|
714
|
-
source: 'Claude Desktop (Microsoft Store/MSIX)',
|
|
715
|
-
exists: fs.existsSync(file),
|
|
716
|
-
};
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
function claudeDesktopMcpCandidates() {
|
|
720
|
-
const documented = claudeDesktopMcpPath();
|
|
721
|
-
const normal = {
|
|
722
|
-
file: documented,
|
|
723
|
-
source: process.platform === 'win32'
|
|
724
|
-
? 'Claude Desktop (Windows documented AppData)'
|
|
725
|
-
: process.platform === 'darwin'
|
|
726
|
-
? 'Claude Desktop (macOS)'
|
|
727
|
-
: 'Claude Desktop (Linux/XDG)',
|
|
728
|
-
exists: fs.existsSync(documented),
|
|
729
|
-
};
|
|
730
|
-
// Store/MSIX Claude Desktop reads a virtualized AppData path. Prefer it when present.
|
|
731
|
-
return dedupePaths([
|
|
732
|
-
...claudeDesktopMsixCandidates(),
|
|
733
|
-
normal,
|
|
734
|
-
]);
|
|
735
|
-
}
|
|
736
|
-
function claudeDesktopInstallTargets(configPath) {
|
|
737
|
-
if (configPath) {
|
|
738
|
-
const file = path.resolve(configPath);
|
|
739
|
-
return [{ file, source: 'Claude Desktop (custom config path)', exists: fs.existsSync(file) }];
|
|
740
|
-
}
|
|
741
|
-
const candidates = claudeDesktopMcpCandidates();
|
|
742
|
-
const existing = candidates.filter(candidate => candidate.exists);
|
|
743
|
-
if (existing.length > 0)
|
|
744
|
-
return existing;
|
|
745
|
-
const msix = candidates.find(candidate => candidate.source.includes('MSIX'));
|
|
746
|
-
return [msix || candidates[candidates.length - 1] || {
|
|
747
|
-
file: claudeDesktopMcpPath(),
|
|
748
|
-
source: 'Claude Desktop',
|
|
749
|
-
exists: false,
|
|
750
|
-
}];
|
|
751
|
-
}
|
|
752
677
|
function normalizeClaudeCodeScope(scope) {
|
|
753
678
|
if (scope === 'project' || scope === 'user' || scope === 'local')
|
|
754
679
|
return scope;
|
|
@@ -791,9 +716,8 @@ function detectInstallClients(cursorProject) {
|
|
|
791
716
|
clients.push('cursor');
|
|
792
717
|
if (commandAvailable('claude'))
|
|
793
718
|
clients.push('claude-code');
|
|
794
|
-
if (
|
|
719
|
+
if ((0, discoverPaths_1.claudeDesktopLikelyInstalled)())
|
|
795
720
|
clients.push('claude-desktop');
|
|
796
|
-
}
|
|
797
721
|
return clients;
|
|
798
722
|
}
|
|
799
723
|
function clientInstallArgs(args) {
|
|
@@ -841,7 +765,9 @@ async function installCursorMcpGatewayCommand(args, config) {
|
|
|
841
765
|
async function installMcpGatewayCommand(args, config) {
|
|
842
766
|
const cursorProject = args.cursorProject === 'true';
|
|
843
767
|
const selection = parseClientList(args.clients);
|
|
844
|
-
const clients = selection === 'auto'
|
|
768
|
+
const clients = selection === 'auto'
|
|
769
|
+
? detectInstallClients(cursorProject)
|
|
770
|
+
: selection;
|
|
845
771
|
if (clients.length === 0) {
|
|
846
772
|
throw new Error('No supported MCP clients detected. Re-run with --clients all or a comma list such as cursor,claude-desktop.');
|
|
847
773
|
}
|
|
@@ -876,6 +802,19 @@ async function installMcpGatewayCommand(args, config) {
|
|
|
876
802
|
console.log(`Runtime identity: ${detectedDeveloperName(args)}`);
|
|
877
803
|
console.log('Restart/reload each client to use the protected tools.');
|
|
878
804
|
console.log('Smoke test prompt: "Use lookup_customer to look up customer C-1001."');
|
|
805
|
+
if (args.upload === 'true') {
|
|
806
|
+
console.log('');
|
|
807
|
+
console.log('Uploading desktop inventory to AI Inventory…');
|
|
808
|
+
await (0, discover_1.runDiscoverUpload)({
|
|
809
|
+
apiKey: args.apiKey || config.apiKey,
|
|
810
|
+
apiUrl: args.apiUrl,
|
|
811
|
+
userEmail: args.developerName,
|
|
812
|
+
silent: false,
|
|
813
|
+
}, config);
|
|
814
|
+
}
|
|
815
|
+
else {
|
|
816
|
+
console.log('Run with --upload to refresh AI Inventory / Desktop MCP coverage in the app.');
|
|
817
|
+
}
|
|
879
818
|
}
|
|
880
819
|
async function installClaudeCodeMcpGatewayCommand(args, config) {
|
|
881
820
|
const gatewayConfig = resolveGatewayConfig(args, config, {
|
|
@@ -940,7 +879,7 @@ async function installClaudeDesktopMcpGatewayCommand(args, config) {
|
|
|
940
879
|
agentNamePrefix: 'claude-desktop',
|
|
941
880
|
userObjective: 'Developer requested this action from Claude Desktop.',
|
|
942
881
|
});
|
|
943
|
-
const targets = claudeDesktopInstallTargets(args.configPath);
|
|
882
|
+
const targets = (0, discoverPaths_1.claudeDesktopInstallTargets)(args.configPath);
|
|
944
883
|
const nodeExe = process.execPath;
|
|
945
884
|
const serverName = args.serverName || MANAGED_SERVER_NAME;
|
|
946
885
|
const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND;
|
package/dist/index.js
CHANGED
|
@@ -208,7 +208,7 @@ function printHelp() {
|
|
|
208
208
|
$ fullcourtdefense install-cursor-hook --project true # this repo only
|
|
209
209
|
$ fullcourtdefense uninstall-cursor-hook
|
|
210
210
|
$ fullcourtdefense install-mcp-gateway --clients auto --shield-id <id> --shield-key <key> --mcp-command npm --mcp-args "run mcp"
|
|
211
|
-
$ fullcourtdefense install-mcp-gateway --clients all --mcp-command
|
|
211
|
+
$ fullcourtdefense install-mcp-gateway --clients all --shield-id <id> --shield-key <key> --mcp-command npm --mcp-args "run mcp" --upload --api-key <key>
|
|
212
212
|
$ fullcourtdefense install-cursor-mcp-gateway --project true --shield-id <id> --shield-key <key> --mcp-command npm --mcp-args "run mcp"
|
|
213
213
|
$ fullcourtdefense install-claude-code-mcp-gateway --scope local --shield-id <id> --shield-key <key> --mcp-command npm --mcp-args "run mcp"
|
|
214
214
|
$ fullcourtdefense install-claude-code-mcp-gateway --scope project --shield-id <id> --mcp-command npm --mcp-args "run mcp"
|
|
@@ -406,6 +406,8 @@ async function main() {
|
|
|
406
406
|
approvalPollMs: flags['approval-poll-ms'],
|
|
407
407
|
scanResponse: flags['scan-response'],
|
|
408
408
|
failClosed: flags['fail-closed'],
|
|
409
|
+
upload: flags.upload,
|
|
410
|
+
apiKey: flags['api-key'],
|
|
409
411
|
};
|
|
410
412
|
await (0, mcpGateway_1.installMcpGatewayCommand)(args, config);
|
|
411
413
|
break;
|