{"record":{"id":"b3b6f815fae1ecfe","repo":"payloadcms/payload","slug":"staged-upload-not-found-complete-the-upload-actio","errorCode":null,"errorMessage":"Staged upload not found. Complete the upload action first, or use base64 for small local files.","messagePattern":"Staged upload not found\\. Complete the upload action first, or use base64 for small local files\\.","errorType":"validation","errorClass":"APIError","httpStatus":400,"severity":"error","filePath":"packages/plugin-mcp/src/mcp/builtin/collections/fileInput.ts","lineNumber":61,"sourceCode":"export async function resolveFile({\n  collectionSlug,\n  input,\n  req,\n}: {\n  collectionSlug: CollectionSlug\n  input?: FileInput\n  req: PayloadRequest\n}): Promise<File | undefined> {\n  if (!input) {\n    return undefined\n  }\n\n  if (input.source === 'uploadReference') {\n    try {\n      return await getFileFromUploadInstructions({ collectionSlug, file: input.file, req })\n    } catch (error) {\n      if (error instanceof Error && error.message === 'Staged upload was not found.') {\n        throw new APIError(\n          'Staged upload not found. Complete the upload action first, or use base64 for small local files.',\n          400,\n        )\n      }\n      throw error\n    }\n  }\n\n  const uploadConfig = req.payload.collections[collectionSlug]?.config.upload\n\n  if (!uploadConfig) {\n    throw new APIError(`Collection \"${collectionSlug}\" does not support file uploads.`, 400)\n  }\n\n  const maxFileSize = req.payload.config.upload.limits?.fileSize\n  let file: File\n\n  if (input.source === 'base64') {","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/payloadcms/payload/blob/00c58b35c0ed348ddc22daabf467b139727214fd/packages/plugin-mcp/src/mcp/builtin/collections/fileInput.ts#L43-L79","documentation":"The MCP file-input resolver throws this 400 when `source: 'uploadReference'` is used but `getFileFromUploadInstructions` cannot locate a staged upload matching the supplied `file.uploadReference`. It is a deliberate re-wrap of the internal 'Staged upload was not found.' error so the caller knows the two-step upload flow was not completed.","triggerScenarios":"Calling an MCP upload tool with `source: 'uploadReference'` before the dispatch PUT to the signed URL has finished; passing an `uploadReference` whose staged prefix was already consumed or whose SAS token expired; passing an uploadReference produced for collection A to a tool operating on collection B.","commonSituations":"Forgetting the dispatch step after `generateUploadInstructions`; calling the create/update MCP tool twice with the same reference (second call finds nothing); Azure SAS token older than 3 hours; cross-collection upload-reference reuse.","solutions":["Complete the dispatch upload (PUT the bytes to the signed URL returned by `generateUploadInstructions`) before calling the MCP tool with that `uploadReference`","For small local files, switch to `source: 'base64'` which is single-step","Regenerate upload instructions if the signed URL has expired and obtain a fresh `uploadReference`","Verify the `uploadReference.prefix` matches the collection the tool is operating on"],"exampleFix":"// before — uploadReference supplied before the dispatch PUT\n{ source: 'uploadReference', file: { filename, mimeType, size, uploadReference: { prefix } } }\n// after — small file via base64, single step\n{ source: 'base64', name: 'logo.png', mimeType: 'image/png', data: base64String }","handlingStrategy":"validation","validationCode":"// Verify the dispatch PUT succeeded before passing an uploadReference\nasync function ensureDispatched(signedUrl: string, bytes: Buffer) {\n  const res = await fetch(signedUrl, { method: 'PUT', body: bytes })\n  if (!res.ok) throw new Error(`dispatch upload failed: ${res.status}`)\n}","typeGuard":null,"tryCatchPattern":"import { APIError } from 'payload'\ntry {\n  await tool.call({ source: 'uploadReference', file })\n} catch (e) {\n  if (e instanceof APIError && e.statusCode === 400 && /Staged upload not found/.test(e.message)) {\n    // fall back to base64 for small files\n    return tool.call({ source: 'base64', name: file.filename, mimeType: file.mimeType, data: bytes.toString('base64') })\n  }\n  throw e\n}","preventionTips":["Always run the dispatch PUT and confirm a 2xx before referencing the staged upload","Treat uploadReference as single-use — do not replay it","Default to base64 for files under a few hundred KB to avoid the two-step flow","Regenerate upload instructions if more than ~3 hours pass (Azure SAS window)"],"tags":["mcp","file-upload","upload-reference","azure","gcs","validation"],"backgroundTag":null,"analyzedSha":"00c58b35c0ed348ddc22daabf467b139727214fd","analyzedAt":"2026-08-12T20:45:03.758Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}