🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@ai-sdk/react

Package Overview
Dependencies
Maintainers
3
Versions
1100
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ai-sdk/react - npm Package Compare versions

Comparing version
4.0.30
to
4.0.31
+12
-1
dist/index.d.ts

@@ -231,2 +231,7 @@ import { FlexibleSchema, FetchFunction, Resolvable, InferSchema } from '@ai-sdk/provider-utils';

/**
* A permission an MCP App may request and a host may allow.
*/
type MCPAppPermission = 'camera' | 'microphone' | 'geolocation' | 'clipboardWrite';
type MCPAppDisplayMode = 'inline' | 'fullscreen' | 'pip';

@@ -289,2 +294,8 @@ type MCPAppMetadata = {

innerSandbox?: string;
/**
* Iframe capabilities the host allows an app to use. Deny-by-default: a
* server-requested permission (camera/microphone/geolocation/clipboardWrite)
* is only granted when it also appears here. Omitting this grants nothing.
*/
allowedPermissions?: MCPAppPermission[];
};

@@ -320,2 +331,2 @@ type MCPAppRendererProps = {

export { Chat, Experimental_UseObjectHelpers, Experimental_UseObjectOptions, UseRealtimeOptions as Experimental_UseRealtimeOptions, UseRealtimeReturn as Experimental_UseRealtimeReturn, MCPAppBridgeHandlers, MCPAppMetadata, MCPAppRendererProps, MCPAppSandboxConfig, UseChatHelpers, UseChatOptions, UseCompletionHelpers, UseObjectHelpers, UseObjectOptions, MCPAppRenderer as experimental_MCPAppRenderer, experimental_useObject, experimental_useRealtime, useChat, useCompletion, useObject };
export { Chat, Experimental_UseObjectHelpers, Experimental_UseObjectOptions, UseRealtimeOptions as Experimental_UseRealtimeOptions, UseRealtimeReturn as Experimental_UseRealtimeReturn, MCPAppBridgeHandlers, MCPAppMetadata, MCPAppPermission, MCPAppRendererProps, MCPAppSandboxConfig, UseChatHelpers, UseChatOptions, UseCompletionHelpers, UseObjectHelpers, UseObjectOptions, MCPAppRenderer as experimental_MCPAppRenderer, experimental_useObject, experimental_useRealtime, useChat, useCompletion, useObject };

@@ -716,2 +716,34 @@ var __accessCheck = (obj, member, msg) => {

}
function assertResourceReadParams(params) {
if (!isJSONObject(params) || typeof params.uri !== "string") {
throw new Error("Invalid resources/read params");
}
if (!params.uri.startsWith("ui://")) {
throw new Error(
`resources/read is limited to ui:// resources: ${params.uri}`
);
}
return { uri: params.uri };
}
function assertOpenLinkParams(params) {
if (!isJSONObject(params) || typeof params.url !== "string") {
throw new Error("Invalid ui/open-link params");
}
let scheme;
try {
scheme = new URL(params.url).protocol;
} catch (e) {
throw new Error(`Invalid ui/open-link url: ${params.url}`);
}
if (scheme !== "https:" && scheme !== "http:" && scheme !== "mailto:") {
throw new Error(`Disallowed ui/open-link scheme: ${scheme}`);
}
return { url: params.url };
}
function assertDisplayModeParams(params) {
if (!isJSONObject(params) || params.mode !== "inline" && params.mode !== "fullscreen" && params.mode !== "pip") {
throw new Error("Invalid ui/request-display-mode params");
}
return { mode: params.mode };
}
var MCPAppBridge = class {

@@ -757,6 +789,14 @@ constructor({

/**
* Whether a `message` event came from the expected proxy window and origin.
* The origin check is skipped only when `targetOrigin` is the `'*'` default.
* Callers that intercept events before {@link handleMessage} share this check.
*/
acceptsEvent(event) {
return event.source === this.targetWindow && (this.targetOrigin === "*" || event.origin === this.targetOrigin);
}
/**
* Processes one `message` event from the sandbox proxy iframe.
*/
handleMessage(event) {
if (event.source !== this.targetWindow || !isJsonRpcMessage(event.data)) {
if (!this.acceptsEvent(event) || !isJsonRpcMessage(event.data)) {
return;

@@ -894,3 +934,5 @@ }

}
return this.handlers.readResource(request.params);
return this.handlers.readResource(
assertResourceReadParams(request.params)
);
case "resources/list":

@@ -905,3 +947,3 @@ if (this.handlers.listResources == null) {

}
return this.handlers.openLink(request.params);
return this.handlers.openLink(assertOpenLinkParams(request.params));
case "ui/message":

@@ -914,3 +956,3 @@ return (_c = (_b = (_a = this.handlers).sendMessage) == null ? void 0 : _b.call(_a, request.params)) != null ? _c : {};

_g,
request.params
assertDisplayModeParams(request.params)
)) != null ? _j : { mode: (_i = this.hostContext.displayMode) != null ? _i : "inline" };

@@ -1035,15 +1077,13 @@ default:

}
function getMCPAppAllowAttribute(permissions) {
if (permissions == null) {
var MCP_APP_PERMISSION_FEATURES = {
camera: "camera",
microphone: "microphone",
geolocation: "geolocation",
clipboardWrite: "clipboard-write"
};
function getMCPAppAllowAttribute(permissions, allowedPermissions) {
if (permissions == null || allowedPermissions == null) {
return void 0;
}
const allow = [];
if (permissions.camera)
allow.push("camera");
if (permissions.microphone)
allow.push("microphone");
if (permissions.geolocation)
allow.push("geolocation");
if (permissions.clipboardWrite)
allow.push("clipboard-write");
const allow = allowedPermissions.filter((permission) => Boolean(permissions[permission])).map((permission) => MCP_APP_PERMISSION_FEATURES[permission]);
return allow.length > 0 ? allow.join("; ") : void 0;

@@ -1077,2 +1117,11 @@ }

import { jsx } from "react/jsx-runtime";
function deriveTargetOrigin(url) {
var _a;
const location = typeof window !== "undefined" ? window.location : void 0;
try {
return new URL(url, location == null ? void 0 : location.href).origin;
} catch (e) {
return (_a = location == null ? void 0 : location.origin) != null ? _a : "null";
}
}
function sendToolState({

@@ -1113,6 +1162,9 @@ bridge,

hostContextRef.current = hostContext;
const targetOrigin = (_a = sandbox.targetOrigin) != null ? _a : "*";
const sandboxUrl = String(sandbox.url);
const targetOrigin = (_a = sandbox.targetOrigin) != null ? _a : deriveTargetOrigin(sandboxUrl);
const resourceCSP = getMCPAppCSP((_b = resource.meta) == null ? void 0 : _b.csp);
const resourceAllow = getMCPAppAllowAttribute((_c = resource.meta) == null ? void 0 : _c.permissions);
const resourceAllow = getMCPAppAllowAttribute(
(_c = resource.meta) == null ? void 0 : _c.permissions,
sandbox.allowedPermissions
);
const innerSandbox = (_d = sandbox.innerSandbox) != null ? _d : MCP_APP_DEFAULT_INNER_SANDBOX;

@@ -1154,3 +1206,6 @@ const bridgeHandlers = useMemo(

var _a2;
if (event.source === targetWindow && ((_a2 = event.data) == null ? void 0 : _a2.jsonrpc) === "2.0" && event.data.method === "ui/notifications/sandbox-proxy-ready") {
if (!bridge.acceptsEvent(event)) {
return;
}
if (((_a2 = event.data) == null ? void 0 : _a2.jsonrpc) === "2.0" && event.data.method === "ui/notifications/sandbox-proxy-ready") {
bridge.sendSandboxResourceReady({

@@ -1215,2 +1270,3 @@ html: resource.html,

style: sandbox.style,
allow: resourceAllow,
sandbox: (_f = sandbox.outerSandbox) != null ? _f : MCP_APP_DEFAULT_OUTER_SANDBOX

@@ -1217,0 +1273,0 @@ }

+4
-4
{
"name": "@ai-sdk/react",
"version": "4.0.30",
"version": "4.0.31",
"type": "module",

@@ -31,6 +31,6 @@ "license": "Apache-2.0",

"throttleit": "2.1.0",
"@ai-sdk/mcp": "2.0.13",
"ai": "7.0.28",
"@ai-sdk/mcp": "2.0.14",
"@ai-sdk/provider": "4.0.3",
"@ai-sdk/provider-utils": "5.0.10"
"@ai-sdk/provider-utils": "5.0.10",
"ai": "7.0.28"
},

@@ -37,0 +37,0 @@ "devDependencies": {

@@ -12,2 +12,18 @@ import { useEffect, useMemo, useRef } from 'react';

/**
* Derives the concrete origin of the sandbox proxy from its URL, so outbound
* postMessage targets a specific origin instead of `'*'` and inbound messages
* can be origin-checked. The proxy must be served from a stable, concrete
* origin (the default outer sandbox keeps `allow-same-origin`). Falls back to
* the host origin on a malformed URL, never `'*'`.
*/
function deriveTargetOrigin(url: string): string {
const location = typeof window !== 'undefined' ? window.location : undefined;
try {
return new URL(url, location?.href).origin;
} catch {
return location?.origin ?? 'null';
}
}
function sendToolState({

@@ -54,6 +70,9 @@ bridge,

hostContextRef.current = hostContext;
const targetOrigin = sandbox.targetOrigin ?? '*';
const sandboxUrl = String(sandbox.url);
const targetOrigin = sandbox.targetOrigin ?? deriveTargetOrigin(sandboxUrl);
const resourceCSP = getMCPAppCSP(resource.meta?.csp);
const resourceAllow = getMCPAppAllowAttribute(resource.meta?.permissions);
const resourceAllow = getMCPAppAllowAttribute(
resource.meta?.permissions,
sandbox.allowedPermissions,
);
const innerSandbox = sandbox.innerSandbox ?? MCP_APP_DEFAULT_INNER_SANDBOX;

@@ -97,4 +116,8 @@ const bridgeHandlers = useMemo(

const onMessage = (event: MessageEvent) => {
// Only handle messages from the proxy window and expected origin.
if (!bridge.acceptsEvent(event)) {
return;
}
if (
event.source === targetWindow &&
event.data?.jsonrpc === '2.0' &&

@@ -164,2 +187,5 @@ event.data.method === 'ui/notifications/sandbox-proxy-ready'

style={sandbox.style}
// Permissions Policy is hierarchical: the outer frame must delegate a
// feature for the proxy to re-delegate it to the inner app frame.
allow={resourceAllow}
sandbox={sandbox.outerSandbox ?? MCP_APP_DEFAULT_OUTER_SANDBOX}

@@ -166,0 +192,0 @@ />

@@ -67,2 +67,57 @@ import { isJSONObject } from '@ai-sdk/provider';

/**
* Validates `resources/read` params and limits reads to `ui://` app resources.
*/
function assertResourceReadParams(params: unknown): { uri: string } {
if (!isJSONObject(params) || typeof params.uri !== 'string') {
throw new Error('Invalid resources/read params');
}
if (!params.uri.startsWith('ui://')) {
throw new Error(
`resources/read is limited to ui:// resources: ${params.uri}`,
);
}
return { uri: params.uri };
}
/**
* Validates `ui/open-link` params and allows only `https:`/`http:`/`mailto:`
* URLs.
*/
function assertOpenLinkParams(params: unknown): { url: string } {
if (!isJSONObject(params) || typeof params.url !== 'string') {
throw new Error('Invalid ui/open-link params');
}
let scheme: string;
try {
scheme = new URL(params.url).protocol;
} catch {
throw new Error(`Invalid ui/open-link url: ${params.url}`);
}
if (scheme !== 'https:' && scheme !== 'http:' && scheme !== 'mailto:') {
throw new Error(`Disallowed ui/open-link scheme: ${scheme}`);
}
return { url: params.url };
}
/**
* Validates params for `ui/request-display-mode`.
*/
function assertDisplayModeParams(params: unknown): {
mode: 'inline' | 'fullscreen' | 'pip';
} {
if (
!isJSONObject(params) ||
(params.mode !== 'inline' &&
params.mode !== 'fullscreen' &&
params.mode !== 'pip')
) {
throw new Error('Invalid ui/request-display-mode params');
}
return { mode: params.mode };
}
/**
* Host-side JSON-RPC bridge for one MCP App iframe.

@@ -150,6 +205,18 @@ *

/**
* Whether a `message` event came from the expected proxy window and origin.
* The origin check is skipped only when `targetOrigin` is the `'*'` default.
* Callers that intercept events before {@link handleMessage} share this check.
*/
acceptsEvent(event: MessageEvent): boolean {
return (
event.source === this.targetWindow &&
(this.targetOrigin === '*' || event.origin === this.targetOrigin)
);
}
/**
* Processes one `message` event from the sandbox proxy iframe.
*/
handleMessage(event: MessageEvent): void {
if (event.source !== this.targetWindow || !isJsonRpcMessage(event.data)) {
if (!this.acceptsEvent(event) || !isJsonRpcMessage(event.data)) {
return;

@@ -312,3 +379,5 @@ }

}
return this.handlers.readResource(request.params as { uri: string });
return this.handlers.readResource(
assertResourceReadParams(request.params),
);

@@ -325,3 +394,3 @@ case 'resources/list':

}
return this.handlers.openLink(request.params as { url: string });
return this.handlers.openLink(assertOpenLinkParams(request.params));

@@ -337,3 +406,3 @@ case 'ui/message':

this.handlers.requestDisplayMode?.(
request.params as { mode: 'inline' | 'fullscreen' | 'pip' },
assertDisplayModeParams(request.params),
) ?? { mode: this.hostContext.displayMode ?? 'inline' }

@@ -340,0 +409,0 @@ );

export { MCPAppRenderer as experimental_MCPAppRenderer } from './app-renderer';
export type { MCPAppPermission } from './sandbox';
export type {

@@ -3,0 +4,0 @@ MCPAppBridgeHandlers,

@@ -98,11 +98,31 @@ import type { MCPAppResourceCSP } from '@ai-sdk/mcp';

/**
* A permission an MCP App may request and a host may allow.
*/
export type MCPAppPermission =
| 'camera'
| 'microphone'
| 'geolocation'
| 'clipboardWrite';
const MCP_APP_PERMISSION_FEATURES: Record<MCPAppPermission, string> = {
camera: 'camera',
microphone: 'microphone',
geolocation: 'geolocation',
clipboardWrite: 'clipboard-write',
};
/**
* Converts MCP App permission metadata into an iframe `allow` attribute.
*
* Deny-by-default: a capability is granted only when it is both requested in
* `permissions` and present in the host `allowedPermissions` allowlist.
* Omitting the allowlist grants nothing, mirroring `allowedTools` in the bridge.
*
* @example
* ```ts
* const allow = getMCPAppAllowAttribute({
* microphone: {},
* clipboardWrite: {},
* });
* // "microphone; clipboard-write"
* const allow = getMCPAppAllowAttribute(
* { microphone: {}, camera: {} },
* ['microphone'],
* );
* // "microphone" — camera requested by server but not host-allowed
* ```

@@ -112,14 +132,13 @@ */

permissions?: Record<string, unknown>,
allowedPermissions?: MCPAppPermission[],
): string | undefined {
if (permissions == null) {
if (permissions == null || allowedPermissions == null) {
return undefined;
}
const allow = [];
if (permissions.camera) allow.push('camera');
if (permissions.microphone) allow.push('microphone');
if (permissions.geolocation) allow.push('geolocation');
if (permissions.clipboardWrite) allow.push('clipboard-write');
const allow = allowedPermissions
.filter(permission => Boolean(permissions[permission]))
.map(permission => MCP_APP_PERMISSION_FEATURES[permission]);
return allow.length > 0 ? allow.join('; ') : undefined;
}
import type { MCPAppResource } from '@ai-sdk/mcp';
import type { DynamicToolUIPart, ToolUIPart, UITools } from 'ai';
import type { CSSProperties, ReactNode } from 'react';
import type { MCPAppPermission } from './sandbox';

@@ -58,2 +59,8 @@ export type { MCPAppResource };

innerSandbox?: string;
/**
* Iframe capabilities the host allows an app to use. Deny-by-default: a
* server-requested permission (camera/microphone/geolocation/clipboardWrite)
* is only granted when it also appears here. Omitting this grants nothing.
*/
allowedPermissions?: MCPAppPermission[];
};

@@ -60,0 +67,0 @@

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display