@solongate/proxy 0.30.2 → 0.32.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/index.js +90 -46
- package/dist/init.js +50 -3
- package/dist/lib.js +16 -20
- package/hooks/audit.mjs +38 -6
- package/hooks/guard.mjs +82 -14
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
4
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
5
|
-
}) : x)(function(x) {
|
|
6
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
7
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
-
});
|
|
9
3
|
var __esm = (fn, res) => function __init() {
|
|
10
4
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
5
|
};
|
|
@@ -465,8 +459,8 @@ var init_cli_utils = __esm({
|
|
|
465
459
|
|
|
466
460
|
// src/init.ts
|
|
467
461
|
var init_exports = {};
|
|
468
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as
|
|
469
|
-
import { resolve as resolve3, join, dirname as dirname2 } from "path";
|
|
462
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
463
|
+
import { resolve as resolve3, join as join2, dirname as dirname2 } from "path";
|
|
470
464
|
import { homedir } from "os";
|
|
471
465
|
import { fileURLToPath } from "url";
|
|
472
466
|
import { execFileSync } from "child_process";
|
|
@@ -540,13 +534,16 @@ function isAlreadyProtected(server) {
|
|
|
540
534
|
}
|
|
541
535
|
return false;
|
|
542
536
|
}
|
|
543
|
-
function wrapServer(serverName, server, policy) {
|
|
537
|
+
function wrapServer(serverName, server, policy, agentName) {
|
|
544
538
|
const env = { ...server.env ?? {} };
|
|
545
539
|
env.SOLONGATE_API_KEY = "${SOLONGATE_API_KEY}";
|
|
546
540
|
const proxyArgs = ["-y", "@solongate/proxy@latest"];
|
|
547
541
|
if (policy) {
|
|
548
542
|
proxyArgs.push("--policy", policy);
|
|
549
543
|
}
|
|
544
|
+
if (agentName) {
|
|
545
|
+
proxyArgs.push("--agent-name", agentName);
|
|
546
|
+
}
|
|
550
547
|
proxyArgs.push("--verbose", "--", server.command, ...server.args ?? []);
|
|
551
548
|
return {
|
|
552
549
|
command: "npx",
|
|
@@ -639,7 +636,7 @@ EXAMPLES
|
|
|
639
636
|
console.log(help);
|
|
640
637
|
}
|
|
641
638
|
function readHookScript(filename) {
|
|
642
|
-
return readFileSync4(
|
|
639
|
+
return readFileSync4(join2(HOOKS_DIR, filename), "utf-8");
|
|
643
640
|
}
|
|
644
641
|
function unlockProtectedDirs() {
|
|
645
642
|
const dirs = [".solongate", ".claude", ".cursor", ".gemini", ".antigravity", ".openclaw", ".perplexity"];
|
|
@@ -694,12 +691,12 @@ function unlockProtectedDirs() {
|
|
|
694
691
|
function installHooks(selectedTools = [], wrappedMcpConfig) {
|
|
695
692
|
unlockProtectedDirs();
|
|
696
693
|
const hooksDir = resolve3(".solongate", "hooks");
|
|
697
|
-
|
|
694
|
+
mkdirSync3(hooksDir, { recursive: true });
|
|
698
695
|
const hookFiles = ["guard.mjs", "audit.mjs", "stop.mjs"];
|
|
699
696
|
let hooksUpdated = 0;
|
|
700
697
|
let hooksSkipped = 0;
|
|
701
698
|
for (const filename of hookFiles) {
|
|
702
|
-
const hookPath =
|
|
699
|
+
const hookPath = join2(hooksDir, filename);
|
|
703
700
|
const latest = readHookScript(filename);
|
|
704
701
|
let needsWrite = true;
|
|
705
702
|
if (existsSync4(hookPath)) {
|
|
@@ -731,7 +728,7 @@ function installHooks(selectedTools = [], wrappedMcpConfig) {
|
|
|
731
728
|
const skippedNames = [];
|
|
732
729
|
for (const client of clients) {
|
|
733
730
|
const clientDir = resolve3(client.dir);
|
|
734
|
-
|
|
731
|
+
mkdirSync3(clientDir, { recursive: true });
|
|
735
732
|
const guardCmd = `node .solongate/hooks/guard.mjs ${client.agentId} "${client.agentName}"`;
|
|
736
733
|
const auditCmd = `node .solongate/hooks/audit.mjs ${client.agentId} "${client.agentName}"`;
|
|
737
734
|
const stopCmd = `node .solongate/hooks/stop.mjs ${client.agentId} "${client.agentName}"`;
|
|
@@ -766,7 +763,7 @@ function installHooks(selectedTools = [], wrappedMcpConfig) {
|
|
|
766
763
|
}
|
|
767
764
|
}
|
|
768
765
|
function installStandardHookConfig(clientDir, clientName, guardCmd, auditCmd, stopCmd) {
|
|
769
|
-
const settingsPath =
|
|
766
|
+
const settingsPath = join2(clientDir, "settings.json");
|
|
770
767
|
const hookSettings = {
|
|
771
768
|
PreToolUse: [
|
|
772
769
|
{ matcher: "", hooks: [{ type: "command", command: guardCmd }] }
|
|
@@ -799,12 +796,30 @@ function installCursorConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
|
|
|
799
796
|
],
|
|
800
797
|
postToolUse: [
|
|
801
798
|
{ matcher: "", command: auditCmd }
|
|
799
|
+
],
|
|
800
|
+
beforeShellExecution: [
|
|
801
|
+
{ matcher: "", command: guardCmd, failClosed: true }
|
|
802
|
+
],
|
|
803
|
+
afterShellExecution: [
|
|
804
|
+
{ matcher: "", command: auditCmd }
|
|
805
|
+
],
|
|
806
|
+
beforeReadFile: [
|
|
807
|
+
{ matcher: "", command: guardCmd, failClosed: true }
|
|
808
|
+
],
|
|
809
|
+
afterFileEdit: [
|
|
810
|
+
{ matcher: "", command: auditCmd }
|
|
811
|
+
],
|
|
812
|
+
beforeMCPExecution: [
|
|
813
|
+
{ matcher: "", command: guardCmd, failClosed: true }
|
|
814
|
+
],
|
|
815
|
+
afterMCPExecution: [
|
|
816
|
+
{ matcher: "", command: auditCmd }
|
|
802
817
|
]
|
|
803
818
|
};
|
|
804
819
|
const hooksContent = JSON.stringify({ version: 1, hooks: hookConfig }, null, 2) + "\n";
|
|
805
|
-
const userCursorDir =
|
|
820
|
+
const userCursorDir = join2(homedir(), ".cursor");
|
|
806
821
|
if (existsSync4(userCursorDir)) {
|
|
807
|
-
const userHooksPath =
|
|
822
|
+
const userHooksPath = join2(userCursorDir, "hooks.json");
|
|
808
823
|
const existingUserHooks = existsSync4(userHooksPath) ? readFileSync4(userHooksPath, "utf-8") : "";
|
|
809
824
|
if (hooksContent !== existingUserHooks) {
|
|
810
825
|
writeFileSync2(userHooksPath, hooksContent);
|
|
@@ -812,7 +827,7 @@ function installCursorConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
|
|
|
812
827
|
changed = true;
|
|
813
828
|
}
|
|
814
829
|
}
|
|
815
|
-
const hooksPath =
|
|
830
|
+
const hooksPath = join2(clientDir, "hooks.json");
|
|
816
831
|
const existingHooks = existsSync4(hooksPath) ? readFileSync4(hooksPath, "utf-8") : "";
|
|
817
832
|
if (hooksContent !== existingHooks) {
|
|
818
833
|
writeFileSync2(hooksPath, hooksContent);
|
|
@@ -820,8 +835,22 @@ function installCursorConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
|
|
|
820
835
|
changed = true;
|
|
821
836
|
}
|
|
822
837
|
if (wrappedMcpConfig) {
|
|
823
|
-
const
|
|
824
|
-
const
|
|
838
|
+
const cursorMcpConfig = { mcpServers: {} };
|
|
839
|
+
for (const [name, server] of Object.entries(wrappedMcpConfig.mcpServers)) {
|
|
840
|
+
const args = [...server.args ?? []];
|
|
841
|
+
const agentIdx = args.indexOf("--agent-name");
|
|
842
|
+
if (agentIdx >= 0 && agentIdx + 1 < args.length) {
|
|
843
|
+
args[agentIdx + 1] = "cursor";
|
|
844
|
+
} else {
|
|
845
|
+
const sepIdx = args.indexOf("--");
|
|
846
|
+
if (sepIdx >= 0) {
|
|
847
|
+
args.splice(sepIdx, 0, "--agent-name", "cursor");
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
cursorMcpConfig.mcpServers[name] = { ...server, args };
|
|
851
|
+
}
|
|
852
|
+
const mcpPath = join2(clientDir, "mcp.json");
|
|
853
|
+
const mcpStr = JSON.stringify(cursorMcpConfig, null, 2) + "\n";
|
|
825
854
|
const existingMcp = existsSync4(mcpPath) ? readFileSync4(mcpPath, "utf-8") : "";
|
|
826
855
|
if (mcpStr !== existingMcp) {
|
|
827
856
|
writeFileSync2(mcpPath, mcpStr);
|
|
@@ -829,7 +858,7 @@ function installCursorConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
|
|
|
829
858
|
changed = true;
|
|
830
859
|
}
|
|
831
860
|
}
|
|
832
|
-
const staleSettings =
|
|
861
|
+
const staleSettings = join2(clientDir, "settings.json");
|
|
833
862
|
if (existsSync4(staleSettings)) {
|
|
834
863
|
try {
|
|
835
864
|
const content = JSON.parse(readFileSync4(staleSettings, "utf-8"));
|
|
@@ -844,7 +873,7 @@ function installCursorConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
|
|
|
844
873
|
return changed ? "installed" : "skipped";
|
|
845
874
|
}
|
|
846
875
|
function installGeminiConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcpConfig) {
|
|
847
|
-
const settingsPath =
|
|
876
|
+
const settingsPath = join2(clientDir, "settings.json");
|
|
848
877
|
let existing = {};
|
|
849
878
|
try {
|
|
850
879
|
existing = JSON.parse(readFileSync4(settingsPath, "utf-8"));
|
|
@@ -852,7 +881,19 @@ function installGeminiConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
|
|
|
852
881
|
}
|
|
853
882
|
const merged = { ...existing };
|
|
854
883
|
if (wrappedMcpConfig) {
|
|
855
|
-
|
|
884
|
+
const geminiMcp = {};
|
|
885
|
+
for (const [name, server] of Object.entries(wrappedMcpConfig.mcpServers)) {
|
|
886
|
+
const args = [...server.args ?? []];
|
|
887
|
+
const agentIdx = args.indexOf("--agent-name");
|
|
888
|
+
if (agentIdx >= 0 && agentIdx + 1 < args.length) {
|
|
889
|
+
args[agentIdx + 1] = "gemini-cli";
|
|
890
|
+
} else {
|
|
891
|
+
const sepIdx = args.indexOf("--");
|
|
892
|
+
if (sepIdx >= 0) args.splice(sepIdx, 0, "--agent-name", "gemini-cli");
|
|
893
|
+
}
|
|
894
|
+
geminiMcp[name] = { ...server, args };
|
|
895
|
+
}
|
|
896
|
+
merged.mcpServers = geminiMcp;
|
|
856
897
|
}
|
|
857
898
|
merged.hooks = {
|
|
858
899
|
BeforeTool: [
|
|
@@ -1186,7 +1227,7 @@ var init_init = __esm({
|
|
|
1186
1227
|
"mcp.json",
|
|
1187
1228
|
".claude/mcp.json"
|
|
1188
1229
|
];
|
|
1189
|
-
CLAUDE_DESKTOP_PATHS = process.platform === "win32" ? [
|
|
1230
|
+
CLAUDE_DESKTOP_PATHS = process.platform === "win32" ? [join2(process.env["APPDATA"] ?? "", "Claude", "claude_desktop_config.json")] : process.platform === "darwin" ? [join2(process.env["HOME"] ?? "", "Library", "Application Support", "Claude", "claude_desktop_config.json")] : [join2(process.env["HOME"] ?? "", ".config", "claude", "claude_desktop_config.json")];
|
|
1190
1231
|
sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1191
1232
|
SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1192
1233
|
spinnerInterval = null;
|
|
@@ -1539,8 +1580,8 @@ var init_inject = __esm({
|
|
|
1539
1580
|
|
|
1540
1581
|
// src/create.ts
|
|
1541
1582
|
var create_exports = {};
|
|
1542
|
-
import { mkdirSync as
|
|
1543
|
-
import { resolve as resolve5, join as
|
|
1583
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, existsSync as existsSync6 } from "fs";
|
|
1584
|
+
import { resolve as resolve5, join as join3 } from "path";
|
|
1544
1585
|
import { execSync as execSync2 } from "child_process";
|
|
1545
1586
|
function withSpinner(message, fn) {
|
|
1546
1587
|
const frames = ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"];
|
|
@@ -1620,7 +1661,7 @@ EXAMPLES
|
|
|
1620
1661
|
}
|
|
1621
1662
|
function createProject(dir, name, _policy) {
|
|
1622
1663
|
writeFileSync4(
|
|
1623
|
-
|
|
1664
|
+
join3(dir, "package.json"),
|
|
1624
1665
|
JSON.stringify(
|
|
1625
1666
|
{
|
|
1626
1667
|
name,
|
|
@@ -1650,7 +1691,7 @@ function createProject(dir, name, _policy) {
|
|
|
1650
1691
|
) + "\n"
|
|
1651
1692
|
);
|
|
1652
1693
|
writeFileSync4(
|
|
1653
|
-
|
|
1694
|
+
join3(dir, "tsconfig.json"),
|
|
1654
1695
|
JSON.stringify(
|
|
1655
1696
|
{
|
|
1656
1697
|
compilerOptions: {
|
|
@@ -1670,9 +1711,9 @@ function createProject(dir, name, _policy) {
|
|
|
1670
1711
|
2
|
|
1671
1712
|
) + "\n"
|
|
1672
1713
|
);
|
|
1673
|
-
|
|
1714
|
+
mkdirSync4(join3(dir, "src"), { recursive: true });
|
|
1674
1715
|
writeFileSync4(
|
|
1675
|
-
|
|
1716
|
+
join3(dir, "src", "index.ts"),
|
|
1676
1717
|
`#!/usr/bin/env node
|
|
1677
1718
|
|
|
1678
1719
|
console.log = (...args: unknown[]) => {
|
|
@@ -1717,7 +1758,7 @@ console.log('Press Ctrl+C to stop.');
|
|
|
1717
1758
|
`
|
|
1718
1759
|
);
|
|
1719
1760
|
writeFileSync4(
|
|
1720
|
-
|
|
1761
|
+
join3(dir, ".mcp.json"),
|
|
1721
1762
|
JSON.stringify(
|
|
1722
1763
|
{
|
|
1723
1764
|
mcpServers: {
|
|
@@ -1735,12 +1776,12 @@ console.log('Press Ctrl+C to stop.');
|
|
|
1735
1776
|
) + "\n"
|
|
1736
1777
|
);
|
|
1737
1778
|
writeFileSync4(
|
|
1738
|
-
|
|
1779
|
+
join3(dir, ".env"),
|
|
1739
1780
|
`SOLONGATE_API_KEY=sg_live_YOUR_KEY_HERE
|
|
1740
1781
|
`
|
|
1741
1782
|
);
|
|
1742
1783
|
writeFileSync4(
|
|
1743
|
-
|
|
1784
|
+
join3(dir, ".gitignore"),
|
|
1744
1785
|
`node_modules/
|
|
1745
1786
|
dist/
|
|
1746
1787
|
*.solongate-backup
|
|
@@ -1759,7 +1800,7 @@ async function main3() {
|
|
|
1759
1800
|
process.exit(1);
|
|
1760
1801
|
}
|
|
1761
1802
|
withSpinner(`Setting up ${opts.name}...`, () => {
|
|
1762
|
-
|
|
1803
|
+
mkdirSync4(dir, { recursive: true });
|
|
1763
1804
|
createProject(dir, opts.name, opts.policy);
|
|
1764
1805
|
});
|
|
1765
1806
|
if (!opts.noInstall) {
|
|
@@ -2170,8 +2211,8 @@ import {
|
|
|
2170
2211
|
ListResourceTemplatesRequestSchema
|
|
2171
2212
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
2172
2213
|
import { createServer as createHttpServer } from "http";
|
|
2173
|
-
import { resolve as resolve2 } from "path";
|
|
2174
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
2214
|
+
import { resolve as resolve2, join } from "path";
|
|
2215
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, mkdirSync as mkdirSync2, appendFileSync } from "fs";
|
|
2175
2216
|
|
|
2176
2217
|
// ../core/dist/index.js
|
|
2177
2218
|
import { z } from "zod";
|
|
@@ -6227,23 +6268,26 @@ var SolonGateProxy = class {
|
|
|
6227
6268
|
const clientVersion = this.server.getClientVersion();
|
|
6228
6269
|
log2(`MCP clientInfo raw: ${JSON.stringify(clientVersion)}`);
|
|
6229
6270
|
try {
|
|
6230
|
-
const
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6271
|
+
const debugInfo = {
|
|
6272
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6273
|
+
clientInfo: clientVersion,
|
|
6274
|
+
agentNameFlag: this.config.agentName ?? null,
|
|
6275
|
+
resolvedAgentId: this.agentId,
|
|
6276
|
+
resolvedAgentName: this.agentName,
|
|
6277
|
+
pid: process.pid
|
|
6278
|
+
};
|
|
6279
|
+
const debugDir = resolve2(".solongate");
|
|
6280
|
+
mkdirSync2(debugDir, { recursive: true });
|
|
6281
|
+
appendFileSync(join(debugDir, ".debug-proxy"), JSON.stringify(debugInfo) + "\n");
|
|
6240
6282
|
} catch {
|
|
6241
6283
|
}
|
|
6242
|
-
if (clientVersion?.name) {
|
|
6284
|
+
if (clientVersion?.name && !this.config.agentName) {
|
|
6243
6285
|
const normalized = this.normalizeAgentName(clientVersion.name);
|
|
6244
6286
|
this.agentId = normalized.id;
|
|
6245
6287
|
this.agentName = normalized.name;
|
|
6246
6288
|
log2(`Agent identified from MCP clientInfo: ${this.agentName} (raw: ${clientVersion.name})`);
|
|
6289
|
+
} else if (this.config.agentName) {
|
|
6290
|
+
log2(`Agent identity from --agent-name flag: ${this.agentName} (clientInfo: ${clientVersion?.name || "none"})`);
|
|
6247
6291
|
}
|
|
6248
6292
|
}
|
|
6249
6293
|
};
|
package/dist/init.js
CHANGED
|
@@ -118,13 +118,16 @@ function isAlreadyProtected(server) {
|
|
|
118
118
|
}
|
|
119
119
|
return false;
|
|
120
120
|
}
|
|
121
|
-
function wrapServer(serverName, server, policy) {
|
|
121
|
+
function wrapServer(serverName, server, policy, agentName) {
|
|
122
122
|
const env = { ...server.env ?? {} };
|
|
123
123
|
env.SOLONGATE_API_KEY = "${SOLONGATE_API_KEY}";
|
|
124
124
|
const proxyArgs = ["-y", "@solongate/proxy@latest"];
|
|
125
125
|
if (policy) {
|
|
126
126
|
proxyArgs.push("--policy", policy);
|
|
127
127
|
}
|
|
128
|
+
if (agentName) {
|
|
129
|
+
proxyArgs.push("--agent-name", agentName);
|
|
130
|
+
}
|
|
128
131
|
proxyArgs.push("--verbose", "--", server.command, ...server.args ?? []);
|
|
129
132
|
return {
|
|
130
133
|
command: "npx",
|
|
@@ -379,6 +382,24 @@ function installCursorConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
|
|
|
379
382
|
],
|
|
380
383
|
postToolUse: [
|
|
381
384
|
{ matcher: "", command: auditCmd }
|
|
385
|
+
],
|
|
386
|
+
beforeShellExecution: [
|
|
387
|
+
{ matcher: "", command: guardCmd, failClosed: true }
|
|
388
|
+
],
|
|
389
|
+
afterShellExecution: [
|
|
390
|
+
{ matcher: "", command: auditCmd }
|
|
391
|
+
],
|
|
392
|
+
beforeReadFile: [
|
|
393
|
+
{ matcher: "", command: guardCmd, failClosed: true }
|
|
394
|
+
],
|
|
395
|
+
afterFileEdit: [
|
|
396
|
+
{ matcher: "", command: auditCmd }
|
|
397
|
+
],
|
|
398
|
+
beforeMCPExecution: [
|
|
399
|
+
{ matcher: "", command: guardCmd, failClosed: true }
|
|
400
|
+
],
|
|
401
|
+
afterMCPExecution: [
|
|
402
|
+
{ matcher: "", command: auditCmd }
|
|
382
403
|
]
|
|
383
404
|
};
|
|
384
405
|
const hooksContent = JSON.stringify({ version: 1, hooks: hookConfig }, null, 2) + "\n";
|
|
@@ -400,8 +421,22 @@ function installCursorConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
|
|
|
400
421
|
changed = true;
|
|
401
422
|
}
|
|
402
423
|
if (wrappedMcpConfig) {
|
|
424
|
+
const cursorMcpConfig = { mcpServers: {} };
|
|
425
|
+
for (const [name, server] of Object.entries(wrappedMcpConfig.mcpServers)) {
|
|
426
|
+
const args = [...server.args ?? []];
|
|
427
|
+
const agentIdx = args.indexOf("--agent-name");
|
|
428
|
+
if (agentIdx >= 0 && agentIdx + 1 < args.length) {
|
|
429
|
+
args[agentIdx + 1] = "cursor";
|
|
430
|
+
} else {
|
|
431
|
+
const sepIdx = args.indexOf("--");
|
|
432
|
+
if (sepIdx >= 0) {
|
|
433
|
+
args.splice(sepIdx, 0, "--agent-name", "cursor");
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
cursorMcpConfig.mcpServers[name] = { ...server, args };
|
|
437
|
+
}
|
|
403
438
|
const mcpPath = join(clientDir, "mcp.json");
|
|
404
|
-
const mcpStr = JSON.stringify(
|
|
439
|
+
const mcpStr = JSON.stringify(cursorMcpConfig, null, 2) + "\n";
|
|
405
440
|
const existingMcp = existsSync(mcpPath) ? readFileSync(mcpPath, "utf-8") : "";
|
|
406
441
|
if (mcpStr !== existingMcp) {
|
|
407
442
|
writeFileSync(mcpPath, mcpStr);
|
|
@@ -432,7 +467,19 @@ function installGeminiConfig(clientDir, guardCmd, auditCmd, _stopCmd, wrappedMcp
|
|
|
432
467
|
}
|
|
433
468
|
const merged = { ...existing };
|
|
434
469
|
if (wrappedMcpConfig) {
|
|
435
|
-
|
|
470
|
+
const geminiMcp = {};
|
|
471
|
+
for (const [name, server] of Object.entries(wrappedMcpConfig.mcpServers)) {
|
|
472
|
+
const args = [...server.args ?? []];
|
|
473
|
+
const agentIdx = args.indexOf("--agent-name");
|
|
474
|
+
if (agentIdx >= 0 && agentIdx + 1 < args.length) {
|
|
475
|
+
args[agentIdx + 1] = "gemini-cli";
|
|
476
|
+
} else {
|
|
477
|
+
const sepIdx = args.indexOf("--");
|
|
478
|
+
if (sepIdx >= 0) args.splice(sepIdx, 0, "--agent-name", "gemini-cli");
|
|
479
|
+
}
|
|
480
|
+
geminiMcp[name] = { ...server, args };
|
|
481
|
+
}
|
|
482
|
+
merged.mcpServers = geminiMcp;
|
|
436
483
|
}
|
|
437
484
|
merged.hooks = {
|
|
438
485
|
BeforeTool: [
|
package/dist/lib.js
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
-
}) : x)(function(x) {
|
|
4
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
-
});
|
|
7
|
-
|
|
8
1
|
// ../core/dist/index.js
|
|
9
2
|
import { z } from "zod";
|
|
10
3
|
var __defProp = Object.defineProperty;
|
|
@@ -3619,8 +3612,8 @@ import {
|
|
|
3619
3612
|
ListResourceTemplatesRequestSchema
|
|
3620
3613
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
3621
3614
|
import { createServer as createHttpServer } from "http";
|
|
3622
|
-
import { resolve as resolve2 } from "path";
|
|
3623
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
3615
|
+
import { resolve as resolve2, join } from "path";
|
|
3616
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, mkdirSync as mkdirSync2, appendFileSync } from "fs";
|
|
3624
3617
|
|
|
3625
3618
|
// src/config.ts
|
|
3626
3619
|
import { readFileSync, existsSync } from "fs";
|
|
@@ -4468,23 +4461,26 @@ var SolonGateProxy = class {
|
|
|
4468
4461
|
const clientVersion = this.server.getClientVersion();
|
|
4469
4462
|
log2(`MCP clientInfo raw: ${JSON.stringify(clientVersion)}`);
|
|
4470
4463
|
try {
|
|
4471
|
-
const
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4464
|
+
const debugInfo = {
|
|
4465
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4466
|
+
clientInfo: clientVersion,
|
|
4467
|
+
agentNameFlag: this.config.agentName ?? null,
|
|
4468
|
+
resolvedAgentId: this.agentId,
|
|
4469
|
+
resolvedAgentName: this.agentName,
|
|
4470
|
+
pid: process.pid
|
|
4471
|
+
};
|
|
4472
|
+
const debugDir = resolve2(".solongate");
|
|
4473
|
+
mkdirSync2(debugDir, { recursive: true });
|
|
4474
|
+
appendFileSync(join(debugDir, ".debug-proxy"), JSON.stringify(debugInfo) + "\n");
|
|
4481
4475
|
} catch {
|
|
4482
4476
|
}
|
|
4483
|
-
if (clientVersion?.name) {
|
|
4477
|
+
if (clientVersion?.name && !this.config.agentName) {
|
|
4484
4478
|
const normalized = this.normalizeAgentName(clientVersion.name);
|
|
4485
4479
|
this.agentId = normalized.id;
|
|
4486
4480
|
this.agentName = normalized.name;
|
|
4487
4481
|
log2(`Agent identified from MCP clientInfo: ${this.agentName} (raw: ${clientVersion.name})`);
|
|
4482
|
+
} else if (this.config.agentName) {
|
|
4483
|
+
log2(`Agent identity from --agent-name flag: ${this.agentName} (clientInfo: ${clientVersion?.name || "none"})`);
|
|
4488
4484
|
}
|
|
4489
4485
|
}
|
|
4490
4486
|
};
|
package/hooks/audit.mjs
CHANGED
|
@@ -38,8 +38,12 @@ process.stdin.on('end', async () => {
|
|
|
38
38
|
try {
|
|
39
39
|
const data = JSON.parse(input);
|
|
40
40
|
|
|
41
|
-
// Debug:
|
|
42
|
-
try {
|
|
41
|
+
// Debug: append raw stdin to file for agent detection troubleshooting
|
|
42
|
+
try {
|
|
43
|
+
const debugLine = JSON.stringify({ ts: new Date().toISOString(), argv: process.argv.slice(2), cursor_version: data.cursor_version || null, tool_name: data.tool_name || data.toolName, agent_id: AGENT_ID, hook_event_name: data.hook_event_name || null }) + '\n';
|
|
44
|
+
const { appendFileSync: afs } = await import('node:fs');
|
|
45
|
+
afs(resolve('.solongate', '.debug-audit-log'), debugLine);
|
|
46
|
+
} catch {}
|
|
43
47
|
|
|
44
48
|
// Auto-detect agent from stdin data (overrides CLI args if detected)
|
|
45
49
|
if (data.cursor_version) {
|
|
@@ -50,9 +54,35 @@ process.stdin.on('end', async () => {
|
|
|
50
54
|
AGENT_NAME = 'Gemini CLI';
|
|
51
55
|
}
|
|
52
56
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
const hookEvent = data.hook_event_name || '';
|
|
58
|
+
|
|
59
|
+
// Map Cursor-specific hook events to standard tool_name/tool_input
|
|
60
|
+
let toolName = data.tool_name || data.toolName || '';
|
|
61
|
+
let toolInput = data.tool_input || data.toolInput || data.params || {};
|
|
62
|
+
|
|
63
|
+
if (!toolName && hookEvent) {
|
|
64
|
+
switch (hookEvent) {
|
|
65
|
+
case 'afterShellExecution':
|
|
66
|
+
toolName = 'Shell';
|
|
67
|
+
toolInput = { command: data.command || '' };
|
|
68
|
+
break;
|
|
69
|
+
case 'afterFileEdit':
|
|
70
|
+
toolName = 'Edit';
|
|
71
|
+
toolInput = { file_path: data.file_path || '' };
|
|
72
|
+
break;
|
|
73
|
+
case 'afterMCPExecution':
|
|
74
|
+
toolName = data.tool_name || data.toolName || `mcp:${data.server_name || 'unknown'}`;
|
|
75
|
+
toolInput = data.arguments || {};
|
|
76
|
+
break;
|
|
77
|
+
case 'postToolUse':
|
|
78
|
+
case 'postToolUseFailure':
|
|
79
|
+
toolName = data.tool_name || data.toolName || 'unknown';
|
|
80
|
+
break;
|
|
81
|
+
default:
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (!toolName) toolName = 'unknown';
|
|
56
86
|
|
|
57
87
|
if (toolName === 'Bash' && JSON.stringify(toolInput).includes('audit-logs')) {
|
|
58
88
|
process.exit(0);
|
|
@@ -71,14 +101,16 @@ process.stdin.on('end', async () => {
|
|
|
71
101
|
}
|
|
72
102
|
} catch {}
|
|
73
103
|
|
|
74
|
-
// Cursor uses
|
|
104
|
+
// Cursor uses tool_output, Claude uses tool_response
|
|
75
105
|
const toolResponse = data.tool_response || data.toolResponse || {};
|
|
106
|
+
const toolOutput = data.tool_output || data.toolOutput || '';
|
|
76
107
|
const resultJson = data.result_json ? (typeof data.result_json === 'string' ? data.result_json : JSON.stringify(data.result_json)) : '';
|
|
77
108
|
|
|
78
109
|
const hasError = guardDenied ||
|
|
79
110
|
toolResponse.error ||
|
|
80
111
|
toolResponse.exitCode > 0 ||
|
|
81
112
|
toolResponse.isError ||
|
|
113
|
+
(toolOutput && typeof toolOutput === 'string' && toolOutput.includes('"error"')) ||
|
|
82
114
|
(resultJson && resultJson.includes('"error"'));
|
|
83
115
|
|
|
84
116
|
const argsSummary = {};
|
package/hooks/guard.mjs
CHANGED
|
@@ -46,17 +46,33 @@ let AGENT_ID = process.argv[2] || 'claude-code';
|
|
|
46
46
|
let AGENT_NAME = process.argv[3] || 'Claude Code';
|
|
47
47
|
|
|
48
48
|
// ── Per-tool block/allow output ──
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
49
|
+
// Response format depends on BOTH the agent AND the hook event type:
|
|
50
|
+
// Claude Code: exit 2 + stderr = BLOCK, exit 0 = ALLOW
|
|
51
|
+
// Cursor preToolUse: {"permission": "deny/allow", "user_message": "..."}
|
|
52
|
+
// Cursor before*: {"continue": false/true, "userMessage": "..."}
|
|
53
|
+
// Gemini CLI: {"decision": "deny/allow", "reason": "..."}
|
|
54
|
+
let HOOK_EVENT = ''; // set from stdin hook_event_name
|
|
55
|
+
const CURSOR_BEFORE_EVENTS = new Set([
|
|
56
|
+
'beforeShellExecution', 'beforeReadFile', 'beforeMCPExecution',
|
|
57
|
+
]);
|
|
58
|
+
|
|
52
59
|
function blockTool(reason) {
|
|
53
60
|
if (AGENT_ID === 'cursor') {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
if (CURSOR_BEFORE_EVENTS.has(HOOK_EVENT)) {
|
|
62
|
+
// Cursor before* events use continue: false
|
|
63
|
+
process.stdout.write(JSON.stringify({
|
|
64
|
+
continue: false,
|
|
65
|
+
userMessage: `[SolonGate] ${reason}`,
|
|
66
|
+
agentMessage: `BLOCKED by SolonGate security policy: ${reason}`,
|
|
67
|
+
}));
|
|
68
|
+
} else {
|
|
69
|
+
// Cursor preToolUse uses permission: deny
|
|
70
|
+
process.stdout.write(JSON.stringify({
|
|
71
|
+
permission: 'deny',
|
|
72
|
+
user_message: `[SolonGate] ${reason}`,
|
|
73
|
+
agent_message: `BLOCKED by SolonGate security policy: ${reason}`,
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
60
76
|
process.exit(0);
|
|
61
77
|
} else if (AGENT_ID === 'gemini-cli') {
|
|
62
78
|
process.stdout.write(JSON.stringify({
|
|
@@ -73,7 +89,11 @@ function blockTool(reason) {
|
|
|
73
89
|
|
|
74
90
|
function allowTool() {
|
|
75
91
|
if (AGENT_ID === 'cursor') {
|
|
76
|
-
|
|
92
|
+
if (CURSOR_BEFORE_EVENTS.has(HOOK_EVENT)) {
|
|
93
|
+
process.stdout.write(JSON.stringify({ continue: true }));
|
|
94
|
+
} else {
|
|
95
|
+
process.stdout.write(JSON.stringify({ permission: 'allow' }));
|
|
96
|
+
}
|
|
77
97
|
} else if (AGENT_ID === 'gemini-cli') {
|
|
78
98
|
process.stdout.write(JSON.stringify({ decision: 'allow' }));
|
|
79
99
|
}
|
|
@@ -384,14 +404,62 @@ process.stdin.on('end', async () => {
|
|
|
384
404
|
AGENT_NAME = 'Gemini CLI';
|
|
385
405
|
}
|
|
386
406
|
|
|
387
|
-
//
|
|
407
|
+
// Track hook event name for response format selection
|
|
408
|
+
HOOK_EVENT = raw.hook_event_name || '';
|
|
409
|
+
|
|
410
|
+
// Debug: append guard invocation to debug log
|
|
411
|
+
try {
|
|
412
|
+
const { appendFileSync: afs, mkdirSync: mds } = await import('node:fs');
|
|
413
|
+
mds(resolve('.solongate'), { recursive: true });
|
|
414
|
+
const debugLine = JSON.stringify({ ts: new Date().toISOString(), hook: 'guard', argv: process.argv.slice(2), cursor_version: raw.cursor_version || null, tool_name: raw.tool_name || raw.toolName || raw.command, agent_id: AGENT_ID, hook_event_name: HOOK_EVENT }) + '\n';
|
|
415
|
+
afs(resolve('.solongate', '.debug-guard-log'), debugLine);
|
|
416
|
+
} catch {}
|
|
417
|
+
|
|
418
|
+
// Map Cursor-specific hook events to standard tool_name/tool_input format
|
|
419
|
+
// beforeShellExecution: {command, cwd} → tool_name=Shell, tool_input={command}
|
|
420
|
+
// beforeReadFile: {file_path} → tool_name=Read, tool_input={file_path}
|
|
421
|
+
// afterFileEdit: {file_path, new_content} → tool_name=Edit, tool_input={file_path}
|
|
422
|
+
// beforeMCPExecution: {server_name, tool_name, arguments} → tool_name=<mcp_tool>, tool_input=arguments
|
|
423
|
+
let mappedToolName = raw.tool_name || raw.toolName || '';
|
|
424
|
+
let mappedToolInput = raw.tool_input || raw.toolInput || raw.params || {};
|
|
425
|
+
|
|
426
|
+
if (!mappedToolName && HOOK_EVENT) {
|
|
427
|
+
switch (HOOK_EVENT) {
|
|
428
|
+
case 'beforeShellExecution':
|
|
429
|
+
mappedToolName = 'Shell';
|
|
430
|
+
mappedToolInput = { command: raw.command || '', cwd: raw.cwd || '' };
|
|
431
|
+
break;
|
|
432
|
+
case 'afterShellExecution':
|
|
433
|
+
mappedToolName = 'Shell';
|
|
434
|
+
mappedToolInput = { command: raw.command || '', cwd: raw.cwd || '' };
|
|
435
|
+
break;
|
|
436
|
+
case 'beforeReadFile':
|
|
437
|
+
mappedToolName = 'Read';
|
|
438
|
+
mappedToolInput = { file_path: raw.file_path || '' };
|
|
439
|
+
break;
|
|
440
|
+
case 'afterFileEdit':
|
|
441
|
+
mappedToolName = 'Edit';
|
|
442
|
+
mappedToolInput = { file_path: raw.file_path || '', new_content: raw.new_content || '' };
|
|
443
|
+
break;
|
|
444
|
+
case 'beforeMCPExecution':
|
|
445
|
+
mappedToolName = raw.tool_name || raw.toolName || `mcp:${raw.server_name || 'unknown'}`;
|
|
446
|
+
mappedToolInput = raw.arguments || raw.tool_input || {};
|
|
447
|
+
break;
|
|
448
|
+
case 'afterMCPExecution':
|
|
449
|
+
mappedToolName = raw.tool_name || raw.toolName || `mcp:${raw.server_name || 'unknown'}`;
|
|
450
|
+
mappedToolInput = raw.arguments || raw.tool_input || {};
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Normalize field names across tools
|
|
388
456
|
const data = {
|
|
389
457
|
...raw,
|
|
390
|
-
tool_name:
|
|
391
|
-
tool_input:
|
|
458
|
+
tool_name: mappedToolName,
|
|
459
|
+
tool_input: mappedToolInput,
|
|
392
460
|
tool_response: raw.tool_response || raw.toolResponse || {},
|
|
393
461
|
cwd: raw.cwd || process.cwd(),
|
|
394
|
-
session_id: raw.session_id || raw.sessionId || '',
|
|
462
|
+
session_id: raw.session_id || raw.sessionId || raw.conversation_id || '',
|
|
395
463
|
};
|
|
396
464
|
const args = data.tool_input;
|
|
397
465
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "AI tool security proxy — protect any AI tool server with customizable policies, path/command constraints, rate limiting, and audit logging. Zero code changes required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|