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:
commit
c516cf0894
8 changed files with 272 additions and 1 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
40
src/app.ts
40
src/app.ts
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
10
src/types.ts
10
src/types.ts
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue