diff --git a/specification/draft/apps.mdx b/specification/draft/apps.mdx index e448bb9f..aed5611f 100644 --- a/specification/draft/apps.mdx +++ b/specification/draft/apps.mdx @@ -979,6 +979,49 @@ 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: { + content?: ContentBlock[], + structuredContent?: Record + } +} + +// 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 provide the context to the model in future turns +- MAY overwrite the previous model context with the new update +- MAY defer sending the context to the model until the next user message (including `ui/message`) +- MAY dedupe identical `ui/update-model-context` calls +- If multiple updates are received before the next user message, Host SHOULD only send the last update to the model +- 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. @@ -1222,10 +1265,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 1e55f6bd..66d5f830 100644 --- a/src/app-bridge.test.ts +++ b/src/app-bridge.test.ts @@ -442,6 +442,82 @@ describe("App <-> AppBridge integration", () => { logger: "TestApp", }); }); + + it("app.updateModelContext 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.updateModelContext({ + content: [{ type: "text", text: "User selected 3 items" }], + }); + + expect(receivedContexts).toHaveLength(1); + expect(receivedContexts[0]).toMatchObject({ + content: [{ type: "text", text: "User selected 3 items" }], + }); + expect(result).toEqual({}); + }); + + it("app.updateModelContext 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.updateModelContext({ + content: [ + { type: "text", text: "Filter applied" }, + { type: "text", text: "Category: electronics" }, + ], + }); + + expect(receivedContexts).toHaveLength(1); + expect(receivedContexts[0]).toMatchObject({ + content: [ + { type: "text", text: "Filter applied" }, + { type: "text", text: "Category: electronics" }, + ], + }); + expect(result).toEqual({}); + }); + + it("app.updateModelContext works with structuredContent", async () => { + const receivedContexts: unknown[] = []; + bridge.onupdatemodelcontext = async (params) => { + receivedContexts.push(params); + return {}; + }; + + await app.connect(appTransport); + const result = await app.updateModelContext({ + structuredContent: { selectedItems: 3, total: 150.0, currency: "USD" }, + }); + + expect(receivedContexts).toHaveLength(1); + expect(receivedContexts[0]).toMatchObject({ + structuredContent: { selectedItems: 3, total: 150.0, currency: "USD" }, + }); + expect(result).toEqual({}); + }); + + it("app.updateModelContext throws when handler throws", async () => { + bridge.onupdatemodelcontext = async () => { + throw new Error("Context update failed"); + }; + + await app.connect(appTransport); + await expect( + app.updateModelContext({ + content: [{ type: "text", text: "Test" }], + }), + ).rejects.toThrow("Context update failed"); + }); }); describe("App -> Host requests", () => { diff --git a/src/app-bridge.ts b/src/app-bridge.ts index f9ff6999..69b89938 100644 --- a/src/app-bridge.ts +++ b/src/app-bridge.ts @@ -5,6 +5,7 @@ import { CallToolRequestSchema, CallToolResult, CallToolResultSchema, + EmptyResult, Implementation, ListPromptsRequest, ListPromptsRequestSchema, @@ -51,6 +52,8 @@ import { type McpUiToolResultNotification, LATEST_PROTOCOL_VERSION, McpUiAppCapabilities, + McpUiUpdateModelContextRequest, + McpUiUpdateModelContextRequestSchema, McpUiHostCapabilities, McpUiHostContext, McpUiHostContextChangedNotification, @@ -66,7 +69,6 @@ import { McpUiOpenLinkRequestSchema, McpUiOpenLinkResult, McpUiResourceTeardownRequest, - McpUiResourceTeardownResult, McpUiResourceTeardownResultSchema, McpUiSandboxProxyReadyNotification, McpUiSandboxProxyReadyNotificationSchema, @@ -633,6 +635,48 @@ 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. + * + * The host will typically defer sending the context to the model until the + * next user message (including `ui/message`), and will only send the last + * update received. + * + * @example + * ```typescript + * bridge.onupdatemodelcontext = async ({ content, structuredContent }, extra) => { + * // Update the model context with the new snapshot + * modelContext = { + * type: "app_context", + * content, + * structuredContent, + * timestamp: Date.now() + * }; + * return {}; + * }; + * ``` + * + * @see {@link McpUiUpdateModelContextRequest} for the request 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 129b5802..2b43cd5e 100644 --- a/src/app.ts +++ b/src/app.ts @@ -9,6 +9,7 @@ import { CallToolRequestSchema, CallToolResult, CallToolResultSchema, + EmptyResultSchema, Implementation, ListToolsRequest, ListToolsRequestSchema, @@ -20,6 +21,7 @@ import { PostMessageTransport } from "./message-transport"; import { LATEST_PROTOCOL_VERSION, McpUiAppCapabilities, + McpUiUpdateModelContextRequest, McpUiHostCapabilities, McpUiHostContext, McpUiHostContextChangedNotification, @@ -809,6 +811,52 @@ export class App extends Protocol { }); } + /** + * Update the host's model context with app state. + * + * Unlike `sendLog`, which is for debugging/telemetry, context updates + * are intended to be available to the model in future reasoning, + * without requiring a follow-up action (like `sendMessage`). + * + * The host will typically defer sending the context to the model until the + * next user message (including `ui/message`), and will only send the last + * update received. Each call overwrites any previous context update. + * + * @param params - Context content and/or structured content + * @param options - Request options (timeout, etc.) + * + * @throws {Error} If the host rejects the context update (e.g., unsupported content type) + * + * @example Update model context with current app state + * ```typescript + * await app.updateModelContext({ + * content: [{ type: "text", text: "User selected 3 items totaling $150.00" }] + * }); + * ``` + * + * @example Update with structured content + * ```typescript + * await app.updateModelContext({ + * structuredContent: { selectedItems: 3, total: 150.00, currency: "USD" } + * }); + * ``` + * + * @returns Promise that resolves when the context update is acknowledged + */ + updateModelContext( + params: McpUiUpdateModelContextRequest["params"], + options?: RequestOptions, + ) { + return this.request( + { + method: "ui/update-model-context", + params, + }, + EmptyResultSchema, + 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 61385e4a..1d87d9c8 100644 --- a/src/generated/schema.json +++ b/src/generated/schema.json @@ -162,6 +162,92 @@ } }, "additionalProperties": false + }, + "updateModelContext": { + "description": "Host accepts context updates (ui/update-model-context) to be included in the model's context for future turns.", + "type": "object", + "properties": { + "text": { + "description": "Host supports text content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "image": { + "description": "Host supports image content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "audio": { + "description": "Host supports audio content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "resource": { + "description": "Host supports resource content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "resourceLink": { + "description": "Host supports resource link content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "structuredContent": { + "description": "Host supports structured content.", + "type": "object", + "properties": {}, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "message": { + "description": "Host supports receiving content messages (ui/message) from the Guest UI.", + "type": "object", + "properties": { + "text": { + "description": "Host supports text content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "image": { + "description": "Host supports image content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "audio": { + "description": "Host supports audio content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "resource": { + "description": "Host supports resource content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "resourceLink": { + "description": "Host supports resource link content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "structuredContent": { + "description": "Host supports structured content.", + "type": "object", + "properties": {}, + "additionalProperties": false + } + }, + "additionalProperties": false } }, "additionalProperties": false @@ -2377,6 +2463,92 @@ } }, "additionalProperties": false + }, + "updateModelContext": { + "description": "Host accepts context updates (ui/update-model-context) to be included in the model's context for future turns.", + "type": "object", + "properties": { + "text": { + "description": "Host supports text content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "image": { + "description": "Host supports image content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "audio": { + "description": "Host supports audio content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "resource": { + "description": "Host supports resource content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "resourceLink": { + "description": "Host supports resource link content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "structuredContent": { + "description": "Host supports structured content.", + "type": "object", + "properties": {}, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "message": { + "description": "Host supports receiving content messages (ui/message) from the Guest UI.", + "type": "object", + "properties": { + "text": { + "description": "Host supports text content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "image": { + "description": "Host supports image content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "audio": { + "description": "Host supports audio content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "resource": { + "description": "Host supports resource content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "resourceLink": { + "description": "Host supports resource link content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "structuredContent": { + "description": "Host supports structured content.", + "type": "object", + "properties": {}, + "additionalProperties": false + } + }, + "additionalProperties": false } }, "additionalProperties": false, @@ -4631,6 +4803,49 @@ ], "description": "Style variables for theming MCP apps.\n\nIndividual style keys are optional - hosts may provide any subset of these values.\nValues are strings containing CSS values (colors, sizes, font stacks, etc.).\n\nNote: This type uses `Record` rather than `Partial>`\nfor compatibility with Zod schema generation. Both are functionally equivalent for validation." }, + "McpUiSupportedContentBlockModalities": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "text": { + "description": "Host supports text content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "image": { + "description": "Host supports image content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "audio": { + "description": "Host supports audio content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "resource": { + "description": "Host supports resource content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "resourceLink": { + "description": "Host supports resource link content blocks.", + "type": "object", + "properties": {}, + "additionalProperties": false + }, + "structuredContent": { + "description": "Host supports structured content.", + "type": "object", + "properties": {}, + "additionalProperties": false + } + }, + "additionalProperties": false + }, "McpUiTheme": { "$schema": "https://json-schema.org/draft/2020-12/schema", "anyOf": [ @@ -5138,6 +5353,351 @@ } ], "description": "Tool visibility scope - who can access the tool." + }, + "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": { + "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" + } + }, + "theme": { + "type": "string", + "enum": ["light", "dark"] + } + }, + "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 + } + ] + } + }, + "structuredContent": { + "description": "Structured content for machine-readable context data.", + "type": "object", + "propertyNames": { + "type": "string" + }, + "additionalProperties": { + "description": "Structured content for machine-readable context data." + } + } + }, + "additionalProperties": false + } + }, + "required": ["method", "params"], + "additionalProperties": false } } } diff --git a/src/generated/schema.test.ts b/src/generated/schema.test.ts index 5bbcd5ca..727c28d2 100644 --- a/src/generated/schema.test.ts +++ b/src/generated/schema.test.ts @@ -83,6 +83,10 @@ export type McpUiResourceTeardownResultSchemaInferredType = z.infer< typeof generated.McpUiResourceTeardownResultSchema >; +export type McpUiSupportedContentBlockModalitiesSchemaInferredType = z.infer< + typeof generated.McpUiSupportedContentBlockModalitiesSchema +>; + export type McpUiHostCapabilitiesSchemaInferredType = z.infer< typeof generated.McpUiHostCapabilitiesSchema >; @@ -135,6 +139,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 >; @@ -223,6 +231,12 @@ expectType( expectType( {} as spec.McpUiResourceTeardownResult, ); +expectType( + {} as McpUiSupportedContentBlockModalitiesSchemaInferredType, +); +expectType( + {} as spec.McpUiSupportedContentBlockModalities, +); expectType( {} as McpUiHostCapabilitiesSchemaInferredType, ); @@ -289,6 +303,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 8de9bb5a..69abe496 100644 --- a/src/generated/schema.ts +++ b/src/generated/schema.ts @@ -358,6 +358,36 @@ export const McpUiResourceTeardownResultSchema = z.record( z.unknown(), ); +export const McpUiSupportedContentBlockModalitiesSchema = z.object({ + /** @description Host supports text content blocks. */ + text: z.object({}).optional().describe("Host supports text content blocks."), + /** @description Host supports image content blocks. */ + image: z + .object({}) + .optional() + .describe("Host supports image content blocks."), + /** @description Host supports audio content blocks. */ + audio: z + .object({}) + .optional() + .describe("Host supports audio content blocks."), + /** @description Host supports resource content blocks. */ + resource: z + .object({}) + .optional() + .describe("Host supports resource content blocks."), + /** @description Host supports resource link content blocks. */ + resourceLink: z + .object({}) + .optional() + .describe("Host supports resource link content blocks."), + /** @description Host supports structured content. */ + structuredContent: z + .object({}) + .optional() + .describe("Host supports structured content."), +}); + /** * @description Capabilities supported by the host application. * @see {@link McpUiInitializeResult} for the initialization result that includes these capabilities @@ -411,6 +441,15 @@ export const McpUiHostCapabilitiesSchema = z.object({ }) .optional() .describe("Sandbox configuration applied by the host."), + /** @description Host accepts context updates (ui/update-model-context) to be included in the model's context for future turns. */ + updateModelContext: + McpUiSupportedContentBlockModalitiesSchema.optional().describe( + "Host accepts context updates (ui/update-model-context) to be included in the model's context for future turns.", + ), + /** @description Host supports receiving content messages (ui/message) from the Guest UI. */ + message: McpUiSupportedContentBlockModalitiesSchema.optional().describe( + "Host supports receiving content messages (ui/message) from the Guest UI.", + ), }); /** @@ -714,6 +753,39 @@ 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. + * + * The host will typically defer sending the context to the model until the next user message + * (including `ui/message`), and will only send the last update received. + * + * @see {@link app.App.updateModelContext} for the method that sends this request + */ +export const McpUiUpdateModelContextRequestSchema = z.object({ + method: z.literal("ui/update-model-context"), + params: z.object({ + /** @description Context content blocks (text, image, etc.). */ + content: z + .array(ContentBlockSchema) + .optional() + .describe("Context content blocks (text, image, etc.)."), + /** @description Structured content for machine-readable context data. */ + structuredContent: z + .record( + z.string(), + z + .unknown() + .describe("Structured content for machine-readable context data."), + ) + .optional() + .describe("Structured content for machine-readable context data."), + }), +}); + /** * @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 3e872094..998e4d26 100644 --- a/src/spec.types.ts +++ b/src/spec.types.ts @@ -384,6 +384,28 @@ 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. + * + * The host will typically defer sending the context to the model until the next user message + * (including `ui/message`), and will only send the last update received. + * + * @see {@link app.App.updateModelContext} for the method that sends this request + */ +export interface McpUiUpdateModelContextRequest { + method: "ui/update-model-context"; + params: { + /** @description Context content blocks (text, image, etc.). */ + content?: ContentBlock[]; + /** @description Structured content for machine-readable context data. */ + structuredContent?: Record; + }; +} + /** * @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 @@ -404,6 +426,21 @@ export interface McpUiResourceTeardownResult { [key: string]: unknown; } +export interface McpUiSupportedContentBlockModalities { + /** @description Host supports text content blocks. */ + text?: {}; + /** @description Host supports image content blocks. */ + image?: {}; + /** @description Host supports audio content blocks. */ + audio?: {}; + /** @description Host supports resource content blocks. */ + resource?: {}; + /** @description Host supports resource link content blocks. */ + resourceLink?: {}; + /** @description Host supports structured content. */ + structuredContent?: {}; +} + /** * @description Capabilities supported by the host application. * @see {@link McpUiInitializeResult} for the initialization result that includes these capabilities @@ -432,6 +469,10 @@ export interface McpUiHostCapabilities { /** @description CSP domains approved by the host. */ csp?: McpUiResourceCsp; }; + /** @description Host accepts context updates (ui/update-model-context) to be included in the model's context for future turns. */ + updateModelContext?: McpUiSupportedContentBlockModalities; + /** @description Host supports receiving content messages (ui/message) from the Guest UI. */ + message?: McpUiSupportedContentBlockModalities; } /** diff --git a/src/types.ts b/src/types.ts index 006680cf..5cbc0b48 100644 --- a/src/types.ts +++ b/src/types.ts @@ -36,6 +36,8 @@ export { type McpUiOpenLinkResult, type McpUiMessageRequest, type McpUiMessageResult, + type McpUiUpdateModelContextRequest, + type McpUiSupportedContentBlockModalities, type McpUiSandboxProxyReadyNotification, type McpUiSandboxResourceReadyNotification, type McpUiSizeChangedNotification, @@ -66,6 +68,7 @@ import type { McpUiInitializeRequest, McpUiOpenLinkRequest, McpUiMessageRequest, + McpUiUpdateModelContextRequest, McpUiResourceTeardownRequest, McpUiRequestDisplayModeRequest, McpUiHostContextChangedNotification, @@ -94,6 +97,8 @@ export { McpUiOpenLinkResultSchema, McpUiMessageRequestSchema, McpUiMessageResultSchema, + McpUiUpdateModelContextRequestSchema, + McpUiSupportedContentBlockModalitiesSchema, McpUiSandboxProxyReadyNotificationSchema, McpUiSandboxResourceReadyNotificationSchema, McpUiSizeChangedNotificationSchema, @@ -153,6 +158,7 @@ export type AppRequest = | McpUiInitializeRequest | McpUiOpenLinkRequest | McpUiMessageRequest + | McpUiUpdateModelContextRequest | McpUiResourceTeardownRequest | McpUiRequestDisplayModeRequest | CallToolRequest