@solongate/proxy 0.3.1 → 0.4.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 +1041 -723
- package/dist/pull-push.js +143 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,29 +4,520 @@ var __esm = (fn, res) => function __init() {
|
|
|
4
4
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
+
// src/config.ts
|
|
8
|
+
import { readFileSync, existsSync } from "fs";
|
|
9
|
+
import { resolve } from "path";
|
|
10
|
+
async function fetchCloudPolicy(apiKey, apiUrl, policyId) {
|
|
11
|
+
const url = `${apiUrl}/api/v1/policies/${policyId ?? "default"}`;
|
|
12
|
+
const res = await fetch(url, {
|
|
13
|
+
headers: { "Authorization": `Bearer ${apiKey}` }
|
|
14
|
+
});
|
|
15
|
+
if (!res.ok) {
|
|
16
|
+
const body = await res.text().catch(() => "");
|
|
17
|
+
throw new Error(`Failed to fetch policy from cloud (${res.status}): ${body}`);
|
|
18
|
+
}
|
|
19
|
+
const data = await res.json();
|
|
20
|
+
return {
|
|
21
|
+
id: String(data.id ?? "cloud"),
|
|
22
|
+
name: String(data.name ?? "Cloud Policy"),
|
|
23
|
+
version: Number(data._version ?? 1),
|
|
24
|
+
rules: data.rules ?? [],
|
|
25
|
+
createdAt: String(data._created_at ?? ""),
|
|
26
|
+
updatedAt: ""
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
async function sendAuditLog(apiKey, apiUrl, entry) {
|
|
30
|
+
const url = `${apiUrl}/api/v1/audit-logs`;
|
|
31
|
+
try {
|
|
32
|
+
const res = await fetch(url, {
|
|
33
|
+
method: "POST",
|
|
34
|
+
headers: {
|
|
35
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
36
|
+
"Content-Type": "application/json"
|
|
37
|
+
},
|
|
38
|
+
body: JSON.stringify(entry)
|
|
39
|
+
});
|
|
40
|
+
if (!res.ok) {
|
|
41
|
+
const body = await res.text().catch(() => "");
|
|
42
|
+
process.stderr.write(`[SolonGate] Audit log failed (${res.status}): ${body}
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
45
|
+
} catch (err) {
|
|
46
|
+
process.stderr.write(`[SolonGate] Audit log error: ${err instanceof Error ? err.message : String(err)}
|
|
47
|
+
`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function loadPolicy(source) {
|
|
51
|
+
if (typeof source === "object") return source;
|
|
52
|
+
if (PRESETS[source]) return PRESETS[source];
|
|
53
|
+
const filePath = resolve(source);
|
|
54
|
+
if (existsSync(filePath)) {
|
|
55
|
+
const content = readFileSync(filePath, "utf-8");
|
|
56
|
+
return JSON.parse(content);
|
|
57
|
+
}
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Unknown policy "${source}". Use a preset (${Object.keys(PRESETS).join(", ")}), a JSON file path, or a PolicySet object.`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
function parseArgs(argv) {
|
|
63
|
+
const args = argv.slice(2);
|
|
64
|
+
let policySource = "restricted";
|
|
65
|
+
let name = "solongate-proxy";
|
|
66
|
+
let verbose = false;
|
|
67
|
+
let rateLimitPerTool;
|
|
68
|
+
let globalRateLimit;
|
|
69
|
+
let configFile;
|
|
70
|
+
let apiKey;
|
|
71
|
+
let apiUrl;
|
|
72
|
+
let upstreamUrl;
|
|
73
|
+
let upstreamTransport;
|
|
74
|
+
let port;
|
|
75
|
+
let separatorIndex = args.indexOf("--");
|
|
76
|
+
const flags = separatorIndex >= 0 ? args.slice(0, separatorIndex) : args;
|
|
77
|
+
const upstreamArgs = separatorIndex >= 0 ? args.slice(separatorIndex + 1) : [];
|
|
78
|
+
for (let i = 0; i < flags.length; i++) {
|
|
79
|
+
switch (flags[i]) {
|
|
80
|
+
case "--policy":
|
|
81
|
+
policySource = flags[++i];
|
|
82
|
+
break;
|
|
83
|
+
case "--name":
|
|
84
|
+
name = flags[++i];
|
|
85
|
+
break;
|
|
86
|
+
case "--verbose":
|
|
87
|
+
verbose = true;
|
|
88
|
+
break;
|
|
89
|
+
case "--rate-limit":
|
|
90
|
+
rateLimitPerTool = parseInt(flags[++i], 10);
|
|
91
|
+
break;
|
|
92
|
+
case "--global-rate-limit":
|
|
93
|
+
globalRateLimit = parseInt(flags[++i], 10);
|
|
94
|
+
break;
|
|
95
|
+
case "--config":
|
|
96
|
+
configFile = flags[++i];
|
|
97
|
+
break;
|
|
98
|
+
case "--api-key":
|
|
99
|
+
apiKey = flags[++i];
|
|
100
|
+
break;
|
|
101
|
+
case "--api-url":
|
|
102
|
+
apiUrl = flags[++i];
|
|
103
|
+
break;
|
|
104
|
+
case "--upstream-url":
|
|
105
|
+
upstreamUrl = flags[++i];
|
|
106
|
+
break;
|
|
107
|
+
case "--upstream-transport":
|
|
108
|
+
upstreamTransport = flags[++i];
|
|
109
|
+
break;
|
|
110
|
+
case "--port":
|
|
111
|
+
port = parseInt(flags[++i], 10);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (!apiKey) {
|
|
116
|
+
const envKey = process.env.SOLONGATE_API_KEY;
|
|
117
|
+
if (envKey) {
|
|
118
|
+
apiKey = envKey;
|
|
119
|
+
} else {
|
|
120
|
+
throw new Error(
|
|
121
|
+
"A valid SolonGate API key is required.\n\nUsage: solongate-proxy --api-key sg_live_xxx -- <command>\n or: set SOLONGATE_API_KEY=sg_live_xxx\n\nGet your API key at https://solongate.com\n"
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
"Invalid API key format. Keys must start with 'sg_live_' or 'sg_test_'.\nGet your API key at https://solongate.com\n"
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
const resolvedPolicyPath = resolvePolicyPath(policySource);
|
|
131
|
+
if (configFile) {
|
|
132
|
+
const filePath = resolve(configFile);
|
|
133
|
+
const content = readFileSync(filePath, "utf-8");
|
|
134
|
+
const fileConfig = JSON.parse(content);
|
|
135
|
+
if (!fileConfig.upstream) {
|
|
136
|
+
throw new Error('Config file must include "upstream" with at least "command" or "url"');
|
|
137
|
+
}
|
|
138
|
+
const cfgPolicySource = fileConfig.policy ?? policySource;
|
|
139
|
+
return {
|
|
140
|
+
upstream: fileConfig.upstream,
|
|
141
|
+
policy: loadPolicy(cfgPolicySource),
|
|
142
|
+
name: fileConfig.name ?? name,
|
|
143
|
+
verbose: fileConfig.verbose ?? verbose,
|
|
144
|
+
rateLimitPerTool: fileConfig.rateLimitPerTool ?? rateLimitPerTool,
|
|
145
|
+
globalRateLimit: fileConfig.globalRateLimit ?? globalRateLimit,
|
|
146
|
+
apiKey: apiKey ?? fileConfig.apiKey,
|
|
147
|
+
apiUrl: apiUrl ?? fileConfig.apiUrl,
|
|
148
|
+
port: port ?? fileConfig.port,
|
|
149
|
+
policyPath: resolvePolicyPath(cfgPolicySource) ?? void 0
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (upstreamUrl) {
|
|
153
|
+
const transport = upstreamTransport ?? (upstreamUrl.includes("/sse") ? "sse" : "http");
|
|
154
|
+
return {
|
|
155
|
+
upstream: {
|
|
156
|
+
transport,
|
|
157
|
+
command: "",
|
|
158
|
+
// not used for URL-based transports
|
|
159
|
+
url: upstreamUrl
|
|
160
|
+
},
|
|
161
|
+
policy: loadPolicy(policySource),
|
|
162
|
+
name,
|
|
163
|
+
verbose,
|
|
164
|
+
rateLimitPerTool,
|
|
165
|
+
globalRateLimit,
|
|
166
|
+
apiKey,
|
|
167
|
+
apiUrl,
|
|
168
|
+
port,
|
|
169
|
+
policyPath: resolvedPolicyPath ?? void 0
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
if (upstreamArgs.length === 0) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
"No upstream server command provided.\n\nUsage: solongate-proxy [options] -- <command> [args...]\n\nExamples:\n solongate-proxy -- node my-server.js\n solongate-proxy --policy restricted -- npx @openclaw/server\n solongate-proxy --upstream-url http://localhost:3001/mcp\n solongate-proxy --config solongate.json\n"
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
const [command, ...commandArgs] = upstreamArgs;
|
|
178
|
+
return {
|
|
179
|
+
upstream: {
|
|
180
|
+
transport: upstreamTransport ?? "stdio",
|
|
181
|
+
command,
|
|
182
|
+
args: commandArgs,
|
|
183
|
+
env: { ...process.env }
|
|
184
|
+
},
|
|
185
|
+
policy: loadPolicy(policySource),
|
|
186
|
+
name,
|
|
187
|
+
verbose,
|
|
188
|
+
rateLimitPerTool,
|
|
189
|
+
globalRateLimit,
|
|
190
|
+
apiKey,
|
|
191
|
+
apiUrl,
|
|
192
|
+
port,
|
|
193
|
+
policyPath: resolvedPolicyPath ?? void 0
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function resolvePolicyPath(source) {
|
|
197
|
+
if (PRESETS[source]) return null;
|
|
198
|
+
const filePath = resolve(source);
|
|
199
|
+
if (existsSync(filePath)) return filePath;
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
var PRESETS;
|
|
203
|
+
var init_config = __esm({
|
|
204
|
+
"src/config.ts"() {
|
|
205
|
+
"use strict";
|
|
206
|
+
PRESETS = {
|
|
207
|
+
restricted: {
|
|
208
|
+
id: "restricted",
|
|
209
|
+
name: "Restricted",
|
|
210
|
+
description: "Blocks dangerous commands (rm -rf, curl|bash, dd, etc.), allows safe tools with path restrictions",
|
|
211
|
+
version: 1,
|
|
212
|
+
rules: [
|
|
213
|
+
{
|
|
214
|
+
id: "deny-dangerous-commands",
|
|
215
|
+
description: "Block dangerous shell commands",
|
|
216
|
+
effect: "DENY",
|
|
217
|
+
priority: 50,
|
|
218
|
+
toolPattern: "*",
|
|
219
|
+
permission: "EXECUTE",
|
|
220
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
221
|
+
enabled: true,
|
|
222
|
+
commandConstraints: {
|
|
223
|
+
denied: [
|
|
224
|
+
"rm -rf *",
|
|
225
|
+
"rm -r /*",
|
|
226
|
+
"mkfs*",
|
|
227
|
+
"dd if=*",
|
|
228
|
+
"curl*|*bash*",
|
|
229
|
+
"curl*|*sh*",
|
|
230
|
+
"wget*|*bash*",
|
|
231
|
+
"wget*|*sh*",
|
|
232
|
+
"shutdown*",
|
|
233
|
+
"reboot*",
|
|
234
|
+
"kill -9*",
|
|
235
|
+
"chmod*777*",
|
|
236
|
+
"iptables*",
|
|
237
|
+
"passwd*",
|
|
238
|
+
"useradd*",
|
|
239
|
+
"userdel*"
|
|
240
|
+
]
|
|
241
|
+
},
|
|
242
|
+
createdAt: "",
|
|
243
|
+
updatedAt: ""
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
id: "deny-sensitive-paths",
|
|
247
|
+
description: "Block access to sensitive files",
|
|
248
|
+
effect: "DENY",
|
|
249
|
+
priority: 51,
|
|
250
|
+
toolPattern: "*",
|
|
251
|
+
permission: "EXECUTE",
|
|
252
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
253
|
+
enabled: true,
|
|
254
|
+
pathConstraints: {
|
|
255
|
+
denied: [
|
|
256
|
+
"**/.env*",
|
|
257
|
+
"**/.ssh/**",
|
|
258
|
+
"**/.aws/**",
|
|
259
|
+
"**/.kube/**",
|
|
260
|
+
"**/credentials*",
|
|
261
|
+
"**/secrets*",
|
|
262
|
+
"**/*.pem",
|
|
263
|
+
"**/*.key",
|
|
264
|
+
"/etc/passwd",
|
|
265
|
+
"/etc/shadow",
|
|
266
|
+
"/proc/**",
|
|
267
|
+
"/dev/**"
|
|
268
|
+
]
|
|
269
|
+
},
|
|
270
|
+
createdAt: "",
|
|
271
|
+
updatedAt: ""
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
id: "allow-rest",
|
|
275
|
+
description: "Allow all other tool calls",
|
|
276
|
+
effect: "ALLOW",
|
|
277
|
+
priority: 1e3,
|
|
278
|
+
toolPattern: "*",
|
|
279
|
+
permission: "EXECUTE",
|
|
280
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
281
|
+
enabled: true,
|
|
282
|
+
createdAt: "",
|
|
283
|
+
updatedAt: ""
|
|
284
|
+
}
|
|
285
|
+
],
|
|
286
|
+
createdAt: "",
|
|
287
|
+
updatedAt: ""
|
|
288
|
+
},
|
|
289
|
+
"read-only": {
|
|
290
|
+
id: "read-only",
|
|
291
|
+
name: "Read Only",
|
|
292
|
+
description: "Only allows read/list/get/search/query tools",
|
|
293
|
+
version: 1,
|
|
294
|
+
rules: [
|
|
295
|
+
{
|
|
296
|
+
id: "allow-read",
|
|
297
|
+
description: "Allow read tools",
|
|
298
|
+
effect: "ALLOW",
|
|
299
|
+
priority: 100,
|
|
300
|
+
toolPattern: "*read*",
|
|
301
|
+
permission: "EXECUTE",
|
|
302
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
303
|
+
enabled: true,
|
|
304
|
+
createdAt: "",
|
|
305
|
+
updatedAt: ""
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
id: "allow-list",
|
|
309
|
+
description: "Allow list tools",
|
|
310
|
+
effect: "ALLOW",
|
|
311
|
+
priority: 101,
|
|
312
|
+
toolPattern: "*list*",
|
|
313
|
+
permission: "EXECUTE",
|
|
314
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
315
|
+
enabled: true,
|
|
316
|
+
createdAt: "",
|
|
317
|
+
updatedAt: ""
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
id: "allow-get",
|
|
321
|
+
description: "Allow get tools",
|
|
322
|
+
effect: "ALLOW",
|
|
323
|
+
priority: 102,
|
|
324
|
+
toolPattern: "*get*",
|
|
325
|
+
permission: "EXECUTE",
|
|
326
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
327
|
+
enabled: true,
|
|
328
|
+
createdAt: "",
|
|
329
|
+
updatedAt: ""
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
id: "allow-search",
|
|
333
|
+
description: "Allow search tools",
|
|
334
|
+
effect: "ALLOW",
|
|
335
|
+
priority: 103,
|
|
336
|
+
toolPattern: "*search*",
|
|
337
|
+
permission: "EXECUTE",
|
|
338
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
339
|
+
enabled: true,
|
|
340
|
+
createdAt: "",
|
|
341
|
+
updatedAt: ""
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
id: "allow-query",
|
|
345
|
+
description: "Allow query tools",
|
|
346
|
+
effect: "ALLOW",
|
|
347
|
+
priority: 104,
|
|
348
|
+
toolPattern: "*query*",
|
|
349
|
+
permission: "EXECUTE",
|
|
350
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
351
|
+
enabled: true,
|
|
352
|
+
createdAt: "",
|
|
353
|
+
updatedAt: ""
|
|
354
|
+
}
|
|
355
|
+
],
|
|
356
|
+
createdAt: "",
|
|
357
|
+
updatedAt: ""
|
|
358
|
+
},
|
|
359
|
+
sandbox: {
|
|
360
|
+
id: "sandbox",
|
|
361
|
+
name: "Sandbox",
|
|
362
|
+
description: "Allows file ops within working directory only, blocks dangerous commands, allows safe shell commands",
|
|
363
|
+
version: 1,
|
|
364
|
+
rules: [
|
|
365
|
+
{
|
|
366
|
+
id: "deny-dangerous-commands",
|
|
367
|
+
description: "Block dangerous shell commands",
|
|
368
|
+
effect: "DENY",
|
|
369
|
+
priority: 50,
|
|
370
|
+
toolPattern: "*",
|
|
371
|
+
permission: "EXECUTE",
|
|
372
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
373
|
+
enabled: true,
|
|
374
|
+
commandConstraints: {
|
|
375
|
+
denied: [
|
|
376
|
+
"rm -rf *",
|
|
377
|
+
"rm -r /*",
|
|
378
|
+
"mkfs*",
|
|
379
|
+
"dd if=*",
|
|
380
|
+
"curl*|*bash*",
|
|
381
|
+
"wget*|*sh*",
|
|
382
|
+
"shutdown*",
|
|
383
|
+
"reboot*",
|
|
384
|
+
"chmod*777*"
|
|
385
|
+
]
|
|
386
|
+
},
|
|
387
|
+
createdAt: "",
|
|
388
|
+
updatedAt: ""
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
id: "allow-safe-commands",
|
|
392
|
+
description: "Allow safe shell commands only",
|
|
393
|
+
effect: "ALLOW",
|
|
394
|
+
priority: 100,
|
|
395
|
+
toolPattern: "*shell*",
|
|
396
|
+
permission: "EXECUTE",
|
|
397
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
398
|
+
enabled: true,
|
|
399
|
+
commandConstraints: {
|
|
400
|
+
allowed: [
|
|
401
|
+
"ls*",
|
|
402
|
+
"cat*",
|
|
403
|
+
"head*",
|
|
404
|
+
"tail*",
|
|
405
|
+
"wc*",
|
|
406
|
+
"grep*",
|
|
407
|
+
"find*",
|
|
408
|
+
"echo*",
|
|
409
|
+
"pwd",
|
|
410
|
+
"whoami",
|
|
411
|
+
"date",
|
|
412
|
+
"env",
|
|
413
|
+
"git*",
|
|
414
|
+
"npm*",
|
|
415
|
+
"pnpm*",
|
|
416
|
+
"yarn*",
|
|
417
|
+
"node*",
|
|
418
|
+
"python*",
|
|
419
|
+
"pip*"
|
|
420
|
+
]
|
|
421
|
+
},
|
|
422
|
+
createdAt: "",
|
|
423
|
+
updatedAt: ""
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
id: "deny-sensitive-paths",
|
|
427
|
+
description: "Block access to sensitive files",
|
|
428
|
+
effect: "DENY",
|
|
429
|
+
priority: 51,
|
|
430
|
+
toolPattern: "*",
|
|
431
|
+
permission: "EXECUTE",
|
|
432
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
433
|
+
enabled: true,
|
|
434
|
+
pathConstraints: {
|
|
435
|
+
denied: [
|
|
436
|
+
"**/.env*",
|
|
437
|
+
"**/.ssh/**",
|
|
438
|
+
"**/.aws/**",
|
|
439
|
+
"**/credentials*",
|
|
440
|
+
"**/*.pem",
|
|
441
|
+
"**/*.key"
|
|
442
|
+
]
|
|
443
|
+
},
|
|
444
|
+
createdAt: "",
|
|
445
|
+
updatedAt: ""
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
id: "allow-rest",
|
|
449
|
+
description: "Allow all other tools",
|
|
450
|
+
effect: "ALLOW",
|
|
451
|
+
priority: 1e3,
|
|
452
|
+
toolPattern: "*",
|
|
453
|
+
permission: "EXECUTE",
|
|
454
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
455
|
+
enabled: true,
|
|
456
|
+
createdAt: "",
|
|
457
|
+
updatedAt: ""
|
|
458
|
+
}
|
|
459
|
+
],
|
|
460
|
+
createdAt: "",
|
|
461
|
+
updatedAt: ""
|
|
462
|
+
},
|
|
463
|
+
permissive: {
|
|
464
|
+
id: "permissive",
|
|
465
|
+
name: "Permissive",
|
|
466
|
+
description: "Allows all tool calls (monitoring only)",
|
|
467
|
+
version: 1,
|
|
468
|
+
rules: [
|
|
469
|
+
{
|
|
470
|
+
id: "allow-all",
|
|
471
|
+
description: "Allow all",
|
|
472
|
+
effect: "ALLOW",
|
|
473
|
+
priority: 1e3,
|
|
474
|
+
toolPattern: "*",
|
|
475
|
+
permission: "EXECUTE",
|
|
476
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
477
|
+
enabled: true,
|
|
478
|
+
createdAt: "",
|
|
479
|
+
updatedAt: ""
|
|
480
|
+
}
|
|
481
|
+
],
|
|
482
|
+
createdAt: "",
|
|
483
|
+
updatedAt: ""
|
|
484
|
+
},
|
|
485
|
+
"deny-all": {
|
|
486
|
+
id: "deny-all",
|
|
487
|
+
name: "Deny All",
|
|
488
|
+
description: "Blocks all tool calls",
|
|
489
|
+
version: 1,
|
|
490
|
+
rules: [],
|
|
491
|
+
createdAt: "",
|
|
492
|
+
updatedAt: ""
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
|
|
7
498
|
// src/init.ts
|
|
8
499
|
var init_exports = {};
|
|
9
|
-
import { readFileSync as
|
|
500
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, copyFileSync, mkdirSync } from "fs";
|
|
10
501
|
import { resolve as resolve2, join } from "path";
|
|
11
502
|
import { createInterface } from "readline";
|
|
12
503
|
function findConfigFile(explicitPath) {
|
|
13
504
|
if (explicitPath) {
|
|
14
|
-
if (
|
|
505
|
+
if (existsSync3(explicitPath)) {
|
|
15
506
|
return { path: resolve2(explicitPath), type: "mcp" };
|
|
16
507
|
}
|
|
17
508
|
return null;
|
|
18
509
|
}
|
|
19
510
|
for (const searchPath of SEARCH_PATHS) {
|
|
20
511
|
const full = resolve2(searchPath);
|
|
21
|
-
if (
|
|
512
|
+
if (existsSync3(full)) return { path: full, type: "mcp" };
|
|
22
513
|
}
|
|
23
514
|
for (const desktopPath of CLAUDE_DESKTOP_PATHS) {
|
|
24
|
-
if (
|
|
515
|
+
if (existsSync3(desktopPath)) return { path: desktopPath, type: "claude-desktop" };
|
|
25
516
|
}
|
|
26
517
|
return null;
|
|
27
518
|
}
|
|
28
519
|
function readConfig(filePath) {
|
|
29
|
-
const content =
|
|
520
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
30
521
|
const parsed = JSON.parse(content);
|
|
31
522
|
if (parsed.mcpServers) return parsed;
|
|
32
523
|
throw new Error(`Unrecognized config format in ${filePath}`);
|
|
@@ -97,7 +588,7 @@ function parseInitArgs(argv) {
|
|
|
97
588
|
}
|
|
98
589
|
}
|
|
99
590
|
if (!POLICY_PRESETS.includes(options.policy)) {
|
|
100
|
-
if (!
|
|
591
|
+
if (!existsSync3(resolve2(options.policy))) {
|
|
101
592
|
console.error(`Unknown policy: ${options.policy}`);
|
|
102
593
|
console.error(`Available presets: ${POLICY_PRESETS.join(", ")}`);
|
|
103
594
|
process.exit(1);
|
|
@@ -141,16 +632,16 @@ function installClaudeCodeHooks(apiKey) {
|
|
|
141
632
|
const hooksDir = resolve2(".claude", "hooks");
|
|
142
633
|
mkdirSync(hooksDir, { recursive: true });
|
|
143
634
|
const guardPath = join(hooksDir, "guard.mjs");
|
|
144
|
-
|
|
635
|
+
writeFileSync2(guardPath, GUARD_SCRIPT);
|
|
145
636
|
console.error(` Created ${guardPath}`);
|
|
146
637
|
const auditPath = join(hooksDir, "audit.mjs");
|
|
147
|
-
|
|
638
|
+
writeFileSync2(auditPath, AUDIT_SCRIPT);
|
|
148
639
|
console.error(` Created ${auditPath}`);
|
|
149
640
|
const settingsPath = resolve2(".claude", "settings.json");
|
|
150
641
|
let settings = {};
|
|
151
|
-
if (
|
|
642
|
+
if (existsSync3(settingsPath)) {
|
|
152
643
|
try {
|
|
153
|
-
settings = JSON.parse(
|
|
644
|
+
settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
|
|
154
645
|
} catch {
|
|
155
646
|
}
|
|
156
647
|
}
|
|
@@ -184,7 +675,7 @@ function installClaudeCodeHooks(apiKey) {
|
|
|
184
675
|
const envObj = settings.env || {};
|
|
185
676
|
envObj.SOLONGATE_API_KEY = apiKey;
|
|
186
677
|
settings.env = envObj;
|
|
187
|
-
|
|
678
|
+
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
188
679
|
console.error(` Created ${settingsPath}`);
|
|
189
680
|
console.error("");
|
|
190
681
|
console.error(" Claude Code hooks installed!");
|
|
@@ -193,7 +684,7 @@ function installClaudeCodeHooks(apiKey) {
|
|
|
193
684
|
}
|
|
194
685
|
function ensureEnvFile() {
|
|
195
686
|
const envPath = resolve2(".env");
|
|
196
|
-
if (!
|
|
687
|
+
if (!existsSync3(envPath)) {
|
|
197
688
|
const envContent = `# SolonGate API Keys
|
|
198
689
|
# Get your keys from the dashboard: https://solongate.com
|
|
199
690
|
# IMPORTANT: Never commit this file to git!
|
|
@@ -204,14 +695,14 @@ SOLONGATE_API_KEY=sg_live_your_key_here
|
|
|
204
695
|
# Test key \u2014 local-only, no cloud connection (for development)
|
|
205
696
|
# SOLONGATE_API_KEY=sg_test_your_key_here
|
|
206
697
|
`;
|
|
207
|
-
|
|
698
|
+
writeFileSync2(envPath, envContent);
|
|
208
699
|
console.error(` Created .env with placeholder API keys`);
|
|
209
700
|
console.error(` \u2192 Edit .env and replace with your real keys from https://solongate.com`);
|
|
210
701
|
console.error("");
|
|
211
702
|
}
|
|
212
703
|
const gitignorePath = resolve2(".gitignore");
|
|
213
|
-
if (
|
|
214
|
-
let gitignore =
|
|
704
|
+
if (existsSync3(gitignorePath)) {
|
|
705
|
+
let gitignore = readFileSync3(gitignorePath, "utf-8");
|
|
215
706
|
let updated = false;
|
|
216
707
|
if (!gitignore.includes(".env")) {
|
|
217
708
|
gitignore = gitignore.trimEnd() + "\n.env\n.env.local\n";
|
|
@@ -222,11 +713,11 @@ SOLONGATE_API_KEY=sg_live_your_key_here
|
|
|
222
713
|
updated = true;
|
|
223
714
|
}
|
|
224
715
|
if (updated) {
|
|
225
|
-
|
|
716
|
+
writeFileSync2(gitignorePath, gitignore);
|
|
226
717
|
console.error(` Updated .gitignore (added .env + .mcp.json)`);
|
|
227
718
|
}
|
|
228
719
|
} else {
|
|
229
|
-
|
|
720
|
+
writeFileSync2(gitignorePath, ".env\n.env.local\n.mcp.json\nnode_modules/\n");
|
|
230
721
|
console.error(` Created .gitignore (with .env and .mcp.json excluded)`);
|
|
231
722
|
}
|
|
232
723
|
}
|
|
@@ -274,7 +765,7 @@ async function main() {
|
|
|
274
765
|
console.error("");
|
|
275
766
|
const backupPath = configInfo.path + ".solongate-backup";
|
|
276
767
|
if (options.restore) {
|
|
277
|
-
if (!
|
|
768
|
+
if (!existsSync3(backupPath)) {
|
|
278
769
|
console.error(" No backup found. Nothing to restore.");
|
|
279
770
|
process.exit(1);
|
|
280
771
|
}
|
|
@@ -331,8 +822,8 @@ async function main() {
|
|
|
331
822
|
let apiKey = options.apiKey || process.env.SOLONGATE_API_KEY || "";
|
|
332
823
|
if (!apiKey) {
|
|
333
824
|
const envPath = resolve2(".env");
|
|
334
|
-
if (
|
|
335
|
-
const envContent =
|
|
825
|
+
if (existsSync3(envPath)) {
|
|
826
|
+
const envContent = readFileSync3(envPath, "utf-8");
|
|
336
827
|
const match = envContent.match(/^SOLONGATE_API_KEY=(sg_(?:live|test)_\w+)/m);
|
|
337
828
|
if (match) apiKey = match[1];
|
|
338
829
|
}
|
|
@@ -367,16 +858,16 @@ async function main() {
|
|
|
367
858
|
console.error(JSON.stringify(newConfig, null, 2));
|
|
368
859
|
process.exit(0);
|
|
369
860
|
}
|
|
370
|
-
if (!
|
|
861
|
+
if (!existsSync3(backupPath)) {
|
|
371
862
|
copyFileSync(configInfo.path, backupPath);
|
|
372
863
|
console.error(` Backup: ${backupPath}`);
|
|
373
864
|
}
|
|
374
865
|
if (configInfo.type === "claude-desktop") {
|
|
375
|
-
const original = JSON.parse(
|
|
866
|
+
const original = JSON.parse(readFileSync3(configInfo.path, "utf-8"));
|
|
376
867
|
original.mcpServers = newConfig.mcpServers;
|
|
377
|
-
|
|
868
|
+
writeFileSync2(configInfo.path, JSON.stringify(original, null, 2) + "\n");
|
|
378
869
|
} else {
|
|
379
|
-
|
|
870
|
+
writeFileSync2(configInfo.path, JSON.stringify(newConfig, null, 2) + "\n");
|
|
380
871
|
}
|
|
381
872
|
console.error(" Config updated!");
|
|
382
873
|
console.error("");
|
|
@@ -635,7 +1126,7 @@ process.stdin.on('end', async () => {
|
|
|
635
1126
|
|
|
636
1127
|
// src/inject.ts
|
|
637
1128
|
var inject_exports = {};
|
|
638
|
-
import { readFileSync as
|
|
1129
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, copyFileSync as copyFileSync2 } from "fs";
|
|
639
1130
|
import { resolve as resolve3 } from "path";
|
|
640
1131
|
import { execSync } from "child_process";
|
|
641
1132
|
function parseInjectArgs(argv) {
|
|
@@ -668,7 +1159,7 @@ function parseInjectArgs(argv) {
|
|
|
668
1159
|
return opts;
|
|
669
1160
|
}
|
|
670
1161
|
function printHelp2() {
|
|
671
|
-
|
|
1162
|
+
log3(`
|
|
672
1163
|
SolonGate Inject \u2014 Add security to your MCP server in seconds
|
|
673
1164
|
|
|
674
1165
|
USAGE
|
|
@@ -692,7 +1183,7 @@ WHAT IT DOES
|
|
|
692
1183
|
All tool() calls are automatically protected by SolonGate.
|
|
693
1184
|
`);
|
|
694
1185
|
}
|
|
695
|
-
function
|
|
1186
|
+
function log3(msg) {
|
|
696
1187
|
process.stderr.write(msg + "\n");
|
|
697
1188
|
}
|
|
698
1189
|
function printBanner(subtitle) {
|
|
@@ -705,18 +1196,18 @@ function printBanner(subtitle) {
|
|
|
705
1196
|
" \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"
|
|
706
1197
|
];
|
|
707
1198
|
const colors = [c.blue1, c.blue2, c.blue3, c.blue4, c.blue5, c.blue6];
|
|
708
|
-
|
|
1199
|
+
log3("");
|
|
709
1200
|
for (let i = 0; i < lines.length; i++) {
|
|
710
|
-
|
|
1201
|
+
log3(`${c.bold}${colors[i]}${lines[i]}${c.reset}`);
|
|
711
1202
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
1203
|
+
log3("");
|
|
1204
|
+
log3(` ${c.dim}${c.italic}${subtitle}${c.reset}`);
|
|
1205
|
+
log3("");
|
|
715
1206
|
}
|
|
716
1207
|
function detectProject() {
|
|
717
|
-
if (!
|
|
1208
|
+
if (!existsSync4(resolve3("package.json"))) return false;
|
|
718
1209
|
try {
|
|
719
|
-
const pkg = JSON.parse(
|
|
1210
|
+
const pkg = JSON.parse(readFileSync4(resolve3("package.json"), "utf-8"));
|
|
720
1211
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
721
1212
|
return !!(allDeps["@modelcontextprotocol/sdk"] || allDeps["@modelcontextprotocol/server"]);
|
|
722
1213
|
} catch {
|
|
@@ -725,18 +1216,18 @@ function detectProject() {
|
|
|
725
1216
|
}
|
|
726
1217
|
function findTsEntryFile() {
|
|
727
1218
|
try {
|
|
728
|
-
const pkg = JSON.parse(
|
|
1219
|
+
const pkg = JSON.parse(readFileSync4(resolve3("package.json"), "utf-8"));
|
|
729
1220
|
if (pkg.bin) {
|
|
730
1221
|
const binPath = typeof pkg.bin === "string" ? pkg.bin : Object.values(pkg.bin)[0];
|
|
731
1222
|
if (typeof binPath === "string") {
|
|
732
1223
|
const srcPath = binPath.replace(/^\.\/dist\//, "./src/").replace(/\.js$/, ".ts");
|
|
733
|
-
if (
|
|
734
|
-
if (
|
|
1224
|
+
if (existsSync4(resolve3(srcPath))) return resolve3(srcPath);
|
|
1225
|
+
if (existsSync4(resolve3(binPath))) return resolve3(binPath);
|
|
735
1226
|
}
|
|
736
1227
|
}
|
|
737
1228
|
if (pkg.main) {
|
|
738
1229
|
const srcPath = pkg.main.replace(/^\.\/dist\//, "./src/").replace(/\.js$/, ".ts");
|
|
739
|
-
if (
|
|
1230
|
+
if (existsSync4(resolve3(srcPath))) return resolve3(srcPath);
|
|
740
1231
|
}
|
|
741
1232
|
} catch {
|
|
742
1233
|
}
|
|
@@ -750,9 +1241,9 @@ function findTsEntryFile() {
|
|
|
750
1241
|
];
|
|
751
1242
|
for (const c3 of candidates) {
|
|
752
1243
|
const full = resolve3(c3);
|
|
753
|
-
if (
|
|
1244
|
+
if (existsSync4(full)) {
|
|
754
1245
|
try {
|
|
755
|
-
const content =
|
|
1246
|
+
const content = readFileSync4(full, "utf-8");
|
|
756
1247
|
if (content.includes("McpServer") || content.includes("McpServer")) {
|
|
757
1248
|
return full;
|
|
758
1249
|
}
|
|
@@ -761,39 +1252,39 @@ function findTsEntryFile() {
|
|
|
761
1252
|
}
|
|
762
1253
|
}
|
|
763
1254
|
for (const c3 of candidates) {
|
|
764
|
-
if (
|
|
1255
|
+
if (existsSync4(resolve3(c3))) return resolve3(c3);
|
|
765
1256
|
}
|
|
766
1257
|
return null;
|
|
767
1258
|
}
|
|
768
1259
|
function detectPackageManager() {
|
|
769
|
-
if (
|
|
770
|
-
if (
|
|
1260
|
+
if (existsSync4(resolve3("pnpm-lock.yaml"))) return "pnpm";
|
|
1261
|
+
if (existsSync4(resolve3("yarn.lock"))) return "yarn";
|
|
771
1262
|
return "npm";
|
|
772
1263
|
}
|
|
773
1264
|
function installSdk() {
|
|
774
1265
|
try {
|
|
775
|
-
const pkg = JSON.parse(
|
|
1266
|
+
const pkg = JSON.parse(readFileSync4(resolve3("package.json"), "utf-8"));
|
|
776
1267
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
777
1268
|
if (allDeps["@solongate/sdk"]) {
|
|
778
|
-
|
|
1269
|
+
log3(" @solongate/sdk already installed");
|
|
779
1270
|
return true;
|
|
780
1271
|
}
|
|
781
1272
|
} catch {
|
|
782
1273
|
}
|
|
783
1274
|
const pm = detectPackageManager();
|
|
784
1275
|
const cmd = pm === "yarn" ? "yarn add @solongate/sdk" : `${pm} install @solongate/sdk`;
|
|
785
|
-
|
|
1276
|
+
log3(` Installing @solongate/sdk via ${pm}...`);
|
|
786
1277
|
try {
|
|
787
1278
|
execSync(cmd, { stdio: "pipe", cwd: process.cwd() });
|
|
788
1279
|
return true;
|
|
789
1280
|
} catch (err) {
|
|
790
|
-
|
|
791
|
-
|
|
1281
|
+
log3(` Failed to install: ${err instanceof Error ? err.message : String(err)}`);
|
|
1282
|
+
log3(" You can install manually: npm install @solongate/sdk");
|
|
792
1283
|
return false;
|
|
793
1284
|
}
|
|
794
1285
|
}
|
|
795
1286
|
function injectTypeScript(filePath) {
|
|
796
|
-
const original =
|
|
1287
|
+
const original = readFileSync4(filePath, "utf-8");
|
|
797
1288
|
const changes = [];
|
|
798
1289
|
let modified = original;
|
|
799
1290
|
if (modified.includes("SecureMcpServer")) {
|
|
@@ -871,14 +1362,14 @@ function findImportInsertPosition(content) {
|
|
|
871
1362
|
}
|
|
872
1363
|
function showDiff(result) {
|
|
873
1364
|
if (result.original === result.modified) {
|
|
874
|
-
|
|
1365
|
+
log3(" No changes needed.");
|
|
875
1366
|
return;
|
|
876
1367
|
}
|
|
877
1368
|
const origLines = result.original.split("\n");
|
|
878
1369
|
const modLines = result.modified.split("\n");
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
1370
|
+
log3("");
|
|
1371
|
+
log3(` File: ${result.file}`);
|
|
1372
|
+
log3(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
882
1373
|
const maxLines = Math.max(origLines.length, modLines.length);
|
|
883
1374
|
let diffCount = 0;
|
|
884
1375
|
for (let i = 0; i < maxLines; i++) {
|
|
@@ -886,99 +1377,99 @@ function showDiff(result) {
|
|
|
886
1377
|
const mod = modLines[i];
|
|
887
1378
|
if (orig !== mod) {
|
|
888
1379
|
if (diffCount < 30) {
|
|
889
|
-
if (orig !== void 0)
|
|
890
|
-
if (mod !== void 0)
|
|
1380
|
+
if (orig !== void 0) log3(` - ${orig}`);
|
|
1381
|
+
if (mod !== void 0) log3(` + ${mod}`);
|
|
891
1382
|
}
|
|
892
1383
|
diffCount++;
|
|
893
1384
|
}
|
|
894
1385
|
}
|
|
895
1386
|
if (diffCount > 30) {
|
|
896
|
-
|
|
1387
|
+
log3(` ... and ${diffCount - 30} more line changes`);
|
|
897
1388
|
}
|
|
898
|
-
|
|
1389
|
+
log3("");
|
|
899
1390
|
}
|
|
900
1391
|
async function main2() {
|
|
901
1392
|
const opts = parseInjectArgs(process.argv);
|
|
902
1393
|
printBanner("Inject SDK");
|
|
903
1394
|
if (!detectProject()) {
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
1395
|
+
log3(" Could not detect a TypeScript MCP server project.");
|
|
1396
|
+
log3(" Make sure you are in a project directory with:");
|
|
1397
|
+
log3(" package.json + @modelcontextprotocol/sdk in dependencies");
|
|
1398
|
+
log3("");
|
|
1399
|
+
log3(" To create a new MCP server: npx @solongate/proxy create <name>");
|
|
909
1400
|
process.exit(1);
|
|
910
1401
|
}
|
|
911
|
-
|
|
1402
|
+
log3(" Language: TypeScript");
|
|
912
1403
|
const entryFile = opts.file ? resolve3(opts.file) : findTsEntryFile();
|
|
913
|
-
if (!entryFile || !
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1404
|
+
if (!entryFile || !existsSync4(entryFile)) {
|
|
1405
|
+
log3(` Could not find entry file.${opts.file ? ` File not found: ${opts.file}` : ""}`);
|
|
1406
|
+
log3("");
|
|
1407
|
+
log3(" Specify it manually: --file <path>");
|
|
1408
|
+
log3("");
|
|
1409
|
+
log3(" Common entry points:");
|
|
1410
|
+
log3(" src/index.ts, src/server.ts, index.ts");
|
|
920
1411
|
process.exit(1);
|
|
921
1412
|
}
|
|
922
|
-
|
|
923
|
-
|
|
1413
|
+
log3(` Entry: ${entryFile}`);
|
|
1414
|
+
log3("");
|
|
924
1415
|
const backupPath = entryFile + ".solongate-backup";
|
|
925
1416
|
if (opts.restore) {
|
|
926
|
-
if (!
|
|
927
|
-
|
|
1417
|
+
if (!existsSync4(backupPath)) {
|
|
1418
|
+
log3(" No backup found. Nothing to restore.");
|
|
928
1419
|
process.exit(1);
|
|
929
1420
|
}
|
|
930
1421
|
copyFileSync2(backupPath, entryFile);
|
|
931
|
-
|
|
932
|
-
|
|
1422
|
+
log3(` Restored original file from backup.`);
|
|
1423
|
+
log3(` Backup: ${backupPath}`);
|
|
933
1424
|
process.exit(0);
|
|
934
1425
|
}
|
|
935
1426
|
if (!opts.skipInstall && !opts.dryRun) {
|
|
936
1427
|
installSdk();
|
|
937
|
-
|
|
1428
|
+
log3("");
|
|
938
1429
|
}
|
|
939
1430
|
const result = injectTypeScript(entryFile);
|
|
940
|
-
|
|
1431
|
+
log3(` Changes (${result.changes.length}):`);
|
|
941
1432
|
for (const change of result.changes) {
|
|
942
|
-
|
|
1433
|
+
log3(` - ${change}`);
|
|
943
1434
|
}
|
|
944
1435
|
if (result.changes.length === 1 && result.changes[0].includes("Already injected")) {
|
|
945
|
-
|
|
946
|
-
|
|
1436
|
+
log3("");
|
|
1437
|
+
log3(" Your MCP server is already protected by SolonGate!");
|
|
947
1438
|
process.exit(0);
|
|
948
1439
|
}
|
|
949
1440
|
if (result.original === result.modified) {
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1441
|
+
log3("");
|
|
1442
|
+
log3(" No changes were made. The file may not contain recognizable MCP patterns.");
|
|
1443
|
+
log3(" See docs: https://solongate.com/docs/integration");
|
|
953
1444
|
process.exit(0);
|
|
954
1445
|
}
|
|
955
1446
|
if (opts.dryRun) {
|
|
956
|
-
|
|
957
|
-
|
|
1447
|
+
log3("");
|
|
1448
|
+
log3(" --- DRY RUN (no changes written) ---");
|
|
958
1449
|
showDiff(result);
|
|
959
|
-
|
|
1450
|
+
log3(" To apply: npx @solongate/proxy inject");
|
|
960
1451
|
process.exit(0);
|
|
961
1452
|
}
|
|
962
|
-
if (!
|
|
1453
|
+
if (!existsSync4(backupPath)) {
|
|
963
1454
|
copyFileSync2(entryFile, backupPath);
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1455
|
+
log3("");
|
|
1456
|
+
log3(` Backup: ${backupPath}`);
|
|
1457
|
+
}
|
|
1458
|
+
writeFileSync3(entryFile, result.modified);
|
|
1459
|
+
log3("");
|
|
1460
|
+
log3(" \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\u2510");
|
|
1461
|
+
log3(" \u2502 SolonGate SDK injected successfully! \u2502");
|
|
1462
|
+
log3(" \u2502 \u2502");
|
|
1463
|
+
log3(" \u2502 McpServer \u2192 SecureMcpServer \u2502");
|
|
1464
|
+
log3(" \u2502 All tool() calls are now auto-protected. \u2502");
|
|
1465
|
+
log3(" \u2502 \u2502");
|
|
1466
|
+
log3(" \u2502 Set your API key: \u2502");
|
|
1467
|
+
log3(" \u2502 export SOLONGATE_API_KEY=sg_live_xxx \u2502");
|
|
1468
|
+
log3(" \u2502 \u2502");
|
|
1469
|
+
log3(" \u2502 To undo: \u2502");
|
|
1470
|
+
log3(" \u2502 npx @solongate/proxy inject --restore \u2502");
|
|
1471
|
+
log3(" \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\u2518");
|
|
1472
|
+
log3("");
|
|
982
1473
|
}
|
|
983
1474
|
var c;
|
|
984
1475
|
var init_inject = __esm({
|
|
@@ -999,7 +1490,7 @@ var init_inject = __esm({
|
|
|
999
1490
|
blue6: "\x1B[38;2;170;200;250m"
|
|
1000
1491
|
};
|
|
1001
1492
|
main2().catch((err) => {
|
|
1002
|
-
|
|
1493
|
+
log3(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
1003
1494
|
process.exit(1);
|
|
1004
1495
|
});
|
|
1005
1496
|
}
|
|
@@ -1007,10 +1498,10 @@ var init_inject = __esm({
|
|
|
1007
1498
|
|
|
1008
1499
|
// src/create.ts
|
|
1009
1500
|
var create_exports = {};
|
|
1010
|
-
import { mkdirSync as mkdirSync2, writeFileSync as
|
|
1501
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync4, existsSync as existsSync5 } from "fs";
|
|
1011
1502
|
import { resolve as resolve4, join as join2 } from "path";
|
|
1012
1503
|
import { execSync as execSync2 } from "child_process";
|
|
1013
|
-
function
|
|
1504
|
+
function log4(msg) {
|
|
1014
1505
|
process.stderr.write(msg + "\n");
|
|
1015
1506
|
}
|
|
1016
1507
|
function printBanner2(subtitle) {
|
|
@@ -1023,13 +1514,13 @@ function printBanner2(subtitle) {
|
|
|
1023
1514
|
" \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"
|
|
1024
1515
|
];
|
|
1025
1516
|
const colors = [c2.blue1, c2.blue2, c2.blue3, c2.blue4, c2.blue5, c2.blue6];
|
|
1026
|
-
|
|
1517
|
+
log4("");
|
|
1027
1518
|
for (let i = 0; i < lines.length; i++) {
|
|
1028
|
-
|
|
1519
|
+
log4(`${c2.bold}${colors[i]}${lines[i]}${c2.reset}`);
|
|
1029
1520
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1521
|
+
log4("");
|
|
1522
|
+
log4(` ${c2.dim}${c2.italic}${subtitle}${c2.reset}`);
|
|
1523
|
+
log4("");
|
|
1033
1524
|
}
|
|
1034
1525
|
function withSpinner(message, fn) {
|
|
1035
1526
|
const frames = ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"];
|
|
@@ -1078,20 +1569,20 @@ function parseCreateArgs(argv) {
|
|
|
1078
1569
|
}
|
|
1079
1570
|
}
|
|
1080
1571
|
if (!opts.name) {
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1572
|
+
log4("");
|
|
1573
|
+
log4(" Error: Project name required.");
|
|
1574
|
+
log4("");
|
|
1575
|
+
log4(" Usage: npx @solongate/proxy create <name>");
|
|
1576
|
+
log4("");
|
|
1577
|
+
log4(" Examples:");
|
|
1578
|
+
log4(" npx @solongate/proxy create my-mcp-server");
|
|
1579
|
+
log4(" npx @solongate/proxy create weather-api");
|
|
1089
1580
|
process.exit(1);
|
|
1090
1581
|
}
|
|
1091
1582
|
return opts;
|
|
1092
1583
|
}
|
|
1093
1584
|
function printHelp3() {
|
|
1094
|
-
|
|
1585
|
+
log4(`
|
|
1095
1586
|
SolonGate Create \u2014 Scaffold a secure MCP server in seconds
|
|
1096
1587
|
|
|
1097
1588
|
USAGE
|
|
@@ -1108,7 +1599,7 @@ EXAMPLES
|
|
|
1108
1599
|
`);
|
|
1109
1600
|
}
|
|
1110
1601
|
function createProject(dir, name, _policy) {
|
|
1111
|
-
|
|
1602
|
+
writeFileSync4(
|
|
1112
1603
|
join2(dir, "package.json"),
|
|
1113
1604
|
JSON.stringify(
|
|
1114
1605
|
{
|
|
@@ -1138,7 +1629,7 @@ function createProject(dir, name, _policy) {
|
|
|
1138
1629
|
2
|
|
1139
1630
|
) + "\n"
|
|
1140
1631
|
);
|
|
1141
|
-
|
|
1632
|
+
writeFileSync4(
|
|
1142
1633
|
join2(dir, "tsconfig.json"),
|
|
1143
1634
|
JSON.stringify(
|
|
1144
1635
|
{
|
|
@@ -1160,7 +1651,7 @@ function createProject(dir, name, _policy) {
|
|
|
1160
1651
|
) + "\n"
|
|
1161
1652
|
);
|
|
1162
1653
|
mkdirSync2(join2(dir, "src"), { recursive: true });
|
|
1163
|
-
|
|
1654
|
+
writeFileSync4(
|
|
1164
1655
|
join2(dir, "src", "index.ts"),
|
|
1165
1656
|
`#!/usr/bin/env node
|
|
1166
1657
|
|
|
@@ -1205,7 +1696,7 @@ console.log('');
|
|
|
1205
1696
|
console.log('Press Ctrl+C to stop.');
|
|
1206
1697
|
`
|
|
1207
1698
|
);
|
|
1208
|
-
|
|
1699
|
+
writeFileSync4(
|
|
1209
1700
|
join2(dir, ".mcp.json"),
|
|
1210
1701
|
JSON.stringify(
|
|
1211
1702
|
{
|
|
@@ -1223,12 +1714,12 @@ console.log('Press Ctrl+C to stop.');
|
|
|
1223
1714
|
2
|
|
1224
1715
|
) + "\n"
|
|
1225
1716
|
);
|
|
1226
|
-
|
|
1717
|
+
writeFileSync4(
|
|
1227
1718
|
join2(dir, ".env"),
|
|
1228
1719
|
`SOLONGATE_API_KEY=sg_live_YOUR_KEY_HERE
|
|
1229
1720
|
`
|
|
1230
1721
|
);
|
|
1231
|
-
|
|
1722
|
+
writeFileSync4(
|
|
1232
1723
|
join2(dir, ".gitignore"),
|
|
1233
1724
|
`node_modules/
|
|
1234
1725
|
dist/
|
|
@@ -1243,8 +1734,8 @@ async function main3() {
|
|
|
1243
1734
|
const opts = parseCreateArgs(process.argv);
|
|
1244
1735
|
const dir = resolve4(opts.name);
|
|
1245
1736
|
printBanner2("Create MCP Server");
|
|
1246
|
-
if (
|
|
1247
|
-
|
|
1737
|
+
if (existsSync5(dir)) {
|
|
1738
|
+
log4(` ${c2.red}Error:${c2.reset} Directory "${opts.name}" already exists.`);
|
|
1248
1739
|
process.exit(1);
|
|
1249
1740
|
}
|
|
1250
1741
|
withSpinner(`Setting up ${opts.name}...`, () => {
|
|
@@ -1256,19 +1747,19 @@ async function main3() {
|
|
|
1256
1747
|
execSync2("npm install", { cwd: dir, stdio: "pipe" });
|
|
1257
1748
|
});
|
|
1258
1749
|
}
|
|
1259
|
-
|
|
1750
|
+
log4("");
|
|
1260
1751
|
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1261
1752
|
const W = 52;
|
|
1262
1753
|
const hr = "\u2500".repeat(W + 2);
|
|
1263
1754
|
const bLine = (text) => {
|
|
1264
1755
|
const visible = stripAnsi(text);
|
|
1265
1756
|
const padding = W - visible.length;
|
|
1266
|
-
|
|
1757
|
+
log4(` ${c2.dim}\u2502${c2.reset} ${text}${" ".repeat(Math.max(0, padding))} ${c2.dim}\u2502${c2.reset}`);
|
|
1267
1758
|
};
|
|
1268
|
-
const bEmpty = () =>
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1759
|
+
const bEmpty = () => log4(` ${c2.dim}\u2502${c2.reset} ${" ".repeat(W)} ${c2.dim}\u2502${c2.reset}`);
|
|
1760
|
+
log4(` ${c2.dim}\u256D${hr}\u256E${c2.reset}`);
|
|
1761
|
+
log4(` ${c2.dim}\u2502${c2.reset} ${c2.green}${c2.bold}\u2714 MCP Server created successfully!${c2.reset}${" ".repeat(W - 35)} ${c2.dim}\u2502${c2.reset}`);
|
|
1762
|
+
log4(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
|
|
1272
1763
|
bEmpty();
|
|
1273
1764
|
bLine(`${c2.yellow}Next steps:${c2.reset}`);
|
|
1274
1765
|
bEmpty();
|
|
@@ -1277,7 +1768,7 @@ async function main3() {
|
|
|
1277
1768
|
bLine(` ${c2.cyan}$${c2.reset} npm run dev ${c2.dim}# dev mode${c2.reset}`);
|
|
1278
1769
|
bLine(` ${c2.cyan}$${c2.reset} npm start ${c2.dim}# production${c2.reset}`);
|
|
1279
1770
|
bEmpty();
|
|
1280
|
-
|
|
1771
|
+
log4(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
|
|
1281
1772
|
bEmpty();
|
|
1282
1773
|
bLine(`${c2.yellow}Set your API key:${c2.reset}`);
|
|
1283
1774
|
bEmpty();
|
|
@@ -1285,14 +1776,14 @@ async function main3() {
|
|
|
1285
1776
|
bEmpty();
|
|
1286
1777
|
bLine(`${c2.dim}https://dashboard.solongate.com/api-keys/${c2.reset}`);
|
|
1287
1778
|
bEmpty();
|
|
1288
|
-
|
|
1779
|
+
log4(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
|
|
1289
1780
|
bEmpty();
|
|
1290
1781
|
bLine(`${c2.yellow}Use with OpenClaw:${c2.reset}`);
|
|
1291
1782
|
bEmpty();
|
|
1292
1783
|
bLine(` ${c2.cyan}$${c2.reset} solongate-proxy -- npx @openclaw/server`);
|
|
1293
1784
|
bLine(` ${c2.dim}Wraps OpenClaw servers with SolonGate protection${c2.reset}`);
|
|
1294
1785
|
bEmpty();
|
|
1295
|
-
|
|
1786
|
+
log4(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
|
|
1296
1787
|
bEmpty();
|
|
1297
1788
|
bLine(`${c2.yellow}Use with Claude Code / Cursor / MCP client:${c2.reset}`);
|
|
1298
1789
|
bEmpty();
|
|
@@ -1301,516 +1792,170 @@ async function main3() {
|
|
|
1301
1792
|
bLine(` ${c2.dim}3.${c2.reset} .mcp.json is auto-detected on startup`);
|
|
1302
1793
|
bEmpty();
|
|
1303
1794
|
bLine(`${c2.yellow}Direct test (without MCP client):${c2.reset}`);
|
|
1304
|
-
bEmpty();
|
|
1305
|
-
bLine(` ${c2.dim}1.${c2.reset} Replace ${c2.blue3}sg_live_YOUR_KEY_HERE${c2.reset} in .env`);
|
|
1306
|
-
bLine(` ${c2.dim}2.${c2.reset} ${c2.cyan}$${c2.reset} npm run build && npm start`);
|
|
1307
|
-
bEmpty();
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
}
|
|
1311
|
-
var c2;
|
|
1312
|
-
var init_create = __esm({
|
|
1313
|
-
"src/create.ts"() {
|
|
1314
|
-
"use strict";
|
|
1315
|
-
c2 = {
|
|
1316
|
-
reset: "\x1B[0m",
|
|
1317
|
-
bold: "\x1B[1m",
|
|
1318
|
-
dim: "\x1B[2m",
|
|
1319
|
-
italic: "\x1B[3m",
|
|
1320
|
-
white: "\x1B[97m",
|
|
1321
|
-
gray: "\x1B[90m",
|
|
1322
|
-
blue1: "\x1B[38;2;20;50;160m",
|
|
1323
|
-
blue2: "\x1B[38;2;40;80;190m",
|
|
1324
|
-
blue3: "\x1B[38;2;60;110;215m",
|
|
1325
|
-
blue4: "\x1B[38;2;90;140;230m",
|
|
1326
|
-
blue5: "\x1B[38;2;130;170;240m",
|
|
1327
|
-
blue6: "\x1B[38;2;170;200;250m",
|
|
1328
|
-
green: "\x1B[38;2;80;200;120m",
|
|
1329
|
-
red: "\x1B[38;2;220;80;80m",
|
|
1330
|
-
cyan: "\x1B[38;2;100;200;220m",
|
|
1331
|
-
yellow: "\x1B[38;2;220;200;80m",
|
|
1332
|
-
bgBlue: "\x1B[48;2;20;50;160m"
|
|
1333
|
-
};
|
|
1334
|
-
main3().catch((err) => {
|
|
1335
|
-
|
|
1336
|
-
process.exit(1);
|
|
1337
|
-
});
|
|
1338
|
-
}
|
|
1339
|
-
});
|
|
1340
|
-
|
|
1341
|
-
// src/
|
|
1342
|
-
|
|
1343
|
-
import {
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
const
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
const data = await res.json();
|
|
1354
|
-
return {
|
|
1355
|
-
id: String(data.id ?? "cloud"),
|
|
1356
|
-
name: String(data.name ?? "Cloud Policy"),
|
|
1357
|
-
version: Number(data._version ?? 1),
|
|
1358
|
-
rules: data.rules ?? [],
|
|
1359
|
-
createdAt: String(data._created_at ?? ""),
|
|
1360
|
-
updatedAt: ""
|
|
1361
|
-
};
|
|
1362
|
-
}
|
|
1363
|
-
async function sendAuditLog(apiKey, apiUrl, entry) {
|
|
1364
|
-
const url = `${apiUrl}/api/v1/audit-logs`;
|
|
1365
|
-
try {
|
|
1366
|
-
const res = await fetch(url, {
|
|
1367
|
-
method: "POST",
|
|
1368
|
-
headers: {
|
|
1369
|
-
"Authorization": `Bearer ${apiKey}`,
|
|
1370
|
-
"Content-Type": "application/json"
|
|
1371
|
-
},
|
|
1372
|
-
body: JSON.stringify(entry)
|
|
1373
|
-
});
|
|
1374
|
-
if (!res.ok) {
|
|
1375
|
-
const body = await res.text().catch(() => "");
|
|
1376
|
-
process.stderr.write(`[SolonGate] Audit log failed (${res.status}): ${body}
|
|
1377
|
-
`);
|
|
1378
|
-
}
|
|
1379
|
-
} catch (err) {
|
|
1380
|
-
process.stderr.write(`[SolonGate] Audit log error: ${err instanceof Error ? err.message : String(err)}
|
|
1381
|
-
`);
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
var PRESETS = {
|
|
1385
|
-
restricted: {
|
|
1386
|
-
id: "restricted",
|
|
1387
|
-
name: "Restricted",
|
|
1388
|
-
description: "Blocks dangerous commands (rm -rf, curl|bash, dd, etc.), allows safe tools with path restrictions",
|
|
1389
|
-
version: 1,
|
|
1390
|
-
rules: [
|
|
1391
|
-
{
|
|
1392
|
-
id: "deny-dangerous-commands",
|
|
1393
|
-
description: "Block dangerous shell commands",
|
|
1394
|
-
effect: "DENY",
|
|
1395
|
-
priority: 50,
|
|
1396
|
-
toolPattern: "*",
|
|
1397
|
-
permission: "EXECUTE",
|
|
1398
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
1399
|
-
enabled: true,
|
|
1400
|
-
commandConstraints: {
|
|
1401
|
-
denied: [
|
|
1402
|
-
"rm -rf *",
|
|
1403
|
-
"rm -r /*",
|
|
1404
|
-
"mkfs*",
|
|
1405
|
-
"dd if=*",
|
|
1406
|
-
"curl*|*bash*",
|
|
1407
|
-
"curl*|*sh*",
|
|
1408
|
-
"wget*|*bash*",
|
|
1409
|
-
"wget*|*sh*",
|
|
1410
|
-
"shutdown*",
|
|
1411
|
-
"reboot*",
|
|
1412
|
-
"kill -9*",
|
|
1413
|
-
"chmod*777*",
|
|
1414
|
-
"iptables*",
|
|
1415
|
-
"passwd*",
|
|
1416
|
-
"useradd*",
|
|
1417
|
-
"userdel*"
|
|
1418
|
-
]
|
|
1419
|
-
},
|
|
1420
|
-
createdAt: "",
|
|
1421
|
-
updatedAt: ""
|
|
1422
|
-
},
|
|
1423
|
-
{
|
|
1424
|
-
id: "deny-sensitive-paths",
|
|
1425
|
-
description: "Block access to sensitive files",
|
|
1426
|
-
effect: "DENY",
|
|
1427
|
-
priority: 51,
|
|
1428
|
-
toolPattern: "*",
|
|
1429
|
-
permission: "EXECUTE",
|
|
1430
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
1431
|
-
enabled: true,
|
|
1432
|
-
pathConstraints: {
|
|
1433
|
-
denied: [
|
|
1434
|
-
"**/.env*",
|
|
1435
|
-
"**/.ssh/**",
|
|
1436
|
-
"**/.aws/**",
|
|
1437
|
-
"**/.kube/**",
|
|
1438
|
-
"**/credentials*",
|
|
1439
|
-
"**/secrets*",
|
|
1440
|
-
"**/*.pem",
|
|
1441
|
-
"**/*.key",
|
|
1442
|
-
"/etc/passwd",
|
|
1443
|
-
"/etc/shadow",
|
|
1444
|
-
"/proc/**",
|
|
1445
|
-
"/dev/**"
|
|
1446
|
-
]
|
|
1447
|
-
},
|
|
1448
|
-
createdAt: "",
|
|
1449
|
-
updatedAt: ""
|
|
1450
|
-
},
|
|
1451
|
-
{
|
|
1452
|
-
id: "allow-rest",
|
|
1453
|
-
description: "Allow all other tool calls",
|
|
1454
|
-
effect: "ALLOW",
|
|
1455
|
-
priority: 1e3,
|
|
1456
|
-
toolPattern: "*",
|
|
1457
|
-
permission: "EXECUTE",
|
|
1458
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
1459
|
-
enabled: true,
|
|
1460
|
-
createdAt: "",
|
|
1461
|
-
updatedAt: ""
|
|
1462
|
-
}
|
|
1463
|
-
],
|
|
1464
|
-
createdAt: "",
|
|
1465
|
-
updatedAt: ""
|
|
1466
|
-
},
|
|
1467
|
-
"read-only": {
|
|
1468
|
-
id: "read-only",
|
|
1469
|
-
name: "Read Only",
|
|
1470
|
-
description: "Only allows read/list/get/search/query tools",
|
|
1471
|
-
version: 1,
|
|
1472
|
-
rules: [
|
|
1473
|
-
{
|
|
1474
|
-
id: "allow-read",
|
|
1475
|
-
description: "Allow read tools",
|
|
1476
|
-
effect: "ALLOW",
|
|
1477
|
-
priority: 100,
|
|
1478
|
-
toolPattern: "*read*",
|
|
1479
|
-
permission: "EXECUTE",
|
|
1480
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
1481
|
-
enabled: true,
|
|
1482
|
-
createdAt: "",
|
|
1483
|
-
updatedAt: ""
|
|
1484
|
-
},
|
|
1485
|
-
{
|
|
1486
|
-
id: "allow-list",
|
|
1487
|
-
description: "Allow list tools",
|
|
1488
|
-
effect: "ALLOW",
|
|
1489
|
-
priority: 101,
|
|
1490
|
-
toolPattern: "*list*",
|
|
1491
|
-
permission: "EXECUTE",
|
|
1492
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
1493
|
-
enabled: true,
|
|
1494
|
-
createdAt: "",
|
|
1495
|
-
updatedAt: ""
|
|
1496
|
-
},
|
|
1497
|
-
{
|
|
1498
|
-
id: "allow-get",
|
|
1499
|
-
description: "Allow get tools",
|
|
1500
|
-
effect: "ALLOW",
|
|
1501
|
-
priority: 102,
|
|
1502
|
-
toolPattern: "*get*",
|
|
1503
|
-
permission: "EXECUTE",
|
|
1504
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
1505
|
-
enabled: true,
|
|
1506
|
-
createdAt: "",
|
|
1507
|
-
updatedAt: ""
|
|
1508
|
-
},
|
|
1509
|
-
{
|
|
1510
|
-
id: "allow-search",
|
|
1511
|
-
description: "Allow search tools",
|
|
1512
|
-
effect: "ALLOW",
|
|
1513
|
-
priority: 103,
|
|
1514
|
-
toolPattern: "*search*",
|
|
1515
|
-
permission: "EXECUTE",
|
|
1516
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
1517
|
-
enabled: true,
|
|
1518
|
-
createdAt: "",
|
|
1519
|
-
updatedAt: ""
|
|
1520
|
-
},
|
|
1521
|
-
{
|
|
1522
|
-
id: "allow-query",
|
|
1523
|
-
description: "Allow query tools",
|
|
1524
|
-
effect: "ALLOW",
|
|
1525
|
-
priority: 104,
|
|
1526
|
-
toolPattern: "*query*",
|
|
1527
|
-
permission: "EXECUTE",
|
|
1528
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
1529
|
-
enabled: true,
|
|
1530
|
-
createdAt: "",
|
|
1531
|
-
updatedAt: ""
|
|
1532
|
-
}
|
|
1533
|
-
],
|
|
1534
|
-
createdAt: "",
|
|
1535
|
-
updatedAt: ""
|
|
1536
|
-
},
|
|
1537
|
-
sandbox: {
|
|
1538
|
-
id: "sandbox",
|
|
1539
|
-
name: "Sandbox",
|
|
1540
|
-
description: "Allows file ops within working directory only, blocks dangerous commands, allows safe shell commands",
|
|
1541
|
-
version: 1,
|
|
1542
|
-
rules: [
|
|
1543
|
-
{
|
|
1544
|
-
id: "deny-dangerous-commands",
|
|
1545
|
-
description: "Block dangerous shell commands",
|
|
1546
|
-
effect: "DENY",
|
|
1547
|
-
priority: 50,
|
|
1548
|
-
toolPattern: "*",
|
|
1549
|
-
permission: "EXECUTE",
|
|
1550
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
1551
|
-
enabled: true,
|
|
1552
|
-
commandConstraints: {
|
|
1553
|
-
denied: [
|
|
1554
|
-
"rm -rf *",
|
|
1555
|
-
"rm -r /*",
|
|
1556
|
-
"mkfs*",
|
|
1557
|
-
"dd if=*",
|
|
1558
|
-
"curl*|*bash*",
|
|
1559
|
-
"wget*|*sh*",
|
|
1560
|
-
"shutdown*",
|
|
1561
|
-
"reboot*",
|
|
1562
|
-
"chmod*777*"
|
|
1563
|
-
]
|
|
1564
|
-
},
|
|
1565
|
-
createdAt: "",
|
|
1566
|
-
updatedAt: ""
|
|
1567
|
-
},
|
|
1568
|
-
{
|
|
1569
|
-
id: "allow-safe-commands",
|
|
1570
|
-
description: "Allow safe shell commands only",
|
|
1571
|
-
effect: "ALLOW",
|
|
1572
|
-
priority: 100,
|
|
1573
|
-
toolPattern: "*shell*",
|
|
1574
|
-
permission: "EXECUTE",
|
|
1575
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
1576
|
-
enabled: true,
|
|
1577
|
-
commandConstraints: {
|
|
1578
|
-
allowed: [
|
|
1579
|
-
"ls*",
|
|
1580
|
-
"cat*",
|
|
1581
|
-
"head*",
|
|
1582
|
-
"tail*",
|
|
1583
|
-
"wc*",
|
|
1584
|
-
"grep*",
|
|
1585
|
-
"find*",
|
|
1586
|
-
"echo*",
|
|
1587
|
-
"pwd",
|
|
1588
|
-
"whoami",
|
|
1589
|
-
"date",
|
|
1590
|
-
"env",
|
|
1591
|
-
"git*",
|
|
1592
|
-
"npm*",
|
|
1593
|
-
"pnpm*",
|
|
1594
|
-
"yarn*",
|
|
1595
|
-
"node*",
|
|
1596
|
-
"python*",
|
|
1597
|
-
"pip*"
|
|
1598
|
-
]
|
|
1599
|
-
},
|
|
1600
|
-
createdAt: "",
|
|
1601
|
-
updatedAt: ""
|
|
1602
|
-
},
|
|
1603
|
-
{
|
|
1604
|
-
id: "deny-sensitive-paths",
|
|
1605
|
-
description: "Block access to sensitive files",
|
|
1606
|
-
effect: "DENY",
|
|
1607
|
-
priority: 51,
|
|
1608
|
-
toolPattern: "*",
|
|
1609
|
-
permission: "EXECUTE",
|
|
1610
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
1611
|
-
enabled: true,
|
|
1612
|
-
pathConstraints: {
|
|
1613
|
-
denied: [
|
|
1614
|
-
"**/.env*",
|
|
1615
|
-
"**/.ssh/**",
|
|
1616
|
-
"**/.aws/**",
|
|
1617
|
-
"**/credentials*",
|
|
1618
|
-
"**/*.pem",
|
|
1619
|
-
"**/*.key"
|
|
1620
|
-
]
|
|
1621
|
-
},
|
|
1622
|
-
createdAt: "",
|
|
1623
|
-
updatedAt: ""
|
|
1624
|
-
},
|
|
1625
|
-
{
|
|
1626
|
-
id: "allow-rest",
|
|
1627
|
-
description: "Allow all other tools",
|
|
1628
|
-
effect: "ALLOW",
|
|
1629
|
-
priority: 1e3,
|
|
1630
|
-
toolPattern: "*",
|
|
1631
|
-
permission: "EXECUTE",
|
|
1632
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
1633
|
-
enabled: true,
|
|
1634
|
-
createdAt: "",
|
|
1635
|
-
updatedAt: ""
|
|
1636
|
-
}
|
|
1637
|
-
],
|
|
1638
|
-
createdAt: "",
|
|
1639
|
-
updatedAt: ""
|
|
1640
|
-
},
|
|
1641
|
-
permissive: {
|
|
1642
|
-
id: "permissive",
|
|
1643
|
-
name: "Permissive",
|
|
1644
|
-
description: "Allows all tool calls (monitoring only)",
|
|
1645
|
-
version: 1,
|
|
1646
|
-
rules: [
|
|
1647
|
-
{
|
|
1648
|
-
id: "allow-all",
|
|
1649
|
-
description: "Allow all",
|
|
1650
|
-
effect: "ALLOW",
|
|
1651
|
-
priority: 1e3,
|
|
1652
|
-
toolPattern: "*",
|
|
1653
|
-
permission: "EXECUTE",
|
|
1654
|
-
minimumTrustLevel: "UNTRUSTED",
|
|
1655
|
-
enabled: true,
|
|
1656
|
-
createdAt: "",
|
|
1657
|
-
updatedAt: ""
|
|
1658
|
-
}
|
|
1659
|
-
],
|
|
1660
|
-
createdAt: "",
|
|
1661
|
-
updatedAt: ""
|
|
1662
|
-
},
|
|
1663
|
-
"deny-all": {
|
|
1664
|
-
id: "deny-all",
|
|
1665
|
-
name: "Deny All",
|
|
1666
|
-
description: "Blocks all tool calls",
|
|
1667
|
-
version: 1,
|
|
1668
|
-
rules: [],
|
|
1669
|
-
createdAt: "",
|
|
1670
|
-
updatedAt: ""
|
|
1671
|
-
}
|
|
1672
|
-
};
|
|
1673
|
-
function loadPolicy(source) {
|
|
1674
|
-
if (typeof source === "object") return source;
|
|
1675
|
-
if (PRESETS[source]) return PRESETS[source];
|
|
1676
|
-
const filePath = resolve(source);
|
|
1677
|
-
if (existsSync(filePath)) {
|
|
1678
|
-
const content = readFileSync(filePath, "utf-8");
|
|
1679
|
-
return JSON.parse(content);
|
|
1680
|
-
}
|
|
1681
|
-
throw new Error(
|
|
1682
|
-
`Unknown policy "${source}". Use a preset (${Object.keys(PRESETS).join(", ")}), a JSON file path, or a PolicySet object.`
|
|
1683
|
-
);
|
|
1684
|
-
}
|
|
1685
|
-
function parseArgs(argv) {
|
|
1686
|
-
const args = argv.slice(2);
|
|
1687
|
-
let policySource = "restricted";
|
|
1688
|
-
let name = "solongate-proxy";
|
|
1689
|
-
let verbose = false;
|
|
1690
|
-
let rateLimitPerTool;
|
|
1691
|
-
let globalRateLimit;
|
|
1692
|
-
let configFile;
|
|
1693
|
-
let apiKey;
|
|
1694
|
-
let apiUrl;
|
|
1695
|
-
let upstreamUrl;
|
|
1696
|
-
let upstreamTransport;
|
|
1697
|
-
let port;
|
|
1698
|
-
let separatorIndex = args.indexOf("--");
|
|
1699
|
-
const flags = separatorIndex >= 0 ? args.slice(0, separatorIndex) : args;
|
|
1700
|
-
const upstreamArgs = separatorIndex >= 0 ? args.slice(separatorIndex + 1) : [];
|
|
1701
|
-
for (let i = 0; i < flags.length; i++) {
|
|
1702
|
-
switch (flags[i]) {
|
|
1703
|
-
case "--policy":
|
|
1704
|
-
policySource = flags[++i];
|
|
1705
|
-
break;
|
|
1706
|
-
case "--name":
|
|
1707
|
-
name = flags[++i];
|
|
1708
|
-
break;
|
|
1709
|
-
case "--verbose":
|
|
1710
|
-
verbose = true;
|
|
1711
|
-
break;
|
|
1712
|
-
case "--rate-limit":
|
|
1713
|
-
rateLimitPerTool = parseInt(flags[++i], 10);
|
|
1714
|
-
break;
|
|
1715
|
-
case "--global-rate-limit":
|
|
1716
|
-
globalRateLimit = parseInt(flags[++i], 10);
|
|
1717
|
-
break;
|
|
1718
|
-
case "--config":
|
|
1719
|
-
configFile = flags[++i];
|
|
1720
|
-
break;
|
|
1795
|
+
bEmpty();
|
|
1796
|
+
bLine(` ${c2.dim}1.${c2.reset} Replace ${c2.blue3}sg_live_YOUR_KEY_HERE${c2.reset} in .env`);
|
|
1797
|
+
bLine(` ${c2.dim}2.${c2.reset} ${c2.cyan}$${c2.reset} npm run build && npm start`);
|
|
1798
|
+
bEmpty();
|
|
1799
|
+
log4(` ${c2.dim}\u2570${hr}\u256F${c2.reset}`);
|
|
1800
|
+
log4("");
|
|
1801
|
+
}
|
|
1802
|
+
var c2;
|
|
1803
|
+
var init_create = __esm({
|
|
1804
|
+
"src/create.ts"() {
|
|
1805
|
+
"use strict";
|
|
1806
|
+
c2 = {
|
|
1807
|
+
reset: "\x1B[0m",
|
|
1808
|
+
bold: "\x1B[1m",
|
|
1809
|
+
dim: "\x1B[2m",
|
|
1810
|
+
italic: "\x1B[3m",
|
|
1811
|
+
white: "\x1B[97m",
|
|
1812
|
+
gray: "\x1B[90m",
|
|
1813
|
+
blue1: "\x1B[38;2;20;50;160m",
|
|
1814
|
+
blue2: "\x1B[38;2;40;80;190m",
|
|
1815
|
+
blue3: "\x1B[38;2;60;110;215m",
|
|
1816
|
+
blue4: "\x1B[38;2;90;140;230m",
|
|
1817
|
+
blue5: "\x1B[38;2;130;170;240m",
|
|
1818
|
+
blue6: "\x1B[38;2;170;200;250m",
|
|
1819
|
+
green: "\x1B[38;2;80;200;120m",
|
|
1820
|
+
red: "\x1B[38;2;220;80;80m",
|
|
1821
|
+
cyan: "\x1B[38;2;100;200;220m",
|
|
1822
|
+
yellow: "\x1B[38;2;220;200;80m",
|
|
1823
|
+
bgBlue: "\x1B[48;2;20;50;160m"
|
|
1824
|
+
};
|
|
1825
|
+
main3().catch((err) => {
|
|
1826
|
+
log4(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
1827
|
+
process.exit(1);
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
});
|
|
1831
|
+
|
|
1832
|
+
// src/pull-push.ts
|
|
1833
|
+
var pull_push_exports = {};
|
|
1834
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync6 } from "fs";
|
|
1835
|
+
import { resolve as resolve5 } from "path";
|
|
1836
|
+
function parseCliArgs() {
|
|
1837
|
+
const args = process.argv.slice(2);
|
|
1838
|
+
const command = args[0];
|
|
1839
|
+
let apiKey = process.env.SOLONGATE_API_KEY || "";
|
|
1840
|
+
let file = "policy.json";
|
|
1841
|
+
let policyId;
|
|
1842
|
+
for (let i = 1; i < args.length; i++) {
|
|
1843
|
+
switch (args[i]) {
|
|
1721
1844
|
case "--api-key":
|
|
1722
|
-
apiKey =
|
|
1723
|
-
break;
|
|
1724
|
-
case "--api-url":
|
|
1725
|
-
apiUrl = flags[++i];
|
|
1726
|
-
break;
|
|
1727
|
-
case "--upstream-url":
|
|
1728
|
-
upstreamUrl = flags[++i];
|
|
1845
|
+
apiKey = args[++i];
|
|
1729
1846
|
break;
|
|
1730
|
-
case "--
|
|
1731
|
-
|
|
1847
|
+
case "--policy":
|
|
1848
|
+
case "--output":
|
|
1849
|
+
case "--file":
|
|
1850
|
+
case "-f":
|
|
1851
|
+
case "-o":
|
|
1852
|
+
file = args[++i];
|
|
1732
1853
|
break;
|
|
1733
|
-
case "--
|
|
1734
|
-
|
|
1854
|
+
case "--policy-id":
|
|
1855
|
+
case "--id":
|
|
1856
|
+
policyId = args[++i];
|
|
1735
1857
|
break;
|
|
1736
1858
|
}
|
|
1737
1859
|
}
|
|
1738
1860
|
if (!apiKey) {
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
}
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
);
|
|
1746
|
-
}
|
|
1747
|
-
}
|
|
1748
|
-
if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
|
|
1749
|
-
throw new Error(
|
|
1750
|
-
"Invalid API key format. Keys must start with 'sg_live_' or 'sg_test_'.\nGet your API key at https://solongate.com\n"
|
|
1751
|
-
);
|
|
1861
|
+
log5("ERROR: API key required.");
|
|
1862
|
+
log5("");
|
|
1863
|
+
log5("Usage:");
|
|
1864
|
+
log5(` solongate-proxy ${command} --api-key sg_live_... --file policy.json`);
|
|
1865
|
+
log5(` SOLONGATE_API_KEY=sg_live_... solongate-proxy ${command}`);
|
|
1866
|
+
process.exit(1);
|
|
1752
1867
|
}
|
|
1753
|
-
if (
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
const fileConfig = JSON.parse(content);
|
|
1757
|
-
if (!fileConfig.upstream) {
|
|
1758
|
-
throw new Error('Config file must include "upstream" with at least "command" or "url"');
|
|
1759
|
-
}
|
|
1760
|
-
return {
|
|
1761
|
-
upstream: fileConfig.upstream,
|
|
1762
|
-
policy: loadPolicy(fileConfig.policy ?? policySource),
|
|
1763
|
-
name: fileConfig.name ?? name,
|
|
1764
|
-
verbose: fileConfig.verbose ?? verbose,
|
|
1765
|
-
rateLimitPerTool: fileConfig.rateLimitPerTool ?? rateLimitPerTool,
|
|
1766
|
-
globalRateLimit: fileConfig.globalRateLimit ?? globalRateLimit,
|
|
1767
|
-
apiKey: apiKey ?? fileConfig.apiKey,
|
|
1768
|
-
apiUrl: apiUrl ?? fileConfig.apiUrl,
|
|
1769
|
-
port: port ?? fileConfig.port
|
|
1770
|
-
};
|
|
1868
|
+
if (!apiKey.startsWith("sg_live_")) {
|
|
1869
|
+
log5("ERROR: Pull/push requires a live API key (sg_live_...).");
|
|
1870
|
+
process.exit(1);
|
|
1771
1871
|
}
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1872
|
+
return { command, apiKey, file: resolve5(file), policyId };
|
|
1873
|
+
}
|
|
1874
|
+
async function pull(apiKey, file, policyId) {
|
|
1875
|
+
const apiUrl = "https://api.solongate.com";
|
|
1876
|
+
log5(`Pulling policy from dashboard...`);
|
|
1877
|
+
const policy = await fetchCloudPolicy(apiKey, apiUrl, policyId);
|
|
1878
|
+
const json = JSON.stringify(policy, null, 2) + "\n";
|
|
1879
|
+
writeFileSync5(file, json, "utf-8");
|
|
1880
|
+
log5(`Saved: ${file}`);
|
|
1881
|
+
log5(` Name: ${policy.name}`);
|
|
1882
|
+
log5(` Version: ${policy.version}`);
|
|
1883
|
+
log5(` Rules: ${policy.rules.length}`);
|
|
1884
|
+
log5("");
|
|
1885
|
+
log5("Done. Policy pulled from dashboard to local file.");
|
|
1886
|
+
}
|
|
1887
|
+
async function push(apiKey, file) {
|
|
1888
|
+
const apiUrl = "https://api.solongate.com";
|
|
1889
|
+
if (!existsSync6(file)) {
|
|
1890
|
+
log5(`ERROR: File not found: ${file}`);
|
|
1891
|
+
process.exit(1);
|
|
1790
1892
|
}
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
);
|
|
1893
|
+
const content = readFileSync5(file, "utf-8");
|
|
1894
|
+
let policy;
|
|
1895
|
+
try {
|
|
1896
|
+
policy = JSON.parse(content);
|
|
1897
|
+
} catch {
|
|
1898
|
+
log5(`ERROR: Invalid JSON in ${file}`);
|
|
1899
|
+
process.exit(1);
|
|
1795
1900
|
}
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1901
|
+
log5(`Pushing policy to dashboard...`);
|
|
1902
|
+
log5(` File: ${file}`);
|
|
1903
|
+
log5(` Name: ${policy.name || "Unnamed"}`);
|
|
1904
|
+
log5(` Rules: ${(policy.rules || []).length}`);
|
|
1905
|
+
const res = await fetch(`${apiUrl}/api/v1/policies`, {
|
|
1906
|
+
method: "POST",
|
|
1907
|
+
headers: {
|
|
1908
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
1909
|
+
"Content-Type": "application/json"
|
|
1803
1910
|
},
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1911
|
+
body: JSON.stringify({
|
|
1912
|
+
id: policy.id || "default",
|
|
1913
|
+
name: policy.name || "Local Policy",
|
|
1914
|
+
description: policy.description || "Pushed from local file",
|
|
1915
|
+
version: policy.version || 1,
|
|
1916
|
+
rules: policy.rules || []
|
|
1917
|
+
})
|
|
1918
|
+
});
|
|
1919
|
+
if (!res.ok) {
|
|
1920
|
+
const body = await res.text().catch(() => "");
|
|
1921
|
+
log5(`ERROR: Push failed (${res.status}): ${body}`);
|
|
1922
|
+
process.exit(1);
|
|
1923
|
+
}
|
|
1924
|
+
const data = await res.json();
|
|
1925
|
+
log5(` Cloud version: ${data._version ?? "created"}`);
|
|
1926
|
+
log5("");
|
|
1927
|
+
log5("Done. Policy pushed from local file to dashboard.");
|
|
1928
|
+
}
|
|
1929
|
+
async function main4() {
|
|
1930
|
+
const { command, apiKey, file, policyId } = parseCliArgs();
|
|
1931
|
+
try {
|
|
1932
|
+
if (command === "pull") {
|
|
1933
|
+
await pull(apiKey, file, policyId);
|
|
1934
|
+
} else if (command === "push") {
|
|
1935
|
+
await push(apiKey, file);
|
|
1936
|
+
} else {
|
|
1937
|
+
log5(`Unknown command: ${command}`);
|
|
1938
|
+
log5("Usage: solongate-proxy pull|push --api-key sg_live_... --file policy.json");
|
|
1939
|
+
process.exit(1);
|
|
1940
|
+
}
|
|
1941
|
+
} catch (err) {
|
|
1942
|
+
log5(`ERROR: ${err instanceof Error ? err.message : String(err)}`);
|
|
1943
|
+
process.exit(1);
|
|
1944
|
+
}
|
|
1813
1945
|
}
|
|
1946
|
+
var log5;
|
|
1947
|
+
var init_pull_push = __esm({
|
|
1948
|
+
"src/pull-push.ts"() {
|
|
1949
|
+
"use strict";
|
|
1950
|
+
init_config();
|
|
1951
|
+
log5 = (...args) => process.stderr.write(`[SolonGate] ${args.map(String).join(" ")}
|
|
1952
|
+
`);
|
|
1953
|
+
main4();
|
|
1954
|
+
}
|
|
1955
|
+
});
|
|
1956
|
+
|
|
1957
|
+
// src/index.ts
|
|
1958
|
+
init_config();
|
|
1814
1959
|
|
|
1815
1960
|
// src/proxy.ts
|
|
1816
1961
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -3348,7 +3493,202 @@ var SolonGate = class {
|
|
|
3348
3493
|
};
|
|
3349
3494
|
|
|
3350
3495
|
// src/proxy.ts
|
|
3351
|
-
|
|
3496
|
+
init_config();
|
|
3497
|
+
|
|
3498
|
+
// src/sync.ts
|
|
3499
|
+
init_config();
|
|
3500
|
+
import { readFileSync as readFileSync2, writeFileSync, watch, existsSync as existsSync2 } from "fs";
|
|
3501
|
+
var log = (...args) => process.stderr.write(`[SolonGate Sync] ${args.map(String).join(" ")}
|
|
3502
|
+
`);
|
|
3503
|
+
var PolicySyncManager = class {
|
|
3504
|
+
localPath;
|
|
3505
|
+
apiKey;
|
|
3506
|
+
apiUrl;
|
|
3507
|
+
pollIntervalMs;
|
|
3508
|
+
onPolicyUpdate;
|
|
3509
|
+
currentPolicy;
|
|
3510
|
+
localVersion;
|
|
3511
|
+
cloudVersion;
|
|
3512
|
+
skipNextWatch = false;
|
|
3513
|
+
debounceTimer = null;
|
|
3514
|
+
pollTimer = null;
|
|
3515
|
+
watcher = null;
|
|
3516
|
+
isLiveKey;
|
|
3517
|
+
constructor(opts) {
|
|
3518
|
+
this.localPath = opts.localPath;
|
|
3519
|
+
this.apiKey = opts.apiKey;
|
|
3520
|
+
this.apiUrl = opts.apiUrl;
|
|
3521
|
+
this.pollIntervalMs = opts.pollIntervalMs ?? 6e4;
|
|
3522
|
+
this.onPolicyUpdate = opts.onPolicyUpdate;
|
|
3523
|
+
this.currentPolicy = opts.initialPolicy;
|
|
3524
|
+
this.localVersion = opts.initialPolicy.version ?? 0;
|
|
3525
|
+
this.cloudVersion = 0;
|
|
3526
|
+
this.isLiveKey = opts.apiKey.startsWith("sg_live_");
|
|
3527
|
+
}
|
|
3528
|
+
/**
|
|
3529
|
+
* Start watching local file and polling cloud.
|
|
3530
|
+
*/
|
|
3531
|
+
start() {
|
|
3532
|
+
if (this.localPath && existsSync2(this.localPath)) {
|
|
3533
|
+
this.startFileWatcher();
|
|
3534
|
+
}
|
|
3535
|
+
if (this.isLiveKey) {
|
|
3536
|
+
this.pushToCloud(this.currentPolicy).catch(() => {
|
|
3537
|
+
});
|
|
3538
|
+
this.startPolling();
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
/**
|
|
3542
|
+
* Stop all watchers and timers.
|
|
3543
|
+
*/
|
|
3544
|
+
stop() {
|
|
3545
|
+
if (this.watcher) {
|
|
3546
|
+
this.watcher.close();
|
|
3547
|
+
this.watcher = null;
|
|
3548
|
+
}
|
|
3549
|
+
if (this.pollTimer) {
|
|
3550
|
+
clearInterval(this.pollTimer);
|
|
3551
|
+
this.pollTimer = null;
|
|
3552
|
+
}
|
|
3553
|
+
if (this.debounceTimer) {
|
|
3554
|
+
clearTimeout(this.debounceTimer);
|
|
3555
|
+
this.debounceTimer = null;
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
/**
|
|
3559
|
+
* Watch local file for changes (debounced).
|
|
3560
|
+
*/
|
|
3561
|
+
startFileWatcher() {
|
|
3562
|
+
if (!this.localPath) return;
|
|
3563
|
+
const filePath = this.localPath;
|
|
3564
|
+
try {
|
|
3565
|
+
this.watcher = watch(filePath, () => {
|
|
3566
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
3567
|
+
this.debounceTimer = setTimeout(() => this.onFileChange(filePath), 300);
|
|
3568
|
+
});
|
|
3569
|
+
log(`Watching ${filePath} for changes`);
|
|
3570
|
+
} catch (err) {
|
|
3571
|
+
log(`File watch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
/**
|
|
3575
|
+
* Handle local file change event.
|
|
3576
|
+
*/
|
|
3577
|
+
async onFileChange(filePath) {
|
|
3578
|
+
if (this.skipNextWatch) {
|
|
3579
|
+
this.skipNextWatch = false;
|
|
3580
|
+
return;
|
|
3581
|
+
}
|
|
3582
|
+
try {
|
|
3583
|
+
if (!existsSync2(filePath)) {
|
|
3584
|
+
log("Policy file deleted \u2014 keeping current policy");
|
|
3585
|
+
return;
|
|
3586
|
+
}
|
|
3587
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
3588
|
+
const newPolicy = JSON.parse(content);
|
|
3589
|
+
if (newPolicy.version <= this.localVersion) {
|
|
3590
|
+
newPolicy.version = Math.max(this.localVersion, this.cloudVersion) + 1;
|
|
3591
|
+
this.writeToFile(newPolicy);
|
|
3592
|
+
}
|
|
3593
|
+
if (this.policiesEqual(newPolicy, this.currentPolicy)) return;
|
|
3594
|
+
log(`File changed: ${newPolicy.name} v${newPolicy.version}`);
|
|
3595
|
+
this.localVersion = newPolicy.version;
|
|
3596
|
+
this.currentPolicy = newPolicy;
|
|
3597
|
+
this.onPolicyUpdate(newPolicy);
|
|
3598
|
+
if (this.isLiveKey) {
|
|
3599
|
+
try {
|
|
3600
|
+
const result = await this.pushToCloud(newPolicy);
|
|
3601
|
+
this.cloudVersion = result.version;
|
|
3602
|
+
log(`Pushed to cloud: v${result.version}`);
|
|
3603
|
+
} catch (err) {
|
|
3604
|
+
log(`Cloud push failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3607
|
+
} catch (err) {
|
|
3608
|
+
log(`File read error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3609
|
+
}
|
|
3610
|
+
}
|
|
3611
|
+
/**
|
|
3612
|
+
* Poll cloud for policy changes.
|
|
3613
|
+
*/
|
|
3614
|
+
startPolling() {
|
|
3615
|
+
this.pollTimer = setInterval(() => this.onPollTick(), this.pollIntervalMs);
|
|
3616
|
+
}
|
|
3617
|
+
/**
|
|
3618
|
+
* Handle poll tick — fetch cloud policy and compare.
|
|
3619
|
+
*/
|
|
3620
|
+
async onPollTick() {
|
|
3621
|
+
try {
|
|
3622
|
+
const cloudPolicy = await fetchCloudPolicy(this.apiKey, this.apiUrl);
|
|
3623
|
+
const cloudVer = cloudPolicy.version ?? 0;
|
|
3624
|
+
if (cloudVer <= this.localVersion && this.policiesEqual(cloudPolicy, this.currentPolicy)) {
|
|
3625
|
+
return;
|
|
3626
|
+
}
|
|
3627
|
+
if (cloudVer > this.localVersion || !this.policiesEqual(cloudPolicy, this.currentPolicy)) {
|
|
3628
|
+
log(`Cloud update: ${cloudPolicy.name} v${cloudVer} (was v${this.localVersion})`);
|
|
3629
|
+
this.cloudVersion = cloudVer;
|
|
3630
|
+
this.localVersion = cloudVer;
|
|
3631
|
+
this.currentPolicy = cloudPolicy;
|
|
3632
|
+
this.onPolicyUpdate(cloudPolicy);
|
|
3633
|
+
if (this.localPath) {
|
|
3634
|
+
this.writeToFile(cloudPolicy);
|
|
3635
|
+
log(`Updated local file: ${this.localPath}`);
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
} catch {
|
|
3639
|
+
}
|
|
3640
|
+
}
|
|
3641
|
+
/**
|
|
3642
|
+
* Push policy to cloud API.
|
|
3643
|
+
*/
|
|
3644
|
+
async pushToCloud(policy) {
|
|
3645
|
+
const url = `${this.apiUrl}/api/v1/policies`;
|
|
3646
|
+
const res = await fetch(url, {
|
|
3647
|
+
method: "POST",
|
|
3648
|
+
headers: {
|
|
3649
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
3650
|
+
"Content-Type": "application/json"
|
|
3651
|
+
},
|
|
3652
|
+
body: JSON.stringify({
|
|
3653
|
+
id: policy.id || "default",
|
|
3654
|
+
name: policy.name || "Default Policy",
|
|
3655
|
+
description: policy.description || "Synced from proxy",
|
|
3656
|
+
version: policy.version || 1,
|
|
3657
|
+
rules: policy.rules
|
|
3658
|
+
})
|
|
3659
|
+
});
|
|
3660
|
+
if (!res.ok) {
|
|
3661
|
+
const body = await res.text().catch(() => "");
|
|
3662
|
+
throw new Error(`Push failed (${res.status}): ${body}`);
|
|
3663
|
+
}
|
|
3664
|
+
const data = await res.json();
|
|
3665
|
+
return { version: Number(data._version ?? policy.version) };
|
|
3666
|
+
}
|
|
3667
|
+
/**
|
|
3668
|
+
* Write policy to local file (with loop prevention).
|
|
3669
|
+
*/
|
|
3670
|
+
writeToFile(policy) {
|
|
3671
|
+
if (!this.localPath) return;
|
|
3672
|
+
this.skipNextWatch = true;
|
|
3673
|
+
try {
|
|
3674
|
+
const json = JSON.stringify(policy, null, 2) + "\n";
|
|
3675
|
+
writeFileSync(this.localPath, json, "utf-8");
|
|
3676
|
+
} catch (err) {
|
|
3677
|
+
log(`File write error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3678
|
+
this.skipNextWatch = false;
|
|
3679
|
+
}
|
|
3680
|
+
}
|
|
3681
|
+
/**
|
|
3682
|
+
* Compare two policies by rules content (ignoring timestamps).
|
|
3683
|
+
*/
|
|
3684
|
+
policiesEqual(a, b) {
|
|
3685
|
+
if (a.id !== b.id || a.name !== b.name || a.rules.length !== b.rules.length) return false;
|
|
3686
|
+
return JSON.stringify(a.rules) === JSON.stringify(b.rules);
|
|
3687
|
+
}
|
|
3688
|
+
};
|
|
3689
|
+
|
|
3690
|
+
// src/proxy.ts
|
|
3691
|
+
var log2 = (...args) => process.stderr.write(`[SolonGate] ${args.map(String).join(" ")}
|
|
3352
3692
|
`);
|
|
3353
3693
|
var Mutex = class {
|
|
3354
3694
|
queue = [];
|
|
@@ -3358,8 +3698,8 @@ var Mutex = class {
|
|
|
3358
3698
|
this.locked = true;
|
|
3359
3699
|
return;
|
|
3360
3700
|
}
|
|
3361
|
-
return new Promise((
|
|
3362
|
-
this.queue.push(
|
|
3701
|
+
return new Promise((resolve6) => {
|
|
3702
|
+
this.queue.push(resolve6);
|
|
3363
3703
|
});
|
|
3364
3704
|
}
|
|
3365
3705
|
release() {
|
|
@@ -3377,6 +3717,7 @@ var SolonGateProxy = class {
|
|
|
3377
3717
|
client = null;
|
|
3378
3718
|
server = null;
|
|
3379
3719
|
callMutex = new Mutex();
|
|
3720
|
+
syncManager = null;
|
|
3380
3721
|
upstreamTools = [];
|
|
3381
3722
|
constructor(config) {
|
|
3382
3723
|
this.config = config;
|
|
@@ -3393,20 +3734,20 @@ var SolonGateProxy = class {
|
|
|
3393
3734
|
});
|
|
3394
3735
|
const warnings = this.gate.getWarnings();
|
|
3395
3736
|
for (const w of warnings) {
|
|
3396
|
-
|
|
3737
|
+
log2("WARNING:", w);
|
|
3397
3738
|
}
|
|
3398
3739
|
}
|
|
3399
3740
|
/**
|
|
3400
3741
|
* Start the proxy: connect to upstream, then serve downstream.
|
|
3401
3742
|
*/
|
|
3402
3743
|
async start() {
|
|
3403
|
-
|
|
3744
|
+
log2("Starting SolonGate Proxy...");
|
|
3404
3745
|
const apiUrl = this.config.apiUrl ?? "https://api.solongate.com";
|
|
3405
3746
|
if (this.config.apiKey) {
|
|
3406
3747
|
if (this.config.apiKey.startsWith("sg_test_")) {
|
|
3407
|
-
|
|
3748
|
+
log2("Using test API key \u2014 skipping online validation.");
|
|
3408
3749
|
} else {
|
|
3409
|
-
|
|
3750
|
+
log2(`Validating license with ${apiUrl}...`);
|
|
3410
3751
|
try {
|
|
3411
3752
|
const res = await fetch(`${apiUrl}/api/v1/auth/me`, {
|
|
3412
3753
|
headers: {
|
|
@@ -3416,17 +3757,17 @@ var SolonGateProxy = class {
|
|
|
3416
3757
|
signal: AbortSignal.timeout(1e4)
|
|
3417
3758
|
});
|
|
3418
3759
|
if (res.status === 401) {
|
|
3419
|
-
|
|
3760
|
+
log2("ERROR: Invalid or expired API key.");
|
|
3420
3761
|
process.exit(1);
|
|
3421
3762
|
}
|
|
3422
3763
|
if (res.status === 403) {
|
|
3423
|
-
|
|
3764
|
+
log2("ERROR: Your subscription is inactive. Renew at https://solongate.com");
|
|
3424
3765
|
process.exit(1);
|
|
3425
3766
|
}
|
|
3426
|
-
|
|
3767
|
+
log2("License validated.");
|
|
3427
3768
|
} catch (err) {
|
|
3428
|
-
|
|
3429
|
-
|
|
3769
|
+
log2(`ERROR: Unable to reach SolonGate license server. Check your internet connection.`);
|
|
3770
|
+
log2(`Details: ${err instanceof Error ? err.message : String(err)}`);
|
|
3430
3771
|
process.exit(1);
|
|
3431
3772
|
}
|
|
3432
3773
|
}
|
|
@@ -3434,28 +3775,27 @@ var SolonGateProxy = class {
|
|
|
3434
3775
|
try {
|
|
3435
3776
|
const cloudPolicy = await fetchCloudPolicy(this.config.apiKey, apiUrl);
|
|
3436
3777
|
this.config.policy = cloudPolicy;
|
|
3437
|
-
|
|
3778
|
+
log2(`Loaded cloud policy: ${cloudPolicy.name} (${cloudPolicy.rules.length} rules)`);
|
|
3438
3779
|
} catch (err) {
|
|
3439
|
-
|
|
3780
|
+
log2(`Cloud policy fetch failed, using local policy: ${err instanceof Error ? err.message : String(err)}`);
|
|
3440
3781
|
}
|
|
3441
3782
|
}
|
|
3442
3783
|
}
|
|
3443
3784
|
this.gate.loadPolicy(this.config.policy);
|
|
3444
|
-
|
|
3785
|
+
log2(`Policy: ${this.config.policy.name} (${this.config.policy.rules.length} rules)`);
|
|
3445
3786
|
const transport = this.config.upstream.transport ?? "stdio";
|
|
3446
3787
|
if (transport === "stdio") {
|
|
3447
|
-
|
|
3788
|
+
log2(`Upstream: [stdio] ${this.config.upstream.command} ${(this.config.upstream.args ?? []).join(" ")}`);
|
|
3448
3789
|
} else {
|
|
3449
|
-
|
|
3790
|
+
log2(`Upstream: [${transport}] ${this.config.upstream.url}`);
|
|
3450
3791
|
}
|
|
3451
3792
|
await this.connectUpstream();
|
|
3452
3793
|
await this.discoverTools();
|
|
3453
3794
|
this.registerToolsToCloud();
|
|
3454
3795
|
this.registerServerToCloud();
|
|
3455
|
-
this.
|
|
3796
|
+
this.startPolicySync();
|
|
3456
3797
|
this.createServer();
|
|
3457
3798
|
await this.serve();
|
|
3458
|
-
this.startPolicyPolling();
|
|
3459
3799
|
}
|
|
3460
3800
|
/**
|
|
3461
3801
|
* Connect to the upstream MCP server.
|
|
@@ -3493,7 +3833,7 @@ var SolonGateProxy = class {
|
|
|
3493
3833
|
break;
|
|
3494
3834
|
}
|
|
3495
3835
|
}
|
|
3496
|
-
|
|
3836
|
+
log2(`Connected to upstream server (${upstreamTransport})`);
|
|
3497
3837
|
}
|
|
3498
3838
|
/**
|
|
3499
3839
|
* Discover tools from the upstream server.
|
|
@@ -3506,9 +3846,9 @@ var SolonGateProxy = class {
|
|
|
3506
3846
|
description: t.description,
|
|
3507
3847
|
inputSchema: t.inputSchema
|
|
3508
3848
|
}));
|
|
3509
|
-
|
|
3849
|
+
log2(`Discovered ${this.upstreamTools.length} tools from upstream:`);
|
|
3510
3850
|
for (const tool of this.upstreamTools) {
|
|
3511
|
-
|
|
3851
|
+
log2(` - ${tool.name}: ${tool.description ?? "(no description)"}`);
|
|
3512
3852
|
}
|
|
3513
3853
|
}
|
|
3514
3854
|
/**
|
|
@@ -3537,13 +3877,13 @@ var SolonGateProxy = class {
|
|
|
3537
3877
|
const { name, arguments: args } = request.params;
|
|
3538
3878
|
const argsSize = JSON.stringify(args ?? {}).length;
|
|
3539
3879
|
if (argsSize > MAX_ARGUMENT_SIZE) {
|
|
3540
|
-
|
|
3880
|
+
log2(`DENY: ${name} \u2014 payload size ${argsSize} exceeds limit ${MAX_ARGUMENT_SIZE}`);
|
|
3541
3881
|
return {
|
|
3542
3882
|
content: [{ type: "text", text: `Request payload too large (${Math.round(argsSize / 1024)}KB > ${Math.round(MAX_ARGUMENT_SIZE / 1024)}KB limit)` }],
|
|
3543
3883
|
isError: true
|
|
3544
3884
|
};
|
|
3545
3885
|
}
|
|
3546
|
-
|
|
3886
|
+
log2(`Tool call: ${name}`);
|
|
3547
3887
|
await this.callMutex.acquire();
|
|
3548
3888
|
const startTime = Date.now();
|
|
3549
3889
|
try {
|
|
@@ -3560,10 +3900,10 @@ var SolonGateProxy = class {
|
|
|
3560
3900
|
);
|
|
3561
3901
|
const decision = result.isError ? "DENY" : "ALLOW";
|
|
3562
3902
|
const evaluationTimeMs = Date.now() - startTime;
|
|
3563
|
-
|
|
3903
|
+
log2(`Result: ${decision} (${evaluationTimeMs}ms)`);
|
|
3564
3904
|
if (this.config.apiKey && !this.config.apiKey.startsWith("sg_test_")) {
|
|
3565
3905
|
const apiUrl = this.config.apiUrl ?? "https://api.solongate.com";
|
|
3566
|
-
|
|
3906
|
+
log2(`Sending audit log: ${name} \u2192 ${decision} (key: ${this.config.apiKey.slice(0, 16)}...)`);
|
|
3567
3907
|
let reason = "allowed";
|
|
3568
3908
|
let matchedRule;
|
|
3569
3909
|
if (result.isError) {
|
|
@@ -3586,7 +3926,7 @@ var SolonGateProxy = class {
|
|
|
3586
3926
|
evaluationTimeMs
|
|
3587
3927
|
});
|
|
3588
3928
|
} else {
|
|
3589
|
-
|
|
3929
|
+
log2(`Skipping audit log (apiKey: ${this.config.apiKey ? "test key" : "not set"})`);
|
|
3590
3930
|
}
|
|
3591
3931
|
return {
|
|
3592
3932
|
content: [...result.content],
|
|
@@ -3660,13 +4000,13 @@ var SolonGateProxy = class {
|
|
|
3660
4000
|
registered++;
|
|
3661
4001
|
} else {
|
|
3662
4002
|
const body = await res.text().catch(() => "");
|
|
3663
|
-
|
|
4003
|
+
log2(`Tool registration failed for "${tool.name}" (${res.status}): ${body}`);
|
|
3664
4004
|
}
|
|
3665
4005
|
}).catch((err) => {
|
|
3666
|
-
|
|
4006
|
+
log2(`Tool registration error for "${tool.name}": ${err instanceof Error ? err.message : String(err)}`);
|
|
3667
4007
|
});
|
|
3668
4008
|
}
|
|
3669
|
-
|
|
4009
|
+
log2(`Registering ${total} tools to dashboard...`);
|
|
3670
4010
|
}
|
|
3671
4011
|
/**
|
|
3672
4012
|
* Guess tool permissions from tool name.
|
|
@@ -3720,68 +4060,42 @@ var SolonGateProxy = class {
|
|
|
3720
4060
|
})
|
|
3721
4061
|
}).then(async (res) => {
|
|
3722
4062
|
if (res.ok) {
|
|
3723
|
-
|
|
4063
|
+
log2(`Registered MCP server "${serverName}" to dashboard.`);
|
|
3724
4064
|
} else if (res.status === 409) {
|
|
3725
|
-
|
|
4065
|
+
log2(`MCP server "${serverName}" already registered.`);
|
|
3726
4066
|
} else {
|
|
3727
4067
|
const body = await res.text().catch(() => "");
|
|
3728
|
-
|
|
4068
|
+
log2(`MCP server registration failed (${res.status}): ${body}`);
|
|
3729
4069
|
}
|
|
3730
4070
|
}).catch((err) => {
|
|
3731
|
-
|
|
4071
|
+
log2(`MCP server registration error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3732
4072
|
});
|
|
3733
4073
|
}
|
|
3734
4074
|
/**
|
|
3735
|
-
*
|
|
3736
|
-
*
|
|
4075
|
+
* Start bidirectional policy sync between local JSON file and cloud dashboard.
|
|
4076
|
+
*
|
|
4077
|
+
* - Watches local policy.json for changes → pushes to cloud API
|
|
4078
|
+
* - Polls cloud API for dashboard changes → writes to local policy.json
|
|
4079
|
+
* - Version number determines which is newer (higher wins, cloud wins on tie)
|
|
3737
4080
|
*/
|
|
3738
|
-
|
|
3739
|
-
|
|
4081
|
+
startPolicySync() {
|
|
4082
|
+
const apiKey = this.config.apiKey;
|
|
4083
|
+
if (!apiKey) return;
|
|
3740
4084
|
const apiUrl = this.config.apiUrl ?? "https://api.solongate.com";
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
description: policy.description || `Policy synced from proxy`,
|
|
3752
|
-
version: policy.version || 1,
|
|
3753
|
-
rules: policy.rules
|
|
3754
|
-
})
|
|
3755
|
-
}).then(async (res) => {
|
|
3756
|
-
if (res.ok) {
|
|
3757
|
-
log(`Synced policy "${policy.name}" to dashboard.`);
|
|
3758
|
-
} else {
|
|
3759
|
-
const body = await res.text().catch(() => "");
|
|
3760
|
-
log(`Policy sync failed (${res.status}): ${body}`);
|
|
4085
|
+
this.syncManager = new PolicySyncManager({
|
|
4086
|
+
localPath: this.config.policyPath ?? null,
|
|
4087
|
+
apiKey,
|
|
4088
|
+
apiUrl,
|
|
4089
|
+
pollIntervalMs: 6e4,
|
|
4090
|
+
initialPolicy: this.config.policy,
|
|
4091
|
+
onPolicyUpdate: (policy) => {
|
|
4092
|
+
this.config.policy = policy;
|
|
4093
|
+
this.gate.loadPolicy(policy);
|
|
4094
|
+
log2(`Policy hot-reloaded: ${policy.name} v${policy.version} (${policy.rules.length} rules)`);
|
|
3761
4095
|
}
|
|
3762
|
-
}).catch((err) => {
|
|
3763
|
-
log(`Policy sync error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3764
4096
|
});
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
* Poll for policy updates from dashboard every 60 seconds.
|
|
3768
|
-
* When user changes policy in dashboard, proxy picks it up automatically.
|
|
3769
|
-
*/
|
|
3770
|
-
startPolicyPolling() {
|
|
3771
|
-
if (!this.config.apiKey || this.config.apiKey.startsWith("sg_test_")) return;
|
|
3772
|
-
const apiUrl = this.config.apiUrl ?? "https://api.solongate.com";
|
|
3773
|
-
const POLL_INTERVAL = 6e4;
|
|
3774
|
-
setInterval(async () => {
|
|
3775
|
-
try {
|
|
3776
|
-
const newPolicy = await fetchCloudPolicy(this.config.apiKey, apiUrl);
|
|
3777
|
-
if (newPolicy.version !== this.config.policy.version || newPolicy.rules.length !== this.config.policy.rules.length) {
|
|
3778
|
-
this.config.policy = newPolicy;
|
|
3779
|
-
this.gate.loadPolicy(newPolicy);
|
|
3780
|
-
log(`Policy updated from dashboard: ${newPolicy.name} v${newPolicy.version} (${newPolicy.rules.length} rules)`);
|
|
3781
|
-
}
|
|
3782
|
-
} catch {
|
|
3783
|
-
}
|
|
3784
|
-
}, POLL_INTERVAL);
|
|
4097
|
+
this.syncManager.start();
|
|
4098
|
+
log2("Bidirectional policy sync started.");
|
|
3785
4099
|
}
|
|
3786
4100
|
/**
|
|
3787
4101
|
* Start serving downstream.
|
|
@@ -3807,14 +4121,14 @@ var SolonGateProxy = class {
|
|
|
3807
4121
|
}
|
|
3808
4122
|
});
|
|
3809
4123
|
httpServer.listen(this.config.port, () => {
|
|
3810
|
-
|
|
3811
|
-
|
|
4124
|
+
log2(`Proxy is live on http://localhost:${this.config.port}/mcp`);
|
|
4125
|
+
log2("All tool calls are now protected by SolonGate.");
|
|
3812
4126
|
});
|
|
3813
4127
|
} else {
|
|
3814
4128
|
const transport = new StdioServerTransport();
|
|
3815
4129
|
await this.server.connect(transport);
|
|
3816
|
-
|
|
3817
|
-
|
|
4130
|
+
log2("Proxy is live. All tool calls are now protected by SolonGate.");
|
|
4131
|
+
log2("Waiting for requests...");
|
|
3818
4132
|
}
|
|
3819
4133
|
}
|
|
3820
4134
|
};
|
|
@@ -3832,7 +4146,7 @@ console.error = (...args) => {
|
|
|
3832
4146
|
process.stderr.write(`[SolonGate ERROR] ${args.map(String).join(" ")}
|
|
3833
4147
|
`);
|
|
3834
4148
|
};
|
|
3835
|
-
async function
|
|
4149
|
+
async function main5() {
|
|
3836
4150
|
const subcommand = process.argv[2];
|
|
3837
4151
|
if (subcommand === "init") {
|
|
3838
4152
|
process.argv.splice(2, 1);
|
|
@@ -3849,6 +4163,10 @@ async function main4() {
|
|
|
3849
4163
|
await Promise.resolve().then(() => (init_create(), create_exports));
|
|
3850
4164
|
return;
|
|
3851
4165
|
}
|
|
4166
|
+
if (subcommand === "pull" || subcommand === "push") {
|
|
4167
|
+
await Promise.resolve().then(() => (init_pull_push(), pull_push_exports));
|
|
4168
|
+
return;
|
|
4169
|
+
}
|
|
3852
4170
|
try {
|
|
3853
4171
|
const config = parseArgs(process.argv);
|
|
3854
4172
|
const proxy = new SolonGateProxy(config);
|
|
@@ -3860,4 +4178,4 @@ async function main4() {
|
|
|
3860
4178
|
process.exit(1);
|
|
3861
4179
|
}
|
|
3862
4180
|
}
|
|
3863
|
-
|
|
4181
|
+
main5();
|