refactor: use MCP resource types for ui/download-file params

Replace custom {filename, content, mimeType, encoding} params with
standard MCP resource types: EmbeddedResource for inline content and
ResourceLink for host-resolved references.

New params shape:
  params: { contents: (EmbeddedResource | ResourceLink)[] }

This reuses existing MCP primitives instead of inventing new ones,
and enables hosts to show resource 'bubbles' or resolve ResourceLink
URIs via resources/read on the MCP server.
This commit is contained in:
Anton Pidkuiko MacBook 2026-02-20 12:00:35 +00:00
commit 3757271641
No known key found for this signature in database

View file

@ -621,35 +621,37 @@ export class AppBridge extends Protocol<
* Register a handler for file download requests from the View.
*
* The View sends `ui/download-file` requests when the user wants to
* download a file. The host should show a confirmation dialog and then
* trigger the download.
* download a file. The params contain an array of MCP resource content
* items — either `EmbeddedResource` (inline data) or `ResourceLink`
* (URI the host can fetch). The host should show a confirmation dialog
* and then trigger the download.
*
* @param callback - Handler that receives download params and returns a result
* - `params.filename` - Suggested filename for the download
* - `params.content` - File content (text or base64-encoded)
* - `params.mimeType` - MIME type of the file
* - `params.encoding` - Content encoding ("utf-8" or "base64", defaults to "utf-8")
* - `params.contents` - Array of `EmbeddedResource` or `ResourceLink` items
* - `extra` - Request metadata (abort signal, session info)
* - Returns: `Promise<McpUiDownloadFileResult>` with optional `isError` flag
*
* @example
* ```ts
* bridge.ondownloadfile = async ({ filename, content, mimeType, encoding }, extra) => {
* const confirmed = await showDialog({
* message: `Download "${filename}"?`,
* buttons: ["Download", "Cancel"],
* });
* if (!confirmed) return { isError: true };
*
* const blob = encoding === "base64"
* ? new Blob([Uint8Array.from(atob(content), c => c.charCodeAt(0))], { type: mimeType })
* : new Blob([content], { type: mimeType });
* const url = URL.createObjectURL(blob);
* const link = document.createElement("a");
* link.href = url;
* link.download = filename;
* link.click();
* URL.revokeObjectURL(url);
* bridge.ondownloadfile = async ({ contents }, extra) => {
* for (const item of contents) {
* if (item.type === "resource") {
* // EmbeddedResource — inline content
* const res = item.resource;
* const blob = res.blob
* ? new Blob([Uint8Array.from(atob(res.blob), c => c.charCodeAt(0))], { type: res.mimeType })
* : new Blob([res.text ?? ""], { type: res.mimeType });
* const url = URL.createObjectURL(blob);
* const link = document.createElement("a");
* link.href = url;
* link.download = res.uri.split("/").pop() ?? "download";
* link.click();
* URL.revokeObjectURL(url);
* } else if (item.type === "resource_link") {
* // ResourceLink — host fetches or opens directly
* window.open(item.uri, "_blank");
* }
* }
* return {};
* };
* ```