fullcourtdefense-cli 1.1.14 → 1.1.15

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.
@@ -44,6 +44,7 @@ const SECRET_KEY = /key|token|secret|password|credential|api[_-]?key/i;
44
44
  function candidateConfigPaths(cwd, extra) {
45
45
  const home = os.homedir();
46
46
  const appData = process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
47
+ const localAppData = process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
47
48
  const xdg = process.env.XDG_CONFIG_HOME || path.join(home, '.config');
48
49
  const list = [
49
50
  // Cursor
@@ -64,6 +65,22 @@ function candidateConfigPaths(cwd, extra) {
64
65
  { path: path.join(cwd, '.mcp.json'), source: 'Repo (.mcp.json)' },
65
66
  { path: path.join(cwd, 'mcp.json'), source: 'Repo (mcp.json)' },
66
67
  ];
68
+ if (process.platform === 'win32') {
69
+ const packagesDir = path.join(localAppData, 'Packages');
70
+ try {
71
+ for (const packageName of fs.readdirSync(packagesDir)) {
72
+ if (!/^Claude_/i.test(packageName))
73
+ continue;
74
+ list.push({
75
+ path: path.join(packagesDir, packageName, 'LocalCache', 'Roaming', 'Claude', 'claude_desktop_config.json'),
76
+ source: 'Claude Desktop (win MSIX)',
77
+ });
78
+ }
79
+ }
80
+ catch {
81
+ // Missing Packages directory is normal on non-Store installs.
82
+ }
83
+ }
67
84
  if (extra)
68
85
  list.push({ path: path.resolve(extra), source: 'Custom path' });
69
86
  return list;
@@ -29,7 +29,14 @@ export interface InstallClaudeDesktopMcpGatewayArgs extends McpGatewayArgs {
29
29
  configPath?: string;
30
30
  serverName?: string;
31
31
  }
32
+ export interface InstallMcpGatewayArgs extends McpGatewayArgs {
33
+ clients?: string;
34
+ cursorProject?: string;
35
+ claudeCodeScope?: string;
36
+ serverName?: string;
37
+ }
32
38
  export declare function mcpGatewayCommand(args: McpGatewayArgs, config: BotGuardConfig): Promise<void>;
33
39
  export declare function installCursorMcpGatewayCommand(args: InstallCursorMcpGatewayArgs, config: BotGuardConfig): Promise<void>;
40
+ export declare function installMcpGatewayCommand(args: InstallMcpGatewayArgs, config: BotGuardConfig): Promise<void>;
34
41
  export declare function installClaudeCodeMcpGatewayCommand(args: InstallClaudeCodeMcpGatewayArgs, config: BotGuardConfig): Promise<void>;
35
42
  export declare function installClaudeDesktopMcpGatewayCommand(args: InstallClaudeDesktopMcpGatewayArgs, config: BotGuardConfig): Promise<void>;
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.mcpGatewayCommand = mcpGatewayCommand;
37
37
  exports.installCursorMcpGatewayCommand = installCursorMcpGatewayCommand;
38
+ exports.installMcpGatewayCommand = installMcpGatewayCommand;
38
39
  exports.installClaudeCodeMcpGatewayCommand = installClaudeCodeMcpGatewayCommand;
39
40
  exports.installClaudeDesktopMcpGatewayCommand = installClaudeDesktopMcpGatewayCommand;
40
41
  const fs = __importStar(require("fs"));
@@ -663,6 +664,16 @@ function writeMcpServerConfig(file, serverName, nodeExe, commandArgs) {
663
664
  fs.mkdirSync(path.dirname(file), { recursive: true });
664
665
  fs.writeFileSync(file, `${JSON.stringify(json, null, 2)}\n`, 'utf8');
665
666
  }
667
+ function dedupePaths(candidates) {
668
+ const seen = new Set();
669
+ return candidates.filter(candidate => {
670
+ const key = path.resolve(candidate.file).toLowerCase();
671
+ if (seen.has(key))
672
+ return false;
673
+ seen.add(key);
674
+ return true;
675
+ });
676
+ }
666
677
  function claudeDesktopMcpPath() {
667
678
  const home = os.homedir();
668
679
  if (process.platform === 'win32') {
@@ -675,11 +686,128 @@ function claudeDesktopMcpPath() {
675
686
  const xdg = process.env.XDG_CONFIG_HOME || path.join(home, '.config');
676
687
  return path.join(xdg, 'Claude', 'claude_desktop_config.json');
677
688
  }
689
+ function claudeDesktopMsixCandidates() {
690
+ if (process.platform !== 'win32')
691
+ return [];
692
+ const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
693
+ const packagesDir = path.join(localAppData, 'Packages');
694
+ let packageNames = [];
695
+ try {
696
+ packageNames = fs.readdirSync(packagesDir)
697
+ .filter(name => /^Claude_/i.test(name));
698
+ }
699
+ catch {
700
+ return [];
701
+ }
702
+ return packageNames.map(name => {
703
+ const file = path.join(packagesDir, name, 'LocalCache', 'Roaming', 'Claude', 'claude_desktop_config.json');
704
+ return {
705
+ file,
706
+ source: 'Claude Desktop (Microsoft Store/MSIX)',
707
+ exists: fs.existsSync(file),
708
+ };
709
+ });
710
+ }
711
+ function claudeDesktopMcpCandidates() {
712
+ const documented = claudeDesktopMcpPath();
713
+ const normal = {
714
+ file: documented,
715
+ source: process.platform === 'win32'
716
+ ? 'Claude Desktop (Windows documented AppData)'
717
+ : process.platform === 'darwin'
718
+ ? 'Claude Desktop (macOS)'
719
+ : 'Claude Desktop (Linux/XDG)',
720
+ exists: fs.existsSync(documented),
721
+ };
722
+ // Store/MSIX Claude Desktop reads a virtualized AppData path. Prefer it when present.
723
+ return dedupePaths([
724
+ ...claudeDesktopMsixCandidates(),
725
+ normal,
726
+ ]);
727
+ }
728
+ function claudeDesktopInstallTargets(configPath) {
729
+ if (configPath) {
730
+ const file = path.resolve(configPath);
731
+ return [{ file, source: 'Claude Desktop (custom config path)', exists: fs.existsSync(file) }];
732
+ }
733
+ const candidates = claudeDesktopMcpCandidates();
734
+ const existing = candidates.filter(candidate => candidate.exists);
735
+ if (existing.length > 0)
736
+ return existing;
737
+ const msix = candidates.find(candidate => candidate.source.includes('MSIX'));
738
+ return [msix || candidates[candidates.length - 1] || {
739
+ file: claudeDesktopMcpPath(),
740
+ source: 'Claude Desktop',
741
+ exists: false,
742
+ }];
743
+ }
678
744
  function normalizeClaudeCodeScope(scope) {
679
745
  if (scope === 'project' || scope === 'user' || scope === 'local')
680
746
  return scope;
681
747
  return 'local';
682
748
  }
749
+ function parseClientList(value) {
750
+ const raw = (value || 'auto').trim().toLowerCase();
751
+ if (!raw || raw === 'auto')
752
+ return 'auto';
753
+ const aliases = {
754
+ cursor: 'cursor',
755
+ 'claude-code': 'claude-code',
756
+ claude_code: 'claude-code',
757
+ code: 'claude-code',
758
+ 'claude-desktop': 'claude-desktop',
759
+ claude_desktop: 'claude-desktop',
760
+ desktop: 'claude-desktop',
761
+ };
762
+ const requested = raw === 'all'
763
+ ? ['cursor', 'claude-code', 'claude-desktop']
764
+ : raw.split(',').map(part => part.trim()).filter(Boolean);
765
+ const clients = requested.map(client => aliases[client]);
766
+ const unknown = requested.filter((_, index) => !clients[index]);
767
+ if (unknown.length > 0)
768
+ throw new Error(`Unknown client(s): ${unknown.join(', ')}. Use auto, all, cursor, claude-code, claude-desktop.`);
769
+ return Array.from(new Set(clients));
770
+ }
771
+ function commandAvailable(command) {
772
+ const result = (0, child_process_1.spawnSync)(process.platform === 'win32' ? 'where' : 'command', process.platform === 'win32' ? [command] : ['-v', command], {
773
+ stdio: 'ignore',
774
+ shell: process.platform !== 'win32',
775
+ });
776
+ return result.status === 0;
777
+ }
778
+ function detectInstallClients(cursorProject) {
779
+ const clients = [];
780
+ const cursorGlobal = path.join(os.homedir(), '.cursor');
781
+ const cursorProjectDir = path.join(process.cwd(), '.cursor');
782
+ if (fs.existsSync(cursorGlobal) || fs.existsSync(cursorProjectDir) || cursorProject)
783
+ clients.push('cursor');
784
+ if (commandAvailable('claude'))
785
+ clients.push('claude-code');
786
+ if (claudeDesktopMcpCandidates().some(candidate => candidate.exists) || fs.existsSync(path.dirname(claudeDesktopMcpPath()))) {
787
+ clients.push('claude-desktop');
788
+ }
789
+ return clients;
790
+ }
791
+ function clientInstallArgs(args) {
792
+ return {
793
+ mcpCommand: args.mcpCommand,
794
+ mcpArgs: args.mcpArgs,
795
+ agentName: args.agentName,
796
+ agentClient: args.agentClient,
797
+ developerName: args.developerName,
798
+ shieldId: args.shieldId,
799
+ shieldKey: args.shieldKey,
800
+ apiUrl: args.apiUrl,
801
+ environment: args.environment,
802
+ userObjective: args.userObjective,
803
+ authority: args.authority,
804
+ approvalMode: args.approvalMode,
805
+ approvalTimeoutMs: args.approvalTimeoutMs,
806
+ approvalPollMs: args.approvalPollMs,
807
+ scanResponse: args.scanResponse,
808
+ failClosed: args.failClosed,
809
+ };
810
+ }
683
811
  async function installCursorMcpGatewayCommand(args, config) {
684
812
  const gatewayConfig = resolveGatewayConfig(args, config, {
685
813
  agentClient: 'cursor',
@@ -702,6 +830,45 @@ async function installCursorMcpGatewayCommand(args, config) {
702
830
  console.log(`Downstream: ${downstreamCommand} ${downstreamArgs.join(' ')}`);
703
831
  console.log('Restart Cursor or reload MCP servers to use the protected tools.');
704
832
  }
833
+ async function installMcpGatewayCommand(args, config) {
834
+ const cursorProject = args.cursorProject === 'true';
835
+ const selection = parseClientList(args.clients);
836
+ const clients = selection === 'auto' ? detectInstallClients(cursorProject) : selection;
837
+ if (clients.length === 0) {
838
+ throw new Error('No supported MCP clients detected. Re-run with --clients all or a comma list such as cursor,claude-desktop.');
839
+ }
840
+ const baseArgs = clientInstallArgs(args);
841
+ const successes = [];
842
+ const failures = [];
843
+ for (const client of clients) {
844
+ try {
845
+ if (client === 'cursor') {
846
+ await installCursorMcpGatewayCommand({ ...baseArgs, project: String(cursorProject), serverName: args.serverName }, config);
847
+ }
848
+ else if (client === 'claude-code') {
849
+ await installClaudeCodeMcpGatewayCommand({ ...baseArgs, scope: args.claudeCodeScope, serverName: args.serverName }, config);
850
+ }
851
+ else {
852
+ await installClaudeDesktopMcpGatewayCommand({ ...baseArgs, serverName: args.serverName }, config);
853
+ }
854
+ successes.push(client);
855
+ }
856
+ catch (error) {
857
+ failures.push(`${client}: ${error instanceof Error ? error.message : String(error)}`);
858
+ }
859
+ }
860
+ console.log('');
861
+ console.log('AgentGuard one-install summary:');
862
+ console.log(`Installed: ${successes.length > 0 ? successes.join(', ') : 'none'}`);
863
+ if (failures.length > 0) {
864
+ console.log('Failed:');
865
+ for (const failure of failures)
866
+ console.log(`- ${failure}`);
867
+ }
868
+ console.log(`Runtime identity: ${detectedDeveloperName(args)}`);
869
+ console.log('Restart/reload each client to use the protected tools.');
870
+ console.log('Smoke test prompt: "Use lookup_customer to look up customer C-1001."');
871
+ }
705
872
  async function installClaudeCodeMcpGatewayCommand(args, config) {
706
873
  const gatewayConfig = resolveGatewayConfig(args, config, {
707
874
  agentClient: 'claude-code',
@@ -753,7 +920,7 @@ async function installClaudeDesktopMcpGatewayCommand(args, config) {
753
920
  agentNamePrefix: 'claude-desktop',
754
921
  userObjective: 'Developer requested this action from Claude Desktop.',
755
922
  });
756
- const file = args.configPath ? path.resolve(args.configPath) : claudeDesktopMcpPath();
923
+ const targets = claudeDesktopInstallTargets(args.configPath);
757
924
  const nodeExe = process.execPath;
758
925
  const serverName = args.serverName || MANAGED_SERVER_NAME;
759
926
  const downstreamCommand = args.mcpCommand || process.env.FCD_MCP_COMMAND;
@@ -761,9 +928,13 @@ async function installClaudeDesktopMcpGatewayCommand(args, config) {
761
928
  throw new Error('Pass --mcp-command for the real MCP server you want to protect.');
762
929
  const downstreamArgs = parseArgsList(args.mcpArgs || process.env.FCD_MCP_ARGS);
763
930
  const commandArgs = buildGatewayCommandArgs(gatewayConfig, downstreamCommand, downstreamArgs);
764
- writeMcpServerConfig(file, serverName, nodeExe, commandArgs);
931
+ for (const target of targets) {
932
+ writeMcpServerConfig(target.file, serverName, nodeExe, commandArgs);
933
+ }
765
934
  console.log('AgentGuard MCP Gateway installed for Claude Desktop.');
766
- console.log(`Config: ${file}`);
935
+ for (const target of targets) {
936
+ console.log(`Config: ${target.file} (${target.source})`);
937
+ }
767
938
  console.log(`Server: ${serverName}`);
768
939
  console.log(`Downstream: ${downstreamCommand} ${downstreamArgs.join(' ')}`);
769
940
  console.log('Restart Claude Desktop to load the protected tools.');
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ const discover_1 = require("./commands/discover");
11
11
  const hook_1 = require("./commands/hook");
12
12
  const installCursorHook_1 = require("./commands/installCursorHook");
13
13
  const mcpGateway_1 = require("./commands/mcpGateway");
14
- const VERSION = '1.1.14';
14
+ const VERSION = '1.1.15';
15
15
  function parseArgs(argv) {
16
16
  const flags = {};
17
17
  let command = '';
@@ -73,6 +73,9 @@ function printHelp() {
73
73
  (shell, MCP) on this machine is scanned by your Shield.
74
74
  uninstall-cursor-hook
75
75
  Removes the FullCourtDefense Cursor hook entries.
76
+ install-mcp-gateway
77
+ One command to install the AgentGuard MCP Gateway into detected
78
+ clients (Cursor, Claude Code, Claude Desktop).
76
79
  mcp-gateway
77
80
  Runs a local MCP gateway that checks AgentGuard runtime/action
78
81
  policies before forwarding tool calls to a real MCP server.
@@ -148,9 +151,13 @@ function printHelp() {
148
151
  --mcp-args <args> MCP server args/path, for example ./server.js
149
152
  --mcp-tool <tool> MCP tool to test, or all
150
153
  --server-name <name> MCP gateway server name for client installers
154
+ --clients <list> Gateway install clients: auto, all, cursor,
155
+ claude-code, claude-desktop
151
156
  --developer-name <name> Runtime identity for user-scoped policies (auto-detected if omitted)
152
157
  --agent-name <name> Runtime agent name (auto-generated from identity + client if omitted)
153
158
  --scope <scope> Claude Code MCP scope: local, project, or user
159
+ --claude-code-scope <s> Claude Code scope for install-mcp-gateway
160
+ --cursor-project <bool> Cursor project config for install-mcp-gateway
154
161
  --config-path <path> Claude Desktop config override path
155
162
  --mcp-auth-type <type> HTTP MCP auth: none, bearer, basic, api-key
156
163
  --mcp-token <token> HTTP MCP bearer token
@@ -198,6 +205,8 @@ function printHelp() {
198
205
  $ fullcourtdefense install-cursor-hook --events prompt,shell,mcp,file
199
206
  $ fullcourtdefense install-cursor-hook --project true # this repo only
200
207
  $ fullcourtdefense uninstall-cursor-hook
208
+ $ fullcourtdefense install-mcp-gateway --clients auto --shield-id <id> --shield-key <key> --mcp-command npm --mcp-args "run mcp"
209
+ $ fullcourtdefense install-mcp-gateway --clients all --mcp-command node --mcp-args ./server.js
201
210
  $ fullcourtdefense install-cursor-mcp-gateway --project true --shield-id <id> --shield-key <key> --mcp-command npm --mcp-args "run mcp"
202
211
  $ fullcourtdefense install-claude-code-mcp-gateway --scope local --shield-id <id> --shield-key <key> --mcp-command npm --mcp-args "run mcp"
203
212
  $ fullcourtdefense install-claude-code-mcp-gateway --scope project --shield-id <id> --mcp-command npm --mcp-args "run mcp"
@@ -368,6 +377,31 @@ async function main() {
368
377
  await (0, mcpGateway_1.mcpGatewayCommand)(args, config);
369
378
  break;
370
379
  }
380
+ case 'install-mcp-gateway': {
381
+ const args = {
382
+ clients: flags.clients,
383
+ cursorProject: flags['cursor-project'],
384
+ claudeCodeScope: flags['claude-code-scope'] || flags.scope,
385
+ serverName: flags['server-name'],
386
+ mcpCommand: flags['mcp-command'],
387
+ mcpArgs: flags['mcp-args'],
388
+ agentName: flags['agent-name'],
389
+ developerName: flags['developer-name'],
390
+ shieldId: flags['shield-id'],
391
+ shieldKey: flags['shield-key'],
392
+ apiUrl: flags['api-url'],
393
+ environment: flags.environment,
394
+ userObjective: flags['user-objective'],
395
+ authority: flags.authority,
396
+ approvalMode: flags['approval-mode'],
397
+ approvalTimeoutMs: flags['approval-timeout-ms'],
398
+ approvalPollMs: flags['approval-poll-ms'],
399
+ scanResponse: flags['scan-response'],
400
+ failClosed: flags['fail-closed'],
401
+ };
402
+ await (0, mcpGateway_1.installMcpGatewayCommand)(args, config);
403
+ break;
404
+ }
371
405
  case 'install-cursor-mcp-gateway': {
372
406
  const args = {
373
407
  project: flags.project,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullcourtdefense-cli",
3
- "version": "1.1.14",
3
+ "version": "1.1.15",
4
4
  "description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {