@mcp-ts/sdk 1.3.10 → 1.5.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 (86) hide show
  1. package/README.md +20 -27
  2. package/dist/adapters/agui-adapter.d.mts +16 -0
  3. package/dist/adapters/agui-adapter.d.ts +16 -0
  4. package/dist/adapters/agui-adapter.js +185 -0
  5. package/dist/adapters/agui-adapter.js.map +1 -1
  6. package/dist/adapters/agui-adapter.mjs +185 -0
  7. package/dist/adapters/agui-adapter.mjs.map +1 -1
  8. package/dist/adapters/agui-middleware.d.mts +2 -0
  9. package/dist/adapters/agui-middleware.d.ts +2 -0
  10. package/dist/adapters/agui-middleware.js.map +1 -1
  11. package/dist/adapters/agui-middleware.mjs.map +1 -1
  12. package/dist/adapters/ai-adapter.d.mts +21 -0
  13. package/dist/adapters/ai-adapter.d.ts +21 -0
  14. package/dist/adapters/ai-adapter.js +175 -0
  15. package/dist/adapters/ai-adapter.js.map +1 -1
  16. package/dist/adapters/ai-adapter.mjs +175 -0
  17. package/dist/adapters/ai-adapter.mjs.map +1 -1
  18. package/dist/adapters/langchain-adapter.d.mts +16 -0
  19. package/dist/adapters/langchain-adapter.d.ts +16 -0
  20. package/dist/adapters/langchain-adapter.js +179 -0
  21. package/dist/adapters/langchain-adapter.js.map +1 -1
  22. package/dist/adapters/langchain-adapter.mjs +179 -0
  23. package/dist/adapters/langchain-adapter.mjs.map +1 -1
  24. package/dist/client/index.d.mts +4 -190
  25. package/dist/client/index.d.ts +4 -190
  26. package/dist/client/index.js +218 -54
  27. package/dist/client/index.js.map +1 -1
  28. package/dist/client/index.mjs +215 -55
  29. package/dist/client/index.mjs.map +1 -1
  30. package/dist/client/react.d.mts +31 -17
  31. package/dist/client/react.d.ts +31 -17
  32. package/dist/client/react.js +447 -103
  33. package/dist/client/react.js.map +1 -1
  34. package/dist/client/react.mjs +443 -105
  35. package/dist/client/react.mjs.map +1 -1
  36. package/dist/client/vue.d.mts +5 -4
  37. package/dist/client/vue.d.ts +5 -4
  38. package/dist/client/vue.js +239 -63
  39. package/dist/client/vue.js.map +1 -1
  40. package/dist/client/vue.mjs +236 -64
  41. package/dist/client/vue.mjs.map +1 -1
  42. package/dist/index-DcYfpY3H.d.mts +295 -0
  43. package/dist/index-GfC_eNEv.d.ts +295 -0
  44. package/dist/index.d.mts +5 -3
  45. package/dist/index.d.ts +5 -3
  46. package/dist/index.js +1120 -59
  47. package/dist/index.js.map +1 -1
  48. package/dist/index.mjs +1097 -60
  49. package/dist/index.mjs.map +1 -1
  50. package/dist/server/index.d.mts +2 -2
  51. package/dist/server/index.d.ts +2 -2
  52. package/dist/server/index.js +18 -5
  53. package/dist/server/index.js.map +1 -1
  54. package/dist/server/index.mjs +18 -5
  55. package/dist/server/index.mjs.map +1 -1
  56. package/dist/shared/index.d.mts +86 -4
  57. package/dist/shared/index.d.ts +86 -4
  58. package/dist/shared/index.js +874 -0
  59. package/dist/shared/index.js.map +1 -1
  60. package/dist/shared/index.mjs +865 -1
  61. package/dist/shared/index.mjs.map +1 -1
  62. package/dist/tool-router-Bo8qZbsD.d.ts +325 -0
  63. package/dist/tool-router-XnWVxPzv.d.mts +325 -0
  64. package/dist/{types-CW6lghof.d.mts → types-CfCoIsWI.d.mts} +27 -1
  65. package/dist/{types-CW6lghof.d.ts → types-CfCoIsWI.d.ts} +27 -1
  66. package/package.json +15 -12
  67. package/src/adapters/agui-adapter.ts +79 -0
  68. package/src/adapters/ai-adapter.ts +75 -0
  69. package/src/adapters/langchain-adapter.ts +75 -1
  70. package/src/client/core/app-host.ts +252 -65
  71. package/src/client/core/constants.ts +30 -0
  72. package/src/client/index.ts +6 -1
  73. package/src/client/react/index.ts +3 -0
  74. package/src/client/react/use-app-host.ts +8 -15
  75. package/src/client/react/use-mcp-apps.tsx +262 -49
  76. package/src/client/react/use-mcp.ts +23 -12
  77. package/src/client/utils/app-host-utils.ts +62 -0
  78. package/src/client/vue/use-mcp.ts +23 -12
  79. package/src/server/index.ts +2 -0
  80. package/src/server/mcp/oauth-client.ts +34 -9
  81. package/src/shared/index.ts +36 -0
  82. package/src/shared/meta-tools.ts +387 -0
  83. package/src/shared/schema-compressor.ts +124 -0
  84. package/src/shared/tool-index.ts +499 -0
  85. package/src/shared/tool-router.ts +469 -0
  86. package/src/shared/types.ts +30 -0
@@ -356,18 +356,28 @@ function useMcp(options) {
356
356
  );
357
357
  }
358
358
  case "auth_required": {
359
- if (event.authUrl) {
360
- onLog?.("info", `OAuth required - redirecting to ${event.authUrl}`, { authUrl: event.authUrl });
361
- if (!suppressAuthRedirectSessionsRef.current.has(event.sessionId)) {
362
- if (onRedirect) {
363
- onRedirect(event.authUrl);
364
- } else if (typeof window !== "undefined") {
365
- window.location.href = event.authUrl;
366
- }
359
+ const url2 = (event.authUrl || "").trim();
360
+ if (!url2) {
361
+ onLog?.("error", "OAuth required but authorization URL is missing", { sessionId: event.sessionId });
362
+ return prev.map(
363
+ (c) => c.sessionId === event.sessionId ? {
364
+ ...c,
365
+ state: "FAILED",
366
+ error: "OAuth authorization URL not available",
367
+ authUrl: void 0
368
+ } : c
369
+ );
370
+ }
371
+ onLog?.("info", `OAuth required - redirecting to ${url2}`, { authUrl: url2 });
372
+ if (!suppressAuthRedirectSessionsRef.current.has(event.sessionId)) {
373
+ if (onRedirect) {
374
+ onRedirect(url2);
375
+ } else if (typeof window !== "undefined") {
376
+ window.location.href = url2;
367
377
  }
368
378
  }
369
379
  return prev.map(
370
- (c) => c.sessionId === event.sessionId ? { ...c, state: "AUTHENTICATING", authUrl: event.authUrl } : c
380
+ (c) => c.sessionId === event.sessionId ? { ...c, state: "AUTHENTICATING", authUrl: url2 } : c
371
381
  );
372
382
  }
373
383
  case "error": {
@@ -583,22 +593,88 @@ function useMcp(options) {
583
593
  ]
584
594
  );
585
595
  }
586
- var HOST_INFO = { name: "mcp-ts-host", version: "1.0.0" };
587
- var SANDBOX_PERMISSIONS = [
588
- "allow-scripts",
589
- // Required for app JavaScript execution
590
- "allow-forms",
591
- // Required for form submissions
592
- "allow-same-origin",
593
- // Required for Blob URL correctness
594
- "allow-modals",
595
- // Required for dialogs/alerts
596
- "allow-popups",
597
- // Required for opening links
598
- "allow-downloads"
599
- // Required for file downloads
600
- ].join(" ");
601
- var MCP_URI_SCHEMES = ["ui://", "mcp-app://"];
596
+
597
+ // src/client/core/constants.ts
598
+ var SANDBOX_PROXY_READY_METHOD = "ui/notifications/sandbox-proxy-ready";
599
+ var SANDBOX_RESOURCE_READY_METHOD = "ui/notifications/sandbox-resource-ready";
600
+ var APP_HOST_DEFAULTS = {
601
+ /** Default timeout for waiting for the sandbox proxy to be ready (ms). */
602
+ SANDBOX_TIMEOUT_MS: 1e4,
603
+ /** Default host info reported to guest apps. */
604
+ HOST_INFO: { name: "mcp-ts-host", version: "1.0.0" },
605
+ /** Supported MCP App URI schemes. */
606
+ URI_SCHEMES: ["ui://", "mcp-app://"],
607
+ /** Default theme for the host context. */
608
+ THEME: "dark",
609
+ /** Default platform for the host context. */
610
+ PLATFORM: "web",
611
+ /** Default max height for the iframe container (px). */
612
+ MAX_HEIGHT: 6e3
613
+ };
614
+
615
+ // src/client/utils/app-host-utils.ts
616
+ var DEFAULT_SANDBOX_TIMEOUT_MS = APP_HOST_DEFAULTS.SANDBOX_TIMEOUT_MS;
617
+ async function setupSandboxProxyIframe(iframe, sandboxProxyUrl) {
618
+ iframe.style.width = "100%";
619
+ iframe.style.height = "100%";
620
+ iframe.style.border = "none";
621
+ iframe.style.backgroundColor = "transparent";
622
+ iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-downloads");
623
+ const onReady = new Promise((resolve, reject) => {
624
+ let settled = false;
625
+ const cleanup = () => {
626
+ window.removeEventListener("message", messageListener);
627
+ iframe.removeEventListener("error", errorListener);
628
+ };
629
+ const timeoutId = setTimeout(() => {
630
+ if (!settled) {
631
+ settled = true;
632
+ cleanup();
633
+ reject(new Error("Timed out waiting for sandbox proxy iframe to be ready"));
634
+ }
635
+ }, DEFAULT_SANDBOX_TIMEOUT_MS);
636
+ const messageListener = (event) => {
637
+ if (event.source === iframe.contentWindow) {
638
+ if (event.data?.method === SANDBOX_PROXY_READY_METHOD) {
639
+ if (!settled) {
640
+ settled = true;
641
+ clearTimeout(timeoutId);
642
+ cleanup();
643
+ resolve();
644
+ }
645
+ }
646
+ }
647
+ };
648
+ const errorListener = () => {
649
+ if (!settled) {
650
+ settled = true;
651
+ clearTimeout(timeoutId);
652
+ cleanup();
653
+ reject(new Error("Failed to load sandbox proxy iframe"));
654
+ }
655
+ };
656
+ window.addEventListener("message", messageListener);
657
+ iframe.addEventListener("error", errorListener);
658
+ });
659
+ iframe.src = sandboxProxyUrl.href;
660
+ return { onReady };
661
+ }
662
+
663
+ // src/client/core/app-host.ts
664
+ var DEFAULT_MCP_APP_CSP = {
665
+ "default-src": "'self'",
666
+ "script-src": "'self' 'unsafe-inline' 'unsafe-eval' https: blob:",
667
+ "style-src": "'self' 'unsafe-inline' https:",
668
+ "connect-src": "'self' https: wss:",
669
+ "img-src": "'self' data: https: blob:",
670
+ "font-src": "'self' data: https:",
671
+ "media-src": "'self' https: blob:",
672
+ "frame-src": "'none'",
673
+ "object-src": "'none'",
674
+ "base-uri": "'self'"
675
+ };
676
+ var HOST_INFO = APP_HOST_DEFAULTS.HOST_INFO;
677
+ var MCP_URI_SCHEMES = APP_HOST_DEFAULTS.URI_SCHEMES;
602
678
  var AppHost = class {
603
679
  constructor(client, iframe, options) {
604
680
  this.client = client;
@@ -607,10 +683,12 @@ var AppHost = class {
607
683
  __publicField(this, "sessionId");
608
684
  __publicField(this, "resourceCache", /* @__PURE__ */ new Map());
609
685
  __publicField(this, "debug");
610
- /** Callback for app messages (e.g., chat messages from the app) */
686
+ __publicField(this, "sandboxConfig");
687
+ __publicField(this, "options");
611
688
  __publicField(this, "onAppMessage");
612
- this.debug = options?.debug ?? false;
613
- this.configureSandbox();
689
+ this.options = options || {};
690
+ this.debug = this.options.debug ?? false;
691
+ this.sandboxConfig = this.options.sandbox;
614
692
  this.bridge = this.initializeBridge();
615
693
  }
616
694
  // ============================================
@@ -637,19 +715,35 @@ var AppHost = class {
637
715
  }
638
716
  }
639
717
  /**
640
- * Launch an MCP App from a URL or MCP resource URI.
718
+ * Launch an MCP App from a URL, MCP resource URI, or RAW HTML.
641
719
  * Loads the HTML first, then establishes bridge connection.
642
720
  */
643
- async launch(url, sessionId) {
721
+ async launch(source, sessionId) {
644
722
  if (sessionId) this.sessionId = sessionId;
645
723
  const initializedPromise = this.onAppReady();
646
- if (this.isMcpUri(url)) {
647
- await this.launchMcpApp(url);
648
- } else {
649
- this.iframe.src = url;
724
+ let htmlToRender = source.html;
725
+ if (!htmlToRender && source.uri) {
726
+ if (this.isMcpUri(source.uri)) {
727
+ htmlToRender = await this.readMcpAppHtml(source.uri);
728
+ }
729
+ }
730
+ if (!htmlToRender && source.uri && !this.isMcpUri(source.uri)) {
731
+ this.iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-downloads");
732
+ this.iframe.src = source.uri;
733
+ await this.onIframeReady();
734
+ await this.connectBridge();
735
+ } else if (htmlToRender) {
736
+ if (!this.sandboxConfig) {
737
+ throw new Error("Sandbox configuration requires a proxy URL to render HTML safely.");
738
+ }
739
+ await this.launchSandboxedHtml(htmlToRender, this.sandboxConfig);
740
+ await this.connectBridge();
741
+ this.log("Sending HTML resource to sandbox proxy (MCP Apps notification)");
742
+ await this.bridge.sendSandboxResourceReady({
743
+ html: htmlToRender,
744
+ csp: this.sandboxConfig.csp
745
+ });
650
746
  }
651
- await this.onIframeReady();
652
- await this.connectBridge();
653
747
  this.log("Waiting for app initialization");
654
748
  await Promise.race([
655
749
  initializedPromise,
@@ -660,6 +754,19 @@ var AppHost = class {
660
754
  ]);
661
755
  this.log("App launched and ready");
662
756
  }
757
+ // Set host context manually
758
+ setHostContext(context) {
759
+ this.options.hostContext = context;
760
+ if (this.bridge) {
761
+ this.bridge.setHostContext(context);
762
+ }
763
+ }
764
+ // Send streaming inputs manually
765
+ sendToolInputPartial(params) {
766
+ if (this.bridge) {
767
+ this.bridge.sendToolInputPartial(params);
768
+ }
769
+ }
663
770
  /**
664
771
  * Wait for app to signal initialization complete
665
772
  */
@@ -710,14 +817,17 @@ var AppHost = class {
710
817
  this.log("Sending tool cancellation to app");
711
818
  this.bridge.sendToolCancelled({ reason });
712
819
  }
820
+ /**
821
+ * Tell the guest UI the resource is being torn down (unload / cleanup).
822
+ * Forwards to {@link AppBridge.teardownResource} on `@modelcontextprotocol/ext-apps/app-bridge`.
823
+ */
824
+ teardownResource(params = {}) {
825
+ this.log("Sending resource teardown to app");
826
+ this.bridge.teardownResource(params);
827
+ }
713
828
  // ============================================
714
829
  // Private: Initialization
715
830
  // ============================================
716
- configureSandbox() {
717
- if (this.iframe.sandbox.value !== SANDBOX_PERMISSIONS) {
718
- this.iframe.sandbox.value = SANDBOX_PERMISSIONS;
719
- }
720
- }
721
831
  initializeBridge() {
722
832
  const bridge = new appBridge.AppBridge(
723
833
  null,
@@ -726,12 +836,10 @@ var AppHost = class {
726
836
  openLinks: {},
727
837
  serverTools: {},
728
838
  logging: {},
729
- // Declare support for model context updates
730
839
  updateModelContext: { text: {} }
731
840
  },
732
841
  {
733
- // Initial host context
734
- hostContext: {
842
+ hostContext: this.options.hostContext || {
735
843
  theme: "dark",
736
844
  platform: "web",
737
845
  containerDimensions: { maxHeight: 6e3 },
@@ -740,19 +848,56 @@ var AppHost = class {
740
848
  }
741
849
  }
742
850
  );
851
+ bridge.fallbackRequestHandler = this.options.onFallbackRequest;
743
852
  bridge.oncalltool = (params) => this.handleToolCall(params);
744
- bridge.onopenlink = this.handleOpenLink.bind(this);
745
- bridge.onmessage = this.handleMessage.bind(this);
746
- bridge.onloggingmessage = (params) => this.log(`App log [${params.level}]: ${params.data}`);
853
+ if (this.options.onReadResource) {
854
+ bridge.onreadresource = async (params) => {
855
+ const resp = await this.options.onReadResource(params.uri);
856
+ return {
857
+ contents: resp.contents.map((c) => ({
858
+ uri: params.uri,
859
+ text: c.text,
860
+ blob: c.blob
861
+ }))
862
+ };
863
+ };
864
+ }
865
+ bridge.onopenlink = async (params, extra) => {
866
+ if (this.options.onOpenLink) {
867
+ return await this.options.onOpenLink(params, extra);
868
+ }
869
+ return this.handleOpenLink(params);
870
+ };
871
+ bridge.onmessage = async (params, extra) => {
872
+ if (this.options.onMessage) {
873
+ return await this.options.onMessage(params, extra);
874
+ }
875
+ return this.handleMessage(params);
876
+ };
877
+ bridge.onloggingmessage = (params) => {
878
+ this.log(`App log [${params.level}]: ${params.data}`);
879
+ if (this.options.onLoggingMessage) {
880
+ this.options.onLoggingMessage(params);
881
+ }
882
+ };
747
883
  bridge.onupdatemodelcontext = async () => ({});
748
- bridge.onsizechange = async ({ width, height }) => {
749
- if (height !== void 0) this.iframe.style.height = `${height}px`;
750
- if (width !== void 0) this.iframe.style.minWidth = `min(${width}px, 100%)`;
884
+ bridge.onsizechange = async (params) => {
885
+ const { width, height } = params;
886
+ if (height !== void 0 && height > 0) {
887
+ this.iframe.style.height = `${height}px`;
888
+ }
889
+ if (width !== void 0 && width > 0) this.iframe.style.minWidth = `min(${width}px, 100%)`;
890
+ if (this.options.onSizeChanged) {
891
+ this.options.onSizeChanged(params);
892
+ }
751
893
  return {};
752
894
  };
753
- bridge.onrequestdisplaymode = async (params) => ({
754
- mode: params.mode === "fullscreen" ? "fullscreen" : "inline"
755
- });
895
+ bridge.onrequestdisplaymode = async (params, extra) => {
896
+ if (this.options.onRequestDisplayMode) {
897
+ return await this.options.onRequestDisplayMode(params, extra);
898
+ }
899
+ return { mode: params.mode === "fullscreen" ? "fullscreen" : "inline" };
900
+ };
756
901
  return bridge;
757
902
  }
758
903
  async connectBridge() {
@@ -766,6 +911,9 @@ var AppHost = class {
766
911
  this.log("Bridge connected successfully");
767
912
  } catch (error) {
768
913
  this.log("Bridge connection failed", "error");
914
+ if (this.options.onError) {
915
+ this.options.onError(error instanceof Error ? error : new Error(String(error)));
916
+ }
769
917
  throw error;
770
918
  }
771
919
  }
@@ -773,8 +921,11 @@ var AppHost = class {
773
921
  // Private: Bridge Event Handlers
774
922
  // ============================================
775
923
  async handleToolCall(params) {
776
- if (!this.client.isConnected()) {
777
- throw new Error("Client disconnected");
924
+ if (this.options.onCallTool) {
925
+ return await this.options.onCallTool(params);
926
+ }
927
+ if (!this.client || !this.client.isConnected()) {
928
+ throw new Error("Client disconnected or not provided");
778
929
  }
779
930
  const sessionId = await this.getSessionId();
780
931
  if (!sessionId) {
@@ -798,13 +949,19 @@ var AppHost = class {
798
949
  // ============================================
799
950
  // Private: Resource Loading
800
951
  // ============================================
801
- async launchMcpApp(uri) {
802
- if (!this.client.isConnected()) {
803
- throw new Error("Client must be connected");
952
+ async launchSandboxedHtml(html, config) {
953
+ const sandboxUrlString = config.url instanceof URL ? config.url.href : config.url;
954
+ const url = new URL(sandboxUrlString, globalThis.location?.href);
955
+ if (config.csp && Object.keys(config.csp).length > 0) {
956
+ url.searchParams.set("csp", JSON.stringify(config.csp));
804
957
  }
958
+ const { onReady } = await setupSandboxProxyIframe(this.iframe, url);
959
+ await onReady;
960
+ }
961
+ async readMcpAppHtml(uri) {
805
962
  const sessionId = await this.getSessionId();
806
- if (!sessionId) {
807
- throw new Error("No active session");
963
+ if (!sessionId && !this.options.onReadResource) {
964
+ throw new Error("No active session.");
808
965
  }
809
966
  const response = await this.fetchResourceWithCache(sessionId, uri);
810
967
  if (!response?.contents?.length) {
@@ -815,10 +972,18 @@ var AppHost = class {
815
972
  if (!html) {
816
973
  throw new Error(`Invalid content in resource: ${uri}`);
817
974
  }
818
- const blob = new Blob([html], { type: "text/html" });
819
- this.iframe.src = URL.createObjectURL(blob);
975
+ return html;
820
976
  }
821
977
  async fetchResourceWithCache(sessionId, uri) {
978
+ if (this.options.onReadResource) {
979
+ return await this.options.onReadResource(uri);
980
+ }
981
+ if (!sessionId) {
982
+ throw new Error("No active session");
983
+ }
984
+ if (!this.client) {
985
+ throw new Error("No client to read resource from");
986
+ }
822
987
  if (this.hasClientCache()) {
823
988
  return this.client.getOrFetchResource(sessionId, uri);
824
989
  }
@@ -831,8 +996,11 @@ var AppHost = class {
831
996
  }
832
997
  async preloadResource(uri) {
833
998
  try {
999
+ if (this.options.onReadResource) {
1000
+ return await this.options.onReadResource(uri);
1001
+ }
834
1002
  const sessionId = await this.getSessionId();
835
- if (!sessionId) return null;
1003
+ if (!sessionId || !this.client) return null;
836
1004
  return await this.client.readResource(sessionId, uri);
837
1005
  } catch (error) {
838
1006
  this.log(`Preload failed for ${uri}`, "warn");
@@ -844,6 +1012,7 @@ var AppHost = class {
844
1012
  // ============================================
845
1013
  async getSessionId() {
846
1014
  if (this.sessionId) return this.sessionId;
1015
+ if (!this.client) return void 0;
847
1016
  const result = await this.client.getSessions();
848
1017
  return result.sessions?.[0]?.sessionId;
849
1018
  }
@@ -851,6 +1020,7 @@ var AppHost = class {
851
1020
  return MCP_URI_SCHEMES.some((scheme) => url.startsWith(scheme));
852
1021
  }
853
1022
  hasClientCache() {
1023
+ if (!this.client) return false;
854
1024
  return "getOrFetchResource" in this.client && typeof this.client.getOrFetchResource === "function";
855
1025
  }
856
1026
  extractUiResourceUri(tool) {
@@ -894,10 +1064,7 @@ function useAppHost(client, iframeRef, options) {
894
1064
  initializingRef.current = true;
895
1065
  const initHost = async () => {
896
1066
  try {
897
- const appHost = new AppHost(client, iframeRef.current);
898
- appHost.onAppMessage = (params) => {
899
- onMessageRef.current?.(params);
900
- };
1067
+ const appHost = new AppHost(client, iframeRef.current, options);
901
1068
  setHost(appHost);
902
1069
  await appHost.start();
903
1070
  } catch (err) {
@@ -913,44 +1080,163 @@ function useAppHost(client, iframeRef, options) {
913
1080
  }, [client, iframeRef]);
914
1081
  return { host, error };
915
1082
  }
916
- var McpAppView = react.memo(function McpAppView2({
1083
+
1084
+ // src/shared/meta-tools.ts
1085
+ function resolveMetaToolProxy(toolName, args) {
1086
+ if (toolName === "mcp_execute_tool") {
1087
+ const innerName = args?.toolName;
1088
+ const innerArgs = args?.args;
1089
+ return {
1090
+ toolName: typeof innerName === "string" && innerName ? innerName : toolName,
1091
+ args: innerArgs && typeof innerArgs === "object" && !Array.isArray(innerArgs) ? innerArgs : {}
1092
+ };
1093
+ }
1094
+ const match = toolName.match(/(?:tool_[^_]+_)?(.+)$/);
1095
+ const resolvedName = match?.[1] ?? toolName;
1096
+ return { toolName: resolvedName, args: args ?? {} };
1097
+ }
1098
+ var McpAppViewInner = react.forwardRef(function McpAppView({
917
1099
  clientRef,
918
1100
  name,
1101
+ toolResourceUri,
1102
+ html,
919
1103
  input,
920
1104
  result,
921
- status,
922
- className
923
- }) {
1105
+ status = "idle",
1106
+ toolInputPartial,
1107
+ toolCancelled,
1108
+ sandbox,
1109
+ hostContext,
1110
+ onCallTool,
1111
+ onReadResource,
1112
+ onFallbackRequest,
1113
+ onMessage,
1114
+ onOpenLink,
1115
+ onLoggingMessage,
1116
+ onSizeChanged,
1117
+ onError: onHostError,
1118
+ className,
1119
+ loader
1120
+ }, ref) {
924
1121
  const mcpClient = clientRef.current;
925
- const metadata = getMcpAppMetadata(mcpClient, name);
1122
+ const { toolName: resolvedToolName, args: resolvedInput } = resolveMetaToolProxy(name, input);
1123
+ const metadata = getMcpAppMetadata(mcpClient, resolvedToolName, resolvedInput);
926
1124
  const sseClient = mcpClient?.sseClient ?? null;
927
- const resourceUri = metadata?.resourceUri;
1125
+ const resourceUri = toolResourceUri || metadata?.resourceUri;
928
1126
  const appSessionId = metadata?.sessionId;
929
1127
  const iframeRef = react.useRef(null);
930
- const { host, error: hostError } = useAppHost(sseClient, iframeRef);
1128
+ const containerRef = react.useRef(null);
1129
+ const preFullscreenHeightRef = react.useRef(null);
1130
+ const displayModeRef = react.useRef("inline");
1131
+ const [displayMode, setDisplayMode] = react.useState("inline");
1132
+ const setDisplayModeWithRef = (mode) => {
1133
+ displayModeRef.current = mode;
1134
+ setDisplayMode(mode);
1135
+ };
1136
+ const { host, error: hostError } = useAppHost(sseClient, iframeRef, {
1137
+ sandbox,
1138
+ hostContext,
1139
+ onCallTool,
1140
+ onReadResource,
1141
+ onFallbackRequest,
1142
+ onMessage,
1143
+ onOpenLink,
1144
+ onLoggingMessage,
1145
+ // Intercept onSizeChanged: when exiting fullscreen, ignore guest resize events
1146
+ // that arrive with the shrunken viewport dimensions, and restore the pre-fullscreen height.
1147
+ onSizeChanged: (params) => {
1148
+ if (displayModeRef.current === "inline" && preFullscreenHeightRef.current !== null) {
1149
+ const savedHeight = preFullscreenHeightRef.current;
1150
+ preFullscreenHeightRef.current = null;
1151
+ if (iframeRef.current) {
1152
+ iframeRef.current.style.height = `${savedHeight}px`;
1153
+ }
1154
+ return;
1155
+ }
1156
+ onSizeChanged?.(params);
1157
+ },
1158
+ onError: onHostError,
1159
+ onRequestDisplayMode: async (params) => {
1160
+ if (params.mode === "fullscreen") {
1161
+ if (iframeRef.current) {
1162
+ const h = iframeRef.current.getBoundingClientRect().height;
1163
+ if (h > 0) preFullscreenHeightRef.current = h;
1164
+ }
1165
+ try {
1166
+ if (containerRef.current?.requestFullscreen) {
1167
+ await containerRef.current.requestFullscreen();
1168
+ } else if (containerRef.current?.webkitRequestFullscreen) {
1169
+ await containerRef.current.webkitRequestFullscreen();
1170
+ }
1171
+ setDisplayModeWithRef("fullscreen");
1172
+ } catch (err) {
1173
+ console.warn("[McpAppHost] requestFullscreen failed:", err);
1174
+ preFullscreenHeightRef.current = null;
1175
+ return { mode: "inline" };
1176
+ }
1177
+ } else if (params.mode === "inline") {
1178
+ restoreHeightAfterFullscreen();
1179
+ try {
1180
+ if (document.fullscreenElement) {
1181
+ await document.exitFullscreen();
1182
+ }
1183
+ } catch (err) {
1184
+ }
1185
+ setDisplayModeWithRef("inline");
1186
+ }
1187
+ return { mode: params.mode };
1188
+ }
1189
+ });
1190
+ react.useImperativeHandle(
1191
+ ref,
1192
+ () => ({
1193
+ teardownResource: (params) => {
1194
+ host?.teardownResource(params ?? {});
1195
+ }
1196
+ }),
1197
+ [host]
1198
+ );
931
1199
  const [isLaunched, setIsLaunched] = react.useState(false);
932
1200
  const [error, setError] = react.useState(null);
933
1201
  const sentInputRef = react.useRef(false);
934
1202
  const sentResultRef = react.useRef(false);
935
- const lastInputRef = react.useRef(input);
1203
+ const lastInputRef = react.useRef(resolvedInput);
936
1204
  const lastResultRef = react.useRef(result);
937
1205
  const lastStatusRef = react.useRef(status);
938
1206
  react.useEffect(() => {
939
1207
  setIsLaunched(false);
940
1208
  setError(null);
941
1209
  }, [resourceUri, appSessionId]);
1210
+ const restoreHeightAfterFullscreen = () => {
1211
+ const savedHeight = preFullscreenHeightRef.current;
1212
+ if (savedHeight && iframeRef.current) {
1213
+ iframeRef.current.style.height = `${savedHeight}px`;
1214
+ }
1215
+ preFullscreenHeightRef.current = null;
1216
+ };
1217
+ react.useEffect(() => {
1218
+ const onFullscreenChange = () => {
1219
+ const isFullscreen = !!document.fullscreenElement;
1220
+ if (!isFullscreen && displayModeRef.current === "fullscreen") {
1221
+ restoreHeightAfterFullscreen();
1222
+ setDisplayModeWithRef("inline");
1223
+ }
1224
+ };
1225
+ document.addEventListener("fullscreenchange", onFullscreenChange);
1226
+ return () => document.removeEventListener("fullscreenchange", onFullscreenChange);
1227
+ }, []);
942
1228
  react.useEffect(() => {
943
- if (!host || !resourceUri || !appSessionId) return;
944
- host.launch(resourceUri, appSessionId).then(() => setIsLaunched(true)).catch((err) => setError(err instanceof Error ? err : new Error(String(err))));
945
- }, [host, resourceUri, appSessionId]);
1229
+ if (!host || !resourceUri && !html) return;
1230
+ host.launch({ uri: resourceUri, html }, appSessionId).then(() => setIsLaunched(true)).catch((err) => setError(err instanceof Error ? err : new Error(String(err))));
1231
+ }, [host, resourceUri, html, appSessionId]);
946
1232
  react.useEffect(() => {
947
- if (!host || !isLaunched || !resourceUri || !appSessionId || !input) return;
948
- if (!sentInputRef.current || JSON.stringify(input) !== JSON.stringify(lastInputRef.current)) {
1233
+ if (!host || !isLaunched || !resourceUri || !appSessionId || !resolvedInput) return;
1234
+ if (!sentInputRef.current || JSON.stringify(resolvedInput) !== JSON.stringify(lastInputRef.current)) {
949
1235
  sentInputRef.current = true;
950
- lastInputRef.current = input;
951
- host.sendToolInput(input);
1236
+ lastInputRef.current = resolvedInput;
1237
+ host.sendToolInput(resolvedInput);
952
1238
  }
953
- }, [host, isLaunched, input, resourceUri, appSessionId, name]);
1239
+ }, [host, isLaunched, resolvedInput, resourceUri, appSessionId, resolvedToolName]);
954
1240
  react.useEffect(() => {
955
1241
  if (!host || !isLaunched || !resourceUri || !appSessionId || result === void 0) return;
956
1242
  if (status !== "complete") return;
@@ -960,7 +1246,7 @@ var McpAppView = react.memo(function McpAppView2({
960
1246
  const formattedResult = typeof result === "string" ? { content: [{ type: "text", text: result }] } : result;
961
1247
  host.sendToolResult(formattedResult);
962
1248
  }
963
- }, [host, isLaunched, result, status, resourceUri, appSessionId, name]);
1249
+ }, [host, isLaunched, result, status, resourceUri, appSessionId, resolvedToolName]);
964
1250
  react.useEffect(() => {
965
1251
  if (status === "executing" && lastStatusRef.current !== "executing") {
966
1252
  sentInputRef.current = false;
@@ -968,7 +1254,26 @@ var McpAppView = react.memo(function McpAppView2({
968
1254
  }
969
1255
  lastStatusRef.current = status;
970
1256
  }, [status]);
971
- if (!metadata || !sseClient) {
1257
+ react.useEffect(() => {
1258
+ if (!host) return;
1259
+ const mergedCtx = {
1260
+ theme: APP_HOST_DEFAULTS.THEME,
1261
+ platform: APP_HOST_DEFAULTS.PLATFORM,
1262
+ containerDimensions: { maxHeight: APP_HOST_DEFAULTS.MAX_HEIGHT },
1263
+ availableDisplayModes: ["inline", "fullscreen"],
1264
+ ...hostContext || {},
1265
+ displayMode
1266
+ // always override with our authoritative state
1267
+ };
1268
+ host.setHostContext(mergedCtx);
1269
+ }, [host, hostContext, displayMode]);
1270
+ react.useEffect(() => {
1271
+ if (host && toolInputPartial) host.sendToolInputPartial(toolInputPartial);
1272
+ }, [host, toolInputPartial]);
1273
+ react.useEffect(() => {
1274
+ if (host && toolCancelled) host.sendToolCancelled("User cancelled");
1275
+ }, [host, toolCancelled]);
1276
+ if (!metadata && !html && !toolResourceUri) {
972
1277
  return null;
973
1278
  }
974
1279
  const displayError = error || hostError;
@@ -978,43 +1283,76 @@ var McpAppView = react.memo(function McpAppView2({
978
1283
  displayError.message || String(displayError)
979
1284
  ] });
980
1285
  }
981
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `w-full border border-gray-700 rounded overflow-hidden bg-white min-h-96 my-2 relative ${className || ""}`, children: [
1286
+ const opacityClass = isLaunched ? "opacity-100" : "opacity-0";
1287
+ let containerClass = `w-full border border-gray-700 rounded bg-transparent my-2 relative ${className || ""}`;
1288
+ let iframeClass = `w-full transition-opacity duration-300 ${opacityClass}`;
1289
+ if (displayMode === "fullscreen") {
1290
+ containerClass = `w-full h-full bg-black m-0 p-0 flex flex-col relative`;
1291
+ iframeClass = `w-full flex-1 transition-opacity duration-300 ${opacityClass}`;
1292
+ }
1293
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: containerClass, children: [
1294
+ displayMode === "fullscreen" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute top-0 right-0 p-2 z-[100000] w-full bg-gradient-to-b from-black/80 to-transparent flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsxs(
1295
+ "button",
1296
+ {
1297
+ title: "Exit Fullscreen",
1298
+ onClick: () => {
1299
+ restoreHeightAfterFullscreen();
1300
+ if (document.fullscreenElement) document.exitFullscreen();
1301
+ setDisplayModeWithRef("inline");
1302
+ },
1303
+ className: "px-4 py-2 bg-gray-800 hover:bg-gray-700 text-white rounded-md shadow flex items-center gap-2 border border-gray-600 transition-colors",
1304
+ children: [
1305
+ /* @__PURE__ */ jsxRuntime.jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3" }) }),
1306
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: "Exit" })
1307
+ ]
1308
+ }
1309
+ ) }),
982
1310
  /* @__PURE__ */ jsxRuntime.jsx(
983
1311
  "iframe",
984
1312
  {
985
1313
  ref: iframeRef,
986
1314
  sandbox: "allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-downloads",
987
- className: "w-full h-full min-h-96",
988
- style: { height: "auto" },
1315
+ allow: "fullscreen",
1316
+ className: iframeClass,
989
1317
  title: "MCP App"
990
1318
  }
991
1319
  ),
992
- !isLaunched && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-gray-900/50 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-6 h-6 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" }) })
1320
+ !isLaunched && loader && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-transparent flex items-center justify-center pointer-events-none z-10", children: loader })
993
1321
  ] });
994
1322
  });
1323
+ var McpAppView2 = react.memo(McpAppViewInner);
1324
+ McpAppView2.displayName = "McpAppView";
1325
+ var McpAppRenderer = react.memo(
1326
+ react.forwardRef(function McpAppRenderer2({ client, ...props }, ref) {
1327
+ const clientRef = react.useRef(client || null);
1328
+ clientRef.current = client || null;
1329
+ return /* @__PURE__ */ jsxRuntime.jsx(McpAppView2, { ref, clientRef, ...props });
1330
+ })
1331
+ );
995
1332
  function useMcpApps(mcpClient) {
996
- const clientRef = react.useRef(mcpClient);
997
- clientRef.current = mcpClient;
998
1333
  const getAppMetadata = react.useCallback(
999
- (toolName) => getMcpAppMetadata(clientRef.current, toolName),
1000
- []
1334
+ (toolName) => getMcpAppMetadata(mcpClient, toolName),
1335
+ [mcpClient]
1001
1336
  );
1002
- const McpAppRenderer = react.useMemo(() => {
1003
- const Renderer = react.memo(function McpAppRenderer2(props) {
1004
- return /* @__PURE__ */ jsxRuntime.jsx(McpAppView, { clientRef, ...props });
1005
- });
1006
- Renderer.displayName = "McpAppRenderer";
1007
- return Renderer;
1008
- }, []);
1009
- return { getAppMetadata, McpAppRenderer };
1337
+ const BoundMcpAppRenderer = react.useMemo(() => {
1338
+ const Renderer = react.forwardRef(
1339
+ function BoundMcpAppRenderer2(props, ref) {
1340
+ return /* @__PURE__ */ jsxRuntime.jsx(McpAppRenderer, { ref, client: mcpClient, ...props });
1341
+ }
1342
+ );
1343
+ Renderer.displayName = "BoundMcpAppRenderer";
1344
+ return react.memo(Renderer);
1345
+ }, [mcpClient]);
1346
+ return { getAppMetadata, McpAppRenderer: BoundMcpAppRenderer };
1010
1347
  }
1011
1348
  function extractToolName(fullName) {
1012
1349
  const match = fullName.match(/(?:tool_[^_]+_)?(.+)$/);
1013
1350
  return match?.[1] || fullName;
1014
1351
  }
1015
- function getMcpAppMetadata(mcpClient, toolName) {
1352
+ function getMcpAppMetadata(mcpClient, toolName, input) {
1016
1353
  if (!mcpClient) return void 0;
1017
- const extractedName = extractToolName(toolName);
1354
+ const { toolName: proxyToolName } = resolveMetaToolProxy(toolName, input);
1355
+ const extractedName = extractToolName(proxyToolName);
1018
1356
  for (const conn of mcpClient.connections) {
1019
1357
  for (const tool of conn.tools) {
1020
1358
  const candidateName = extractToolName(tool.name);
@@ -1031,8 +1369,14 @@ function getMcpAppMetadata(mcpClient, toolName) {
1031
1369
  return void 0;
1032
1370
  }
1033
1371
 
1372
+ exports.APP_HOST_DEFAULTS = APP_HOST_DEFAULTS;
1034
1373
  exports.AppHost = AppHost;
1374
+ exports.DEFAULT_MCP_APP_CSP = DEFAULT_MCP_APP_CSP;
1375
+ exports.McpAppRenderer = McpAppRenderer;
1376
+ exports.SANDBOX_PROXY_READY_METHOD = SANDBOX_PROXY_READY_METHOD;
1377
+ exports.SANDBOX_RESOURCE_READY_METHOD = SANDBOX_RESOURCE_READY_METHOD;
1035
1378
  exports.SSEClient = SSEClient;
1379
+ exports.getMcpAppMetadata = getMcpAppMetadata;
1036
1380
  exports.useAppHost = useAppHost;
1037
1381
  exports.useMcp = useMcp;
1038
1382
  exports.useMcpApps = useMcpApps;