@solongate/proxy 0.2.1 → 0.2.2

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 +153 -41
  2. package/dist/init.js +149 -37
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6,20 +6,9 @@ var __esm = (fn, res) => function __init() {
6
6
 
7
7
  // src/init.ts
8
8
  var init_exports = {};
9
- import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, copyFileSync } from "fs";
10
- import { resolve as resolve2, join, dirname } from "path";
9
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, copyFileSync, mkdirSync } from "fs";
10
+ import { resolve as resolve2, join } from "path";
11
11
  import { createInterface } from "readline";
12
- function findProxyPath() {
13
- const candidates = [
14
- resolve2(dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1")), "index.js"),
15
- resolve2("node_modules", "@solongate", "proxy", "dist", "index.js"),
16
- resolve2("packages", "proxy", "dist", "index.js")
17
- ];
18
- for (const p of candidates) {
19
- if (existsSync2(p)) return p.replace(/\\/g, "/");
20
- }
21
- return "solongate-proxy";
22
- }
23
12
  function findConfigFile(explicitPath) {
24
13
  if (explicitPath) {
25
14
  if (existsSync2(explicitPath)) {
@@ -46,11 +35,14 @@ function isAlreadyProtected(server) {
46
35
  const cmdStr = [server.command, ...server.args ?? []].join(" ");
47
36
  return cmdStr.includes("solongate") || cmdStr.includes("solongate-proxy");
48
37
  }
49
- function wrapServer(server, policy, proxyPath) {
38
+ function wrapServer(server, policy, apiKey) {
50
39
  return {
51
- command: "node",
40
+ command: "npx",
52
41
  args: [
53
- proxyPath,
42
+ "-y",
43
+ "@solongate/proxy",
44
+ "--api-key",
45
+ apiKey,
54
46
  "--policy",
55
47
  policy,
56
48
  "--verbose",
@@ -58,10 +50,7 @@ function wrapServer(server, policy, proxyPath) {
58
50
  server.command,
59
51
  ...server.args ?? []
60
52
  ],
61
- env: {
62
- ...server.env,
63
- SOLONGATE_API_KEY: server.env?.SOLONGATE_API_KEY ?? "sg_live_YOUR_KEY_HERE"
64
- }
53
+ ...server.env ? { env: server.env } : {}
65
54
  };
66
55
  }
67
56
  async function prompt(question) {
@@ -89,6 +78,9 @@ function parseInitArgs(argv) {
89
78
  case "--policy":
90
79
  options.policy = args[++i];
91
80
  break;
81
+ case "--api-key":
82
+ options.apiKey = args[++i];
83
+ break;
92
84
  case "--all":
93
85
  options.all = true;
94
86
  break;
@@ -124,13 +116,14 @@ OPTIONS
124
116
  --config <path> Path to MCP config file (default: auto-detect)
125
117
  --policy <preset> Policy preset or JSON file (default: restricted)
126
118
  Presets: ${POLICY_PRESETS.join(", ")}
119
+ --api-key <key> SolonGate API key (sg_live_... or sg_test_...)
127
120
  --all Protect all servers without prompting
128
121
  --dry-run Preview changes without writing
129
122
  --restore Restore original config from backup
130
123
  -h, --help Show this help message
131
124
 
132
125
  EXAMPLES
133
- solongate-init # Interactive setup
126
+ solongate-init --api-key sg_live_xxx # Setup with API key
134
127
  solongate-init --all # Protect everything
135
128
  solongate-init --policy read-only # Use read-only policy
136
129
  solongate-init --dry-run # Preview changes
@@ -144,6 +137,44 @@ POLICY PRESETS
144
137
  `;
145
138
  console.error(help);
146
139
  }
140
+ function installClaudeCodeHooks(apiKey) {
141
+ const hooksDir = resolve2(".claude", "hooks");
142
+ mkdirSync(hooksDir, { recursive: true });
143
+ const hookPath = join(hooksDir, "audit.mjs");
144
+ writeFileSync(hookPath, HOOK_SCRIPT);
145
+ console.error(` Created ${hookPath}`);
146
+ const settingsPath = resolve2(".claude", "settings.json");
147
+ let settings = {};
148
+ if (existsSync2(settingsPath)) {
149
+ try {
150
+ settings = JSON.parse(readFileSync2(settingsPath, "utf-8"));
151
+ } catch {
152
+ }
153
+ }
154
+ settings.hooks = {
155
+ PostToolUse: [
156
+ {
157
+ matcher: ".*",
158
+ hooks: [
159
+ {
160
+ type: "command",
161
+ command: "node .claude/hooks/audit.mjs",
162
+ timeout: 10,
163
+ async: true
164
+ }
165
+ ]
166
+ }
167
+ ]
168
+ };
169
+ const envObj = settings.env || {};
170
+ envObj.SOLONGATE_API_KEY = apiKey;
171
+ settings.env = envObj;
172
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
173
+ console.error(` Created ${settingsPath}`);
174
+ console.error("");
175
+ console.error(" Claude Code hooks installed!");
176
+ console.error(" Built-in tools (Read, Write, Edit, Bash) will now be audit-logged.");
177
+ }
147
178
  function ensureEnvFile() {
148
179
  const envPath = resolve2(".env");
149
180
  if (!existsSync2(envPath)) {
@@ -281,14 +312,34 @@ async function main() {
281
312
  process.exit(0);
282
313
  }
283
314
  console.error("");
284
- console.error(` Policy: ${options.policy}`);
315
+ let apiKey = options.apiKey || process.env.SOLONGATE_API_KEY || "";
316
+ if (!apiKey) {
317
+ const envPath = resolve2(".env");
318
+ if (existsSync2(envPath)) {
319
+ const envContent = readFileSync2(envPath, "utf-8");
320
+ const match = envContent.match(/^SOLONGATE_API_KEY=(sg_(?:live|test)_\w+)/m);
321
+ if (match) apiKey = match[1];
322
+ }
323
+ }
324
+ if (!apiKey || apiKey === "sg_live_your_key_here") {
325
+ apiKey = await prompt(" Enter your SolonGate API key (from https://dashboard.solongate.com): ");
326
+ if (!apiKey) {
327
+ console.error(" API key is required. Get one at https://dashboard.solongate.com");
328
+ process.exit(1);
329
+ }
330
+ }
331
+ if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
332
+ console.error(" Invalid API key format. Must start with sg_live_ or sg_test_");
333
+ process.exit(1);
334
+ }
335
+ console.error(` Policy: ${options.policy}`);
336
+ console.error(` API Key: ${apiKey.slice(0, 12)}...${apiKey.slice(-4)}`);
285
337
  console.error(` Protecting: ${toProtect.join(", ")}`);
286
338
  console.error("");
287
- const proxyPath = findProxyPath();
288
339
  const newConfig = { mcpServers: {} };
289
340
  for (const name of serverNames) {
290
341
  if (toProtect.includes(name)) {
291
- newConfig.mcpServers[name] = wrapServer(config.mcpServers[name], options.policy, proxyPath);
342
+ newConfig.mcpServers[name] = wrapServer(config.mcpServers[name], options.policy, apiKey);
292
343
  } else {
293
344
  newConfig.mcpServers[name] = config.mcpServers[name];
294
345
  }
@@ -313,22 +364,12 @@ async function main() {
313
364
  }
314
365
  console.error(" Config updated!");
315
366
  console.error("");
316
- 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");
317
- console.error(" \u2502 Your MCP servers are now protected by \u2502");
318
- console.error(" \u2502 SolonGate security policies. \u2502");
319
- console.error(" \u2502 \u2502");
320
- console.error(" \u2502 Next steps: \u2502");
321
- console.error(" \u2502 1. Replace sg_live_YOUR_KEY_HERE in your \u2502");
322
- console.error(" \u2502 config with your real API key from \u2502");
323
- console.error(" \u2502 https://solongate.com \u2502");
324
- console.error(" \u2502 2. Restart your MCP client (Claude Code \u2502");
325
- console.error(" \u2502 or Claude Desktop) to apply changes. \u2502");
326
- console.error(" \u2502 \u2502");
327
- console.error(" \u2502 To undo: solongate-init --restore \u2502");
328
- 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");
367
+ installClaudeCodeHooks(apiKey);
329
368
  console.error("");
330
369
  ensureEnvFile();
331
370
  console.error("");
371
+ console.error(" \u2500\u2500 Summary \u2500\u2500");
372
+ console.error("");
332
373
  for (const name of toProtect) {
333
374
  console.error(` \u2713 ${name} \u2014 protected (${options.policy})`);
334
375
  }
@@ -339,8 +380,20 @@ async function main() {
339
380
  console.error(` \u25CB ${name} \u2014 skipped`);
340
381
  }
341
382
  console.error("");
383
+ 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");
384
+ console.error(" \u2502 Setup complete! \u2502");
385
+ console.error(" \u2502 \u2502");
386
+ console.error(" \u2502 MCP tools \u2192 Proxy audit logging \u2502");
387
+ console.error(" \u2502 Built-in tools \u2192 Hook audit logging \u2502");
388
+ console.error(" \u2502 \u2502");
389
+ console.error(" \u2502 View logs: https://dashboard.solongate.com \u2502");
390
+ console.error(" \u2502 To undo: solongate-init --restore \u2502");
391
+ console.error(" \u2502 \u2502");
392
+ console.error(" \u2502 Restart your MCP client to apply changes. \u2502");
393
+ 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");
394
+ console.error("");
342
395
  }
343
- var POLICY_PRESETS, SEARCH_PATHS, CLAUDE_DESKTOP_PATHS;
396
+ var POLICY_PRESETS, SEARCH_PATHS, CLAUDE_DESKTOP_PATHS, HOOK_SCRIPT;
344
397
  var init_init = __esm({
345
398
  "src/init.ts"() {
346
399
  "use strict";
@@ -351,6 +404,65 @@ var init_init = __esm({
351
404
  ".claude/mcp.json"
352
405
  ];
353
406
  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")];
407
+ HOOK_SCRIPT = `#!/usr/bin/env node
408
+ /**
409
+ * SolonGate Audit Hook for Claude Code
410
+ * Captures ALL tool calls (built-in + MCP) and sends to SolonGate Cloud.
411
+ * Auto-installed by: npx @solongate/proxy init
412
+ */
413
+
414
+ const API_KEY = process.env.SOLONGATE_API_KEY || '';
415
+ const API_URL = process.env.SOLONGATE_API_URL || 'https://api.solongate.com';
416
+
417
+ if (!API_KEY || !API_KEY.startsWith('sg_live_')) process.exit(0);
418
+
419
+ let input = '';
420
+ process.stdin.on('data', c => input += c);
421
+ process.stdin.on('end', async () => {
422
+ try {
423
+ const data = JSON.parse(input);
424
+ const toolName = data.tool_name || 'unknown';
425
+ const toolInput = data.tool_input || {};
426
+
427
+ // Skip logging the audit hook itself to avoid loops
428
+ if (toolName === 'Bash' && JSON.stringify(toolInput).includes('audit-logs')) {
429
+ process.exit(0);
430
+ }
431
+
432
+ const hasError = data.tool_response?.error ||
433
+ data.tool_response?.exitCode > 0 ||
434
+ data.tool_response?.isError;
435
+
436
+ // Truncate large argument values for security
437
+ const argsSummary = {};
438
+ for (const [k, v] of Object.entries(toolInput)) {
439
+ argsSummary[k] = typeof v === 'string' && v.length > 200
440
+ ? v.slice(0, 200) + '...'
441
+ : v;
442
+ }
443
+
444
+ await fetch(\`\${API_URL}/api/v1/audit-logs\`, {
445
+ method: 'POST',
446
+ headers: {
447
+ 'Authorization': \`Bearer \${API_KEY}\`,
448
+ 'Content-Type': 'application/json',
449
+ },
450
+ body: JSON.stringify({
451
+ tool: toolName,
452
+ arguments: argsSummary,
453
+ decision: hasError ? 'DENY' : 'ALLOW',
454
+ reason: hasError ? 'tool returned error' : 'allowed',
455
+ source: 'claude-code-hook',
456
+ evaluationTimeMs: 0,
457
+ }),
458
+ signal: AbortSignal.timeout(5000),
459
+ });
460
+ } catch {
461
+ // Silent
462
+ }
463
+ process.exit(0);
464
+ });
465
+ `;
354
466
  main().catch((err) => {
355
467
  console.error(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
356
468
  process.exit(1);
@@ -732,7 +844,7 @@ var init_inject = __esm({
732
844
 
733
845
  // src/create.ts
734
846
  var create_exports = {};
735
- import { mkdirSync, writeFileSync as writeFileSync3, existsSync as existsSync4 } from "fs";
847
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3, existsSync as existsSync4 } from "fs";
736
848
  import { resolve as resolve4, join as join2 } from "path";
737
849
  import { execSync as execSync2 } from "child_process";
738
850
  function log3(msg) {
@@ -884,7 +996,7 @@ function createProject(dir, name, _policy) {
884
996
  2
885
997
  ) + "\n"
886
998
  );
887
- mkdirSync(join2(dir, "src"), { recursive: true });
999
+ mkdirSync2(join2(dir, "src"), { recursive: true });
888
1000
  writeFileSync3(
889
1001
  join2(dir, "src", "index.ts"),
890
1002
  `#!/usr/bin/env node
@@ -973,7 +1085,7 @@ async function main3() {
973
1085
  process.exit(1);
974
1086
  }
975
1087
  withSpinner(`Setting up ${opts.name}...`, () => {
976
- mkdirSync(dir, { recursive: true });
1088
+ mkdirSync2(dir, { recursive: true });
977
1089
  createProject(dir, opts.name, opts.policy);
978
1090
  });
979
1091
  if (!opts.noInstall) {
package/dist/init.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/init.ts
4
- import { readFileSync, writeFileSync, existsSync, copyFileSync } from "fs";
5
- import { resolve, join, dirname } from "path";
4
+ import { readFileSync, writeFileSync, existsSync, copyFileSync, mkdirSync } from "fs";
5
+ import { resolve, join } from "path";
6
6
  import { createInterface } from "readline";
7
7
  var POLICY_PRESETS = ["restricted", "read-only", "permissive", "deny-all"];
8
8
  var SEARCH_PATHS = [
@@ -11,17 +11,6 @@ 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 findProxyPath() {
15
- const candidates = [
16
- resolve(dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1")), "index.js"),
17
- resolve("node_modules", "@solongate", "proxy", "dist", "index.js"),
18
- resolve("packages", "proxy", "dist", "index.js")
19
- ];
20
- for (const p of candidates) {
21
- if (existsSync(p)) return p.replace(/\\/g, "/");
22
- }
23
- return "solongate-proxy";
24
- }
25
14
  function findConfigFile(explicitPath) {
26
15
  if (explicitPath) {
27
16
  if (existsSync(explicitPath)) {
@@ -48,11 +37,14 @@ function isAlreadyProtected(server) {
48
37
  const cmdStr = [server.command, ...server.args ?? []].join(" ");
49
38
  return cmdStr.includes("solongate") || cmdStr.includes("solongate-proxy");
50
39
  }
51
- function wrapServer(server, policy, proxyPath) {
40
+ function wrapServer(server, policy, apiKey) {
52
41
  return {
53
- command: "node",
42
+ command: "npx",
54
43
  args: [
55
- proxyPath,
44
+ "-y",
45
+ "@solongate/proxy",
46
+ "--api-key",
47
+ apiKey,
56
48
  "--policy",
57
49
  policy,
58
50
  "--verbose",
@@ -60,10 +52,7 @@ function wrapServer(server, policy, proxyPath) {
60
52
  server.command,
61
53
  ...server.args ?? []
62
54
  ],
63
- env: {
64
- ...server.env,
65
- SOLONGATE_API_KEY: server.env?.SOLONGATE_API_KEY ?? "sg_live_YOUR_KEY_HERE"
66
- }
55
+ ...server.env ? { env: server.env } : {}
67
56
  };
68
57
  }
69
58
  async function prompt(question) {
@@ -91,6 +80,9 @@ function parseInitArgs(argv) {
91
80
  case "--policy":
92
81
  options.policy = args[++i];
93
82
  break;
83
+ case "--api-key":
84
+ options.apiKey = args[++i];
85
+ break;
94
86
  case "--all":
95
87
  options.all = true;
96
88
  break;
@@ -126,13 +118,14 @@ OPTIONS
126
118
  --config <path> Path to MCP config file (default: auto-detect)
127
119
  --policy <preset> Policy preset or JSON file (default: restricted)
128
120
  Presets: ${POLICY_PRESETS.join(", ")}
121
+ --api-key <key> SolonGate API key (sg_live_... or sg_test_...)
129
122
  --all Protect all servers without prompting
130
123
  --dry-run Preview changes without writing
131
124
  --restore Restore original config from backup
132
125
  -h, --help Show this help message
133
126
 
134
127
  EXAMPLES
135
- solongate-init # Interactive setup
128
+ solongate-init --api-key sg_live_xxx # Setup with API key
136
129
  solongate-init --all # Protect everything
137
130
  solongate-init --policy read-only # Use read-only policy
138
131
  solongate-init --dry-run # Preview changes
@@ -146,6 +139,103 @@ POLICY PRESETS
146
139
  `;
147
140
  console.error(help);
148
141
  }
142
+ var HOOK_SCRIPT = `#!/usr/bin/env node
143
+ /**
144
+ * SolonGate Audit Hook for Claude Code
145
+ * Captures ALL tool calls (built-in + MCP) and sends to SolonGate Cloud.
146
+ * Auto-installed by: npx @solongate/proxy init
147
+ */
148
+
149
+ const API_KEY = process.env.SOLONGATE_API_KEY || '';
150
+ const API_URL = process.env.SOLONGATE_API_URL || 'https://api.solongate.com';
151
+
152
+ if (!API_KEY || !API_KEY.startsWith('sg_live_')) process.exit(0);
153
+
154
+ let input = '';
155
+ process.stdin.on('data', c => input += c);
156
+ process.stdin.on('end', async () => {
157
+ try {
158
+ const data = JSON.parse(input);
159
+ const toolName = data.tool_name || 'unknown';
160
+ const toolInput = data.tool_input || {};
161
+
162
+ // Skip logging the audit hook itself to avoid loops
163
+ if (toolName === 'Bash' && JSON.stringify(toolInput).includes('audit-logs')) {
164
+ process.exit(0);
165
+ }
166
+
167
+ const hasError = data.tool_response?.error ||
168
+ data.tool_response?.exitCode > 0 ||
169
+ data.tool_response?.isError;
170
+
171
+ // Truncate large argument values for security
172
+ const argsSummary = {};
173
+ for (const [k, v] of Object.entries(toolInput)) {
174
+ argsSummary[k] = typeof v === 'string' && v.length > 200
175
+ ? v.slice(0, 200) + '...'
176
+ : v;
177
+ }
178
+
179
+ await fetch(\`\${API_URL}/api/v1/audit-logs\`, {
180
+ method: 'POST',
181
+ headers: {
182
+ 'Authorization': \`Bearer \${API_KEY}\`,
183
+ 'Content-Type': 'application/json',
184
+ },
185
+ body: JSON.stringify({
186
+ tool: toolName,
187
+ arguments: argsSummary,
188
+ decision: hasError ? 'DENY' : 'ALLOW',
189
+ reason: hasError ? 'tool returned error' : 'allowed',
190
+ source: 'claude-code-hook',
191
+ evaluationTimeMs: 0,
192
+ }),
193
+ signal: AbortSignal.timeout(5000),
194
+ });
195
+ } catch {
196
+ // Silent
197
+ }
198
+ process.exit(0);
199
+ });
200
+ `;
201
+ function installClaudeCodeHooks(apiKey) {
202
+ const hooksDir = resolve(".claude", "hooks");
203
+ mkdirSync(hooksDir, { recursive: true });
204
+ const hookPath = join(hooksDir, "audit.mjs");
205
+ writeFileSync(hookPath, HOOK_SCRIPT);
206
+ console.error(` Created ${hookPath}`);
207
+ const settingsPath = resolve(".claude", "settings.json");
208
+ let settings = {};
209
+ if (existsSync(settingsPath)) {
210
+ try {
211
+ settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
212
+ } catch {
213
+ }
214
+ }
215
+ settings.hooks = {
216
+ PostToolUse: [
217
+ {
218
+ matcher: ".*",
219
+ hooks: [
220
+ {
221
+ type: "command",
222
+ command: "node .claude/hooks/audit.mjs",
223
+ timeout: 10,
224
+ async: true
225
+ }
226
+ ]
227
+ }
228
+ ]
229
+ };
230
+ const envObj = settings.env || {};
231
+ envObj.SOLONGATE_API_KEY = apiKey;
232
+ settings.env = envObj;
233
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
234
+ console.error(` Created ${settingsPath}`);
235
+ console.error("");
236
+ console.error(" Claude Code hooks installed!");
237
+ console.error(" Built-in tools (Read, Write, Edit, Bash) will now be audit-logged.");
238
+ }
149
239
  function ensureEnvFile() {
150
240
  const envPath = resolve(".env");
151
241
  if (!existsSync(envPath)) {
@@ -283,14 +373,34 @@ async function main() {
283
373
  process.exit(0);
284
374
  }
285
375
  console.error("");
286
- console.error(` Policy: ${options.policy}`);
376
+ let apiKey = options.apiKey || process.env.SOLONGATE_API_KEY || "";
377
+ if (!apiKey) {
378
+ const envPath = resolve(".env");
379
+ if (existsSync(envPath)) {
380
+ const envContent = readFileSync(envPath, "utf-8");
381
+ const match = envContent.match(/^SOLONGATE_API_KEY=(sg_(?:live|test)_\w+)/m);
382
+ if (match) apiKey = match[1];
383
+ }
384
+ }
385
+ if (!apiKey || apiKey === "sg_live_your_key_here") {
386
+ apiKey = await prompt(" Enter your SolonGate API key (from https://dashboard.solongate.com): ");
387
+ if (!apiKey) {
388
+ console.error(" API key is required. Get one at https://dashboard.solongate.com");
389
+ process.exit(1);
390
+ }
391
+ }
392
+ if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
393
+ console.error(" Invalid API key format. Must start with sg_live_ or sg_test_");
394
+ process.exit(1);
395
+ }
396
+ console.error(` Policy: ${options.policy}`);
397
+ console.error(` API Key: ${apiKey.slice(0, 12)}...${apiKey.slice(-4)}`);
287
398
  console.error(` Protecting: ${toProtect.join(", ")}`);
288
399
  console.error("");
289
- const proxyPath = findProxyPath();
290
400
  const newConfig = { mcpServers: {} };
291
401
  for (const name of serverNames) {
292
402
  if (toProtect.includes(name)) {
293
- newConfig.mcpServers[name] = wrapServer(config.mcpServers[name], options.policy, proxyPath);
403
+ newConfig.mcpServers[name] = wrapServer(config.mcpServers[name], options.policy, apiKey);
294
404
  } else {
295
405
  newConfig.mcpServers[name] = config.mcpServers[name];
296
406
  }
@@ -315,22 +425,12 @@ async function main() {
315
425
  }
316
426
  console.error(" Config updated!");
317
427
  console.error("");
318
- 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");
319
- console.error(" \u2502 Your MCP servers are now protected by \u2502");
320
- console.error(" \u2502 SolonGate security policies. \u2502");
321
- console.error(" \u2502 \u2502");
322
- console.error(" \u2502 Next steps: \u2502");
323
- console.error(" \u2502 1. Replace sg_live_YOUR_KEY_HERE in your \u2502");
324
- console.error(" \u2502 config with your real API key from \u2502");
325
- console.error(" \u2502 https://solongate.com \u2502");
326
- console.error(" \u2502 2. Restart your MCP client (Claude Code \u2502");
327
- console.error(" \u2502 or Claude Desktop) to apply changes. \u2502");
328
- console.error(" \u2502 \u2502");
329
- console.error(" \u2502 To undo: solongate-init --restore \u2502");
330
- 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");
428
+ installClaudeCodeHooks(apiKey);
331
429
  console.error("");
332
430
  ensureEnvFile();
333
431
  console.error("");
432
+ console.error(" \u2500\u2500 Summary \u2500\u2500");
433
+ console.error("");
334
434
  for (const name of toProtect) {
335
435
  console.error(` \u2713 ${name} \u2014 protected (${options.policy})`);
336
436
  }
@@ -341,6 +441,18 @@ async function main() {
341
441
  console.error(` \u25CB ${name} \u2014 skipped`);
342
442
  }
343
443
  console.error("");
444
+ 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");
445
+ console.error(" \u2502 Setup complete! \u2502");
446
+ console.error(" \u2502 \u2502");
447
+ console.error(" \u2502 MCP tools \u2192 Proxy audit logging \u2502");
448
+ console.error(" \u2502 Built-in tools \u2192 Hook audit logging \u2502");
449
+ console.error(" \u2502 \u2502");
450
+ console.error(" \u2502 View logs: https://dashboard.solongate.com \u2502");
451
+ console.error(" \u2502 To undo: solongate-init --restore \u2502");
452
+ console.error(" \u2502 \u2502");
453
+ console.error(" \u2502 Restart your MCP client to apply changes. \u2502");
454
+ 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");
455
+ console.error("");
344
456
  }
345
457
  main().catch((err) => {
346
458
  console.error(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "MCP security proxy \u00e2\u20ac\u201d protect any MCP server with policies, input validation, rate limiting, and audit logging. Zero code changes required.",
5
5
  "type": "module",
6
6
  "bin": {