nuwax-mcp-stdio-proxy 1.4.4 → 1.4.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.
- package/dist/bridge.js +33 -15
- package/dist/index.js +70 -22
- package/package.json +1 -1
package/dist/bridge.js
CHANGED
|
@@ -49,17 +49,36 @@ export class PersistentMcpBridge {
|
|
|
49
49
|
this.log.warn(`${LOG_TAG} Already running, stopping first`);
|
|
50
50
|
await this.stop();
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
const serverCount = Object.keys(servers).length;
|
|
53
|
+
this.log.info(`${LOG_TAG} Starting with ${serverCount} persistent servers (parallel)`);
|
|
54
|
+
// 1. 并行启动 HTTP server 和所有 MCP server 子进程。
|
|
55
|
+
// 两者完全独立:HTTP server 在请求时才查询 this.servers,
|
|
56
|
+
// startServer 会在异步 spawnAndConnect 前先同步写入 this.servers,
|
|
57
|
+
// 所以 HTTP server 收到第一个请求时所有 entry 都已注册。
|
|
58
|
+
//
|
|
59
|
+
// 提前保存 serverPromises 引用:若 startHttpServer 抛出,在 catch 中先
|
|
60
|
+
// await Promise.allSettled(serverPromises) 等所有 spawnAndConnect 跑完
|
|
61
|
+
// (保证 entry.client / entry.transport 已赋值),再调用 stopServer,
|
|
62
|
+
// 才能可靠关闭子进程,防止孤儿进程残留。
|
|
63
|
+
const serverPromises = Object.entries(servers).map(([id, config]) => this.startServer(id, config));
|
|
64
|
+
try {
|
|
65
|
+
await Promise.all([
|
|
66
|
+
this.startHttpServer(options?.port),
|
|
67
|
+
...serverPromises,
|
|
68
|
+
]);
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
this.log.error(`${LOG_TAG} 启动失败,清理已 spawn 的子进程:`, e);
|
|
72
|
+
// 等待所有 spawnAndConnect 完成,确保 entry.client/transport 已赋值后再清理
|
|
73
|
+
await Promise.allSettled(serverPromises);
|
|
74
|
+
await Promise.all(Array.from(this.servers.keys()).map((id) => this.stopServer(id)));
|
|
75
|
+
this.servers.clear();
|
|
76
|
+
throw e;
|
|
56
77
|
}
|
|
57
|
-
// 2. Start HTTP server
|
|
58
|
-
await this.startHttpServer(options?.port);
|
|
59
78
|
this.running = true;
|
|
60
|
-
//
|
|
79
|
+
// 2. Start periodic session cleanup
|
|
61
80
|
this.sessionCleanupTimer = setInterval(() => this.cleanupStaleSessions(), SESSION_CLEANUP_INTERVAL_MS);
|
|
62
|
-
this.log.info(`${LOG_TAG} Bridge ready on port ${this.port}`);
|
|
81
|
+
this.log.info(`${LOG_TAG} Bridge ready on port ${this.port} (${serverCount} servers)`);
|
|
63
82
|
}
|
|
64
83
|
/**
|
|
65
84
|
* Stop bridge: close HTTP, kill all child processes
|
|
@@ -72,15 +91,16 @@ export class PersistentMcpBridge {
|
|
|
72
91
|
clearInterval(this.sessionCleanupTimer);
|
|
73
92
|
this.sessionCleanupTimer = null;
|
|
74
93
|
}
|
|
75
|
-
//
|
|
76
|
-
|
|
94
|
+
// 并行关闭所有 HTTP sessions(各 session transport 独立,无顺序依赖)。
|
|
95
|
+
// 内层 async 函数自行 catch 错误后不再 throw,Promise.all 足够,不需要 allSettled。
|
|
96
|
+
await Promise.all(Array.from(this.httpSessions.entries()).map(async ([key, session]) => {
|
|
77
97
|
try {
|
|
78
98
|
await session.transport.close();
|
|
79
99
|
}
|
|
80
100
|
catch (e) {
|
|
81
101
|
this.log.warn(`${LOG_TAG} Error closing HTTP session ${key}:`, e);
|
|
82
102
|
}
|
|
83
|
-
}
|
|
103
|
+
}));
|
|
84
104
|
this.httpSessions.clear();
|
|
85
105
|
// Close HTTP server
|
|
86
106
|
if (this.httpServer) {
|
|
@@ -90,10 +110,8 @@ export class PersistentMcpBridge {
|
|
|
90
110
|
this.httpServer = null;
|
|
91
111
|
this.port = 0;
|
|
92
112
|
}
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
await this.stopServer(id);
|
|
96
|
-
}
|
|
113
|
+
// 并行关闭所有 MCP server 子进程(各自独立,无顺序依赖)
|
|
114
|
+
await Promise.all(Array.from(this.servers.keys()).map((id) => this.stopServer(id)));
|
|
97
115
|
this.servers.clear();
|
|
98
116
|
this.log.info(`${LOG_TAG} Stopped`);
|
|
99
117
|
}
|
package/dist/index.js
CHANGED
|
@@ -6785,12 +6785,12 @@ var require_dist = __commonJS({
|
|
|
6785
6785
|
throw new Error(`Unknown format "${name}"`);
|
|
6786
6786
|
return f;
|
|
6787
6787
|
};
|
|
6788
|
-
function addFormats(ajv, list,
|
|
6788
|
+
function addFormats(ajv, list, fs3, exportName) {
|
|
6789
6789
|
var _a2;
|
|
6790
6790
|
var _b;
|
|
6791
6791
|
(_a2 = (_b = ajv.opts.code).formats) !== null && _a2 !== void 0 ? _a2 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
6792
6792
|
for (const f of list)
|
|
6793
|
-
ajv.addFormat(f,
|
|
6793
|
+
ajv.addFormat(f, fs3[f]);
|
|
6794
6794
|
}
|
|
6795
6795
|
module.exports = exports = formatsPlugin;
|
|
6796
6796
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -6798,6 +6798,9 @@ var require_dist = __commonJS({
|
|
|
6798
6798
|
}
|
|
6799
6799
|
});
|
|
6800
6800
|
|
|
6801
|
+
// src/index.ts
|
|
6802
|
+
import * as fs2 from "fs";
|
|
6803
|
+
|
|
6801
6804
|
// src/logger.ts
|
|
6802
6805
|
function log(level, msg) {
|
|
6803
6806
|
process.stderr.write(`[nuwax-mcp-proxy] ${level}: ${msg}
|
|
@@ -23764,7 +23767,7 @@ var StdioServerTransport = class {
|
|
|
23764
23767
|
|
|
23765
23768
|
// src/constants.ts
|
|
23766
23769
|
var PKG_NAME = "nuwax-mcp-stdio-proxy";
|
|
23767
|
-
var PKG_VERSION = "1.4.
|
|
23770
|
+
var PKG_VERSION = "1.4.6";
|
|
23768
23771
|
|
|
23769
23772
|
// src/shared.ts
|
|
23770
23773
|
async function discoverTools(client) {
|
|
@@ -25379,14 +25382,24 @@ var PersistentMcpBridge = class {
|
|
|
25379
25382
|
this.log.warn(`${LOG_TAG} Already running, stopping first`);
|
|
25380
25383
|
await this.stop();
|
|
25381
25384
|
}
|
|
25382
|
-
|
|
25383
|
-
|
|
25384
|
-
|
|
25385
|
+
const serverCount = Object.keys(servers).length;
|
|
25386
|
+
this.log.info(`${LOG_TAG} Starting with ${serverCount} persistent servers (parallel)`);
|
|
25387
|
+
const serverPromises = Object.entries(servers).map(([id, config2]) => this.startServer(id, config2));
|
|
25388
|
+
try {
|
|
25389
|
+
await Promise.all([
|
|
25390
|
+
this.startHttpServer(options?.port),
|
|
25391
|
+
...serverPromises
|
|
25392
|
+
]);
|
|
25393
|
+
} catch (e) {
|
|
25394
|
+
this.log.error(`${LOG_TAG} \u542F\u52A8\u5931\u8D25\uFF0C\u6E05\u7406\u5DF2 spawn \u7684\u5B50\u8FDB\u7A0B:`, e);
|
|
25395
|
+
await Promise.allSettled(serverPromises);
|
|
25396
|
+
await Promise.all(Array.from(this.servers.keys()).map((id) => this.stopServer(id)));
|
|
25397
|
+
this.servers.clear();
|
|
25398
|
+
throw e;
|
|
25385
25399
|
}
|
|
25386
|
-
await this.startHttpServer(options?.port);
|
|
25387
25400
|
this.running = true;
|
|
25388
25401
|
this.sessionCleanupTimer = setInterval(() => this.cleanupStaleSessions(), SESSION_CLEANUP_INTERVAL_MS);
|
|
25389
|
-
this.log.info(`${LOG_TAG} Bridge ready on port ${this.port}`);
|
|
25402
|
+
this.log.info(`${LOG_TAG} Bridge ready on port ${this.port} (${serverCount} servers)`);
|
|
25390
25403
|
}
|
|
25391
25404
|
/**
|
|
25392
25405
|
* Stop bridge: close HTTP, kill all child processes
|
|
@@ -25398,13 +25411,15 @@ var PersistentMcpBridge = class {
|
|
|
25398
25411
|
clearInterval(this.sessionCleanupTimer);
|
|
25399
25412
|
this.sessionCleanupTimer = null;
|
|
25400
25413
|
}
|
|
25401
|
-
|
|
25402
|
-
|
|
25403
|
-
|
|
25404
|
-
|
|
25405
|
-
|
|
25406
|
-
|
|
25407
|
-
|
|
25414
|
+
await Promise.all(
|
|
25415
|
+
Array.from(this.httpSessions.entries()).map(async ([key, session]) => {
|
|
25416
|
+
try {
|
|
25417
|
+
await session.transport.close();
|
|
25418
|
+
} catch (e) {
|
|
25419
|
+
this.log.warn(`${LOG_TAG} Error closing HTTP session ${key}:`, e);
|
|
25420
|
+
}
|
|
25421
|
+
})
|
|
25422
|
+
);
|
|
25408
25423
|
this.httpSessions.clear();
|
|
25409
25424
|
if (this.httpServer) {
|
|
25410
25425
|
await new Promise((resolve) => {
|
|
@@ -25413,9 +25428,9 @@ var PersistentMcpBridge = class {
|
|
|
25413
25428
|
this.httpServer = null;
|
|
25414
25429
|
this.port = 0;
|
|
25415
25430
|
}
|
|
25416
|
-
|
|
25417
|
-
|
|
25418
|
-
|
|
25431
|
+
await Promise.all(
|
|
25432
|
+
Array.from(this.servers.keys()).map((id) => this.stopServer(id))
|
|
25433
|
+
);
|
|
25419
25434
|
this.servers.clear();
|
|
25420
25435
|
this.log.info(`${LOG_TAG} Stopped`);
|
|
25421
25436
|
}
|
|
@@ -25782,6 +25797,7 @@ function parseCliArgs() {
|
|
|
25782
25797
|
}
|
|
25783
25798
|
function parseStdioArgs(args) {
|
|
25784
25799
|
let configJson;
|
|
25800
|
+
let configFile;
|
|
25785
25801
|
let allowTools;
|
|
25786
25802
|
let denyTools;
|
|
25787
25803
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -25789,6 +25805,9 @@ function parseStdioArgs(args) {
|
|
|
25789
25805
|
if (arg === "--config" && i + 1 < args.length) {
|
|
25790
25806
|
i++;
|
|
25791
25807
|
configJson = args[i];
|
|
25808
|
+
} else if (arg === "--config-file" && i + 1 < args.length) {
|
|
25809
|
+
i++;
|
|
25810
|
+
configFile = args[i];
|
|
25792
25811
|
} else if (arg === "--allow-tools" && i + 1 < args.length) {
|
|
25793
25812
|
i++;
|
|
25794
25813
|
allowTools = args[i].split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -25797,16 +25816,21 @@ function parseStdioArgs(args) {
|
|
|
25797
25816
|
denyTools = args[i].split(",").map((s) => s.trim()).filter(Boolean);
|
|
25798
25817
|
}
|
|
25799
25818
|
}
|
|
25800
|
-
if (!configJson) {
|
|
25801
|
-
logError("Missing --config argument");
|
|
25819
|
+
if (!configJson && !configFile) {
|
|
25820
|
+
logError("Missing --config or --config-file argument");
|
|
25802
25821
|
logError(`Usage: nuwax-mcp-stdio-proxy --config '{"mcpServers":{...}}'`);
|
|
25822
|
+
logError(" or: nuwax-mcp-stdio-proxy --config-file /path/to/config.json");
|
|
25823
|
+
process.exit(1);
|
|
25824
|
+
}
|
|
25825
|
+
if (configJson && configFile) {
|
|
25826
|
+
logError("Cannot use both --config and --config-file");
|
|
25803
25827
|
process.exit(1);
|
|
25804
25828
|
}
|
|
25805
25829
|
if (allowTools && denyTools) {
|
|
25806
25830
|
logError("Cannot use both --allow-tools and --deny-tools");
|
|
25807
25831
|
process.exit(1);
|
|
25808
25832
|
}
|
|
25809
|
-
const config2 = parseConfigJson(configJson);
|
|
25833
|
+
const config2 = configFile ? parseConfigFile(configFile) : parseConfigJson(configJson);
|
|
25810
25834
|
return { mode: "stdio", config: config2, allowTools, denyTools };
|
|
25811
25835
|
}
|
|
25812
25836
|
function parseConvertArgs(args) {
|
|
@@ -25860,6 +25884,7 @@ function parseConvertArgs(args) {
|
|
|
25860
25884
|
function parseProxyArgs(args) {
|
|
25861
25885
|
let port;
|
|
25862
25886
|
let config2;
|
|
25887
|
+
let configFile;
|
|
25863
25888
|
for (let i = 0; i < args.length; i++) {
|
|
25864
25889
|
const arg = args[i];
|
|
25865
25890
|
if (arg === "--port" && i + 1 < args.length) {
|
|
@@ -25873,6 +25898,9 @@ function parseProxyArgs(args) {
|
|
|
25873
25898
|
} else if (arg === "--config" && i + 1 < args.length) {
|
|
25874
25899
|
i++;
|
|
25875
25900
|
config2 = parseConfigJson(args[i]);
|
|
25901
|
+
} else if (arg === "--config-file" && i + 1 < args.length) {
|
|
25902
|
+
i++;
|
|
25903
|
+
configFile = args[i];
|
|
25876
25904
|
} else {
|
|
25877
25905
|
logError(`Unknown argument: "${arg}"`);
|
|
25878
25906
|
printProxyUsage();
|
|
@@ -25884,8 +25912,11 @@ function parseProxyArgs(args) {
|
|
|
25884
25912
|
printProxyUsage();
|
|
25885
25913
|
process.exit(1);
|
|
25886
25914
|
}
|
|
25915
|
+
if (configFile) {
|
|
25916
|
+
config2 = parseConfigFile(configFile);
|
|
25917
|
+
}
|
|
25887
25918
|
if (!config2) {
|
|
25888
|
-
logError("--config is required for proxy mode");
|
|
25919
|
+
logError("--config or --config-file is required for proxy mode");
|
|
25889
25920
|
printProxyUsage();
|
|
25890
25921
|
process.exit(1);
|
|
25891
25922
|
}
|
|
@@ -25903,13 +25934,29 @@ function parseConfigJson(json2) {
|
|
|
25903
25934
|
process.exit(1);
|
|
25904
25935
|
}
|
|
25905
25936
|
}
|
|
25937
|
+
function parseConfigFile(filePath) {
|
|
25938
|
+
try {
|
|
25939
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
25940
|
+
const config2 = JSON.parse(content);
|
|
25941
|
+
if (!config2.mcpServers || typeof config2.mcpServers !== "object") {
|
|
25942
|
+
throw new Error('config must contain a "mcpServers" object');
|
|
25943
|
+
}
|
|
25944
|
+
return config2;
|
|
25945
|
+
} catch (e) {
|
|
25946
|
+
logError(`Failed to read or parse config file "${filePath}": ${e}`);
|
|
25947
|
+
process.exit(1);
|
|
25948
|
+
}
|
|
25949
|
+
}
|
|
25906
25950
|
function printUsage() {
|
|
25907
25951
|
logError("Usage:");
|
|
25908
25952
|
logError(` nuwax-mcp-stdio-proxy --config '{"mcpServers":{...}}' [OPTIONS] (stdio aggregation)`);
|
|
25953
|
+
logError(" nuwax-mcp-stdio-proxy --config-file <FILE> [OPTIONS] (stdio aggregation from file)");
|
|
25909
25954
|
logError(" nuwax-mcp-stdio-proxy convert [URL] [OPTIONS] (remote \u2192 stdio)");
|
|
25910
25955
|
logError(" nuwax-mcp-stdio-proxy proxy --port <PORT> --config '...' (HTTP server)");
|
|
25911
25956
|
logError("");
|
|
25912
25957
|
logError("Options (stdio / convert):");
|
|
25958
|
+
logError(" --config <JSON> MCP config JSON string");
|
|
25959
|
+
logError(" --config-file <FILE> MCP config JSON file path");
|
|
25913
25960
|
logError(" --allow-tools <TOOLS> Tool whitelist (comma-separated)");
|
|
25914
25961
|
logError(" --deny-tools <TOOLS> Tool blacklist (comma-separated)");
|
|
25915
25962
|
}
|
|
@@ -25928,6 +25975,7 @@ function printConvertUsage() {
|
|
|
25928
25975
|
}
|
|
25929
25976
|
function printProxyUsage() {
|
|
25930
25977
|
logError(`Usage: nuwax-mcp-stdio-proxy proxy --port <PORT> --config '{"mcpServers":{...}}'`);
|
|
25978
|
+
logError(" or: nuwax-mcp-stdio-proxy proxy --port <PORT> --config-file <FILE>");
|
|
25931
25979
|
}
|
|
25932
25980
|
async function main() {
|
|
25933
25981
|
const args = parseCliArgs();
|
package/package.json
CHANGED