Skip to content

Commit c516cf0

Browse files
authored
Merge pull request #152 from modelcontextprotocol/martinalong/mcp-apps/display-mode-updates
[MCP Apps] Have apps request display mode instead
2 parents e316259 + 46d38f4 commit c516cf0

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
@@ -677,6 +677,39 @@ Host behavior:
677677
* Host SHOULD add the message to the conversation context, preserving the specified role.
678678
* Host MAY request user consent.
679679

680+
`ui/request-display-mode` - Request host to change display mode
681+
682+
```typescript
683+
// Request
684+
{
685+
jsonrpc: "2.0",
686+
id: 3,
687+
method: "ui/request-display-mode",
688+
params: {
689+
mode: "inline" | "fullscreen" | "pip" // Requested display mode
690+
}
691+
}
692+
693+
// Success Response
694+
{
695+
jsonrpc: "2.0",
696+
id: 3,
697+
result: {
698+
mode: "inline" | "fullscreen" | "pip" // Actual display mode set
699+
}
700+
}
701+
```
702+
703+
Host behavior:
704+
* App MUST check if the requested mode is in `availableDisplayModes` from host context.
705+
* 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.
706+
* If the requested mode is not available, Host SHOULD return the current display mode in the response.
707+
* Host MAY coerce modes on certain platforms (e.g., "pip" to "fullscreen" on mobile).
708+
709+
Guest UI behavior:
710+
* Guest UI SHOULD check `availableDisplayModes` in host context before requesting a mode change.
711+
* Guest UI MUST handle the response mode differing from the requested mode.
712+
680713
#### Notifications (Host → UI)
681714

682715
`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

@@ -844,6 +846,44 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
844846
);
845847
}
846848

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

src/generated/schema.json

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2845,6 +2845,67 @@
28452845
},
28462846
"additionalProperties": {}
28472847
},
2848+
"McpUiRequestDisplayModeRequest": {
2849+
"$schema": "https://json-schema.org/draft/2020-12/schema",
2850+
"type": "object",
2851+
"properties": {
2852+
"method": {
2853+
"type": "string",
2854+
"const": "ui/request-display-mode"
2855+
},
2856+
"params": {
2857+
"type": "object",
2858+
"properties": {
2859+
"mode": {
2860+
"description": "The display mode being requested.",
2861+
"anyOf": [
2862+
{
2863+
"type": "string",
2864+
"const": "inline"
2865+
},
2866+
{
2867+
"type": "string",
2868+
"const": "fullscreen"
2869+
},
2870+
{
2871+
"type": "string",
2872+
"const": "pip"
2873+
}
2874+
]
2875+
}
2876+
},
2877+
"required": ["mode"],
2878+
"additionalProperties": false
2879+
}
2880+
},
2881+
"required": ["method", "params"],
2882+
"additionalProperties": false
2883+
},
2884+
"McpUiRequestDisplayModeResult": {
2885+
"$schema": "https://json-schema.org/draft/2020-12/schema",
2886+
"type": "object",
2887+
"properties": {
2888+
"mode": {
2889+
"description": "The display mode that was actually set. May differ from requested if not supported.",
2890+
"anyOf": [
2891+
{
2892+
"type": "string",
2893+
"const": "inline"
2894+
},
2895+
{
2896+
"type": "string",
2897+
"const": "fullscreen"
2898+
},
2899+
{
2900+
"type": "string",
2901+
"const": "pip"
2902+
}
2903+
]
2904+
}
2905+
},
2906+
"required": ["mode"],
2907+
"additionalProperties": {}
2908+
},
28482909
"McpUiResourceCsp": {
28492910
"$schema": "https://json-schema.org/draft/2020-12/schema",
28502911
"type": "object",

src/generated/schema.test.ts

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

98+
export type McpUiRequestDisplayModeRequestSchemaInferredType = z.infer<
99+
typeof generated.McpUiRequestDisplayModeRequestSchema
100+
>;
101+
102+
export type McpUiRequestDisplayModeResultSchemaInferredType = z.infer<
103+
typeof generated.McpUiRequestDisplayModeResultSchema
104+
>;
105+
98106
export type McpUiMessageRequestSchemaInferredType = z.infer<
99107
typeof generated.McpUiMessageRequestSchema
100108
>;
@@ -217,6 +225,18 @@ expectType<spec.McpUiResourceCsp>({} as McpUiResourceCspSchemaInferredType);
217225
expectType<McpUiResourceCspSchemaInferredType>({} as spec.McpUiResourceCsp);
218226
expectType<spec.McpUiResourceMeta>({} as McpUiResourceMetaSchemaInferredType);
219227
expectType<McpUiResourceMetaSchemaInferredType>({} as spec.McpUiResourceMeta);
228+
expectType<spec.McpUiRequestDisplayModeRequest>(
229+
{} as McpUiRequestDisplayModeRequestSchemaInferredType,
230+
);
231+
expectType<McpUiRequestDisplayModeRequestSchemaInferredType>(
232+
{} as spec.McpUiRequestDisplayModeRequest,
233+
);
234+
expectType<spec.McpUiRequestDisplayModeResult>(
235+
{} as McpUiRequestDisplayModeResultSchemaInferredType,
236+
);
237+
expectType<McpUiRequestDisplayModeResultSchemaInferredType>(
238+
{} as spec.McpUiRequestDisplayModeResult,
239+
);
220240
expectType<spec.McpUiMessageRequest>(
221241
{} as McpUiMessageRequestSchemaInferredType,
222242
);

src/generated/schema.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,31 @@ export const McpUiResourceMetaSchema = z.object({
424424
),
425425
});
426426

427+
/**
428+
* @description Request to change the display mode of the UI.
429+
* The host will respond with the actual display mode that was set,
430+
* which may differ from the requested mode if not supported.
431+
* @see {@link app.App.requestDisplayMode} for the method that sends this request
432+
*/
433+
export const McpUiRequestDisplayModeRequestSchema = z.object({
434+
method: z.literal("ui/request-display-mode"),
435+
params: z.object({
436+
/** @description The display mode being requested. */
437+
mode: McpUiDisplayModeSchema.describe("The display mode being requested."),
438+
}),
439+
});
440+
441+
/**
442+
* @description Result from requesting a display mode change.
443+
* @see {@link McpUiRequestDisplayModeRequest}
444+
*/
445+
export const McpUiRequestDisplayModeResultSchema = z.looseObject({
446+
/** @description The display mode that was actually set. May differ from requested if not supported. */
447+
mode: McpUiDisplayModeSchema.describe(
448+
"The display mode that was actually set. May differ from requested if not supported.",
449+
),
450+
});
451+
427452
/**
428453
* @description Request to send a message to the host's chat interface.
429454
* @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
@@ -485,3 +485,31 @@ export interface McpUiResourceMeta {
485485
/** @description Visual boundary preference - true if UI prefers a visible border. */
486486
prefersBorder?: boolean;
487487
}
488+
489+
/**
490+
* @description Request to change the display mode of the UI.
491+
* The host will respond with the actual display mode that was set,
492+
* which may differ from the requested mode if not supported.
493+
* @see {@link app.App.requestDisplayMode} for the method that sends this request
494+
*/
495+
export interface McpUiRequestDisplayModeRequest {
496+
method: "ui/request-display-mode";
497+
params: {
498+
/** @description The display mode being requested. */
499+
mode: McpUiDisplayMode;
500+
};
501+
}
502+
503+
/**
504+
* @description Result from requesting a display mode change.
505+
* @see {@link McpUiRequestDisplayModeRequest}
506+
*/
507+
export interface McpUiRequestDisplayModeResult {
508+
/** @description The display mode that was actually set. May differ from requested if not supported. */
509+
mode: McpUiDisplayMode;
510+
/**
511+
* Index signature required for MCP SDK `Protocol` class compatibility.
512+
* Note: The schema intentionally omits this to enforce strict validation.
513+
*/
514+
[key: string]: unknown;
515+
}

src/types.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ export {
3939
type McpUiInitializedNotification,
4040
type McpUiResourceCsp,
4141
type McpUiResourceMeta,
42+
type McpUiRequestDisplayModeRequest,
43+
type McpUiRequestDisplayModeResult,
4244
} from "./spec.types.js";
4345

4446
// Import types needed for protocol type unions (not re-exported, just used internally)
@@ -47,6 +49,7 @@ import type {
4749
McpUiOpenLinkRequest,
4850
McpUiMessageRequest,
4951
McpUiResourceTeardownRequest,
52+
McpUiRequestDisplayModeRequest,
5053
McpUiHostContextChangedNotification,
5154
McpUiToolInputNotification,
5255
McpUiToolInputPartialNotification,
@@ -60,6 +63,7 @@ import type {
6063
McpUiOpenLinkResult,
6164
McpUiMessageResult,
6265
McpUiResourceTeardownResult,
66+
McpUiRequestDisplayModeResult,
6367
} from "./spec.types.js";
6468

6569
// Re-export all schemas from generated/schema.ts (already PascalCase)
@@ -89,6 +93,8 @@ export {
8993
McpUiInitializedNotificationSchema,
9094
McpUiResourceCspSchema,
9195
McpUiResourceMetaSchema,
96+
McpUiRequestDisplayModeRequestSchema,
97+
McpUiRequestDisplayModeResultSchema,
9298
} from "./generated/schema.js";
9399

94100
// Re-export SDK types used in protocol type unions
@@ -117,7 +123,7 @@ import {
117123
* All request types in the MCP Apps protocol.
118124
*
119125
* Includes:
120-
* - MCP UI requests (initialize, open-link, message, resource-teardown)
126+
* - MCP UI requests (initialize, open-link, message, resource-teardown, request-display-mode)
121127
* - MCP server requests forwarded from the app (tools/call, resources/*, prompts/list)
122128
* - Protocol requests (ping)
123129
*/
@@ -126,6 +132,7 @@ export type AppRequest =
126132
| McpUiOpenLinkRequest
127133
| McpUiMessageRequest
128134
| McpUiResourceTeardownRequest
135+
| McpUiRequestDisplayModeRequest
129136
| CallToolRequest
130137
| ListToolsRequest
131138
| ListResourcesRequest
@@ -172,6 +179,7 @@ export type AppResult =
172179
| McpUiOpenLinkResult
173180
| McpUiMessageResult
174181
| McpUiResourceTeardownResult
182+
| McpUiRequestDisplayModeResult
175183
| CallToolResult
176184
| ListToolsResult
177185
| ListResourcesResult

0 commit comments

Comments
 (0)