@aiwerk/mcp-bridge 1.7.0 → 1.7.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.
@@ -153,7 +153,7 @@ All logs go to stderr. Stdout is reserved for the MCP protocol (stdio mode).
153
153
  function cmdInit(logger) {
154
154
  initConfigDir(logger);
155
155
  }
156
- function cmdCatalog(logger, offline) {
156
+ function cmdCatalog(logger) {
157
157
  const catalogPath = join(PACKAGE_ROOT, "servers", "index.json");
158
158
  if (!existsSync(catalogPath)) {
159
159
  logger.error("Server catalog not found");
@@ -303,7 +303,7 @@ async function main() {
303
303
  cmdInit(logger);
304
304
  break;
305
305
  case "catalog":
306
- cmdCatalog(logger, args.offline);
306
+ cmdCatalog(logger);
307
307
  break;
308
308
  case "servers":
309
309
  cmdServers(logger, args.configPath);
@@ -108,7 +108,11 @@ export function loadConfig(options = {}) {
108
108
  }
109
109
  // Read and parse config
110
110
  const rawConfig = JSON.parse(readFileSync(configPath, "utf-8"));
111
- // Resolve ${VAR} placeholders using .env + process.env
111
+ // Merge order: .env file values take priority over process.env for config resolution.
112
+ // This is intentional: .env is the user-controlled secrets file, process.env may have
113
+ // stale or system-level values. Note: dotenv loads .env INTO process.env without
114
+ // overwriting (opposite direction), but our config resolver uses this merged map
115
+ // where .env wins.
112
116
  const mergedEnv = { ...dotEnv };
113
117
  for (const [k, v] of Object.entries(process.env)) {
114
118
  if (mergedEnv[k] === undefined)
@@ -84,7 +84,13 @@ export class OpenAIEmbedding {
84
84
  .map((item) => item.embedding);
85
85
  }
86
86
  dimensions() {
87
- return 1536;
87
+ if (this.model.includes("3-large"))
88
+ return 3072;
89
+ if (this.model.includes("3-small"))
90
+ return 1536;
91
+ if (this.model.includes("ada-002"))
92
+ return 1536;
93
+ return 1536; // safe default
88
94
  }
89
95
  }
90
96
  export class OllamaEmbedding {
@@ -81,7 +81,7 @@ export declare class McpRouter {
81
81
  private readonly maxConcurrent;
82
82
  private readonly states;
83
83
  private intentRouter;
84
- private readonly promotion;
84
+ private promotion;
85
85
  constructor(servers: Record<string, McpServerConfig>, clientConfig: McpClientConfig, logger: Logger, transportRefs?: Partial<RouterTransportRefs>);
86
86
  static generateDescription(servers: Record<string, McpServerConfig>): string;
87
87
  dispatch(server?: string, action?: string, tool?: string, params?: any): Promise<RouterDispatchResponse>;
@@ -23,6 +23,7 @@ export declare function applyMaxResultSize(result: any, serverConfig: McpServerC
23
23
  */
24
24
  export declare function applyTrustLevel(result: any, serverName: string, serverConfig: McpServerConfig): any;
25
25
  /**
26
- * Full security pipeline: truncate → sanitize trust-tag
26
+ * Full security pipeline: truncate → trust-tag (which includes sanitize for trust="sanitize")
27
+ * Note: sanitization only runs when trust="sanitize". trust="untrusted" wraps without sanitizing.
27
28
  */
28
29
  export declare function processResult(result: any, serverName: string, serverConfig: McpServerConfig, clientConfig: McpClientConfig): any;
@@ -117,7 +117,8 @@ export function applyTrustLevel(result, serverName, serverConfig) {
117
117
  }
118
118
  }
119
119
  /**
120
- * Full security pipeline: truncate → sanitize trust-tag
120
+ * Full security pipeline: truncate → trust-tag (which includes sanitize for trust="sanitize")
121
+ * Note: sanitization only runs when trust="sanitize". trust="untrusted" wraps without sanitizing.
121
122
  */
122
123
  export function processResult(result, serverName, serverConfig, clientConfig) {
123
124
  let processed = applyMaxResultSize(result, serverConfig, clientConfig);
@@ -9,6 +9,7 @@ export declare class StandaloneServer {
9
9
  private logger;
10
10
  private router;
11
11
  private initialized;
12
+ private lspMode;
12
13
  private directTools;
13
14
  private directConnections;
14
15
  constructor(config: BridgeConfig, logger: Logger);
@@ -14,6 +14,7 @@ export class StandaloneServer {
14
14
  logger;
15
15
  router = null;
16
16
  initialized = false;
17
+ lspMode = false;
17
18
  // Direct mode state
18
19
  directTools = [];
19
20
  directConnections = new Map();
@@ -45,9 +46,12 @@ export class StandaloneServer {
45
46
  progress = false;
46
47
  // If we're reading an LSP body, check if we have enough bytes
47
48
  if (lspContentLength >= 0 && lspHeadersDone) {
48
- if (buffer.length >= lspContentLength) {
49
- const body = buffer.slice(0, lspContentLength);
50
- buffer = buffer.slice(lspContentLength);
49
+ const bufferBytes = Buffer.byteLength(buffer, "utf8");
50
+ if (bufferBytes >= lspContentLength) {
51
+ // Extract exactly lspContentLength bytes (LSP spec defines Content-Length in bytes)
52
+ const bodyBuffer = Buffer.from(buffer, "utf8").slice(0, lspContentLength);
53
+ const body = bodyBuffer.toString("utf8");
54
+ buffer = buffer.substring(body.length);
51
55
  lspContentLength = -1;
52
56
  lspHeadersDone = false;
53
57
  const trimmed = body.trim();
@@ -80,6 +84,7 @@ export class StandaloneServer {
80
84
  }
81
85
  if (trimmed.startsWith("Content-Length:")) {
82
86
  // Start of LSP-framed message
87
+ this.lspMode = true;
83
88
  const lengthStr = trimmed.slice("Content-Length:".length).trim();
84
89
  const length = parseInt(lengthStr, 10);
85
90
  if (!isNaN(length) && length > 0) {
@@ -135,7 +140,14 @@ export class StandaloneServer {
135
140
  });
136
141
  }
137
142
  writeResponse(stdout, response) {
138
- stdout.write(JSON.stringify(response) + "\n");
143
+ const json = JSON.stringify(response);
144
+ if (this.lspMode) {
145
+ const byteLength = Buffer.byteLength(json, "utf8");
146
+ stdout.write(`Content-Length: ${byteLength}\r\n\r\n${json}`);
147
+ }
148
+ else {
149
+ stdout.write(json + "\n");
150
+ }
139
151
  }
140
152
  /** Handle a single MCP JSON-RPC request. */
141
153
  async handleRequest(request) {
@@ -3,7 +3,7 @@ import { BaseTransport } from "./transport-base.js";
3
3
  export declare class SseTransport extends BaseTransport {
4
4
  private endpointUrl;
5
5
  private sseAbortController;
6
- private currentDataBuffer;
6
+ private resolvedHeaders;
7
7
  protected get transportName(): string;
8
8
  connect(): Promise<void>;
9
9
  private _onEndpointReceived;
@@ -3,15 +3,15 @@ import { BaseTransport, resolveEnvRecord, warnIfNonTlsRemoteUrl } from "./transp
3
3
  export class SseTransport extends BaseTransport {
4
4
  endpointUrl = null;
5
5
  sseAbortController = null;
6
- currentDataBuffer = [];
6
+ resolvedHeaders = null;
7
7
  get transportName() { return "SSE"; }
8
8
  async connect() {
9
9
  if (!this.config.url) {
10
10
  throw new Error("SSE transport requires URL");
11
11
  }
12
12
  warnIfNonTlsRemoteUrl(this.config.url, this.logger);
13
- // Validate that all header env vars resolve (fail fast)
14
- resolveEnvRecord(this.config.headers || {}, "header");
13
+ // Resolve headers once and cache for all subsequent requests
14
+ this.resolvedHeaders = resolveEnvRecord(this.config.headers || {}, "header");
15
15
  if (this.sseAbortController) {
16
16
  this.sseAbortController.abort();
17
17
  }
@@ -36,10 +36,8 @@ export class SseTransport extends BaseTransport {
36
36
  async startEventStream() {
37
37
  if (!this.config.url)
38
38
  return;
39
- const headers = resolveEnvRecord({
40
- ...this.config.headers,
41
- "Accept": "text/event-stream"
42
- }, "header");
39
+ const base = this.resolvedHeaders ?? resolveEnvRecord(this.config.headers || {}, "header");
40
+ const headers = { ...base, "Accept": "text/event-stream" };
43
41
  try {
44
42
  const response = await fetch(this.config.url, {
45
43
  method: "GET",
@@ -132,10 +130,8 @@ export class SseTransport extends BaseTransport {
132
130
  if (!this.connected || !this.endpointUrl) {
133
131
  throw new Error("SSE transport not connected or no endpoint URL");
134
132
  }
135
- const headers = resolveEnvRecord({
136
- ...this.config.headers,
137
- "Content-Type": "application/json"
138
- }, "header");
133
+ const base = this.resolvedHeaders ?? resolveEnvRecord(this.config.headers || {}, "header");
134
+ const headers = { ...base, "Content-Type": "application/json" };
139
135
  const response = await fetch(this.endpointUrl, {
140
136
  method: "POST",
141
137
  headers,
@@ -158,10 +154,8 @@ export class SseTransport extends BaseTransport {
158
154
  reject(new Error(`Request timeout after ${requestTimeout}ms`));
159
155
  }, requestTimeout);
160
156
  this.pendingRequests.set(id, { resolve, reject, timeout });
161
- const headers = resolveEnvRecord({
162
- ...this.config.headers,
163
- "Content-Type": "application/json"
164
- }, "header");
157
+ const base = this.resolvedHeaders ?? resolveEnvRecord(this.config.headers || {}, "header");
158
+ const headers = { ...base, "Content-Type": "application/json" };
165
159
  // The response arrives via the SSE stream (handleMessage), not from this fetch.
166
160
  // The fetch only confirms the server accepted the request (HTTP 200).
167
161
  // If the fetch fails, we reject immediately; otherwise we wait for the SSE stream.
package/dist/src/types.js CHANGED
@@ -1,5 +1,8 @@
1
- let globalRequestId = 1;
1
+ let globalRequestId = 0;
2
2
  export function nextRequestId() {
3
- globalRequestId = (globalRequestId + 1) % Number.MAX_SAFE_INTEGER;
3
+ globalRequestId++;
4
+ if (globalRequestId >= Number.MAX_SAFE_INTEGER) {
5
+ globalRequestId = 1;
6
+ }
4
7
  return globalRequestId;
5
8
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiwerk/mcp-bridge",
3
- "version": "1.7.0",
3
+ "version": "1.7.2",
4
4
  "description": "Standalone MCP server that multiplexes multiple MCP servers into one interface",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",