From 225d9cffd63cba765bfc2afed8039271bd593dec Mon Sep 17 00:00:00 2001 From: Olivier Chafik Date: Fri, 16 Jan 2026 23:54:43 +0000 Subject: [PATCH] feat(ext-apps): add widgetSessionId to spec - Add McpUiToolResultMeta interface with widgetSessionId field - Document Widget Session Consolidation in apps.mdx spec - When multiple tool results share the same widgetSessionId, only the latest widget is displayed - Previous widgets are gracefully torn down via ui/resource-teardown --- specification/draft/apps.mdx | 38 ++++++++++++++++++++++++++++++++++++ src/generated/schema.json | 11 +++++++++++ src/generated/schema.test.ts | 10 ++++++++++ src/generated/schema.ts | 21 ++++++++++++++++++++ src/spec.types.ts | 16 +++++++++++++++ 5 files changed, 96 insertions(+) diff --git a/specification/draft/apps.mdx b/specification/draft/apps.mdx index 59eaa0206..ee225134f 100644 --- a/specification/draft/apps.mdx +++ b/specification/draft/apps.mdx @@ -1410,6 +1410,44 @@ This pattern enables interactive, self-updating widgets. Note: Tools with `visibility: ["app"]` are hidden from the agent but remain callable by apps via `tools/call`. This enables UI-only interactions (refresh buttons, form submissions) without exposing implementation details to the model. See the Visibility section under Resource Discovery for details. +### Widget Session Consolidation + +When the same logical widget is updated multiple times during a conversation, +hosts can consolidate these into a single visible widget using `widgetSessionId`: + +```typescript +{ + content: [{ type: "text", text: "Cart updated" }], + structuredContent: { items: [...] }, + _meta: { + ui: { + widgetSessionId: "user-cart-abc123" + } + } +} +``` + +**Host Behavior:** + +1. When a tool result with `_meta.ui.widgetSessionId` is rendered +2. Find any earlier widgets in the conversation with the same `widgetSessionId` +3. Call `ui/resource-teardown` on superseded widgets (allows graceful cleanup) +4. Hide superseded widgets from the UI (model context unaffected) +5. Only the latest widget remains visible + +**Use Cases:** + +- Shopping cart that updates as items are added/removed +- Live data dashboards that refresh with new data +- Multi-step forms that show only the current state + +**Implementation Notes:** + +- Scope is conversation-wide (widgets persist across turns) +- Model context remains unaffected (all tool results still visible to model) +- Teardown allows apps to save state before being hidden +- Only applies to MCP App widgets (tools with `_meta.ui.resourceUri`) + ### Client\<\>Server Capability Negotiation Clients and servers negotiate MCP Apps support through the standard MCP extensions capability mechanism (defined in SEP-1724). diff --git a/src/generated/schema.json b/src/generated/schema.json index e17767d99..9533f79c3 100644 --- a/src/generated/schema.json +++ b/src/generated/schema.json @@ -4960,6 +4960,17 @@ }, "additionalProperties": false }, + "McpUiToolResultMeta": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "widgetSessionId": { + "description": "Unique session identifier for widget consolidation.\nWhen multiple tool results share the same widgetSessionId,\nonly the latest widget is displayed. Previous widgets are\ngracefully torn down via ui/resource-teardown and hidden.", + "type": "string" + } + }, + "additionalProperties": false + }, "McpUiToolResultNotification": { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", diff --git a/src/generated/schema.test.ts b/src/generated/schema.test.ts index 727c28d29..20629597a 100644 --- a/src/generated/schema.test.ts +++ b/src/generated/schema.test.ts @@ -119,6 +119,10 @@ export type McpUiToolMetaSchemaInferredType = z.infer< typeof generated.McpUiToolMetaSchema >; +export type McpUiToolResultMetaSchemaInferredType = z.infer< + typeof generated.McpUiToolResultMetaSchema +>; + export type McpUiMessageRequestSchemaInferredType = z.infer< typeof generated.McpUiMessageRequestSchema >; @@ -277,6 +281,12 @@ expectType( ); expectType({} as McpUiToolMetaSchemaInferredType); expectType({} as spec.McpUiToolMeta); +expectType( + {} as McpUiToolResultMetaSchemaInferredType, +); +expectType( + {} as spec.McpUiToolResultMeta, +); expectType( {} as McpUiMessageRequestSchemaInferredType, ); diff --git a/src/generated/schema.ts b/src/generated/schema.ts index 32277d23d..0d76ddd76 100644 --- a/src/generated/schema.ts +++ b/src/generated/schema.ts @@ -565,6 +565,27 @@ export const McpUiToolMetaSchema = z.object({ ), }); +/** + * @description UI-related metadata for tool results. + * Used in CallToolResult._meta.ui to provide rendering hints to the host. + */ +export const McpUiToolResultMetaSchema = z.object({ + /** + * @description Unique session identifier for widget consolidation. + * When multiple tool results share the same widgetSessionId, + * only the latest widget is displayed. Previous widgets are + * gracefully torn down via ui/resource-teardown and hidden. + * + * @example "shopping-cart-session-123" + */ + widgetSessionId: z + .string() + .optional() + .describe( + "Unique session identifier for widget consolidation.\nWhen multiple tool results share the same widgetSessionId,\nonly the latest widget is displayed. Previous widgets are\ngracefully torn down via ui/resource-teardown and hidden.", + ), +}); + /** * @description Request to send a message to the host's chat interface. * @see {@link app!App.sendMessage} for the method that sends this request diff --git a/src/spec.types.ts b/src/spec.types.ts index cb6af1f78..5fcbd1f48 100644 --- a/src/spec.types.ts +++ b/src/spec.types.ts @@ -630,6 +630,22 @@ export interface McpUiToolMeta { visibility?: McpUiToolVisibility[]; } +/** + * @description UI-related metadata for tool results. + * Used in CallToolResult._meta.ui to provide rendering hints to the host. + */ +export interface McpUiToolResultMeta { + /** + * @description Unique session identifier for widget consolidation. + * When multiple tool results share the same widgetSessionId, + * only the latest widget is displayed. Previous widgets are + * gracefully torn down via ui/resource-teardown and hidden. + * + * @example "shopping-cart-session-123" + */ + widgetSessionId?: string; +} + /** * Method string constants for MCP Apps protocol messages. *