@mcp-ts/sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +297 -0
  3. package/dist/adapters/agui-adapter.d.mts +119 -0
  4. package/dist/adapters/agui-adapter.d.ts +119 -0
  5. package/dist/adapters/agui-adapter.js +109 -0
  6. package/dist/adapters/agui-adapter.js.map +1 -0
  7. package/dist/adapters/agui-adapter.mjs +107 -0
  8. package/dist/adapters/agui-adapter.mjs.map +1 -0
  9. package/dist/adapters/agui-middleware.d.mts +171 -0
  10. package/dist/adapters/agui-middleware.d.ts +171 -0
  11. package/dist/adapters/agui-middleware.js +429 -0
  12. package/dist/adapters/agui-middleware.js.map +1 -0
  13. package/dist/adapters/agui-middleware.mjs +417 -0
  14. package/dist/adapters/agui-middleware.mjs.map +1 -0
  15. package/dist/adapters/ai-adapter.d.mts +38 -0
  16. package/dist/adapters/ai-adapter.d.ts +38 -0
  17. package/dist/adapters/ai-adapter.js +82 -0
  18. package/dist/adapters/ai-adapter.js.map +1 -0
  19. package/dist/adapters/ai-adapter.mjs +80 -0
  20. package/dist/adapters/ai-adapter.mjs.map +1 -0
  21. package/dist/adapters/langchain-adapter.d.mts +46 -0
  22. package/dist/adapters/langchain-adapter.d.ts +46 -0
  23. package/dist/adapters/langchain-adapter.js +102 -0
  24. package/dist/adapters/langchain-adapter.js.map +1 -0
  25. package/dist/adapters/langchain-adapter.mjs +100 -0
  26. package/dist/adapters/langchain-adapter.mjs.map +1 -0
  27. package/dist/adapters/mastra-adapter.d.mts +49 -0
  28. package/dist/adapters/mastra-adapter.d.ts +49 -0
  29. package/dist/adapters/mastra-adapter.js +95 -0
  30. package/dist/adapters/mastra-adapter.js.map +1 -0
  31. package/dist/adapters/mastra-adapter.mjs +93 -0
  32. package/dist/adapters/mastra-adapter.mjs.map +1 -0
  33. package/dist/client/index.d.mts +119 -0
  34. package/dist/client/index.d.ts +119 -0
  35. package/dist/client/index.js +225 -0
  36. package/dist/client/index.js.map +1 -0
  37. package/dist/client/index.mjs +223 -0
  38. package/dist/client/index.mjs.map +1 -0
  39. package/dist/client/react.d.mts +151 -0
  40. package/dist/client/react.d.ts +151 -0
  41. package/dist/client/react.js +492 -0
  42. package/dist/client/react.js.map +1 -0
  43. package/dist/client/react.mjs +489 -0
  44. package/dist/client/react.mjs.map +1 -0
  45. package/dist/client/vue.d.mts +157 -0
  46. package/dist/client/vue.d.ts +157 -0
  47. package/dist/client/vue.js +474 -0
  48. package/dist/client/vue.js.map +1 -0
  49. package/dist/client/vue.mjs +471 -0
  50. package/dist/client/vue.mjs.map +1 -0
  51. package/dist/events-BP6WyRNh.d.mts +110 -0
  52. package/dist/events-BP6WyRNh.d.ts +110 -0
  53. package/dist/index.d.mts +10 -0
  54. package/dist/index.d.ts +10 -0
  55. package/dist/index.js +2784 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/index.mjs +2723 -0
  58. package/dist/index.mjs.map +1 -0
  59. package/dist/multi-session-client-BOFgPypS.d.ts +389 -0
  60. package/dist/multi-session-client-DMF3ED2O.d.mts +389 -0
  61. package/dist/server/index.d.mts +269 -0
  62. package/dist/server/index.d.ts +269 -0
  63. package/dist/server/index.js +2444 -0
  64. package/dist/server/index.js.map +1 -0
  65. package/dist/server/index.mjs +2414 -0
  66. package/dist/server/index.mjs.map +1 -0
  67. package/dist/shared/index.d.mts +24 -0
  68. package/dist/shared/index.d.ts +24 -0
  69. package/dist/shared/index.js +223 -0
  70. package/dist/shared/index.js.map +1 -0
  71. package/dist/shared/index.mjs +190 -0
  72. package/dist/shared/index.mjs.map +1 -0
  73. package/dist/types-SbDlA2VX.d.mts +153 -0
  74. package/dist/types-SbDlA2VX.d.ts +153 -0
  75. package/dist/utils-0qmYrqoa.d.mts +92 -0
  76. package/dist/utils-0qmYrqoa.d.ts +92 -0
  77. package/package.json +165 -0
  78. package/src/adapters/agui-adapter.ts +210 -0
  79. package/src/adapters/agui-middleware.ts +512 -0
  80. package/src/adapters/ai-adapter.ts +115 -0
  81. package/src/adapters/langchain-adapter.ts +127 -0
  82. package/src/adapters/mastra-adapter.ts +126 -0
  83. package/src/client/core/sse-client.ts +340 -0
  84. package/src/client/index.ts +26 -0
  85. package/src/client/react/index.ts +10 -0
  86. package/src/client/react/useMcp.ts +558 -0
  87. package/src/client/vue/index.ts +10 -0
  88. package/src/client/vue/useMcp.ts +542 -0
  89. package/src/index.ts +11 -0
  90. package/src/server/handlers/nextjs-handler.ts +216 -0
  91. package/src/server/handlers/sse-handler.ts +699 -0
  92. package/src/server/index.ts +57 -0
  93. package/src/server/mcp/multi-session-client.ts +132 -0
  94. package/src/server/mcp/oauth-client.ts +1168 -0
  95. package/src/server/mcp/storage-oauth-provider.ts +239 -0
  96. package/src/server/storage/file-backend.ts +169 -0
  97. package/src/server/storage/index.ts +115 -0
  98. package/src/server/storage/memory-backend.ts +132 -0
  99. package/src/server/storage/redis-backend.ts +210 -0
  100. package/src/server/storage/redis.ts +160 -0
  101. package/src/server/storage/types.ts +109 -0
  102. package/src/shared/constants.ts +29 -0
  103. package/src/shared/errors.ts +133 -0
  104. package/src/shared/events.ts +166 -0
  105. package/src/shared/index.ts +70 -0
  106. package/src/shared/types.ts +274 -0
  107. package/src/shared/utils.ts +16 -0
@@ -0,0 +1,699 @@
1
+ /**
2
+ * SSE (Server-Sent Events) Handler for MCP Connections
3
+ * Provides real-time connection state updates to clients
4
+ * Based on Cloudflare's agents pattern but adapted for HTTP/SSE
5
+ */
6
+
7
+ import type { McpConnectionEvent, McpObservabilityEvent } from '../../shared/events.js';
8
+ import type {
9
+ McpRpcRequest,
10
+ McpRpcResponse,
11
+ // RPC Param types
12
+ ConnectParams,
13
+ DisconnectParams,
14
+ SessionParams,
15
+ CallToolParams,
16
+ GetPromptParams,
17
+ ReadResourceParams,
18
+ FinishAuthParams,
19
+ // RPC Result types
20
+ SessionListResult,
21
+ ConnectResult,
22
+ DisconnectResult,
23
+ RestoreSessionResult,
24
+ FinishAuthResult,
25
+ ListToolsRpcResult,
26
+ ListPromptsResult,
27
+ ListResourcesResult,
28
+ } from '../../shared/types.js';
29
+ import { RpcErrorCodes } from '../../shared/errors.js';
30
+ import { MCPClient } from '../mcp/oauth-client.js';
31
+ import { storage } from '../storage/index.js';
32
+
33
+ export interface ClientMetadata {
34
+ clientName?: string;
35
+ clientUri?: string;
36
+ logoUri?: string;
37
+ policyUri?: string;
38
+ }
39
+
40
+ export interface SSEHandlerOptions {
41
+ /**
42
+ * User/Client identifier
43
+ */
44
+ identity: string;
45
+
46
+ /**
47
+ * Optional callback for authentication/authorization
48
+ */
49
+ onAuth?: (identity: string) => Promise<boolean>;
50
+
51
+ /**
52
+ * Heartbeat interval in ms (default: 30000)
53
+ */
54
+ heartbeatInterval?: number;
55
+
56
+ /**
57
+ * Static OAuth client metadata defaults (for all connections)
58
+ */
59
+ clientDefaults?: ClientMetadata;
60
+
61
+ /**
62
+ * Dynamic OAuth client metadata getter (per-request, useful for multi-tenant)
63
+ * Takes precedence over clientDefaults
64
+ */
65
+ getClientMetadata?: (request?: any) => ClientMetadata | Promise<ClientMetadata>;
66
+ }
67
+
68
+ /**
69
+ * SSE Connection Manager
70
+ * Handles a single SSE connection and manages MCP operations
71
+ */
72
+ export class SSEConnectionManager {
73
+ private identity: string;
74
+ private clients: Map<string, MCPClient> = new Map();
75
+ private heartbeatTimer?: NodeJS.Timeout;
76
+ private isActive: boolean = true;
77
+
78
+ constructor(
79
+ private options: SSEHandlerOptions,
80
+ private sendEvent: (event: McpConnectionEvent | McpObservabilityEvent | McpRpcResponse) => void
81
+ ) {
82
+ this.identity = options.identity;
83
+ this.startHeartbeat();
84
+ }
85
+
86
+ /**
87
+ * Get resolved client metadata (dynamic > static > defaults)
88
+ */
89
+ private async getResolvedClientMetadata(request?: any): Promise<ClientMetadata> {
90
+ // Priority: getClientMetadata() > clientDefaults > empty object
91
+ let metadata: ClientMetadata = {};
92
+
93
+ // Start with static defaults
94
+ if (this.options.clientDefaults) {
95
+ metadata = { ...this.options.clientDefaults };
96
+ }
97
+
98
+ // Override with dynamic metadata if provided
99
+ if (this.options.getClientMetadata) {
100
+ const dynamicMetadata = await this.options.getClientMetadata(request);
101
+ metadata = { ...metadata, ...dynamicMetadata };
102
+ }
103
+
104
+ return metadata;
105
+ }
106
+
107
+ /**
108
+ * Start heartbeat to keep connection alive
109
+ */
110
+ private startHeartbeat(): void {
111
+ const interval = this.options.heartbeatInterval || 30000;
112
+ this.heartbeatTimer = setInterval(() => {
113
+ if (this.isActive) {
114
+ this.sendEvent({
115
+ level: 'debug',
116
+ message: 'heartbeat',
117
+ timestamp: Date.now(),
118
+ } as McpObservabilityEvent);
119
+ }
120
+ }, interval);
121
+ }
122
+
123
+ /**
124
+ * Handle incoming RPC requests
125
+ */
126
+ async handleRequest(request: McpRpcRequest): Promise<void> {
127
+ try {
128
+ let result: SessionListResult | ConnectResult | DisconnectResult | RestoreSessionResult | FinishAuthResult | ListToolsRpcResult | ListPromptsResult | ListResourcesResult | unknown;
129
+
130
+ switch (request.method) {
131
+ case 'getSessions':
132
+ result = await this.getSessions();
133
+ break;
134
+
135
+ case 'connect':
136
+ result = await this.connect(request.params as ConnectParams);
137
+ break;
138
+
139
+ case 'disconnect':
140
+ result = await this.disconnect(request.params as DisconnectParams);
141
+ break;
142
+
143
+ case 'listTools':
144
+ result = await this.listTools(request.params as SessionParams);
145
+ break;
146
+
147
+ case 'callTool':
148
+ result = await this.callTool(request.params as CallToolParams);
149
+ break;
150
+
151
+ case 'restoreSession':
152
+ result = await this.restoreSession(request.params as SessionParams);
153
+ break;
154
+
155
+ case 'finishAuth':
156
+ result = await this.finishAuth(request.params as FinishAuthParams);
157
+ break;
158
+
159
+ case 'listPrompts':
160
+ result = await this.listPrompts(request.params as SessionParams);
161
+ break;
162
+
163
+ case 'getPrompt':
164
+ result = await this.getPrompt(request.params as GetPromptParams);
165
+ break;
166
+
167
+ case 'listResources':
168
+ result = await this.listResources(request.params as SessionParams);
169
+ break;
170
+
171
+ case 'readResource':
172
+ result = await this.readResource(request.params as ReadResourceParams);
173
+ break;
174
+
175
+ default:
176
+ throw new Error(`Unknown method: ${request.method}`);
177
+ }
178
+
179
+ this.sendEvent({
180
+ id: request.id,
181
+ result,
182
+ });
183
+ } catch (error) {
184
+ this.sendEvent({
185
+ id: request.id,
186
+ error: {
187
+ code: RpcErrorCodes.EXECUTION_ERROR,
188
+ message: error instanceof Error ? error.message : 'Unknown error',
189
+ },
190
+ });
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Get all user sessions
196
+ */
197
+ private async getSessions(): Promise<SessionListResult> {
198
+ const sessions = await storage.getIdentitySessionsData(this.identity);
199
+
200
+ this.sendEvent({
201
+ level: 'debug',
202
+ message: `Retrieved ${sessions.length} sessions for identity ${this.identity}`,
203
+ timestamp: Date.now(),
204
+ metadata: {
205
+ identity: this.identity,
206
+ sessionCount: sessions.length,
207
+ sessions: sessions.map(s => ({
208
+ sessionId: s.sessionId,
209
+ serverId: s.serverId,
210
+ serverName: s.serverName,
211
+ })),
212
+ },
213
+ });
214
+
215
+ return {
216
+ sessions: sessions.map((s) => ({
217
+ sessionId: s.sessionId,
218
+ serverId: s.serverId,
219
+ serverName: s.serverName,
220
+ serverUrl: s.serverUrl,
221
+ transport: s.transportType,
222
+ })),
223
+ };
224
+ }
225
+
226
+ /**
227
+ * Connect to an MCP server
228
+ */
229
+ private async connect(params: ConnectParams): Promise<ConnectResult> {
230
+ const { serverId, serverName, serverUrl, callbackUrl, transportType } = params;
231
+
232
+ // Check for existing connections
233
+ const existingSessions = await storage.getIdentitySessionsData(this.identity);
234
+ const duplicate = existingSessions.find(s =>
235
+ s.serverId === serverId || s.serverUrl === serverUrl
236
+ );
237
+
238
+ if (duplicate) {
239
+ throw new Error(`Connection already exists for server: ${duplicate.serverUrl || duplicate.serverId} (${duplicate.serverName})`);
240
+ }
241
+
242
+ // Generate session ID
243
+ const sessionId = await storage.generateSessionId();
244
+
245
+ // Emit connecting state
246
+ this.emitConnectionEvent({
247
+ type: 'state_changed',
248
+ sessionId,
249
+ serverId,
250
+ serverName,
251
+ state: 'CONNECTING',
252
+ previousState: 'DISCONNECTED',
253
+ timestamp: Date.now(),
254
+ });
255
+
256
+ try {
257
+ // Get resolved client metadata
258
+ const clientMetadata = await this.getResolvedClientMetadata();
259
+
260
+ // Create MCP client
261
+ const client = new MCPClient({
262
+ identity: this.identity,
263
+ sessionId,
264
+ serverId,
265
+ serverName,
266
+ serverUrl,
267
+ callbackUrl,
268
+ transportType,
269
+ ...clientMetadata, // Spread client metadata (clientName, clientUri, logoUri, policyUri)
270
+ onRedirect: (authUrl) => {
271
+ // Emit auth required event
272
+ this.emitConnectionEvent({
273
+ type: 'auth_required',
274
+ sessionId,
275
+ serverId,
276
+ authUrl,
277
+ timestamp: Date.now(),
278
+ });
279
+ },
280
+ });
281
+
282
+ // Note: Session will be created by MCPClient after successful connection
283
+ // This ensures sessions only exist for successful or OAuth-pending connections
284
+
285
+ // Store client
286
+ this.clients.set(sessionId, client);
287
+
288
+ // Subscribe to client events
289
+ client.onConnectionEvent((event) => {
290
+ this.emitConnectionEvent(event);
291
+ });
292
+
293
+ client.onObservabilityEvent((event) => {
294
+ this.sendEvent(event);
295
+ });
296
+
297
+ // Attempt connection
298
+ await client.connect();
299
+
300
+ // Fetch tools
301
+ const tools = await client.listTools();
302
+
303
+ // Debug: Check session state after connection
304
+ const sessionAfterConnect = await storage.getSession(this.identity, sessionId);
305
+ console.log(`[SSE Handler] After connect() - Session ${sessionId}:`, {
306
+ serverId: sessionAfterConnect?.serverId,
307
+ });
308
+
309
+ this.emitConnectionEvent({
310
+ type: 'tools_discovered',
311
+ sessionId,
312
+ serverId,
313
+ toolCount: tools.tools.length,
314
+ tools: tools.tools,
315
+ timestamp: Date.now(),
316
+ });
317
+
318
+ return {
319
+ sessionId,
320
+ success: true,
321
+ };
322
+ } catch (error) {
323
+ this.emitConnectionEvent({
324
+ type: 'error',
325
+ sessionId,
326
+ serverId,
327
+ error: error instanceof Error ? error.message : 'Connection failed',
328
+ errorType: 'connection',
329
+ timestamp: Date.now(),
330
+ });
331
+
332
+ // Clean up client
333
+ this.clients.delete(sessionId);
334
+
335
+ throw error;
336
+ }
337
+ }
338
+
339
+ /**
340
+ * Disconnect from an MCP server
341
+ */
342
+ private async disconnect(params: DisconnectParams): Promise<DisconnectResult> {
343
+ const { sessionId } = params;
344
+ const client = this.clients.get(sessionId);
345
+
346
+ if (client) {
347
+ await client.clearSession();
348
+ client.disconnect();
349
+ this.clients.delete(sessionId);
350
+ } else {
351
+ // Handle orphaned sessions (e.g., OAuth flow failed before client was stored)
352
+ // Directly remove from storage since there's no active client
353
+ await storage.removeSession(this.identity, sessionId);
354
+ }
355
+
356
+ return { success: true };
357
+ }
358
+
359
+ /**
360
+ * Helper to get or restore a client
361
+ */
362
+ private async getOrCreateClient(sessionId: string): Promise<MCPClient> {
363
+ let client = this.clients.get(sessionId);
364
+
365
+ if (!client) {
366
+ client = new MCPClient({
367
+ identity: this.identity,
368
+ sessionId,
369
+ });
370
+
371
+ // Subscribe to events
372
+ client.onConnectionEvent((event) => {
373
+ this.emitConnectionEvent(event);
374
+ });
375
+
376
+ client.onObservabilityEvent((event) => {
377
+ this.sendEvent(event);
378
+ });
379
+
380
+ await client.connect();
381
+ this.clients.set(sessionId, client);
382
+ }
383
+
384
+ return client;
385
+ }
386
+
387
+ /**
388
+ * List tools from a session
389
+ */
390
+ private async listTools(params: SessionParams): Promise<ListToolsRpcResult> {
391
+ const { sessionId } = params;
392
+ const client = await this.getOrCreateClient(sessionId);
393
+ const result = await client.listTools();
394
+ return { tools: result.tools };
395
+ }
396
+
397
+ /**
398
+ * Call a tool
399
+ */
400
+ private async callTool(params: CallToolParams): Promise<unknown> {
401
+ const { sessionId, toolName, toolArgs } = params;
402
+ const client = await this.getOrCreateClient(sessionId);
403
+ return await client.callTool(toolName, toolArgs);
404
+ }
405
+
406
+ /**
407
+ * Refresh/validate a session
408
+ */
409
+ private async restoreSession(params: SessionParams): Promise<RestoreSessionResult> {
410
+ const { sessionId } = params;
411
+
412
+ this.sendEvent({
413
+ level: 'debug',
414
+ message: `Starting session refresh for ${sessionId}`,
415
+ timestamp: Date.now(),
416
+ metadata: { sessionId, identity: this.identity },
417
+ });
418
+
419
+ // Emit validating state
420
+ const session = await storage.getSession(this.identity, sessionId);
421
+ if (!session) {
422
+ this.sendEvent({
423
+ level: 'error',
424
+ message: `Session not found: ${sessionId}`,
425
+ timestamp: Date.now(),
426
+ metadata: { sessionId, identity: this.identity },
427
+ });
428
+ throw new Error('Session not found');
429
+ }
430
+
431
+ this.sendEvent({
432
+ level: 'debug',
433
+ message: `Session found in Redis`,
434
+ timestamp: Date.now(),
435
+ metadata: {
436
+ sessionId,
437
+ serverId: session.serverId,
438
+ serverName: session.serverName,
439
+ serverUrl: session.serverUrl,
440
+ transportType: session.transportType,
441
+ },
442
+ });
443
+
444
+ this.emitConnectionEvent({
445
+ type: 'state_changed',
446
+ sessionId,
447
+ serverId: session.serverId || 'unknown',
448
+ serverName: session.serverName || 'Unknown',
449
+ state: 'VALIDATING',
450
+ previousState: 'DISCONNECTED',
451
+ timestamp: Date.now(),
452
+ });
453
+
454
+ try {
455
+ // Get resolved client metadata
456
+ const clientMetadata = await this.getResolvedClientMetadata();
457
+
458
+ // Try to restore and validate
459
+ const client = new MCPClient({
460
+ identity: this.identity,
461
+ sessionId,
462
+ ...clientMetadata, // Include metadata for consistency
463
+ });
464
+
465
+ // Subscribe to events
466
+ client.onConnectionEvent((event) => {
467
+ this.emitConnectionEvent(event);
468
+ });
469
+
470
+ client.onObservabilityEvent((event) => {
471
+ this.sendEvent(event);
472
+ });
473
+
474
+ await client.connect();
475
+ this.clients.set(sessionId, client);
476
+
477
+ const tools = await client.listTools();
478
+
479
+
480
+
481
+ this.emitConnectionEvent({
482
+ type: 'tools_discovered',
483
+ sessionId,
484
+ serverId: session.serverId || 'unknown',
485
+ toolCount: tools.tools.length,
486
+ tools: tools.tools,
487
+ timestamp: Date.now(),
488
+ });
489
+
490
+ return { success: true, toolCount: tools.tools.length };
491
+ } catch (error) {
492
+ this.emitConnectionEvent({
493
+ type: 'error',
494
+ sessionId,
495
+ serverId: session.serverId || 'unknown',
496
+ error: error instanceof Error ? error.message : 'Validation failed',
497
+ errorType: 'validation',
498
+ timestamp: Date.now(),
499
+ });
500
+
501
+ throw error;
502
+ }
503
+ }
504
+
505
+ /**
506
+ * Complete OAuth authorization
507
+ */
508
+ private async finishAuth(params: FinishAuthParams): Promise<FinishAuthResult> {
509
+ const { sessionId, code } = params;
510
+
511
+ this.sendEvent({
512
+ level: 'debug',
513
+ message: `Completing OAuth for session ${sessionId}`,
514
+ timestamp: Date.now(),
515
+ metadata: { sessionId, identity: this.identity },
516
+ });
517
+
518
+ const session = await storage.getSession(this.identity, sessionId);
519
+ if (!session) {
520
+ throw new Error('Session not found');
521
+ }
522
+
523
+ this.emitConnectionEvent({
524
+ type: 'state_changed',
525
+ sessionId,
526
+ serverId: session.serverId || 'unknown',
527
+ serverName: session.serverName || 'Unknown',
528
+ state: 'AUTHENTICATING',
529
+ previousState: 'DISCONNECTED',
530
+ timestamp: Date.now(),
531
+ });
532
+
533
+ try {
534
+ const client = new MCPClient({
535
+ identity: this.identity,
536
+ sessionId,
537
+ });
538
+
539
+ // Subscribe to events
540
+ client.onConnectionEvent((event) => {
541
+ this.emitConnectionEvent(event);
542
+ });
543
+
544
+ await client.finishAuth(code);
545
+ this.clients.set(sessionId, client);
546
+
547
+ const tools = await client.listTools();
548
+
549
+
550
+
551
+ this.emitConnectionEvent({
552
+ type: 'tools_discovered',
553
+ sessionId,
554
+ serverId: session.serverId || 'unknown',
555
+ toolCount: tools.tools.length,
556
+ tools: tools.tools,
557
+ timestamp: Date.now(),
558
+ });
559
+
560
+ return { success: true, toolCount: tools.tools.length };
561
+ } catch (error) {
562
+ this.emitConnectionEvent({
563
+ type: 'error',
564
+ sessionId,
565
+ serverId: session.serverId || 'unknown',
566
+ error: error instanceof Error ? error.message : 'OAuth completion failed',
567
+ errorType: 'auth',
568
+ timestamp: Date.now(),
569
+ });
570
+
571
+ throw error;
572
+ }
573
+ }
574
+
575
+ /**
576
+ * List prompts from a session
577
+ */
578
+ private async listPrompts(params: SessionParams): Promise<ListPromptsResult> {
579
+ const { sessionId } = params;
580
+ const client = await this.getOrCreateClient(sessionId);
581
+ const result = await client.listPrompts();
582
+ return { prompts: result.prompts };
583
+ }
584
+
585
+ /**
586
+ * Get a specific prompt
587
+ */
588
+ private async getPrompt(params: GetPromptParams): Promise<unknown> {
589
+ const { sessionId, name, args } = params;
590
+ const client = await this.getOrCreateClient(sessionId);
591
+ return await client.getPrompt(name, args);
592
+ }
593
+
594
+ /**
595
+ * List resources from a session
596
+ */
597
+ private async listResources(params: SessionParams): Promise<ListResourcesResult> {
598
+ const { sessionId } = params;
599
+ const client = await this.getOrCreateClient(sessionId);
600
+ const result = await client.listResources();
601
+ return { resources: result.resources };
602
+ }
603
+
604
+ /**
605
+ * Read a specific resource
606
+ */
607
+ private async readResource(params: ReadResourceParams): Promise<unknown> {
608
+ const { sessionId, uri } = params;
609
+ const client = await this.getOrCreateClient(sessionId);
610
+ return await client.readResource(uri);
611
+ }
612
+
613
+ /**
614
+ * Emit connection event
615
+ */
616
+ private emitConnectionEvent(event: McpConnectionEvent): void {
617
+ this.sendEvent(event);
618
+ }
619
+
620
+ /**
621
+ * Cleanup and close all connections
622
+ */
623
+ dispose(): void {
624
+ this.isActive = false;
625
+
626
+ if (this.heartbeatTimer) {
627
+ clearInterval(this.heartbeatTimer);
628
+ }
629
+
630
+ for (const client of this.clients.values()) {
631
+ client.disconnect();
632
+ }
633
+
634
+ this.clients.clear();
635
+ }
636
+ }
637
+
638
+ /**
639
+ * Create SSE endpoint handler
640
+ * Compatible with various Node.js frameworks
641
+ */
642
+ export function createSSEHandler(options: SSEHandlerOptions) {
643
+ return async (req: any, res: any) => {
644
+ // Set SSE headers
645
+ res.writeHead(200, {
646
+ 'Content-Type': 'text/event-stream',
647
+ 'Cache-Control': 'no-cache',
648
+ 'Connection': 'keep-alive',
649
+ 'Access-Control-Allow-Origin': '*',
650
+ });
651
+
652
+ // Send initial connection event
653
+ sendSSE(res, 'connected', { timestamp: Date.now() });
654
+
655
+ // Create connection manager
656
+ const manager = new SSEConnectionManager(options, (event) => {
657
+ // Determine event type
658
+ if ('id' in event) {
659
+ // RPC response
660
+ sendSSE(res, 'rpc-response', event);
661
+ } else if ('type' in event && 'sessionId' in event) {
662
+ // Connection event
663
+ sendSSE(res, 'connection', event);
664
+ } else {
665
+ // Observability event
666
+ sendSSE(res, 'observability', event);
667
+ }
668
+ });
669
+
670
+ // Handle client disconnect
671
+ req.on('close', () => {
672
+ manager.dispose();
673
+ });
674
+
675
+ // Handle incoming messages (if using POST body or other methods)
676
+ if (req.method === 'POST') {
677
+ let body = '';
678
+ req.on('data', (chunk: Buffer) => {
679
+ body += chunk.toString();
680
+ });
681
+ req.on('end', async () => {
682
+ try {
683
+ const request: McpRpcRequest = JSON.parse(body);
684
+ await manager.handleRequest(request);
685
+ } catch (error) {
686
+ console.error('[SSE] Error handling request:', error);
687
+ }
688
+ });
689
+ }
690
+ };
691
+ }
692
+
693
+ /**
694
+ * Send SSE event
695
+ */
696
+ function sendSSE(res: any, event: string, data: any): void {
697
+ res.write(`event: ${event}\n`);
698
+ res.write(`data: ${JSON.stringify(data)}\n\n`);
699
+ }