fullcourtdefense-cli 1.14.12 → 1.14.14

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.
@@ -12,6 +12,10 @@ export interface DiscoverArgs {
12
12
  agentCiGate?: AgentCiGateUpload;
13
13
  extraPath?: string;
14
14
  deep?: string;
15
+ /** Optional comma-separated dev roots (or "auto") to sweep for project MCP configs in OTHER repos. */
16
+ scanRoot?: string;
17
+ /** Allow deep stdio probing (process spawn) for servers found via --scan-root. Default: off. */
18
+ deepRoots?: string;
15
19
  silent?: string;
16
20
  userEmail?: string;
17
21
  schedule?: string;
@@ -482,10 +482,18 @@ async function probeHttpMcpTools(url, timeoutMs = 8000) {
482
482
  }
483
483
  return [];
484
484
  }
485
- async function enrichWithDeepProbe(servers, silent) {
485
+ async function enrichWithDeepProbe(servers, silent, options = {}) {
486
486
  for (const server of servers) {
487
487
  if (server.transport !== 'http' && server.transport !== 'sse') {
488
488
  if (server.transport === 'stdio' && server.command) {
489
+ // Safety: never auto-spawn stdio server processes discovered in OTHER
490
+ // projects via --scan-root; inventory them without live tools unless
491
+ // the user opts in with --deep-roots. (HTTP probing below is fine —
492
+ // it's a network call, not a process spawn.)
493
+ if (options.skipStdioForConfigPaths?.has(path.resolve(server.configPath).toLowerCase())) {
494
+ server.warnings.push('Deep stdio probe skipped for config outside the current project (--scan-root). Pass --deep-roots true to probe these too.');
495
+ continue;
496
+ }
489
497
  const downstream = (0, discoverProxy_1.isFcdGatewayServer)(server) ? (0, discoverProxy_1.extractGatewayDownstream)(server) : null;
490
498
  const command = downstream?.command || server.command;
491
499
  const args = downstream?.args || server.args || [];
@@ -769,8 +777,30 @@ async function discoverCommand(args, config) {
769
777
  const scanned = [];
770
778
  let found = [];
771
779
  let clientCoverage = [];
780
+ const scanRootConfigPaths = new Set();
772
781
  if (runMcp) {
773
782
  const candidates = candidateConfigPaths(cwd, args.extraPath);
783
+ // Optional --scan-root sweep: find project-scoped MCP configs in OTHER repos
784
+ // under the given dev roots (bounded walk — see scanRootsForProjectConfigs).
785
+ // Without the flag, behavior is exactly the known-locations scan.
786
+ const scanRoots = (0, discoverPaths_1.resolveScanRoots)(args.scanRoot);
787
+ if (scanRoots.length > 0) {
788
+ const sweep = (0, discoverPaths_1.scanRootsForProjectConfigs)(scanRoots);
789
+ const known = new Set(candidates.map(c => path.resolve(c.path).toLowerCase()));
790
+ let added = 0;
791
+ for (const candidate of sweep.candidates) {
792
+ const key = path.resolve(candidate.path).toLowerCase();
793
+ if (known.has(key))
794
+ continue;
795
+ known.add(key);
796
+ candidates.push(candidate);
797
+ scanRootConfigPaths.add(key);
798
+ added += 1;
799
+ }
800
+ if (!silent) {
801
+ console.log(`${COLOR.gray}Scan-root sweep: checked ${sweep.scannedDirs} folder(s) under ${scanRoots.length} root(s) — found ${added} additional project config(s).${COLOR.reset}`);
802
+ }
803
+ }
774
804
  // Self-heal BEFORE parsing: configs written by old CLI versions can carry a stale
775
805
  // generic `--agent-name` that breaks dashboard policy matching. Fix them in place so
776
806
  // every discovery run converges the fleet to the per-server identity contract.
@@ -793,7 +823,9 @@ async function discoverCommand(args, config) {
793
823
  if (deep && found.length > 0) {
794
824
  if (!silent)
795
825
  console.log(`${COLOR.gray}Running deep probe (HTTP/SSE/stdio tools/list)…${COLOR.reset}`);
796
- await enrichWithDeepProbe(found, silent);
826
+ await enrichWithDeepProbe(found, silent, {
827
+ skipStdioForConfigPaths: args.deepRoots === 'true' ? undefined : scanRootConfigPaths,
828
+ });
797
829
  }
798
830
  }
799
831
  const secrets = surfaces.has('secrets') || surfaces.has('posture')
@@ -14,5 +14,22 @@ export declare function claudeDesktopLikelyInstalled(): boolean;
14
14
  export declare function claudeDesktopInstallTargets(configPath?: string): ClaudeDesktopConfigCandidate[];
15
15
  /** All MCP client config locations discover / install should check. */
16
16
  export declare function candidateConfigPaths(cwd: string, extra?: string): ConfigPathCandidate[];
17
+ /** Common places developers keep code, used by `--scan-root auto`. */
18
+ export declare function defaultScanRoots(): string[];
19
+ /**
20
+ * Bounded walk of developer folders looking for project-scoped MCP configs
21
+ * (`--scan-root`). Depth- and volume-capped so it stays fast and predictable —
22
+ * this is a repo-root sweep, not a disk scan. Hidden/dependency/build folders
23
+ * are pruned; only the known project config filenames are checked.
24
+ */
25
+ export declare function scanRootsForProjectConfigs(roots: string[], options?: {
26
+ maxDepth?: number;
27
+ maxDirs?: number;
28
+ }): {
29
+ candidates: ConfigPathCandidate[];
30
+ scannedDirs: number;
31
+ };
32
+ /** Resolve the --scan-root flag: comma-separated paths, or "auto" for common dev folders. */
33
+ export declare function resolveScanRoots(flag: string | undefined): string[];
17
34
  /** Include config paths for apps that exist on disk even when the MCP file is not created yet. */
18
35
  export declare function discoverScanTargets(cwd: string, extra?: string): ConfigPathCandidate[];
@@ -38,6 +38,9 @@ exports.claudeDesktopConfigCandidates = claudeDesktopConfigCandidates;
38
38
  exports.claudeDesktopLikelyInstalled = claudeDesktopLikelyInstalled;
39
39
  exports.claudeDesktopInstallTargets = claudeDesktopInstallTargets;
40
40
  exports.candidateConfigPaths = candidateConfigPaths;
41
+ exports.defaultScanRoots = defaultScanRoots;
42
+ exports.scanRootsForProjectConfigs = scanRootsForProjectConfigs;
43
+ exports.resolveScanRoots = resolveScanRoots;
41
44
  exports.discoverScanTargets = discoverScanTargets;
42
45
  const fs = __importStar(require("fs"));
43
46
  const os = __importStar(require("os"));
@@ -176,6 +179,122 @@ function candidateConfigPaths(cwd, extra) {
176
179
  return true;
177
180
  });
178
181
  }
182
+ /** Project-scoped MCP config files that can sit at any repo root. */
183
+ const PROJECT_CONFIG_FILES = [
184
+ { rel: path.join('.cursor', 'mcp.json'), source: 'Cursor (project)', clientKey: 'cursor' },
185
+ { rel: '.mcp.json', source: 'Claude Code (.mcp.json project)', clientKey: 'claude_code' },
186
+ { rel: path.join('.claude', 'settings.json'), source: 'Claude Code (project)', clientKey: 'claude_code' },
187
+ { rel: path.join('.vscode', 'mcp.json'), source: 'VS Code (project)', clientKey: 'vscode' },
188
+ { rel: path.join('.codex', 'config.toml'), source: 'Codex (project)', clientKey: 'codex' },
189
+ { rel: path.join('.gemini', 'settings.json'), source: 'Gemini CLI (project)', clientKey: 'gemini_cli' },
190
+ { rel: 'mcp.json', source: 'Repo (mcp.json)', clientKey: 'other' },
191
+ ];
192
+ /** Dependency/build/VCS folders that never contain project MCP configs. */
193
+ const SCAN_SKIP_DIRS = new Set([
194
+ 'node_modules', 'dist', 'build', 'out', 'vendor', 'venv', 'env', '__pycache__',
195
+ 'target', 'coverage', 'tmp', 'temp', 'obj', 'bin', 'packages', 'bower_components',
196
+ 'site-packages', 'deps', '.terraform',
197
+ ]);
198
+ /** Common places developers keep code, used by `--scan-root auto`. */
199
+ function defaultScanRoots() {
200
+ const home = os.homedir();
201
+ const roots = [
202
+ path.join(home, 'repos'),
203
+ path.join(home, 'projects'),
204
+ path.join(home, 'code'),
205
+ path.join(home, 'dev'),
206
+ path.join(home, 'src'),
207
+ path.join(home, 'work'),
208
+ path.join(home, 'Documents', 'GitHub'),
209
+ path.join(home, 'Documents', 'repos'),
210
+ path.join(home, 'Documents', 'projects'),
211
+ ];
212
+ if (process.platform === 'win32') {
213
+ const drive = (process.env.SystemDrive || 'C:') + path.sep;
214
+ for (const name of ['dev', 'src', 'repos', 'code', 'projects', 'work', 'git']) {
215
+ roots.push(path.join(drive, name));
216
+ }
217
+ }
218
+ return roots.filter(root => {
219
+ try {
220
+ return fs.statSync(root).isDirectory();
221
+ }
222
+ catch {
223
+ return false;
224
+ }
225
+ });
226
+ }
227
+ /**
228
+ * Bounded walk of developer folders looking for project-scoped MCP configs
229
+ * (`--scan-root`). Depth- and volume-capped so it stays fast and predictable —
230
+ * this is a repo-root sweep, not a disk scan. Hidden/dependency/build folders
231
+ * are pruned; only the known project config filenames are checked.
232
+ */
233
+ function scanRootsForProjectConfigs(roots, options = {}) {
234
+ const maxDepth = options.maxDepth ?? 4;
235
+ const maxDirs = options.maxDirs ?? 25000;
236
+ const candidates = [];
237
+ const visited = new Set();
238
+ let scannedDirs = 0;
239
+ const queue = [];
240
+ for (const root of roots) {
241
+ try {
242
+ const resolved = path.resolve(root);
243
+ if (fs.statSync(resolved).isDirectory())
244
+ queue.push({ dir: resolved, depth: 0 });
245
+ }
246
+ catch { /* missing root — skip */ }
247
+ }
248
+ while (queue.length > 0 && scannedDirs < maxDirs) {
249
+ const { dir, depth } = queue.shift();
250
+ const key = dir.toLowerCase();
251
+ if (visited.has(key))
252
+ continue;
253
+ visited.add(key);
254
+ scannedDirs += 1;
255
+ for (const config of PROJECT_CONFIG_FILES) {
256
+ const file = path.join(dir, config.rel);
257
+ try {
258
+ if (fs.existsSync(file) && fs.statSync(file).isFile()) {
259
+ candidates.push({ path: file, source: config.source, clientKey: config.clientKey });
260
+ }
261
+ }
262
+ catch { /* unreadable — skip */ }
263
+ }
264
+ if (depth >= maxDepth)
265
+ continue;
266
+ let entries;
267
+ try {
268
+ entries = fs.readdirSync(dir, { withFileTypes: true });
269
+ }
270
+ catch {
271
+ continue; // permission denied etc.
272
+ }
273
+ for (const entry of entries) {
274
+ if (!entry.isDirectory())
275
+ continue;
276
+ const name = entry.name;
277
+ if (name.startsWith('.') || SCAN_SKIP_DIRS.has(name.toLowerCase()))
278
+ continue;
279
+ queue.push({ dir: path.join(dir, name), depth: depth + 1 });
280
+ }
281
+ }
282
+ return { candidates, scannedDirs };
283
+ }
284
+ /** Resolve the --scan-root flag: comma-separated paths, or "auto" for common dev folders. */
285
+ function resolveScanRoots(flag) {
286
+ if (!flag?.trim())
287
+ return [];
288
+ const parts = flag.split(',').map(part => part.trim()).filter(Boolean);
289
+ const roots = [];
290
+ for (const part of parts) {
291
+ if (part.toLowerCase() === 'auto')
292
+ roots.push(...defaultScanRoots());
293
+ else
294
+ roots.push(path.resolve(part));
295
+ }
296
+ return [...new Set(roots.map(root => root))];
297
+ }
179
298
  /** Include config paths for apps that exist on disk even when the MCP file is not created yet. */
180
299
  function discoverScanTargets(cwd, extra) {
181
300
  const candidates = candidateConfigPaths(cwd, extra);
@@ -25,6 +25,7 @@ export declare function isFcdGatewayServer(server: ProxyClassifiableServer): boo
25
25
  export declare function extractGatewayDownstream(server: ProxyClassifiableServer): {
26
26
  command?: string;
27
27
  args?: string[];
28
+ url?: string;
28
29
  label?: string;
29
30
  } | null;
30
31
  /** Per-client downstream targets wrapped by FCD gateway entries in the same config file. */
@@ -64,6 +64,16 @@ function isFcdGatewayServer(server) {
64
64
  }
65
65
  function extractGatewayDownstream(server) {
66
66
  const args = server.args || [];
67
+ const urlIdx = args.indexOf('--mcp-url');
68
+ if (urlIdx >= 0 && args[urlIdx + 1]) {
69
+ const url = args[urlIdx + 1];
70
+ let label;
71
+ try {
72
+ label = new URL(url).hostname;
73
+ }
74
+ catch { /* leave undefined */ }
75
+ return { url, label };
76
+ }
67
77
  const cmdIdx = args.indexOf('--mcp-command');
68
78
  if (cmdIdx < 0 || !args[cmdIdx + 1])
69
79
  return null;
@@ -138,6 +148,8 @@ function buildGatewayWrapIndex(servers) {
138
148
  wrapped.add(downstream.label.toLowerCase());
139
149
  if (downstream?.command)
140
150
  wrapped.add(downstreamFingerprint(downstream.command, downstream.args));
151
+ if (downstream?.url)
152
+ wrapped.add(downstream.url.toLowerCase());
141
153
  wrapped.add(server.serverName.toLowerCase());
142
154
  }
143
155
  wrapsByConfig.set(configPath, wrapped);
@@ -5,6 +5,10 @@ export declare const ALL_GATEWAY_CLIENTS: GatewayClient[];
5
5
  export interface McpGatewayArgs {
6
6
  mcpCommand?: string;
7
7
  mcpArgs?: string;
8
+ /** Remote MCP server URL (Streamable HTTP / SSE) — alternative to --mcp-command. */
9
+ mcpUrl?: string;
10
+ /** Optional JSON object of extra HTTP headers for the remote MCP (e.g. auth). */
11
+ mcpHeaders?: string;
8
12
  agentName?: string;
9
13
  agentClient?: string;
10
14
  developerName?: string;
@@ -55,6 +59,20 @@ export interface InstallMcpGatewayArgs extends McpGatewayArgs {
55
59
  upload?: string;
56
60
  apiKey?: string;
57
61
  }
62
+ /**
63
+ * How the gateway reaches the real MCP server: spawn a local process (stdio)
64
+ * or speak Streamable HTTP/SSE to a remote URL. Both flow through the exact
65
+ * same policy enforcement in McpGatewayServer.
66
+ */
67
+ export type DownstreamSpec = {
68
+ kind: 'stdio';
69
+ command: string;
70
+ args: string[];
71
+ } | {
72
+ kind: 'http';
73
+ url: string;
74
+ headers?: Record<string, string>;
75
+ };
58
76
  export declare function mcpGatewayCommand(args: McpGatewayArgs, config: BotGuardConfig): Promise<void>;
59
77
  export declare function installCursorMcpGatewayCommand(args: InstallCursorMcpGatewayArgs, config: BotGuardConfig): Promise<void>;
60
78
  export declare function installMcpGatewayCommand(args: InstallMcpGatewayArgs, config: BotGuardConfig): Promise<void>;
@@ -79,6 +79,18 @@ const CLIENT_LABELS = {
79
79
  windsurf: 'Windsurf',
80
80
  vscode: 'VS Code',
81
81
  };
82
+ function parseHeadersFlag(value) {
83
+ if (!value?.trim())
84
+ return undefined;
85
+ try {
86
+ const parsed = JSON.parse(value);
87
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
88
+ return Object.fromEntries(Object.entries(parsed).map(([k, v]) => [k, String(v)]));
89
+ }
90
+ }
91
+ catch { /* fall through */ }
92
+ throw new Error('--mcp-headers must be a JSON object, e.g. {"Authorization":"Bearer ..."}');
93
+ }
82
94
  function parseArgsList(value) {
83
95
  if (!value?.trim())
84
96
  return [];
@@ -399,6 +411,127 @@ class StdioMcpClient {
399
411
  this.pending.clear();
400
412
  }
401
413
  }
414
+ /**
415
+ * Downstream client for remote HTTP/SSE MCP servers (MCP Streamable HTTP
416
+ * transport). JSON-RPC messages are POSTed to the server; responses may come
417
+ * back as plain JSON or as a text/event-stream body. Session continuity uses
418
+ * the Mcp-Session-Id header. Endpoint variants (base, /mcp, stripped /sse)
419
+ * are tried on initialize — the first one that answers wins.
420
+ */
421
+ class HttpMcpClient {
422
+ url;
423
+ extraHeaders;
424
+ endpoint;
425
+ sessionId;
426
+ nextId = 1;
427
+ constructor(url, extraHeaders = {}) {
428
+ this.url = url;
429
+ this.extraHeaders = extraHeaders;
430
+ if (!url)
431
+ throw new Error('Missing downstream MCP URL. Pass --mcp-url.');
432
+ }
433
+ async initialize() {
434
+ const normalized = this.url.replace(/\/$/, '');
435
+ const candidates = [...new Set([
436
+ normalized,
437
+ normalized.replace(/\/sse(\?.*)?$/i, ''),
438
+ `${normalized}/mcp`,
439
+ normalized.replace(/\/sse(\?.*)?$/i, '/mcp'),
440
+ ])].filter(Boolean);
441
+ let lastError;
442
+ for (const endpoint of candidates) {
443
+ try {
444
+ const init = await this.post(endpoint, 'initialize', {
445
+ protocolVersion: '2024-11-05',
446
+ capabilities: {},
447
+ clientInfo: { name: 'agentguard-mcp-gateway', version: '0.1.0' },
448
+ }, 15000);
449
+ if (init?.error) {
450
+ lastError = new Error(init.error.message || `MCP initialize failed at ${endpoint}`);
451
+ continue;
452
+ }
453
+ this.endpoint = endpoint;
454
+ await this.post(endpoint, 'notifications/initialized', {}, 15000, true).catch(() => undefined);
455
+ return;
456
+ }
457
+ catch (error) {
458
+ lastError = error instanceof Error ? error : new Error(String(error));
459
+ }
460
+ }
461
+ throw lastError || new Error(`Could not initialize remote MCP server at ${this.url}`);
462
+ }
463
+ async listTools() {
464
+ const response = await this.request('tools/list', {});
465
+ const tools = response?.tools;
466
+ if (!Array.isArray(tools))
467
+ return [];
468
+ return tools
469
+ .filter(tool => typeof tool.name === 'string')
470
+ .map(tool => ({
471
+ name: String(tool.name),
472
+ description: typeof tool.description === 'string'
473
+ ? String(tool.description)
474
+ : undefined,
475
+ inputSchema: tool.inputSchema,
476
+ }));
477
+ }
478
+ async callTool(name, args) {
479
+ return this.request('tools/call', { name, arguments: args });
480
+ }
481
+ close() {
482
+ // Stateless HTTP transport — nothing to tear down.
483
+ }
484
+ async request(method, params) {
485
+ if (!this.endpoint)
486
+ throw new Error('Remote MCP client not initialized.');
487
+ const message = await this.post(this.endpoint, method, params, 60000);
488
+ if (message?.error)
489
+ throw new Error(message.error.message || `MCP request failed: ${method}`);
490
+ return message?.result;
491
+ }
492
+ async post(endpoint, method, params, timeoutMs, isNotification = false) {
493
+ const headers = {
494
+ 'Content-Type': 'application/json',
495
+ Accept: 'application/json, text/event-stream',
496
+ ...this.extraHeaders,
497
+ };
498
+ if (this.sessionId)
499
+ headers['Mcp-Session-Id'] = this.sessionId;
500
+ const body = { jsonrpc: '2.0', method, params };
501
+ if (!isNotification)
502
+ body.id = this.nextId++;
503
+ const controller = new AbortController();
504
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
505
+ try {
506
+ const resp = await fetch(endpoint, {
507
+ method: 'POST',
508
+ headers,
509
+ body: JSON.stringify(body),
510
+ signal: controller.signal,
511
+ });
512
+ const nextSession = resp.headers.get('mcp-session-id');
513
+ if (nextSession)
514
+ this.sessionId = nextSession;
515
+ if (isNotification)
516
+ return null;
517
+ if (!resp.ok && resp.status !== 202) {
518
+ throw new Error(`Remote MCP server returned HTTP ${resp.status} for ${method}`);
519
+ }
520
+ const contentType = resp.headers.get('content-type') || '';
521
+ if (contentType.includes('text/event-stream')) {
522
+ const text = await resp.text();
523
+ const dataLine = text.split('\n').find(line => line.startsWith('data: '));
524
+ if (!dataLine)
525
+ throw new Error(`Remote MCP server sent an empty event stream for ${method}`);
526
+ return JSON.parse(dataLine.slice(6));
527
+ }
528
+ return await resp.json();
529
+ }
530
+ finally {
531
+ clearTimeout(timer);
532
+ }
533
+ }
534
+ }
402
535
  class AgentGuardApi {
403
536
  config;
404
537
  constructor(config) {
@@ -515,16 +648,14 @@ class AgentGuardApi {
515
648
  }
516
649
  class McpGatewayServer {
517
650
  gatewayConfig;
518
- downstreamCommand;
519
- downstreamArgs;
651
+ downstreamSpec;
520
652
  downstream;
521
653
  downstreamReady;
522
654
  buffer = '';
523
655
  api;
524
- constructor(gatewayConfig, downstreamCommand, downstreamArgs) {
656
+ constructor(gatewayConfig, downstreamSpec) {
525
657
  this.gatewayConfig = gatewayConfig;
526
- this.downstreamCommand = downstreamCommand;
527
- this.downstreamArgs = downstreamArgs;
658
+ this.downstreamSpec = downstreamSpec;
528
659
  this.api = new AgentGuardApi(gatewayConfig);
529
660
  }
530
661
  start() {
@@ -581,7 +712,9 @@ class McpGatewayServer {
581
712
  }
582
713
  async ensureDownstream() {
583
714
  if (!this.downstreamReady) {
584
- this.downstream = new StdioMcpClient(this.downstreamCommand, this.downstreamArgs);
715
+ this.downstream = this.downstreamSpec.kind === 'http'
716
+ ? new HttpMcpClient(this.downstreamSpec.url, this.downstreamSpec.headers)
717
+ : new StdioMcpClient(this.downstreamSpec.command, this.downstreamSpec.args);
585
718
  this.downstreamReady = this.downstream.initialize();
586
719
  }
587
720
  await this.downstreamReady;
@@ -722,10 +855,17 @@ class McpGatewayServer {
722
855
  }
723
856
  async function mcpGatewayCommand(args, config) {
724
857
  const gatewayConfig = resolveGatewayConfig(args, config);
725
- const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND || '';
726
- const downstreamArgs = parseArgsList(args.mcpArgs || process.env.FCD_MCP_ARGS);
727
- const server = new McpGatewayServer(gatewayConfig, downstreamCommand, downstreamArgs);
728
- process.stderr.write(`AgentGuard MCP Gateway running for ${gatewayConfig.agentName}. Downstream: ${downstreamCommand} ${downstreamArgs.join(' ')}\n`);
858
+ const downstreamUrl = args.mcpUrl || process.env.FCD_MCP_URL || '';
859
+ const spec = downstreamUrl
860
+ ? { kind: 'http', url: downstreamUrl, headers: parseHeadersFlag(args.mcpHeaders || process.env.FCD_MCP_HEADERS) }
861
+ : {
862
+ kind: 'stdio',
863
+ command: args.mcpCommand || process.env.FCD_MCP_COMMAND || '',
864
+ args: parseArgsList(args.mcpArgs || process.env.FCD_MCP_ARGS),
865
+ };
866
+ const server = new McpGatewayServer(gatewayConfig, spec);
867
+ const downstreamLabel = spec.kind === 'http' ? spec.url : `${spec.command} ${spec.args.join(' ')}`;
868
+ process.stderr.write(`AgentGuard MCP Gateway running for ${gatewayConfig.agentName}. Downstream: ${downstreamLabel}\n`);
729
869
  server.start();
730
870
  }
731
871
  function cursorMcpPath(projectScope) {
@@ -751,12 +891,38 @@ function readJson(file) {
751
891
  function gatewayScriptPath() {
752
892
  return path.resolve(process.argv[1] || path.join(__dirname, '..', 'index.js'));
753
893
  }
754
- function buildGatewayCommandArgs(gatewayConfig, downstreamCommand, downstreamArgs, options = {}) {
894
+ /**
895
+ * Resolve which real MCP server an installer should protect: a local stdio
896
+ * command (--mcp-command/--mcp-args) or a remote HTTP/SSE URL (--mcp-url,
897
+ * optionally with --mcp-headers for auth).
898
+ */
899
+ function resolveDownstreamSpec(args) {
900
+ const url = args.mcpUrl || process.env.FCD_MCP_URL;
901
+ if (url) {
902
+ return { kind: 'http', url, headers: parseHeadersFlag(args.mcpHeaders || process.env.FCD_MCP_HEADERS) };
903
+ }
904
+ const command = args.mcpCommand || process.env.FCD_MCP_COMMAND;
905
+ if (!command) {
906
+ throw new Error('Pass --mcp-command (local stdio MCP) or --mcp-url (remote HTTP/SSE MCP) for the server you want to protect.');
907
+ }
908
+ return { kind: 'stdio', command, args: parseArgsList(args.mcpArgs || process.env.FCD_MCP_ARGS) };
909
+ }
910
+ function downstreamSpecLabel(spec) {
911
+ return spec.kind === 'http' ? spec.url : `${spec.command} ${spec.args.join(' ')}`.trim();
912
+ }
913
+ function buildGatewayCommandArgs(gatewayConfig, downstream, options = {}) {
914
+ const downstreamFlags = downstream.kind === 'http'
915
+ ? [
916
+ '--mcp-url', downstream.url,
917
+ ...(downstream.headers && Object.keys(downstream.headers).length > 0
918
+ ? ['--mcp-headers', JSON.stringify(downstream.headers)]
919
+ : []),
920
+ ]
921
+ : ['--mcp-command', downstream.command, '--mcp-args', JSON.stringify(downstream.args)];
755
922
  const commandArgs = [
756
923
  gatewayScriptPath(),
757
924
  'mcp-gateway',
758
- '--mcp-command', downstreamCommand,
759
- '--mcp-args', JSON.stringify(downstreamArgs),
925
+ ...downstreamFlags,
760
926
  '--agent-name', gatewayConfig.agentName,
761
927
  '--agent-client', gatewayConfig.agentClient,
762
928
  '--developer-name', gatewayConfig.developerName,
@@ -978,16 +1144,13 @@ async function installCursorMcpGatewayCommand(args, config) {
978
1144
  const nodeExe = process.execPath;
979
1145
  const serverName = args.serverName || MANAGED_SERVER_NAME;
980
1146
  const gatewayConfig = applyPerServerAgentName(baseGatewayConfig, args, serverName);
981
- const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND;
982
- if (!downstreamCommand)
983
- throw new Error('Pass --mcp-command for the real MCP server you want to protect.');
984
- const downstreamArgs = parseArgsList(args.mcpArgs || process.env.FCD_MCP_ARGS);
985
- const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstreamCommand, downstreamArgs, INSTALL_GATEWAY_ARGS);
1147
+ const downstream = resolveDownstreamSpec(args);
1148
+ const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstream, INSTALL_GATEWAY_ARGS);
986
1149
  writeMcpServerConfig(file, serverName, nodeExe, commandArgs);
987
1150
  console.log(`AgentGuard MCP Gateway installed for Cursor (${projectScope ? 'project' : 'global'}).`);
988
1151
  console.log(`Config: ${file}`);
989
1152
  console.log(`Server: ${serverName}`);
990
- console.log(`Downstream: ${downstreamCommand} ${downstreamArgs.join(' ')}`);
1153
+ console.log(`Downstream: ${downstreamSpecLabel(downstream)}`);
991
1154
  console.log('Restart Cursor or reload MCP servers to use the protected tools.');
992
1155
  }
993
1156
  async function installMcpGatewayCommand(args, config) {
@@ -1092,9 +1255,21 @@ function wrappedEntryNeedsHeal(entry) {
1092
1255
  const args = Array.isArray(entry?.args) ? entry.args.map(String) : [];
1093
1256
  return args.includes('--shield-id') && !args.includes('--shield-key');
1094
1257
  }
1095
- /** Recover the original downstream command+args from a wrapped entry's args. */
1258
+ /** Recover the original downstream server (stdio command or remote URL) from a wrapped entry's args. */
1096
1259
  function extractWrappedDownstream(entry) {
1097
1260
  const args = Array.isArray(entry?.args) ? entry.args.map(String) : [];
1261
+ const ui = args.indexOf('--mcp-url');
1262
+ if (ui !== -1 && args[ui + 1]) {
1263
+ let headers;
1264
+ const hi = args.indexOf('--mcp-headers');
1265
+ if (hi !== -1 && args[hi + 1]) {
1266
+ try {
1267
+ headers = parseHeadersFlag(args[hi + 1]);
1268
+ }
1269
+ catch { /* leave undefined */ }
1270
+ }
1271
+ return { kind: 'http', url: args[ui + 1], headers };
1272
+ }
1098
1273
  const ci = args.indexOf('--mcp-command');
1099
1274
  if (ci === -1 || ci + 1 >= args.length)
1100
1275
  return null;
@@ -1109,7 +1284,7 @@ function extractWrappedDownstream(entry) {
1109
1284
  }
1110
1285
  catch { /* leave empty */ }
1111
1286
  }
1112
- return { command, args: downstreamArgs };
1287
+ return { kind: 'stdio', command, args: downstreamArgs };
1113
1288
  }
1114
1289
  /** All server maps inside a parsed JSON config, across every known client shape. */
1115
1290
  function collectJsonServerMaps(json) {
@@ -1314,7 +1489,7 @@ function wrapJsonConfigFile(file, gatewayConfig, agentClient, dryRun) {
1314
1489
  if (downstream) {
1315
1490
  const perServer = { ...gatewayConfig, agentClient, agentName: perServerAgentName(gatewayConfig.developerName, agentClient, name) };
1316
1491
  entry.command = nodeExe;
1317
- entry.args = buildGatewayCommandArgs(perServer, downstream.command, downstream.args, INSTALL_GATEWAY_ARGS);
1492
+ entry.args = buildGatewayCommandArgs(perServer, downstream, INSTALL_GATEWAY_ARGS);
1318
1493
  stats.healed.push(name);
1319
1494
  changed = true;
1320
1495
  continue;
@@ -1323,14 +1498,35 @@ function wrapJsonConfigFile(file, gatewayConfig, agentClient, dryRun) {
1323
1498
  stats.skippedManaged.push(name);
1324
1499
  continue;
1325
1500
  }
1501
+ const perServer = { ...gatewayConfig, agentClient, agentName: perServerAgentName(gatewayConfig.developerName, agentClient, name) };
1326
1502
  if (typeof entry.command !== 'string' || !entry.command) {
1327
- stats.skippedRemote.push(name);
1503
+ // Remote HTTP/SSE MCP: wrap the URL behind a local stdio gateway that
1504
+ // enforces policies, then forwards over Streamable HTTP. Original url,
1505
+ // headers, and transport type are baked into the args for full unwrap.
1506
+ const remoteUrl = typeof entry.url === 'string' && entry.url
1507
+ ? entry.url
1508
+ : (typeof entry.serverUrl === 'string' ? entry.serverUrl : '');
1509
+ if (!remoteUrl || !/^https?:\/\//i.test(remoteUrl)) {
1510
+ stats.skippedRemote.push(name);
1511
+ continue;
1512
+ }
1513
+ const headers = entry.headers && typeof entry.headers === 'object' && !Array.isArray(entry.headers)
1514
+ ? Object.fromEntries(Object.entries(entry.headers).map(([k, v]) => [k, String(v)]))
1515
+ : undefined;
1516
+ entry.command = nodeExe;
1517
+ entry.args = buildGatewayCommandArgs(perServer, { kind: 'http', url: remoteUrl, headers }, INSTALL_GATEWAY_ARGS);
1518
+ delete entry.url;
1519
+ delete entry.serverUrl;
1520
+ delete entry.headers;
1521
+ if (typeof entry.type === 'string')
1522
+ entry.type = 'stdio'; // VS Code-style configs must match the new transport
1523
+ stats.wrapped.push(name);
1524
+ changed = true;
1328
1525
  continue;
1329
1526
  }
1330
1527
  const downstreamCommand = entry.command;
1331
1528
  const downstreamArgs = Array.isArray(entry.args) ? entry.args.map(String) : [];
1332
- const perServer = { ...gatewayConfig, agentClient, agentName: perServerAgentName(gatewayConfig.developerName, agentClient, name) };
1333
- const wrappedArgs = buildGatewayCommandArgs(perServer, downstreamCommand, downstreamArgs, INSTALL_GATEWAY_ARGS);
1529
+ const wrappedArgs = buildGatewayCommandArgs(perServer, { kind: 'stdio', command: downstreamCommand, args: downstreamArgs }, INSTALL_GATEWAY_ARGS);
1334
1530
  entry.command = nodeExe;
1335
1531
  entry.args = wrappedArgs;
1336
1532
  // entry.env and any other fields are preserved as-is.
@@ -1369,8 +1565,19 @@ function unwrapJsonConfigFile(file, dryRun) {
1369
1565
  const downstream = extractWrappedDownstream(entry);
1370
1566
  if (!downstream)
1371
1567
  continue;
1372
- entry.command = downstream.command;
1373
- entry.args = downstream.args;
1568
+ if (downstream.kind === 'http') {
1569
+ delete entry.command;
1570
+ delete entry.args;
1571
+ entry.url = downstream.url;
1572
+ if (downstream.headers && Object.keys(downstream.headers).length > 0)
1573
+ entry.headers = downstream.headers;
1574
+ if (typeof entry.type === 'string')
1575
+ entry.type = /\/sse(\?|$)/i.test(downstream.url) ? 'sse' : 'http';
1576
+ }
1577
+ else {
1578
+ entry.command = downstream.command;
1579
+ entry.args = downstream.args;
1580
+ }
1374
1581
  restored.push(name);
1375
1582
  changed = true;
1376
1583
  }
@@ -1481,7 +1688,7 @@ function transformCodexToml(file, mode, gatewayConfig, dryRun) {
1481
1688
  continue;
1482
1689
  }
1483
1690
  const perServer = { ...gatewayConfig, agentClient: 'codex', agentName: perServerAgentName(gatewayConfig.developerName, 'codex', section.name) };
1484
- const wrappedArgs = buildGatewayCommandArgs(perServer, section.command, section.args || [], INSTALL_GATEWAY_ARGS);
1691
+ const wrappedArgs = buildGatewayCommandArgs(perServer, { kind: 'stdio', command: section.command, args: section.args || [] }, INSTALL_GATEWAY_ARGS);
1485
1692
  const indent = lines[section.cmdLine].match(/^\s*/)?.[0] || '';
1486
1693
  lines[section.cmdLine] = `${indent}command = ${JSON.stringify(nodeExe)}`;
1487
1694
  const argsLineText = `${indent}args = ${tomlStringArrayLiteral(wrappedArgs)}`;
@@ -1496,7 +1703,8 @@ function transformCodexToml(file, mode, gatewayConfig, dryRun) {
1496
1703
  if (!wrappedAlready)
1497
1704
  continue;
1498
1705
  const downstream = extractWrappedDownstream({ args: section.args });
1499
- if (!downstream)
1706
+ // Codex TOML wraps are always stdio (remote URLs never get wrapped into TOML).
1707
+ if (!downstream || downstream.kind !== 'stdio')
1500
1708
  continue;
1501
1709
  const indent = lines[section.cmdLine].match(/^\s*/)?.[0] || '';
1502
1710
  lines[section.cmdLine] = `${indent}command = ${JSON.stringify(downstream.command)}`;
@@ -1656,12 +1864,9 @@ async function installClaudeCodeMcpGatewayCommand(args, config) {
1656
1864
  const nodeExe = process.execPath;
1657
1865
  const serverName = args.serverName || MANAGED_SERVER_NAME;
1658
1866
  const gatewayConfig = applyPerServerAgentName(baseGatewayConfig, args, serverName);
1659
- const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND;
1660
- if (!downstreamCommand)
1661
- throw new Error('Pass --mcp-command for the real MCP server you want to protect.');
1662
- const downstreamArgs = parseArgsList(args.mcpArgs || process.env.FCD_MCP_ARGS);
1867
+ const downstream = resolveDownstreamSpec(args);
1663
1868
  const includeShieldKey = false;
1664
- const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstreamCommand, downstreamArgs, {
1869
+ const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstream, {
1665
1870
  ...INSTALL_GATEWAY_ARGS,
1666
1871
  includeShieldKey,
1667
1872
  });
@@ -1691,7 +1896,7 @@ async function installClaudeCodeMcpGatewayCommand(args, config) {
1691
1896
  if (result.status === 0) {
1692
1897
  console.log(`AgentGuard MCP Gateway installed for Claude Code (${scope}).`);
1693
1898
  console.log(`Server: ${serverName}`);
1694
- console.log(`Downstream: ${downstreamCommand} ${downstreamArgs.join(' ')}`);
1899
+ console.log(`Downstream: ${downstreamSpecLabel(downstream)}`);
1695
1900
  if (!includeShieldKey && gatewayConfig.shieldKey) {
1696
1901
  console.log('Note: Shield key was loaded from local config/env and omitted from project .mcp.json. Runtime will use the developer machine config/env.');
1697
1902
  }
@@ -1704,7 +1909,7 @@ async function installClaudeCodeMcpGatewayCommand(args, config) {
1704
1909
  console.log('Claude Code CLI was not available, so project config was written directly.');
1705
1910
  console.log(`Config: ${file}`);
1706
1911
  console.log(`Server: ${serverName}`);
1707
- console.log(`Downstream: ${downstreamCommand} ${downstreamArgs.join(' ')}`);
1912
+ console.log(`Downstream: ${downstreamSpecLabel(downstream)}`);
1708
1913
  if (!includeShieldKey && gatewayConfig.shieldKey) {
1709
1914
  console.log('Note: Shield key was loaded from local config/env and omitted from project .mcp.json. Runtime will use the developer machine config/env.');
1710
1915
  }
@@ -1723,11 +1928,8 @@ async function installClaudeDesktopMcpGatewayCommand(args, config) {
1723
1928
  const nodeExe = process.execPath;
1724
1929
  const serverName = args.serverName || MANAGED_SERVER_NAME;
1725
1930
  const gatewayConfig = applyPerServerAgentName(baseGatewayConfig, args, serverName);
1726
- const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND;
1727
- if (!downstreamCommand)
1728
- throw new Error('Pass --mcp-command for the real MCP server you want to protect.');
1729
- const downstreamArgs = parseArgsList(args.mcpArgs || process.env.FCD_MCP_ARGS);
1730
- const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstreamCommand, downstreamArgs, INSTALL_GATEWAY_ARGS);
1931
+ const downstream = resolveDownstreamSpec(args);
1932
+ const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstream, INSTALL_GATEWAY_ARGS);
1731
1933
  for (const target of targets) {
1732
1934
  writeMcpServerConfig(target.file, serverName, nodeExe, commandArgs);
1733
1935
  }
@@ -1736,7 +1938,7 @@ async function installClaudeDesktopMcpGatewayCommand(args, config) {
1736
1938
  console.log(`Config: ${target.file} (${target.source})`);
1737
1939
  }
1738
1940
  console.log(`Server: ${serverName}`);
1739
- console.log(`Downstream: ${downstreamCommand} ${downstreamArgs.join(' ')}`);
1941
+ console.log(`Downstream: ${downstreamSpecLabel(downstream)}`);
1740
1942
  console.log('Restart Claude Desktop to load the protected tools.');
1741
1943
  }
1742
1944
  async function installCodexMcpGatewayCommand(args, config) {
@@ -1750,11 +1952,8 @@ async function installCodexMcpGatewayCommand(args, config) {
1750
1952
  const nodeExe = process.execPath;
1751
1953
  const serverName = args.serverName || MANAGED_SERVER_NAME;
1752
1954
  const gatewayConfig = applyPerServerAgentName(baseGatewayConfig, args, serverName);
1753
- const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND;
1754
- if (!downstreamCommand)
1755
- throw new Error('Pass --mcp-command for the real MCP server you want to protect.');
1756
- const downstreamArgs = parseArgsList(args.mcpArgs || process.env.FCD_MCP_ARGS);
1757
- const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstreamCommand, downstreamArgs, INSTALL_GATEWAY_ARGS);
1955
+ const downstream = resolveDownstreamSpec(args);
1956
+ const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstream, INSTALL_GATEWAY_ARGS);
1758
1957
  writeCodexMcpServer(file, serverName, nodeExe, commandArgs);
1759
1958
  console.log(`AgentGuard MCP Gateway installed for Codex (${projectScope ? 'project' : 'global'}).`);
1760
1959
  console.log(`Config: ${file}`);
@@ -1772,11 +1971,8 @@ async function installGeminiMcpGatewayCommand(args, config) {
1772
1971
  const nodeExe = process.execPath;
1773
1972
  const serverName = args.serverName || MANAGED_SERVER_NAME;
1774
1973
  const gatewayConfig = applyPerServerAgentName(baseGatewayConfig, args, serverName);
1775
- const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND;
1776
- if (!downstreamCommand)
1777
- throw new Error('Pass --mcp-command for the real MCP server you want to protect.');
1778
- const downstreamArgs = parseArgsList(args.mcpArgs || process.env.FCD_MCP_ARGS);
1779
- const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstreamCommand, downstreamArgs, INSTALL_GATEWAY_ARGS);
1974
+ const downstream = resolveDownstreamSpec(args);
1975
+ const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstream, INSTALL_GATEWAY_ARGS);
1780
1976
  writeJsonMcpServer(file, serverName, nodeExe, commandArgs);
1781
1977
  console.log(`AgentGuard MCP Gateway installed for Gemini CLI (${projectScope ? 'project' : 'user'}).`);
1782
1978
  console.log(`Config: ${file}`);
@@ -1793,11 +1989,8 @@ async function installWindsurfMcpGatewayCommand(args, config) {
1793
1989
  const nodeExe = process.execPath;
1794
1990
  const serverName = args.serverName || MANAGED_SERVER_NAME;
1795
1991
  const gatewayConfig = applyPerServerAgentName(baseGatewayConfig, args, serverName);
1796
- const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND;
1797
- if (!downstreamCommand)
1798
- throw new Error('Pass --mcp-command for the real MCP server you want to protect.');
1799
- const downstreamArgs = parseArgsList(args.mcpArgs || process.env.FCD_MCP_ARGS);
1800
- const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstreamCommand, downstreamArgs, INSTALL_GATEWAY_ARGS);
1992
+ const downstream = resolveDownstreamSpec(args);
1993
+ const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstream, INSTALL_GATEWAY_ARGS);
1801
1994
  writeJsonMcpServer(file, serverName, nodeExe, commandArgs);
1802
1995
  console.log('AgentGuard MCP Gateway installed for Windsurf.');
1803
1996
  console.log(`Config: ${file}`);
@@ -1814,11 +2007,8 @@ async function installVscodeMcpGatewayCommand(args, config) {
1814
2007
  const nodeExe = process.execPath;
1815
2008
  const serverName = args.serverName || MANAGED_SERVER_NAME;
1816
2009
  const gatewayConfig = applyPerServerAgentName(baseGatewayConfig, args, serverName);
1817
- const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND;
1818
- if (!downstreamCommand)
1819
- throw new Error('Pass --mcp-command for the real MCP server you want to protect.');
1820
- const downstreamArgs = parseArgsList(args.mcpArgs || process.env.FCD_MCP_ARGS);
1821
- const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstreamCommand, downstreamArgs, INSTALL_GATEWAY_ARGS);
2010
+ const downstream = resolveDownstreamSpec(args);
2011
+ const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstream, INSTALL_GATEWAY_ARGS);
1822
2012
  const targets = [];
1823
2013
  if (projectScope)
1824
2014
  targets.push(vscodeMcpPath(true));
package/dist/index.js CHANGED
@@ -327,6 +327,8 @@ function printHelp() {
327
327
  $ fullcourtdefense discover --surface secrets
328
328
  $ fullcourtdefense discover --surface agent-files
329
329
  $ fullcourtdefense discover --deep --upload --silent --user-email you@company.com
330
+ $ fullcourtdefense discover --surface mcp --scan-root auto --upload
331
+ $ fullcourtdefense discover --surface mcp --scan-root "C:\dev,D:\work" --upload
330
332
  $ fullcourtdefense discover --type mcp --json
331
333
  $ fullcourtdefense install-cursor-hook
332
334
  $ fullcourtdefense install-cursor-hook --shadow true # monitor only
@@ -342,7 +344,9 @@ function printHelp() {
342
344
  $ fullcourtdefense install-claude-code-mcp-gateway --scope local --mcp-command npm --mcp-args "run mcp"
343
345
  $ fullcourtdefense install-claude-code-mcp-gateway --scope project --mcp-command npm --mcp-args "run mcp"
344
346
  $ fullcourtdefense install-claude-desktop-mcp-gateway --mcp-command npm --mcp-args "run mcp"
347
+ $ fullcourtdefense install-cursor-mcp-gateway --server-name my-remote --mcp-url https://mcp.company.com/mcp
345
348
  $ fullcourtdefense mcp-gateway --mcp-command npm --mcp-args "run mcp"
349
+ $ fullcourtdefense mcp-gateway --mcp-url https://mcp.company.com/mcp --mcp-headers "{\"Authorization\":\"Bearer TOKEN\"}"
346
350
  $ fullcourtdefense login --token <fleet-enrollment-token>
347
351
  $ fullcourtdefense install-all
348
352
  $ fullcourtdefense install-all --auto-protect true --upload
@@ -366,6 +370,8 @@ async function main() {
366
370
  serverName: flags['server-name'],
367
371
  mcpCommand: flags['mcp-command'],
368
372
  mcpArgs: flags['mcp-args'],
373
+ mcpUrl: flags['mcp-url'],
374
+ mcpHeaders: flags['mcp-headers'],
369
375
  agentName: flags['agent-name'],
370
376
  developerName: flags['developer-name'],
371
377
  shieldId: flags['shield-id'],
@@ -385,6 +391,8 @@ async function main() {
385
391
  const buildGatewayArgs = () => ({
386
392
  mcpCommand: flags['mcp-command'],
387
393
  mcpArgs: flags['mcp-args'],
394
+ mcpUrl: flags['mcp-url'],
395
+ mcpHeaders: flags['mcp-headers'],
388
396
  agentName: flags['agent-name'],
389
397
  agentClient: flags['agent-client'],
390
398
  developerName: flags['developer-name'],
@@ -520,6 +528,8 @@ async function main() {
520
528
  connectorName: flags['connector-name'],
521
529
  extraPath: flags.path,
522
530
  deep: flags.deep,
531
+ scanRoot: flags['scan-root'],
532
+ deepRoots: flags['deep-roots'],
523
533
  silent: flags.silent,
524
534
  userEmail: flags['user-email'],
525
535
  schedule: flags.schedule,
package/dist/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.14.12"
2
+ "version": "1.14.14"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullcourtdefense-cli",
3
- "version": "1.14.12",
3
+ "version": "1.14.14",
4
4
  "description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -25,6 +25,7 @@
25
25
  "test:realworld-posture": "npm run build && node scripts/test-realworld-posture-fixtures.js",
26
26
  "test:discover-stdio-mcp": "npm run build && node scripts/test-discover-stdio-mcp-tools.js",
27
27
  "test:per-server-agent-name": "npm run build && node scripts/test-per-server-agent-name.js",
28
+ "test:remote-mcp-gateway": "npm run build && node scripts/test-remote-mcp-gateway.js",
28
29
  "test:posture-blast": "npm run build && node scripts/test-posture-blast-radius.js",
29
30
  "test:ping-e2e": "npm run build && node scripts/test-ping-e2e.js",
30
31
  "prepublishOnly": "npm run build"