@winstonsayno/mcp-gateway 0.0.0-stage → 0.4.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 (118) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/LICENSE +21 -0
  3. package/README.md +432 -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 +276 -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 +47 -0
  27. package/dist/gateway/api.d.ts.map +1 -0
  28. package/dist/gateway/api.js +324 -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 +261 -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 +18 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +14 -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 +48 -0
  67. package/dist/registry/index.d.ts.map +1 -0
  68. package/dist/registry/index.js +171 -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/tool-filter.d.ts +24 -0
  107. package/dist/utils/tool-filter.d.ts.map +1 -0
  108. package/dist/utils/tool-filter.js +48 -0
  109. package/dist/utils/tool-filter.js.map +1 -0
  110. package/dist/utils/types.d.ts +223 -0
  111. package/dist/utils/types.d.ts.map +1 -0
  112. package/dist/utils/types.js +5 -0
  113. package/dist/utils/types.js.map +1 -0
  114. package/dist/utils/version.d.ts +6 -0
  115. package/dist/utils/version.d.ts.map +1 -0
  116. package/dist/utils/version.js +18 -0
  117. package/dist/utils/version.js.map +1 -0
  118. package/package.json +79 -4
@@ -0,0 +1,171 @@
1
+ /**
2
+ * MCP Server Registry
3
+ * Manages the lifecycle of all registered MCP servers
4
+ */
5
+ import { EventEmitter } from 'events';
6
+ import { logger } from '../utils/logger.js';
7
+ import { filterTools, isToolAllowed } from '../utils/tool-filter.js';
8
+ export class ServerRegistry extends EventEmitter {
9
+ healthCheckMs;
10
+ servers = new Map();
11
+ health = new Map();
12
+ tools = new Map(); // serverId -> tools
13
+ healthCheckInterval;
14
+ constructor(healthCheckMs = 30_000) {
15
+ super();
16
+ this.healthCheckMs = healthCheckMs;
17
+ }
18
+ // ─── Registration ───────────────────────────────────────────────────────────
19
+ register(config) {
20
+ if (this.servers.has(config.id)) {
21
+ logger.warn(`Server "${config.id}" is already registered. Overwriting.`);
22
+ }
23
+ this.servers.set(config.id, config);
24
+ this.health.set(config.id, {
25
+ serverId: config.id,
26
+ status: 'unknown',
27
+ lastChecked: new Date(),
28
+ });
29
+ logger.info(`Registered MCP server: ${config.id} (${config.name})`);
30
+ this.emit('registered', config);
31
+ }
32
+ unregister(serverId) {
33
+ if (!this.servers.has(serverId))
34
+ return false;
35
+ this.servers.delete(serverId);
36
+ this.health.delete(serverId);
37
+ this.tools.delete(serverId);
38
+ logger.info(`Unregistered MCP server: ${serverId}`);
39
+ this.emit('unregistered', serverId);
40
+ return true;
41
+ }
42
+ // ─── Queries ────────────────────────────────────────────────────────────────
43
+ getServer(id) {
44
+ return this.servers.get(id);
45
+ }
46
+ getAllServers() {
47
+ return Array.from(this.servers.values());
48
+ }
49
+ getEnabledServers() {
50
+ return this.getAllServers().filter((s) => s.enabled !== false);
51
+ }
52
+ getServersByTag(tag) {
53
+ return this.getAllServers().filter((s) => s.tags?.includes(tag));
54
+ }
55
+ getHealth(serverId) {
56
+ return this.health.get(serverId);
57
+ }
58
+ getAllHealth() {
59
+ return Array.from(this.health.values());
60
+ }
61
+ // ─── Tool Registry ──────────────────────────────────────────────────────────
62
+ /**
63
+ * Store a server's tool list, applying its `tools` allow/deny filter.
64
+ * Only exposed tools are kept, so discovery, routing and counts never see
65
+ * filtered ones.
66
+ */
67
+ setTools(serverId, all) {
68
+ const tools = filterTools(all, this.servers.get(serverId)?.tools);
69
+ if (tools.length !== all.length) {
70
+ logger.debug(`Server "${serverId}": ${all.length - tools.length} of ${all.length} tools hidden by its tools filter`);
71
+ }
72
+ for (const t of tools) {
73
+ const owners = this.findTools(t.name).filter((o) => o.serverId !== serverId);
74
+ if (owners.length > 0) {
75
+ logger.warn(`Tool "${t.name}" is exposed by multiple servers (${[serverId, ...owners.map((o) => o.serverId)].join(', ')}); ` +
76
+ 'callers must pass "server" to disambiguate');
77
+ }
78
+ }
79
+ this.tools.set(serverId, tools);
80
+ this.emit('tools-updated', serverId, tools);
81
+ }
82
+ /** Whether the server's `tools` filter lets `toolName` through. */
83
+ isToolExposed(serverId, toolName) {
84
+ return isToolAllowed(toolName, this.servers.get(serverId)?.tools);
85
+ }
86
+ getTools(serverId) {
87
+ return this.tools.get(serverId) ?? [];
88
+ }
89
+ getAllTools() {
90
+ return Array.from(this.tools.values()).flat();
91
+ }
92
+ /** Every server's entry for a tool name (more than one means the name is ambiguous). */
93
+ findTools(toolName) {
94
+ const found = [];
95
+ for (const tools of this.tools.values()) {
96
+ const t = tools.find((x) => x.name === toolName);
97
+ if (t)
98
+ found.push(t);
99
+ }
100
+ return found;
101
+ }
102
+ findTool(toolName) {
103
+ for (const tools of this.tools.values()) {
104
+ const found = tools.find((t) => t.name === toolName);
105
+ if (found)
106
+ return found;
107
+ }
108
+ return undefined;
109
+ }
110
+ // ─── Health Updates ─────────────────────────────────────────────────────────
111
+ updateHealth(serverId, status, latencyMs, errorMessage, extra = {}) {
112
+ if (!this.servers.has(serverId))
113
+ return; // never resurrect an unregistered server
114
+ const prev = this.health.get(serverId);
115
+ const updated = {
116
+ serverId,
117
+ status,
118
+ lastChecked: new Date(),
119
+ latencyMs,
120
+ errorMessage,
121
+ toolCount: this.tools.get(serverId)?.length,
122
+ connectedSince: 'connectedSince' in extra ? extra.connectedSince : prev?.connectedSince,
123
+ reconnect: 'reconnect' in extra ? extra.reconnect : prev?.reconnect,
124
+ };
125
+ if (!updated.connectedSince)
126
+ delete updated.connectedSince;
127
+ if (!updated.reconnect)
128
+ delete updated.reconnect;
129
+ this.health.set(serverId, updated);
130
+ if (prev?.status !== status) {
131
+ logger.info(`Server "${serverId}" status changed: ${prev?.status ?? 'unknown'} → ${status}`);
132
+ this.emit('health-changed', updated);
133
+ }
134
+ }
135
+ // ─── Lifecycle ──────────────────────────────────────────────────────────────
136
+ startHealthChecks(checkFn) {
137
+ this.stopHealthChecks(); // never stack multiple intervals
138
+ let running = false;
139
+ this.healthCheckInterval = setInterval(() => {
140
+ if (running)
141
+ return; // skip a tick rather than overlap slow checks
142
+ running = true;
143
+ void Promise.allSettled(this.getEnabledServers().map((server) => checkFn(server.id).catch((err) => {
144
+ logger.debug(`Health check failed for ${server.id}: ${String(err)}`);
145
+ }))).finally(() => {
146
+ running = false;
147
+ });
148
+ }, this.healthCheckMs);
149
+ this.healthCheckInterval.unref();
150
+ }
151
+ stopHealthChecks() {
152
+ if (this.healthCheckInterval) {
153
+ clearInterval(this.healthCheckInterval);
154
+ this.healthCheckInterval = undefined;
155
+ }
156
+ }
157
+ // ─── Summary ────────────────────────────────────────────────────────────────
158
+ getSummary() {
159
+ const allHealth = this.getAllHealth();
160
+ return {
161
+ total: allHealth.length,
162
+ online: allHealth.filter((h) => h.status === 'online').length,
163
+ offline: allHealth.filter((h) => h.status === 'offline').length,
164
+ degraded: allHealth.filter((h) => h.status === 'degraded').length,
165
+ reconnecting: allHealth.filter((h) => h.status === 'reconnecting').length,
166
+ unknown: allHealth.filter((h) => h.status === 'unknown').length,
167
+ totalTools: this.getAllTools().length,
168
+ };
169
+ }
170
+ }
171
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/registry/index.ts"],"names":[],"mappings":"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"}
@@ -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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"}