Thanks to visit codestin.com
Credit goes to github.com

Skip to content

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

Merged
antonpk1 merged 4 commits intomainfrom
feat/download-file
Feb 24, 2026
Merged

feat: add ui/download-file method for host-mediated file downloads#475
antonpk1 merged 4 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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good call — removed the two bullets that were just restating standard EmbeddedResource/ResourceLink behavior. Kept the download-specific ones (confirmation dialog, filename derivation, security policy, path sanitization). Updated in f07bdfb.

Remove bullets that restate standard EmbeddedResource/ResourceLink behavior
already defined in the MCP resource spec. Keep download-specific host behavior
(confirmation dialog, filename derivation, security policy, path sanitization).
ochafik
ochafik previously approved these changes Feb 24, 2026
Copy link
Contributor

@ochafik ochafik left a comment

Choose a reason for hiding this comment

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

Thanks @antonpk1 !

Copy link
Contributor

Choose a reason for hiding this comment

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

can you add a test to app-bridge.test.ts that exercices the two ends?

Copy link
Contributor

@ochafik ochafik left a comment

Choose a reason for hiding this comment

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

Thanks!

@antonpk1 antonpk1 merged commit e4a9766 into main Feb 24, 2026
9 of 18 checks passed

The `contents` array uses standard MCP resource types (`EmbeddedResource` and `ResourceLink`), avoiding custom content formats. For `EmbeddedResource`, content is inline via `text` (UTF-8) or `blob` (base64). For `ResourceLink`, the host can retrieve the content directly from the URI.

Host behavior:
Copy link
Contributor

Choose a reason for hiding this comment

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

@antonpk1 should we provide some guidance on payload size? postMessage can be fairly constrained (depending on both browser and device).

@idosal
Copy link
Contributor

idosal commented Feb 24, 2026

Cool @antonpk1 ! Sorry, missed this. I'm a little late to the party, but I think we might run into issues with postMessage for large files. Perhaps we need some guidance.

ochafik added a commit that referenced this pull request Feb 24, 2026
Changes since 1.1.1:
- feat: add ui/download-file method for host-mediated file downloads (#475)
- pdf-server: robust path validation + folder/file root support (#497)
- ci: auto-fix prettier formatting in pre-commit hook and CI (#498)
ochafik added a commit that referenced this pull request Feb 25, 2026
* chore: bump ext-apps to 1.1.2

Changes since 1.1.1:
- feat: add ui/download-file method for host-mediated file downloads (#475)
- pdf-server: robust path validation + folder/file root support (#497)
- ci: auto-fix prettier formatting in pre-commit hook and CI (#498)

* fix: sync local SDK build into node_modules for examples

npm workspaces hoists example dependencies to the root node_modules,
but installs the published registry copy of @modelcontextprotocol/ext-apps
instead of linking to the local source. This causes type-check failures
when examples use features not yet published to npm.

Add scripts/link-self.mjs which copies the freshly-built dist/ and
package.json into the hoisted node_modules copy after each SDK build,
ensuring examples always type-check against the latest local types.

See: npm/feedback#774

* Revert "fix: sync local SDK build into node_modules for examples"

This reverts commit e3387e8.
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.

4 participants