@sonoma-security/mcp-gateway 0.1.4 → 0.1.5

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 +83 -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 +224 -35
  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 -5
  53. package/dist/sonoma-client.d.ts.map +1 -1
  54. package/dist/sonoma-client.js +42 -2
  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":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAejC;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,UAAkB;IAC3C,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,0BAA0B,UAAU,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,OAAO,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAClD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAwC,CAAC;IAEvE,iDAAiD;IACjD,IAAI,YAAY,IAAI,GAAG,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;QAC1C,OAAO,mBAAmB,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC;IAED,2CAA2C;IAC3C,OAAO,GAAoB,CAAC;AAC9B,CAAC;AAED;;GAEG;AACH,SAAS,mBAAmB,CAAC,MAA2B;IACtD,MAAM,OAAO,GAAsB,EAAE,CAAC;IAEtC,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QACtB,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/D,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI;gBACJ,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,GAAG,EAAE,MAAM,CAAC,GAAG;aAChB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,CAAC;AACrB,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAkB,EAAE,WAAW,GAAG,QAAQ;IAC7E,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAEzD,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,iCAAiC,YAAY,EAAE,CAAC,CAAC;IACnE,CAAC;IAED,MAAM,OAAO,GAAG,YAAY,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IACpD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAwB,CAAC;IAEvD,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,0BAA0B,YAAY,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,4CAA4C;IAC5C,MAAM,YAAY,GAAG,CAAC,WAAW,EAAE,QAAQ,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,aAAa,CAAC,CAAC;IACtG,IAAI,YAAwC,CAAC;IAE7C,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAChC,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,YAAY,GAAG,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YACpC,MAAM;QACR,CAAC;IACH,CAAC;IAED,IAAI,CAAC,YAAY,EAAE,OAAO,EAAE,CAAC;QAC3B,0DAA0D;QAC1D,MAAM,OAAO,GAAsB,EAAE,CAAC;QACtC,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5D,2DAA2D;YAC3D,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,CACxC,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAC/D,CAAC;YACF,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,OAAO,CAAC,IAAI,CAAC;oBACX,IAAI;oBACJ,OAAO,EAAE,MAAM,CAAC,OAAO;oBACvB,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,GAAG,EAAE,MAAM,CAAC,GAAG;iBAChB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,CAAC;IACrB,CAAC;IAED,4CAA4C;IAC5C,MAAM,OAAO,GAAsB,EAAE,CAAC;IACtC,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC;QAClE,OAAO,CAAC,IAAI,CAAC;YACX,IAAI;YACJ,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,GAAG,EAAE,MAAM,CAAC,GAAG;SAChB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,CAAC;AACrB,CAAC;AAqBD,MAAM,UAAU,iBAAiB;IAC/B,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAClC,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,MAAM,KAAK,GAAwB,EAAE,CAAC;IAEtC,kDAAkD;IAClD,uDAAuD;IACvD,KAAK,CAAC,IAAI,CAAC;QACT,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC;QAChC,IAAI,EAAE,aAAa;QACnB,WAAW,EAAE,4CAA4C;KAC1D,CAAC,CAAC;IAEH,6CAA6C;IAC7C,KAAK,CAAC,IAAI,CAAC;QACT,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,CAAC;QACvC,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,0BAA0B;KACxC,CAAC,CAAC;IAEH,4CAA4C;IAC5C,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,qBAAqB,EAAE,QAAQ,EAAE,4BAA4B,CAAC;YAC1F,IAAI,EAAE,gBAAgB;YACtB,WAAW,EAAE,4BAA4B;SAC1C,CAAC,CAAC;IACL,CAAC;SAAM,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QAChC,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,EAAE,QAAQ,EAAE,4BAA4B,CAAC;YAC7E,IAAI,EAAE,gBAAgB;YACtB,WAAW,EAAE,8BAA8B;SAC5C,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,QAAQ;QACR,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,4BAA4B,CAAC;YACnE,IAAI,EAAE,gBAAgB;YACtB,WAAW,EAAE,4BAA4B;SAC1C,CAAC,CAAC;IACL,CAAC;IAED,UAAU;IACV,KAAK,CAAC,IAAI,CAAC;QACT,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,CAAC;QACvC,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,mCAAmC;KACjD,CAAC,CAAC;IAEH,8BAA8B;IAC9B,KAAK,CAAC,IAAI,CAAC;QACT,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,iBAAiB,CAAC;QAC3D,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,4BAA4B;KAC1C,CAAC,CAAC;IAEH,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,uBAAuB;IACrC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAElC,IAAI,UAAkB,CAAC;IACvB,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,UAAU,GAAG,IAAI,CACf,OAAO,EAAE,EACT,SAAS,EACT,qBAAqB,EACrB,QAAQ,EACR,4BAA4B,CAC7B,CAAC;IACJ,CAAC;SAAM,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QAChC,UAAU,GAAG,IAAI,CACf,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,EAAE,EACzB,QAAQ,EACR,4BAA4B,CAC7B,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,QAAQ;QACR,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,4BAA4B,CAAC,CAAC;IAClF,CAAC;IAED,OAAO,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;AACpD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAK,GAAG,KAAK;IAC5C,MAAM,WAAW,GAAG,iBAAiB,EAAE,CAAC;IACxC,MAAM,eAAe,GAAG;QACtB,QAAQ;QACR,gBAAgB;QAChB,sBAAsB;QACtB,0BAA0B;QAC1B,aAAa;QACb,8BAA8B;KAC/B,CAAC;IAEF,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,WAAW,EAAE,CAAC;QACzC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACtB,IAAI,KAAK;gBAAE,OAAO,CAAC,KAAK,CAAC,uBAAuB,IAAI,EAAE,CAAC,CAAC;YACxD,SAAS;QACX,CAAC;QAED,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAwB,CAAC;YAE1D,IAAI,CAAC,MAAM,CAAC,UAAU;gBAAE,SAAS;YAEjC,+CAA+C;YAC/C,KAAK,MAAM,WAAW,IAAI,eAAe,EAAE,CAAC;gBAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;gBAC7C,IAAI,KAAK,EAAE,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC5D,IAAI,KAAK,EAAE,CAAC;wBACV,OAAO,CAAC,KAAK,CAAC,kBAAkB,IAAI,yBAAyB,WAAW,MAAM,IAAI,EAAE,CAAC,CAAC;oBACxF,CAAC;oBACD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;gBAC/B,CAAC;YACH,CAAC;YAED,mEAAmE;YACnE,KAAK,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;gBACnE,MAAM,eAAe,GAAG,KAAK,CAAC,IAAI,EAAE,IAAI,CACtC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,8BAA8B,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAC5F,CAAC;gBACF,IAAI,eAAe,IAAI,KAAK,CAAC,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC9E,IAAI,KAAK,EAAE,CAAC;wBACV,OAAO,CAAC,KAAK,CAAC,kBAAkB,IAAI,yBAAyB,SAAS,MAAM,IAAI,EAAE,CAAC,CAAC;oBACtF,CAAC;oBACD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC;gBAC1C,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,KAAK;gBAAE,OAAO,CAAC,KAAK,CAAC,6BAA6B,IAAI,EAAE,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO;QACL,OAAO,EAAE,EAAE;QACX,KAAK,EAAE,IAAI;KACZ,CAAC;AACJ,CAAC"}
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;IAoGrB;;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,108 @@ 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
86
  if (!this.policy || this.policy.mode === "disabled") {
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));
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
106
+ return true;
107
+ }
108
+ // Tool-level rule - check if pattern matches the tool name
109
+ return matchesToolPattern(toolName, rule.toolPattern);
110
+ });
111
+ // No matching rules found
112
+ if (matchingRules.length === 0) {
113
+ if (this.policy.mode === "blocklist") {
114
+ // Blocklist with no matches = not blocked
115
+ return false;
116
+ }
117
+ if (this.policy.mode === "allowlist") {
118
+ // Allowlist with no matches = blocked (not explicitly allowed)
119
+ this.log(`Tool not in allowlist: ${serverName}.${toolName}`);
120
+ return true;
121
+ }
122
+ return false;
123
+ }
124
+ // Sort rules by priority (higher first), then by pattern specificity (more specific first)
125
+ const sortedRules = [...matchingRules].sort((a, b) => {
126
+ // First sort by explicit priority (higher wins)
127
+ if (a.priority !== b.priority)
128
+ return b.priority - a.priority;
129
+ // Then by pattern specificity (more specific wins)
130
+ const specA = getPatternSpecificity(a.toolPattern);
131
+ const specB = getPatternSpecificity(b.toolPattern);
132
+ return specB - specA;
133
+ });
134
+ // Get the highest priority rule
135
+ const topRule = sortedRules[0];
136
+ // Find if there's a deny rule at the same priority level
137
+ const topPriority = topRule.priority;
138
+ const topSpecificity = getPatternSpecificity(topRule.toolPattern);
139
+ const denyAtTopLevel = sortedRules.find((r) => r.status === "blocked" &&
140
+ r.priority === topPriority &&
141
+ getPatternSpecificity(r.toolPattern) === topSpecificity);
84
142
  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}`);
143
+ // Blocklist: block if any matching rule says "blocked"
144
+ // Deny wins at same priority level
145
+ if (denyAtTopLevel) {
146
+ this.log(`Tool blocked by policy: ${serverName}.${toolName} (pattern: ${denyAtTopLevel.toolPattern ?? "server-level"})`);
88
147
  return true;
89
148
  }
90
149
  return false;
91
150
  }
92
151
  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}`);
152
+ // Allowlist: allow only if there's an "allowed" rule
153
+ // But deny wins at same priority level
154
+ if (denyAtTopLevel) {
155
+ this.log(`Tool blocked by deny rule: ${serverName}.${toolName} (pattern: ${denyAtTopLevel.toolPattern ?? "server-level"})`);
96
156
  return true;
97
157
  }
98
- return false;
158
+ // Check if top rule allows
159
+ if (topRule.status === "allowed") {
160
+ return false; // Allowed
161
+ }
162
+ this.log(`Tool not in allowlist: ${serverName}.${toolName}`);
163
+ return true; // No allow rule = blocked
99
164
  }
100
165
  return false;
101
166
  }
@@ -230,16 +295,12 @@ export class McpGateway {
230
295
  });
231
296
  }
232
297
  /**
233
- * Connect to an upstream MCP server
298
+ * Create a stdio transport for command-based servers.
234
299
  */
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
- });
300
+ createStdioTransport(config) {
301
+ if (!config.command) {
302
+ throw new Error(`Server ${config.name} has neither url nor command`);
303
+ }
243
304
  // Build env with only defined values (filter out undefined from process.env)
244
305
  let env;
245
306
  if (config.env) {
@@ -251,27 +312,121 @@ export class McpGateway {
251
312
  }
252
313
  Object.assign(env, config.env);
253
314
  }
254
- const transport = new StdioClientTransport({
315
+ return new StdioClientTransport({
255
316
  command: config.command,
256
317
  args: config.args,
257
318
  env,
258
319
  cwd: config.cwd,
259
320
  });
321
+ }
322
+ /**
323
+ * Build HTTP request options from config headers.
324
+ */
325
+ getHttpRequestInit(config) {
326
+ if (config.headers && Object.keys(config.headers).length > 0) {
327
+ return { headers: config.headers };
328
+ }
329
+ return undefined;
330
+ }
331
+ /**
332
+ * Connect a client to a transport with a timeout.
333
+ */
334
+ async connectWithTimeout(client, transport, name) {
335
+ const connectPromise = client.connect(transport);
336
+ const timeoutPromise = new Promise((_, reject) => {
337
+ setTimeout(() => reject(new Error(`Connection timeout after ${CONNECT_TIMEOUT_MS}ms: ${name}`)), CONNECT_TIMEOUT_MS);
338
+ });
339
+ await Promise.race([connectPromise, timeoutPromise]);
340
+ }
341
+ /**
342
+ * Connect to an upstream MCP server.
343
+ * For URL-based servers, tries StreamableHTTP first, then falls back to SSE.
344
+ */
345
+ async connectUpstream(config, existingConnection) {
346
+ const label = config.url ?? config.command ?? config.name;
347
+ this.log(`Connecting to upstream: ${config.name} (${label})`);
348
+ const serverName = config.name;
349
+ const makeClient = () => new Client({
350
+ name: `gateway-client-${serverName}`,
351
+ version: "0.1.0",
352
+ }, {
353
+ capabilities: {},
354
+ listChanged: {
355
+ tools: {
356
+ autoRefresh: true,
357
+ debounceMs: 200,
358
+ onChanged: (error, tools) => {
359
+ if (error) {
360
+ this.log(`Failed to refresh tools from ${serverName}: ${error.message}`);
361
+ return;
362
+ }
363
+ const upstream = this.upstreams.get(serverName);
364
+ if (upstream) {
365
+ upstream.tools = tools || [];
366
+ this.log(`Tools updated from ${serverName}: ${upstream.tools.length} tools`);
367
+ }
368
+ this.server.sendToolListChanged().catch((err) => {
369
+ this.log(`Failed to notify client of tool list change: ${err}`);
370
+ });
371
+ },
372
+ },
373
+ },
374
+ });
375
+ let client;
376
+ let transport;
377
+ if (config.url) {
378
+ const url = new URL(config.url);
379
+ const requestInit = this.getHttpRequestInit(config);
380
+ // Try OAuth pre-auth for HTTP servers (discovers if server needs OAuth)
381
+ let authProvider;
382
+ try {
383
+ authProvider = await authenticateUpstream({
384
+ serverUrl: config.url,
385
+ serverName: config.name,
386
+ store: this.upstreamTokenStore,
387
+ debug: this.debug,
388
+ });
389
+ this.log(` ${config.name}: OAuth pre-auth succeeded`);
390
+ }
391
+ catch (err) {
392
+ // Server may not require OAuth, or user declined; try without auth
393
+ this.log(` ${config.name}: OAuth pre-auth skipped (${err instanceof Error ? err.message : err})`);
394
+ }
395
+ // Try StreamableHTTP first, fall back to SSE
396
+ try {
397
+ client = makeClient();
398
+ transport = new StreamableHTTPClientTransport(url, { requestInit, authProvider });
399
+ await this.connectWithTimeout(client, transport, config.name);
400
+ this.log(` ${config.name}: connected via StreamableHTTP`);
401
+ }
402
+ catch {
403
+ this.log(` ${config.name}: StreamableHTTP failed, trying SSE`);
404
+ client = makeClient();
405
+ transport = new SSEClientTransport(url, { requestInit, authProvider });
406
+ await this.connectWithTimeout(client, transport, config.name);
407
+ this.log(` ${config.name}: connected via SSE`);
408
+ }
409
+ }
410
+ else {
411
+ client = makeClient();
412
+ transport = this.createStdioTransport(config);
413
+ await this.connectWithTimeout(client, transport, config.name);
414
+ }
260
415
  // Set up close handler for auto-reconnect
261
416
  transport.onclose = () => {
262
417
  const upstream = this.upstreams.get(config.name);
263
418
  if (upstream) {
264
419
  upstream.connected = false;
265
420
  this.log(`Upstream disconnected: ${config.name}`);
421
+ // Notify AI client that tools changed (disconnected server's tools are no longer available)
422
+ if (upstream.tools.length > 0) {
423
+ this.server.sendToolListChanged().catch((err) => {
424
+ this.log(`Failed to notify client of tool list change: ${err}`);
425
+ });
426
+ }
266
427
  this.scheduleReconnect(config.name);
267
428
  }
268
429
  };
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
430
  // Fetch tools from this upstream (also with timeout)
276
431
  const listToolsPromise = client.listTools();
277
432
  const listToolsTimeout = new Promise((_, reject) => {
@@ -308,10 +463,21 @@ export class McpGateway {
308
463
  if (!this.started)
309
464
  return;
310
465
  try {
466
+ const oldToolNames = new Set(upstream.tools.map((t) => t.name));
311
467
  const newUpstream = await this.connectUpstream(upstream.config, upstream);
312
468
  newUpstream.reconnectAttempts = 0; // Reset on successful reconnect
313
469
  this.upstreams.set(serverName, newUpstream);
314
470
  this.log(`Reconnected to ${serverName}`);
471
+ // Notify AI client if tools changed during disconnect
472
+ const newToolNames = new Set(newUpstream.tools.map((t) => t.name));
473
+ const toolsChanged = oldToolNames.size !== newToolNames.size ||
474
+ [...oldToolNames].some((name) => !newToolNames.has(name));
475
+ if (toolsChanged) {
476
+ this.log(`Tools changed on ${serverName} after reconnect`);
477
+ this.server.sendToolListChanged().catch((err) => {
478
+ this.log(`Failed to notify client of tool list change: ${err}`);
479
+ });
480
+ }
315
481
  }
316
482
  catch (error) {
317
483
  this.log(`Reconnect failed for ${serverName}: ${error}`);
@@ -362,8 +528,11 @@ export class McpGateway {
362
528
  this.log(`Policy refresh error: ${err}`);
363
529
  });
364
530
  }, 5 * 60 * 1000);
365
- // Start periodic telemetry flush (every 30 seconds)
366
- this.sonomaClient.startTelemetryFlush(30 * 1000);
531
+ // Start periodic telemetry flush
532
+ // Default: 30 seconds, configurable via env for testing
533
+ const parsedFlushMs = parseInt(process.env.SONOMA_TELEMETRY_FLUSH_MS ?? "30000", 10);
534
+ const flushIntervalMs = Number.isNaN(parsedFlushMs) ? 30000 : parsedFlushMs;
535
+ this.sonomaClient.startTelemetryFlush(flushIntervalMs);
367
536
  }
368
537
  // Connect to all upstream servers
369
538
  for (const serverConfig of this.config.servers) {
@@ -372,11 +541,28 @@ export class McpGateway {
372
541
  this.upstreams.set(serverConfig.name, upstream);
373
542
  }
374
543
  catch (error) {
375
- console.error(`Failed to connect to ${serverConfig.name}:`, error);
544
+ // Security: serverConfig.name is from config, not user input
545
+ console.error(`Failed to connect to ${serverConfig.name}:`, error); // nosemgrep: unsafe-formatstring
376
546
  // Continue with other servers
377
547
  }
378
548
  }
379
549
  this.log(`Connected to ${this.upstreams.size}/${this.config.servers.length} upstreams`);
550
+ // Report discovered tools to Sonoma
551
+ if (this.sonomaClient) {
552
+ const serversWithTools = Array.from(this.upstreams.entries()).map(([serverName, upstream]) => ({
553
+ serverIdentifier: upstream.config.packageName || serverName,
554
+ serverName,
555
+ tools: upstream.tools.map((tool) => ({
556
+ name: tool.name,
557
+ description: tool.description,
558
+ inputSchema: tool.inputSchema,
559
+ })),
560
+ }));
561
+ // Fire and forget - don't block startup
562
+ this.sonomaClient.reportTools(serversWithTools).catch((err) => {
563
+ this.log(`Failed to report tools: ${err}`);
564
+ });
565
+ }
380
566
  // Start the server (stdio transport to AI client)
381
567
  const transport = new StdioServerTransport();
382
568
  await this.server.connect(transport);
@@ -408,6 +594,9 @@ export class McpGateway {
408
594
  this.log(`Disconnected from ${name}`);
409
595
  }
410
596
  catch (error) {
597
+ // Security: `name` comes from this.upstreams Map keys, which are MCP server names
598
+ // from admin-controlled config (serverConfig.name), not user input.
599
+ // nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
411
600
  console.error(`Error disconnecting from ${name}:`, error);
412
601
  }
413
602
  }