fullcourtdefense-cli 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/mcpGateway.d.ts +6 -0
- package/dist/commands/mcpGateway.js +356 -0
- package/dist/index.js +26 -0
- package/dist/version.json +1 -1
- package/package.json +1 -1
|
@@ -58,6 +58,12 @@ export interface InstallMcpGatewayArgs extends McpGatewayArgs {
|
|
|
58
58
|
export declare function mcpGatewayCommand(args: McpGatewayArgs, config: BotGuardConfig): Promise<void>;
|
|
59
59
|
export declare function installCursorMcpGatewayCommand(args: InstallCursorMcpGatewayArgs, config: BotGuardConfig): Promise<void>;
|
|
60
60
|
export declare function installMcpGatewayCommand(args: InstallMcpGatewayArgs, config: BotGuardConfig): Promise<void>;
|
|
61
|
+
export interface ProtectAllArgs extends McpGatewayArgs {
|
|
62
|
+
dryRun?: string;
|
|
63
|
+
config?: string;
|
|
64
|
+
}
|
|
65
|
+
export declare function protectAllCommand(args: ProtectAllArgs, config: BotGuardConfig): Promise<void>;
|
|
66
|
+
export declare function unprotectAllCommand(args: ProtectAllArgs, config: BotGuardConfig): Promise<void>;
|
|
61
67
|
export declare function installClaudeCodeMcpGatewayCommand(args: InstallClaudeCodeMcpGatewayArgs, config: BotGuardConfig): Promise<void>;
|
|
62
68
|
export declare function installClaudeDesktopMcpGatewayCommand(args: InstallClaudeDesktopMcpGatewayArgs, config: BotGuardConfig): Promise<void>;
|
|
63
69
|
export declare function installCodexMcpGatewayCommand(args: InstallCodexMcpGatewayArgs, config: BotGuardConfig): Promise<void>;
|
|
@@ -37,6 +37,8 @@ exports.ALL_GATEWAY_CLIENTS = void 0;
|
|
|
37
37
|
exports.mcpGatewayCommand = mcpGatewayCommand;
|
|
38
38
|
exports.installCursorMcpGatewayCommand = installCursorMcpGatewayCommand;
|
|
39
39
|
exports.installMcpGatewayCommand = installMcpGatewayCommand;
|
|
40
|
+
exports.protectAllCommand = protectAllCommand;
|
|
41
|
+
exports.unprotectAllCommand = unprotectAllCommand;
|
|
40
42
|
exports.installClaudeCodeMcpGatewayCommand = installClaudeCodeMcpGatewayCommand;
|
|
41
43
|
exports.installClaudeDesktopMcpGatewayCommand = installClaudeDesktopMcpGatewayCommand;
|
|
42
44
|
exports.installCodexMcpGatewayCommand = installCodexMcpGatewayCommand;
|
|
@@ -940,6 +942,360 @@ async function installMcpGatewayCommand(args, config) {
|
|
|
940
942
|
console.log('Add --upload to refresh AI Fleet after install.');
|
|
941
943
|
}
|
|
942
944
|
}
|
|
945
|
+
const CLIENT_KEY_TO_AGENT = {
|
|
946
|
+
cursor: 'cursor',
|
|
947
|
+
claude_code: 'claude-code',
|
|
948
|
+
claude_desktop: 'claude-desktop',
|
|
949
|
+
codex: 'codex',
|
|
950
|
+
gemini_cli: 'gemini-cli',
|
|
951
|
+
windsurf: 'windsurf',
|
|
952
|
+
vscode: 'vscode',
|
|
953
|
+
};
|
|
954
|
+
function newWrapStats() { return { wrapped: [], skippedManaged: [], skippedRemote: [] }; }
|
|
955
|
+
/** True when an MCP server entry already runs through the AgentGuard gateway. */
|
|
956
|
+
function isGatewayWrappedEntry(entry) {
|
|
957
|
+
if (!entry || typeof entry !== 'object')
|
|
958
|
+
return false;
|
|
959
|
+
const args = Array.isArray(entry.args) ? entry.args.map(String) : [];
|
|
960
|
+
return args.includes('mcp-gateway');
|
|
961
|
+
}
|
|
962
|
+
/** Recover the original downstream command+args from a wrapped entry's args. */
|
|
963
|
+
function extractWrappedDownstream(entry) {
|
|
964
|
+
const args = Array.isArray(entry?.args) ? entry.args.map(String) : [];
|
|
965
|
+
const ci = args.indexOf('--mcp-command');
|
|
966
|
+
if (ci === -1 || ci + 1 >= args.length)
|
|
967
|
+
return null;
|
|
968
|
+
const command = args[ci + 1];
|
|
969
|
+
let downstreamArgs = [];
|
|
970
|
+
const ai = args.indexOf('--mcp-args');
|
|
971
|
+
if (ai !== -1 && args[ai + 1]) {
|
|
972
|
+
try {
|
|
973
|
+
const parsed = JSON.parse(args[ai + 1]);
|
|
974
|
+
if (Array.isArray(parsed))
|
|
975
|
+
downstreamArgs = parsed.map(String);
|
|
976
|
+
}
|
|
977
|
+
catch { /* leave empty */ }
|
|
978
|
+
}
|
|
979
|
+
return { command, args: downstreamArgs };
|
|
980
|
+
}
|
|
981
|
+
/** All server maps inside a parsed JSON config, across every known client shape. */
|
|
982
|
+
function collectJsonServerMaps(json) {
|
|
983
|
+
const maps = [];
|
|
984
|
+
const pushIfMap = (m) => { if (m && typeof m === 'object' && !Array.isArray(m))
|
|
985
|
+
maps.push(m); };
|
|
986
|
+
if (json && typeof json === 'object') {
|
|
987
|
+
pushIfMap(json.mcpServers); // Cursor, Claude, Gemini, Windsurf, Kiro, Codex mcp.json
|
|
988
|
+
pushIfMap(json.servers); // VS Code .vscode/mcp.json
|
|
989
|
+
if (json.mcp && typeof json.mcp === 'object')
|
|
990
|
+
pushIfMap(json.mcp.servers); // VS Code settings.json
|
|
991
|
+
if (json.projects && typeof json.projects === 'object') {
|
|
992
|
+
for (const proj of Object.values(json.projects))
|
|
993
|
+
pushIfMap(proj?.mcpServers); // Claude Code .claude.json
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
return maps;
|
|
997
|
+
}
|
|
998
|
+
function backupFile(file) {
|
|
999
|
+
try {
|
|
1000
|
+
const bak = `${file}.fcd-backup-${Date.now()}`;
|
|
1001
|
+
fs.copyFileSync(file, bak);
|
|
1002
|
+
}
|
|
1003
|
+
catch { /* best effort */ }
|
|
1004
|
+
}
|
|
1005
|
+
function perServerAgentName(developerName, agentClient, serverName) {
|
|
1006
|
+
return `${safeIdentityPart(developerName)}-${agentClient}-${safeIdentityPart(serverName)}`;
|
|
1007
|
+
}
|
|
1008
|
+
function wrapJsonConfigFile(file, gatewayConfig, agentClient, dryRun) {
|
|
1009
|
+
const stats = newWrapStats();
|
|
1010
|
+
let json;
|
|
1011
|
+
try {
|
|
1012
|
+
json = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
1013
|
+
}
|
|
1014
|
+
catch {
|
|
1015
|
+
return null; // not JSON / unreadable — skip silently
|
|
1016
|
+
}
|
|
1017
|
+
if (!json || typeof json !== 'object')
|
|
1018
|
+
return null;
|
|
1019
|
+
const maps = collectJsonServerMaps(json);
|
|
1020
|
+
if (maps.length === 0)
|
|
1021
|
+
return stats;
|
|
1022
|
+
const nodeExe = process.execPath;
|
|
1023
|
+
let changed = false;
|
|
1024
|
+
for (const map of maps) {
|
|
1025
|
+
for (const [name, entry] of Object.entries(map)) {
|
|
1026
|
+
if (!entry || typeof entry !== 'object')
|
|
1027
|
+
continue;
|
|
1028
|
+
if (name === MANAGED_SERVER_NAME || isGatewayWrappedEntry(entry)) {
|
|
1029
|
+
stats.skippedManaged.push(name);
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
1032
|
+
if (typeof entry.command !== 'string' || !entry.command) {
|
|
1033
|
+
stats.skippedRemote.push(name);
|
|
1034
|
+
continue;
|
|
1035
|
+
}
|
|
1036
|
+
const downstreamCommand = entry.command;
|
|
1037
|
+
const downstreamArgs = Array.isArray(entry.args) ? entry.args.map(String) : [];
|
|
1038
|
+
const perServer = { ...gatewayConfig, agentName: perServerAgentName(gatewayConfig.developerName, agentClient, name) };
|
|
1039
|
+
const wrappedArgs = buildGatewayCommandArgs(perServer, downstreamCommand, downstreamArgs, INSTALL_GATEWAY_ARGS);
|
|
1040
|
+
entry.command = nodeExe;
|
|
1041
|
+
entry.args = wrappedArgs;
|
|
1042
|
+
// entry.env and any other fields are preserved as-is.
|
|
1043
|
+
stats.wrapped.push(name);
|
|
1044
|
+
changed = true;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
if (changed && !dryRun) {
|
|
1048
|
+
backupFile(file);
|
|
1049
|
+
fs.writeFileSync(file, `${JSON.stringify(json, null, 2)}\n`, 'utf8');
|
|
1050
|
+
}
|
|
1051
|
+
return stats;
|
|
1052
|
+
}
|
|
1053
|
+
function unwrapJsonConfigFile(file, dryRun) {
|
|
1054
|
+
let json;
|
|
1055
|
+
try {
|
|
1056
|
+
json = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
1057
|
+
}
|
|
1058
|
+
catch {
|
|
1059
|
+
return null;
|
|
1060
|
+
}
|
|
1061
|
+
if (!json || typeof json !== 'object')
|
|
1062
|
+
return null;
|
|
1063
|
+
const maps = collectJsonServerMaps(json);
|
|
1064
|
+
const restored = [];
|
|
1065
|
+
let changed = false;
|
|
1066
|
+
for (const map of maps) {
|
|
1067
|
+
for (const [name, entry] of Object.entries(map)) {
|
|
1068
|
+
if (name === MANAGED_SERVER_NAME) {
|
|
1069
|
+
delete map[name];
|
|
1070
|
+
changed = true;
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
if (!isGatewayWrappedEntry(entry))
|
|
1074
|
+
continue;
|
|
1075
|
+
const downstream = extractWrappedDownstream(entry);
|
|
1076
|
+
if (!downstream)
|
|
1077
|
+
continue;
|
|
1078
|
+
entry.command = downstream.command;
|
|
1079
|
+
entry.args = downstream.args;
|
|
1080
|
+
restored.push(name);
|
|
1081
|
+
changed = true;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
if (changed && !dryRun) {
|
|
1085
|
+
backupFile(file);
|
|
1086
|
+
fs.writeFileSync(file, `${JSON.stringify(json, null, 2)}\n`, 'utf8');
|
|
1087
|
+
}
|
|
1088
|
+
return { restored };
|
|
1089
|
+
}
|
|
1090
|
+
/** Parse a single-line TOML scalar string value: command = "x". */
|
|
1091
|
+
function parseTomlInlineString(line) {
|
|
1092
|
+
const m = line.match(/=\s*"((?:[^"\\]|\\.)*)"/);
|
|
1093
|
+
if (!m)
|
|
1094
|
+
return undefined;
|
|
1095
|
+
try {
|
|
1096
|
+
return JSON.parse(`"${m[1]}"`);
|
|
1097
|
+
}
|
|
1098
|
+
catch {
|
|
1099
|
+
return m[1];
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
/** Parse a single-line TOML string array: args = ["a", "b"]. */
|
|
1103
|
+
function parseTomlInlineStringArray(line) {
|
|
1104
|
+
const m = line.match(/=\s*(\[.*\])\s*$/);
|
|
1105
|
+
if (!m)
|
|
1106
|
+
return undefined;
|
|
1107
|
+
try {
|
|
1108
|
+
const parsed = JSON.parse(m[1]);
|
|
1109
|
+
if (Array.isArray(parsed))
|
|
1110
|
+
return parsed.map(String);
|
|
1111
|
+
}
|
|
1112
|
+
catch { /* not single-line */ }
|
|
1113
|
+
return undefined;
|
|
1114
|
+
}
|
|
1115
|
+
function tomlStringArrayLiteral(values) {
|
|
1116
|
+
return `[${values.map(v => JSON.stringify(v)).join(', ')}]`;
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* Wrap/unwrap Codex config.toml in place, line by line, so [mcp_servers.NAME.env]
|
|
1120
|
+
* subtables and unrelated config are preserved. Only single-line command/args
|
|
1121
|
+
* are handled; multi-line arrays are skipped (rare for MCP servers).
|
|
1122
|
+
*/
|
|
1123
|
+
function transformCodexToml(file, mode, gatewayConfig, dryRun) {
|
|
1124
|
+
let content;
|
|
1125
|
+
try {
|
|
1126
|
+
content = fs.readFileSync(file, 'utf8');
|
|
1127
|
+
}
|
|
1128
|
+
catch {
|
|
1129
|
+
return null;
|
|
1130
|
+
}
|
|
1131
|
+
const lines = content.split(/\r?\n/);
|
|
1132
|
+
const stats = newWrapStats();
|
|
1133
|
+
const nodeExe = process.execPath;
|
|
1134
|
+
const sections = [];
|
|
1135
|
+
let current = null;
|
|
1136
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1137
|
+
const trimmed = lines[i].trim();
|
|
1138
|
+
const header = trimmed.match(/^\[(.+?)\]\s*$/);
|
|
1139
|
+
if (header) {
|
|
1140
|
+
const tablePath = header[1];
|
|
1141
|
+
const isServer = /^mcp_servers\.[^.]+$/.test(tablePath);
|
|
1142
|
+
if (isServer) {
|
|
1143
|
+
current = { name: tablePath.slice('mcp_servers.'.length), cmdLine: -1, argsLine: -1 };
|
|
1144
|
+
sections.push(current);
|
|
1145
|
+
}
|
|
1146
|
+
else
|
|
1147
|
+
current = null;
|
|
1148
|
+
continue;
|
|
1149
|
+
}
|
|
1150
|
+
if (current) {
|
|
1151
|
+
if (/^command\s*=/.test(trimmed) && current.cmdLine === -1) {
|
|
1152
|
+
current.cmdLine = i;
|
|
1153
|
+
current.command = parseTomlInlineString(lines[i]);
|
|
1154
|
+
}
|
|
1155
|
+
else if (/^args\s*=/.test(trimmed) && current.argsLine === -1) {
|
|
1156
|
+
current.argsLine = i;
|
|
1157
|
+
current.args = parseTomlInlineStringArray(lines[i]);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
let changed = false;
|
|
1162
|
+
for (const section of sections) {
|
|
1163
|
+
if (section.cmdLine === -1 || !section.command) {
|
|
1164
|
+
continue;
|
|
1165
|
+
}
|
|
1166
|
+
const wrappedAlready = section.command === nodeExe && (section.args || []).includes('mcp-gateway');
|
|
1167
|
+
if (mode === 'wrap') {
|
|
1168
|
+
if (section.name === MANAGED_SERVER_NAME || wrappedAlready) {
|
|
1169
|
+
stats.skippedManaged.push(section.name);
|
|
1170
|
+
continue;
|
|
1171
|
+
}
|
|
1172
|
+
const perServer = { ...gatewayConfig, agentName: perServerAgentName(gatewayConfig.developerName, 'codex', section.name) };
|
|
1173
|
+
const wrappedArgs = buildGatewayCommandArgs(perServer, section.command, section.args || [], INSTALL_GATEWAY_ARGS);
|
|
1174
|
+
const indent = lines[section.cmdLine].match(/^\s*/)?.[0] || '';
|
|
1175
|
+
lines[section.cmdLine] = `${indent}command = ${JSON.stringify(nodeExe)}`;
|
|
1176
|
+
const argsLineText = `${indent}args = ${tomlStringArrayLiteral(wrappedArgs)}`;
|
|
1177
|
+
if (section.argsLine !== -1)
|
|
1178
|
+
lines[section.argsLine] = argsLineText;
|
|
1179
|
+
else
|
|
1180
|
+
lines.splice(section.cmdLine + 1, 0, argsLineText);
|
|
1181
|
+
stats.wrapped.push(section.name);
|
|
1182
|
+
changed = true;
|
|
1183
|
+
}
|
|
1184
|
+
else {
|
|
1185
|
+
if (!wrappedAlready)
|
|
1186
|
+
continue;
|
|
1187
|
+
const downstream = extractWrappedDownstream({ args: section.args });
|
|
1188
|
+
if (!downstream)
|
|
1189
|
+
continue;
|
|
1190
|
+
const indent = lines[section.cmdLine].match(/^\s*/)?.[0] || '';
|
|
1191
|
+
lines[section.cmdLine] = `${indent}command = ${JSON.stringify(downstream.command)}`;
|
|
1192
|
+
if (section.argsLine !== -1)
|
|
1193
|
+
lines[section.argsLine] = `${indent}args = ${tomlStringArrayLiteral(downstream.args)}`;
|
|
1194
|
+
stats.wrapped.push(section.name);
|
|
1195
|
+
changed = true;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
if (changed && !dryRun) {
|
|
1199
|
+
backupFile(file);
|
|
1200
|
+
fs.writeFileSync(file, lines.join('\n'), 'utf8');
|
|
1201
|
+
}
|
|
1202
|
+
return stats;
|
|
1203
|
+
}
|
|
1204
|
+
function protectAllTargetFiles(extra) {
|
|
1205
|
+
const cwd = process.cwd();
|
|
1206
|
+
const seen = new Set();
|
|
1207
|
+
const files = [];
|
|
1208
|
+
for (const candidate of (0, discoverPaths_1.candidateConfigPaths)(cwd, extra)) {
|
|
1209
|
+
const key = path.resolve(candidate.path).toLowerCase();
|
|
1210
|
+
if (seen.has(key))
|
|
1211
|
+
continue;
|
|
1212
|
+
if (!fs.existsSync(candidate.path))
|
|
1213
|
+
continue;
|
|
1214
|
+
seen.add(key);
|
|
1215
|
+
files.push(candidate);
|
|
1216
|
+
}
|
|
1217
|
+
for (const target of (0, discoverPaths_1.claudeDesktopInstallTargets)()) {
|
|
1218
|
+
const key = path.resolve(target.file).toLowerCase();
|
|
1219
|
+
if (target.exists && !seen.has(key)) {
|
|
1220
|
+
seen.add(key);
|
|
1221
|
+
files.push({ path: target.file, source: target.source, clientKey: 'claude_desktop' });
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
return files;
|
|
1225
|
+
}
|
|
1226
|
+
async function protectAllCommand(args, config) {
|
|
1227
|
+
(0, config_1.requireCliSetup)(config, { shieldId: args.shieldId, shieldKey: args.shieldKey, apiUrl: args.apiUrl }, { requireOrganizationId: false });
|
|
1228
|
+
const gatewayConfig = resolveGatewayConfig(args, config);
|
|
1229
|
+
const dryRun = args.dryRun === 'true';
|
|
1230
|
+
const files = protectAllTargetFiles(args.config);
|
|
1231
|
+
console.log('\n\x1b[1m\x1b[36mFullCourtDefense — protect every MCP server in every client\x1b[0m');
|
|
1232
|
+
console.log(`\x1b[2mRuntime identity: ${gatewayConfig.developerName} | Shield: ${gatewayConfig.shieldId}${dryRun ? ' | DRY RUN' : ''}\x1b[0m\n`);
|
|
1233
|
+
if (files.length === 0) {
|
|
1234
|
+
console.log('No MCP client config files found on this machine. Configure an MCP server in any client, then re-run.');
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
let totalWrapped = 0;
|
|
1238
|
+
let totalManaged = 0;
|
|
1239
|
+
let totalRemote = 0;
|
|
1240
|
+
for (const file of files) {
|
|
1241
|
+
const agentClient = CLIENT_KEY_TO_AGENT[file.clientKey] || 'cursor';
|
|
1242
|
+
const isToml = /\.toml$/i.test(file.path);
|
|
1243
|
+
const stats = isToml
|
|
1244
|
+
? transformCodexToml(file.path, 'wrap', gatewayConfig, dryRun)
|
|
1245
|
+
: wrapJsonConfigFile(file.path, gatewayConfig, agentClient, dryRun);
|
|
1246
|
+
if (!stats)
|
|
1247
|
+
continue;
|
|
1248
|
+
if (stats.wrapped.length === 0 && stats.skippedManaged.length === 0 && stats.skippedRemote.length === 0)
|
|
1249
|
+
continue;
|
|
1250
|
+
totalWrapped += stats.wrapped.length;
|
|
1251
|
+
totalManaged += stats.skippedManaged.length;
|
|
1252
|
+
totalRemote += stats.skippedRemote.length;
|
|
1253
|
+
console.log(`\x1b[1m${file.source}\x1b[0m \x1b[2m${file.path}\x1b[0m`);
|
|
1254
|
+
if (stats.wrapped.length)
|
|
1255
|
+
console.log(` \x1b[32m✓ wrapped:\x1b[0m ${stats.wrapped.join(', ')}`);
|
|
1256
|
+
if (stats.skippedManaged.length)
|
|
1257
|
+
console.log(` \x1b[2m• already protected:\x1b[0m ${stats.skippedManaged.join(', ')}`);
|
|
1258
|
+
if (stats.skippedRemote.length)
|
|
1259
|
+
console.log(` \x1b[33m• skipped (remote/no command):\x1b[0m ${stats.skippedRemote.join(', ')}`);
|
|
1260
|
+
console.log('');
|
|
1261
|
+
}
|
|
1262
|
+
console.log('\x1b[1mSummary\x1b[0m');
|
|
1263
|
+
console.log(` ${dryRun ? 'would wrap' : 'wrapped'}: ${totalWrapped} already protected: ${totalManaged} skipped remote: ${totalRemote}`);
|
|
1264
|
+
console.log(` Each protected server enforces your org Action Policies (allow / block / wait-for-human-approval) on its tool calls.`);
|
|
1265
|
+
if (!dryRun && totalWrapped > 0)
|
|
1266
|
+
console.log(' Backups saved next to each edited file (*.fcd-backup-*). Restart each client to load the protected servers.');
|
|
1267
|
+
console.log(' Reverse anytime with: \x1b[1mfullcourtdefense unprotect-all\x1b[0m');
|
|
1268
|
+
}
|
|
1269
|
+
async function unprotectAllCommand(args, config) {
|
|
1270
|
+
const dryRun = args.dryRun === 'true';
|
|
1271
|
+
const files = protectAllTargetFiles(args.config);
|
|
1272
|
+
let gatewayConfig;
|
|
1273
|
+
try {
|
|
1274
|
+
gatewayConfig = resolveGatewayConfig(args, config);
|
|
1275
|
+
}
|
|
1276
|
+
catch { /* unwrap doesn't need creds */ }
|
|
1277
|
+
const fallbackConfig = gatewayConfig || { developerName: 'local-user' };
|
|
1278
|
+
console.log('\n\x1b[1m\x1b[36mFullCourtDefense — remove gateway wrapping from all clients\x1b[0m\n');
|
|
1279
|
+
let total = 0;
|
|
1280
|
+
for (const file of files) {
|
|
1281
|
+
const isToml = /\.toml$/i.test(file.path);
|
|
1282
|
+
if (isToml) {
|
|
1283
|
+
const stats = transformCodexToml(file.path, 'unwrap', fallbackConfig, dryRun);
|
|
1284
|
+
if (stats && stats.wrapped.length) {
|
|
1285
|
+
total += stats.wrapped.length;
|
|
1286
|
+
console.log(`\x1b[1m${file.source}\x1b[0m restored: ${stats.wrapped.join(', ')}`);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
else {
|
|
1290
|
+
const res = unwrapJsonConfigFile(file.path, dryRun);
|
|
1291
|
+
if (res && res.restored.length) {
|
|
1292
|
+
total += res.restored.length;
|
|
1293
|
+
console.log(`\x1b[1m${file.source}\x1b[0m restored: ${res.restored.join(', ')}`);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
console.log(`\n${dryRun ? 'Would restore' : 'Restored'} ${total} server(s) to their original commands. Restart each client.`);
|
|
1298
|
+
}
|
|
943
1299
|
async function installClaudeCodeMcpGatewayCommand(args, config) {
|
|
944
1300
|
const gatewayConfig = resolveGatewayConfig(args, config, {
|
|
945
1301
|
agentClient: 'claude-code',
|
package/dist/index.js
CHANGED
|
@@ -124,6 +124,12 @@ function printHelp() {
|
|
|
124
124
|
are allowed, blocked, or paused for human approval per policy.
|
|
125
125
|
uninstall-cursor-hook
|
|
126
126
|
Removes the FullCourtDefense Cursor hook entries.
|
|
127
|
+
protect-all Auto-detect EVERY MCP server already configured in EVERY client
|
|
128
|
+
(Cursor, Claude Code/Desktop, Codex, Gemini, Windsurf, VS Code)
|
|
129
|
+
and wrap each through the Shield proxy — no --mcp-command needed.
|
|
130
|
+
Enforces your org Action Policies on every tool. --dry-run to preview.
|
|
131
|
+
unprotect-all
|
|
132
|
+
Reverse protect-all: restore every server to its original command.
|
|
127
133
|
install-mcp-gateway
|
|
128
134
|
Install MCP gateway into selected clients (--clients all for every tool).
|
|
129
135
|
install-cursor-mcp-gateway
|
|
@@ -499,6 +505,26 @@ async function main() {
|
|
|
499
505
|
await (0, mcpGateway_1.installMcpGatewayCommand)(buildInstallArgs(), config);
|
|
500
506
|
break;
|
|
501
507
|
}
|
|
508
|
+
case 'protect-all':
|
|
509
|
+
case 'wrap-all': {
|
|
510
|
+
const args = {
|
|
511
|
+
...buildGatewayArgs(),
|
|
512
|
+
dryRun: flags['dry-run'],
|
|
513
|
+
config: flags.config,
|
|
514
|
+
};
|
|
515
|
+
await (0, mcpGateway_1.protectAllCommand)(args, config);
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
case 'unprotect-all':
|
|
519
|
+
case 'unwrap-all': {
|
|
520
|
+
const args = {
|
|
521
|
+
...buildGatewayArgs(),
|
|
522
|
+
dryRun: flags['dry-run'],
|
|
523
|
+
config: flags.config,
|
|
524
|
+
};
|
|
525
|
+
await (0, mcpGateway_1.unprotectAllCommand)(args, config);
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
502
528
|
case 'install-cursor-mcp-gateway': {
|
|
503
529
|
const args = {
|
|
504
530
|
...buildGatewayArgs(),
|
package/dist/version.json
CHANGED