@solongate/proxy 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/init.js ADDED
@@ -0,0 +1,275 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/init.ts
4
+ import { readFileSync, writeFileSync, existsSync, copyFileSync } from "fs";
5
+ import { resolve, join, dirname } from "path";
6
+ import { createInterface } from "readline";
7
+ var POLICY_PRESETS = ["restricted", "read-only", "permissive", "deny-all"];
8
+ var SEARCH_PATHS = [
9
+ ".mcp.json",
10
+ "mcp.json",
11
+ ".claude/mcp.json"
12
+ ];
13
+ var CLAUDE_DESKTOP_PATHS = process.platform === "win32" ? [join(process.env["APPDATA"] ?? "", "Claude", "claude_desktop_config.json")] : process.platform === "darwin" ? [join(process.env["HOME"] ?? "", "Library", "Application Support", "Claude", "claude_desktop_config.json")] : [join(process.env["HOME"] ?? "", ".config", "claude", "claude_desktop_config.json")];
14
+ function findProxyPath() {
15
+ const candidates = [
16
+ resolve(dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/, "$1")), "index.js"),
17
+ resolve("node_modules", "@solongate", "proxy", "dist", "index.js"),
18
+ resolve("packages", "proxy", "dist", "index.js")
19
+ ];
20
+ for (const p of candidates) {
21
+ if (existsSync(p)) return p.replace(/\\/g, "/");
22
+ }
23
+ return "solongate-proxy";
24
+ }
25
+ function findConfigFile(explicitPath) {
26
+ if (explicitPath) {
27
+ if (existsSync(explicitPath)) {
28
+ return { path: resolve(explicitPath), type: "mcp" };
29
+ }
30
+ return null;
31
+ }
32
+ for (const searchPath of SEARCH_PATHS) {
33
+ const full = resolve(searchPath);
34
+ if (existsSync(full)) return { path: full, type: "mcp" };
35
+ }
36
+ for (const desktopPath of CLAUDE_DESKTOP_PATHS) {
37
+ if (existsSync(desktopPath)) return { path: desktopPath, type: "claude-desktop" };
38
+ }
39
+ return null;
40
+ }
41
+ function readConfig(filePath) {
42
+ const content = readFileSync(filePath, "utf-8");
43
+ const parsed = JSON.parse(content);
44
+ if (parsed.mcpServers) return parsed;
45
+ throw new Error(`Unrecognized config format in ${filePath}`);
46
+ }
47
+ function isAlreadyProtected(server) {
48
+ const cmdStr = [server.command, ...server.args ?? []].join(" ");
49
+ return cmdStr.includes("solongate") || cmdStr.includes("solongate-proxy");
50
+ }
51
+ function wrapServer(server, policy, proxyPath) {
52
+ return {
53
+ command: "node",
54
+ args: [
55
+ proxyPath,
56
+ "--policy",
57
+ policy,
58
+ "--verbose",
59
+ "--",
60
+ server.command,
61
+ ...server.args ?? []
62
+ ],
63
+ env: server.env
64
+ };
65
+ }
66
+ async function prompt(question) {
67
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
68
+ return new Promise((res) => {
69
+ rl.question(question, (answer) => {
70
+ rl.close();
71
+ res(answer.trim());
72
+ });
73
+ });
74
+ }
75
+ function parseInitArgs(argv) {
76
+ const args = argv.slice(2);
77
+ const options = {
78
+ policy: "restricted",
79
+ all: false,
80
+ dryRun: false,
81
+ restore: false
82
+ };
83
+ for (let i = 0; i < args.length; i++) {
84
+ switch (args[i]) {
85
+ case "--config":
86
+ options.configPath = args[++i];
87
+ break;
88
+ case "--policy":
89
+ options.policy = args[++i];
90
+ break;
91
+ case "--all":
92
+ options.all = true;
93
+ break;
94
+ case "--dry-run":
95
+ options.dryRun = true;
96
+ break;
97
+ case "--restore":
98
+ options.restore = true;
99
+ break;
100
+ case "--help":
101
+ case "-h":
102
+ printHelp();
103
+ process.exit(0);
104
+ }
105
+ }
106
+ if (!POLICY_PRESETS.includes(options.policy)) {
107
+ if (!existsSync(resolve(options.policy))) {
108
+ console.error(`Unknown policy: ${options.policy}`);
109
+ console.error(`Available presets: ${POLICY_PRESETS.join(", ")}`);
110
+ process.exit(1);
111
+ }
112
+ }
113
+ return options;
114
+ }
115
+ function printHelp() {
116
+ const help = `
117
+ SolonGate Init \u2014 Protect your MCP servers in seconds
118
+
119
+ USAGE
120
+ solongate-init [options]
121
+
122
+ OPTIONS
123
+ --config <path> Path to MCP config file (default: auto-detect)
124
+ --policy <preset> Policy preset or JSON file (default: restricted)
125
+ Presets: ${POLICY_PRESETS.join(", ")}
126
+ --all Protect all servers without prompting
127
+ --dry-run Preview changes without writing
128
+ --restore Restore original config from backup
129
+ -h, --help Show this help message
130
+
131
+ EXAMPLES
132
+ solongate-init # Interactive setup
133
+ solongate-init --all # Protect everything
134
+ solongate-init --policy read-only # Use read-only policy
135
+ solongate-init --dry-run # Preview changes
136
+ solongate-init --restore # Undo protection
137
+
138
+ POLICY PRESETS
139
+ restricted Block shell/exec/eval, allow reads and writes (recommended)
140
+ read-only Only allow read/list/get/search/query operations
141
+ permissive Allow everything (monitoring + audit only)
142
+ deny-all Block all tool calls
143
+ `;
144
+ console.error(help);
145
+ }
146
+ async function main() {
147
+ const options = parseInitArgs(process.argv);
148
+ console.error("");
149
+ console.error(" \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");
150
+ console.error(" \u2551 SolonGate \u2014 Init Setup \u2551");
151
+ console.error(" \u2551 Secure your MCP servers in seconds \u2551");
152
+ console.error(" \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");
153
+ console.error("");
154
+ const configInfo = findConfigFile(options.configPath);
155
+ if (!configInfo) {
156
+ console.error(" No MCP config file found.");
157
+ console.error(" Searched: .mcp.json, mcp.json, Claude Desktop config");
158
+ console.error("");
159
+ console.error(" Create a .mcp.json file first, or specify --config <path>");
160
+ process.exit(1);
161
+ }
162
+ console.error(` Config: ${configInfo.path}`);
163
+ console.error(` Type: ${configInfo.type === "claude-desktop" ? "Claude Desktop" : "MCP JSON"}`);
164
+ console.error("");
165
+ const backupPath = configInfo.path + ".solongate-backup";
166
+ if (options.restore) {
167
+ if (!existsSync(backupPath)) {
168
+ console.error(" No backup found. Nothing to restore.");
169
+ process.exit(1);
170
+ }
171
+ copyFileSync(backupPath, configInfo.path);
172
+ console.error(" Restored original config from backup.");
173
+ process.exit(0);
174
+ }
175
+ const config = readConfig(configInfo.path);
176
+ const serverNames = Object.keys(config.mcpServers);
177
+ if (serverNames.length === 0) {
178
+ console.error(" No MCP servers found in config.");
179
+ process.exit(1);
180
+ }
181
+ console.error(` Found ${serverNames.length} MCP server(s):`);
182
+ console.error("");
183
+ const toProtect = [];
184
+ const alreadyProtected = [];
185
+ const skipped = [];
186
+ for (const name of serverNames) {
187
+ const server = config.mcpServers[name];
188
+ if (isAlreadyProtected(server)) {
189
+ alreadyProtected.push(name);
190
+ console.error(` [protected] ${name}`);
191
+ } else {
192
+ console.error(` [exposed] ${name} \u2192 ${server.command} ${(server.args ?? []).join(" ")}`);
193
+ if (options.all) {
194
+ toProtect.push(name);
195
+ }
196
+ }
197
+ }
198
+ console.error("");
199
+ if (alreadyProtected.length === serverNames.length) {
200
+ console.error(" All servers are already protected by SolonGate!");
201
+ process.exit(0);
202
+ }
203
+ if (!options.all) {
204
+ const exposed = serverNames.filter((n) => !alreadyProtected.includes(n));
205
+ for (const name of exposed) {
206
+ const answer = await prompt(` Protect "${name}"? [Y/n] `);
207
+ if (answer === "" || answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") {
208
+ toProtect.push(name);
209
+ } else {
210
+ skipped.push(name);
211
+ }
212
+ }
213
+ }
214
+ if (toProtect.length === 0) {
215
+ console.error(" No servers selected for protection.");
216
+ process.exit(0);
217
+ }
218
+ console.error("");
219
+ console.error(` Policy: ${options.policy}`);
220
+ console.error(` Protecting: ${toProtect.join(", ")}`);
221
+ console.error("");
222
+ const proxyPath = findProxyPath();
223
+ const newConfig = { mcpServers: {} };
224
+ for (const name of serverNames) {
225
+ if (toProtect.includes(name)) {
226
+ newConfig.mcpServers[name] = wrapServer(config.mcpServers[name], options.policy, proxyPath);
227
+ } else {
228
+ newConfig.mcpServers[name] = config.mcpServers[name];
229
+ }
230
+ }
231
+ if (options.dryRun) {
232
+ console.error(" --- DRY RUN (no changes written) ---");
233
+ console.error("");
234
+ console.error(" New config:");
235
+ console.error(JSON.stringify(newConfig, null, 2));
236
+ process.exit(0);
237
+ }
238
+ if (!existsSync(backupPath)) {
239
+ copyFileSync(configInfo.path, backupPath);
240
+ console.error(` Backup: ${backupPath}`);
241
+ }
242
+ if (configInfo.type === "claude-desktop") {
243
+ const original = JSON.parse(readFileSync(configInfo.path, "utf-8"));
244
+ original.mcpServers = newConfig.mcpServers;
245
+ writeFileSync(configInfo.path, JSON.stringify(original, null, 2) + "\n");
246
+ } else {
247
+ writeFileSync(configInfo.path, JSON.stringify(newConfig, null, 2) + "\n");
248
+ }
249
+ console.error(" Config updated!");
250
+ console.error("");
251
+ console.error(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
252
+ console.error(" \u2502 Your MCP servers are now protected by \u2502");
253
+ console.error(" \u2502 SolonGate security policies. \u2502");
254
+ console.error(" \u2502 \u2502");
255
+ console.error(" \u2502 Restart your MCP client (Claude Code \u2502");
256
+ console.error(" \u2502 or Claude Desktop) to apply changes. \u2502");
257
+ console.error(" \u2502 \u2502");
258
+ console.error(" \u2502 To undo: solongate-init --restore \u2502");
259
+ console.error(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
260
+ console.error("");
261
+ for (const name of toProtect) {
262
+ console.error(` \u2713 ${name} \u2014 protected (${options.policy})`);
263
+ }
264
+ for (const name of alreadyProtected) {
265
+ console.error(` \u25CF ${name} \u2014 already protected`);
266
+ }
267
+ for (const name of skipped) {
268
+ console.error(` \u25CB ${name} \u2014 skipped`);
269
+ }
270
+ console.error("");
271
+ }
272
+ main().catch((err) => {
273
+ console.error(`Fatal: ${err instanceof Error ? err.message : String(err)}`);
274
+ process.exit(1);
275
+ });
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@solongate/proxy",
3
+ "version": "0.1.0",
4
+ "description": "MCP security proxy — protect any MCP server with policies, input validation, rate limiting, and audit logging. Zero code changes required.",
5
+ "type": "module",
6
+ "bin": {
7
+ "solongate-proxy": "./dist/index.js",
8
+ "solongate-init": "./dist/init.js"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsup",
22
+ "dev": "tsx src/index.ts",
23
+ "typecheck": "tsc --noEmit",
24
+ "clean": "rm -rf dist .turbo"
25
+ },
26
+ "keywords": [
27
+ "mcp",
28
+ "model-context-protocol",
29
+ "security",
30
+ "proxy",
31
+ "gateway",
32
+ "firewall",
33
+ "ai-security",
34
+ "tool-security",
35
+ "claude",
36
+ "openclaw",
37
+ "solongate",
38
+ "prompt-injection",
39
+ "path-traversal",
40
+ "rate-limiting"
41
+ ],
42
+ "author": "SolonGate <hello@solongate.com>",
43
+ "license": "MIT",
44
+ "homepage": "https://solongate.com",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "https://github.com/solongate/solongate"
48
+ },
49
+ "engines": {
50
+ "node": ">=18.0.0"
51
+ },
52
+ "dependencies": {
53
+ "@modelcontextprotocol/sdk": "^1.12.0",
54
+ "zod": "^3.25.0"
55
+ },
56
+ "devDependencies": {
57
+ "@solongate/sdk": "workspace:*",
58
+ "@solongate/core": "workspace:*",
59
+ "@solongate/policy-engine": "workspace:*",
60
+ "@solongate/tsconfig": "workspace:*",
61
+ "tsup": "^8.3.0",
62
+ "tsx": "^4.19.0",
63
+ "typescript": "^5.7.0"
64
+ }
65
+ }