@mcp-ts/sdk 1.3.2 → 1.3.3

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 (58) hide show
  1. package/README.md +405 -406
  2. package/dist/adapters/agui-adapter.d.mts +1 -1
  3. package/dist/adapters/agui-adapter.d.ts +1 -1
  4. package/dist/adapters/agui-middleware.d.mts +1 -1
  5. package/dist/adapters/agui-middleware.d.ts +1 -1
  6. package/dist/adapters/ai-adapter.d.mts +1 -1
  7. package/dist/adapters/ai-adapter.d.ts +1 -1
  8. package/dist/adapters/langchain-adapter.d.mts +1 -1
  9. package/dist/adapters/langchain-adapter.d.ts +1 -1
  10. package/dist/adapters/mastra-adapter.d.mts +1 -1
  11. package/dist/adapters/mastra-adapter.d.ts +1 -1
  12. package/dist/client/index.d.mts +8 -64
  13. package/dist/client/index.d.ts +8 -64
  14. package/dist/client/index.js +91 -173
  15. package/dist/client/index.js.map +1 -1
  16. package/dist/client/index.mjs +91 -173
  17. package/dist/client/index.mjs.map +1 -1
  18. package/dist/client/react.d.mts +12 -2
  19. package/dist/client/react.d.ts +12 -2
  20. package/dist/client/react.js +119 -182
  21. package/dist/client/react.js.map +1 -1
  22. package/dist/client/react.mjs +119 -182
  23. package/dist/client/react.mjs.map +1 -1
  24. package/dist/client/vue.d.mts +24 -4
  25. package/dist/client/vue.d.ts +24 -4
  26. package/dist/client/vue.js +121 -182
  27. package/dist/client/vue.js.map +1 -1
  28. package/dist/client/vue.mjs +121 -182
  29. package/dist/client/vue.mjs.map +1 -1
  30. package/dist/index.d.mts +2 -2
  31. package/dist/index.d.ts +2 -2
  32. package/dist/index.js +215 -250
  33. package/dist/index.js.map +1 -1
  34. package/dist/index.mjs +215 -250
  35. package/dist/index.mjs.map +1 -1
  36. package/dist/{multi-session-client-B1DBx5yR.d.mts → multi-session-client-DzjmT7FX.d.mts} +1 -0
  37. package/dist/{multi-session-client-DyFzyJUx.d.ts → multi-session-client-FAFpUzZ4.d.ts} +1 -0
  38. package/dist/server/index.d.mts +16 -21
  39. package/dist/server/index.d.ts +16 -21
  40. package/dist/server/index.js +124 -77
  41. package/dist/server/index.js.map +1 -1
  42. package/dist/server/index.mjs +124 -77
  43. package/dist/server/index.mjs.map +1 -1
  44. package/dist/shared/index.d.mts +2 -2
  45. package/dist/shared/index.d.ts +2 -2
  46. package/dist/shared/index.js.map +1 -1
  47. package/dist/shared/index.mjs.map +1 -1
  48. package/dist/{types-PjM1W07s.d.mts → types-CW6lghof.d.mts} +5 -0
  49. package/dist/{types-PjM1W07s.d.ts → types-CW6lghof.d.ts} +5 -0
  50. package/package.json +1 -1
  51. package/src/client/core/sse-client.ts +354 -493
  52. package/src/client/react/use-mcp.ts +75 -23
  53. package/src/client/vue/use-mcp.ts +111 -48
  54. package/src/server/handlers/nextjs-handler.ts +207 -217
  55. package/src/server/handlers/sse-handler.ts +10 -0
  56. package/src/server/mcp/oauth-client.ts +41 -32
  57. package/src/server/storage/types.ts +12 -5
  58. package/src/shared/types.ts +5 -0
@@ -64,6 +64,13 @@ export interface UseMcpOptions {
64
64
  * @default 60000
65
65
  */
66
66
  requestTimeout?: number;
67
+
68
+ /**
69
+ * Enable client debug logs.
70
+ * @default false
71
+ */
72
+ debug?: boolean;
73
+
67
74
  }
68
75
 
69
76
  export interface McpConnection {
@@ -74,6 +81,7 @@ export interface McpConnection {
74
81
  transport?: string;
75
82
  state: McpConnectionState;
76
83
  tools: ToolInfo[];
84
+ authUrl?: string;
77
85
  error?: string;
78
86
  createdAt?: Date;
79
87
  }
@@ -150,6 +158,11 @@ export interface McpClient {
150
158
  */
151
159
  finishAuth: (sessionId: string, code: string) => Promise<FinishAuthResult>;
152
160
 
161
+ /**
162
+ * Explicitly resume OAuth flow for an existing session
163
+ */
164
+ resumeAuth: (sessionId: string) => Promise<void>;
165
+
153
166
  /**
154
167
  * Call a tool from a session
155
168
  */
@@ -207,6 +220,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
207
220
 
208
221
  const clientRef = useRef<SSEClient | null>(null);
209
222
  const isMountedRef = useRef(true);
223
+ const suppressAuthRedirectSessionsRef = useRef<Set<string>>(new Set());
210
224
 
211
225
  const [connections, setConnections] = useState<McpConnection[]>([]);
212
226
  const [status, setStatus] = useState<'connecting' | 'connected' | 'disconnected' | 'error'>(
@@ -239,7 +253,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
239
253
  setStatus(newStatus);
240
254
  }
241
255
  },
242
- requestTimeout: options.requestTimeout,
256
+ debug: options.debug,
243
257
  };
244
258
 
245
259
  const client = new SSEClient(clientOptions);
@@ -262,21 +276,36 @@ export function useMcp(options: UseMcpOptions): McpClient {
262
276
  /**
263
277
  * Update connections based on event
264
278
  */
265
- const updateConnectionsFromEvent = useCallback((event: McpConnectionEvent) => {
266
- if (!isMountedRef.current) return;
267
-
268
- setConnections((prev: McpConnection[]) => {
269
- switch (event.type) {
270
- case 'state_changed': {
271
- const existing = prev.find((c: McpConnection) => c.sessionId === event.sessionId);
272
- if (existing) {
273
- return prev.map((c: McpConnection) =>
274
- c.sessionId === event.sessionId ? {
275
- ...c,
276
- state: event.state,
277
- // update createdAt if present in event, otherwise keep existing
278
- createdAt: event.createdAt ? new Date(event.createdAt) : c.createdAt
279
- } : c
279
+ const updateConnectionsFromEvent = useCallback((event: McpConnectionEvent) => {
280
+ if (!isMountedRef.current) return;
281
+
282
+ const isTransientReconnectState = (state: McpConnectionState): boolean =>
283
+ state === 'INITIALIZING' ||
284
+ state === 'VALIDATING' ||
285
+ state === 'RECONNECTING' ||
286
+ state === 'CONNECTING' ||
287
+ state === 'CONNECTED' ||
288
+ state === 'DISCOVERING';
289
+
290
+ setConnections((prev: McpConnection[]) => {
291
+ switch (event.type) {
292
+ case 'state_changed': {
293
+ const existing = prev.find((c: McpConnection) => c.sessionId === event.sessionId);
294
+ if (existing) {
295
+ // In stateless per-request transport, tool calls can emit transient reconnect states.
296
+ // Keep READY sticky to avoid UI flicker from READY -> CONNECTING -> CONNECTED.
297
+ const nextState =
298
+ existing.state === 'READY' && isTransientReconnectState(event.state)
299
+ ? existing.state
300
+ : event.state;
301
+
302
+ return prev.map((c: McpConnection) =>
303
+ c.sessionId === event.sessionId ? {
304
+ ...c,
305
+ state: nextState,
306
+ // update createdAt if present in event, otherwise keep existing
307
+ createdAt: event.createdAt ? new Date(event.createdAt) : c.createdAt
308
+ } : c
280
309
  );
281
310
  } else {
282
311
  // Fix: Don't add back disconnected sessions that were just removed
@@ -315,14 +344,17 @@ export function useMcp(options: UseMcpOptions): McpClient {
315
344
  if (event.authUrl) {
316
345
  onLog?.('info', `OAuth required - redirecting to ${event.authUrl}`, { authUrl: event.authUrl });
317
346
 
318
- if (onRedirect) {
319
- onRedirect(event.authUrl);
320
- } else if (typeof window !== 'undefined') {
321
- window.location.href = event.authUrl;
347
+ // Suppress redirects/popups for auto-restore on page load.
348
+ if (!suppressAuthRedirectSessionsRef.current.has(event.sessionId)) {
349
+ if (onRedirect) {
350
+ onRedirect(event.authUrl);
351
+ } else if (typeof window !== 'undefined') {
352
+ window.location.href = event.authUrl;
353
+ }
322
354
  }
323
355
  }
324
356
  return prev.map((c: McpConnection) =>
325
- c.sessionId === event.sessionId ? { ...c, state: 'AUTHENTICATING' } : c
357
+ c.sessionId === event.sessionId ? { ...c, state: 'AUTHENTICATING', authUrl: event.authUrl } : c
326
358
  );
327
359
  }
328
360
 
@@ -340,7 +372,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
340
372
  return prev;
341
373
  }
342
374
  });
343
- }, [onLog]);
375
+ }, [onLog, onRedirect]);
344
376
 
345
377
  /**
346
378
  * Load sessions from server
@@ -363,7 +395,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
363
395
  serverName: s.serverName ?? 'Unknown Server',
364
396
  serverUrl: s.serverUrl,
365
397
  transport: s.transport,
366
- state: 'VALIDATING' as McpConnectionState,
398
+ state: (s.active === false ? 'AUTHENTICATING' : 'VALIDATING') as McpConnectionState,
367
399
  createdAt: new Date(s.createdAt),
368
400
  tools: [],
369
401
  }))
@@ -375,9 +407,16 @@ export function useMcp(options: UseMcpOptions): McpClient {
375
407
  sessions.map(async (session: SessionInfo) => {
376
408
  if (clientRef.current) {
377
409
  try {
410
+ // Pending auth sessions should not auto-trigger popup/redirect on reload.
411
+ if (session.active === false) {
412
+ return;
413
+ }
414
+ suppressAuthRedirectSessionsRef.current.add(session.sessionId);
378
415
  await clientRef.current.restoreSession(session.sessionId);
379
416
  } catch (error) {
380
417
  console.error(`[useMcp] Failed to validate session ${session.sessionId}:`, error);
418
+ } finally {
419
+ suppressAuthRedirectSessionsRef.current.delete(session.sessionId);
381
420
  }
382
421
  }
383
422
  })
@@ -461,6 +500,18 @@ export function useMcp(options: UseMcpOptions): McpClient {
461
500
  return await clientRef.current.finishAuth(sessionId, code);
462
501
  }, []);
463
502
 
503
+ /**
504
+ * Explicit user action to resume OAuth for an existing pending session.
505
+ */
506
+ const resumeAuth = useCallback(async (sessionId: string): Promise<void> => {
507
+ if (!clientRef.current) {
508
+ throw new Error('SSE client not initialized');
509
+ }
510
+ // Ensure this attempt is not suppressed as background restore.
511
+ suppressAuthRedirectSessionsRef.current.delete(sessionId);
512
+ await clientRef.current.restoreSession(sessionId);
513
+ }, []);
514
+
464
515
  /**
465
516
  * Call a tool
466
517
  */
@@ -578,6 +629,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
578
629
  connectSSE,
579
630
  disconnectSSE,
580
631
  finishAuth,
632
+ resumeAuth,
581
633
  callTool,
582
634
  listTools,
583
635
  listPrompts,
@@ -16,7 +16,7 @@ import type {
16
16
  SessionInfo,
17
17
  } from '../../shared/types';
18
18
 
19
- export interface UseMcpOptions {
19
+ export interface UseMcpOptions {
20
20
  /**
21
21
  * SSE endpoint URL
22
22
  */
@@ -53,12 +53,24 @@ export interface UseMcpOptions {
53
53
  * Debug logging callback
54
54
  */
55
55
  onLog?: (level: string, message: string, metadata?: Record<string, unknown>) => void;
56
- /**
57
- * Optional callback to handle OAuth redirects (e.g. for popup flow)
58
- * If provided, this will be called instead of window.location.href assignment
59
- */
60
- onRedirect?: (url: string) => void;
61
- }
56
+ /**
57
+ * Optional callback to handle OAuth redirects (e.g. for popup flow)
58
+ * If provided, this will be called instead of window.location.href assignment
59
+ */
60
+ onRedirect?: (url: string) => void;
61
+
62
+ /**
63
+ * Request timeout in milliseconds
64
+ * @default 60000
65
+ */
66
+ requestTimeout?: number;
67
+
68
+ /**
69
+ * Enable client debug logs.
70
+ * @default false
71
+ */
72
+ debug?: boolean;
73
+ }
62
74
 
63
75
  export interface McpConnection {
64
76
  sessionId: string;
@@ -68,6 +80,7 @@ export interface McpConnection {
68
80
  transport?: string;
69
81
  state: McpConnectionState;
70
82
  tools: ToolInfo[];
83
+ authUrl?: string;
71
84
  error?: string;
72
85
  createdAt?: Date;
73
86
  }
@@ -144,6 +157,11 @@ export interface McpClient {
144
157
  */
145
158
  finishAuth: (sessionId: string, code: string) => Promise<FinishAuthResult>;
146
159
 
160
+ /**
161
+ * Explicitly resume OAuth flow for an existing session
162
+ */
163
+ resumeAuth: (sessionId: string) => Promise<void>;
164
+
147
165
  /**
148
166
  * Call a tool from a session
149
167
  */
@@ -173,11 +191,16 @@ export interface McpClient {
173
191
  */
174
192
  listResources: (sessionId: string) => Promise<ListResourcesResult>;
175
193
 
176
- /**
177
- * Read a specific resource
178
- */
179
- readResource: (sessionId: string, uri: string) => Promise<unknown>;
180
- }
194
+ /**
195
+ * Read a specific resource
196
+ */
197
+ readResource: (sessionId: string, uri: string) => Promise<unknown>;
198
+
199
+ /**
200
+ * Access the underlying SSEClient instance (for advanced usage like AppHost)
201
+ */
202
+ sseClient: SSEClient | null;
203
+ }
181
204
 
182
205
  /**
183
206
  * Vue Composable for MCP connection management with SSE
@@ -197,6 +220,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
197
220
  // Use shallowRef for client instance as it doesn't need deep reactivity
198
221
  const clientRef = shallowRef<SSEClient | null>(null);
199
222
  const isMountedRef = ref(true);
223
+ const suppressAuthRedirectSessions = ref(new Set<string>());
200
224
 
201
225
  const connections = ref<McpConnection[]>([]);
202
226
  const status = ref<'connecting' | 'connected' | 'disconnected' | 'error'>('disconnected');
@@ -205,20 +229,35 @@ export function useMcp(options: UseMcpOptions): McpClient {
205
229
  /**
206
230
  * Update connections based on event
207
231
  */
208
- const updateConnectionsFromEvent = (event: McpConnectionEvent) => {
209
- if (!isMountedRef.value) return;
210
-
211
- switch (event.type) {
212
- case 'state_changed': {
213
- const existing = connections.value.find((c) => c.sessionId === event.sessionId);
214
- if (existing) {
215
- const index = connections.value.indexOf(existing);
216
- connections.value[index] = {
217
- ...existing,
218
- state: event.state,
219
- // update createdAt if present in event, otherwise keep existing
220
- createdAt: event.createdAt ? new Date(event.createdAt) : existing.createdAt
221
- };
232
+ const updateConnectionsFromEvent = (event: McpConnectionEvent) => {
233
+ if (!isMountedRef.value) return;
234
+
235
+ const isTransientReconnectState = (state: McpConnectionState): boolean =>
236
+ state === 'INITIALIZING' ||
237
+ state === 'VALIDATING' ||
238
+ state === 'RECONNECTING' ||
239
+ state === 'CONNECTING' ||
240
+ state === 'CONNECTED' ||
241
+ state === 'DISCOVERING';
242
+
243
+ switch (event.type) {
244
+ case 'state_changed': {
245
+ const existing = connections.value.find((c) => c.sessionId === event.sessionId);
246
+ if (existing) {
247
+ // In stateless per-request transport, tool calls can emit transient reconnect states.
248
+ // Keep READY sticky to avoid UI flicker from READY -> CONNECTING -> CONNECTED.
249
+ const nextState =
250
+ existing.state === 'READY' && isTransientReconnectState(event.state)
251
+ ? existing.state
252
+ : event.state;
253
+
254
+ const index = connections.value.indexOf(existing);
255
+ connections.value[index] = {
256
+ ...existing,
257
+ state: nextState,
258
+ // update createdAt if present in event, otherwise keep existing
259
+ createdAt: event.createdAt ? new Date(event.createdAt) : existing.createdAt
260
+ };
222
261
  } else {
223
262
  // Fix: Don't add back disconnected sessions that were just removed
224
263
  if (event.state === 'DISCONNECTED') {
@@ -250,15 +289,18 @@ export function useMcp(options: UseMcpOptions): McpClient {
250
289
  if (event.authUrl) {
251
290
  onLog?.('info', `OAuth required - redirecting to ${event.authUrl}`, { authUrl: event.authUrl });
252
291
 
253
- if (onRedirect) {
254
- onRedirect(event.authUrl);
255
- } else if (typeof window !== 'undefined') {
256
- window.location.href = event.authUrl;
292
+ // Suppress redirects/popups for background auto-restore on page load.
293
+ if (!suppressAuthRedirectSessions.value.has(event.sessionId)) {
294
+ if (onRedirect) {
295
+ onRedirect(event.authUrl);
296
+ } else if (typeof window !== 'undefined') {
297
+ window.location.href = event.authUrl;
298
+ }
257
299
  }
258
300
  }
259
301
  const index = connections.value.findIndex((c) => c.sessionId === event.sessionId);
260
302
  if (index !== -1) {
261
- connections.value[index] = { ...connections.value[index], state: 'AUTHENTICATING' };
303
+ connections.value[index] = { ...connections.value[index], state: 'AUTHENTICATING', authUrl: event.authUrl };
262
304
  }
263
305
  break;
264
306
  }
@@ -298,7 +340,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
298
340
  serverName: s.serverName ?? 'Unknown Server',
299
341
  serverUrl: s.serverUrl,
300
342
  transport: s.transport,
301
- state: 'VALIDATING' as McpConnectionState,
343
+ state: (s.active === false ? 'AUTHENTICATING' : 'VALIDATING') as McpConnectionState,
302
344
  createdAt: new Date(s.createdAt),
303
345
  tools: [],
304
346
  }));
@@ -309,9 +351,16 @@ export function useMcp(options: UseMcpOptions): McpClient {
309
351
  sessions.map(async (session: SessionInfo) => {
310
352
  if (clientRef.value) {
311
353
  try {
354
+ // Pending auth sessions should not auto-trigger popup/redirect on reload.
355
+ if (session.active === false) {
356
+ return;
357
+ }
358
+ suppressAuthRedirectSessions.value.add(session.sessionId);
312
359
  await clientRef.value.restoreSession(session.sessionId);
313
360
  } catch (error) {
314
361
  console.error(`[useMcp] Failed to validate session ${session.sessionId}:`, error);
362
+ } finally {
363
+ suppressAuthRedirectSessions.value.delete(session.sessionId);
315
364
  }
316
365
  }
317
366
  })
@@ -335,10 +384,10 @@ export function useMcp(options: UseMcpOptions): McpClient {
335
384
  clientRef.value.disconnect();
336
385
  }
337
386
 
338
- const clientOptions: SSEClientOptions = {
339
- url,
340
- identity,
341
- authToken,
387
+ const clientOptions: SSEClientOptions = {
388
+ url,
389
+ identity,
390
+ authToken,
342
391
  onConnectionEvent: (event) => {
343
392
  // Update local state based on event
344
393
  updateConnectionsFromEvent(event);
@@ -349,12 +398,13 @@ export function useMcp(options: UseMcpOptions): McpClient {
349
398
  onObservabilityEvent: (event) => {
350
399
  onLog?.(event.level || 'info', event.message || event.displayMessage || 'No message', event.metadata);
351
400
  },
352
- onStatusChange: (newStatus) => {
353
- if (isMountedRef.value) {
354
- status.value = newStatus;
355
- }
356
- },
357
- };
401
+ onStatusChange: (newStatus) => {
402
+ if (isMountedRef.value) {
403
+ status.value = newStatus;
404
+ }
405
+ },
406
+ debug: options.debug,
407
+ };
358
408
 
359
409
  const client = new SSEClient(clientOptions);
360
410
  clientRef.value = client;
@@ -444,6 +494,17 @@ export function useMcp(options: UseMcpOptions): McpClient {
444
494
  return await clientRef.value.finishAuth(sessionId, code);
445
495
  };
446
496
 
497
+ /**
498
+ * Explicit user action to resume OAuth for an existing pending session.
499
+ */
500
+ const resumeAuth = async (sessionId: string): Promise<void> => {
501
+ if (!clientRef.value) {
502
+ throw new Error('SSE client not initialized');
503
+ }
504
+ suppressAuthRedirectSessions.value.delete(sessionId);
505
+ await clientRef.value.restoreSession(sessionId);
506
+ };
507
+
447
508
  /**
448
509
  * Call a tool
449
510
  */
@@ -544,11 +605,13 @@ export function useMcp(options: UseMcpOptions): McpClient {
544
605
  connectSSE,
545
606
  disconnectSSE,
546
607
  finishAuth,
608
+ resumeAuth,
547
609
  callTool,
548
610
  listTools,
549
- listPrompts,
550
- getPrompt,
551
- listResources,
552
- readResource,
553
- };
554
- }
611
+ listPrompts,
612
+ getPrompt,
613
+ listResources,
614
+ readResource,
615
+ sseClient: clientRef.value,
616
+ };
617
+ }