Skip to content

Commit 45abd21

Browse files
committed
feat: add hook for sending data to client
1 parent 0abd1dc commit 45abd21

File tree

2 files changed

+200
-0
lines changed

2 files changed

+200
-0
lines changed

src/ui/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { useRenderData } from "./useRenderData.js";
2+
export { useUIActions } from "./useUIActions.js";

src/ui/hooks/useUIActions.ts

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import { useCallback, useMemo } from "react";
2+
3+
/** Options for sending a message */
4+
interface SendMessageOptions {
5+
/** Target origin for postMessage. Defaults to "*" */
6+
targetOrigin?: string;
7+
}
8+
9+
/** Return type for the useUIActions hook */
10+
interface UseUIActionsResult {
11+
/**
12+
* Sends an intent message to the host.
13+
* Indicates the user has interacted with the UI and expressed an intent for the host to act on.
14+
* @param intent - The intent identifier
15+
* @param params - Optional parameters for the intent
16+
*/
17+
intent: <T = unknown>(intent: string, params?: T) => void;
18+
19+
/**
20+
* Sends a notify message to the host.
21+
* Indicates the iframe already acted upon user interaction and is notifying the host.
22+
* @param message - The notification message
23+
*/
24+
notify: (message: string) => void;
25+
26+
/**
27+
* Sends a prompt message to the host.
28+
* Asks the host to run a prompt.
29+
* @param prompt - The prompt text to run
30+
*/
31+
prompt: (prompt: string) => void;
32+
33+
/**
34+
* Sends a tool call message to the host.
35+
* Asks the host to execute a tool.
36+
* @param toolName - The name of the tool to call
37+
* @param params - Optional parameters for the tool
38+
*/
39+
tool: <T = unknown>(toolName: string, params?: T) => void;
40+
41+
/**
42+
* Sends a link message to the host.
43+
* Asks the host to navigate to a URL.
44+
* @param url - The URL to navigate to
45+
*/
46+
link: (url: string) => void;
47+
48+
/**
49+
* Reports a size change to the host.
50+
* Used for auto-resizing iframes.
51+
* @param dimensions - The new dimensions (width and/or height)
52+
*/
53+
reportSizeChange: (dimensions: { width?: number; height?: number }) => void;
54+
}
55+
56+
/**
57+
* Hook for sending UI actions to the parent window via postMessage.
58+
* This is used by iframe-based UI components to communicate back to an MCP client.
59+
*
60+
* Implements the MCP-UI embeddable UI communication protocol.
61+
* All actions are fire-and-forget - use `useRenderData` to receive responses from the host.
62+
*
63+
* @param defaultOptions - Default options applied to all messages
64+
* @returns An object containing action methods:
65+
* - intent: Send an intent for the host to act on
66+
* - notify: Notify the host of something that happened
67+
* - prompt: Ask the host to run a prompt
68+
* - tool: Ask the host to run a tool call
69+
* - link: Ask the host to navigate to a URL
70+
* - reportSizeChange: Report iframe size changes
71+
*
72+
* @example
73+
* ```tsx
74+
* function MyComponent() {
75+
* const { data } = useRenderData<MyData>();
76+
* const { intent, tool, link } = useUIActions();
77+
*
78+
* const handleCreateTask = () => {
79+
* intent("create-task", { title: "Buy groceries" });
80+
* };
81+
*
82+
* const handleRefresh = () => {
83+
* // Ask host to run a tool, host will send new data via render data
84+
* tool("refresh-data", { id: data?.id });
85+
* };
86+
*
87+
* const handleOpenDocs = () => {
88+
* link("https://docs.example.com");
89+
* };
90+
*
91+
* return <button onClick={handleCreateTask}>Create Task</button>;
92+
* }
93+
* ```
94+
*/
95+
export function useUIActions(defaultOptions?: SendMessageOptions): UseUIActionsResult {
96+
const targetOrigin = defaultOptions?.targetOrigin ?? "*";
97+
98+
const intent = useCallback(
99+
<T = unknown>(intentName: string, params?: T): void => {
100+
window.parent.postMessage(
101+
{
102+
type: "intent",
103+
payload: {
104+
intent: intentName,
105+
params,
106+
},
107+
},
108+
targetOrigin
109+
);
110+
},
111+
[targetOrigin]
112+
);
113+
114+
const notify = useCallback(
115+
(message: string): void => {
116+
window.parent.postMessage(
117+
{
118+
type: "notify",
119+
payload: {
120+
message,
121+
},
122+
},
123+
targetOrigin
124+
);
125+
},
126+
[targetOrigin]
127+
);
128+
129+
const prompt = useCallback(
130+
(promptText: string): void => {
131+
window.parent.postMessage(
132+
{
133+
type: "prompt",
134+
payload: {
135+
prompt: promptText,
136+
},
137+
},
138+
targetOrigin
139+
);
140+
},
141+
[targetOrigin]
142+
);
143+
144+
const tool = useCallback(
145+
<T = unknown>(toolName: string, params?: T): void => {
146+
window.parent.postMessage(
147+
{
148+
type: "tool",
149+
payload: {
150+
toolName,
151+
params,
152+
},
153+
},
154+
targetOrigin
155+
);
156+
},
157+
[targetOrigin]
158+
);
159+
160+
const link = useCallback(
161+
(url: string): void => {
162+
window.parent.postMessage(
163+
{
164+
type: "link",
165+
payload: {
166+
url,
167+
},
168+
},
169+
targetOrigin
170+
);
171+
},
172+
[targetOrigin]
173+
);
174+
175+
const reportSizeChange = useCallback(
176+
(dimensions: { width?: number; height?: number }): void => {
177+
window.parent.postMessage(
178+
{
179+
type: "ui-size-change",
180+
payload: dimensions,
181+
},
182+
targetOrigin
183+
);
184+
},
185+
[targetOrigin]
186+
);
187+
188+
return useMemo(
189+
() => ({
190+
intent,
191+
notify,
192+
prompt,
193+
tool,
194+
link,
195+
reportSizeChange,
196+
}),
197+
[intent, notify, prompt, tool, link, reportSizeChange]
198+
);
199+
}

0 commit comments

Comments
 (0)