@ai-sdk/react 4.0.0-canary.125 → 4.0.0-canary.127
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/CHANGELOG.md +16 -0
- package/dist/index.d.ts +76 -2
- package/dist/index.js +570 -0
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
- package/src/index.ts +1 -0
- package/src/mcp-apps/app-frame.tsx +164 -0
- package/src/mcp-apps/app-renderer.tsx +98 -0
- package/src/mcp-apps/bridge.ts +405 -0
- package/src/mcp-apps/index.ts +8 -0
- package/src/mcp-apps/sandbox.ts +74 -0
- package/src/mcp-apps/types.ts +100 -0
- package/src/mcp-apps/utils.ts +84 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @ai-sdk/react
|
|
2
2
|
|
|
3
|
+
## 4.0.0-canary.127
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 611f621: feat(mcp): feat(mcp): add support for MCP Apps
|
|
8
|
+
- Updated dependencies [611f621]
|
|
9
|
+
- @ai-sdk/mcp@2.0.0-canary.44
|
|
10
|
+
|
|
11
|
+
## 4.0.0-canary.126
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [f634bac]
|
|
16
|
+
- @ai-sdk/provider-utils@5.0.0-canary.35
|
|
17
|
+
- ai@7.0.0-canary.126
|
|
18
|
+
|
|
3
19
|
## 4.0.0-canary.125
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { UIMessage, AbstractChat, ChatInit, CompletionRequestOptions, UseCompletionOptions, DeepPartial } from 'ai';
|
|
1
|
+
import { UIMessage, AbstractChat, ChatInit, CompletionRequestOptions, UseCompletionOptions, DeepPartial, ToolUIPart, UITools, DynamicToolUIPart } from 'ai';
|
|
2
2
|
export { CreateUIMessage, UIMessage, UseCompletionOptions } from 'ai';
|
|
3
3
|
import { FlexibleSchema, FetchFunction, Resolvable, InferSchema } from '@ai-sdk/provider-utils';
|
|
4
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
5
|
+
import * as react from 'react';
|
|
6
|
+
import { CSSProperties, ReactNode } from 'react';
|
|
7
|
+
import { MCPAppResource } from '@ai-sdk/mcp';
|
|
8
|
+
export { MCPAppResource } from '@ai-sdk/mcp';
|
|
4
9
|
|
|
5
10
|
declare class Chat<UI_MESSAGE extends UIMessage> extends AbstractChat<UI_MESSAGE> {
|
|
6
11
|
#private;
|
|
@@ -175,4 +180,73 @@ declare function useObject<SCHEMA extends FlexibleSchema, RESULT = InferSchema<S
|
|
|
175
180
|
initialValue, fetch, onError, onFinish, headers, credentials, }: Experimental_UseObjectOptions<SCHEMA, RESULT>): Experimental_UseObjectHelpers<RESULT, INPUT>;
|
|
176
181
|
declare const experimental_useObject: typeof useObject;
|
|
177
182
|
|
|
178
|
-
|
|
183
|
+
type MCPAppDisplayMode = 'inline' | 'fullscreen' | 'pip';
|
|
184
|
+
type MCPAppMetadata = {
|
|
185
|
+
resourceUri: string;
|
|
186
|
+
mimeType: MCPAppResource['mimeType'];
|
|
187
|
+
visibility?: Array<'model' | 'app'>;
|
|
188
|
+
};
|
|
189
|
+
type MCPAppHostContext = {
|
|
190
|
+
theme?: 'light' | 'dark';
|
|
191
|
+
displayMode?: MCPAppDisplayMode;
|
|
192
|
+
availableDisplayModes?: MCPAppDisplayMode[];
|
|
193
|
+
[key: string]: unknown;
|
|
194
|
+
};
|
|
195
|
+
type MCPAppToolCallParams = {
|
|
196
|
+
name: string;
|
|
197
|
+
arguments?: Record<string, unknown>;
|
|
198
|
+
};
|
|
199
|
+
type MCPAppBridgeHandlers = {
|
|
200
|
+
allowedTools?: string[];
|
|
201
|
+
callTool?: (params: MCPAppToolCallParams) => Promise<unknown> | unknown;
|
|
202
|
+
readResource?: (params: {
|
|
203
|
+
uri: string;
|
|
204
|
+
}) => Promise<unknown> | unknown;
|
|
205
|
+
listResources?: (params?: unknown) => Promise<unknown> | unknown;
|
|
206
|
+
openLink?: (params: {
|
|
207
|
+
url: string;
|
|
208
|
+
}) => Promise<unknown> | unknown;
|
|
209
|
+
sendMessage?: (params: unknown) => Promise<unknown> | unknown;
|
|
210
|
+
updateModelContext?: (params: unknown) => Promise<unknown> | unknown;
|
|
211
|
+
requestDisplayMode?: (params: {
|
|
212
|
+
mode: MCPAppDisplayMode;
|
|
213
|
+
}) => Promise<{
|
|
214
|
+
mode: MCPAppDisplayMode;
|
|
215
|
+
}> | {
|
|
216
|
+
mode: MCPAppDisplayMode;
|
|
217
|
+
};
|
|
218
|
+
onSizeChange?: (params: {
|
|
219
|
+
width?: number;
|
|
220
|
+
height?: number;
|
|
221
|
+
}) => void;
|
|
222
|
+
onInitialized?: () => void;
|
|
223
|
+
onRequestTeardown?: (params: unknown) => void;
|
|
224
|
+
onLog?: (params: unknown) => void;
|
|
225
|
+
onError?: (error: Error) => void;
|
|
226
|
+
};
|
|
227
|
+
type MCPAppSandboxConfig = {
|
|
228
|
+
url: string | URL;
|
|
229
|
+
title?: string;
|
|
230
|
+
className?: string;
|
|
231
|
+
style?: CSSProperties;
|
|
232
|
+
targetOrigin?: string;
|
|
233
|
+
outerSandbox?: string;
|
|
234
|
+
innerSandbox?: string;
|
|
235
|
+
};
|
|
236
|
+
type MCPAppRendererProps = {
|
|
237
|
+
part: ToolUIPart<UITools> | DynamicToolUIPart;
|
|
238
|
+
sandbox: MCPAppSandboxConfig;
|
|
239
|
+
resource?: MCPAppResource;
|
|
240
|
+
loadResource?: (app: MCPAppMetadata) => Promise<MCPAppResource>;
|
|
241
|
+
handlers?: MCPAppBridgeHandlers;
|
|
242
|
+
hostInfo?: {
|
|
243
|
+
name: string;
|
|
244
|
+
version: string;
|
|
245
|
+
};
|
|
246
|
+
hostContext?: MCPAppHostContext;
|
|
247
|
+
fallback?: ReactNode;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
declare function MCPAppRenderer({ part, sandbox, resource: resourceProp, loadResource, handlers, hostInfo, hostContext, fallback, }: MCPAppRendererProps): string | number | boolean | Iterable<react.ReactNode> | react_jsx_runtime.JSX.Element | null;
|
|
251
|
+
|
|
252
|
+
export { Chat, Experimental_UseObjectHelpers, Experimental_UseObjectOptions, MCPAppBridgeHandlers, MCPAppMetadata, MCPAppRendererProps, MCPAppSandboxConfig, UseChatHelpers, UseChatOptions, UseCompletionHelpers, MCPAppRenderer as experimental_MCPAppRenderer, experimental_useObject, useChat, useCompletion };
|
package/dist/index.js
CHANGED
|
@@ -503,8 +503,578 @@ function useObject({
|
|
|
503
503
|
};
|
|
504
504
|
}
|
|
505
505
|
var experimental_useObject = useObject;
|
|
506
|
+
|
|
507
|
+
// src/mcp-apps/app-renderer.tsx
|
|
508
|
+
import { useEffect as useEffect4, useState as useState3 } from "react";
|
|
509
|
+
|
|
510
|
+
// src/mcp-apps/app-frame.tsx
|
|
511
|
+
import { useEffect as useEffect3, useMemo, useRef as useRef4 } from "react";
|
|
512
|
+
|
|
513
|
+
// src/mcp-apps/bridge.ts
|
|
514
|
+
import { isJSONObject } from "@ai-sdk/provider";
|
|
515
|
+
var MCP_APP_PROTOCOL_VERSION = "2026-01-26";
|
|
516
|
+
function isJsonRpcMessage(value) {
|
|
517
|
+
return value != null && typeof value === "object" && !Array.isArray(value) && "jsonrpc" in value && value.jsonrpc === "2.0";
|
|
518
|
+
}
|
|
519
|
+
function isRequest(message) {
|
|
520
|
+
return "method" in message && "id" in message;
|
|
521
|
+
}
|
|
522
|
+
function isNotification(message) {
|
|
523
|
+
return "method" in message && !("id" in message);
|
|
524
|
+
}
|
|
525
|
+
function toError(error) {
|
|
526
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
527
|
+
}
|
|
528
|
+
function assertToolCallParams(params) {
|
|
529
|
+
if (!isJSONObject(params) || typeof params.name !== "string") {
|
|
530
|
+
throw new Error("Invalid tools/call params");
|
|
531
|
+
}
|
|
532
|
+
return {
|
|
533
|
+
name: params.name,
|
|
534
|
+
arguments: isJSONObject(params.arguments) ? params.arguments : void 0
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
var MCPAppBridge = class {
|
|
538
|
+
constructor({
|
|
539
|
+
targetWindow,
|
|
540
|
+
targetOrigin = "*",
|
|
541
|
+
handlers = {},
|
|
542
|
+
hostInfo = { name: "ai-sdk-react", version: "1.0.0" },
|
|
543
|
+
hostContext = { displayMode: "inline" }
|
|
544
|
+
}) {
|
|
545
|
+
this.initialized = false;
|
|
546
|
+
this.pendingNotifications = [];
|
|
547
|
+
this.nextRequestId = 0;
|
|
548
|
+
this.pendingResponses = /* @__PURE__ */ new Map();
|
|
549
|
+
this.targetWindow = targetWindow;
|
|
550
|
+
this.targetOrigin = targetOrigin;
|
|
551
|
+
this.handlers = handlers;
|
|
552
|
+
this.hostInfo = hostInfo;
|
|
553
|
+
this.hostContext = hostContext;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Replaces the callbacks used to serve iframe requests.
|
|
557
|
+
*/
|
|
558
|
+
setHandlers(handlers) {
|
|
559
|
+
this.handlers = handlers;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Updates host context and notifies the iframe after initialization.
|
|
563
|
+
*
|
|
564
|
+
* @example
|
|
565
|
+
* ```ts
|
|
566
|
+
* bridge.setHostContext({ theme: 'dark', displayMode: 'inline' });
|
|
567
|
+
* ```
|
|
568
|
+
*/
|
|
569
|
+
setHostContext(hostContext) {
|
|
570
|
+
this.hostContext = hostContext;
|
|
571
|
+
this.sendNotification({
|
|
572
|
+
method: "ui/notifications/host-context-changed",
|
|
573
|
+
params: hostContext
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Processes one `message` event from the sandbox proxy iframe.
|
|
578
|
+
*/
|
|
579
|
+
handleMessage(event) {
|
|
580
|
+
if (event.source !== this.targetWindow || !isJsonRpcMessage(event.data)) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const message = event.data;
|
|
584
|
+
if ("result" in message || "error" in message) {
|
|
585
|
+
this.handleResponse(message);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if (isRequest(message)) {
|
|
589
|
+
void this.handleRequest(message);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
if (isNotification(message)) {
|
|
593
|
+
this.handleNotification(message);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Sends app HTML and sandbox settings to the sandbox proxy.
|
|
598
|
+
*/
|
|
599
|
+
sendSandboxResourceReady(params) {
|
|
600
|
+
this.post({
|
|
601
|
+
jsonrpc: "2.0",
|
|
602
|
+
method: "ui/notifications/sandbox-resource-ready",
|
|
603
|
+
params
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Sends final tool arguments to the MCP App.
|
|
608
|
+
*/
|
|
609
|
+
sendToolInput(input) {
|
|
610
|
+
this.sendNotification({
|
|
611
|
+
method: "ui/notifications/tool-input",
|
|
612
|
+
params: { arguments: input }
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Sends a completed MCP tool result to the MCP App.
|
|
617
|
+
*/
|
|
618
|
+
sendToolResult(result) {
|
|
619
|
+
this.sendNotification({
|
|
620
|
+
method: "ui/notifications/tool-result",
|
|
621
|
+
params: result
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Notifies the MCP App that the related tool call was cancelled.
|
|
626
|
+
*/
|
|
627
|
+
sendToolCancelled(reason) {
|
|
628
|
+
this.sendNotification({
|
|
629
|
+
method: "ui/notifications/tool-cancelled",
|
|
630
|
+
params: reason != null ? { reason } : {}
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Requests graceful teardown before the host removes the iframe.
|
|
635
|
+
*/
|
|
636
|
+
teardownResource() {
|
|
637
|
+
return this.request("ui/resource-teardown", {});
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Rejects pending bridge requests and clears queued notifications.
|
|
641
|
+
*/
|
|
642
|
+
close() {
|
|
643
|
+
for (const pending of this.pendingResponses.values()) {
|
|
644
|
+
pending.reject(new Error("MCP App bridge closed"));
|
|
645
|
+
}
|
|
646
|
+
this.pendingResponses.clear();
|
|
647
|
+
this.pendingNotifications = [];
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Resolves or rejects a host-initiated request when the iframe responds.
|
|
651
|
+
*/
|
|
652
|
+
handleResponse(response) {
|
|
653
|
+
const pending = this.pendingResponses.get(response.id);
|
|
654
|
+
if (pending == null) {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
this.pendingResponses.delete(response.id);
|
|
658
|
+
if (response.error != null) {
|
|
659
|
+
pending.reject(new Error(response.error.message));
|
|
660
|
+
} else {
|
|
661
|
+
pending.resolve(response.result);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Runs a handler for an iframe request and posts the JSON-RPC response.
|
|
666
|
+
*/
|
|
667
|
+
async handleRequest(request) {
|
|
668
|
+
var _a, _b;
|
|
669
|
+
try {
|
|
670
|
+
const result = await this.getRequestResult(request);
|
|
671
|
+
this.post({ jsonrpc: "2.0", id: request.id, result });
|
|
672
|
+
} catch (error) {
|
|
673
|
+
const normalizedError = toError(error);
|
|
674
|
+
(_b = (_a = this.handlers).onError) == null ? void 0 : _b.call(_a, normalizedError);
|
|
675
|
+
this.post({
|
|
676
|
+
jsonrpc: "2.0",
|
|
677
|
+
id: request.id,
|
|
678
|
+
error: { code: -32603, message: normalizedError.message }
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Maps supported iframe request methods to host callbacks.
|
|
684
|
+
*/
|
|
685
|
+
async getRequestResult(request) {
|
|
686
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
687
|
+
switch (request.method) {
|
|
688
|
+
case "ui/initialize":
|
|
689
|
+
return {
|
|
690
|
+
protocolVersion: MCP_APP_PROTOCOL_VERSION,
|
|
691
|
+
hostCapabilities: {
|
|
692
|
+
...this.handlers.callTool != null ? { serverTools: {} } : {},
|
|
693
|
+
...this.handlers.readResource != null ? { serverResources: {} } : {},
|
|
694
|
+
...this.handlers.onLog != null ? { logging: {} } : {}
|
|
695
|
+
},
|
|
696
|
+
hostInfo: this.hostInfo,
|
|
697
|
+
hostContext: this.hostContext
|
|
698
|
+
};
|
|
699
|
+
case "tools/call": {
|
|
700
|
+
if (this.handlers.callTool == null) {
|
|
701
|
+
throw new Error("No tools/call handler configured");
|
|
702
|
+
}
|
|
703
|
+
const params = assertToolCallParams(request.params);
|
|
704
|
+
if (this.handlers.allowedTools != null && !this.handlers.allowedTools.includes(params.name)) {
|
|
705
|
+
throw new Error(`Tool is not app-visible: ${params.name}`);
|
|
706
|
+
}
|
|
707
|
+
return this.handlers.callTool(params);
|
|
708
|
+
}
|
|
709
|
+
case "resources/read":
|
|
710
|
+
if (this.handlers.readResource == null) {
|
|
711
|
+
throw new Error("No resources/read handler configured");
|
|
712
|
+
}
|
|
713
|
+
return this.handlers.readResource(request.params);
|
|
714
|
+
case "resources/list":
|
|
715
|
+
if (this.handlers.listResources == null) {
|
|
716
|
+
throw new Error("No resources/list handler configured");
|
|
717
|
+
}
|
|
718
|
+
return this.handlers.listResources(request.params);
|
|
719
|
+
case "ui/open-link":
|
|
720
|
+
if (this.handlers.openLink == null) {
|
|
721
|
+
throw new Error("No ui/open-link handler configured");
|
|
722
|
+
}
|
|
723
|
+
return this.handlers.openLink(request.params);
|
|
724
|
+
case "ui/message":
|
|
725
|
+
return (_c = (_b = (_a = this.handlers).sendMessage) == null ? void 0 : _b.call(_a, request.params)) != null ? _c : {};
|
|
726
|
+
case "ui/update-model-context":
|
|
727
|
+
return (_f = (_e = (_d = this.handlers).updateModelContext) == null ? void 0 : _e.call(_d, request.params)) != null ? _f : {};
|
|
728
|
+
case "ui/request-display-mode":
|
|
729
|
+
return (_j = (_h = (_g = this.handlers).requestDisplayMode) == null ? void 0 : _h.call(
|
|
730
|
+
_g,
|
|
731
|
+
request.params
|
|
732
|
+
)) != null ? _j : { mode: (_i = this.hostContext.displayMode) != null ? _i : "inline" };
|
|
733
|
+
default:
|
|
734
|
+
throw new Error(`Unsupported MCP App method: ${request.method}`);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Handles iframe lifecycle and telemetry notifications.
|
|
739
|
+
*/
|
|
740
|
+
handleNotification(notification) {
|
|
741
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
742
|
+
switch (notification.method) {
|
|
743
|
+
case "ui/notifications/initialized":
|
|
744
|
+
this.initialized = true;
|
|
745
|
+
this.flushNotifications();
|
|
746
|
+
(_b = (_a = this.handlers).onInitialized) == null ? void 0 : _b.call(_a);
|
|
747
|
+
break;
|
|
748
|
+
case "ui/notifications/size-changed":
|
|
749
|
+
(_d = (_c = this.handlers).onSizeChange) == null ? void 0 : _d.call(
|
|
750
|
+
_c,
|
|
751
|
+
notification.params
|
|
752
|
+
);
|
|
753
|
+
break;
|
|
754
|
+
case "ui/notifications/request-teardown":
|
|
755
|
+
(_f = (_e = this.handlers).onRequestTeardown) == null ? void 0 : _f.call(_e, notification.params);
|
|
756
|
+
break;
|
|
757
|
+
case "notifications/message":
|
|
758
|
+
(_h = (_g = this.handlers).onLog) == null ? void 0 : _h.call(_g, notification.params);
|
|
759
|
+
break;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Sends a host-to-iframe notification, queueing it until app initialization.
|
|
764
|
+
*/
|
|
765
|
+
sendNotification(notification) {
|
|
766
|
+
const message = { jsonrpc: "2.0", ...notification };
|
|
767
|
+
if (!this.initialized && !notification.method.includes("sandbox")) {
|
|
768
|
+
this.pendingNotifications.push(message);
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
this.post(message);
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Sends notifications that were queued before `ui/notifications/initialized`.
|
|
775
|
+
*/
|
|
776
|
+
flushNotifications() {
|
|
777
|
+
const notifications = this.pendingNotifications;
|
|
778
|
+
this.pendingNotifications = [];
|
|
779
|
+
for (const notification of notifications) {
|
|
780
|
+
this.post(notification);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Sends a host-initiated JSON-RPC request to the iframe.
|
|
785
|
+
*/
|
|
786
|
+
request(method, params) {
|
|
787
|
+
const id = this.nextRequestId++;
|
|
788
|
+
this.post({ jsonrpc: "2.0", id, method, params });
|
|
789
|
+
return new Promise((resolve2, reject) => {
|
|
790
|
+
this.pendingResponses.set(id, { resolve: resolve2, reject });
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Posts a JSON-RPC message to the sandbox proxy iframe.
|
|
795
|
+
*/
|
|
796
|
+
post(message) {
|
|
797
|
+
this.targetWindow.postMessage(message, this.targetOrigin);
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
|
|
801
|
+
// src/mcp-apps/sandbox.ts
|
|
802
|
+
var MCP_APP_DEFAULT_OUTER_SANDBOX = "allow-scripts allow-same-origin allow-forms";
|
|
803
|
+
var MCP_APP_DEFAULT_INNER_SANDBOX = "allow-scripts allow-forms";
|
|
804
|
+
function getMCPAppCSP(csp) {
|
|
805
|
+
var _a, _b, _c;
|
|
806
|
+
if (csp == null) {
|
|
807
|
+
return void 0;
|
|
808
|
+
}
|
|
809
|
+
const connectSrc = ["'self'", ...(_a = csp.connectDomains) != null ? _a : []];
|
|
810
|
+
const imgSrc = ["'self'", "data:", ...(_b = csp.resourceDomains) != null ? _b : []];
|
|
811
|
+
const frameSrc = ["'self'", ...(_c = csp.frameDomains) != null ? _c : []];
|
|
812
|
+
return [
|
|
813
|
+
"default-src 'none'",
|
|
814
|
+
"script-src 'unsafe-inline'",
|
|
815
|
+
"style-src 'unsafe-inline'",
|
|
816
|
+
`connect-src ${connectSrc.join(" ")}`,
|
|
817
|
+
`img-src ${imgSrc.join(" ")}`,
|
|
818
|
+
`font-src ${imgSrc.join(" ")}`,
|
|
819
|
+
`frame-src ${frameSrc.join(" ")}`
|
|
820
|
+
].join("; ");
|
|
821
|
+
}
|
|
822
|
+
function getMCPAppAllowAttribute(permissions) {
|
|
823
|
+
if (permissions == null) {
|
|
824
|
+
return void 0;
|
|
825
|
+
}
|
|
826
|
+
const allow = [];
|
|
827
|
+
if (permissions.camera)
|
|
828
|
+
allow.push("camera");
|
|
829
|
+
if (permissions.microphone)
|
|
830
|
+
allow.push("microphone");
|
|
831
|
+
if (permissions.geolocation)
|
|
832
|
+
allow.push("geolocation");
|
|
833
|
+
if (permissions.clipboardWrite)
|
|
834
|
+
allow.push("clipboard-write");
|
|
835
|
+
return allow.length > 0 ? allow.join("; ") : void 0;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// src/mcp-apps/utils.ts
|
|
839
|
+
import { isJSONObject as isJSONObject2 } from "@ai-sdk/provider";
|
|
840
|
+
function getMCPAppFromToolPart(part) {
|
|
841
|
+
var _a;
|
|
842
|
+
const mcpMetadata = (_a = part.toolMetadata) == null ? void 0 : _a.mcp;
|
|
843
|
+
const rawAppMetadata = isJSONObject2(mcpMetadata) ? mcpMetadata.app : void 0;
|
|
844
|
+
const appMetadata = isJSONObject2(rawAppMetadata) ? rawAppMetadata : void 0;
|
|
845
|
+
if (appMetadata == null || appMetadata.mimeType !== "text/html;profile=mcp-app" || typeof appMetadata.resourceUri !== "string" || !appMetadata.resourceUri.startsWith("ui://") || appMetadata.visibility != null && (!Array.isArray(appMetadata.visibility) || appMetadata.visibility.some(
|
|
846
|
+
(value) => value !== "model" && value !== "app"
|
|
847
|
+
))) {
|
|
848
|
+
return void 0;
|
|
849
|
+
}
|
|
850
|
+
return appMetadata;
|
|
851
|
+
}
|
|
852
|
+
function normalizeMCPAppToolResult(output) {
|
|
853
|
+
if (output != null && typeof output === "object" && "content" in output) {
|
|
854
|
+
return output;
|
|
855
|
+
}
|
|
856
|
+
return {
|
|
857
|
+
content: [],
|
|
858
|
+
structuredContent: output
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// src/mcp-apps/app-frame.tsx
|
|
863
|
+
import { jsx } from "react/jsx-runtime";
|
|
864
|
+
function sendToolState({
|
|
865
|
+
bridge,
|
|
866
|
+
input,
|
|
867
|
+
output
|
|
868
|
+
}) {
|
|
869
|
+
if (bridge == null) {
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
if (input !== void 0) {
|
|
873
|
+
bridge.sendToolInput(input);
|
|
874
|
+
}
|
|
875
|
+
if (output !== void 0) {
|
|
876
|
+
bridge.sendToolResult(normalizeMCPAppToolResult(output));
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
function MCPAppFrame({
|
|
880
|
+
app,
|
|
881
|
+
resource,
|
|
882
|
+
input,
|
|
883
|
+
output,
|
|
884
|
+
sandbox,
|
|
885
|
+
handlers,
|
|
886
|
+
hostInfo,
|
|
887
|
+
hostContext
|
|
888
|
+
}) {
|
|
889
|
+
var _a, _b, _c, _d, _e, _f;
|
|
890
|
+
const iframeRef = useRef4(null);
|
|
891
|
+
const bridgeRef = useRef4(void 0);
|
|
892
|
+
const inputRef = useRef4(input);
|
|
893
|
+
const outputRef = useRef4(output);
|
|
894
|
+
const hostContextRef = useRef4(hostContext);
|
|
895
|
+
const initializedRef = useRef4(false);
|
|
896
|
+
inputRef.current = input;
|
|
897
|
+
outputRef.current = output;
|
|
898
|
+
hostContextRef.current = hostContext;
|
|
899
|
+
const targetOrigin = (_a = sandbox.targetOrigin) != null ? _a : "*";
|
|
900
|
+
const sandboxUrl = String(sandbox.url);
|
|
901
|
+
const resourceCSP = getMCPAppCSP((_b = resource.meta) == null ? void 0 : _b.csp);
|
|
902
|
+
const resourceAllow = getMCPAppAllowAttribute((_c = resource.meta) == null ? void 0 : _c.permissions);
|
|
903
|
+
const innerSandbox = (_d = sandbox.innerSandbox) != null ? _d : MCP_APP_DEFAULT_INNER_SANDBOX;
|
|
904
|
+
const bridgeHandlers = useMemo(
|
|
905
|
+
() => ({
|
|
906
|
+
...handlers,
|
|
907
|
+
onInitialized: () => {
|
|
908
|
+
var _a2;
|
|
909
|
+
initializedRef.current = true;
|
|
910
|
+
(_a2 = handlers == null ? void 0 : handlers.onInitialized) == null ? void 0 : _a2.call(handlers);
|
|
911
|
+
sendToolState({
|
|
912
|
+
bridge: bridgeRef.current,
|
|
913
|
+
input: inputRef.current,
|
|
914
|
+
output: outputRef.current
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
}),
|
|
918
|
+
[handlers]
|
|
919
|
+
);
|
|
920
|
+
const bridgeHandlersRef = useRef4(bridgeHandlers);
|
|
921
|
+
bridgeHandlersRef.current = bridgeHandlers;
|
|
922
|
+
useEffect3(() => {
|
|
923
|
+
const iframe = iframeRef.current;
|
|
924
|
+
const targetWindow = iframe == null ? void 0 : iframe.contentWindow;
|
|
925
|
+
if (targetWindow == null) {
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
initializedRef.current = false;
|
|
929
|
+
const bridge = new MCPAppBridge({
|
|
930
|
+
targetWindow,
|
|
931
|
+
targetOrigin,
|
|
932
|
+
handlers: bridgeHandlersRef.current,
|
|
933
|
+
hostInfo,
|
|
934
|
+
hostContext: hostContextRef.current
|
|
935
|
+
});
|
|
936
|
+
bridgeRef.current = bridge;
|
|
937
|
+
const onMessage = (event) => {
|
|
938
|
+
var _a2;
|
|
939
|
+
if (event.source === targetWindow && ((_a2 = event.data) == null ? void 0 : _a2.jsonrpc) === "2.0" && event.data.method === "ui/notifications/sandbox-proxy-ready") {
|
|
940
|
+
bridge.sendSandboxResourceReady({
|
|
941
|
+
html: resource.html,
|
|
942
|
+
csp: resourceCSP,
|
|
943
|
+
sandbox: innerSandbox,
|
|
944
|
+
allow: resourceAllow
|
|
945
|
+
});
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
bridge.handleMessage(event);
|
|
949
|
+
};
|
|
950
|
+
window.addEventListener("message", onMessage);
|
|
951
|
+
return () => {
|
|
952
|
+
initializedRef.current = false;
|
|
953
|
+
window.removeEventListener("message", onMessage);
|
|
954
|
+
void bridge.teardownResource().catch(() => {
|
|
955
|
+
});
|
|
956
|
+
bridge.close();
|
|
957
|
+
bridgeRef.current = void 0;
|
|
958
|
+
};
|
|
959
|
+
}, [
|
|
960
|
+
hostInfo,
|
|
961
|
+
innerSandbox,
|
|
962
|
+
resource.html,
|
|
963
|
+
resourceAllow,
|
|
964
|
+
resourceCSP,
|
|
965
|
+
sandboxUrl,
|
|
966
|
+
targetOrigin
|
|
967
|
+
]);
|
|
968
|
+
useEffect3(() => {
|
|
969
|
+
var _a2;
|
|
970
|
+
(_a2 = bridgeRef.current) == null ? void 0 : _a2.setHandlers(bridgeHandlers);
|
|
971
|
+
}, [bridgeHandlers]);
|
|
972
|
+
useEffect3(() => {
|
|
973
|
+
var _a2;
|
|
974
|
+
if (hostContext != null) {
|
|
975
|
+
(_a2 = bridgeRef.current) == null ? void 0 : _a2.setHostContext(hostContext);
|
|
976
|
+
}
|
|
977
|
+
}, [hostContext]);
|
|
978
|
+
useEffect3(() => {
|
|
979
|
+
var _a2;
|
|
980
|
+
if (initializedRef.current && input !== void 0) {
|
|
981
|
+
(_a2 = bridgeRef.current) == null ? void 0 : _a2.sendToolInput(input);
|
|
982
|
+
}
|
|
983
|
+
}, [input]);
|
|
984
|
+
useEffect3(() => {
|
|
985
|
+
var _a2;
|
|
986
|
+
if (initializedRef.current && output !== void 0) {
|
|
987
|
+
(_a2 = bridgeRef.current) == null ? void 0 : _a2.sendToolResult(normalizeMCPAppToolResult(output));
|
|
988
|
+
}
|
|
989
|
+
}, [output]);
|
|
990
|
+
return /* @__PURE__ */ jsx(
|
|
991
|
+
"iframe",
|
|
992
|
+
{
|
|
993
|
+
ref: iframeRef,
|
|
994
|
+
title: "MCP App",
|
|
995
|
+
"aria-label": (_e = sandbox.title) != null ? _e : app.resourceUri,
|
|
996
|
+
src: sandboxUrl,
|
|
997
|
+
className: sandbox.className,
|
|
998
|
+
style: sandbox.style,
|
|
999
|
+
sandbox: (_f = sandbox.outerSandbox) != null ? _f : MCP_APP_DEFAULT_OUTER_SANDBOX
|
|
1000
|
+
}
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// src/mcp-apps/app-renderer.tsx
|
|
1005
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
1006
|
+
function getToolPartOutput(part) {
|
|
1007
|
+
return part.state === "output-available" ? part.output : void 0;
|
|
1008
|
+
}
|
|
1009
|
+
function getToolPartInput(part) {
|
|
1010
|
+
return part.state === "input-available" || part.state === "output-available" ? part.input : void 0;
|
|
1011
|
+
}
|
|
1012
|
+
function MCPAppRenderer({
|
|
1013
|
+
part,
|
|
1014
|
+
sandbox,
|
|
1015
|
+
resource: resourceProp,
|
|
1016
|
+
loadResource,
|
|
1017
|
+
handlers,
|
|
1018
|
+
hostInfo,
|
|
1019
|
+
hostContext,
|
|
1020
|
+
fallback = null
|
|
1021
|
+
}) {
|
|
1022
|
+
const app = getMCPAppFromToolPart(part);
|
|
1023
|
+
const [cachedApp, setCachedApp] = useState3();
|
|
1024
|
+
const [loadedResource, setLoadedResource] = useState3();
|
|
1025
|
+
useEffect4(() => {
|
|
1026
|
+
if (app != null) {
|
|
1027
|
+
setCachedApp(
|
|
1028
|
+
(previous) => (previous == null ? void 0 : previous.resourceUri) === app.resourceUri ? previous : app
|
|
1029
|
+
);
|
|
1030
|
+
}
|
|
1031
|
+
}, [app == null ? void 0 : app.resourceUri]);
|
|
1032
|
+
const appForRender = app != null ? app : cachedApp;
|
|
1033
|
+
useEffect4(() => {
|
|
1034
|
+
if (appForRender == null || resourceProp != null || loadResource == null) {
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
let cancelled = false;
|
|
1038
|
+
const resourceUri = appForRender.resourceUri;
|
|
1039
|
+
loadResource(appForRender).then((resource2) => {
|
|
1040
|
+
if (!cancelled) {
|
|
1041
|
+
setLoadedResource({ resourceUri, resource: resource2 });
|
|
1042
|
+
}
|
|
1043
|
+
}).catch((error2) => {
|
|
1044
|
+
if (!cancelled) {
|
|
1045
|
+
setLoadedResource({
|
|
1046
|
+
resourceUri,
|
|
1047
|
+
error: error2 instanceof Error ? error2 : new Error(String(error2))
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
});
|
|
1051
|
+
return () => {
|
|
1052
|
+
cancelled = true;
|
|
1053
|
+
};
|
|
1054
|
+
}, [appForRender == null ? void 0 : appForRender.resourceUri, loadResource, resourceProp]);
|
|
1055
|
+
const loadedResourceForApp = (loadedResource == null ? void 0 : loadedResource.resourceUri) === (appForRender == null ? void 0 : appForRender.resourceUri) ? loadedResource : void 0;
|
|
1056
|
+
const resource = resourceProp != null ? resourceProp : loadedResourceForApp == null ? void 0 : loadedResourceForApp.resource;
|
|
1057
|
+
const error = resourceProp == null ? loadedResourceForApp == null ? void 0 : loadedResourceForApp.error : void 0;
|
|
1058
|
+
if (appForRender == null || error != null || resource == null) {
|
|
1059
|
+
return fallback;
|
|
1060
|
+
}
|
|
1061
|
+
return /* @__PURE__ */ jsx2(
|
|
1062
|
+
MCPAppFrame,
|
|
1063
|
+
{
|
|
1064
|
+
app: appForRender,
|
|
1065
|
+
resource,
|
|
1066
|
+
input: getToolPartInput(part),
|
|
1067
|
+
output: getToolPartOutput(part),
|
|
1068
|
+
sandbox,
|
|
1069
|
+
handlers,
|
|
1070
|
+
hostInfo,
|
|
1071
|
+
hostContext
|
|
1072
|
+
}
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
506
1075
|
export {
|
|
507
1076
|
Chat,
|
|
1077
|
+
MCPAppRenderer as experimental_MCPAppRenderer,
|
|
508
1078
|
experimental_useObject,
|
|
509
1079
|
useChat,
|
|
510
1080
|
useCompletion
|