Files over SFTP
Let an agent list, read, write and move files on your server while Sallyport keeps the private key.
On this page
Every SSH host you connect is a file server for your agents too. Sallyport signs in with your key, does the file operation and returns the result. The agent never sees the key, and every operation lands in the journal.
Set it up
There is nothing more to add. Connect the SSH host once: the same connection, key and host key serve its commands and its files. The server needs its SFTP subsystem, which OpenSSH turns on by default.
Choose which operations wait for you
| Operation | Class |
|---|---|
list, stat, read | Read. A read of a private key, .env, a password file or a path with .. counts as a write. |
write, mkdir, rename | Write |
overwrite, delete, rmdir | Destructive |
File operations run without asking by default. Set each class on the agent’s page in Agents, as for commands. The approval card shows the host, the operation and the path, and for a write the text that goes into the file. Bytes that are not text show as their size and SHA-256.
Use it from an agent
list_access names the host’s two file tools. _sftp_read takes reads only, so a client can allow it once. _sftp_write takes every operation.
| Argument | Meaning |
|---|---|
op | list, stat, read, write, overwrite, mkdir, rename, delete or rmdir. Required. |
path | The file or directory: absolute, or relative to the login directory. Required. |
to | rename: the new path. A rename never replaces a file that is already there. |
offset, length | read: the part of the file to read. |
content, content_base64 | write, overwrite: the file as text, or its bytes in base64. write makes a new file and never replaces one that is there; overwrite replaces the whole file, one card for a person to approve. |
intent | One sentence on why, shown to the person who approves. |
call_id | Continues a call that waits for approval. |
max_inline_bytes | How much comes in the answer. 24 KiB by default; fetch_body reads the rest. |
_sftp_write{
"op": "write",
"path": "upload/ledger.csv",
"content": "id,amount\n1,100.50\n",
"intent": "Send the September ledger to the bank"
}Move large files over HTTP
A file too large for one MCP call goes through the HTTP proxy with the agent’s token. The path after the connection’s id is the file’s absolute path, and the method is the operation. A download streams as the server sends it, and an upload takes up to 32 MiB.
| Request | Operation |
|---|---|
GET /p/{id}/srv/report.csv | read. Add ?offset= and &length= for a part. |
GET /p/{id}/srv/reports/ | list: a trailing slash. |
HEAD /p/{id}/srv/report.csv | stat |
PUT /p/{id}/upload/ledger.csv | write; with ?op=overwrite, overwrite. The body is the file. |
POST /p/{id}/upload/2026?op=mkdir | mkdir |
POST /p/{id}/inbox/a.xml?op=rename&to=/archive/a.xml | rename |
DELETE /p/{id}/inbox/a.xml | delete. With a trailing slash, rmdir. |
export SP_TOKEN='sp_live_…'
BINDING='<ssh-connection-id>'
curl --fail-with-body -X PUT -H "Authorization: Bearer $SP_TOKEN" \
-H 'X-Sallyport-Intent: Send the September ledger to the bank' \
--data-binary @ledger.csv \
"https://mcp.sallyport.cloud/p/$BINDING/upload/ledger.csv"
curl --fail-with-body -H "Authorization: Bearer $SP_TOKEN" \
-o ledger-copy.csv \
"https://mcp.sallyport.cloud/p/$BINDING/upload/ledger.csv"Read the result
- A read answers with
contentwhen the file is text andcontent_base64when it is not, and withfile_size. - A listing answers with
entries: name, type, size, mode and time of change.statanswers with oneentry. - A refusal from the server is the answer, and nothing else was done:
no_such_file,permission_denied,already_exists,is_a_directoryorfailure, with the server’s message. On/p/it arrives as 404, 403 or 409. - Open the call in Activity to see File operation result: the target, the path and the bytes moved. The journal keeps the operation and the path, never what is in the file.