diff --git a/package-lock.json b/package-lock.json index 099cff78..e668bcb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1377,6 +1377,90 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@oclif/core/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@oven/bun-darwin-aarch64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-aarch64/-/bun-darwin-aarch64-1.3.4.tgz", + "integrity": "sha512-2Ie4jDGvNGuPSD+pyyBKL8dJmX+bZfDNYEalwgROImVtwB1XYAatJK20dMaRlPA7jOhjvS9Io+4IZAJu7Js0AA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-darwin-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64/-/bun-darwin-x64-1.3.4.tgz", + "integrity": "sha512-4/BJojT8hk5g6Gecjn5yI7y96/+9Mtzsvdp9+2dcy9sTMdlV7jBvDzswqyJPZyQqw0F3HV3Vu9XuMubZwKd9lA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-darwin-x64-baseline": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@oven/bun-darwin-x64-baseline/-/bun-darwin-x64-baseline-1.3.4.tgz", + "integrity": "sha512-ZYxzIOCDqylTMsnWYERjKMMuK2b4an4qbloBmUZTwLHmVzos00yrhtpitZhJBgH6yB/l4Q5eoJ2W98UKtFFeiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oven/bun-linux-aarch64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64/-/bun-linux-aarch64-1.3.4.tgz", + "integrity": "sha512-8DUIlanftMdFxLGq2FxwKwfrp8O4ZofF/8Oc6lxCyEFmg2hixbHhL04+fPfJIi5D4hZloynxZdwTeDbGv/Kc4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-aarch64-musl": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64-musl/-/bun-linux-aarch64-musl-1.3.4.tgz", + "integrity": "sha512-6UtmM4wXgRKz+gnLZEfddfsuBSVQpJr09K12e5pbdnLzeWgXYlBT5FG8S7SVn1t6cbgBMnigEsFjWwfTuMNoCw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oven/bun-linux-x64": { "node_modules/@oven/bun-darwin-aarch64": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/@oven/bun-darwin-aarch64/-/bun-darwin-aarch64-1.3.4.tgz", @@ -1433,6 +1517,43 @@ "linux" ] }, + "node_modules/@oven/bun-windows-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@oven/bun-windows-x64/-/bun-windows-x64-1.3.4.tgz", + "integrity": "sha512-nswsuN6+HZPim6x4tFpDFpMa/qpTKfywbGvCkzxwrbJO9MtpuW/54NA1nFbHhpV14OLU0xuxyBj2PK4FHq4MlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oven/bun-windows-x64-baseline": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@oven/bun-windows-x64-baseline/-/bun-windows-x64-baseline-1.3.4.tgz", + "integrity": "sha512-ZQiSDFfSUdOrPTiL2GvkxlC/kMED4fsJwdZnwJK6S9ylXnk9xY/9ZXfe1615SFLQl2LsVRzJAtjQLeM0BifIKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", "node_modules/@oven/bun-linux-aarch64-musl": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/@oven/bun-linux-aarch64-musl/-/bun-linux-aarch64-musl-1.3.4.tgz", diff --git a/specification/draft/apps.mdx b/specification/draft/apps.mdx index f4a77c5b..1d4214f9 100644 --- a/specification/draft/apps.mdx +++ b/specification/draft/apps.mdx @@ -499,6 +499,48 @@ MCP Apps introduces additional JSON-RPC methods for UI-specific functionality: Host SHOULD open the URL in the user's default browser or a new tab. +`ui/request-display-mode` - Request display mode change + +```typescript +// Request +{ + jsonrpc: "2.0", + id: 2, + method: "ui/request-display-mode", + params: { + displayMode: "inline" | "fullscreen" | "pip" + } +} + +// Success Response +{ + jsonrpc: "2.0", + id: 2, + result: { + success: boolean, // Whether the request was honored + currentDisplayMode: string // The actual display mode after the request + } +} + +// Error Response (if denied or failed) +{ + jsonrpc: "2.0", + id: 2, + error: { + code: -32000, // Implementation-defined error + message: "Display mode change denied" | "Unsupported display mode" + } +} +``` + +Guest UI can request a display mode change from the Host. The Host maintains full control and MAY deny the request based on: +- User preferences or policies +- Platform constraints (e.g., mobile platforms may not support fullscreen) +- Current application state +- Security considerations + +The Host MUST return `success: true` if the request was honored. If denied, the Host MUST return `success: false`. The Host SHOULD return the updated `currentDisplayMode`. The Host MAY send a `ui/notifications/host-context-changed` notification after honoring the request to update all context fields. + `ui/message` - Send message content to the host's chat interface ```typescript diff --git a/src/app-bridge.ts b/src/app-bridge.ts index bed0a75d..e3ba96de 100644 --- a/src/app-bridge.ts +++ b/src/app-bridge.ts @@ -65,6 +65,9 @@ import { McpUiOpenLinkRequest, McpUiOpenLinkRequestSchema, McpUiOpenLinkResult, + McpUiRequestDisplayModeRequest, + McpUiRequestDisplayModeRequestSchema, + McpUiRequestDisplayModeResult, McpUiResourceTeardownRequest, McpUiResourceTeardownResult, McpUiResourceTeardownResultSchema, @@ -494,6 +497,48 @@ export class AppBridge extends Protocol< ); } + /** + * Register a handler for display mode requests from the Guest UI. + * + * The Guest UI sends `ui/request-display-mode` requests when it wants to change + * its display mode (e.g., fullscreen, picture-in-picture). The handler should + * evaluate the request based on the host's capabilities and user preferences, + * then return the result indicating success or denial. + * + * @param callback - Handler that receives display mode params and returns a result + * - params.displayMode - Requested display mode ("inline", "fullscreen", "pip") + * - extra - Request metadata (abort signal, session info) + * - Returns: Promise with success flag and current mode + * + * @example + * ```typescript + * bridge.onrequestdisplaymode = async ({ displayMode }, extra) => { + * if (displayMode === "fullscreen" && !hostSupportsFullscreen()) { + * return { success: false, currentDisplayMode: "inline" }; + * } + * + * await setAppDisplayMode(displayMode); + * return { success: true, currentDisplayMode: displayMode }; + * }; + * ``` + * + * @see {@link McpUiRequestDisplayModeRequest} for the request type + * @see {@link McpUiRequestDisplayModeResult} for the result type + */ + set onrequestdisplaymode( + callback: ( + params: McpUiRequestDisplayModeRequest["params"], + extra: RequestHandlerExtra, + ) => Promise, + ) { + this.setRequestHandler( + McpUiRequestDisplayModeRequestSchema, + async (request, extra) => { + return callback(request.params, extra); + }, + ); + } + /** * Register a handler for logging messages from the Guest UI. * diff --git a/src/app.ts b/src/app.ts index c4970fcd..b76e9c28 100644 --- a/src/app.ts +++ b/src/app.ts @@ -30,6 +30,8 @@ import { McpUiMessageResultSchema, McpUiOpenLinkRequest, McpUiOpenLinkResultSchema, + McpUiRequestDisplayModeRequest, + McpUiRequestDisplayModeResultSchema, McpUiResourceTeardownRequest, McpUiResourceTeardownRequestSchema, McpUiResourceTeardownResult, @@ -839,6 +841,48 @@ export class App extends Protocol { ); } + /** + * Request the host to change the app's display mode. + * + * Apps can request different display modes (inline, fullscreen, pip) to optimize + * their UI for various contexts. The host may accept or reject the request based + * on user preferences or platform capabilities. + * + * @param params - Desired display mode + * @param options - Request options (timeout, etc.) + * @returns Result indicating success and the current display mode + * + * @throws {Error} If the host denies the request (e.g., unsupported mode) + * @throws {Error} If the request times out or the connection is lost + * + * @example Request fullscreen mode + * ```typescript + * try { + * const result = await app.requestDisplayMode({ displayMode: "fullscreen" }); + * if (result.success) { + * console.log("Now in", result.currentDisplayMode, "mode"); + * } + * } catch (error) { + * console.error("Failed to change display mode:", error); + * } + * ``` + * + * @see {@link McpUiRequestDisplayModeRequest} for request structure + */ + requestDisplayMode( + params: McpUiRequestDisplayModeRequest["params"], + options?: RequestOptions, + ) { + return this.request( + { + method: "ui/request-display-mode", + params, + }, + McpUiRequestDisplayModeResultSchema, + options, + ); + } + /** * Notify the host of UI size changes. * diff --git a/src/generated/schema.json b/src/generated/schema.json index f70b60f9..00b427b4 100644 --- a/src/generated/schema.json +++ b/src/generated/schema.json @@ -1545,6 +1545,58 @@ }, "additionalProperties": {} }, + "McpUiRequestDisplayModeRequest": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "method": { + "type": "string", + "const": "ui/request-display-mode" + }, + "params": { + "type": "object", + "properties": { + "displayMode": { + "description": "Requested display mode.", + "anyOf": [ + { + "type": "string", + "const": "inline" + }, + { + "type": "string", + "const": "fullscreen" + }, + { + "type": "string", + "const": "pip" + } + ] + } + }, + "required": ["displayMode"], + "additionalProperties": false + } + }, + "required": ["method", "params"], + "additionalProperties": false + }, + "McpUiRequestDisplayModeResult": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "success": { + "description": "Whether the display mode change was successful.", + "type": "boolean" + }, + "currentDisplayMode": { + "description": "The current display mode after the request.", + "type": "string" + } + }, + "required": ["success", "currentDisplayMode"], + "additionalProperties": {} + }, "McpUiResourceCsp": { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", diff --git a/src/generated/schema.test.ts b/src/generated/schema.test.ts index 051ac8e5..d9be3d42 100644 --- a/src/generated/schema.test.ts +++ b/src/generated/schema.test.ts @@ -27,6 +27,14 @@ export type McpUiOpenLinkResultSchemaInferredType = z.infer< typeof generated.McpUiOpenLinkResultSchema >; +export type McpUiRequestDisplayModeRequestSchemaInferredType = z.infer< + typeof generated.McpUiRequestDisplayModeRequestSchema +>; + +export type McpUiRequestDisplayModeResultSchemaInferredType = z.infer< + typeof generated.McpUiRequestDisplayModeResultSchema +>; + export type McpUiMessageResultSchemaInferredType = z.infer< typeof generated.McpUiMessageResultSchema >; @@ -123,6 +131,18 @@ expectType( expectType( {} as spec.McpUiOpenLinkResult, ); +expectType( + {} as McpUiRequestDisplayModeRequestSchemaInferredType, +); +expectType( + {} as spec.McpUiRequestDisplayModeRequest, +); +expectType( + {} as McpUiRequestDisplayModeResultSchemaInferredType, +); +expectType( + {} as spec.McpUiRequestDisplayModeResult, +); expectType({} as McpUiMessageResultSchemaInferredType); expectType({} as spec.McpUiMessageResult); expectType( diff --git a/src/generated/schema.ts b/src/generated/schema.ts index baff9658..1eb068fa 100644 --- a/src/generated/schema.ts +++ b/src/generated/schema.ts @@ -50,6 +50,36 @@ export const McpUiOpenLinkResultSchema = z.looseObject({ ), }); +/** + * @description Request to change the display mode of the Guest UI. + * Sent from the Guest UI to the Host when requesting a change in how the UI is + * displayed, such as switching to fullscreen or picture-in-picture mode. + * The host may deny the request based on user preferences or capabilities. + * @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 Requested display mode. */ + displayMode: McpUiDisplayModeSchema.describe("Requested display mode."), + }), +}); + +/** + * @description Result from a display mode change request. + * @see {@link McpUiRequestDisplayModeRequest} + */ +export const McpUiRequestDisplayModeResultSchema = z.looseObject({ + /** @description Whether the display mode change was successful. */ + success: z + .boolean() + .describe("Whether the display mode change was successful."), + /** @description The current display mode after the request. */ + currentDisplayMode: z + .string() + .describe("The current display mode after the request."), +}); + /** * @description Result from sending a message. * @see {@link McpUiMessageRequest} diff --git a/src/spec.types.ts b/src/spec.types.ts index 01cc4713..5e14bedc 100644 --- a/src/spec.types.ts +++ b/src/spec.types.ts @@ -62,6 +62,37 @@ export interface McpUiOpenLinkResult { [key: string]: unknown; } +/** + * @description Request to change the display mode of the Guest UI. + * Sent from the Guest UI to the Host when requesting a change in how the UI is + * displayed, such as switching to fullscreen or picture-in-picture mode. + * The host may deny the request based on user preferences or capabilities. + * @see {@link app.App.requestDisplayMode} for the method that sends this request + */ +export interface McpUiRequestDisplayModeRequest { + method: "ui/request-display-mode"; + params: { + /** @description Requested display mode. */ + displayMode: McpUiDisplayMode; + }; +} + +/** + * @description Result from a display mode change request. + * @see {@link McpUiRequestDisplayModeRequest} + */ +export interface McpUiRequestDisplayModeResult { + /** @description Whether the display mode change was successful. */ + success: boolean; + /** @description The current display mode after the request. */ + currentDisplayMode: string; + /** + * Index signature required for MCP SDK `Protocol` class compatibility. + * Note: The schema intentionally omits this to enforce strict validation. + */ + [key: string]: unknown; +} + /** * @description Request to send a message to the host's chat interface. * @see {@link app.App.sendMessage} for the method that sends this request diff --git a/src/types.ts b/src/types.ts index 8544b27e..503397f7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -16,6 +16,8 @@ export { type McpUiDisplayMode, type McpUiOpenLinkRequest, type McpUiOpenLinkResult, + type McpUiRequestDisplayModeRequest, + type McpUiRequestDisplayModeResult, type McpUiMessageRequest, type McpUiMessageResult, type McpUiSandboxProxyReadyNotification, @@ -65,6 +67,8 @@ export { McpUiDisplayModeSchema, McpUiOpenLinkRequestSchema, McpUiOpenLinkResultSchema, + McpUiRequestDisplayModeRequestSchema, + McpUiRequestDisplayModeResultSchema, McpUiMessageRequestSchema, McpUiMessageResultSchema, McpUiSandboxProxyReadyNotificationSchema,