@solongate/proxy 0.3.1 → 0.4.1
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 +1075 -717
- package/dist/pull-push.js +183 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,29 +4,536 @@ 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
|
+
let resolvedId = policyId;
|
|
12
|
+
if (!resolvedId) {
|
|
13
|
+
const listRes = await fetch(`${apiUrl}/api/v1/policies`, {
|
|
14
|
+
headers: { "Authorization": `Bearer ${apiKey}` }
|
|
15
|
+
});
|
|
16
|
+
if (!listRes.ok) {
|
|
17
|
+
const body = await listRes.text().catch(() => "");
|
|
18
|
+
throw new Error(`Failed to list policies from cloud (${listRes.status}): ${body}`);
|
|
19
|
+
}
|
|
20
|
+
const listData = await listRes.json();
|
|
21
|
+
const policies = listData.policies ?? [];
|
|
22
|
+
if (policies.length === 0) {
|
|
23
|
+
throw new Error("No policies found in cloud. Create one in the dashboard first.");
|
|
24
|
+
}
|
|
25
|
+
resolvedId = policies[0].id;
|
|
26
|
+
}
|
|
27
|
+
const url = `${apiUrl}/api/v1/policies/${resolvedId}`;
|
|
28
|
+
const res = await fetch(url, {
|
|
29
|
+
headers: { "Authorization": `Bearer ${apiKey}` }
|
|
30
|
+
});
|
|
31
|
+
if (!res.ok) {
|
|
32
|
+
const body = await res.text().catch(() => "");
|
|
33
|
+
throw new Error(`Failed to fetch policy from cloud (${res.status}): ${body}`);
|
|
34
|
+
}
|
|
35
|
+
const data = await res.json();
|
|
36
|
+
return {
|
|
37
|
+
id: String(data.id ?? "cloud"),
|
|
38
|
+
name: String(data.name ?? "Cloud Policy"),
|
|
39
|
+
version: Number(data._version ?? 1),
|
|
40
|
+
rules: data.rules ?? [],
|
|
41
|
+
createdAt: String(data._created_at ?? ""),
|
|
42
|
+
updatedAt: ""
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async function sendAuditLog(apiKey, apiUrl, entry) {
|
|
46
|
+
const url = `${apiUrl}/api/v1/audit-logs`;
|
|
47
|
+
try {
|
|
48
|
+
const res = await fetch(url, {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: {
|
|
51
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
52
|
+
"Content-Type": "application/json"
|
|
53
|
+
},
|
|
54
|
+
body: JSON.stringify(entry)
|
|
55
|
+
});
|
|
56
|
+
if (!res.ok) {
|
|
57
|
+
const body = await res.text().catch(() => "");
|
|
58
|
+
process.stderr.write(`[SolonGate] Audit log failed (${res.status}): ${body}
|
|
59
|
+
`);
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
process.stderr.write(`[SolonGate] Audit log error: ${err instanceof Error ? err.message : String(err)}
|
|
63
|
+
`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function loadPolicy(source) {
|
|
67
|
+
if (typeof source === "object") return source;
|
|
68
|
+
if (PRESETS[source]) return PRESETS[source];
|
|
69
|
+
const filePath = resolve(source);
|
|
70
|
+
if (existsSync(filePath)) {
|
|
71
|
+
const content = readFileSync(filePath, "utf-8");
|
|
72
|
+
return JSON.parse(content);
|
|
73
|
+
}
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Unknown policy "${source}". Use a preset (${Object.keys(PRESETS).join(", ")}), a JSON file path, or a PolicySet object.`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
function parseArgs(argv) {
|
|
79
|
+
const args = argv.slice(2);
|
|
80
|
+
let policySource = "restricted";
|
|
81
|
+
let name = "solongate-proxy";
|
|
82
|
+
let verbose = false;
|
|
83
|
+
let rateLimitPerTool;
|
|
84
|
+
let globalRateLimit;
|
|
85
|
+
let configFile;
|
|
86
|
+
let apiKey;
|
|
87
|
+
let apiUrl;
|
|
88
|
+
let upstreamUrl;
|
|
89
|
+
let upstreamTransport;
|
|
90
|
+
let port;
|
|
91
|
+
let separatorIndex = args.indexOf("--");
|
|
92
|
+
const flags = separatorIndex >= 0 ? args.slice(0, separatorIndex) : args;
|
|
93
|
+
const upstreamArgs = separatorIndex >= 0 ? args.slice(separatorIndex + 1) : [];
|
|
94
|
+
for (let i = 0; i < flags.length; i++) {
|
|
95
|
+
switch (flags[i]) {
|
|
96
|
+
case "--policy":
|
|
97
|
+
policySource = flags[++i];
|
|
98
|
+
break;
|
|
99
|
+
case "--name":
|
|
100
|
+
name = flags[++i];
|
|
101
|
+
break;
|
|
102
|
+
case "--verbose":
|
|
103
|
+
verbose = true;
|
|
104
|
+
break;
|
|
105
|
+
case "--rate-limit":
|
|
106
|
+
rateLimitPerTool = parseInt(flags[++i], 10);
|
|
107
|
+
break;
|
|
108
|
+
case "--global-rate-limit":
|
|
109
|
+
globalRateLimit = parseInt(flags[++i], 10);
|
|
110
|
+
break;
|
|
111
|
+
case "--config":
|
|
112
|
+
configFile = flags[++i];
|
|
113
|
+
break;
|
|
114
|
+
case "--api-key":
|
|
115
|
+
apiKey = flags[++i];
|
|
116
|
+
break;
|
|
117
|
+
case "--api-url":
|
|
118
|
+
apiUrl = flags[++i];
|
|
119
|
+
break;
|
|
120
|
+
case "--upstream-url":
|
|
121
|
+
upstreamUrl = flags[++i];
|
|
122
|
+
break;
|
|
123
|
+
case "--upstream-transport":
|
|
124
|
+
upstreamTransport = flags[++i];
|
|
125
|
+
break;
|
|
126
|
+
case "--port":
|
|
127
|
+
port = parseInt(flags[++i], 10);
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (!apiKey) {
|
|
132
|
+
const envKey = process.env.SOLONGATE_API_KEY;
|
|
133
|
+
if (envKey) {
|
|
134
|
+
apiKey = envKey;
|
|
135
|
+
} else {
|
|
136
|
+
throw new Error(
|
|
137
|
+
"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"
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
"Invalid API key format. Keys must start with 'sg_live_' or 'sg_test_'.\nGet your API key at https://solongate.com\n"
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
const resolvedPolicyPath = resolvePolicyPath(policySource);
|
|
147
|
+
if (configFile) {
|
|
148
|
+
const filePath = resolve(configFile);
|
|
149
|
+
const content = readFileSync(filePath, "utf-8");
|
|
150
|
+
const fileConfig = JSON.parse(content);
|
|
151
|
+
if (!fileConfig.upstream) {
|
|
152
|
+
throw new Error('Config file must include "upstream" with at least "command" or "url"');
|
|
153
|
+
}
|
|
154
|
+
const cfgPolicySource = fileConfig.policy ?? policySource;
|
|
155
|
+
return {
|
|
156
|
+
upstream: fileConfig.upstream,
|
|
157
|
+
policy: loadPolicy(cfgPolicySource),
|
|
158
|
+
name: fileConfig.name ?? name,
|
|
159
|
+
verbose: fileConfig.verbose ?? verbose,
|
|
160
|
+
rateLimitPerTool: fileConfig.rateLimitPerTool ?? rateLimitPerTool,
|
|
161
|
+
globalRateLimit: fileConfig.globalRateLimit ?? globalRateLimit,
|
|
162
|
+
apiKey: apiKey ?? fileConfig.apiKey,
|
|
163
|
+
apiUrl: apiUrl ?? fileConfig.apiUrl,
|
|
164
|
+
port: port ?? fileConfig.port,
|
|
165
|
+
policyPath: resolvePolicyPath(cfgPolicySource) ?? void 0
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
if (upstreamUrl) {
|
|
169
|
+
const transport = upstreamTransport ?? (upstreamUrl.includes("/sse") ? "sse" : "http");
|
|
170
|
+
return {
|
|
171
|
+
upstream: {
|
|
172
|
+
transport,
|
|
173
|
+
command: "",
|
|
174
|
+
// not used for URL-based transports
|
|
175
|
+
url: upstreamUrl
|
|
176
|
+
},
|
|
177
|
+
policy: loadPolicy(policySource),
|
|
178
|
+
name,
|
|
179
|
+
verbose,
|
|
180
|
+
rateLimitPerTool,
|
|
181
|
+
globalRateLimit,
|
|
182
|
+
apiKey,
|
|
183
|
+
apiUrl,
|
|
184
|
+
port,
|
|
185
|
+
policyPath: resolvedPolicyPath ?? void 0
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (upstreamArgs.length === 0) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
"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"
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
const [command, ...commandArgs] = upstreamArgs;
|
|
194
|
+
return {
|
|
195
|
+
upstream: {
|
|
196
|
+
transport: upstreamTransport ?? "stdio",
|
|
197
|
+
command,
|
|
198
|
+
args: commandArgs,
|
|
199
|
+
env: { ...process.env }
|
|
200
|
+
},
|
|
201
|
+
policy: loadPolicy(policySource),
|
|
202
|
+
name,
|
|
203
|
+
verbose,
|
|
204
|
+
rateLimitPerTool,
|
|
205
|
+
globalRateLimit,
|
|
206
|
+
apiKey,
|
|
207
|
+
apiUrl,
|
|
208
|
+
port,
|
|
209
|
+
policyPath: resolvedPolicyPath ?? void 0
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function resolvePolicyPath(source) {
|
|
213
|
+
if (PRESETS[source]) return null;
|
|
214
|
+
const filePath = resolve(source);
|
|
215
|
+
if (existsSync(filePath)) return filePath;
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
var PRESETS;
|
|
219
|
+
var init_config = __esm({
|
|
220
|
+
"src/config.ts"() {
|
|
221
|
+
"use strict";
|
|
222
|
+
PRESETS = {
|
|
223
|
+
restricted: {
|
|
224
|
+
id: "restricted",
|
|
225
|
+
name: "Restricted",
|
|
226
|
+
description: "Blocks dangerous commands (rm -rf, curl|bash, dd, etc.), allows safe tools with path restrictions",
|
|
227
|
+
version: 1,
|
|
228
|
+
rules: [
|
|
229
|
+
{
|
|
230
|
+
id: "deny-dangerous-commands",
|
|
231
|
+
description: "Block dangerous shell commands",
|
|
232
|
+
effect: "DENY",
|
|
233
|
+
priority: 50,
|
|
234
|
+
toolPattern: "*",
|
|
235
|
+
permission: "EXECUTE",
|
|
236
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
237
|
+
enabled: true,
|
|
238
|
+
commandConstraints: {
|
|
239
|
+
denied: [
|
|
240
|
+
"rm -rf *",
|
|
241
|
+
"rm -r /*",
|
|
242
|
+
"mkfs*",
|
|
243
|
+
"dd if=*",
|
|
244
|
+
"curl*|*bash*",
|
|
245
|
+
"curl*|*sh*",
|
|
246
|
+
"wget*|*bash*",
|
|
247
|
+
"wget*|*sh*",
|
|
248
|
+
"shutdown*",
|
|
249
|
+
"reboot*",
|
|
250
|
+
"kill -9*",
|
|
251
|
+
"chmod*777*",
|
|
252
|
+
"iptables*",
|
|
253
|
+
"passwd*",
|
|
254
|
+
"useradd*",
|
|
255
|
+
"userdel*"
|
|
256
|
+
]
|
|
257
|
+
},
|
|
258
|
+
createdAt: "",
|
|
259
|
+
updatedAt: ""
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
id: "deny-sensitive-paths",
|
|
263
|
+
description: "Block access to sensitive files",
|
|
264
|
+
effect: "DENY",
|
|
265
|
+
priority: 51,
|
|
266
|
+
toolPattern: "*",
|
|
267
|
+
permission: "EXECUTE",
|
|
268
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
269
|
+
enabled: true,
|
|
270
|
+
pathConstraints: {
|
|
271
|
+
denied: [
|
|
272
|
+
"**/.env*",
|
|
273
|
+
"**/.ssh/**",
|
|
274
|
+
"**/.aws/**",
|
|
275
|
+
"**/.kube/**",
|
|
276
|
+
"**/credentials*",
|
|
277
|
+
"**/secrets*",
|
|
278
|
+
"**/*.pem",
|
|
279
|
+
"**/*.key",
|
|
280
|
+
"/etc/passwd",
|
|
281
|
+
"/etc/shadow",
|
|
282
|
+
"/proc/**",
|
|
283
|
+
"/dev/**"
|
|
284
|
+
]
|
|
285
|
+
},
|
|
286
|
+
createdAt: "",
|
|
287
|
+
updatedAt: ""
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
id: "allow-rest",
|
|
291
|
+
description: "Allow all other tool calls",
|
|
292
|
+
effect: "ALLOW",
|
|
293
|
+
priority: 1e3,
|
|
294
|
+
toolPattern: "*",
|
|
295
|
+
permission: "EXECUTE",
|
|
296
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
297
|
+
enabled: true,
|
|
298
|
+
createdAt: "",
|
|
299
|
+
updatedAt: ""
|
|
300
|
+
}
|
|
301
|
+
],
|
|
302
|
+
createdAt: "",
|
|
303
|
+
updatedAt: ""
|
|
304
|
+
},
|
|
305
|
+
"read-only": {
|
|
306
|
+
id: "read-only",
|
|
307
|
+
name: "Read Only",
|
|
308
|
+
description: "Only allows read/list/get/search/query tools",
|
|
309
|
+
version: 1,
|
|
310
|
+
rules: [
|
|
311
|
+
{
|
|
312
|
+
id: "allow-read",
|
|
313
|
+
description: "Allow read tools",
|
|
314
|
+
effect: "ALLOW",
|
|
315
|
+
priority: 100,
|
|
316
|
+
toolPattern: "*read*",
|
|
317
|
+
permission: "EXECUTE",
|
|
318
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
319
|
+
enabled: true,
|
|
320
|
+
createdAt: "",
|
|
321
|
+
updatedAt: ""
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
id: "allow-list",
|
|
325
|
+
description: "Allow list tools",
|
|
326
|
+
effect: "ALLOW",
|
|
327
|
+
priority: 101,
|
|
328
|
+
toolPattern: "*list*",
|
|
329
|
+
permission: "EXECUTE",
|
|
330
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
331
|
+
enabled: true,
|
|
332
|
+
createdAt: "",
|
|
333
|
+
updatedAt: ""
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
id: "allow-get",
|
|
337
|
+
description: "Allow get tools",
|
|
338
|
+
effect: "ALLOW",
|
|
339
|
+
priority: 102,
|
|
340
|
+
toolPattern: "*get*",
|
|
341
|
+
permission: "EXECUTE",
|
|
342
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
343
|
+
enabled: true,
|
|
344
|
+
createdAt: "",
|
|
345
|
+
updatedAt: ""
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
id: "allow-search",
|
|
349
|
+
description: "Allow search tools",
|
|
350
|
+
effect: "ALLOW",
|
|
351
|
+
priority: 103,
|
|
352
|
+
toolPattern: "*search*",
|
|
353
|
+
permission: "EXECUTE",
|
|
354
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
355
|
+
enabled: true,
|
|
356
|
+
createdAt: "",
|
|
357
|
+
updatedAt: ""
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
id: "allow-query",
|
|
361
|
+
description: "Allow query tools",
|
|
362
|
+
effect: "ALLOW",
|
|
363
|
+
priority: 104,
|
|
364
|
+
toolPattern: "*query*",
|
|
365
|
+
permission: "EXECUTE",
|
|
366
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
367
|
+
enabled: true,
|
|
368
|
+
createdAt: "",
|
|
369
|
+
updatedAt: ""
|
|
370
|
+
}
|
|
371
|
+
],
|
|
372
|
+
createdAt: "",
|
|
373
|
+
updatedAt: ""
|
|
374
|
+
},
|
|
375
|
+
sandbox: {
|
|
376
|
+
id: "sandbox",
|
|
377
|
+
name: "Sandbox",
|
|
378
|
+
description: "Allows file ops within working directory only, blocks dangerous commands, allows safe shell commands",
|
|
379
|
+
version: 1,
|
|
380
|
+
rules: [
|
|
381
|
+
{
|
|
382
|
+
id: "deny-dangerous-commands",
|
|
383
|
+
description: "Block dangerous shell commands",
|
|
384
|
+
effect: "DENY",
|
|
385
|
+
priority: 50,
|
|
386
|
+
toolPattern: "*",
|
|
387
|
+
permission: "EXECUTE",
|
|
388
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
389
|
+
enabled: true,
|
|
390
|
+
commandConstraints: {
|
|
391
|
+
denied: [
|
|
392
|
+
"rm -rf *",
|
|
393
|
+
"rm -r /*",
|
|
394
|
+
"mkfs*",
|
|
395
|
+
"dd if=*",
|
|
396
|
+
"curl*|*bash*",
|
|
397
|
+
"wget*|*sh*",
|
|
398
|
+
"shutdown*",
|
|
399
|
+
"reboot*",
|
|
400
|
+
"chmod*777*"
|
|
401
|
+
]
|
|
402
|
+
},
|
|
403
|
+
createdAt: "",
|
|
404
|
+
updatedAt: ""
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
id: "allow-safe-commands",
|
|
408
|
+
description: "Allow safe shell commands only",
|
|
409
|
+
effect: "ALLOW",
|
|
410
|
+
priority: 100,
|
|
411
|
+
toolPattern: "*shell*",
|
|
412
|
+
permission: "EXECUTE",
|
|
413
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
414
|
+
enabled: true,
|
|
415
|
+
commandConstraints: {
|
|
416
|
+
allowed: [
|
|
417
|
+
"ls*",
|
|
418
|
+
"cat*",
|
|
419
|
+
"head*",
|
|
420
|
+
"tail*",
|
|
421
|
+
"wc*",
|
|
422
|
+
"grep*",
|
|
423
|
+
"find*",
|
|
424
|
+
"echo*",
|
|
425
|
+
"pwd",
|
|
426
|
+
"whoami",
|
|
427
|
+
"date",
|
|
428
|
+
"env",
|
|
429
|
+
"git*",
|
|
430
|
+
"npm*",
|
|
431
|
+
"pnpm*",
|
|
432
|
+
"yarn*",
|
|
433
|
+
"node*",
|
|
434
|
+
"python*",
|
|
435
|
+
"pip*"
|
|
436
|
+
]
|
|
437
|
+
},
|
|
438
|
+
createdAt: "",
|
|
439
|
+
updatedAt: ""
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
id: "deny-sensitive-paths",
|
|
443
|
+
description: "Block access to sensitive files",
|
|
444
|
+
effect: "DENY",
|
|
445
|
+
priority: 51,
|
|
446
|
+
toolPattern: "*",
|
|
447
|
+
permission: "EXECUTE",
|
|
448
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
449
|
+
enabled: true,
|
|
450
|
+
pathConstraints: {
|
|
451
|
+
denied: [
|
|
452
|
+
"**/.env*",
|
|
453
|
+
"**/.ssh/**",
|
|
454
|
+
"**/.aws/**",
|
|
455
|
+
"**/credentials*",
|
|
456
|
+
"**/*.pem",
|
|
457
|
+
"**/*.key"
|
|
458
|
+
]
|
|
459
|
+
},
|
|
460
|
+
createdAt: "",
|
|
461
|
+
updatedAt: ""
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
id: "allow-rest",
|
|
465
|
+
description: "Allow all other tools",
|
|
466
|
+
effect: "ALLOW",
|
|
467
|
+
priority: 1e3,
|
|
468
|
+
toolPattern: "*",
|
|
469
|
+
permission: "EXECUTE",
|
|
470
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
471
|
+
enabled: true,
|
|
472
|
+
createdAt: "",
|
|
473
|
+
updatedAt: ""
|
|
474
|
+
}
|
|
475
|
+
],
|
|
476
|
+
createdAt: "",
|
|
477
|
+
updatedAt: ""
|
|
478
|
+
},
|
|
479
|
+
permissive: {
|
|
480
|
+
id: "permissive",
|
|
481
|
+
name: "Permissive",
|
|
482
|
+
description: "Allows all tool calls (monitoring only)",
|
|
483
|
+
version: 1,
|
|
484
|
+
rules: [
|
|
485
|
+
{
|
|
486
|
+
id: "allow-all",
|
|
487
|
+
description: "Allow all",
|
|
488
|
+
effect: "ALLOW",
|
|
489
|
+
priority: 1e3,
|
|
490
|
+
toolPattern: "*",
|
|
491
|
+
permission: "EXECUTE",
|
|
492
|
+
minimumTrustLevel: "UNTRUSTED",
|
|
493
|
+
enabled: true,
|
|
494
|
+
createdAt: "",
|
|
495
|
+
updatedAt: ""
|
|
496
|
+
}
|
|
497
|
+
],
|
|
498
|
+
createdAt: "",
|
|
499
|
+
updatedAt: ""
|
|
500
|
+
},
|
|
501
|
+
"deny-all": {
|
|
502
|
+
id: "deny-all",
|
|
503
|
+
name: "Deny All",
|
|
504
|
+
description: "Blocks all tool calls",
|
|
505
|
+
version: 1,
|
|
506
|
+
rules: [],
|
|
507
|
+
createdAt: "",
|
|
508
|
+
updatedAt: ""
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
|
|
7
514
|
// src/init.ts
|
|
8
515
|
var init_exports = {};
|
|
9
|
-
import { readFileSync as
|
|
516
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, copyFileSync, mkdirSync } from "fs";
|
|
10
517
|
import { resolve as resolve2, join } from "path";
|
|
11
518
|
import { createInterface } from "readline";
|
|
12
519
|
function findConfigFile(explicitPath) {
|
|
13
520
|
if (explicitPath) {
|
|
14
|
-
if (
|
|
521
|
+
if (existsSync3(explicitPath)) {
|
|
15
522
|
return { path: resolve2(explicitPath), type: "mcp" };
|
|
16
523
|
}
|
|
17
524
|
return null;
|
|
18
525
|
}
|
|
19
526
|
for (const searchPath of SEARCH_PATHS) {
|
|
20
527
|
const full = resolve2(searchPath);
|
|
21
|
-
if (
|
|
528
|
+
if (existsSync3(full)) return { path: full, type: "mcp" };
|
|
22
529
|
}
|
|
23
530
|
for (const desktopPath of CLAUDE_DESKTOP_PATHS) {
|
|
24
|
-
if (
|
|
531
|
+
if (existsSync3(desktopPath)) return { path: desktopPath, type: "claude-desktop" };
|
|
25
532
|
}
|
|
26
533
|
return null;
|
|
27
534
|
}
|
|
28
535
|
function readConfig(filePath) {
|
|
29
|
-
const content =
|
|
536
|
+
const content = readFileSync3(filePath, "utf-8");
|
|
30
537
|
const parsed = JSON.parse(content);
|
|
31
538
|
if (parsed.mcpServers) return parsed;
|
|
32
539
|
throw new Error(`Unrecognized config format in ${filePath}`);
|
|
@@ -97,7 +604,7 @@ function parseInitArgs(argv) {
|
|
|
97
604
|
}
|
|
98
605
|
}
|
|
99
606
|
if (!POLICY_PRESETS.includes(options.policy)) {
|
|
100
|
-
if (!
|
|
607
|
+
if (!existsSync3(resolve2(options.policy))) {
|
|
101
608
|
console.error(`Unknown policy: ${options.policy}`);
|
|
102
609
|
console.error(`Available presets: ${POLICY_PRESETS.join(", ")}`);
|
|
103
610
|
process.exit(1);
|
|
@@ -141,16 +648,16 @@ function installClaudeCodeHooks(apiKey) {
|
|
|
141
648
|
const hooksDir = resolve2(".claude", "hooks");
|
|
142
649
|
mkdirSync(hooksDir, { recursive: true });
|
|
143
650
|
const guardPath = join(hooksDir, "guard.mjs");
|
|
144
|
-
|
|
651
|
+
writeFileSync2(guardPath, GUARD_SCRIPT);
|
|
145
652
|
console.error(` Created ${guardPath}`);
|
|
146
653
|
const auditPath = join(hooksDir, "audit.mjs");
|
|
147
|
-
|
|
654
|
+
writeFileSync2(auditPath, AUDIT_SCRIPT);
|
|
148
655
|
console.error(` Created ${auditPath}`);
|
|
149
656
|
const settingsPath = resolve2(".claude", "settings.json");
|
|
150
657
|
let settings = {};
|
|
151
|
-
if (
|
|
658
|
+
if (existsSync3(settingsPath)) {
|
|
152
659
|
try {
|
|
153
|
-
settings = JSON.parse(
|
|
660
|
+
settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
|
|
154
661
|
} catch {
|
|
155
662
|
}
|
|
156
663
|
}
|
|
@@ -184,7 +691,7 @@ function installClaudeCodeHooks(apiKey) {
|
|
|
184
691
|
const envObj = settings.env || {};
|
|
185
692
|
envObj.SOLONGATE_API_KEY = apiKey;
|
|
186
693
|
settings.env = envObj;
|
|
187
|
-
|
|
694
|
+
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
188
695
|
console.error(` Created ${settingsPath}`);
|
|
189
696
|
console.error("");
|
|
190
697
|
console.error(" Claude Code hooks installed!");
|
|
@@ -193,7 +700,7 @@ function installClaudeCodeHooks(apiKey) {
|
|
|
193
700
|
}
|
|
194
701
|
function ensureEnvFile() {
|
|
195
702
|
const envPath = resolve2(".env");
|
|
196
|
-
if (!
|
|
703
|
+
if (!existsSync3(envPath)) {
|
|
197
704
|
const envContent = `# SolonGate API Keys
|
|
198
705
|
# Get your keys from the dashboard: https://solongate.com
|
|
199
706
|
# IMPORTANT: Never commit this file to git!
|
|
@@ -204,14 +711,14 @@ SOLONGATE_API_KEY=sg_live_your_key_here
|
|
|
204
711
|
# Test key \u2014 local-only, no cloud connection (for development)
|
|
205
712
|
# SOLONGATE_API_KEY=sg_test_your_key_here
|
|
206
713
|
`;
|
|
207
|
-
|
|
714
|
+
writeFileSync2(envPath, envContent);
|
|
208
715
|
console.error(` Created .env with placeholder API keys`);
|
|
209
716
|
console.error(` \u2192 Edit .env and replace with your real keys from https://solongate.com`);
|
|
210
717
|
console.error("");
|
|
211
718
|
}
|
|
212
719
|
const gitignorePath = resolve2(".gitignore");
|
|
213
|
-
if (
|
|
214
|
-
let gitignore =
|
|
720
|
+
if (existsSync3(gitignorePath)) {
|
|
721
|
+
let gitignore = readFileSync3(gitignorePath, "utf-8");
|
|
215
722
|
let updated = false;
|
|
216
723
|
if (!gitignore.includes(".env")) {
|
|
217
724
|
gitignore = gitignore.trimEnd() + "\n.env\n.env.local\n";
|
|
@@ -222,11 +729,11 @@ SOLONGATE_API_KEY=sg_live_your_key_here
|
|
|
222
729
|
updated = true;
|
|
223
730
|
}
|
|
224
731
|
if (updated) {
|
|
225
|
-
|
|
732
|
+
writeFileSync2(gitignorePath, gitignore);
|
|
226
733
|
console.error(` Updated .gitignore (added .env + .mcp.json)`);
|
|
227
734
|
}
|
|
228
735
|
} else {
|
|
229
|
-
|
|
736
|
+
writeFileSync2(gitignorePath, ".env\n.env.local\n.mcp.json\nnode_modules/\n");
|
|
230
737
|
console.error(` Created .gitignore (with .env and .mcp.json excluded)`);
|
|
231
738
|
}
|
|
232
739
|
}
|
|
@@ -274,7 +781,7 @@ async function main() {
|
|
|
274
781
|
console.error("");
|
|
275
782
|
const backupPath = configInfo.path + ".solongate-backup";
|
|
276
783
|
if (options.restore) {
|
|
277
|
-
if (!
|
|
784
|
+
if (!existsSync3(backupPath)) {
|
|
278
785
|
console.error(" No backup found. Nothing to restore.");
|
|
279
786
|
process.exit(1);
|
|
280
787
|
}
|
|
@@ -331,8 +838,8 @@ async function main() {
|
|
|
331
838
|
let apiKey = options.apiKey || process.env.SOLONGATE_API_KEY || "";
|
|
332
839
|
if (!apiKey) {
|
|
333
840
|
const envPath = resolve2(".env");
|
|
334
|
-
if (
|
|
335
|
-
const envContent =
|
|
841
|
+
if (existsSync3(envPath)) {
|
|
842
|
+
const envContent = readFileSync3(envPath, "utf-8");
|
|
336
843
|
const match = envContent.match(/^SOLONGATE_API_KEY=(sg_(?:live|test)_\w+)/m);
|
|
337
844
|
if (match) apiKey = match[1];
|
|
338
845
|
}
|
|
@@ -367,16 +874,16 @@ async function main() {
|
|
|
367
874
|
console.error(JSON.stringify(newConfig, null, 2));
|
|
368
875
|
process.exit(0);
|
|
369
876
|
}
|
|
370
|
-
if (!
|
|
877
|
+
if (!existsSync3(backupPath)) {
|
|
371
878
|
copyFileSync(configInfo.path, backupPath);
|
|
372
879
|
console.error(` Backup: ${backupPath}`);
|
|
373
880
|
}
|
|
374
881
|
if (configInfo.type === "claude-desktop") {
|
|
375
|
-
const original = JSON.parse(
|
|
882
|
+
const original = JSON.parse(readFileSync3(configInfo.path, "utf-8"));
|
|
376
883
|
original.mcpServers = newConfig.mcpServers;
|
|
377
|
-
|
|
884
|
+
writeFileSync2(configInfo.path, JSON.stringify(original, null, 2) + "\n");
|
|
378
885
|
} else {
|
|
379
|
-
|
|
886
|
+
writeFileSync2(configInfo.path, JSON.stringify(newConfig, null, 2) + "\n");
|
|
380
887
|
}
|
|
381
888
|
console.error(" Config updated!");
|
|
382
889
|
console.error("");
|
|
@@ -635,7 +1142,7 @@ process.stdin.on('end', async () => {
|
|
|
635
1142
|
|
|
636
1143
|
// src/inject.ts
|
|
637
1144
|
var inject_exports = {};
|
|
638
|
-
import { readFileSync as
|
|
1145
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, copyFileSync as copyFileSync2 } from "fs";
|
|
639
1146
|
import { resolve as resolve3 } from "path";
|
|
640
1147
|
import { execSync } from "child_process";
|
|
641
1148
|
function parseInjectArgs(argv) {
|
|
@@ -668,7 +1175,7 @@ function parseInjectArgs(argv) {
|
|
|
668
1175
|
return opts;
|
|
669
1176
|
}
|
|
670
1177
|
function printHelp2() {
|
|
671
|
-
|
|
1178
|
+
log3(`
|
|
672
1179
|
SolonGate Inject \u2014 Add security to your MCP server in seconds
|
|
673
1180
|
|
|
674
1181
|
USAGE
|
|
@@ -692,7 +1199,7 @@ WHAT IT DOES
|
|
|
692
1199
|
All tool() calls are automatically protected by SolonGate.
|
|
693
1200
|
`);
|
|
694
1201
|
}
|
|
695
|
-
function
|
|
1202
|
+
function log3(msg) {
|
|
696
1203
|
process.stderr.write(msg + "\n");
|
|
697
1204
|
}
|
|
698
1205
|
function printBanner(subtitle) {
|
|
@@ -705,18 +1212,18 @@ function printBanner(subtitle) {
|
|
|
705
1212
|
" \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
1213
|
];
|
|
707
1214
|
const colors = [c.blue1, c.blue2, c.blue3, c.blue4, c.blue5, c.blue6];
|
|
708
|
-
|
|
1215
|
+
log3("");
|
|
709
1216
|
for (let i = 0; i < lines.length; i++) {
|
|
710
|
-
|
|
1217
|
+
log3(`${c.bold}${colors[i]}${lines[i]}${c.reset}`);
|
|
711
1218
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
1219
|
+
log3("");
|
|
1220
|
+
log3(` ${c.dim}${c.italic}${subtitle}${c.reset}`);
|
|
1221
|
+
log3("");
|
|
715
1222
|
}
|
|
716
1223
|
function detectProject() {
|
|
717
|
-
if (!
|
|
1224
|
+
if (!existsSync4(resolve3("package.json"))) return false;
|
|
718
1225
|
try {
|
|
719
|
-
const pkg = JSON.parse(
|
|
1226
|
+
const pkg = JSON.parse(readFileSync4(resolve3("package.json"), "utf-8"));
|
|
720
1227
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
721
1228
|
return !!(allDeps["@modelcontextprotocol/sdk"] || allDeps["@modelcontextprotocol/server"]);
|
|
722
1229
|
} catch {
|
|
@@ -725,18 +1232,18 @@ function detectProject() {
|
|
|
725
1232
|
}
|
|
726
1233
|
function findTsEntryFile() {
|
|
727
1234
|
try {
|
|
728
|
-
const pkg = JSON.parse(
|
|
1235
|
+
const pkg = JSON.parse(readFileSync4(resolve3("package.json"), "utf-8"));
|
|
729
1236
|
if (pkg.bin) {
|
|
730
1237
|
const binPath = typeof pkg.bin === "string" ? pkg.bin : Object.values(pkg.bin)[0];
|
|
731
1238
|
if (typeof binPath === "string") {
|
|
732
1239
|
const srcPath = binPath.replace(/^\.\/dist\//, "./src/").replace(/\.js$/, ".ts");
|
|
733
|
-
if (
|
|
734
|
-
if (
|
|
1240
|
+
if (existsSync4(resolve3(srcPath))) return resolve3(srcPath);
|
|
1241
|
+
if (existsSync4(resolve3(binPath))) return resolve3(binPath);
|
|
735
1242
|
}
|
|
736
1243
|
}
|
|
737
1244
|
if (pkg.main) {
|
|
738
1245
|
const srcPath = pkg.main.replace(/^\.\/dist\//, "./src/").replace(/\.js$/, ".ts");
|
|
739
|
-
if (
|
|
1246
|
+
if (existsSync4(resolve3(srcPath))) return resolve3(srcPath);
|
|
740
1247
|
}
|
|
741
1248
|
} catch {
|
|
742
1249
|
}
|
|
@@ -750,9 +1257,9 @@ function findTsEntryFile() {
|
|
|
750
1257
|
];
|
|
751
1258
|
for (const c3 of candidates) {
|
|
752
1259
|
const full = resolve3(c3);
|
|
753
|
-
if (
|
|
1260
|
+
if (existsSync4(full)) {
|
|
754
1261
|
try {
|
|
755
|
-
const content =
|
|
1262
|
+
const content = readFileSync4(full, "utf-8");
|
|
756
1263
|
if (content.includes("McpServer") || content.includes("McpServer")) {
|
|
757
1264
|
return full;
|
|
758
1265
|
}
|
|
@@ -761,39 +1268,39 @@ function findTsEntryFile() {
|
|
|
761
1268
|
}
|
|
762
1269
|
}
|
|
763
1270
|
for (const c3 of candidates) {
|
|
764
|
-
if (
|
|
1271
|
+
if (existsSync4(resolve3(c3))) return resolve3(c3);
|
|
765
1272
|
}
|
|
766
1273
|
return null;
|
|
767
1274
|
}
|
|
768
1275
|
function detectPackageManager() {
|
|
769
|
-
if (
|
|
770
|
-
if (
|
|
1276
|
+
if (existsSync4(resolve3("pnpm-lock.yaml"))) return "pnpm";
|
|
1277
|
+
if (existsSync4(resolve3("yarn.lock"))) return "yarn";
|
|
771
1278
|
return "npm";
|
|
772
1279
|
}
|
|
773
1280
|
function installSdk() {
|
|
774
1281
|
try {
|
|
775
|
-
const pkg = JSON.parse(
|
|
1282
|
+
const pkg = JSON.parse(readFileSync4(resolve3("package.json"), "utf-8"));
|
|
776
1283
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
777
1284
|
if (allDeps["@solongate/sdk"]) {
|
|
778
|
-
|
|
1285
|
+
log3(" @solongate/sdk already installed");
|
|
779
1286
|
return true;
|
|
780
1287
|
}
|
|
781
1288
|
} catch {
|
|
782
1289
|
}
|
|
783
1290
|
const pm = detectPackageManager();
|
|
784
1291
|
const cmd = pm === "yarn" ? "yarn add @solongate/sdk" : `${pm} install @solongate/sdk`;
|
|
785
|
-
|
|
1292
|
+
log3(` Installing @solongate/sdk via ${pm}...`);
|
|
786
1293
|
try {
|
|
787
1294
|
execSync(cmd, { stdio: "pipe", cwd: process.cwd() });
|
|
788
1295
|
return true;
|
|
789
1296
|
} catch (err) {
|
|
790
|
-
|
|
791
|
-
|
|
1297
|
+
log3(` Failed to install: ${err instanceof Error ? err.message : String(err)}`);
|
|
1298
|
+
log3(" You can install manually: npm install @solongate/sdk");
|
|
792
1299
|
return false;
|
|
793
1300
|
}
|
|
794
1301
|
}
|
|
795
1302
|
function injectTypeScript(filePath) {
|
|
796
|
-
const original =
|
|
1303
|
+
const original = readFileSync4(filePath, "utf-8");
|
|
797
1304
|
const changes = [];
|
|
798
1305
|
let modified = original;
|
|
799
1306
|
if (modified.includes("SecureMcpServer")) {
|
|
@@ -871,14 +1378,14 @@ function findImportInsertPosition(content) {
|
|
|
871
1378
|
}
|
|
872
1379
|
function showDiff(result) {
|
|
873
1380
|
if (result.original === result.modified) {
|
|
874
|
-
|
|
1381
|
+
log3(" No changes needed.");
|
|
875
1382
|
return;
|
|
876
1383
|
}
|
|
877
1384
|
const origLines = result.original.split("\n");
|
|
878
1385
|
const modLines = result.modified.split("\n");
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
1386
|
+
log3("");
|
|
1387
|
+
log3(` File: ${result.file}`);
|
|
1388
|
+
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
1389
|
const maxLines = Math.max(origLines.length, modLines.length);
|
|
883
1390
|
let diffCount = 0;
|
|
884
1391
|
for (let i = 0; i < maxLines; i++) {
|
|
@@ -886,99 +1393,99 @@ function showDiff(result) {
|
|
|
886
1393
|
const mod = modLines[i];
|
|
887
1394
|
if (orig !== mod) {
|
|
888
1395
|
if (diffCount < 30) {
|
|
889
|
-
if (orig !== void 0)
|
|
890
|
-
if (mod !== void 0)
|
|
1396
|
+
if (orig !== void 0) log3(` - ${orig}`);
|
|
1397
|
+
if (mod !== void 0) log3(` + ${mod}`);
|
|
891
1398
|
}
|
|
892
1399
|
diffCount++;
|
|
893
1400
|
}
|
|
894
1401
|
}
|
|
895
1402
|
if (diffCount > 30) {
|
|
896
|
-
|
|
1403
|
+
log3(` ... and ${diffCount - 30} more line changes`);
|
|
897
1404
|
}
|
|
898
|
-
|
|
1405
|
+
log3("");
|
|
899
1406
|
}
|
|
900
1407
|
async function main2() {
|
|
901
1408
|
const opts = parseInjectArgs(process.argv);
|
|
902
1409
|
printBanner("Inject SDK");
|
|
903
1410
|
if (!detectProject()) {
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
1411
|
+
log3(" Could not detect a TypeScript MCP server project.");
|
|
1412
|
+
log3(" Make sure you are in a project directory with:");
|
|
1413
|
+
log3(" package.json + @modelcontextprotocol/sdk in dependencies");
|
|
1414
|
+
log3("");
|
|
1415
|
+
log3(" To create a new MCP server: npx @solongate/proxy create <name>");
|
|
909
1416
|
process.exit(1);
|
|
910
1417
|
}
|
|
911
|
-
|
|
1418
|
+
log3(" Language: TypeScript");
|
|
912
1419
|
const entryFile = opts.file ? resolve3(opts.file) : findTsEntryFile();
|
|
913
|
-
if (!entryFile || !
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1420
|
+
if (!entryFile || !existsSync4(entryFile)) {
|
|
1421
|
+
log3(` Could not find entry file.${opts.file ? ` File not found: ${opts.file}` : ""}`);
|
|
1422
|
+
log3("");
|
|
1423
|
+
log3(" Specify it manually: --file <path>");
|
|
1424
|
+
log3("");
|
|
1425
|
+
log3(" Common entry points:");
|
|
1426
|
+
log3(" src/index.ts, src/server.ts, index.ts");
|
|
920
1427
|
process.exit(1);
|
|
921
1428
|
}
|
|
922
|
-
|
|
923
|
-
|
|
1429
|
+
log3(` Entry: ${entryFile}`);
|
|
1430
|
+
log3("");
|
|
924
1431
|
const backupPath = entryFile + ".solongate-backup";
|
|
925
1432
|
if (opts.restore) {
|
|
926
|
-
if (!
|
|
927
|
-
|
|
1433
|
+
if (!existsSync4(backupPath)) {
|
|
1434
|
+
log3(" No backup found. Nothing to restore.");
|
|
928
1435
|
process.exit(1);
|
|
929
1436
|
}
|
|
930
1437
|
copyFileSync2(backupPath, entryFile);
|
|
931
|
-
|
|
932
|
-
|
|
1438
|
+
log3(` Restored original file from backup.`);
|
|
1439
|
+
log3(` Backup: ${backupPath}`);
|
|
933
1440
|
process.exit(0);
|
|
934
1441
|
}
|
|
935
1442
|
if (!opts.skipInstall && !opts.dryRun) {
|
|
936
1443
|
installSdk();
|
|
937
|
-
|
|
1444
|
+
log3("");
|
|
938
1445
|
}
|
|
939
1446
|
const result = injectTypeScript(entryFile);
|
|
940
|
-
|
|
1447
|
+
log3(` Changes (${result.changes.length}):`);
|
|
941
1448
|
for (const change of result.changes) {
|
|
942
|
-
|
|
1449
|
+
log3(` - ${change}`);
|
|
943
1450
|
}
|
|
944
1451
|
if (result.changes.length === 1 && result.changes[0].includes("Already injected")) {
|
|
945
|
-
|
|
946
|
-
|
|
1452
|
+
log3("");
|
|
1453
|
+
log3(" Your MCP server is already protected by SolonGate!");
|
|
947
1454
|
process.exit(0);
|
|
948
1455
|
}
|
|
949
1456
|
if (result.original === result.modified) {
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
1457
|
+
log3("");
|
|
1458
|
+
log3(" No changes were made. The file may not contain recognizable MCP patterns.");
|
|
1459
|
+
log3(" See docs: https://solongate.com/docs/integration");
|
|
953
1460
|
process.exit(0);
|
|
954
1461
|
}
|
|
955
1462
|
if (opts.dryRun) {
|
|
956
|
-
|
|
957
|
-
|
|
1463
|
+
log3("");
|
|
1464
|
+
log3(" --- DRY RUN (no changes written) ---");
|
|
958
1465
|
showDiff(result);
|
|
959
|
-
|
|
1466
|
+
log3(" To apply: npx @solongate/proxy inject");
|
|
960
1467
|
process.exit(0);
|
|
961
1468
|
}
|
|
962
|
-
if (!
|
|
1469
|
+
if (!existsSync4(backupPath)) {
|
|
963
1470
|
copyFileSync2(entryFile, backupPath);
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1471
|
+
log3("");
|
|
1472
|
+
log3(` Backup: ${backupPath}`);
|
|
1473
|
+
}
|
|
1474
|
+
writeFileSync3(entryFile, result.modified);
|
|
1475
|
+
log3("");
|
|
1476
|
+
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");
|
|
1477
|
+
log3(" \u2502 SolonGate SDK injected successfully! \u2502");
|
|
1478
|
+
log3(" \u2502 \u2502");
|
|
1479
|
+
log3(" \u2502 McpServer \u2192 SecureMcpServer \u2502");
|
|
1480
|
+
log3(" \u2502 All tool() calls are now auto-protected. \u2502");
|
|
1481
|
+
log3(" \u2502 \u2502");
|
|
1482
|
+
log3(" \u2502 Set your API key: \u2502");
|
|
1483
|
+
log3(" \u2502 export SOLONGATE_API_KEY=sg_live_xxx \u2502");
|
|
1484
|
+
log3(" \u2502 \u2502");
|
|
1485
|
+
log3(" \u2502 To undo: \u2502");
|
|
1486
|
+
log3(" \u2502 npx @solongate/proxy inject --restore \u2502");
|
|
1487
|
+
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");
|
|
1488
|
+
log3("");
|
|
982
1489
|
}
|
|
983
1490
|
var c;
|
|
984
1491
|
var init_inject = __esm({
|
|
@@ -999,7 +1506,7 @@ var init_inject = __esm({
|
|
|
999
1506
|
blue6: "\x1B[38;2;170;200;250m"
|
|
1000
1507
|
};
|
|
1001
1508
|
main2().catch((err) => {
|
|
1002
|
-
|
|
1509
|
+
log3(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
1003
1510
|
process.exit(1);
|
|
1004
1511
|
});
|
|
1005
1512
|
}
|
|
@@ -1007,10 +1514,10 @@ var init_inject = __esm({
|
|
|
1007
1514
|
|
|
1008
1515
|
// src/create.ts
|
|
1009
1516
|
var create_exports = {};
|
|
1010
|
-
import { mkdirSync as mkdirSync2, writeFileSync as
|
|
1517
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync4, existsSync as existsSync5 } from "fs";
|
|
1011
1518
|
import { resolve as resolve4, join as join2 } from "path";
|
|
1012
1519
|
import { execSync as execSync2 } from "child_process";
|
|
1013
|
-
function
|
|
1520
|
+
function log4(msg) {
|
|
1014
1521
|
process.stderr.write(msg + "\n");
|
|
1015
1522
|
}
|
|
1016
1523
|
function printBanner2(subtitle) {
|
|
@@ -1023,13 +1530,13 @@ function printBanner2(subtitle) {
|
|
|
1023
1530
|
" \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
1531
|
];
|
|
1025
1532
|
const colors = [c2.blue1, c2.blue2, c2.blue3, c2.blue4, c2.blue5, c2.blue6];
|
|
1026
|
-
|
|
1533
|
+
log4("");
|
|
1027
1534
|
for (let i = 0; i < lines.length; i++) {
|
|
1028
|
-
|
|
1535
|
+
log4(`${c2.bold}${colors[i]}${lines[i]}${c2.reset}`);
|
|
1029
1536
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1537
|
+
log4("");
|
|
1538
|
+
log4(` ${c2.dim}${c2.italic}${subtitle}${c2.reset}`);
|
|
1539
|
+
log4("");
|
|
1033
1540
|
}
|
|
1034
1541
|
function withSpinner(message, fn) {
|
|
1035
1542
|
const frames = ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"];
|
|
@@ -1078,20 +1585,20 @@ function parseCreateArgs(argv) {
|
|
|
1078
1585
|
}
|
|
1079
1586
|
}
|
|
1080
1587
|
if (!opts.name) {
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1588
|
+
log4("");
|
|
1589
|
+
log4(" Error: Project name required.");
|
|
1590
|
+
log4("");
|
|
1591
|
+
log4(" Usage: npx @solongate/proxy create <name>");
|
|
1592
|
+
log4("");
|
|
1593
|
+
log4(" Examples:");
|
|
1594
|
+
log4(" npx @solongate/proxy create my-mcp-server");
|
|
1595
|
+
log4(" npx @solongate/proxy create weather-api");
|
|
1089
1596
|
process.exit(1);
|
|
1090
1597
|
}
|
|
1091
1598
|
return opts;
|
|
1092
1599
|
}
|
|
1093
1600
|
function printHelp3() {
|
|
1094
|
-
|
|
1601
|
+
log4(`
|
|
1095
1602
|
SolonGate Create \u2014 Scaffold a secure MCP server in seconds
|
|
1096
1603
|
|
|
1097
1604
|
USAGE
|
|
@@ -1108,7 +1615,7 @@ EXAMPLES
|
|
|
1108
1615
|
`);
|
|
1109
1616
|
}
|
|
1110
1617
|
function createProject(dir, name, _policy) {
|
|
1111
|
-
|
|
1618
|
+
writeFileSync4(
|
|
1112
1619
|
join2(dir, "package.json"),
|
|
1113
1620
|
JSON.stringify(
|
|
1114
1621
|
{
|
|
@@ -1138,7 +1645,7 @@ function createProject(dir, name, _policy) {
|
|
|
1138
1645
|
2
|
|
1139
1646
|
) + "\n"
|
|
1140
1647
|
);
|
|
1141
|
-
|
|
1648
|
+
writeFileSync4(
|
|
1142
1649
|
join2(dir, "tsconfig.json"),
|
|
1143
1650
|
JSON.stringify(
|
|
1144
1651
|
{
|
|
@@ -1160,7 +1667,7 @@ function createProject(dir, name, _policy) {
|
|
|
1160
1667
|
) + "\n"
|
|
1161
1668
|
);
|
|
1162
1669
|
mkdirSync2(join2(dir, "src"), { recursive: true });
|
|
1163
|
-
|
|
1670
|
+
writeFileSync4(
|
|
1164
1671
|
join2(dir, "src", "index.ts"),
|
|
1165
1672
|
`#!/usr/bin/env node
|
|
1166
1673
|
|
|
@@ -1205,7 +1712,7 @@ console.log('');
|
|
|
1205
1712
|
console.log('Press Ctrl+C to stop.');
|
|
1206
1713
|
`
|
|
1207
1714
|
);
|
|
1208
|
-
|
|
1715
|
+
writeFileSync4(
|
|
1209
1716
|
join2(dir, ".mcp.json"),
|
|
1210
1717
|
JSON.stringify(
|
|
1211
1718
|
{
|
|
@@ -1223,12 +1730,12 @@ console.log('Press Ctrl+C to stop.');
|
|
|
1223
1730
|
2
|
|
1224
1731
|
) + "\n"
|
|
1225
1732
|
);
|
|
1226
|
-
|
|
1733
|
+
writeFileSync4(
|
|
1227
1734
|
join2(dir, ".env"),
|
|
1228
1735
|
`SOLONGATE_API_KEY=sg_live_YOUR_KEY_HERE
|
|
1229
1736
|
`
|
|
1230
1737
|
);
|
|
1231
|
-
|
|
1738
|
+
writeFileSync4(
|
|
1232
1739
|
join2(dir, ".gitignore"),
|
|
1233
1740
|
`node_modules/
|
|
1234
1741
|
dist/
|
|
@@ -1243,8 +1750,8 @@ async function main3() {
|
|
|
1243
1750
|
const opts = parseCreateArgs(process.argv);
|
|
1244
1751
|
const dir = resolve4(opts.name);
|
|
1245
1752
|
printBanner2("Create MCP Server");
|
|
1246
|
-
if (
|
|
1247
|
-
|
|
1753
|
+
if (existsSync5(dir)) {
|
|
1754
|
+
log4(` ${c2.red}Error:${c2.reset} Directory "${opts.name}" already exists.`);
|
|
1248
1755
|
process.exit(1);
|
|
1249
1756
|
}
|
|
1250
1757
|
withSpinner(`Setting up ${opts.name}...`, () => {
|
|
@@ -1256,19 +1763,19 @@ async function main3() {
|
|
|
1256
1763
|
execSync2("npm install", { cwd: dir, stdio: "pipe" });
|
|
1257
1764
|
});
|
|
1258
1765
|
}
|
|
1259
|
-
|
|
1766
|
+
log4("");
|
|
1260
1767
|
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1261
1768
|
const W = 52;
|
|
1262
1769
|
const hr = "\u2500".repeat(W + 2);
|
|
1263
1770
|
const bLine = (text) => {
|
|
1264
1771
|
const visible = stripAnsi(text);
|
|
1265
1772
|
const padding = W - visible.length;
|
|
1266
|
-
|
|
1773
|
+
log4(` ${c2.dim}\u2502${c2.reset} ${text}${" ".repeat(Math.max(0, padding))} ${c2.dim}\u2502${c2.reset}`);
|
|
1267
1774
|
};
|
|
1268
|
-
const bEmpty = () =>
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1775
|
+
const bEmpty = () => log4(` ${c2.dim}\u2502${c2.reset} ${" ".repeat(W)} ${c2.dim}\u2502${c2.reset}`);
|
|
1776
|
+
log4(` ${c2.dim}\u256D${hr}\u256E${c2.reset}`);
|
|
1777
|
+
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}`);
|
|
1778
|
+
log4(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
|
|
1272
1779
|
bEmpty();
|
|
1273
1780
|
bLine(`${c2.yellow}Next steps:${c2.reset}`);
|
|
1274
1781
|
bEmpty();
|
|
@@ -1277,7 +1784,7 @@ async function main3() {
|
|
|
1277
1784
|
bLine(` ${c2.cyan}$${c2.reset} npm run dev ${c2.dim}# dev mode${c2.reset}`);
|
|
1278
1785
|
bLine(` ${c2.cyan}$${c2.reset} npm start ${c2.dim}# production${c2.reset}`);
|
|
1279
1786
|
bEmpty();
|
|
1280
|
-
|
|
1787
|
+
log4(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
|
|
1281
1788
|
bEmpty();
|
|
1282
1789
|
bLine(`${c2.yellow}Set your API key:${c2.reset}`);
|
|
1283
1790
|
bEmpty();
|
|
@@ -1285,14 +1792,14 @@ async function main3() {
|
|
|
1285
1792
|
bEmpty();
|
|
1286
1793
|
bLine(`${c2.dim}https://dashboard.solongate.com/api-keys/${c2.reset}`);
|
|
1287
1794
|
bEmpty();
|
|
1288
|
-
|
|
1795
|
+
log4(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
|
|
1289
1796
|
bEmpty();
|
|
1290
1797
|
bLine(`${c2.yellow}Use with OpenClaw:${c2.reset}`);
|
|
1291
1798
|
bEmpty();
|
|
1292
1799
|
bLine(` ${c2.cyan}$${c2.reset} solongate-proxy -- npx @openclaw/server`);
|
|
1293
1800
|
bLine(` ${c2.dim}Wraps OpenClaw servers with SolonGate protection${c2.reset}`);
|
|
1294
1801
|
bEmpty();
|
|
1295
|
-
|
|
1802
|
+
log4(` ${c2.dim}\u251C${hr}\u2524${c2.reset}`);
|
|
1296
1803
|
bEmpty();
|
|
1297
1804
|
bLine(`${c2.yellow}Use with Claude Code / Cursor / MCP client:${c2.reset}`);
|
|
1298
1805
|
bEmpty();
|
|
@@ -1305,512 +1812,190 @@ async function main3() {
|
|
|
1305
1812
|
bLine(` ${c2.dim}1.${c2.reset} Replace ${c2.blue3}sg_live_YOUR_KEY_HERE${c2.reset} in .env`);
|
|
1306
1813
|
bLine(` ${c2.dim}2.${c2.reset} ${c2.cyan}$${c2.reset} npm run build && npm start`);
|
|
1307
1814
|
bEmpty();
|
|
1308
|
-
|
|
1309
|
-
|
|
1815
|
+
log4(` ${c2.dim}\u2570${hr}\u256F${c2.reset}`);
|
|
1816
|
+
log4("");
|
|
1310
1817
|
}
|
|
1311
1818
|
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
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
const
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
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;
|
|
1819
|
+
var init_create = __esm({
|
|
1820
|
+
"src/create.ts"() {
|
|
1821
|
+
"use strict";
|
|
1822
|
+
c2 = {
|
|
1823
|
+
reset: "\x1B[0m",
|
|
1824
|
+
bold: "\x1B[1m",
|
|
1825
|
+
dim: "\x1B[2m",
|
|
1826
|
+
italic: "\x1B[3m",
|
|
1827
|
+
white: "\x1B[97m",
|
|
1828
|
+
gray: "\x1B[90m",
|
|
1829
|
+
blue1: "\x1B[38;2;20;50;160m",
|
|
1830
|
+
blue2: "\x1B[38;2;40;80;190m",
|
|
1831
|
+
blue3: "\x1B[38;2;60;110;215m",
|
|
1832
|
+
blue4: "\x1B[38;2;90;140;230m",
|
|
1833
|
+
blue5: "\x1B[38;2;130;170;240m",
|
|
1834
|
+
blue6: "\x1B[38;2;170;200;250m",
|
|
1835
|
+
green: "\x1B[38;2;80;200;120m",
|
|
1836
|
+
red: "\x1B[38;2;220;80;80m",
|
|
1837
|
+
cyan: "\x1B[38;2;100;200;220m",
|
|
1838
|
+
yellow: "\x1B[38;2;220;200;80m",
|
|
1839
|
+
bgBlue: "\x1B[48;2;20;50;160m"
|
|
1840
|
+
};
|
|
1841
|
+
main3().catch((err) => {
|
|
1842
|
+
log4(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
1843
|
+
process.exit(1);
|
|
1844
|
+
});
|
|
1845
|
+
}
|
|
1846
|
+
});
|
|
1847
|
+
|
|
1848
|
+
// src/pull-push.ts
|
|
1849
|
+
var pull_push_exports = {};
|
|
1850
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync6 } from "fs";
|
|
1851
|
+
import { resolve as resolve5 } from "path";
|
|
1852
|
+
function loadEnv() {
|
|
1853
|
+
if (process.env.SOLONGATE_API_KEY) return;
|
|
1854
|
+
const envPath = resolve5(".env");
|
|
1855
|
+
if (!existsSync6(envPath)) return;
|
|
1856
|
+
try {
|
|
1857
|
+
const content = readFileSync5(envPath, "utf-8");
|
|
1858
|
+
for (const line of content.split("\n")) {
|
|
1859
|
+
const trimmed = line.trim();
|
|
1860
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1861
|
+
const eq = trimmed.indexOf("=");
|
|
1862
|
+
if (eq === -1) continue;
|
|
1863
|
+
const key = trimmed.slice(0, eq).trim();
|
|
1864
|
+
const val = trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
|
|
1865
|
+
if (key === "SOLONGATE_API_KEY") {
|
|
1866
|
+
process.env.SOLONGATE_API_KEY = val;
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
} catch {
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
function parseCliArgs() {
|
|
1874
|
+
loadEnv();
|
|
1875
|
+
const args = process.argv.slice(2);
|
|
1876
|
+
const command = args[0];
|
|
1877
|
+
let apiKey = process.env.SOLONGATE_API_KEY || "";
|
|
1878
|
+
let file = "policy.json";
|
|
1879
|
+
let policyId;
|
|
1880
|
+
for (let i = 1; i < args.length; i++) {
|
|
1881
|
+
switch (args[i]) {
|
|
1721
1882
|
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];
|
|
1883
|
+
apiKey = args[++i];
|
|
1729
1884
|
break;
|
|
1730
|
-
case "--
|
|
1731
|
-
|
|
1885
|
+
case "--policy":
|
|
1886
|
+
case "--output":
|
|
1887
|
+
case "--file":
|
|
1888
|
+
case "-f":
|
|
1889
|
+
case "-o":
|
|
1890
|
+
file = args[++i];
|
|
1732
1891
|
break;
|
|
1733
|
-
case "--
|
|
1734
|
-
|
|
1892
|
+
case "--policy-id":
|
|
1893
|
+
case "--id":
|
|
1894
|
+
policyId = args[++i];
|
|
1735
1895
|
break;
|
|
1736
1896
|
}
|
|
1737
1897
|
}
|
|
1738
1898
|
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
|
-
);
|
|
1899
|
+
log5("ERROR: API key not found.");
|
|
1900
|
+
log5("");
|
|
1901
|
+
log5("Set it in .env file:");
|
|
1902
|
+
log5(" SOLONGATE_API_KEY=sg_live_...");
|
|
1903
|
+
log5("");
|
|
1904
|
+
log5("Or pass via environment:");
|
|
1905
|
+
log5(` SOLONGATE_API_KEY=sg_live_... solongate-proxy ${command}`);
|
|
1906
|
+
process.exit(1);
|
|
1752
1907
|
}
|
|
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
|
-
};
|
|
1908
|
+
if (!apiKey.startsWith("sg_live_")) {
|
|
1909
|
+
log5("ERROR: Pull/push requires a live API key (sg_live_...).");
|
|
1910
|
+
process.exit(1);
|
|
1771
1911
|
}
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1912
|
+
return { command, apiKey, file: resolve5(file), policyId };
|
|
1913
|
+
}
|
|
1914
|
+
async function pull(apiKey, file, policyId) {
|
|
1915
|
+
const apiUrl = "https://api.solongate.com";
|
|
1916
|
+
log5(`Pulling policy from dashboard...`);
|
|
1917
|
+
const policy = await fetchCloudPolicy(apiKey, apiUrl, policyId);
|
|
1918
|
+
const json = JSON.stringify(policy, null, 2) + "\n";
|
|
1919
|
+
writeFileSync5(file, json, "utf-8");
|
|
1920
|
+
log5(`Saved: ${file}`);
|
|
1921
|
+
log5(` Name: ${policy.name}`);
|
|
1922
|
+
log5(` Version: ${policy.version}`);
|
|
1923
|
+
log5(` Rules: ${policy.rules.length}`);
|
|
1924
|
+
log5("");
|
|
1925
|
+
log5("Done. Policy pulled from dashboard to local file.");
|
|
1926
|
+
}
|
|
1927
|
+
async function push(apiKey, file) {
|
|
1928
|
+
const apiUrl = "https://api.solongate.com";
|
|
1929
|
+
if (!existsSync6(file)) {
|
|
1930
|
+
log5(`ERROR: File not found: ${file}`);
|
|
1931
|
+
process.exit(1);
|
|
1790
1932
|
}
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
);
|
|
1933
|
+
const content = readFileSync5(file, "utf-8");
|
|
1934
|
+
let policy;
|
|
1935
|
+
try {
|
|
1936
|
+
policy = JSON.parse(content);
|
|
1937
|
+
} catch {
|
|
1938
|
+
log5(`ERROR: Invalid JSON in ${file}`);
|
|
1939
|
+
process.exit(1);
|
|
1795
1940
|
}
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1941
|
+
log5(`Pushing policy to dashboard...`);
|
|
1942
|
+
log5(` File: ${file}`);
|
|
1943
|
+
log5(` Name: ${policy.name || "Unnamed"}`);
|
|
1944
|
+
log5(` Rules: ${(policy.rules || []).length}`);
|
|
1945
|
+
const res = await fetch(`${apiUrl}/api/v1/policies`, {
|
|
1946
|
+
method: "POST",
|
|
1947
|
+
headers: {
|
|
1948
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
1949
|
+
"Content-Type": "application/json"
|
|
1803
1950
|
},
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1951
|
+
body: JSON.stringify({
|
|
1952
|
+
id: policy.id || "default",
|
|
1953
|
+
name: policy.name || "Local Policy",
|
|
1954
|
+
description: policy.description || "Pushed from local file",
|
|
1955
|
+
version: policy.version || 1,
|
|
1956
|
+
rules: policy.rules || []
|
|
1957
|
+
})
|
|
1958
|
+
});
|
|
1959
|
+
if (!res.ok) {
|
|
1960
|
+
const body = await res.text().catch(() => "");
|
|
1961
|
+
log5(`ERROR: Push failed (${res.status}): ${body}`);
|
|
1962
|
+
process.exit(1);
|
|
1963
|
+
}
|
|
1964
|
+
const data = await res.json();
|
|
1965
|
+
log5(` Cloud version: ${data._version ?? "created"}`);
|
|
1966
|
+
log5("");
|
|
1967
|
+
log5("Done. Policy pushed from local file to dashboard.");
|
|
1968
|
+
}
|
|
1969
|
+
async function main4() {
|
|
1970
|
+
const { command, apiKey, file, policyId } = parseCliArgs();
|
|
1971
|
+
try {
|
|
1972
|
+
if (command === "pull") {
|
|
1973
|
+
await pull(apiKey, file, policyId);
|
|
1974
|
+
} else if (command === "push") {
|
|
1975
|
+
await push(apiKey, file);
|
|
1976
|
+
} else {
|
|
1977
|
+
log5(`Unknown command: ${command}`);
|
|
1978
|
+
log5("Usage: solongate-proxy pull|push [--file policy.json]");
|
|
1979
|
+
process.exit(1);
|
|
1980
|
+
}
|
|
1981
|
+
} catch (err) {
|
|
1982
|
+
log5(`ERROR: ${err instanceof Error ? err.message : String(err)}`);
|
|
1983
|
+
process.exit(1);
|
|
1984
|
+
}
|
|
1813
1985
|
}
|
|
1986
|
+
var log5;
|
|
1987
|
+
var init_pull_push = __esm({
|
|
1988
|
+
"src/pull-push.ts"() {
|
|
1989
|
+
"use strict";
|
|
1990
|
+
init_config();
|
|
1991
|
+
log5 = (...args) => process.stderr.write(`[SolonGate] ${args.map(String).join(" ")}
|
|
1992
|
+
`);
|
|
1993
|
+
main4();
|
|
1994
|
+
}
|
|
1995
|
+
});
|
|
1996
|
+
|
|
1997
|
+
// src/index.ts
|
|
1998
|
+
init_config();
|
|
1814
1999
|
|
|
1815
2000
|
// src/proxy.ts
|
|
1816
2001
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -3348,7 +3533,202 @@ var SolonGate = class {
|
|
|
3348
3533
|
};
|
|
3349
3534
|
|
|
3350
3535
|
// src/proxy.ts
|
|
3351
|
-
|
|
3536
|
+
init_config();
|
|
3537
|
+
|
|
3538
|
+
// src/sync.ts
|
|
3539
|
+
init_config();
|
|
3540
|
+
import { readFileSync as readFileSync2, writeFileSync, watch, existsSync as existsSync2 } from "fs";
|
|
3541
|
+
var log = (...args) => process.stderr.write(`[SolonGate Sync] ${args.map(String).join(" ")}
|
|
3542
|
+
`);
|
|
3543
|
+
var PolicySyncManager = class {
|
|
3544
|
+
localPath;
|
|
3545
|
+
apiKey;
|
|
3546
|
+
apiUrl;
|
|
3547
|
+
pollIntervalMs;
|
|
3548
|
+
onPolicyUpdate;
|
|
3549
|
+
currentPolicy;
|
|
3550
|
+
localVersion;
|
|
3551
|
+
cloudVersion;
|
|
3552
|
+
skipNextWatch = false;
|
|
3553
|
+
debounceTimer = null;
|
|
3554
|
+
pollTimer = null;
|
|
3555
|
+
watcher = null;
|
|
3556
|
+
isLiveKey;
|
|
3557
|
+
constructor(opts) {
|
|
3558
|
+
this.localPath = opts.localPath;
|
|
3559
|
+
this.apiKey = opts.apiKey;
|
|
3560
|
+
this.apiUrl = opts.apiUrl;
|
|
3561
|
+
this.pollIntervalMs = opts.pollIntervalMs ?? 6e4;
|
|
3562
|
+
this.onPolicyUpdate = opts.onPolicyUpdate;
|
|
3563
|
+
this.currentPolicy = opts.initialPolicy;
|
|
3564
|
+
this.localVersion = opts.initialPolicy.version ?? 0;
|
|
3565
|
+
this.cloudVersion = 0;
|
|
3566
|
+
this.isLiveKey = opts.apiKey.startsWith("sg_live_");
|
|
3567
|
+
}
|
|
3568
|
+
/**
|
|
3569
|
+
* Start watching local file and polling cloud.
|
|
3570
|
+
*/
|
|
3571
|
+
start() {
|
|
3572
|
+
if (this.localPath && existsSync2(this.localPath)) {
|
|
3573
|
+
this.startFileWatcher();
|
|
3574
|
+
}
|
|
3575
|
+
if (this.isLiveKey) {
|
|
3576
|
+
this.pushToCloud(this.currentPolicy).catch(() => {
|
|
3577
|
+
});
|
|
3578
|
+
this.startPolling();
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3581
|
+
/**
|
|
3582
|
+
* Stop all watchers and timers.
|
|
3583
|
+
*/
|
|
3584
|
+
stop() {
|
|
3585
|
+
if (this.watcher) {
|
|
3586
|
+
this.watcher.close();
|
|
3587
|
+
this.watcher = null;
|
|
3588
|
+
}
|
|
3589
|
+
if (this.pollTimer) {
|
|
3590
|
+
clearInterval(this.pollTimer);
|
|
3591
|
+
this.pollTimer = null;
|
|
3592
|
+
}
|
|
3593
|
+
if (this.debounceTimer) {
|
|
3594
|
+
clearTimeout(this.debounceTimer);
|
|
3595
|
+
this.debounceTimer = null;
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
/**
|
|
3599
|
+
* Watch local file for changes (debounced).
|
|
3600
|
+
*/
|
|
3601
|
+
startFileWatcher() {
|
|
3602
|
+
if (!this.localPath) return;
|
|
3603
|
+
const filePath = this.localPath;
|
|
3604
|
+
try {
|
|
3605
|
+
this.watcher = watch(filePath, () => {
|
|
3606
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
3607
|
+
this.debounceTimer = setTimeout(() => this.onFileChange(filePath), 300);
|
|
3608
|
+
});
|
|
3609
|
+
log(`Watching ${filePath} for changes`);
|
|
3610
|
+
} catch (err) {
|
|
3611
|
+
log(`File watch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
/**
|
|
3615
|
+
* Handle local file change event.
|
|
3616
|
+
*/
|
|
3617
|
+
async onFileChange(filePath) {
|
|
3618
|
+
if (this.skipNextWatch) {
|
|
3619
|
+
this.skipNextWatch = false;
|
|
3620
|
+
return;
|
|
3621
|
+
}
|
|
3622
|
+
try {
|
|
3623
|
+
if (!existsSync2(filePath)) {
|
|
3624
|
+
log("Policy file deleted \u2014 keeping current policy");
|
|
3625
|
+
return;
|
|
3626
|
+
}
|
|
3627
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
3628
|
+
const newPolicy = JSON.parse(content);
|
|
3629
|
+
if (newPolicy.version <= this.localVersion) {
|
|
3630
|
+
newPolicy.version = Math.max(this.localVersion, this.cloudVersion) + 1;
|
|
3631
|
+
this.writeToFile(newPolicy);
|
|
3632
|
+
}
|
|
3633
|
+
if (this.policiesEqual(newPolicy, this.currentPolicy)) return;
|
|
3634
|
+
log(`File changed: ${newPolicy.name} v${newPolicy.version}`);
|
|
3635
|
+
this.localVersion = newPolicy.version;
|
|
3636
|
+
this.currentPolicy = newPolicy;
|
|
3637
|
+
this.onPolicyUpdate(newPolicy);
|
|
3638
|
+
if (this.isLiveKey) {
|
|
3639
|
+
try {
|
|
3640
|
+
const result = await this.pushToCloud(newPolicy);
|
|
3641
|
+
this.cloudVersion = result.version;
|
|
3642
|
+
log(`Pushed to cloud: v${result.version}`);
|
|
3643
|
+
} catch (err) {
|
|
3644
|
+
log(`Cloud push failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3647
|
+
} catch (err) {
|
|
3648
|
+
log(`File read error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3649
|
+
}
|
|
3650
|
+
}
|
|
3651
|
+
/**
|
|
3652
|
+
* Poll cloud for policy changes.
|
|
3653
|
+
*/
|
|
3654
|
+
startPolling() {
|
|
3655
|
+
this.pollTimer = setInterval(() => this.onPollTick(), this.pollIntervalMs);
|
|
3656
|
+
}
|
|
3657
|
+
/**
|
|
3658
|
+
* Handle poll tick — fetch cloud policy and compare.
|
|
3659
|
+
*/
|
|
3660
|
+
async onPollTick() {
|
|
3661
|
+
try {
|
|
3662
|
+
const cloudPolicy = await fetchCloudPolicy(this.apiKey, this.apiUrl);
|
|
3663
|
+
const cloudVer = cloudPolicy.version ?? 0;
|
|
3664
|
+
if (cloudVer <= this.localVersion && this.policiesEqual(cloudPolicy, this.currentPolicy)) {
|
|
3665
|
+
return;
|
|
3666
|
+
}
|
|
3667
|
+
if (cloudVer > this.localVersion || !this.policiesEqual(cloudPolicy, this.currentPolicy)) {
|
|
3668
|
+
log(`Cloud update: ${cloudPolicy.name} v${cloudVer} (was v${this.localVersion})`);
|
|
3669
|
+
this.cloudVersion = cloudVer;
|
|
3670
|
+
this.localVersion = cloudVer;
|
|
3671
|
+
this.currentPolicy = cloudPolicy;
|
|
3672
|
+
this.onPolicyUpdate(cloudPolicy);
|
|
3673
|
+
if (this.localPath) {
|
|
3674
|
+
this.writeToFile(cloudPolicy);
|
|
3675
|
+
log(`Updated local file: ${this.localPath}`);
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
} catch {
|
|
3679
|
+
}
|
|
3680
|
+
}
|
|
3681
|
+
/**
|
|
3682
|
+
* Push policy to cloud API.
|
|
3683
|
+
*/
|
|
3684
|
+
async pushToCloud(policy) {
|
|
3685
|
+
const url = `${this.apiUrl}/api/v1/policies`;
|
|
3686
|
+
const res = await fetch(url, {
|
|
3687
|
+
method: "POST",
|
|
3688
|
+
headers: {
|
|
3689
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
3690
|
+
"Content-Type": "application/json"
|
|
3691
|
+
},
|
|
3692
|
+
body: JSON.stringify({
|
|
3693
|
+
id: policy.id || "default",
|
|
3694
|
+
name: policy.name || "Default Policy",
|
|
3695
|
+
description: policy.description || "Synced from proxy",
|
|
3696
|
+
version: policy.version || 1,
|
|
3697
|
+
rules: policy.rules
|
|
3698
|
+
})
|
|
3699
|
+
});
|
|
3700
|
+
if (!res.ok) {
|
|
3701
|
+
const body = await res.text().catch(() => "");
|
|
3702
|
+
throw new Error(`Push failed (${res.status}): ${body}`);
|
|
3703
|
+
}
|
|
3704
|
+
const data = await res.json();
|
|
3705
|
+
return { version: Number(data._version ?? policy.version) };
|
|
3706
|
+
}
|
|
3707
|
+
/**
|
|
3708
|
+
* Write policy to local file (with loop prevention).
|
|
3709
|
+
*/
|
|
3710
|
+
writeToFile(policy) {
|
|
3711
|
+
if (!this.localPath) return;
|
|
3712
|
+
this.skipNextWatch = true;
|
|
3713
|
+
try {
|
|
3714
|
+
const json = JSON.stringify(policy, null, 2) + "\n";
|
|
3715
|
+
writeFileSync(this.localPath, json, "utf-8");
|
|
3716
|
+
} catch (err) {
|
|
3717
|
+
log(`File write error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3718
|
+
this.skipNextWatch = false;
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
/**
|
|
3722
|
+
* Compare two policies by rules content (ignoring timestamps).
|
|
3723
|
+
*/
|
|
3724
|
+
policiesEqual(a, b) {
|
|
3725
|
+
if (a.id !== b.id || a.name !== b.name || a.rules.length !== b.rules.length) return false;
|
|
3726
|
+
return JSON.stringify(a.rules) === JSON.stringify(b.rules);
|
|
3727
|
+
}
|
|
3728
|
+
};
|
|
3729
|
+
|
|
3730
|
+
// src/proxy.ts
|
|
3731
|
+
var log2 = (...args) => process.stderr.write(`[SolonGate] ${args.map(String).join(" ")}
|
|
3352
3732
|
`);
|
|
3353
3733
|
var Mutex = class {
|
|
3354
3734
|
queue = [];
|
|
@@ -3358,8 +3738,8 @@ var Mutex = class {
|
|
|
3358
3738
|
this.locked = true;
|
|
3359
3739
|
return;
|
|
3360
3740
|
}
|
|
3361
|
-
return new Promise((
|
|
3362
|
-
this.queue.push(
|
|
3741
|
+
return new Promise((resolve6) => {
|
|
3742
|
+
this.queue.push(resolve6);
|
|
3363
3743
|
});
|
|
3364
3744
|
}
|
|
3365
3745
|
release() {
|
|
@@ -3377,6 +3757,7 @@ var SolonGateProxy = class {
|
|
|
3377
3757
|
client = null;
|
|
3378
3758
|
server = null;
|
|
3379
3759
|
callMutex = new Mutex();
|
|
3760
|
+
syncManager = null;
|
|
3380
3761
|
upstreamTools = [];
|
|
3381
3762
|
constructor(config) {
|
|
3382
3763
|
this.config = config;
|
|
@@ -3393,20 +3774,20 @@ var SolonGateProxy = class {
|
|
|
3393
3774
|
});
|
|
3394
3775
|
const warnings = this.gate.getWarnings();
|
|
3395
3776
|
for (const w of warnings) {
|
|
3396
|
-
|
|
3777
|
+
log2("WARNING:", w);
|
|
3397
3778
|
}
|
|
3398
3779
|
}
|
|
3399
3780
|
/**
|
|
3400
3781
|
* Start the proxy: connect to upstream, then serve downstream.
|
|
3401
3782
|
*/
|
|
3402
3783
|
async start() {
|
|
3403
|
-
|
|
3784
|
+
log2("Starting SolonGate Proxy...");
|
|
3404
3785
|
const apiUrl = this.config.apiUrl ?? "https://api.solongate.com";
|
|
3405
3786
|
if (this.config.apiKey) {
|
|
3406
3787
|
if (this.config.apiKey.startsWith("sg_test_")) {
|
|
3407
|
-
|
|
3788
|
+
log2("Using test API key \u2014 skipping online validation.");
|
|
3408
3789
|
} else {
|
|
3409
|
-
|
|
3790
|
+
log2(`Validating license with ${apiUrl}...`);
|
|
3410
3791
|
try {
|
|
3411
3792
|
const res = await fetch(`${apiUrl}/api/v1/auth/me`, {
|
|
3412
3793
|
headers: {
|
|
@@ -3416,17 +3797,17 @@ var SolonGateProxy = class {
|
|
|
3416
3797
|
signal: AbortSignal.timeout(1e4)
|
|
3417
3798
|
});
|
|
3418
3799
|
if (res.status === 401) {
|
|
3419
|
-
|
|
3800
|
+
log2("ERROR: Invalid or expired API key.");
|
|
3420
3801
|
process.exit(1);
|
|
3421
3802
|
}
|
|
3422
3803
|
if (res.status === 403) {
|
|
3423
|
-
|
|
3804
|
+
log2("ERROR: Your subscription is inactive. Renew at https://solongate.com");
|
|
3424
3805
|
process.exit(1);
|
|
3425
3806
|
}
|
|
3426
|
-
|
|
3807
|
+
log2("License validated.");
|
|
3427
3808
|
} catch (err) {
|
|
3428
|
-
|
|
3429
|
-
|
|
3809
|
+
log2(`ERROR: Unable to reach SolonGate license server. Check your internet connection.`);
|
|
3810
|
+
log2(`Details: ${err instanceof Error ? err.message : String(err)}`);
|
|
3430
3811
|
process.exit(1);
|
|
3431
3812
|
}
|
|
3432
3813
|
}
|
|
@@ -3434,28 +3815,27 @@ var SolonGateProxy = class {
|
|
|
3434
3815
|
try {
|
|
3435
3816
|
const cloudPolicy = await fetchCloudPolicy(this.config.apiKey, apiUrl);
|
|
3436
3817
|
this.config.policy = cloudPolicy;
|
|
3437
|
-
|
|
3818
|
+
log2(`Loaded cloud policy: ${cloudPolicy.name} (${cloudPolicy.rules.length} rules)`);
|
|
3438
3819
|
} catch (err) {
|
|
3439
|
-
|
|
3820
|
+
log2(`Cloud policy fetch failed, using local policy: ${err instanceof Error ? err.message : String(err)}`);
|
|
3440
3821
|
}
|
|
3441
3822
|
}
|
|
3442
3823
|
}
|
|
3443
3824
|
this.gate.loadPolicy(this.config.policy);
|
|
3444
|
-
|
|
3825
|
+
log2(`Policy: ${this.config.policy.name} (${this.config.policy.rules.length} rules)`);
|
|
3445
3826
|
const transport = this.config.upstream.transport ?? "stdio";
|
|
3446
3827
|
if (transport === "stdio") {
|
|
3447
|
-
|
|
3828
|
+
log2(`Upstream: [stdio] ${this.config.upstream.command} ${(this.config.upstream.args ?? []).join(" ")}`);
|
|
3448
3829
|
} else {
|
|
3449
|
-
|
|
3830
|
+
log2(`Upstream: [${transport}] ${this.config.upstream.url}`);
|
|
3450
3831
|
}
|
|
3451
3832
|
await this.connectUpstream();
|
|
3452
3833
|
await this.discoverTools();
|
|
3453
3834
|
this.registerToolsToCloud();
|
|
3454
3835
|
this.registerServerToCloud();
|
|
3455
|
-
this.
|
|
3836
|
+
this.startPolicySync();
|
|
3456
3837
|
this.createServer();
|
|
3457
3838
|
await this.serve();
|
|
3458
|
-
this.startPolicyPolling();
|
|
3459
3839
|
}
|
|
3460
3840
|
/**
|
|
3461
3841
|
* Connect to the upstream MCP server.
|
|
@@ -3493,7 +3873,7 @@ var SolonGateProxy = class {
|
|
|
3493
3873
|
break;
|
|
3494
3874
|
}
|
|
3495
3875
|
}
|
|
3496
|
-
|
|
3876
|
+
log2(`Connected to upstream server (${upstreamTransport})`);
|
|
3497
3877
|
}
|
|
3498
3878
|
/**
|
|
3499
3879
|
* Discover tools from the upstream server.
|
|
@@ -3506,9 +3886,9 @@ var SolonGateProxy = class {
|
|
|
3506
3886
|
description: t.description,
|
|
3507
3887
|
inputSchema: t.inputSchema
|
|
3508
3888
|
}));
|
|
3509
|
-
|
|
3889
|
+
log2(`Discovered ${this.upstreamTools.length} tools from upstream:`);
|
|
3510
3890
|
for (const tool of this.upstreamTools) {
|
|
3511
|
-
|
|
3891
|
+
log2(` - ${tool.name}: ${tool.description ?? "(no description)"}`);
|
|
3512
3892
|
}
|
|
3513
3893
|
}
|
|
3514
3894
|
/**
|
|
@@ -3537,13 +3917,13 @@ var SolonGateProxy = class {
|
|
|
3537
3917
|
const { name, arguments: args } = request.params;
|
|
3538
3918
|
const argsSize = JSON.stringify(args ?? {}).length;
|
|
3539
3919
|
if (argsSize > MAX_ARGUMENT_SIZE) {
|
|
3540
|
-
|
|
3920
|
+
log2(`DENY: ${name} \u2014 payload size ${argsSize} exceeds limit ${MAX_ARGUMENT_SIZE}`);
|
|
3541
3921
|
return {
|
|
3542
3922
|
content: [{ type: "text", text: `Request payload too large (${Math.round(argsSize / 1024)}KB > ${Math.round(MAX_ARGUMENT_SIZE / 1024)}KB limit)` }],
|
|
3543
3923
|
isError: true
|
|
3544
3924
|
};
|
|
3545
3925
|
}
|
|
3546
|
-
|
|
3926
|
+
log2(`Tool call: ${name}`);
|
|
3547
3927
|
await this.callMutex.acquire();
|
|
3548
3928
|
const startTime = Date.now();
|
|
3549
3929
|
try {
|
|
@@ -3560,10 +3940,10 @@ var SolonGateProxy = class {
|
|
|
3560
3940
|
);
|
|
3561
3941
|
const decision = result.isError ? "DENY" : "ALLOW";
|
|
3562
3942
|
const evaluationTimeMs = Date.now() - startTime;
|
|
3563
|
-
|
|
3943
|
+
log2(`Result: ${decision} (${evaluationTimeMs}ms)`);
|
|
3564
3944
|
if (this.config.apiKey && !this.config.apiKey.startsWith("sg_test_")) {
|
|
3565
3945
|
const apiUrl = this.config.apiUrl ?? "https://api.solongate.com";
|
|
3566
|
-
|
|
3946
|
+
log2(`Sending audit log: ${name} \u2192 ${decision} (key: ${this.config.apiKey.slice(0, 16)}...)`);
|
|
3567
3947
|
let reason = "allowed";
|
|
3568
3948
|
let matchedRule;
|
|
3569
3949
|
if (result.isError) {
|
|
@@ -3586,7 +3966,7 @@ var SolonGateProxy = class {
|
|
|
3586
3966
|
evaluationTimeMs
|
|
3587
3967
|
});
|
|
3588
3968
|
} else {
|
|
3589
|
-
|
|
3969
|
+
log2(`Skipping audit log (apiKey: ${this.config.apiKey ? "test key" : "not set"})`);
|
|
3590
3970
|
}
|
|
3591
3971
|
return {
|
|
3592
3972
|
content: [...result.content],
|
|
@@ -3660,13 +4040,13 @@ var SolonGateProxy = class {
|
|
|
3660
4040
|
registered++;
|
|
3661
4041
|
} else {
|
|
3662
4042
|
const body = await res.text().catch(() => "");
|
|
3663
|
-
|
|
4043
|
+
log2(`Tool registration failed for "${tool.name}" (${res.status}): ${body}`);
|
|
3664
4044
|
}
|
|
3665
4045
|
}).catch((err) => {
|
|
3666
|
-
|
|
4046
|
+
log2(`Tool registration error for "${tool.name}": ${err instanceof Error ? err.message : String(err)}`);
|
|
3667
4047
|
});
|
|
3668
4048
|
}
|
|
3669
|
-
|
|
4049
|
+
log2(`Registering ${total} tools to dashboard...`);
|
|
3670
4050
|
}
|
|
3671
4051
|
/**
|
|
3672
4052
|
* Guess tool permissions from tool name.
|
|
@@ -3720,68 +4100,42 @@ var SolonGateProxy = class {
|
|
|
3720
4100
|
})
|
|
3721
4101
|
}).then(async (res) => {
|
|
3722
4102
|
if (res.ok) {
|
|
3723
|
-
|
|
4103
|
+
log2(`Registered MCP server "${serverName}" to dashboard.`);
|
|
3724
4104
|
} else if (res.status === 409) {
|
|
3725
|
-
|
|
4105
|
+
log2(`MCP server "${serverName}" already registered.`);
|
|
3726
4106
|
} else {
|
|
3727
4107
|
const body = await res.text().catch(() => "");
|
|
3728
|
-
|
|
4108
|
+
log2(`MCP server registration failed (${res.status}): ${body}`);
|
|
3729
4109
|
}
|
|
3730
4110
|
}).catch((err) => {
|
|
3731
|
-
|
|
4111
|
+
log2(`MCP server registration error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3732
4112
|
});
|
|
3733
4113
|
}
|
|
3734
4114
|
/**
|
|
3735
|
-
*
|
|
3736
|
-
*
|
|
4115
|
+
* Start bidirectional policy sync between local JSON file and cloud dashboard.
|
|
4116
|
+
*
|
|
4117
|
+
* - Watches local policy.json for changes → pushes to cloud API
|
|
4118
|
+
* - Polls cloud API for dashboard changes → writes to local policy.json
|
|
4119
|
+
* - Version number determines which is newer (higher wins, cloud wins on tie)
|
|
3737
4120
|
*/
|
|
3738
|
-
|
|
3739
|
-
|
|
4121
|
+
startPolicySync() {
|
|
4122
|
+
const apiKey = this.config.apiKey;
|
|
4123
|
+
if (!apiKey) return;
|
|
3740
4124
|
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}`);
|
|
4125
|
+
this.syncManager = new PolicySyncManager({
|
|
4126
|
+
localPath: this.config.policyPath ?? null,
|
|
4127
|
+
apiKey,
|
|
4128
|
+
apiUrl,
|
|
4129
|
+
pollIntervalMs: 6e4,
|
|
4130
|
+
initialPolicy: this.config.policy,
|
|
4131
|
+
onPolicyUpdate: (policy) => {
|
|
4132
|
+
this.config.policy = policy;
|
|
4133
|
+
this.gate.loadPolicy(policy);
|
|
4134
|
+
log2(`Policy hot-reloaded: ${policy.name} v${policy.version} (${policy.rules.length} rules)`);
|
|
3761
4135
|
}
|
|
3762
|
-
}).catch((err) => {
|
|
3763
|
-
log(`Policy sync error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3764
4136
|
});
|
|
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);
|
|
4137
|
+
this.syncManager.start();
|
|
4138
|
+
log2("Bidirectional policy sync started.");
|
|
3785
4139
|
}
|
|
3786
4140
|
/**
|
|
3787
4141
|
* Start serving downstream.
|
|
@@ -3807,14 +4161,14 @@ var SolonGateProxy = class {
|
|
|
3807
4161
|
}
|
|
3808
4162
|
});
|
|
3809
4163
|
httpServer.listen(this.config.port, () => {
|
|
3810
|
-
|
|
3811
|
-
|
|
4164
|
+
log2(`Proxy is live on http://localhost:${this.config.port}/mcp`);
|
|
4165
|
+
log2("All tool calls are now protected by SolonGate.");
|
|
3812
4166
|
});
|
|
3813
4167
|
} else {
|
|
3814
4168
|
const transport = new StdioServerTransport();
|
|
3815
4169
|
await this.server.connect(transport);
|
|
3816
|
-
|
|
3817
|
-
|
|
4170
|
+
log2("Proxy is live. All tool calls are now protected by SolonGate.");
|
|
4171
|
+
log2("Waiting for requests...");
|
|
3818
4172
|
}
|
|
3819
4173
|
}
|
|
3820
4174
|
};
|
|
@@ -3832,7 +4186,7 @@ console.error = (...args) => {
|
|
|
3832
4186
|
process.stderr.write(`[SolonGate ERROR] ${args.map(String).join(" ")}
|
|
3833
4187
|
`);
|
|
3834
4188
|
};
|
|
3835
|
-
async function
|
|
4189
|
+
async function main5() {
|
|
3836
4190
|
const subcommand = process.argv[2];
|
|
3837
4191
|
if (subcommand === "init") {
|
|
3838
4192
|
process.argv.splice(2, 1);
|
|
@@ -3849,6 +4203,10 @@ async function main4() {
|
|
|
3849
4203
|
await Promise.resolve().then(() => (init_create(), create_exports));
|
|
3850
4204
|
return;
|
|
3851
4205
|
}
|
|
4206
|
+
if (subcommand === "pull" || subcommand === "push") {
|
|
4207
|
+
await Promise.resolve().then(() => (init_pull_push(), pull_push_exports));
|
|
4208
|
+
return;
|
|
4209
|
+
}
|
|
3852
4210
|
try {
|
|
3853
4211
|
const config = parseArgs(process.argv);
|
|
3854
4212
|
const proxy = new SolonGateProxy(config);
|
|
@@ -3860,4 +4218,4 @@ async function main4() {
|
|
|
3860
4218
|
process.exit(1);
|
|
3861
4219
|
}
|
|
3862
4220
|
}
|
|
3863
|
-
|
|
4221
|
+
main5();
|