@winstonsayno/mcp-gateway 0.0.0-stage → 0.3.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/CHANGELOG.md +124 -0
- package/LICENSE +21 -0
- package/README.md +377 -2
- package/dashboard/README.md +27 -0
- package/dashboard/index.html +415 -0
- package/dist/auth/middleware.d.ts +24 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +108 -0
- package/dist/auth/middleware.js.map +1 -0
- package/dist/auth/ratelimit.d.ts +19 -0
- package/dist/auth/ratelimit.d.ts.map +1 -0
- package/dist/auth/ratelimit.js +81 -0
- package/dist/auth/ratelimit.js.map +1 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +129 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/loader.d.ts +13 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +268 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/watcher.d.ts +29 -0
- package/dist/config/watcher.d.ts.map +1 -0
- package/dist/config/watcher.js +96 -0
- package/dist/config/watcher.js.map +1 -0
- package/dist/gateway/api.d.ts +29 -0
- package/dist/gateway/api.d.ts.map +1 -0
- package/dist/gateway/api.js +280 -0
- package/dist/gateway/api.js.map +1 -0
- package/dist/gateway/index.d.ts +49 -0
- package/dist/gateway/index.d.ts.map +1 -0
- package/dist/gateway/index.js +260 -0
- package/dist/gateway/index.js.map +1 -0
- package/dist/gateway/supervisor.d.ts +58 -0
- package/dist/gateway/supervisor.d.ts.map +1 -0
- package/dist/gateway/supervisor.js +205 -0
- package/dist/gateway/supervisor.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/cors.d.ts +19 -0
- package/dist/middleware/cors.d.ts.map +1 -0
- package/dist/middleware/cors.js +65 -0
- package/dist/middleware/cors.js.map +1 -0
- package/dist/middleware/error-handler.d.ts +40 -0
- package/dist/middleware/error-handler.d.ts.map +1 -0
- package/dist/middleware/error-handler.js +98 -0
- package/dist/middleware/error-handler.js.map +1 -0
- package/dist/middleware/request-id.d.ts +23 -0
- package/dist/middleware/request-id.d.ts.map +1 -0
- package/dist/middleware/request-id.js +30 -0
- package/dist/middleware/request-id.js.map +1 -0
- package/dist/middleware/timeout.d.ts +12 -0
- package/dist/middleware/timeout.d.ts.map +1 -0
- package/dist/middleware/timeout.js +39 -0
- package/dist/middleware/timeout.js.map +1 -0
- package/dist/monitor/index.d.ts +49 -0
- package/dist/monitor/index.d.ts.map +1 -0
- package/dist/monitor/index.js +204 -0
- package/dist/monitor/index.js.map +1 -0
- package/dist/proxy/index.d.ts +86 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/index.js +389 -0
- package/dist/proxy/index.js.map +1 -0
- package/dist/registry/index.d.ts +41 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +157 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/transport/channel.d.ts +61 -0
- package/dist/transport/channel.d.ts.map +1 -0
- package/dist/transport/channel.js +38 -0
- package/dist/transport/channel.js.map +1 -0
- package/dist/transport/sse-parser.d.ts +30 -0
- package/dist/transport/sse-parser.d.ts.map +1 -0
- package/dist/transport/sse-parser.js +107 -0
- package/dist/transport/sse-parser.js.map +1 -0
- package/dist/transport/sse.d.ts +39 -0
- package/dist/transport/sse.d.ts.map +1 -0
- package/dist/transport/sse.js +142 -0
- package/dist/transport/sse.js.map +1 -0
- package/dist/transport/stdio.d.ts +35 -0
- package/dist/transport/stdio.d.ts.map +1 -0
- package/dist/transport/stdio.js +149 -0
- package/dist/transport/stdio.js.map +1 -0
- package/dist/transport/streamable-http.d.ts +45 -0
- package/dist/transport/streamable-http.d.ts.map +1 -0
- package/dist/transport/streamable-http.js +183 -0
- package/dist/transport/streamable-http.js.map +1 -0
- package/dist/transport/websocket.d.ts +38 -0
- package/dist/transport/websocket.d.ts.map +1 -0
- package/dist/transport/websocket.js +172 -0
- package/dist/transport/websocket.js.map +1 -0
- package/dist/utils/logger.d.ts +16 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +60 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/mutex.d.ts +25 -0
- package/dist/utils/mutex.d.ts.map +1 -0
- package/dist/utils/mutex.js +62 -0
- package/dist/utils/mutex.js.map +1 -0
- package/dist/utils/semaphore.d.ts +16 -0
- package/dist/utils/semaphore.d.ts.map +1 -0
- package/dist/utils/semaphore.js +42 -0
- package/dist/utils/semaphore.js.map +1 -0
- package/dist/utils/types.d.ts +215 -0
- package/dist/utils/types.d.ts.map +1 -0
- package/dist/utils/types.js +5 -0
- package/dist/utils/types.js.map +1 -0
- package/dist/utils/version.d.ts +6 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +18 -0
- package/dist/utils/version.js.map +1 -0
- package/package.json +78 -4
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streamable HTTP channel — the current MCP HTTP transport (protocol 2025-03-26+).
|
|
3
|
+
*
|
|
4
|
+
* - Every client→server message is a `POST <url>` with
|
|
5
|
+
* `Accept: application/json, text/event-stream`.
|
|
6
|
+
* - The server answers `202 Accepted` (notifications / responses), a single
|
|
7
|
+
* `application/json` body, or a `text/event-stream` that carries the
|
|
8
|
+
* response (plus any server requests/notifications sent before it).
|
|
9
|
+
* - A session id returned in `Mcp-Session-Id` on `initialize` is echoed on
|
|
10
|
+
* every later request; the negotiated version goes in `MCP-Protocol-Version`.
|
|
11
|
+
* - `close()` ends the session with `DELETE` (best effort).
|
|
12
|
+
*
|
|
13
|
+
* Not implemented: the optional standalone `GET` stream for unsolicited server
|
|
14
|
+
* notifications, and SSE stream resumption. Neither is needed for tool calls.
|
|
15
|
+
*
|
|
16
|
+
* @module transport/streamable-http
|
|
17
|
+
*/
|
|
18
|
+
import type { McpServerConfig } from '../utils/types.js';
|
|
19
|
+
import { type ChannelOptions, type JsonRpcId, type JsonRpcMessage, type UpstreamChannel } from './channel.js';
|
|
20
|
+
export declare class StreamableHttpChannel implements UpstreamChannel {
|
|
21
|
+
private readonly config;
|
|
22
|
+
private readonly options;
|
|
23
|
+
readonly kind = "streamable-http";
|
|
24
|
+
onmessage?: (message: JsonRpcMessage) => void;
|
|
25
|
+
onclose?: (error: Error) => void;
|
|
26
|
+
private readonly url;
|
|
27
|
+
private readonly headers;
|
|
28
|
+
private sessionId?;
|
|
29
|
+
private protocolVersion?;
|
|
30
|
+
private closed;
|
|
31
|
+
private lost;
|
|
32
|
+
/** Open POST requests, keyed by JSON-RPC request id when they carry one. */
|
|
33
|
+
private readonly inflight;
|
|
34
|
+
constructor(config: McpServerConfig, options: ChannelOptions);
|
|
35
|
+
start(): Promise<void>;
|
|
36
|
+
setProtocolVersion(version: string): void;
|
|
37
|
+
private requestHeaders;
|
|
38
|
+
send(message: JsonRpcMessage): Promise<void>;
|
|
39
|
+
private deliver;
|
|
40
|
+
abandon(id: JsonRpcId): void;
|
|
41
|
+
private lose;
|
|
42
|
+
private abortAll;
|
|
43
|
+
close(): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=streamable-http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streamable-http.d.ts","sourceRoot":"","sources":["../../src/transport/streamable-http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,eAAe,EACrB,MAAM,cAAc,CAAC;AAGtB,qBAAa,qBAAsB,YAAW,eAAe;IAezD,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAf1B,QAAQ,CAAC,IAAI,qBAAqB;IAClC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;IAC9C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAEjC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAM;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;IACjD,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,eAAe,CAAC,CAAS;IACjC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,IAAI,CAAS;IACrB,4EAA4E;IAC5E,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkD;gBAGxD,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,cAAc;IAOpC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAIzC,OAAO,CAAC,cAAc;IAOhB,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IA2ElD,OAAO,CAAC,OAAO;IAWf,OAAO,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAK5B,OAAO,CAAC,IAAI;IAOZ,OAAO,CAAC,QAAQ;IAKV,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAkB7B"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streamable HTTP channel — the current MCP HTTP transport (protocol 2025-03-26+).
|
|
3
|
+
*
|
|
4
|
+
* - Every client→server message is a `POST <url>` with
|
|
5
|
+
* `Accept: application/json, text/event-stream`.
|
|
6
|
+
* - The server answers `202 Accepted` (notifications / responses), a single
|
|
7
|
+
* `application/json` body, or a `text/event-stream` that carries the
|
|
8
|
+
* response (plus any server requests/notifications sent before it).
|
|
9
|
+
* - A session id returned in `Mcp-Session-Id` on `initialize` is echoed on
|
|
10
|
+
* every later request; the negotiated version goes in `MCP-Protocol-Version`.
|
|
11
|
+
* - `close()` ends the session with `DELETE` (best effort).
|
|
12
|
+
*
|
|
13
|
+
* Not implemented: the optional standalone `GET` stream for unsolicited server
|
|
14
|
+
* notifications, and SSE stream resumption. Neither is needed for tool calls.
|
|
15
|
+
*
|
|
16
|
+
* @module transport/streamable-http
|
|
17
|
+
*/
|
|
18
|
+
import { logger } from '../utils/logger.js';
|
|
19
|
+
import { errMessage, expandRecord, toMessages, } from './channel.js';
|
|
20
|
+
import { readSseStream } from './sse-parser.js';
|
|
21
|
+
export class StreamableHttpChannel {
|
|
22
|
+
config;
|
|
23
|
+
options;
|
|
24
|
+
kind = 'streamable-http';
|
|
25
|
+
onmessage;
|
|
26
|
+
onclose;
|
|
27
|
+
url;
|
|
28
|
+
headers;
|
|
29
|
+
sessionId;
|
|
30
|
+
protocolVersion;
|
|
31
|
+
closed = false;
|
|
32
|
+
lost = false;
|
|
33
|
+
/** Open POST requests, keyed by JSON-RPC request id when they carry one. */
|
|
34
|
+
inflight = new Map();
|
|
35
|
+
constructor(config, options) {
|
|
36
|
+
this.config = config;
|
|
37
|
+
this.options = options;
|
|
38
|
+
if (!config.url)
|
|
39
|
+
throw new Error(`Server "${config.id}" has no url configured`);
|
|
40
|
+
this.url = new URL(config.url);
|
|
41
|
+
this.headers = expandRecord(config.headers);
|
|
42
|
+
}
|
|
43
|
+
async start() {
|
|
44
|
+
// Nothing to open: the session starts with the `initialize` POST.
|
|
45
|
+
}
|
|
46
|
+
setProtocolVersion(version) {
|
|
47
|
+
this.protocolVersion = version;
|
|
48
|
+
}
|
|
49
|
+
requestHeaders(extra = {}) {
|
|
50
|
+
const h = { ...this.headers, ...extra };
|
|
51
|
+
if (this.sessionId)
|
|
52
|
+
h['mcp-session-id'] = this.sessionId;
|
|
53
|
+
if (this.protocolVersion)
|
|
54
|
+
h['mcp-protocol-version'] = this.protocolVersion;
|
|
55
|
+
return h;
|
|
56
|
+
}
|
|
57
|
+
async send(message) {
|
|
58
|
+
if (this.closed || this.lost)
|
|
59
|
+
throw new Error('Streamable HTTP channel is closed');
|
|
60
|
+
const key = message.method !== undefined && message.id !== undefined && message.id !== null ? message.id : Symbol('post');
|
|
61
|
+
const abort = new AbortController();
|
|
62
|
+
this.inflight.set(key, abort);
|
|
63
|
+
const done = () => {
|
|
64
|
+
if (this.inflight.get(key) === abort)
|
|
65
|
+
this.inflight.delete(key);
|
|
66
|
+
};
|
|
67
|
+
let res;
|
|
68
|
+
try {
|
|
69
|
+
res = await fetch(this.url, {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: this.requestHeaders({
|
|
72
|
+
'Content-Type': 'application/json',
|
|
73
|
+
Accept: 'application/json, text/event-stream',
|
|
74
|
+
}),
|
|
75
|
+
body: JSON.stringify(message),
|
|
76
|
+
redirect: 'error',
|
|
77
|
+
signal: abort.signal,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
done();
|
|
82
|
+
if (abort.signal.aborted)
|
|
83
|
+
throw new Error('Request aborted');
|
|
84
|
+
// The server is unreachable: whatever session it held is gone.
|
|
85
|
+
const e = new Error(`POST ${this.url.origin}${this.url.pathname} failed: ${errMessage(err)}`);
|
|
86
|
+
this.lose(e);
|
|
87
|
+
throw e;
|
|
88
|
+
}
|
|
89
|
+
const sid = res.headers.get('mcp-session-id');
|
|
90
|
+
if (sid)
|
|
91
|
+
this.sessionId = sid;
|
|
92
|
+
if (res.status === 404 && this.sessionId && message.method !== 'initialize') {
|
|
93
|
+
done();
|
|
94
|
+
await res.body?.cancel().catch(() => { });
|
|
95
|
+
const e = new Error('MCP session expired (HTTP 404)');
|
|
96
|
+
this.lose(e);
|
|
97
|
+
throw e;
|
|
98
|
+
}
|
|
99
|
+
if (!res.ok) {
|
|
100
|
+
done();
|
|
101
|
+
const text = (await res.text().catch(() => '')).slice(0, 200);
|
|
102
|
+
throw new Error(`MCP server returned HTTP ${res.status}${text ? `: ${text}` : ''}`);
|
|
103
|
+
}
|
|
104
|
+
if (res.status === 202 || !res.body) {
|
|
105
|
+
done();
|
|
106
|
+
await res.body?.cancel().catch(() => { });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const contentType = res.headers.get('content-type') ?? '';
|
|
110
|
+
if (contentType.includes('text/event-stream')) {
|
|
111
|
+
// Deliver events as they arrive; keep the stream open in the background.
|
|
112
|
+
void readSseStream(res.body, (ev) => {
|
|
113
|
+
if (ev.event !== 'message' || !ev.data)
|
|
114
|
+
return;
|
|
115
|
+
this.deliver(ev.data);
|
|
116
|
+
})
|
|
117
|
+
.catch((err) => {
|
|
118
|
+
if (!abort.signal.aborted)
|
|
119
|
+
logger.debug(`[${this.config.id}] response stream error: ${errMessage(err)}`);
|
|
120
|
+
})
|
|
121
|
+
.finally(done);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const text = await res.text();
|
|
126
|
+
if (text.trim())
|
|
127
|
+
this.deliver(text);
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
done();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
deliver(text) {
|
|
134
|
+
let parsed;
|
|
135
|
+
try {
|
|
136
|
+
parsed = JSON.parse(text);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
logger.debug(`[${this.config.id}] ignoring non-JSON message`);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
for (const m of toMessages(parsed))
|
|
143
|
+
this.onmessage?.(m);
|
|
144
|
+
}
|
|
145
|
+
abandon(id) {
|
|
146
|
+
this.inflight.get(id)?.abort();
|
|
147
|
+
this.inflight.delete(id);
|
|
148
|
+
}
|
|
149
|
+
lose(err) {
|
|
150
|
+
if (this.closed || this.lost)
|
|
151
|
+
return;
|
|
152
|
+
this.lost = true;
|
|
153
|
+
this.abortAll();
|
|
154
|
+
this.onclose?.(err);
|
|
155
|
+
}
|
|
156
|
+
abortAll() {
|
|
157
|
+
for (const c of this.inflight.values())
|
|
158
|
+
c.abort();
|
|
159
|
+
this.inflight.clear();
|
|
160
|
+
}
|
|
161
|
+
async close() {
|
|
162
|
+
if (this.closed)
|
|
163
|
+
return;
|
|
164
|
+
this.closed = true;
|
|
165
|
+
this.abortAll();
|
|
166
|
+
if (!this.sessionId || this.lost)
|
|
167
|
+
return;
|
|
168
|
+
// Politely end the session; servers may answer 405 if they do not support it.
|
|
169
|
+
try {
|
|
170
|
+
const res = await fetch(this.url, {
|
|
171
|
+
method: 'DELETE',
|
|
172
|
+
headers: this.requestHeaders(),
|
|
173
|
+
redirect: 'error',
|
|
174
|
+
signal: AbortSignal.timeout(this.options.killGraceMs),
|
|
175
|
+
});
|
|
176
|
+
await res.body?.cancel().catch(() => { });
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// best effort
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=streamable-http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streamable-http.js","sourceRoot":"","sources":["../../src/transport/streamable-http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket channel — one JSON-RPC message per text frame, using the `mcp`
|
|
3
|
+
* subprotocol by default (the same convention as the official SDK's
|
|
4
|
+
* `WebSocketClientTransport`; override per server with `subprotocol`). Uses the `ws` package so custom headers work
|
|
5
|
+
* and Node 20 (no global WebSocket) is supported.
|
|
6
|
+
*
|
|
7
|
+
* Keep-alive pings detect half-open connections: if a pong does not arrive
|
|
8
|
+
* before the next ping, the socket is terminated and reported as lost.
|
|
9
|
+
* Reconnection is handled by the gateway supervisor, which re-runs the MCP
|
|
10
|
+
* handshake on a fresh socket.
|
|
11
|
+
*
|
|
12
|
+
* @module transport/websocket
|
|
13
|
+
*/
|
|
14
|
+
import type { McpServerConfig } from '../utils/types.js';
|
|
15
|
+
import { type ChannelOptions, type JsonRpcMessage, type UpstreamChannel } from './channel.js';
|
|
16
|
+
export interface WebSocketChannelOptions extends ChannelOptions {
|
|
17
|
+
pingIntervalMs?: number;
|
|
18
|
+
}
|
|
19
|
+
export declare class WebSocketChannel implements UpstreamChannel {
|
|
20
|
+
private readonly config;
|
|
21
|
+
private readonly options;
|
|
22
|
+
readonly kind = "websocket";
|
|
23
|
+
onmessage?: (message: JsonRpcMessage) => void;
|
|
24
|
+
onclose?: (error: Error) => void;
|
|
25
|
+
private ws?;
|
|
26
|
+
private closed;
|
|
27
|
+
private lost;
|
|
28
|
+
private pingTimer?;
|
|
29
|
+
private awaitingPong;
|
|
30
|
+
constructor(config: McpServerConfig, options: WebSocketChannelOptions);
|
|
31
|
+
start(): Promise<void>;
|
|
32
|
+
private lose;
|
|
33
|
+
private startPing;
|
|
34
|
+
private stopPing;
|
|
35
|
+
send(message: JsonRpcMessage): Promise<void>;
|
|
36
|
+
close(): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=websocket.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../../src/transport/websocket.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,OAAO,EAAgB,KAAK,cAAc,EAAE,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAK5G,MAAM,WAAW,uBAAwB,SAAQ,cAAc;IAC7D,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,gBAAiB,YAAW,eAAe;IAYpD,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAZ1B,QAAQ,CAAC,IAAI,eAAe;IAC5B,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;IAC9C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAEjC,OAAO,CAAC,EAAE,CAAC,CAAY;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,SAAS,CAAC,CAAiB;IACnC,OAAO,CAAC,YAAY,CAAS;gBAGV,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,uBAAuB;IAKnD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmEtB,OAAO,CAAC,IAAI;IAOZ,OAAO,CAAC,SAAS;IAqBjB,OAAO,CAAC,QAAQ;IAKhB,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAUtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAwB7B"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket channel — one JSON-RPC message per text frame, using the `mcp`
|
|
3
|
+
* subprotocol by default (the same convention as the official SDK's
|
|
4
|
+
* `WebSocketClientTransport`; override per server with `subprotocol`). Uses the `ws` package so custom headers work
|
|
5
|
+
* and Node 20 (no global WebSocket) is supported.
|
|
6
|
+
*
|
|
7
|
+
* Keep-alive pings detect half-open connections: if a pong does not arrive
|
|
8
|
+
* before the next ping, the socket is terminated and reported as lost.
|
|
9
|
+
* Reconnection is handled by the gateway supervisor, which re-runs the MCP
|
|
10
|
+
* handshake on a fresh socket.
|
|
11
|
+
*
|
|
12
|
+
* @module transport/websocket
|
|
13
|
+
*/
|
|
14
|
+
import WebSocket from 'ws';
|
|
15
|
+
import { logger } from '../utils/logger.js';
|
|
16
|
+
import { expandRecord } from './channel.js';
|
|
17
|
+
const SUBPROTOCOL = 'mcp';
|
|
18
|
+
const MAX_PAYLOAD = 16 * 1024 * 1024;
|
|
19
|
+
export class WebSocketChannel {
|
|
20
|
+
config;
|
|
21
|
+
options;
|
|
22
|
+
kind = 'websocket';
|
|
23
|
+
onmessage;
|
|
24
|
+
onclose;
|
|
25
|
+
ws;
|
|
26
|
+
closed = false;
|
|
27
|
+
lost = false;
|
|
28
|
+
pingTimer;
|
|
29
|
+
awaitingPong = false;
|
|
30
|
+
constructor(config, options) {
|
|
31
|
+
this.config = config;
|
|
32
|
+
this.options = options;
|
|
33
|
+
if (!config.url)
|
|
34
|
+
throw new Error(`Server "${config.id}" has no url configured`);
|
|
35
|
+
}
|
|
36
|
+
start() {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
let settled = false;
|
|
39
|
+
// `subprotocol: ""` connects without requesting one (for servers that reject it).
|
|
40
|
+
const sub = this.config.subprotocol ?? SUBPROTOCOL;
|
|
41
|
+
const ws = new WebSocket(this.config.url, sub ? [sub] : [], {
|
|
42
|
+
headers: expandRecord(this.config.headers),
|
|
43
|
+
handshakeTimeout: this.options.connectTimeoutMs,
|
|
44
|
+
maxPayload: MAX_PAYLOAD,
|
|
45
|
+
});
|
|
46
|
+
this.ws = ws;
|
|
47
|
+
ws.on('open', () => {
|
|
48
|
+
settled = true;
|
|
49
|
+
this.startPing();
|
|
50
|
+
resolve();
|
|
51
|
+
});
|
|
52
|
+
ws.on('unexpected-response', (_req, res) => {
|
|
53
|
+
res.resume();
|
|
54
|
+
const err = new Error(`WebSocket upgrade rejected with HTTP ${res.statusCode}`);
|
|
55
|
+
ws.terminate();
|
|
56
|
+
if (!settled) {
|
|
57
|
+
settled = true;
|
|
58
|
+
reject(err);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
ws.on('message', (raw, isBinary) => {
|
|
62
|
+
if (isBinary)
|
|
63
|
+
return;
|
|
64
|
+
let msg;
|
|
65
|
+
try {
|
|
66
|
+
msg = JSON.parse(raw.toString());
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
logger.debug(`[${this.config.id}] ignoring non-JSON WebSocket frame`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Batches are arrays of messages.
|
|
73
|
+
for (const m of Array.isArray(msg) ? msg : [msg]) {
|
|
74
|
+
if (m && typeof m === 'object')
|
|
75
|
+
this.onmessage?.(m);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
ws.on('pong', () => {
|
|
79
|
+
this.awaitingPong = false;
|
|
80
|
+
});
|
|
81
|
+
ws.on('error', (err) => {
|
|
82
|
+
logger.debug(`[${this.config.id}] WebSocket error: ${err.message}`);
|
|
83
|
+
if (!settled) {
|
|
84
|
+
settled = true;
|
|
85
|
+
reject(err);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
ws.on('close', (code, reason) => {
|
|
89
|
+
this.stopPing();
|
|
90
|
+
if (!settled) {
|
|
91
|
+
settled = true;
|
|
92
|
+
reject(new Error(`WebSocket closed during connect (code=${code})`));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
this.lose(new Error(`WebSocket closed (code=${code}, reason=${reason.toString() || 'none'})`));
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
lose(err) {
|
|
100
|
+
if (this.closed || this.lost)
|
|
101
|
+
return;
|
|
102
|
+
this.lost = true;
|
|
103
|
+
logger.warn(`[${this.config.id}] ${err.message}`);
|
|
104
|
+
this.onclose?.(err);
|
|
105
|
+
}
|
|
106
|
+
startPing() {
|
|
107
|
+
const interval = this.options.pingIntervalMs ?? 30_000;
|
|
108
|
+
if (!(interval > 0))
|
|
109
|
+
return;
|
|
110
|
+
this.pingTimer = setInterval(() => {
|
|
111
|
+
const ws = this.ws;
|
|
112
|
+
if (!ws || ws.readyState !== WebSocket.OPEN)
|
|
113
|
+
return;
|
|
114
|
+
if (this.awaitingPong) {
|
|
115
|
+
// No pong since the last ping: the connection is dead.
|
|
116
|
+
ws.terminate();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
this.awaitingPong = true;
|
|
120
|
+
try {
|
|
121
|
+
ws.ping();
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// ignore — 'close' will follow
|
|
125
|
+
}
|
|
126
|
+
}, interval);
|
|
127
|
+
this.pingTimer.unref();
|
|
128
|
+
}
|
|
129
|
+
stopPing() {
|
|
130
|
+
if (this.pingTimer)
|
|
131
|
+
clearInterval(this.pingTimer);
|
|
132
|
+
this.pingTimer = undefined;
|
|
133
|
+
}
|
|
134
|
+
send(message) {
|
|
135
|
+
const ws = this.ws;
|
|
136
|
+
if (this.closed || this.lost || !ws || ws.readyState !== WebSocket.OPEN) {
|
|
137
|
+
return Promise.reject(new Error('WebSocket channel is not connected'));
|
|
138
|
+
}
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
ws.send(JSON.stringify(message), (err) => (err ? reject(err) : resolve()));
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
async close() {
|
|
144
|
+
if (this.closed)
|
|
145
|
+
return;
|
|
146
|
+
this.closed = true;
|
|
147
|
+
this.stopPing();
|
|
148
|
+
const ws = this.ws;
|
|
149
|
+
if (!ws)
|
|
150
|
+
return;
|
|
151
|
+
if (ws.readyState === WebSocket.CLOSED)
|
|
152
|
+
return;
|
|
153
|
+
await new Promise((resolve) => {
|
|
154
|
+
const force = setTimeout(() => {
|
|
155
|
+
ws.terminate();
|
|
156
|
+
resolve();
|
|
157
|
+
}, this.options.killGraceMs);
|
|
158
|
+
force.unref();
|
|
159
|
+
ws.once('close', () => {
|
|
160
|
+
clearTimeout(force);
|
|
161
|
+
resolve();
|
|
162
|
+
});
|
|
163
|
+
try {
|
|
164
|
+
ws.close(1000, 'Client disconnect');
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
ws.terminate();
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=websocket.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket.js","sourceRoot":"","sources":["../../src/transport/websocket.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,SAAS,MAAM,IAAI,CAAC;AAE3B,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAkE,MAAM,cAAc,CAAC;AAE5G,MAAM,WAAW,GAAG,KAAK,CAAC;AAC1B,MAAM,WAAW,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAMrC,MAAM,OAAO,gBAAgB;IAYR;IACA;IAZV,IAAI,GAAG,WAAW,CAAC;IAC5B,SAAS,CAAqC;IAC9C,OAAO,CAA0B;IAEzB,EAAE,CAAa;IACf,MAAM,GAAG,KAAK,CAAC;IACf,IAAI,GAAG,KAAK,CAAC;IACb,SAAS,CAAkB;IAC3B,YAAY,GAAG,KAAK,CAAC;IAE7B,YACmB,MAAuB,EACvB,OAAgC;QADhC,WAAM,GAAN,MAAM,CAAiB;QACvB,YAAO,GAAP,OAAO,CAAyB;QAEjD,IAAI,CAAC,MAAM,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,WAAW,MAAM,CAAC,EAAE,yBAAyB,CAAC,CAAC;IAClF,CAAC;IAED,KAAK;QACH,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC3C,IAAI,OAAO,GAAG,KAAK,CAAC;YACpB,kFAAkF;YAClF,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,IAAI,WAAW,CAAC;YACnD,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,GAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE;gBAC3D,OAAO,EAAE,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;gBAC1C,gBAAgB,EAAE,IAAI,CAAC,OAAO,CAAC,gBAAgB;gBAC/C,UAAU,EAAE,WAAW;aACxB,CAAC,CAAC;YACH,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;YAEb,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;gBACjB,OAAO,GAAG,IAAI,CAAC;gBACf,IAAI,CAAC,SAAS,EAAE,CAAC;gBACjB,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC,CAAC;YAEH,EAAE,CAAC,EAAE,CAAC,qBAAqB,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;gBACzC,GAAG,CAAC,MAAM,EAAE,CAAC;gBACb,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,wCAAwC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC;gBAChF,EAAE,CAAC,SAAS,EAAE,CAAC;gBACf,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,OAAO,GAAG,IAAI,CAAC;oBACf,MAAM,CAAC,GAAG,CAAC,CAAC;gBACd,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,QAAQ,EAAE,EAAE;gBACjC,IAAI,QAAQ;oBAAE,OAAO;gBACrB,IAAI,GAAY,CAAC;gBACjB,IAAI,CAAC;oBACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;gBACnC,CAAC;gBAAC,MAAM,CAAC;oBACP,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE,qCAAqC,CAAC,CAAC;oBACtE,OAAO;gBACT,CAAC;gBACD,kCAAkC;gBAClC,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;oBACjD,IAAI,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ;wBAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAmB,CAAC,CAAC;gBACxE,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;gBACjB,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;YAC5B,CAAC,CAAC,CAAC;YAEH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBACrB,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE,sBAAsB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;gBACpE,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,OAAO,GAAG,IAAI,CAAC;oBACf,MAAM,CAAC,GAAG,CAAC,CAAC;gBACd,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;gBAC9B,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAChB,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,OAAO,GAAG,IAAI,CAAC;oBACf,MAAM,CAAC,IAAI,KAAK,CAAC,yCAAyC,IAAI,GAAG,CAAC,CAAC,CAAC;oBACpE,OAAO;gBACT,CAAC;gBACD,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,0BAA0B,IAAI,YAAY,MAAM,CAAC,QAAQ,EAAE,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC;YACjG,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,IAAI,CAAC,GAAU;QACrB,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI;YAAE,OAAO;QACrC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAClD,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;IACtB,CAAC;IAEO,SAAS;QACf,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,IAAI,MAAM,CAAC;QACvD,IAAI,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC;YAAE,OAAO;QAC5B,IAAI,CAAC,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE;YAChC,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;YACnB,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI;gBAAE,OAAO;YACpD,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;gBACtB,uDAAuD;gBACvD,EAAE,CAAC,SAAS,EAAE,CAAC;gBACf,OAAO;YACT,CAAC;YACD,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;YACzB,IAAI,CAAC;gBACH,EAAE,CAAC,IAAI,EAAE,CAAC;YACZ,CAAC;YAAC,MAAM,CAAC;gBACP,+BAA+B;YACjC,CAAC;QACH,CAAC,EAAE,QAAQ,CAAC,CAAC;QACb,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;IACzB,CAAC;IAEO,QAAQ;QACd,IAAI,IAAI,CAAC,SAAS;YAAE,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAClD,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED,IAAI,CAAC,OAAuB;QAC1B,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;QACnB,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;YACxE,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC,CAAC;QACzE,CAAC;QACD,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC3C,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAC7E,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChB,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;QACnB,IAAI,CAAC,EAAE;YAAE,OAAO;QAChB,IAAI,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,MAAM;YAAE,OAAO;QAC/C,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YAClC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,EAAE,CAAC,SAAS,EAAE,CAAC;gBACf,OAAO,EAAE,CAAC;YACZ,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;YAC7B,KAAK,CAAC,KAAK,EAAE,CAAC;YACd,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;gBACpB,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC,CAAC;YACH,IAAI,CAAC;gBACH,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;YACtC,CAAC;YAAC,MAAM,CAAC;gBACP,EAAE,CAAC,SAAS,EAAE,CAAC;YACjB,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal structured logger for mcp-gateway
|
|
3
|
+
*/
|
|
4
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
5
|
+
export type { LogLevel };
|
|
6
|
+
export declare class Logger {
|
|
7
|
+
private level;
|
|
8
|
+
setLevel(level: LogLevel): void;
|
|
9
|
+
private log;
|
|
10
|
+
debug(message: string, meta?: Record<string, unknown>): void;
|
|
11
|
+
info(message: string, meta?: Record<string, unknown>): void;
|
|
12
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
13
|
+
error(message: string, meta?: Record<string, unknown>): void;
|
|
14
|
+
}
|
|
15
|
+
export declare const logger: Logger;
|
|
16
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,KAAK,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAkBpD,YAAY,EAAE,QAAQ,EAAE,CAAC;AAEzB,qBAAa,MAAM;IACjB,OAAO,CAAC,KAAK,CAAoB;IAEjC,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;IAI/B,OAAO,CAAC,GAAG;IAkBX,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAI5D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAI3D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAI3D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;CAG7D;AAUD,eAAO,MAAM,MAAM,QAAe,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal structured logger for mcp-gateway
|
|
3
|
+
*/
|
|
4
|
+
const LEVELS = {
|
|
5
|
+
debug: 0,
|
|
6
|
+
info: 1,
|
|
7
|
+
warn: 2,
|
|
8
|
+
error: 3,
|
|
9
|
+
};
|
|
10
|
+
const COLORS = {
|
|
11
|
+
debug: '\x1b[90m', // gray
|
|
12
|
+
info: '\x1b[36m', // cyan
|
|
13
|
+
warn: '\x1b[33m', // yellow
|
|
14
|
+
error: '\x1b[31m', // red
|
|
15
|
+
};
|
|
16
|
+
const RESET = '\x1b[0m';
|
|
17
|
+
export class Logger {
|
|
18
|
+
level = 'info';
|
|
19
|
+
setLevel(level) {
|
|
20
|
+
this.level = level;
|
|
21
|
+
}
|
|
22
|
+
log(level, message, meta) {
|
|
23
|
+
if (LEVELS[level] < LEVELS[this.level])
|
|
24
|
+
return;
|
|
25
|
+
const ts = new Date().toISOString();
|
|
26
|
+
const color = process.stdout.isTTY ? COLORS[level] : '';
|
|
27
|
+
const reset = process.stdout.isTTY ? RESET : '';
|
|
28
|
+
const prefix = `${color}[${level.toUpperCase().padEnd(5)}]${reset}`;
|
|
29
|
+
const metaStr = meta ? ` ${safeStringify(meta)}` : '';
|
|
30
|
+
const output = `${ts} ${prefix} ${message}${metaStr}`;
|
|
31
|
+
if (level === 'error' || level === 'warn') {
|
|
32
|
+
process.stderr.write(output + '\n');
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
process.stdout.write(output + '\n');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
debug(message, meta) {
|
|
39
|
+
this.log('debug', message, meta);
|
|
40
|
+
}
|
|
41
|
+
info(message, meta) {
|
|
42
|
+
this.log('info', message, meta);
|
|
43
|
+
}
|
|
44
|
+
warn(message, meta) {
|
|
45
|
+
this.log('warn', message, meta);
|
|
46
|
+
}
|
|
47
|
+
error(message, meta) {
|
|
48
|
+
this.log('error', message, meta);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function safeStringify(value) {
|
|
52
|
+
try {
|
|
53
|
+
return JSON.stringify(value, (_k, v) => (typeof v === 'bigint' ? v.toString() : v));
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return '[unserializable meta]';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export const logger = new Logger();
|
|
60
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,MAAM,GAA6B;IACvC,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;CACT,CAAC;AAEF,MAAM,MAAM,GAA6B;IACvC,KAAK,EAAE,UAAU,EAAE,OAAO;IAC1B,IAAI,EAAE,UAAU,EAAG,OAAO;IAC1B,IAAI,EAAE,UAAU,EAAG,SAAS;IAC5B,KAAK,EAAE,UAAU,EAAE,MAAM;CAC1B,CAAC;AAEF,MAAM,KAAK,GAAG,SAAS,CAAC;AAIxB,MAAM,OAAO,MAAM;IACT,KAAK,GAAa,MAAM,CAAC;IAEjC,QAAQ,CAAC,KAAe;QACtB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAEO,GAAG,CAAC,KAAe,EAAE,OAAe,EAAE,IAA8B;QAC1E,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO;QAE/C,MAAM,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAChD,MAAM,MAAM,GAAG,GAAG,KAAK,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC;QACpE,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAEtD,MAAM,MAAM,GAAG,GAAG,EAAE,IAAI,MAAM,IAAI,OAAO,GAAG,OAAO,EAAE,CAAC;QAEtD,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;YAC1C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;QACtC,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,IAA8B;QACnD,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,IAA8B;QAClD,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,IAA8B;QAClD,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,IAA8B;QACnD,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;CACF;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACtF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,uBAAuB,CAAC;IACjC,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async Mutex
|
|
3
|
+
*
|
|
4
|
+
* Bug fix (v0.1.0): the server registry could attempt to restart the same
|
|
5
|
+
* MCP server process multiple times concurrently when several requests
|
|
6
|
+
* arrived simultaneously during a restart window, causing duplicate processes.
|
|
7
|
+
* This lightweight mutex serialises critical sections without external deps.
|
|
8
|
+
*
|
|
9
|
+
* @module utils/mutex
|
|
10
|
+
*/
|
|
11
|
+
export declare class Mutex {
|
|
12
|
+
private _queue;
|
|
13
|
+
private _locked;
|
|
14
|
+
/**
|
|
15
|
+
* Acquire the lock. The returned release function is idempotent: calling it
|
|
16
|
+
* more than once is a no-op (previously a double release could unlock the
|
|
17
|
+
* mutex while another holder was still inside the critical section).
|
|
18
|
+
*/
|
|
19
|
+
acquire(): Promise<() => void>;
|
|
20
|
+
/** Hand the lock directly to the next waiter, or unlock if none. */
|
|
21
|
+
private _release;
|
|
22
|
+
get isLocked(): boolean;
|
|
23
|
+
runExclusive<T>(fn: () => Promise<T>): Promise<T>;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=mutex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutex.d.ts","sourceRoot":"","sources":["../../src/utils/mutex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,qBAAa,KAAK;IAChB,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,OAAO,CAAS;IAExB;;;;OAIG;IACH,OAAO,IAAI,OAAO,CAAC,MAAM,IAAI,CAAC;IAmB9B,oEAAoE;IACpE,OAAO,CAAC,QAAQ;IAShB,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAEK,YAAY,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAQxD"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async Mutex
|
|
3
|
+
*
|
|
4
|
+
* Bug fix (v0.1.0): the server registry could attempt to restart the same
|
|
5
|
+
* MCP server process multiple times concurrently when several requests
|
|
6
|
+
* arrived simultaneously during a restart window, causing duplicate processes.
|
|
7
|
+
* This lightweight mutex serialises critical sections without external deps.
|
|
8
|
+
*
|
|
9
|
+
* @module utils/mutex
|
|
10
|
+
*/
|
|
11
|
+
export class Mutex {
|
|
12
|
+
_queue = [];
|
|
13
|
+
_locked = false;
|
|
14
|
+
/**
|
|
15
|
+
* Acquire the lock. The returned release function is idempotent: calling it
|
|
16
|
+
* more than once is a no-op (previously a double release could unlock the
|
|
17
|
+
* mutex while another holder was still inside the critical section).
|
|
18
|
+
*/
|
|
19
|
+
acquire() {
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
const grant = () => {
|
|
22
|
+
let released = false;
|
|
23
|
+
resolve(() => {
|
|
24
|
+
if (released)
|
|
25
|
+
return;
|
|
26
|
+
released = true;
|
|
27
|
+
this._release();
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
if (!this._locked) {
|
|
31
|
+
this._locked = true;
|
|
32
|
+
grant();
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
this._queue.push(grant);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/** Hand the lock directly to the next waiter, or unlock if none. */
|
|
40
|
+
_release() {
|
|
41
|
+
const next = this._queue.shift();
|
|
42
|
+
if (next) {
|
|
43
|
+
next(); // lock stays held, ownership transfers
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
this._locked = false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
get isLocked() {
|
|
50
|
+
return this._locked;
|
|
51
|
+
}
|
|
52
|
+
async runExclusive(fn) {
|
|
53
|
+
const release = await this.acquire();
|
|
54
|
+
try {
|
|
55
|
+
return await fn();
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
release();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=mutex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutex.js","sourceRoot":"","sources":["../../src/utils/mutex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,OAAO,KAAK;IACR,MAAM,GAAsB,EAAE,CAAC;IAC/B,OAAO,GAAG,KAAK,CAAC;IAExB;;;;OAIG;IACH,OAAO;QACL,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7B,MAAM,KAAK,GAAG,GAAG,EAAE;gBACjB,IAAI,QAAQ,GAAG,KAAK,CAAC;gBACrB,OAAO,CAAC,GAAG,EAAE;oBACX,IAAI,QAAQ;wBAAE,OAAO;oBACrB,QAAQ,GAAG,IAAI,CAAC;oBAChB,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAClB,CAAC,CAAC,CAAC;YACL,CAAC,CAAC;YACF,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;gBACpB,KAAK,EAAE,CAAC;YACV,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,oEAAoE;IAC5D,QAAQ;QACd,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACjC,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,EAAE,CAAC,CAAC,uCAAuC;QACjD,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACvB,CAAC;IACH,CAAC;IAED,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,YAAY,CAAI,EAAoB;QACxC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACrC,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAC;QACpB,CAAC;gBAAS,CAAC;YACT,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;CACF"}
|