Skip to content

Commit 6727390

Browse files
committed
feat: add ui/update-context
1 parent 402bf48 commit 6727390

File tree

9 files changed

+629
-3
lines changed

9 files changed

+629
-3
lines changed

specification/draft/apps.mdx

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ UI iframes can use the following subset of standard MCP protocol messages:
379379

380380
**Notifications:**
381381

382-
- `notifications/message` - Log messages to host
382+
- `notifications/message` - Log messages to host (for logging)
383383

384384
**Lifecycle:**
385385

@@ -533,6 +533,45 @@ Host SHOULD open the URL in the user's default browser or a new tab.
533533

534534
Host SHOULD add the message to the conversation thread, preserving the specified role.
535535

536+
`ui/update-context` - Update the agent's conversation context
537+
538+
```typescript
539+
// Request
540+
{
541+
jsonrpc: "2.0",
542+
id: 3,
543+
method: "ui/update-context",
544+
params: {
545+
role: "user",
546+
content: ContentBlock[]
547+
}
548+
}
549+
550+
// Success Response
551+
{
552+
jsonrpc: "2.0",
553+
id: 3,
554+
result: {} // Empty result on success
555+
}
556+
557+
// Error Response (if denied or failed)
558+
{
559+
jsonrpc: "2.0",
560+
id: 3,
561+
error: {
562+
code: -32000, // Implementation-defined error
563+
message: "Context update denied" | "Invalid content format"
564+
}
565+
}
566+
```
567+
568+
Guest UI MAY send this request to inform the agent about app state changes that should be stored in the conversation context for future reasoning. This event serves a different use case from `notifications/message` (logging) and `ui/message` (which also trigger followups).
569+
570+
Host behavior:
571+
- SHOULD store the context update in the conversation context
572+
- MAY display context updates to the user
573+
- MAY filter or aggregate context updates
574+
536575
#### Notifications (Host → UI)
537576

538577
`ui/notifications/tool-input` - Host MUST send this notification with the complete tool arguments after the Guest UI's initialize request completes.
@@ -765,9 +804,12 @@ sequenceDiagram
765804
else Message
766805
UI ->> H: ui/message
767806
H -->> H: Process message and follow up
768-
else Notify
807+
else Context update
808+
UI ->> H: ui/update-context
809+
H ->> H: Process context update and store in conversation context
810+
else Log
769811
UI ->> H: notifications/message
770-
H ->> H: Process notification and store in context
812+
H ->> H: Record log for debugging/telemetry
771813
else Resource read
772814
UI ->> H: resources/read
773815
H ->> S: resources/read

src/app-bridge.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,52 @@ describe("App <-> AppBridge integration", () => {
212212
logger: "TestApp",
213213
});
214214
});
215+
216+
it("app.sendContext triggers bridge.oncontext and returns result", async () => {
217+
const receivedContexts: unknown[] = [];
218+
bridge.oncontext = (params) => {
219+
receivedContexts.push(params);
220+
};
221+
222+
await app.connect(appTransport);
223+
const result = await app.sendContext({
224+
role: "user",
225+
content: [{ type: "text", text: "User selected 3 items" }],
226+
});
227+
228+
expect(receivedContexts).toHaveLength(1);
229+
expect(receivedContexts[0]).toMatchObject({
230+
role: "user",
231+
content: [{ type: "text", text: "User selected 3 items" }],
232+
});
233+
expect(result).toEqual({});
234+
});
235+
236+
it("app.sendContext works with multiple content blocks", async () => {
237+
const receivedContexts: unknown[] = [];
238+
bridge.oncontext = (params) => {
239+
receivedContexts.push(params);
240+
};
241+
242+
await app.connect(appTransport);
243+
const result = await app.sendContext({
244+
role: "user",
245+
content: [
246+
{ type: "text", text: "Filter applied" },
247+
{ type: "text", text: "Category: electronics" },
248+
],
249+
});
250+
251+
expect(receivedContexts).toHaveLength(1);
252+
expect(receivedContexts[0]).toMatchObject({
253+
role: "user",
254+
content: [
255+
{ type: "text", text: "Filter applied" },
256+
{ type: "text", text: "Category: electronics" },
257+
],
258+
});
259+
expect(result).toEqual({});
260+
});
215261
});
216262

217263
describe("App -> Host requests", () => {

src/app-bridge.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ import {
3939
type McpUiToolResultNotification,
4040
LATEST_PROTOCOL_VERSION,
4141
McpUiAppCapabilities,
42+
McpUiUpdateContextRequest,
43+
McpUiUpdateContextRequestSchema,
4244
McpUiHostCapabilities,
4345
McpUiHostContext,
4446
McpUiHostContextChangedNotification,
@@ -502,6 +504,43 @@ export class AppBridge extends Protocol<Request, Notification, Result> {
502504
);
503505
}
504506

507+
/**
508+
* Register a handler for context updates from the Guest UI.
509+
*
510+
* The Guest UI sends `ui/update-context` requests to inform the agent
511+
* about app state changes that should be stored in the conversation context for
512+
* future reasoning. Unlike logging messages, context updates are intended to be
513+
* available to the agent for decision making.
514+
*
515+
* @param callback - Handler that receives context update params
516+
* - params.role - Message role (currently only "user")
517+
* - params.content - Content blocks (text, image, etc.)
518+
*
519+
* @example
520+
* ```typescript
521+
* bridge.oncontext = ({ role, content }) => {
522+
* // Store context update for agent reasoning
523+
* conversationContext.push({
524+
* type: "app_context",
525+
* role,
526+
* content,
527+
* timestamp: Date.now()
528+
* });
529+
* };
530+
* ```
531+
*/
532+
set oncontext(
533+
callback: (params: McpUiUpdateContextRequest["params"]) => void,
534+
) {
535+
this.setRequestHandler(
536+
McpUiUpdateContextRequestSchema,
537+
async (request) => {
538+
callback(request.params);
539+
return {};
540+
},
541+
);
542+
}
543+
505544
/**
506545
* Verify that the guest supports the capability required for the given request method.
507546
* @internal

src/app.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import {
2121
import {
2222
LATEST_PROTOCOL_VERSION,
2323
McpUiAppCapabilities,
24+
McpUiUpdateContextRequest,
25+
McpUiUpdateContextResultSchema,
2426
McpUiHostCapabilities,
2527
McpUiHostContextChangedNotification,
2628
McpUiHostContextChangedNotificationSchema,
@@ -673,6 +675,40 @@ export class App extends Protocol<Request, Notification, Result> {
673675
});
674676
}
675677

678+
/**
679+
* Send context updates to the host for storage in the agent's conversation context.
680+
*
681+
* Unlike `sendLog` which is for debugging/telemetry, context updates are intended
682+
* to inform the agent about app state changes that should be available for future
683+
* reasoning without requiring a follow-up action (i.e., a prompt).
684+
*
685+
* @param params - Context role and content (same structure as ui/message)
686+
* @param options - Request options (timeout, etc.)
687+
*
688+
* @example Notify agent of significant state change
689+
* ```typescript
690+
* await app.sendContext({
691+
* role: "user",
692+
* content: [{ type: "text", text: "User selected 3 items totaling $150.00" }]
693+
* });
694+
* ```
695+
*
696+
* @returns Promise that resolves when the context update is acknowledged
697+
*/
698+
sendContext(
699+
params: McpUiUpdateContextRequest["params"],
700+
options?: RequestOptions
701+
) {
702+
return this.request(
703+
<McpUiUpdateContextRequest>{
704+
method: "ui/update-context",
705+
params,
706+
},
707+
McpUiUpdateContextResultSchema,
708+
options
709+
);
710+
}
711+
676712
/**
677713
* Request the host to open an external URL in the default browser.
678714
*

0 commit comments

Comments
 (0)