Agents and MCP
Everything an operator can do through the admin API, an agent can do through MCP, with the same JSON payloads and the same error object.
Boot sequence
GET http://<host>:<admin-port>/.well-known/siftp.json(no auth): version, protocols this server serves, and links to the docs for that exact version.- Read the
llms_txtURL it gives (or/llms.txtfor the latest release). GET /api/v1/openapi.json(no auth): every route and schema, including the error-code enum.- Act with the admin token: REST, or MCP over
POST /mcporsiftpd mcp-stdio.
{"name":"siftp","version":"0.2.1","protocols":["ftp","ftps-explicit","ftps-implicit","sftp","webdav-readonly"],"docs":"https://siftp.org/v/0.2.1/docs/","llms_txt":"https://siftp.org/v/0.2.1/llms.txt","docs_latest":"https://siftp.org/docs","openapi":"/api/v1/openapi.json","mcp":"/mcp","metrics":"/metrics","health":"/healthz","ready":"/readyz"}
Errors
{"code":"permission_denied","message":"user 'ops' is defined in the config file /etc/siftp/siftp.toml and is read-only through the API","hint":"edit the [[users]] entry in the config file and reload, or manage a different user name through the API","docs_url":"https://siftp.org/docs/errors#permission_denied"}
Branch on code (ten values, Error codes); show message; do what hint says. FTP replies start with the code (550 permission_denied: ...), SFTP status messages too.
Admin API
H="Authorization: Bearer $SIFTP_ADMIN_TOKEN"
curl -H "$H" http://127.0.0.1:8080/api/v1/users
curl -H "$H" -H 'Content-Type: application/json' \
-d '{"name":"erin","password":"...","permissions":"rwl"}' \
http://127.0.0.1:8080/api/v1/users
curl -H "$H" -X POST http://127.0.0.1:8080/api/v1/users/erin/password
curl -H "$H" 'http://127.0.0.1:8080/api/v1/files?user=erin&path=/'
| Route | Purpose |
|---|---|
GET/POST /api/v1/users | List, create (stored durably) |
GET/PUT/DELETE /api/v1/users/{name} | Read, replace (omitted fields kept), delete (files kept) |
POST /api/v1/users/{name}/password | Set, or generate and return once |
GET /api/v1/sessions, DELETE /api/v1/sessions/{id} | Live sessions, terminate one |
GET /api/v1/files?user=&path= | Listing inside a user's root |
GET /api/v1/audit?lines=, GET /api/v1/audit/verify | Audit tail as {"lines": [...]}, oldest first, each line a JSON string to parse; chain check |
GET /api/v1/stats, GET /metrics | Counters as JSON, Prometheus text |
POST /api/v1/reload | Reload config, users, keys, certificate |
The OpenAPI document gives each route's parameters, request body, and response schema. password_hash is always null in responses. Users with "source": "config" are read-only; manage "source": "store" users.
MCP
| Tool | Does |
|---|---|
siftp_capabilities | The capability document |
siftp_list_users, siftp_get_user | Read users |
siftp_create_user, siftp_update_user, siftp_delete_user | Manage users |
siftp_set_password | Set or rotate a password |
siftp_list_files | List a directory in a user's root |
siftp_list_sessions, siftp_kill_session | Sessions |
siftp_tail_audit, siftp_verify_audit | Audit log |
siftp_stats | Counters |
siftp_reload_config | Reload |
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"siftp_stats","arguments":{}}}' \
| siftpd mcp-stdio --config /etc/siftp/siftp.toml
Tool results are the admin JSON as text content; failures come back with isError: true and the error object as text. Over HTTP, POST /mcp takes one JSON-RPC message per request with the admin token; wrong tokens count toward the login ban.
Checking your work
- Read back what you changed:
siftp_get_user,siftp_list_files. - Logins, transfers, file changes, and admin changes are in the audit log with
user,action,path,result(lookups are not);siftp_verify_auditconfirms the log is intact. siftp_statsshows which data path served transfers (transfers_sendfile,transfers_pool, …).