siftp

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

  1. 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.
  2. Read the llms_txt URL it gives (or /llms.txt for the latest release).
  3. GET /api/v1/openapi.json (no auth): every route and schema, including the error-code enum.
  4. Act with the admin token: REST, or MCP over POST /mcp or siftpd 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=/'
RoutePurpose
GET/POST /api/v1/usersList, create (stored durably)
GET/PUT/DELETE /api/v1/users/{name}Read, replace (omitted fields kept), delete (files kept)
POST /api/v1/users/{name}/passwordSet, 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/verifyAudit tail as {"lines": [...]}, oldest first, each line a JSON string to parse; chain check
GET /api/v1/stats, GET /metricsCounters as JSON, Prometheus text
POST /api/v1/reloadReload 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

ToolDoes
siftp_capabilitiesThe capability document
siftp_list_users, siftp_get_userRead users
siftp_create_user, siftp_update_user, siftp_delete_userManage users
siftp_set_passwordSet or rotate a password
siftp_list_filesList a directory in a user's root
siftp_list_sessions, siftp_kill_sessionSessions
siftp_tail_audit, siftp_verify_auditAudit log
siftp_statsCounters
siftp_reload_configReload
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