Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions specification/draft/apps.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,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.
Expand Down
56 changes: 56 additions & 0 deletions src/app-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 };
});
}

/**
Expand Down Expand Up @@ -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.
*
Expand Down
40 changes: 40 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ import {
McpUiToolInputPartialNotificationSchema,
McpUiToolResultNotification,
McpUiToolResultNotificationSchema,
McpUiRequestDisplayModeRequest,
McpUiRequestDisplayModeResultSchema,
} from "./types";
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";

Expand Down Expand Up @@ -839,6 +841,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.
*
Expand Down
61 changes: 61 additions & 0 deletions src/generated/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1545,6 +1545,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",
Expand Down
20 changes: 20 additions & 0 deletions src/generated/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,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
>;
Expand Down Expand Up @@ -195,6 +203,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,
);
Expand Down
25 changes: 25 additions & 0 deletions src/generated/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,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
Expand Down
28 changes: 28 additions & 0 deletions src/spec.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,3 +371,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;
}
10 changes: 9 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,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)
Expand All @@ -44,6 +46,7 @@ import type {
McpUiOpenLinkRequest,
McpUiMessageRequest,
McpUiResourceTeardownRequest,
McpUiRequestDisplayModeRequest,
McpUiHostContextChangedNotification,
McpUiToolInputNotification,
McpUiToolInputPartialNotification,
Expand All @@ -57,6 +60,7 @@ import type {
McpUiOpenLinkResult,
McpUiMessageResult,
McpUiResourceTeardownResult,
McpUiRequestDisplayModeResult,
} from "./spec.types.js";

// Re-export all schemas from generated/schema.ts (already PascalCase)
Expand Down Expand Up @@ -85,6 +89,8 @@ export {
McpUiInitializedNotificationSchema,
McpUiResourceCspSchema,
McpUiResourceMetaSchema,
McpUiRequestDisplayModeRequestSchema,
McpUiRequestDisplayModeResultSchema,
} from "./generated/schema.js";

// Re-export SDK types used in protocol type unions
Expand Down Expand Up @@ -113,7 +119,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)
*/
Expand All @@ -122,6 +128,7 @@ export type AppRequest =
| McpUiOpenLinkRequest
| McpUiMessageRequest
| McpUiResourceTeardownRequest
| McpUiRequestDisplayModeRequest
| CallToolRequest
| ListToolsRequest
| ListResourcesRequest
Expand Down Expand Up @@ -168,6 +175,7 @@ export type AppResult =
| McpUiOpenLinkResult
| McpUiMessageResult
| McpUiResourceTeardownResult
| McpUiRequestDisplayModeResult
| CallToolResult
| ListToolsResult
| ListResourcesResult
Expand Down
Loading