@mcp-ts/sdk 2.3.4 → 2.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 (82) hide show
  1. package/README.md +113 -30
  2. package/dist/adapters/agui-adapter.d.mts +3 -3
  3. package/dist/adapters/agui-adapter.d.ts +3 -3
  4. package/dist/adapters/agui-middleware.d.mts +3 -3
  5. package/dist/adapters/agui-middleware.d.ts +3 -3
  6. package/dist/adapters/ai-adapter.d.mts +3 -3
  7. package/dist/adapters/ai-adapter.d.ts +3 -3
  8. package/dist/adapters/langchain-adapter.d.mts +3 -3
  9. package/dist/adapters/langchain-adapter.d.ts +3 -3
  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 +2 -2
  13. package/dist/client/index.d.ts +2 -2
  14. package/dist/client/index.js +2 -2
  15. package/dist/client/index.js.map +1 -1
  16. package/dist/client/index.mjs +2 -2
  17. package/dist/client/index.mjs.map +1 -1
  18. package/dist/client/react.d.mts +9 -8
  19. package/dist/client/react.d.ts +9 -8
  20. package/dist/client/react.js +66 -26
  21. package/dist/client/react.js.map +1 -1
  22. package/dist/client/react.mjs +67 -27
  23. package/dist/client/react.mjs.map +1 -1
  24. package/dist/client/vue.d.mts +6 -5
  25. package/dist/client/vue.d.ts +6 -5
  26. package/dist/client/vue.js +27 -17
  27. package/dist/client/vue.js.map +1 -1
  28. package/dist/client/vue.mjs +27 -17
  29. package/dist/client/vue.mjs.map +1 -1
  30. package/dist/{index-Cfjsme-a.d.mts → index-ByIjEReo.d.mts} +2 -2
  31. package/dist/{index-CmjMd2ac.d.ts → index-CtXvKl8N.d.ts} +2 -2
  32. package/dist/index.d.mts +5 -5
  33. package/dist/index.d.ts +5 -5
  34. package/dist/index.js +728 -306
  35. package/dist/index.js.map +1 -1
  36. package/dist/index.mjs +722 -304
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/{multi-session-client-C7hGqzgM.d.mts → multi-session-client-CIMUGF8S.d.mts} +15 -33
  39. package/dist/{multi-session-client-C_kPHpD5.d.ts → multi-session-client-CnvZEGPY.d.ts} +15 -33
  40. package/dist/server/index.d.mts +45 -17
  41. package/dist/server/index.d.ts +45 -17
  42. package/dist/server/index.js +720 -300
  43. package/dist/server/index.js.map +1 -1
  44. package/dist/server/index.mjs +716 -299
  45. package/dist/server/index.mjs.map +1 -1
  46. package/dist/shared/index.d.mts +9 -8
  47. package/dist/shared/index.d.ts +9 -8
  48. package/dist/shared/index.js +7 -5
  49. package/dist/shared/index.js.map +1 -1
  50. package/dist/shared/index.mjs +5 -4
  51. package/dist/shared/index.mjs.map +1 -1
  52. package/dist/{tool-router-BMzhoNYt.d.ts → tool-router-CZMrOG8J.d.ts} +1 -1
  53. package/dist/{tool-router-BhHsvBfP.d.mts → tool-router-CuApsDiV.d.mts} +1 -1
  54. package/dist/{types-CxFaaZrM.d.mts → types-DCk_IF4L.d.mts} +5 -3
  55. package/dist/{types-CxFaaZrM.d.ts → types-DCk_IF4L.d.ts} +5 -3
  56. package/migrations/neon/20260513010000_install_mcp_sessions.sql +40 -3
  57. package/migrations/neon/20260513020000_add_session_cleanup_cron.sql +4 -4
  58. package/migrations/supabase/20260330195700_install_mcp_sessions.sql +67 -3
  59. package/migrations/supabase/20260421010000_add_session_cleanup_cron.sql +6 -6
  60. package/package.json +13 -3
  61. package/src/client/core/sse-client.ts +2 -2
  62. package/src/client/react/index.ts +1 -1
  63. package/src/client/react/oauth-popup.tsx +34 -13
  64. package/src/client/react/use-mcp.ts +19 -45
  65. package/src/client/utils/session-state.ts +43 -0
  66. package/src/client/vue/use-mcp.ts +18 -47
  67. package/src/server/handlers/sse-handler.ts +9 -4
  68. package/src/server/mcp/multi-session-client.ts +2 -6
  69. package/src/server/mcp/oauth-client.ts +73 -101
  70. package/src/server/mcp/storage-oauth-provider.ts +79 -50
  71. package/src/server/storage/file-backend.ts +74 -18
  72. package/src/server/storage/index.ts +2 -4
  73. package/src/server/storage/memory-backend.ts +49 -13
  74. package/src/server/storage/neon-backend.ts +201 -68
  75. package/src/server/storage/redis-backend.ts +81 -23
  76. package/src/server/storage/session-lifecycle.ts +78 -0
  77. package/src/server/storage/sqlite-backend.ts +89 -15
  78. package/src/server/storage/supabase-backend.ts +188 -63
  79. package/src/server/storage/types.ts +49 -16
  80. package/src/shared/constants.ts +5 -6
  81. package/src/shared/types.ts +5 -3
  82. package/src/shared/utils.ts +26 -0
@@ -1,5 +1,5 @@
1
1
  import { forwardRef, useRef, useState, useImperativeHandle, useEffect, memo, useCallback, useMemo } from 'react';
2
- import { nanoid } from 'nanoid';
2
+ import { customAlphabet, nanoid } from 'nanoid';
3
3
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
4
  import { AppBridge, PostMessageTransport } from '@modelcontextprotocol/ext-apps/app-bridge';
5
5
 
@@ -48,8 +48,8 @@ var SSEClient = class {
48
48
  async getSession(sessionId) {
49
49
  return this.sendRequest("getSession", { sessionId });
50
50
  }
51
- async finishAuth(sessionId, code) {
52
- return this.sendRequest("finishAuth", { sessionId, code });
51
+ async finishAuth(state, code) {
52
+ return this.sendRequest("finishAuth", { state, code });
53
53
  }
54
54
  async listPrompts(sessionId) {
55
55
  return this.sendRequest("listPrompts", { sessionId });
@@ -251,6 +251,20 @@ var SSEClient = class {
251
251
  }
252
252
  };
253
253
 
254
+ // src/client/utils/session-state.ts
255
+ function getInitialConnectionState(status) {
256
+ return status === "active" ? "VALIDATING" : "AUTHENTICATING";
257
+ }
258
+ function isTransientReconnectState(state) {
259
+ return state === "INITIALIZING" || state === "VALIDATING" || state === "RECONNECTING" || state === "CONNECTING" || state === "CONNECTED" || state === "DISCOVERING";
260
+ }
261
+ function getVisibleConnectionState(incomingState, existingState, previousState) {
262
+ if (incomingState === "INITIALIZING" && (existingState === "AUTHENTICATING" || existingState === "AUTHENTICATED" || previousState === "AUTHENTICATING" || previousState === "AUTHENTICATED")) {
263
+ return existingState === "AUTHENTICATED" || previousState === "AUTHENTICATED" ? "AUTHENTICATED" : "AUTHENTICATING";
264
+ }
265
+ return incomingState;
266
+ }
267
+
254
268
  // src/client/react/use-mcp.ts
255
269
  function useMcp(options) {
256
270
  const {
@@ -308,26 +322,21 @@ function useMcp(options) {
308
322
  }, [url, userId, authToken, autoConnect, autoInitialize]);
309
323
  const updateConnectionsFromEvent = useCallback((event) => {
310
324
  if (!isMountedRef.current) return;
311
- const isTransientReconnectState = (state) => state === "INITIALIZING" || state === "VALIDATING" || state === "RECONNECTING" || state === "CONNECTING" || state === "CONNECTED" || state === "DISCOVERING";
312
- const getVisibleState = (incomingState, existingState, previousState) => {
313
- if (incomingState === "INITIALIZING" && (existingState === "AUTHENTICATING" || existingState === "AUTHENTICATED" || previousState === "AUTHENTICATING" || previousState === "AUTHENTICATED")) {
314
- return existingState === "AUTHENTICATED" || previousState === "AUTHENTICATED" ? "AUTHENTICATED" : "AUTHENTICATING";
315
- }
316
- return incomingState;
317
- };
318
325
  setConnections((prev) => {
319
326
  switch (event.type) {
320
327
  case "state_changed": {
321
328
  const existing = prev.find((c) => c.sessionId === event.sessionId);
322
329
  if (existing) {
323
- const normalizedState = getVisibleState(event.state, existing.state, event.previousState);
330
+ const normalizedState = getVisibleConnectionState(event.state, existing.state, event.previousState);
324
331
  const nextState = existing.state === "READY" && isTransientReconnectState(normalizedState) ? existing.state : normalizedState;
332
+ const updatedAt = /* @__PURE__ */ new Date();
325
333
  return prev.map(
326
334
  (c) => c.sessionId === event.sessionId ? {
327
335
  ...c,
328
336
  state: nextState,
329
337
  // update createdAt if present in event, otherwise keep existing
330
- createdAt: event.createdAt ? new Date(event.createdAt) : c.createdAt
338
+ createdAt: event.createdAt ? new Date(event.createdAt) : c.createdAt,
339
+ updatedAt
331
340
  } : c
332
341
  );
333
342
  } else {
@@ -343,8 +352,9 @@ function useMcp(options) {
343
352
  serverUrl: event.serverUrl,
344
353
  // New connections do not have prior local state, so we normalize
345
354
  // only against the server-reported previous state.
346
- state: getVisibleState(event.state, void 0, event.previousState),
355
+ state: getVisibleConnectionState(event.state, void 0, event.previousState),
347
356
  createdAt: event.createdAt ? new Date(event.createdAt) : void 0,
357
+ updatedAt: /* @__PURE__ */ new Date(),
348
358
  tools: []
349
359
  }
350
360
  ];
@@ -355,7 +365,7 @@ function useMcp(options) {
355
365
  clientRef.current.preloadToolUiResources(event.sessionId, event.tools);
356
366
  }
357
367
  return prev.map(
358
- (c) => c.sessionId === event.sessionId ? { ...c, tools: event.tools, state: "READY" } : c
368
+ (c) => c.sessionId === event.sessionId ? { ...c, tools: event.tools, state: "READY", updatedAt: /* @__PURE__ */ new Date() } : c
359
369
  );
360
370
  }
361
371
  case "auth_required": {
@@ -410,8 +420,9 @@ function useMcp(options) {
410
420
  serverName: s.serverName ?? "Unknown Server",
411
421
  serverUrl: s.serverUrl,
412
422
  transport: s.transport,
413
- state: s.active === false ? "AUTHENTICATING" : "VALIDATING",
423
+ state: getInitialConnectionState(s.status),
414
424
  createdAt: new Date(s.createdAt),
425
+ updatedAt: new Date(s.updatedAt ?? s.createdAt),
415
426
  tools: []
416
427
  }))
417
428
  );
@@ -420,7 +431,7 @@ function useMcp(options) {
420
431
  sessions.map(async (session) => {
421
432
  if (clientRef.current) {
422
433
  try {
423
- if (session.active === false) {
434
+ if (session.status !== "active") {
424
435
  return;
425
436
  }
426
437
  suppressAuthRedirectSessionsRef.current.add(session.sessionId);
@@ -491,11 +502,11 @@ function useMcp(options) {
491
502
  const disconnectSSE = useCallback(() => {
492
503
  clientRef.current?.disconnect();
493
504
  }, []);
494
- const finishAuth = useCallback(async (sessionId, code) => {
505
+ const finishAuth = useCallback(async (state, code) => {
495
506
  if (!clientRef.current) {
496
507
  throw new Error("SSE client not initialized");
497
508
  }
498
- return await clientRef.current.finishAuth(sessionId, code);
509
+ return await clientRef.current.finishAuth(state, code);
499
510
  }, []);
500
511
  const resumeAuth = useCallback(async (sessionId) => {
501
512
  if (!clientRef.current) {
@@ -619,6 +630,27 @@ function useMcp(options) {
619
630
  ]
620
631
  );
621
632
  }
633
+ var OAUTH_STATE_SEPARATOR = ".";
634
+ customAlphabet(
635
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
636
+ 1
637
+ );
638
+ customAlphabet(
639
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
640
+ 11
641
+ );
642
+ function parseOAuthState(state) {
643
+ const separatorIndex = state.indexOf(OAUTH_STATE_SEPARATOR);
644
+ if (separatorIndex <= 0 || separatorIndex === state.length - 1) {
645
+ return void 0;
646
+ }
647
+ const nonce = state.slice(0, separatorIndex);
648
+ const sessionId = state.slice(separatorIndex + 1);
649
+ if (!nonce || !sessionId || sessionId.includes(OAUTH_STATE_SEPARATOR)) {
650
+ return void 0;
651
+ }
652
+ return { nonce, sessionId };
653
+ }
622
654
  var AUTH_CODE_MESSAGE = "MCP_AUTH_CODE";
623
655
  var AUTH_RESULT_MESSAGE = "MCP_AUTH_RESULT";
624
656
  var AUTH_CHANNEL_NAME = "mcp-auth-channel";
@@ -697,9 +729,10 @@ function useMcpOAuthPopup(connections, finishAuth) {
697
729
  return;
698
730
  }
699
731
  const popupWindow = event.source && "postMessage" in event.source ? event.source : null;
700
- const targetSessionId = typeof event.data.sessionId === "string" ? event.data.sessionId : "";
732
+ const rawState = typeof event.data.state === "string" ? event.data.state : typeof event.data.sessionId === "string" ? event.data.sessionId : "";
733
+ const targetSessionId = rawState ? parseOAuthState(rawState)?.sessionId || rawState : "";
701
734
  if (popupWindow && targetSessionId) {
702
- pendingPopupsRef.current.set(targetSessionId, popupWindow);
735
+ pendingPopupsRef.current.set(targetSessionId, { popupWindow, state: rawState });
703
736
  }
704
737
  if (!targetSessionId) {
705
738
  if (popupWindow) {
@@ -715,6 +748,7 @@ function useMcpOAuthPopup(connections, finishAuth) {
715
748
  if (popupWindow) {
716
749
  postPopupResult(popupWindow, {
717
750
  sessionId: targetSessionId,
751
+ state: rawState,
718
752
  success: false,
719
753
  error: "OAuth session not found in the current client state"
720
754
  });
@@ -727,13 +761,14 @@ function useMcpOAuthPopup(connections, finishAuth) {
727
761
  }
728
762
  processingCodesRef.current.add(codeKey);
729
763
  try {
730
- await finishAuth(targetSession.sessionId, code);
764
+ await finishAuth(rawState, code);
731
765
  } catch (error) {
732
766
  processingCodesRef.current.delete(codeKey);
733
767
  pendingPopupsRef.current.delete(targetSession.sessionId);
734
768
  if (popupWindow) {
735
769
  postPopupResult(popupWindow, {
736
- sessionId: targetSession.sessionId,
770
+ sessionId: rawState,
771
+ state: rawState,
737
772
  success: false,
738
773
  error: error instanceof Error ? error.message : "Failed to finish auth"
739
774
  });
@@ -756,10 +791,13 @@ function useMcpOAuthPopup(connections, finishAuth) {
756
791
  }, [connections, finishAuth]);
757
792
  useEffect(() => {
758
793
  for (const connection of connections) {
759
- const popupWindow = pendingPopupsRef.current.get(connection.sessionId) || null;
794
+ const pendingPopup = pendingPopupsRef.current.get(connection.sessionId);
795
+ const popupWindow = pendingPopup?.popupWindow || null;
796
+ const resultState = pendingPopup?.state || connection.sessionId;
760
797
  if (connection.state === "AUTHENTICATED" || connection.state === "READY" || connection.state === "CONNECTED") {
761
798
  postPopupResult(popupWindow, {
762
- sessionId: connection.sessionId,
799
+ sessionId: resultState,
800
+ state: resultState,
763
801
  success: true
764
802
  });
765
803
  for (const codeKey of processingCodesRef.current) {
@@ -772,7 +810,8 @@ function useMcpOAuthPopup(connections, finishAuth) {
772
810
  }
773
811
  if (connection.state === "FAILED") {
774
812
  postPopupResult(popupWindow, {
775
- sessionId: connection.sessionId,
813
+ sessionId: resultState,
814
+ state: resultState,
776
815
  success: false,
777
816
  error: connection.error || "Failed to complete authorization"
778
817
  });
@@ -824,7 +863,8 @@ function McpOAuthCallbackContent({
824
863
  if (event.data?.type !== AUTH_RESULT_MESSAGE) {
825
864
  return;
826
865
  }
827
- if (event.data.sessionId !== sessionId) {
866
+ const resultState = typeof event.data.state === "string" ? event.data.state : typeof event.data.sessionId === "string" ? event.data.sessionId : "";
867
+ if (resultState !== sessionId) {
828
868
  return;
829
869
  }
830
870
  if (event.data.success) {
@@ -841,7 +881,7 @@ function McpOAuthCallbackContent({
841
881
  };
842
882
  window.addEventListener("message", handleResult);
843
883
  channel?.addEventListener("message", handleResult);
844
- const payload = { type: AUTH_CODE_MESSAGE, code, sessionId };
884
+ const payload = { type: AUTH_CODE_MESSAGE, code, state: sessionId, sessionId };
845
885
  if (window.opener) {
846
886
  try {
847
887
  window.opener.postMessage(payload, window.location.origin);