@aiwerk/mcp-bridge 2.6.4 → 2.6.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/bin/mcp-bridge.js +8 -6
- package/dist/src/config.js +19 -5
- package/dist/src/mcp-router.js +1 -1
- package/dist/src/security.js +9 -5
- package/dist/src/standalone-server.js +1 -1
- package/dist/src/transport-sse.js +4 -3
- package/dist/src/transport-stdio.js +20 -2
- package/dist/src/transport-streamable-http.js +4 -4
- package/package.json +1 -1
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/config.js
CHANGED
|
@@ -48,16 +48,30 @@ export function parseEnvFile(content) {
|
|
|
48
48
|
if (eqIdx === -1)
|
|
49
49
|
continue;
|
|
50
50
|
const key = trimmed.substring(0, eqIdx).trim();
|
|
51
|
-
|
|
51
|
+
const rawValue = trimmed.substring(eqIdx + 1).trim();
|
|
52
|
+
let value;
|
|
53
|
+
let wasQuoted = false;
|
|
52
54
|
// Strip surrounding quotes and handle escaped quotes within
|
|
53
|
-
if ((
|
|
54
|
-
(
|
|
55
|
-
const quote =
|
|
56
|
-
value =
|
|
55
|
+
if ((rawValue.startsWith('"') && rawValue.endsWith('"')) ||
|
|
56
|
+
(rawValue.startsWith("'") && rawValue.endsWith("'"))) {
|
|
57
|
+
const quote = rawValue[0];
|
|
58
|
+
value = rawValue.slice(1, -1);
|
|
57
59
|
// Unescape escaped quotes: \" → " or \' → '
|
|
58
60
|
value = value.replace(new RegExp(`\\\\${quote}`, "g"), quote);
|
|
59
61
|
// Unescape escaped backslashes: \\ → \
|
|
60
62
|
value = value.replace(/\\\\/g, "\\");
|
|
63
|
+
wasQuoted = true;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
value = rawValue;
|
|
67
|
+
}
|
|
68
|
+
// Strip inline comments (KEY=value # comment) for unquoted values only.
|
|
69
|
+
// Quoted values preserve # characters literally: KEY="val#ue" → val#ue
|
|
70
|
+
if (!wasQuoted) {
|
|
71
|
+
const hashIdx = value.indexOf(" #");
|
|
72
|
+
if (hashIdx !== -1) {
|
|
73
|
+
value = value.substring(0, hashIdx).trimEnd();
|
|
74
|
+
}
|
|
61
75
|
}
|
|
62
76
|
if (key)
|
|
63
77
|
env[key] = value;
|
package/dist/src/mcp-router.js
CHANGED
|
@@ -200,7 +200,7 @@ export class McpRouter {
|
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
202
|
if (normalizedAction !== "call") {
|
|
203
|
-
return this.error("invalid_params", `action must be one of: list, call, batch, refresh, schema, intent`);
|
|
203
|
+
return this.error("invalid_params", `action must be one of: list, call, batch, refresh, schema, intent, status, promotions`);
|
|
204
204
|
}
|
|
205
205
|
if (!tool) {
|
|
206
206
|
return this.error("invalid_params", "tool is required for action=call");
|
package/dist/src/security.js
CHANGED
|
@@ -213,16 +213,20 @@ export function processResult(result, serverName, serverConfig, clientConfig) {
|
|
|
213
213
|
const wasTruncated = processed !== null && typeof processed === "object" && processed._truncated === true;
|
|
214
214
|
// Sanitize step (only for trust=sanitize, handled inside applyTrustLevel)
|
|
215
215
|
processed = applyTrustLevel(processed, serverName, serverConfig);
|
|
216
|
-
// If both truncated and untrusted, flatten the metadata to top level
|
|
216
|
+
// If both truncated and untrusted/sanitize, flatten the metadata to top level
|
|
217
|
+
// to avoid double-wrapping ({ _trust, result: { _truncated, result: actual } })
|
|
217
218
|
const trust = serverConfig.trust ?? "trusted";
|
|
218
|
-
if (wasTruncated && trust === "untrusted") {
|
|
219
|
-
|
|
220
|
-
_trust: "untrusted",
|
|
221
|
-
_server: serverName,
|
|
219
|
+
if (wasTruncated && (trust === "untrusted" || trust === "sanitize")) {
|
|
220
|
+
const flat = {
|
|
222
221
|
_truncated: true,
|
|
223
222
|
_originalLength: processed.result?._originalLength,
|
|
224
223
|
result: processed.result?.result,
|
|
225
224
|
};
|
|
225
|
+
if (trust === "untrusted") {
|
|
226
|
+
flat._trust = "untrusted";
|
|
227
|
+
flat._server = serverName;
|
|
228
|
+
}
|
|
229
|
+
return flat;
|
|
226
230
|
}
|
|
227
231
|
return processed;
|
|
228
232
|
}
|
|
@@ -28,7 +28,7 @@ export class StandaloneServer {
|
|
|
28
28
|
this.logger = logger;
|
|
29
29
|
this.tokenManager = new OAuth2TokenManager(logger, new FileTokenStore());
|
|
30
30
|
if (this.isRouterMode()) {
|
|
31
|
-
this.router = new McpRouter(config.servers
|
|
31
|
+
this.router = new McpRouter(config.servers ?? {}, config, logger);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
isRouterMode() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OAuth2TokenManager } from "./oauth2-token-manager.js";
|
|
2
|
-
import { BaseTransport, isAuthCodeOAuth2, resolveOAuth2Config, resolveServerHeaders, resolveServerHeadersAsync, warnIfNonTlsRemoteUrl, } from "./transport-base.js";
|
|
2
|
+
import { BaseTransport, isAuthCodeOAuth2, isDeviceCodeOAuth2, resolveOAuth2Config, resolveServerHeaders, resolveServerHeadersAsync, warnIfNonTlsRemoteUrl, } from "./transport-base.js";
|
|
3
3
|
export class SseTransport extends BaseTransport {
|
|
4
4
|
endpointUrl = null;
|
|
5
5
|
sseAbortController = null;
|
|
@@ -65,7 +65,8 @@ export class SseTransport extends BaseTransport {
|
|
|
65
65
|
if (this.config.auth?.type !== "oauth2") {
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
|
-
|
|
68
|
+
// Auth code and device code flows use the token store, not the in-memory cache
|
|
69
|
+
if (isAuthCodeOAuth2(this.config.auth) || isDeviceCodeOAuth2(this.config.auth)) {
|
|
69
70
|
return;
|
|
70
71
|
}
|
|
71
72
|
const oauth2Config = resolveOAuth2Config(this.config, undefined, this.clientConfig.envFallback);
|
|
@@ -111,7 +112,7 @@ export class SseTransport extends BaseTransport {
|
|
|
111
112
|
if (done)
|
|
112
113
|
break;
|
|
113
114
|
buffer += decoder.decode(value, { stream: true });
|
|
114
|
-
const lines = buffer.split(
|
|
115
|
+
const lines = buffer.split(/\r?\n/);
|
|
115
116
|
buffer = lines.pop() || "";
|
|
116
117
|
for (const line of lines) {
|
|
117
118
|
this.processEventLine(line, state);
|
|
@@ -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);
|
|
@@ -122,7 +125,22 @@ export class StdioTransport extends BaseTransport {
|
|
|
122
125
|
cleanup();
|
|
123
126
|
reject(error);
|
|
124
127
|
};
|
|
125
|
-
const onFirstData = () =>
|
|
128
|
+
const onFirstData = (chunk) => {
|
|
129
|
+
// Validate that first data looks like JSON-RPC (starts with { or Content-Length).
|
|
130
|
+
// Some servers write banner text to stdout instead of stderr, which would
|
|
131
|
+
// cause a false-positive connect (we'd think the transport is ready).
|
|
132
|
+
const text = chunk.toString().trim();
|
|
133
|
+
// Accept empty/whitespace readiness signals (common in lightweight stdio MCP servers),
|
|
134
|
+
// JSON messages, or LSP framing headers.
|
|
135
|
+
if (text === "" || text.startsWith("{") || text.startsWith("Content-Length")) {
|
|
136
|
+
settleResolve();
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
this.logger.warn(`[mcp-bridge] Stdio process sent non-JSON data on stdout: ${text.substring(0, 80)}`);
|
|
140
|
+
// Still listen for valid data — don't reject yet, the next chunk might be valid
|
|
141
|
+
this.process?.stdout?.once("data", onFirstData);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
126
144
|
const onProcessError = (error) => settleReject(error);
|
|
127
145
|
const onProcessExit = () => settleReject(new Error("MCP server exited before stdout became ready"));
|
|
128
146
|
this.process.stdout.once("data", onFirstData);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OAuth2TokenManager } from "./oauth2-token-manager.js";
|
|
2
|
-
import { BaseTransport, isAuthCodeOAuth2, resolveOAuth2Config, resolveServerHeaders, resolveServerHeadersAsync, warnIfNonTlsRemoteUrl, } from "./transport-base.js";
|
|
2
|
+
import { BaseTransport, isAuthCodeOAuth2, isDeviceCodeOAuth2, resolveOAuth2Config, resolveServerHeaders, resolveServerHeadersAsync, warnIfNonTlsRemoteUrl, } from "./transport-base.js";
|
|
3
3
|
export class StreamableHttpTransport extends BaseTransport {
|
|
4
4
|
sessionId;
|
|
5
5
|
resolvedHeaders = null;
|
|
@@ -48,8 +48,8 @@ export class StreamableHttpTransport extends BaseTransport {
|
|
|
48
48
|
if (this.config.auth?.type !== "oauth2") {
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
|
-
//
|
|
52
|
-
if (isAuthCodeOAuth2(this.config.auth)) {
|
|
51
|
+
// Auth code and device code tokens are managed via TokenStore, not the in-memory cache
|
|
52
|
+
if (isAuthCodeOAuth2(this.config.auth) || isDeviceCodeOAuth2(this.config.auth)) {
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
55
|
const oauth2Config = resolveOAuth2Config(this.config, undefined, this.clientConfig.envFallback);
|
|
@@ -146,7 +146,7 @@ export class StreamableHttpTransport extends BaseTransport {
|
|
|
146
146
|
if (done)
|
|
147
147
|
break;
|
|
148
148
|
partial += decoder.decode(value, { stream: true });
|
|
149
|
-
const lines = partial.split(
|
|
149
|
+
const lines = partial.split(/\r?\n/);
|
|
150
150
|
// Keep the last (potentially incomplete) line in partial
|
|
151
151
|
partial = lines.pop() || "";
|
|
152
152
|
for (const line of lines) {
|