@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.
- package/dist/adapters/langchain-adapter.js.map +1 -1
- package/dist/adapters/langchain-adapter.mjs.map +1 -1
- package/dist/client/index.d.mts +3 -189
- package/dist/client/index.d.ts +3 -189
- package/dist/client/index.js +218 -54
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +215 -55
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/react.d.mts +21 -14
- package/dist/client/react.d.ts +21 -14
- package/dist/client/react.js +402 -83
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +400 -85
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.d.mts +3 -2
- package/dist/client/vue.d.ts +3 -2
- package/dist/client/vue.js +239 -63
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +236 -64
- package/dist/client/vue.mjs.map +1 -1
- package/dist/index-CQr9q0bF.d.mts +295 -0
- package/dist/index-nE_7Io0I.d.ts +295 -0
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +237 -58
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +230 -59
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.js +15 -4
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +15 -4
- package/dist/server/index.mjs.map +1 -1
- package/package.json +13 -11
- package/src/adapters/langchain-adapter.ts +1 -1
- package/src/client/core/app-host.ts +252 -65
- package/src/client/core/constants.ts +30 -0
- package/src/client/index.ts +6 -1
- package/src/client/react/index.ts +1 -0
- package/src/client/react/use-app-host.ts +8 -15
- package/src/client/react/use-mcp-apps.tsx +221 -26
- package/src/client/react/use-mcp.ts +23 -12
- package/src/client/utils/app-host-utils.ts +62 -0
- package/src/client/vue/use-mcp.ts +23 -12
- package/src/server/mcp/oauth-client.ts +31 -8
package/dist/client/react.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { forwardRef, useRef, useState, useImperativeHandle, useEffect, memo, useCallback, useMemo } from 'react';
|
|
2
2
|
import { nanoid } from 'nanoid';
|
|
3
3
|
import { AppBridge, PostMessageTransport } from '@modelcontextprotocol/ext-apps/app-bridge';
|
|
4
4
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
@@ -354,18 +354,28 @@ function useMcp(options) {
|
|
|
354
354
|
);
|
|
355
355
|
}
|
|
356
356
|
case "auth_required": {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
357
|
+
const url2 = (event.authUrl || "").trim();
|
|
358
|
+
if (!url2) {
|
|
359
|
+
onLog?.("error", "OAuth required but authorization URL is missing", { sessionId: event.sessionId });
|
|
360
|
+
return prev.map(
|
|
361
|
+
(c) => c.sessionId === event.sessionId ? {
|
|
362
|
+
...c,
|
|
363
|
+
state: "FAILED",
|
|
364
|
+
error: "OAuth authorization URL not available",
|
|
365
|
+
authUrl: void 0
|
|
366
|
+
} : c
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
onLog?.("info", `OAuth required - redirecting to ${url2}`, { authUrl: url2 });
|
|
370
|
+
if (!suppressAuthRedirectSessionsRef.current.has(event.sessionId)) {
|
|
371
|
+
if (onRedirect) {
|
|
372
|
+
onRedirect(url2);
|
|
373
|
+
} else if (typeof window !== "undefined") {
|
|
374
|
+
window.location.href = url2;
|
|
365
375
|
}
|
|
366
376
|
}
|
|
367
377
|
return prev.map(
|
|
368
|
-
(c) => c.sessionId === event.sessionId ? { ...c, state: "AUTHENTICATING", authUrl:
|
|
378
|
+
(c) => c.sessionId === event.sessionId ? { ...c, state: "AUTHENTICATING", authUrl: url2 } : c
|
|
369
379
|
);
|
|
370
380
|
}
|
|
371
381
|
case "error": {
|
|
@@ -581,22 +591,88 @@ function useMcp(options) {
|
|
|
581
591
|
]
|
|
582
592
|
);
|
|
583
593
|
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
"
|
|
593
|
-
|
|
594
|
-
"
|
|
595
|
-
|
|
596
|
-
"
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
594
|
+
|
|
595
|
+
// src/client/core/constants.ts
|
|
596
|
+
var SANDBOX_PROXY_READY_METHOD = "ui/notifications/sandbox-proxy-ready";
|
|
597
|
+
var SANDBOX_RESOURCE_READY_METHOD = "ui/notifications/sandbox-resource-ready";
|
|
598
|
+
var APP_HOST_DEFAULTS = {
|
|
599
|
+
/** Default timeout for waiting for the sandbox proxy to be ready (ms). */
|
|
600
|
+
SANDBOX_TIMEOUT_MS: 1e4,
|
|
601
|
+
/** Default host info reported to guest apps. */
|
|
602
|
+
HOST_INFO: { name: "mcp-ts-host", version: "1.0.0" },
|
|
603
|
+
/** Supported MCP App URI schemes. */
|
|
604
|
+
URI_SCHEMES: ["ui://", "mcp-app://"],
|
|
605
|
+
/** Default theme for the host context. */
|
|
606
|
+
THEME: "dark",
|
|
607
|
+
/** Default platform for the host context. */
|
|
608
|
+
PLATFORM: "web",
|
|
609
|
+
/** Default max height for the iframe container (px). */
|
|
610
|
+
MAX_HEIGHT: 6e3
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
// src/client/utils/app-host-utils.ts
|
|
614
|
+
var DEFAULT_SANDBOX_TIMEOUT_MS = APP_HOST_DEFAULTS.SANDBOX_TIMEOUT_MS;
|
|
615
|
+
async function setupSandboxProxyIframe(iframe, sandboxProxyUrl) {
|
|
616
|
+
iframe.style.width = "100%";
|
|
617
|
+
iframe.style.height = "100%";
|
|
618
|
+
iframe.style.border = "none";
|
|
619
|
+
iframe.style.backgroundColor = "transparent";
|
|
620
|
+
iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-downloads");
|
|
621
|
+
const onReady = new Promise((resolve, reject) => {
|
|
622
|
+
let settled = false;
|
|
623
|
+
const cleanup = () => {
|
|
624
|
+
window.removeEventListener("message", messageListener);
|
|
625
|
+
iframe.removeEventListener("error", errorListener);
|
|
626
|
+
};
|
|
627
|
+
const timeoutId = setTimeout(() => {
|
|
628
|
+
if (!settled) {
|
|
629
|
+
settled = true;
|
|
630
|
+
cleanup();
|
|
631
|
+
reject(new Error("Timed out waiting for sandbox proxy iframe to be ready"));
|
|
632
|
+
}
|
|
633
|
+
}, DEFAULT_SANDBOX_TIMEOUT_MS);
|
|
634
|
+
const messageListener = (event) => {
|
|
635
|
+
if (event.source === iframe.contentWindow) {
|
|
636
|
+
if (event.data?.method === SANDBOX_PROXY_READY_METHOD) {
|
|
637
|
+
if (!settled) {
|
|
638
|
+
settled = true;
|
|
639
|
+
clearTimeout(timeoutId);
|
|
640
|
+
cleanup();
|
|
641
|
+
resolve();
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
const errorListener = () => {
|
|
647
|
+
if (!settled) {
|
|
648
|
+
settled = true;
|
|
649
|
+
clearTimeout(timeoutId);
|
|
650
|
+
cleanup();
|
|
651
|
+
reject(new Error("Failed to load sandbox proxy iframe"));
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
window.addEventListener("message", messageListener);
|
|
655
|
+
iframe.addEventListener("error", errorListener);
|
|
656
|
+
});
|
|
657
|
+
iframe.src = sandboxProxyUrl.href;
|
|
658
|
+
return { onReady };
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// src/client/core/app-host.ts
|
|
662
|
+
var DEFAULT_MCP_APP_CSP = {
|
|
663
|
+
"default-src": "'self'",
|
|
664
|
+
"script-src": "'self' 'unsafe-inline' 'unsafe-eval' https: blob:",
|
|
665
|
+
"style-src": "'self' 'unsafe-inline' https:",
|
|
666
|
+
"connect-src": "'self' https: wss:",
|
|
667
|
+
"img-src": "'self' data: https: blob:",
|
|
668
|
+
"font-src": "'self' data: https:",
|
|
669
|
+
"media-src": "'self' https: blob:",
|
|
670
|
+
"frame-src": "'none'",
|
|
671
|
+
"object-src": "'none'",
|
|
672
|
+
"base-uri": "'self'"
|
|
673
|
+
};
|
|
674
|
+
var HOST_INFO = APP_HOST_DEFAULTS.HOST_INFO;
|
|
675
|
+
var MCP_URI_SCHEMES = APP_HOST_DEFAULTS.URI_SCHEMES;
|
|
600
676
|
var AppHost = class {
|
|
601
677
|
constructor(client, iframe, options) {
|
|
602
678
|
this.client = client;
|
|
@@ -605,10 +681,12 @@ var AppHost = class {
|
|
|
605
681
|
__publicField(this, "sessionId");
|
|
606
682
|
__publicField(this, "resourceCache", /* @__PURE__ */ new Map());
|
|
607
683
|
__publicField(this, "debug");
|
|
608
|
-
|
|
684
|
+
__publicField(this, "sandboxConfig");
|
|
685
|
+
__publicField(this, "options");
|
|
609
686
|
__publicField(this, "onAppMessage");
|
|
610
|
-
this.
|
|
611
|
-
this.
|
|
687
|
+
this.options = options || {};
|
|
688
|
+
this.debug = this.options.debug ?? false;
|
|
689
|
+
this.sandboxConfig = this.options.sandbox;
|
|
612
690
|
this.bridge = this.initializeBridge();
|
|
613
691
|
}
|
|
614
692
|
// ============================================
|
|
@@ -635,19 +713,35 @@ var AppHost = class {
|
|
|
635
713
|
}
|
|
636
714
|
}
|
|
637
715
|
/**
|
|
638
|
-
* Launch an MCP App from a URL
|
|
716
|
+
* Launch an MCP App from a URL, MCP resource URI, or RAW HTML.
|
|
639
717
|
* Loads the HTML first, then establishes bridge connection.
|
|
640
718
|
*/
|
|
641
|
-
async launch(
|
|
719
|
+
async launch(source, sessionId) {
|
|
642
720
|
if (sessionId) this.sessionId = sessionId;
|
|
643
721
|
const initializedPromise = this.onAppReady();
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
722
|
+
let htmlToRender = source.html;
|
|
723
|
+
if (!htmlToRender && source.uri) {
|
|
724
|
+
if (this.isMcpUri(source.uri)) {
|
|
725
|
+
htmlToRender = await this.readMcpAppHtml(source.uri);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
if (!htmlToRender && source.uri && !this.isMcpUri(source.uri)) {
|
|
729
|
+
this.iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-downloads");
|
|
730
|
+
this.iframe.src = source.uri;
|
|
731
|
+
await this.onIframeReady();
|
|
732
|
+
await this.connectBridge();
|
|
733
|
+
} else if (htmlToRender) {
|
|
734
|
+
if (!this.sandboxConfig) {
|
|
735
|
+
throw new Error("Sandbox configuration requires a proxy URL to render HTML safely.");
|
|
736
|
+
}
|
|
737
|
+
await this.launchSandboxedHtml(htmlToRender, this.sandboxConfig);
|
|
738
|
+
await this.connectBridge();
|
|
739
|
+
this.log("Sending HTML resource to sandbox proxy (MCP Apps notification)");
|
|
740
|
+
await this.bridge.sendSandboxResourceReady({
|
|
741
|
+
html: htmlToRender,
|
|
742
|
+
csp: this.sandboxConfig.csp
|
|
743
|
+
});
|
|
648
744
|
}
|
|
649
|
-
await this.onIframeReady();
|
|
650
|
-
await this.connectBridge();
|
|
651
745
|
this.log("Waiting for app initialization");
|
|
652
746
|
await Promise.race([
|
|
653
747
|
initializedPromise,
|
|
@@ -658,6 +752,19 @@ var AppHost = class {
|
|
|
658
752
|
]);
|
|
659
753
|
this.log("App launched and ready");
|
|
660
754
|
}
|
|
755
|
+
// Set host context manually
|
|
756
|
+
setHostContext(context) {
|
|
757
|
+
this.options.hostContext = context;
|
|
758
|
+
if (this.bridge) {
|
|
759
|
+
this.bridge.setHostContext(context);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
// Send streaming inputs manually
|
|
763
|
+
sendToolInputPartial(params) {
|
|
764
|
+
if (this.bridge) {
|
|
765
|
+
this.bridge.sendToolInputPartial(params);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
661
768
|
/**
|
|
662
769
|
* Wait for app to signal initialization complete
|
|
663
770
|
*/
|
|
@@ -708,14 +815,17 @@ var AppHost = class {
|
|
|
708
815
|
this.log("Sending tool cancellation to app");
|
|
709
816
|
this.bridge.sendToolCancelled({ reason });
|
|
710
817
|
}
|
|
818
|
+
/**
|
|
819
|
+
* Tell the guest UI the resource is being torn down (unload / cleanup).
|
|
820
|
+
* Forwards to {@link AppBridge.teardownResource} on `@modelcontextprotocol/ext-apps/app-bridge`.
|
|
821
|
+
*/
|
|
822
|
+
teardownResource(params = {}) {
|
|
823
|
+
this.log("Sending resource teardown to app");
|
|
824
|
+
this.bridge.teardownResource(params);
|
|
825
|
+
}
|
|
711
826
|
// ============================================
|
|
712
827
|
// Private: Initialization
|
|
713
828
|
// ============================================
|
|
714
|
-
configureSandbox() {
|
|
715
|
-
if (this.iframe.sandbox.value !== SANDBOX_PERMISSIONS) {
|
|
716
|
-
this.iframe.sandbox.value = SANDBOX_PERMISSIONS;
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
829
|
initializeBridge() {
|
|
720
830
|
const bridge = new AppBridge(
|
|
721
831
|
null,
|
|
@@ -724,12 +834,10 @@ var AppHost = class {
|
|
|
724
834
|
openLinks: {},
|
|
725
835
|
serverTools: {},
|
|
726
836
|
logging: {},
|
|
727
|
-
// Declare support for model context updates
|
|
728
837
|
updateModelContext: { text: {} }
|
|
729
838
|
},
|
|
730
839
|
{
|
|
731
|
-
|
|
732
|
-
hostContext: {
|
|
840
|
+
hostContext: this.options.hostContext || {
|
|
733
841
|
theme: "dark",
|
|
734
842
|
platform: "web",
|
|
735
843
|
containerDimensions: { maxHeight: 6e3 },
|
|
@@ -738,19 +846,56 @@ var AppHost = class {
|
|
|
738
846
|
}
|
|
739
847
|
}
|
|
740
848
|
);
|
|
849
|
+
bridge.fallbackRequestHandler = this.options.onFallbackRequest;
|
|
741
850
|
bridge.oncalltool = (params) => this.handleToolCall(params);
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
851
|
+
if (this.options.onReadResource) {
|
|
852
|
+
bridge.onreadresource = async (params) => {
|
|
853
|
+
const resp = await this.options.onReadResource(params.uri);
|
|
854
|
+
return {
|
|
855
|
+
contents: resp.contents.map((c) => ({
|
|
856
|
+
uri: params.uri,
|
|
857
|
+
text: c.text,
|
|
858
|
+
blob: c.blob
|
|
859
|
+
}))
|
|
860
|
+
};
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
bridge.onopenlink = async (params, extra) => {
|
|
864
|
+
if (this.options.onOpenLink) {
|
|
865
|
+
return await this.options.onOpenLink(params, extra);
|
|
866
|
+
}
|
|
867
|
+
return this.handleOpenLink(params);
|
|
868
|
+
};
|
|
869
|
+
bridge.onmessage = async (params, extra) => {
|
|
870
|
+
if (this.options.onMessage) {
|
|
871
|
+
return await this.options.onMessage(params, extra);
|
|
872
|
+
}
|
|
873
|
+
return this.handleMessage(params);
|
|
874
|
+
};
|
|
875
|
+
bridge.onloggingmessage = (params) => {
|
|
876
|
+
this.log(`App log [${params.level}]: ${params.data}`);
|
|
877
|
+
if (this.options.onLoggingMessage) {
|
|
878
|
+
this.options.onLoggingMessage(params);
|
|
879
|
+
}
|
|
880
|
+
};
|
|
745
881
|
bridge.onupdatemodelcontext = async () => ({});
|
|
746
|
-
bridge.onsizechange = async (
|
|
747
|
-
|
|
748
|
-
if (
|
|
882
|
+
bridge.onsizechange = async (params) => {
|
|
883
|
+
const { width, height } = params;
|
|
884
|
+
if (height !== void 0 && height > 0) {
|
|
885
|
+
this.iframe.style.height = `${height}px`;
|
|
886
|
+
}
|
|
887
|
+
if (width !== void 0 && width > 0) this.iframe.style.minWidth = `min(${width}px, 100%)`;
|
|
888
|
+
if (this.options.onSizeChanged) {
|
|
889
|
+
this.options.onSizeChanged(params);
|
|
890
|
+
}
|
|
749
891
|
return {};
|
|
750
892
|
};
|
|
751
|
-
bridge.onrequestdisplaymode = async (params) =>
|
|
752
|
-
|
|
753
|
-
|
|
893
|
+
bridge.onrequestdisplaymode = async (params, extra) => {
|
|
894
|
+
if (this.options.onRequestDisplayMode) {
|
|
895
|
+
return await this.options.onRequestDisplayMode(params, extra);
|
|
896
|
+
}
|
|
897
|
+
return { mode: params.mode === "fullscreen" ? "fullscreen" : "inline" };
|
|
898
|
+
};
|
|
754
899
|
return bridge;
|
|
755
900
|
}
|
|
756
901
|
async connectBridge() {
|
|
@@ -764,6 +909,9 @@ var AppHost = class {
|
|
|
764
909
|
this.log("Bridge connected successfully");
|
|
765
910
|
} catch (error) {
|
|
766
911
|
this.log("Bridge connection failed", "error");
|
|
912
|
+
if (this.options.onError) {
|
|
913
|
+
this.options.onError(error instanceof Error ? error : new Error(String(error)));
|
|
914
|
+
}
|
|
767
915
|
throw error;
|
|
768
916
|
}
|
|
769
917
|
}
|
|
@@ -771,8 +919,11 @@ var AppHost = class {
|
|
|
771
919
|
// Private: Bridge Event Handlers
|
|
772
920
|
// ============================================
|
|
773
921
|
async handleToolCall(params) {
|
|
774
|
-
if (
|
|
775
|
-
|
|
922
|
+
if (this.options.onCallTool) {
|
|
923
|
+
return await this.options.onCallTool(params);
|
|
924
|
+
}
|
|
925
|
+
if (!this.client || !this.client.isConnected()) {
|
|
926
|
+
throw new Error("Client disconnected or not provided");
|
|
776
927
|
}
|
|
777
928
|
const sessionId = await this.getSessionId();
|
|
778
929
|
if (!sessionId) {
|
|
@@ -796,13 +947,19 @@ var AppHost = class {
|
|
|
796
947
|
// ============================================
|
|
797
948
|
// Private: Resource Loading
|
|
798
949
|
// ============================================
|
|
799
|
-
async
|
|
800
|
-
|
|
801
|
-
|
|
950
|
+
async launchSandboxedHtml(html, config) {
|
|
951
|
+
const sandboxUrlString = config.url instanceof URL ? config.url.href : config.url;
|
|
952
|
+
const url = new URL(sandboxUrlString, globalThis.location?.href);
|
|
953
|
+
if (config.csp && Object.keys(config.csp).length > 0) {
|
|
954
|
+
url.searchParams.set("csp", JSON.stringify(config.csp));
|
|
802
955
|
}
|
|
956
|
+
const { onReady } = await setupSandboxProxyIframe(this.iframe, url);
|
|
957
|
+
await onReady;
|
|
958
|
+
}
|
|
959
|
+
async readMcpAppHtml(uri) {
|
|
803
960
|
const sessionId = await this.getSessionId();
|
|
804
|
-
if (!sessionId) {
|
|
805
|
-
throw new Error("No active session");
|
|
961
|
+
if (!sessionId && !this.options.onReadResource) {
|
|
962
|
+
throw new Error("No active session.");
|
|
806
963
|
}
|
|
807
964
|
const response = await this.fetchResourceWithCache(sessionId, uri);
|
|
808
965
|
if (!response?.contents?.length) {
|
|
@@ -813,10 +970,18 @@ var AppHost = class {
|
|
|
813
970
|
if (!html) {
|
|
814
971
|
throw new Error(`Invalid content in resource: ${uri}`);
|
|
815
972
|
}
|
|
816
|
-
|
|
817
|
-
this.iframe.src = URL.createObjectURL(blob);
|
|
973
|
+
return html;
|
|
818
974
|
}
|
|
819
975
|
async fetchResourceWithCache(sessionId, uri) {
|
|
976
|
+
if (this.options.onReadResource) {
|
|
977
|
+
return await this.options.onReadResource(uri);
|
|
978
|
+
}
|
|
979
|
+
if (!sessionId) {
|
|
980
|
+
throw new Error("No active session");
|
|
981
|
+
}
|
|
982
|
+
if (!this.client) {
|
|
983
|
+
throw new Error("No client to read resource from");
|
|
984
|
+
}
|
|
820
985
|
if (this.hasClientCache()) {
|
|
821
986
|
return this.client.getOrFetchResource(sessionId, uri);
|
|
822
987
|
}
|
|
@@ -829,8 +994,11 @@ var AppHost = class {
|
|
|
829
994
|
}
|
|
830
995
|
async preloadResource(uri) {
|
|
831
996
|
try {
|
|
997
|
+
if (this.options.onReadResource) {
|
|
998
|
+
return await this.options.onReadResource(uri);
|
|
999
|
+
}
|
|
832
1000
|
const sessionId = await this.getSessionId();
|
|
833
|
-
if (!sessionId) return null;
|
|
1001
|
+
if (!sessionId || !this.client) return null;
|
|
834
1002
|
return await this.client.readResource(sessionId, uri);
|
|
835
1003
|
} catch (error) {
|
|
836
1004
|
this.log(`Preload failed for ${uri}`, "warn");
|
|
@@ -842,6 +1010,7 @@ var AppHost = class {
|
|
|
842
1010
|
// ============================================
|
|
843
1011
|
async getSessionId() {
|
|
844
1012
|
if (this.sessionId) return this.sessionId;
|
|
1013
|
+
if (!this.client) return void 0;
|
|
845
1014
|
const result = await this.client.getSessions();
|
|
846
1015
|
return result.sessions?.[0]?.sessionId;
|
|
847
1016
|
}
|
|
@@ -849,6 +1018,7 @@ var AppHost = class {
|
|
|
849
1018
|
return MCP_URI_SCHEMES.some((scheme) => url.startsWith(scheme));
|
|
850
1019
|
}
|
|
851
1020
|
hasClientCache() {
|
|
1021
|
+
if (!this.client) return false;
|
|
852
1022
|
return "getOrFetchResource" in this.client && typeof this.client.getOrFetchResource === "function";
|
|
853
1023
|
}
|
|
854
1024
|
extractUiResourceUri(tool) {
|
|
@@ -892,10 +1062,7 @@ function useAppHost(client, iframeRef, options) {
|
|
|
892
1062
|
initializingRef.current = true;
|
|
893
1063
|
const initHost = async () => {
|
|
894
1064
|
try {
|
|
895
|
-
const appHost = new AppHost(client, iframeRef.current);
|
|
896
|
-
appHost.onAppMessage = (params) => {
|
|
897
|
-
onMessageRef.current?.(params);
|
|
898
|
-
};
|
|
1065
|
+
const appHost = new AppHost(client, iframeRef.current, options);
|
|
899
1066
|
setHost(appHost);
|
|
900
1067
|
await appHost.start();
|
|
901
1068
|
} catch (err) {
|
|
@@ -911,21 +1078,106 @@ function useAppHost(client, iframeRef, options) {
|
|
|
911
1078
|
}, [client, iframeRef]);
|
|
912
1079
|
return { host, error };
|
|
913
1080
|
}
|
|
914
|
-
var
|
|
1081
|
+
var McpAppViewInner = forwardRef(function McpAppView({
|
|
915
1082
|
clientRef,
|
|
916
1083
|
name,
|
|
1084
|
+
toolResourceUri,
|
|
1085
|
+
html,
|
|
917
1086
|
input,
|
|
918
1087
|
result,
|
|
919
|
-
status,
|
|
920
|
-
|
|
921
|
-
|
|
1088
|
+
status = "idle",
|
|
1089
|
+
toolInputPartial,
|
|
1090
|
+
toolCancelled,
|
|
1091
|
+
sandbox,
|
|
1092
|
+
hostContext,
|
|
1093
|
+
onCallTool,
|
|
1094
|
+
onReadResource,
|
|
1095
|
+
onFallbackRequest,
|
|
1096
|
+
onMessage,
|
|
1097
|
+
onOpenLink,
|
|
1098
|
+
onLoggingMessage,
|
|
1099
|
+
onSizeChanged,
|
|
1100
|
+
onError: onHostError,
|
|
1101
|
+
className,
|
|
1102
|
+
loader
|
|
1103
|
+
}, ref) {
|
|
922
1104
|
const mcpClient = clientRef.current;
|
|
923
1105
|
const metadata = getMcpAppMetadata(mcpClient, name);
|
|
924
1106
|
const sseClient = mcpClient?.sseClient ?? null;
|
|
925
|
-
const resourceUri = metadata?.resourceUri;
|
|
1107
|
+
const resourceUri = toolResourceUri || metadata?.resourceUri;
|
|
926
1108
|
const appSessionId = metadata?.sessionId;
|
|
927
1109
|
const iframeRef = useRef(null);
|
|
928
|
-
const
|
|
1110
|
+
const containerRef = useRef(null);
|
|
1111
|
+
const preFullscreenHeightRef = useRef(null);
|
|
1112
|
+
const displayModeRef = useRef("inline");
|
|
1113
|
+
const [displayMode, setDisplayMode] = useState("inline");
|
|
1114
|
+
const setDisplayModeWithRef = (mode) => {
|
|
1115
|
+
displayModeRef.current = mode;
|
|
1116
|
+
setDisplayMode(mode);
|
|
1117
|
+
};
|
|
1118
|
+
const { host, error: hostError } = useAppHost(sseClient, iframeRef, {
|
|
1119
|
+
sandbox,
|
|
1120
|
+
hostContext,
|
|
1121
|
+
onCallTool,
|
|
1122
|
+
onReadResource,
|
|
1123
|
+
onFallbackRequest,
|
|
1124
|
+
onMessage,
|
|
1125
|
+
onOpenLink,
|
|
1126
|
+
onLoggingMessage,
|
|
1127
|
+
// Intercept onSizeChanged: when exiting fullscreen, ignore guest resize events
|
|
1128
|
+
// that arrive with the shrunken viewport dimensions, and restore the pre-fullscreen height.
|
|
1129
|
+
onSizeChanged: (params) => {
|
|
1130
|
+
if (displayModeRef.current === "inline" && preFullscreenHeightRef.current !== null) {
|
|
1131
|
+
const savedHeight = preFullscreenHeightRef.current;
|
|
1132
|
+
preFullscreenHeightRef.current = null;
|
|
1133
|
+
if (iframeRef.current) {
|
|
1134
|
+
iframeRef.current.style.height = `${savedHeight}px`;
|
|
1135
|
+
}
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
onSizeChanged?.(params);
|
|
1139
|
+
},
|
|
1140
|
+
onError: onHostError,
|
|
1141
|
+
onRequestDisplayMode: async (params) => {
|
|
1142
|
+
if (params.mode === "fullscreen") {
|
|
1143
|
+
if (iframeRef.current) {
|
|
1144
|
+
const h = iframeRef.current.getBoundingClientRect().height;
|
|
1145
|
+
if (h > 0) preFullscreenHeightRef.current = h;
|
|
1146
|
+
}
|
|
1147
|
+
try {
|
|
1148
|
+
if (containerRef.current?.requestFullscreen) {
|
|
1149
|
+
await containerRef.current.requestFullscreen();
|
|
1150
|
+
} else if (containerRef.current?.webkitRequestFullscreen) {
|
|
1151
|
+
await containerRef.current.webkitRequestFullscreen();
|
|
1152
|
+
}
|
|
1153
|
+
setDisplayModeWithRef("fullscreen");
|
|
1154
|
+
} catch (err) {
|
|
1155
|
+
console.warn("[McpAppHost] requestFullscreen failed:", err);
|
|
1156
|
+
preFullscreenHeightRef.current = null;
|
|
1157
|
+
return { mode: "inline" };
|
|
1158
|
+
}
|
|
1159
|
+
} else if (params.mode === "inline") {
|
|
1160
|
+
restoreHeightAfterFullscreen();
|
|
1161
|
+
try {
|
|
1162
|
+
if (document.fullscreenElement) {
|
|
1163
|
+
await document.exitFullscreen();
|
|
1164
|
+
}
|
|
1165
|
+
} catch (err) {
|
|
1166
|
+
}
|
|
1167
|
+
setDisplayModeWithRef("inline");
|
|
1168
|
+
}
|
|
1169
|
+
return { mode: params.mode };
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
useImperativeHandle(
|
|
1173
|
+
ref,
|
|
1174
|
+
() => ({
|
|
1175
|
+
teardownResource: (params) => {
|
|
1176
|
+
host?.teardownResource(params ?? {});
|
|
1177
|
+
}
|
|
1178
|
+
}),
|
|
1179
|
+
[host]
|
|
1180
|
+
);
|
|
929
1181
|
const [isLaunched, setIsLaunched] = useState(false);
|
|
930
1182
|
const [error, setError] = useState(null);
|
|
931
1183
|
const sentInputRef = useRef(false);
|
|
@@ -937,10 +1189,28 @@ var McpAppView = memo(function McpAppView2({
|
|
|
937
1189
|
setIsLaunched(false);
|
|
938
1190
|
setError(null);
|
|
939
1191
|
}, [resourceUri, appSessionId]);
|
|
1192
|
+
const restoreHeightAfterFullscreen = () => {
|
|
1193
|
+
const savedHeight = preFullscreenHeightRef.current;
|
|
1194
|
+
if (savedHeight && iframeRef.current) {
|
|
1195
|
+
iframeRef.current.style.height = `${savedHeight}px`;
|
|
1196
|
+
}
|
|
1197
|
+
preFullscreenHeightRef.current = null;
|
|
1198
|
+
};
|
|
940
1199
|
useEffect(() => {
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1200
|
+
const onFullscreenChange = () => {
|
|
1201
|
+
const isFullscreen = !!document.fullscreenElement;
|
|
1202
|
+
if (!isFullscreen && displayModeRef.current === "fullscreen") {
|
|
1203
|
+
restoreHeightAfterFullscreen();
|
|
1204
|
+
setDisplayModeWithRef("inline");
|
|
1205
|
+
}
|
|
1206
|
+
};
|
|
1207
|
+
document.addEventListener("fullscreenchange", onFullscreenChange);
|
|
1208
|
+
return () => document.removeEventListener("fullscreenchange", onFullscreenChange);
|
|
1209
|
+
}, []);
|
|
1210
|
+
useEffect(() => {
|
|
1211
|
+
if (!host || !resourceUri && !html) return;
|
|
1212
|
+
host.launch({ uri: resourceUri, html }, appSessionId).then(() => setIsLaunched(true)).catch((err) => setError(err instanceof Error ? err : new Error(String(err))));
|
|
1213
|
+
}, [host, resourceUri, html, appSessionId]);
|
|
944
1214
|
useEffect(() => {
|
|
945
1215
|
if (!host || !isLaunched || !resourceUri || !appSessionId || !input) return;
|
|
946
1216
|
if (!sentInputRef.current || JSON.stringify(input) !== JSON.stringify(lastInputRef.current)) {
|
|
@@ -966,7 +1236,26 @@ var McpAppView = memo(function McpAppView2({
|
|
|
966
1236
|
}
|
|
967
1237
|
lastStatusRef.current = status;
|
|
968
1238
|
}, [status]);
|
|
969
|
-
|
|
1239
|
+
useEffect(() => {
|
|
1240
|
+
if (!host) return;
|
|
1241
|
+
const mergedCtx = {
|
|
1242
|
+
theme: APP_HOST_DEFAULTS.THEME,
|
|
1243
|
+
platform: APP_HOST_DEFAULTS.PLATFORM,
|
|
1244
|
+
containerDimensions: { maxHeight: APP_HOST_DEFAULTS.MAX_HEIGHT },
|
|
1245
|
+
availableDisplayModes: ["inline", "fullscreen"],
|
|
1246
|
+
...hostContext || {},
|
|
1247
|
+
displayMode
|
|
1248
|
+
// always override with our authoritative state
|
|
1249
|
+
};
|
|
1250
|
+
host.setHostContext(mergedCtx);
|
|
1251
|
+
}, [host, hostContext, displayMode]);
|
|
1252
|
+
useEffect(() => {
|
|
1253
|
+
if (host && toolInputPartial) host.sendToolInputPartial(toolInputPartial);
|
|
1254
|
+
}, [host, toolInputPartial]);
|
|
1255
|
+
useEffect(() => {
|
|
1256
|
+
if (host && toolCancelled) host.sendToolCancelled("User cancelled");
|
|
1257
|
+
}, [host, toolCancelled]);
|
|
1258
|
+
if (!metadata && !html && !toolResourceUri) {
|
|
970
1259
|
return null;
|
|
971
1260
|
}
|
|
972
1261
|
const displayError = error || hostError;
|
|
@@ -976,20 +1265,45 @@ var McpAppView = memo(function McpAppView2({
|
|
|
976
1265
|
displayError.message || String(displayError)
|
|
977
1266
|
] });
|
|
978
1267
|
}
|
|
979
|
-
|
|
1268
|
+
const opacityClass = isLaunched ? "opacity-100" : "opacity-0";
|
|
1269
|
+
let containerClass = `w-full border border-gray-700 rounded bg-transparent my-2 relative ${className || ""}`;
|
|
1270
|
+
let iframeClass = `w-full transition-opacity duration-300 ${opacityClass}`;
|
|
1271
|
+
if (displayMode === "fullscreen") {
|
|
1272
|
+
containerClass = `w-full h-full bg-black m-0 p-0 flex flex-col relative`;
|
|
1273
|
+
iframeClass = `w-full flex-1 transition-opacity duration-300 ${opacityClass}`;
|
|
1274
|
+
}
|
|
1275
|
+
return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: containerClass, children: [
|
|
1276
|
+
displayMode === "fullscreen" && /* @__PURE__ */ 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__ */ jsxs(
|
|
1277
|
+
"button",
|
|
1278
|
+
{
|
|
1279
|
+
title: "Exit Fullscreen",
|
|
1280
|
+
onClick: () => {
|
|
1281
|
+
restoreHeightAfterFullscreen();
|
|
1282
|
+
if (document.fullscreenElement) document.exitFullscreen();
|
|
1283
|
+
setDisplayModeWithRef("inline");
|
|
1284
|
+
},
|
|
1285
|
+
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",
|
|
1286
|
+
children: [
|
|
1287
|
+
/* @__PURE__ */ 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__ */ 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" }) }),
|
|
1288
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Exit" })
|
|
1289
|
+
]
|
|
1290
|
+
}
|
|
1291
|
+
) }),
|
|
980
1292
|
/* @__PURE__ */ jsx(
|
|
981
1293
|
"iframe",
|
|
982
1294
|
{
|
|
983
1295
|
ref: iframeRef,
|
|
984
1296
|
sandbox: "allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-downloads",
|
|
985
|
-
|
|
986
|
-
|
|
1297
|
+
allow: "fullscreen",
|
|
1298
|
+
className: iframeClass,
|
|
987
1299
|
title: "MCP App"
|
|
988
1300
|
}
|
|
989
1301
|
),
|
|
990
|
-
!isLaunched && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-
|
|
1302
|
+
!isLaunched && loader && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-transparent flex items-center justify-center pointer-events-none z-10", children: loader })
|
|
991
1303
|
] });
|
|
992
1304
|
});
|
|
1305
|
+
var McpAppView2 = memo(McpAppViewInner);
|
|
1306
|
+
McpAppView2.displayName = "McpAppView";
|
|
993
1307
|
function useMcpApps(mcpClient) {
|
|
994
1308
|
const clientRef = useRef(mcpClient);
|
|
995
1309
|
clientRef.current = mcpClient;
|
|
@@ -998,9 +1312,10 @@ function useMcpApps(mcpClient) {
|
|
|
998
1312
|
[]
|
|
999
1313
|
);
|
|
1000
1314
|
const McpAppRenderer = useMemo(() => {
|
|
1001
|
-
const
|
|
1002
|
-
return /* @__PURE__ */ jsx(
|
|
1315
|
+
const Inner = forwardRef(function McpAppRenderer2(props, ref) {
|
|
1316
|
+
return /* @__PURE__ */ jsx(McpAppView2, { ref, clientRef, ...props });
|
|
1003
1317
|
});
|
|
1318
|
+
const Renderer = memo(Inner);
|
|
1004
1319
|
Renderer.displayName = "McpAppRenderer";
|
|
1005
1320
|
return Renderer;
|
|
1006
1321
|
}, []);
|
|
@@ -1029,6 +1344,6 @@ function getMcpAppMetadata(mcpClient, toolName) {
|
|
|
1029
1344
|
return void 0;
|
|
1030
1345
|
}
|
|
1031
1346
|
|
|
1032
|
-
export { AppHost, SSEClient, useAppHost, useMcp, useMcpApps };
|
|
1347
|
+
export { APP_HOST_DEFAULTS, AppHost, DEFAULT_MCP_APP_CSP, SANDBOX_PROXY_READY_METHOD, SANDBOX_RESOURCE_READY_METHOD, SSEClient, useAppHost, useMcp, useMcpApps };
|
|
1033
1348
|
//# sourceMappingURL=react.mjs.map
|
|
1034
1349
|
//# sourceMappingURL=react.mjs.map
|