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