@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.
Files changed (114) hide show
  1. package/CHANGELOG.md +124 -0
  2. package/LICENSE +21 -0
  3. package/README.md +377 -2
  4. package/dashboard/README.md +27 -0
  5. package/dashboard/index.html +415 -0
  6. package/dist/auth/middleware.d.ts +24 -0
  7. package/dist/auth/middleware.d.ts.map +1 -0
  8. package/dist/auth/middleware.js +108 -0
  9. package/dist/auth/middleware.js.map +1 -0
  10. package/dist/auth/ratelimit.d.ts +19 -0
  11. package/dist/auth/ratelimit.d.ts.map +1 -0
  12. package/dist/auth/ratelimit.js +81 -0
  13. package/dist/auth/ratelimit.js.map +1 -0
  14. package/dist/cli.d.ts +6 -0
  15. package/dist/cli.d.ts.map +1 -0
  16. package/dist/cli.js +129 -0
  17. package/dist/cli.js.map +1 -0
  18. package/dist/config/loader.d.ts +13 -0
  19. package/dist/config/loader.d.ts.map +1 -0
  20. package/dist/config/loader.js +268 -0
  21. package/dist/config/loader.js.map +1 -0
  22. package/dist/config/watcher.d.ts +29 -0
  23. package/dist/config/watcher.d.ts.map +1 -0
  24. package/dist/config/watcher.js +96 -0
  25. package/dist/config/watcher.js.map +1 -0
  26. package/dist/gateway/api.d.ts +29 -0
  27. package/dist/gateway/api.d.ts.map +1 -0
  28. package/dist/gateway/api.js +280 -0
  29. package/dist/gateway/api.js.map +1 -0
  30. package/dist/gateway/index.d.ts +49 -0
  31. package/dist/gateway/index.d.ts.map +1 -0
  32. package/dist/gateway/index.js +260 -0
  33. package/dist/gateway/index.js.map +1 -0
  34. package/dist/gateway/supervisor.d.ts +58 -0
  35. package/dist/gateway/supervisor.d.ts.map +1 -0
  36. package/dist/gateway/supervisor.js +205 -0
  37. package/dist/gateway/supervisor.js.map +1 -0
  38. package/dist/index.d.ts +15 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +12 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/middleware/cors.d.ts +19 -0
  43. package/dist/middleware/cors.d.ts.map +1 -0
  44. package/dist/middleware/cors.js +65 -0
  45. package/dist/middleware/cors.js.map +1 -0
  46. package/dist/middleware/error-handler.d.ts +40 -0
  47. package/dist/middleware/error-handler.d.ts.map +1 -0
  48. package/dist/middleware/error-handler.js +98 -0
  49. package/dist/middleware/error-handler.js.map +1 -0
  50. package/dist/middleware/request-id.d.ts +23 -0
  51. package/dist/middleware/request-id.d.ts.map +1 -0
  52. package/dist/middleware/request-id.js +30 -0
  53. package/dist/middleware/request-id.js.map +1 -0
  54. package/dist/middleware/timeout.d.ts +12 -0
  55. package/dist/middleware/timeout.d.ts.map +1 -0
  56. package/dist/middleware/timeout.js +39 -0
  57. package/dist/middleware/timeout.js.map +1 -0
  58. package/dist/monitor/index.d.ts +49 -0
  59. package/dist/monitor/index.d.ts.map +1 -0
  60. package/dist/monitor/index.js +204 -0
  61. package/dist/monitor/index.js.map +1 -0
  62. package/dist/proxy/index.d.ts +86 -0
  63. package/dist/proxy/index.d.ts.map +1 -0
  64. package/dist/proxy/index.js +389 -0
  65. package/dist/proxy/index.js.map +1 -0
  66. package/dist/registry/index.d.ts +41 -0
  67. package/dist/registry/index.d.ts.map +1 -0
  68. package/dist/registry/index.js +157 -0
  69. package/dist/registry/index.js.map +1 -0
  70. package/dist/transport/channel.d.ts +61 -0
  71. package/dist/transport/channel.d.ts.map +1 -0
  72. package/dist/transport/channel.js +38 -0
  73. package/dist/transport/channel.js.map +1 -0
  74. package/dist/transport/sse-parser.d.ts +30 -0
  75. package/dist/transport/sse-parser.d.ts.map +1 -0
  76. package/dist/transport/sse-parser.js +107 -0
  77. package/dist/transport/sse-parser.js.map +1 -0
  78. package/dist/transport/sse.d.ts +39 -0
  79. package/dist/transport/sse.d.ts.map +1 -0
  80. package/dist/transport/sse.js +142 -0
  81. package/dist/transport/sse.js.map +1 -0
  82. package/dist/transport/stdio.d.ts +35 -0
  83. package/dist/transport/stdio.d.ts.map +1 -0
  84. package/dist/transport/stdio.js +149 -0
  85. package/dist/transport/stdio.js.map +1 -0
  86. package/dist/transport/streamable-http.d.ts +45 -0
  87. package/dist/transport/streamable-http.d.ts.map +1 -0
  88. package/dist/transport/streamable-http.js +183 -0
  89. package/dist/transport/streamable-http.js.map +1 -0
  90. package/dist/transport/websocket.d.ts +38 -0
  91. package/dist/transport/websocket.d.ts.map +1 -0
  92. package/dist/transport/websocket.js +172 -0
  93. package/dist/transport/websocket.js.map +1 -0
  94. package/dist/utils/logger.d.ts +16 -0
  95. package/dist/utils/logger.d.ts.map +1 -0
  96. package/dist/utils/logger.js +60 -0
  97. package/dist/utils/logger.js.map +1 -0
  98. package/dist/utils/mutex.d.ts +25 -0
  99. package/dist/utils/mutex.d.ts.map +1 -0
  100. package/dist/utils/mutex.js +62 -0
  101. package/dist/utils/mutex.js.map +1 -0
  102. package/dist/utils/semaphore.d.ts +16 -0
  103. package/dist/utils/semaphore.d.ts.map +1 -0
  104. package/dist/utils/semaphore.js +42 -0
  105. package/dist/utils/semaphore.js.map +1 -0
  106. package/dist/utils/types.d.ts +215 -0
  107. package/dist/utils/types.d.ts.map +1 -0
  108. package/dist/utils/types.js +5 -0
  109. package/dist/utils/types.js.map +1 -0
  110. package/dist/utils/version.d.ts +6 -0
  111. package/dist/utils/version.d.ts.map +1 -0
  112. package/dist/utils/version.js +18 -0
  113. package/dist/utils/version.js.map +1 -0
  114. package/package.json +78 -4
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Upstream channel abstraction.
3
+ *
4
+ * A channel moves raw JSON-RPC messages between the gateway and one upstream
5
+ * MCP server. It knows nothing about request ids, timeouts or the MCP
6
+ * handshake — that is the job of the session layer in `proxy/index.ts`, which
7
+ * is shared by every transport (stdio, SSE, WebSocket, Streamable HTTP).
8
+ *
9
+ * Lifecycle: `start()` → any number of `send()` → `close()`.
10
+ * `onclose` fires once when the channel goes away *unexpectedly* (process
11
+ * exit, socket close, stream end); it does not fire for `close()`.
12
+ *
13
+ * @module transport/channel
14
+ */
15
+ import type { McpServerConfig } from '../utils/types.js';
16
+ export type JsonRpcId = string | number;
17
+ export interface JsonRpcMessage {
18
+ jsonrpc: '2.0';
19
+ id?: JsonRpcId | null;
20
+ method?: string;
21
+ params?: unknown;
22
+ result?: unknown;
23
+ error?: {
24
+ code: number;
25
+ message: string;
26
+ data?: unknown;
27
+ };
28
+ }
29
+ export interface UpstreamChannel {
30
+ /** Short transport name, for logs. */
31
+ readonly kind: string;
32
+ /** Open the connection. Resolves once `send()` may be called. */
33
+ start(): Promise<void>;
34
+ /** Deliver one message. Rejects when the message could not be handed over. */
35
+ send(message: JsonRpcMessage): Promise<void>;
36
+ /** Intentionally close the channel and release every resource. */
37
+ close(): Promise<void>;
38
+ /** Called for every message received from the server. */
39
+ onmessage?: (message: JsonRpcMessage) => void;
40
+ /** Called once when the channel is lost without `close()` being called. */
41
+ onclose?: (error: Error) => void;
42
+ /** Informs HTTP-based channels of the protocol version negotiated in `initialize`. */
43
+ setProtocolVersion?(version: string): void;
44
+ /** Called when a request is abandoned (e.g. timed out) so the channel can free per-request resources. */
45
+ abandon?(id: JsonRpcId): void;
46
+ }
47
+ /** Expand `${VAR}` references against the gateway's own environment. */
48
+ export declare function expandEnv(value: string): string;
49
+ export declare function expandRecord(record: Record<string, string> | undefined): Record<string, string>;
50
+ /** Error message including the `cause` (fetch() hides the real reason there). */
51
+ export declare function errMessage(err: unknown): string;
52
+ /** Parse a JSON value into zero or more JSON-RPC messages (batches are arrays). */
53
+ export declare function toMessages(value: unknown): JsonRpcMessage[];
54
+ export interface ChannelOptions {
55
+ /** Time allowed for `start()` (connect + handshake prerequisites). */
56
+ connectTimeoutMs: number;
57
+ /** Grace period used by the stdio channel between stdin close / SIGTERM / SIGKILL. */
58
+ killGraceMs: number;
59
+ }
60
+ export type ChannelFactory = (config: McpServerConfig, options: ChannelOptions) => UpstreamChannel;
61
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../src/transport/channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAExC,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAC3D;AAED,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iEAAiE;IACjE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,8EAA8E;IAC9E,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,kEAAkE;IAClE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,yDAAyD;IACzD,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;IAC9C,2EAA2E;IAC3E,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,sFAAsF;IACtF,kBAAkB,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,yGAAyG;IACzG,OAAO,CAAC,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI,CAAC;CAC/B;AAED,wEAAwE;AACxE,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAI/F;AAED,iFAAiF;AACjF,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAM/C;AAED,mFAAmF;AACnF,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,EAAE,CAK3D;AAED,MAAM,WAAW,cAAc;IAC7B,sEAAsE;IACtE,gBAAgB,EAAE,MAAM,CAAC;IACzB,sFAAsF;IACtF,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,cAAc,KAAK,eAAe,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Upstream channel abstraction.
3
+ *
4
+ * A channel moves raw JSON-RPC messages between the gateway and one upstream
5
+ * MCP server. It knows nothing about request ids, timeouts or the MCP
6
+ * handshake — that is the job of the session layer in `proxy/index.ts`, which
7
+ * is shared by every transport (stdio, SSE, WebSocket, Streamable HTTP).
8
+ *
9
+ * Lifecycle: `start()` → any number of `send()` → `close()`.
10
+ * `onclose` fires once when the channel goes away *unexpectedly* (process
11
+ * exit, socket close, stream end); it does not fire for `close()`.
12
+ *
13
+ * @module transport/channel
14
+ */
15
+ /** Expand `${VAR}` references against the gateway's own environment. */
16
+ export function expandEnv(value) {
17
+ return value.replace(/\$\{([^}]+)\}/g, (_, name) => process.env[name] ?? '');
18
+ }
19
+ export function expandRecord(record) {
20
+ const out = {};
21
+ for (const [k, v] of Object.entries(record ?? {}))
22
+ out[k] = expandEnv(v);
23
+ return out;
24
+ }
25
+ /** Error message including the `cause` (fetch() hides the real reason there). */
26
+ export function errMessage(err) {
27
+ if (err instanceof Error) {
28
+ const cause = err.cause;
29
+ return cause instanceof Error ? `${err.message} (${cause.message})` : err.message;
30
+ }
31
+ return String(err);
32
+ }
33
+ /** Parse a JSON value into zero or more JSON-RPC messages (batches are arrays). */
34
+ export function toMessages(value) {
35
+ const list = Array.isArray(value) ? value : [value];
36
+ return list.filter((m) => !!m && typeof m === 'object' && m.jsonrpc === '2.0');
37
+ }
38
+ //# sourceMappingURL=channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.js","sourceRoot":"","sources":["../../src/transport/channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAkCH,wEAAwE;AACxE,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,OAAO,KAAK,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC,EAAE,IAAY,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;AACvF,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAA0C;IACrE,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;IACzE,OAAO,GAAG,CAAC;AACb,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,UAAU,CAAC,GAAY;IACrC,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAI,GAAmC,CAAC,KAAK,CAAC;QACzD,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC;IACpF,CAAC;IACD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;AACrB,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACpD,OAAO,IAAI,CAAC,MAAM,CAChB,CAAC,CAAC,EAAuB,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAK,CAA2B,CAAC,OAAO,KAAK,KAAK,CAC3G,CAAC;AACJ,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Incremental `text/event-stream` parser (WHATWG HTML §9.2).
3
+ *
4
+ * Feed it decoded text chunks of any size; it calls `onEvent` for every
5
+ * complete event. Parser state survives chunk boundaries, so events, lines
6
+ * and even CRLF pairs split across TCP chunks are handled.
7
+ *
8
+ * @module transport/sse-parser
9
+ */
10
+ export interface SseEvent {
11
+ event: string;
12
+ data: string;
13
+ id?: string;
14
+ }
15
+ export declare class SseParser {
16
+ private readonly onEvent;
17
+ private readonly maxBufferChars;
18
+ private buffer;
19
+ private eventType;
20
+ private dataLines;
21
+ private lastId;
22
+ private pendingCR;
23
+ constructor(onEvent: (event: SseEvent) => void, maxBufferChars?: number);
24
+ push(chunk: string): void;
25
+ private line;
26
+ private dispatch;
27
+ }
28
+ /** Read a fetch() body stream through an SseParser until it ends. */
29
+ export declare function readSseStream(body: ReadableStream<Uint8Array>, onEvent: (event: SseEvent) => void): Promise<void>;
30
+ //# sourceMappingURL=sse-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-parser.d.ts","sourceRoot":"","sources":["../../src/transport/sse-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,qBAAa,SAAS;IAQlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,cAAc;IARjC,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,SAAS,CAAM;IACvB,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,SAAS,CAAS;gBAGP,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,EAClC,cAAc,SAAmB;IAGpD,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAuBzB,OAAO,CAAC,IAAI;IA0BZ,OAAO,CAAC,QAAQ;CAWjB;AAED,qEAAqE;AACrE,wBAAsB,aAAa,CACjC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,EAChC,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,GACjC,OAAO,CAAC,IAAI,CAAC,CAef"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Incremental `text/event-stream` parser (WHATWG HTML §9.2).
3
+ *
4
+ * Feed it decoded text chunks of any size; it calls `onEvent` for every
5
+ * complete event. Parser state survives chunk boundaries, so events, lines
6
+ * and even CRLF pairs split across TCP chunks are handled.
7
+ *
8
+ * @module transport/sse-parser
9
+ */
10
+ export class SseParser {
11
+ onEvent;
12
+ maxBufferChars;
13
+ buffer = '';
14
+ eventType = '';
15
+ dataLines = [];
16
+ lastId;
17
+ pendingCR = false;
18
+ constructor(onEvent, maxBufferChars = 16 * 1024 * 1024) {
19
+ this.onEvent = onEvent;
20
+ this.maxBufferChars = maxBufferChars;
21
+ }
22
+ push(chunk) {
23
+ if (this.pendingCR && chunk.startsWith('\n'))
24
+ chunk = chunk.slice(1);
25
+ this.pendingCR = false;
26
+ this.buffer += chunk;
27
+ let start = 0;
28
+ for (let i = 0; i < this.buffer.length; i++) {
29
+ const ch = this.buffer[i];
30
+ if (ch !== '\n' && ch !== '\r')
31
+ continue;
32
+ this.line(this.buffer.slice(start, i));
33
+ if (ch === '\r') {
34
+ if (i + 1 === this.buffer.length)
35
+ this.pendingCR = true;
36
+ else if (this.buffer[i + 1] === '\n')
37
+ i++;
38
+ }
39
+ start = i + 1;
40
+ }
41
+ this.buffer = this.buffer.slice(start);
42
+ if (this.buffer.length > this.maxBufferChars) {
43
+ this.buffer = '';
44
+ throw new Error(`SSE line exceeded ${this.maxBufferChars} characters`);
45
+ }
46
+ }
47
+ line(line) {
48
+ if (line === '') {
49
+ this.dispatch();
50
+ return;
51
+ }
52
+ if (line.startsWith(':'))
53
+ return; // comment / keep-alive
54
+ const colon = line.indexOf(':');
55
+ const field = colon === -1 ? line : line.slice(0, colon);
56
+ let value = colon === -1 ? '' : line.slice(colon + 1);
57
+ if (value.startsWith(' '))
58
+ value = value.slice(1);
59
+ switch (field) {
60
+ case 'event':
61
+ this.eventType = value;
62
+ break;
63
+ case 'data':
64
+ this.dataLines.push(value);
65
+ break;
66
+ case 'id':
67
+ if (!value.includes('\0'))
68
+ this.lastId = value;
69
+ break;
70
+ default:
71
+ break; // "retry" and unknown fields are ignored
72
+ }
73
+ }
74
+ dispatch() {
75
+ if (this.dataLines.length === 0) {
76
+ this.eventType = '';
77
+ return;
78
+ }
79
+ const event = { event: this.eventType || 'message', data: this.dataLines.join('\n') };
80
+ if (this.lastId !== undefined)
81
+ event.id = this.lastId;
82
+ this.eventType = '';
83
+ this.dataLines = [];
84
+ this.onEvent(event);
85
+ }
86
+ }
87
+ /** Read a fetch() body stream through an SseParser until it ends. */
88
+ export async function readSseStream(body, onEvent) {
89
+ const parser = new SseParser(onEvent);
90
+ const decoder = new TextDecoder();
91
+ const reader = body.getReader();
92
+ try {
93
+ for (;;) {
94
+ const { done, value } = await reader.read();
95
+ if (done)
96
+ break;
97
+ parser.push(decoder.decode(value, { stream: true }));
98
+ }
99
+ const tail = decoder.decode();
100
+ if (tail)
101
+ parser.push(tail);
102
+ }
103
+ finally {
104
+ reader.releaseLock();
105
+ }
106
+ }
107
+ //# sourceMappingURL=sse-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-parser.js","sourceRoot":"","sources":["../../src/transport/sse-parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * SSE channel — the MCP "HTTP with SSE" transport (protocol 2024-11-05).
3
+ *
4
+ * 1. `GET <url>` with `Accept: text/event-stream` opens the server→client stream.
5
+ * 2. The server's first event is `endpoint`; its data is the URL (usually with a
6
+ * `sessionId` query) that client→server messages must be POSTed to.
7
+ * 3. Every JSON-RPC message from the server arrives as a `message` event.
8
+ *
9
+ * Reconnection is *not* done here: an SSE session's server-side state dies with
10
+ * the stream, so the gateway's supervisor re-runs the full MCP handshake
11
+ * instead (see `gateway/supervisor.ts`).
12
+ *
13
+ * Security: the announced endpoint must have the same origin as the SSE URL, so
14
+ * a malicious server cannot make the gateway POST its configured headers
15
+ * (often credentials) to another host. Redirects are not followed for POSTs.
16
+ *
17
+ * @module transport/sse
18
+ */
19
+ import type { McpServerConfig } from '../utils/types.js';
20
+ import { type ChannelOptions, type JsonRpcMessage, type UpstreamChannel } from './channel.js';
21
+ export declare class SseChannel implements UpstreamChannel {
22
+ private readonly config;
23
+ private readonly options;
24
+ readonly kind = "sse";
25
+ onmessage?: (message: JsonRpcMessage) => void;
26
+ onclose?: (error: Error) => void;
27
+ private readonly url;
28
+ private readonly headers;
29
+ private readonly abort;
30
+ private endpoint?;
31
+ private closed;
32
+ private lost;
33
+ constructor(config: McpServerConfig, options: ChannelOptions);
34
+ start(): Promise<void>;
35
+ private lose;
36
+ send(message: JsonRpcMessage): Promise<void>;
37
+ close(): Promise<void>;
38
+ }
39
+ //# sourceMappingURL=sse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../../src/transport/sse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,OAAO,EAA4B,KAAK,cAAc,EAAE,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAGxH,qBAAa,UAAW,YAAW,eAAe;IAa9C,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAb1B,QAAQ,CAAC,IAAI,SAAS;IACtB,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,QAAQ,CAAC,KAAK,CAAyB;IAC/C,OAAO,CAAC,QAAQ,CAAC,CAAM;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,IAAI,CAAS;gBAGF,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,cAAc;IAOpC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA2E5B,OAAO,CAAC,IAAI;IAQN,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAc5C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAI7B"}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * SSE channel — the MCP "HTTP with SSE" transport (protocol 2024-11-05).
3
+ *
4
+ * 1. `GET <url>` with `Accept: text/event-stream` opens the server→client stream.
5
+ * 2. The server's first event is `endpoint`; its data is the URL (usually with a
6
+ * `sessionId` query) that client→server messages must be POSTed to.
7
+ * 3. Every JSON-RPC message from the server arrives as a `message` event.
8
+ *
9
+ * Reconnection is *not* done here: an SSE session's server-side state dies with
10
+ * the stream, so the gateway's supervisor re-runs the full MCP handshake
11
+ * instead (see `gateway/supervisor.ts`).
12
+ *
13
+ * Security: the announced endpoint must have the same origin as the SSE URL, so
14
+ * a malicious server cannot make the gateway POST its configured headers
15
+ * (often credentials) to another host. Redirects are not followed for POSTs.
16
+ *
17
+ * @module transport/sse
18
+ */
19
+ import { logger } from '../utils/logger.js';
20
+ import { errMessage, expandRecord } from './channel.js';
21
+ import { readSseStream } from './sse-parser.js';
22
+ export class SseChannel {
23
+ config;
24
+ options;
25
+ kind = 'sse';
26
+ onmessage;
27
+ onclose;
28
+ url;
29
+ headers;
30
+ abort = new AbortController();
31
+ endpoint;
32
+ closed = false;
33
+ lost = false;
34
+ constructor(config, options) {
35
+ this.config = config;
36
+ this.options = options;
37
+ if (!config.url)
38
+ throw new Error(`Server "${config.id}" has no url configured`);
39
+ this.url = new URL(config.url);
40
+ this.headers = expandRecord(config.headers);
41
+ }
42
+ async start() {
43
+ let timer;
44
+ let onEndpoint;
45
+ let onFail;
46
+ const ready = new Promise((resolve, reject) => {
47
+ onEndpoint = resolve;
48
+ onFail = reject;
49
+ timer = setTimeout(() => reject(new Error(`SSE server did not announce an endpoint within ${this.options.connectTimeoutMs}ms`)), this.options.connectTimeoutMs);
50
+ });
51
+ let res;
52
+ try {
53
+ res = await fetch(this.url, {
54
+ method: 'GET',
55
+ headers: { ...this.headers, Accept: 'text/event-stream', 'Cache-Control': 'no-cache' },
56
+ signal: this.abort.signal,
57
+ });
58
+ }
59
+ catch (err) {
60
+ clearTimeout(timer);
61
+ throw new Error(`SSE connect to ${this.url.origin}${this.url.pathname} failed: ${errMessage(err)}`);
62
+ }
63
+ if (!res.ok || !res.body) {
64
+ clearTimeout(timer);
65
+ await res.body?.cancel().catch(() => { });
66
+ throw new Error(`SSE server returned HTTP ${res.status}`);
67
+ }
68
+ const contentType = res.headers.get('content-type') ?? '';
69
+ if (!contentType.includes('text/event-stream')) {
70
+ clearTimeout(timer);
71
+ await res.body.cancel().catch(() => { });
72
+ throw new Error(`SSE server returned unexpected content-type "${contentType}"`);
73
+ }
74
+ // Consume the stream in the background for the lifetime of the channel.
75
+ readSseStream(res.body, (ev) => {
76
+ if (ev.event === 'endpoint') {
77
+ try {
78
+ const endpoint = new URL(ev.data.trim(), this.url);
79
+ if (endpoint.origin !== this.url.origin) {
80
+ throw new Error(`endpoint origin ${endpoint.origin} does not match ${this.url.origin}`);
81
+ }
82
+ this.endpoint = endpoint;
83
+ onEndpoint();
84
+ }
85
+ catch (err) {
86
+ onFail(new Error(`Invalid SSE endpoint event: ${errMessage(err)}`));
87
+ }
88
+ return;
89
+ }
90
+ if (ev.event !== 'message')
91
+ return;
92
+ let parsed;
93
+ try {
94
+ parsed = JSON.parse(ev.data);
95
+ }
96
+ catch {
97
+ logger.debug(`[${this.config.id}] ignoring non-JSON SSE message`);
98
+ return;
99
+ }
100
+ if (parsed && typeof parsed === 'object')
101
+ this.onmessage?.(parsed);
102
+ }).then(() => this.lose(new Error('SSE stream ended'), onFail), (err) => this.lose(new Error(`SSE stream error: ${errMessage(err)}`), onFail));
103
+ try {
104
+ await ready;
105
+ }
106
+ catch (err) {
107
+ this.abort.abort();
108
+ throw err;
109
+ }
110
+ finally {
111
+ clearTimeout(timer);
112
+ }
113
+ }
114
+ lose(err, failStart) {
115
+ failStart(err); // no-op once start() settled
116
+ if (this.closed || this.lost)
117
+ return;
118
+ this.lost = true;
119
+ this.abort.abort();
120
+ this.onclose?.(err);
121
+ }
122
+ async send(message) {
123
+ if (this.closed || this.lost || !this.endpoint)
124
+ throw new Error('SSE channel is not connected');
125
+ const res = await fetch(this.endpoint, {
126
+ method: 'POST',
127
+ headers: { ...this.headers, 'Content-Type': 'application/json' },
128
+ body: JSON.stringify(message),
129
+ redirect: 'error',
130
+ signal: this.abort.signal,
131
+ });
132
+ // Body is informational ("Accepted"); drain it so the socket is reused.
133
+ await res.text().catch(() => '');
134
+ if (!res.ok)
135
+ throw new Error(`POST to SSE endpoint returned HTTP ${res.status}`);
136
+ }
137
+ async close() {
138
+ this.closed = true;
139
+ this.abort.abort();
140
+ }
141
+ }
142
+ //# sourceMappingURL=sse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse.js","sourceRoot":"","sources":["../../src/transport/sse.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * stdio channel — spawns the MCP server as a child process and exchanges
3
+ * newline-delimited JSON-RPC over its stdin/stdout (MCP stdio transport).
4
+ *
5
+ * Carries over the v0.2 / audit fixes:
6
+ * - stdout is decoded as a stream so multi-byte UTF-8 split across chunks survives;
7
+ * - stream errors (EPIPE on a dead child) never become unhandled 'error' events;
8
+ * - spawn errors and unexpected exits are reported once through `onclose`;
9
+ * - shutdown follows the MCP spec: close stdin → SIGTERM → SIGKILL, waiting
10
+ * for the real exit instead of trusting `proc.killed`;
11
+ * - un-terminated stdout is capped.
12
+ *
13
+ * @module transport/stdio
14
+ */
15
+ import type { McpServerConfig } from '../utils/types.js';
16
+ import { type ChannelOptions, type JsonRpcMessage, type UpstreamChannel } from './channel.js';
17
+ export declare class StdioChannel implements UpstreamChannel {
18
+ private readonly config;
19
+ private readonly options;
20
+ readonly kind = "stdio";
21
+ onmessage?: (message: JsonRpcMessage) => void;
22
+ onclose?: (error: Error) => void;
23
+ private proc?;
24
+ private buffer;
25
+ private closing;
26
+ private lost;
27
+ private exited;
28
+ constructor(config: McpServerConfig, options: ChannelOptions);
29
+ start(): Promise<void>;
30
+ private lose;
31
+ private drain;
32
+ send(message: JsonRpcMessage): Promise<void>;
33
+ close(): Promise<void>;
34
+ }
35
+ //# sourceMappingURL=stdio.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../src/transport/stdio.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,OAAO,EAA2B,KAAK,cAAc,EAAE,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAIvH,qBAAa,YAAa,YAAW,eAAe;IAYhD,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAZ1B,QAAQ,CAAC,IAAI,WAAW;IACxB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;IAC9C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAEjC,OAAO,CAAC,IAAI,CAAC,CAAe;IAC5B,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,MAAM,CAAiB;gBAGZ,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,cAAc;IAG1C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA2DtB,OAAO,CAAC,IAAI;IAMZ,OAAO,CAAC,KAAK;IAiBP,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ5C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CA2B7B"}
@@ -0,0 +1,149 @@
1
+ /**
2
+ * stdio channel — spawns the MCP server as a child process and exchanges
3
+ * newline-delimited JSON-RPC over its stdin/stdout (MCP stdio transport).
4
+ *
5
+ * Carries over the v0.2 / audit fixes:
6
+ * - stdout is decoded as a stream so multi-byte UTF-8 split across chunks survives;
7
+ * - stream errors (EPIPE on a dead child) never become unhandled 'error' events;
8
+ * - spawn errors and unexpected exits are reported once through `onclose`;
9
+ * - shutdown follows the MCP spec: close stdin → SIGTERM → SIGKILL, waiting
10
+ * for the real exit instead of trusting `proc.killed`;
11
+ * - un-terminated stdout is capped.
12
+ *
13
+ * @module transport/stdio
14
+ */
15
+ import { spawn } from 'child_process';
16
+ import { logger } from '../utils/logger.js';
17
+ import { expandEnv, expandRecord } from './channel.js';
18
+ const MAX_BUFFER_CHARS = 16 * 1024 * 1024;
19
+ export class StdioChannel {
20
+ config;
21
+ options;
22
+ kind = 'stdio';
23
+ onmessage;
24
+ onclose;
25
+ proc;
26
+ buffer = '';
27
+ closing = false;
28
+ lost = false;
29
+ exited;
30
+ constructor(config, options) {
31
+ this.config = config;
32
+ this.options = options;
33
+ }
34
+ start() {
35
+ const { config } = this;
36
+ if (!config.command)
37
+ return Promise.reject(new Error(`Server "${config.id}" has no command configured`));
38
+ // ${VAR} is expanded in args too (the examples pass e.g. ${DATABASE_URL} as an argument).
39
+ const proc = spawn(config.command, (config.args ?? []).map(expandEnv), {
40
+ env: { ...process.env, ...expandRecord(config.env) },
41
+ stdio: ['pipe', 'pipe', 'pipe'],
42
+ });
43
+ this.proc = proc;
44
+ let markExited;
45
+ this.exited = new Promise((r) => (markExited = r));
46
+ proc.stdout?.setEncoding('utf8');
47
+ proc.stdout?.on('data', (chunk) => {
48
+ this.buffer += chunk;
49
+ this.drain();
50
+ if (this.buffer.length > MAX_BUFFER_CHARS) {
51
+ logger.error(`[${config.id}] stdout line exceeded ${MAX_BUFFER_CHARS} chars; dropping session`);
52
+ this.buffer = '';
53
+ this.lose(new Error('MCP server sent an oversized message'));
54
+ proc.kill('SIGKILL');
55
+ }
56
+ });
57
+ proc.stderr?.on('data', (chunk) => {
58
+ logger.debug(`[${config.id}] stderr: ${chunk.toString().trim()}`);
59
+ });
60
+ const onStreamError = (err) => logger.debug(`[${config.id}] stdio stream error: ${err.message}`);
61
+ proc.stdin?.on('error', onStreamError);
62
+ proc.stdout?.on('error', onStreamError);
63
+ proc.stderr?.on('error', onStreamError);
64
+ return new Promise((resolve, reject) => {
65
+ let started = false;
66
+ proc.once('spawn', () => {
67
+ started = true;
68
+ resolve();
69
+ });
70
+ proc.on('error', (err) => {
71
+ logger.error(`[${config.id}] spawn error: ${err.message}`);
72
+ markExited(); // 'exit' may never fire if spawn failed
73
+ if (!started) {
74
+ started = true;
75
+ this.lost = true;
76
+ reject(err);
77
+ return;
78
+ }
79
+ this.lose(err);
80
+ });
81
+ proc.on('exit', (code, signal) => {
82
+ markExited();
83
+ if (!this.closing)
84
+ logger.warn(`Server "${config.id}" exited (code=${code}, signal=${signal})`);
85
+ this.lose(new Error(`MCP server "${config.id}" exited unexpectedly (code=${code}, signal=${signal})`));
86
+ });
87
+ });
88
+ }
89
+ lose(err) {
90
+ if (this.lost || this.closing)
91
+ return;
92
+ this.lost = true;
93
+ this.onclose?.(err);
94
+ }
95
+ drain() {
96
+ if (!this.buffer.includes('\n'))
97
+ return;
98
+ const lines = this.buffer.split('\n');
99
+ this.buffer = lines.pop() ?? '';
100
+ for (const line of lines) {
101
+ const trimmed = line.trim();
102
+ if (!trimmed)
103
+ continue;
104
+ let msg;
105
+ try {
106
+ msg = JSON.parse(trimmed);
107
+ }
108
+ catch {
109
+ continue; // Non-JSON line (some servers log to stdout) — ignore
110
+ }
111
+ if (msg && typeof msg === 'object')
112
+ this.onmessage?.(msg);
113
+ }
114
+ }
115
+ async send(message) {
116
+ const stdin = this.proc?.stdin;
117
+ if (this.lost || this.closing || !stdin || !stdin.writable) {
118
+ throw new Error(`Server "${this.config.id}" is not writable`);
119
+ }
120
+ stdin.write(JSON.stringify(message) + '\n');
121
+ }
122
+ async close() {
123
+ if (this.closing)
124
+ return;
125
+ this.closing = true;
126
+ const proc = this.proc;
127
+ if (!proc)
128
+ return;
129
+ const grace = this.options.killGraceMs;
130
+ const isRunning = () => proc.exitCode === null && proc.signalCode === null && proc.pid !== undefined;
131
+ const waitExit = (ms) => Promise.race([this.exited, new Promise((r) => setTimeout(r, ms).unref())]);
132
+ if (isRunning()) {
133
+ proc.stdin?.end();
134
+ await waitExit(Math.min(grace, 500));
135
+ }
136
+ if (isRunning()) {
137
+ proc.kill('SIGTERM');
138
+ await waitExit(grace);
139
+ }
140
+ if (isRunning()) {
141
+ proc.kill('SIGKILL');
142
+ await waitExit(grace);
143
+ }
144
+ proc.stdin?.destroy();
145
+ proc.stdout?.destroy();
146
+ proc.stderr?.destroy();
147
+ }
148
+ }
149
+ //# sourceMappingURL=stdio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/transport/stdio.ts"],"names":[],"mappings":"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"}