@solongate/proxy 0.4.9 → 0.5.1
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 +106 -92
- package/dist/init.js +104 -90
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -542,9 +542,9 @@ function findConfigFile(explicitPath, createIfMissing = false) {
|
|
|
542
542
|
};
|
|
543
543
|
const starterPath = resolve2(".mcp.json");
|
|
544
544
|
writeFileSync2(starterPath, JSON.stringify(starterConfig, null, 2) + "\n");
|
|
545
|
-
console.
|
|
546
|
-
console.
|
|
547
|
-
return { path: starterPath, type: "mcp" };
|
|
545
|
+
console.log(" Created .mcp.json with starter servers (filesystem, playwright).");
|
|
546
|
+
console.log("");
|
|
547
|
+
return { path: starterPath, type: "mcp", created: true };
|
|
548
548
|
}
|
|
549
549
|
for (const desktopPath of CLAUDE_DESKTOP_PATHS) {
|
|
550
550
|
if (existsSync3(desktopPath)) return { path: desktopPath, type: "claude-desktop" };
|
|
@@ -569,10 +569,8 @@ function isAlreadyProtected(server) {
|
|
|
569
569
|
}
|
|
570
570
|
return false;
|
|
571
571
|
}
|
|
572
|
-
function wrapServer(server, policy
|
|
573
|
-
const
|
|
574
|
-
env.SOLONGATE_API_KEY = apiKey;
|
|
575
|
-
return {
|
|
572
|
+
function wrapServer(server, policy) {
|
|
573
|
+
const result = {
|
|
576
574
|
command: "npx",
|
|
577
575
|
args: [
|
|
578
576
|
"-y",
|
|
@@ -583,9 +581,12 @@ function wrapServer(server, policy, apiKey) {
|
|
|
583
581
|
"--",
|
|
584
582
|
server.command,
|
|
585
583
|
...server.args ?? []
|
|
586
|
-
]
|
|
587
|
-
env
|
|
584
|
+
]
|
|
588
585
|
};
|
|
586
|
+
if (server.env && Object.keys(server.env).length > 0) {
|
|
587
|
+
result.env = { ...server.env };
|
|
588
|
+
}
|
|
589
|
+
return result;
|
|
589
590
|
}
|
|
590
591
|
async function prompt(question) {
|
|
591
592
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
@@ -632,8 +633,8 @@ function parseInitArgs(argv) {
|
|
|
632
633
|
}
|
|
633
634
|
if (!POLICY_PRESETS.includes(options.policy)) {
|
|
634
635
|
if (!existsSync3(resolve2(options.policy))) {
|
|
635
|
-
console.
|
|
636
|
-
console.
|
|
636
|
+
console.log(`Unknown policy: ${options.policy}`);
|
|
637
|
+
console.log(`Available presets: ${POLICY_PRESETS.join(", ")}`);
|
|
637
638
|
process.exit(1);
|
|
638
639
|
}
|
|
639
640
|
}
|
|
@@ -669,17 +670,17 @@ POLICY PRESETS
|
|
|
669
670
|
permissive Allow everything (monitoring + audit only)
|
|
670
671
|
deny-all Block all tool calls
|
|
671
672
|
`;
|
|
672
|
-
console.
|
|
673
|
+
console.log(help);
|
|
673
674
|
}
|
|
674
675
|
function installClaudeCodeHooks(apiKey) {
|
|
675
676
|
const hooksDir = resolve2(".claude", "hooks");
|
|
676
677
|
mkdirSync(hooksDir, { recursive: true });
|
|
677
678
|
const guardPath = join(hooksDir, "guard.mjs");
|
|
678
679
|
writeFileSync2(guardPath, GUARD_SCRIPT);
|
|
679
|
-
console.
|
|
680
|
+
console.log(` Created ${guardPath}`);
|
|
680
681
|
const auditPath = join(hooksDir, "audit.mjs");
|
|
681
682
|
writeFileSync2(auditPath, AUDIT_SCRIPT);
|
|
682
|
-
console.
|
|
683
|
+
console.log(` Created ${auditPath}`);
|
|
683
684
|
const settingsPath = resolve2(".claude", "settings.json");
|
|
684
685
|
let settings = {};
|
|
685
686
|
if (existsSync3(settingsPath)) {
|
|
@@ -719,29 +720,26 @@ function installClaudeCodeHooks(apiKey) {
|
|
|
719
720
|
envObj.SOLONGATE_API_KEY = apiKey;
|
|
720
721
|
settings.env = envObj;
|
|
721
722
|
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
722
|
-
console.
|
|
723
|
-
console.
|
|
724
|
-
console.
|
|
725
|
-
console.
|
|
726
|
-
console.
|
|
723
|
+
console.log(` Created ${settingsPath}`);
|
|
724
|
+
console.log("");
|
|
725
|
+
console.log(" Claude Code hooks installed!");
|
|
726
|
+
console.log(" PreToolUse \u2192 guard.mjs (blocks dangerous calls)");
|
|
727
|
+
console.log(" PostToolUse \u2192 audit.mjs (logs all calls to dashboard)");
|
|
727
728
|
}
|
|
728
729
|
function ensureEnvFile() {
|
|
729
730
|
const envPath = resolve2(".env");
|
|
730
731
|
if (!existsSync3(envPath)) {
|
|
731
|
-
const envContent = `# SolonGate
|
|
732
|
-
# Get your
|
|
732
|
+
const envContent = `# SolonGate Configuration
|
|
733
|
+
# Get your API key from: https://dashboard.solongate.com/api-keys
|
|
733
734
|
# IMPORTANT: Never commit this file to git!
|
|
734
735
|
|
|
735
|
-
#
|
|
736
|
+
# Your SolonGate API key \u2014 the proxy reads this automatically
|
|
736
737
|
SOLONGATE_API_KEY=sg_live_your_key_here
|
|
737
|
-
|
|
738
|
-
# Test key \u2014 local-only, no cloud connection (for development)
|
|
739
|
-
# SOLONGATE_API_KEY=sg_test_your_key_here
|
|
740
738
|
`;
|
|
741
739
|
writeFileSync2(envPath, envContent);
|
|
742
|
-
console.
|
|
743
|
-
console.
|
|
744
|
-
console.
|
|
740
|
+
console.log(` Created .env`);
|
|
741
|
+
console.log(` \u2192 Set your API key in .env (get one at https://dashboard.solongate.com)`);
|
|
742
|
+
console.log("");
|
|
745
743
|
}
|
|
746
744
|
const gitignorePath = resolve2(".gitignore");
|
|
747
745
|
if (existsSync3(gitignorePath)) {
|
|
@@ -757,11 +755,11 @@ SOLONGATE_API_KEY=sg_live_your_key_here
|
|
|
757
755
|
}
|
|
758
756
|
if (updated) {
|
|
759
757
|
writeFileSync2(gitignorePath, gitignore);
|
|
760
|
-
console.
|
|
758
|
+
console.log(` Updated .gitignore (added .env + .mcp.json)`);
|
|
761
759
|
}
|
|
762
760
|
} else {
|
|
763
761
|
writeFileSync2(gitignorePath, ".env\n.env.local\n.mcp.json\nnode_modules/\n");
|
|
764
|
-
console.
|
|
762
|
+
console.log(` Created .gitignore (with .env and .mcp.json excluded)`);
|
|
765
763
|
}
|
|
766
764
|
}
|
|
767
765
|
async function main() {
|
|
@@ -787,57 +785,60 @@ async function main() {
|
|
|
787
785
|
" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
|
|
788
786
|
];
|
|
789
787
|
const bannerColors = [_c.blue1, _c.blue2, _c.blue3, _c.blue4, _c.blue5, _c.blue6];
|
|
790
|
-
console.
|
|
788
|
+
console.log("");
|
|
791
789
|
for (let i = 0; i < bannerLines.length; i++) {
|
|
792
|
-
console.
|
|
790
|
+
console.log(`${_c.bold}${bannerColors[i]}${bannerLines[i]}${_c.reset}`);
|
|
793
791
|
}
|
|
794
|
-
console.
|
|
795
|
-
console.
|
|
796
|
-
console.
|
|
792
|
+
console.log("");
|
|
793
|
+
console.log(` ${_c.dim}${_c.italic}Init Setup${_c.reset}`);
|
|
794
|
+
console.log("");
|
|
795
|
+
await sleep(400);
|
|
797
796
|
const configInfo = findConfigFile(options.configPath, true);
|
|
798
797
|
if (!configInfo) {
|
|
799
|
-
console.
|
|
798
|
+
console.log(" No MCP config found and could not create one.");
|
|
800
799
|
process.exit(1);
|
|
801
800
|
}
|
|
802
|
-
console.
|
|
803
|
-
console.
|
|
804
|
-
console.
|
|
801
|
+
console.log(` Config: ${configInfo.path}`);
|
|
802
|
+
console.log(` Type: ${configInfo.type === "claude-desktop" ? "Claude Desktop" : "MCP JSON"}`);
|
|
803
|
+
console.log("");
|
|
805
804
|
const backupPath = configInfo.path + ".solongate-backup";
|
|
806
805
|
if (options.restore) {
|
|
807
806
|
if (!existsSync3(backupPath)) {
|
|
808
|
-
console.
|
|
807
|
+
console.log(" No backup found. Nothing to restore.");
|
|
809
808
|
process.exit(1);
|
|
810
809
|
}
|
|
811
810
|
copyFileSync(backupPath, configInfo.path);
|
|
812
|
-
console.
|
|
811
|
+
console.log(" Restored original config from backup.");
|
|
813
812
|
process.exit(0);
|
|
814
813
|
}
|
|
815
814
|
const config = readConfig(configInfo.path);
|
|
816
815
|
const serverNames = Object.keys(config.mcpServers);
|
|
817
816
|
if (serverNames.length === 0) {
|
|
818
|
-
console.
|
|
817
|
+
console.log(" No MCP servers found in config.");
|
|
819
818
|
process.exit(1);
|
|
820
819
|
}
|
|
821
|
-
|
|
822
|
-
console.
|
|
820
|
+
await sleep(300);
|
|
821
|
+
console.log(` Found ${serverNames.length} MCP server(s):`);
|
|
822
|
+
console.log("");
|
|
823
823
|
const toProtect = [];
|
|
824
824
|
const alreadyProtected = [];
|
|
825
825
|
const skipped = [];
|
|
826
826
|
for (const name of serverNames) {
|
|
827
827
|
const server = config.mcpServers[name];
|
|
828
|
+
await sleep(200);
|
|
828
829
|
if (isAlreadyProtected(server)) {
|
|
829
830
|
alreadyProtected.push(name);
|
|
830
|
-
console.
|
|
831
|
+
console.log(` [protected] ${name}`);
|
|
831
832
|
} else {
|
|
832
|
-
console.
|
|
833
|
+
console.log(` [exposed] ${name} \u2192 ${server.command} ${(server.args ?? []).join(" ")}`);
|
|
833
834
|
if (options.all) {
|
|
834
835
|
toProtect.push(name);
|
|
835
836
|
}
|
|
836
837
|
}
|
|
837
838
|
}
|
|
838
|
-
console.
|
|
839
|
+
console.log("");
|
|
839
840
|
if (alreadyProtected.length === serverNames.length) {
|
|
840
|
-
console.
|
|
841
|
+
console.log(" All servers are already protected by SolonGate!");
|
|
841
842
|
ensureEnvFile();
|
|
842
843
|
process.exit(0);
|
|
843
844
|
}
|
|
@@ -853,11 +854,14 @@ async function main() {
|
|
|
853
854
|
}
|
|
854
855
|
}
|
|
855
856
|
if (toProtect.length === 0) {
|
|
856
|
-
console.
|
|
857
|
+
console.log(" No servers selected for protection.");
|
|
857
858
|
ensureEnvFile();
|
|
858
859
|
process.exit(0);
|
|
859
860
|
}
|
|
860
|
-
console.
|
|
861
|
+
console.log("");
|
|
862
|
+
await sleep(300);
|
|
863
|
+
ensureEnvFile();
|
|
864
|
+
console.log("");
|
|
861
865
|
let apiKey = options.apiKey || process.env.SOLONGATE_API_KEY || "";
|
|
862
866
|
if (!apiKey) {
|
|
863
867
|
const envPath = resolve2(".env");
|
|
@@ -870,43 +874,47 @@ async function main() {
|
|
|
870
874
|
if (!apiKey || apiKey === "sg_live_your_key_here") {
|
|
871
875
|
if (options.all) {
|
|
872
876
|
apiKey = "sg_test_demo_init_key";
|
|
873
|
-
console.
|
|
874
|
-
console.
|
|
875
|
-
console.
|
|
877
|
+
console.log(" No API key found \u2014 using demo test key.");
|
|
878
|
+
console.log(" Set your real key in .env for cloud features.");
|
|
879
|
+
console.log("");
|
|
876
880
|
} else {
|
|
877
881
|
apiKey = await prompt(" Enter your SolonGate API key (from https://dashboard.solongate.com): ");
|
|
878
882
|
}
|
|
879
883
|
if (!apiKey) {
|
|
880
|
-
console.
|
|
884
|
+
console.log(" API key is required. Get one at https://dashboard.solongate.com");
|
|
881
885
|
process.exit(1);
|
|
882
886
|
}
|
|
883
887
|
}
|
|
884
888
|
if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
|
|
885
|
-
console.
|
|
889
|
+
console.log(" Invalid API key format. Must start with sg_live_ or sg_test_");
|
|
886
890
|
process.exit(1);
|
|
887
891
|
}
|
|
888
|
-
|
|
889
|
-
console.
|
|
890
|
-
|
|
891
|
-
console.
|
|
892
|
+
await sleep(300);
|
|
893
|
+
console.log(` Policy: ${options.policy}`);
|
|
894
|
+
await sleep(150);
|
|
895
|
+
console.log(` API Key: ${apiKey.slice(0, 12)}...${apiKey.slice(-4)}`);
|
|
896
|
+
await sleep(150);
|
|
897
|
+
console.log(` Protecting: ${toProtect.join(", ")}`);
|
|
898
|
+
console.log("");
|
|
892
899
|
const newConfig = { mcpServers: {} };
|
|
893
900
|
for (const name of serverNames) {
|
|
894
901
|
if (toProtect.includes(name)) {
|
|
895
|
-
newConfig.mcpServers[name] = wrapServer(config.mcpServers[name], options.policy
|
|
902
|
+
newConfig.mcpServers[name] = wrapServer(config.mcpServers[name], options.policy);
|
|
896
903
|
} else {
|
|
897
904
|
newConfig.mcpServers[name] = config.mcpServers[name];
|
|
898
905
|
}
|
|
899
906
|
}
|
|
900
907
|
if (options.dryRun) {
|
|
901
|
-
console.
|
|
902
|
-
console.
|
|
903
|
-
console.
|
|
904
|
-
console.
|
|
908
|
+
console.log(" --- DRY RUN (no changes written) ---");
|
|
909
|
+
console.log("");
|
|
910
|
+
console.log(" New config:");
|
|
911
|
+
console.log(JSON.stringify(newConfig, null, 2));
|
|
905
912
|
process.exit(0);
|
|
906
913
|
}
|
|
907
|
-
|
|
914
|
+
await sleep(400);
|
|
915
|
+
if (!configInfo.created && !existsSync3(backupPath)) {
|
|
908
916
|
copyFileSync(configInfo.path, backupPath);
|
|
909
|
-
console.
|
|
917
|
+
console.log(` Backup: ${backupPath}`);
|
|
910
918
|
}
|
|
911
919
|
if (configInfo.type === "claude-desktop") {
|
|
912
920
|
const original = JSON.parse(readFileSync3(configInfo.path, "utf-8"));
|
|
@@ -915,38 +923,43 @@ async function main() {
|
|
|
915
923
|
} else {
|
|
916
924
|
writeFileSync2(configInfo.path, JSON.stringify(newConfig, null, 2) + "\n");
|
|
917
925
|
}
|
|
918
|
-
|
|
919
|
-
console.
|
|
926
|
+
await sleep(300);
|
|
927
|
+
console.log(" Config updated!");
|
|
928
|
+
console.log("");
|
|
929
|
+
await sleep(500);
|
|
920
930
|
installClaudeCodeHooks(apiKey);
|
|
921
|
-
console.
|
|
922
|
-
|
|
923
|
-
console.
|
|
924
|
-
console.
|
|
925
|
-
console.error("");
|
|
931
|
+
console.log("");
|
|
932
|
+
await sleep(400);
|
|
933
|
+
console.log(" \u2500\u2500 Summary \u2500\u2500");
|
|
934
|
+
console.log("");
|
|
926
935
|
for (const name of toProtect) {
|
|
927
|
-
|
|
936
|
+
await sleep(200);
|
|
937
|
+
console.log(` \u2713 ${name} \u2014 protected (${options.policy})`);
|
|
928
938
|
}
|
|
929
939
|
for (const name of alreadyProtected) {
|
|
930
|
-
|
|
940
|
+
await sleep(200);
|
|
941
|
+
console.log(` \u25CF ${name} \u2014 already protected`);
|
|
931
942
|
}
|
|
932
943
|
for (const name of skipped) {
|
|
933
|
-
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
console.
|
|
937
|
-
|
|
938
|
-
console.
|
|
939
|
-
console.
|
|
940
|
-
console.
|
|
941
|
-
console.
|
|
942
|
-
console.
|
|
943
|
-
console.
|
|
944
|
-
console.
|
|
945
|
-
console.
|
|
946
|
-
console.
|
|
947
|
-
console.
|
|
944
|
+
await sleep(200);
|
|
945
|
+
console.log(` \u25CB ${name} \u2014 skipped`);
|
|
946
|
+
}
|
|
947
|
+
console.log("");
|
|
948
|
+
await sleep(500);
|
|
949
|
+
console.log(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
950
|
+
console.log(" \u2502 Setup complete! \u2502");
|
|
951
|
+
console.log(" \u2502 \u2502");
|
|
952
|
+
console.log(" \u2502 MCP tools \u2192 Proxy audit logging \u2502");
|
|
953
|
+
console.log(" \u2502 Built-in tools \u2192 Hook audit logging \u2502");
|
|
954
|
+
console.log(" \u2502 \u2502");
|
|
955
|
+
console.log(" \u2502 View logs: https://dashboard.solongate.com \u2502");
|
|
956
|
+
console.log(" \u2502 To undo: solongate-init --restore \u2502");
|
|
957
|
+
console.log(" \u2502 \u2502");
|
|
958
|
+
console.log(" \u2502 Restart your MCP client to apply changes. \u2502");
|
|
959
|
+
console.log(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
960
|
+
console.log("");
|
|
948
961
|
}
|
|
949
|
-
var POLICY_PRESETS, SEARCH_PATHS, CLAUDE_DESKTOP_PATHS, GUARD_SCRIPT, AUDIT_SCRIPT;
|
|
962
|
+
var POLICY_PRESETS, SEARCH_PATHS, CLAUDE_DESKTOP_PATHS, sleep, GUARD_SCRIPT, AUDIT_SCRIPT;
|
|
950
963
|
var init_init = __esm({
|
|
951
964
|
"src/init.ts"() {
|
|
952
965
|
"use strict";
|
|
@@ -957,6 +970,7 @@ var init_init = __esm({
|
|
|
957
970
|
".claude/mcp.json"
|
|
958
971
|
];
|
|
959
972
|
CLAUDE_DESKTOP_PATHS = process.platform === "win32" ? [join(process.env["APPDATA"] ?? "", "Claude", "claude_desktop_config.json")] : process.platform === "darwin" ? [join(process.env["HOME"] ?? "", "Library", "Application Support", "Claude", "claude_desktop_config.json")] : [join(process.env["HOME"] ?? "", ".config", "claude", "claude_desktop_config.json")];
|
|
973
|
+
sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
960
974
|
GUARD_SCRIPT = `#!/usr/bin/env node
|
|
961
975
|
/**
|
|
962
976
|
* SolonGate Guard Hook for Claude Code (PreToolUse)
|
|
@@ -1164,7 +1178,7 @@ process.stdin.on('end', async () => {
|
|
|
1164
1178
|
});
|
|
1165
1179
|
`;
|
|
1166
1180
|
main().catch((err) => {
|
|
1167
|
-
console.
|
|
1181
|
+
console.log(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
1168
1182
|
process.exit(1);
|
|
1169
1183
|
});
|
|
1170
1184
|
}
|
package/dist/init.js
CHANGED
|
@@ -11,6 +11,7 @@ var SEARCH_PATHS = [
|
|
|
11
11
|
".claude/mcp.json"
|
|
12
12
|
];
|
|
13
13
|
var CLAUDE_DESKTOP_PATHS = process.platform === "win32" ? [join(process.env["APPDATA"] ?? "", "Claude", "claude_desktop_config.json")] : process.platform === "darwin" ? [join(process.env["HOME"] ?? "", "Library", "Application Support", "Claude", "claude_desktop_config.json")] : [join(process.env["HOME"] ?? "", ".config", "claude", "claude_desktop_config.json")];
|
|
14
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
14
15
|
function findConfigFile(explicitPath, createIfMissing = false) {
|
|
15
16
|
if (explicitPath) {
|
|
16
17
|
if (existsSync(explicitPath)) {
|
|
@@ -37,9 +38,9 @@ function findConfigFile(explicitPath, createIfMissing = false) {
|
|
|
37
38
|
};
|
|
38
39
|
const starterPath = resolve(".mcp.json");
|
|
39
40
|
writeFileSync(starterPath, JSON.stringify(starterConfig, null, 2) + "\n");
|
|
40
|
-
console.
|
|
41
|
-
console.
|
|
42
|
-
return { path: starterPath, type: "mcp" };
|
|
41
|
+
console.log(" Created .mcp.json with starter servers (filesystem, playwright).");
|
|
42
|
+
console.log("");
|
|
43
|
+
return { path: starterPath, type: "mcp", created: true };
|
|
43
44
|
}
|
|
44
45
|
for (const desktopPath of CLAUDE_DESKTOP_PATHS) {
|
|
45
46
|
if (existsSync(desktopPath)) return { path: desktopPath, type: "claude-desktop" };
|
|
@@ -64,10 +65,8 @@ function isAlreadyProtected(server) {
|
|
|
64
65
|
}
|
|
65
66
|
return false;
|
|
66
67
|
}
|
|
67
|
-
function wrapServer(server, policy
|
|
68
|
-
const
|
|
69
|
-
env.SOLONGATE_API_KEY = apiKey;
|
|
70
|
-
return {
|
|
68
|
+
function wrapServer(server, policy) {
|
|
69
|
+
const result = {
|
|
71
70
|
command: "npx",
|
|
72
71
|
args: [
|
|
73
72
|
"-y",
|
|
@@ -78,9 +77,12 @@ function wrapServer(server, policy, apiKey) {
|
|
|
78
77
|
"--",
|
|
79
78
|
server.command,
|
|
80
79
|
...server.args ?? []
|
|
81
|
-
]
|
|
82
|
-
env
|
|
80
|
+
]
|
|
83
81
|
};
|
|
82
|
+
if (server.env && Object.keys(server.env).length > 0) {
|
|
83
|
+
result.env = { ...server.env };
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
84
86
|
}
|
|
85
87
|
async function prompt(question) {
|
|
86
88
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
@@ -127,8 +129,8 @@ function parseInitArgs(argv) {
|
|
|
127
129
|
}
|
|
128
130
|
if (!POLICY_PRESETS.includes(options.policy)) {
|
|
129
131
|
if (!existsSync(resolve(options.policy))) {
|
|
130
|
-
console.
|
|
131
|
-
console.
|
|
132
|
+
console.log(`Unknown policy: ${options.policy}`);
|
|
133
|
+
console.log(`Available presets: ${POLICY_PRESETS.join(", ")}`);
|
|
132
134
|
process.exit(1);
|
|
133
135
|
}
|
|
134
136
|
}
|
|
@@ -164,7 +166,7 @@ POLICY PRESETS
|
|
|
164
166
|
permissive Allow everything (monitoring + audit only)
|
|
165
167
|
deny-all Block all tool calls
|
|
166
168
|
`;
|
|
167
|
-
console.
|
|
169
|
+
console.log(help);
|
|
168
170
|
}
|
|
169
171
|
var GUARD_SCRIPT = `#!/usr/bin/env node
|
|
170
172
|
/**
|
|
@@ -377,10 +379,10 @@ function installClaudeCodeHooks(apiKey) {
|
|
|
377
379
|
mkdirSync(hooksDir, { recursive: true });
|
|
378
380
|
const guardPath = join(hooksDir, "guard.mjs");
|
|
379
381
|
writeFileSync(guardPath, GUARD_SCRIPT);
|
|
380
|
-
console.
|
|
382
|
+
console.log(` Created ${guardPath}`);
|
|
381
383
|
const auditPath = join(hooksDir, "audit.mjs");
|
|
382
384
|
writeFileSync(auditPath, AUDIT_SCRIPT);
|
|
383
|
-
console.
|
|
385
|
+
console.log(` Created ${auditPath}`);
|
|
384
386
|
const settingsPath = resolve(".claude", "settings.json");
|
|
385
387
|
let settings = {};
|
|
386
388
|
if (existsSync(settingsPath)) {
|
|
@@ -420,29 +422,26 @@ function installClaudeCodeHooks(apiKey) {
|
|
|
420
422
|
envObj.SOLONGATE_API_KEY = apiKey;
|
|
421
423
|
settings.env = envObj;
|
|
422
424
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
423
|
-
console.
|
|
424
|
-
console.
|
|
425
|
-
console.
|
|
426
|
-
console.
|
|
427
|
-
console.
|
|
425
|
+
console.log(` Created ${settingsPath}`);
|
|
426
|
+
console.log("");
|
|
427
|
+
console.log(" Claude Code hooks installed!");
|
|
428
|
+
console.log(" PreToolUse \u2192 guard.mjs (blocks dangerous calls)");
|
|
429
|
+
console.log(" PostToolUse \u2192 audit.mjs (logs all calls to dashboard)");
|
|
428
430
|
}
|
|
429
431
|
function ensureEnvFile() {
|
|
430
432
|
const envPath = resolve(".env");
|
|
431
433
|
if (!existsSync(envPath)) {
|
|
432
|
-
const envContent = `# SolonGate
|
|
433
|
-
# Get your
|
|
434
|
+
const envContent = `# SolonGate Configuration
|
|
435
|
+
# Get your API key from: https://dashboard.solongate.com/api-keys
|
|
434
436
|
# IMPORTANT: Never commit this file to git!
|
|
435
437
|
|
|
436
|
-
#
|
|
438
|
+
# Your SolonGate API key \u2014 the proxy reads this automatically
|
|
437
439
|
SOLONGATE_API_KEY=sg_live_your_key_here
|
|
438
|
-
|
|
439
|
-
# Test key \u2014 local-only, no cloud connection (for development)
|
|
440
|
-
# SOLONGATE_API_KEY=sg_test_your_key_here
|
|
441
440
|
`;
|
|
442
441
|
writeFileSync(envPath, envContent);
|
|
443
|
-
console.
|
|
444
|
-
console.
|
|
445
|
-
console.
|
|
442
|
+
console.log(` Created .env`);
|
|
443
|
+
console.log(` \u2192 Set your API key in .env (get one at https://dashboard.solongate.com)`);
|
|
444
|
+
console.log("");
|
|
446
445
|
}
|
|
447
446
|
const gitignorePath = resolve(".gitignore");
|
|
448
447
|
if (existsSync(gitignorePath)) {
|
|
@@ -458,11 +457,11 @@ SOLONGATE_API_KEY=sg_live_your_key_here
|
|
|
458
457
|
}
|
|
459
458
|
if (updated) {
|
|
460
459
|
writeFileSync(gitignorePath, gitignore);
|
|
461
|
-
console.
|
|
460
|
+
console.log(` Updated .gitignore (added .env + .mcp.json)`);
|
|
462
461
|
}
|
|
463
462
|
} else {
|
|
464
463
|
writeFileSync(gitignorePath, ".env\n.env.local\n.mcp.json\nnode_modules/\n");
|
|
465
|
-
console.
|
|
464
|
+
console.log(` Created .gitignore (with .env and .mcp.json excluded)`);
|
|
466
465
|
}
|
|
467
466
|
}
|
|
468
467
|
async function main() {
|
|
@@ -488,57 +487,60 @@ async function main() {
|
|
|
488
487
|
" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
|
|
489
488
|
];
|
|
490
489
|
const bannerColors = [_c.blue1, _c.blue2, _c.blue3, _c.blue4, _c.blue5, _c.blue6];
|
|
491
|
-
console.
|
|
490
|
+
console.log("");
|
|
492
491
|
for (let i = 0; i < bannerLines.length; i++) {
|
|
493
|
-
console.
|
|
492
|
+
console.log(`${_c.bold}${bannerColors[i]}${bannerLines[i]}${_c.reset}`);
|
|
494
493
|
}
|
|
495
|
-
console.
|
|
496
|
-
console.
|
|
497
|
-
console.
|
|
494
|
+
console.log("");
|
|
495
|
+
console.log(` ${_c.dim}${_c.italic}Init Setup${_c.reset}`);
|
|
496
|
+
console.log("");
|
|
497
|
+
await sleep(400);
|
|
498
498
|
const configInfo = findConfigFile(options.configPath, true);
|
|
499
499
|
if (!configInfo) {
|
|
500
|
-
console.
|
|
500
|
+
console.log(" No MCP config found and could not create one.");
|
|
501
501
|
process.exit(1);
|
|
502
502
|
}
|
|
503
|
-
console.
|
|
504
|
-
console.
|
|
505
|
-
console.
|
|
503
|
+
console.log(` Config: ${configInfo.path}`);
|
|
504
|
+
console.log(` Type: ${configInfo.type === "claude-desktop" ? "Claude Desktop" : "MCP JSON"}`);
|
|
505
|
+
console.log("");
|
|
506
506
|
const backupPath = configInfo.path + ".solongate-backup";
|
|
507
507
|
if (options.restore) {
|
|
508
508
|
if (!existsSync(backupPath)) {
|
|
509
|
-
console.
|
|
509
|
+
console.log(" No backup found. Nothing to restore.");
|
|
510
510
|
process.exit(1);
|
|
511
511
|
}
|
|
512
512
|
copyFileSync(backupPath, configInfo.path);
|
|
513
|
-
console.
|
|
513
|
+
console.log(" Restored original config from backup.");
|
|
514
514
|
process.exit(0);
|
|
515
515
|
}
|
|
516
516
|
const config = readConfig(configInfo.path);
|
|
517
517
|
const serverNames = Object.keys(config.mcpServers);
|
|
518
518
|
if (serverNames.length === 0) {
|
|
519
|
-
console.
|
|
519
|
+
console.log(" No MCP servers found in config.");
|
|
520
520
|
process.exit(1);
|
|
521
521
|
}
|
|
522
|
-
|
|
523
|
-
console.
|
|
522
|
+
await sleep(300);
|
|
523
|
+
console.log(` Found ${serverNames.length} MCP server(s):`);
|
|
524
|
+
console.log("");
|
|
524
525
|
const toProtect = [];
|
|
525
526
|
const alreadyProtected = [];
|
|
526
527
|
const skipped = [];
|
|
527
528
|
for (const name of serverNames) {
|
|
528
529
|
const server = config.mcpServers[name];
|
|
530
|
+
await sleep(200);
|
|
529
531
|
if (isAlreadyProtected(server)) {
|
|
530
532
|
alreadyProtected.push(name);
|
|
531
|
-
console.
|
|
533
|
+
console.log(` [protected] ${name}`);
|
|
532
534
|
} else {
|
|
533
|
-
console.
|
|
535
|
+
console.log(` [exposed] ${name} \u2192 ${server.command} ${(server.args ?? []).join(" ")}`);
|
|
534
536
|
if (options.all) {
|
|
535
537
|
toProtect.push(name);
|
|
536
538
|
}
|
|
537
539
|
}
|
|
538
540
|
}
|
|
539
|
-
console.
|
|
541
|
+
console.log("");
|
|
540
542
|
if (alreadyProtected.length === serverNames.length) {
|
|
541
|
-
console.
|
|
543
|
+
console.log(" All servers are already protected by SolonGate!");
|
|
542
544
|
ensureEnvFile();
|
|
543
545
|
process.exit(0);
|
|
544
546
|
}
|
|
@@ -554,11 +556,14 @@ async function main() {
|
|
|
554
556
|
}
|
|
555
557
|
}
|
|
556
558
|
if (toProtect.length === 0) {
|
|
557
|
-
console.
|
|
559
|
+
console.log(" No servers selected for protection.");
|
|
558
560
|
ensureEnvFile();
|
|
559
561
|
process.exit(0);
|
|
560
562
|
}
|
|
561
|
-
console.
|
|
563
|
+
console.log("");
|
|
564
|
+
await sleep(300);
|
|
565
|
+
ensureEnvFile();
|
|
566
|
+
console.log("");
|
|
562
567
|
let apiKey = options.apiKey || process.env.SOLONGATE_API_KEY || "";
|
|
563
568
|
if (!apiKey) {
|
|
564
569
|
const envPath = resolve(".env");
|
|
@@ -571,43 +576,47 @@ async function main() {
|
|
|
571
576
|
if (!apiKey || apiKey === "sg_live_your_key_here") {
|
|
572
577
|
if (options.all) {
|
|
573
578
|
apiKey = "sg_test_demo_init_key";
|
|
574
|
-
console.
|
|
575
|
-
console.
|
|
576
|
-
console.
|
|
579
|
+
console.log(" No API key found \u2014 using demo test key.");
|
|
580
|
+
console.log(" Set your real key in .env for cloud features.");
|
|
581
|
+
console.log("");
|
|
577
582
|
} else {
|
|
578
583
|
apiKey = await prompt(" Enter your SolonGate API key (from https://dashboard.solongate.com): ");
|
|
579
584
|
}
|
|
580
585
|
if (!apiKey) {
|
|
581
|
-
console.
|
|
586
|
+
console.log(" API key is required. Get one at https://dashboard.solongate.com");
|
|
582
587
|
process.exit(1);
|
|
583
588
|
}
|
|
584
589
|
}
|
|
585
590
|
if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
|
|
586
|
-
console.
|
|
591
|
+
console.log(" Invalid API key format. Must start with sg_live_ or sg_test_");
|
|
587
592
|
process.exit(1);
|
|
588
593
|
}
|
|
589
|
-
|
|
590
|
-
console.
|
|
591
|
-
|
|
592
|
-
console.
|
|
594
|
+
await sleep(300);
|
|
595
|
+
console.log(` Policy: ${options.policy}`);
|
|
596
|
+
await sleep(150);
|
|
597
|
+
console.log(` API Key: ${apiKey.slice(0, 12)}...${apiKey.slice(-4)}`);
|
|
598
|
+
await sleep(150);
|
|
599
|
+
console.log(` Protecting: ${toProtect.join(", ")}`);
|
|
600
|
+
console.log("");
|
|
593
601
|
const newConfig = { mcpServers: {} };
|
|
594
602
|
for (const name of serverNames) {
|
|
595
603
|
if (toProtect.includes(name)) {
|
|
596
|
-
newConfig.mcpServers[name] = wrapServer(config.mcpServers[name], options.policy
|
|
604
|
+
newConfig.mcpServers[name] = wrapServer(config.mcpServers[name], options.policy);
|
|
597
605
|
} else {
|
|
598
606
|
newConfig.mcpServers[name] = config.mcpServers[name];
|
|
599
607
|
}
|
|
600
608
|
}
|
|
601
609
|
if (options.dryRun) {
|
|
602
|
-
console.
|
|
603
|
-
console.
|
|
604
|
-
console.
|
|
605
|
-
console.
|
|
610
|
+
console.log(" --- DRY RUN (no changes written) ---");
|
|
611
|
+
console.log("");
|
|
612
|
+
console.log(" New config:");
|
|
613
|
+
console.log(JSON.stringify(newConfig, null, 2));
|
|
606
614
|
process.exit(0);
|
|
607
615
|
}
|
|
608
|
-
|
|
616
|
+
await sleep(400);
|
|
617
|
+
if (!configInfo.created && !existsSync(backupPath)) {
|
|
609
618
|
copyFileSync(configInfo.path, backupPath);
|
|
610
|
-
console.
|
|
619
|
+
console.log(` Backup: ${backupPath}`);
|
|
611
620
|
}
|
|
612
621
|
if (configInfo.type === "claude-desktop") {
|
|
613
622
|
const original = JSON.parse(readFileSync(configInfo.path, "utf-8"));
|
|
@@ -616,38 +625,43 @@ async function main() {
|
|
|
616
625
|
} else {
|
|
617
626
|
writeFileSync(configInfo.path, JSON.stringify(newConfig, null, 2) + "\n");
|
|
618
627
|
}
|
|
619
|
-
|
|
620
|
-
console.
|
|
628
|
+
await sleep(300);
|
|
629
|
+
console.log(" Config updated!");
|
|
630
|
+
console.log("");
|
|
631
|
+
await sleep(500);
|
|
621
632
|
installClaudeCodeHooks(apiKey);
|
|
622
|
-
console.
|
|
623
|
-
|
|
624
|
-
console.
|
|
625
|
-
console.
|
|
626
|
-
console.error("");
|
|
633
|
+
console.log("");
|
|
634
|
+
await sleep(400);
|
|
635
|
+
console.log(" \u2500\u2500 Summary \u2500\u2500");
|
|
636
|
+
console.log("");
|
|
627
637
|
for (const name of toProtect) {
|
|
628
|
-
|
|
638
|
+
await sleep(200);
|
|
639
|
+
console.log(` \u2713 ${name} \u2014 protected (${options.policy})`);
|
|
629
640
|
}
|
|
630
641
|
for (const name of alreadyProtected) {
|
|
631
|
-
|
|
642
|
+
await sleep(200);
|
|
643
|
+
console.log(` \u25CF ${name} \u2014 already protected`);
|
|
632
644
|
}
|
|
633
645
|
for (const name of skipped) {
|
|
634
|
-
|
|
646
|
+
await sleep(200);
|
|
647
|
+
console.log(` \u25CB ${name} \u2014 skipped`);
|
|
635
648
|
}
|
|
636
|
-
console.
|
|
637
|
-
|
|
638
|
-
console.
|
|
639
|
-
console.
|
|
640
|
-
console.
|
|
641
|
-
console.
|
|
642
|
-
console.
|
|
643
|
-
console.
|
|
644
|
-
console.
|
|
645
|
-
console.
|
|
646
|
-
console.
|
|
647
|
-
console.
|
|
648
|
-
console.
|
|
649
|
+
console.log("");
|
|
650
|
+
await sleep(500);
|
|
651
|
+
console.log(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
652
|
+
console.log(" \u2502 Setup complete! \u2502");
|
|
653
|
+
console.log(" \u2502 \u2502");
|
|
654
|
+
console.log(" \u2502 MCP tools \u2192 Proxy audit logging \u2502");
|
|
655
|
+
console.log(" \u2502 Built-in tools \u2192 Hook audit logging \u2502");
|
|
656
|
+
console.log(" \u2502 \u2502");
|
|
657
|
+
console.log(" \u2502 View logs: https://dashboard.solongate.com \u2502");
|
|
658
|
+
console.log(" \u2502 To undo: solongate-init --restore \u2502");
|
|
659
|
+
console.log(" \u2502 \u2502");
|
|
660
|
+
console.log(" \u2502 Restart your MCP client to apply changes. \u2502");
|
|
661
|
+
console.log(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
662
|
+
console.log("");
|
|
649
663
|
}
|
|
650
664
|
main().catch((err) => {
|
|
651
|
-
console.
|
|
665
|
+
console.log(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
652
666
|
process.exit(1);
|
|
653
667
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "MCP security proxy — protect any MCP server with customizable policies, path/command constraints, rate limiting, and audit logging. Zero code changes required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|