Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive guide for building production-ready MCP servers with tools, resources, prompts, and React widgets using mcp-use.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/widgets/files.md
1# File Handling in Widgets23Upload and download files from within widgets using the `useFiles` hook.45> **ChatGPT Apps SDK only.** The MCP Apps spec (SEP-1865) has [deferred file handling](https://github.com/modelcontextprotocol/ext-apps/issues/201). MCP Apps clients (Claude, Goose, VS Code) do not support file operations. Always check `isSupported` before calling `upload` or `getDownloadUrl`.67---89## `useFiles` Hook1011```tsx12import { useFiles } from "mcp-use/react";1314const { upload, getDownloadUrl, isSupported } = useFiles();15```1617| Property | Type | Description |18|----------|------|-------------|19| `isSupported` | `boolean` | `true` only in ChatGPT Apps SDK. Always check before calling upload/getDownloadUrl. |20| `upload` | `(file: File, options?) => Promise<FileMetadata>` | Upload a file. Model-visible by default. |21| `getDownloadUrl` | `(file: FileMetadata) => Promise<{ downloadUrl: string }>` | Get a temporary download URL (~5 min). |2223`FileMetadata` type: `{ fileId: string }`2425---2627## Basic Pattern2829Always guard with `isSupported`. Render a fallback for MCP Apps clients:3031```tsx32import { useFiles, useWidget, McpUseProvider } from "mcp-use/react";33import { useState } from "react";3435export default function FileWidget() {36const { isPending } = useWidget();37const { upload, getDownloadUrl, isSupported } = useFiles();38const [fileId, setFileId] = useState<string | null>(null);39const [downloadUrl, setDownloadUrl] = useState<string | null>(null);4041if (isPending) return <McpUseProvider autoSize><div>Loading...</div></McpUseProvider>;4243// Renders in MCP Apps clients (Claude, Goose, etc.)44if (!isSupported) {45return (46<McpUseProvider autoSize>47<p>File operations require ChatGPT Apps SDK.</p>48</McpUseProvider>49);50}5152async function handleUpload(e: React.ChangeEvent<HTMLInputElement>) {53const file = e.target.files?.[0];54if (!file) return;55const { fileId } = await upload(file);56setFileId(fileId);57}5859async function handleGetLink() {60if (!fileId) return;61const { downloadUrl } = await getDownloadUrl({ fileId });62setDownloadUrl(downloadUrl);63}6465return (66<McpUseProvider autoSize>67<input type="file" onChange={handleUpload} />68{fileId && <button onClick={handleGetLink}>Get download link</button>}69{downloadUrl && <a href={downloadUrl} target="_blank">Download</a>}70</McpUseProvider>71);72}73```7475---7677## Model Visibility7879By default, uploaded files are tracked in widget state under `imageIds` so ChatGPT includes them in the model's conversation context.8081```tsx82// Model-visible (default) — ChatGPT includes the file in context83const { fileId } = await upload(file);8485// Private — file is uploaded but model won't see it86const { fileId } = await upload(file, { modelVisible: false });87```8889Use `modelVisible: false` when the file is for widget-internal use only (e.g. a reference image the widget processes, a config file the user uploads privately).9091When `modelVisible: true` (default), the new `fileId` is appended to `imageIds` in widget state, preserving any existing IDs and other state fields.9293---9495## Storing fileId for Later9697Download URLs are **temporary** (~5 minutes). Store the `fileId`, not the URL:9899```tsx100const { state, setState } = useWidget<{ uploadedFileId: string | null }>();101102async function handleUpload(file: File) {103const { fileId } = await upload(file);104// Persist fileId in widget state so model can reference the upload105await setState({ uploadedFileId: fileId });106}107108async function handleDownload() {109if (!state?.uploadedFileId) return;110// Call getDownloadUrl fresh each time — don't cache the URL111const { downloadUrl } = await getDownloadUrl({ fileId: state.uploadedFileId });112window.open(downloadUrl, "_blank");113}114```115116---117118## Error Handling119120Both `upload` and `getDownloadUrl` throw if called when `isSupported` is `false`. They also throw on host-level errors (network failure, policy violation):121122```tsx123try {124const { fileId } = await upload(file);125setFileId(fileId);126} catch (err) {127console.error("Upload failed:", err);128setError(err instanceof Error ? err.message : "Upload failed");129}130```131132---133134## Key Rules135136- **Always check `isSupported` first** — render a fallback UI for non-ChatGPT hosts137- **Never cache download URLs** — they expire; call `getDownloadUrl` each time you need one138- **Store `fileId` in widget state** if the user may need to re-download later139- **Use `{ modelVisible: false }`** for files the model should not see140- `upload` + `getDownloadUrl` are only available in ChatGPT Apps SDK — MCP Apps support is deferred141142---143144## Reference145146- Full API reference: https://docs.mcp-use.com/typescript/server/widget-components/usefiles147- Example server: `examples/server/ui/files/`148