Skip to main content
Glama
borgbackup

borg-mcp

Official
by borgbackup
README.md
<p align="center">
  <img src="assets/logo.svg" alt="borg-mcp" width="480">
</p>

A **read-only [MCP](https://modelcontextprotocol.io/) server** for
[BorgBackup](https://www.borgbackup.org/) operational status.

borg-mcp lets AI agents (Claude, and other MCP clients) answer questions like
*"is my backup fresh?"*, *"how big is the repo?"*, *"what would prune remove?"*
— without any ability to modify or destroy backups.

It is a companion project to [borg](https://github.com/borgbackup/borg),
born from [borg#9955](https://github.com/borgbackup/borg/issues/9955).
It is intentionally **not** part of borg core: the safety boundary for
untrusted AI agents lives in this small, auditable project.

**Status: pre-alpha, under development.** Requires borg2 (no borg 1.x support).

## Security model

The MCP client (the agent) is treated as **untrusted input** — it may be
prompt-injected or simply wrong. Therefore:

- **Command allowlist, not passthrough.** Only a fixed set of borg
  subcommands with fixed argument templates is ever executed. No
  agent-supplied flags, no shell.
- **Repository allowlist.** Repositories are configured server-side with
  aliases; the agent refers to `"home"`, never to a raw path or URL.
- **No secrets over MCP.** Passphrases come from configured passcommands on
  the server side and never appear in tool results or logs.
- **Sanitized, size-capped output.** Raw file listings are off by default
  (explicit config opt-in); all listings are paginated. borg's stderr is
  sanitized before an error reaches the agent: tracebacks are withheld
  (logged server-side instead), the passcommand line is redacted, and
  control characters are stripped.
- **Read-only by construction — and by enforcement.** borg-mcp never runs
  prune/delete/compact/repair/restore. For defense in depth, run it against
  repositories accessed via an SSH key that is restricted server-side to
  read-only `borg serve`, and run borg-mcp itself as an unprivileged user.
- **Audit log.** Every tool invocation is logged.

Never in scope: executing destructive operations, key export, passphrase
handling over MCP, arbitrary borg commands.

## Requirements

- Python >= 3.11
- borg2, installed from the
  [master branch](https://github.com/borgbackup/borg) (borg-mcp drives the
  `borg` CLI; it does not import borg internals)

## Installation

There is no PyPI release; install from git:

```
pip install git+https://github.com/borgbackup/borg-mcp.git
```

or from a checkout: `pip install .`

## Configuration

TOML, at `~/.config/borg-mcp/config.toml` (or `/etc/borg-mcp.toml`).
The config file holds all security-relevant settings and must not be
writable by the agent's account — there are deliberately no CLI overrides.

```toml
[server]
allow_file_listing = false   # raw archive contents listing, off by default
timeout = 300
max_items = 1000             # pagination cap for listings

[repos.home]
location = "ssh://backup@host/./home"
description = "workstation home dirs, nightly"
passcommand = "cat /path/to/passphrase-file"
```

The passcommand must read the passphrase from somewhere safe: a file only
readable by the borg-mcp user, or a keyring/secret-service CLI. Never put
the passphrase inline (as in `passcommand = "echo secret"`): the command
line is visible in `ps` output while it runs.

## Usage

```
borg-mcp serve [--config PATH]      # run the stdio MCP server
borg-mcp check [--config PATH] [ALIAS ...]
                                    # validate config; with aliases, test
                                    # repository access
```

MCP client configuration (e.g. Claude Code `.mcp.json`):

```json
{
  "mcpServers": {
    "borg": {
      "command": "borg-mcp",
      "args": ["serve", "--config", "/path/to/config.toml"]
    }
  }
}
```

## Tools

| tool | answers |
|---|---|
| `list_repositories` | which repositories can I ask about? |
| `repo_info` | repository ID, size, encryption mode |
| `list_archives` | which archives exist? (paginated, filterable) |
| `archive_info` | stats, duration, hostname for one archive |
| `latest_archive` | is my backup fresh? |
| `prune_preview` | what *would* a prune with these keep rules remove? |

`prune_preview` never deletes anything: `--dry-run` is part of the command
template and cannot be influenced by the client, and the test suite asserts
that every prune command borg-mcp can construct contains it.

These tools exist only when `allow_file_listing = true`, because they
disclose the names of the backed up files:

| tool | answers |
|---|---|
| `list_archive_contents` | which files are in this archive? |
| `diff_archives` | which files changed between two archives? |

## License

BSD-3-Clause, see [LICENSE](LICENSE) and [AUTHORS](AUTHORS).