@aiwerk/mcp-bridge 2.6.5 → 2.6.7
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/bin/mcp-bridge.js
CHANGED
|
@@ -19,22 +19,23 @@ function createLogger(level) {
|
|
|
19
19
|
const levels = { error: 0, warn: 1, info: 2, debug: 3 };
|
|
20
20
|
const threshold = levels[level];
|
|
21
21
|
const ts = () => new Date().toISOString().replace("T", " ").replace("Z", "");
|
|
22
|
+
const fmt = (a) => a instanceof Error ? (a.stack || a.message) : String(a);
|
|
22
23
|
return {
|
|
23
24
|
error: (...args) => {
|
|
24
25
|
if (threshold >= 0)
|
|
25
|
-
process.stderr.write(`[${ts()}] [ERROR] ${args.map(
|
|
26
|
+
process.stderr.write(`[${ts()}] [ERROR] ${args.map(fmt).join(" ")}\n`);
|
|
26
27
|
},
|
|
27
28
|
warn: (...args) => {
|
|
28
29
|
if (threshold >= 1)
|
|
29
|
-
process.stderr.write(`[${ts()}] [WARN] ${args.map(
|
|
30
|
+
process.stderr.write(`[${ts()}] [WARN] ${args.map(fmt).join(" ")}\n`);
|
|
30
31
|
},
|
|
31
32
|
info: (...args) => {
|
|
32
33
|
if (threshold >= 2)
|
|
33
|
-
process.stderr.write(`[${ts()}] [INFO] ${args.map(
|
|
34
|
+
process.stderr.write(`[${ts()}] [INFO] ${args.map(fmt).join(" ")}\n`);
|
|
34
35
|
},
|
|
35
36
|
debug: (...args) => {
|
|
36
37
|
if (threshold >= 3)
|
|
37
|
-
process.stderr.write(`[${ts()}] [DEBUG] ${args.map(
|
|
38
|
+
process.stderr.write(`[${ts()}] [DEBUG] ${args.map(fmt).join(" ")}\n`);
|
|
38
39
|
},
|
|
39
40
|
};
|
|
40
41
|
}
|
|
@@ -176,7 +177,7 @@ function cmdCatalog(logger) {
|
|
|
176
177
|
process.exit(1);
|
|
177
178
|
}
|
|
178
179
|
const catalog = JSON.parse(readFileSync(catalogPath, "utf-8"));
|
|
179
|
-
const servers = catalog.servers || {};
|
|
180
|
+
const servers = catalog.recipes || catalog.servers || {};
|
|
180
181
|
process.stdout.write("\nAvailable servers:\n\n");
|
|
181
182
|
process.stdout.write(" Server Transport Description\n");
|
|
182
183
|
process.stdout.write(" " + "─".repeat(60) + "\n");
|
|
@@ -218,7 +219,7 @@ function cmdSearch(query, logger) {
|
|
|
218
219
|
process.exit(1);
|
|
219
220
|
}
|
|
220
221
|
const catalog = JSON.parse(readFileSync(catalogPath, "utf-8"));
|
|
221
|
-
const servers = catalog.servers || {};
|
|
222
|
+
const servers = catalog.recipes || catalog.servers || {};
|
|
222
223
|
const lowerQuery = query.toLowerCase();
|
|
223
224
|
const matches = Object.entries(servers).filter(([name, info]) => {
|
|
224
225
|
return name.toLowerCase().includes(lowerQuery) ||
|
|
@@ -255,6 +256,7 @@ function cmdInstall(serverName, logger) {
|
|
|
255
256
|
}
|
|
256
257
|
}
|
|
257
258
|
catch (err) {
|
|
259
|
+
logger.error("Install failed:", err instanceof Error ? err.message : String(err));
|
|
258
260
|
process.exit(1);
|
|
259
261
|
}
|
|
260
262
|
}
|
package/dist/src/security.js
CHANGED
|
@@ -155,6 +155,18 @@ function truncateJsonAware(value, limit) {
|
|
|
155
155
|
return null;
|
|
156
156
|
}
|
|
157
157
|
function truncateArray(arr, limit) {
|
|
158
|
+
// For very large arrays, skip binary search (too many JSON.stringify calls)
|
|
159
|
+
// and use progressive halving instead
|
|
160
|
+
if (arr.length > 1000) {
|
|
161
|
+
let count = arr.length;
|
|
162
|
+
while (count > 1) {
|
|
163
|
+
count = Math.ceil(count / 2);
|
|
164
|
+
if (JSON.stringify(arr.slice(0, count)).length <= limit) {
|
|
165
|
+
return arr.slice(0, count);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return arr.slice(0, 1);
|
|
169
|
+
}
|
|
158
170
|
// Binary search for the number of elements that fit
|
|
159
171
|
let lo = 0;
|
|
160
172
|
let hi = arr.length;
|
|
@@ -213,20 +225,19 @@ export function processResult(result, serverName, serverConfig, clientConfig) {
|
|
|
213
225
|
const wasTruncated = processed !== null && typeof processed === "object" && processed._truncated === true;
|
|
214
226
|
// Sanitize step (only for trust=sanitize, handled inside applyTrustLevel)
|
|
215
227
|
processed = applyTrustLevel(processed, serverName, serverConfig);
|
|
216
|
-
// If both truncated and untrusted
|
|
217
|
-
// to avoid double-wrapping ({ _trust, result: { _truncated, result: actual } })
|
|
228
|
+
// If both truncated and untrusted, flatten the metadata to top level
|
|
229
|
+
// to avoid double-wrapping ({ _trust, result: { _truncated, result: actual } }).
|
|
230
|
+
// Note: sanitize mode does NOT wrap — it sanitizes in-place, so the truncation
|
|
231
|
+
// markers (_truncated, _originalLength) are already at the right level.
|
|
218
232
|
const trust = serverConfig.trust ?? "trusted";
|
|
219
|
-
if (wasTruncated &&
|
|
220
|
-
|
|
233
|
+
if (wasTruncated && trust === "untrusted") {
|
|
234
|
+
return {
|
|
235
|
+
_trust: "untrusted",
|
|
236
|
+
_server: serverName,
|
|
221
237
|
_truncated: true,
|
|
222
238
|
_originalLength: processed.result?._originalLength,
|
|
223
239
|
result: processed.result?.result,
|
|
224
240
|
};
|
|
225
|
-
if (trust === "untrusted") {
|
|
226
|
-
flat._trust = "untrusted";
|
|
227
|
-
flat._server = serverName;
|
|
228
|
-
}
|
|
229
|
-
return flat;
|
|
230
241
|
}
|
|
231
242
|
return processed;
|
|
232
243
|
}
|
|
@@ -30,6 +30,14 @@ export class StandaloneServer {
|
|
|
30
30
|
if (this.isRouterMode()) {
|
|
31
31
|
this.router = new McpRouter(config.servers ?? {}, config, logger);
|
|
32
32
|
}
|
|
33
|
+
else {
|
|
34
|
+
// Warn if security config is used in direct mode where processResult() doesn't run
|
|
35
|
+
const hasSecurityConfig = Object.values(config.servers ?? {}).some(s => s.trust && s.trust !== "trusted" || s.maxResultChars || s.toolFilter);
|
|
36
|
+
if (hasSecurityConfig || config.maxResultChars) {
|
|
37
|
+
logger.warn("[mcp-bridge] Security config (trust/maxResultChars/toolFilter) detected in direct mode. " +
|
|
38
|
+
"These settings only apply in router mode. Consider switching to mode: \"router\".");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
33
41
|
}
|
|
34
42
|
isRouterMode() {
|
|
35
43
|
return (this.config.mode ?? "router") === "router";
|
|
@@ -106,6 +106,8 @@ export class BaseTransport {
|
|
|
106
106
|
this.scheduleReconnect();
|
|
107
107
|
}
|
|
108
108
|
}, reconnectInterval);
|
|
109
|
+
// Allow Node.js process to exit gracefully even if reconnect is pending
|
|
110
|
+
this.reconnectTimer.unref();
|
|
109
111
|
}
|
|
110
112
|
/** Cancel any scheduled reconnection timer. */
|
|
111
113
|
cleanupReconnectTimer() {
|
|
@@ -103,7 +103,10 @@ export class StdioTransport extends BaseTransport {
|
|
|
103
103
|
let settled = false;
|
|
104
104
|
let timeout;
|
|
105
105
|
const cleanup = () => {
|
|
106
|
-
|
|
106
|
+
// Note: we don't remove onFirstData from stdout because it may have
|
|
107
|
+
// been re-registered via once("data") and the Node.js internal wrapper
|
|
108
|
+
// differs from the original reference. The settled flag ensures
|
|
109
|
+
// onFirstData is a no-op after cleanup.
|
|
107
110
|
this.process?.off("error", onProcessError);
|
|
108
111
|
this.process?.off("exit", onProcessExit);
|
|
109
112
|
clearTimeout(timeout);
|