@sonoma-security/mcp-gateway 0.1.4 → 0.1.6

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.
Files changed (62) hide show
  1. package/README.md +104 -45
  2. package/dist/__tests__/config.test.js +28 -0
  3. package/dist/__tests__/config.test.js.map +1 -1
  4. package/dist/__tests__/ssrf-protection.test.d.ts +2 -0
  5. package/dist/__tests__/ssrf-protection.test.d.ts.map +1 -0
  6. package/dist/__tests__/ssrf-protection.test.js +389 -0
  7. package/dist/__tests__/ssrf-protection.test.js.map +1 -0
  8. package/dist/auth/client.d.ts +2 -0
  9. package/dist/auth/client.d.ts.map +1 -1
  10. package/dist/auth/client.js +17 -15
  11. package/dist/auth/client.js.map +1 -1
  12. package/dist/auth/crypto.d.ts +23 -0
  13. package/dist/auth/crypto.d.ts.map +1 -0
  14. package/dist/auth/crypto.js +78 -0
  15. package/dist/auth/crypto.js.map +1 -0
  16. package/dist/auth/index.d.ts +4 -1
  17. package/dist/auth/index.d.ts.map +1 -1
  18. package/dist/auth/index.js +4 -1
  19. package/dist/auth/index.js.map +1 -1
  20. package/dist/auth/server.d.ts +2 -0
  21. package/dist/auth/server.d.ts.map +1 -1
  22. package/dist/auth/server.js +337 -59
  23. package/dist/auth/server.js.map +1 -1
  24. package/dist/auth/storage.d.ts.map +1 -1
  25. package/dist/auth/storage.js +2 -72
  26. package/dist/auth/storage.js.map +1 -1
  27. package/dist/auth/upstream-oauth-provider.d.ts +41 -0
  28. package/dist/auth/upstream-oauth-provider.d.ts.map +1 -0
  29. package/dist/auth/upstream-oauth-provider.js +88 -0
  30. package/dist/auth/upstream-oauth-provider.js.map +1 -0
  31. package/dist/auth/upstream-oauth.d.ts +31 -0
  32. package/dist/auth/upstream-oauth.d.ts.map +1 -0
  33. package/dist/auth/upstream-oauth.js +79 -0
  34. package/dist/auth/upstream-oauth.js.map +1 -0
  35. package/dist/auth/upstream-token-store.d.ts +27 -0
  36. package/dist/auth/upstream-token-store.d.ts.map +1 -0
  37. package/dist/auth/upstream-token-store.js +103 -0
  38. package/dist/auth/upstream-token-store.js.map +1 -0
  39. package/dist/cli.js +91 -63
  40. package/dist/cli.js.map +1 -1
  41. package/dist/config.d.ts.map +1 -1
  42. package/dist/config.js +94 -9
  43. package/dist/config.js.map +1 -1
  44. package/dist/gateway.d.ts +23 -1
  45. package/dist/gateway.d.ts.map +1 -1
  46. package/dist/gateway.js +203 -41
  47. package/dist/gateway.js.map +1 -1
  48. package/dist/pattern-matcher.d.ts +47 -0
  49. package/dist/pattern-matcher.d.ts.map +1 -0
  50. package/dist/pattern-matcher.js +98 -0
  51. package/dist/pattern-matcher.js.map +1 -0
  52. package/dist/sonoma-client.d.ts +21 -6
  53. package/dist/sonoma-client.d.ts.map +1 -1
  54. package/dist/sonoma-client.js +45 -5
  55. package/dist/sonoma-client.js.map +1 -1
  56. package/dist/ssrf-protection.d.ts +59 -0
  57. package/dist/ssrf-protection.d.ts.map +1 -0
  58. package/dist/ssrf-protection.js +253 -0
  59. package/dist/ssrf-protection.js.map +1 -0
  60. package/dist/types.d.ts +6 -2
  61. package/dist/types.d.ts.map +1 -1
  62. package/package.json +2 -2
package/dist/config.js CHANGED
@@ -2,18 +2,79 @@
2
2
  * Config loader for MCP Gateway
3
3
  * Parses Claude Desktop / Cursor style MCP configs
4
4
  */
5
- import { readFileSync, existsSync } from "node:fs";
6
- import { homedir } from "node:os";
7
- import { join } from "node:path";
5
+ import { readFileSync, existsSync, realpathSync } from "node:fs";
6
+ import { homedir, tmpdir } from "node:os";
7
+ import { join, normalize } from "node:path";
8
+ /**
9
+ * Validate that a config path is safe to read.
10
+ * Prevents potential file inclusion attacks by ensuring paths:
11
+ * 1. End in .json (expected config format)
12
+ * 2. Don't contain path traversal sequences after normalization
13
+ * 3. Resolve to an expected location
14
+ */
15
+ function validateConfigPath(configPath) {
16
+ // Normalize the path to resolve . and ..
17
+ const normalizedPath = normalize(configPath);
18
+ // Must end in .json
19
+ if (!normalizedPath.toLowerCase().endsWith(".json")) {
20
+ throw new Error(`Invalid config path: ${configPath}. Config files must have .json extension.`);
21
+ }
22
+ // Check for path traversal attempts that would escape after normalization
23
+ // This catches things like /etc/../../../passwd.json
24
+ if (existsSync(normalizedPath)) {
25
+ try {
26
+ const realPath = realpathSync(normalizedPath);
27
+ const home = homedir();
28
+ // Allow paths within:
29
+ // - User's home directory
30
+ // - /usr/local/etc/sonoma (MDM configs)
31
+ // - Current working directory
32
+ // - System temp directory (for tests and transient configs)
33
+ // Note: Use realpathSync on tmpdir to handle macOS /var -> /private/var symlink
34
+ const tempDir = tmpdir();
35
+ let resolvedTempDir = tempDir;
36
+ try {
37
+ resolvedTempDir = realpathSync(tempDir);
38
+ }
39
+ catch {
40
+ // Ignore if temp dir doesn't exist (unlikely)
41
+ }
42
+ const allowedPrefixes = [
43
+ home,
44
+ "/usr/local/etc/sonoma",
45
+ process.cwd(),
46
+ tempDir,
47
+ resolvedTempDir,
48
+ // Windows paths
49
+ process.env.APPDATA || "",
50
+ process.env.LOCALAPPDATA || "",
51
+ ].filter(Boolean);
52
+ const isAllowed = allowedPrefixes.some((prefix) => realPath.startsWith(prefix) || realPath === prefix);
53
+ if (!isAllowed) {
54
+ throw new Error(`Config path not in allowed location: ${configPath}. ` +
55
+ `Allowed: home directory, /usr/local/etc/sonoma, or current directory.`);
56
+ }
57
+ }
58
+ catch (e) {
59
+ // realpathSync will throw if file doesn't exist - that's fine, existsSync handles that
60
+ if (e.code !== "ENOENT") {
61
+ throw e;
62
+ }
63
+ }
64
+ }
65
+ }
8
66
  /**
9
67
  * Load gateway config from a JSON file
10
68
  */
11
69
  export function loadConfig(configPath) {
70
+ validateConfigPath(configPath);
12
71
  if (!existsSync(configPath)) {
13
72
  throw new Error(`Config file not found: ${configPath}`);
14
73
  }
15
74
  const content = readFileSync(configPath, "utf-8");
16
- const raw = JSON.parse(content);
75
+ // Strip UTF-8 BOM if present (Windows PowerShell adds this)
76
+ const cleanContent = content.replace(/^\uFEFF/, "");
77
+ const raw = JSON.parse(cleanContent);
17
78
  // Check if this is a Claude Desktop style config
18
79
  if ("mcpServers" in raw && raw.mcpServers) {
19
80
  return convertClaudeConfig(raw);
@@ -28,11 +89,17 @@ function convertClaudeConfig(config) {
28
89
  const servers = [];
29
90
  if (config.mcpServers) {
30
91
  for (const [name, server] of Object.entries(config.mcpServers)) {
92
+ if (server.disabled)
93
+ continue;
94
+ if (!server.command && !server.url)
95
+ continue;
31
96
  servers.push({
32
97
  name,
33
98
  command: server.command,
34
99
  args: server.args,
35
100
  env: server.env,
101
+ url: server.url,
102
+ headers: server.headers,
36
103
  });
37
104
  }
38
105
  }
@@ -58,20 +125,24 @@ function convertClaudeConfig(config) {
58
125
  */
59
126
  export function loadFromParentConfig(configPath, gatewayName = "sonoma") {
60
127
  const expandedPath = configPath.replace(/^~/, homedir());
128
+ validateConfigPath(expandedPath);
61
129
  if (!existsSync(expandedPath)) {
62
130
  throw new Error(`Parent config file not found: ${expandedPath}`);
63
131
  }
64
132
  const content = readFileSync(expandedPath, "utf-8");
65
- const raw = JSON.parse(content);
133
+ // Strip UTF-8 BOM if present (Windows PowerShell adds this)
134
+ const cleanContent = content.replace(/^\uFEFF/, "");
135
+ const raw = JSON.parse(cleanContent);
66
136
  if (!raw.mcpServers) {
67
137
  throw new Error(`No mcpServers found in ${expandedPath}`);
68
138
  }
69
- // Find our gateway entry - try common names
139
+ // Find our gateway entry - try common names (only match entries with nested servers)
70
140
  const gatewayNames = [gatewayName, "sonoma", "sonoma-gateway", "sonoma-local-gateway", "mcp-gateway"];
71
141
  let gatewayEntry;
72
142
  for (const name of gatewayNames) {
73
- if (raw.mcpServers[name]) {
74
- gatewayEntry = raw.mcpServers[name];
143
+ const entry = raw.mcpServers[name];
144
+ if (entry?.servers && Object.keys(entry.servers).length > 0) {
145
+ gatewayEntry = entry;
75
146
  break;
76
147
  }
77
148
  }
@@ -79,6 +150,10 @@ export function loadFromParentConfig(configPath, gatewayName = "sonoma") {
79
150
  // Fall back to loading all servers except our own gateway
80
151
  const servers = [];
81
152
  for (const [name, server] of Object.entries(raw.mcpServers)) {
153
+ if (server.disabled)
154
+ continue;
155
+ if (!server.command && !server.url)
156
+ continue;
82
157
  // Skip gateway entries (they have --mcp-json-path in args)
83
158
  const isGateway = server.args?.some(arg => arg.includes("mcp-gateway") || arg.includes("--mcp-json-path"));
84
159
  if (!isGateway) {
@@ -87,6 +162,8 @@ export function loadFromParentConfig(configPath, gatewayName = "sonoma") {
87
162
  command: server.command,
88
163
  args: server.args,
89
164
  env: server.env,
165
+ url: server.url,
166
+ headers: server.headers,
90
167
  });
91
168
  }
92
169
  }
@@ -95,11 +172,17 @@ export function loadFromParentConfig(configPath, gatewayName = "sonoma") {
95
172
  // Extract nested servers from gateway entry
96
173
  const servers = [];
97
174
  for (const [name, server] of Object.entries(gatewayEntry.servers)) {
175
+ if (server.disabled)
176
+ continue;
177
+ if (!server.command && !server.url)
178
+ continue;
98
179
  servers.push({
99
180
  name,
100
181
  command: server.command,
101
182
  args: server.args,
102
183
  env: server.env,
184
+ url: server.url,
185
+ headers: server.headers,
103
186
  });
104
187
  }
105
188
  return { servers };
@@ -200,7 +283,9 @@ export function autoDetectConfig(debug = false) {
200
283
  }
201
284
  try {
202
285
  const content = readFileSync(path, "utf-8");
203
- const config = JSON.parse(content);
286
+ // Strip UTF-8 BOM if present (Windows PowerShell adds this)
287
+ const cleanContent = content.replace(/^\uFEFF/, "");
288
+ const config = JSON.parse(cleanContent);
204
289
  if (!config.mcpServers)
205
290
  continue;
206
291
  // Look for a gateway entry with nested servers
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/gateway.d.ts CHANGED
@@ -23,11 +23,20 @@ export declare class McpGateway {
23
23
  private sonomaClient;
24
24
  private policy;
25
25
  private policyRefreshInterval;
26
+ private upstreamTokenStore;
26
27
  private started;
27
28
  constructor(config: GatewayConfig);
28
29
  private log;
29
30
  /**
30
31
  * Check if a tool is blocked by policy
32
+ *
33
+ * Supports both server-level and tool-level blocking:
34
+ * - Server-level: `toolPattern = null` blocks/allows entire server
35
+ * - Tool-level: `toolPattern = "read_*"` blocks/allows specific tools
36
+ *
37
+ * Priority ordering: higher priority wins, then more specific patterns
38
+ * Deny wins: in case of equal priority, blocked status takes precedence
39
+ *
31
40
  * @returns true if blocked, false if allowed
32
41
  */
33
42
  private isToolBlocked;
@@ -36,7 +45,20 @@ export declare class McpGateway {
36
45
  */
37
46
  private setupHandlers;
38
47
  /**
39
- * Connect to an upstream MCP server
48
+ * Create a stdio transport for command-based servers.
49
+ */
50
+ private createStdioTransport;
51
+ /**
52
+ * Build HTTP request options from config headers.
53
+ */
54
+ private getHttpRequestInit;
55
+ /**
56
+ * Connect a client to a transport with a timeout.
57
+ */
58
+ private connectWithTimeout;
59
+ /**
60
+ * Connect to an upstream MCP server.
61
+ * For URL-based servers, tries StreamableHTTP first, then falls back to SSE.
40
62
  */
41
63
  private connectUpstream;
42
64
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAeH,OAAO,KAAK,EACV,aAAa,EAEb,aAAa,EACd,MAAM,YAAY,CAAC;AAqBpB,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,YAAY,CAAkC;IACtD,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,KAAK,CAAU;IACvB,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,qBAAqB,CAA+C;IAC5E,OAAO,CAAC,OAAO,CAAS;gBAEZ,MAAM,EAAE,aAAa;IA6BjC,OAAO,CAAC,GAAG;IAMX;;;OAGG;IACH,OAAO,CAAC,aAAa;IAwCrB;;OAEG;IACH,OAAO,CAAC,aAAa;IAuJrB;;OAEG;YACW,eAAe;IA8E7B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAiCzB;;OAEG;IACH,OAAO,CAAC,WAAW;IAUnB;;OAEG;YACW,aAAa;IAW3B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA2C5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgC3B;;OAEG;IACH,SAAS,IAAI,aAAa,EAAE;IAI5B;;OAEG;IACH,WAAW,IAAI,IAAI;CAGpB"}
1
+ {"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAiBH,OAAO,KAAK,EACV,aAAa,EAEb,aAAa,EACd,MAAM,YAAY,CAAC;AA0BpB,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,YAAY,CAAkC;IACtD,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,KAAK,CAAU;IACvB,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,qBAAqB,CAA+C;IAC5E,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,OAAO,CAAS;gBAEZ,MAAM,EAAE,aAAa;IA8BjC,OAAO,CAAC,GAAG;IAOX;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,aAAa;IAsErB;;OAEG;IACH,OAAO,CAAC,aAAa;IAuJrB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAyB5B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAO1B;;OAEG;YACW,kBAAkB;IAehC;;;OAGG;YACW,eAAe;IA0H7B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA8CzB;;OAEG;IACH,OAAO,CAAC,WAAW;IAUnB;;OAEG;YACW,aAAa;IAW3B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkE5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAmC3B;;OAEG;IACH,SAAS,IAAI,aAAa,EAAE;IAI5B;;OAEG;IACH,WAAW,IAAI,IAAI;CAGpB"}
package/dist/gateway.js CHANGED
@@ -14,10 +14,14 @@
14
14
  */
15
15
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
16
16
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
17
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
18
+ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
17
19
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
18
20
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
19
21
  import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
20
22
  import { SonomaClient } from "./sonoma-client.js";
23
+ import { matchesToolPattern, getPatternSpecificity } from "./pattern-matcher.js";
24
+ import { UpstreamTokenStore, authenticateUpstream } from "./auth/index.js";
21
25
  // Timeout configuration
22
26
  const CONNECT_TIMEOUT_MS = 10_000; // 10 seconds
23
27
  const TOOL_CALL_TIMEOUT_MS = 30_000; // 30 seconds
@@ -35,10 +39,12 @@ export class McpGateway {
35
39
  sonomaClient = null;
36
40
  policy = null;
37
41
  policyRefreshInterval = null;
42
+ upstreamTokenStore;
38
43
  started = false;
39
44
  constructor(config) {
40
45
  this.config = config;
41
46
  this.debug = config.debug ?? false;
47
+ this.upstreamTokenStore = new UpstreamTokenStore();
42
48
  // Initialize Sonoma client if endpoint provided
43
49
  // Auth priority: OAuth token (user-linked) > org API key (device-level)
44
50
  if (config.sonomaEndpoint) {
@@ -53,49 +59,81 @@ export class McpGateway {
53
59
  version: "0.1.0",
54
60
  }, {
55
61
  capabilities: {
56
- tools: {},
62
+ tools: { listChanged: true },
57
63
  },
58
64
  });
59
65
  this.setupHandlers();
60
66
  }
61
67
  log(message, ...args) {
62
68
  if (this.debug) {
63
- console.error(`[gateway] ${message}`, ...args);
69
+ // Security: message is from developer code, not user input
70
+ console.error(`[gateway] ${message}`, ...args); // nosemgrep: unsafe-formatstring
64
71
  }
65
72
  }
66
73
  /**
67
74
  * Check if a tool is blocked by policy
75
+ *
76
+ * Supports both server-level and tool-level blocking:
77
+ * - Server-level: `toolPattern = null` blocks/allows entire server
78
+ * - Tool-level: `toolPattern = "read_*"` blocks/allows specific tools
79
+ *
80
+ * Priority ordering: higher priority wins, then more specific patterns
81
+ * Deny wins: in case of equal priority, blocked status takes precedence
82
+ *
68
83
  * @returns true if blocked, false if allowed
69
84
  */
70
85
  isToolBlocked(serverName, toolName) {
71
- if (!this.policy || this.policy.mode === "disabled") {
86
+ if (!this.policy) {
72
87
  return false;
73
88
  }
74
- // Get identifiers to check
89
+ // Get server identifiers to check
75
90
  const upstream = this.upstreams.get(serverName);
76
91
  const packageName = upstream?.config.packageName;
77
- // Identifiers to check: packageName (preferred) or server name
78
- const identifiers = [];
92
+ // Build set of identifiers: packageName (preferred) or server name
93
+ const serverIdentifiers = new Set();
79
94
  if (packageName)
80
- identifiers.push(packageName);
81
- identifiers.push(serverName);
82
- // Check if any identifier is in the list
83
- const matchedEntry = this.policy.list.find((entry) => identifiers.includes(entry.identifier));
84
- if (this.policy.mode === "blocklist") {
85
- // Blocklist: block if found in list with blocked status
86
- if (matchedEntry && matchedEntry.status === "blocked") {
87
- this.log(`Tool blocked by policy: ${serverName}.${toolName}`);
95
+ serverIdentifiers.add(packageName);
96
+ serverIdentifiers.add(serverName);
97
+ // Find all matching rules (server-level and tool-level)
98
+ const matchingRules = this.policy.list.filter((rule) => {
99
+ // Check if server identifier matches (or rule is global with "*")
100
+ const serverMatches = serverIdentifiers.has(rule.identifier) || rule.identifier === "*";
101
+ if (!serverMatches)
102
+ return false;
103
+ // Check tool pattern match
104
+ if (!rule.toolPattern) {
105
+ // Server-level rule - applies to all tools on this server
88
106
  return true;
89
107
  }
108
+ // Tool-level rule - check if pattern matches the tool name
109
+ return matchesToolPattern(toolName, rule.toolPattern);
110
+ });
111
+ // No matching rules = not blocked (unlisted servers are allowed)
112
+ if (matchingRules.length === 0) {
90
113
  return false;
91
114
  }
92
- if (this.policy.mode === "allowlist") {
93
- // Allowlist: block if NOT found in list with allowed status
94
- if (!matchedEntry || matchedEntry.status !== "allowed") {
95
- this.log(`Tool not in allowlist: ${serverName}.${toolName}`);
96
- return true;
97
- }
98
- return false;
115
+ // Sort rules by priority (higher first), then by pattern specificity (more specific first)
116
+ const sortedRules = [...matchingRules].sort((a, b) => {
117
+ // First sort by explicit priority (higher wins)
118
+ if (a.priority !== b.priority)
119
+ return b.priority - a.priority;
120
+ // Then by pattern specificity (more specific wins)
121
+ const specA = getPatternSpecificity(a.toolPattern);
122
+ const specB = getPatternSpecificity(b.toolPattern);
123
+ return specB - specA;
124
+ });
125
+ // Get the highest priority rule
126
+ const topRule = sortedRules[0];
127
+ // Find if there's a deny rule at the same priority level
128
+ const topPriority = topRule.priority;
129
+ const topSpecificity = getPatternSpecificity(topRule.toolPattern);
130
+ const denyAtTopLevel = sortedRules.find((r) => r.status === "blocked" &&
131
+ r.priority === topPriority &&
132
+ getPatternSpecificity(r.toolPattern) === topSpecificity);
133
+ // Block if any matching rule says "blocked" (deny wins at same priority)
134
+ if (denyAtTopLevel) {
135
+ this.log(`Tool blocked by policy: ${serverName}.${toolName} (pattern: ${denyAtTopLevel.toolPattern ?? "server-level"})`);
136
+ return true;
99
137
  }
100
138
  return false;
101
139
  }
@@ -230,16 +268,12 @@ export class McpGateway {
230
268
  });
231
269
  }
232
270
  /**
233
- * Connect to an upstream MCP server
271
+ * Create a stdio transport for command-based servers.
234
272
  */
235
- async connectUpstream(config, existingConnection) {
236
- this.log(`Connecting to upstream: ${config.name} (${config.command})`);
237
- const client = new Client({
238
- name: `gateway-client-${config.name}`,
239
- version: "0.1.0",
240
- }, {
241
- capabilities: {},
242
- });
273
+ createStdioTransport(config) {
274
+ if (!config.command) {
275
+ throw new Error(`Server ${config.name} has neither url nor command`);
276
+ }
243
277
  // Build env with only defined values (filter out undefined from process.env)
244
278
  let env;
245
279
  if (config.env) {
@@ -251,27 +285,121 @@ export class McpGateway {
251
285
  }
252
286
  Object.assign(env, config.env);
253
287
  }
254
- const transport = new StdioClientTransport({
288
+ return new StdioClientTransport({
255
289
  command: config.command,
256
290
  args: config.args,
257
291
  env,
258
292
  cwd: config.cwd,
259
293
  });
294
+ }
295
+ /**
296
+ * Build HTTP request options from config headers.
297
+ */
298
+ getHttpRequestInit(config) {
299
+ if (config.headers && Object.keys(config.headers).length > 0) {
300
+ return { headers: config.headers };
301
+ }
302
+ return undefined;
303
+ }
304
+ /**
305
+ * Connect a client to a transport with a timeout.
306
+ */
307
+ async connectWithTimeout(client, transport, name) {
308
+ const connectPromise = client.connect(transport);
309
+ const timeoutPromise = new Promise((_, reject) => {
310
+ setTimeout(() => reject(new Error(`Connection timeout after ${CONNECT_TIMEOUT_MS}ms: ${name}`)), CONNECT_TIMEOUT_MS);
311
+ });
312
+ await Promise.race([connectPromise, timeoutPromise]);
313
+ }
314
+ /**
315
+ * Connect to an upstream MCP server.
316
+ * For URL-based servers, tries StreamableHTTP first, then falls back to SSE.
317
+ */
318
+ async connectUpstream(config, existingConnection) {
319
+ const label = config.url ?? config.command ?? config.name;
320
+ this.log(`Connecting to upstream: ${config.name} (${label})`);
321
+ const serverName = config.name;
322
+ const makeClient = () => new Client({
323
+ name: `gateway-client-${serverName}`,
324
+ version: "0.1.0",
325
+ }, {
326
+ capabilities: {},
327
+ listChanged: {
328
+ tools: {
329
+ autoRefresh: true,
330
+ debounceMs: 200,
331
+ onChanged: (error, tools) => {
332
+ if (error) {
333
+ this.log(`Failed to refresh tools from ${serverName}: ${error.message}`);
334
+ return;
335
+ }
336
+ const upstream = this.upstreams.get(serverName);
337
+ if (upstream) {
338
+ upstream.tools = tools || [];
339
+ this.log(`Tools updated from ${serverName}: ${upstream.tools.length} tools`);
340
+ }
341
+ this.server.sendToolListChanged().catch((err) => {
342
+ this.log(`Failed to notify client of tool list change: ${err}`);
343
+ });
344
+ },
345
+ },
346
+ },
347
+ });
348
+ let client;
349
+ let transport;
350
+ if (config.url) {
351
+ const url = new URL(config.url);
352
+ const requestInit = this.getHttpRequestInit(config);
353
+ // Try OAuth pre-auth for HTTP servers (discovers if server needs OAuth)
354
+ let authProvider;
355
+ try {
356
+ authProvider = await authenticateUpstream({
357
+ serverUrl: config.url,
358
+ serverName: config.name,
359
+ store: this.upstreamTokenStore,
360
+ debug: this.debug,
361
+ });
362
+ this.log(` ${config.name}: OAuth pre-auth succeeded`);
363
+ }
364
+ catch (err) {
365
+ // Server may not require OAuth, or user declined; try without auth
366
+ this.log(` ${config.name}: OAuth pre-auth skipped (${err instanceof Error ? err.message : err})`);
367
+ }
368
+ // Try StreamableHTTP first, fall back to SSE
369
+ try {
370
+ client = makeClient();
371
+ transport = new StreamableHTTPClientTransport(url, { requestInit, authProvider });
372
+ await this.connectWithTimeout(client, transport, config.name);
373
+ this.log(` ${config.name}: connected via StreamableHTTP`);
374
+ }
375
+ catch {
376
+ this.log(` ${config.name}: StreamableHTTP failed, trying SSE`);
377
+ client = makeClient();
378
+ transport = new SSEClientTransport(url, { requestInit, authProvider });
379
+ await this.connectWithTimeout(client, transport, config.name);
380
+ this.log(` ${config.name}: connected via SSE`);
381
+ }
382
+ }
383
+ else {
384
+ client = makeClient();
385
+ transport = this.createStdioTransport(config);
386
+ await this.connectWithTimeout(client, transport, config.name);
387
+ }
260
388
  // Set up close handler for auto-reconnect
261
389
  transport.onclose = () => {
262
390
  const upstream = this.upstreams.get(config.name);
263
391
  if (upstream) {
264
392
  upstream.connected = false;
265
393
  this.log(`Upstream disconnected: ${config.name}`);
394
+ // Notify AI client that tools changed (disconnected server's tools are no longer available)
395
+ if (upstream.tools.length > 0) {
396
+ this.server.sendToolListChanged().catch((err) => {
397
+ this.log(`Failed to notify client of tool list change: ${err}`);
398
+ });
399
+ }
266
400
  this.scheduleReconnect(config.name);
267
401
  }
268
402
  };
269
- // Connect with timeout
270
- const connectPromise = client.connect(transport);
271
- const timeoutPromise = new Promise((_, reject) => {
272
- setTimeout(() => reject(new Error(`Connection timeout after ${CONNECT_TIMEOUT_MS}ms: ${config.name}`)), CONNECT_TIMEOUT_MS);
273
- });
274
- await Promise.race([connectPromise, timeoutPromise]);
275
403
  // Fetch tools from this upstream (also with timeout)
276
404
  const listToolsPromise = client.listTools();
277
405
  const listToolsTimeout = new Promise((_, reject) => {
@@ -308,10 +436,21 @@ export class McpGateway {
308
436
  if (!this.started)
309
437
  return;
310
438
  try {
439
+ const oldToolNames = new Set(upstream.tools.map((t) => t.name));
311
440
  const newUpstream = await this.connectUpstream(upstream.config, upstream);
312
441
  newUpstream.reconnectAttempts = 0; // Reset on successful reconnect
313
442
  this.upstreams.set(serverName, newUpstream);
314
443
  this.log(`Reconnected to ${serverName}`);
444
+ // Notify AI client if tools changed during disconnect
445
+ const newToolNames = new Set(newUpstream.tools.map((t) => t.name));
446
+ const toolsChanged = oldToolNames.size !== newToolNames.size ||
447
+ [...oldToolNames].some((name) => !newToolNames.has(name));
448
+ if (toolsChanged) {
449
+ this.log(`Tools changed on ${serverName} after reconnect`);
450
+ this.server.sendToolListChanged().catch((err) => {
451
+ this.log(`Failed to notify client of tool list change: ${err}`);
452
+ });
453
+ }
315
454
  }
316
455
  catch (error) {
317
456
  this.log(`Reconnect failed for ${serverName}: ${error}`);
@@ -338,7 +477,7 @@ export class McpGateway {
338
477
  return;
339
478
  try {
340
479
  this.policy = await this.sonomaClient.fetchPolicy();
341
- this.log(`Policy refreshed: mode=${this.policy.mode}, ${this.policy.list.length} rules`);
480
+ this.log(`Policy refreshed: ${this.policy.list.length} rules`);
342
481
  }
343
482
  catch (error) {
344
483
  this.log(`Failed to refresh policy: ${error}`);
@@ -362,8 +501,11 @@ export class McpGateway {
362
501
  this.log(`Policy refresh error: ${err}`);
363
502
  });
364
503
  }, 5 * 60 * 1000);
365
- // Start periodic telemetry flush (every 30 seconds)
366
- this.sonomaClient.startTelemetryFlush(30 * 1000);
504
+ // Start periodic telemetry flush
505
+ // Default: 30 seconds, configurable via env for testing
506
+ const parsedFlushMs = parseInt(process.env.SONOMA_TELEMETRY_FLUSH_MS ?? "30000", 10);
507
+ const flushIntervalMs = Number.isNaN(parsedFlushMs) ? 30000 : parsedFlushMs;
508
+ this.sonomaClient.startTelemetryFlush(flushIntervalMs);
367
509
  }
368
510
  // Connect to all upstream servers
369
511
  for (const serverConfig of this.config.servers) {
@@ -372,11 +514,28 @@ export class McpGateway {
372
514
  this.upstreams.set(serverConfig.name, upstream);
373
515
  }
374
516
  catch (error) {
375
- console.error(`Failed to connect to ${serverConfig.name}:`, error);
517
+ // Security: serverConfig.name is from config, not user input
518
+ console.error(`Failed to connect to ${serverConfig.name}:`, error); // nosemgrep: unsafe-formatstring
376
519
  // Continue with other servers
377
520
  }
378
521
  }
379
522
  this.log(`Connected to ${this.upstreams.size}/${this.config.servers.length} upstreams`);
523
+ // Report discovered tools to Sonoma
524
+ if (this.sonomaClient) {
525
+ const serversWithTools = Array.from(this.upstreams.entries()).map(([serverName, upstream]) => ({
526
+ serverIdentifier: upstream.config.packageName || serverName,
527
+ serverName,
528
+ tools: upstream.tools.map((tool) => ({
529
+ name: tool.name,
530
+ description: tool.description,
531
+ inputSchema: tool.inputSchema,
532
+ })),
533
+ }));
534
+ // Fire and forget - don't block startup
535
+ this.sonomaClient.reportTools(serversWithTools).catch((err) => {
536
+ this.log(`Failed to report tools: ${err}`);
537
+ });
538
+ }
380
539
  // Start the server (stdio transport to AI client)
381
540
  const transport = new StdioServerTransport();
382
541
  await this.server.connect(transport);
@@ -408,6 +567,9 @@ export class McpGateway {
408
567
  this.log(`Disconnected from ${name}`);
409
568
  }
410
569
  catch (error) {
570
+ // Security: `name` comes from this.upstreams Map keys, which are MCP server names
571
+ // from admin-controlled config (serverConfig.name), not user input.
572
+ // nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
411
573
  console.error(`Error disconnecting from ${name}:`, error);
412
574
  }
413
575
  }