@solongate/proxy 0.4.8 → 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 +108 -106
- package/dist/init.js +107 -105
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -516,7 +516,7 @@ var init_exports = {};
|
|
|
516
516
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, copyFileSync, mkdirSync } from "fs";
|
|
517
517
|
import { resolve as resolve2, join } from "path";
|
|
518
518
|
import { createInterface } from "readline";
|
|
519
|
-
function findConfigFile(explicitPath) {
|
|
519
|
+
function findConfigFile(explicitPath, createIfMissing = false) {
|
|
520
520
|
if (explicitPath) {
|
|
521
521
|
if (existsSync3(explicitPath)) {
|
|
522
522
|
return { path: resolve2(explicitPath), type: "mcp" };
|
|
@@ -527,6 +527,25 @@ function findConfigFile(explicitPath) {
|
|
|
527
527
|
const full = resolve2(searchPath);
|
|
528
528
|
if (existsSync3(full)) return { path: full, type: "mcp" };
|
|
529
529
|
}
|
|
530
|
+
if (createIfMissing) {
|
|
531
|
+
const starterConfig = {
|
|
532
|
+
mcpServers: {
|
|
533
|
+
"filesystem": {
|
|
534
|
+
command: "npx",
|
|
535
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "."]
|
|
536
|
+
},
|
|
537
|
+
"playwright": {
|
|
538
|
+
command: "npx",
|
|
539
|
+
args: ["-y", "@playwright/mcp@latest"]
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
const starterPath = resolve2(".mcp.json");
|
|
544
|
+
writeFileSync2(starterPath, JSON.stringify(starterConfig, null, 2) + "\n");
|
|
545
|
+
console.log(" Created .mcp.json with starter servers (filesystem, playwright).");
|
|
546
|
+
console.log("");
|
|
547
|
+
return { path: starterPath, type: "mcp", created: true };
|
|
548
|
+
}
|
|
530
549
|
for (const desktopPath of CLAUDE_DESKTOP_PATHS) {
|
|
531
550
|
if (existsSync3(desktopPath)) return { path: desktopPath, type: "claude-desktop" };
|
|
532
551
|
}
|
|
@@ -550,10 +569,8 @@ function isAlreadyProtected(server) {
|
|
|
550
569
|
}
|
|
551
570
|
return false;
|
|
552
571
|
}
|
|
553
|
-
function wrapServer(server, policy
|
|
554
|
-
const
|
|
555
|
-
env.SOLONGATE_API_KEY = apiKey;
|
|
556
|
-
return {
|
|
572
|
+
function wrapServer(server, policy) {
|
|
573
|
+
const result = {
|
|
557
574
|
command: "npx",
|
|
558
575
|
args: [
|
|
559
576
|
"-y",
|
|
@@ -564,9 +581,12 @@ function wrapServer(server, policy, apiKey) {
|
|
|
564
581
|
"--",
|
|
565
582
|
server.command,
|
|
566
583
|
...server.args ?? []
|
|
567
|
-
]
|
|
568
|
-
env
|
|
584
|
+
]
|
|
569
585
|
};
|
|
586
|
+
if (server.env && Object.keys(server.env).length > 0) {
|
|
587
|
+
result.env = { ...server.env };
|
|
588
|
+
}
|
|
589
|
+
return result;
|
|
570
590
|
}
|
|
571
591
|
async function prompt(question) {
|
|
572
592
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
@@ -613,8 +633,8 @@ function parseInitArgs(argv) {
|
|
|
613
633
|
}
|
|
614
634
|
if (!POLICY_PRESETS.includes(options.policy)) {
|
|
615
635
|
if (!existsSync3(resolve2(options.policy))) {
|
|
616
|
-
console.
|
|
617
|
-
console.
|
|
636
|
+
console.log(`Unknown policy: ${options.policy}`);
|
|
637
|
+
console.log(`Available presets: ${POLICY_PRESETS.join(", ")}`);
|
|
618
638
|
process.exit(1);
|
|
619
639
|
}
|
|
620
640
|
}
|
|
@@ -650,17 +670,17 @@ POLICY PRESETS
|
|
|
650
670
|
permissive Allow everything (monitoring + audit only)
|
|
651
671
|
deny-all Block all tool calls
|
|
652
672
|
`;
|
|
653
|
-
console.
|
|
673
|
+
console.log(help);
|
|
654
674
|
}
|
|
655
675
|
function installClaudeCodeHooks(apiKey) {
|
|
656
676
|
const hooksDir = resolve2(".claude", "hooks");
|
|
657
677
|
mkdirSync(hooksDir, { recursive: true });
|
|
658
678
|
const guardPath = join(hooksDir, "guard.mjs");
|
|
659
679
|
writeFileSync2(guardPath, GUARD_SCRIPT);
|
|
660
|
-
console.
|
|
680
|
+
console.log(` Created ${guardPath}`);
|
|
661
681
|
const auditPath = join(hooksDir, "audit.mjs");
|
|
662
682
|
writeFileSync2(auditPath, AUDIT_SCRIPT);
|
|
663
|
-
console.
|
|
683
|
+
console.log(` Created ${auditPath}`);
|
|
664
684
|
const settingsPath = resolve2(".claude", "settings.json");
|
|
665
685
|
let settings = {};
|
|
666
686
|
if (existsSync3(settingsPath)) {
|
|
@@ -700,29 +720,26 @@ function installClaudeCodeHooks(apiKey) {
|
|
|
700
720
|
envObj.SOLONGATE_API_KEY = apiKey;
|
|
701
721
|
settings.env = envObj;
|
|
702
722
|
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
703
|
-
console.
|
|
704
|
-
console.
|
|
705
|
-
console.
|
|
706
|
-
console.
|
|
707
|
-
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)");
|
|
708
728
|
}
|
|
709
729
|
function ensureEnvFile() {
|
|
710
730
|
const envPath = resolve2(".env");
|
|
711
731
|
if (!existsSync3(envPath)) {
|
|
712
|
-
const envContent = `# SolonGate
|
|
713
|
-
# Get your
|
|
732
|
+
const envContent = `# SolonGate Configuration
|
|
733
|
+
# Get your API key from: https://dashboard.solongate.com/api-keys
|
|
714
734
|
# IMPORTANT: Never commit this file to git!
|
|
715
735
|
|
|
716
|
-
#
|
|
736
|
+
# Your SolonGate API key \u2014 the proxy reads this automatically
|
|
717
737
|
SOLONGATE_API_KEY=sg_live_your_key_here
|
|
718
|
-
|
|
719
|
-
# Test key \u2014 local-only, no cloud connection (for development)
|
|
720
|
-
# SOLONGATE_API_KEY=sg_test_your_key_here
|
|
721
738
|
`;
|
|
722
739
|
writeFileSync2(envPath, envContent);
|
|
723
|
-
console.
|
|
724
|
-
console.
|
|
725
|
-
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("");
|
|
726
743
|
}
|
|
727
744
|
const gitignorePath = resolve2(".gitignore");
|
|
728
745
|
if (existsSync3(gitignorePath)) {
|
|
@@ -738,11 +755,11 @@ SOLONGATE_API_KEY=sg_live_your_key_here
|
|
|
738
755
|
}
|
|
739
756
|
if (updated) {
|
|
740
757
|
writeFileSync2(gitignorePath, gitignore);
|
|
741
|
-
console.
|
|
758
|
+
console.log(` Updated .gitignore (added .env + .mcp.json)`);
|
|
742
759
|
}
|
|
743
760
|
} else {
|
|
744
761
|
writeFileSync2(gitignorePath, ".env\n.env.local\n.mcp.json\nnode_modules/\n");
|
|
745
|
-
console.
|
|
762
|
+
console.log(` Created .gitignore (with .env and .mcp.json excluded)`);
|
|
746
763
|
}
|
|
747
764
|
}
|
|
748
765
|
async function main() {
|
|
@@ -768,54 +785,39 @@ async function main() {
|
|
|
768
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"
|
|
769
786
|
];
|
|
770
787
|
const bannerColors = [_c.blue1, _c.blue2, _c.blue3, _c.blue4, _c.blue5, _c.blue6];
|
|
771
|
-
console.
|
|
788
|
+
console.log("");
|
|
772
789
|
for (let i = 0; i < bannerLines.length; i++) {
|
|
773
|
-
console.
|
|
790
|
+
console.log(`${_c.bold}${bannerColors[i]}${bannerLines[i]}${_c.reset}`);
|
|
774
791
|
}
|
|
775
|
-
console.
|
|
776
|
-
console.
|
|
777
|
-
console.
|
|
778
|
-
|
|
792
|
+
console.log("");
|
|
793
|
+
console.log(` ${_c.dim}${_c.italic}Init Setup${_c.reset}`);
|
|
794
|
+
console.log("");
|
|
795
|
+
const configInfo = findConfigFile(options.configPath, true);
|
|
779
796
|
if (!configInfo) {
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
"filesystem": {
|
|
783
|
-
command: "npx",
|
|
784
|
-
args: ["-y", "@modelcontextprotocol/server-filesystem", "."]
|
|
785
|
-
},
|
|
786
|
-
"playwright": {
|
|
787
|
-
command: "npx",
|
|
788
|
-
args: ["-y", "@playwright/mcp@latest"]
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
};
|
|
792
|
-
const starterPath = resolve2(".mcp.json");
|
|
793
|
-
writeFileSync2(starterPath, JSON.stringify(starterConfig, null, 2) + "\n");
|
|
794
|
-
console.error(" No MCP config found \u2014 created .mcp.json with starter servers.");
|
|
795
|
-
console.error("");
|
|
796
|
-
configInfo = { path: starterPath, type: "mcp" };
|
|
797
|
+
console.log(" No MCP config found and could not create one.");
|
|
798
|
+
process.exit(1);
|
|
797
799
|
}
|
|
798
|
-
console.
|
|
799
|
-
console.
|
|
800
|
-
console.
|
|
800
|
+
console.log(` Config: ${configInfo.path}`);
|
|
801
|
+
console.log(` Type: ${configInfo.type === "claude-desktop" ? "Claude Desktop" : "MCP JSON"}`);
|
|
802
|
+
console.log("");
|
|
801
803
|
const backupPath = configInfo.path + ".solongate-backup";
|
|
802
804
|
if (options.restore) {
|
|
803
805
|
if (!existsSync3(backupPath)) {
|
|
804
|
-
console.
|
|
806
|
+
console.log(" No backup found. Nothing to restore.");
|
|
805
807
|
process.exit(1);
|
|
806
808
|
}
|
|
807
809
|
copyFileSync(backupPath, configInfo.path);
|
|
808
|
-
console.
|
|
810
|
+
console.log(" Restored original config from backup.");
|
|
809
811
|
process.exit(0);
|
|
810
812
|
}
|
|
811
813
|
const config = readConfig(configInfo.path);
|
|
812
814
|
const serverNames = Object.keys(config.mcpServers);
|
|
813
815
|
if (serverNames.length === 0) {
|
|
814
|
-
console.
|
|
816
|
+
console.log(" No MCP servers found in config.");
|
|
815
817
|
process.exit(1);
|
|
816
818
|
}
|
|
817
|
-
console.
|
|
818
|
-
console.
|
|
819
|
+
console.log(` Found ${serverNames.length} MCP server(s):`);
|
|
820
|
+
console.log("");
|
|
819
821
|
const toProtect = [];
|
|
820
822
|
const alreadyProtected = [];
|
|
821
823
|
const skipped = [];
|
|
@@ -823,17 +825,17 @@ async function main() {
|
|
|
823
825
|
const server = config.mcpServers[name];
|
|
824
826
|
if (isAlreadyProtected(server)) {
|
|
825
827
|
alreadyProtected.push(name);
|
|
826
|
-
console.
|
|
828
|
+
console.log(` [protected] ${name}`);
|
|
827
829
|
} else {
|
|
828
|
-
console.
|
|
830
|
+
console.log(` [exposed] ${name} \u2192 ${server.command} ${(server.args ?? []).join(" ")}`);
|
|
829
831
|
if (options.all) {
|
|
830
832
|
toProtect.push(name);
|
|
831
833
|
}
|
|
832
834
|
}
|
|
833
835
|
}
|
|
834
|
-
console.
|
|
836
|
+
console.log("");
|
|
835
837
|
if (alreadyProtected.length === serverNames.length) {
|
|
836
|
-
console.
|
|
838
|
+
console.log(" All servers are already protected by SolonGate!");
|
|
837
839
|
ensureEnvFile();
|
|
838
840
|
process.exit(0);
|
|
839
841
|
}
|
|
@@ -849,11 +851,13 @@ async function main() {
|
|
|
849
851
|
}
|
|
850
852
|
}
|
|
851
853
|
if (toProtect.length === 0) {
|
|
852
|
-
console.
|
|
854
|
+
console.log(" No servers selected for protection.");
|
|
853
855
|
ensureEnvFile();
|
|
854
856
|
process.exit(0);
|
|
855
857
|
}
|
|
856
|
-
console.
|
|
858
|
+
console.log("");
|
|
859
|
+
ensureEnvFile();
|
|
860
|
+
console.log("");
|
|
857
861
|
let apiKey = options.apiKey || process.env.SOLONGATE_API_KEY || "";
|
|
858
862
|
if (!apiKey) {
|
|
859
863
|
const envPath = resolve2(".env");
|
|
@@ -866,43 +870,43 @@ async function main() {
|
|
|
866
870
|
if (!apiKey || apiKey === "sg_live_your_key_here") {
|
|
867
871
|
if (options.all) {
|
|
868
872
|
apiKey = "sg_test_demo_init_key";
|
|
869
|
-
console.
|
|
870
|
-
console.
|
|
871
|
-
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("");
|
|
872
876
|
} else {
|
|
873
877
|
apiKey = await prompt(" Enter your SolonGate API key (from https://dashboard.solongate.com): ");
|
|
874
878
|
}
|
|
875
879
|
if (!apiKey) {
|
|
876
|
-
console.
|
|
880
|
+
console.log(" API key is required. Get one at https://dashboard.solongate.com");
|
|
877
881
|
process.exit(1);
|
|
878
882
|
}
|
|
879
883
|
}
|
|
880
884
|
if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
|
|
881
|
-
console.
|
|
885
|
+
console.log(" Invalid API key format. Must start with sg_live_ or sg_test_");
|
|
882
886
|
process.exit(1);
|
|
883
887
|
}
|
|
884
|
-
console.
|
|
885
|
-
console.
|
|
886
|
-
console.
|
|
887
|
-
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("");
|
|
888
892
|
const newConfig = { mcpServers: {} };
|
|
889
893
|
for (const name of serverNames) {
|
|
890
894
|
if (toProtect.includes(name)) {
|
|
891
|
-
newConfig.mcpServers[name] = wrapServer(config.mcpServers[name], options.policy
|
|
895
|
+
newConfig.mcpServers[name] = wrapServer(config.mcpServers[name], options.policy);
|
|
892
896
|
} else {
|
|
893
897
|
newConfig.mcpServers[name] = config.mcpServers[name];
|
|
894
898
|
}
|
|
895
899
|
}
|
|
896
900
|
if (options.dryRun) {
|
|
897
|
-
console.
|
|
898
|
-
console.
|
|
899
|
-
console.
|
|
900
|
-
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));
|
|
901
905
|
process.exit(0);
|
|
902
906
|
}
|
|
903
|
-
if (!existsSync3(backupPath)) {
|
|
907
|
+
if (!configInfo.created && !existsSync3(backupPath)) {
|
|
904
908
|
copyFileSync(configInfo.path, backupPath);
|
|
905
|
-
console.
|
|
909
|
+
console.log(` Backup: ${backupPath}`);
|
|
906
910
|
}
|
|
907
911
|
if (configInfo.type === "claude-desktop") {
|
|
908
912
|
const original = JSON.parse(readFileSync3(configInfo.path, "utf-8"));
|
|
@@ -911,36 +915,34 @@ async function main() {
|
|
|
911
915
|
} else {
|
|
912
916
|
writeFileSync2(configInfo.path, JSON.stringify(newConfig, null, 2) + "\n");
|
|
913
917
|
}
|
|
914
|
-
console.
|
|
915
|
-
console.
|
|
918
|
+
console.log(" Config updated!");
|
|
919
|
+
console.log("");
|
|
916
920
|
installClaudeCodeHooks(apiKey);
|
|
917
|
-
console.
|
|
918
|
-
|
|
919
|
-
console.
|
|
920
|
-
console.error(" \u2500\u2500 Summary \u2500\u2500");
|
|
921
|
-
console.error("");
|
|
921
|
+
console.log("");
|
|
922
|
+
console.log(" \u2500\u2500 Summary \u2500\u2500");
|
|
923
|
+
console.log("");
|
|
922
924
|
for (const name of toProtect) {
|
|
923
|
-
console.
|
|
925
|
+
console.log(` \u2713 ${name} \u2014 protected (${options.policy})`);
|
|
924
926
|
}
|
|
925
927
|
for (const name of alreadyProtected) {
|
|
926
|
-
console.
|
|
928
|
+
console.log(` \u25CF ${name} \u2014 already protected`);
|
|
927
929
|
}
|
|
928
930
|
for (const name of skipped) {
|
|
929
|
-
console.
|
|
930
|
-
}
|
|
931
|
-
console.
|
|
932
|
-
console.
|
|
933
|
-
console.
|
|
934
|
-
console.
|
|
935
|
-
console.
|
|
936
|
-
console.
|
|
937
|
-
console.
|
|
938
|
-
console.
|
|
939
|
-
console.
|
|
940
|
-
console.
|
|
941
|
-
console.
|
|
942
|
-
console.
|
|
943
|
-
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("");
|
|
944
946
|
}
|
|
945
947
|
var POLICY_PRESETS, SEARCH_PATHS, CLAUDE_DESKTOP_PATHS, GUARD_SCRIPT, AUDIT_SCRIPT;
|
|
946
948
|
var init_init = __esm({
|
|
@@ -1160,7 +1162,7 @@ process.stdin.on('end', async () => {
|
|
|
1160
1162
|
});
|
|
1161
1163
|
`;
|
|
1162
1164
|
main().catch((err) => {
|
|
1163
|
-
console.
|
|
1165
|
+
console.log(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
1164
1166
|
process.exit(1);
|
|
1165
1167
|
});
|
|
1166
1168
|
}
|
package/dist/init.js
CHANGED
|
@@ -11,7 +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
|
-
function findConfigFile(explicitPath) {
|
|
14
|
+
function findConfigFile(explicitPath, createIfMissing = false) {
|
|
15
15
|
if (explicitPath) {
|
|
16
16
|
if (existsSync(explicitPath)) {
|
|
17
17
|
return { path: resolve(explicitPath), type: "mcp" };
|
|
@@ -22,6 +22,25 @@ function findConfigFile(explicitPath) {
|
|
|
22
22
|
const full = resolve(searchPath);
|
|
23
23
|
if (existsSync(full)) return { path: full, type: "mcp" };
|
|
24
24
|
}
|
|
25
|
+
if (createIfMissing) {
|
|
26
|
+
const starterConfig = {
|
|
27
|
+
mcpServers: {
|
|
28
|
+
"filesystem": {
|
|
29
|
+
command: "npx",
|
|
30
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "."]
|
|
31
|
+
},
|
|
32
|
+
"playwright": {
|
|
33
|
+
command: "npx",
|
|
34
|
+
args: ["-y", "@playwright/mcp@latest"]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const starterPath = resolve(".mcp.json");
|
|
39
|
+
writeFileSync(starterPath, JSON.stringify(starterConfig, null, 2) + "\n");
|
|
40
|
+
console.log(" Created .mcp.json with starter servers (filesystem, playwright).");
|
|
41
|
+
console.log("");
|
|
42
|
+
return { path: starterPath, type: "mcp", created: true };
|
|
43
|
+
}
|
|
25
44
|
for (const desktopPath of CLAUDE_DESKTOP_PATHS) {
|
|
26
45
|
if (existsSync(desktopPath)) return { path: desktopPath, type: "claude-desktop" };
|
|
27
46
|
}
|
|
@@ -45,10 +64,8 @@ function isAlreadyProtected(server) {
|
|
|
45
64
|
}
|
|
46
65
|
return false;
|
|
47
66
|
}
|
|
48
|
-
function wrapServer(server, policy
|
|
49
|
-
const
|
|
50
|
-
env.SOLONGATE_API_KEY = apiKey;
|
|
51
|
-
return {
|
|
67
|
+
function wrapServer(server, policy) {
|
|
68
|
+
const result = {
|
|
52
69
|
command: "npx",
|
|
53
70
|
args: [
|
|
54
71
|
"-y",
|
|
@@ -59,9 +76,12 @@ function wrapServer(server, policy, apiKey) {
|
|
|
59
76
|
"--",
|
|
60
77
|
server.command,
|
|
61
78
|
...server.args ?? []
|
|
62
|
-
]
|
|
63
|
-
env
|
|
79
|
+
]
|
|
64
80
|
};
|
|
81
|
+
if (server.env && Object.keys(server.env).length > 0) {
|
|
82
|
+
result.env = { ...server.env };
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
65
85
|
}
|
|
66
86
|
async function prompt(question) {
|
|
67
87
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
@@ -108,8 +128,8 @@ function parseInitArgs(argv) {
|
|
|
108
128
|
}
|
|
109
129
|
if (!POLICY_PRESETS.includes(options.policy)) {
|
|
110
130
|
if (!existsSync(resolve(options.policy))) {
|
|
111
|
-
console.
|
|
112
|
-
console.
|
|
131
|
+
console.log(`Unknown policy: ${options.policy}`);
|
|
132
|
+
console.log(`Available presets: ${POLICY_PRESETS.join(", ")}`);
|
|
113
133
|
process.exit(1);
|
|
114
134
|
}
|
|
115
135
|
}
|
|
@@ -145,7 +165,7 @@ POLICY PRESETS
|
|
|
145
165
|
permissive Allow everything (monitoring + audit only)
|
|
146
166
|
deny-all Block all tool calls
|
|
147
167
|
`;
|
|
148
|
-
console.
|
|
168
|
+
console.log(help);
|
|
149
169
|
}
|
|
150
170
|
var GUARD_SCRIPT = `#!/usr/bin/env node
|
|
151
171
|
/**
|
|
@@ -358,10 +378,10 @@ function installClaudeCodeHooks(apiKey) {
|
|
|
358
378
|
mkdirSync(hooksDir, { recursive: true });
|
|
359
379
|
const guardPath = join(hooksDir, "guard.mjs");
|
|
360
380
|
writeFileSync(guardPath, GUARD_SCRIPT);
|
|
361
|
-
console.
|
|
381
|
+
console.log(` Created ${guardPath}`);
|
|
362
382
|
const auditPath = join(hooksDir, "audit.mjs");
|
|
363
383
|
writeFileSync(auditPath, AUDIT_SCRIPT);
|
|
364
|
-
console.
|
|
384
|
+
console.log(` Created ${auditPath}`);
|
|
365
385
|
const settingsPath = resolve(".claude", "settings.json");
|
|
366
386
|
let settings = {};
|
|
367
387
|
if (existsSync(settingsPath)) {
|
|
@@ -401,29 +421,26 @@ function installClaudeCodeHooks(apiKey) {
|
|
|
401
421
|
envObj.SOLONGATE_API_KEY = apiKey;
|
|
402
422
|
settings.env = envObj;
|
|
403
423
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
404
|
-
console.
|
|
405
|
-
console.
|
|
406
|
-
console.
|
|
407
|
-
console.
|
|
408
|
-
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)");
|
|
409
429
|
}
|
|
410
430
|
function ensureEnvFile() {
|
|
411
431
|
const envPath = resolve(".env");
|
|
412
432
|
if (!existsSync(envPath)) {
|
|
413
|
-
const envContent = `# SolonGate
|
|
414
|
-
# Get your
|
|
433
|
+
const envContent = `# SolonGate Configuration
|
|
434
|
+
# Get your API key from: https://dashboard.solongate.com/api-keys
|
|
415
435
|
# IMPORTANT: Never commit this file to git!
|
|
416
436
|
|
|
417
|
-
#
|
|
437
|
+
# Your SolonGate API key \u2014 the proxy reads this automatically
|
|
418
438
|
SOLONGATE_API_KEY=sg_live_your_key_here
|
|
419
|
-
|
|
420
|
-
# Test key \u2014 local-only, no cloud connection (for development)
|
|
421
|
-
# SOLONGATE_API_KEY=sg_test_your_key_here
|
|
422
439
|
`;
|
|
423
440
|
writeFileSync(envPath, envContent);
|
|
424
|
-
console.
|
|
425
|
-
console.
|
|
426
|
-
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("");
|
|
427
444
|
}
|
|
428
445
|
const gitignorePath = resolve(".gitignore");
|
|
429
446
|
if (existsSync(gitignorePath)) {
|
|
@@ -439,11 +456,11 @@ SOLONGATE_API_KEY=sg_live_your_key_here
|
|
|
439
456
|
}
|
|
440
457
|
if (updated) {
|
|
441
458
|
writeFileSync(gitignorePath, gitignore);
|
|
442
|
-
console.
|
|
459
|
+
console.log(` Updated .gitignore (added .env + .mcp.json)`);
|
|
443
460
|
}
|
|
444
461
|
} else {
|
|
445
462
|
writeFileSync(gitignorePath, ".env\n.env.local\n.mcp.json\nnode_modules/\n");
|
|
446
|
-
console.
|
|
463
|
+
console.log(` Created .gitignore (with .env and .mcp.json excluded)`);
|
|
447
464
|
}
|
|
448
465
|
}
|
|
449
466
|
async function main() {
|
|
@@ -469,54 +486,39 @@ async function main() {
|
|
|
469
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"
|
|
470
487
|
];
|
|
471
488
|
const bannerColors = [_c.blue1, _c.blue2, _c.blue3, _c.blue4, _c.blue5, _c.blue6];
|
|
472
|
-
console.
|
|
489
|
+
console.log("");
|
|
473
490
|
for (let i = 0; i < bannerLines.length; i++) {
|
|
474
|
-
console.
|
|
491
|
+
console.log(`${_c.bold}${bannerColors[i]}${bannerLines[i]}${_c.reset}`);
|
|
475
492
|
}
|
|
476
|
-
console.
|
|
477
|
-
console.
|
|
478
|
-
console.
|
|
479
|
-
|
|
493
|
+
console.log("");
|
|
494
|
+
console.log(` ${_c.dim}${_c.italic}Init Setup${_c.reset}`);
|
|
495
|
+
console.log("");
|
|
496
|
+
const configInfo = findConfigFile(options.configPath, true);
|
|
480
497
|
if (!configInfo) {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
"filesystem": {
|
|
484
|
-
command: "npx",
|
|
485
|
-
args: ["-y", "@modelcontextprotocol/server-filesystem", "."]
|
|
486
|
-
},
|
|
487
|
-
"playwright": {
|
|
488
|
-
command: "npx",
|
|
489
|
-
args: ["-y", "@playwright/mcp@latest"]
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
};
|
|
493
|
-
const starterPath = resolve(".mcp.json");
|
|
494
|
-
writeFileSync(starterPath, JSON.stringify(starterConfig, null, 2) + "\n");
|
|
495
|
-
console.error(" No MCP config found \u2014 created .mcp.json with starter servers.");
|
|
496
|
-
console.error("");
|
|
497
|
-
configInfo = { path: starterPath, type: "mcp" };
|
|
498
|
+
console.log(" No MCP config found and could not create one.");
|
|
499
|
+
process.exit(1);
|
|
498
500
|
}
|
|
499
|
-
console.
|
|
500
|
-
console.
|
|
501
|
-
console.
|
|
501
|
+
console.log(` Config: ${configInfo.path}`);
|
|
502
|
+
console.log(` Type: ${configInfo.type === "claude-desktop" ? "Claude Desktop" : "MCP JSON"}`);
|
|
503
|
+
console.log("");
|
|
502
504
|
const backupPath = configInfo.path + ".solongate-backup";
|
|
503
505
|
if (options.restore) {
|
|
504
506
|
if (!existsSync(backupPath)) {
|
|
505
|
-
console.
|
|
507
|
+
console.log(" No backup found. Nothing to restore.");
|
|
506
508
|
process.exit(1);
|
|
507
509
|
}
|
|
508
510
|
copyFileSync(backupPath, configInfo.path);
|
|
509
|
-
console.
|
|
511
|
+
console.log(" Restored original config from backup.");
|
|
510
512
|
process.exit(0);
|
|
511
513
|
}
|
|
512
514
|
const config = readConfig(configInfo.path);
|
|
513
515
|
const serverNames = Object.keys(config.mcpServers);
|
|
514
516
|
if (serverNames.length === 0) {
|
|
515
|
-
console.
|
|
517
|
+
console.log(" No MCP servers found in config.");
|
|
516
518
|
process.exit(1);
|
|
517
519
|
}
|
|
518
|
-
console.
|
|
519
|
-
console.
|
|
520
|
+
console.log(` Found ${serverNames.length} MCP server(s):`);
|
|
521
|
+
console.log("");
|
|
520
522
|
const toProtect = [];
|
|
521
523
|
const alreadyProtected = [];
|
|
522
524
|
const skipped = [];
|
|
@@ -524,17 +526,17 @@ async function main() {
|
|
|
524
526
|
const server = config.mcpServers[name];
|
|
525
527
|
if (isAlreadyProtected(server)) {
|
|
526
528
|
alreadyProtected.push(name);
|
|
527
|
-
console.
|
|
529
|
+
console.log(` [protected] ${name}`);
|
|
528
530
|
} else {
|
|
529
|
-
console.
|
|
531
|
+
console.log(` [exposed] ${name} \u2192 ${server.command} ${(server.args ?? []).join(" ")}`);
|
|
530
532
|
if (options.all) {
|
|
531
533
|
toProtect.push(name);
|
|
532
534
|
}
|
|
533
535
|
}
|
|
534
536
|
}
|
|
535
|
-
console.
|
|
537
|
+
console.log("");
|
|
536
538
|
if (alreadyProtected.length === serverNames.length) {
|
|
537
|
-
console.
|
|
539
|
+
console.log(" All servers are already protected by SolonGate!");
|
|
538
540
|
ensureEnvFile();
|
|
539
541
|
process.exit(0);
|
|
540
542
|
}
|
|
@@ -550,11 +552,13 @@ async function main() {
|
|
|
550
552
|
}
|
|
551
553
|
}
|
|
552
554
|
if (toProtect.length === 0) {
|
|
553
|
-
console.
|
|
555
|
+
console.log(" No servers selected for protection.");
|
|
554
556
|
ensureEnvFile();
|
|
555
557
|
process.exit(0);
|
|
556
558
|
}
|
|
557
|
-
console.
|
|
559
|
+
console.log("");
|
|
560
|
+
ensureEnvFile();
|
|
561
|
+
console.log("");
|
|
558
562
|
let apiKey = options.apiKey || process.env.SOLONGATE_API_KEY || "";
|
|
559
563
|
if (!apiKey) {
|
|
560
564
|
const envPath = resolve(".env");
|
|
@@ -567,43 +571,43 @@ async function main() {
|
|
|
567
571
|
if (!apiKey || apiKey === "sg_live_your_key_here") {
|
|
568
572
|
if (options.all) {
|
|
569
573
|
apiKey = "sg_test_demo_init_key";
|
|
570
|
-
console.
|
|
571
|
-
console.
|
|
572
|
-
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("");
|
|
573
577
|
} else {
|
|
574
578
|
apiKey = await prompt(" Enter your SolonGate API key (from https://dashboard.solongate.com): ");
|
|
575
579
|
}
|
|
576
580
|
if (!apiKey) {
|
|
577
|
-
console.
|
|
581
|
+
console.log(" API key is required. Get one at https://dashboard.solongate.com");
|
|
578
582
|
process.exit(1);
|
|
579
583
|
}
|
|
580
584
|
}
|
|
581
585
|
if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
|
|
582
|
-
console.
|
|
586
|
+
console.log(" Invalid API key format. Must start with sg_live_ or sg_test_");
|
|
583
587
|
process.exit(1);
|
|
584
588
|
}
|
|
585
|
-
console.
|
|
586
|
-
console.
|
|
587
|
-
console.
|
|
588
|
-
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("");
|
|
589
593
|
const newConfig = { mcpServers: {} };
|
|
590
594
|
for (const name of serverNames) {
|
|
591
595
|
if (toProtect.includes(name)) {
|
|
592
|
-
newConfig.mcpServers[name] = wrapServer(config.mcpServers[name], options.policy
|
|
596
|
+
newConfig.mcpServers[name] = wrapServer(config.mcpServers[name], options.policy);
|
|
593
597
|
} else {
|
|
594
598
|
newConfig.mcpServers[name] = config.mcpServers[name];
|
|
595
599
|
}
|
|
596
600
|
}
|
|
597
601
|
if (options.dryRun) {
|
|
598
|
-
console.
|
|
599
|
-
console.
|
|
600
|
-
console.
|
|
601
|
-
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));
|
|
602
606
|
process.exit(0);
|
|
603
607
|
}
|
|
604
|
-
if (!existsSync(backupPath)) {
|
|
608
|
+
if (!configInfo.created && !existsSync(backupPath)) {
|
|
605
609
|
copyFileSync(configInfo.path, backupPath);
|
|
606
|
-
console.
|
|
610
|
+
console.log(` Backup: ${backupPath}`);
|
|
607
611
|
}
|
|
608
612
|
if (configInfo.type === "claude-desktop") {
|
|
609
613
|
const original = JSON.parse(readFileSync(configInfo.path, "utf-8"));
|
|
@@ -612,38 +616,36 @@ async function main() {
|
|
|
612
616
|
} else {
|
|
613
617
|
writeFileSync(configInfo.path, JSON.stringify(newConfig, null, 2) + "\n");
|
|
614
618
|
}
|
|
615
|
-
console.
|
|
616
|
-
console.
|
|
619
|
+
console.log(" Config updated!");
|
|
620
|
+
console.log("");
|
|
617
621
|
installClaudeCodeHooks(apiKey);
|
|
618
|
-
console.
|
|
619
|
-
|
|
620
|
-
console.
|
|
621
|
-
console.error(" \u2500\u2500 Summary \u2500\u2500");
|
|
622
|
-
console.error("");
|
|
622
|
+
console.log("");
|
|
623
|
+
console.log(" \u2500\u2500 Summary \u2500\u2500");
|
|
624
|
+
console.log("");
|
|
623
625
|
for (const name of toProtect) {
|
|
624
|
-
console.
|
|
626
|
+
console.log(` \u2713 ${name} \u2014 protected (${options.policy})`);
|
|
625
627
|
}
|
|
626
628
|
for (const name of alreadyProtected) {
|
|
627
|
-
console.
|
|
629
|
+
console.log(` \u25CF ${name} \u2014 already protected`);
|
|
628
630
|
}
|
|
629
631
|
for (const name of skipped) {
|
|
630
|
-
console.
|
|
632
|
+
console.log(` \u25CB ${name} \u2014 skipped`);
|
|
631
633
|
}
|
|
632
|
-
console.
|
|
633
|
-
console.
|
|
634
|
-
console.
|
|
635
|
-
console.
|
|
636
|
-
console.
|
|
637
|
-
console.
|
|
638
|
-
console.
|
|
639
|
-
console.
|
|
640
|
-
console.
|
|
641
|
-
console.
|
|
642
|
-
console.
|
|
643
|
-
console.
|
|
644
|
-
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("");
|
|
645
647
|
}
|
|
646
648
|
main().catch((err) => {
|
|
647
|
-
console.
|
|
649
|
+
console.log(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
648
650
|
process.exit(1);
|
|
649
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": {
|