@aiwerk/mcp-bridge 1.8.0 → 2.0.0

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.
@@ -1,15 +1,17 @@
1
1
  import { nextRequestId } from "./types.js";
2
- import { BaseTransport, resolveEnvRecord, warnIfNonTlsRemoteUrl } from "./transport-base.js";
2
+ import { BaseTransport, resolveServerHeaders, warnIfNonTlsRemoteUrl } from "./transport-base.js";
3
3
  export class StreamableHttpTransport extends BaseTransport {
4
4
  sessionId;
5
+ resolvedHeaders = null;
6
+ pendingRequestControllers = new Map();
5
7
  get transportName() { return "streamable-http"; }
6
8
  async connect() {
7
9
  if (!this.config.url) {
8
10
  throw new Error("Streamable HTTP transport requires URL");
9
11
  }
10
12
  warnIfNonTlsRemoteUrl(this.config.url, this.logger);
11
- // Validate that all header env vars resolve (fail fast)
12
- resolveEnvRecord(this.config.headers || {}, "header");
13
+ // Validate that all header/auth env vars resolve (fail fast)
14
+ this.resolvedHeaders = resolveServerHeaders(this.config);
13
15
  await this.probeServer();
14
16
  this.connected = true;
15
17
  this.backoffDelay = this.clientConfig.reconnectIntervalMs || 30000;
@@ -23,25 +25,32 @@ export class StreamableHttpTransport extends BaseTransport {
23
25
  const requestWithId = { ...request, id };
24
26
  return new Promise((resolve, reject) => {
25
27
  const requestTimeout = this.clientConfig.requestTimeoutMs || 60000;
28
+ const abortController = new AbortController();
26
29
  const timeout = setTimeout(() => {
30
+ abortController.abort();
31
+ this.pendingRequestControllers.delete(id);
27
32
  this.pendingRequests.delete(id);
28
33
  reject(new Error(`Request timeout after ${requestTimeout}ms`));
29
34
  }, requestTimeout);
30
35
  this.pendingRequests.set(id, { resolve, reject, timeout });
31
- const headers = resolveEnvRecord({
36
+ this.pendingRequestControllers.set(id, abortController);
37
+ const base = this.resolvedHeaders ?? resolveServerHeaders(this.config);
38
+ const headers = {
39
+ ...base,
32
40
  "Accept": "application/json, text/event-stream",
33
- ...this.config.headers,
34
41
  "Content-Type": "application/json"
35
- }, "header");
42
+ };
36
43
  if (this.sessionId) {
37
44
  headers["mcp-session-id"] = this.sessionId;
38
45
  }
39
46
  fetch(this.config.url, {
40
47
  method: "POST",
41
48
  headers,
42
- body: JSON.stringify(requestWithId)
49
+ body: JSON.stringify(requestWithId),
50
+ signal: abortController.signal
43
51
  })
44
52
  .then(async (response) => {
53
+ this.pendingRequestControllers.delete(id);
45
54
  const responseSessionId = response.headers.get("mcp-session-id");
46
55
  if (responseSessionId) {
47
56
  this.sessionId = responseSessionId;
@@ -98,6 +107,7 @@ export class StreamableHttpTransport extends BaseTransport {
98
107
  }
99
108
  })
100
109
  .catch(error => {
110
+ this.pendingRequestControllers.delete(id);
101
111
  clearTimeout(timeout);
102
112
  this.pendingRequests.delete(id);
103
113
  if (error.name === 'TypeError' && error.message.includes('fetch')) {
@@ -112,11 +122,12 @@ export class StreamableHttpTransport extends BaseTransport {
112
122
  if (!this.connected || !this.config.url) {
113
123
  throw new Error("Streamable HTTP transport not connected");
114
124
  }
115
- const headers = resolveEnvRecord({
125
+ const base = this.resolvedHeaders ?? resolveServerHeaders(this.config);
126
+ const headers = {
127
+ ...base,
116
128
  "Accept": "application/json, text/event-stream",
117
- ...this.config.headers,
118
129
  "Content-Type": "application/json"
119
- }, "header");
130
+ };
120
131
  if (this.sessionId) {
121
132
  headers["mcp-session-id"] = this.sessionId;
122
133
  }
@@ -146,10 +157,10 @@ export class StreamableHttpTransport extends BaseTransport {
146
157
  if (!this.config.url)
147
158
  return;
148
159
  try {
149
- const optionsResponse = await fetch(this.config.url, { method: "OPTIONS" });
160
+ const headers = this.resolvedHeaders ?? resolveServerHeaders(this.config);
161
+ const optionsResponse = await fetch(this.config.url, { method: "OPTIONS", headers });
150
162
  if (optionsResponse.ok)
151
163
  return;
152
- const headers = resolveEnvRecord(this.config.headers || {}, "header");
153
164
  const headResponse = await fetch(this.config.url, { method: "HEAD", headers });
154
165
  if (!headResponse.ok) {
155
166
  this.logger.warn(`[mcp-bridge] Streamable HTTP server probe: OPTIONS ${optionsResponse.status}, HEAD ${headResponse.status} (non-blocking, connection continues)`);
@@ -162,11 +173,15 @@ export class StreamableHttpTransport extends BaseTransport {
162
173
  async disconnect() {
163
174
  this.connected = false;
164
175
  this.cleanupReconnectTimer();
176
+ for (const [, controller] of this.pendingRequestControllers) {
177
+ controller.abort();
178
+ }
179
+ this.pendingRequestControllers.clear();
165
180
  // Send DELETE request if we have a session to clean up
166
181
  if (this.sessionId && this.config.url) {
167
182
  try {
168
- const headers = resolveEnvRecord(this.config.headers || {}, "header");
169
- headers["mcp-session-id"] = this.sessionId;
183
+ const base = this.resolvedHeaders ?? resolveServerHeaders(this.config);
184
+ const headers = { ...base, "mcp-session-id": this.sessionId };
170
185
  await fetch(this.config.url, {
171
186
  method: "DELETE",
172
187
  headers
@@ -180,4 +195,7 @@ export class StreamableHttpTransport extends BaseTransport {
180
195
  }
181
196
  this.rejectAllPending("Connection closed");
182
197
  }
198
+ async shutdown() {
199
+ await this.disconnect();
200
+ }
183
201
  }
@@ -4,12 +4,26 @@ export interface Logger {
4
4
  error: (...args: unknown[]) => void;
5
5
  debug: (...args: unknown[]) => void;
6
6
  }
7
+ export type HttpAuthConfig = {
8
+ type: "bearer";
9
+ token: string;
10
+ } | {
11
+ type: "header";
12
+ headers: Record<string, string>;
13
+ };
14
+ export interface RetryConfig {
15
+ maxAttempts?: number;
16
+ delayMs?: number;
17
+ backoffMultiplier?: number;
18
+ retryOn?: Array<"timeout" | "connection_error">;
19
+ }
7
20
  export interface McpServerConfig {
8
21
  transport: "sse" | "stdio" | "streamable-http";
9
22
  /** Human-readable description for router tool description generation */
10
23
  description?: string;
11
24
  url?: string;
12
25
  headers?: Record<string, string>;
26
+ auth?: HttpAuthConfig;
13
27
  command?: string;
14
28
  args?: string[];
15
29
  env?: Record<string, string>;
@@ -20,6 +34,7 @@ export interface McpServerConfig {
20
34
  allow?: string[];
21
35
  };
22
36
  maxResultChars?: number;
37
+ retry?: RetryConfig;
23
38
  }
24
39
  export interface McpClientConfig {
25
40
  servers: Record<string, McpServerConfig>;
@@ -28,8 +43,10 @@ export interface McpClientConfig {
28
43
  reconnectIntervalMs?: number;
29
44
  connectionTimeoutMs?: number;
30
45
  requestTimeoutMs?: number;
46
+ shutdownTimeoutMs?: number;
31
47
  routerIdleTimeoutMs?: number;
32
48
  routerMaxConcurrent?: number;
49
+ maxBatchSize?: number;
33
50
  schemaCompression?: {
34
51
  enabled?: boolean;
35
52
  maxDescriptionLength?: number;
@@ -48,6 +65,13 @@ export interface McpClientConfig {
48
65
  minCalls?: number;
49
66
  decayMs?: number;
50
67
  };
68
+ retry?: RetryConfig;
69
+ resultCache?: {
70
+ enabled?: boolean;
71
+ maxEntries?: number;
72
+ defaultTtlMs?: number;
73
+ cacheTtl?: Record<string, number>;
74
+ };
51
75
  }
52
76
  export interface McpTool {
53
77
  name: string;
@@ -91,6 +115,7 @@ export interface McpResponse {
91
115
  export interface McpTransport {
92
116
  connect(): Promise<void>;
93
117
  disconnect(): Promise<void>;
118
+ shutdown?(timeoutMs?: number): Promise<void>;
94
119
  sendRequest(request: McpRequest): Promise<McpResponse>;
95
120
  sendNotification(notification: any): Promise<void>;
96
121
  isConnected(): boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiwerk/mcp-bridge",
3
- "version": "1.8.0",
3
+ "version": "2.0.0",
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",