@solongate/proxy 0.4.9 → 0.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/index.js +89 -91
- package/dist/init.js +88 -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,39 +785,39 @@ 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("");
|
|
797
795
|
const configInfo = findConfigFile(options.configPath, true);
|
|
798
796
|
if (!configInfo) {
|
|
799
|
-
console.
|
|
797
|
+
console.log(" No MCP config found and could not create one.");
|
|
800
798
|
process.exit(1);
|
|
801
799
|
}
|
|
802
|
-
console.
|
|
803
|
-
console.
|
|
804
|
-
console.
|
|
800
|
+
console.log(` Config: ${configInfo.path}`);
|
|
801
|
+
console.log(` Type: ${configInfo.type === "claude-desktop" ? "Claude Desktop" : "MCP JSON"}`);
|
|
802
|
+
console.log("");
|
|
805
803
|
const backupPath = configInfo.path + ".solongate-backup";
|
|
806
804
|
if (options.restore) {
|
|
807
805
|
if (!existsSync3(backupPath)) {
|
|
808
|
-
console.
|
|
806
|
+
console.log(" No backup found. Nothing to restore.");
|
|
809
807
|
process.exit(1);
|
|
810
808
|
}
|
|
811
809
|
copyFileSync(backupPath, configInfo.path);
|
|
812
|
-
console.
|
|
810
|
+
console.log(" Restored original config from backup.");
|
|
813
811
|
process.exit(0);
|
|
814
812
|
}
|
|
815
813
|
const config = readConfig(configInfo.path);
|
|
816
814
|
const serverNames = Object.keys(config.mcpServers);
|
|
817
815
|
if (serverNames.length === 0) {
|
|
818
|
-
console.
|
|
816
|
+
console.log(" No MCP servers found in config.");
|
|
819
817
|
process.exit(1);
|
|
820
818
|
}
|
|
821
|
-
console.
|
|
822
|
-
console.
|
|
819
|
+
console.log(` Found ${serverNames.length} MCP server(s):`);
|
|
820
|
+
console.log("");
|
|
823
821
|
const toProtect = [];
|
|
824
822
|
const alreadyProtected = [];
|
|
825
823
|
const skipped = [];
|
|
@@ -827,17 +825,17 @@ async function main() {
|
|
|
827
825
|
const server = config.mcpServers[name];
|
|
828
826
|
if (isAlreadyProtected(server)) {
|
|
829
827
|
alreadyProtected.push(name);
|
|
830
|
-
console.
|
|
828
|
+
console.log(` [protected] ${name}`);
|
|
831
829
|
} else {
|
|
832
|
-
console.
|
|
830
|
+
console.log(` [exposed] ${name} \u2192 ${server.command} ${(server.args ?? []).join(" ")}`);
|
|
833
831
|
if (options.all) {
|
|
834
832
|
toProtect.push(name);
|
|
835
833
|
}
|
|
836
834
|
}
|
|
837
835
|
}
|
|
838
|
-
console.
|
|
836
|
+
console.log("");
|
|
839
837
|
if (alreadyProtected.length === serverNames.length) {
|
|
840
|
-
console.
|
|
838
|
+
console.log(" All servers are already protected by SolonGate!");
|
|
841
839
|
ensureEnvFile();
|
|
842
840
|
process.exit(0);
|
|
843
841
|
}
|
|
@@ -853,11 +851,13 @@ async function main() {
|
|
|
853
851
|
}
|
|
854
852
|
}
|
|
855
853
|
if (toProtect.length === 0) {
|
|
856
|
-
console.
|
|
854
|
+
console.log(" No servers selected for protection.");
|
|
857
855
|
ensureEnvFile();
|
|
858
856
|
process.exit(0);
|
|
859
857
|
}
|
|
860
|
-
console.
|
|
858
|
+
console.log("");
|
|
859
|
+
ensureEnvFile();
|
|
860
|
+
console.log("");
|
|
861
861
|
let apiKey = options.apiKey || process.env.SOLONGATE_API_KEY || "";
|
|
862
862
|
if (!apiKey) {
|
|
863
863
|
const envPath = resolve2(".env");
|
|
@@ -870,43 +870,43 @@ async function main() {
|
|
|
870
870
|
if (!apiKey || apiKey === "sg_live_your_key_here") {
|
|
871
871
|
if (options.all) {
|
|
872
872
|
apiKey = "sg_test_demo_init_key";
|
|
873
|
-
console.
|
|
874
|
-
console.
|
|
875
|
-
console.
|
|
873
|
+
console.log(" No API key found \u2014 using demo test key.");
|
|
874
|
+
console.log(" Set your real key in .env for cloud features.");
|
|
875
|
+
console.log("");
|
|
876
876
|
} else {
|
|
877
877
|
apiKey = await prompt(" Enter your SolonGate API key (from https://dashboard.solongate.com): ");
|
|
878
878
|
}
|
|
879
879
|
if (!apiKey) {
|
|
880
|
-
console.
|
|
880
|
+
console.log(" API key is required. Get one at https://dashboard.solongate.com");
|
|
881
881
|
process.exit(1);
|
|
882
882
|
}
|
|
883
883
|
}
|
|
884
884
|
if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
|
|
885
|
-
console.
|
|
885
|
+
console.log(" Invalid API key format. Must start with sg_live_ or sg_test_");
|
|
886
886
|
process.exit(1);
|
|
887
887
|
}
|
|
888
|
-
console.
|
|
889
|
-
console.
|
|
890
|
-
console.
|
|
891
|
-
console.
|
|
888
|
+
console.log(` Policy: ${options.policy}`);
|
|
889
|
+
console.log(` API Key: ${apiKey.slice(0, 12)}...${apiKey.slice(-4)}`);
|
|
890
|
+
console.log(` Protecting: ${toProtect.join(", ")}`);
|
|
891
|
+
console.log("");
|
|
892
892
|
const newConfig = { mcpServers: {} };
|
|
893
893
|
for (const name of serverNames) {
|
|
894
894
|
if (toProtect.includes(name)) {
|
|
895
|
-
newConfig.mcpServers[name] = wrapServer(config.mcpServers[name], options.policy
|
|
895
|
+
newConfig.mcpServers[name] = wrapServer(config.mcpServers[name], options.policy);
|
|
896
896
|
} else {
|
|
897
897
|
newConfig.mcpServers[name] = config.mcpServers[name];
|
|
898
898
|
}
|
|
899
899
|
}
|
|
900
900
|
if (options.dryRun) {
|
|
901
|
-
console.
|
|
902
|
-
console.
|
|
903
|
-
console.
|
|
904
|
-
console.
|
|
901
|
+
console.log(" --- DRY RUN (no changes written) ---");
|
|
902
|
+
console.log("");
|
|
903
|
+
console.log(" New config:");
|
|
904
|
+
console.log(JSON.stringify(newConfig, null, 2));
|
|
905
905
|
process.exit(0);
|
|
906
906
|
}
|
|
907
|
-
if (!existsSync3(backupPath)) {
|
|
907
|
+
if (!configInfo.created && !existsSync3(backupPath)) {
|
|
908
908
|
copyFileSync(configInfo.path, backupPath);
|
|
909
|
-
console.
|
|
909
|
+
console.log(` Backup: ${backupPath}`);
|
|
910
910
|
}
|
|
911
911
|
if (configInfo.type === "claude-desktop") {
|
|
912
912
|
const original = JSON.parse(readFileSync3(configInfo.path, "utf-8"));
|
|
@@ -915,36 +915,34 @@ async function main() {
|
|
|
915
915
|
} else {
|
|
916
916
|
writeFileSync2(configInfo.path, JSON.stringify(newConfig, null, 2) + "\n");
|
|
917
917
|
}
|
|
918
|
-
console.
|
|
919
|
-
console.
|
|
918
|
+
console.log(" Config updated!");
|
|
919
|
+
console.log("");
|
|
920
920
|
installClaudeCodeHooks(apiKey);
|
|
921
|
-
console.
|
|
922
|
-
|
|
923
|
-
console.
|
|
924
|
-
console.error(" \u2500\u2500 Summary \u2500\u2500");
|
|
925
|
-
console.error("");
|
|
921
|
+
console.log("");
|
|
922
|
+
console.log(" \u2500\u2500 Summary \u2500\u2500");
|
|
923
|
+
console.log("");
|
|
926
924
|
for (const name of toProtect) {
|
|
927
|
-
console.
|
|
925
|
+
console.log(` \u2713 ${name} \u2014 protected (${options.policy})`);
|
|
928
926
|
}
|
|
929
927
|
for (const name of alreadyProtected) {
|
|
930
|
-
console.
|
|
928
|
+
console.log(` \u25CF ${name} \u2014 already protected`);
|
|
931
929
|
}
|
|
932
930
|
for (const name of skipped) {
|
|
933
|
-
console.
|
|
934
|
-
}
|
|
935
|
-
console.
|
|
936
|
-
console.
|
|
937
|
-
console.
|
|
938
|
-
console.
|
|
939
|
-
console.
|
|
940
|
-
console.
|
|
941
|
-
console.
|
|
942
|
-
console.
|
|
943
|
-
console.
|
|
944
|
-
console.
|
|
945
|
-
console.
|
|
946
|
-
console.
|
|
947
|
-
console.
|
|
931
|
+
console.log(` \u25CB ${name} \u2014 skipped`);
|
|
932
|
+
}
|
|
933
|
+
console.log("");
|
|
934
|
+
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");
|
|
935
|
+
console.log(" \u2502 Setup complete! \u2502");
|
|
936
|
+
console.log(" \u2502 \u2502");
|
|
937
|
+
console.log(" \u2502 MCP tools \u2192 Proxy audit logging \u2502");
|
|
938
|
+
console.log(" \u2502 Built-in tools \u2192 Hook audit logging \u2502");
|
|
939
|
+
console.log(" \u2502 \u2502");
|
|
940
|
+
console.log(" \u2502 View logs: https://dashboard.solongate.com \u2502");
|
|
941
|
+
console.log(" \u2502 To undo: solongate-init --restore \u2502");
|
|
942
|
+
console.log(" \u2502 \u2502");
|
|
943
|
+
console.log(" \u2502 Restart your MCP client to apply changes. \u2502");
|
|
944
|
+
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");
|
|
945
|
+
console.log("");
|
|
948
946
|
}
|
|
949
947
|
var POLICY_PRESETS, SEARCH_PATHS, CLAUDE_DESKTOP_PATHS, GUARD_SCRIPT, AUDIT_SCRIPT;
|
|
950
948
|
var init_init = __esm({
|
|
@@ -1164,7 +1162,7 @@ process.stdin.on('end', async () => {
|
|
|
1164
1162
|
});
|
|
1165
1163
|
`;
|
|
1166
1164
|
main().catch((err) => {
|
|
1167
|
-
console.
|
|
1165
|
+
console.log(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
1168
1166
|
process.exit(1);
|
|
1169
1167
|
});
|
|
1170
1168
|
}
|
package/dist/init.js
CHANGED
|
@@ -37,9 +37,9 @@ function findConfigFile(explicitPath, createIfMissing = false) {
|
|
|
37
37
|
};
|
|
38
38
|
const starterPath = resolve(".mcp.json");
|
|
39
39
|
writeFileSync(starterPath, JSON.stringify(starterConfig, null, 2) + "\n");
|
|
40
|
-
console.
|
|
41
|
-
console.
|
|
42
|
-
return { path: starterPath, type: "mcp" };
|
|
40
|
+
console.log(" Created .mcp.json with starter servers (filesystem, playwright).");
|
|
41
|
+
console.log("");
|
|
42
|
+
return { path: starterPath, type: "mcp", created: true };
|
|
43
43
|
}
|
|
44
44
|
for (const desktopPath of CLAUDE_DESKTOP_PATHS) {
|
|
45
45
|
if (existsSync(desktopPath)) return { path: desktopPath, type: "claude-desktop" };
|
|
@@ -64,10 +64,8 @@ function isAlreadyProtected(server) {
|
|
|
64
64
|
}
|
|
65
65
|
return false;
|
|
66
66
|
}
|
|
67
|
-
function wrapServer(server, policy
|
|
68
|
-
const
|
|
69
|
-
env.SOLONGATE_API_KEY = apiKey;
|
|
70
|
-
return {
|
|
67
|
+
function wrapServer(server, policy) {
|
|
68
|
+
const result = {
|
|
71
69
|
command: "npx",
|
|
72
70
|
args: [
|
|
73
71
|
"-y",
|
|
@@ -78,9 +76,12 @@ function wrapServer(server, policy, apiKey) {
|
|
|
78
76
|
"--",
|
|
79
77
|
server.command,
|
|
80
78
|
...server.args ?? []
|
|
81
|
-
]
|
|
82
|
-
env
|
|
79
|
+
]
|
|
83
80
|
};
|
|
81
|
+
if (server.env && Object.keys(server.env).length > 0) {
|
|
82
|
+
result.env = { ...server.env };
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
84
85
|
}
|
|
85
86
|
async function prompt(question) {
|
|
86
87
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
@@ -127,8 +128,8 @@ function parseInitArgs(argv) {
|
|
|
127
128
|
}
|
|
128
129
|
if (!POLICY_PRESETS.includes(options.policy)) {
|
|
129
130
|
if (!existsSync(resolve(options.policy))) {
|
|
130
|
-
console.
|
|
131
|
-
console.
|
|
131
|
+
console.log(`Unknown policy: ${options.policy}`);
|
|
132
|
+
console.log(`Available presets: ${POLICY_PRESETS.join(", ")}`);
|
|
132
133
|
process.exit(1);
|
|
133
134
|
}
|
|
134
135
|
}
|
|
@@ -164,7 +165,7 @@ POLICY PRESETS
|
|
|
164
165
|
permissive Allow everything (monitoring + audit only)
|
|
165
166
|
deny-all Block all tool calls
|
|
166
167
|
`;
|
|
167
|
-
console.
|
|
168
|
+
console.log(help);
|
|
168
169
|
}
|
|
169
170
|
var GUARD_SCRIPT = `#!/usr/bin/env node
|
|
170
171
|
/**
|
|
@@ -377,10 +378,10 @@ function installClaudeCodeHooks(apiKey) {
|
|
|
377
378
|
mkdirSync(hooksDir, { recursive: true });
|
|
378
379
|
const guardPath = join(hooksDir, "guard.mjs");
|
|
379
380
|
writeFileSync(guardPath, GUARD_SCRIPT);
|
|
380
|
-
console.
|
|
381
|
+
console.log(` Created ${guardPath}`);
|
|
381
382
|
const auditPath = join(hooksDir, "audit.mjs");
|
|
382
383
|
writeFileSync(auditPath, AUDIT_SCRIPT);
|
|
383
|
-
console.
|
|
384
|
+
console.log(` Created ${auditPath}`);
|
|
384
385
|
const settingsPath = resolve(".claude", "settings.json");
|
|
385
386
|
let settings = {};
|
|
386
387
|
if (existsSync(settingsPath)) {
|
|
@@ -420,29 +421,26 @@ function installClaudeCodeHooks(apiKey) {
|
|
|
420
421
|
envObj.SOLONGATE_API_KEY = apiKey;
|
|
421
422
|
settings.env = envObj;
|
|
422
423
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
423
|
-
console.
|
|
424
|
-
console.
|
|
425
|
-
console.
|
|
426
|
-
console.
|
|
427
|
-
console.
|
|
424
|
+
console.log(` Created ${settingsPath}`);
|
|
425
|
+
console.log("");
|
|
426
|
+
console.log(" Claude Code hooks installed!");
|
|
427
|
+
console.log(" PreToolUse \u2192 guard.mjs (blocks dangerous calls)");
|
|
428
|
+
console.log(" PostToolUse \u2192 audit.mjs (logs all calls to dashboard)");
|
|
428
429
|
}
|
|
429
430
|
function ensureEnvFile() {
|
|
430
431
|
const envPath = resolve(".env");
|
|
431
432
|
if (!existsSync(envPath)) {
|
|
432
|
-
const envContent = `# SolonGate
|
|
433
|
-
# Get your
|
|
433
|
+
const envContent = `# SolonGate Configuration
|
|
434
|
+
# Get your API key from: https://dashboard.solongate.com/api-keys
|
|
434
435
|
# IMPORTANT: Never commit this file to git!
|
|
435
436
|
|
|
436
|
-
#
|
|
437
|
+
# Your SolonGate API key \u2014 the proxy reads this automatically
|
|
437
438
|
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
439
|
`;
|
|
442
440
|
writeFileSync(envPath, envContent);
|
|
443
|
-
console.
|
|
444
|
-
console.
|
|
445
|
-
console.
|
|
441
|
+
console.log(` Created .env`);
|
|
442
|
+
console.log(` \u2192 Set your API key in .env (get one at https://dashboard.solongate.com)`);
|
|
443
|
+
console.log("");
|
|
446
444
|
}
|
|
447
445
|
const gitignorePath = resolve(".gitignore");
|
|
448
446
|
if (existsSync(gitignorePath)) {
|
|
@@ -458,11 +456,11 @@ SOLONGATE_API_KEY=sg_live_your_key_here
|
|
|
458
456
|
}
|
|
459
457
|
if (updated) {
|
|
460
458
|
writeFileSync(gitignorePath, gitignore);
|
|
461
|
-
console.
|
|
459
|
+
console.log(` Updated .gitignore (added .env + .mcp.json)`);
|
|
462
460
|
}
|
|
463
461
|
} else {
|
|
464
462
|
writeFileSync(gitignorePath, ".env\n.env.local\n.mcp.json\nnode_modules/\n");
|
|
465
|
-
console.
|
|
463
|
+
console.log(` Created .gitignore (with .env and .mcp.json excluded)`);
|
|
466
464
|
}
|
|
467
465
|
}
|
|
468
466
|
async function main() {
|
|
@@ -488,39 +486,39 @@ async function main() {
|
|
|
488
486
|
" \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
487
|
];
|
|
490
488
|
const bannerColors = [_c.blue1, _c.blue2, _c.blue3, _c.blue4, _c.blue5, _c.blue6];
|
|
491
|
-
console.
|
|
489
|
+
console.log("");
|
|
492
490
|
for (let i = 0; i < bannerLines.length; i++) {
|
|
493
|
-
console.
|
|
491
|
+
console.log(`${_c.bold}${bannerColors[i]}${bannerLines[i]}${_c.reset}`);
|
|
494
492
|
}
|
|
495
|
-
console.
|
|
496
|
-
console.
|
|
497
|
-
console.
|
|
493
|
+
console.log("");
|
|
494
|
+
console.log(` ${_c.dim}${_c.italic}Init Setup${_c.reset}`);
|
|
495
|
+
console.log("");
|
|
498
496
|
const configInfo = findConfigFile(options.configPath, true);
|
|
499
497
|
if (!configInfo) {
|
|
500
|
-
console.
|
|
498
|
+
console.log(" No MCP config found and could not create one.");
|
|
501
499
|
process.exit(1);
|
|
502
500
|
}
|
|
503
|
-
console.
|
|
504
|
-
console.
|
|
505
|
-
console.
|
|
501
|
+
console.log(` Config: ${configInfo.path}`);
|
|
502
|
+
console.log(` Type: ${configInfo.type === "claude-desktop" ? "Claude Desktop" : "MCP JSON"}`);
|
|
503
|
+
console.log("");
|
|
506
504
|
const backupPath = configInfo.path + ".solongate-backup";
|
|
507
505
|
if (options.restore) {
|
|
508
506
|
if (!existsSync(backupPath)) {
|
|
509
|
-
console.
|
|
507
|
+
console.log(" No backup found. Nothing to restore.");
|
|
510
508
|
process.exit(1);
|
|
511
509
|
}
|
|
512
510
|
copyFileSync(backupPath, configInfo.path);
|
|
513
|
-
console.
|
|
511
|
+
console.log(" Restored original config from backup.");
|
|
514
512
|
process.exit(0);
|
|
515
513
|
}
|
|
516
514
|
const config = readConfig(configInfo.path);
|
|
517
515
|
const serverNames = Object.keys(config.mcpServers);
|
|
518
516
|
if (serverNames.length === 0) {
|
|
519
|
-
console.
|
|
517
|
+
console.log(" No MCP servers found in config.");
|
|
520
518
|
process.exit(1);
|
|
521
519
|
}
|
|
522
|
-
console.
|
|
523
|
-
console.
|
|
520
|
+
console.log(` Found ${serverNames.length} MCP server(s):`);
|
|
521
|
+
console.log("");
|
|
524
522
|
const toProtect = [];
|
|
525
523
|
const alreadyProtected = [];
|
|
526
524
|
const skipped = [];
|
|
@@ -528,17 +526,17 @@ async function main() {
|
|
|
528
526
|
const server = config.mcpServers[name];
|
|
529
527
|
if (isAlreadyProtected(server)) {
|
|
530
528
|
alreadyProtected.push(name);
|
|
531
|
-
console.
|
|
529
|
+
console.log(` [protected] ${name}`);
|
|
532
530
|
} else {
|
|
533
|
-
console.
|
|
531
|
+
console.log(` [exposed] ${name} \u2192 ${server.command} ${(server.args ?? []).join(" ")}`);
|
|
534
532
|
if (options.all) {
|
|
535
533
|
toProtect.push(name);
|
|
536
534
|
}
|
|
537
535
|
}
|
|
538
536
|
}
|
|
539
|
-
console.
|
|
537
|
+
console.log("");
|
|
540
538
|
if (alreadyProtected.length === serverNames.length) {
|
|
541
|
-
console.
|
|
539
|
+
console.log(" All servers are already protected by SolonGate!");
|
|
542
540
|
ensureEnvFile();
|
|
543
541
|
process.exit(0);
|
|
544
542
|
}
|
|
@@ -554,11 +552,13 @@ async function main() {
|
|
|
554
552
|
}
|
|
555
553
|
}
|
|
556
554
|
if (toProtect.length === 0) {
|
|
557
|
-
console.
|
|
555
|
+
console.log(" No servers selected for protection.");
|
|
558
556
|
ensureEnvFile();
|
|
559
557
|
process.exit(0);
|
|
560
558
|
}
|
|
561
|
-
console.
|
|
559
|
+
console.log("");
|
|
560
|
+
ensureEnvFile();
|
|
561
|
+
console.log("");
|
|
562
562
|
let apiKey = options.apiKey || process.env.SOLONGATE_API_KEY || "";
|
|
563
563
|
if (!apiKey) {
|
|
564
564
|
const envPath = resolve(".env");
|
|
@@ -571,43 +571,43 @@ async function main() {
|
|
|
571
571
|
if (!apiKey || apiKey === "sg_live_your_key_here") {
|
|
572
572
|
if (options.all) {
|
|
573
573
|
apiKey = "sg_test_demo_init_key";
|
|
574
|
-
console.
|
|
575
|
-
console.
|
|
576
|
-
console.
|
|
574
|
+
console.log(" No API key found \u2014 using demo test key.");
|
|
575
|
+
console.log(" Set your real key in .env for cloud features.");
|
|
576
|
+
console.log("");
|
|
577
577
|
} else {
|
|
578
578
|
apiKey = await prompt(" Enter your SolonGate API key (from https://dashboard.solongate.com): ");
|
|
579
579
|
}
|
|
580
580
|
if (!apiKey) {
|
|
581
|
-
console.
|
|
581
|
+
console.log(" API key is required. Get one at https://dashboard.solongate.com");
|
|
582
582
|
process.exit(1);
|
|
583
583
|
}
|
|
584
584
|
}
|
|
585
585
|
if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
|
|
586
|
-
console.
|
|
586
|
+
console.log(" Invalid API key format. Must start with sg_live_ or sg_test_");
|
|
587
587
|
process.exit(1);
|
|
588
588
|
}
|
|
589
|
-
console.
|
|
590
|
-
console.
|
|
591
|
-
console.
|
|
592
|
-
console.
|
|
589
|
+
console.log(` Policy: ${options.policy}`);
|
|
590
|
+
console.log(` API Key: ${apiKey.slice(0, 12)}...${apiKey.slice(-4)}`);
|
|
591
|
+
console.log(` Protecting: ${toProtect.join(", ")}`);
|
|
592
|
+
console.log("");
|
|
593
593
|
const newConfig = { mcpServers: {} };
|
|
594
594
|
for (const name of serverNames) {
|
|
595
595
|
if (toProtect.includes(name)) {
|
|
596
|
-
newConfig.mcpServers[name] = wrapServer(config.mcpServers[name], options.policy
|
|
596
|
+
newConfig.mcpServers[name] = wrapServer(config.mcpServers[name], options.policy);
|
|
597
597
|
} else {
|
|
598
598
|
newConfig.mcpServers[name] = config.mcpServers[name];
|
|
599
599
|
}
|
|
600
600
|
}
|
|
601
601
|
if (options.dryRun) {
|
|
602
|
-
console.
|
|
603
|
-
console.
|
|
604
|
-
console.
|
|
605
|
-
console.
|
|
602
|
+
console.log(" --- DRY RUN (no changes written) ---");
|
|
603
|
+
console.log("");
|
|
604
|
+
console.log(" New config:");
|
|
605
|
+
console.log(JSON.stringify(newConfig, null, 2));
|
|
606
606
|
process.exit(0);
|
|
607
607
|
}
|
|
608
|
-
if (!existsSync(backupPath)) {
|
|
608
|
+
if (!configInfo.created && !existsSync(backupPath)) {
|
|
609
609
|
copyFileSync(configInfo.path, backupPath);
|
|
610
|
-
console.
|
|
610
|
+
console.log(` Backup: ${backupPath}`);
|
|
611
611
|
}
|
|
612
612
|
if (configInfo.type === "claude-desktop") {
|
|
613
613
|
const original = JSON.parse(readFileSync(configInfo.path, "utf-8"));
|
|
@@ -616,38 +616,36 @@ async function main() {
|
|
|
616
616
|
} else {
|
|
617
617
|
writeFileSync(configInfo.path, JSON.stringify(newConfig, null, 2) + "\n");
|
|
618
618
|
}
|
|
619
|
-
console.
|
|
620
|
-
console.
|
|
619
|
+
console.log(" Config updated!");
|
|
620
|
+
console.log("");
|
|
621
621
|
installClaudeCodeHooks(apiKey);
|
|
622
|
-
console.
|
|
623
|
-
|
|
624
|
-
console.
|
|
625
|
-
console.error(" \u2500\u2500 Summary \u2500\u2500");
|
|
626
|
-
console.error("");
|
|
622
|
+
console.log("");
|
|
623
|
+
console.log(" \u2500\u2500 Summary \u2500\u2500");
|
|
624
|
+
console.log("");
|
|
627
625
|
for (const name of toProtect) {
|
|
628
|
-
console.
|
|
626
|
+
console.log(` \u2713 ${name} \u2014 protected (${options.policy})`);
|
|
629
627
|
}
|
|
630
628
|
for (const name of alreadyProtected) {
|
|
631
|
-
console.
|
|
629
|
+
console.log(` \u25CF ${name} \u2014 already protected`);
|
|
632
630
|
}
|
|
633
631
|
for (const name of skipped) {
|
|
634
|
-
console.
|
|
632
|
+
console.log(` \u25CB ${name} \u2014 skipped`);
|
|
635
633
|
}
|
|
636
|
-
console.
|
|
637
|
-
console.
|
|
638
|
-
console.
|
|
639
|
-
console.
|
|
640
|
-
console.
|
|
641
|
-
console.
|
|
642
|
-
console.
|
|
643
|
-
console.
|
|
644
|
-
console.
|
|
645
|
-
console.
|
|
646
|
-
console.
|
|
647
|
-
console.
|
|
648
|
-
console.
|
|
634
|
+
console.log("");
|
|
635
|
+
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");
|
|
636
|
+
console.log(" \u2502 Setup complete! \u2502");
|
|
637
|
+
console.log(" \u2502 \u2502");
|
|
638
|
+
console.log(" \u2502 MCP tools \u2192 Proxy audit logging \u2502");
|
|
639
|
+
console.log(" \u2502 Built-in tools \u2192 Hook audit logging \u2502");
|
|
640
|
+
console.log(" \u2502 \u2502");
|
|
641
|
+
console.log(" \u2502 View logs: https://dashboard.solongate.com \u2502");
|
|
642
|
+
console.log(" \u2502 To undo: solongate-init --restore \u2502");
|
|
643
|
+
console.log(" \u2502 \u2502");
|
|
644
|
+
console.log(" \u2502 Restart your MCP client to apply changes. \u2502");
|
|
645
|
+
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");
|
|
646
|
+
console.log("");
|
|
649
647
|
}
|
|
650
648
|
main().catch((err) => {
|
|
651
|
-
console.
|
|
649
|
+
console.log(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
652
650
|
process.exit(1);
|
|
653
651
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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": {
|