@debugelectron/debug-electron-mcp 1.6.10 → 1.7.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.
@@ -0,0 +1,1077 @@
1
+ "use strict";
2
+ exports.id = 86;
3
+ exports.ids = [86];
4
+ exports.modules = {
5
+
6
+ /***/ 86
7
+ (__unused_webpack_module, __webpack_exports__, __webpack_require__) {
8
+
9
+
10
+ // EXPORTS
11
+ __webpack_require__.d(__webpack_exports__, {
12
+ startHttpServer: () => (/* binding */ startHttpServer)
13
+ });
14
+
15
+ // EXTERNAL MODULE: external "node:crypto"
16
+ var external_node_crypto_ = __webpack_require__(598);
17
+ // EXTERNAL MODULE: external "node:http"
18
+ var external_node_http_ = __webpack_require__(67);
19
+ // EXTERNAL MODULE: external "@hono/node-server"
20
+ var node_server_ = __webpack_require__(373);
21
+ // EXTERNAL MODULE: ./node_modules/@modelcontextprotocol/sdk/dist/esm/types.js + 1 modules
22
+ var types = __webpack_require__(68);
23
+ ;// ./node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.js
24
+ /**
25
+ * Web Standards Streamable HTTP Server Transport
26
+ *
27
+ * This is the core transport implementation using Web Standard APIs (Request, Response, ReadableStream).
28
+ * It can run on any runtime that supports Web Standards: Node.js 18+, Cloudflare Workers, Deno, Bun, etc.
29
+ *
30
+ * For Node.js Express/HTTP compatibility, use `StreamableHTTPServerTransport` which wraps this transport.
31
+ */
32
+
33
+ /**
34
+ * Server transport for Web Standards Streamable HTTP: this implements the MCP Streamable HTTP transport specification
35
+ * using Web Standard APIs (Request, Response, ReadableStream).
36
+ *
37
+ * This transport works on any runtime that supports Web Standards: Node.js 18+, Cloudflare Workers, Deno, Bun, etc.
38
+ *
39
+ * Usage example:
40
+ *
41
+ * ```typescript
42
+ * // Stateful mode - server sets the session ID
43
+ * const statefulTransport = new WebStandardStreamableHTTPServerTransport({
44
+ * sessionIdGenerator: () => crypto.randomUUID(),
45
+ * });
46
+ *
47
+ * // Stateless mode - explicitly set session ID to undefined
48
+ * const statelessTransport = new WebStandardStreamableHTTPServerTransport({
49
+ * sessionIdGenerator: undefined,
50
+ * });
51
+ *
52
+ * // Hono.js usage
53
+ * app.all('/mcp', async (c) => {
54
+ * return transport.handleRequest(c.req.raw);
55
+ * });
56
+ *
57
+ * // Cloudflare Workers usage
58
+ * export default {
59
+ * async fetch(request: Request): Promise<Response> {
60
+ * return transport.handleRequest(request);
61
+ * }
62
+ * };
63
+ * ```
64
+ *
65
+ * In stateful mode:
66
+ * - Session ID is generated and included in response headers
67
+ * - Session ID is always included in initialization responses
68
+ * - Requests with invalid session IDs are rejected with 404 Not Found
69
+ * - Non-initialization requests without a session ID are rejected with 400 Bad Request
70
+ * - State is maintained in-memory (connections, message history)
71
+ *
72
+ * In stateless mode:
73
+ * - No Session ID is included in any responses
74
+ * - No session validation is performed
75
+ */
76
+ class WebStandardStreamableHTTPServerTransport {
77
+ constructor(options = {}) {
78
+ this._started = false;
79
+ this._hasHandledRequest = false;
80
+ this._streamMapping = new Map();
81
+ this._requestToStreamMapping = new Map();
82
+ this._requestResponseMap = new Map();
83
+ this._initialized = false;
84
+ this._enableJsonResponse = false;
85
+ this._standaloneSseStreamId = '_GET_stream';
86
+ this.sessionIdGenerator = options.sessionIdGenerator;
87
+ this._enableJsonResponse = options.enableJsonResponse ?? false;
88
+ this._eventStore = options.eventStore;
89
+ this._onsessioninitialized = options.onsessioninitialized;
90
+ this._onsessionclosed = options.onsessionclosed;
91
+ this._allowedHosts = options.allowedHosts;
92
+ this._allowedOrigins = options.allowedOrigins;
93
+ this._enableDnsRebindingProtection = options.enableDnsRebindingProtection ?? false;
94
+ this._retryInterval = options.retryInterval;
95
+ }
96
+ /**
97
+ * Starts the transport. This is required by the Transport interface but is a no-op
98
+ * for the Streamable HTTP transport as connections are managed per-request.
99
+ */
100
+ async start() {
101
+ if (this._started) {
102
+ throw new Error('Transport already started');
103
+ }
104
+ this._started = true;
105
+ }
106
+ /**
107
+ * Helper to create a JSON error response
108
+ */
109
+ createJsonErrorResponse(status, code, message, options) {
110
+ const error = { code, message };
111
+ if (options?.data !== undefined) {
112
+ error.data = options.data;
113
+ }
114
+ return new Response(JSON.stringify({
115
+ jsonrpc: '2.0',
116
+ error,
117
+ id: null
118
+ }), {
119
+ status,
120
+ headers: {
121
+ 'Content-Type': 'application/json',
122
+ ...options?.headers
123
+ }
124
+ });
125
+ }
126
+ /**
127
+ * Validates request headers for DNS rebinding protection.
128
+ * @returns Error response if validation fails, undefined if validation passes.
129
+ */
130
+ validateRequestHeaders(req) {
131
+ // Skip validation if protection is not enabled
132
+ if (!this._enableDnsRebindingProtection) {
133
+ return undefined;
134
+ }
135
+ // Validate Host header if allowedHosts is configured
136
+ if (this._allowedHosts && this._allowedHosts.length > 0) {
137
+ const hostHeader = req.headers.get('host');
138
+ if (!hostHeader || !this._allowedHosts.includes(hostHeader)) {
139
+ const error = `Invalid Host header: ${hostHeader}`;
140
+ this.onerror?.(new Error(error));
141
+ return this.createJsonErrorResponse(403, -32000, error);
142
+ }
143
+ }
144
+ // Validate Origin header if allowedOrigins is configured
145
+ if (this._allowedOrigins && this._allowedOrigins.length > 0) {
146
+ const originHeader = req.headers.get('origin');
147
+ if (originHeader && !this._allowedOrigins.includes(originHeader)) {
148
+ const error = `Invalid Origin header: ${originHeader}`;
149
+ this.onerror?.(new Error(error));
150
+ return this.createJsonErrorResponse(403, -32000, error);
151
+ }
152
+ }
153
+ return undefined;
154
+ }
155
+ /**
156
+ * Handles an incoming HTTP request, whether GET, POST, or DELETE
157
+ * Returns a Response object (Web Standard)
158
+ */
159
+ async handleRequest(req, options) {
160
+ // In stateless mode (no sessionIdGenerator), each request must use a fresh transport.
161
+ // Reusing a stateless transport causes message ID collisions between clients.
162
+ if (!this.sessionIdGenerator && this._hasHandledRequest) {
163
+ throw new Error('Stateless transport cannot be reused across requests. Create a new transport per request.');
164
+ }
165
+ this._hasHandledRequest = true;
166
+ // Validate request headers for DNS rebinding protection
167
+ const validationError = this.validateRequestHeaders(req);
168
+ if (validationError) {
169
+ return validationError;
170
+ }
171
+ switch (req.method) {
172
+ case 'POST':
173
+ return this.handlePostRequest(req, options);
174
+ case 'GET':
175
+ return this.handleGetRequest(req);
176
+ case 'DELETE':
177
+ return this.handleDeleteRequest(req);
178
+ default:
179
+ return this.handleUnsupportedRequest();
180
+ }
181
+ }
182
+ /**
183
+ * Writes a priming event to establish resumption capability.
184
+ * Only sends if eventStore is configured (opt-in for resumability) and
185
+ * the client's protocol version supports empty SSE data (>= 2025-11-25).
186
+ */
187
+ async writePrimingEvent(controller, encoder, streamId, protocolVersion) {
188
+ if (!this._eventStore) {
189
+ return;
190
+ }
191
+ // Priming events have empty data which older clients cannot handle.
192
+ // Only send priming events to clients with protocol version >= 2025-11-25
193
+ // which includes the fix for handling empty SSE data.
194
+ if (protocolVersion < '2025-11-25') {
195
+ return;
196
+ }
197
+ const primingEventId = await this._eventStore.storeEvent(streamId, {});
198
+ let primingEvent = `id: ${primingEventId}\ndata: \n\n`;
199
+ if (this._retryInterval !== undefined) {
200
+ primingEvent = `id: ${primingEventId}\nretry: ${this._retryInterval}\ndata: \n\n`;
201
+ }
202
+ controller.enqueue(encoder.encode(primingEvent));
203
+ }
204
+ /**
205
+ * Handles GET requests for SSE stream
206
+ */
207
+ async handleGetRequest(req) {
208
+ // The client MUST include an Accept header, listing text/event-stream as a supported content type.
209
+ const acceptHeader = req.headers.get('accept');
210
+ if (!acceptHeader?.includes('text/event-stream')) {
211
+ return this.createJsonErrorResponse(406, -32000, 'Not Acceptable: Client must accept text/event-stream');
212
+ }
213
+ // If an Mcp-Session-Id is returned by the server during initialization,
214
+ // clients using the Streamable HTTP transport MUST include it
215
+ // in the Mcp-Session-Id header on all of their subsequent HTTP requests.
216
+ const sessionError = this.validateSession(req);
217
+ if (sessionError) {
218
+ return sessionError;
219
+ }
220
+ const protocolError = this.validateProtocolVersion(req);
221
+ if (protocolError) {
222
+ return protocolError;
223
+ }
224
+ // Handle resumability: check for Last-Event-ID header
225
+ if (this._eventStore) {
226
+ const lastEventId = req.headers.get('last-event-id');
227
+ if (lastEventId) {
228
+ return this.replayEvents(lastEventId);
229
+ }
230
+ }
231
+ // Check if there's already an active standalone SSE stream for this session
232
+ if (this._streamMapping.get(this._standaloneSseStreamId) !== undefined) {
233
+ // Only one GET SSE stream is allowed per session
234
+ return this.createJsonErrorResponse(409, -32000, 'Conflict: Only one SSE stream is allowed per session');
235
+ }
236
+ const encoder = new TextEncoder();
237
+ let streamController;
238
+ // Create a ReadableStream with a controller we can use to push SSE events
239
+ const readable = new ReadableStream({
240
+ start: controller => {
241
+ streamController = controller;
242
+ },
243
+ cancel: () => {
244
+ // Stream was cancelled by client
245
+ this._streamMapping.delete(this._standaloneSseStreamId);
246
+ }
247
+ });
248
+ const headers = {
249
+ 'Content-Type': 'text/event-stream',
250
+ 'Cache-Control': 'no-cache, no-transform',
251
+ Connection: 'keep-alive'
252
+ };
253
+ // After initialization, always include the session ID if we have one
254
+ if (this.sessionId !== undefined) {
255
+ headers['mcp-session-id'] = this.sessionId;
256
+ }
257
+ // Store the stream mapping with the controller for pushing data
258
+ this._streamMapping.set(this._standaloneSseStreamId, {
259
+ controller: streamController,
260
+ encoder,
261
+ cleanup: () => {
262
+ this._streamMapping.delete(this._standaloneSseStreamId);
263
+ try {
264
+ streamController.close();
265
+ }
266
+ catch {
267
+ // Controller might already be closed
268
+ }
269
+ }
270
+ });
271
+ return new Response(readable, { headers });
272
+ }
273
+ /**
274
+ * Replays events that would have been sent after the specified event ID
275
+ * Only used when resumability is enabled
276
+ */
277
+ async replayEvents(lastEventId) {
278
+ if (!this._eventStore) {
279
+ return this.createJsonErrorResponse(400, -32000, 'Event store not configured');
280
+ }
281
+ try {
282
+ // If getStreamIdForEventId is available, use it for conflict checking
283
+ let streamId;
284
+ if (this._eventStore.getStreamIdForEventId) {
285
+ streamId = await this._eventStore.getStreamIdForEventId(lastEventId);
286
+ if (!streamId) {
287
+ return this.createJsonErrorResponse(400, -32000, 'Invalid event ID format');
288
+ }
289
+ // Check conflict with the SAME streamId we'll use for mapping
290
+ if (this._streamMapping.get(streamId) !== undefined) {
291
+ return this.createJsonErrorResponse(409, -32000, 'Conflict: Stream already has an active connection');
292
+ }
293
+ }
294
+ const headers = {
295
+ 'Content-Type': 'text/event-stream',
296
+ 'Cache-Control': 'no-cache, no-transform',
297
+ Connection: 'keep-alive'
298
+ };
299
+ if (this.sessionId !== undefined) {
300
+ headers['mcp-session-id'] = this.sessionId;
301
+ }
302
+ // Create a ReadableStream with controller for SSE
303
+ const encoder = new TextEncoder();
304
+ let streamController;
305
+ const readable = new ReadableStream({
306
+ start: controller => {
307
+ streamController = controller;
308
+ },
309
+ cancel: () => {
310
+ // Stream was cancelled by client
311
+ // Cleanup will be handled by the mapping
312
+ }
313
+ });
314
+ // Replay events - returns the streamId for backwards compatibility
315
+ const replayedStreamId = await this._eventStore.replayEventsAfter(lastEventId, {
316
+ send: async (eventId, message) => {
317
+ const success = this.writeSSEEvent(streamController, encoder, message, eventId);
318
+ if (!success) {
319
+ this.onerror?.(new Error('Failed replay events'));
320
+ try {
321
+ streamController.close();
322
+ }
323
+ catch {
324
+ // Controller might already be closed
325
+ }
326
+ }
327
+ }
328
+ });
329
+ this._streamMapping.set(replayedStreamId, {
330
+ controller: streamController,
331
+ encoder,
332
+ cleanup: () => {
333
+ this._streamMapping.delete(replayedStreamId);
334
+ try {
335
+ streamController.close();
336
+ }
337
+ catch {
338
+ // Controller might already be closed
339
+ }
340
+ }
341
+ });
342
+ return new Response(readable, { headers });
343
+ }
344
+ catch (error) {
345
+ this.onerror?.(error);
346
+ return this.createJsonErrorResponse(500, -32000, 'Error replaying events');
347
+ }
348
+ }
349
+ /**
350
+ * Writes an event to an SSE stream via controller with proper formatting
351
+ */
352
+ writeSSEEvent(controller, encoder, message, eventId) {
353
+ try {
354
+ let eventData = `event: message\n`;
355
+ // Include event ID if provided - this is important for resumability
356
+ if (eventId) {
357
+ eventData += `id: ${eventId}\n`;
358
+ }
359
+ eventData += `data: ${JSON.stringify(message)}\n\n`;
360
+ controller.enqueue(encoder.encode(eventData));
361
+ return true;
362
+ }
363
+ catch {
364
+ return false;
365
+ }
366
+ }
367
+ /**
368
+ * Handles unsupported requests (PUT, PATCH, etc.)
369
+ */
370
+ handleUnsupportedRequest() {
371
+ return new Response(JSON.stringify({
372
+ jsonrpc: '2.0',
373
+ error: {
374
+ code: -32000,
375
+ message: 'Method not allowed.'
376
+ },
377
+ id: null
378
+ }), {
379
+ status: 405,
380
+ headers: {
381
+ Allow: 'GET, POST, DELETE',
382
+ 'Content-Type': 'application/json'
383
+ }
384
+ });
385
+ }
386
+ /**
387
+ * Handles POST requests containing JSON-RPC messages
388
+ */
389
+ async handlePostRequest(req, options) {
390
+ try {
391
+ // Validate the Accept header
392
+ const acceptHeader = req.headers.get('accept');
393
+ // The client MUST include an Accept header, listing both application/json and text/event-stream as supported content types.
394
+ if (!acceptHeader?.includes('application/json') || !acceptHeader.includes('text/event-stream')) {
395
+ return this.createJsonErrorResponse(406, -32000, 'Not Acceptable: Client must accept both application/json and text/event-stream');
396
+ }
397
+ const ct = req.headers.get('content-type');
398
+ if (!ct || !ct.includes('application/json')) {
399
+ return this.createJsonErrorResponse(415, -32000, 'Unsupported Media Type: Content-Type must be application/json');
400
+ }
401
+ // Build request info from headers
402
+ const requestInfo = {
403
+ headers: Object.fromEntries(req.headers.entries())
404
+ };
405
+ let rawMessage;
406
+ if (options?.parsedBody !== undefined) {
407
+ rawMessage = options.parsedBody;
408
+ }
409
+ else {
410
+ try {
411
+ rawMessage = await req.json();
412
+ }
413
+ catch {
414
+ return this.createJsonErrorResponse(400, -32700, 'Parse error: Invalid JSON');
415
+ }
416
+ }
417
+ let messages;
418
+ // handle batch and single messages
419
+ try {
420
+ if (Array.isArray(rawMessage)) {
421
+ messages = rawMessage.map(msg => types/* JSONRPCMessageSchema */.OR.parse(msg));
422
+ }
423
+ else {
424
+ messages = [types/* JSONRPCMessageSchema */.OR.parse(rawMessage)];
425
+ }
426
+ }
427
+ catch {
428
+ return this.createJsonErrorResponse(400, -32700, 'Parse error: Invalid JSON-RPC message');
429
+ }
430
+ // Check if this is an initialization request
431
+ // https://spec.modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle/
432
+ const isInitializationRequest = messages.some(types/* isInitializeRequest */.eK);
433
+ if (isInitializationRequest) {
434
+ // If it's a server with session management and the session ID is already set we should reject the request
435
+ // to avoid re-initialization.
436
+ if (this._initialized && this.sessionId !== undefined) {
437
+ return this.createJsonErrorResponse(400, -32600, 'Invalid Request: Server already initialized');
438
+ }
439
+ if (messages.length > 1) {
440
+ return this.createJsonErrorResponse(400, -32600, 'Invalid Request: Only one initialization request is allowed');
441
+ }
442
+ this.sessionId = this.sessionIdGenerator?.();
443
+ this._initialized = true;
444
+ // If we have a session ID and an onsessioninitialized handler, call it immediately
445
+ // This is needed in cases where the server needs to keep track of multiple sessions
446
+ if (this.sessionId && this._onsessioninitialized) {
447
+ await Promise.resolve(this._onsessioninitialized(this.sessionId));
448
+ }
449
+ }
450
+ if (!isInitializationRequest) {
451
+ // If an Mcp-Session-Id is returned by the server during initialization,
452
+ // clients using the Streamable HTTP transport MUST include it
453
+ // in the Mcp-Session-Id header on all of their subsequent HTTP requests.
454
+ const sessionError = this.validateSession(req);
455
+ if (sessionError) {
456
+ return sessionError;
457
+ }
458
+ // Mcp-Protocol-Version header is required for all requests after initialization.
459
+ const protocolError = this.validateProtocolVersion(req);
460
+ if (protocolError) {
461
+ return protocolError;
462
+ }
463
+ }
464
+ // check if it contains requests
465
+ const hasRequests = messages.some(types/* isJSONRPCRequest */.vo);
466
+ if (!hasRequests) {
467
+ // if it only contains notifications or responses, return 202
468
+ for (const message of messages) {
469
+ this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo });
470
+ }
471
+ return new Response(null, { status: 202 });
472
+ }
473
+ // The default behavior is to use SSE streaming
474
+ // but in some cases server will return JSON responses
475
+ const streamId = crypto.randomUUID();
476
+ // Extract protocol version for priming event decision.
477
+ // For initialize requests, get from request params.
478
+ // For other requests, get from header (already validated).
479
+ const initRequest = messages.find(m => (0,types/* isInitializeRequest */.eK)(m));
480
+ const clientProtocolVersion = initRequest
481
+ ? initRequest.params.protocolVersion
482
+ : (req.headers.get('mcp-protocol-version') ?? types/* DEFAULT_NEGOTIATED_PROTOCOL_VERSION */.PC);
483
+ if (this._enableJsonResponse) {
484
+ // For JSON response mode, return a Promise that resolves when all responses are ready
485
+ return new Promise(resolve => {
486
+ this._streamMapping.set(streamId, {
487
+ resolveJson: resolve,
488
+ cleanup: () => {
489
+ this._streamMapping.delete(streamId);
490
+ }
491
+ });
492
+ for (const message of messages) {
493
+ if ((0,types/* isJSONRPCRequest */.vo)(message)) {
494
+ this._requestToStreamMapping.set(message.id, streamId);
495
+ }
496
+ }
497
+ for (const message of messages) {
498
+ this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo });
499
+ }
500
+ });
501
+ }
502
+ // SSE streaming mode - use ReadableStream with controller for more reliable data pushing
503
+ const encoder = new TextEncoder();
504
+ let streamController;
505
+ const readable = new ReadableStream({
506
+ start: controller => {
507
+ streamController = controller;
508
+ },
509
+ cancel: () => {
510
+ // Stream was cancelled by client
511
+ this._streamMapping.delete(streamId);
512
+ }
513
+ });
514
+ const headers = {
515
+ 'Content-Type': 'text/event-stream',
516
+ 'Cache-Control': 'no-cache',
517
+ Connection: 'keep-alive'
518
+ };
519
+ // After initialization, always include the session ID if we have one
520
+ if (this.sessionId !== undefined) {
521
+ headers['mcp-session-id'] = this.sessionId;
522
+ }
523
+ // Store the response for this request to send messages back through this connection
524
+ // We need to track by request ID to maintain the connection
525
+ for (const message of messages) {
526
+ if ((0,types/* isJSONRPCRequest */.vo)(message)) {
527
+ this._streamMapping.set(streamId, {
528
+ controller: streamController,
529
+ encoder,
530
+ cleanup: () => {
531
+ this._streamMapping.delete(streamId);
532
+ try {
533
+ streamController.close();
534
+ }
535
+ catch {
536
+ // Controller might already be closed
537
+ }
538
+ }
539
+ });
540
+ this._requestToStreamMapping.set(message.id, streamId);
541
+ }
542
+ }
543
+ // Write priming event if event store is configured (after mapping is set up)
544
+ await this.writePrimingEvent(streamController, encoder, streamId, clientProtocolVersion);
545
+ // handle each message
546
+ for (const message of messages) {
547
+ // Build closeSSEStream callback for requests when eventStore is configured
548
+ // AND client supports resumability (protocol version >= 2025-11-25).
549
+ // Old clients can't resume if the stream is closed early because they
550
+ // didn't receive a priming event with an event ID.
551
+ let closeSSEStream;
552
+ let closeStandaloneSSEStream;
553
+ if ((0,types/* isJSONRPCRequest */.vo)(message) && this._eventStore && clientProtocolVersion >= '2025-11-25') {
554
+ closeSSEStream = () => {
555
+ this.closeSSEStream(message.id);
556
+ };
557
+ closeStandaloneSSEStream = () => {
558
+ this.closeStandaloneSSEStream();
559
+ };
560
+ }
561
+ this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo, closeSSEStream, closeStandaloneSSEStream });
562
+ }
563
+ // The server SHOULD NOT close the SSE stream before sending all JSON-RPC responses
564
+ // This will be handled by the send() method when responses are ready
565
+ return new Response(readable, { status: 200, headers });
566
+ }
567
+ catch (error) {
568
+ // return JSON-RPC formatted error
569
+ this.onerror?.(error);
570
+ return this.createJsonErrorResponse(400, -32700, 'Parse error', { data: String(error) });
571
+ }
572
+ }
573
+ /**
574
+ * Handles DELETE requests to terminate sessions
575
+ */
576
+ async handleDeleteRequest(req) {
577
+ const sessionError = this.validateSession(req);
578
+ if (sessionError) {
579
+ return sessionError;
580
+ }
581
+ const protocolError = this.validateProtocolVersion(req);
582
+ if (protocolError) {
583
+ return protocolError;
584
+ }
585
+ await Promise.resolve(this._onsessionclosed?.(this.sessionId));
586
+ await this.close();
587
+ return new Response(null, { status: 200 });
588
+ }
589
+ /**
590
+ * Validates session ID for non-initialization requests.
591
+ * Returns Response error if invalid, undefined otherwise
592
+ */
593
+ validateSession(req) {
594
+ if (this.sessionIdGenerator === undefined) {
595
+ // If the sessionIdGenerator ID is not set, the session management is disabled
596
+ // and we don't need to validate the session ID
597
+ return undefined;
598
+ }
599
+ if (!this._initialized) {
600
+ // If the server has not been initialized yet, reject all requests
601
+ return this.createJsonErrorResponse(400, -32000, 'Bad Request: Server not initialized');
602
+ }
603
+ const sessionId = req.headers.get('mcp-session-id');
604
+ if (!sessionId) {
605
+ // Non-initialization requests without a session ID should return 400 Bad Request
606
+ return this.createJsonErrorResponse(400, -32000, 'Bad Request: Mcp-Session-Id header is required');
607
+ }
608
+ if (sessionId !== this.sessionId) {
609
+ // Reject requests with invalid session ID with 404 Not Found
610
+ return this.createJsonErrorResponse(404, -32001, 'Session not found');
611
+ }
612
+ return undefined;
613
+ }
614
+ /**
615
+ * Validates the MCP-Protocol-Version header on incoming requests.
616
+ *
617
+ * For initialization: Version negotiation handles unknown versions gracefully
618
+ * (server responds with its supported version).
619
+ *
620
+ * For subsequent requests with MCP-Protocol-Version header:
621
+ * - Accept if in supported list
622
+ * - 400 if unsupported
623
+ *
624
+ * For HTTP requests without the MCP-Protocol-Version header:
625
+ * - Accept and default to the version negotiated at initialization
626
+ */
627
+ validateProtocolVersion(req) {
628
+ const protocolVersion = req.headers.get('mcp-protocol-version');
629
+ if (protocolVersion !== null && !types/* SUPPORTED_PROTOCOL_VERSIONS */.Iu.includes(protocolVersion)) {
630
+ return this.createJsonErrorResponse(400, -32000, `Bad Request: Unsupported protocol version: ${protocolVersion} (supported versions: ${types/* SUPPORTED_PROTOCOL_VERSIONS */.Iu.join(', ')})`);
631
+ }
632
+ return undefined;
633
+ }
634
+ async close() {
635
+ // Close all SSE connections
636
+ this._streamMapping.forEach(({ cleanup }) => {
637
+ cleanup();
638
+ });
639
+ this._streamMapping.clear();
640
+ // Clear any pending responses
641
+ this._requestResponseMap.clear();
642
+ this.onclose?.();
643
+ }
644
+ /**
645
+ * Close an SSE stream for a specific request, triggering client reconnection.
646
+ * Use this to implement polling behavior during long-running operations -
647
+ * client will reconnect after the retry interval specified in the priming event.
648
+ */
649
+ closeSSEStream(requestId) {
650
+ const streamId = this._requestToStreamMapping.get(requestId);
651
+ if (!streamId)
652
+ return;
653
+ const stream = this._streamMapping.get(streamId);
654
+ if (stream) {
655
+ stream.cleanup();
656
+ }
657
+ }
658
+ /**
659
+ * Close the standalone GET SSE stream, triggering client reconnection.
660
+ * Use this to implement polling behavior for server-initiated notifications.
661
+ */
662
+ closeStandaloneSSEStream() {
663
+ const stream = this._streamMapping.get(this._standaloneSseStreamId);
664
+ if (stream) {
665
+ stream.cleanup();
666
+ }
667
+ }
668
+ async send(message, options) {
669
+ let requestId = options?.relatedRequestId;
670
+ if ((0,types/* isJSONRPCResultResponse */.ig)(message) || (0,types/* isJSONRPCErrorResponse */.LW)(message)) {
671
+ // If the message is a response, use the request ID from the message
672
+ requestId = message.id;
673
+ }
674
+ // Check if this message should be sent on the standalone SSE stream (no request ID)
675
+ // Ignore notifications from tools (which have relatedRequestId set)
676
+ // Those will be sent via dedicated response SSE streams
677
+ if (requestId === undefined) {
678
+ // For standalone SSE streams, we can only send requests and notifications
679
+ if ((0,types/* isJSONRPCResultResponse */.ig)(message) || (0,types/* isJSONRPCErrorResponse */.LW)(message)) {
680
+ throw new Error('Cannot send a response on a standalone SSE stream unless resuming a previous client request');
681
+ }
682
+ // Generate and store event ID if event store is provided
683
+ // Store even if stream is disconnected so events can be replayed on reconnect
684
+ let eventId;
685
+ if (this._eventStore) {
686
+ // Stores the event and gets the generated event ID
687
+ eventId = await this._eventStore.storeEvent(this._standaloneSseStreamId, message);
688
+ }
689
+ const standaloneSse = this._streamMapping.get(this._standaloneSseStreamId);
690
+ if (standaloneSse === undefined) {
691
+ // Stream is disconnected - event is stored for replay, nothing more to do
692
+ return;
693
+ }
694
+ // Send the message to the standalone SSE stream
695
+ if (standaloneSse.controller && standaloneSse.encoder) {
696
+ this.writeSSEEvent(standaloneSse.controller, standaloneSse.encoder, message, eventId);
697
+ }
698
+ return;
699
+ }
700
+ // Get the response for this request
701
+ const streamId = this._requestToStreamMapping.get(requestId);
702
+ if (!streamId) {
703
+ throw new Error(`No connection established for request ID: ${String(requestId)}`);
704
+ }
705
+ const stream = this._streamMapping.get(streamId);
706
+ if (!this._enableJsonResponse && stream?.controller && stream?.encoder) {
707
+ // For SSE responses, generate event ID if event store is provided
708
+ let eventId;
709
+ if (this._eventStore) {
710
+ eventId = await this._eventStore.storeEvent(streamId, message);
711
+ }
712
+ // Write the event to the response stream
713
+ this.writeSSEEvent(stream.controller, stream.encoder, message, eventId);
714
+ }
715
+ if ((0,types/* isJSONRPCResultResponse */.ig)(message) || (0,types/* isJSONRPCErrorResponse */.LW)(message)) {
716
+ this._requestResponseMap.set(requestId, message);
717
+ const relatedIds = Array.from(this._requestToStreamMapping.entries())
718
+ .filter(([_, sid]) => sid === streamId)
719
+ .map(([id]) => id);
720
+ // Check if we have responses for all requests using this connection
721
+ const allResponsesReady = relatedIds.every(id => this._requestResponseMap.has(id));
722
+ if (allResponsesReady) {
723
+ if (!stream) {
724
+ throw new Error(`No connection established for request ID: ${String(requestId)}`);
725
+ }
726
+ if (this._enableJsonResponse && stream.resolveJson) {
727
+ // All responses ready, send as JSON
728
+ const headers = {
729
+ 'Content-Type': 'application/json'
730
+ };
731
+ if (this.sessionId !== undefined) {
732
+ headers['mcp-session-id'] = this.sessionId;
733
+ }
734
+ const responses = relatedIds.map(id => this._requestResponseMap.get(id));
735
+ if (responses.length === 1) {
736
+ stream.resolveJson(new Response(JSON.stringify(responses[0]), { status: 200, headers }));
737
+ }
738
+ else {
739
+ stream.resolveJson(new Response(JSON.stringify(responses), { status: 200, headers }));
740
+ }
741
+ }
742
+ else {
743
+ // End the SSE stream
744
+ stream.cleanup();
745
+ }
746
+ // Clean up
747
+ for (const id of relatedIds) {
748
+ this._requestResponseMap.delete(id);
749
+ this._requestToStreamMapping.delete(id);
750
+ }
751
+ }
752
+ }
753
+ }
754
+ }
755
+ //# sourceMappingURL=webStandardStreamableHttp.js.map
756
+ ;// ./node_modules/@modelcontextprotocol/sdk/dist/esm/server/streamableHttp.js
757
+ /**
758
+ * Node.js HTTP Streamable HTTP Server Transport
759
+ *
760
+ * This is a thin wrapper around `WebStandardStreamableHTTPServerTransport` that provides
761
+ * compatibility with Node.js HTTP server (IncomingMessage/ServerResponse).
762
+ *
763
+ * For web-standard environments (Cloudflare Workers, Deno, Bun), use `WebStandardStreamableHTTPServerTransport` directly.
764
+ */
765
+
766
+
767
+ /**
768
+ * Server transport for Streamable HTTP: this implements the MCP Streamable HTTP transport specification.
769
+ * It supports both SSE streaming and direct HTTP responses.
770
+ *
771
+ * This is a wrapper around `WebStandardStreamableHTTPServerTransport` that provides Node.js HTTP compatibility.
772
+ * It uses the `@hono/node-server` library to convert between Node.js HTTP and Web Standard APIs.
773
+ *
774
+ * Usage example:
775
+ *
776
+ * ```typescript
777
+ * // Stateful mode - server sets the session ID
778
+ * const statefulTransport = new StreamableHTTPServerTransport({
779
+ * sessionIdGenerator: () => randomUUID(),
780
+ * });
781
+ *
782
+ * // Stateless mode - explicitly set session ID to undefined
783
+ * const statelessTransport = new StreamableHTTPServerTransport({
784
+ * sessionIdGenerator: undefined,
785
+ * });
786
+ *
787
+ * // Using with pre-parsed request body
788
+ * app.post('/mcp', (req, res) => {
789
+ * transport.handleRequest(req, res, req.body);
790
+ * });
791
+ * ```
792
+ *
793
+ * In stateful mode:
794
+ * - Session ID is generated and included in response headers
795
+ * - Session ID is always included in initialization responses
796
+ * - Requests with invalid session IDs are rejected with 404 Not Found
797
+ * - Non-initialization requests without a session ID are rejected with 400 Bad Request
798
+ * - State is maintained in-memory (connections, message history)
799
+ *
800
+ * In stateless mode:
801
+ * - No Session ID is included in any responses
802
+ * - No session validation is performed
803
+ */
804
+ class StreamableHTTPServerTransport {
805
+ constructor(options = {}) {
806
+ // Store auth and parsedBody per request for passing through to handleRequest
807
+ this._requestContext = new WeakMap();
808
+ this._webStandardTransport = new WebStandardStreamableHTTPServerTransport(options);
809
+ // Create a request listener that wraps the web standard transport
810
+ // getRequestListener converts Node.js HTTP to Web Standard and properly handles SSE streaming
811
+ // overrideGlobalObjects: false prevents Hono from overwriting global Response, which would
812
+ // break frameworks like Next.js whose response classes extend the native Response
813
+ this._requestListener = (0,node_server_.getRequestListener)(async (webRequest) => {
814
+ // Get context if available (set during handleRequest)
815
+ const context = this._requestContext.get(webRequest);
816
+ return this._webStandardTransport.handleRequest(webRequest, {
817
+ authInfo: context?.authInfo,
818
+ parsedBody: context?.parsedBody
819
+ });
820
+ }, { overrideGlobalObjects: false });
821
+ }
822
+ /**
823
+ * Gets the session ID for this transport instance.
824
+ */
825
+ get sessionId() {
826
+ return this._webStandardTransport.sessionId;
827
+ }
828
+ /**
829
+ * Sets callback for when the transport is closed.
830
+ */
831
+ set onclose(handler) {
832
+ this._webStandardTransport.onclose = handler;
833
+ }
834
+ get onclose() {
835
+ return this._webStandardTransport.onclose;
836
+ }
837
+ /**
838
+ * Sets callback for transport errors.
839
+ */
840
+ set onerror(handler) {
841
+ this._webStandardTransport.onerror = handler;
842
+ }
843
+ get onerror() {
844
+ return this._webStandardTransport.onerror;
845
+ }
846
+ /**
847
+ * Sets callback for incoming messages.
848
+ */
849
+ set onmessage(handler) {
850
+ this._webStandardTransport.onmessage = handler;
851
+ }
852
+ get onmessage() {
853
+ return this._webStandardTransport.onmessage;
854
+ }
855
+ /**
856
+ * Starts the transport. This is required by the Transport interface but is a no-op
857
+ * for the Streamable HTTP transport as connections are managed per-request.
858
+ */
859
+ async start() {
860
+ return this._webStandardTransport.start();
861
+ }
862
+ /**
863
+ * Closes the transport and all active connections.
864
+ */
865
+ async close() {
866
+ return this._webStandardTransport.close();
867
+ }
868
+ /**
869
+ * Sends a JSON-RPC message through the transport.
870
+ */
871
+ async send(message, options) {
872
+ return this._webStandardTransport.send(message, options);
873
+ }
874
+ /**
875
+ * Handles an incoming HTTP request, whether GET or POST.
876
+ *
877
+ * This method converts Node.js HTTP objects to Web Standard Request/Response
878
+ * and delegates to the underlying WebStandardStreamableHTTPServerTransport.
879
+ *
880
+ * @param req - Node.js IncomingMessage, optionally with auth property from middleware
881
+ * @param res - Node.js ServerResponse
882
+ * @param parsedBody - Optional pre-parsed body from body-parser middleware
883
+ */
884
+ async handleRequest(req, res, parsedBody) {
885
+ // Store context for this request to pass through auth and parsedBody
886
+ // We need to intercept the request creation to attach this context
887
+ const authInfo = req.auth;
888
+ // Create a custom handler that includes our context
889
+ // overrideGlobalObjects: false prevents Hono from overwriting global Response, which would
890
+ // break frameworks like Next.js whose response classes extend the native Response
891
+ const handler = (0,node_server_.getRequestListener)(async (webRequest) => {
892
+ return this._webStandardTransport.handleRequest(webRequest, {
893
+ authInfo,
894
+ parsedBody
895
+ });
896
+ }, { overrideGlobalObjects: false });
897
+ // Delegate to the request listener which handles all the Node.js <-> Web Standard conversion
898
+ // including proper SSE streaming support
899
+ await handler(req, res);
900
+ }
901
+ /**
902
+ * Close an SSE stream for a specific request, triggering client reconnection.
903
+ * Use this to implement polling behavior during long-running operations -
904
+ * client will reconnect after the retry interval specified in the priming event.
905
+ */
906
+ closeSSEStream(requestId) {
907
+ this._webStandardTransport.closeSSEStream(requestId);
908
+ }
909
+ /**
910
+ * Close the standalone GET SSE stream, triggering client reconnection.
911
+ * Use this to implement polling behavior for server-initiated notifications.
912
+ */
913
+ closeStandaloneSSEStream() {
914
+ this._webStandardTransport.closeStandaloneSSEStream();
915
+ }
916
+ }
917
+ //# sourceMappingURL=streamableHttp.js.map
918
+ // EXTERNAL MODULE: ./src/create-server.ts + 25 modules
919
+ var create_server = __webpack_require__(146);
920
+ // EXTERNAL MODULE: ./src/utils/logger.ts
921
+ var logger = __webpack_require__(628);
922
+ // EXTERNAL MODULE: ./src/tools.ts
923
+ var tools = __webpack_require__(623);
924
+ ;// ./src/serve.ts
925
+
926
+
927
+
928
+
929
+
930
+
931
+
932
+ /** Read the full request body as a string */
933
+ function readBody(req) {
934
+ return new Promise((resolve, reject) => {
935
+ const chunks = [];
936
+ req.on('data', (chunk) => chunks.push(chunk));
937
+ req.on('end', () => resolve(Buffer.concat(chunks).toString()));
938
+ req.on('error', reject);
939
+ });
940
+ }
941
+ /** Send a JSON response */
942
+ function sendJson(res, status, data) {
943
+ const body = JSON.stringify(data);
944
+ res.writeHead(status, {
945
+ 'Content-Type': 'application/json',
946
+ 'Content-Length': Buffer.byteLength(body),
947
+ });
948
+ res.end(body);
949
+ }
950
+ /**
951
+ * Start the MCP server in HTTP mode using StreamableHTTPServerTransport.
952
+ * Uses only Node built-in http — no express or other dependencies.
953
+ * Supports multiple concurrent AI client sessions.
954
+ */
955
+ async function startHttpServer(port = 3100) {
956
+ // Map of session ID -> transport
957
+ const transports = {};
958
+ const server = (0,external_node_http_.createServer)(async (req, res) => {
959
+ const url = new URL(req.url || '/', `http://localhost:${port}`);
960
+ const path = url.pathname;
961
+ // Health check
962
+ if (path === '/health' && req.method === 'GET') {
963
+ sendJson(res, 200, {
964
+ status: 'ok',
965
+ name: '@debugelectron/debug-electron-mcp',
966
+ mode: 'http',
967
+ activeSessions: Object.keys(transports).length,
968
+ tools: tools/* tools */.Y.map((t) => t.name),
969
+ });
970
+ return;
971
+ }
972
+ // Only handle /mcp
973
+ if (path !== '/mcp') {
974
+ sendJson(res, 404, { error: 'Not found' });
975
+ return;
976
+ }
977
+ const sessionId = req.headers['mcp-session-id'];
978
+ try {
979
+ if (req.method === 'POST') {
980
+ const bodyStr = await readBody(req);
981
+ const body = bodyStr ? JSON.parse(bodyStr) : undefined;
982
+ let transport;
983
+ if (sessionId && transports[sessionId]) {
984
+ transport = transports[sessionId];
985
+ }
986
+ else if (!sessionId && (0,types/* isInitializeRequest */.eK)(body)) {
987
+ // New initialization request
988
+ transport = new StreamableHTTPServerTransport({
989
+ sessionIdGenerator: () => (0,external_node_crypto_.randomUUID)(),
990
+ onsessioninitialized: (sid) => {
991
+ logger/* logger */.vF.info(`HTTP session initialized: ${sid}`);
992
+ transports[sid] = transport;
993
+ },
994
+ });
995
+ transport.onclose = () => {
996
+ const sid = transport.sessionId;
997
+ if (sid && transports[sid]) {
998
+ logger/* logger */.vF.info(`HTTP session closed: ${sid}`);
999
+ delete transports[sid];
1000
+ }
1001
+ };
1002
+ const mcpServer = (0,create_server/* createMcpServer */.c)();
1003
+ await mcpServer.connect(transport);
1004
+ await transport.handleRequest(req, res, body);
1005
+ return;
1006
+ }
1007
+ else {
1008
+ sendJson(res, 400, {
1009
+ jsonrpc: '2.0',
1010
+ error: { code: -32000, message: 'Bad Request: No valid session ID provided' },
1011
+ id: null,
1012
+ });
1013
+ return;
1014
+ }
1015
+ await transport.handleRequest(req, res, body);
1016
+ }
1017
+ else if (req.method === 'GET') {
1018
+ if (!sessionId || !transports[sessionId]) {
1019
+ res.writeHead(400);
1020
+ res.end('Invalid or missing session ID');
1021
+ return;
1022
+ }
1023
+ await transports[sessionId].handleRequest(req, res);
1024
+ }
1025
+ else if (req.method === 'DELETE') {
1026
+ if (!sessionId || !transports[sessionId]) {
1027
+ res.writeHead(400);
1028
+ res.end('Invalid or missing session ID');
1029
+ return;
1030
+ }
1031
+ await transports[sessionId].handleRequest(req, res);
1032
+ }
1033
+ else {
1034
+ res.writeHead(405);
1035
+ res.end('Method not allowed');
1036
+ }
1037
+ }
1038
+ catch (error) {
1039
+ logger/* logger */.vF.error('Error handling MCP request:', error);
1040
+ if (!res.headersSent) {
1041
+ sendJson(res, 500, {
1042
+ jsonrpc: '2.0',
1043
+ error: { code: -32603, message: 'Internal server error' },
1044
+ id: null,
1045
+ });
1046
+ }
1047
+ }
1048
+ });
1049
+ server.listen(port, '127.0.0.1', () => {
1050
+ logger/* logger */.vF.info(`Electron MCP HTTP Server listening on http://127.0.0.1:${port}`);
1051
+ logger/* logger */.vF.info(`MCP endpoint: http://localhost:${port}/mcp`);
1052
+ logger/* logger */.vF.info(`Health check: http://localhost:${port}/health`);
1053
+ logger/* logger */.vF.info('Available tools:', tools/* tools */.Y.map((t) => t.name).join(', '));
1054
+ });
1055
+ // Handle shutdown
1056
+ process.on('SIGINT', async () => {
1057
+ logger/* logger */.vF.info('Shutting down HTTP server...');
1058
+ for (const sid in transports) {
1059
+ try {
1060
+ await transports[sid].close();
1061
+ delete transports[sid];
1062
+ }
1063
+ catch (error) {
1064
+ logger/* logger */.vF.error(`Error closing session ${sid}:`, error);
1065
+ }
1066
+ }
1067
+ server.close();
1068
+ process.exit(0);
1069
+ });
1070
+ }
1071
+
1072
+
1073
+ /***/ }
1074
+
1075
+ };
1076
+ ;
1077
+ //# sourceMappingURL=86.index.js.map