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 +67 -26
- package/dist/modes/stdio.d.ts +1 -1
- package/dist/modes/stdio.js +31 -6
- package/dist/types.d.ts +12 -0
- package/package.json +1 -1
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.
|
|
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
|
-
|
|
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}": ${
|
|
23880
|
+
`Server "${id}": ${serverTools.length} tool(s)${serverTools.length > 0 ? " \u2014 " + serverTools.map((t) => t.name).join(", ") : ""}`
|
|
23860
23881
|
);
|
|
23861
|
-
for (const tool of
|
|
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:
|
|
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
|
-
|
|
25757
|
-
|
|
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
|
-
|
|
25763
|
-
|
|
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":{...}}'
|
|
25862
|
-
logError(" nuwax-mcp-stdio-proxy convert [URL] [OPTIONS]
|
|
25863
|
-
logError(" nuwax-mcp-stdio-proxy proxy --port <PORT> --config '...'
|
|
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);
|
package/dist/modes/stdio.d.ts
CHANGED
|
@@ -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>;
|
package/dist/modes/stdio.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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:
|
|
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