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 SHOULD add the message to the conversation context, preserving the specified role.
|
||||||
* Host MAY request user consent.
|
* 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)
|
#### 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.
|
`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,
|
McpUiSandboxProxyReadyNotification,
|
||||||
McpUiSandboxProxyReadyNotificationSchema,
|
McpUiSandboxProxyReadyNotificationSchema,
|
||||||
McpUiSizeChangedNotificationSchema,
|
McpUiSizeChangedNotificationSchema,
|
||||||
|
McpUiRequestDisplayModeRequest,
|
||||||
|
McpUiRequestDisplayModeRequestSchema,
|
||||||
|
McpUiRequestDisplayModeResult,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
export * from "./types";
|
export * from "./types";
|
||||||
export { RESOURCE_URI_META_KEY, RESOURCE_MIME_TYPE } from "./app";
|
export { RESOURCE_URI_META_KEY, RESOURCE_MIME_TYPE } from "./app";
|
||||||
|
|
@ -222,6 +225,13 @@ export class AppBridge extends Protocol<
|
||||||
this.onping?.(request.params, extra);
|
this.onping?.(request.params, extra);
|
||||||
return {};
|
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.
|
* Register a handler for logging messages from the Guest UI.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
40
src/app.ts
40
src/app.ts
|
|
@ -42,6 +42,8 @@ import {
|
||||||
McpUiToolInputPartialNotificationSchema,
|
McpUiToolInputPartialNotificationSchema,
|
||||||
McpUiToolResultNotification,
|
McpUiToolResultNotification,
|
||||||
McpUiToolResultNotificationSchema,
|
McpUiToolResultNotificationSchema,
|
||||||
|
McpUiRequestDisplayModeRequest,
|
||||||
|
McpUiRequestDisplayModeResultSchema,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
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.
|
* Notify the host of UI size changes.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -2845,6 +2845,67 @@
|
||||||
},
|
},
|
||||||
"additionalProperties": {}
|
"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": {
|
"McpUiResourceCsp": {
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,14 @@ export type McpUiResourceMetaSchemaInferredType = z.infer<
|
||||||
typeof generated.McpUiResourceMetaSchema
|
typeof generated.McpUiResourceMetaSchema
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export type McpUiRequestDisplayModeRequestSchemaInferredType = z.infer<
|
||||||
|
typeof generated.McpUiRequestDisplayModeRequestSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type McpUiRequestDisplayModeResultSchemaInferredType = z.infer<
|
||||||
|
typeof generated.McpUiRequestDisplayModeResultSchema
|
||||||
|
>;
|
||||||
|
|
||||||
export type McpUiMessageRequestSchemaInferredType = z.infer<
|
export type McpUiMessageRequestSchemaInferredType = z.infer<
|
||||||
typeof generated.McpUiMessageRequestSchema
|
typeof generated.McpUiMessageRequestSchema
|
||||||
>;
|
>;
|
||||||
|
|
@ -217,6 +225,18 @@ expectType<spec.McpUiResourceCsp>({} as McpUiResourceCspSchemaInferredType);
|
||||||
expectType<McpUiResourceCspSchemaInferredType>({} as spec.McpUiResourceCsp);
|
expectType<McpUiResourceCspSchemaInferredType>({} as spec.McpUiResourceCsp);
|
||||||
expectType<spec.McpUiResourceMeta>({} as McpUiResourceMetaSchemaInferredType);
|
expectType<spec.McpUiResourceMeta>({} as McpUiResourceMetaSchemaInferredType);
|
||||||
expectType<McpUiResourceMetaSchemaInferredType>({} as spec.McpUiResourceMeta);
|
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>(
|
expectType<spec.McpUiMessageRequest>(
|
||||||
{} as McpUiMessageRequestSchemaInferredType,
|
{} 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.
|
* @description Request to send a message to the host's chat interface.
|
||||||
* @see {@link app.App.sendMessage} for the method that sends this request
|
* @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. */
|
/** @description Visual boundary preference - true if UI prefers a visible border. */
|
||||||
prefersBorder?: boolean;
|
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 McpUiInitializedNotification,
|
||||||
type McpUiResourceCsp,
|
type McpUiResourceCsp,
|
||||||
type McpUiResourceMeta,
|
type McpUiResourceMeta,
|
||||||
|
type McpUiRequestDisplayModeRequest,
|
||||||
|
type McpUiRequestDisplayModeResult,
|
||||||
} from "./spec.types.js";
|
} from "./spec.types.js";
|
||||||
|
|
||||||
// Import types needed for protocol type unions (not re-exported, just used internally)
|
// Import types needed for protocol type unions (not re-exported, just used internally)
|
||||||
|
|
@ -47,6 +49,7 @@ import type {
|
||||||
McpUiOpenLinkRequest,
|
McpUiOpenLinkRequest,
|
||||||
McpUiMessageRequest,
|
McpUiMessageRequest,
|
||||||
McpUiResourceTeardownRequest,
|
McpUiResourceTeardownRequest,
|
||||||
|
McpUiRequestDisplayModeRequest,
|
||||||
McpUiHostContextChangedNotification,
|
McpUiHostContextChangedNotification,
|
||||||
McpUiToolInputNotification,
|
McpUiToolInputNotification,
|
||||||
McpUiToolInputPartialNotification,
|
McpUiToolInputPartialNotification,
|
||||||
|
|
@ -60,6 +63,7 @@ import type {
|
||||||
McpUiOpenLinkResult,
|
McpUiOpenLinkResult,
|
||||||
McpUiMessageResult,
|
McpUiMessageResult,
|
||||||
McpUiResourceTeardownResult,
|
McpUiResourceTeardownResult,
|
||||||
|
McpUiRequestDisplayModeResult,
|
||||||
} from "./spec.types.js";
|
} from "./spec.types.js";
|
||||||
|
|
||||||
// Re-export all schemas from generated/schema.ts (already PascalCase)
|
// Re-export all schemas from generated/schema.ts (already PascalCase)
|
||||||
|
|
@ -89,6 +93,8 @@ export {
|
||||||
McpUiInitializedNotificationSchema,
|
McpUiInitializedNotificationSchema,
|
||||||
McpUiResourceCspSchema,
|
McpUiResourceCspSchema,
|
||||||
McpUiResourceMetaSchema,
|
McpUiResourceMetaSchema,
|
||||||
|
McpUiRequestDisplayModeRequestSchema,
|
||||||
|
McpUiRequestDisplayModeResultSchema,
|
||||||
} from "./generated/schema.js";
|
} from "./generated/schema.js";
|
||||||
|
|
||||||
// Re-export SDK types used in protocol type unions
|
// Re-export SDK types used in protocol type unions
|
||||||
|
|
@ -117,7 +123,7 @@ import {
|
||||||
* All request types in the MCP Apps protocol.
|
* All request types in the MCP Apps protocol.
|
||||||
*
|
*
|
||||||
* Includes:
|
* 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)
|
* - MCP server requests forwarded from the app (tools/call, resources/*, prompts/list)
|
||||||
* - Protocol requests (ping)
|
* - Protocol requests (ping)
|
||||||
*/
|
*/
|
||||||
|
|
@ -126,6 +132,7 @@ export type AppRequest =
|
||||||
| McpUiOpenLinkRequest
|
| McpUiOpenLinkRequest
|
||||||
| McpUiMessageRequest
|
| McpUiMessageRequest
|
||||||
| McpUiResourceTeardownRequest
|
| McpUiResourceTeardownRequest
|
||||||
|
| McpUiRequestDisplayModeRequest
|
||||||
| CallToolRequest
|
| CallToolRequest
|
||||||
| ListToolsRequest
|
| ListToolsRequest
|
||||||
| ListResourcesRequest
|
| ListResourcesRequest
|
||||||
|
|
@ -172,6 +179,7 @@ export type AppResult =
|
||||||
| McpUiOpenLinkResult
|
| McpUiOpenLinkResult
|
||||||
| McpUiMessageResult
|
| McpUiMessageResult
|
||||||
| McpUiResourceTeardownResult
|
| McpUiResourceTeardownResult
|
||||||
|
| McpUiRequestDisplayModeResult
|
||||||
| CallToolResult
|
| CallToolResult
|
||||||
| ListToolsResult
|
| ListToolsResult
|
||||||
| ListResourcesResult
|
| ListResourcesResult
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue