Send a file
upload and download print a one-time browser URL. The person uses their own machine. You never need their local disk, and you should not paste binaries into chat.
There is no upload --to. The path is the first argument. It is relative to the current working directory and must stay under $HOME.
MCP write_file / read_file are UTF-8 text only. Use transfer for CSVs, zips, images, and anything awkward to paste.
Done looks like
- In:
upload pollprints success and the file exists at the path you named. - Out: the person opens the URL once and the browser saves the file (or a
.tar.gzfor a directory). No poll step.
Worked example: get a spreadsheet into the repo
Job: a human has q2-leads.csv on their laptop. You need it at inbox/q2-leads.csv.
upload inbox/q2-leads.csv
Expected output (shape, not exact host):
Open this URL in your browser to upload (repository members):
https://…/sails/transfer/…
Target: ~/inbox/q2-leads.csv
Transfer session: 3f2a9c1e-…
- Send that URL to the person.
- They sign in as a repository member and drop the file.
- You wait:
upload poll 3f2a9c1e-…
When poll returns success, the file is in the live workspace and autosave has it. Then you can read it, publish it, or hand it to another command.
Do not pass --wait from bash_exec. That call buffers until the upload finishes, so you never see the URL. Print the URL, then upload poll ID.
Worked example: hand someone a report
download sites/sails/pages/blog/launch.page.md
That prints a one-time URL. First successful GET downloads the file and consumes the link. There is no download poll.
A directory is packed as gzip tar:
download ./site
# browser saves site.tar.gz
Upload a folder
upload assets --dir
upload poll TRANSFER_ID
The browser can pick a folder, or they can upload a .tar / .tar.gz / .tgz and it extracts into assets.
Flags
| Command | What it does |
|---|---|
upload PATH | One-time URL for a single file at PATH |
upload PATH --dir | One-time URL for a directory (folder picker or archive) |
upload poll ID | Block until that upload finishes, fails, or expires |
upload PATH --wait | Block in one command — avoid inside bash_exec |
download PATH | One-time URL; directories become .tar.gz |
Common flag: --timeout SECS (default 900). After that the session expires; run the command again.
upload help
download help
Failure modes
| What you see | What it means | What to do |
|---|---|---|
| Browser asks to sign in / 403 | Transfer URLs are for repository members | They must be signed in as a member of this repo |
| Link dead on second open | Single-use (download completes on first GET) | Run upload / download again |
upload poll times out | Nobody finished the browser step, or --timeout elapsed | New upload PATH, new URL |
| Path rejected | Outside $HOME / the active repo mount | Use a path under the workspace |
| File missing after “I uploaded” | You never polled, or they hit a different session | Confirm the transfer id from the upload output, then upload poll |
| 100 MiB exceeded | Hard limit | Split the archive, or fetch a public URL with curl -o instead |
You used upload --to DIR | That flag does not exist | upload DIR/filename or upload DIR --dir |
When not to use this
| Job | Use instead |
|---|---|
| Small text edit already in chat | MCP write_file / edit_file |
| File is already on the public internet | curl -o path https://… |
| API key, token, DSN | secret open — see Store a secret |
| Public URL for a folder | Publish a site |
| Another repository should copy a folder | Share a directory with another repo |
See File transfer.