restic-defensive-mcp
Máy chủ MCP stdio chỉ đọc về mặt cấu trúc để kiểm tra các kho lưu trữ restic. Kho lưu trữ và thông tin xác thực được niêm phong khi khởi động; chỉ một tập hợp con cố định các lệnh con restic có thể chạy (snapshots/ls/find/stats). Không có shell, không có backup/restore/forget/prune.
Tài liệu
restic-defensive-mcp
Structurally read-only MCP server (stdio) for inspecting restic repositories through an untrusted MCP client without exposing a shell, free-form CLI, or mutation path.
Are my backups present and recent, is their metadata readable, and does a snapshot contain the files I expect?
Why not expose restic directly?
Restic is a powerful backup tool. An unrestricted caller can:
- delete retention history (
forget/prune) - overwrite or extract data (
backup/restore/dump) - unlock or rewrite repository state
- accept arbitrary repository URLs and password commands
Thin wrappers that expose "run restic with these args" or per-call repository overrides recreate that blast radius inside MCP.
This project is different by construction:
- Repositories are declared and sealed at process start
- MCP surface is exclusively read-only (no feature flag for writes)
- Only a compiled-in subset of restic subcommands can be built
- No shell:
exec.CommandContextwith fixed argv only - Hosts, tags, paths, and result sizes are bounded
- Secrets come from permission-checked files, never
RESTIC_PASSWORD_COMMAND - Errors are structured and redacted
- Every tool reports an explicit cost class (
light/moderate/expensive)
Requirements
- Go 1.25+ (build)
- restic 0.17.1+ on
PATH(orrestic_binaryin config); tested with 0.19.x - An MCP client that supports local stdio servers
Install (from source)
git clone https://github.com/ThomasCrouzet/restic-defensive-mcp.git
cd restic-defensive-mcp
make build
./bin/restic-defensive-mcp --version
Quick start
- Create password and repository location files (regular files, no symlinks). Avoid placing the password in shell history:
secret_dir="${XDG_CONFIG_HOME:-$HOME/.config}/restic-defensive-mcp"
install -d -m 700 "$secret_dir"
install -m 600 /dev/null "$secret_dir/repository"
printf '%s\n' '/path/to/your/repo' > "$secret_dir/repository"
install -m 600 /dev/null "$secret_dir/password"
"${EDITOR:-vi}" "$secret_dir/password"
-
Copy and edit
config.example.yaml, including the two file paths above. -
Run the freshly built binary:
./bin/restic-defensive-mcp --config /path/to/config.yaml
- Point your MCP host at the binary with
--config(stdio transport only).
Example MCP host snippet (illustrative):
{
"mcpServers": {
"restic-defensive": {
"command": "/absolute/path/to/restic-defensive-mcp",
"args": ["--config", "/etc/restic-defensive-mcp/config.yaml"]
}
}
}
Tools (exactly seven)
| Tool | Cost | Purpose |
|---|---|---|
restic_capabilities | light | Versions, limits, backends, warnings |
list_repositories | light | Configured IDs and allowlists (no URLs) |
list_snapshots | light | Bounded snapshot list |
get_snapshot | light | One snapshot metadata |
browse_snapshot | moderate | Directory listing inside allowlisted path |
find_files | moderate–expensive | Simple glob find (not arbitrary regex) |
repository_stats | moderate–expensive | Size/count stats for allowlisted modes |
There is no run_restic, backup, restore, forget, prune, unlock,
check --read-data, or repository URL argument.
Full request/response shapes: docs/tool-contract.md.
Configuration
See config.example.yaml. Principles:
- MCP callers pass only
repository_id - Prefer
repository_file+password_fileover inline secrets - Repository locations and backend credentials are loaded and sealed at boot; changing their source files does not retarget a running server
allowed_hosts/allowed_tags/allowed_pathsare enforcement, not cosmetics- Empty allowlists mean no restriction for that dimension; the server logs a
boot-time warning (
empty_allowlist) so operators notice - Unknown YAML keys and multiple YAML documents are rejected
- Secret files must be regular, non-symlink files; Unix requires mode
0600or stricter, while Windows deployments must enforce equivalent NTFS ACLs find_filesrequires an explicitpathwhen multiple roots are allowlisted- Runtime limits: zeros become defaults; values outside absolute min/max are rejected (not silently clamped). Defaults are the recommended starting point; values may be raised only up to those ceilings
Supported backends (v0.1)
| Backend | Supported |
|---|---|
| local filesystem | yes (absolute paths only) |
| S3 | yes (credentials via allowlisted env files) |
| Backblaze B2 | yes |
| REST server | yes |
| SFTP | no (spawns ssh) |
| rclone | no (spawns rclone) |
| other | no |
Compatibility notes: docs/restic-compatibility.md.
Local effects: cache and locks
Inspection is not a pure no-op on disk or on the repository lock table:
- restic may create or update a local cache (
cache_diror default) - restic may take a repository lock during snapshots/ls/find/stats
This server never intentionally mutates backup contents or snapshot sets.
It also never runs full data verification (restic check --read-data).
repository_stats answers size/count questions only.
Details: SECURITY.md.
Privacy limits
Snapshot file names, paths, hosts, tags, sizes, and timestamps are sensitive. This server:
- never returns file contents
- never returns repository URLs or secret file paths
- bounds listings and find results
- sanitizes control characters in names
- keeps audit logs free of full paths by default
Demo (local temporary repository)
make demo
Runs the end-to-end harness against a throwaway restic repository created by
internal/testrepo. It exercises every MCP tool, proves path denial and the
absence of mutation tools, then removes the fixture. Repository operations stay
local; normal Go module downloads may still occur on a fresh checkout.
Development
make fmt
make lint
make test # unit tests
make race # race detector
make fuzz-smoke # short run of every fuzz target
make integration # real restic temp repo (requires restic)
make vet
Dependencies
| Module | Why |
|---|---|
github.com/modelcontextprotocol/go-sdk | Official MCP Go SDK (stdio server/client) |
gopkg.in/yaml.v3 | Config file parsing |
Restic itself is an external binary, not a Go module.
Non-goals
- Backup orchestration or scheduling
- Restore / forget / prune / unlock / repair
- Generic restic CLI proxy
- Autorestic config import
- Remote multi-tenant API
- Full
restic check --read-dataas a casual tool - Telemetry or auto-update
License
MIT, see LICENSE.