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 { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
||||||
import type { CallToolResult, Tool } from "@modelcontextprotocol/sdk/types.js";
|
import type { CallToolResult, Tool } from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
|
@ -43,6 +43,7 @@ export async function connectToServer(serverUrl: URL): Promise<ServerInfo> {
|
||||||
interface UiResourceData {
|
interface UiResourceData {
|
||||||
html: string;
|
html: string;
|
||||||
csp?: McpUiResourceCsp;
|
csp?: McpUiResourceCsp;
|
||||||
|
permissions?: McpUiResourcePermissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ToolCallInfo {
|
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;
|
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 keys:", Object.keys(content));
|
||||||
log.info("Resource content._meta:", (content as any)._meta);
|
log.info("Resource content._meta:", (content as any)._meta);
|
||||||
|
|
||||||
// Try both _meta (spec) and meta (Python SDK quirk)
|
// Try both _meta (spec) and meta (Python SDK quirk)
|
||||||
const contentMeta = (content as any)._meta || (content as any).meta;
|
const contentMeta = (content as any)._meta || (content as any).meta;
|
||||||
const csp = contentMeta?.ui?.csp;
|
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!),
|
new PostMessageTransport(iframe.contentWindow!, iframe.contentWindow!),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Load inner iframe HTML with CSP metadata
|
// Load inner iframe HTML with CSP and permissions metadata
|
||||||
const { html, csp } = await appResourcePromise;
|
const { html, csp, permissions } = await appResourcePromise;
|
||||||
log.info("Sending UI resource HTML to MCP App", csp ? `(CSP: ${JSON.stringify(csp)})` : "");
|
log.info("Sending UI resource HTML to MCP App", csp ? `(CSP: ${JSON.stringify(csp)})` : "", permissions ? `(Permissions: ${JSON.stringify(permissions)})` : "");
|
||||||
await appBridge.sendSandboxResourceReady({ html, csp });
|
await appBridge.sendSandboxResourceReady({ html, csp, permissions });
|
||||||
|
|
||||||
// Wait for inner iframe to be ready
|
// Wait for inner iframe to be ready
|
||||||
log.info("Waiting for MCP App to initialize...");
|
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
|
// 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.
|
// 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) => {
|
window.addEventListener("message", async (event) => {
|
||||||
if (event.source === window.parent) {
|
if (event.source === window.parent) {
|
||||||
// Validate that messages from parent come from the expected host origin.
|
// 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) {
|
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") {
|
if (typeof sandbox === "string") {
|
||||||
inner.setAttribute("sandbox", sandbox);
|
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") {
|
if (typeof html === "string") {
|
||||||
// Use document.write instead of srcdoc for WebGL compatibility.
|
// Use document.write instead of srcdoc for WebGL compatibility.
|
||||||
// srcdoc creates an opaque origin which prevents WebGL canvas updates
|
// srcdoc creates an opaque origin which prevents WebGL canvas updates
|
||||||
|
|
|
||||||
|
|
@ -132,11 +132,23 @@ interface McpUiResourceCsp {
|
||||||
*/
|
*/
|
||||||
resourceDomains?: string[],
|
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[],
|
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[],
|
baseUriDomains?: string[],
|
||||||
}
|
}
|
||||||
|
|
@ -149,6 +161,39 @@ interface UIResourceMeta {
|
||||||
* Hosts use this to enforce appropriate CSP headers.
|
* Hosts use this to enforce appropriate CSP headers.
|
||||||
*/
|
*/
|
||||||
csp?: McpUiResourceCsp,
|
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
|
* 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).
|
frameDomains?: string[]; // Origins for nested iframes (frame-src directive).
|
||||||
baseUriDomains?: string[]; // Allowed base URIs for the document (base-uri 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;
|
domain?: string;
|
||||||
prefersBorder?: boolean;
|
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.
|
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:
|
5. The Sandbox MUST load the raw HTML of the Guest UI with CSP settings that:
|
||||||
- Enforce the domains declared in `ui.csp` metadata
|
- Enforce the domains declared in `ui.csp` metadata
|
||||||
- Prevent nested iframes (`frame-src 'none'`)
|
- If `frameDomains` is provided, allow nested iframes from declared origins; otherwise use `frame-src 'none'`
|
||||||
- Block dangerous features (`object-src 'none'`, `base-uri 'self'`)
|
- 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
|
- 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.
|
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).
|
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.
|
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
|
### Container Dimensions
|
||||||
|
|
||||||
The `HostContext` provides sizing information via `containerDimensions`:
|
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",
|
method: "ui/notifications/sandbox-resource-ready",
|
||||||
params: {
|
params: {
|
||||||
html: string, // HTML content to load
|
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
|
### Lifecycle
|
||||||
|
|
||||||
|
|
@ -1489,6 +1601,7 @@ Hosts MUST enforce Content Security Policies based on resource metadata.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const csp = resource._meta?.ui?.csp;
|
const csp = resource._meta?.ui?.csp;
|
||||||
|
const permissions = resource._meta?.ui?.permissions;
|
||||||
|
|
||||||
const cspValue = `
|
const cspValue = `
|
||||||
default-src 'none';
|
default-src 'none';
|
||||||
|
|
@ -1498,10 +1611,17 @@ const cspValue = `
|
||||||
img-src 'self' data: ${csp?.resourceDomains?.join(' ') || ''};
|
img-src 'self' data: ${csp?.resourceDomains?.join(' ') || ''};
|
||||||
font-src 'self' ${csp?.resourceDomains?.join(' ') || ''};
|
font-src 'self' ${csp?.resourceDomains?.join(' ') || ''};
|
||||||
media-src 'self' data: ${csp?.resourceDomains?.join(' ') || ''};
|
media-src 'self' data: ${csp?.resourceDomains?.join(' ') || ''};
|
||||||
frame-src 'none';
|
frame-src ${csp?.frameDomains?.join(' ') || "'none'"};
|
||||||
object-src '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:**
|
**Security Requirements:**
|
||||||
|
|
|
||||||
239
src/generated/schema.json
generated
239
src/generated/schema.json
generated
|
|
@ -89,6 +89,79 @@
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {},
|
"properties": {},
|
||||||
"additionalProperties": false
|
"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
|
"additionalProperties": false
|
||||||
|
|
@ -2231,6 +2304,79 @@
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {},
|
"properties": {},
|
||||||
"additionalProperties": false
|
"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,
|
"additionalProperties": false,
|
||||||
|
|
@ -3532,6 +3678,37 @@
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"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": {
|
"domain": {
|
||||||
"description": "Dedicated origin for widget sandbox.",
|
"description": "Dedicated origin for widget sandbox.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
@ -3543,6 +3720,37 @@
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"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": {
|
"McpUiResourceTeardownRequest": {
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
@ -3638,6 +3846,37 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"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"],
|
"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
|
typeof generated.McpUiResourceCspSchema
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export type McpUiResourcePermissionsSchemaInferredType = z.infer<
|
||||||
|
typeof generated.McpUiResourcePermissionsSchema
|
||||||
|
>;
|
||||||
|
|
||||||
export type McpUiSizeChangedNotificationSchemaInferredType = z.infer<
|
export type McpUiSizeChangedNotificationSchemaInferredType = z.infer<
|
||||||
typeof generated.McpUiSizeChangedNotificationSchema
|
typeof generated.McpUiSizeChangedNotificationSchema
|
||||||
>;
|
>;
|
||||||
|
|
@ -173,6 +177,12 @@ expectType<McpUiSandboxProxyReadyNotificationSchemaInferredType>(
|
||||||
);
|
);
|
||||||
expectType<spec.McpUiResourceCsp>({} as McpUiResourceCspSchemaInferredType);
|
expectType<spec.McpUiResourceCsp>({} as McpUiResourceCspSchemaInferredType);
|
||||||
expectType<McpUiResourceCspSchemaInferredType>({} as spec.McpUiResourceCsp);
|
expectType<McpUiResourceCspSchemaInferredType>({} as spec.McpUiResourceCsp);
|
||||||
|
expectType<spec.McpUiResourcePermissions>(
|
||||||
|
{} as McpUiResourcePermissionsSchemaInferredType,
|
||||||
|
);
|
||||||
|
expectType<McpUiResourcePermissionsSchemaInferredType>(
|
||||||
|
{} as spec.McpUiResourcePermissions,
|
||||||
|
);
|
||||||
expectType<spec.McpUiSizeChangedNotification>(
|
expectType<spec.McpUiSizeChangedNotification>(
|
||||||
{} as McpUiSizeChangedNotificationSchemaInferredType,
|
{} 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)."),
|
.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).
|
* @description Notification of UI size changes (bidirectional: Guest <-> Host).
|
||||||
* @see {@link app.App.sendSizeChanged} for the method to send this from Guest UI
|
* @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."),
|
.describe("Host can proxy resource reads to the MCP server."),
|
||||||
/** @description Host accepts log messages. */
|
/** @description Host accepts log messages. */
|
||||||
logging: z.object({}).optional().describe("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(
|
csp: McpUiResourceCspSchema.optional().describe(
|
||||||
"Content Security Policy configuration.",
|
"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. */
|
/** @description Dedicated origin for widget sandbox. */
|
||||||
domain: z
|
domain: z
|
||||||
.string()
|
.string()
|
||||||
|
|
@ -514,6 +566,10 @@ export const McpUiSandboxResourceReadyNotificationSchema = z.object({
|
||||||
csp: McpUiResourceCspSchema.optional().describe(
|
csp: McpUiResourceCspSchema.optional().describe(
|
||||||
"CSP configuration from resource metadata.",
|
"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;
|
sandbox?: string;
|
||||||
/** @description CSP configuration from resource metadata. */
|
/** @description CSP configuration from resource metadata. */
|
||||||
csp?: McpUiResourceCsp;
|
csp?: McpUiResourceCsp;
|
||||||
|
/** @description Sandbox permissions from resource metadata. */
|
||||||
|
permissions?: McpUiResourcePermissions;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -423,6 +425,13 @@ export interface McpUiHostCapabilities {
|
||||||
};
|
};
|
||||||
/** @description Host accepts log messages. */
|
/** @description Host accepts log messages. */
|
||||||
logging?: {};
|
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[];
|
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.
|
* @description UI Resource metadata for security and rendering configuration.
|
||||||
*/
|
*/
|
||||||
export interface McpUiResourceMeta {
|
export interface McpUiResourceMeta {
|
||||||
/** @description Content Security Policy configuration. */
|
/** @description Content Security Policy configuration. */
|
||||||
csp?: McpUiResourceCsp;
|
csp?: McpUiResourceCsp;
|
||||||
|
/** @description Sandbox permissions requested by the UI. */
|
||||||
|
permissions?: McpUiResourcePermissions;
|
||||||
/** @description Dedicated origin for widget sandbox. */
|
/** @description Dedicated origin for widget sandbox. */
|
||||||
domain?: string;
|
domain?: string;
|
||||||
/** @description Visual boundary preference - true if UI prefers a visible border. */
|
/** @description Visual boundary preference - true if UI prefers a visible border. */
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ export {
|
||||||
type McpUiInitializeResult,
|
type McpUiInitializeResult,
|
||||||
type McpUiInitializedNotification,
|
type McpUiInitializedNotification,
|
||||||
type McpUiResourceCsp,
|
type McpUiResourceCsp,
|
||||||
|
type McpUiResourcePermissions,
|
||||||
type McpUiResourceMeta,
|
type McpUiResourceMeta,
|
||||||
type McpUiRequestDisplayModeRequest,
|
type McpUiRequestDisplayModeRequest,
|
||||||
type McpUiRequestDisplayModeResult,
|
type McpUiRequestDisplayModeResult,
|
||||||
|
|
@ -110,6 +111,7 @@ export {
|
||||||
McpUiInitializeResultSchema,
|
McpUiInitializeResultSchema,
|
||||||
McpUiInitializedNotificationSchema,
|
McpUiInitializedNotificationSchema,
|
||||||
McpUiResourceCspSchema,
|
McpUiResourceCspSchema,
|
||||||
|
McpUiResourcePermissionsSchema,
|
||||||
McpUiResourceMetaSchema,
|
McpUiResourceMetaSchema,
|
||||||
McpUiRequestDisplayModeRequestSchema,
|
McpUiRequestDisplayModeRequestSchema,
|
||||||
McpUiRequestDisplayModeResultSchema,
|
McpUiRequestDisplayModeResultSchema,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue