@mcp-ts/sdk 1.3.9 → 1.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 (47) hide show
  1. package/README.md +0 -1
  2. package/dist/adapters/langchain-adapter.js.map +1 -1
  3. package/dist/adapters/langchain-adapter.mjs.map +1 -1
  4. package/dist/client/index.d.mts +3 -189
  5. package/dist/client/index.d.ts +3 -189
  6. package/dist/client/index.js +218 -54
  7. package/dist/client/index.js.map +1 -1
  8. package/dist/client/index.mjs +215 -55
  9. package/dist/client/index.mjs.map +1 -1
  10. package/dist/client/react.d.mts +29 -40
  11. package/dist/client/react.d.ts +29 -40
  12. package/dist/client/react.js +492 -147
  13. package/dist/client/react.js.map +1 -1
  14. package/dist/client/react.mjs +490 -149
  15. package/dist/client/react.mjs.map +1 -1
  16. package/dist/client/vue.d.mts +3 -2
  17. package/dist/client/vue.d.ts +3 -2
  18. package/dist/client/vue.js +239 -63
  19. package/dist/client/vue.js.map +1 -1
  20. package/dist/client/vue.mjs +236 -64
  21. package/dist/client/vue.mjs.map +1 -1
  22. package/dist/index-CQr9q0bF.d.mts +295 -0
  23. package/dist/index-nE_7Io0I.d.ts +295 -0
  24. package/dist/index.d.mts +2 -1
  25. package/dist/index.d.ts +2 -1
  26. package/dist/index.js +315 -64
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.mjs +303 -65
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/server/index.js +93 -10
  31. package/dist/server/index.js.map +1 -1
  32. package/dist/server/index.mjs +88 -10
  33. package/dist/server/index.mjs.map +1 -1
  34. package/package.json +13 -11
  35. package/src/adapters/langchain-adapter.ts +1 -1
  36. package/src/client/core/app-host.ts +252 -65
  37. package/src/client/core/constants.ts +30 -0
  38. package/src/client/index.ts +6 -1
  39. package/src/client/react/index.ts +6 -1
  40. package/src/client/react/use-app-host.ts +13 -19
  41. package/src/client/react/use-mcp-apps.tsx +297 -125
  42. package/src/client/react/use-mcp.ts +75 -36
  43. package/src/client/utils/app-host-utils.ts +62 -0
  44. package/src/client/vue/use-mcp.ts +23 -12
  45. package/src/server/mcp/oauth-client.ts +31 -8
  46. package/src/server/storage/crypto.ts +92 -0
  47. package/src/server/storage/supabase-backend.ts +7 -6
@@ -312,19 +312,31 @@ function useMcp(options) {
312
312
  break;
313
313
  }
314
314
  case "auth_required": {
315
- if (event.authUrl) {
316
- onLog?.("info", `OAuth required - redirecting to ${event.authUrl}`, { authUrl: event.authUrl });
317
- if (!suppressAuthRedirectSessions.value.has(event.sessionId)) {
318
- if (onRedirect) {
319
- onRedirect(event.authUrl);
320
- } else if (typeof window !== "undefined") {
321
- window.location.href = event.authUrl;
322
- }
315
+ const url2 = (event.authUrl || "").trim();
316
+ if (!url2) {
317
+ onLog?.("error", "OAuth required but authorization URL is missing", { sessionId: event.sessionId });
318
+ const index2 = connections.value.findIndex((c) => c.sessionId === event.sessionId);
319
+ if (index2 !== -1) {
320
+ connections.value[index2] = {
321
+ ...connections.value[index2],
322
+ state: "FAILED",
323
+ error: "OAuth authorization URL not available",
324
+ authUrl: void 0
325
+ };
326
+ }
327
+ break;
328
+ }
329
+ onLog?.("info", `OAuth required - redirecting to ${url2}`, { authUrl: url2 });
330
+ if (!suppressAuthRedirectSessions.value.has(event.sessionId)) {
331
+ if (onRedirect) {
332
+ onRedirect(url2);
333
+ } else if (typeof window !== "undefined") {
334
+ window.location.href = url2;
323
335
  }
324
336
  }
325
337
  const index = connections.value.findIndex((c) => c.sessionId === event.sessionId);
326
338
  if (index !== -1) {
327
- connections.value[index] = { ...connections.value[index], state: "AUTHENTICATING", authUrl: event.authUrl };
339
+ connections.value[index] = { ...connections.value[index], state: "AUTHENTICATING", authUrl: url2 };
328
340
  }
329
341
  break;
330
342
  }
@@ -533,22 +545,88 @@ function useMcp(options) {
533
545
  sseClient: clientRef.value
534
546
  };
535
547
  }
536
- var HOST_INFO = { name: "mcp-ts-host", version: "1.0.0" };
537
- var SANDBOX_PERMISSIONS = [
538
- "allow-scripts",
539
- // Required for app JavaScript execution
540
- "allow-forms",
541
- // Required for form submissions
542
- "allow-same-origin",
543
- // Required for Blob URL correctness
544
- "allow-modals",
545
- // Required for dialogs/alerts
546
- "allow-popups",
547
- // Required for opening links
548
- "allow-downloads"
549
- // Required for file downloads
550
- ].join(" ");
551
- var MCP_URI_SCHEMES = ["ui://", "mcp-app://"];
548
+
549
+ // src/client/core/constants.ts
550
+ var SANDBOX_PROXY_READY_METHOD = "ui/notifications/sandbox-proxy-ready";
551
+ var SANDBOX_RESOURCE_READY_METHOD = "ui/notifications/sandbox-resource-ready";
552
+ var APP_HOST_DEFAULTS = {
553
+ /** Default timeout for waiting for the sandbox proxy to be ready (ms). */
554
+ SANDBOX_TIMEOUT_MS: 1e4,
555
+ /** Default host info reported to guest apps. */
556
+ HOST_INFO: { name: "mcp-ts-host", version: "1.0.0" },
557
+ /** Supported MCP App URI schemes. */
558
+ URI_SCHEMES: ["ui://", "mcp-app://"],
559
+ /** Default theme for the host context. */
560
+ THEME: "dark",
561
+ /** Default platform for the host context. */
562
+ PLATFORM: "web",
563
+ /** Default max height for the iframe container (px). */
564
+ MAX_HEIGHT: 6e3
565
+ };
566
+
567
+ // src/client/utils/app-host-utils.ts
568
+ var DEFAULT_SANDBOX_TIMEOUT_MS = APP_HOST_DEFAULTS.SANDBOX_TIMEOUT_MS;
569
+ async function setupSandboxProxyIframe(iframe, sandboxProxyUrl) {
570
+ iframe.style.width = "100%";
571
+ iframe.style.height = "100%";
572
+ iframe.style.border = "none";
573
+ iframe.style.backgroundColor = "transparent";
574
+ iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-downloads");
575
+ const onReady = new Promise((resolve, reject) => {
576
+ let settled = false;
577
+ const cleanup = () => {
578
+ window.removeEventListener("message", messageListener);
579
+ iframe.removeEventListener("error", errorListener);
580
+ };
581
+ const timeoutId = setTimeout(() => {
582
+ if (!settled) {
583
+ settled = true;
584
+ cleanup();
585
+ reject(new Error("Timed out waiting for sandbox proxy iframe to be ready"));
586
+ }
587
+ }, DEFAULT_SANDBOX_TIMEOUT_MS);
588
+ const messageListener = (event) => {
589
+ if (event.source === iframe.contentWindow) {
590
+ if (event.data?.method === SANDBOX_PROXY_READY_METHOD) {
591
+ if (!settled) {
592
+ settled = true;
593
+ clearTimeout(timeoutId);
594
+ cleanup();
595
+ resolve();
596
+ }
597
+ }
598
+ }
599
+ };
600
+ const errorListener = () => {
601
+ if (!settled) {
602
+ settled = true;
603
+ clearTimeout(timeoutId);
604
+ cleanup();
605
+ reject(new Error("Failed to load sandbox proxy iframe"));
606
+ }
607
+ };
608
+ window.addEventListener("message", messageListener);
609
+ iframe.addEventListener("error", errorListener);
610
+ });
611
+ iframe.src = sandboxProxyUrl.href;
612
+ return { onReady };
613
+ }
614
+
615
+ // src/client/core/app-host.ts
616
+ var DEFAULT_MCP_APP_CSP = {
617
+ "default-src": "'self'",
618
+ "script-src": "'self' 'unsafe-inline' 'unsafe-eval' https: blob:",
619
+ "style-src": "'self' 'unsafe-inline' https:",
620
+ "connect-src": "'self' https: wss:",
621
+ "img-src": "'self' data: https: blob:",
622
+ "font-src": "'self' data: https:",
623
+ "media-src": "'self' https: blob:",
624
+ "frame-src": "'none'",
625
+ "object-src": "'none'",
626
+ "base-uri": "'self'"
627
+ };
628
+ var HOST_INFO = APP_HOST_DEFAULTS.HOST_INFO;
629
+ var MCP_URI_SCHEMES = APP_HOST_DEFAULTS.URI_SCHEMES;
552
630
  var AppHost = class {
553
631
  constructor(client, iframe, options) {
554
632
  this.client = client;
@@ -557,10 +635,12 @@ var AppHost = class {
557
635
  __publicField(this, "sessionId");
558
636
  __publicField(this, "resourceCache", /* @__PURE__ */ new Map());
559
637
  __publicField(this, "debug");
560
- /** Callback for app messages (e.g., chat messages from the app) */
638
+ __publicField(this, "sandboxConfig");
639
+ __publicField(this, "options");
561
640
  __publicField(this, "onAppMessage");
562
- this.debug = options?.debug ?? false;
563
- this.configureSandbox();
641
+ this.options = options || {};
642
+ this.debug = this.options.debug ?? false;
643
+ this.sandboxConfig = this.options.sandbox;
564
644
  this.bridge = this.initializeBridge();
565
645
  }
566
646
  // ============================================
@@ -587,19 +667,35 @@ var AppHost = class {
587
667
  }
588
668
  }
589
669
  /**
590
- * Launch an MCP App from a URL or MCP resource URI.
670
+ * Launch an MCP App from a URL, MCP resource URI, or RAW HTML.
591
671
  * Loads the HTML first, then establishes bridge connection.
592
672
  */
593
- async launch(url, sessionId) {
673
+ async launch(source, sessionId) {
594
674
  if (sessionId) this.sessionId = sessionId;
595
675
  const initializedPromise = this.onAppReady();
596
- if (this.isMcpUri(url)) {
597
- await this.launchMcpApp(url);
598
- } else {
599
- this.iframe.src = url;
676
+ let htmlToRender = source.html;
677
+ if (!htmlToRender && source.uri) {
678
+ if (this.isMcpUri(source.uri)) {
679
+ htmlToRender = await this.readMcpAppHtml(source.uri);
680
+ }
681
+ }
682
+ if (!htmlToRender && source.uri && !this.isMcpUri(source.uri)) {
683
+ this.iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-downloads");
684
+ this.iframe.src = source.uri;
685
+ await this.onIframeReady();
686
+ await this.connectBridge();
687
+ } else if (htmlToRender) {
688
+ if (!this.sandboxConfig) {
689
+ throw new Error("Sandbox configuration requires a proxy URL to render HTML safely.");
690
+ }
691
+ await this.launchSandboxedHtml(htmlToRender, this.sandboxConfig);
692
+ await this.connectBridge();
693
+ this.log("Sending HTML resource to sandbox proxy (MCP Apps notification)");
694
+ await this.bridge.sendSandboxResourceReady({
695
+ html: htmlToRender,
696
+ csp: this.sandboxConfig.csp
697
+ });
600
698
  }
601
- await this.onIframeReady();
602
- await this.connectBridge();
603
699
  this.log("Waiting for app initialization");
604
700
  await Promise.race([
605
701
  initializedPromise,
@@ -610,6 +706,19 @@ var AppHost = class {
610
706
  ]);
611
707
  this.log("App launched and ready");
612
708
  }
709
+ // Set host context manually
710
+ setHostContext(context) {
711
+ this.options.hostContext = context;
712
+ if (this.bridge) {
713
+ this.bridge.setHostContext(context);
714
+ }
715
+ }
716
+ // Send streaming inputs manually
717
+ sendToolInputPartial(params) {
718
+ if (this.bridge) {
719
+ this.bridge.sendToolInputPartial(params);
720
+ }
721
+ }
613
722
  /**
614
723
  * Wait for app to signal initialization complete
615
724
  */
@@ -660,14 +769,17 @@ var AppHost = class {
660
769
  this.log("Sending tool cancellation to app");
661
770
  this.bridge.sendToolCancelled({ reason });
662
771
  }
772
+ /**
773
+ * Tell the guest UI the resource is being torn down (unload / cleanup).
774
+ * Forwards to {@link AppBridge.teardownResource} on `@modelcontextprotocol/ext-apps/app-bridge`.
775
+ */
776
+ teardownResource(params = {}) {
777
+ this.log("Sending resource teardown to app");
778
+ this.bridge.teardownResource(params);
779
+ }
663
780
  // ============================================
664
781
  // Private: Initialization
665
782
  // ============================================
666
- configureSandbox() {
667
- if (this.iframe.sandbox.value !== SANDBOX_PERMISSIONS) {
668
- this.iframe.sandbox.value = SANDBOX_PERMISSIONS;
669
- }
670
- }
671
783
  initializeBridge() {
672
784
  const bridge = new appBridge.AppBridge(
673
785
  null,
@@ -676,12 +788,10 @@ var AppHost = class {
676
788
  openLinks: {},
677
789
  serverTools: {},
678
790
  logging: {},
679
- // Declare support for model context updates
680
791
  updateModelContext: { text: {} }
681
792
  },
682
793
  {
683
- // Initial host context
684
- hostContext: {
794
+ hostContext: this.options.hostContext || {
685
795
  theme: "dark",
686
796
  platform: "web",
687
797
  containerDimensions: { maxHeight: 6e3 },
@@ -690,19 +800,56 @@ var AppHost = class {
690
800
  }
691
801
  }
692
802
  );
803
+ bridge.fallbackRequestHandler = this.options.onFallbackRequest;
693
804
  bridge.oncalltool = (params) => this.handleToolCall(params);
694
- bridge.onopenlink = this.handleOpenLink.bind(this);
695
- bridge.onmessage = this.handleMessage.bind(this);
696
- bridge.onloggingmessage = (params) => this.log(`App log [${params.level}]: ${params.data}`);
805
+ if (this.options.onReadResource) {
806
+ bridge.onreadresource = async (params) => {
807
+ const resp = await this.options.onReadResource(params.uri);
808
+ return {
809
+ contents: resp.contents.map((c) => ({
810
+ uri: params.uri,
811
+ text: c.text,
812
+ blob: c.blob
813
+ }))
814
+ };
815
+ };
816
+ }
817
+ bridge.onopenlink = async (params, extra) => {
818
+ if (this.options.onOpenLink) {
819
+ return await this.options.onOpenLink(params, extra);
820
+ }
821
+ return this.handleOpenLink(params);
822
+ };
823
+ bridge.onmessage = async (params, extra) => {
824
+ if (this.options.onMessage) {
825
+ return await this.options.onMessage(params, extra);
826
+ }
827
+ return this.handleMessage(params);
828
+ };
829
+ bridge.onloggingmessage = (params) => {
830
+ this.log(`App log [${params.level}]: ${params.data}`);
831
+ if (this.options.onLoggingMessage) {
832
+ this.options.onLoggingMessage(params);
833
+ }
834
+ };
697
835
  bridge.onupdatemodelcontext = async () => ({});
698
- bridge.onsizechange = async ({ width, height }) => {
699
- if (height !== void 0) this.iframe.style.height = `${height}px`;
700
- if (width !== void 0) this.iframe.style.minWidth = `min(${width}px, 100%)`;
836
+ bridge.onsizechange = async (params) => {
837
+ const { width, height } = params;
838
+ if (height !== void 0 && height > 0) {
839
+ this.iframe.style.height = `${height}px`;
840
+ }
841
+ if (width !== void 0 && width > 0) this.iframe.style.minWidth = `min(${width}px, 100%)`;
842
+ if (this.options.onSizeChanged) {
843
+ this.options.onSizeChanged(params);
844
+ }
701
845
  return {};
702
846
  };
703
- bridge.onrequestdisplaymode = async (params) => ({
704
- mode: params.mode === "fullscreen" ? "fullscreen" : "inline"
705
- });
847
+ bridge.onrequestdisplaymode = async (params, extra) => {
848
+ if (this.options.onRequestDisplayMode) {
849
+ return await this.options.onRequestDisplayMode(params, extra);
850
+ }
851
+ return { mode: params.mode === "fullscreen" ? "fullscreen" : "inline" };
852
+ };
706
853
  return bridge;
707
854
  }
708
855
  async connectBridge() {
@@ -716,6 +863,9 @@ var AppHost = class {
716
863
  this.log("Bridge connected successfully");
717
864
  } catch (error) {
718
865
  this.log("Bridge connection failed", "error");
866
+ if (this.options.onError) {
867
+ this.options.onError(error instanceof Error ? error : new Error(String(error)));
868
+ }
719
869
  throw error;
720
870
  }
721
871
  }
@@ -723,8 +873,11 @@ var AppHost = class {
723
873
  // Private: Bridge Event Handlers
724
874
  // ============================================
725
875
  async handleToolCall(params) {
726
- if (!this.client.isConnected()) {
727
- throw new Error("Client disconnected");
876
+ if (this.options.onCallTool) {
877
+ return await this.options.onCallTool(params);
878
+ }
879
+ if (!this.client || !this.client.isConnected()) {
880
+ throw new Error("Client disconnected or not provided");
728
881
  }
729
882
  const sessionId = await this.getSessionId();
730
883
  if (!sessionId) {
@@ -748,13 +901,19 @@ var AppHost = class {
748
901
  // ============================================
749
902
  // Private: Resource Loading
750
903
  // ============================================
751
- async launchMcpApp(uri) {
752
- if (!this.client.isConnected()) {
753
- throw new Error("Client must be connected");
904
+ async launchSandboxedHtml(html, config) {
905
+ const sandboxUrlString = config.url instanceof URL ? config.url.href : config.url;
906
+ const url = new URL(sandboxUrlString, globalThis.location?.href);
907
+ if (config.csp && Object.keys(config.csp).length > 0) {
908
+ url.searchParams.set("csp", JSON.stringify(config.csp));
754
909
  }
910
+ const { onReady } = await setupSandboxProxyIframe(this.iframe, url);
911
+ await onReady;
912
+ }
913
+ async readMcpAppHtml(uri) {
755
914
  const sessionId = await this.getSessionId();
756
- if (!sessionId) {
757
- throw new Error("No active session");
915
+ if (!sessionId && !this.options.onReadResource) {
916
+ throw new Error("No active session.");
758
917
  }
759
918
  const response = await this.fetchResourceWithCache(sessionId, uri);
760
919
  if (!response?.contents?.length) {
@@ -765,10 +924,18 @@ var AppHost = class {
765
924
  if (!html) {
766
925
  throw new Error(`Invalid content in resource: ${uri}`);
767
926
  }
768
- const blob = new Blob([html], { type: "text/html" });
769
- this.iframe.src = URL.createObjectURL(blob);
927
+ return html;
770
928
  }
771
929
  async fetchResourceWithCache(sessionId, uri) {
930
+ if (this.options.onReadResource) {
931
+ return await this.options.onReadResource(uri);
932
+ }
933
+ if (!sessionId) {
934
+ throw new Error("No active session");
935
+ }
936
+ if (!this.client) {
937
+ throw new Error("No client to read resource from");
938
+ }
772
939
  if (this.hasClientCache()) {
773
940
  return this.client.getOrFetchResource(sessionId, uri);
774
941
  }
@@ -781,8 +948,11 @@ var AppHost = class {
781
948
  }
782
949
  async preloadResource(uri) {
783
950
  try {
951
+ if (this.options.onReadResource) {
952
+ return await this.options.onReadResource(uri);
953
+ }
784
954
  const sessionId = await this.getSessionId();
785
- if (!sessionId) return null;
955
+ if (!sessionId || !this.client) return null;
786
956
  return await this.client.readResource(sessionId, uri);
787
957
  } catch (error) {
788
958
  this.log(`Preload failed for ${uri}`, "warn");
@@ -794,6 +964,7 @@ var AppHost = class {
794
964
  // ============================================
795
965
  async getSessionId() {
796
966
  if (this.sessionId) return this.sessionId;
967
+ if (!this.client) return void 0;
797
968
  const result = await this.client.getSessions();
798
969
  return result.sessions?.[0]?.sessionId;
799
970
  }
@@ -801,6 +972,7 @@ var AppHost = class {
801
972
  return MCP_URI_SCHEMES.some((scheme) => url.startsWith(scheme));
802
973
  }
803
974
  hasClientCache() {
975
+ if (!this.client) return false;
804
976
  return "getOrFetchResource" in this.client && typeof this.client.getOrFetchResource === "function";
805
977
  }
806
978
  extractUiResourceUri(tool) {
@@ -830,7 +1002,11 @@ var AppHost = class {
830
1002
  }
831
1003
  };
832
1004
 
1005
+ exports.APP_HOST_DEFAULTS = APP_HOST_DEFAULTS;
833
1006
  exports.AppHost = AppHost;
1007
+ exports.DEFAULT_MCP_APP_CSP = DEFAULT_MCP_APP_CSP;
1008
+ exports.SANDBOX_PROXY_READY_METHOD = SANDBOX_PROXY_READY_METHOD;
1009
+ exports.SANDBOX_RESOURCE_READY_METHOD = SANDBOX_RESOURCE_READY_METHOD;
834
1010
  exports.SSEClient = SSEClient;
835
1011
  exports.useMcp = useMcp;
836
1012
  //# sourceMappingURL=vue.js.map