@mcp-ts/sdk 2.3.3 → 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.
- package/README.md +113 -30
- package/dist/adapters/agui-adapter.d.mts +3 -3
- package/dist/adapters/agui-adapter.d.ts +3 -3
- package/dist/adapters/agui-middleware.d.mts +3 -3
- package/dist/adapters/agui-middleware.d.ts +3 -3
- package/dist/adapters/ai-adapter.d.mts +3 -3
- package/dist/adapters/ai-adapter.d.ts +3 -3
- package/dist/adapters/langchain-adapter.d.mts +3 -3
- package/dist/adapters/langchain-adapter.d.ts +3 -3
- package/dist/adapters/mastra-adapter.d.mts +1 -1
- package/dist/adapters/mastra-adapter.d.ts +1 -1
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +2 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +2 -2
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/react.d.mts +9 -8
- package/dist/client/react.d.ts +9 -8
- package/dist/client/react.js +66 -26
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +67 -27
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.d.mts +6 -5
- package/dist/client/vue.d.ts +6 -5
- package/dist/client/vue.js +27 -17
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +27 -17
- package/dist/client/vue.mjs.map +1 -1
- package/dist/{index-Cfjsme-a.d.mts → index-ByIjEReo.d.mts} +2 -2
- package/dist/{index-CmjMd2ac.d.ts → index-CtXvKl8N.d.ts} +2 -2
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +729 -397
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +724 -396
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-DYNe6az3.d.mts → multi-session-client-CIMUGF8S.d.mts} +15 -41
- package/dist/{multi-session-client-BYtguGJm.d.ts → multi-session-client-CnvZEGPY.d.ts} +15 -41
- package/dist/server/index.d.mts +45 -17
- package/dist/server/index.d.ts +45 -17
- package/dist/server/index.js +721 -391
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +718 -391
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +9 -8
- package/dist/shared/index.d.ts +9 -8
- package/dist/shared/index.js +7 -5
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs +5 -4
- package/dist/shared/index.mjs.map +1 -1
- package/dist/{tool-router-BMzhoNYt.d.ts → tool-router-CZMrOG8J.d.ts} +1 -1
- package/dist/{tool-router-BhHsvBfP.d.mts → tool-router-CuApsDiV.d.mts} +1 -1
- package/dist/{types-CxFaaZrM.d.mts → types-DCk_IF4L.d.mts} +5 -3
- package/dist/{types-CxFaaZrM.d.ts → types-DCk_IF4L.d.ts} +5 -3
- package/migrations/neon/20260513010000_install_mcp_sessions.sql +40 -3
- package/migrations/neon/20260513020000_add_session_cleanup_cron.sql +4 -4
- package/migrations/supabase/20260330195700_install_mcp_sessions.sql +67 -3
- package/migrations/supabase/20260421010000_add_session_cleanup_cron.sql +6 -6
- package/package.json +13 -3
- package/src/client/core/sse-client.ts +2 -2
- package/src/client/react/index.ts +1 -1
- package/src/client/react/oauth-popup.tsx +34 -13
- package/src/client/react/use-mcp.ts +19 -45
- package/src/client/utils/session-state.ts +43 -0
- package/src/client/vue/use-mcp.ts +18 -47
- package/src/server/handlers/sse-handler.ts +9 -4
- package/src/server/mcp/multi-session-client.ts +2 -6
- package/src/server/mcp/oauth-client.ts +73 -220
- package/src/server/mcp/storage-oauth-provider.ts +79 -50
- package/src/server/storage/file-backend.ts +74 -18
- package/src/server/storage/index.ts +2 -4
- package/src/server/storage/memory-backend.ts +49 -13
- package/src/server/storage/neon-backend.ts +201 -68
- package/src/server/storage/redis-backend.ts +81 -23
- package/src/server/storage/session-lifecycle.ts +78 -0
- package/src/server/storage/sqlite-backend.ts +89 -15
- package/src/server/storage/supabase-backend.ts +188 -63
- package/src/server/storage/types.ts +49 -16
- package/src/shared/constants.ts +5 -6
- package/src/shared/types.ts +5 -3
- package/src/shared/utils.ts +26 -0
package/dist/client/react.mjs
CHANGED
|
@@ -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(
|
|
52
|
-
return this.sendRequest("finishAuth", {
|
|
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 =
|
|
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:
|
|
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.
|
|
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.
|
|
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 (
|
|
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(
|
|
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
|
|
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(
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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);
|