<a download> link, a blob: URL, or a data: URL. Send each export through app.downloadFile instead.
This page is for developers whose MCP App already renders in Claude. If you don’t have an MCP App yet, start with Add an interactive UI to your MCP server.
Send a file from a view
A view is one rendered instance of your app’s UI in Claude. To save a file, callapp.downloadFile with the file’s contents in the view’s script. The user sees the Download file dialog, or Download files for several files. The dialog lists the files in your request, and the user clicks Download to start the download.
app.downloadFile requires @modelcontextprotocol/ext-apps 1.1.2 or later.
Check whether Claude offers downloads
Claude doesn’t offer downloads everywhere that your view can appear, so check each time a view starts. Afterapp.connect() resolves, read app.getHostCapabilities()?.downloadFile in the view’s script:
- If
downloadFileis set: show your export control - If
downloadFileisn’t set: offer the content another way, such as text that the user can copy
Add an export button to a view
Send the download request when the user clicks a control in the view, while the view is on screen, so that the user expects the dialog. To run this sample, add<button id="export"> and an element with id="status" to the view’s HTML. Clicks have no effect while a request is pending, and the user sees no button where Claude doesn’t offer downloads:
shopping-list.csv.
Build the contents of a download request
Put each file’s data and name inside the request’s contents, and don’t include links:
- File data: use the
textfield for text and theblobfield for binary data. Encodeblobas standard base64 with nodata:prefix. - Links: omit
resource_linkitems, because Claude doesn’t fetch them for the view. If you send onlyresource_linkitems, Claude refuses the request. If you mix them with files, Claude can also refuse the request. - File name: end
uriwith a name and an extension, such asfile:///report.csv. The last path segment becomes the saved file’s name. - Characters in the file name: write a literal space instead of
%20. Percent-encoding isn’t decoded.- Characters such as
%,:,",*,<,>,|,~, and\become_.
- Characters such as
- Several files: you can send several files in one request
Send one download request at a time
Wait for the result of one request before you send the next. If you send a second request from the same view while the Download file dialog is open, one of the two requests ends withisError: true.
Handle the result of a download request
ReadisError in the result of app.downloadFile:
false: Claude started the downloadtrue: tell the user that the download didn’t finish
- Claude refused the request
- The user canceled
- Claude closed the Download file dialog
- Claude couldn’t start the download
File types that Claude refuses
If you send a file whose name ends in an extension on Claude’s blocked list, Claude refuses the file.- Blocked: the list includes programs, installers, shortcuts, and some script types. Blocked extensions include
.exe,.bat,.js,.jar,.dmg, and.lnk. - Saved after the user confirms the download: other script types, such as
.pyand.sh
isError: true.
Troubleshoot a rejected download request
For failures outside theisError cases, the promise from app.downloadFile rejects. Read the error’s message and code.
Invalid params for ui/download-file
The error’s message includes this text when you send a malformed request. Causes include a blob that isn’t standard base64 and an item that has no uri.
Request timed out
The error’s message includes this text when the MCP Apps SDK stops waiting for a result, after 60 seconds by default. Claude sets no time limit on the Download file dialog. To wait longer, pass timeout in milliseconds in the second argument to app.downloadFile, such as { timeout: 600_000 }.
After a timeout, the dialog can remain open with a working Download button. Don’t tell the user that nothing was saved.
Code -32601, Method not found
Claude doesn’t offer downloads where this view appears. Check app.getHostCapabilities()?.downloadFile before you send the request.
Next steps
- Open external links from MCP Apps: take the user to a URL outside Claude
- Design guidelines: decide which interactions belong in the app and which in the conversation
- Troubleshoot MCP Apps: open developer tools to inspect the view’s requests