feat: add ui/download-file method for host-mediated file downloads

Add a new ui/download-file JSON-RPC request that allows MCP App views to
request file downloads through the host. Since MCP Apps run in sandboxed
iframes where direct downloads are blocked (allow-downloads not set),
this provides a host-mediated mechanism for file exports.

Spec changes (draft):
- Add ui/download-file request to Requests (View → Host) section
- Add downloadFile capability to HostCapabilities
- Document host behavior requirements (confirmation dialog, filename
  sanitization, base64 decoding, size limits)

SDK changes:
- Add McpUiDownloadFileRequest/Result interfaces to spec.types.ts
- Add downloadFile() method to App class
- Add ondownloadfile setter to AppBridge class
- Add DOWNLOAD_FILE_METHOD constant
- Generate Zod schemas

Example:
- Add download demo to integration-server (exports JSON with server time)
- Capability check: only shows download button when host advertises
  downloadFile capability

Based on developer feedback requesting file export capabilities for
visualization tools, document editors, and data analysis apps.
This commit is contained in:
Anton Pidkuiko MacBook 2026-02-19 21:25:48 +00:00
commit 494c73e7ca
No known key found for this signature in database

View file

@ -33,6 +33,8 @@ import {
McpUiMessageResultSchema,
McpUiOpenLinkRequest,
McpUiOpenLinkResultSchema,
McpUiDownloadFileRequest,
McpUiDownloadFileResultSchema,
McpUiResourceTeardownRequest,
McpUiResourceTeardownRequestSchema,
McpUiResourceTeardownResult,
@ -930,6 +932,59 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
/** @deprecated Use {@link openLink `openLink`} instead */
sendOpenLink: App["openLink"] = this.openLink;
/**
* Request the host to download a file.
*
* Since MCP Apps run in sandboxed iframes where direct downloads are blocked,
* this provides a host-mediated mechanism for file exports. The host will
* typically show a confirmation dialog before initiating the download.
*
* @param params - File content, filename, MIME type, and optional encoding
* @param options - Request options (timeout, etc.)
* @returns Result with `isError: true` if the host denied the request (e.g., user cancelled)
*
* @throws {Error} If the request times out or the connection is lost
*
* @example Download a JSON export
* ```ts
* const data = JSON.stringify({ items: selectedItems }, null, 2);
* const { isError } = await app.downloadFile({
* filename: "export.json",
* content: data,
* mimeType: "application/json",
* });
* if (isError) {
* console.warn("Download denied or cancelled");
* }
* ```
*
* @example Download binary content (base64)
* ```ts
* const { isError } = await app.downloadFile({
* filename: "image.png",
* content: base64EncodedPng,
* mimeType: "image/png",
* encoding: "base64",
* });
* ```
*
* @see {@link McpUiDownloadFileRequest `McpUiDownloadFileRequest`} for request structure
* @see {@link McpUiDownloadFileResult `McpUiDownloadFileResult`} for result structure
*/
downloadFile(
params: McpUiDownloadFileRequest["params"],
options?: RequestOptions,
) {
return this.request(
<McpUiDownloadFileRequest>{
method: "ui/download-file",
params,
},
McpUiDownloadFileResultSchema,
options,
);
}
/**
* Request a change to the display mode.
*