ext-apps/src/generated/schema.ts
Olivier Chafik 8b09f4278c
fix: proper CSP handling in basic-host sandboxing (HTTP headers, worker-src, document.write) (#234)
* fix: Move CSP to HTTP headers + add worker-src, frameDomains, baseUriDomains, permissions

Security improvements:
- CSP is now set via HTTP headers in serve.ts instead of meta tags
  (meta tag CSP can be tampered with by same-origin content)
- CSP passed as query param to sandbox.html for header-based enforcement

New CSP/permissions features (borrowed from PR #158):
- frameDomains: control frame-src directive for nested iframes
- baseUriDomains: control base-uri directive
- permissions: camera, microphone, geolocation via iframe allow attribute

WebGL fix:
- Use document.write() instead of srcdoc for inner iframe content
  (srcdoc creates opaque origin that breaks WebGL canvas updates)
- Add worker-src directive with blob: support (critical for WebGL apps
  like CesiumJS/Three.js that use workers for tile decoding, terrain
  processing, image processing)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: Remove permissions support (defer to PR #158)

Keep this PR focused on CSP security fixes only.
Permissions (camera, microphone, geolocation) will be
handled in #158.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* cleanup McpUiResourceCsp type usage / duplicate defs

* fix: guard against CSP injection in domain parameters

Validate CSP domain entries to reject characters that could:
- Break out of CSP directives (semicolons, newlines)
- Inject CSP keywords like 'unsafe-eval' (quotes)
- Inject multiple sources in one entry (spaces)

This prevents injection attacks where malicious domains could
override the security policy.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 14:32:48 +00:00

704 lines
26 KiB
TypeScript
Generated

// Generated by ts-to-zod
// Post-processed for Zod v3/v4 compatibility and MCP SDK integration
// Run: npm run generate:schemas
import { z } from "zod";
import {
ContentBlockSchema,
CallToolResultSchema,
ImplementationSchema,
RequestIdSchema,
ToolSchema,
} from "@modelcontextprotocol/sdk/types.js";
/**
* @description Color theme preference for the host environment.
*/
export const McpUiThemeSchema = z
.union([z.literal("light"), z.literal("dark")])
.describe("Color theme preference for the host environment.");
/**
* @description Display mode for UI presentation.
*/
export const McpUiDisplayModeSchema = z
.union([z.literal("inline"), z.literal("fullscreen"), z.literal("pip")])
.describe("Display mode for UI presentation.");
/**
* @description CSS variable keys available to MCP apps for theming.
*/
export const McpUiStyleVariableKeySchema = z
.union([
z.literal("--color-background-primary"),
z.literal("--color-background-secondary"),
z.literal("--color-background-tertiary"),
z.literal("--color-background-inverse"),
z.literal("--color-background-ghost"),
z.literal("--color-background-info"),
z.literal("--color-background-danger"),
z.literal("--color-background-success"),
z.literal("--color-background-warning"),
z.literal("--color-background-disabled"),
z.literal("--color-text-primary"),
z.literal("--color-text-secondary"),
z.literal("--color-text-tertiary"),
z.literal("--color-text-inverse"),
z.literal("--color-text-ghost"),
z.literal("--color-text-info"),
z.literal("--color-text-danger"),
z.literal("--color-text-success"),
z.literal("--color-text-warning"),
z.literal("--color-text-disabled"),
z.literal("--color-text-ghost"),
z.literal("--color-border-primary"),
z.literal("--color-border-secondary"),
z.literal("--color-border-tertiary"),
z.literal("--color-border-inverse"),
z.literal("--color-border-ghost"),
z.literal("--color-border-info"),
z.literal("--color-border-danger"),
z.literal("--color-border-success"),
z.literal("--color-border-warning"),
z.literal("--color-border-disabled"),
z.literal("--color-ring-primary"),
z.literal("--color-ring-secondary"),
z.literal("--color-ring-inverse"),
z.literal("--color-ring-info"),
z.literal("--color-ring-danger"),
z.literal("--color-ring-success"),
z.literal("--color-ring-warning"),
z.literal("--font-sans"),
z.literal("--font-mono"),
z.literal("--font-weight-normal"),
z.literal("--font-weight-medium"),
z.literal("--font-weight-semibold"),
z.literal("--font-weight-bold"),
z.literal("--font-text-xs-size"),
z.literal("--font-text-sm-size"),
z.literal("--font-text-md-size"),
z.literal("--font-text-lg-size"),
z.literal("--font-heading-xs-size"),
z.literal("--font-heading-sm-size"),
z.literal("--font-heading-md-size"),
z.literal("--font-heading-lg-size"),
z.literal("--font-heading-xl-size"),
z.literal("--font-heading-2xl-size"),
z.literal("--font-heading-3xl-size"),
z.literal("--font-text-xs-line-height"),
z.literal("--font-text-sm-line-height"),
z.literal("--font-text-md-line-height"),
z.literal("--font-text-lg-line-height"),
z.literal("--font-heading-xs-line-height"),
z.literal("--font-heading-sm-line-height"),
z.literal("--font-heading-md-line-height"),
z.literal("--font-heading-lg-line-height"),
z.literal("--font-heading-xl-line-height"),
z.literal("--font-heading-2xl-line-height"),
z.literal("--font-heading-3xl-line-height"),
z.literal("--border-radius-xs"),
z.literal("--border-radius-sm"),
z.literal("--border-radius-md"),
z.literal("--border-radius-lg"),
z.literal("--border-radius-xl"),
z.literal("--border-radius-full"),
z.literal("--border-width-regular"),
z.literal("--shadow-hairline"),
z.literal("--shadow-sm"),
z.literal("--shadow-md"),
z.literal("--shadow-lg"),
])
.describe("CSS variable keys available to MCP apps for theming.");
/**
* @description Style variables for theming MCP apps.
*
* Individual style keys are optional - hosts may provide any subset of these values.
* Values are strings containing CSS values (colors, sizes, font stacks, etc.).
*
* Note: This type uses `Record<K, string | undefined>` rather than `Partial<Record<K, string>>`
* for compatibility with Zod schema generation. Both are functionally equivalent for validation.
*/
export const McpUiStylesSchema = z
.record(
McpUiStyleVariableKeySchema.describe(
"Style variables for theming MCP apps.\n\nIndividual style keys are optional - hosts may provide any subset of these values.\nValues are strings containing CSS values (colors, sizes, font stacks, etc.).\n\nNote: This type uses `Record<K, string | undefined>` rather than `Partial<Record<K, string>>`\nfor compatibility with Zod schema generation. Both are functionally equivalent for validation.",
),
z
.union([z.string(), z.undefined()])
.describe(
"Style variables for theming MCP apps.\n\nIndividual style keys are optional - hosts may provide any subset of these values.\nValues are strings containing CSS values (colors, sizes, font stacks, etc.).\n\nNote: This type uses `Record<K, string | undefined>` rather than `Partial<Record<K, string>>`\nfor compatibility with Zod schema generation. Both are functionally equivalent for validation.",
),
)
.describe(
"Style variables for theming MCP apps.\n\nIndividual style keys are optional - hosts may provide any subset of these values.\nValues are strings containing CSS values (colors, sizes, font stacks, etc.).\n\nNote: This type uses `Record<K, string | undefined>` rather than `Partial<Record<K, string>>`\nfor compatibility with Zod schema generation. Both are functionally equivalent for validation.",
);
/**
* @description Request to open an external URL in the host's default browser.
* @see {@link app.App.sendOpenLink} for the method that sends this request
*/
export const McpUiOpenLinkRequestSchema = z.object({
method: z.literal("ui/open-link"),
params: z.object({
/** @description URL to open in the host's browser */
url: z.string().describe("URL to open in the host's browser"),
}),
});
/**
* @description Result from opening a URL.
* @see {@link McpUiOpenLinkRequest}
*/
export const McpUiOpenLinkResultSchema = z
.object({
/** @description True if the host failed to open the URL (e.g., due to security policy). */
isError: z
.boolean()
.optional()
.describe(
"True if the host failed to open the URL (e.g., due to security policy).",
),
})
.passthrough();
/**
* @description Result from sending a message.
* @see {@link McpUiMessageRequest}
*/
export const McpUiMessageResultSchema = z
.object({
/** @description True if the host rejected or failed to deliver the message. */
isError: z
.boolean()
.optional()
.describe("True if the host rejected or failed to deliver the message."),
})
.passthrough();
/**
* @description Notification that the sandbox proxy iframe is ready to receive content.
* @internal
* @see https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx#sandbox-proxy
*/
export const McpUiSandboxProxyReadyNotificationSchema = z.object({
method: z.literal("ui/notifications/sandbox-proxy-ready"),
params: z.object({}),
});
/**
* @description Content Security Policy configuration for UI resources.
*/
export const McpUiResourceCspSchema = z.object({
/** @description Origins for network requests (fetch/XHR/WebSocket). */
connectDomains: z
.array(z.string())
.optional()
.describe("Origins for network requests (fetch/XHR/WebSocket)."),
/** @description Origins for static resources (scripts, images, styles, fonts). */
resourceDomains: z
.array(z.string())
.optional()
.describe("Origins for static resources (scripts, images, styles, fonts)."),
/** @description Origins for nested iframes (frame-src directive). */
frameDomains: z
.array(z.string())
.optional()
.describe("Origins for nested iframes (frame-src directive)."),
/** @description Allowed base URIs for the document (base-uri directive). */
baseUriDomains: z
.array(z.string())
.optional()
.describe("Allowed base URIs for the document (base-uri directive)."),
});
/**
* @description Notification of UI size changes (bidirectional: Guest <-> Host).
* @see {@link app.App.sendSizeChanged} for the method to send this from Guest UI
*/
export const McpUiSizeChangedNotificationSchema = z.object({
method: z.literal("ui/notifications/size-changed"),
params: z.object({
/** @description New width in pixels. */
width: z.number().optional().describe("New width in pixels."),
/** @description New height in pixels. */
height: z.number().optional().describe("New height in pixels."),
}),
});
/**
* @description Notification containing complete tool arguments (Host -> Guest UI).
*/
export const McpUiToolInputNotificationSchema = z.object({
method: z.literal("ui/notifications/tool-input"),
params: z.object({
/** @description Complete tool call arguments as key-value pairs. */
arguments: z
.record(
z.string(),
z
.unknown()
.describe("Complete tool call arguments as key-value pairs."),
)
.optional()
.describe("Complete tool call arguments as key-value pairs."),
}),
});
/**
* @description Notification containing partial/streaming tool arguments (Host -> Guest UI).
*/
export const McpUiToolInputPartialNotificationSchema = z.object({
method: z.literal("ui/notifications/tool-input-partial"),
params: z.object({
/** @description Partial tool call arguments (incomplete, may change). */
arguments: z
.record(
z.string(),
z
.unknown()
.describe("Partial tool call arguments (incomplete, may change)."),
)
.optional()
.describe("Partial tool call arguments (incomplete, may change)."),
}),
});
/**
* @description Notification that tool execution was cancelled (Host -> Guest UI).
* Host MUST send this if tool execution was cancelled for any reason (user action,
* sampling error, classifier intervention, etc.).
*/
export const McpUiToolCancelledNotificationSchema = z.object({
method: z.literal("ui/notifications/tool-cancelled"),
params: z.object({
/** @description Optional reason for the cancellation (e.g., "user action", "timeout"). */
reason: z
.string()
.optional()
.describe(
'Optional reason for the cancellation (e.g., "user action", "timeout").',
),
}),
});
/**
* @description CSS blocks that can be injected by apps.
*/
export const McpUiHostCssSchema = z.object({
/** @description CSS for font loading (@font-face rules or @import statements). Apps must apply using applyHostFonts(). */
fonts: z
.string()
.optional()
.describe("CSS for font loading (@font-face rules or"),
});
/**
* @description Style configuration for theming MCP apps.
*/
export const McpUiHostStylesSchema = z.object({
/** @description CSS variables for theming the app. */
variables: McpUiStylesSchema.optional().describe(
"CSS variables for theming the app.",
),
/** @description CSS blocks that apps can inject. */
css: McpUiHostCssSchema.optional().describe(
"CSS blocks that apps can inject.",
),
});
/**
* @description Request for graceful shutdown of the Guest UI (Host -> Guest UI).
* @see {@link app-bridge.AppBridge.teardownResource} for the host method that sends this
*/
export const McpUiResourceTeardownRequestSchema = z.object({
method: z.literal("ui/resource-teardown"),
params: z.object({}),
});
/**
* @description Result from graceful shutdown request.
* @see {@link McpUiResourceTeardownRequest}
*/
export const McpUiResourceTeardownResultSchema = z.record(
z.string(),
z.unknown(),
);
/**
* @description Capabilities supported by the host application.
* @see {@link McpUiInitializeResult} for the initialization result that includes these capabilities
*/
export const McpUiHostCapabilitiesSchema = z.object({
/** @description Experimental features (structure TBD). */
experimental: z
.object({})
.optional()
.describe("Experimental features (structure TBD)."),
/** @description Host supports opening external URLs. */
openLinks: z
.object({})
.optional()
.describe("Host supports opening external URLs."),
/** @description Host can proxy tool calls to the MCP server. */
serverTools: z
.object({
/** @description Host supports tools/list_changed notifications. */
listChanged: z
.boolean()
.optional()
.describe("Host supports tools/list_changed notifications."),
})
.optional()
.describe("Host can proxy tool calls to the MCP server."),
/** @description Host can proxy resource reads to the MCP server. */
serverResources: z
.object({
/** @description Host supports resources/list_changed notifications. */
listChanged: z
.boolean()
.optional()
.describe("Host supports resources/list_changed notifications."),
})
.optional()
.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 Capabilities provided by the Guest UI (App).
* @see {@link McpUiInitializeRequest} for the initialization request that includes these capabilities
*/
export const McpUiAppCapabilitiesSchema = z.object({
/** @description Experimental features (structure TBD). */
experimental: z
.object({})
.optional()
.describe("Experimental features (structure TBD)."),
/** @description App exposes MCP-style tools that the host can call. */
tools: z
.object({
/** @description App supports tools/list_changed notifications. */
listChanged: z
.boolean()
.optional()
.describe("App supports tools/list_changed notifications."),
})
.optional()
.describe("App exposes MCP-style tools that the host can call."),
});
/**
* @description Notification that Guest UI has completed initialization (Guest UI -> Host).
* @see {@link app.App.connect} for the method that sends this notification
*/
export const McpUiInitializedNotificationSchema = z.object({
method: z.literal("ui/notifications/initialized"),
params: z.object({}).optional(),
});
/**
* @description UI Resource metadata for security and rendering configuration.
*/
export const McpUiResourceMetaSchema = z.object({
/** @description Content Security Policy configuration. */
csp: McpUiResourceCspSchema.optional().describe(
"Content Security Policy configuration.",
),
/** @description Dedicated origin for widget sandbox. */
domain: z
.string()
.optional()
.describe("Dedicated origin for widget sandbox."),
/** @description Visual boundary preference - true if UI prefers a visible border. */
prefersBorder: z
.boolean()
.optional()
.describe(
"Visual boundary preference - true if UI prefers a visible border.",
),
});
/**
* @description Request to change the display mode of the UI.
* The host will respond with the actual display mode that was set,
* which may differ from the requested mode if not supported.
* @see {@link app.App.requestDisplayMode} for the method that sends this request
*/
export const McpUiRequestDisplayModeRequestSchema = z.object({
method: z.literal("ui/request-display-mode"),
params: z.object({
/** @description The display mode being requested. */
mode: McpUiDisplayModeSchema.describe("The display mode being requested."),
}),
});
/**
* @description Result from requesting a display mode change.
* @see {@link McpUiRequestDisplayModeRequest}
*/
export const McpUiRequestDisplayModeResultSchema = z
.object({
/** @description The display mode that was actually set. May differ from requested if not supported. */
mode: McpUiDisplayModeSchema.describe(
"The display mode that was actually set. May differ from requested if not supported.",
),
})
.passthrough();
/**
* @description Tool visibility scope - who can access the tool.
*/
export const McpUiToolVisibilitySchema = z
.union([z.literal("model"), z.literal("app")])
.describe("Tool visibility scope - who can access the tool.");
/**
* @description UI-related metadata for tools.
*/
export const McpUiToolMetaSchema = z.object({
/**
* URI of the UI resource to display for this tool, if any.
* This is converted to `_meta["ui/resourceUri"]`.
*
* @example "ui://weather/widget.html"
*/
resourceUri: z.string().optional(),
/**
* @description Who can access this tool. Default: ["model", "app"]
* - "model": Tool visible to and callable by the agent
* - "app": Tool callable by the app from this server only
*/
visibility: z
.array(McpUiToolVisibilitySchema)
.optional()
.describe(
'Who can access this tool. Default: ["model", "app"]\n- "model": Tool visible to and callable by the agent\n- "app": Tool callable by the app from this server only',
),
});
/**
* @description Request to send a message to the host's chat interface.
* @see {@link app.App.sendMessage} for the method that sends this request
*/
export const McpUiMessageRequestSchema = z.object({
method: z.literal("ui/message"),
params: z.object({
/** @description Message role, currently only "user" is supported. */
role: z
.literal("user")
.describe('Message role, currently only "user" is supported.'),
/** @description Message content blocks (text, image, etc.). */
content: z
.array(ContentBlockSchema)
.describe("Message content blocks (text, image, etc.)."),
}),
});
/**
* @description Notification containing HTML resource for the sandbox proxy to load.
* @internal
* @see https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx#sandbox-proxy
*/
export const McpUiSandboxResourceReadyNotificationSchema = z.object({
method: z.literal("ui/notifications/sandbox-resource-ready"),
params: z.object({
/** @description HTML content to load into the inner iframe. */
html: z.string().describe("HTML content to load into the inner iframe."),
/** @description Optional override for the inner iframe's sandbox attribute. */
sandbox: z
.string()
.optional()
.describe("Optional override for the inner iframe's sandbox attribute."),
/** @description CSP configuration from resource metadata. */
csp: McpUiResourceCspSchema.optional().describe(
"CSP configuration from resource metadata.",
),
}),
});
/**
* @description Notification containing tool execution result (Host -> Guest UI).
*/
export const McpUiToolResultNotificationSchema = z.object({
method: z.literal("ui/notifications/tool-result"),
/** @description Standard MCP tool execution result. */
params: CallToolResultSchema.describe("Standard MCP tool execution result."),
});
/**
* @description Rich context about the host environment provided to Guest UIs.
*/
export const McpUiHostContextSchema = z
.object({
/** @description Metadata of the tool call that instantiated this App. */
toolInfo: z
.object({
/** @description JSON-RPC id of the tools/call request. */
id: RequestIdSchema.optional().describe(
"JSON-RPC id of the tools/call request.",
),
/** @description Tool definition including name, inputSchema, etc. */
tool: ToolSchema.describe(
"Tool definition including name, inputSchema, etc.",
),
})
.optional()
.describe("Metadata of the tool call that instantiated this App."),
/** @description Current color theme preference. */
theme: McpUiThemeSchema.optional().describe(
"Current color theme preference.",
),
/** @description Style configuration for theming the app. */
styles: McpUiHostStylesSchema.optional().describe(
"Style configuration for theming the app.",
),
/** @description How the UI is currently displayed. */
displayMode: McpUiDisplayModeSchema.optional().describe(
"How the UI is currently displayed.",
),
/** @description Display modes the host supports. */
availableDisplayModes: z
.array(z.string())
.optional()
.describe("Display modes the host supports."),
/**
* @description Container dimensions. Represents the dimensions of the iframe or other
* container holding the app. Specify either width or maxWidth, and either height or maxHeight.
*/
containerDimensions: z
.union([
z.object({
/** @description Fixed container height in pixels. */
height: z.number().describe("Fixed container height in pixels."),
}),
z.object({
/** @description Maximum container height in pixels. */
maxHeight: z
.union([z.number(), z.undefined()])
.optional()
.describe("Maximum container height in pixels."),
}),
])
.and(
z.union([
z.object({
/** @description Fixed container width in pixels. */
width: z.number().describe("Fixed container width in pixels."),
}),
z.object({
/** @description Maximum container width in pixels. */
maxWidth: z
.union([z.number(), z.undefined()])
.optional()
.describe("Maximum container width in pixels."),
}),
]),
)
.optional()
.describe(
"Container dimensions. Represents the dimensions of the iframe or other\ncontainer holding the app. Specify either width or maxWidth, and either height or maxHeight.",
),
/** @description User's language and region preference in BCP 47 format. */
locale: z
.string()
.optional()
.describe("User's language and region preference in BCP 47 format."),
/** @description User's timezone in IANA format. */
timeZone: z.string().optional().describe("User's timezone in IANA format."),
/** @description Host application identifier. */
userAgent: z.string().optional().describe("Host application identifier."),
/** @description Platform type for responsive design decisions. */
platform: z
.union([z.literal("web"), z.literal("desktop"), z.literal("mobile")])
.optional()
.describe("Platform type for responsive design decisions."),
/** @description Device input capabilities. */
deviceCapabilities: z
.object({
/** @description Whether the device supports touch input. */
touch: z
.boolean()
.optional()
.describe("Whether the device supports touch input."),
/** @description Whether the device supports hover interactions. */
hover: z
.boolean()
.optional()
.describe("Whether the device supports hover interactions."),
})
.optional()
.describe("Device input capabilities."),
/** @description Mobile safe area boundaries in pixels. */
safeAreaInsets: z
.object({
/** @description Top safe area inset in pixels. */
top: z.number().describe("Top safe area inset in pixels."),
/** @description Right safe area inset in pixels. */
right: z.number().describe("Right safe area inset in pixels."),
/** @description Bottom safe area inset in pixels. */
bottom: z.number().describe("Bottom safe area inset in pixels."),
/** @description Left safe area inset in pixels. */
left: z.number().describe("Left safe area inset in pixels."),
})
.optional()
.describe("Mobile safe area boundaries in pixels."),
})
.passthrough();
/**
* @description Notification that host context has changed (Host -> Guest UI).
* @see {@link McpUiHostContext} for the full context structure
*/
export const McpUiHostContextChangedNotificationSchema = z.object({
method: z.literal("ui/notifications/host-context-changed"),
/** @description Partial context update containing only changed fields. */
params: McpUiHostContextSchema.describe(
"Partial context update containing only changed fields.",
),
});
/**
* @description Initialization request sent from Guest UI to Host.
* @see {@link app.App.connect} for the method that sends this request
*/
export const McpUiInitializeRequestSchema = z.object({
method: z.literal("ui/initialize"),
params: z.object({
/** @description App identification (name and version). */
appInfo: ImplementationSchema.describe(
"App identification (name and version).",
),
/** @description Features and capabilities this app provides. */
appCapabilities: McpUiAppCapabilitiesSchema.describe(
"Features and capabilities this app provides.",
),
/** @description Protocol version this app supports. */
protocolVersion: z.string().describe("Protocol version this app supports."),
}),
});
/**
* @description Initialization result returned from Host to Guest UI.
* @see {@link McpUiInitializeRequest}
*/
export const McpUiInitializeResultSchema = z
.object({
/** @description Negotiated protocol version string (e.g., "2025-11-21"). */
protocolVersion: z
.string()
.describe('Negotiated protocol version string (e.g., "2025-11-21").'),
/** @description Host application identification and version. */
hostInfo: ImplementationSchema.describe(
"Host application identification and version.",
),
/** @description Features and capabilities provided by the host. */
hostCapabilities: McpUiHostCapabilitiesSchema.describe(
"Features and capabilities provided by the host.",
),
/** @description Rich context about the host environment. */
hostContext: McpUiHostContextSchema.describe(
"Rich context about the host environment.",
),
})
.passthrough();