fullcourtdefense-cli 1.3.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.js +67 -52
- package/package.json +1 -1
|
@@ -103,6 +103,55 @@ function scoreRisk(s) {
|
|
|
103
103
|
}
|
|
104
104
|
return { level, tags };
|
|
105
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
|
+
}
|
|
106
155
|
function parseConfigFile(filePath, source) {
|
|
107
156
|
let raw;
|
|
108
157
|
try {
|
|
@@ -118,56 +167,21 @@ function parseConfigFile(filePath, source) {
|
|
|
118
167
|
catch {
|
|
119
168
|
return [];
|
|
120
169
|
}
|
|
121
|
-
const servers = extractServerMap(parsed);
|
|
122
170
|
const out = [];
|
|
123
|
-
for (const [name, cfgRaw] of Object.entries(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
.map((t) => {
|
|
136
|
-
if (typeof t === 'string')
|
|
137
|
-
return { name: t };
|
|
138
|
-
if (t && typeof t.name === 'string')
|
|
139
|
-
return { name: t.name, description: typeof t.description === 'string' ? t.description : undefined };
|
|
140
|
-
return null;
|
|
141
|
-
})
|
|
142
|
-
.filter(Boolean)
|
|
143
|
-
: [];
|
|
144
|
-
const { level, tags } = scoreRisk({ serverName: name, command, args, url });
|
|
145
|
-
const warnings = [];
|
|
146
|
-
const blob = `${command || ''} ${(args || []).join(' ')} ${JSON.stringify(env)}`;
|
|
147
|
-
if (SECRET_VALUE.test(blob))
|
|
148
|
-
warnings.push('Hardcoded secret/API token detected in config (rotate + use env var).');
|
|
149
|
-
for (const [k, v] of Object.entries(env)) {
|
|
150
|
-
const val = String(v ?? '');
|
|
151
|
-
if (SECRET_KEY.test(k) && val && !/^\$\{?[A-Za-z0-9_]+\}?$/.test(val)) {
|
|
152
|
-
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);
|
|
153
183
|
}
|
|
154
184
|
}
|
|
155
|
-
out.push({
|
|
156
|
-
serverName: name,
|
|
157
|
-
command,
|
|
158
|
-
args,
|
|
159
|
-
url,
|
|
160
|
-
transport,
|
|
161
|
-
envKeys,
|
|
162
|
-
tools,
|
|
163
|
-
source,
|
|
164
|
-
configPath: filePath,
|
|
165
|
-
riskLevel: level,
|
|
166
|
-
riskTags: tags,
|
|
167
|
-
warnings,
|
|
168
|
-
probeMode: 'config',
|
|
169
|
-
proxyStatus: 'unknown',
|
|
170
|
-
});
|
|
171
185
|
}
|
|
172
186
|
return out;
|
|
173
187
|
}
|
|
@@ -256,13 +270,14 @@ async function enrichWithDeepProbe(servers, silent) {
|
|
|
256
270
|
server.probeMode = 'deep';
|
|
257
271
|
}
|
|
258
272
|
}
|
|
259
|
-
/**
|
|
273
|
+
/** Dedupe within the same config file + server name only (keep separate entries per client config path). */
|
|
260
274
|
function dedupe(servers) {
|
|
261
|
-
const
|
|
275
|
+
const byKey = new Map();
|
|
262
276
|
for (const s of servers) {
|
|
263
|
-
const
|
|
277
|
+
const key = `${s.configPath.toLowerCase()}::${s.serverName.toLowerCase()}`;
|
|
278
|
+
const existing = byKey.get(key);
|
|
264
279
|
if (!existing) {
|
|
265
|
-
|
|
280
|
+
byKey.set(key, { ...s });
|
|
266
281
|
continue;
|
|
267
282
|
}
|
|
268
283
|
if (!existing.source.includes(s.source))
|
|
@@ -277,7 +292,7 @@ function dedupe(servers) {
|
|
|
277
292
|
if (s.probeMode === 'deep')
|
|
278
293
|
existing.probeMode = 'deep';
|
|
279
294
|
}
|
|
280
|
-
return [...
|
|
295
|
+
return [...byKey.values()];
|
|
281
296
|
}
|
|
282
297
|
const COLOR = {
|
|
283
298
|
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|