Skip to content

Commit 67ce38c

Browse files
committed
Set widget state
1 parent e514e6c commit 67ce38c

File tree

7 files changed

+246
-1
lines changed

7 files changed

+246
-1
lines changed

specification/draft/apps.mdx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,58 @@ Guest UI behavior:
800800
* Guest UI SHOULD check `availableDisplayModes` in host context before requesting a mode change.
801801
* Guest UI MUST handle the response mode differing from the requested mode.
802802

803+
`ui/set-widget-state` - Persist widget state for inclusion in future prompts
804+
805+
```typescript
806+
// Request
807+
{
808+
jsonrpc: "2.0",
809+
id: 4,
810+
method: "ui/set-widget-state",
811+
params: {
812+
toolId: string, // Unique identifier for this widget/tool instance
813+
toolName: string, // Human-readable name of the tool (shown in prompts)
814+
content: string // JSON string containing the widget's current state
815+
}
816+
}
817+
818+
// Success Response
819+
{
820+
jsonrpc: "2.0",
821+
id: 4,
822+
result: {} // Empty result on success
823+
}
824+
825+
// Error Response (if failed)
826+
{
827+
jsonrpc: "2.0",
828+
id: 4,
829+
error: {
830+
code: -32000, // Implementation-defined error
831+
message: "Failed to store widget state"
832+
}
833+
}
834+
```
835+
836+
This request allows apps to persist state that the model can see in subsequent messages. The state is stored per-conversation and sent with each completion request, enabling the model to be aware of the current widget configuration and user selections.
837+
838+
Host behavior:
839+
* Host MUST store the widget state associated with the current conversation.
840+
* Host MUST include all stored widget states in subsequent completion requests to the model.
841+
* Host SHOULD treat widget state content as untrusted third-party data and wrap it appropriately when presenting to the model.
842+
* Host MAY implement size limits on widget state content.
843+
844+
Guest UI behavior:
845+
* Guest UI SHOULD call this method whenever its state changes meaningfully in a way that the model should be aware of.
846+
* Guest UI SHOULD use a consistent `toolId` for the same widget instance.
847+
* Guest UI SHOULD serialize state as a JSON string in the `content` field.
848+
* Guest UI SHOULD include relevant context such as selected items, current view configuration, and user choices.
849+
850+
Example use cases:
851+
* A chart widget reports which data point the user has selected
852+
* A form widget reports current field values before submission
853+
* A data explorer reports active filters and sort order
854+
803855
#### Notifications (Host → UI)
804856

805857
`ui/notifications/tool-input` - Host MUST send this notification with the complete tool arguments after the Guest UI's initialize request completes.

src/app.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ import {
4545
McpUiToolResultNotificationSchema,
4646
McpUiRequestDisplayModeRequest,
4747
McpUiRequestDisplayModeResultSchema,
48+
McpUiSetWidgetStateRequest,
49+
McpUiSetWidgetStateResultSchema,
4850
} from "./types";
4951
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
5052

@@ -886,6 +888,53 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
886888
);
887889
}
888890

891+
/**
892+
* Set widget state that will be included in future prompts.
893+
*
894+
* This allows apps to persist state that Claude can see in subsequent messages.
895+
* The state is treated as untrusted third-party content and will be wrapped
896+
* in a document block with explicit trust metadata on the backend.
897+
*
898+
* Call this method whenever your widget's state changes meaningfully in a way
899+
* that Claude should be aware of. The state is stored per-conversation and
900+
* sent with each completion request.
901+
*
902+
* @param params - Widget state parameters
903+
* @param params.toolId - Unique identifier for this widget/tool instance
904+
* @param params.toolName - Human-readable name of the tool (shown in prompts)
905+
* @param params.content - JSON string containing the widget's current state
906+
* @param options - Request options (timeout, etc.)
907+
* @returns Result indicating success or error
908+
*
909+
* @example Persist chart configuration
910+
* ```typescript
911+
* await app.setWidgetState({
912+
* toolId: "customer-segmentation-chart",
913+
* toolName: "Customer Segmentation",
914+
* content: JSON.stringify({
915+
* xAxis: "revenue",
916+
* yAxis: "engagement",
917+
* hiddenSegments: ["inactive"]
918+
* })
919+
* });
920+
* ```
921+
*
922+
* @see {@link McpUiSetWidgetStateRequest} for request structure
923+
*/
924+
setWidgetState(
925+
params: McpUiSetWidgetStateRequest["params"],
926+
options?: RequestOptions,
927+
) {
928+
return this.request(
929+
<McpUiSetWidgetStateRequest>{
930+
method: "ui/set-widget-state",
931+
params,
932+
},
933+
McpUiSetWidgetStateResultSchema,
934+
options,
935+
);
936+
}
937+
889938
/**
890939
* Notify the host of UI size changes.
891940
*

src/generated/schema.json

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/generated/schema.test.ts

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/generated/schema.ts

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/spec.types.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,3 +548,35 @@ export interface McpUiToolMeta {
548548
*/
549549
visibility?: McpUiToolVisibility[];
550550
}
551+
552+
/**
553+
* @description Request to set widget state that will be included in future prompts.
554+
* The state is treated as untrusted third-party content and will be wrapped
555+
* in a document block with explicit trust metadata.
556+
* @see {@link app.App.setWidgetState} for the method that sends this request
557+
*/
558+
export interface McpUiSetWidgetStateRequest {
559+
method: "ui/set-widget-state";
560+
params: {
561+
/** @description Unique identifier for this widget/tool instance. Typically the app_id or tool name. */
562+
toolId: string;
563+
/** @description Human-readable name of the tool (shown in prompts). */
564+
toolName: string;
565+
/** @description JSON string containing the widget's current state. This will be included in future completion requests. */
566+
content: string;
567+
};
568+
}
569+
570+
/**
571+
* @description Result from setting widget state.
572+
* @see {@link McpUiSetWidgetStateRequest}
573+
*/
574+
export interface McpUiSetWidgetStateResult {
575+
/** @description True if the host failed to store the widget state. */
576+
isError?: boolean;
577+
/**
578+
* Index signature required for MCP SDK `Protocol` class compatibility.
579+
* Note: The schema intentionally omits this to enforce strict validation.
580+
*/
581+
[key: string]: unknown;
582+
}

src/types.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ export {
4444
type McpUiRequestDisplayModeResult,
4545
type McpUiToolVisibility,
4646
type McpUiToolMeta,
47+
type McpUiSetWidgetStateRequest,
48+
type McpUiSetWidgetStateResult,
4749
} from "./spec.types.js";
4850

4951
// Import types needed for protocol type unions (not re-exported, just used internally)
@@ -53,6 +55,7 @@ import type {
5355
McpUiMessageRequest,
5456
McpUiResourceTeardownRequest,
5557
McpUiRequestDisplayModeRequest,
58+
McpUiSetWidgetStateRequest,
5659
McpUiHostContextChangedNotification,
5760
McpUiToolInputNotification,
5861
McpUiToolInputPartialNotification,
@@ -67,6 +70,7 @@ import type {
6770
McpUiMessageResult,
6871
McpUiResourceTeardownResult,
6972
McpUiRequestDisplayModeResult,
73+
McpUiSetWidgetStateResult,
7074
} from "./spec.types.js";
7175

7276
// Re-export all schemas from generated/schema.ts (already PascalCase)
@@ -101,6 +105,8 @@ export {
101105
McpUiRequestDisplayModeResultSchema,
102106
McpUiToolVisibilitySchema,
103107
McpUiToolMetaSchema,
108+
McpUiSetWidgetStateRequestSchema,
109+
McpUiSetWidgetStateResultSchema,
104110
} from "./generated/schema.js";
105111

106112
// Re-export SDK types used in protocol type unions
@@ -129,7 +135,7 @@ import {
129135
* All request types in the MCP Apps protocol.
130136
*
131137
* Includes:
132-
* - MCP UI requests (initialize, open-link, message, resource-teardown, request-display-mode)
138+
* - MCP UI requests (initialize, open-link, message, resource-teardown, request-display-mode, set-widget-state)
133139
* - MCP server requests forwarded from the app (tools/call, resources/*, prompts/list)
134140
* - Protocol requests (ping)
135141
*/
@@ -139,6 +145,7 @@ export type AppRequest =
139145
| McpUiMessageRequest
140146
| McpUiResourceTeardownRequest
141147
| McpUiRequestDisplayModeRequest
148+
| McpUiSetWidgetStateRequest
142149
| CallToolRequest
143150
| ListToolsRequest
144151
| ListResourcesRequest
@@ -186,6 +193,7 @@ export type AppResult =
186193
| McpUiMessageResult
187194
| McpUiResourceTeardownResult
188195
| McpUiRequestDisplayModeResult
196+
| McpUiSetWidgetStateResult
189197
| CallToolResult
190198
| ListToolsResult
191199
| ListResourcesResult

0 commit comments

Comments
 (0)