From f52da0f6c5b957f4af09453633b7bc6319dd9c72 Mon Sep 17 00:00:00 2001 From: ved015 Date: Fri, 3 Jul 2026 20:50:35 +0530 Subject: [PATCH 1/2] fix(mcp): show clear error for oversized file uploads The MCP transport rejects JSON-RPC bodies over 4 MiB with a bare 413, so large file uploads died with a generic "Upload failed" widget error. The upload widget now rejects oversized files at selection time with the size limit spelled out, advertises the limit in the dropzone copy, and maps any transport 413 that still occurs to the same friendly message. Co-authored-by: Cursor --- apps/mcp/src/widget/views/Upload.tsx | 65 +++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/apps/mcp/src/widget/views/Upload.tsx b/apps/mcp/src/widget/views/Upload.tsx index ed71c856a..63cd4a36c 100644 --- a/apps/mcp/src/widget/views/Upload.tsx +++ b/apps/mcp/src/widget/views/Upload.tsx @@ -29,15 +29,52 @@ function formatFileSize(bytes: number): string { const ACCEPT = ".txt,.pdf,.png,.jpg,.jpeg,.mp4" +// The MCP transport rejects JSON-RPC bodies over 4 MiB with a bare 413 +// (MAXIMUM_MESSAGE_SIZE_BYTES in the agents SDK). The file travels base64 +// encoded (~4/3 inflation) inside that body, so cap the raw file size such +// that the encoded payload plus the JSON-RPC envelope stays under the limit. +const TRANSPORT_MESSAGE_LIMIT_BYTES = 4 * 1024 * 1024 +const ENVELOPE_ALLOWANCE_BYTES = 64 * 1024 +const MAX_UPLOAD_BYTES = Math.floor( + ((TRANSPORT_MESSAGE_LIMIT_BYTES - ENVELOPE_ALLOWANCE_BYTES) * 3) / 4, +) + +const FILE_TOO_LARGE_MESSAGE = (size: number) => + `This file is ${formatFileSize(size)}. The maximum upload size is ${formatFileSize(MAX_UPLOAD_BYTES)} โ€” please choose a smaller file.` + +// The transport-level 413 surfaces as an opaque error string; translate it +// so the user sees the size limit instead of a generic failure. +function friendlyUploadError(raw: string, fileSize: number): string { + if (/413|too large|payload/i.test(raw)) { + return FILE_TOO_LARGE_MESSAGE(fileSize) + } + return raw +} + export function Upload({ activeTag, writableTags, onAdvance, onError }: Props) { const { callTool } = useApp() const log = useLog() const [file, setFile] = useState(null) + const [fileError, setFileError] = useState(null) const [selectedTag, setSelectedTag] = useState( activeTag ?? writableTags[0] ?? null, ) const [uploading, setUploading] = useState(false) + const handleFileSelect = (selected: File) => { + if (selected.size > MAX_UPLOAD_BYTES) { + log( + "warning", + `[upload] rejected oversized file: ${selected.name} (${selected.size}B > ${MAX_UPLOAD_BYTES}B)`, + ) + setFileError(FILE_TOO_LARGE_MESSAGE(selected.size)) + setFile(null) + return + } + setFileError(null) + setFile(selected) + } + const options = useMemo( () => writableTags.map((tag) => ({ value: tag, label: tag })), [writableTags], @@ -59,13 +96,17 @@ export function Upload({ activeTag, writableTags, onAdvance, onError }: Props) { }) if (!result.ok || !result.data) { log("error", `[upload] failed: ${result.error}`) - onError(result.error ?? "Upload failed") + onError( + result.error + ? friendlyUploadError(result.error, file.size) + : "Upload failed", + ) return } onAdvance(result.data) } catch (err) { log("error", `[upload] threw: ${err}`) - onError(String(err)) + onError(friendlyUploadError(String(err), file.size)) } finally { setUploading(false) } @@ -102,11 +143,21 @@ export function Upload({ activeTag, writableTags, onAdvance, onError }: Props) { /> ) : ( - + + + {fileError ? ( +

+ {fileError} +

+ ) : null} +
)} {writableTags.length > 0 ? ( From 521db2b61504f435998224c8142b631d39d500ab Mon Sep 17 00:00:00 2001 From: Ishaan Gupta Date: Sat, 4 Jul 2026 20:15:25 +0530 Subject: [PATCH 2/2] fix(mcp): use function declarations in upload widget Address Graphite style review on Upload.tsx. Co-authored-by: Cursor --- apps/mcp/src/widget/views/Upload.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/mcp/src/widget/views/Upload.tsx b/apps/mcp/src/widget/views/Upload.tsx index 63cd4a36c..6e34490b2 100644 --- a/apps/mcp/src/widget/views/Upload.tsx +++ b/apps/mcp/src/widget/views/Upload.tsx @@ -39,14 +39,15 @@ const MAX_UPLOAD_BYTES = Math.floor( ((TRANSPORT_MESSAGE_LIMIT_BYTES - ENVELOPE_ALLOWANCE_BYTES) * 3) / 4, ) -const FILE_TOO_LARGE_MESSAGE = (size: number) => - `This file is ${formatFileSize(size)}. The maximum upload size is ${formatFileSize(MAX_UPLOAD_BYTES)} โ€” please choose a smaller file.` +function fileTooLargeMessage(size: number): string { + return `This file is ${formatFileSize(size)}. The maximum upload size is ${formatFileSize(MAX_UPLOAD_BYTES)} โ€” please choose a smaller file.` +} // The transport-level 413 surfaces as an opaque error string; translate it // so the user sees the size limit instead of a generic failure. function friendlyUploadError(raw: string, fileSize: number): string { if (/413|too large|payload/i.test(raw)) { - return FILE_TOO_LARGE_MESSAGE(fileSize) + return fileTooLargeMessage(fileSize) } return raw } @@ -61,13 +62,13 @@ export function Upload({ activeTag, writableTags, onAdvance, onError }: Props) { ) const [uploading, setUploading] = useState(false) - const handleFileSelect = (selected: File) => { + function handleFileSelect(selected: File) { if (selected.size > MAX_UPLOAD_BYTES) { log( "warning", `[upload] rejected oversized file: ${selected.name} (${selected.size}B > ${MAX_UPLOAD_BYTES}B)`, ) - setFileError(FILE_TOO_LARGE_MESSAGE(selected.size)) + setFileError(fileTooLargeMessage(selected.size)) setFile(null) return }