feat: enhance sandbox capability negotiation (#158)
* feat: enhance sandbox capability negotiation * Update specification/draft/apps.mdx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * add all csp and permissions to host capabilities + update SEP * feat: add clipboard-write permission support Adds clipboardWrite to McpUiResourcePermissions for clipboard access. Maps to Permission Policy 'clipboard-write' feature. * rm files added by mistake * refactor: change McpUiResourcePermissions properties to empty objects * chore: remove scratch files and redundant cross-env dependency --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Olivier Chafik <ochafik@anthropic.com>
This commit is contained in:
parent
9f09932baf
commit
9acc52cb96
8 changed files with 496 additions and 16 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { RESOURCE_MIME_TYPE, getToolUiResourceUri, type McpUiSandboxProxyReadyNotification, AppBridge, PostMessageTransport, type McpUiResourceCsp } from "@modelcontextprotocol/ext-apps/app-bridge";
|
||||
import { RESOURCE_MIME_TYPE, getToolUiResourceUri, type McpUiSandboxProxyReadyNotification, AppBridge, PostMessageTransport, type McpUiResourceCsp, type McpUiResourcePermissions } from "@modelcontextprotocol/ext-apps/app-bridge";
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
||||
import type { CallToolResult, Tool } from "@modelcontextprotocol/sdk/types.js";
|
||||
|
|
@ -43,6 +43,7 @@ export async function connectToServer(serverUrl: URL): Promise<ServerInfo> {
|
|||
interface UiResourceData {
|
||||
html: string;
|
||||
csp?: McpUiResourceCsp;
|
||||
permissions?: McpUiResourcePermissions;
|
||||
}
|
||||
|
||||
export interface ToolCallInfo {
|
||||
|
|
@ -105,15 +106,16 @@ async function getUiResource(serverInfo: ServerInfo, uri: string): Promise<UiRes
|
|||
|
||||
const html = "blob" in content ? atob(content.blob) : content.text;
|
||||
|
||||
// Extract CSP metadata from resource content._meta.ui.csp (or content.meta for Python SDK)
|
||||
// Extract CSP and permissions metadata from resource content._meta.ui (or content.meta for Python SDK)
|
||||
log.info("Resource content keys:", Object.keys(content));
|
||||
log.info("Resource content._meta:", (content as any)._meta);
|
||||
|
||||
// Try both _meta (spec) and meta (Python SDK quirk)
|
||||
const contentMeta = (content as any)._meta || (content as any).meta;
|
||||
const csp = contentMeta?.ui?.csp;
|
||||
const permissions = contentMeta?.ui?.permissions;
|
||||
|
||||
return { html, csp };
|
||||
return { html, csp, permissions };
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -168,10 +170,10 @@ export async function initializeApp(
|
|||
new PostMessageTransport(iframe.contentWindow!, iframe.contentWindow!),
|
||||
);
|
||||
|
||||
// Load inner iframe HTML with CSP metadata
|
||||
const { html, csp } = await appResourcePromise;
|
||||
log.info("Sending UI resource HTML to MCP App", csp ? `(CSP: ${JSON.stringify(csp)})` : "");
|
||||
await appBridge.sendSandboxResourceReady({ html, csp });
|
||||
// Load inner iframe HTML with CSP and permissions metadata
|
||||
const { html, csp, permissions } = await appResourcePromise;
|
||||
log.info("Sending UI resource HTML to MCP App", csp ? `(CSP: ${JSON.stringify(csp)})` : "", permissions ? `(Permissions: ${JSON.stringify(permissions)})` : "");
|
||||
await appBridge.sendSandboxResourceReady({ html, csp, permissions });
|
||||
|
||||
// Wait for inner iframe to be ready
|
||||
log.info("Waiting for MCP App to initialize...");
|
||||
|
|
|
|||
|
|
@ -66,6 +66,24 @@ const PROXY_READY_NOTIFICATION: McpUiSandboxProxyReadyNotification["method"] =
|
|||
// Security: CSP is enforced via HTTP headers on sandbox.html (set by serve.ts
|
||||
// based on ?csp= query param). This is tamper-proof unlike meta tags.
|
||||
|
||||
// Build iframe allow attribute from permissions
|
||||
function buildAllowAttribute(permissions?: {
|
||||
camera?: boolean;
|
||||
microphone?: boolean;
|
||||
geolocation?: boolean;
|
||||
clipboardWrite?: boolean;
|
||||
}): string {
|
||||
if (!permissions) return "";
|
||||
|
||||
const allowList: string[] = [];
|
||||
if (permissions.camera) allowList.push("camera");
|
||||
if (permissions.microphone) allowList.push("microphone");
|
||||
if (permissions.geolocation) allowList.push("geolocation");
|
||||
if (permissions.clipboardWrite) allowList.push("clipboard-write");
|
||||
|
||||
return allowList.join("; ");
|
||||
}
|
||||
|
||||
window.addEventListener("message", async (event) => {
|
||||
if (event.source === window.parent) {
|
||||
// Validate that messages from parent come from the expected host origin.
|
||||
|
|
@ -81,10 +99,16 @@ window.addEventListener("message", async (event) => {
|
|||
}
|
||||
|
||||
if (event.data && event.data.method === RESOURCE_READY_NOTIFICATION) {
|
||||
const { html, sandbox } = event.data.params;
|
||||
const { html, sandbox, permissions } = event.data.params;
|
||||
if (typeof sandbox === "string") {
|
||||
inner.setAttribute("sandbox", sandbox);
|
||||
}
|
||||
// Set Permission Policy allow attribute if permissions are requested
|
||||
const allowAttribute = buildAllowAttribute(permissions);
|
||||
if (allowAttribute) {
|
||||
console.log("[Sandbox] Setting allow attribute:", allowAttribute);
|
||||
inner.setAttribute("allow", allowAttribute);
|
||||
}
|
||||
if (typeof html === "string") {
|
||||
// Use document.write instead of srcdoc for WebGL compatibility.
|
||||
// srcdoc creates an opaque origin which prevents WebGL canvas updates
|
||||
|
|
|
|||
|
|
@ -132,11 +132,23 @@ interface McpUiResourceCsp {
|
|||
*/
|
||||
resourceDomains?: string[],
|
||||
/**
|
||||
* Origins for nested iframes (frame-src directive).
|
||||
* Origins for nested iframes
|
||||
*
|
||||
* - Empty or omitted = no nested iframes allowed (`frame-src 'none'`)
|
||||
* - Maps to CSP `frame-src` directive
|
||||
*
|
||||
* @example
|
||||
* ["https://www.youtube.com", "https://player.vimeo.com"]
|
||||
*/
|
||||
frameDomains?: string[],
|
||||
/**
|
||||
* Allowed base URIs for the document (base-uri directive).
|
||||
* Allowed base URIs for the document
|
||||
*
|
||||
* - Empty or omitted = only same origin allowed (`base-uri 'self'`)
|
||||
* - Maps to CSP `base-uri` directive
|
||||
*
|
||||
* @example
|
||||
* ["https://cdn.example.com"]
|
||||
*/
|
||||
baseUriDomains?: string[],
|
||||
}
|
||||
|
|
@ -149,6 +161,39 @@ interface UIResourceMeta {
|
|||
* Hosts use this to enforce appropriate CSP headers.
|
||||
*/
|
||||
csp?: McpUiResourceCsp,
|
||||
/**
|
||||
* Sandbox permissions requested by the UI
|
||||
*
|
||||
* Servers declare which browser capabilities their UI needs.
|
||||
* Hosts MAY honor these by setting appropriate iframe `allow` attributes.
|
||||
* Apps SHOULD NOT assume permissions are granted; use JS feature detection as fallback.
|
||||
*/
|
||||
permissions?: {
|
||||
/**
|
||||
* Request camera access
|
||||
*
|
||||
* Maps to Permission Policy `camera` feature
|
||||
*/
|
||||
camera?: boolean,
|
||||
/**
|
||||
* Request microphone access
|
||||
*
|
||||
* Maps to Permission Policy `microphone` feature
|
||||
*/
|
||||
microphone?: boolean,
|
||||
/**
|
||||
* Request geolocation access
|
||||
*
|
||||
* Maps to Permission Policy `geolocation` feature
|
||||
*/
|
||||
geolocation?: boolean,
|
||||
/**
|
||||
* Request clipboard write access
|
||||
*
|
||||
* Maps to Permission Policy `clipboard-write` feature
|
||||
*/
|
||||
clipboardWrite?: boolean,
|
||||
},
|
||||
/**
|
||||
* Dedicated origin for widget
|
||||
*
|
||||
|
|
@ -193,6 +238,12 @@ The resource content is returned via `resources/read`:
|
|||
frameDomains?: string[]; // Origins for nested iframes (frame-src directive).
|
||||
baseUriDomains?: string[]; // Allowed base URIs for the document (base-uri directive).
|
||||
};
|
||||
permissions?: {
|
||||
camera?: boolean; // Request camera access
|
||||
microphone?: boolean; // Request microphone access
|
||||
geolocation?: boolean; // Request geolocation access
|
||||
clipboardWrite?: boolean; // Request clipboard write access
|
||||
};
|
||||
domain?: string;
|
||||
prefersBorder?: boolean;
|
||||
};
|
||||
|
|
@ -416,9 +467,11 @@ If the Host is a web page, it MUST wrap the Guest UI and communicate with it thr
|
|||
4. Once the Sandbox is ready, the Host MUST send the raw HTML resource to load in a `ui/notifications/sandbox-resource-ready` notification.
|
||||
5. The Sandbox MUST load the raw HTML of the Guest UI with CSP settings that:
|
||||
- Enforce the domains declared in `ui.csp` metadata
|
||||
- Prevent nested iframes (`frame-src 'none'`)
|
||||
- Block dangerous features (`object-src 'none'`, `base-uri 'self'`)
|
||||
- If `frameDomains` is provided, allow nested iframes from declared origins; otherwise use `frame-src 'none'`
|
||||
- If `baseUriDomains` is provided, allow base URIs from declared origins; otherwise use `base-uri 'self'`
|
||||
- Block dangerous features (`object-src 'none'`)
|
||||
- Apply restrictive defaults if no CSP metadata is provided
|
||||
- If `permissions` is declared, the Sandbox MAY set the inner iframe's `allow` attribute accordingly
|
||||
6. The Sandbox MUST forward messages sent by the Host to the Guest UI, and vice versa, for any method that doesn’t start with `ui/notifications/sandbox-`. This includes lifecycle messages, e.g., `ui/initialize` request & `ui/notifications/initialized` notification both sent by the Guest UI. The Host MUST NOT send any request or notification to the Guest UI before it receives an `initialized` notification.
|
||||
7. The Sandbox SHOULD NOT create/send any requests to the Host or to the Guest UI (this would require synthesizing new request ids).
|
||||
8. The Host MAY forward any message from the Guest UI (coming via the Sandbox) to the MCP Apps server, for any method that doesn’t start with `ui/`. While the Host SHOULD ensure the Guest UI’s MCP connection is spec-compliant, it MAY decide to block some messages or subject them to further user approval.
|
||||
|
|
@ -535,6 +588,53 @@ Example:
|
|||
}
|
||||
```
|
||||
|
||||
### Host Capabilities
|
||||
|
||||
`HostCapabilities` are sent to the Guest UI as part of the response to `ui/initialize` (inside `McpUiInitializeResult`).
|
||||
They describe the features and capabilities that the Host supports.
|
||||
|
||||
```typescript
|
||||
interface HostCapabilities {
|
||||
/** Experimental features (structure TBD). */
|
||||
experimental?: {};
|
||||
/** Host supports opening external URLs. */
|
||||
openLinks?: {};
|
||||
/** Host can proxy tool calls to the MCP server. */
|
||||
serverTools?: {
|
||||
/** Host supports tools/list_changed notifications. */
|
||||
listChanged?: boolean;
|
||||
};
|
||||
/** Host can proxy resource reads to the MCP server. */
|
||||
serverResources?: {
|
||||
/** Host supports resources/list_changed notifications. */
|
||||
listChanged?: boolean;
|
||||
};
|
||||
/** Host accepts log messages. */
|
||||
logging?: {};
|
||||
/** Sandbox configuration applied by the host. */
|
||||
sandbox?: {
|
||||
/** Permissions granted by the host (camera, microphone, geolocation, clipboard-write). */
|
||||
permissions?: {
|
||||
camera?: boolean;
|
||||
microphone?: boolean;
|
||||
geolocation?: boolean;
|
||||
clipboardWrite?: boolean;
|
||||
};
|
||||
/** CSP domains approved by the host. */
|
||||
csp?: {
|
||||
/** Approved origins for network requests (fetch/XHR/WebSocket). */
|
||||
connectDomains?: string[];
|
||||
/** Approved origins for static resources (scripts, images, styles, fonts). */
|
||||
resourceDomains?: string[];
|
||||
/** Approved origins for nested iframes (frame-src directive). */
|
||||
frameDomains?: string[];
|
||||
/** Approved base URIs for the document (base-uri directive). */
|
||||
baseUriDomains?: string[];
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Container Dimensions
|
||||
|
||||
The `HostContext` provides sizing information via `containerDimensions`:
|
||||
|
|
@ -1028,12 +1128,24 @@ These messages are reserved for web-based hosts that implement the recommended d
|
|||
method: "ui/notifications/sandbox-resource-ready",
|
||||
params: {
|
||||
html: string, // HTML content to load
|
||||
sandbox: string // Optional override for inner iframe `sandbox` attribute
|
||||
sandbox?: string, // Optional override for inner iframe `sandbox` attribute
|
||||
csp?: { // CSP configuration from resource metadata
|
||||
connectDomains?: string[],
|
||||
resourceDomains?: string[],
|
||||
frameDomains?: string[],
|
||||
baseUriDomains?: string[],
|
||||
},
|
||||
permissions?: { // Sandbox permissions from resource metadata
|
||||
camera?: boolean,
|
||||
microphone?: boolean,
|
||||
geolocation?: boolean,
|
||||
clipboardWrite?: boolean,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
These messages facilitate the communication between the outer sandbox proxy iframe and the host, enabling secure loading of untrusted HTML content.
|
||||
These messages facilitate the communication between the outer sandbox proxy iframe and the host, enabling secure loading of untrusted HTML content. The `permissions` field maps to the inner iframe's `allow` attribute for Permission Policy features.
|
||||
|
||||
### Lifecycle
|
||||
|
||||
|
|
@ -1489,6 +1601,7 @@ Hosts MUST enforce Content Security Policies based on resource metadata.
|
|||
|
||||
```typescript
|
||||
const csp = resource._meta?.ui?.csp;
|
||||
const permissions = resource._meta?.ui?.permissions;
|
||||
|
||||
const cspValue = `
|
||||
default-src 'none';
|
||||
|
|
@ -1498,10 +1611,17 @@ const cspValue = `
|
|||
img-src 'self' data: ${csp?.resourceDomains?.join(' ') || ''};
|
||||
font-src 'self' ${csp?.resourceDomains?.join(' ') || ''};
|
||||
media-src 'self' data: ${csp?.resourceDomains?.join(' ') || ''};
|
||||
frame-src 'none';
|
||||
frame-src ${csp?.frameDomains?.join(' ') || "'none'"};
|
||||
object-src 'none';
|
||||
base-uri 'self';
|
||||
base-uri ${csp?.baseUriDomains?.join(' ') || "'self'"};
|
||||
`;
|
||||
|
||||
// Permission Policy for iframe allow attribute
|
||||
const allowList: string[] = [];
|
||||
if (permissions?.camera) allowList.push('camera');
|
||||
if (permissions?.microphone) allowList.push('microphone');
|
||||
if (permissions?.geolocation) allowList.push('geolocation');
|
||||
const allowAttribute = allowList.join(' ');
|
||||
```
|
||||
|
||||
**Security Requirements:**
|
||||
|
|
|
|||
239
src/generated/schema.json
generated
239
src/generated/schema.json
generated
|
|
@ -89,6 +89,79 @@
|
|||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"sandbox": {
|
||||
"description": "Sandbox configuration applied by the host.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"permissions": {
|
||||
"description": "Permissions granted by the host (camera, microphone, geolocation).",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"camera": {
|
||||
"description": "Request camera access (Permission Policy `camera` feature).",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"microphone": {
|
||||
"description": "Request microphone access (Permission Policy `microphone` feature).",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"geolocation": {
|
||||
"description": "Request geolocation access (Permission Policy `geolocation` feature).",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"clipboardWrite": {
|
||||
"description": "Request clipboard write access (Permission Policy `clipboard-write` feature).",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"csp": {
|
||||
"description": "CSP domains approved by the host.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"connectDomains": {
|
||||
"description": "Origins for network requests (fetch/XHR/WebSocket).",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resourceDomains": {
|
||||
"description": "Origins for static resources (scripts, images, styles, fonts).",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"frameDomains": {
|
||||
"description": "Origins for nested iframes (frame-src directive).",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"baseUriDomains": {
|
||||
"description": "Allowed base URIs for the document (base-uri directive).",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
|
@ -2231,6 +2304,79 @@
|
|||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"sandbox": {
|
||||
"description": "Sandbox configuration applied by the host.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"permissions": {
|
||||
"description": "Permissions granted by the host (camera, microphone, geolocation).",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"camera": {
|
||||
"description": "Request camera access (Permission Policy `camera` feature).",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"microphone": {
|
||||
"description": "Request microphone access (Permission Policy `microphone` feature).",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"geolocation": {
|
||||
"description": "Request geolocation access (Permission Policy `geolocation` feature).",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"clipboardWrite": {
|
||||
"description": "Request clipboard write access (Permission Policy `clipboard-write` feature).",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"csp": {
|
||||
"description": "CSP domains approved by the host.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"connectDomains": {
|
||||
"description": "Origins for network requests (fetch/XHR/WebSocket).",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resourceDomains": {
|
||||
"description": "Origins for static resources (scripts, images, styles, fonts).",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"frameDomains": {
|
||||
"description": "Origins for nested iframes (frame-src directive).",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"baseUriDomains": {
|
||||
"description": "Allowed base URIs for the document (base-uri directive).",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
|
@ -3532,6 +3678,37 @@
|
|||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"permissions": {
|
||||
"description": "Sandbox permissions requested by the UI.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"camera": {
|
||||
"description": "Request camera access (Permission Policy `camera` feature).",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"microphone": {
|
||||
"description": "Request microphone access (Permission Policy `microphone` feature).",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"geolocation": {
|
||||
"description": "Request geolocation access (Permission Policy `geolocation` feature).",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"clipboardWrite": {
|
||||
"description": "Request clipboard write access (Permission Policy `clipboard-write` feature).",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"domain": {
|
||||
"description": "Dedicated origin for widget sandbox.",
|
||||
"type": "string"
|
||||
|
|
@ -3543,6 +3720,37 @@
|
|||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"McpUiResourcePermissions": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"camera": {
|
||||
"description": "Request camera access (Permission Policy `camera` feature).",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"microphone": {
|
||||
"description": "Request microphone access (Permission Policy `microphone` feature).",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"geolocation": {
|
||||
"description": "Request geolocation access (Permission Policy `geolocation` feature).",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"clipboardWrite": {
|
||||
"description": "Request clipboard write access (Permission Policy `clipboard-write` feature).",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"McpUiResourceTeardownRequest": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
|
|
@ -3638,6 +3846,37 @@
|
|||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"permissions": {
|
||||
"description": "Sandbox permissions from resource metadata.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"camera": {
|
||||
"description": "Request camera access (Permission Policy `camera` feature).",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"microphone": {
|
||||
"description": "Request microphone access (Permission Policy `microphone` feature).",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"geolocation": {
|
||||
"description": "Request geolocation access (Permission Policy `geolocation` feature).",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"clipboardWrite": {
|
||||
"description": "Request clipboard write access (Permission Policy `clipboard-write` feature).",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["html"],
|
||||
|
|
|
|||
10
src/generated/schema.test.ts
generated
10
src/generated/schema.test.ts
generated
|
|
@ -47,6 +47,10 @@ export type McpUiResourceCspSchemaInferredType = z.infer<
|
|||
typeof generated.McpUiResourceCspSchema
|
||||
>;
|
||||
|
||||
export type McpUiResourcePermissionsSchemaInferredType = z.infer<
|
||||
typeof generated.McpUiResourcePermissionsSchema
|
||||
>;
|
||||
|
||||
export type McpUiSizeChangedNotificationSchemaInferredType = z.infer<
|
||||
typeof generated.McpUiSizeChangedNotificationSchema
|
||||
>;
|
||||
|
|
@ -173,6 +177,12 @@ expectType<McpUiSandboxProxyReadyNotificationSchemaInferredType>(
|
|||
);
|
||||
expectType<spec.McpUiResourceCsp>({} as McpUiResourceCspSchemaInferredType);
|
||||
expectType<McpUiResourceCspSchemaInferredType>({} as spec.McpUiResourceCsp);
|
||||
expectType<spec.McpUiResourcePermissions>(
|
||||
{} as McpUiResourcePermissionsSchemaInferredType,
|
||||
);
|
||||
expectType<McpUiResourcePermissionsSchemaInferredType>(
|
||||
{} as spec.McpUiResourcePermissions,
|
||||
);
|
||||
expectType<spec.McpUiSizeChangedNotification>(
|
||||
{} as McpUiSizeChangedNotificationSchemaInferredType,
|
||||
);
|
||||
|
|
|
|||
56
src/generated/schema.ts
generated
56
src/generated/schema.ts
generated
|
|
@ -211,6 +211,40 @@ export const McpUiResourceCspSchema = z.object({
|
|||
.describe("Allowed base URIs for the document (base-uri directive)."),
|
||||
});
|
||||
|
||||
/**
|
||||
* @description Sandbox permissions requested by the UI resource.
|
||||
* Hosts MAY honor these by setting appropriate iframe `allow` attributes.
|
||||
* Apps SHOULD NOT assume permissions are granted; use JS feature detection as fallback.
|
||||
*/
|
||||
export const McpUiResourcePermissionsSchema = z.object({
|
||||
/** @description Request camera access (Permission Policy `camera` feature). */
|
||||
camera: z
|
||||
.object({})
|
||||
.optional()
|
||||
.describe("Request camera access (Permission Policy `camera` feature)."),
|
||||
/** @description Request microphone access (Permission Policy `microphone` feature). */
|
||||
microphone: z
|
||||
.object({})
|
||||
.optional()
|
||||
.describe(
|
||||
"Request microphone access (Permission Policy `microphone` feature).",
|
||||
),
|
||||
/** @description Request geolocation access (Permission Policy `geolocation` feature). */
|
||||
geolocation: z
|
||||
.object({})
|
||||
.optional()
|
||||
.describe(
|
||||
"Request geolocation access (Permission Policy `geolocation` feature).",
|
||||
),
|
||||
/** @description Request clipboard write access (Permission Policy `clipboard-write` feature). */
|
||||
clipboardWrite: z
|
||||
.object({})
|
||||
.optional()
|
||||
.describe(
|
||||
"Request clipboard write access (Permission Policy `clipboard-write` feature).",
|
||||
),
|
||||
});
|
||||
|
||||
/**
|
||||
* @description Notification of UI size changes (bidirectional: Guest <-> Host).
|
||||
* @see {@link app.App.sendSizeChanged} for the method to send this from Guest UI
|
||||
|
|
@ -363,6 +397,20 @@ export const McpUiHostCapabilitiesSchema = z.object({
|
|||
.describe("Host can proxy resource reads to the MCP server."),
|
||||
/** @description Host accepts log messages. */
|
||||
logging: z.object({}).optional().describe("Host accepts log messages."),
|
||||
/** @description Sandbox configuration applied by the host. */
|
||||
sandbox: z
|
||||
.object({
|
||||
/** @description Permissions granted by the host (camera, microphone, geolocation). */
|
||||
permissions: McpUiResourcePermissionsSchema.optional().describe(
|
||||
"Permissions granted by the host (camera, microphone, geolocation).",
|
||||
),
|
||||
/** @description CSP domains approved by the host. */
|
||||
csp: McpUiResourceCspSchema.optional().describe(
|
||||
"CSP domains approved by the host.",
|
||||
),
|
||||
})
|
||||
.optional()
|
||||
.describe("Sandbox configuration applied by the host."),
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
@ -405,6 +453,10 @@ export const McpUiResourceMetaSchema = z.object({
|
|||
csp: McpUiResourceCspSchema.optional().describe(
|
||||
"Content Security Policy configuration.",
|
||||
),
|
||||
/** @description Sandbox permissions requested by the UI. */
|
||||
permissions: McpUiResourcePermissionsSchema.optional().describe(
|
||||
"Sandbox permissions requested by the UI.",
|
||||
),
|
||||
/** @description Dedicated origin for widget sandbox. */
|
||||
domain: z
|
||||
.string()
|
||||
|
|
@ -514,6 +566,10 @@ export const McpUiSandboxResourceReadyNotificationSchema = z.object({
|
|||
csp: McpUiResourceCspSchema.optional().describe(
|
||||
"CSP configuration from resource metadata.",
|
||||
),
|
||||
/** @description Sandbox permissions from resource metadata. */
|
||||
permissions: McpUiResourcePermissionsSchema.optional().describe(
|
||||
"Sandbox permissions from resource metadata.",
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -220,6 +220,8 @@ export interface McpUiSandboxResourceReadyNotification {
|
|||
sandbox?: string;
|
||||
/** @description CSP configuration from resource metadata. */
|
||||
csp?: McpUiResourceCsp;
|
||||
/** @description Sandbox permissions from resource metadata. */
|
||||
permissions?: McpUiResourcePermissions;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -423,6 +425,13 @@ export interface McpUiHostCapabilities {
|
|||
};
|
||||
/** @description Host accepts log messages. */
|
||||
logging?: {};
|
||||
/** @description Sandbox configuration applied by the host. */
|
||||
sandbox?: {
|
||||
/** @description Permissions granted by the host (camera, microphone, geolocation). */
|
||||
permissions?: McpUiResourcePermissions;
|
||||
/** @description CSP domains approved by the host. */
|
||||
csp?: McpUiResourceCsp;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -498,12 +507,30 @@ export interface McpUiResourceCsp {
|
|||
baseUriDomains?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Sandbox permissions requested by the UI resource.
|
||||
* Hosts MAY honor these by setting appropriate iframe `allow` attributes.
|
||||
* Apps SHOULD NOT assume permissions are granted; use JS feature detection as fallback.
|
||||
*/
|
||||
export interface McpUiResourcePermissions {
|
||||
/** @description Request camera access (Permission Policy `camera` feature). */
|
||||
camera?: {};
|
||||
/** @description Request microphone access (Permission Policy `microphone` feature). */
|
||||
microphone?: {};
|
||||
/** @description Request geolocation access (Permission Policy `geolocation` feature). */
|
||||
geolocation?: {};
|
||||
/** @description Request clipboard write access (Permission Policy `clipboard-write` feature). */
|
||||
clipboardWrite?: {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description UI Resource metadata for security and rendering configuration.
|
||||
*/
|
||||
export interface McpUiResourceMeta {
|
||||
/** @description Content Security Policy configuration. */
|
||||
csp?: McpUiResourceCsp;
|
||||
/** @description Sandbox permissions requested by the UI. */
|
||||
permissions?: McpUiResourcePermissions;
|
||||
/** @description Dedicated origin for widget sandbox. */
|
||||
domain?: string;
|
||||
/** @description Visual boundary preference - true if UI prefers a visible border. */
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ export {
|
|||
type McpUiInitializeResult,
|
||||
type McpUiInitializedNotification,
|
||||
type McpUiResourceCsp,
|
||||
type McpUiResourcePermissions,
|
||||
type McpUiResourceMeta,
|
||||
type McpUiRequestDisplayModeRequest,
|
||||
type McpUiRequestDisplayModeResult,
|
||||
|
|
@ -110,6 +111,7 @@ export {
|
|||
McpUiInitializeResultSchema,
|
||||
McpUiInitializedNotificationSchema,
|
||||
McpUiResourceCspSchema,
|
||||
McpUiResourcePermissionsSchema,
|
||||
McpUiResourceMetaSchema,
|
||||
McpUiRequestDisplayModeRequestSchema,
|
||||
McpUiRequestDisplayModeResultSchema,
|
||||
|
|
|
|||
Loading…
Reference in a new issue