Upload one or more files to Clueso. Pick a mode by client + where the file lives:
0. **files** — ChatGPT only: files the user attached in the conversation. ChatGPT fills each entry (download_url + file_id) itself; pass the attachments here rather than asking the user to re-upload. Returns one mcp_upload_id per file.
1. **file_name** — HOSTED upload, the default for any non-UI / programmatic upload (Claude Code, Cursor, Claude Desktop, scripts). Returns an upload URL on Clueso's OWN base domain + a ready-to-run curl that streams a single local file to it; Clueso relays the bytes to storage server-side. The PUT targets the base domain — NOT cloud storage directly — so it works on desktop/agent clients that can't reach or are blocked from S3. Requirement: the client must be able to PUT bytes to the Clueso base domain (run the returned curl, or any HTTP PUT). The agent (or the user at a shell prompt) runs the curl. Prefer this whenever there's no human at a browser.
**file_names** (without request_hosted_upload) is the same for many files in ONE call: a token and URL per name plus ONE `curl` that sends them all. Run it from the folder the names are relative to (for edit_animation `upload_ids`, the folder holding manifest.json, names like 'entries/city.js').
2. **file_url**: Pass a public https URL. Server fetches and stages the file. Returns mcp_upload_id immediately. Use when the file is already on the open web — no user interaction needed.
3. **request_hosted_upload** (UI mode — use ONLY when a human should pick files in a browser: many files at once, or a host with no shell / no PUT capability): Returns a single upload_token + upload_page URL. Share the link with the user; they open it in a new browser tab, drop their files, click Done. Then call check_uploads(upload_token) to retrieve all mcp_upload_ids. Call once for all files.
Hosted uploads cover any number of files per call: one call issues one upload_token, and that token covers every file the user drops on the page. Repeat calls issue additional tokens, each tracking only its own files.
The returned mcp_upload_id (prefixed `mup_`) can be passed to:
- add_elements / update_elements (image or video → an element ON a clip: pass it as `type_data.mcp_upload_id`, on either tool — this is how a local image becomes on-canvas content, and how an existing element's source is swapped). To fill an animation's image slot, pass it inside `type_data.parameter_values` on update_elements only — parameter_values is an update-path field and is stripped on add.
- add_audio (audio → project music track that plays under all clips)
- add_clips(kind='video') (video or audio → sequential clip with auto-transcription)
- add_clips(kind='pptx') (.ppt/.pptx → slide clips)
- add_article_media (image/GIF → article asset)
- analyze_audio (audio → transcript / silences / beats / features / energy / sections)
- edit_animation (a .js or .json file → `files: [{path, upload_id}]`, or `upload_ids` when each upload is named by its path, so a big animation file never goes inline)
**purpose: 'font'** (with file_name, file_names, file_url or files; display_name optional) adds the file to this workspace's brand fonts once it arrives: the curl's reply (this reply for file_url and files, check_uploads for the hosted page) carries `font: {name, family, format, converted_from}` or `font_error`. TTF, OTF, WOFF or WOFF2; WOFF and WOFF2 are stored as TTF so the renderer and text measurer read them. The name is display_name, else the file's family without build dates or versions ("Cursor Mono 260406" → "Cursor Mono"); use it exactly in font_setting.font and animation manifests. One face per name; a name already in the workspace is refused. Needs an editor or admin, on a plan with custom fonts.