@sonoma-security/mcp-gateway 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.
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Secure token storage for MCP Gateway OAuth credentials
3
+ *
4
+ * Stores tokens in ~/.sonoma/gateway-credentials.json with AES-256-GCM encryption.
5
+ * The encryption key is derived from a machine-specific identifier to prevent
6
+ * tokens from being copied between machines.
7
+ */
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
9
+ import { homedir, hostname } from "node:os";
10
+ import { join } from "node:path";
11
+ import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "node:crypto";
12
+ const SONOMA_DIR = join(homedir(), ".sonoma");
13
+ const CREDENTIALS_PATH = join(SONOMA_DIR, "gateway-credentials.json");
14
+ const DEVICE_ID_PATH = join(SONOMA_DIR, "device-id");
15
+ // Encryption settings
16
+ const ALGORITHM = "aes-256-gcm";
17
+ const KEY_LENGTH = 32;
18
+ const IV_LENGTH = 16;
19
+ const SALT_LENGTH = 32;
20
+ /**
21
+ * Get or create a stable device ID for this machine
22
+ */
23
+ function getDeviceId() {
24
+ ensureSonomaDir();
25
+ if (existsSync(DEVICE_ID_PATH)) {
26
+ return readFileSync(DEVICE_ID_PATH, "utf-8").trim();
27
+ }
28
+ // Generate new device ID: dev_{random_hex}
29
+ const id = `dev_${randomBytes(16).toString("hex")}`;
30
+ writeFileSync(DEVICE_ID_PATH, id);
31
+ return id;
32
+ }
33
+ /**
34
+ * Derive an encryption key from machine-specific data
35
+ */
36
+ function deriveKey(salt) {
37
+ // Use device ID and hostname as key material
38
+ const deviceId = getDeviceId();
39
+ const keyMaterial = `${deviceId}:${hostname()}:sonoma-mcp-gateway`;
40
+ return scryptSync(keyMaterial, salt, KEY_LENGTH);
41
+ }
42
+ /**
43
+ * Encrypt data using AES-256-GCM
44
+ */
45
+ function encrypt(data) {
46
+ const salt = randomBytes(SALT_LENGTH);
47
+ const key = deriveKey(salt);
48
+ const iv = randomBytes(IV_LENGTH);
49
+ const cipher = createCipheriv(ALGORITHM, key, iv);
50
+ let encrypted = cipher.update(data, "utf8", "hex");
51
+ encrypted += cipher.final("hex");
52
+ const authTag = cipher.getAuthTag();
53
+ // Format: salt:iv:authTag:encrypted (all hex)
54
+ return [salt.toString("hex"), iv.toString("hex"), authTag.toString("hex"), encrypted].join(":");
55
+ }
56
+ /**
57
+ * Decrypt data using AES-256-GCM
58
+ */
59
+ function decrypt(encryptedData) {
60
+ const [saltHex, ivHex, authTagHex, encrypted] = encryptedData.split(":");
61
+ if (!saltHex || !ivHex || !authTagHex || !encrypted) {
62
+ throw new Error("Invalid encrypted data format");
63
+ }
64
+ const salt = Buffer.from(saltHex, "hex");
65
+ const iv = Buffer.from(ivHex, "hex");
66
+ const authTag = Buffer.from(authTagHex, "hex");
67
+ const key = deriveKey(salt);
68
+ const decipher = createDecipheriv(ALGORITHM, key, iv);
69
+ decipher.setAuthTag(authTag);
70
+ let decrypted = decipher.update(encrypted, "hex", "utf8");
71
+ decrypted += decipher.final("utf8");
72
+ return decrypted;
73
+ }
74
+ /**
75
+ * Ensure the ~/.sonoma directory exists
76
+ */
77
+ function ensureSonomaDir() {
78
+ if (!existsSync(SONOMA_DIR)) {
79
+ mkdirSync(SONOMA_DIR, { recursive: true, mode: 0o700 });
80
+ }
81
+ }
82
+ /**
83
+ * Load stored credentials from disk
84
+ */
85
+ export function loadCredentials() {
86
+ if (!existsSync(CREDENTIALS_PATH)) {
87
+ return null;
88
+ }
89
+ try {
90
+ const encryptedData = readFileSync(CREDENTIALS_PATH, "utf-8");
91
+ const decrypted = decrypt(encryptedData);
92
+ return JSON.parse(decrypted);
93
+ }
94
+ catch (error) {
95
+ // If decryption fails (e.g., machine changed), return null
96
+ console.error("[auth] Failed to load credentials:", error);
97
+ return null;
98
+ }
99
+ }
100
+ /**
101
+ * Save credentials to disk (encrypted)
102
+ */
103
+ export function saveCredentials(credentials) {
104
+ ensureSonomaDir();
105
+ const data = JSON.stringify(credentials, null, 2);
106
+ const encrypted = encrypt(data);
107
+ writeFileSync(CREDENTIALS_PATH, encrypted, { mode: 0o600 });
108
+ }
109
+ /**
110
+ * Clear all stored credentials
111
+ */
112
+ export function clearCredentials() {
113
+ if (existsSync(CREDENTIALS_PATH)) {
114
+ unlinkSync(CREDENTIALS_PATH);
115
+ }
116
+ }
117
+ /**
118
+ * Convert stored credentials to MCP SDK OAuthTokens format
119
+ */
120
+ export function toOAuthTokens(creds) {
121
+ if (!creds.accessToken) {
122
+ return undefined;
123
+ }
124
+ return {
125
+ access_token: creds.accessToken,
126
+ token_type: creds.tokenType || "Bearer",
127
+ refresh_token: creds.refreshToken,
128
+ expires_in: creds.expiresAt ? Math.floor((creds.expiresAt - Date.now()) / 1000) : undefined,
129
+ scope: creds.scope,
130
+ };
131
+ }
132
+ /**
133
+ * Convert MCP SDK OAuthTokens to stored credentials format
134
+ */
135
+ export function fromOAuthTokens(tokens, existing) {
136
+ const expiresAt = tokens.expires_in ? Date.now() + tokens.expires_in * 1000 : undefined;
137
+ return {
138
+ ...existing,
139
+ accessToken: tokens.access_token,
140
+ refreshToken: tokens.refresh_token || existing?.refreshToken,
141
+ expiresAt,
142
+ tokenType: tokens.token_type,
143
+ scope: tokens.scope,
144
+ lastRefreshed: Date.now(),
145
+ };
146
+ }
147
+ /**
148
+ * Convert stored credentials to MCP SDK client information format
149
+ */
150
+ export function toClientInformation(creds) {
151
+ if (!creds.clientId) {
152
+ return undefined;
153
+ }
154
+ return {
155
+ client_id: creds.clientId,
156
+ client_secret: creds.clientSecret,
157
+ client_secret_expires_at: creds.clientSecretExpiresAt,
158
+ };
159
+ }
160
+ /**
161
+ * Check if stored tokens are expired or about to expire
162
+ */
163
+ export function isTokenExpired(creds, bufferMs = 60000) {
164
+ if (!creds.accessToken || !creds.expiresAt) {
165
+ return true;
166
+ }
167
+ return Date.now() + bufferMs >= creds.expiresAt;
168
+ }
169
+ /**
170
+ * Check if we have a valid refresh token
171
+ */
172
+ export function hasRefreshToken(creds) {
173
+ return !!creds.refreshToken;
174
+ }
175
+ /**
176
+ * Get device ID for telemetry
177
+ */
178
+ export function getStoredDeviceId() {
179
+ return getDeviceId();
180
+ }
181
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/auth/storage.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI entry point for MCP Gateway
4
+ *
5
+ * Usage:
6
+ * npx @sonoma/mcp-gateway --config ./config.json
7
+ * npx @sonoma/mcp-gateway --auto # Auto-detect Claude Desktop config
8
+ * npx @sonoma/mcp-gateway --login # Authenticate with Sonoma
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;GAOG"}
package/dist/cli.js ADDED
@@ -0,0 +1,272 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI entry point for MCP Gateway
4
+ *
5
+ * Usage:
6
+ * npx @sonoma/mcp-gateway --config ./config.json
7
+ * npx @sonoma/mcp-gateway --auto # Auto-detect Claude Desktop config
8
+ * npx @sonoma/mcp-gateway --login # Authenticate with Sonoma
9
+ */
10
+ import { parseArgs } from "node:util";
11
+ import { readFileSync, existsSync } from "node:fs";
12
+ import { McpGateway } from "./gateway";
13
+ import { loadConfig, findClaudeDesktopConfig, loadFromParentConfig } from "./config";
14
+ import { login, logout, getAuthStatus } from "./auth";
15
+ import { SonomaClient } from "./sonoma-client";
16
+ const DEFAULT_SONOMA_ENDPOINT = "https://app.sonoma.dev";
17
+ const MDM_CONFIG_PATH = "/usr/local/etc/sonoma/config";
18
+ /**
19
+ * Read org API key from MDM config file or environment
20
+ * Priority: 1. SONOMA_API_KEY env var, 2. /usr/local/etc/sonoma/config
21
+ */
22
+ function getOrgApiKey() {
23
+ // Check env var first
24
+ if (process.env.SONOMA_API_KEY) {
25
+ return {
26
+ apiKey: process.env.SONOMA_API_KEY,
27
+ endpoint: process.env.SONOMA_ENDPOINT || null,
28
+ };
29
+ }
30
+ // Check MDM config file
31
+ if (existsSync(MDM_CONFIG_PATH)) {
32
+ try {
33
+ const content = readFileSync(MDM_CONFIG_PATH, "utf-8");
34
+ const lines = content.split("\n");
35
+ let apiKey = null;
36
+ let endpoint = null;
37
+ for (const line of lines) {
38
+ const [key, ...valueParts] = line.split("=");
39
+ const value = valueParts.join("=").trim();
40
+ if (key?.trim() === "API_KEY")
41
+ apiKey = value;
42
+ if (key?.trim() === "ENDPOINT")
43
+ endpoint = value;
44
+ }
45
+ return { apiKey, endpoint };
46
+ }
47
+ catch {
48
+ // Ignore read errors
49
+ }
50
+ }
51
+ return { apiKey: null, endpoint: null };
52
+ }
53
+ async function main() {
54
+ const { values } = parseArgs({
55
+ options: {
56
+ config: { type: "string", short: "c" },
57
+ auto: { type: "boolean", short: "a" },
58
+ // Read servers from parent config file (like Lasso MCP Gateway)
59
+ // This enables single-file config where servers are nested under our entry
60
+ "mcp-json-path": { type: "string" },
61
+ debug: { type: "boolean", short: "d" },
62
+ help: { type: "boolean", short: "h" },
63
+ version: { type: "boolean", short: "v" },
64
+ // Auth commands
65
+ login: { type: "boolean" },
66
+ logout: { type: "boolean" },
67
+ status: { type: "boolean" },
68
+ endpoint: { type: "string", short: "e" },
69
+ },
70
+ strict: true,
71
+ });
72
+ if (values.help) {
73
+ printHelp();
74
+ process.exit(0);
75
+ }
76
+ if (values.version) {
77
+ console.error("@sonoma/mcp-gateway v0.1.0");
78
+ process.exit(0);
79
+ }
80
+ // Auth commands
81
+ const sonomaEndpoint = values.endpoint || DEFAULT_SONOMA_ENDPOINT;
82
+ if (values.login) {
83
+ await login({ sonomaEndpoint, debug: values.debug });
84
+ process.exit(0);
85
+ }
86
+ if (values.logout) {
87
+ logout();
88
+ process.exit(0);
89
+ }
90
+ if (values.status) {
91
+ const status = getAuthStatus();
92
+ if (status.loggedIn) {
93
+ console.error("Logged in to:", status.endpoint);
94
+ console.error("Token expires:", status.expiresAt?.toISOString() || "unknown");
95
+ console.error("Has refresh token:", status.hasRefreshToken);
96
+ }
97
+ else {
98
+ console.error("Not logged in. Run: sonoma-gateway --login");
99
+ }
100
+ process.exit(0);
101
+ }
102
+ // Gateway mode
103
+ let config;
104
+ if (values["mcp-json-path"]) {
105
+ // Single-file config pattern (like Lasso MCP Gateway)
106
+ // Reads nested "servers" from parent config file
107
+ console.error(`Reading servers from: ${values["mcp-json-path"]}`);
108
+ config = loadFromParentConfig(values["mcp-json-path"]);
109
+ }
110
+ else if (values.config) {
111
+ config = loadConfig(values.config);
112
+ }
113
+ else if (values.auto) {
114
+ const claudeConfig = findClaudeDesktopConfig();
115
+ if (!claudeConfig) {
116
+ console.error("Could not find Claude Desktop config");
117
+ process.exit(1);
118
+ }
119
+ console.error(`Using Claude Desktop config: ${claudeConfig}`);
120
+ config = loadConfig(claudeConfig);
121
+ }
122
+ else {
123
+ console.error("No config specified. Use --config, --mcp-json-path, or --auto");
124
+ printHelp();
125
+ process.exit(1);
126
+ }
127
+ if (values.debug) {
128
+ config.debug = true;
129
+ }
130
+ // Set endpoint from CLI or config or MDM config
131
+ const orgConfig = getOrgApiKey();
132
+ if (!config.sonomaEndpoint) {
133
+ config.sonomaEndpoint = orgConfig.endpoint || sonomaEndpoint;
134
+ }
135
+ // Store org API key in config for gateway
136
+ if (orgConfig.apiKey) {
137
+ config.sonomaApiKey = orgConfig.apiKey;
138
+ if (values.debug) {
139
+ console.error("[cli] Found org API key from", process.env.SONOMA_API_KEY ? "env" : MDM_CONFIG_PATH);
140
+ }
141
+ }
142
+ // Check auth status and handle auto-login if needed
143
+ let authStatus = getAuthStatus();
144
+ if (!authStatus.loggedIn && !config.sonomaApiKey) {
145
+ // No OAuth token and no org API key - need to login
146
+ console.error("No authentication found. Opening browser to login...");
147
+ console.error("");
148
+ await login({ sonomaEndpoint: config.sonomaEndpoint, debug: values.debug });
149
+ authStatus = getAuthStatus();
150
+ }
151
+ // Fetch policy to check gatewayAuthMode
152
+ // Admin can require user_id mode which forces OAuth even if org key exists
153
+ if (config.sonomaEndpoint) {
154
+ const tempClient = new SonomaClient({
155
+ endpoint: config.sonomaEndpoint,
156
+ orgApiKey: config.sonomaApiKey,
157
+ debug: values.debug,
158
+ });
159
+ try {
160
+ const policy = await tempClient.fetchPolicy();
161
+ if (values.debug) {
162
+ console.error(`[cli] Policy: mode=${policy.mode}, authMode=${policy.gatewayAuthMode}`);
163
+ }
164
+ // If admin requires user_id mode but we only have org key, force OAuth login
165
+ if (policy.gatewayAuthMode === "user_id" && !authStatus.loggedIn) {
166
+ console.error("Organization requires user authentication for MCP visibility.");
167
+ console.error("Opening browser to login...");
168
+ console.error("");
169
+ await login({ sonomaEndpoint: config.sonomaEndpoint, debug: values.debug });
170
+ authStatus = getAuthStatus();
171
+ if (!authStatus.loggedIn) {
172
+ console.error("Login required but not completed. Exiting.");
173
+ process.exit(1);
174
+ }
175
+ }
176
+ }
177
+ catch (error) {
178
+ if (values.debug) {
179
+ console.error("[cli] Failed to fetch policy:", error);
180
+ }
181
+ // Continue anyway - gateway will retry policy fetch
182
+ }
183
+ }
184
+ if (values.debug) {
185
+ if (authStatus.loggedIn) {
186
+ console.error("[cli] Using OAuth token (user-linked telemetry)");
187
+ }
188
+ else if (config.sonomaApiKey) {
189
+ console.error("[cli] Using org API key (device-level telemetry)");
190
+ }
191
+ }
192
+ const gateway = new McpGateway(config);
193
+ // Handle shutdown gracefully
194
+ const shutdown = async () => {
195
+ console.error("\nShutting down...");
196
+ await gateway.stop();
197
+ process.exit(0);
198
+ };
199
+ process.on("SIGINT", shutdown);
200
+ process.on("SIGTERM", shutdown);
201
+ try {
202
+ await gateway.start();
203
+ }
204
+ catch (error) {
205
+ console.error("Failed to start gateway:", error);
206
+ process.exit(1);
207
+ }
208
+ }
209
+ function printHelp() {
210
+ // Use stderr for help output - stdout is reserved for MCP JSON-RPC
211
+ console.error(`
212
+ @sonoma/mcp-gateway - Local MCP Gateway for tool-level visibility
213
+
214
+ USAGE:
215
+ npx @sonoma/mcp-gateway [OPTIONS]
216
+
217
+ OPTIONS:
218
+ -c, --config <path> Path to gateway config JSON file
219
+ --mcp-json-path <path> Read servers from parent MCP config (single-file mode)
220
+ -a, --auto Auto-detect Claude Desktop config
221
+ -d, --debug Enable debug logging
222
+ -h, --help Show this help message
223
+ -v, --version Show version
224
+
225
+ AUTH OPTIONS:
226
+ --login Authenticate with Sonoma (opens browser)
227
+ --logout Clear stored credentials
228
+ --status Show authentication status
229
+ -e, --endpoint <url> Sonoma API endpoint (default: https://app.sonoma.dev)
230
+
231
+ SINGLE-FILE CONFIG (recommended):
232
+ Configure in your Claude Desktop / Cursor config with nested "servers":
233
+ {
234
+ "mcpServers": {
235
+ "sonoma": {
236
+ "command": "npx",
237
+ "args": ["@sonoma/mcp-gateway", "--mcp-json-path", "~/.cursor/mcp.json"],
238
+ "env": { "SONOMA_API_KEY": "your-key" },
239
+ "servers": {
240
+ "filesystem": {
241
+ "command": "npx",
242
+ "args": ["@modelcontextprotocol/server-filesystem", "/tmp"]
243
+ }
244
+ }
245
+ }
246
+ }
247
+ }
248
+
249
+ SEPARATE CONFIG FILE:
250
+ {
251
+ "servers": [
252
+ { "name": "filesystem", "command": "npx", "args": ["@modelcontextprotocol/server-filesystem", "/tmp"] }
253
+ ],
254
+ "sonomaEndpoint": "https://app.sonoma.dev"
255
+ }
256
+
257
+ EXAMPLES:
258
+ # Single-file config (servers nested in Claude Desktop config)
259
+ npx @sonoma/mcp-gateway --mcp-json-path ~/.cursor/mcp.json
260
+
261
+ # Separate config file
262
+ npx @sonoma/mcp-gateway --config ./my-servers.json
263
+
264
+ # Auto-detect Claude Desktop config
265
+ npx @sonoma/mcp-gateway --auto
266
+ `);
267
+ }
268
+ main().catch((error) => {
269
+ console.error("Fatal error:", error);
270
+ process.exit(1);
271
+ });
272
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Config loader for MCP Gateway
3
+ * Parses Claude Desktop / Cursor style MCP configs
4
+ */
5
+ import type { GatewayConfig } from "./types";
6
+ /**
7
+ * Load gateway config from a JSON file
8
+ */
9
+ export declare function loadConfig(configPath: string): GatewayConfig;
10
+ /**
11
+ * Load servers from parent MCP config file (single-file config pattern)
12
+ *
13
+ * This enables the Lasso MCP Gateway pattern where you configure:
14
+ * {
15
+ * "mcpServers": {
16
+ * "sonoma": {
17
+ * "command": "npx",
18
+ * "args": ["@sonoma/mcp-gateway", "--mcp-json-path", "~/.cursor/mcp.json"],
19
+ * "servers": {
20
+ * "filesystem": { "command": "npx", "args": ["@modelcontextprotocol/server-filesystem", "."] }
21
+ * }
22
+ * }
23
+ * }
24
+ * }
25
+ *
26
+ * The gateway reads the parent config, finds its own entry, and extracts the nested "servers".
27
+ */
28
+ export declare function loadFromParentConfig(configPath: string, gatewayName?: string): GatewayConfig;
29
+ /**
30
+ * Find Claude Desktop config file
31
+ */
32
+ export declare function findClaudeDesktopConfig(): string | null;
33
+ /**
34
+ * Create a minimal test config
35
+ */
36
+ export declare function createTestConfig(): GatewayConfig;
37
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAmB,MAAM,SAAS,CAAC;AAc9D;;GAEG;AACH,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,aAAa,CAe5D;AAsBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,SAAW,GAAG,aAAa,CAyD9F;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,GAAG,IAAI,CAwBvD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,aAAa,CAKhD"}
package/dist/config.js ADDED
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Config loader for MCP Gateway
3
+ * Parses Claude Desktop / Cursor style MCP configs
4
+ */
5
+ import { readFileSync, existsSync } from "node:fs";
6
+ import { homedir } from "node:os";
7
+ import { join } from "node:path";
8
+ /**
9
+ * Load gateway config from a JSON file
10
+ */
11
+ export function loadConfig(configPath) {
12
+ if (!existsSync(configPath)) {
13
+ throw new Error(`Config file not found: ${configPath}`);
14
+ }
15
+ const content = readFileSync(configPath, "utf-8");
16
+ const raw = JSON.parse(content);
17
+ // Check if this is a Claude Desktop style config
18
+ if ("mcpServers" in raw && raw.mcpServers) {
19
+ return convertClaudeConfig(raw);
20
+ }
21
+ // Otherwise treat as native gateway config
22
+ return raw;
23
+ }
24
+ /**
25
+ * Convert Claude Desktop config format to gateway config
26
+ */
27
+ function convertClaudeConfig(config) {
28
+ const servers = [];
29
+ if (config.mcpServers) {
30
+ for (const [name, server] of Object.entries(config.mcpServers)) {
31
+ servers.push({
32
+ name,
33
+ command: server.command,
34
+ args: server.args,
35
+ env: server.env,
36
+ });
37
+ }
38
+ }
39
+ return { servers };
40
+ }
41
+ /**
42
+ * Load servers from parent MCP config file (single-file config pattern)
43
+ *
44
+ * This enables the Lasso MCP Gateway pattern where you configure:
45
+ * {
46
+ * "mcpServers": {
47
+ * "sonoma": {
48
+ * "command": "npx",
49
+ * "args": ["@sonoma/mcp-gateway", "--mcp-json-path", "~/.cursor/mcp.json"],
50
+ * "servers": {
51
+ * "filesystem": { "command": "npx", "args": ["@modelcontextprotocol/server-filesystem", "."] }
52
+ * }
53
+ * }
54
+ * }
55
+ * }
56
+ *
57
+ * The gateway reads the parent config, finds its own entry, and extracts the nested "servers".
58
+ */
59
+ export function loadFromParentConfig(configPath, gatewayName = "sonoma") {
60
+ const expandedPath = configPath.replace(/^~/, homedir());
61
+ if (!existsSync(expandedPath)) {
62
+ throw new Error(`Parent config file not found: ${expandedPath}`);
63
+ }
64
+ const content = readFileSync(expandedPath, "utf-8");
65
+ const raw = JSON.parse(content);
66
+ if (!raw.mcpServers) {
67
+ throw new Error(`No mcpServers found in ${expandedPath}`);
68
+ }
69
+ // Find our gateway entry - try common names
70
+ const gatewayNames = [gatewayName, "sonoma", "sonoma-gateway", "mcp-gateway"];
71
+ let gatewayEntry;
72
+ for (const name of gatewayNames) {
73
+ if (raw.mcpServers[name]) {
74
+ gatewayEntry = raw.mcpServers[name];
75
+ break;
76
+ }
77
+ }
78
+ if (!gatewayEntry?.servers) {
79
+ // Fall back to loading all servers except our own gateway
80
+ const servers = [];
81
+ for (const [name, server] of Object.entries(raw.mcpServers)) {
82
+ // Skip gateway entries (they have --mcp-json-path in args)
83
+ const isGateway = server.args?.some(arg => arg.includes("mcp-gateway") || arg.includes("--mcp-json-path"));
84
+ if (!isGateway) {
85
+ servers.push({
86
+ name,
87
+ command: server.command,
88
+ args: server.args,
89
+ env: server.env,
90
+ });
91
+ }
92
+ }
93
+ return { servers };
94
+ }
95
+ // Extract nested servers from gateway entry
96
+ const servers = [];
97
+ for (const [name, server] of Object.entries(gatewayEntry.servers)) {
98
+ servers.push({
99
+ name,
100
+ command: server.command,
101
+ args: server.args,
102
+ env: server.env,
103
+ });
104
+ }
105
+ return { servers };
106
+ }
107
+ /**
108
+ * Find Claude Desktop config file
109
+ */
110
+ export function findClaudeDesktopConfig() {
111
+ const platform = process.platform;
112
+ let configPath;
113
+ if (platform === "darwin") {
114
+ configPath = join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
115
+ }
116
+ else if (platform === "win32") {
117
+ configPath = join(process.env.APPDATA || "", "Claude", "claude_desktop_config.json");
118
+ }
119
+ else {
120
+ // Linux
121
+ configPath = join(homedir(), ".config", "claude", "claude_desktop_config.json");
122
+ }
123
+ return existsSync(configPath) ? configPath : null;
124
+ }
125
+ /**
126
+ * Create a minimal test config
127
+ */
128
+ export function createTestConfig() {
129
+ return {
130
+ servers: [],
131
+ debug: true,
132
+ };
133
+ }
134
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}