@solongate/proxy 0.3.0 → 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 +1054 -703
- 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();
|
|
@@ -1305,8 +1796,8 @@ async function main3() {
|
|
|
1305
1796
|
bLine(` ${c2.dim}1.${c2.reset} Replace ${c2.blue3}sg_live_YOUR_KEY_HERE${c2.reset} in .env`);
|
|
1306
1797
|
bLine(` ${c2.dim}2.${c2.reset} ${c2.cyan}$${c2.reset} npm run build && npm start`);
|
|
1307
1798
|
bEmpty();
|
|
1308
|
-
|
|
1309
|
-
|
|
1799
|
+
log4(` ${c2.dim}\u2570${hr}\u256F${c2.reset}`);
|
|
1800
|
+
log4("");
|
|
1310
1801
|
}
|
|
1311
1802
|
var c2;
|
|
1312
1803
|
var init_create = __esm({
|
|
@@ -1323,494 +1814,148 @@ var init_create = __esm({
|
|
|
1323
1814
|
blue2: "\x1B[38;2;40;80;190m",
|
|
1324
1815
|
blue3: "\x1B[38;2;60;110;215m",
|
|
1325
1816
|
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;
|
|
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 =
|
|
1845
|
+
apiKey = args[++i];
|
|
1723
1846
|
break;
|
|
1724
|
-
case "--
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
case "
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
case "--upstream-transport":
|
|
1731
|
-
upstreamTransport = flags[++i];
|
|
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";
|
|
@@ -2037,6 +2182,14 @@ function matchParts(pathParts, pi, patternParts, qi) {
|
|
|
2037
2182
|
qi++;
|
|
2038
2183
|
continue;
|
|
2039
2184
|
}
|
|
2185
|
+
if (pattern.includes("*")) {
|
|
2186
|
+
if (!matchSegmentGlob(pathParts[pi], pattern)) {
|
|
2187
|
+
return false;
|
|
2188
|
+
}
|
|
2189
|
+
pi++;
|
|
2190
|
+
qi++;
|
|
2191
|
+
continue;
|
|
2192
|
+
}
|
|
2040
2193
|
if (pattern !== pathParts[pi]) {
|
|
2041
2194
|
return false;
|
|
2042
2195
|
}
|
|
@@ -2073,6 +2226,31 @@ function isPathAllowed(path, constraints) {
|
|
|
2073
2226
|
}
|
|
2074
2227
|
return true;
|
|
2075
2228
|
}
|
|
2229
|
+
function matchSegmentGlob(segment, pattern) {
|
|
2230
|
+
const startsWithStar = pattern.startsWith("*");
|
|
2231
|
+
const endsWithStar = pattern.endsWith("*");
|
|
2232
|
+
if (pattern === "*") return true;
|
|
2233
|
+
if (startsWithStar && endsWithStar) {
|
|
2234
|
+
const infix = pattern.slice(1, -1);
|
|
2235
|
+
return segment.toLowerCase().includes(infix.toLowerCase());
|
|
2236
|
+
}
|
|
2237
|
+
if (startsWithStar) {
|
|
2238
|
+
const suffix = pattern.slice(1);
|
|
2239
|
+
return segment.toLowerCase().endsWith(suffix.toLowerCase());
|
|
2240
|
+
}
|
|
2241
|
+
if (endsWithStar) {
|
|
2242
|
+
const prefix = pattern.slice(0, -1);
|
|
2243
|
+
return segment.toLowerCase().startsWith(prefix.toLowerCase());
|
|
2244
|
+
}
|
|
2245
|
+
const starIdx = pattern.indexOf("*");
|
|
2246
|
+
if (starIdx !== -1) {
|
|
2247
|
+
const prefix = pattern.slice(0, starIdx);
|
|
2248
|
+
const suffix = pattern.slice(starIdx + 1);
|
|
2249
|
+
const seg = segment.toLowerCase();
|
|
2250
|
+
return seg.startsWith(prefix.toLowerCase()) && seg.endsWith(suffix.toLowerCase()) && seg.length >= prefix.length + suffix.length;
|
|
2251
|
+
}
|
|
2252
|
+
return segment === pattern;
|
|
2253
|
+
}
|
|
2076
2254
|
function extractPathArguments(args) {
|
|
2077
2255
|
const paths = [];
|
|
2078
2256
|
for (const value of Object.values(args)) {
|
|
@@ -3315,7 +3493,202 @@ var SolonGate = class {
|
|
|
3315
3493
|
};
|
|
3316
3494
|
|
|
3317
3495
|
// src/proxy.ts
|
|
3318
|
-
|
|
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(" ")}
|
|
3319
3692
|
`);
|
|
3320
3693
|
var Mutex = class {
|
|
3321
3694
|
queue = [];
|
|
@@ -3325,8 +3698,8 @@ var Mutex = class {
|
|
|
3325
3698
|
this.locked = true;
|
|
3326
3699
|
return;
|
|
3327
3700
|
}
|
|
3328
|
-
return new Promise((
|
|
3329
|
-
this.queue.push(
|
|
3701
|
+
return new Promise((resolve6) => {
|
|
3702
|
+
this.queue.push(resolve6);
|
|
3330
3703
|
});
|
|
3331
3704
|
}
|
|
3332
3705
|
release() {
|
|
@@ -3344,6 +3717,7 @@ var SolonGateProxy = class {
|
|
|
3344
3717
|
client = null;
|
|
3345
3718
|
server = null;
|
|
3346
3719
|
callMutex = new Mutex();
|
|
3720
|
+
syncManager = null;
|
|
3347
3721
|
upstreamTools = [];
|
|
3348
3722
|
constructor(config) {
|
|
3349
3723
|
this.config = config;
|
|
@@ -3360,20 +3734,20 @@ var SolonGateProxy = class {
|
|
|
3360
3734
|
});
|
|
3361
3735
|
const warnings = this.gate.getWarnings();
|
|
3362
3736
|
for (const w of warnings) {
|
|
3363
|
-
|
|
3737
|
+
log2("WARNING:", w);
|
|
3364
3738
|
}
|
|
3365
3739
|
}
|
|
3366
3740
|
/**
|
|
3367
3741
|
* Start the proxy: connect to upstream, then serve downstream.
|
|
3368
3742
|
*/
|
|
3369
3743
|
async start() {
|
|
3370
|
-
|
|
3744
|
+
log2("Starting SolonGate Proxy...");
|
|
3371
3745
|
const apiUrl = this.config.apiUrl ?? "https://api.solongate.com";
|
|
3372
3746
|
if (this.config.apiKey) {
|
|
3373
3747
|
if (this.config.apiKey.startsWith("sg_test_")) {
|
|
3374
|
-
|
|
3748
|
+
log2("Using test API key \u2014 skipping online validation.");
|
|
3375
3749
|
} else {
|
|
3376
|
-
|
|
3750
|
+
log2(`Validating license with ${apiUrl}...`);
|
|
3377
3751
|
try {
|
|
3378
3752
|
const res = await fetch(`${apiUrl}/api/v1/auth/me`, {
|
|
3379
3753
|
headers: {
|
|
@@ -3383,17 +3757,17 @@ var SolonGateProxy = class {
|
|
|
3383
3757
|
signal: AbortSignal.timeout(1e4)
|
|
3384
3758
|
});
|
|
3385
3759
|
if (res.status === 401) {
|
|
3386
|
-
|
|
3760
|
+
log2("ERROR: Invalid or expired API key.");
|
|
3387
3761
|
process.exit(1);
|
|
3388
3762
|
}
|
|
3389
3763
|
if (res.status === 403) {
|
|
3390
|
-
|
|
3764
|
+
log2("ERROR: Your subscription is inactive. Renew at https://solongate.com");
|
|
3391
3765
|
process.exit(1);
|
|
3392
3766
|
}
|
|
3393
|
-
|
|
3767
|
+
log2("License validated.");
|
|
3394
3768
|
} catch (err) {
|
|
3395
|
-
|
|
3396
|
-
|
|
3769
|
+
log2(`ERROR: Unable to reach SolonGate license server. Check your internet connection.`);
|
|
3770
|
+
log2(`Details: ${err instanceof Error ? err.message : String(err)}`);
|
|
3397
3771
|
process.exit(1);
|
|
3398
3772
|
}
|
|
3399
3773
|
}
|
|
@@ -3401,28 +3775,27 @@ var SolonGateProxy = class {
|
|
|
3401
3775
|
try {
|
|
3402
3776
|
const cloudPolicy = await fetchCloudPolicy(this.config.apiKey, apiUrl);
|
|
3403
3777
|
this.config.policy = cloudPolicy;
|
|
3404
|
-
|
|
3778
|
+
log2(`Loaded cloud policy: ${cloudPolicy.name} (${cloudPolicy.rules.length} rules)`);
|
|
3405
3779
|
} catch (err) {
|
|
3406
|
-
|
|
3780
|
+
log2(`Cloud policy fetch failed, using local policy: ${err instanceof Error ? err.message : String(err)}`);
|
|
3407
3781
|
}
|
|
3408
3782
|
}
|
|
3409
3783
|
}
|
|
3410
3784
|
this.gate.loadPolicy(this.config.policy);
|
|
3411
|
-
|
|
3785
|
+
log2(`Policy: ${this.config.policy.name} (${this.config.policy.rules.length} rules)`);
|
|
3412
3786
|
const transport = this.config.upstream.transport ?? "stdio";
|
|
3413
3787
|
if (transport === "stdio") {
|
|
3414
|
-
|
|
3788
|
+
log2(`Upstream: [stdio] ${this.config.upstream.command} ${(this.config.upstream.args ?? []).join(" ")}`);
|
|
3415
3789
|
} else {
|
|
3416
|
-
|
|
3790
|
+
log2(`Upstream: [${transport}] ${this.config.upstream.url}`);
|
|
3417
3791
|
}
|
|
3418
3792
|
await this.connectUpstream();
|
|
3419
3793
|
await this.discoverTools();
|
|
3420
3794
|
this.registerToolsToCloud();
|
|
3421
3795
|
this.registerServerToCloud();
|
|
3422
|
-
this.
|
|
3796
|
+
this.startPolicySync();
|
|
3423
3797
|
this.createServer();
|
|
3424
3798
|
await this.serve();
|
|
3425
|
-
this.startPolicyPolling();
|
|
3426
3799
|
}
|
|
3427
3800
|
/**
|
|
3428
3801
|
* Connect to the upstream MCP server.
|
|
@@ -3460,7 +3833,7 @@ var SolonGateProxy = class {
|
|
|
3460
3833
|
break;
|
|
3461
3834
|
}
|
|
3462
3835
|
}
|
|
3463
|
-
|
|
3836
|
+
log2(`Connected to upstream server (${upstreamTransport})`);
|
|
3464
3837
|
}
|
|
3465
3838
|
/**
|
|
3466
3839
|
* Discover tools from the upstream server.
|
|
@@ -3473,9 +3846,9 @@ var SolonGateProxy = class {
|
|
|
3473
3846
|
description: t.description,
|
|
3474
3847
|
inputSchema: t.inputSchema
|
|
3475
3848
|
}));
|
|
3476
|
-
|
|
3849
|
+
log2(`Discovered ${this.upstreamTools.length} tools from upstream:`);
|
|
3477
3850
|
for (const tool of this.upstreamTools) {
|
|
3478
|
-
|
|
3851
|
+
log2(` - ${tool.name}: ${tool.description ?? "(no description)"}`);
|
|
3479
3852
|
}
|
|
3480
3853
|
}
|
|
3481
3854
|
/**
|
|
@@ -3504,13 +3877,13 @@ var SolonGateProxy = class {
|
|
|
3504
3877
|
const { name, arguments: args } = request.params;
|
|
3505
3878
|
const argsSize = JSON.stringify(args ?? {}).length;
|
|
3506
3879
|
if (argsSize > MAX_ARGUMENT_SIZE) {
|
|
3507
|
-
|
|
3880
|
+
log2(`DENY: ${name} \u2014 payload size ${argsSize} exceeds limit ${MAX_ARGUMENT_SIZE}`);
|
|
3508
3881
|
return {
|
|
3509
3882
|
content: [{ type: "text", text: `Request payload too large (${Math.round(argsSize / 1024)}KB > ${Math.round(MAX_ARGUMENT_SIZE / 1024)}KB limit)` }],
|
|
3510
3883
|
isError: true
|
|
3511
3884
|
};
|
|
3512
3885
|
}
|
|
3513
|
-
|
|
3886
|
+
log2(`Tool call: ${name}`);
|
|
3514
3887
|
await this.callMutex.acquire();
|
|
3515
3888
|
const startTime = Date.now();
|
|
3516
3889
|
try {
|
|
@@ -3527,10 +3900,10 @@ var SolonGateProxy = class {
|
|
|
3527
3900
|
);
|
|
3528
3901
|
const decision = result.isError ? "DENY" : "ALLOW";
|
|
3529
3902
|
const evaluationTimeMs = Date.now() - startTime;
|
|
3530
|
-
|
|
3903
|
+
log2(`Result: ${decision} (${evaluationTimeMs}ms)`);
|
|
3531
3904
|
if (this.config.apiKey && !this.config.apiKey.startsWith("sg_test_")) {
|
|
3532
3905
|
const apiUrl = this.config.apiUrl ?? "https://api.solongate.com";
|
|
3533
|
-
|
|
3906
|
+
log2(`Sending audit log: ${name} \u2192 ${decision} (key: ${this.config.apiKey.slice(0, 16)}...)`);
|
|
3534
3907
|
let reason = "allowed";
|
|
3535
3908
|
let matchedRule;
|
|
3536
3909
|
if (result.isError) {
|
|
@@ -3553,7 +3926,7 @@ var SolonGateProxy = class {
|
|
|
3553
3926
|
evaluationTimeMs
|
|
3554
3927
|
});
|
|
3555
3928
|
} else {
|
|
3556
|
-
|
|
3929
|
+
log2(`Skipping audit log (apiKey: ${this.config.apiKey ? "test key" : "not set"})`);
|
|
3557
3930
|
}
|
|
3558
3931
|
return {
|
|
3559
3932
|
content: [...result.content],
|
|
@@ -3627,13 +4000,13 @@ var SolonGateProxy = class {
|
|
|
3627
4000
|
registered++;
|
|
3628
4001
|
} else {
|
|
3629
4002
|
const body = await res.text().catch(() => "");
|
|
3630
|
-
|
|
4003
|
+
log2(`Tool registration failed for "${tool.name}" (${res.status}): ${body}`);
|
|
3631
4004
|
}
|
|
3632
4005
|
}).catch((err) => {
|
|
3633
|
-
|
|
4006
|
+
log2(`Tool registration error for "${tool.name}": ${err instanceof Error ? err.message : String(err)}`);
|
|
3634
4007
|
});
|
|
3635
4008
|
}
|
|
3636
|
-
|
|
4009
|
+
log2(`Registering ${total} tools to dashboard...`);
|
|
3637
4010
|
}
|
|
3638
4011
|
/**
|
|
3639
4012
|
* Guess tool permissions from tool name.
|
|
@@ -3687,68 +4060,42 @@ var SolonGateProxy = class {
|
|
|
3687
4060
|
})
|
|
3688
4061
|
}).then(async (res) => {
|
|
3689
4062
|
if (res.ok) {
|
|
3690
|
-
|
|
4063
|
+
log2(`Registered MCP server "${serverName}" to dashboard.`);
|
|
3691
4064
|
} else if (res.status === 409) {
|
|
3692
|
-
|
|
4065
|
+
log2(`MCP server "${serverName}" already registered.`);
|
|
3693
4066
|
} else {
|
|
3694
4067
|
const body = await res.text().catch(() => "");
|
|
3695
|
-
|
|
4068
|
+
log2(`MCP server registration failed (${res.status}): ${body}`);
|
|
3696
4069
|
}
|
|
3697
4070
|
}).catch((err) => {
|
|
3698
|
-
|
|
4071
|
+
log2(`MCP server registration error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3699
4072
|
});
|
|
3700
4073
|
}
|
|
3701
4074
|
/**
|
|
3702
|
-
*
|
|
3703
|
-
*
|
|
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)
|
|
3704
4080
|
*/
|
|
3705
|
-
|
|
3706
|
-
|
|
4081
|
+
startPolicySync() {
|
|
4082
|
+
const apiKey = this.config.apiKey;
|
|
4083
|
+
if (!apiKey) return;
|
|
3707
4084
|
const apiUrl = this.config.apiUrl ?? "https://api.solongate.com";
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
description: policy.description || `Policy synced from proxy`,
|
|
3719
|
-
version: policy.version || 1,
|
|
3720
|
-
rules: policy.rules
|
|
3721
|
-
})
|
|
3722
|
-
}).then(async (res) => {
|
|
3723
|
-
if (res.ok) {
|
|
3724
|
-
log(`Synced policy "${policy.name}" to dashboard.`);
|
|
3725
|
-
} else {
|
|
3726
|
-
const body = await res.text().catch(() => "");
|
|
3727
|
-
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)`);
|
|
3728
4095
|
}
|
|
3729
|
-
}).catch((err) => {
|
|
3730
|
-
log(`Policy sync error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3731
4096
|
});
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
* Poll for policy updates from dashboard every 60 seconds.
|
|
3735
|
-
* When user changes policy in dashboard, proxy picks it up automatically.
|
|
3736
|
-
*/
|
|
3737
|
-
startPolicyPolling() {
|
|
3738
|
-
if (!this.config.apiKey || this.config.apiKey.startsWith("sg_test_")) return;
|
|
3739
|
-
const apiUrl = this.config.apiUrl ?? "https://api.solongate.com";
|
|
3740
|
-
const POLL_INTERVAL = 6e4;
|
|
3741
|
-
setInterval(async () => {
|
|
3742
|
-
try {
|
|
3743
|
-
const newPolicy = await fetchCloudPolicy(this.config.apiKey, apiUrl);
|
|
3744
|
-
if (newPolicy.version !== this.config.policy.version || newPolicy.rules.length !== this.config.policy.rules.length) {
|
|
3745
|
-
this.config.policy = newPolicy;
|
|
3746
|
-
this.gate.loadPolicy(newPolicy);
|
|
3747
|
-
log(`Policy updated from dashboard: ${newPolicy.name} v${newPolicy.version} (${newPolicy.rules.length} rules)`);
|
|
3748
|
-
}
|
|
3749
|
-
} catch {
|
|
3750
|
-
}
|
|
3751
|
-
}, POLL_INTERVAL);
|
|
4097
|
+
this.syncManager.start();
|
|
4098
|
+
log2("Bidirectional policy sync started.");
|
|
3752
4099
|
}
|
|
3753
4100
|
/**
|
|
3754
4101
|
* Start serving downstream.
|
|
@@ -3774,14 +4121,14 @@ var SolonGateProxy = class {
|
|
|
3774
4121
|
}
|
|
3775
4122
|
});
|
|
3776
4123
|
httpServer.listen(this.config.port, () => {
|
|
3777
|
-
|
|
3778
|
-
|
|
4124
|
+
log2(`Proxy is live on http://localhost:${this.config.port}/mcp`);
|
|
4125
|
+
log2("All tool calls are now protected by SolonGate.");
|
|
3779
4126
|
});
|
|
3780
4127
|
} else {
|
|
3781
4128
|
const transport = new StdioServerTransport();
|
|
3782
4129
|
await this.server.connect(transport);
|
|
3783
|
-
|
|
3784
|
-
|
|
4130
|
+
log2("Proxy is live. All tool calls are now protected by SolonGate.");
|
|
4131
|
+
log2("Waiting for requests...");
|
|
3785
4132
|
}
|
|
3786
4133
|
}
|
|
3787
4134
|
};
|
|
@@ -3799,7 +4146,7 @@ console.error = (...args) => {
|
|
|
3799
4146
|
process.stderr.write(`[SolonGate ERROR] ${args.map(String).join(" ")}
|
|
3800
4147
|
`);
|
|
3801
4148
|
};
|
|
3802
|
-
async function
|
|
4149
|
+
async function main5() {
|
|
3803
4150
|
const subcommand = process.argv[2];
|
|
3804
4151
|
if (subcommand === "init") {
|
|
3805
4152
|
process.argv.splice(2, 1);
|
|
@@ -3816,6 +4163,10 @@ async function main4() {
|
|
|
3816
4163
|
await Promise.resolve().then(() => (init_create(), create_exports));
|
|
3817
4164
|
return;
|
|
3818
4165
|
}
|
|
4166
|
+
if (subcommand === "pull" || subcommand === "push") {
|
|
4167
|
+
await Promise.resolve().then(() => (init_pull_push(), pull_push_exports));
|
|
4168
|
+
return;
|
|
4169
|
+
}
|
|
3819
4170
|
try {
|
|
3820
4171
|
const config = parseArgs(process.argv);
|
|
3821
4172
|
const proxy = new SolonGateProxy(config);
|
|
@@ -3827,4 +4178,4 @@ async function main4() {
|
|
|
3827
4178
|
process.exit(1);
|
|
3828
4179
|
}
|
|
3829
4180
|
}
|
|
3830
|
-
|
|
4181
|
+
main5();
|