nuwax-mcp-stdio-proxy 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -23761,7 +23761,7 @@ var StdioServerTransport = class {
23761
23761
 
23762
23762
  // src/constants.ts
23763
23763
  var PKG_NAME = "nuwax-mcp-stdio-proxy";
23764
- var PKG_VERSION = "1.4.0";
23764
+ var PKG_VERSION = "1.4.2";
23765
23765
 
23766
23766
  // src/shared.ts
23767
23767
  async function discoverTools(client) {
@@ -23825,8 +23825,19 @@ function setupGracefulShutdown(cleanupFn) {
23825
23825
  process.on("SIGTERM", () => void shutdown("SIGTERM"));
23826
23826
  }
23827
23827
 
23828
+ // src/filter.ts
23829
+ function filterTools(tools, filter) {
23830
+ if (filter.allowTools && filter.allowTools.size > 0) {
23831
+ return tools.filter((t) => filter.allowTools.has(t.name));
23832
+ }
23833
+ if (filter.denyTools && filter.denyTools.size > 0) {
23834
+ return tools.filter((t) => !filter.denyTools.has(t.name));
23835
+ }
23836
+ return tools;
23837
+ }
23838
+
23828
23839
  // src/modes/stdio.ts
23829
- async function runStdio(config2) {
23840
+ async function runStdio(config2, allowTools, denyTools) {
23830
23841
  const entries = Object.entries(config2.mcpServers);
23831
23842
  if (entries.length === 0) {
23832
23843
  logError("No MCP servers configured in mcpServers");
@@ -23854,11 +23865,21 @@ async function runStdio(config2) {
23854
23865
  const { client, cleanup } = connected;
23855
23866
  clients.set(id, client);
23856
23867
  cleanups.set(id, cleanup);
23857
- const allServerTools = await discoverTools(client);
23868
+ let serverTools = await discoverTools(client);
23869
+ if (entry.allowTools || entry.denyTools) {
23870
+ const perFilter = {};
23871
+ if (entry.allowTools) perFilter.allowTools = new Set(entry.allowTools);
23872
+ if (entry.denyTools) perFilter.denyTools = new Set(entry.denyTools);
23873
+ const before = serverTools.length;
23874
+ serverTools = filterTools(serverTools, perFilter);
23875
+ if (serverTools.length !== before) {
23876
+ logInfo(`Server "${id}": filtered ${before} \u2192 ${serverTools.length} tool(s)`);
23877
+ }
23878
+ }
23858
23879
  logInfo(
23859
- `Server "${id}": ${allServerTools.length} tool(s)${allServerTools.length > 0 ? " \u2014 " + allServerTools.map((t) => t.name).join(", ") : ""}`
23880
+ `Server "${id}": ${serverTools.length} tool(s)${serverTools.length > 0 ? " \u2014 " + serverTools.map((t) => t.name).join(", ") : ""}`
23860
23881
  );
23861
- for (const tool of allServerTools) {
23882
+ for (const tool of serverTools) {
23862
23883
  if (toolToClient.has(tool.name)) {
23863
23884
  logWarn(
23864
23885
  `Tool "${tool.name}" from "${id}" shadows existing tool from "${toolToServer.get(tool.name)}"`
@@ -23878,9 +23899,17 @@ async function runStdio(config2) {
23878
23899
  }
23879
23900
  const aggregatedTools = Array.from(toolsByName.values());
23880
23901
  logInfo(`Aggregated ${aggregatedTools.length} unique tool(s) from ${clients.size} server(s)`);
23902
+ const toolFilter = {};
23903
+ if (allowTools) toolFilter.allowTools = new Set(allowTools);
23904
+ if (denyTools) toolFilter.denyTools = new Set(denyTools);
23905
+ const filteredTools = filterTools(aggregatedTools, toolFilter);
23906
+ const filteredNames = new Set(filteredTools.map((t) => t.name));
23907
+ if (filteredTools.length !== aggregatedTools.length) {
23908
+ logInfo(`After filtering: ${filteredTools.length} tool(s) \u2014 ${filteredTools.map((t) => t.name).join(", ")}`);
23909
+ }
23881
23910
  const { server } = await createToolProxyServer({
23882
- tools: aggregatedTools,
23883
- resolveClient: (name) => toolToClient.get(name),
23911
+ tools: filteredTools,
23912
+ resolveClient: (name) => filteredNames.has(name) ? toolToClient.get(name) : void 0,
23884
23913
  errorLabel: (name) => `"${name}" (server: "${toolToServer.get(name) || "unknown"}")`
23885
23914
  });
23886
23915
  logInfo("Proxy server running on stdio");
@@ -23907,17 +23936,6 @@ async function runStdio(config2) {
23907
23936
  });
23908
23937
  }
23909
23938
 
23910
- // src/filter.ts
23911
- function filterTools(tools, filter) {
23912
- if (filter.allowTools && filter.allowTools.size > 0) {
23913
- return tools.filter((t) => filter.allowTools.has(t.name));
23914
- }
23915
- if (filter.denyTools && filter.denyTools.size > 0) {
23916
- return tools.filter((t) => !filter.denyTools.has(t.name));
23917
- }
23918
- return tools;
23919
- }
23920
-
23921
23939
  // src/detect.ts
23922
23940
  async function detectProtocol(url2, headers) {
23923
23941
  logInfo(`Auto-detecting protocol for ${url2}...`);
@@ -25753,14 +25771,33 @@ function parseCliArgs() {
25753
25771
  return parseStdioArgs(args);
25754
25772
  }
25755
25773
  function parseStdioArgs(args) {
25756
- const idx = args.indexOf("--config");
25757
- if (idx === -1 || idx + 1 >= args.length) {
25774
+ let configJson;
25775
+ let allowTools;
25776
+ let denyTools;
25777
+ for (let i = 0; i < args.length; i++) {
25778
+ const arg = args[i];
25779
+ if (arg === "--config" && i + 1 < args.length) {
25780
+ i++;
25781
+ configJson = args[i];
25782
+ } else if (arg === "--allow-tools" && i + 1 < args.length) {
25783
+ i++;
25784
+ allowTools = args[i].split(",").map((s) => s.trim()).filter(Boolean);
25785
+ } else if (arg === "--deny-tools" && i + 1 < args.length) {
25786
+ i++;
25787
+ denyTools = args[i].split(",").map((s) => s.trim()).filter(Boolean);
25788
+ }
25789
+ }
25790
+ if (!configJson) {
25758
25791
  logError("Missing --config argument");
25759
25792
  logError(`Usage: nuwax-mcp-stdio-proxy --config '{"mcpServers":{...}}'`);
25760
25793
  process.exit(1);
25761
25794
  }
25762
- const config2 = parseConfigJson(args[idx + 1]);
25763
- return { mode: "stdio", config: config2 };
25795
+ if (allowTools && denyTools) {
25796
+ logError("Cannot use both --allow-tools and --deny-tools");
25797
+ process.exit(1);
25798
+ }
25799
+ const config2 = parseConfigJson(configJson);
25800
+ return { mode: "stdio", config: config2, allowTools, denyTools };
25764
25801
  }
25765
25802
  function parseConvertArgs(args) {
25766
25803
  let url2;
@@ -25858,9 +25895,13 @@ function parseConfigJson(json2) {
25858
25895
  }
25859
25896
  function printUsage() {
25860
25897
  logError("Usage:");
25861
- logError(` nuwax-mcp-stdio-proxy --config '{"mcpServers":{...}}' (stdio aggregation)`);
25862
- logError(" nuwax-mcp-stdio-proxy convert [URL] [OPTIONS] (remote \u2192 stdio)");
25863
- logError(" nuwax-mcp-stdio-proxy proxy --port <PORT> --config '...' (HTTP server)");
25898
+ logError(` nuwax-mcp-stdio-proxy --config '{"mcpServers":{...}}' [OPTIONS] (stdio aggregation)`);
25899
+ logError(" nuwax-mcp-stdio-proxy convert [URL] [OPTIONS] (remote \u2192 stdio)");
25900
+ logError(" nuwax-mcp-stdio-proxy proxy --port <PORT> --config '...' (HTTP server)");
25901
+ logError("");
25902
+ logError("Options (stdio / convert):");
25903
+ logError(" --allow-tools <TOOLS> Tool whitelist (comma-separated)");
25904
+ logError(" --deny-tools <TOOLS> Tool blacklist (comma-separated)");
25864
25905
  }
25865
25906
  function printConvertUsage() {
25866
25907
  logError("Usage: nuwax-mcp-stdio-proxy convert [URL] [OPTIONS]");
@@ -25882,7 +25923,7 @@ async function main() {
25882
25923
  const args = parseCliArgs();
25883
25924
  switch (args.mode) {
25884
25925
  case "stdio":
25885
- await runStdio(args.config);
25926
+ await runStdio(args.config, args.allowTools, args.denyTools);
25886
25927
  break;
25887
25928
  case "convert":
25888
25929
  await runConvert(args);
@@ -5,4 +5,4 @@
5
5
  * into a single stdio MCP endpoint.
6
6
  */
7
7
  import type { McpServersConfig } from '../types.js';
8
- export declare function runStdio(config: McpServersConfig): Promise<void>;
8
+ export declare function runStdio(config: McpServersConfig, allowTools?: string[], denyTools?: string[]): Promise<void>;
@@ -8,7 +8,8 @@ import { isSseEntry, isStreamableEntry } from '../types.js';
8
8
  import { logInfo, logWarn, logError } from '../logger.js';
9
9
  import { buildBaseEnv, connectStdio, connectStreamable, connectSse } from '../transport.js';
10
10
  import { discoverTools, createToolProxyServer, setupGracefulShutdown } from '../shared.js';
11
- export async function runStdio(config) {
11
+ import { filterTools } from '../filter.js';
12
+ export async function runStdio(config, allowTools, denyTools) {
12
13
  const entries = Object.entries(config.mcpServers);
13
14
  if (entries.length === 0) {
14
15
  logError('No MCP servers configured in mcpServers');
@@ -37,9 +38,22 @@ export async function runStdio(config) {
37
38
  const { client, cleanup } = connected;
38
39
  clients.set(id, client);
39
40
  cleanups.set(id, cleanup);
40
- const allServerTools = await discoverTools(client);
41
- logInfo(`Server "${id}": ${allServerTools.length} tool(s)${allServerTools.length > 0 ? ' — ' + allServerTools.map((t) => t.name).join(', ') : ''}`);
42
- for (const tool of allServerTools) {
41
+ let serverTools = await discoverTools(client);
42
+ // Per-server tool filtering (allowTools/denyTools in config entry)
43
+ if (entry.allowTools || entry.denyTools) {
44
+ const perFilter = {};
45
+ if (entry.allowTools)
46
+ perFilter.allowTools = new Set(entry.allowTools);
47
+ if (entry.denyTools)
48
+ perFilter.denyTools = new Set(entry.denyTools);
49
+ const before = serverTools.length;
50
+ serverTools = filterTools(serverTools, perFilter);
51
+ if (serverTools.length !== before) {
52
+ logInfo(`Server "${id}": filtered ${before} → ${serverTools.length} tool(s)`);
53
+ }
54
+ }
55
+ logInfo(`Server "${id}": ${serverTools.length} tool(s)${serverTools.length > 0 ? ' — ' + serverTools.map((t) => t.name).join(', ') : ''}`);
56
+ for (const tool of serverTools) {
43
57
  if (toolToClient.has(tool.name)) {
44
58
  logWarn(`Tool "${tool.name}" from "${id}" shadows existing tool from "${toolToServer.get(tool.name)}"`);
45
59
  }
@@ -59,10 +73,21 @@ export async function runStdio(config) {
59
73
  }
60
74
  const aggregatedTools = Array.from(toolsByName.values());
61
75
  logInfo(`Aggregated ${aggregatedTools.length} unique tool(s) from ${clients.size} server(s)`);
76
+ // ---- Phase 1.5: Apply tool filtering (allow/deny) ----
77
+ const toolFilter = {};
78
+ if (allowTools)
79
+ toolFilter.allowTools = new Set(allowTools);
80
+ if (denyTools)
81
+ toolFilter.denyTools = new Set(denyTools);
82
+ const filteredTools = filterTools(aggregatedTools, toolFilter);
83
+ const filteredNames = new Set(filteredTools.map((t) => t.name));
84
+ if (filteredTools.length !== aggregatedTools.length) {
85
+ logInfo(`After filtering: ${filteredTools.length} tool(s) — ${filteredTools.map((t) => t.name).join(', ')}`);
86
+ }
62
87
  // ---- Phase 2: Create the aggregating MCP server ----
63
88
  const { server } = await createToolProxyServer({
64
- tools: aggregatedTools,
65
- resolveClient: (name) => toolToClient.get(name),
89
+ tools: filteredTools,
90
+ resolveClient: (name) => filteredNames.has(name) ? toolToClient.get(name) : undefined,
66
91
  errorLabel: (name) => `"${name}" (server: "${toolToServer.get(name) || 'unknown'}")`,
67
92
  });
68
93
  logInfo('Proxy server running on stdio');
package/dist/types.d.ts CHANGED
@@ -6,6 +6,10 @@ export interface StdioServerEntry {
6
6
  command: string;
7
7
  args?: string[];
8
8
  env?: Record<string, string>;
9
+ /** 工具白名单(只暴露指定工具) */
10
+ allowTools?: string[];
11
+ /** 工具黑名单(排除指定工具) */
12
+ denyTools?: string[];
9
13
  }
10
14
  /**
11
15
  * Streamable HTTP 类型: 连接远程 MCP server (Streamable HTTP)
@@ -18,6 +22,10 @@ export interface StreamableServerEntry {
18
22
  transport?: 'streamable-http';
19
23
  headers?: Record<string, string>;
20
24
  authToken?: string;
25
+ /** 工具白名单(只暴露指定工具) */
26
+ allowTools?: string[];
27
+ /** 工具黑名单(排除指定工具) */
28
+ denyTools?: string[];
21
29
  }
22
30
  /**
23
31
  * SSE 类型: 连接远程 MCP server (Server-Sent Events)
@@ -29,6 +37,10 @@ export interface SseServerEntry {
29
37
  transport: 'sse';
30
38
  headers?: Record<string, string>;
31
39
  authToken?: string;
40
+ /** 工具白名单(只暴露指定工具) */
41
+ allowTools?: string[];
42
+ /** 工具黑名单(排除指定工具) */
43
+ denyTools?: string[];
32
44
  }
33
45
  export type McpServerEntry = StdioServerEntry | StreamableServerEntry | SseServerEntry;
34
46
  export declare function isSseEntry(entry: McpServerEntry): entry is SseServerEntry;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuwax-mcp-stdio-proxy",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
4
4
  "description": "TypeScript MCP proxy — aggregates multiple MCP servers (stdio + streamable-http + SSE) with convert & proxy modes",
5
5
  "type": "module",
6
6
  "bin": {