Skip to content

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

Open
antonpk1 wants to merge 2 commits intomainfrom
feat/download-file
Open

feat: add ui/download-file method for host-mediated file downloads#475
antonpk1 wants to merge 2 commits intomainfrom
feat/download-file

Conversation

@antonpk1
Copy link
Collaborator

@antonpk1 antonpk1 commented Feb 19, 2026

What

Add a new ui/download-file JSON-RPC request that allows MCP App views to request file downloads through the host.

Why

MCP Apps run in sandboxed iframes where direct file downloads are blocked (allow-downloads is not set). Based on developer feedback, many apps need file export capabilities — visualization tools (SVG/PNG export), document editors, data analysis tools, and any app producing downloadable artifacts. ui/download-file provides a host-mediated mechanism for this, consistent with the existing ui/open-link pattern.

Design: MCP Resource Types

Following feedback to reuse existing MCP primitives, the params use standard MCP resource types instead of custom fields:

params: {
  contents: (EmbeddedResource | ResourceLink)[]
}
  • EmbeddedResource — Inline content via resource.text (UTF-8) or resource.blob (base64). The host creates a blob and triggers the download directly.
  • ResourceLink — A reference URI the host resolves via resources/read on the MCP server. This avoids passing large content through postMessage and lets hosts show resource "bubbles" or use native download mechanisms.

This approach:

  • Reuses existing MCP primitives (EmbeddedResource, ResourceLink) instead of inventing new content formats
  • Enables hosts to present downloads as resource bubbles (VS Code pattern) rather than just triggering saves
  • Supports ResourceLink for cases where the host can fetch directly, avoiding unnecessary data transfer through the iframe

Changes

Spec (specification/draft/apps.mdx)

  • Add ui/download-file request to "Requests (View → Host)" section with examples for both EmbeddedResource and ResourceLink
  • Add downloadFile capability to HostCapabilities
  • Document host behavior: confirmation dialog, filename derivation, base64 decoding, ResourceLink resolution via resources/read

SDK

  • spec.types.ts: McpUiDownloadFileRequest uses (EmbeddedResource | ResourceLink)[], McpUiDownloadFileResult, downloadFile in McpUiHostCapabilities, DOWNLOAD_FILE_METHOD constant
  • app.ts: App.downloadFile() method with JSDoc examples for embedded text, embedded blob, and resource link
  • app-bridge.ts: AppBridge.ondownloadfile setter with JSDoc example showing both resource types
  • types.ts: Re-exports, AppRequest/AppResult union additions
  • generate-schemas.ts: Added EmbeddedResourceSchema, ResourceLinkSchema to external type list
  • generated/: Auto-generated Zod schemas using SDK schema imports

Example

  • integration-server: Two side-by-side demo buttons:
    • Embedded — Downloads inline JSON via EmbeddedResource (text content)
    • Link — Downloads a server-hosted resource via ResourceLink (host resolves via resources/read)
  • integration-server/server.ts: Registers a sample downloadable resource at resource:///sample-report.txt

Protocol

// EmbeddedResource (inline content)
{
  jsonrpc: "2.0", id: 1,
  method: "ui/download-file",
  params: {
    contents: [{
      type: "resource",
      resource: {
        uri: "file:///export.json",
        mimeType: "application/json",
        text: "{ \"data\": 42 }"
      }
    }]
  }
}

// ResourceLink (host resolves via resources/read)
{
  jsonrpc: "2.0", id: 1,
  method: "ui/download-file",
  params: {
    contents: [{
      type: "resource_link",
      uri: "resource:///report.pdf",
      name: "Q4 Report",
      mimeType: "application/pdf"
    }]
  }
}

// Success: { jsonrpc: "2.0", id: 1, result: {} }
// Error:   { jsonrpc: "2.0", id: 1, error: { code: -32000, message: "Download denied by user" } }

App SDK Usage

// EmbeddedResource (inline)
await app.downloadFile({
  contents: [{
    type: "resource",
    resource: { uri: "file:///export.json", mimeType: "application/json", text: data },
  }],
});

// ResourceLink (host resolves)
await app.downloadFile({
  contents: [{
    type: "resource_link",
    uri: "resource:///report.pdf",
    name: "Q4 Report",
    mimeType: "application/pdf",
  }],
});

Security

  • Host SHOULD show confirmation dialog (same pattern as ui/open-link)
  • Host SHOULD sanitize filenames to prevent path traversal
  • Host MAY impose size limits
  • No allow-downloads on iframe — download is always host-mediated
  • For ResourceLink, host resolves via MCP resources/read (no direct HTTP fetch from iframe)

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.
@antonpk1 antonpk1 marked this pull request as ready for review February 19, 2026 22:30
@antonpk1 antonpk1 requested a review from ochafik February 19, 2026 22:30
@antonpk1
Copy link
Collaborator Author

cc curious what do you guys think about it? @idosal @liady

@antonpk1 antonpk1 requested a review from idosal February 19, 2026 22:31
@ochafik
Copy link
Contributor

ochafik commented Feb 20, 2026

cc/ @connor4312 @nickcoai for input too :-)

@connor4312
Copy link

connor4312 commented Feb 20, 2026

This is kind of neat

  • I think in VS Code I would not implement this as a direct download but instead have it create a "resource" bubble similar to resources that come from tool calls. Users could view or save that as they wish. That may inform bikeshedding on method naming :)
  • postMessage can transfer arraybuffers. The API for doing so is kind of 'weird' especially when we're trying to say we speak JSON-RPC, but that could be a consideration if we start thinking about performance with larger file downloads.
  • 💡 I wonder if we want to make params be an object like { contents: (MCP.ResourceLink | MCP.EmbeddedResource)[]; }. If the resource is embedded, great (params is aready a pseudo-embedded resource.) If it's a link, then the host can retrieve it on the app's behalf. In the case of a link to an https or file scheme, the hosts can use existing semantics to have the user agent/device fetch/copy them directly, avoiding the need to retrieve and then pass data through the host entirely.

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.
@antonpk1
Copy link
Collaborator Author

thank you @connor4312
using resources here is really good idea, updated the PR :)

Copy link

@connor4312 connor4312 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm I think :)

* For `EmbeddedResource` with `blob`, host MUST decode the content from base64 before creating the file.
* For `ResourceLink`, host MAY fetch the resource on behalf of the app or open the URI directly.
* Host MAY reject the download based on security policy, file size limits, or user preferences.
* Host SHOULD sanitize filenames to prevent path traversal.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is no longer relevant in the resources world

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

Comments