Files and downloads
How to declare a file field with FileRef, and how to use injectFiles and injectDownload in a widget.
Files move in two directions. A different host supports each direction.
- On the server, a tool declares a file field with the
FileRefschema. The host then knows that the field is a file. It can show attach controls and previews. - In the view,
injectFiles()sends files to the host and gets files from the host.injectDownload()tells the host to save resource contents to the user's device.
The two hosts are very different here. Read the host support matrix before you use these functions.
FileRef in a tool schema
FileRef is a Zod object. Put it in an inputSchema or an outputSchema.
import { FileRef } from "ng-mcp-ui/server";
server.registerTool(
{
name: "summarize_document",
inputSchema: { document: FileRef },
},
async ({ document }) => {
const res = await fetch(document.download_url);
// …
},
);
| Field | Type | Notes |
|---|---|---|
file_id |
string | The identifier of the file on the host. |
download_url |
string | The URL that gives the file contents. |
mime_type |
string, optional | |
file_name |
string, optional |
The package exports FileRef as a value and as a type. Thus you can use the same name in a type
position.
injectFiles
injectFiles() gives you three functions. Each function operates on the current host.
import { injectFiles } from "ng-mcp-ui/web";
const files = injectFiles();
await files.upload(file); // → FileMetadata
await files.getDownloadUrl(metadata); // → { downloadUrl }
await files.selectFiles(); // → FileMetadata[]
| Function | Signature |
|---|---|
upload |
(file: File, options?: { library?: boolean }) => Promise<FileMetadata> |
getDownloadUrl |
(file: FileMetadata) => Promise<{ downloadUrl: string }> |
selectFiles |
() => Promise<FileMetadata[]> |
FileMetadata has this shape: { fileId, fileName?, mimeType? }.
Set library: true in the upload options. The host then saves the file to the user's library, if
the host has this function.
Only an Apps SDK host supports these three functions. On an MCP Apps host, all three functions
throw an error. Put the calls in a try/catch block. Do not assume that the host has these
functions.
selectFiles also throws an error on an Apps SDK host that is too old:
selectFiles is not supported by the current host version.
After an upload, and after a successful selection, the Apps SDK adaptor writes the file identifiers into the host view state. The identifiers stay available after a new render.
You must call injectFiles() from an Angular
injection context.
injectDownload
injectDownload() gives you a download function. The function tells the host to save MCP
resource contents.
import { injectDownload } from "ng-mcp-ui/web";
const { download } = injectDownload();
const result = await download({
contents: [{ type: "resource_link", uri: "https://…/report.pdf", name: "report.pdf" }],
});
if (result.isError) {
// The host refused the download, or the host does not have this function.
}
The contents field takes MCP EmbeddedResource blocks or ResourceLink blocks. The function
does not throw an error. It resolves to { isError?: boolean }. Thus you test a value, and you do
not catch an exception.
download is the opposite of injectFiles. Only an MCP Apps host supports it. The host must also
tell the adaptor that it has the downloadFile function. If the host does not have this function,
the adaptor writes a log message and resolves to { isError: true }.
Host support
| Apps SDK | MCP Apps | |
|---|---|---|
upload |
Supported | Throws an error |
getDownloadUrl |
Supported | Throws an error |
selectFiles |
Supported. Throws an error on an old host | Throws an error |
download |
Writes a log message, returns { isError: true } |
Supported, if the host has downloadFile |
No host supports both sets of functions. A widget that needs files on the two runtimes must
therefore have two code paths. Use a try/catch block and a fallback path. Do not identify the
host by its name.