@solongate/proxy 0.1.2 → 0.1.4

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 CHANGED
@@ -285,6 +285,730 @@ var init_init = __esm({
285
285
  }
286
286
  });
287
287
 
288
+ // src/inject.ts
289
+ var inject_exports = {};
290
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, copyFileSync as copyFileSync2 } from "fs";
291
+ import { resolve as resolve3 } from "path";
292
+ import { execSync } from "child_process";
293
+ function parseInjectArgs(argv) {
294
+ const args = argv.slice(2);
295
+ const opts = {
296
+ dryRun: false,
297
+ restore: false,
298
+ skipInstall: false
299
+ };
300
+ for (let i = 0; i < args.length; i++) {
301
+ switch (args[i]) {
302
+ case "--file":
303
+ opts.file = args[++i];
304
+ break;
305
+ case "--dry-run":
306
+ opts.dryRun = true;
307
+ break;
308
+ case "--restore":
309
+ opts.restore = true;
310
+ break;
311
+ case "--skip-install":
312
+ opts.skipInstall = true;
313
+ break;
314
+ case "--help":
315
+ case "-h":
316
+ printHelp2();
317
+ process.exit(0);
318
+ }
319
+ }
320
+ return opts;
321
+ }
322
+ function printHelp2() {
323
+ log2(`
324
+ SolonGate Inject \u2014 Add security to your MCP server in seconds
325
+
326
+ USAGE
327
+ npx @solongate/proxy inject [options]
328
+
329
+ OPTIONS
330
+ --file <path> Entry file to modify (default: auto-detect)
331
+ --dry-run Preview changes without writing
332
+ --restore Restore original file from backup
333
+ --skip-install Don't install SDK package
334
+ -h, --help Show this help message
335
+
336
+ EXAMPLES
337
+ npx @solongate/proxy inject # Auto-detect and inject
338
+ npx @solongate/proxy inject --file src/main.ts # Specify entry file
339
+ npx @solongate/proxy inject --dry-run # Preview changes
340
+ npx @solongate/proxy inject --restore # Undo injection
341
+
342
+ WHAT IT DOES
343
+ Replaces McpServer with SecureMcpServer (2 lines changed)
344
+ All tool() calls are automatically protected by SolonGate.
345
+ `);
346
+ }
347
+ function log2(msg) {
348
+ process.stderr.write(msg + "\n");
349
+ }
350
+ function detectProject() {
351
+ if (!existsSync3(resolve3("package.json"))) return false;
352
+ try {
353
+ const pkg = JSON.parse(readFileSync3(resolve3("package.json"), "utf-8"));
354
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
355
+ return !!(allDeps["@modelcontextprotocol/sdk"] || allDeps["@modelcontextprotocol/server"]);
356
+ } catch {
357
+ return false;
358
+ }
359
+ }
360
+ function findTsEntryFile() {
361
+ try {
362
+ const pkg = JSON.parse(readFileSync3(resolve3("package.json"), "utf-8"));
363
+ if (pkg.bin) {
364
+ const binPath = typeof pkg.bin === "string" ? pkg.bin : Object.values(pkg.bin)[0];
365
+ if (typeof binPath === "string") {
366
+ const srcPath = binPath.replace(/^\.\/dist\//, "./src/").replace(/\.js$/, ".ts");
367
+ if (existsSync3(resolve3(srcPath))) return resolve3(srcPath);
368
+ if (existsSync3(resolve3(binPath))) return resolve3(binPath);
369
+ }
370
+ }
371
+ if (pkg.main) {
372
+ const srcPath = pkg.main.replace(/^\.\/dist\//, "./src/").replace(/\.js$/, ".ts");
373
+ if (existsSync3(resolve3(srcPath))) return resolve3(srcPath);
374
+ }
375
+ } catch {
376
+ }
377
+ const candidates = [
378
+ "src/index.ts",
379
+ "src/server.ts",
380
+ "src/main.ts",
381
+ "index.ts",
382
+ "server.ts",
383
+ "main.ts"
384
+ ];
385
+ for (const c of candidates) {
386
+ const full = resolve3(c);
387
+ if (existsSync3(full)) {
388
+ try {
389
+ const content = readFileSync3(full, "utf-8");
390
+ if (content.includes("McpServer") || content.includes("McpServer")) {
391
+ return full;
392
+ }
393
+ } catch {
394
+ }
395
+ }
396
+ }
397
+ for (const c of candidates) {
398
+ if (existsSync3(resolve3(c))) return resolve3(c);
399
+ }
400
+ return null;
401
+ }
402
+ function detectPackageManager() {
403
+ if (existsSync3(resolve3("pnpm-lock.yaml"))) return "pnpm";
404
+ if (existsSync3(resolve3("yarn.lock"))) return "yarn";
405
+ return "npm";
406
+ }
407
+ function installSdk() {
408
+ try {
409
+ const pkg = JSON.parse(readFileSync3(resolve3("package.json"), "utf-8"));
410
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
411
+ if (allDeps["@solongate/sdk"]) {
412
+ log2(" @solongate/sdk already installed");
413
+ return true;
414
+ }
415
+ } catch {
416
+ }
417
+ const pm = detectPackageManager();
418
+ const cmd = pm === "yarn" ? "yarn add @solongate/sdk" : `${pm} install @solongate/sdk`;
419
+ log2(` Installing @solongate/sdk via ${pm}...`);
420
+ try {
421
+ execSync(cmd, { stdio: "pipe", cwd: process.cwd() });
422
+ return true;
423
+ } catch (err) {
424
+ log2(` Failed to install: ${err instanceof Error ? err.message : String(err)}`);
425
+ log2(" You can install manually: npm install @solongate/sdk");
426
+ return false;
427
+ }
428
+ }
429
+ function injectTypeScript(filePath) {
430
+ const original = readFileSync3(filePath, "utf-8");
431
+ const changes = [];
432
+ let modified = original;
433
+ if (modified.includes("SecureMcpServer")) {
434
+ return { file: filePath, changes: ["Already injected \u2014 skipping"], original, modified };
435
+ }
436
+ const mcpImportPatterns = [
437
+ // Solo import: import { McpServer } from '...'
438
+ /import\s*\{\s*McpServer\s*\}\s*from\s*['"][^'"]+['"]/,
439
+ // Import with others: import { McpServer, ... } from '...'
440
+ /import\s*\{[^}]*McpServer[^}]*\}\s*from\s*['"][^'"]+['"]/
441
+ ];
442
+ let importReplaced = false;
443
+ for (const pattern of mcpImportPatterns) {
444
+ const match = modified.match(pattern);
445
+ if (match) {
446
+ const importLine = match[0];
447
+ const namedImports = importLine.match(/\{([^}]+)\}/)?.[1] ?? "";
448
+ const importNames = namedImports.split(",").map((s) => s.trim()).filter(Boolean);
449
+ if (importNames.length === 1 && importNames[0] === "McpServer") {
450
+ modified = modified.replace(
451
+ importLine,
452
+ `import { SecureMcpServer } from '@solongate/sdk'`
453
+ );
454
+ changes.push("Replaced McpServer import with SecureMcpServer from @solongate/sdk");
455
+ } else {
456
+ const otherImports = importNames.filter((n) => n !== "McpServer");
457
+ const fromModule = importLine.match(/from\s*['"]([^'"]+)['"]/)?.[1] ?? "";
458
+ modified = modified.replace(
459
+ importLine,
460
+ `import { ${otherImports.join(", ")} } from '${fromModule}';
461
+ import { SecureMcpServer } from '@solongate/sdk'`
462
+ );
463
+ changes.push("Removed McpServer from existing import, added SecureMcpServer import from @solongate/sdk");
464
+ }
465
+ importReplaced = true;
466
+ break;
467
+ }
468
+ }
469
+ if (!importReplaced) {
470
+ const insertPos = findImportInsertPosition(modified);
471
+ modified = modified.slice(0, insertPos) + `import { SecureMcpServer } from '@solongate/sdk';
472
+ ` + modified.slice(insertPos);
473
+ changes.push("Added SecureMcpServer import from @solongate/sdk");
474
+ }
475
+ const constructorPattern = /new\s+McpServer\s*\(/g;
476
+ const constructorCount = (modified.match(constructorPattern) || []).length;
477
+ if (constructorCount > 0) {
478
+ modified = modified.replace(constructorPattern, "new SecureMcpServer(");
479
+ changes.push(`Replaced ${constructorCount} McpServer constructor(s) with SecureMcpServer`);
480
+ }
481
+ return { file: filePath, changes, original, modified };
482
+ }
483
+ function findImportInsertPosition(content) {
484
+ let pos = 0;
485
+ if (content.startsWith("#!")) {
486
+ pos = content.indexOf("\n") + 1;
487
+ }
488
+ const lines = content.slice(pos).split("\n");
489
+ let offset = pos;
490
+ for (const line of lines) {
491
+ const trimmed = line.trim();
492
+ if (trimmed === "" || trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*") || trimmed.startsWith("*/")) {
493
+ offset += line.length + 1;
494
+ } else {
495
+ break;
496
+ }
497
+ }
498
+ const importRegex = /^import\s+.+$/gm;
499
+ let lastImportEnd = offset;
500
+ let importMatch;
501
+ while ((importMatch = importRegex.exec(content)) !== null) {
502
+ lastImportEnd = importMatch.index + importMatch[0].length + 1;
503
+ }
504
+ return lastImportEnd > offset ? lastImportEnd : offset;
505
+ }
506
+ function showDiff(result) {
507
+ if (result.original === result.modified) {
508
+ log2(" No changes needed.");
509
+ return;
510
+ }
511
+ const origLines = result.original.split("\n");
512
+ const modLines = result.modified.split("\n");
513
+ log2("");
514
+ log2(` File: ${result.file}`);
515
+ log2(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
516
+ const maxLines = Math.max(origLines.length, modLines.length);
517
+ let diffCount = 0;
518
+ for (let i = 0; i < maxLines; i++) {
519
+ const orig = origLines[i];
520
+ const mod = modLines[i];
521
+ if (orig !== mod) {
522
+ if (diffCount < 30) {
523
+ if (orig !== void 0) log2(` - ${orig}`);
524
+ if (mod !== void 0) log2(` + ${mod}`);
525
+ }
526
+ diffCount++;
527
+ }
528
+ }
529
+ if (diffCount > 30) {
530
+ log2(` ... and ${diffCount - 30} more line changes`);
531
+ }
532
+ log2("");
533
+ }
534
+ async function main2() {
535
+ const opts = parseInjectArgs(process.argv);
536
+ log2("");
537
+ log2(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
538
+ log2(" \u2551 SolonGate \u2014 Inject SDK \u2551");
539
+ log2(" \u2551 Add security to your MCP server \u2551");
540
+ log2(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
541
+ log2("");
542
+ if (!detectProject()) {
543
+ log2(" Could not detect a TypeScript MCP server project.");
544
+ log2(" Make sure you are in a project directory with:");
545
+ log2(" package.json + @modelcontextprotocol/sdk in dependencies");
546
+ log2("");
547
+ log2(" To create a new MCP server: npx @solongate/proxy create <name>");
548
+ process.exit(1);
549
+ }
550
+ log2(" Language: TypeScript");
551
+ const entryFile = opts.file ? resolve3(opts.file) : findTsEntryFile();
552
+ if (!entryFile || !existsSync3(entryFile)) {
553
+ log2(` Could not find entry file.${opts.file ? ` File not found: ${opts.file}` : ""}`);
554
+ log2("");
555
+ log2(" Specify it manually: --file <path>");
556
+ log2("");
557
+ log2(" Common entry points:");
558
+ log2(" src/index.ts, src/server.ts, index.ts");
559
+ process.exit(1);
560
+ }
561
+ log2(` Entry: ${entryFile}`);
562
+ log2("");
563
+ const backupPath = entryFile + ".solongate-backup";
564
+ if (opts.restore) {
565
+ if (!existsSync3(backupPath)) {
566
+ log2(" No backup found. Nothing to restore.");
567
+ process.exit(1);
568
+ }
569
+ copyFileSync2(backupPath, entryFile);
570
+ log2(` Restored original file from backup.`);
571
+ log2(` Backup: ${backupPath}`);
572
+ process.exit(0);
573
+ }
574
+ if (!opts.skipInstall && !opts.dryRun) {
575
+ installSdk();
576
+ log2("");
577
+ }
578
+ const result = injectTypeScript(entryFile);
579
+ log2(` Changes (${result.changes.length}):`);
580
+ for (const change of result.changes) {
581
+ log2(` - ${change}`);
582
+ }
583
+ if (result.changes.length === 1 && result.changes[0].includes("Already injected")) {
584
+ log2("");
585
+ log2(" Your MCP server is already protected by SolonGate!");
586
+ process.exit(0);
587
+ }
588
+ if (result.original === result.modified) {
589
+ log2("");
590
+ log2(" No changes were made. The file may not contain recognizable MCP patterns.");
591
+ log2(" See docs: https://solongate.com/docs/integration");
592
+ process.exit(0);
593
+ }
594
+ if (opts.dryRun) {
595
+ log2("");
596
+ log2(" --- DRY RUN (no changes written) ---");
597
+ showDiff(result);
598
+ log2(" To apply: npx @solongate/proxy inject");
599
+ process.exit(0);
600
+ }
601
+ if (!existsSync3(backupPath)) {
602
+ copyFileSync2(entryFile, backupPath);
603
+ log2("");
604
+ log2(` Backup: ${backupPath}`);
605
+ }
606
+ writeFileSync2(entryFile, result.modified);
607
+ log2("");
608
+ log2(" \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");
609
+ log2(" \u2502 SolonGate SDK injected successfully! \u2502");
610
+ log2(" \u2502 \u2502");
611
+ log2(" \u2502 McpServer \u2192 SecureMcpServer \u2502");
612
+ log2(" \u2502 All tool() calls are now auto-protected. \u2502");
613
+ log2(" \u2502 \u2502");
614
+ log2(" \u2502 Set your API key: \u2502");
615
+ log2(" \u2502 export SOLONGATE_API_KEY=sg_live_xxx \u2502");
616
+ log2(" \u2502 \u2502");
617
+ log2(" \u2502 To undo: \u2502");
618
+ log2(" \u2502 npx @solongate/proxy inject --restore \u2502");
619
+ log2(" \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");
620
+ log2("");
621
+ }
622
+ var init_inject = __esm({
623
+ "src/inject.ts"() {
624
+ "use strict";
625
+ main2().catch((err) => {
626
+ log2(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
627
+ process.exit(1);
628
+ });
629
+ }
630
+ });
631
+
632
+ // src/create.ts
633
+ var create_exports = {};
634
+ import { mkdirSync, writeFileSync as writeFileSync3, existsSync as existsSync4 } from "fs";
635
+ import { resolve as resolve4, join as join2 } from "path";
636
+ import { execSync as execSync2 } from "child_process";
637
+ function log3(msg) {
638
+ process.stderr.write(msg + "\n");
639
+ }
640
+ function parseCreateArgs(argv) {
641
+ const args = argv.slice(2);
642
+ const opts = {
643
+ name: "",
644
+ policy: "restricted",
645
+ noInstall: false
646
+ };
647
+ for (let i = 0; i < args.length; i++) {
648
+ switch (args[i]) {
649
+ case "--policy":
650
+ opts.policy = args[++i];
651
+ break;
652
+ case "--no-install":
653
+ opts.noInstall = true;
654
+ break;
655
+ case "--help":
656
+ case "-h":
657
+ printHelp3();
658
+ process.exit(0);
659
+ break;
660
+ default:
661
+ if (!args[i].startsWith("-") && !opts.name) {
662
+ opts.name = args[i];
663
+ }
664
+ }
665
+ }
666
+ if (!opts.name) {
667
+ log3("");
668
+ log3(" Error: Project name required.");
669
+ log3("");
670
+ log3(" Usage: npx @solongate/proxy create <name>");
671
+ log3("");
672
+ log3(" Examples:");
673
+ log3(" npx @solongate/proxy create my-mcp-server");
674
+ log3(" npx @solongate/proxy create weather-api");
675
+ process.exit(1);
676
+ }
677
+ return opts;
678
+ }
679
+ function printHelp3() {
680
+ log3(`
681
+ SolonGate Create \u2014 Scaffold a secure MCP server in seconds
682
+
683
+ USAGE
684
+ npx @solongate/proxy create <name> [options]
685
+
686
+ OPTIONS
687
+ --policy <preset> Policy preset (default: restricted)
688
+ --no-install Skip dependency installation
689
+ -h, --help Show this help message
690
+
691
+ EXAMPLES
692
+ npx @solongate/proxy create my-server
693
+ npx @solongate/proxy create db-tools --policy read-only
694
+ `);
695
+ }
696
+ function createProject(dir, name, _policy) {
697
+ writeFileSync3(
698
+ join2(dir, "package.json"),
699
+ JSON.stringify(
700
+ {
701
+ name,
702
+ version: "0.1.0",
703
+ type: "module",
704
+ private: true,
705
+ bin: { [name]: "./dist/index.js" },
706
+ scripts: {
707
+ build: "tsup src/index.ts --format esm",
708
+ dev: "tsx src/index.ts",
709
+ start: "node dist/index.js"
710
+ },
711
+ dependencies: {
712
+ "@modelcontextprotocol/sdk": "^1.26.0",
713
+ "@solongate/sdk": "latest",
714
+ zod: "^3.25.0"
715
+ },
716
+ devDependencies: {
717
+ tsup: "^8.3.0",
718
+ tsx: "^4.19.0",
719
+ typescript: "^5.7.0"
720
+ }
721
+ },
722
+ null,
723
+ 2
724
+ ) + "\n"
725
+ );
726
+ writeFileSync3(
727
+ join2(dir, "tsconfig.json"),
728
+ JSON.stringify(
729
+ {
730
+ compilerOptions: {
731
+ target: "ES2022",
732
+ module: "ESNext",
733
+ moduleResolution: "bundler",
734
+ esModuleInterop: true,
735
+ strict: true,
736
+ outDir: "dist",
737
+ rootDir: "src",
738
+ declaration: true,
739
+ skipLibCheck: true
740
+ },
741
+ include: ["src"]
742
+ },
743
+ null,
744
+ 2
745
+ ) + "\n"
746
+ );
747
+ mkdirSync(join2(dir, "src"), { recursive: true });
748
+ writeFileSync3(
749
+ join2(dir, "src", "index.ts"),
750
+ `#!/usr/bin/env node
751
+
752
+ // MCP uses stdout for JSON-RPC \u2014 redirect console to stderr
753
+ console.log = (...args: unknown[]) => {
754
+ process.stderr.write(args.map(String).join(' ') + '\\n');
755
+ };
756
+
757
+ import { SecureMcpServer } from '@solongate/sdk';
758
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
759
+ import { z } from 'zod';
760
+
761
+ // Create a secure MCP server (API key from SOLONGATE_API_KEY env var)
762
+ const server = new SecureMcpServer({
763
+ name: '${name}',
764
+ version: '0.1.0',
765
+ });
766
+
767
+ // \u2500\u2500 Register your tools below \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
768
+
769
+ server.tool(
770
+ 'hello',
771
+ 'Say hello to someone',
772
+ { name: z.string().describe('Name of the person to greet') },
773
+ async ({ name }) => ({
774
+ content: [{ type: 'text', text: \`Hello, \${name}! Welcome to ${name}.\` }],
775
+ }),
776
+ );
777
+
778
+ // Example: Add more tools here
779
+ // server.tool(
780
+ // 'read_data',
781
+ // 'Read data from a source',
782
+ // { query: z.string().describe('What to read') },
783
+ // async ({ query }) => ({
784
+ // content: [{ type: 'text', text: \`Result for: \${query}\` }],
785
+ // }),
786
+ // );
787
+
788
+ // \u2500\u2500 Start the server \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
789
+
790
+ const transport = new StdioServerTransport();
791
+ await server.connect(transport);
792
+ console.log('${name} is running');
793
+ `
794
+ );
795
+ writeFileSync3(
796
+ join2(dir, ".mcp.json"),
797
+ JSON.stringify(
798
+ {
799
+ mcpServers: {
800
+ [name]: {
801
+ command: "node",
802
+ args: ["dist/index.js"],
803
+ env: {
804
+ SOLONGATE_API_KEY: "sg_test_e4460d32_replace_with_your_key"
805
+ }
806
+ }
807
+ }
808
+ },
809
+ null,
810
+ 2
811
+ ) + "\n"
812
+ );
813
+ writeFileSync3(
814
+ join2(dir, ".gitignore"),
815
+ `node_modules/
816
+ dist/
817
+ *.solongate-backup
818
+ .env
819
+ .env.local
820
+ `
821
+ );
822
+ }
823
+ function withSpinner(message, fn) {
824
+ const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
825
+ let i = 0;
826
+ const id = setInterval(() => {
827
+ process.stderr.write(`\r ${frames[i++ % frames.length]} ${message}`);
828
+ }, 80);
829
+ try {
830
+ fn();
831
+ clearInterval(id);
832
+ process.stderr.write(`\r \u2713 ${message}
833
+ `);
834
+ } catch {
835
+ clearInterval(id);
836
+ process.stderr.write(`\r \u2717 ${message} \u2014 failed
837
+ `);
838
+ }
839
+ }
840
+ function installDeps(dir) {
841
+ withSpinner("Installing dependencies...", () => {
842
+ execSync2("npm install", { cwd: dir, stdio: "pipe" });
843
+ });
844
+ }
845
+ async function main3() {
846
+ const opts = parseCreateArgs(process.argv);
847
+ const dir = resolve4(opts.name);
848
+ log3("");
849
+ log3(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
850
+ log3(" \u2551 SolonGate \u2014 Create MCP Server \u2551");
851
+ log3(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
852
+ log3("");
853
+ if (existsSync4(dir)) {
854
+ log3(` Error: Directory "${opts.name}" already exists.`);
855
+ process.exit(1);
856
+ }
857
+ mkdirSync(dir, { recursive: true });
858
+ log3(` Project: ${opts.name}`);
859
+ log3(` Language: TypeScript`);
860
+ log3(` Policy: ${opts.policy}`);
861
+ log3("");
862
+ createProject(dir, opts.name, opts.policy);
863
+ log3(" Files created:");
864
+ log3(" package.json");
865
+ log3(" tsconfig.json");
866
+ log3(" src/index.ts");
867
+ log3(" .mcp.json");
868
+ log3(" .gitignore");
869
+ log3("");
870
+ if (!opts.noInstall) {
871
+ installDeps(dir);
872
+ log3("");
873
+ }
874
+ const W = 46;
875
+ const line = (s) => log3(` \u2502 ${s.padEnd(W)} \u2502`);
876
+ log3(` \u250C${"\u2500".repeat(W + 2)}\u2510`);
877
+ line("Project created!");
878
+ line("");
879
+ line(`cd ${opts.name}`);
880
+ line("");
881
+ line("npm run build # Build");
882
+ line("npm run dev # Dev mode (tsx)");
883
+ line("npm start # Run built server");
884
+ line("");
885
+ line("Set your API key:");
886
+ line("export SOLONGATE_API_KEY=sg_live_xxx");
887
+ log3(` \u2514${"\u2500".repeat(W + 2)}\u2518`);
888
+ log3("");
889
+ }
890
+ var init_create = __esm({
891
+ "src/create.ts"() {
892
+ "use strict";
893
+ main3().catch((err) => {
894
+ log3(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
895
+ process.exit(1);
896
+ });
897
+ }
898
+ });
899
+
900
+ // ../core/dist/index.js
901
+ import { z } from "zod";
902
+ var Permission = {
903
+ READ: "READ",
904
+ WRITE: "WRITE",
905
+ EXECUTE: "EXECUTE"
906
+ };
907
+ var PermissionSchema = z.enum(["READ", "WRITE", "EXECUTE"]);
908
+ var NO_PERMISSIONS = Object.freeze(
909
+ /* @__PURE__ */ new Set()
910
+ );
911
+ var READ_ONLY = Object.freeze(
912
+ /* @__PURE__ */ new Set([Permission.READ])
913
+ );
914
+ var PolicyRuleSchema = z.object({
915
+ id: z.string().min(1).max(256),
916
+ description: z.string().max(1024),
917
+ effect: z.enum(["ALLOW", "DENY"]),
918
+ priority: z.number().int().min(0).max(1e4).default(1e3),
919
+ toolPattern: z.string().min(1).max(512),
920
+ permission: z.enum(["READ", "WRITE", "EXECUTE"]),
921
+ minimumTrustLevel: z.enum(["UNTRUSTED", "VERIFIED", "TRUSTED"]),
922
+ argumentConstraints: z.record(z.unknown()).optional(),
923
+ pathConstraints: z.object({
924
+ allowed: z.array(z.string()).optional(),
925
+ denied: z.array(z.string()).optional(),
926
+ rootDirectory: z.string().optional(),
927
+ allowSymlinks: z.boolean().optional()
928
+ }).optional(),
929
+ enabled: z.boolean().default(true),
930
+ createdAt: z.string().datetime(),
931
+ updatedAt: z.string().datetime()
932
+ });
933
+ var PolicySetSchema = z.object({
934
+ id: z.string().min(1).max(256),
935
+ name: z.string().min(1).max(256),
936
+ description: z.string().max(2048),
937
+ version: z.number().int().min(0),
938
+ rules: z.array(PolicyRuleSchema),
939
+ createdAt: z.string().datetime(),
940
+ updatedAt: z.string().datetime()
941
+ });
942
+ var SECURITY_CONTEXT_TIMEOUT_MS = 5 * 60 * 1e3;
943
+ var DEFAULT_INPUT_GUARD_CONFIG = Object.freeze({
944
+ pathTraversal: true,
945
+ shellInjection: true,
946
+ wildcardAbuse: true,
947
+ lengthLimit: 4096,
948
+ entropyLimit: true,
949
+ ssrf: true,
950
+ sqlInjection: true
951
+ });
952
+ var SSRF_PATTERNS = [
953
+ /^https?:\/\/localhost\b/i,
954
+ /^https?:\/\/127\.\d{1,3}\.\d{1,3}\.\d{1,3}/,
955
+ /^https?:\/\/0\.0\.0\.0/,
956
+ /^https?:\/\/\[::1\]/,
957
+ // IPv6 loopback
958
+ /^https?:\/\/10\.\d{1,3}\.\d{1,3}\.\d{1,3}/,
959
+ // 10.x.x.x
960
+ /^https?:\/\/172\.(1[6-9]|2\d|3[01])\./,
961
+ // 172.16-31.x.x
962
+ /^https?:\/\/192\.168\./,
963
+ // 192.168.x.x
964
+ /^https?:\/\/169\.254\./,
965
+ // Link-local / AWS metadata
966
+ /metadata\.google\.internal/i,
967
+ // GCP metadata
968
+ /^https?:\/\/metadata\b/i,
969
+ // Generic metadata endpoint
970
+ // IPv6 bypass patterns
971
+ /^https?:\/\/\[fe80:/i,
972
+ // IPv6 link-local
973
+ /^https?:\/\/\[fc00:/i,
974
+ // IPv6 unique local
975
+ /^https?:\/\/\[fd[0-9a-f]{2}:/i,
976
+ // IPv6 unique local (fd00::/8)
977
+ /^https?:\/\/\[::ffff:127\./i,
978
+ // IPv4-mapped IPv6 loopback
979
+ /^https?:\/\/\[::ffff:10\./i,
980
+ // IPv4-mapped IPv6 private
981
+ /^https?:\/\/\[::ffff:172\.(1[6-9]|2\d|3[01])\./i,
982
+ // IPv4-mapped IPv6 private
983
+ /^https?:\/\/\[::ffff:192\.168\./i,
984
+ // IPv4-mapped IPv6 private
985
+ /^https?:\/\/\[::ffff:169\.254\./i,
986
+ // IPv4-mapped IPv6 link-local
987
+ // Hex IP bypass (e.g., 0x7f000001 = 127.0.0.1)
988
+ /^https?:\/\/0x[0-9a-f]+\b/i,
989
+ // Octal IP bypass (e.g., 0177.0.0.1 = 127.0.0.1)
990
+ /^https?:\/\/0[0-7]{1,3}\./
991
+ ];
992
+ function detectDecimalIP(value) {
993
+ const match = value.match(/^https?:\/\/(\d{8,10})(?:[:/]|$)/);
994
+ if (!match || !match[1]) return false;
995
+ const decimal = parseInt(match[1], 10);
996
+ if (isNaN(decimal) || decimal > 4294967295) return false;
997
+ return decimal >= 2130706432 && decimal <= 2147483647 || // 127.0.0.0/8
998
+ decimal >= 167772160 && decimal <= 184549375 || // 10.0.0.0/8
999
+ decimal >= 2886729728 && decimal <= 2887778303 || // 172.16.0.0/12
1000
+ decimal >= 3232235520 && decimal <= 3232301055 || // 192.168.0.0/16
1001
+ decimal >= 2851995648 && decimal <= 2852061183 || // 169.254.0.0/16
1002
+ decimal === 0;
1003
+ }
1004
+ function detectSSRF(value) {
1005
+ for (const pattern of SSRF_PATTERNS) {
1006
+ if (pattern.test(value)) return true;
1007
+ }
1008
+ if (detectDecimalIP(value)) return true;
1009
+ return false;
1010
+ }
1011
+
288
1012
  // src/config.ts
289
1013
  import { readFileSync, existsSync } from "fs";
290
1014
  import { resolve } from "path";
@@ -504,6 +1228,9 @@ function parseArgs(argv) {
504
1228
  let configFile;
505
1229
  let apiKey;
506
1230
  let apiUrl;
1231
+ let upstreamUrl;
1232
+ let upstreamTransport;
1233
+ let port;
507
1234
  let separatorIndex = args.indexOf("--");
508
1235
  const flags = separatorIndex >= 0 ? args.slice(0, separatorIndex) : args;
509
1236
  const upstreamArgs = separatorIndex >= 0 ? args.slice(separatorIndex + 1) : [];
@@ -536,14 +1263,43 @@ function parseArgs(argv) {
536
1263
  case "--api-url":
537
1264
  apiUrl = flags[++i];
538
1265
  break;
1266
+ case "--upstream-url":
1267
+ upstreamUrl = flags[++i];
1268
+ break;
1269
+ case "--upstream-transport":
1270
+ upstreamTransport = flags[++i];
1271
+ break;
1272
+ case "--port":
1273
+ port = parseInt(flags[++i], 10);
1274
+ break;
1275
+ }
1276
+ }
1277
+ if (!apiKey) {
1278
+ const envKey = process.env.SOLONGATE_API_KEY;
1279
+ if (envKey) {
1280
+ apiKey = envKey;
1281
+ } else {
1282
+ throw new Error(
1283
+ "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"
1284
+ );
539
1285
  }
540
1286
  }
1287
+ if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
1288
+ throw new Error(
1289
+ "Invalid API key format. Keys must start with 'sg_live_' or 'sg_test_'.\nGet your API key at https://solongate.com\n"
1290
+ );
1291
+ }
541
1292
  if (configFile) {
542
1293
  const filePath = resolve(configFile);
543
1294
  const content = readFileSync(filePath, "utf-8");
544
1295
  const fileConfig = JSON.parse(content);
545
1296
  if (!fileConfig.upstream) {
546
- throw new Error('Config file must include "upstream" with at least "command"');
1297
+ throw new Error('Config file must include "upstream" with at least "command" or "url"');
1298
+ }
1299
+ if (fileConfig.upstream.url && detectSSRF(fileConfig.upstream.url)) {
1300
+ throw new Error(
1301
+ `Upstream URL blocked: "${fileConfig.upstream.url}" points to an internal or private network address.`
1302
+ );
547
1303
  }
548
1304
  return {
549
1305
  upstream: fileConfig.upstream,
@@ -554,17 +1310,45 @@ function parseArgs(argv) {
554
1310
  rateLimitPerTool: fileConfig.rateLimitPerTool ?? rateLimitPerTool,
555
1311
  globalRateLimit: fileConfig.globalRateLimit ?? globalRateLimit,
556
1312
  apiKey: apiKey ?? fileConfig.apiKey,
557
- apiUrl: apiUrl ?? fileConfig.apiUrl
1313
+ apiUrl: apiUrl ?? fileConfig.apiUrl,
1314
+ port: port ?? fileConfig.port
1315
+ };
1316
+ }
1317
+ if (upstreamUrl) {
1318
+ if (detectSSRF(upstreamUrl)) {
1319
+ throw new Error(
1320
+ `Upstream URL blocked: "${upstreamUrl}" points to an internal or private network address.
1321
+ SSRF protection prevents connecting to localhost, private IPs, or cloud metadata endpoints.`
1322
+ );
1323
+ }
1324
+ const transport = upstreamTransport ?? (upstreamUrl.includes("/sse") ? "sse" : "http");
1325
+ return {
1326
+ upstream: {
1327
+ transport,
1328
+ command: "",
1329
+ // not used for URL-based transports
1330
+ url: upstreamUrl
1331
+ },
1332
+ policy: loadPolicy(policySource),
1333
+ name,
1334
+ verbose,
1335
+ validateInput,
1336
+ rateLimitPerTool,
1337
+ globalRateLimit,
1338
+ apiKey,
1339
+ apiUrl,
1340
+ port
558
1341
  };
559
1342
  }
560
1343
  if (upstreamArgs.length === 0) {
561
1344
  throw new Error(
562
- "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 --config solongate.json\n"
1345
+ "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"
563
1346
  );
564
1347
  }
565
1348
  const [command, ...commandArgs] = upstreamArgs;
566
1349
  return {
567
1350
  upstream: {
1351
+ transport: upstreamTransport ?? "stdio",
568
1352
  command,
569
1353
  args: commandArgs,
570
1354
  env: { ...process.env }
@@ -576,15 +1360,19 @@ function parseArgs(argv) {
576
1360
  rateLimitPerTool,
577
1361
  globalRateLimit,
578
1362
  apiKey,
579
- apiUrl
1363
+ apiUrl,
1364
+ port
580
1365
  };
581
1366
  }
582
1367
 
583
1368
  // src/proxy.ts
584
1369
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
585
1370
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1371
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
586
1372
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
587
1373
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
1374
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
1375
+ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
588
1376
  import {
589
1377
  ListToolsRequestSchema,
590
1378
  CallToolRequestSchema,
@@ -594,9 +1382,12 @@ import {
594
1382
  ReadResourceRequestSchema,
595
1383
  ListResourceTemplatesRequestSchema
596
1384
  } from "@modelcontextprotocol/sdk/types.js";
1385
+ import { createServer as createHttpServer } from "http";
597
1386
 
598
- // ../core/dist/index.js
599
- import { z } from "zod";
1387
+ // ../sdk-ts/dist/index.js
1388
+ import { z as z2 } from "zod";
1389
+ import { createHash, randomUUID, createHmac } from "crypto";
1390
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
600
1391
  var SolonGateError = class extends Error {
601
1392
  code;
602
1393
  timestamp;
@@ -658,49 +1449,49 @@ var TrustLevel = {
658
1449
  VERIFIED: "VERIFIED",
659
1450
  TRUSTED: "TRUSTED"
660
1451
  };
661
- var Permission = {
1452
+ var Permission2 = {
662
1453
  READ: "READ",
663
1454
  WRITE: "WRITE",
664
1455
  EXECUTE: "EXECUTE"
665
1456
  };
666
- var PermissionSchema = z.enum(["READ", "WRITE", "EXECUTE"]);
667
- var NO_PERMISSIONS = Object.freeze(
1457
+ z2.enum(["READ", "WRITE", "EXECUTE"]);
1458
+ Object.freeze(
668
1459
  /* @__PURE__ */ new Set()
669
1460
  );
670
- var READ_ONLY = Object.freeze(
671
- /* @__PURE__ */ new Set([Permission.READ])
1461
+ Object.freeze(
1462
+ /* @__PURE__ */ new Set([Permission2.READ])
672
1463
  );
673
1464
  var PolicyEffect = {
674
1465
  ALLOW: "ALLOW",
675
1466
  DENY: "DENY"
676
1467
  };
677
- var PolicyRuleSchema = z.object({
678
- id: z.string().min(1).max(256),
679
- description: z.string().max(1024),
680
- effect: z.enum(["ALLOW", "DENY"]),
681
- priority: z.number().int().min(0).max(1e4).default(1e3),
682
- toolPattern: z.string().min(1).max(512),
683
- permission: z.enum(["READ", "WRITE", "EXECUTE"]),
684
- minimumTrustLevel: z.enum(["UNTRUSTED", "VERIFIED", "TRUSTED"]),
685
- argumentConstraints: z.record(z.unknown()).optional(),
686
- pathConstraints: z.object({
687
- allowed: z.array(z.string()).optional(),
688
- denied: z.array(z.string()).optional(),
689
- rootDirectory: z.string().optional(),
690
- allowSymlinks: z.boolean().optional()
1468
+ var PolicyRuleSchema2 = z2.object({
1469
+ id: z2.string().min(1).max(256),
1470
+ description: z2.string().max(1024),
1471
+ effect: z2.enum(["ALLOW", "DENY"]),
1472
+ priority: z2.number().int().min(0).max(1e4).default(1e3),
1473
+ toolPattern: z2.string().min(1).max(512),
1474
+ permission: z2.enum(["READ", "WRITE", "EXECUTE"]),
1475
+ minimumTrustLevel: z2.enum(["UNTRUSTED", "VERIFIED", "TRUSTED"]),
1476
+ argumentConstraints: z2.record(z2.unknown()).optional(),
1477
+ pathConstraints: z2.object({
1478
+ allowed: z2.array(z2.string()).optional(),
1479
+ denied: z2.array(z2.string()).optional(),
1480
+ rootDirectory: z2.string().optional(),
1481
+ allowSymlinks: z2.boolean().optional()
691
1482
  }).optional(),
692
- enabled: z.boolean().default(true),
693
- createdAt: z.string().datetime(),
694
- updatedAt: z.string().datetime()
1483
+ enabled: z2.boolean().default(true),
1484
+ createdAt: z2.string().datetime(),
1485
+ updatedAt: z2.string().datetime()
695
1486
  });
696
- var PolicySetSchema = z.object({
697
- id: z.string().min(1).max(256),
698
- name: z.string().min(1).max(256),
699
- description: z.string().max(2048),
700
- version: z.number().int().min(0),
701
- rules: z.array(PolicyRuleSchema),
702
- createdAt: z.string().datetime(),
703
- updatedAt: z.string().datetime()
1487
+ var PolicySetSchema2 = z2.object({
1488
+ id: z2.string().min(1).max(256),
1489
+ name: z2.string().min(1).max(256),
1490
+ description: z2.string().max(2048),
1491
+ version: z2.number().int().min(0),
1492
+ rules: z2.array(PolicyRuleSchema2),
1493
+ createdAt: z2.string().datetime(),
1494
+ updatedAt: z2.string().datetime()
704
1495
  });
705
1496
  function createSecurityContext(params) {
706
1497
  return {
@@ -714,14 +1505,12 @@ function createSecurityContext(params) {
714
1505
  }
715
1506
  var DEFAULT_POLICY_EFFECT = "DENY";
716
1507
  var MAX_RULES_PER_POLICY_SET = 1e3;
717
- var SECURITY_CONTEXT_TIMEOUT_MS = 5 * 60 * 1e3;
718
1508
  var POLICY_EVALUATION_TIMEOUT_MS = 100;
719
1509
  var RATE_LIMIT_WINDOW_MS = 6e4;
720
1510
  var RATE_LIMIT_MAX_ENTRIES = 1e4;
721
1511
  var UNSAFE_CONFIGURATION_WARNINGS = {
722
1512
  WILDCARD_ALLOW: "Wildcard ALLOW rules grant permission to ALL tools. This bypasses the default-deny model.",
723
1513
  TRUSTED_LEVEL_EXTERNAL: "Setting trust level to TRUSTED for external requests bypasses all security checks.",
724
- WRITE_WITHOUT_READ: "Granting WRITE without READ is unusual and may indicate a misconfiguration.",
725
1514
  EXECUTE_WITHOUT_REVIEW: "EXECUTE permission allows tools to perform arbitrary actions. Review carefully.",
726
1515
  RATE_LIMIT_ZERO: "A rate limit of 0 means unlimited calls. This removes protection against runaway loops.",
727
1516
  DISABLED_VALIDATION: "Disabling schema validation removes input sanitization protections."
@@ -741,12 +1530,14 @@ function createDeniedToolResult(reason) {
741
1530
  isError: true
742
1531
  };
743
1532
  }
744
- var DEFAULT_INPUT_GUARD_CONFIG = Object.freeze({
1533
+ var DEFAULT_INPUT_GUARD_CONFIG2 = Object.freeze({
745
1534
  pathTraversal: true,
746
1535
  shellInjection: true,
747
1536
  wildcardAbuse: true,
748
1537
  lengthLimit: 4096,
749
- entropyLimit: true
1538
+ entropyLimit: true,
1539
+ ssrf: true,
1540
+ sqlInjection: true
750
1541
  });
751
1542
  var PATH_TRAVERSAL_PATTERNS = [
752
1543
  /\.\.\//,
@@ -772,7 +1563,23 @@ var SENSITIVE_PATHS = [
772
1563
  /c:\\windows\\system32/i,
773
1564
  /c:\\windows\\syswow64/i,
774
1565
  /\/root\//i,
775
- /~\//
1566
+ /~\//,
1567
+ /\.env(\.|$)/i,
1568
+ // .env, .env.local, .env.production
1569
+ /\.aws\/credentials/i,
1570
+ // AWS credentials
1571
+ /\.ssh\/id_/i,
1572
+ // SSH keys
1573
+ /\.kube\/config/i,
1574
+ // Kubernetes config
1575
+ /wp-config\.php/i,
1576
+ // WordPress config
1577
+ /\.git\/config/i,
1578
+ // Git config
1579
+ /\.npmrc/i,
1580
+ // npm credentials
1581
+ /\.pypirc/i
1582
+ // PyPI credentials
776
1583
  ];
777
1584
  function detectPathTraversal(value) {
778
1585
  for (const pattern of PATH_TRAVERSAL_PATTERNS) {
@@ -802,8 +1609,18 @@ var SHELL_INJECTION_PATTERNS = [
802
1609
  // eval command
803
1610
  /\bexec\b/i,
804
1611
  // exec command
805
- /\bsystem\b/i
1612
+ /\bsystem\b/i,
806
1613
  // system call
1614
+ /%0a/i,
1615
+ // URL-encoded newline
1616
+ /%0d/i,
1617
+ // URL-encoded carriage return
1618
+ /%09/i,
1619
+ // URL-encoded tab
1620
+ /\r\n/,
1621
+ // CRLF injection
1622
+ /\n/
1623
+ // Newline (command separator on Unix)
807
1624
  ];
808
1625
  function detectShellInjection(value) {
809
1626
  for (const pattern of SHELL_INJECTION_PATTERNS) {
@@ -818,6 +1635,91 @@ function detectWildcardAbuse(value) {
818
1635
  if (wildcardCount > MAX_WILDCARDS_PER_VALUE) return true;
819
1636
  return false;
820
1637
  }
1638
+ var SSRF_PATTERNS2 = [
1639
+ /^https?:\/\/localhost\b/i,
1640
+ /^https?:\/\/127\.\d{1,3}\.\d{1,3}\.\d{1,3}/,
1641
+ /^https?:\/\/0\.0\.0\.0/,
1642
+ /^https?:\/\/\[::1\]/,
1643
+ // IPv6 loopback
1644
+ /^https?:\/\/10\.\d{1,3}\.\d{1,3}\.\d{1,3}/,
1645
+ // 10.x.x.x
1646
+ /^https?:\/\/172\.(1[6-9]|2\d|3[01])\./,
1647
+ // 172.16-31.x.x
1648
+ /^https?:\/\/192\.168\./,
1649
+ // 192.168.x.x
1650
+ /^https?:\/\/169\.254\./,
1651
+ // Link-local / AWS metadata
1652
+ /metadata\.google\.internal/i,
1653
+ // GCP metadata
1654
+ /^https?:\/\/metadata\b/i,
1655
+ // Generic metadata endpoint
1656
+ // IPv6 bypass patterns
1657
+ /^https?:\/\/\[fe80:/i,
1658
+ // IPv6 link-local
1659
+ /^https?:\/\/\[fc00:/i,
1660
+ // IPv6 unique local
1661
+ /^https?:\/\/\[fd[0-9a-f]{2}:/i,
1662
+ // IPv6 unique local (fd00::/8)
1663
+ /^https?:\/\/\[::ffff:127\./i,
1664
+ // IPv4-mapped IPv6 loopback
1665
+ /^https?:\/\/\[::ffff:10\./i,
1666
+ // IPv4-mapped IPv6 private
1667
+ /^https?:\/\/\[::ffff:172\.(1[6-9]|2\d|3[01])\./i,
1668
+ // IPv4-mapped IPv6 private
1669
+ /^https?:\/\/\[::ffff:192\.168\./i,
1670
+ // IPv4-mapped IPv6 private
1671
+ /^https?:\/\/\[::ffff:169\.254\./i,
1672
+ // IPv4-mapped IPv6 link-local
1673
+ // Hex IP bypass (e.g., 0x7f000001 = 127.0.0.1)
1674
+ /^https?:\/\/0x[0-9a-f]+\b/i,
1675
+ // Octal IP bypass (e.g., 0177.0.0.1 = 127.0.0.1)
1676
+ /^https?:\/\/0[0-7]{1,3}\./
1677
+ ];
1678
+ function detectDecimalIP2(value) {
1679
+ const match = value.match(/^https?:\/\/(\d{8,10})(?:[:/]|$)/);
1680
+ if (!match || !match[1]) return false;
1681
+ const decimal = parseInt(match[1], 10);
1682
+ if (isNaN(decimal) || decimal > 4294967295) return false;
1683
+ return decimal >= 2130706432 && decimal <= 2147483647 || // 127.0.0.0/8
1684
+ decimal >= 167772160 && decimal <= 184549375 || // 10.0.0.0/8
1685
+ decimal >= 2886729728 && decimal <= 2887778303 || // 172.16.0.0/12
1686
+ decimal >= 3232235520 && decimal <= 3232301055 || // 192.168.0.0/16
1687
+ decimal >= 2851995648 && decimal <= 2852061183 || // 169.254.0.0/16
1688
+ decimal === 0;
1689
+ }
1690
+ function detectSSRF2(value) {
1691
+ for (const pattern of SSRF_PATTERNS2) {
1692
+ if (pattern.test(value)) return true;
1693
+ }
1694
+ if (detectDecimalIP2(value)) return true;
1695
+ return false;
1696
+ }
1697
+ var SQL_INJECTION_PATTERNS = [
1698
+ /'\s{0,20}(OR|AND)\s{0,20}'.{0,200}'/i,
1699
+ // ' OR '1'='1 — bounded to prevent ReDoS
1700
+ /'\s{0,10};\s{0,10}(DROP|DELETE|UPDATE|INSERT|ALTER|CREATE|EXEC)/i,
1701
+ // '; DROP TABLE
1702
+ /UNION\s+(ALL\s+)?SELECT/i,
1703
+ // UNION SELECT
1704
+ /--\s*$/m,
1705
+ // SQL comment at end of line
1706
+ /\/\*.{0,500}?\*\//,
1707
+ // SQL block comment — bounded + non-greedy
1708
+ /\bSLEEP\s*\(/i,
1709
+ // Time-based injection
1710
+ /\bBENCHMARK\s*\(/i,
1711
+ // MySQL benchmark
1712
+ /\bWAITFOR\s+DELAY/i,
1713
+ // MSSQL delay
1714
+ /\b(LOAD_FILE|INTO\s+OUTFILE|INTO\s+DUMPFILE)\b/i
1715
+ // File operations
1716
+ ];
1717
+ function detectSQLInjection(value) {
1718
+ for (const pattern of SQL_INJECTION_PATTERNS) {
1719
+ if (pattern.test(value)) return true;
1720
+ }
1721
+ return false;
1722
+ }
821
1723
  function checkLengthLimits(value, maxLength = 4096) {
822
1724
  return value.length <= maxLength;
823
1725
  }
@@ -843,7 +1745,7 @@ function calculateShannonEntropy(str) {
843
1745
  }
844
1746
  return entropy;
845
1747
  }
846
- function sanitizeInput(field, value, config = DEFAULT_INPUT_GUARD_CONFIG) {
1748
+ function sanitizeInput(field, value, config = DEFAULT_INPUT_GUARD_CONFIG2) {
847
1749
  const threats = [];
848
1750
  if (typeof value !== "string") {
849
1751
  if (typeof value === "object" && value !== null) {
@@ -891,6 +1793,22 @@ function sanitizeInput(field, value, config = DEFAULT_INPUT_GUARD_CONFIG) {
891
1793
  description: "High entropy string detected - possible encoded payload"
892
1794
  });
893
1795
  }
1796
+ if (config.ssrf && detectSSRF2(value)) {
1797
+ threats.push({
1798
+ type: "SSRF",
1799
+ field,
1800
+ value: truncate(value, 100),
1801
+ description: "Server-side request forgery pattern detected \u2014 internal/metadata URL blocked"
1802
+ });
1803
+ }
1804
+ if (config.sqlInjection && detectSQLInjection(value)) {
1805
+ threats.push({
1806
+ type: "SQL_INJECTION",
1807
+ field,
1808
+ value: truncate(value, 100),
1809
+ description: "SQL injection pattern detected"
1810
+ });
1811
+ }
894
1812
  return { safe: threats.length === 0, threats };
895
1813
  }
896
1814
  function sanitizeObject(basePath, obj, config) {
@@ -914,9 +1832,6 @@ function truncate(str, maxLen) {
914
1832
  var DEFAULT_TOKEN_TTL_SECONDS = 30;
915
1833
  var TOKEN_ALGORITHM = "HS256";
916
1834
  var MIN_SECRET_LENGTH = 32;
917
-
918
- // ../policy-engine/dist/index.js
919
- import { createHash } from "crypto";
920
1835
  function normalizePath(path) {
921
1836
  let normalized = path.replace(/\\/g, "/");
922
1837
  if (normalized.length > 1 && normalized.endsWith("/")) {
@@ -1064,8 +1979,51 @@ function trustLevelMeetsMinimum(actual, minimum) {
1064
1979
  function argumentConstraintsMatch(constraints, args) {
1065
1980
  for (const [key, constraint] of Object.entries(constraints)) {
1066
1981
  if (!(key in args)) return false;
1067
- if (typeof constraint === "string" && typeof args[key] === "string") {
1068
- if (constraint !== "*" && args[key] !== constraint) return false;
1982
+ const argValue = args[key];
1983
+ if (typeof constraint === "string") {
1984
+ if (constraint === "*") continue;
1985
+ if (typeof argValue === "string") {
1986
+ if (argValue !== constraint) return false;
1987
+ } else {
1988
+ return false;
1989
+ }
1990
+ continue;
1991
+ }
1992
+ if (typeof constraint === "object" && constraint !== null && !Array.isArray(constraint)) {
1993
+ const ops = constraint;
1994
+ const strValue = typeof argValue === "string" ? argValue : void 0;
1995
+ const numValue = typeof argValue === "number" ? argValue : void 0;
1996
+ if ("$contains" in ops && typeof ops.$contains === "string") {
1997
+ if (!strValue || !strValue.includes(ops.$contains)) return false;
1998
+ }
1999
+ if ("$notContains" in ops && typeof ops.$notContains === "string") {
2000
+ if (strValue && strValue.includes(ops.$notContains)) return false;
2001
+ }
2002
+ if ("$startsWith" in ops && typeof ops.$startsWith === "string") {
2003
+ if (!strValue || !strValue.startsWith(ops.$startsWith)) return false;
2004
+ }
2005
+ if ("$endsWith" in ops && typeof ops.$endsWith === "string") {
2006
+ if (!strValue || !strValue.endsWith(ops.$endsWith)) return false;
2007
+ }
2008
+ if ("$in" in ops && Array.isArray(ops.$in)) {
2009
+ if (!ops.$in.includes(argValue)) return false;
2010
+ }
2011
+ if ("$notIn" in ops && Array.isArray(ops.$notIn)) {
2012
+ if (ops.$notIn.includes(argValue)) return false;
2013
+ }
2014
+ if ("$gt" in ops && typeof ops.$gt === "number") {
2015
+ if (numValue === void 0 || numValue <= ops.$gt) return false;
2016
+ }
2017
+ if ("$lt" in ops && typeof ops.$lt === "number") {
2018
+ if (numValue === void 0 || numValue >= ops.$lt) return false;
2019
+ }
2020
+ if ("$gte" in ops && typeof ops.$gte === "number") {
2021
+ if (numValue === void 0 || numValue < ops.$gte) return false;
2022
+ }
2023
+ if ("$lte" in ops && typeof ops.$lte === "number") {
2024
+ if (numValue === void 0 || numValue > ops.$lte) return false;
2025
+ }
2026
+ continue;
1069
2027
  }
1070
2028
  }
1071
2029
  return true;
@@ -1098,13 +2056,21 @@ function evaluatePolicy(policySet, request) {
1098
2056
  matchedRule: null,
1099
2057
  reason: "No matching policy rule found. Default action: DENY.",
1100
2058
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1101
- evaluationTimeMs: endTime - startTime
2059
+ evaluationTimeMs: endTime - startTime,
2060
+ metadata: {
2061
+ evaluatedRules: sortedRules.length,
2062
+ ruleIds: sortedRules.map((r) => r.id),
2063
+ requestContext: {
2064
+ tool: request.toolName,
2065
+ arguments: Object.keys(request.arguments ?? {})
2066
+ }
2067
+ }
1102
2068
  };
1103
2069
  }
1104
2070
  function validatePolicyRule(input) {
1105
2071
  const errors = [];
1106
2072
  const warnings = [];
1107
- const result = PolicyRuleSchema.safeParse(input);
2073
+ const result = PolicyRuleSchema2.safeParse(input);
1108
2074
  if (!result.success) {
1109
2075
  return {
1110
2076
  valid: false,
@@ -1129,7 +2095,7 @@ function validatePolicyRule(input) {
1129
2095
  function validatePolicySet(input) {
1130
2096
  const errors = [];
1131
2097
  const warnings = [];
1132
- const result = PolicySetSchema.safeParse(input);
2098
+ const result = PolicySetSchema2.safeParse(input);
1133
2099
  if (!result.success) {
1134
2100
  return {
1135
2101
  valid: false,
@@ -1223,7 +2189,7 @@ function createDefaultDenyPolicySet() {
1223
2189
  effect: PolicyEffect.DENY,
1224
2190
  priority: 1e4,
1225
2191
  toolPattern: "*",
1226
- permission: Permission.EXECUTE,
2192
+ permission: Permission2.EXECUTE,
1227
2193
  minimumTrustLevel: TrustLevel.UNTRUSTED,
1228
2194
  enabled: true,
1229
2195
  createdAt: now,
@@ -1235,7 +2201,7 @@ function createDefaultDenyPolicySet() {
1235
2201
  effect: PolicyEffect.DENY,
1236
2202
  priority: 1e4,
1237
2203
  toolPattern: "*",
1238
- permission: Permission.WRITE,
2204
+ permission: Permission2.WRITE,
1239
2205
  minimumTrustLevel: TrustLevel.UNTRUSTED,
1240
2206
  enabled: true,
1241
2207
  createdAt: now,
@@ -1247,7 +2213,7 @@ function createDefaultDenyPolicySet() {
1247
2213
  effect: PolicyEffect.DENY,
1248
2214
  priority: 1e4,
1249
2215
  toolPattern: "*",
1250
- permission: Permission.READ,
2216
+ permission: Permission2.READ,
1251
2217
  minimumTrustLevel: TrustLevel.UNTRUSTED,
1252
2218
  enabled: true,
1253
2219
  createdAt: now,
@@ -1417,9 +2383,6 @@ var PolicyStore = class {
1417
2383
  return createHash("sha256").update(serialized).digest("hex");
1418
2384
  }
1419
2385
  };
1420
-
1421
- // ../sdk-ts/dist/index.js
1422
- import { randomUUID, createHmac } from "crypto";
1423
2386
  var DEFAULT_CONFIG = Object.freeze({
1424
2387
  validateSchemas: true,
1425
2388
  enableLogging: true,
@@ -1429,7 +2392,7 @@ var DEFAULT_CONFIG = Object.freeze({
1429
2392
  globalRateLimitPerMinute: 600,
1430
2393
  rateLimitPerTool: 60,
1431
2394
  tokenTtlSeconds: 30,
1432
- inputGuardConfig: DEFAULT_INPUT_GUARD_CONFIG,
2395
+ inputGuardConfig: DEFAULT_INPUT_GUARD_CONFIG2,
1433
2396
  enableVersionedPolicies: true
1434
2397
  });
1435
2398
  function resolveConfig(userConfig) {
@@ -1462,7 +2425,7 @@ async function interceptToolCall(params, upstreamCall, options) {
1462
2425
  toolName: params.name,
1463
2426
  serverName: "default",
1464
2427
  arguments: params.arguments ?? {},
1465
- requiredPermission: Permission.EXECUTE,
2428
+ requiredPermission: Permission2.EXECUTE,
1466
2429
  timestamp
1467
2430
  };
1468
2431
  if (options.rateLimiter) {
@@ -1501,7 +2464,7 @@ async function interceptToolCall(params, upstreamCall, options) {
1501
2464
  }
1502
2465
  }
1503
2466
  if (options.validateSchemas && params.arguments) {
1504
- const guardConfig = options.inputGuardConfig ?? DEFAULT_INPUT_GUARD_CONFIG;
2467
+ const guardConfig = options.inputGuardConfig ?? DEFAULT_INPUT_GUARD_CONFIG2;
1505
2468
  const sanitization = sanitizeInput("arguments", params.arguments, guardConfig);
1506
2469
  if (!sanitization.safe) {
1507
2470
  const threatDescriptions = sanitization.threats.map(
@@ -1514,7 +2477,7 @@ async function interceptToolCall(params, upstreamCall, options) {
1514
2477
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1515
2478
  };
1516
2479
  options.onDecision?.(result);
1517
- const reason = options.verboseErrors ? `Input validation failed: ${threatDescriptions.join("; ")}` : "Input validation failed.";
2480
+ const reason = options.verboseErrors ? `Input validation failed: ${sanitization.threats.length} threat(s) detected` : "Input validation failed.";
1518
2481
  return createDeniedToolResult(reason);
1519
2482
  }
1520
2483
  }
@@ -1534,7 +2497,7 @@ async function interceptToolCall(params, upstreamCall, options) {
1534
2497
  if (options.tokenIssuer) {
1535
2498
  capabilityToken = options.tokenIssuer.issue(
1536
2499
  requestId,
1537
- [Permission.EXECUTE],
2500
+ [Permission2.EXECUTE],
1538
2501
  [params.name]
1539
2502
  );
1540
2503
  }
@@ -1832,6 +2795,25 @@ var RateLimiter = class {
1832
2795
  const resetAt = this.globalRecords.length > 0 ? this.globalRecords[0].timestamp + this.windowMs : now + this.windowMs;
1833
2796
  return { allowed, remaining, resetAt };
1834
2797
  }
2798
+ /**
2799
+ * Atomically checks and records a tool call.
2800
+ * Prevents TOCTOU race conditions between check and record.
2801
+ * Returns the rate limit result; if allowed, the call is already recorded.
2802
+ */
2803
+ checkAndRecord(toolName, limitPerWindow, globalLimit) {
2804
+ const result = this.checkLimit(toolName, limitPerWindow);
2805
+ if (!result.allowed) {
2806
+ return result;
2807
+ }
2808
+ if (globalLimit !== void 0) {
2809
+ const globalResult = this.checkGlobalLimit(globalLimit);
2810
+ if (!globalResult.allowed) {
2811
+ return globalResult;
2812
+ }
2813
+ }
2814
+ this.recordCall(toolName);
2815
+ return result;
2816
+ }
1835
2817
  /**
1836
2818
  * Records a tool call for rate limiting.
1837
2819
  * Call this after successful execution.
@@ -1887,6 +2869,16 @@ var RateLimiter = class {
1887
2869
  return active;
1888
2870
  }
1889
2871
  };
2872
+ var LicenseError = class extends Error {
2873
+ constructor(message) {
2874
+ super(
2875
+ `${message}
2876
+ Get your API key at https://solongate.com
2877
+ Usage: new SolonGate({ name: '...', apiKey: 'sg_live_xxx' })`
2878
+ );
2879
+ this.name = "LicenseError";
2880
+ }
2881
+ };
1890
2882
  var SolonGate = class {
1891
2883
  policyEngine;
1892
2884
  config;
@@ -1895,7 +2887,19 @@ var SolonGate = class {
1895
2887
  tokenIssuer;
1896
2888
  serverVerifier;
1897
2889
  rateLimiter;
2890
+ apiKey;
2891
+ licenseValidated = false;
1898
2892
  constructor(options) {
2893
+ const apiKey = options.apiKey || process.env.SOLONGATE_API_KEY || "";
2894
+ if (!apiKey) {
2895
+ throw new LicenseError("A valid SolonGate API key is required.");
2896
+ }
2897
+ if (!apiKey.startsWith("sg_live_") && !apiKey.startsWith("sg_test_")) {
2898
+ throw new LicenseError(
2899
+ "Invalid API key format. Keys must start with 'sg_live_' or 'sg_test_'."
2900
+ );
2901
+ }
2902
+ this.apiKey = apiKey;
1899
2903
  const { config, warnings } = resolveConfig(options.config);
1900
2904
  this.config = config;
1901
2905
  this.configWarnings = warnings;
@@ -1921,12 +2925,47 @@ var SolonGate = class {
1921
2925
  this.serverVerifier = config.gatewaySecret ? new ServerVerifier({ gatewaySecret: config.gatewaySecret }) : null;
1922
2926
  this.rateLimiter = new RateLimiter();
1923
2927
  }
2928
+ /**
2929
+ * Validate the API key against the SolonGate cloud API.
2930
+ * Called once on first executeToolCall. Throws LicenseError if invalid.
2931
+ * Test keys (sg_test_) skip online validation.
2932
+ */
2933
+ async validateLicense() {
2934
+ if (this.licenseValidated) return;
2935
+ if (this.apiKey.startsWith("sg_test_")) {
2936
+ this.licenseValidated = true;
2937
+ return;
2938
+ }
2939
+ const apiUrl = this.config.apiUrl ?? "https://api.solongate.com";
2940
+ try {
2941
+ const res = await fetch(`${apiUrl}/api/v1/auth/me`, {
2942
+ headers: {
2943
+ "X-API-Key": this.apiKey,
2944
+ "Authorization": `Bearer ${this.apiKey}`
2945
+ },
2946
+ signal: AbortSignal.timeout(1e4)
2947
+ });
2948
+ if (res.status === 401) {
2949
+ throw new LicenseError("Invalid or expired API key.");
2950
+ }
2951
+ if (res.status === 403) {
2952
+ throw new LicenseError("Your subscription is inactive. Renew at https://solongate.com");
2953
+ }
2954
+ this.licenseValidated = true;
2955
+ } catch (err) {
2956
+ if (err instanceof LicenseError) throw err;
2957
+ throw new LicenseError(
2958
+ "Unable to reach SolonGate license server. Check your internet connection."
2959
+ );
2960
+ }
2961
+ }
1924
2962
  /**
1925
2963
  * Intercept and evaluate a tool call against the full security pipeline.
1926
2964
  * If denied at any stage, returns an error result without calling upstream.
1927
2965
  * If allowed, calls upstream and returns the result.
1928
2966
  */
1929
2967
  async executeToolCall(params, upstreamCall) {
2968
+ await this.validateLicense();
1930
2969
  return interceptToolCall(params, upstreamCall, {
1931
2970
  policyEngine: this.policyEngine,
1932
2971
  validateSchemas: this.config.validateSchemas,
@@ -1976,8 +3015,8 @@ var Mutex = class {
1976
3015
  this.locked = true;
1977
3016
  return;
1978
3017
  }
1979
- return new Promise((resolve3) => {
1980
- this.queue.push(resolve3);
3018
+ return new Promise((resolve5) => {
3019
+ this.queue.push(resolve5);
1981
3020
  });
1982
3021
  }
1983
3022
  release() {
@@ -2018,9 +3057,31 @@ var SolonGateProxy = class {
2018
3057
  */
2019
3058
  async start() {
2020
3059
  log("Starting SolonGate Proxy...");
3060
+ const apiUrl = this.config.apiUrl ?? "https://api.solongate.com";
2021
3061
  if (this.config.apiKey) {
2022
- const apiUrl = this.config.apiUrl ?? "https://api.solongate.com";
2023
- log(`Cloud API: ${apiUrl}`);
3062
+ log(`Validating license with ${apiUrl}...`);
3063
+ try {
3064
+ const res = await fetch(`${apiUrl}/api/v1/auth/me`, {
3065
+ headers: {
3066
+ "X-API-Key": this.config.apiKey,
3067
+ "Authorization": `Bearer ${this.config.apiKey}`
3068
+ },
3069
+ signal: AbortSignal.timeout(1e4)
3070
+ });
3071
+ if (res.status === 401) {
3072
+ log("ERROR: Invalid or expired API key.");
3073
+ process.exit(1);
3074
+ }
3075
+ if (res.status === 403) {
3076
+ log("ERROR: Your subscription is inactive. Renew at https://solongate.com");
3077
+ process.exit(1);
3078
+ }
3079
+ log("License validated.");
3080
+ } catch (err) {
3081
+ log(`ERROR: Unable to reach SolonGate license server. Check your internet connection.`);
3082
+ log(`Details: ${err instanceof Error ? err.message : String(err)}`);
3083
+ process.exit(1);
3084
+ }
2024
3085
  try {
2025
3086
  const cloudPolicy = await fetchCloudPolicy(this.config.apiKey, apiUrl);
2026
3087
  this.config.policy = cloudPolicy;
@@ -2030,29 +3091,54 @@ var SolonGateProxy = class {
2030
3091
  }
2031
3092
  }
2032
3093
  log(`Policy: ${this.config.policy.name} (${this.config.policy.rules.length} rules)`);
2033
- log(`Upstream: ${this.config.upstream.command} ${(this.config.upstream.args ?? []).join(" ")}`);
3094
+ const transport = this.config.upstream.transport ?? "stdio";
3095
+ if (transport === "stdio") {
3096
+ log(`Upstream: [stdio] ${this.config.upstream.command} ${(this.config.upstream.args ?? []).join(" ")}`);
3097
+ } else {
3098
+ log(`Upstream: [${transport}] ${this.config.upstream.url}`);
3099
+ }
2034
3100
  await this.connectUpstream();
2035
3101
  await this.discoverTools();
2036
3102
  this.createServer();
2037
3103
  await this.serve();
2038
3104
  }
2039
3105
  /**
2040
- * Connect to the upstream MCP server by spawning it as a child process.
3106
+ * Connect to the upstream MCP server.
3107
+ * Supports stdio (child process), SSE, and StreamableHTTP transports.
2041
3108
  */
2042
3109
  async connectUpstream() {
2043
3110
  this.client = new Client(
2044
3111
  { name: "solongate-proxy-client", version: "0.1.0" },
2045
3112
  { capabilities: {} }
2046
3113
  );
2047
- const transport = new StdioClientTransport({
2048
- command: this.config.upstream.command,
2049
- args: this.config.upstream.args,
2050
- env: this.config.upstream.env,
2051
- cwd: this.config.upstream.cwd,
2052
- stderr: "pipe"
2053
- });
2054
- await this.client.connect(transport);
2055
- log("Connected to upstream server");
3114
+ const upstreamTransport = this.config.upstream.transport ?? "stdio";
3115
+ switch (upstreamTransport) {
3116
+ case "sse": {
3117
+ if (!this.config.upstream.url) throw new Error("--upstream-url required for SSE transport");
3118
+ const transport = new SSEClientTransport(new URL(this.config.upstream.url));
3119
+ await this.client.connect(transport);
3120
+ break;
3121
+ }
3122
+ case "http": {
3123
+ if (!this.config.upstream.url) throw new Error("--upstream-url required for HTTP transport");
3124
+ const transport = new StreamableHTTPClientTransport(new URL(this.config.upstream.url));
3125
+ await this.client.connect(transport);
3126
+ break;
3127
+ }
3128
+ case "stdio":
3129
+ default: {
3130
+ const transport = new StdioClientTransport({
3131
+ command: this.config.upstream.command,
3132
+ args: this.config.upstream.args,
3133
+ env: this.config.upstream.env,
3134
+ cwd: this.config.upstream.cwd,
3135
+ stderr: "pipe"
3136
+ });
3137
+ await this.client.connect(transport);
3138
+ break;
3139
+ }
3140
+ }
3141
+ log(`Connected to upstream server (${upstreamTransport})`);
2056
3142
  }
2057
3143
  /**
2058
3144
  * Discover tools from the upstream server.
@@ -2091,8 +3177,17 @@ var SolonGateProxy = class {
2091
3177
  this.server.setRequestHandler(ListToolsRequestSchema, async () => {
2092
3178
  return { tools: this.upstreamTools };
2093
3179
  });
3180
+ const MAX_ARGUMENT_SIZE = 1024 * 1024;
2094
3181
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
2095
3182
  const { name, arguments: args } = request.params;
3183
+ const argsSize = JSON.stringify(args ?? {}).length;
3184
+ if (argsSize > MAX_ARGUMENT_SIZE) {
3185
+ log(`DENY: ${name} \u2014 payload size ${argsSize} exceeds limit ${MAX_ARGUMENT_SIZE}`);
3186
+ return {
3187
+ content: [{ type: "text", text: `Request payload too large (${Math.round(argsSize / 1024)}KB > ${Math.round(MAX_ARGUMENT_SIZE / 1024)}KB limit)` }],
3188
+ isError: true
3189
+ };
3190
+ }
2096
3191
  log(`Tool call: ${name}`);
2097
3192
  await this.callMutex.acquire();
2098
3193
  const startTime = Date.now();
@@ -2166,14 +3261,38 @@ var SolonGateProxy = class {
2166
3261
  });
2167
3262
  }
2168
3263
  /**
2169
- * Start serving on stdio (downstream to Claude).
3264
+ * Start serving downstream.
3265
+ * If --port is set, serves via StreamableHTTP on that port.
3266
+ * Otherwise, serves on stdio (default for Claude Code / Cursor / etc).
2170
3267
  */
2171
3268
  async serve() {
2172
3269
  if (!this.server) throw new Error("Server not created");
2173
- const transport = new StdioServerTransport();
2174
- await this.server.connect(transport);
2175
- log("Proxy is live. All tool calls are now protected by SolonGate.");
2176
- log("Waiting for requests...");
3270
+ if (this.config.port) {
3271
+ const httpTransport = new StreamableHTTPServerTransport({
3272
+ sessionIdGenerator: () => crypto.randomUUID()
3273
+ });
3274
+ await this.server.connect(httpTransport);
3275
+ const httpServer = createHttpServer(async (req, res) => {
3276
+ if (req.url === "/mcp" || req.url?.startsWith("/mcp?")) {
3277
+ await httpTransport.handleRequest(req, res);
3278
+ } else if (req.url === "/health") {
3279
+ res.writeHead(200, { "Content-Type": "application/json" });
3280
+ res.end(JSON.stringify({ status: "healthy", proxy: this.config.name ?? "solongate-proxy" }));
3281
+ } else {
3282
+ res.writeHead(404);
3283
+ res.end("Not found. Use /mcp for MCP protocol or /health for health check.");
3284
+ }
3285
+ });
3286
+ httpServer.listen(this.config.port, () => {
3287
+ log(`Proxy is live on http://localhost:${this.config.port}/mcp`);
3288
+ log("All tool calls are now protected by SolonGate.");
3289
+ });
3290
+ } else {
3291
+ const transport = new StdioServerTransport();
3292
+ await this.server.connect(transport);
3293
+ log("Proxy is live. All tool calls are now protected by SolonGate.");
3294
+ log("Waiting for requests...");
3295
+ }
2177
3296
  }
2178
3297
  };
2179
3298
 
@@ -2190,13 +3309,23 @@ console.error = (...args) => {
2190
3309
  process.stderr.write(`[SolonGate ERROR] ${args.map(String).join(" ")}
2191
3310
  `);
2192
3311
  };
2193
- async function main2() {
3312
+ async function main4() {
2194
3313
  const subcommand = process.argv[2];
2195
3314
  if (subcommand === "init") {
2196
3315
  process.argv.splice(2, 1);
2197
3316
  await Promise.resolve().then(() => (init_init(), init_exports));
2198
3317
  return;
2199
3318
  }
3319
+ if (subcommand === "inject") {
3320
+ process.argv.splice(2, 1);
3321
+ await Promise.resolve().then(() => (init_inject(), inject_exports));
3322
+ return;
3323
+ }
3324
+ if (subcommand === "create") {
3325
+ process.argv.splice(2, 1);
3326
+ await Promise.resolve().then(() => (init_create(), create_exports));
3327
+ return;
3328
+ }
2200
3329
  try {
2201
3330
  const config = parseArgs(process.argv);
2202
3331
  const proxy = new SolonGateProxy(config);
@@ -2208,4 +3337,4 @@ async function main2() {
2208
3337
  process.exit(1);
2209
3338
  }
2210
3339
  }
2211
- main2();
3340
+ main4();