@mcp-ts/sdk 1.5.0 → 1.5.1

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.
@@ -1,7 +1,7 @@
1
1
  import { forwardRef, useRef, useState, useImperativeHandle, useEffect, memo, useCallback, useMemo } from 'react';
2
2
  import { nanoid } from 'nanoid';
3
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
4
  import { AppBridge, PostMessageTransport } from '@modelcontextprotocol/ext-apps/app-bridge';
4
- import { jsxs, jsx } from 'react/jsx-runtime';
5
5
 
6
6
  var __defProp = Object.defineProperty;
7
7
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -313,12 +313,19 @@ function useMcp(options) {
313
313
  const updateConnectionsFromEvent = useCallback((event) => {
314
314
  if (!isMountedRef.current) return;
315
315
  const isTransientReconnectState = (state) => state === "INITIALIZING" || state === "VALIDATING" || state === "RECONNECTING" || state === "CONNECTING" || state === "CONNECTED" || state === "DISCOVERING";
316
+ const getVisibleState = (incomingState, existingState, previousState) => {
317
+ if (incomingState === "INITIALIZING" && (existingState === "AUTHENTICATING" || existingState === "AUTHENTICATED" || previousState === "AUTHENTICATING" || previousState === "AUTHENTICATED")) {
318
+ return existingState === "AUTHENTICATED" || previousState === "AUTHENTICATED" ? "AUTHENTICATED" : "AUTHENTICATING";
319
+ }
320
+ return incomingState;
321
+ };
316
322
  setConnections((prev) => {
317
323
  switch (event.type) {
318
324
  case "state_changed": {
319
325
  const existing = prev.find((c) => c.sessionId === event.sessionId);
320
326
  if (existing) {
321
- const nextState = existing.state === "READY" && isTransientReconnectState(event.state) ? existing.state : event.state;
327
+ const normalizedState = getVisibleState(event.state, existing.state, event.previousState);
328
+ const nextState = existing.state === "READY" && isTransientReconnectState(normalizedState) ? existing.state : normalizedState;
322
329
  return prev.map(
323
330
  (c) => c.sessionId === event.sessionId ? {
324
331
  ...c,
@@ -338,7 +345,9 @@ function useMcp(options) {
338
345
  serverId: event.serverId,
339
346
  serverName: event.serverName,
340
347
  serverUrl: event.serverUrl,
341
- state: event.state,
348
+ // New connections do not have prior local state, so we normalize
349
+ // only against the server-reported previous state.
350
+ state: getVisibleState(event.state, void 0, event.previousState),
342
351
  createdAt: event.createdAt ? new Date(event.createdAt) : void 0,
343
352
  tools: []
344
353
  }
@@ -591,6 +600,305 @@ function useMcp(options) {
591
600
  ]
592
601
  );
593
602
  }
603
+ var AUTH_CODE_MESSAGE = "MCP_AUTH_CODE";
604
+ var AUTH_RESULT_MESSAGE = "MCP_AUTH_RESULT";
605
+ function postPopupResult(popupWindow, result) {
606
+ popupWindow?.postMessage(
607
+ {
608
+ type: AUTH_RESULT_MESSAGE,
609
+ ...result
610
+ },
611
+ window.location.origin
612
+ );
613
+ }
614
+ function openCenteredPopup(url, options = {}) {
615
+ const {
616
+ width = 600,
617
+ height = 700,
618
+ windowName = "mcp-auth-popup",
619
+ features = [],
620
+ onBlocked
621
+ } = options;
622
+ const left = window.screenX + (window.outerWidth - width) / 2;
623
+ const top = window.screenY + (window.outerHeight - height) / 2;
624
+ const featureList = [
625
+ `width=${width}`,
626
+ `height=${height}`,
627
+ `left=${left}`,
628
+ `top=${top}`,
629
+ "resizable=yes",
630
+ "scrollbars=yes",
631
+ "status=yes",
632
+ ...features
633
+ ].join(",");
634
+ const popup = window.open(url, windowName, featureList);
635
+ if (!popup) {
636
+ onBlocked?.(url);
637
+ }
638
+ return popup;
639
+ }
640
+ function createOAuthPopupRedirectHandler(options = {}) {
641
+ return (url) => {
642
+ openCenteredPopup(url, {
643
+ ...options,
644
+ onBlocked: options.onBlocked ?? ((blockedUrl) => {
645
+ window.alert("Popup blocked! Allow popups for this site to complete authentication.");
646
+ window.location.href = blockedUrl;
647
+ })
648
+ });
649
+ };
650
+ }
651
+ function useMcpOAuthPopup(connections, finishAuth) {
652
+ const pendingPopupsRef = useRef(/* @__PURE__ */ new Map());
653
+ useEffect(() => {
654
+ const handleMessage = async (event) => {
655
+ if (event.origin !== window.location.origin) {
656
+ return;
657
+ }
658
+ if (event.data?.type !== AUTH_CODE_MESSAGE || !event.data.code) {
659
+ return;
660
+ }
661
+ const popupWindow = event.source && "postMessage" in event.source ? event.source : null;
662
+ const targetSessionId = typeof event.data.sessionId === "string" ? event.data.sessionId : "";
663
+ if (!targetSessionId) {
664
+ postPopupResult(popupWindow, {
665
+ success: false,
666
+ error: "Missing OAuth session identifier"
667
+ });
668
+ return;
669
+ }
670
+ const targetSession = connections.find((connection) => connection.sessionId === targetSessionId);
671
+ if (!targetSession) {
672
+ postPopupResult(popupWindow, {
673
+ sessionId: targetSessionId,
674
+ success: false,
675
+ error: "OAuth session not found in the current client state"
676
+ });
677
+ return;
678
+ }
679
+ if (popupWindow) {
680
+ pendingPopupsRef.current.set(targetSession.sessionId, popupWindow);
681
+ }
682
+ try {
683
+ await finishAuth(targetSession.sessionId, event.data.code);
684
+ } catch (error) {
685
+ pendingPopupsRef.current.delete(targetSession.sessionId);
686
+ postPopupResult(popupWindow, {
687
+ sessionId: targetSession.sessionId,
688
+ success: false,
689
+ error: error instanceof Error ? error.message : "Failed to finish auth"
690
+ });
691
+ }
692
+ };
693
+ window.addEventListener("message", handleMessage);
694
+ return () => window.removeEventListener("message", handleMessage);
695
+ }, [connections, finishAuth]);
696
+ useEffect(() => {
697
+ for (const connection of connections) {
698
+ const popupWindow = pendingPopupsRef.current.get(connection.sessionId);
699
+ if (!popupWindow) {
700
+ continue;
701
+ }
702
+ if (connection.state === "AUTHENTICATED") {
703
+ postPopupResult(popupWindow, {
704
+ sessionId: connection.sessionId,
705
+ success: true
706
+ });
707
+ pendingPopupsRef.current.delete(connection.sessionId);
708
+ continue;
709
+ }
710
+ if (connection.state === "FAILED") {
711
+ postPopupResult(popupWindow, {
712
+ sessionId: connection.sessionId,
713
+ success: false,
714
+ error: connection.error || "Failed to complete authorization"
715
+ });
716
+ pendingPopupsRef.current.delete(connection.sessionId);
717
+ }
718
+ }
719
+ }, [connections]);
720
+ }
721
+ function McpOAuthCallbackContent({
722
+ code,
723
+ sessionId,
724
+ title = "Verifying Authorization",
725
+ initialStatus = "Completing your authorization...",
726
+ loadingFallback = "Loading...",
727
+ rootStyle,
728
+ cardStyle,
729
+ titleStyle,
730
+ messageStyle,
731
+ renderContainer,
732
+ debugPhase
733
+ }) {
734
+ const [phase, setPhase] = useState(debugPhase || "loading");
735
+ const [errorMessage, setErrorMessage] = useState("");
736
+ const openerMissing = typeof window !== "undefined" ? !window.opener : false;
737
+ const missingCode = !code;
738
+ const missingSessionId = !sessionId;
739
+ const blockingError = openerMissing ? "Error: No opener window found. This window should be opened from the app." : missingCode ? "Error: No authorization code received." : missingSessionId ? "Error: No OAuth state received." : null;
740
+ useEffect(() => {
741
+ if (debugPhase) {
742
+ setPhase(debugPhase);
743
+ if (debugPhase === "error") setErrorMessage("Test error message representing a real failure.");
744
+ return;
745
+ }
746
+ if (blockingError) {
747
+ setPhase("error");
748
+ setErrorMessage(blockingError);
749
+ return;
750
+ }
751
+ let closed = false;
752
+ const handleResult = (event) => {
753
+ if (event.origin !== window.location.origin) {
754
+ return;
755
+ }
756
+ if (event.data?.type !== AUTH_RESULT_MESSAGE) {
757
+ return;
758
+ }
759
+ if (event.data.sessionId !== sessionId) {
760
+ return;
761
+ }
762
+ if (event.data.success) {
763
+ setPhase("success");
764
+ window.removeEventListener("message", handleResult);
765
+ closed = true;
766
+ window.setTimeout(() => window.close(), 1200);
767
+ return;
768
+ }
769
+ const message = typeof event.data.error === "string" && event.data.error.length > 0 ? event.data.error : "Failed to complete authorization.";
770
+ setPhase("error");
771
+ setErrorMessage(message);
772
+ };
773
+ window.addEventListener("message", handleResult);
774
+ try {
775
+ window.opener.postMessage(
776
+ { type: AUTH_CODE_MESSAGE, code, sessionId },
777
+ window.location.origin
778
+ );
779
+ } catch (error) {
780
+ console.error("Failed to communicate with opener:", error);
781
+ window.setTimeout(() => {
782
+ setPhase("error");
783
+ setErrorMessage("Error: Could not communicate with main window.");
784
+ }, 0);
785
+ }
786
+ return () => {
787
+ if (!closed) {
788
+ window.removeEventListener("message", handleResult);
789
+ }
790
+ };
791
+ }, [blockingError, code, sessionId, debugPhase]);
792
+ const loadingBubbles = /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: "8px", justifyContent: "center", height: "12px", alignItems: "center" }, children: [0, 150, 300].map((delay) => /* @__PURE__ */ jsx(
793
+ "span",
794
+ {
795
+ style: {
796
+ width: "8px",
797
+ height: "8px",
798
+ borderRadius: "50%",
799
+ backgroundColor: "currentColor",
800
+ opacity: 0.3,
801
+ animation: `mcp-pulse 1.2s ease-in-out infinite`,
802
+ animationDelay: `${delay}ms`
803
+ }
804
+ },
805
+ delay
806
+ )) });
807
+ const content = /* @__PURE__ */ jsxs(
808
+ "div",
809
+ {
810
+ style: {
811
+ display: "flex",
812
+ justifyContent: "center",
813
+ alignItems: "center",
814
+ minHeight: "100vh",
815
+ fontFamily: "system-ui, -apple-system, sans-serif",
816
+ flexDirection: "column",
817
+ backgroundColor: "#fafafa",
818
+ color: "#18181b",
819
+ boxSizing: "border-box",
820
+ padding: "1.5rem",
821
+ ...rootStyle
822
+ },
823
+ children: [
824
+ /* @__PURE__ */ jsx("style", { children: `
825
+ @keyframes mcp-pulse { 0%, 100% { transform: scale(0.8); opacity: 0.4; } 50% { transform: scale(1.2); opacity: 1; } }
826
+ @keyframes mcp-fade-up { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
827
+ ` }),
828
+ /* @__PURE__ */ jsx(
829
+ "div",
830
+ {
831
+ style: {
832
+ backgroundColor: "#fff",
833
+ borderRadius: "20px",
834
+ boxShadow: "0 20px 25px -5px rgba(0, 0, 0, 0.05), 0 8px 10px -6px rgba(0, 0, 0, 0.05)",
835
+ width: "100%",
836
+ maxWidth: "400px",
837
+ overflow: "hidden",
838
+ border: "1px solid #f4f4f5",
839
+ display: "flex",
840
+ flexDirection: "column",
841
+ ...cardStyle
842
+ },
843
+ children: /* @__PURE__ */ jsxs("div", { style: { padding: "3rem 2rem", textAlign: "center", animation: "mcp-fade-up 0.4s ease-out" }, children: [
844
+ phase === "loading" && /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "1.5rem", alignItems: "center" }, children: [
845
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", height: "48px", width: "48px", background: "#f8fafc", borderRadius: "12px", border: "1px solid #f1f5f9", color: "#64748b" }, children: /* @__PURE__ */ jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" }) }) }),
846
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
847
+ /* @__PURE__ */ jsx("h2", { style: { margin: 0, fontSize: "1.125rem", fontWeight: 600, ...titleStyle }, children: title }),
848
+ /* @__PURE__ */ jsx("p", { style: { margin: 0, fontSize: "0.9rem", color: "#71717a", lineHeight: 1.5, ...messageStyle }, children: initialStatus })
849
+ ] }),
850
+ loadingBubbles
851
+ ] }),
852
+ phase === "success" && /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "1rem", alignItems: "center" }, children: [
853
+ /* @__PURE__ */ jsxs("svg", { style: { color: "#10b981" }, width: "48", height: "48", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
854
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
855
+ /* @__PURE__ */ jsx("path", { d: "M8 12l3 3 5-5" })
856
+ ] }),
857
+ /* @__PURE__ */ jsx("h2", { style: { margin: 0, fontSize: "1.25rem", fontWeight: 600, ...titleStyle }, children: "Connected" }),
858
+ /* @__PURE__ */ jsx("p", { style: { margin: 0, fontSize: "0.9rem", color: "#71717a", lineHeight: 1.5, ...messageStyle }, children: "Authorization complete. This window will close automatically." })
859
+ ] }),
860
+ phase === "error" && /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "1rem", alignItems: "center" }, children: [
861
+ /* @__PURE__ */ jsxs("svg", { style: { color: "#ef4444" }, width: "48", height: "48", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
862
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
863
+ /* @__PURE__ */ jsx("path", { d: "M15 9l-6 6M9 9l6 6" })
864
+ ] }),
865
+ /* @__PURE__ */ jsx("h2", { style: { margin: 0, fontSize: "1.25rem", fontWeight: 600, ...titleStyle }, children: "Connection Failed" }),
866
+ /* @__PURE__ */ jsx("p", { style: { margin: 0, fontSize: "0.9rem", color: "#ef4444", fontWeight: 500, ...messageStyle }, children: errorMessage }),
867
+ /* @__PURE__ */ jsx(
868
+ "button",
869
+ {
870
+ onClick: () => window.close(),
871
+ style: {
872
+ marginTop: "0.5rem",
873
+ padding: "0.625rem 1.25rem",
874
+ border: "none",
875
+ borderRadius: "8px",
876
+ backgroundColor: "#fef2f2",
877
+ color: "#ef4444",
878
+ cursor: "pointer",
879
+ fontWeight: 600,
880
+ fontSize: "0.875rem"
881
+ },
882
+ children: "Close Window"
883
+ }
884
+ )
885
+ ] })
886
+ ] })
887
+ }
888
+ )
889
+ ]
890
+ }
891
+ );
892
+ if (renderContainer) {
893
+ return /* @__PURE__ */ jsx(Fragment, { children: renderContainer(content) });
894
+ }
895
+ return content;
896
+ }
897
+ function McpOAuthCallbackFallback({
898
+ children = "Loading..."
899
+ }) {
900
+ return /* @__PURE__ */ jsx(Fragment, { children: children || "Loading..." });
901
+ }
594
902
 
595
903
  // src/client/core/constants.ts
596
904
  var SANDBOX_PROXY_READY_METHOD = "ui/notifications/sandbox-proxy-ready";
@@ -1367,6 +1675,6 @@ function getMcpAppMetadata(mcpClient, toolName, input) {
1367
1675
  return void 0;
1368
1676
  }
1369
1677
 
1370
- export { APP_HOST_DEFAULTS, AppHost, DEFAULT_MCP_APP_CSP, McpAppRenderer, SANDBOX_PROXY_READY_METHOD, SANDBOX_RESOURCE_READY_METHOD, SSEClient, getMcpAppMetadata, useAppHost, useMcp, useMcpApps };
1678
+ export { APP_HOST_DEFAULTS, AppHost, DEFAULT_MCP_APP_CSP, McpAppRenderer, McpOAuthCallbackContent, McpOAuthCallbackFallback, SANDBOX_PROXY_READY_METHOD, SANDBOX_RESOURCE_READY_METHOD, SSEClient, createOAuthPopupRedirectHandler, getMcpAppMetadata, openCenteredPopup, useAppHost, useMcp, useMcpApps, useMcpOAuthPopup };
1371
1679
  //# sourceMappingURL=react.mjs.map
1372
1680
  //# sourceMappingURL=react.mjs.map