diff --git a/specification/draft/apps.mdx b/specification/draft/apps.mdx index e7c343b8..97e77dca 100644 --- a/specification/draft/apps.mdx +++ b/specification/draft/apps.mdx @@ -348,6 +348,12 @@ interface McpUiToolMeta { * - "app": Tool callable by the app from this server only */ visibility?: Array<"model" | "app">; + /** + * When the host should render the View in the conversation. Default: "inline" + * - "inline": Render the View as soon as the tool returns + * - "end-of-turn": Defer rendering until the agent's turn is complete + */ + renderTiming?: "inline" | "end-of-turn"; } interface Tool { @@ -419,6 +425,32 @@ Example (app-only tool, hidden from model): - **tools/call behavior:** Host MUST reject `tools/call` requests from apps for tools that don't include `"app"` in visibility - Cross-server tool calls are always blocked for app-only tools +#### Render Timing: + +Some tools produce Views that should only be shown after the agent has finished its turn — for example, an "Apply to Site" action where the user should not interact with the View while the agent is still making additional tool calls. The `renderTiming` field controls this: + +- `renderTiming` defaults to `"inline"` if omitted +- `"inline"`: Host SHOULD render the View as soon as the tool returns its result +- `"end-of-turn"`: Host SHOULD defer rendering the View until the agent's turn is complete (no more tool calls or model output expected) +- Host MAY ignore `renderTiming` and render immediately if it does not support deferred rendering +- This field is orthogonal to `displayMode` (inline/fullscreen/pip), which controls the visual layout of the View + +Example (tool with deferred rendering): + +```json +{ + "name": "apply_changes", + "description": "Apply code changes to the user's site", + "inputSchema": { "type": "object" }, + "_meta": { + "ui": { + "resourceUri": "ui://editor/apply-to-site", + "renderTiming": "end-of-turn" + } + } +} +``` + #### Benefits: - **Performance:** Host can preload templates before tool execution @@ -1754,6 +1786,24 @@ This proposal synthesizes feedback from the UI CWG and MCP-UI community, host im - **Boolean `private` flag:** Simpler but less flexible; doesn't express model-only tools. - **Flat `ui/visibility` key:** Rejected in favor of nested structure for consistency with future `_meta.ui` fields. +#### 6. Render Timing via Tool Metadata + +**Decision:** Use `_meta.ui.renderTiming` to let servers declare when a View should appear in the conversation. + +**Rationale:** + +- The server knows best whether its View requires user interaction during or after the agent's turn +- Orthogonal to the visual `displayMode` (inline/fullscreen/pip) — timing and layout are independent concerns +- Optional field with `"inline"` default preserves backward compatibility +- Addresses a real production need: tools like "Apply to Site" should not show interactive UI while the agent is still making additional tool calls +- Simple two-value enum (`"inline"` | `"end-of-turn"`) covers the observed use cases without over-engineering + +**Alternatives considered:** + +- **Host-side only:** Let hosts decide timing without server input. Rejected because the server has the domain knowledge about whether its View needs deferred rendering. +- **Boolean `deferRendering` flag:** Simpler but less extensible if future timing modes are needed (e.g., `"on-user-action"`). +- **Reuse `displayMode`:** Rejected because `displayMode` controls visual layout (inline/fullscreen/pip), not temporal presentation. Overloading it would create confusion. + ### Backward Compatibility The proposal builds on the existing core protocol. There are no incompatibilities. diff --git a/src/generated/schema.json b/src/generated/schema.json index 522f1592..23d60916 100644 --- a/src/generated/schema.json +++ b/src/generated/schema.json @@ -4013,6 +4013,20 @@ }, "additionalProperties": {} }, + "McpUiRenderTiming": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { + "type": "string", + "const": "inline" + }, + { + "type": "string", + "const": "end-of-turn" + } + ], + "description": "When the host should render the View relative to the agent's turn." + }, "McpUiRequestDisplayModeRequest": { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", @@ -5251,6 +5265,19 @@ ], "description": "Tool visibility scope - who can access the tool." } + }, + "renderTiming": { + "description": "When the host should render the View in the conversation. Default: \"inline\"\n- \"inline\": Render the View as soon as the tool returns\n- \"end-of-turn\": Defer rendering until the agent's turn is complete (no more tool calls)", + "anyOf": [ + { + "type": "string", + "const": "inline" + }, + { + "type": "string", + "const": "end-of-turn" + } + ] } }, "additionalProperties": false diff --git a/src/generated/schema.test.ts b/src/generated/schema.test.ts index cebad70b..86d5c257 100644 --- a/src/generated/schema.test.ts +++ b/src/generated/schema.test.ts @@ -119,6 +119,10 @@ export type McpUiToolVisibilitySchemaInferredType = z.infer< typeof generated.McpUiToolVisibilitySchema >; +export type McpUiRenderTimingSchemaInferredType = z.infer< + typeof generated.McpUiRenderTimingSchema +>; + export type McpUiToolMetaSchemaInferredType = z.infer< typeof generated.McpUiToolMetaSchema >; @@ -293,6 +297,8 @@ expectType( expectType( {} as spec.McpUiToolVisibility, ); +expectType({} as McpUiRenderTimingSchemaInferredType); +expectType({} as spec.McpUiRenderTiming); expectType({} as McpUiToolMetaSchemaInferredType); expectType({} as spec.McpUiToolMeta); expectType( diff --git a/src/generated/schema.ts b/src/generated/schema.ts index 8eb12b5a..6bcc3336 100644 --- a/src/generated/schema.ts +++ b/src/generated/schema.ts @@ -674,6 +674,15 @@ export const McpUiToolVisibilitySchema = z .union([z.literal("model"), z.literal("app")]) .describe("Tool visibility scope - who can access the tool."); +/** + * @description When the host should render the View relative to the agent's turn. + */ +export const McpUiRenderTimingSchema = z + .union([z.literal("inline"), z.literal("end-of-turn")]) + .describe( + "When the host should render the View relative to the agent's turn.", + ); + /** * @description UI-related metadata for tools. */ @@ -699,6 +708,14 @@ export const McpUiToolMetaSchema = z.object({ .describe( 'Who can access this tool. Default: ["model", "app"]\n- "model": Tool visible to and callable by the agent\n- "app": Tool callable by the app from this server only', ), + /** + * @description When the host should render the View in the conversation. Default: "inline" + * - "inline": Render the View as soon as the tool returns + * - "end-of-turn": Defer rendering until the agent's turn is complete (no more tool calls) + */ + renderTiming: McpUiRenderTimingSchema.optional().describe( + 'When the host should render the View in the conversation. Default: "inline"\n- "inline": Render the View as soon as the tool returns\n- "end-of-turn": Defer rendering until the agent\'s turn is complete (no more tool calls)', + ), }); /** diff --git a/src/spec.types.ts b/src/spec.types.ts index 8e0e2eb0..2f8fb4d7 100644 --- a/src/spec.types.ts +++ b/src/spec.types.ts @@ -742,6 +742,11 @@ export interface McpUiRequestDisplayModeResult { */ export type McpUiToolVisibility = "model" | "app"; +/** + * @description When the host should render the View relative to the agent's turn. + */ +export type McpUiRenderTiming = "inline" | "end-of-turn"; + /** * @description UI-related metadata for tools. */ @@ -762,6 +767,12 @@ export interface McpUiToolMeta { * - "app": Tool callable by the app from this server only */ visibility?: McpUiToolVisibility[]; + /** + * @description When the host should render the View in the conversation. Default: "inline" + * - "inline": Render the View as soon as the tool returns + * - "end-of-turn": Defer rendering until the agent's turn is complete (no more tool calls) + */ + renderTiming?: McpUiRenderTiming; } /** diff --git a/src/types.ts b/src/types.ts index 739da6fa..0e36d85c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -63,6 +63,7 @@ export { type McpUiRequestDisplayModeRequest, type McpUiRequestDisplayModeResult, type McpUiToolVisibility, + type McpUiRenderTiming, type McpUiToolMeta, type McpUiClientCapabilities, } from "./spec.types.js"; @@ -129,6 +130,7 @@ export { McpUiRequestDisplayModeRequestSchema, McpUiRequestDisplayModeResultSchema, McpUiToolVisibilitySchema, + McpUiRenderTimingSchema, McpUiToolMetaSchema, } from "./generated/schema.js";