Skip to content

Commit 4b23e1e

Browse files
committed
feat: Add screenshot and click support to SDK
- Add McpUiScreenshotRequest/Result and McpUiClickRequest/Result types - Add onscreenshot and onclick handlers to App class - Add screenshot() and click() methods to AppBridge class - Generate updated Zod schemas
1 parent b705c14 commit 4b23e1e

File tree

8 files changed

+733
-493
lines changed

8 files changed

+733
-493
lines changed

package-lock.json

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

src/app-bridge.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ import {
8181
McpUiRequestDisplayModeRequestSchema,
8282
McpUiRequestDisplayModeResult,
8383
McpUiResourcePermissions,
84+
McpUiScreenshotRequest,
85+
McpUiScreenshotResult,
86+
McpUiScreenshotResultSchema,
87+
McpUiClickRequest,
88+
McpUiClickResult,
89+
McpUiClickResultSchema,
8490
} from "./types";
8591
export * from "./types";
8692
export { RESOURCE_URI_META_KEY, RESOURCE_MIME_TYPE } from "./app";
@@ -1352,6 +1358,76 @@ export class AppBridge extends Protocol<
13521358
);
13531359
}
13541360

1361+
/**
1362+
* Capture a screenshot of the Guest UI.
1363+
*
1364+
* Requests the App to render and capture its current visual state. The App
1365+
* returns the image as a base64-encoded string.
1366+
*
1367+
* @param params - Screenshot options (format, quality)
1368+
* @param options - Request options (timeout, etc.)
1369+
* @returns Promise resolving to the screenshot data
1370+
*
1371+
* @throws {Error} If the App does not support screenshots
1372+
* @throws {Error} If the request times out or the connection is lost
1373+
*
1374+
* @example Capture a PNG screenshot
1375+
* ```typescript
1376+
* const result = await bridge.screenshot({ format: "png" });
1377+
* const img = document.createElement("img");
1378+
* img.src = `data:${result.mimeType};base64,${result.data}`;
1379+
* document.body.appendChild(img);
1380+
* ```
1381+
*/
1382+
async screenshot(
1383+
params?: McpUiScreenshotRequest["params"],
1384+
options?: RequestOptions,
1385+
): Promise<McpUiScreenshotResult> {
1386+
return this.request(
1387+
{
1388+
method: "ui/screenshot" as const,
1389+
params: params ?? {},
1390+
},
1391+
McpUiScreenshotResultSchema,
1392+
options,
1393+
);
1394+
}
1395+
1396+
/**
1397+
* Simulate a click at a specific position in the Guest UI.
1398+
*
1399+
* Requests the App to dispatch a mouse event at the specified coordinates.
1400+
* The App should handle this as if the user clicked at that position.
1401+
*
1402+
* @param params - Click coordinates and options
1403+
* @param options - Request options (timeout, etc.)
1404+
* @returns Promise resolving to the click result
1405+
*
1406+
* @throws {Error} If the App does not support click simulation
1407+
* @throws {Error} If the request times out or the connection is lost
1408+
*
1409+
* @example Simple left click
1410+
* ```typescript
1411+
* const result = await bridge.click({ x: 100, y: 200 });
1412+
* if (result.success) {
1413+
* console.log(`Clicked on: ${result.targetElement}`);
1414+
* }
1415+
* ```
1416+
*/
1417+
async click(
1418+
params: McpUiClickRequest["params"],
1419+
options?: RequestOptions,
1420+
): Promise<McpUiClickResult> {
1421+
return this.request(
1422+
{
1423+
method: "ui/click" as const,
1424+
params,
1425+
},
1426+
McpUiClickResultSchema,
1427+
options,
1428+
);
1429+
}
1430+
13551431
/**
13561432
* Connect to the Guest UI via transport and optionally set up message forwarding.
13571433
*

src/app.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ import { PostMessageTransport } from "./message-transport";
2828
import {
2929
LATEST_PROTOCOL_VERSION,
3030
McpUiAppCapabilities,
31+
McpUiClickRequest,
32+
McpUiClickRequestSchema,
33+
McpUiClickResult,
3134
McpUiUpdateModelContextRequest,
3235
McpUiHostCapabilities,
3336
McpUiHostContext,
@@ -43,6 +46,9 @@ import {
4346
McpUiResourceTeardownRequest,
4447
McpUiResourceTeardownRequestSchema,
4548
McpUiResourceTeardownResult,
49+
McpUiScreenshotRequest,
50+
McpUiScreenshotRequestSchema,
51+
McpUiScreenshotResult,
4652
McpUiSizeChangedNotification,
4753
McpUiToolCancelledNotification,
4854
McpUiToolCancelledNotificationSchema,
@@ -731,6 +737,77 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
731737
);
732738
}
733739

740+
/**
741+
* Handler for screenshot requests from the host.
742+
*
743+
* Set this property to register a handler that captures the current visual
744+
* state of the App. The handler should render the App and return the image
745+
* data as a base64-encoded string.
746+
*
747+
* @param callback - Async function that captures the screenshot
748+
*
749+
* @example Capture a screenshot of the App
750+
* ```typescript
751+
* app.onscreenshot = async (params, extra) => {
752+
* const canvas = await html2canvas(document.body);
753+
* const dataUrl = canvas.toDataURL(params.format ?? "image/png", params.quality);
754+
* const [mimeType, data] = dataUrl.replace("data:", "").split(";base64,");
755+
* return { data, mimeType, width: canvas.width, height: canvas.height };
756+
* };
757+
* ```
758+
*
759+
* @see {@link McpUiScreenshotRequest} for the request structure
760+
* @see {@link McpUiScreenshotResult} for the result structure
761+
*/
762+
set onscreenshot(
763+
callback: (
764+
params: McpUiScreenshotRequest["params"],
765+
extra: RequestHandlerExtra,
766+
) => Promise<McpUiScreenshotResult>,
767+
) {
768+
this.setRequestHandler(McpUiScreenshotRequestSchema, (request, extra) =>
769+
callback(request.params, extra),
770+
);
771+
}
772+
773+
/**
774+
* Handler for click simulation requests from the host.
775+
*
776+
* Set this property to register a handler that simulates a click at the
777+
* specified coordinates in the App. The handler should dispatch appropriate
778+
* mouse events to the target element.
779+
*
780+
* @param callback - Async function that simulates the click
781+
*
782+
* @example Handle click simulation
783+
* ```typescript
784+
* app.onclick = async ({ x, y, type = "click", button = "left" }, extra) => {
785+
* const element = document.elementFromPoint(x, y);
786+
* if (!element) {
787+
* return { success: false, isError: true };
788+
* }
789+
* element.dispatchEvent(new MouseEvent(type, {
790+
* clientX: x, clientY: y, bubbles: true, cancelable: true,
791+
* button: button === "left" ? 0 : button === "right" ? 2 : 1
792+
* }));
793+
* return { success: true, targetElement: element.tagName.toLowerCase() };
794+
* };
795+
* ```
796+
*
797+
* @see {@link McpUiClickRequest} for the request structure
798+
* @see {@link McpUiClickResult} for the result structure
799+
*/
800+
set onclick(
801+
callback: (
802+
params: McpUiClickRequest["params"],
803+
extra: RequestHandlerExtra,
804+
) => Promise<McpUiClickResult>,
805+
) {
806+
this.setRequestHandler(McpUiClickRequestSchema, (request, extra) =>
807+
callback(request.params, extra),
808+
);
809+
}
810+
734811
/**
735812
* Convenience handler for tool call requests from the host.
736813
*

src/generated/schema.json

Lines changed: 152 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: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)