@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.
- package/README.md +49 -0
- package/dist/src/config.js +1 -0
- package/dist/src/index.d.ts +6 -2
- package/dist/src/index.js +4 -1
- package/dist/src/mcp-router.d.ts +32 -0
- package/dist/src/mcp-router.js +228 -22
- package/dist/src/result-cache.d.ts +29 -0
- package/dist/src/result-cache.js +120 -0
- package/dist/src/standalone-server.js +22 -6
- package/dist/src/tool-resolution.d.ts +33 -0
- package/dist/src/tool-resolution.js +135 -0
- package/dist/src/transport-base.d.ts +8 -0
- package/dist/src/transport-base.js +20 -0
- package/dist/src/transport-sse.d.ts +2 -0
- package/dist/src/transport-sse.js +21 -6
- package/dist/src/transport-stdio.d.ts +1 -0
- package/dist/src/transport-stdio.js +20 -8
- package/dist/src/transport-streamable-http.d.ts +3 -0
- package/dist/src/transport-streamable-http.js +32 -14
- package/dist/src/types.d.ts +25 -0
- package/package.json +1 -1
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { nextRequestId } from "./types.js";
|
|
2
|
-
import { BaseTransport,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
|
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
|
-
}
|
|
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
|
|
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
|
|
169
|
-
headers
|
|
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
|
}
|
package/dist/src/types.d.ts
CHANGED
|
@@ -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;
|