# 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`](/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`.

```json
{"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

```json
{"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](/docs/errors.html)); show
`message`; do what `hint` says. FTP replies start with the code
(`550 permission_denied: ...`), SFTP status messages too.

## Admin API

```sh
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 |

```sh
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_audit` confirms the log is intact.
- `siftp_stats` shows which data path served transfers
  (`transfers_sendfile`, `transfers_pool`, …).
