Metadata-Version: 2.4
Name: pbs-mcp
Version: 0.3.0
Summary: MCP server for Proxmox Backup Server (datastore, snapshot, GC, verify, prune)
Author-email: Ahmet Karaca <ahmet@ahmetkaraca.com>
License: GPL-3.0-or-later
Project-URL: Homepage, https://github.com/ahmetem/pbs-mcp
Project-URL: Repository, https://github.com/ahmetem/pbs-mcp
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: mcp>=1.0.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: httpx>=0.27.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Dynamic: license-file

# pbs-mcp

MCP server for **Proxmox Backup Server**. Exposes datastore status, snapshot
inventory, garbage collection, verify, and prune over the PBS REST API as
17 LLM-callable tools. Designed for the
[Model Context Protocol](https://modelcontextprotocol.io).

Türkçe için → [README.tr.md](README.tr.md)

## Why

PBS already has a polished web UI. This server is for the cases where the UI
isn't where you are — answering "is anything broken?" from a chat assistant,
or wiring PBS state into a homelab agent that schedules verifies and prunes
based on real conditions.

## Tools (17)

| # | Tool | Mode | Notes |
|---|------|------|-------|
| 1 | `pbs_health_overview` | read | One-call health report: storage, GC, verify coverage, freshness, failed tasks |
| 2 | `pbs_list_datastores` | read | Configured datastores + schedules |
| 3 | `pbs_datastore_status` | read | total / used / available bytes |
| 4 | `pbs_list_groups` | read | Per-group snapshot count, owner, corruption flag |
| 5 | `pbs_list_snapshots` | read | Newest-first with `limit`; `summary=true` for one row per group |
| 6 | `pbs_get_task_status` | read | UPID → running / OK / error |
| 7 | `pbs_get_task_log` | read | Paginate a task log, or `tail=true` for just the end |
| 8 | `pbs_list_tasks` | read | Recent tasks; full UPIDs handed out for running/failed ones |
| 9 | `pbs_gc_status` | read | Last GC stats: bytes referenced, pending, removed |
| 10 | `pbs_list_verify_jobs` | read | Scheduled verify jobs + re-verify policy |
| 11 | `pbs_prune_dry_run` | read | Preview which snapshots a retention policy would drop |
| 12 | `pbs_run_gc` | **write** | Trigger GC, returns UPID (async) |
| 13 | `pbs_run_verify` | **write** | Trigger verify, optional snapshot scope |
| 14 | `pbs_prune` | **write** | Apply retention policy |
| 15 | `pbs_protect_snapshot` | **write** | Set/clear the protected flag (prune/forget skip protected) |
| 16 | `pbs_forget_snapshot` | **write** | Delete one snapshot (corrupt cleanup) |
| 17 | `pbs_stop_task` | **write** | Abort a running task (e.g. GC stuck on slow NFS) |

Start with `pbs_health_overview` for any "is PBS okay?" question — it fetches
datastore status, GC stats, groups, snapshots, node status, and recent tasks
concurrently and condenses them into one verdict, replacing five separate
tool calls.

Write tools require both `PBS_ALLOW_WRITE=true` in the environment **and**
`confirm=true` in the call itself. Restore is intentionally out of scope —
the standard `proxmox-mcp` already handles restore from PBS via `archive=`
on the Proxmox VE side.

## Setup

### 1. Create an API token in PBS

In a shell on the PBS host (`pct enter 205` from Proxmox if PBS lives in
an LXC, otherwise just SSH):

```bash
# Generate a token under root@pam
proxmox-backup-manager user generate-token root@pam mcp

# Grant it admin on your datastore
proxmox-backup-manager acl update /datastore/<your-datastore> \
  DatastoreAdmin --auth-id 'root@pam!mcp'
```

The `generate-token` output includes a `value` field — that's the secret,
shown only once. Save it.

> Why DatastoreAdmin? PBS performs an owner check on prune. Either you give
> the token DatastoreAdmin (this), or you keep moving backup ownership with
> `change-owner` after every push. Admin scope is simpler and stays inside
> one datastore.

### 2. Configure the MCP server

```bash
git clone https://github.com/ahmetem/pbs-mcp.git
cd pbs-mcp
cp .env.example .env
# Edit .env: fill in PBS_HOST, PBS_TOKEN_ID, PBS_TOKEN_SECRET
pip install -e .
```

### 3. Register with your MCP client

For Claude Desktop, add to `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "pbs": {
      "command": "python",
      "args": ["/absolute/path/to/pbs-mcp/pbs_mcp.py"]
    }
  }
}
```

Restart the client. The `pbs_*` tools should appear.

## Safety model

* **Read-only by default.** Out of the box `PBS_ALLOW_WRITE=false`, so every
  state-changing tool refuses regardless of `confirm`.
* **Two-key requirement on writes.** `PBS_ALLOW_WRITE=true` opens the door;
  each call still needs `confirm=true`. Two independent toggles, two
  intentional actions.
* **Async tasks return UPIDs, not results.** `run_gc` and `run_verify` kick
  off work and hand back a UPID immediately. Poll with `get_task_status`.
  This prevents the MCP request from blocking for hours.
* **Token scoped to one datastore.** ACLs live at `/datastore/<name>`, not
  at `/`. A leaked token can't read PBS user lists or remote sync configs.

## Notes / gotchas

* **First-call cache lag**: PBS caches ACLs for a few seconds. GET requests
  retry once automatically after a short delay on 403 / connect errors, so
  a freshly granted token usually just works. If it still fails, wait a few
  seconds and retry.
* **Token vs user permissions**: PBS API tokens get the intersection of the
  parent user's ACLs and the token's ACLs. With `root@pam!mcp` the parent
  is unrestricted, so only the token's ACL matters in practice.
* **Self-signed cert**: PBS ships with a self-signed cert. The default
  `PBS_VERIFY_TLS=false` is fine for a LAN setup. For real CAs, set
  `PBS_VERIFY_TLS=true` and `PBS_CA_BUNDLE` to a PEM file.
* **UPIDs are tied to their creator**: a UPID created by a now-deleted user
  becomes unreadable. Don't recycle PBS users while there are pending tasks.

## Development

```bash
pip install -e .[dev]
pytest
```

Tests stub the HTTP layer — no PBS instance needed.

## License

GPL-3.0-or-later. See [LICENSE](LICENSE).
