Merge pull request #152 from modelcontextprotocol/martinalong/mcp-apps/display-mode-updates

[MCP Apps] Have apps request display mode instead
This commit is contained in:
martinalong 2025-12-15 16:12:45 -08:00 • committed by GitHub
commit c516cf0894
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 272 additions and 1 deletions

View file

@ -677,6 +677,39 @@ Host behavior:
* Host SHOULD add the message to the conversation context, preserving the specified role.
* Host MAY request user consent.
`ui/request-display-mode` - Request host to change display mode
```typescript
// Request
{
jsonrpc: "2.0",
id: 3,
method: "ui/request-display-mode",
params: {
mode: "inline" | "fullscreen" | "pip" // Requested display mode
}
}
// Success Response
{
jsonrpc: "2.0",
id: 3,
result: {
mode: "inline" | "fullscreen" | "pip" // Actual display mode set
}
}
```
Host behavior:
* App MUST check if the requested mode is in `availableDisplayModes` from host context.
* It is up to the host whether it switches to the requested mode, but the host MUST return the resulting mode (whether updated or not) in the response.
* If the requested mode is not available, Host SHOULD return the current display mode in the response.
* Host MAY coerce modes on certain platforms (e.g., "pip" to "fullscreen" on mobile).
Guest UI behavior:
* Guest UI SHOULD check `availableDisplayModes` in host context before requesting a mode change.
* Guest UI MUST handle the response mode differing from the requested mode.
#### Notifications (Host → UI)
`ui/notifications/tool-input` - Host MUST send this notification with the complete tool arguments after the Guest UI's initialize request completes.

View file

@ -71,6 +71,9 @@ import {
McpUiSandboxProxyReadyNotification,
McpUiSandboxProxyReadyNotificationSchema,
McpUiSizeChangedNotificationSchema,
McpUiRequestDisplayModeRequest,
McpUiRequestDisplayModeRequestSchema,
McpUiRequestDisplayModeResult,
} from "./types";
export * from "./types";
export { RESOURCE_URI_META_KEY, RESOURCE_MIME_TYPE } from "./app";
@ -222,6 +225,13 @@ export class AppBridge extends Protocol<
this.onping?.(request.params, extra);
return {};
});
// Default handler for requestDisplayMode - returns current mode from host context.
// Hosts can override this by setting bridge.onrequestdisplaymode = ...
this.setRequestHandler(McpUiRequestDisplayModeRequestSchema, (request) => {
const currentMode = this._hostContext.displayMode ?? "inline";
return { mode: currentMode };
});
}
/**
@ -494,6 +504,52 @@ export class AppBridge extends Protocol<
);
}
/**
* Register a handler for display mode change requests from the Guest UI.
*
* The Guest UI sends `ui/request-display-mode` requests when it wants to change
* its display mode (e.g., from "inline" to "fullscreen"). The handler should
* check if the requested mode is in `availableDisplayModes` from the host context,
* update the display mode if supported, and return the actual mode that was set.
*
* If the requested mode is not available, the handler should return the current
* display mode instead.
*
* @param callback - Handler that receives the requested mode and returns the actual mode set
* - params.mode - The display mode being requested ("inline" | "fullscreen" | "pip")
* - extra - Request metadata (abort signal, session info)
* - Returns: Promise<McpUiRequestDisplayModeResult> with the actual mode set
*
* @example
* ```typescript
* bridge.onrequestdisplaymode = async ({ mode }, extra) => {
* const availableModes = hostContext.availableDisplayModes ?? ["inline"];
* if (availableModes.includes(mode)) {
* setDisplayMode(mode);
* return { mode };
* }
* // Return current mode if requested mode not available
* return { mode: currentDisplayMode };
* };
* ```
*
* @see {@link McpUiRequestDisplayModeRequest} for the request type
* @see {@link McpUiRequestDisplayModeResult} for the result type
*/
set onrequestdisplaymode(
callback: (
params: McpUiRequestDisplayModeRequest["params"],
extra: RequestHandlerExtra,
) => Promise<McpUiRequestDisplayModeResult>,
) {
this.setRequestHandler(
McpUiRequestDisplayModeRequestSchema,
async (request, extra) => {
return callback(request.params, extra);
},
);
}
/**
* Register a handler for logging messages from the Guest UI.
*

View file

@ -42,6 +42,8 @@ import {
McpUiToolInputPartialNotificationSchema,
McpUiToolResultNotification,
McpUiToolResultNotificationSchema,
McpUiRequestDisplayModeRequest,
McpUiRequestDisplayModeResultSchema,
} from "./types";
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
@ -844,6 +846,44 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
);
}
/**
* Request a change to the display mode.
*
* Requests the host to change the UI container to the specified display mode
* (e.g., "inline", "fullscreen", "pip"). The host will respond with the actual
* display mode that was set, which may differ from the requested mode if
* the requested mode is not available (check `availableDisplayModes` in host context).
*
* @param params - The display mode being requested
* @param options - Request options (timeout, etc.)
* @returns Result containing the actual display mode that was set
*
* @example Request fullscreen mode
* ```typescript
* const context = app.getHostContext();
* if (context?.availableDisplayModes?.includes("fullscreen")) {
* const result = await app.requestDisplayMode({ mode: "fullscreen" });
* console.log("Display mode set to:", result.mode);
* }
* ```
*
* @see {@link McpUiRequestDisplayModeRequest} for request structure
* @see {@link McpUiHostContext} for checking availableDisplayModes
*/
requestDisplayMode(
params: McpUiRequestDisplayModeRequest["params"],
options?: RequestOptions,
) {
return this.request(
<McpUiRequestDisplayModeRequest>{
method: "ui/request-display-mode",
params,
},
McpUiRequestDisplayModeResultSchema,
options,
);
}
/**
* Notify the host of UI size changes.
*

View file

@ -2845,6 +2845,67 @@
},
"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": {
"mode": {
"description": "The display mode being requested.",
"anyOf": [
{
"type": "string",
"const": "inline"
},
{
"type": "string",
"const": "fullscreen"
},
{
"type": "string",
"const": "pip"
}
]
}
},
"required": ["mode"],
"additionalProperties": false
}
},
"required": ["method", "params"],
"additionalProperties": false
},
"McpUiRequestDisplayModeResult": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"mode": {
"description": "The display mode that was actually set. May differ from requested if not supported.",
"anyOf": [
{
"type": "string",
"const": "inline"
},
{
"type": "string",
"const": "fullscreen"
},
{
"type": "string",
"const": "pip"
}
]
}
},
"required": ["mode"],
"additionalProperties": {}
},
"McpUiResourceCsp": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",

View file

@ -95,6 +95,14 @@ export type McpUiResourceMetaSchemaInferredType = z.infer<
typeof generated.McpUiResourceMetaSchema
>;
export type McpUiRequestDisplayModeRequestSchemaInferredType = z.infer<
typeof generated.McpUiRequestDisplayModeRequestSchema
>;
export type McpUiRequestDisplayModeResultSchemaInferredType = z.infer<
typeof generated.McpUiRequestDisplayModeResultSchema
>;
export type McpUiMessageRequestSchemaInferredType = z.infer<
typeof generated.McpUiMessageRequestSchema
>;
@ -217,6 +225,18 @@ expectType<spec.McpUiResourceCsp>({} as McpUiResourceCspSchemaInferredType);
expectType<McpUiResourceCspSchemaInferredType>({} as spec.McpUiResourceCsp);
expectType<spec.McpUiResourceMeta>({} as McpUiResourceMetaSchemaInferredType);
expectType<McpUiResourceMetaSchemaInferredType>({} as spec.McpUiResourceMeta);
expectType<spec.McpUiRequestDisplayModeRequest>(
{} as McpUiRequestDisplayModeRequestSchemaInferredType,
);
expectType<McpUiRequestDisplayModeRequestSchemaInferredType>(
{} as spec.McpUiRequestDisplayModeRequest,
);
expectType<spec.McpUiRequestDisplayModeResult>(
{} as McpUiRequestDisplayModeResultSchemaInferredType,
);
expectType<McpUiRequestDisplayModeResultSchemaInferredType>(
{} as spec.McpUiRequestDisplayModeResult,
);
expectType<spec.McpUiMessageRequest>(
{} as McpUiMessageRequestSchemaInferredType,
);

View file

@ -424,6 +424,31 @@ export const McpUiResourceMetaSchema = z.object({
),
});
/**
* @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.looseObject({
/** @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.",
),
});
/**
* @description Request to send a message to the host's chat interface.
* @see {@link app.App.sendMessage} for the method that sends this request

View file

@ -485,3 +485,31 @@ export interface McpUiResourceMeta {
/** @description Visual boundary preference - true if UI prefers a visible border. */
prefersBorder?: boolean;
}
/**
* @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 interface McpUiRequestDisplayModeRequest {
method: "ui/request-display-mode";
params: {
/** @description The display mode being requested. */
mode: McpUiDisplayMode;
};
}
/**
* @description Result from requesting a display mode change.
* @see {@link McpUiRequestDisplayModeRequest}
*/
export interface McpUiRequestDisplayModeResult {
/** @description The display mode that was actually set. May differ from requested if not supported. */
mode: McpUiDisplayMode;
/**
* Index signature required for MCP SDK `Protocol` class compatibility.
* Note: The schema intentionally omits this to enforce strict validation.
*/
[key: string]: unknown;
}

View file

@ -39,6 +39,8 @@ export {
type McpUiInitializedNotification,
type McpUiResourceCsp,
type McpUiResourceMeta,
type McpUiRequestDisplayModeRequest,
type McpUiRequestDisplayModeResult,
} from "./spec.types.js";
// Import types needed for protocol type unions (not re-exported, just used internally)
@ -47,6 +49,7 @@ import type {
McpUiOpenLinkRequest,
McpUiMessageRequest,
McpUiResourceTeardownRequest,
McpUiRequestDisplayModeRequest,
McpUiHostContextChangedNotification,
McpUiToolInputNotification,
McpUiToolInputPartialNotification,
@ -60,6 +63,7 @@ import type {
McpUiOpenLinkResult,
McpUiMessageResult,
McpUiResourceTeardownResult,
McpUiRequestDisplayModeResult,
} from "./spec.types.js";
// Re-export all schemas from generated/schema.ts (already PascalCase)
@ -89,6 +93,8 @@ export {
McpUiInitializedNotificationSchema,
McpUiResourceCspSchema,
McpUiResourceMetaSchema,
McpUiRequestDisplayModeRequestSchema,
McpUiRequestDisplayModeResultSchema,
} from "./generated/schema.js";
// Re-export SDK types used in protocol type unions
@ -117,7 +123,7 @@ import {
* All request types in the MCP Apps protocol.
*
* Includes:
* - MCP UI requests (initialize, open-link, message, resource-teardown)
* - MCP UI requests (initialize, open-link, message, resource-teardown, request-display-mode)
* - MCP server requests forwarded from the app (tools/call, resources/*, prompts/list)
* - Protocol requests (ping)
*/
@ -126,6 +132,7 @@ export type AppRequest =
| McpUiOpenLinkRequest
| McpUiMessageRequest
| McpUiResourceTeardownRequest
| McpUiRequestDisplayModeRequest
| CallToolRequest
| ListToolsRequest
| ListResourcesRequest
@ -172,6 +179,7 @@ export type AppResult =
| McpUiOpenLinkResult
| McpUiMessageResult
| McpUiResourceTeardownResult
| McpUiRequestDisplayModeResult
| CallToolResult
| ListToolsResult
| ListResourcesResult