@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.
Files changed (3) hide show
  1. package/dist/index.js +89 -91
  2. package/dist/init.js +88 -90
  3. 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.error(" Created .mcp.json with starter servers (filesystem, playwright).");
546
- console.error("");
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, apiKey) {
573
- const env = { ...server.env ?? {} };
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.error(`Unknown policy: ${options.policy}`);
636
- console.error(`Available presets: ${POLICY_PRESETS.join(", ")}`);
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.error(help);
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.error(` Created ${guardPath}`);
680
+ console.log(` Created ${guardPath}`);
680
681
  const auditPath = join(hooksDir, "audit.mjs");
681
682
  writeFileSync2(auditPath, AUDIT_SCRIPT);
682
- console.error(` Created ${auditPath}`);
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.error(` Created ${settingsPath}`);
723
- console.error("");
724
- console.error(" Claude Code hooks installed!");
725
- console.error(" PreToolUse \u2192 guard.mjs (blocks dangerous calls)");
726
- console.error(" PostToolUse \u2192 audit.mjs (logs all calls to dashboard)");
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 API Keys
732
- # Get your keys from the dashboard: https://solongate.com
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
- # Live key \u2014 enables cloud policy sync + audit logging to dashboard
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.error(` Created .env with placeholder API keys`);
743
- console.error(` \u2192 Edit .env and replace with your real keys from https://solongate.com`);
744
- console.error("");
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.error(` Updated .gitignore (added .env + .mcp.json)`);
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.error(` Created .gitignore (with .env and .mcp.json excluded)`);
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.error("");
788
+ console.log("");
791
789
  for (let i = 0; i < bannerLines.length; i++) {
792
- console.error(`${_c.bold}${bannerColors[i]}${bannerLines[i]}${_c.reset}`);
790
+ console.log(`${_c.bold}${bannerColors[i]}${bannerLines[i]}${_c.reset}`);
793
791
  }
794
- console.error("");
795
- console.error(` ${_c.dim}${_c.italic}Init Setup${_c.reset}`);
796
- console.error("");
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.error(" No MCP config found and could not create one.");
797
+ console.log(" No MCP config found and could not create one.");
800
798
  process.exit(1);
801
799
  }
802
- console.error(` Config: ${configInfo.path}`);
803
- console.error(` Type: ${configInfo.type === "claude-desktop" ? "Claude Desktop" : "MCP JSON"}`);
804
- console.error("");
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.error(" No backup found. Nothing to restore.");
806
+ console.log(" No backup found. Nothing to restore.");
809
807
  process.exit(1);
810
808
  }
811
809
  copyFileSync(backupPath, configInfo.path);
812
- console.error(" Restored original config from backup.");
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.error(" No MCP servers found in config.");
816
+ console.log(" No MCP servers found in config.");
819
817
  process.exit(1);
820
818
  }
821
- console.error(` Found ${serverNames.length} MCP server(s):`);
822
- console.error("");
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.error(` [protected] ${name}`);
828
+ console.log(` [protected] ${name}`);
831
829
  } else {
832
- console.error(` [exposed] ${name} \u2192 ${server.command} ${(server.args ?? []).join(" ")}`);
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.error("");
836
+ console.log("");
839
837
  if (alreadyProtected.length === serverNames.length) {
840
- console.error(" All servers are already protected by SolonGate!");
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.error(" No servers selected for protection.");
854
+ console.log(" No servers selected for protection.");
857
855
  ensureEnvFile();
858
856
  process.exit(0);
859
857
  }
860
- console.error("");
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.error(" No API key found \u2014 using demo test key.");
874
- console.error(" For cloud features, set your key in .env or pass --api-key");
875
- console.error("");
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.error(" API key is required. Get one at https://dashboard.solongate.com");
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.error(" Invalid API key format. Must start with sg_live_ or sg_test_");
885
+ console.log(" Invalid API key format. Must start with sg_live_ or sg_test_");
886
886
  process.exit(1);
887
887
  }
888
- console.error(` Policy: ${options.policy}`);
889
- console.error(` API Key: ${apiKey.slice(0, 12)}...${apiKey.slice(-4)}`);
890
- console.error(` Protecting: ${toProtect.join(", ")}`);
891
- console.error("");
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, apiKey);
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.error(" --- DRY RUN (no changes written) ---");
902
- console.error("");
903
- console.error(" New config:");
904
- console.error(JSON.stringify(newConfig, null, 2));
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.error(` Backup: ${backupPath}`);
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.error(" Config updated!");
919
- console.error("");
918
+ console.log(" Config updated!");
919
+ console.log("");
920
920
  installClaudeCodeHooks(apiKey);
921
- console.error("");
922
- ensureEnvFile();
923
- console.error("");
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.error(` \u2713 ${name} \u2014 protected (${options.policy})`);
925
+ console.log(` \u2713 ${name} \u2014 protected (${options.policy})`);
928
926
  }
929
927
  for (const name of alreadyProtected) {
930
- console.error(` \u25CF ${name} \u2014 already protected`);
928
+ console.log(` \u25CF ${name} \u2014 already protected`);
931
929
  }
932
930
  for (const name of skipped) {
933
- console.error(` \u25CB ${name} \u2014 skipped`);
934
- }
935
- console.error("");
936
- console.error(" \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");
937
- console.error(" \u2502 Setup complete! \u2502");
938
- console.error(" \u2502 \u2502");
939
- console.error(" \u2502 MCP tools \u2192 Proxy audit logging \u2502");
940
- console.error(" \u2502 Built-in tools \u2192 Hook audit logging \u2502");
941
- console.error(" \u2502 \u2502");
942
- console.error(" \u2502 View logs: https://dashboard.solongate.com \u2502");
943
- console.error(" \u2502 To undo: solongate-init --restore \u2502");
944
- console.error(" \u2502 \u2502");
945
- console.error(" \u2502 Restart your MCP client to apply changes. \u2502");
946
- console.error(" \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");
947
- console.error("");
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.error(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
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.error(" Created .mcp.json with starter servers (filesystem, playwright).");
41
- console.error("");
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, apiKey) {
68
- const env = { ...server.env ?? {} };
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.error(`Unknown policy: ${options.policy}`);
131
- console.error(`Available presets: ${POLICY_PRESETS.join(", ")}`);
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.error(help);
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.error(` Created ${guardPath}`);
381
+ console.log(` Created ${guardPath}`);
381
382
  const auditPath = join(hooksDir, "audit.mjs");
382
383
  writeFileSync(auditPath, AUDIT_SCRIPT);
383
- console.error(` Created ${auditPath}`);
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.error(` Created ${settingsPath}`);
424
- console.error("");
425
- console.error(" Claude Code hooks installed!");
426
- console.error(" PreToolUse \u2192 guard.mjs (blocks dangerous calls)");
427
- console.error(" PostToolUse \u2192 audit.mjs (logs all calls to dashboard)");
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 API Keys
433
- # Get your keys from the dashboard: https://solongate.com
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
- # Live key \u2014 enables cloud policy sync + audit logging to dashboard
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.error(` Created .env with placeholder API keys`);
444
- console.error(` \u2192 Edit .env and replace with your real keys from https://solongate.com`);
445
- console.error("");
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.error(` Updated .gitignore (added .env + .mcp.json)`);
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.error(` Created .gitignore (with .env and .mcp.json excluded)`);
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.error("");
489
+ console.log("");
492
490
  for (let i = 0; i < bannerLines.length; i++) {
493
- console.error(`${_c.bold}${bannerColors[i]}${bannerLines[i]}${_c.reset}`);
491
+ console.log(`${_c.bold}${bannerColors[i]}${bannerLines[i]}${_c.reset}`);
494
492
  }
495
- console.error("");
496
- console.error(` ${_c.dim}${_c.italic}Init Setup${_c.reset}`);
497
- console.error("");
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.error(" No MCP config found and could not create one.");
498
+ console.log(" No MCP config found and could not create one.");
501
499
  process.exit(1);
502
500
  }
503
- console.error(` Config: ${configInfo.path}`);
504
- console.error(` Type: ${configInfo.type === "claude-desktop" ? "Claude Desktop" : "MCP JSON"}`);
505
- console.error("");
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.error(" No backup found. Nothing to restore.");
507
+ console.log(" No backup found. Nothing to restore.");
510
508
  process.exit(1);
511
509
  }
512
510
  copyFileSync(backupPath, configInfo.path);
513
- console.error(" Restored original config from backup.");
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.error(" No MCP servers found in config.");
517
+ console.log(" No MCP servers found in config.");
520
518
  process.exit(1);
521
519
  }
522
- console.error(` Found ${serverNames.length} MCP server(s):`);
523
- console.error("");
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.error(` [protected] ${name}`);
529
+ console.log(` [protected] ${name}`);
532
530
  } else {
533
- console.error(` [exposed] ${name} \u2192 ${server.command} ${(server.args ?? []).join(" ")}`);
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.error("");
537
+ console.log("");
540
538
  if (alreadyProtected.length === serverNames.length) {
541
- console.error(" All servers are already protected by SolonGate!");
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.error(" No servers selected for protection.");
555
+ console.log(" No servers selected for protection.");
558
556
  ensureEnvFile();
559
557
  process.exit(0);
560
558
  }
561
- console.error("");
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.error(" No API key found \u2014 using demo test key.");
575
- console.error(" For cloud features, set your key in .env or pass --api-key");
576
- console.error("");
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.error(" API key is required. Get one at https://dashboard.solongate.com");
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.error(" Invalid API key format. Must start with sg_live_ or sg_test_");
586
+ console.log(" Invalid API key format. Must start with sg_live_ or sg_test_");
587
587
  process.exit(1);
588
588
  }
589
- console.error(` Policy: ${options.policy}`);
590
- console.error(` API Key: ${apiKey.slice(0, 12)}...${apiKey.slice(-4)}`);
591
- console.error(` Protecting: ${toProtect.join(", ")}`);
592
- console.error("");
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, apiKey);
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.error(" --- DRY RUN (no changes written) ---");
603
- console.error("");
604
- console.error(" New config:");
605
- console.error(JSON.stringify(newConfig, null, 2));
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.error(` Backup: ${backupPath}`);
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.error(" Config updated!");
620
- console.error("");
619
+ console.log(" Config updated!");
620
+ console.log("");
621
621
  installClaudeCodeHooks(apiKey);
622
- console.error("");
623
- ensureEnvFile();
624
- console.error("");
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.error(` \u2713 ${name} \u2014 protected (${options.policy})`);
626
+ console.log(` \u2713 ${name} \u2014 protected (${options.policy})`);
629
627
  }
630
628
  for (const name of alreadyProtected) {
631
- console.error(` \u25CF ${name} \u2014 already protected`);
629
+ console.log(` \u25CF ${name} \u2014 already protected`);
632
630
  }
633
631
  for (const name of skipped) {
634
- console.error(` \u25CB ${name} \u2014 skipped`);
632
+ console.log(` \u25CB ${name} \u2014 skipped`);
635
633
  }
636
- console.error("");
637
- console.error(" \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");
638
- console.error(" \u2502 Setup complete! \u2502");
639
- console.error(" \u2502 \u2502");
640
- console.error(" \u2502 MCP tools \u2192 Proxy audit logging \u2502");
641
- console.error(" \u2502 Built-in tools \u2192 Hook audit logging \u2502");
642
- console.error(" \u2502 \u2502");
643
- console.error(" \u2502 View logs: https://dashboard.solongate.com \u2502");
644
- console.error(" \u2502 To undo: solongate-init --restore \u2502");
645
- console.error(" \u2502 \u2502");
646
- console.error(" \u2502 Restart your MCP client to apply changes. \u2502");
647
- console.error(" \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");
648
- console.error("");
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.error(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
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.4.9",
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": {