fullcourtdefense-cli 1.14.11 → 1.14.13

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.
@@ -43,6 +43,7 @@ const child_process_1 = require("child_process");
43
43
  const config_1 = require("../config");
44
44
  const discoverProxy_1 = require("./discoverProxy");
45
45
  const discoverSchedule_1 = require("./discoverSchedule");
46
+ const mcpGateway_1 = require("./mcpGateway");
46
47
  const discoverPaths_1 = require("./discoverPaths");
47
48
  const knownMcpServers_1 = require("./knownMcpServers");
48
49
  const discoverAgentFiles_1 = require("./discoverAgentFiles");
@@ -770,6 +771,17 @@ async function discoverCommand(args, config) {
770
771
  let clientCoverage = [];
771
772
  if (runMcp) {
772
773
  const candidates = candidateConfigPaths(cwd, args.extraPath);
774
+ // Self-heal BEFORE parsing: configs written by old CLI versions can carry a stale
775
+ // generic `--agent-name` that breaks dashboard policy matching. Fix them in place so
776
+ // every discovery run converges the fleet to the per-server identity contract.
777
+ const healedIdentities = (0, mcpGateway_1.healGatewayAgentIdentities)(candidates.map(c => c.path).filter(p => fs.existsSync(p)));
778
+ if (healedIdentities.length > 0 && !silent) {
779
+ console.log(`${COLOR.yellow}Healed ${healedIdentities.length} stale gateway identit${healedIdentities.length === 1 ? 'y' : 'ies'} (old CLI defaults):${COLOR.reset}`);
780
+ for (const heal of healedIdentities) {
781
+ console.log(` ${COLOR.dim}•${COLOR.reset} ${heal.server}: ${heal.from} → ${heal.to} (${heal.file})`);
782
+ }
783
+ console.log(`${COLOR.gray}Restart the affected AI clients to pick up the corrected identity.${COLOR.reset}\n`);
784
+ }
773
785
  for (const c of candidates) {
774
786
  scanned.push({ path: c.path, source: c.source });
775
787
  if (fs.existsSync(c.path)) {
@@ -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>;
@@ -63,6 +81,22 @@ export interface ProtectAllArgs extends McpGatewayArgs {
63
81
  config?: string;
64
82
  clients?: string;
65
83
  }
84
+ export interface GatewayIdentityHealResult {
85
+ file: string;
86
+ server: string;
87
+ from: string;
88
+ to: string;
89
+ }
90
+ /**
91
+ * SELF-HEAL for the policy-identity contract. Scans MCP client config files (JSON and Codex
92
+ * TOML) for FCD-gateway-wrapped servers whose baked-in `--agent-name` is a stale generic
93
+ * `<developer>-<client>` default, and rewrites it to `<developer>-<server>` so dashboard
94
+ * policies targeting the MCP server (`agentName contains <server>`) match at runtime.
95
+ *
96
+ * Runs automatically from `discover` and `protect-all`, so a customer machine with configs
97
+ * written by an old CLI version converges to the correct identity without manual edits.
98
+ */
99
+ export declare function healGatewayAgentIdentities(files: string[], dryRun?: boolean): GatewayIdentityHealResult[];
66
100
  export declare function protectAllCommand(args: ProtectAllArgs, config: BotGuardConfig): Promise<void>;
67
101
  export declare function unprotectAllCommand(args: ProtectAllArgs, config: BotGuardConfig): Promise<void>;
68
102
  export declare function installClaudeCodeMcpGatewayCommand(args: InstallClaudeCodeMcpGatewayArgs, config: BotGuardConfig): Promise<void>;
@@ -37,6 +37,7 @@ exports.ALL_GATEWAY_CLIENTS = void 0;
37
37
  exports.mcpGatewayCommand = mcpGatewayCommand;
38
38
  exports.installCursorMcpGatewayCommand = installCursorMcpGatewayCommand;
39
39
  exports.installMcpGatewayCommand = installMcpGatewayCommand;
40
+ exports.healGatewayAgentIdentities = healGatewayAgentIdentities;
40
41
  exports.protectAllCommand = protectAllCommand;
41
42
  exports.unprotectAllCommand = unprotectAllCommand;
42
43
  exports.installClaudeCodeMcpGatewayCommand = installClaudeCodeMcpGatewayCommand;
@@ -78,6 +79,18 @@ const CLIENT_LABELS = {
78
79
  windsurf: 'Windsurf',
79
80
  vscode: 'VS Code',
80
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
+ }
81
94
  function parseArgsList(value) {
82
95
  if (!value?.trim())
83
96
  return [];
@@ -398,6 +411,127 @@ class StdioMcpClient {
398
411
  this.pending.clear();
399
412
  }
400
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
+ }
401
535
  class AgentGuardApi {
402
536
  config;
403
537
  constructor(config) {
@@ -514,16 +648,14 @@ class AgentGuardApi {
514
648
  }
515
649
  class McpGatewayServer {
516
650
  gatewayConfig;
517
- downstreamCommand;
518
- downstreamArgs;
651
+ downstreamSpec;
519
652
  downstream;
520
653
  downstreamReady;
521
654
  buffer = '';
522
655
  api;
523
- constructor(gatewayConfig, downstreamCommand, downstreamArgs) {
656
+ constructor(gatewayConfig, downstreamSpec) {
524
657
  this.gatewayConfig = gatewayConfig;
525
- this.downstreamCommand = downstreamCommand;
526
- this.downstreamArgs = downstreamArgs;
658
+ this.downstreamSpec = downstreamSpec;
527
659
  this.api = new AgentGuardApi(gatewayConfig);
528
660
  }
529
661
  start() {
@@ -580,7 +712,9 @@ class McpGatewayServer {
580
712
  }
581
713
  async ensureDownstream() {
582
714
  if (!this.downstreamReady) {
583
- 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);
584
718
  this.downstreamReady = this.downstream.initialize();
585
719
  }
586
720
  await this.downstreamReady;
@@ -721,10 +855,17 @@ class McpGatewayServer {
721
855
  }
722
856
  async function mcpGatewayCommand(args, config) {
723
857
  const gatewayConfig = resolveGatewayConfig(args, config);
724
- const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND || '';
725
- const downstreamArgs = parseArgsList(args.mcpArgs || process.env.FCD_MCP_ARGS);
726
- const server = new McpGatewayServer(gatewayConfig, downstreamCommand, downstreamArgs);
727
- 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`);
728
869
  server.start();
729
870
  }
730
871
  function cursorMcpPath(projectScope) {
@@ -750,12 +891,38 @@ function readJson(file) {
750
891
  function gatewayScriptPath() {
751
892
  return path.resolve(process.argv[1] || path.join(__dirname, '..', 'index.js'));
752
893
  }
753
- 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)];
754
922
  const commandArgs = [
755
923
  gatewayScriptPath(),
756
924
  'mcp-gateway',
757
- '--mcp-command', downstreamCommand,
758
- '--mcp-args', JSON.stringify(downstreamArgs),
925
+ ...downstreamFlags,
759
926
  '--agent-name', gatewayConfig.agentName,
760
927
  '--agent-client', gatewayConfig.agentClient,
761
928
  '--developer-name', gatewayConfig.developerName,
@@ -977,16 +1144,13 @@ async function installCursorMcpGatewayCommand(args, config) {
977
1144
  const nodeExe = process.execPath;
978
1145
  const serverName = args.serverName || MANAGED_SERVER_NAME;
979
1146
  const gatewayConfig = applyPerServerAgentName(baseGatewayConfig, args, serverName);
980
- const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND;
981
- if (!downstreamCommand)
982
- throw new Error('Pass --mcp-command for the real MCP server you want to protect.');
983
- const downstreamArgs = parseArgsList(args.mcpArgs || process.env.FCD_MCP_ARGS);
984
- const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstreamCommand, downstreamArgs, INSTALL_GATEWAY_ARGS);
1147
+ const downstream = resolveDownstreamSpec(args);
1148
+ const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstream, INSTALL_GATEWAY_ARGS);
985
1149
  writeMcpServerConfig(file, serverName, nodeExe, commandArgs);
986
1150
  console.log(`AgentGuard MCP Gateway installed for Cursor (${projectScope ? 'project' : 'global'}).`);
987
1151
  console.log(`Config: ${file}`);
988
1152
  console.log(`Server: ${serverName}`);
989
- console.log(`Downstream: ${downstreamCommand} ${downstreamArgs.join(' ')}`);
1153
+ console.log(`Downstream: ${downstreamSpecLabel(downstream)}`);
990
1154
  console.log('Restart Cursor or reload MCP servers to use the protected tools.');
991
1155
  }
992
1156
  async function installMcpGatewayCommand(args, config) {
@@ -1091,9 +1255,21 @@ function wrappedEntryNeedsHeal(entry) {
1091
1255
  const args = Array.isArray(entry?.args) ? entry.args.map(String) : [];
1092
1256
  return args.includes('--shield-id') && !args.includes('--shield-key');
1093
1257
  }
1094
- /** 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. */
1095
1259
  function extractWrappedDownstream(entry) {
1096
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
+ }
1097
1273
  const ci = args.indexOf('--mcp-command');
1098
1274
  if (ci === -1 || ci + 1 >= args.length)
1099
1275
  return null;
@@ -1108,7 +1284,7 @@ function extractWrappedDownstream(entry) {
1108
1284
  }
1109
1285
  catch { /* leave empty */ }
1110
1286
  }
1111
- return { command, args: downstreamArgs };
1287
+ return { kind: 'stdio', command, args: downstreamArgs };
1112
1288
  }
1113
1289
  /** All server maps inside a parsed JSON config, across every known client shape. */
1114
1290
  function collectJsonServerMaps(json) {
@@ -1142,6 +1318,129 @@ function backupFile(file) {
1142
1318
  function perServerAgentName(developerName, _agentClient, serverName) {
1143
1319
  return `${safeIdentityPart(developerName)}-${safeIdentityPart(serverName)}`;
1144
1320
  }
1321
+ /**
1322
+ * Generic `<developer>-<client>` identities that pre-1.14.11 per-server installs baked into
1323
+ * configs. These are the ONLY values self-heal is allowed to rewrite — a custom --agent-name
1324
+ * an admin chose on purpose is never touched.
1325
+ */
1326
+ const GENERIC_CLIENT_AGENT_SUFFIXES = ['cursor', 'claude-code', 'claude-desktop', 'codex', 'gemini', 'gemini-cli', 'windsurf', 'vscode'];
1327
+ /**
1328
+ * Rewrite a wrapped entry's `--agent-name` to the per-server contract (`<developer>-<server>`)
1329
+ * when it still carries a known-buggy generic client default. Mutates `args` in place.
1330
+ */
1331
+ function healAgentNameInArgs(args, serverName) {
1332
+ const nameIdx = args.indexOf('--agent-name');
1333
+ if (nameIdx === -1 || nameIdx + 1 >= args.length)
1334
+ return { changed: false };
1335
+ const current = String(args[nameIdx + 1]);
1336
+ const devIdx = args.indexOf('--developer-name');
1337
+ const developerName = devIdx !== -1 && args[devIdx + 1] ? String(args[devIdx + 1]) : machineScopedUser();
1338
+ const devPart = safeIdentityPart(developerName);
1339
+ const expected = `${devPart}-${safeIdentityPart(serverName)}`;
1340
+ if (current === expected)
1341
+ return { changed: false };
1342
+ const isGenericDefault = GENERIC_CLIENT_AGENT_SUFFIXES.some(suffix => current === `${devPart}-${suffix}`);
1343
+ if (!isGenericDefault)
1344
+ return { changed: false };
1345
+ args[nameIdx + 1] = expected;
1346
+ return { changed: true, from: current, to: expected };
1347
+ }
1348
+ /**
1349
+ * SELF-HEAL for the policy-identity contract. Scans MCP client config files (JSON and Codex
1350
+ * TOML) for FCD-gateway-wrapped servers whose baked-in `--agent-name` is a stale generic
1351
+ * `<developer>-<client>` default, and rewrites it to `<developer>-<server>` so dashboard
1352
+ * policies targeting the MCP server (`agentName contains <server>`) match at runtime.
1353
+ *
1354
+ * Runs automatically from `discover` and `protect-all`, so a customer machine with configs
1355
+ * written by an old CLI version converges to the correct identity without manual edits.
1356
+ */
1357
+ function healGatewayAgentIdentities(files, dryRun = false) {
1358
+ const healed = [];
1359
+ const seen = new Set();
1360
+ for (const file of files) {
1361
+ let resolvedKey;
1362
+ try {
1363
+ resolvedKey = path.resolve(file).toLowerCase();
1364
+ }
1365
+ catch {
1366
+ continue;
1367
+ }
1368
+ if (seen.has(resolvedKey))
1369
+ continue;
1370
+ seen.add(resolvedKey);
1371
+ if (/\.toml$/i.test(file)) {
1372
+ healed.push(...healCodexTomlAgentIdentities(file, dryRun));
1373
+ continue;
1374
+ }
1375
+ let raw;
1376
+ try {
1377
+ raw = fs.readFileSync(file, 'utf8');
1378
+ }
1379
+ catch {
1380
+ continue;
1381
+ }
1382
+ let json;
1383
+ try {
1384
+ json = JSON.parse(raw);
1385
+ }
1386
+ catch {
1387
+ continue;
1388
+ }
1389
+ const maps = collectJsonServerMaps(json);
1390
+ let changed = false;
1391
+ for (const map of maps) {
1392
+ for (const [name, entry] of Object.entries(map)) {
1393
+ if (name === MANAGED_SERVER_NAME || !isGatewayWrappedEntry(entry))
1394
+ continue;
1395
+ const args = Array.isArray(entry.args) ? entry.args.map(String) : [];
1396
+ const result = healAgentNameInArgs(args, name);
1397
+ if (result.changed) {
1398
+ entry.args = args;
1399
+ healed.push({ file, server: name, from: result.from, to: result.to });
1400
+ changed = true;
1401
+ }
1402
+ }
1403
+ }
1404
+ if (changed && !dryRun) {
1405
+ backupFile(file);
1406
+ fs.writeFileSync(file, `${JSON.stringify(json, null, 2)}\n`, 'utf8');
1407
+ }
1408
+ }
1409
+ return healed;
1410
+ }
1411
+ /** Codex config.toml variant of the identity self-heal (args are written as a JSON-style array). */
1412
+ function healCodexTomlAgentIdentities(file, dryRun) {
1413
+ let raw;
1414
+ try {
1415
+ raw = fs.readFileSync(file, 'utf8');
1416
+ }
1417
+ catch {
1418
+ return [];
1419
+ }
1420
+ const healed = [];
1421
+ const updated = raw.replace(/\[mcp_servers\.("?)([^\]"]+)\1\]([\s\S]*?)(?=\n\[|$)/g, (section, _q, serverName, body) => {
1422
+ if (serverName === MANAGED_SERVER_NAME || !body.includes('mcp-gateway'))
1423
+ return section;
1424
+ const newBody = body.replace(/("--agent-name",\s*")([^"]+)(")/, (m, pre, current, post) => {
1425
+ const devMatch = body.match(/"--developer-name",\s*"([^"]+)"/);
1426
+ const devPart = safeIdentityPart(devMatch?.[1] || machineScopedUser());
1427
+ const expected = `${devPart}-${safeIdentityPart(serverName)}`;
1428
+ if (current === expected)
1429
+ return m;
1430
+ const isGenericDefault = GENERIC_CLIENT_AGENT_SUFFIXES.some(suffix => current === `${devPart}-${suffix}`);
1431
+ if (!isGenericDefault)
1432
+ return m;
1433
+ healed.push({ file, server: serverName, from: current, to: expected });
1434
+ return `${pre}${expected}${post}`;
1435
+ });
1436
+ return section.replace(body, newBody);
1437
+ });
1438
+ if (healed.length > 0 && !dryRun) {
1439
+ backupFile(file);
1440
+ fs.writeFileSync(file, updated, 'utf8');
1441
+ }
1442
+ return healed;
1443
+ }
1145
1444
  /**
1146
1445
  * Per-server installs (`install-*-mcp-gateway --server-name foo`) must report the SAME
1147
1446
  * runtime-agnostic agent identity that protect-all/auto-protect produce (`<developer>-<server>`).
@@ -1190,7 +1489,7 @@ function wrapJsonConfigFile(file, gatewayConfig, agentClient, dryRun) {
1190
1489
  if (downstream) {
1191
1490
  const perServer = { ...gatewayConfig, agentClient, agentName: perServerAgentName(gatewayConfig.developerName, agentClient, name) };
1192
1491
  entry.command = nodeExe;
1193
- entry.args = buildGatewayCommandArgs(perServer, downstream.command, downstream.args, INSTALL_GATEWAY_ARGS);
1492
+ entry.args = buildGatewayCommandArgs(perServer, downstream, INSTALL_GATEWAY_ARGS);
1194
1493
  stats.healed.push(name);
1195
1494
  changed = true;
1196
1495
  continue;
@@ -1199,14 +1498,35 @@ function wrapJsonConfigFile(file, gatewayConfig, agentClient, dryRun) {
1199
1498
  stats.skippedManaged.push(name);
1200
1499
  continue;
1201
1500
  }
1501
+ const perServer = { ...gatewayConfig, agentClient, agentName: perServerAgentName(gatewayConfig.developerName, agentClient, name) };
1202
1502
  if (typeof entry.command !== 'string' || !entry.command) {
1203
- 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;
1204
1525
  continue;
1205
1526
  }
1206
1527
  const downstreamCommand = entry.command;
1207
1528
  const downstreamArgs = Array.isArray(entry.args) ? entry.args.map(String) : [];
1208
- const perServer = { ...gatewayConfig, agentClient, agentName: perServerAgentName(gatewayConfig.developerName, agentClient, name) };
1209
- const wrappedArgs = buildGatewayCommandArgs(perServer, downstreamCommand, downstreamArgs, INSTALL_GATEWAY_ARGS);
1529
+ const wrappedArgs = buildGatewayCommandArgs(perServer, { kind: 'stdio', command: downstreamCommand, args: downstreamArgs }, INSTALL_GATEWAY_ARGS);
1210
1530
  entry.command = nodeExe;
1211
1531
  entry.args = wrappedArgs;
1212
1532
  // entry.env and any other fields are preserved as-is.
@@ -1245,8 +1565,19 @@ function unwrapJsonConfigFile(file, dryRun) {
1245
1565
  const downstream = extractWrappedDownstream(entry);
1246
1566
  if (!downstream)
1247
1567
  continue;
1248
- entry.command = downstream.command;
1249
- 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
+ }
1250
1581
  restored.push(name);
1251
1582
  changed = true;
1252
1583
  }
@@ -1357,7 +1688,7 @@ function transformCodexToml(file, mode, gatewayConfig, dryRun) {
1357
1688
  continue;
1358
1689
  }
1359
1690
  const perServer = { ...gatewayConfig, agentClient: 'codex', agentName: perServerAgentName(gatewayConfig.developerName, 'codex', section.name) };
1360
- 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);
1361
1692
  const indent = lines[section.cmdLine].match(/^\s*/)?.[0] || '';
1362
1693
  lines[section.cmdLine] = `${indent}command = ${JSON.stringify(nodeExe)}`;
1363
1694
  const argsLineText = `${indent}args = ${tomlStringArrayLiteral(wrappedArgs)}`;
@@ -1372,7 +1703,8 @@ function transformCodexToml(file, mode, gatewayConfig, dryRun) {
1372
1703
  if (!wrappedAlready)
1373
1704
  continue;
1374
1705
  const downstream = extractWrappedDownstream({ args: section.args });
1375
- if (!downstream)
1706
+ // Codex TOML wraps are always stdio (remote URLs never get wrapped into TOML).
1707
+ if (!downstream || downstream.kind !== 'stdio')
1376
1708
  continue;
1377
1709
  const indent = lines[section.cmdLine].match(/^\s*/)?.[0] || '';
1378
1710
  lines[section.cmdLine] = `${indent}command = ${JSON.stringify(downstream.command)}`;
@@ -1430,6 +1762,15 @@ async function protectAllCommand(args, config) {
1430
1762
  return;
1431
1763
  }
1432
1764
  console.log(`\x1b[2mAuto-searching every known MCP client on this machine — ${files.length} config file(s) found.\x1b[0m\n`);
1765
+ // Converge stale generic gateway identities (old CLI defaults) to the per-server
1766
+ // contract before wrapping, so policy matching works on already-wrapped entries too.
1767
+ const healedIdentities = healGatewayAgentIdentities(files.map(f => f.path), dryRun);
1768
+ if (healedIdentities.length > 0) {
1769
+ console.log(`\x1b[32m✓ ${dryRun ? 'would fix' : 'fixed'} ${healedIdentities.length} stale gateway identit${healedIdentities.length === 1 ? 'y' : 'ies'}:\x1b[0m`);
1770
+ for (const heal of healedIdentities)
1771
+ console.log(` \x1b[2m•\x1b[0m ${heal.server}: ${heal.from} → ${heal.to}`);
1772
+ console.log('');
1773
+ }
1433
1774
  let totalWrapped = 0;
1434
1775
  let totalHealed = 0;
1435
1776
  let totalManaged = 0;
@@ -1523,17 +1864,20 @@ async function installClaudeCodeMcpGatewayCommand(args, config) {
1523
1864
  const nodeExe = process.execPath;
1524
1865
  const serverName = args.serverName || MANAGED_SERVER_NAME;
1525
1866
  const gatewayConfig = applyPerServerAgentName(baseGatewayConfig, args, serverName);
1526
- const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND;
1527
- if (!downstreamCommand)
1528
- throw new Error('Pass --mcp-command for the real MCP server you want to protect.');
1529
- const downstreamArgs = parseArgsList(args.mcpArgs || process.env.FCD_MCP_ARGS);
1867
+ const downstream = resolveDownstreamSpec(args);
1530
1868
  const includeShieldKey = false;
1531
- const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstreamCommand, downstreamArgs, {
1869
+ const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstream, {
1532
1870
  ...INSTALL_GATEWAY_ARGS,
1533
1871
  includeShieldKey,
1534
1872
  });
1535
1873
  const claudeArgs = ['mcp', 'add', '--scope', scope, '--transport', 'stdio', serverName, '--', nodeExe, ...commandArgs];
1536
- let result = (0, child_process_1.spawnSync)('claude', claudeArgs, {
1874
+ // On Windows `claude` is a .cmd shim, so spawnSync needs shell:true — but with shell:true
1875
+ // Node does NOT quote args. Unquoted args with spaces/JSON (e.g. --mcp-args '["C:\\..."]',
1876
+ // --user-objective 'Developer requested...') get word-split by cmd.exe and written mangled
1877
+ // into the config. Quote them ourselves before handing the line to the shell.
1878
+ const shellQuote = (value) => (/[\s"]/.test(value) ? `"${value.replace(/"/g, '\\"')}"` : value);
1879
+ const spawnClaudeArgs = process.platform === 'win32' ? claudeArgs.map(shellQuote) : claudeArgs;
1880
+ let result = (0, child_process_1.spawnSync)('claude', spawnClaudeArgs, {
1537
1881
  stdio: 'inherit',
1538
1882
  shell: process.platform === 'win32',
1539
1883
  });
@@ -1543,7 +1887,7 @@ async function installClaudeCodeMcpGatewayCommand(args, config) {
1543
1887
  shell: process.platform === 'win32',
1544
1888
  });
1545
1889
  if (removeResult.status === 0) {
1546
- result = (0, child_process_1.spawnSync)('claude', claudeArgs, {
1890
+ result = (0, child_process_1.spawnSync)('claude', spawnClaudeArgs, {
1547
1891
  stdio: 'inherit',
1548
1892
  shell: process.platform === 'win32',
1549
1893
  });
@@ -1552,7 +1896,7 @@ async function installClaudeCodeMcpGatewayCommand(args, config) {
1552
1896
  if (result.status === 0) {
1553
1897
  console.log(`AgentGuard MCP Gateway installed for Claude Code (${scope}).`);
1554
1898
  console.log(`Server: ${serverName}`);
1555
- console.log(`Downstream: ${downstreamCommand} ${downstreamArgs.join(' ')}`);
1899
+ console.log(`Downstream: ${downstreamSpecLabel(downstream)}`);
1556
1900
  if (!includeShieldKey && gatewayConfig.shieldKey) {
1557
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.');
1558
1902
  }
@@ -1565,7 +1909,7 @@ async function installClaudeCodeMcpGatewayCommand(args, config) {
1565
1909
  console.log('Claude Code CLI was not available, so project config was written directly.');
1566
1910
  console.log(`Config: ${file}`);
1567
1911
  console.log(`Server: ${serverName}`);
1568
- console.log(`Downstream: ${downstreamCommand} ${downstreamArgs.join(' ')}`);
1912
+ console.log(`Downstream: ${downstreamSpecLabel(downstream)}`);
1569
1913
  if (!includeShieldKey && gatewayConfig.shieldKey) {
1570
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.');
1571
1915
  }
@@ -1584,11 +1928,8 @@ async function installClaudeDesktopMcpGatewayCommand(args, config) {
1584
1928
  const nodeExe = process.execPath;
1585
1929
  const serverName = args.serverName || MANAGED_SERVER_NAME;
1586
1930
  const gatewayConfig = applyPerServerAgentName(baseGatewayConfig, args, serverName);
1587
- const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND;
1588
- if (!downstreamCommand)
1589
- throw new Error('Pass --mcp-command for the real MCP server you want to protect.');
1590
- const downstreamArgs = parseArgsList(args.mcpArgs || process.env.FCD_MCP_ARGS);
1591
- const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstreamCommand, downstreamArgs, INSTALL_GATEWAY_ARGS);
1931
+ const downstream = resolveDownstreamSpec(args);
1932
+ const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstream, INSTALL_GATEWAY_ARGS);
1592
1933
  for (const target of targets) {
1593
1934
  writeMcpServerConfig(target.file, serverName, nodeExe, commandArgs);
1594
1935
  }
@@ -1597,7 +1938,7 @@ async function installClaudeDesktopMcpGatewayCommand(args, config) {
1597
1938
  console.log(`Config: ${target.file} (${target.source})`);
1598
1939
  }
1599
1940
  console.log(`Server: ${serverName}`);
1600
- console.log(`Downstream: ${downstreamCommand} ${downstreamArgs.join(' ')}`);
1941
+ console.log(`Downstream: ${downstreamSpecLabel(downstream)}`);
1601
1942
  console.log('Restart Claude Desktop to load the protected tools.');
1602
1943
  }
1603
1944
  async function installCodexMcpGatewayCommand(args, config) {
@@ -1611,11 +1952,8 @@ async function installCodexMcpGatewayCommand(args, config) {
1611
1952
  const nodeExe = process.execPath;
1612
1953
  const serverName = args.serverName || MANAGED_SERVER_NAME;
1613
1954
  const gatewayConfig = applyPerServerAgentName(baseGatewayConfig, args, serverName);
1614
- const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND;
1615
- if (!downstreamCommand)
1616
- throw new Error('Pass --mcp-command for the real MCP server you want to protect.');
1617
- const downstreamArgs = parseArgsList(args.mcpArgs || process.env.FCD_MCP_ARGS);
1618
- const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstreamCommand, downstreamArgs, INSTALL_GATEWAY_ARGS);
1955
+ const downstream = resolveDownstreamSpec(args);
1956
+ const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstream, INSTALL_GATEWAY_ARGS);
1619
1957
  writeCodexMcpServer(file, serverName, nodeExe, commandArgs);
1620
1958
  console.log(`AgentGuard MCP Gateway installed for Codex (${projectScope ? 'project' : 'global'}).`);
1621
1959
  console.log(`Config: ${file}`);
@@ -1633,11 +1971,8 @@ async function installGeminiMcpGatewayCommand(args, config) {
1633
1971
  const nodeExe = process.execPath;
1634
1972
  const serverName = args.serverName || MANAGED_SERVER_NAME;
1635
1973
  const gatewayConfig = applyPerServerAgentName(baseGatewayConfig, args, serverName);
1636
- const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND;
1637
- if (!downstreamCommand)
1638
- throw new Error('Pass --mcp-command for the real MCP server you want to protect.');
1639
- const downstreamArgs = parseArgsList(args.mcpArgs || process.env.FCD_MCP_ARGS);
1640
- const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstreamCommand, downstreamArgs, INSTALL_GATEWAY_ARGS);
1974
+ const downstream = resolveDownstreamSpec(args);
1975
+ const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstream, INSTALL_GATEWAY_ARGS);
1641
1976
  writeJsonMcpServer(file, serverName, nodeExe, commandArgs);
1642
1977
  console.log(`AgentGuard MCP Gateway installed for Gemini CLI (${projectScope ? 'project' : 'user'}).`);
1643
1978
  console.log(`Config: ${file}`);
@@ -1654,11 +1989,8 @@ async function installWindsurfMcpGatewayCommand(args, config) {
1654
1989
  const nodeExe = process.execPath;
1655
1990
  const serverName = args.serverName || MANAGED_SERVER_NAME;
1656
1991
  const gatewayConfig = applyPerServerAgentName(baseGatewayConfig, args, serverName);
1657
- const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND;
1658
- if (!downstreamCommand)
1659
- throw new Error('Pass --mcp-command for the real MCP server you want to protect.');
1660
- const downstreamArgs = parseArgsList(args.mcpArgs || process.env.FCD_MCP_ARGS);
1661
- const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstreamCommand, downstreamArgs, INSTALL_GATEWAY_ARGS);
1992
+ const downstream = resolveDownstreamSpec(args);
1993
+ const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstream, INSTALL_GATEWAY_ARGS);
1662
1994
  writeJsonMcpServer(file, serverName, nodeExe, commandArgs);
1663
1995
  console.log('AgentGuard MCP Gateway installed for Windsurf.');
1664
1996
  console.log(`Config: ${file}`);
@@ -1675,11 +2007,8 @@ async function installVscodeMcpGatewayCommand(args, config) {
1675
2007
  const nodeExe = process.execPath;
1676
2008
  const serverName = args.serverName || MANAGED_SERVER_NAME;
1677
2009
  const gatewayConfig = applyPerServerAgentName(baseGatewayConfig, args, serverName);
1678
- const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND;
1679
- if (!downstreamCommand)
1680
- throw new Error('Pass --mcp-command for the real MCP server you want to protect.');
1681
- const downstreamArgs = parseArgsList(args.mcpArgs || process.env.FCD_MCP_ARGS);
1682
- const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstreamCommand, downstreamArgs, INSTALL_GATEWAY_ARGS);
2010
+ const downstream = resolveDownstreamSpec(args);
2011
+ const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstream, INSTALL_GATEWAY_ARGS);
1683
2012
  const targets = [];
1684
2013
  if (projectScope)
1685
2014
  targets.push(vscodeMcpPath(true));
package/dist/index.js CHANGED
@@ -342,7 +342,9 @@ function printHelp() {
342
342
  $ fullcourtdefense install-claude-code-mcp-gateway --scope local --mcp-command npm --mcp-args "run mcp"
343
343
  $ fullcourtdefense install-claude-code-mcp-gateway --scope project --mcp-command npm --mcp-args "run mcp"
344
344
  $ fullcourtdefense install-claude-desktop-mcp-gateway --mcp-command npm --mcp-args "run mcp"
345
+ $ fullcourtdefense install-cursor-mcp-gateway --server-name my-remote --mcp-url https://mcp.company.com/mcp
345
346
  $ fullcourtdefense mcp-gateway --mcp-command npm --mcp-args "run mcp"
347
+ $ fullcourtdefense mcp-gateway --mcp-url https://mcp.company.com/mcp --mcp-headers "{\"Authorization\":\"Bearer TOKEN\"}"
346
348
  $ fullcourtdefense login --token <fleet-enrollment-token>
347
349
  $ fullcourtdefense install-all
348
350
  $ fullcourtdefense install-all --auto-protect true --upload
@@ -366,6 +368,8 @@ async function main() {
366
368
  serverName: flags['server-name'],
367
369
  mcpCommand: flags['mcp-command'],
368
370
  mcpArgs: flags['mcp-args'],
371
+ mcpUrl: flags['mcp-url'],
372
+ mcpHeaders: flags['mcp-headers'],
369
373
  agentName: flags['agent-name'],
370
374
  developerName: flags['developer-name'],
371
375
  shieldId: flags['shield-id'],
@@ -385,6 +389,8 @@ async function main() {
385
389
  const buildGatewayArgs = () => ({
386
390
  mcpCommand: flags['mcp-command'],
387
391
  mcpArgs: flags['mcp-args'],
392
+ mcpUrl: flags['mcp-url'],
393
+ mcpHeaders: flags['mcp-headers'],
388
394
  agentName: flags['agent-name'],
389
395
  agentClient: flags['agent-client'],
390
396
  developerName: flags['developer-name'],
package/dist/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.14.11"
2
+ "version": "1.14.13"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullcourtdefense-cli",
3
- "version": "1.14.11",
3
+ "version": "1.14.13",
4
4
  "description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -24,6 +24,8 @@
24
24
  "test:agent-file-posture": "npm run build && node scripts/test-agent-file-posture.js",
25
25
  "test:realworld-posture": "npm run build && node scripts/test-realworld-posture-fixtures.js",
26
26
  "test:discover-stdio-mcp": "npm run build && node scripts/test-discover-stdio-mcp-tools.js",
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",
27
29
  "test:posture-blast": "npm run build && node scripts/test-posture-blast-radius.js",
28
30
  "test:ping-e2e": "npm run build && node scripts/test-ping-e2e.js",
29
31
  "prepublishOnly": "npm run build"