@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.js
CHANGED
|
@@ -356,18 +356,28 @@ function useMcp(options) {
|
|
|
356
356
|
);
|
|
357
357
|
}
|
|
358
358
|
case "auth_required": {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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:
|
|
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
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
"
|
|
595
|
-
|
|
596
|
-
"
|
|
597
|
-
|
|
598
|
-
"
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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
|
-
|
|
686
|
+
__publicField(this, "sandboxConfig");
|
|
687
|
+
__publicField(this, "options");
|
|
611
688
|
__publicField(this, "onAppMessage");
|
|
612
|
-
this.
|
|
613
|
-
this.
|
|
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
|
|
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(
|
|
721
|
+
async launch(source, sessionId) {
|
|
644
722
|
if (sessionId) this.sessionId = sessionId;
|
|
645
723
|
const initializedPromise = this.onAppReady();
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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
|
-
|
|
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
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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 (
|
|
749
|
-
|
|
750
|
-
if (
|
|
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
|
-
|
|
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 (
|
|
777
|
-
|
|
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
|
|
802
|
-
|
|
803
|
-
|
|
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
|
-
|
|
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,21 +1080,106 @@ function useAppHost(client, iframeRef, options) {
|
|
|
913
1080
|
}, [client, iframeRef]);
|
|
914
1081
|
return { host, error };
|
|
915
1082
|
}
|
|
916
|
-
var
|
|
1083
|
+
var McpAppViewInner = react.forwardRef(function McpAppView({
|
|
917
1084
|
clientRef,
|
|
918
1085
|
name,
|
|
1086
|
+
toolResourceUri,
|
|
1087
|
+
html,
|
|
919
1088
|
input,
|
|
920
1089
|
result,
|
|
921
|
-
status,
|
|
922
|
-
|
|
923
|
-
|
|
1090
|
+
status = "idle",
|
|
1091
|
+
toolInputPartial,
|
|
1092
|
+
toolCancelled,
|
|
1093
|
+
sandbox,
|
|
1094
|
+
hostContext,
|
|
1095
|
+
onCallTool,
|
|
1096
|
+
onReadResource,
|
|
1097
|
+
onFallbackRequest,
|
|
1098
|
+
onMessage,
|
|
1099
|
+
onOpenLink,
|
|
1100
|
+
onLoggingMessage,
|
|
1101
|
+
onSizeChanged,
|
|
1102
|
+
onError: onHostError,
|
|
1103
|
+
className,
|
|
1104
|
+
loader
|
|
1105
|
+
}, ref) {
|
|
924
1106
|
const mcpClient = clientRef.current;
|
|
925
1107
|
const metadata = getMcpAppMetadata(mcpClient, name);
|
|
926
1108
|
const sseClient = mcpClient?.sseClient ?? null;
|
|
927
|
-
const resourceUri = metadata?.resourceUri;
|
|
1109
|
+
const resourceUri = toolResourceUri || metadata?.resourceUri;
|
|
928
1110
|
const appSessionId = metadata?.sessionId;
|
|
929
1111
|
const iframeRef = react.useRef(null);
|
|
930
|
-
const
|
|
1112
|
+
const containerRef = react.useRef(null);
|
|
1113
|
+
const preFullscreenHeightRef = react.useRef(null);
|
|
1114
|
+
const displayModeRef = react.useRef("inline");
|
|
1115
|
+
const [displayMode, setDisplayMode] = react.useState("inline");
|
|
1116
|
+
const setDisplayModeWithRef = (mode) => {
|
|
1117
|
+
displayModeRef.current = mode;
|
|
1118
|
+
setDisplayMode(mode);
|
|
1119
|
+
};
|
|
1120
|
+
const { host, error: hostError } = useAppHost(sseClient, iframeRef, {
|
|
1121
|
+
sandbox,
|
|
1122
|
+
hostContext,
|
|
1123
|
+
onCallTool,
|
|
1124
|
+
onReadResource,
|
|
1125
|
+
onFallbackRequest,
|
|
1126
|
+
onMessage,
|
|
1127
|
+
onOpenLink,
|
|
1128
|
+
onLoggingMessage,
|
|
1129
|
+
// Intercept onSizeChanged: when exiting fullscreen, ignore guest resize events
|
|
1130
|
+
// that arrive with the shrunken viewport dimensions, and restore the pre-fullscreen height.
|
|
1131
|
+
onSizeChanged: (params) => {
|
|
1132
|
+
if (displayModeRef.current === "inline" && preFullscreenHeightRef.current !== null) {
|
|
1133
|
+
const savedHeight = preFullscreenHeightRef.current;
|
|
1134
|
+
preFullscreenHeightRef.current = null;
|
|
1135
|
+
if (iframeRef.current) {
|
|
1136
|
+
iframeRef.current.style.height = `${savedHeight}px`;
|
|
1137
|
+
}
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
onSizeChanged?.(params);
|
|
1141
|
+
},
|
|
1142
|
+
onError: onHostError,
|
|
1143
|
+
onRequestDisplayMode: async (params) => {
|
|
1144
|
+
if (params.mode === "fullscreen") {
|
|
1145
|
+
if (iframeRef.current) {
|
|
1146
|
+
const h = iframeRef.current.getBoundingClientRect().height;
|
|
1147
|
+
if (h > 0) preFullscreenHeightRef.current = h;
|
|
1148
|
+
}
|
|
1149
|
+
try {
|
|
1150
|
+
if (containerRef.current?.requestFullscreen) {
|
|
1151
|
+
await containerRef.current.requestFullscreen();
|
|
1152
|
+
} else if (containerRef.current?.webkitRequestFullscreen) {
|
|
1153
|
+
await containerRef.current.webkitRequestFullscreen();
|
|
1154
|
+
}
|
|
1155
|
+
setDisplayModeWithRef("fullscreen");
|
|
1156
|
+
} catch (err) {
|
|
1157
|
+
console.warn("[McpAppHost] requestFullscreen failed:", err);
|
|
1158
|
+
preFullscreenHeightRef.current = null;
|
|
1159
|
+
return { mode: "inline" };
|
|
1160
|
+
}
|
|
1161
|
+
} else if (params.mode === "inline") {
|
|
1162
|
+
restoreHeightAfterFullscreen();
|
|
1163
|
+
try {
|
|
1164
|
+
if (document.fullscreenElement) {
|
|
1165
|
+
await document.exitFullscreen();
|
|
1166
|
+
}
|
|
1167
|
+
} catch (err) {
|
|
1168
|
+
}
|
|
1169
|
+
setDisplayModeWithRef("inline");
|
|
1170
|
+
}
|
|
1171
|
+
return { mode: params.mode };
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
react.useImperativeHandle(
|
|
1175
|
+
ref,
|
|
1176
|
+
() => ({
|
|
1177
|
+
teardownResource: (params) => {
|
|
1178
|
+
host?.teardownResource(params ?? {});
|
|
1179
|
+
}
|
|
1180
|
+
}),
|
|
1181
|
+
[host]
|
|
1182
|
+
);
|
|
931
1183
|
const [isLaunched, setIsLaunched] = react.useState(false);
|
|
932
1184
|
const [error, setError] = react.useState(null);
|
|
933
1185
|
const sentInputRef = react.useRef(false);
|
|
@@ -939,10 +1191,28 @@ var McpAppView = react.memo(function McpAppView2({
|
|
|
939
1191
|
setIsLaunched(false);
|
|
940
1192
|
setError(null);
|
|
941
1193
|
}, [resourceUri, appSessionId]);
|
|
1194
|
+
const restoreHeightAfterFullscreen = () => {
|
|
1195
|
+
const savedHeight = preFullscreenHeightRef.current;
|
|
1196
|
+
if (savedHeight && iframeRef.current) {
|
|
1197
|
+
iframeRef.current.style.height = `${savedHeight}px`;
|
|
1198
|
+
}
|
|
1199
|
+
preFullscreenHeightRef.current = null;
|
|
1200
|
+
};
|
|
942
1201
|
react.useEffect(() => {
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1202
|
+
const onFullscreenChange = () => {
|
|
1203
|
+
const isFullscreen = !!document.fullscreenElement;
|
|
1204
|
+
if (!isFullscreen && displayModeRef.current === "fullscreen") {
|
|
1205
|
+
restoreHeightAfterFullscreen();
|
|
1206
|
+
setDisplayModeWithRef("inline");
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
document.addEventListener("fullscreenchange", onFullscreenChange);
|
|
1210
|
+
return () => document.removeEventListener("fullscreenchange", onFullscreenChange);
|
|
1211
|
+
}, []);
|
|
1212
|
+
react.useEffect(() => {
|
|
1213
|
+
if (!host || !resourceUri && !html) return;
|
|
1214
|
+
host.launch({ uri: resourceUri, html }, appSessionId).then(() => setIsLaunched(true)).catch((err) => setError(err instanceof Error ? err : new Error(String(err))));
|
|
1215
|
+
}, [host, resourceUri, html, appSessionId]);
|
|
946
1216
|
react.useEffect(() => {
|
|
947
1217
|
if (!host || !isLaunched || !resourceUri || !appSessionId || !input) return;
|
|
948
1218
|
if (!sentInputRef.current || JSON.stringify(input) !== JSON.stringify(lastInputRef.current)) {
|
|
@@ -968,7 +1238,26 @@ var McpAppView = react.memo(function McpAppView2({
|
|
|
968
1238
|
}
|
|
969
1239
|
lastStatusRef.current = status;
|
|
970
1240
|
}, [status]);
|
|
971
|
-
|
|
1241
|
+
react.useEffect(() => {
|
|
1242
|
+
if (!host) return;
|
|
1243
|
+
const mergedCtx = {
|
|
1244
|
+
theme: APP_HOST_DEFAULTS.THEME,
|
|
1245
|
+
platform: APP_HOST_DEFAULTS.PLATFORM,
|
|
1246
|
+
containerDimensions: { maxHeight: APP_HOST_DEFAULTS.MAX_HEIGHT },
|
|
1247
|
+
availableDisplayModes: ["inline", "fullscreen"],
|
|
1248
|
+
...hostContext || {},
|
|
1249
|
+
displayMode
|
|
1250
|
+
// always override with our authoritative state
|
|
1251
|
+
};
|
|
1252
|
+
host.setHostContext(mergedCtx);
|
|
1253
|
+
}, [host, hostContext, displayMode]);
|
|
1254
|
+
react.useEffect(() => {
|
|
1255
|
+
if (host && toolInputPartial) host.sendToolInputPartial(toolInputPartial);
|
|
1256
|
+
}, [host, toolInputPartial]);
|
|
1257
|
+
react.useEffect(() => {
|
|
1258
|
+
if (host && toolCancelled) host.sendToolCancelled("User cancelled");
|
|
1259
|
+
}, [host, toolCancelled]);
|
|
1260
|
+
if (!metadata && !html && !toolResourceUri) {
|
|
972
1261
|
return null;
|
|
973
1262
|
}
|
|
974
1263
|
const displayError = error || hostError;
|
|
@@ -978,20 +1267,45 @@ var McpAppView = react.memo(function McpAppView2({
|
|
|
978
1267
|
displayError.message || String(displayError)
|
|
979
1268
|
] });
|
|
980
1269
|
}
|
|
981
|
-
|
|
1270
|
+
const opacityClass = isLaunched ? "opacity-100" : "opacity-0";
|
|
1271
|
+
let containerClass = `w-full border border-gray-700 rounded bg-transparent my-2 relative ${className || ""}`;
|
|
1272
|
+
let iframeClass = `w-full transition-opacity duration-300 ${opacityClass}`;
|
|
1273
|
+
if (displayMode === "fullscreen") {
|
|
1274
|
+
containerClass = `w-full h-full bg-black m-0 p-0 flex flex-col relative`;
|
|
1275
|
+
iframeClass = `w-full flex-1 transition-opacity duration-300 ${opacityClass}`;
|
|
1276
|
+
}
|
|
1277
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: containerClass, children: [
|
|
1278
|
+
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(
|
|
1279
|
+
"button",
|
|
1280
|
+
{
|
|
1281
|
+
title: "Exit Fullscreen",
|
|
1282
|
+
onClick: () => {
|
|
1283
|
+
restoreHeightAfterFullscreen();
|
|
1284
|
+
if (document.fullscreenElement) document.exitFullscreen();
|
|
1285
|
+
setDisplayModeWithRef("inline");
|
|
1286
|
+
},
|
|
1287
|
+
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",
|
|
1288
|
+
children: [
|
|
1289
|
+
/* @__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" }) }),
|
|
1290
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: "Exit" })
|
|
1291
|
+
]
|
|
1292
|
+
}
|
|
1293
|
+
) }),
|
|
982
1294
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
983
1295
|
"iframe",
|
|
984
1296
|
{
|
|
985
1297
|
ref: iframeRef,
|
|
986
1298
|
sandbox: "allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-downloads",
|
|
987
|
-
|
|
988
|
-
|
|
1299
|
+
allow: "fullscreen",
|
|
1300
|
+
className: iframeClass,
|
|
989
1301
|
title: "MCP App"
|
|
990
1302
|
}
|
|
991
1303
|
),
|
|
992
|
-
!isLaunched && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-
|
|
1304
|
+
!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
1305
|
] });
|
|
994
1306
|
});
|
|
1307
|
+
var McpAppView2 = react.memo(McpAppViewInner);
|
|
1308
|
+
McpAppView2.displayName = "McpAppView";
|
|
995
1309
|
function useMcpApps(mcpClient) {
|
|
996
1310
|
const clientRef = react.useRef(mcpClient);
|
|
997
1311
|
clientRef.current = mcpClient;
|
|
@@ -1000,9 +1314,10 @@ function useMcpApps(mcpClient) {
|
|
|
1000
1314
|
[]
|
|
1001
1315
|
);
|
|
1002
1316
|
const McpAppRenderer = react.useMemo(() => {
|
|
1003
|
-
const
|
|
1004
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1317
|
+
const Inner = react.forwardRef(function McpAppRenderer2(props, ref) {
|
|
1318
|
+
return /* @__PURE__ */ jsxRuntime.jsx(McpAppView2, { ref, clientRef, ...props });
|
|
1005
1319
|
});
|
|
1320
|
+
const Renderer = react.memo(Inner);
|
|
1006
1321
|
Renderer.displayName = "McpAppRenderer";
|
|
1007
1322
|
return Renderer;
|
|
1008
1323
|
}, []);
|
|
@@ -1031,7 +1346,11 @@ function getMcpAppMetadata(mcpClient, toolName) {
|
|
|
1031
1346
|
return void 0;
|
|
1032
1347
|
}
|
|
1033
1348
|
|
|
1349
|
+
exports.APP_HOST_DEFAULTS = APP_HOST_DEFAULTS;
|
|
1034
1350
|
exports.AppHost = AppHost;
|
|
1351
|
+
exports.DEFAULT_MCP_APP_CSP = DEFAULT_MCP_APP_CSP;
|
|
1352
|
+
exports.SANDBOX_PROXY_READY_METHOD = SANDBOX_PROXY_READY_METHOD;
|
|
1353
|
+
exports.SANDBOX_RESOURCE_READY_METHOD = SANDBOX_RESOURCE_READY_METHOD;
|
|
1035
1354
|
exports.SSEClient = SSEClient;
|
|
1036
1355
|
exports.useAppHost = useAppHost;
|
|
1037
1356
|
exports.useMcp = useMcp;
|