diff --git a/specification/draft/apps.mdx b/specification/draft/apps.mdx index 6e33f813..2b98540c 100644 --- a/specification/draft/apps.mdx +++ b/specification/draft/apps.mdx @@ -800,6 +800,46 @@ 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. +`ui/update-model-context` - Update the model context + +```typescript +// Request +{ + jsonrpc: "2.0", + id: 3, + method: "ui/update-model-context", + params: { + role: "user", + content: ContentBlock[] + } +} + +// Success Response +{ + jsonrpc: "2.0", + id: 3, + result: {} // Empty result on success +} + +// Error Response (if denied or failed) +{ + jsonrpc: "2.0", + id: 3, + error: { + code: -32000, // Implementation-defined error + message: "Context update denied" | "Invalid content format" + } +} +``` + +Guest UI MAY send this request to update the Host's model context. This context will be used in future turns. Each request overwrites the previous context sent by the Guest UI. +This event serves a different use case from `notifications/message` (logging) and `ui/message` (which also trigger follow-ups). + +Host behavior: +- SHOULD store the context snapshot in the conversation context +- SHOULD overwrite the previous model context with the new update +- MAY display context updates to the user + #### 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. @@ -1031,10 +1071,15 @@ sequenceDiagram H-->>UI: ui/notifications/tool-result else Message UI ->> H: ui/message + H -->> UI: ui/message response H -->> H: Process message and follow up - else Notify + else Context update + UI ->> H: ui/update-model-context + H ->> H: Store model context (overwrite existing) + H -->> UI: ui/update-model-context response + else Log UI ->> H: notifications/message - H ->> H: Process notification and store in context + H ->> H: Record log for debugging/telemetry else Resource read UI ->> H: resources/read H ->> S: resources/read diff --git a/src/app-bridge.test.ts b/src/app-bridge.test.ts index 969030fb..510e3142 100644 --- a/src/app-bridge.test.ts +++ b/src/app-bridge.test.ts @@ -439,6 +439,68 @@ describe("App <-> AppBridge integration", () => { logger: "TestApp", }); }); + + it("app.sendUpdateModelContext triggers bridge.onupdatemodelcontext and returns result", async () => { + const receivedContexts: unknown[] = []; + bridge.onupdatemodelcontext = async (params) => { + receivedContexts.push(params); + return {}; + }; + + await app.connect(appTransport); + const result = await app.sendUpdateModelContext({ + role: "user", + content: [{ type: "text", text: "User selected 3 items" }], + }); + + expect(receivedContexts).toHaveLength(1); + expect(receivedContexts[0]).toMatchObject({ + role: "user", + content: [{ type: "text", text: "User selected 3 items" }], + }); + expect(result).toEqual({}); + }); + + it("app.sendUpdateModelContext works with multiple content blocks", async () => { + const receivedContexts: unknown[] = []; + bridge.onupdatemodelcontext = async (params) => { + receivedContexts.push(params); + return {}; + }; + + await app.connect(appTransport); + const result = await app.sendUpdateModelContext({ + role: "user", + content: [ + { type: "text", text: "Filter applied" }, + { type: "text", text: "Category: electronics" }, + ], + }); + + expect(receivedContexts).toHaveLength(1); + expect(receivedContexts[0]).toMatchObject({ + role: "user", + content: [ + { type: "text", text: "Filter applied" }, + { type: "text", text: "Category: electronics" }, + ], + }); + expect(result).toEqual({}); + }); + + it("app.sendUpdateModelContext returns error result when handler indicates error", async () => { + bridge.onupdatemodelcontext = async () => { + return { isError: true }; + }; + + await app.connect(appTransport); + const result = await app.sendUpdateModelContext({ + role: "user", + content: [{ type: "text", text: "Test" }], + }); + + expect(result.isError).toBe(true); + }); }); describe("App -> Host requests", () => { diff --git a/src/app-bridge.ts b/src/app-bridge.ts index 2c63eaa9..e9f3603f 100644 --- a/src/app-bridge.ts +++ b/src/app-bridge.ts @@ -51,6 +51,9 @@ import { type McpUiToolResultNotification, LATEST_PROTOCOL_VERSION, McpUiAppCapabilities, + McpUiUpdateModelContextRequest, + McpUiUpdateModelContextRequestSchema, + McpUiUpdateModelContextResult, McpUiHostCapabilities, McpUiHostContext, McpUiHostContextChangedNotification, @@ -66,7 +69,6 @@ import { McpUiOpenLinkRequestSchema, McpUiOpenLinkResult, McpUiResourceTeardownRequest, - McpUiResourceTeardownResult, McpUiResourceTeardownResultSchema, McpUiSandboxProxyReadyNotification, McpUiSandboxProxyReadyNotificationSchema, @@ -633,6 +635,50 @@ export class AppBridge extends Protocol< ); } + /** + * Register a handler for model context updates from the Guest UI. + * + * The Guest UI sends `ui/update-model-context` requests to update the Host's + * model context. Each request overwrites the previous context stored by the Guest UI. + * Unlike logging messages, context updates are intended to be available to + * the model in future turns. Unlike messages, context updates do not trigger follow-ups + * + * @example + * ```typescript + * bridge.onupdatemodelcontext = async ({ role, content }, extra) => { + * try { + * // Update the model context with the new snapshot + * modelContext = { + * type: "app_context", + * role, + * content, + * timestamp: Date.now() + * }; + * return {}; + * } catch (err) { + * // Handle error and signal failure to the app + * return { isError: true }; + * } + * }; + * ``` + * + * @see {@link McpUiUpdateModelContextRequest} for the request type + * @see {@link McpUiUpdateModelContextResult} for the result type + */ + set onupdatemodelcontext( + callback: ( + params: McpUiUpdateModelContextRequest["params"], + extra: RequestHandlerExtra, + ) => Promise, + ) { + this.setRequestHandler( + McpUiUpdateModelContextRequestSchema, + async (request, extra) => { + return callback(request.params, extra); + }, + ); + } + /** * Register a handler for tool call requests from the Guest UI. * diff --git a/src/app.ts b/src/app.ts index 31885302..8281ea8a 100644 --- a/src/app.ts +++ b/src/app.ts @@ -20,6 +20,8 @@ import { PostMessageTransport } from "./message-transport"; import { LATEST_PROTOCOL_VERSION, McpUiAppCapabilities, + McpUiUpdateModelContextRequest, + McpUiUpdateModelContextResultSchema, McpUiHostCapabilities, McpUiHostContext, McpUiHostContextChangedNotification, @@ -809,6 +811,40 @@ export class App extends Protocol { }); } + /** + * Send context updates to the host to be included in the agent's context. + * + * Unlike `sendLog`, which is for debugging/telemetry, context updates + * are inteded to be available to the model in future reasoning, + * without requiring a follow-up action (like `sendMessage`). + * + * @param params - Context role and content (same structure as ui/message) + * @param options - Request options (timeout, etc.) + * + * @example Update model context with current app state + * ```typescript + * await app.sendUpdateModelContext({ + * role: "user", + * content: [{ type: "text", text: "User selected 3 items totaling $150.00" }] + * }); + * ``` + * + * @returns Promise that resolves when the context update is acknowledged + */ + sendUpdateModelContext( + params: McpUiUpdateModelContextRequest["params"], + options?: RequestOptions, + ) { + return this.request( + { + method: "ui/update-model-context", + params, + }, + McpUiUpdateModelContextResultSchema, + options, + ); + } + /** * Request the host to open an external URL in the default browser. * diff --git a/src/generated/schema.json b/src/generated/schema.json index e8359865..85e65e43 100644 --- a/src/generated/schema.json +++ b/src/generated/schema.json @@ -89,6 +89,12 @@ "type": "object", "properties": {}, "additionalProperties": false + }, + "updateModelContext": { + "description": "Host accepts context updates to be included in the model's context for future turns.", + "type": "object", + "properties": {}, + "additionalProperties": false } }, "additionalProperties": false @@ -1882,6 +1888,12 @@ "type": "object", "properties": {}, "additionalProperties": false + }, + "updateModelContext": { + "description": "Host accepts context updates to be included in the model's context for future turns.", + "type": "object", + "properties": {}, + "additionalProperties": false } }, "additionalProperties": false @@ -4275,6 +4287,354 @@ "const": "app" } ] + }, + "McpUiUpdateModelContextRequest": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "method": { + "type": "string", + "const": "ui/update-model-context" + }, + "params": { + "type": "object", + "properties": { + "role": { + "description": "Message role, currently only \"user\" is supported.", + "type": "string", + "const": "user" + }, + "content": { + "description": "Context content blocks (text, image, etc.).", + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "text" + }, + "text": { + "type": "string" + }, + "annotations": { + "type": "object", + "properties": { + "audience": { + "type": "array", + "items": { + "type": "string", + "enum": ["user", "assistant"] + } + }, + "priority": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "lastModified": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + } + }, + "additionalProperties": false + }, + "_meta": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + }, + "required": ["type", "text"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "image" + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "annotations": { + "type": "object", + "properties": { + "audience": { + "type": "array", + "items": { + "type": "string", + "enum": ["user", "assistant"] + } + }, + "priority": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "lastModified": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + } + }, + "additionalProperties": false + }, + "_meta": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + }, + "required": ["type", "data", "mimeType"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "audio" + }, + "data": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "annotations": { + "type": "object", + "properties": { + "audience": { + "type": "array", + "items": { + "type": "string", + "enum": ["user", "assistant"] + } + }, + "priority": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "lastModified": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + } + }, + "additionalProperties": false + }, + "_meta": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + }, + "required": ["type", "data", "mimeType"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "title": { + "type": "string" + }, + "icons": { + "type": "array", + "items": { + "type": "object", + "properties": { + "src": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "sizes": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["src"], + "additionalProperties": false + } + }, + "uri": { + "type": "string" + }, + "description": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "annotations": { + "type": "object", + "properties": { + "audience": { + "type": "array", + "items": { + "type": "string", + "enum": ["user", "assistant"] + } + }, + "priority": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "lastModified": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + } + }, + "additionalProperties": false + }, + "_meta": { + "type": "object", + "properties": {}, + "additionalProperties": {} + }, + "type": { + "type": "string", + "const": "resource_link" + } + }, + "required": ["name", "uri", "type"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "resource" + }, + "resource": { + "anyOf": [ + { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "_meta": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "text": { + "type": "string" + } + }, + "required": ["uri", "text"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "_meta": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + }, + "blob": { + "type": "string" + } + }, + "required": ["uri", "blob"], + "additionalProperties": false + } + ] + }, + "annotations": { + "type": "object", + "properties": { + "audience": { + "type": "array", + "items": { + "type": "string", + "enum": ["user", "assistant"] + } + }, + "priority": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "lastModified": { + "type": "string", + "format": "date-time", + "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$" + } + }, + "additionalProperties": false + }, + "_meta": { + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": {} + } + }, + "required": ["type", "resource"], + "additionalProperties": false + } + ] + } + } + }, + "required": ["role", "content"], + "additionalProperties": false + } + }, + "required": ["method", "params"], + "additionalProperties": false + }, + "McpUiUpdateModelContextResult": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "isError": { + "description": "True if the host rejected or failed to store the context update.", + "type": "boolean" + } + }, + "additionalProperties": {} } } } diff --git a/src/generated/schema.test.ts b/src/generated/schema.test.ts index e9a57981..98e52e75 100644 --- a/src/generated/schema.test.ts +++ b/src/generated/schema.test.ts @@ -71,6 +71,10 @@ export type McpUiHostStylesSchemaInferredType = z.infer< typeof generated.McpUiHostStylesSchema >; +export type McpUiUpdateModelContextResultSchemaInferredType = z.infer< + typeof generated.McpUiUpdateModelContextResultSchema +>; + export type McpUiResourceTeardownRequestSchemaInferredType = z.infer< typeof generated.McpUiResourceTeardownRequestSchema >; @@ -131,6 +135,10 @@ export type McpUiHostContextChangedNotificationSchemaInferredType = z.infer< typeof generated.McpUiHostContextChangedNotificationSchema >; +export type McpUiUpdateModelContextRequestSchemaInferredType = z.infer< + typeof generated.McpUiUpdateModelContextRequestSchema +>; + export type McpUiInitializeRequestSchemaInferredType = z.infer< typeof generated.McpUiInitializeRequestSchema >; @@ -205,6 +213,12 @@ expectType({} as McpUiHostCssSchemaInferredType); expectType({} as spec.McpUiHostCss); expectType({} as McpUiHostStylesSchemaInferredType); expectType({} as spec.McpUiHostStyles); +expectType( + {} as McpUiUpdateModelContextResultSchemaInferredType, +); +expectType( + {} as spec.McpUiUpdateModelContextResult, +); expectType( {} as McpUiResourceTeardownRequestSchemaInferredType, ); @@ -279,6 +293,12 @@ expectType( expectType( {} as spec.McpUiHostContextChangedNotification, ); +expectType( + {} as McpUiUpdateModelContextRequestSchemaInferredType, +); +expectType( + {} as spec.McpUiUpdateModelContextRequest, +); expectType( {} as McpUiInitializeRequestSchemaInferredType, ); diff --git a/src/generated/schema.ts b/src/generated/schema.ts index 9e6120a5..e01184db 100644 --- a/src/generated/schema.ts +++ b/src/generated/schema.ts @@ -315,6 +315,22 @@ export const McpUiHostStylesSchema = z.object({ ), }); +/** + * @description Result from setting the agent's model context. + * @see {@link McpUiUpdateModelContextRequest} + */ +export const McpUiUpdateModelContextResultSchema = z + .object({ + /** @description True if the host rejected or failed to store the context update. */ + isError: z + .boolean() + .optional() + .describe( + "True if the host rejected or failed to store the context update.", + ), + }) + .passthrough(); + /** * @description Request for graceful shutdown of the Guest UI (Host -> Guest UI). * @see {@link app-bridge.AppBridge.teardownResource} for the host method that sends this @@ -372,6 +388,13 @@ export const McpUiHostCapabilitiesSchema = z.object({ .describe("Host can proxy resource reads to the MCP server."), /** @description Host accepts log messages. */ logging: z.object({}).optional().describe("Host accepts log messages."), + /** @description Host accepts context updates to be included in the model's context for future turns. */ + updateModelContext: z + .object({}) + .optional() + .describe( + "Host accepts context updates to be included in the model's context for future turns.", + ), }); /** @@ -642,6 +665,28 @@ export const McpUiHostContextChangedNotificationSchema = z.object({ ), }); +/** + * @description Request to update the agent's context without requiring a follow-up action (Guest UI -> Host). + * + * Unlike `notifications/message` which is for debugging/logging, this request is intended + * to update the Host's model context. Each request overwrites the previous context sent by the Guest UI. + * Unlike messages, context updates do not trigger follow-ups. + * @see {@link app.App.sendUpdateModelContext} for the method that sends this request + */ +export const McpUiUpdateModelContextRequestSchema = z.object({ + method: z.literal("ui/update-model-context"), + params: z.object({ + /** @description Message role, currently only "user" is supported. */ + role: z + .literal("user") + .describe('Message role, currently only "user" is supported.'), + /** @description Context content blocks (text, image, etc.). */ + content: z + .array(ContentBlockSchema) + .describe("Context content blocks (text, image, etc.)."), + }), +}); + /** * @description Initialization request sent from Guest UI to Host. * @see {@link app.App.connect} for the method that sends this request diff --git a/src/spec.types.ts b/src/spec.types.ts index f97a0a6f..3ae6bbd5 100644 --- a/src/spec.types.ts +++ b/src/spec.types.ts @@ -373,6 +373,38 @@ export interface McpUiHostContextChangedNotification { params: McpUiHostContext; } +/** + * @description Request to update the agent's context without requiring a follow-up action (Guest UI -> Host). + * + * Unlike `notifications/message` which is for debugging/logging, this request is intended + * to update the Host's model context. Each request overwrites the previous context sent by the Guest UI. + * Unlike messages, context updates do not trigger follow-ups. + * @see {@link app.App.sendUpdateModelContext} for the method that sends this request + */ +export interface McpUiUpdateModelContextRequest { + method: "ui/update-model-context"; + params: { + /** @description Message role, currently only "user" is supported. */ + role: "user"; + /** @description Context content blocks (text, image, etc.). */ + content: ContentBlock[]; + }; +} + +/** + * @description Result from setting the agent's model context. + * @see {@link McpUiUpdateModelContextRequest} + */ +export interface McpUiUpdateModelContextResult { + /** @description True if the host rejected or failed to store the context update. */ + isError?: boolean; + /** + * Index signature required for MCP SDK `Protocol` class compatibility. + * Note: The schema intentionally omits this to enforce strict validation. + */ + [key: string]: unknown; +} + /** * @description Request for graceful shutdown of the Guest UI (Host -> Guest UI). * @see {@link app-bridge.AppBridge.teardownResource} for the host method that sends this @@ -414,6 +446,8 @@ export interface McpUiHostCapabilities { }; /** @description Host accepts log messages. */ logging?: {}; + /** @description Host accepts context updates to be included in the model's context for future turns. */ + updateModelContext?: {}; } /** diff --git a/src/types.ts b/src/types.ts index d710ad35..e0fc255b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -22,6 +22,8 @@ export { type McpUiOpenLinkResult, type McpUiMessageRequest, type McpUiMessageResult, + type McpUiUpdateModelContextRequest, + type McpUiUpdateModelContextResult, type McpUiSandboxProxyReadyNotification, type McpUiSandboxResourceReadyNotification, type McpUiSizeChangedNotification, @@ -51,6 +53,7 @@ import type { McpUiInitializeRequest, McpUiOpenLinkRequest, McpUiMessageRequest, + McpUiUpdateModelContextRequest, McpUiResourceTeardownRequest, McpUiRequestDisplayModeRequest, McpUiHostContextChangedNotification, @@ -65,6 +68,7 @@ import type { McpUiInitializeResult, McpUiOpenLinkResult, McpUiMessageResult, + McpUiUpdateModelContextResult, McpUiResourceTeardownResult, McpUiRequestDisplayModeResult, } from "./spec.types.js"; @@ -79,6 +83,8 @@ export { McpUiOpenLinkResultSchema, McpUiMessageRequestSchema, McpUiMessageResultSchema, + McpUiUpdateModelContextRequestSchema, + McpUiUpdateModelContextResultSchema, McpUiSandboxProxyReadyNotificationSchema, McpUiSandboxResourceReadyNotificationSchema, McpUiSizeChangedNotificationSchema, @@ -137,6 +143,7 @@ export type AppRequest = | McpUiInitializeRequest | McpUiOpenLinkRequest | McpUiMessageRequest + | McpUiUpdateModelContextRequest | McpUiResourceTeardownRequest | McpUiRequestDisplayModeRequest | CallToolRequest @@ -184,6 +191,7 @@ export type AppResult = | McpUiInitializeResult | McpUiOpenLinkResult | McpUiMessageResult + | McpUiUpdateModelContextResult | McpUiResourceTeardownResult | McpUiRequestDisplayModeResult | CallToolResult