Skip to content

Commit 83860d7

Browse files
committed
Have apps request display mode instead
1 parent 372c241 commit 83860d7

File tree

8 files changed

+272
-1
lines changed

8 files changed

+272
-1
lines changed

specification/draft/apps.mdx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,39 @@ Host behavior:
537537
* Host SHOULD add the message to the conversation context, preserving the specified role.
538538
* Host MAY request user consent.
539539

540+
`ui/request-display-mode` - Request host to change display mode
541+
542+
```typescript
543+
// Request
544+
{
545+
jsonrpc: "2.0",
546+
id: 3,
547+
method: "ui/request-display-mode",
548+
params: {
549+
mode: "inline" | "fullscreen" | "pip" // Requested display mode
550+
}
551+
}
552+
553+
// Success Response
554+
{
555+
jsonrpc: "2.0",
556+
id: 3,
557+
result: {
558+
mode: "inline" | "fullscreen" | "pip" // Actual display mode set
559+
}
560+
}
561+
```
562+
563+
Host behavior:
564+
* Host SHOULD check if the requested mode is in `availableDisplayModes` from host context.
565+
* If the requested mode is available, Host SHOULD switch to that mode and return it in the response.
566+
* If the requested mode is not available, Host SHOULD return the current display mode in the response.
567+
* Host MAY coerce modes on certain platforms (e.g., "pip" to "fullscreen" on mobile).
568+
569+
Guest UI behavior:
570+
* Guest UI SHOULD check `availableDisplayModes` in host context before requesting a mode change.
571+
* Guest UI MUST handle the response mode differing from the requested mode.
572+
540573
#### Notifications (Host → UI)
541574

542575
`ui/notifications/tool-input` - Host MUST send this notification with the complete tool arguments after the Guest UI's initialize request completes.

src/app-bridge.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ import {
7171
McpUiSandboxProxyReadyNotification,
7272
McpUiSandboxProxyReadyNotificationSchema,
7373
McpUiSizeChangedNotificationSchema,
74+
McpUiRequestDisplayModeRequest,
75+
McpUiRequestDisplayModeRequestSchema,
76+
McpUiRequestDisplayModeResult,
7477
} from "./types";
7578
export * from "./types";
7679
export { RESOURCE_URI_META_KEY, RESOURCE_MIME_TYPE } from "./app";
@@ -222,6 +225,13 @@ export class AppBridge extends Protocol<
222225
this.onping?.(request.params, extra);
223226
return {};
224227
});
228+
229+
// Default handler for requestDisplayMode - returns current mode from host context.
230+
// Hosts can override this by setting bridge.onrequestdisplaymode = ...
231+
this.setRequestHandler(McpUiRequestDisplayModeRequestSchema, (request) => {
232+
const currentMode = this._hostContext.displayMode ?? "inline";
233+
return { mode: currentMode };
234+
});
225235
}
226236

227237
/**
@@ -494,6 +504,52 @@ export class AppBridge extends Protocol<
494504
);
495505
}
496506

507+
/**
508+
* Register a handler for display mode change requests from the Guest UI.
509+
*
510+
* The Guest UI sends `ui/request-display-mode` requests when it wants to change
511+
* its display mode (e.g., from "inline" to "fullscreen"). The handler should
512+
* check if the requested mode is in `availableDisplayModes` from the host context,
513+
* update the display mode if supported, and return the actual mode that was set.
514+
*
515+
* If the requested mode is not available, the handler should return the current
516+
* display mode instead.
517+
*
518+
* @param callback - Handler that receives the requested mode and returns the actual mode set
519+
* - params.mode - The display mode being requested ("inline" | "fullscreen" | "pip")
520+
* - extra - Request metadata (abort signal, session info)
521+
* - Returns: Promise<McpUiRequestDisplayModeResult> with the actual mode set
522+
*
523+
* @example
524+
* ```typescript
525+
* bridge.onrequestdisplaymode = async ({ mode }, extra) => {
526+
* const availableModes = hostContext.availableDisplayModes ?? ["inline"];
527+
* if (availableModes.includes(mode)) {
528+
* setDisplayMode(mode);
529+
* return { mode };
530+
* }
531+
* // Return current mode if requested mode not available
532+
* return { mode: currentDisplayMode };
533+
* };
534+
* ```
535+
*
536+
* @see {@link McpUiRequestDisplayModeRequest} for the request type
537+
* @see {@link McpUiRequestDisplayModeResult} for the result type
538+
*/
539+
set onrequestdisplaymode(
540+
callback: (
541+
params: McpUiRequestDisplayModeRequest["params"],
542+
extra: RequestHandlerExtra,
543+
) => Promise<McpUiRequestDisplayModeResult>,
544+
) {
545+
this.setRequestHandler(
546+
McpUiRequestDisplayModeRequestSchema,
547+
async (request, extra) => {
548+
return callback(request.params, extra);
549+
},
550+
);
551+
}
552+
497553
/**
498554
* Register a handler for logging messages from the Guest UI.
499555
*

src/app.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ import {
4242
McpUiToolInputPartialNotificationSchema,
4343
McpUiToolResultNotification,
4444
McpUiToolResultNotificationSchema,
45+
McpUiRequestDisplayModeRequest,
46+
McpUiRequestDisplayModeResultSchema,
4547
} from "./types";
4648
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
4749

@@ -839,6 +841,44 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
839841
);
840842
}
841843

844+
/**
845+
* Request a change to the display mode.
846+
*
847+
* Requests the host to change the UI container to the specified display mode
848+
* (e.g., "inline", "fullscreen", "pip"). The host will respond with the actual
849+
* display mode that was set, which may differ from the requested mode if
850+
* the requested mode is not available (check `availableDisplayModes` in host context).
851+
*
852+
* @param params - The display mode being requested
853+
* @param options - Request options (timeout, etc.)
854+
* @returns Result containing the actual display mode that was set
855+
*
856+
* @example Request fullscreen mode
857+
* ```typescript
858+
* const context = app.getHostContext();
859+
* if (context?.availableDisplayModes?.includes("fullscreen")) {
860+
* const result = await app.requestDisplayMode({ mode: "fullscreen" });
861+
* console.log("Display mode set to:", result.mode);
862+
* }
863+
* ```
864+
*
865+
* @see {@link McpUiRequestDisplayModeRequest} for request structure
866+
* @see {@link McpUiHostContext} for checking availableDisplayModes
867+
*/
868+
requestDisplayMode(
869+
params: McpUiRequestDisplayModeRequest["params"],
870+
options?: RequestOptions,
871+
) {
872+
return this.request(
873+
<McpUiRequestDisplayModeRequest>{
874+
method: "ui/request-display-mode",
875+
params,
876+
},
877+
McpUiRequestDisplayModeResultSchema,
878+
options,
879+
);
880+
}
881+
842882
/**
843883
* Notify the host of UI size changes.
844884
*

src/generated/schema.json

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1545,6 +1545,67 @@
15451545
},
15461546
"additionalProperties": {}
15471547
},
1548+
"McpUiRequestDisplayModeRequest": {
1549+
"$schema": "https://json-schema.org/draft/2020-12/schema",
1550+
"type": "object",
1551+
"properties": {
1552+
"method": {
1553+
"type": "string",
1554+
"const": "ui/request-display-mode"
1555+
},
1556+
"params": {
1557+
"type": "object",
1558+
"properties": {
1559+
"mode": {
1560+
"description": "The display mode being requested.",
1561+
"anyOf": [
1562+
{
1563+
"type": "string",
1564+
"const": "inline"
1565+
},
1566+
{
1567+
"type": "string",
1568+
"const": "fullscreen"
1569+
},
1570+
{
1571+
"type": "string",
1572+
"const": "pip"
1573+
}
1574+
]
1575+
}
1576+
},
1577+
"required": ["mode"],
1578+
"additionalProperties": false
1579+
}
1580+
},
1581+
"required": ["method", "params"],
1582+
"additionalProperties": false
1583+
},
1584+
"McpUiRequestDisplayModeResult": {
1585+
"$schema": "https://json-schema.org/draft/2020-12/schema",
1586+
"type": "object",
1587+
"properties": {
1588+
"mode": {
1589+
"description": "The display mode that was actually set. May differ from requested if not supported.",
1590+
"anyOf": [
1591+
{
1592+
"type": "string",
1593+
"const": "inline"
1594+
},
1595+
{
1596+
"type": "string",
1597+
"const": "fullscreen"
1598+
},
1599+
{
1600+
"type": "string",
1601+
"const": "pip"
1602+
}
1603+
]
1604+
}
1605+
},
1606+
"required": ["mode"],
1607+
"additionalProperties": {}
1608+
},
15481609
"McpUiResourceCsp": {
15491610
"$schema": "https://json-schema.org/draft/2020-12/schema",
15501611
"type": "object",

src/generated/schema.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ export type McpUiResourceMetaSchemaInferredType = z.infer<
8383
typeof generated.McpUiResourceMetaSchema
8484
>;
8585

86+
export type McpUiRequestDisplayModeRequestSchemaInferredType = z.infer<
87+
typeof generated.McpUiRequestDisplayModeRequestSchema
88+
>;
89+
90+
export type McpUiRequestDisplayModeResultSchemaInferredType = z.infer<
91+
typeof generated.McpUiRequestDisplayModeResultSchema
92+
>;
93+
8694
export type McpUiMessageRequestSchemaInferredType = z.infer<
8795
typeof generated.McpUiMessageRequestSchema
8896
>;
@@ -195,6 +203,18 @@ expectType<spec.McpUiResourceCsp>({} as McpUiResourceCspSchemaInferredType);
195203
expectType<McpUiResourceCspSchemaInferredType>({} as spec.McpUiResourceCsp);
196204
expectType<spec.McpUiResourceMeta>({} as McpUiResourceMetaSchemaInferredType);
197205
expectType<McpUiResourceMetaSchemaInferredType>({} as spec.McpUiResourceMeta);
206+
expectType<spec.McpUiRequestDisplayModeRequest>(
207+
{} as McpUiRequestDisplayModeRequestSchemaInferredType,
208+
);
209+
expectType<McpUiRequestDisplayModeRequestSchemaInferredType>(
210+
{} as spec.McpUiRequestDisplayModeRequest,
211+
);
212+
expectType<spec.McpUiRequestDisplayModeResult>(
213+
{} as McpUiRequestDisplayModeResultSchemaInferredType,
214+
);
215+
expectType<McpUiRequestDisplayModeResultSchemaInferredType>(
216+
{} as spec.McpUiRequestDisplayModeResult,
217+
);
198218
expectType<spec.McpUiMessageRequest>(
199219
{} as McpUiMessageRequestSchemaInferredType,
200220
);

src/generated/schema.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,31 @@ export const McpUiResourceMetaSchema = z.object({
307307
),
308308
});
309309

310+
/**
311+
* @description Request to change the display mode of the UI.
312+
* The host will respond with the actual display mode that was set,
313+
* which may differ from the requested mode if not supported.
314+
* @see {@link app.App.requestDisplayMode} for the method that sends this request
315+
*/
316+
export const McpUiRequestDisplayModeRequestSchema = z.object({
317+
method: z.literal("ui/request-display-mode"),
318+
params: z.object({
319+
/** @description The display mode being requested. */
320+
mode: McpUiDisplayModeSchema.describe("The display mode being requested."),
321+
}),
322+
});
323+
324+
/**
325+
* @description Result from requesting a display mode change.
326+
* @see {@link McpUiRequestDisplayModeRequest}
327+
*/
328+
export const McpUiRequestDisplayModeResultSchema = z.looseObject({
329+
/** @description The display mode that was actually set. May differ from requested if not supported. */
330+
mode: McpUiDisplayModeSchema.describe(
331+
"The display mode that was actually set. May differ from requested if not supported.",
332+
),
333+
});
334+
310335
/**
311336
* @description Request to send a message to the host's chat interface.
312337
* @see {@link app.App.sendMessage} for the method that sends this request

src/spec.types.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,3 +371,31 @@ export interface McpUiResourceMeta {
371371
/** @description Visual boundary preference - true if UI prefers a visible border. */
372372
prefersBorder?: boolean;
373373
}
374+
375+
/**
376+
* @description Request to change the display mode of the UI.
377+
* The host will respond with the actual display mode that was set,
378+
* which may differ from the requested mode if not supported.
379+
* @see {@link app.App.requestDisplayMode} for the method that sends this request
380+
*/
381+
export interface McpUiRequestDisplayModeRequest {
382+
method: "ui/request-display-mode";
383+
params: {
384+
/** @description The display mode being requested. */
385+
mode: McpUiDisplayMode;
386+
};
387+
}
388+
389+
/**
390+
* @description Result from requesting a display mode change.
391+
* @see {@link McpUiRequestDisplayModeRequest}
392+
*/
393+
export interface McpUiRequestDisplayModeResult {
394+
/** @description The display mode that was actually set. May differ from requested if not supported. */
395+
mode: McpUiDisplayMode;
396+
/**
397+
* Index signature required for MCP SDK `Protocol` class compatibility.
398+
* Note: The schema intentionally omits this to enforce strict validation.
399+
*/
400+
[key: string]: unknown;
401+
}

src/types.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ export {
3636
type McpUiInitializedNotification,
3737
type McpUiResourceCsp,
3838
type McpUiResourceMeta,
39+
type McpUiRequestDisplayModeRequest,
40+
type McpUiRequestDisplayModeResult,
3941
} from "./spec.types.js";
4042

4143
// Import types needed for protocol type unions (not re-exported, just used internally)
@@ -44,6 +46,7 @@ import type {
4446
McpUiOpenLinkRequest,
4547
McpUiMessageRequest,
4648
McpUiResourceTeardownRequest,
49+
McpUiRequestDisplayModeRequest,
4750
McpUiHostContextChangedNotification,
4851
McpUiToolInputNotification,
4952
McpUiToolInputPartialNotification,
@@ -57,6 +60,7 @@ import type {
5760
McpUiOpenLinkResult,
5861
McpUiMessageResult,
5962
McpUiResourceTeardownResult,
63+
McpUiRequestDisplayModeResult,
6064
} from "./spec.types.js";
6165

6266
// Re-export all schemas from generated/schema.ts (already PascalCase)
@@ -85,6 +89,8 @@ export {
8589
McpUiInitializedNotificationSchema,
8690
McpUiResourceCspSchema,
8791
McpUiResourceMetaSchema,
92+
McpUiRequestDisplayModeRequestSchema,
93+
McpUiRequestDisplayModeResultSchema,
8894
} from "./generated/schema.js";
8995

9096
// Re-export SDK types used in protocol type unions
@@ -113,7 +119,7 @@ import {
113119
* All request types in the MCP Apps protocol.
114120
*
115121
* Includes:
116-
* - MCP UI requests (initialize, open-link, message, resource-teardown)
122+
* - MCP UI requests (initialize, open-link, message, resource-teardown, request-display-mode)
117123
* - MCP server requests forwarded from the app (tools/call, resources/*, prompts/list)
118124
* - Protocol requests (ping)
119125
*/
@@ -122,6 +128,7 @@ export type AppRequest =
122128
| McpUiOpenLinkRequest
123129
| McpUiMessageRequest
124130
| McpUiResourceTeardownRequest
131+
| McpUiRequestDisplayModeRequest
125132
| CallToolRequest
126133
| ListToolsRequest
127134
| ListResourcesRequest
@@ -168,6 +175,7 @@ export type AppResult =
168175
| McpUiOpenLinkResult
169176
| McpUiMessageResult
170177
| McpUiResourceTeardownResult
178+
| McpUiRequestDisplayModeResult
171179
| CallToolResult
172180
| ListToolsResult
173181
| ListResourcesResult

0 commit comments

Comments
 (0)