veeam-aiops 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- veeam_aiops-0.1.0/.gitignore +22 -0
- veeam_aiops-0.1.0/LICENSE +21 -0
- veeam_aiops-0.1.0/PKG-INFO +68 -0
- veeam_aiops-0.1.0/README.md +52 -0
- veeam_aiops-0.1.0/RELEASE_NOTES.md +22 -0
- veeam_aiops-0.1.0/SECURITY.md +68 -0
- veeam_aiops-0.1.0/mcp_server/__init__.py +1 -0
- veeam_aiops-0.1.0/mcp_server/_shared.py +96 -0
- veeam_aiops-0.1.0/mcp_server/server.py +34 -0
- veeam_aiops-0.1.0/mcp_server/tools/__init__.py +1 -0
- veeam_aiops-0.1.0/mcp_server/tools/backups.py +19 -0
- veeam_aiops-0.1.0/mcp_server/tools/jobs.py +127 -0
- veeam_aiops-0.1.0/mcp_server/tools/repositories.py +19 -0
- veeam_aiops-0.1.0/mcp_server/tools/restore.py +39 -0
- veeam_aiops-0.1.0/mcp_server/tools/sessions.py +35 -0
- veeam_aiops-0.1.0/pyproject.toml +59 -0
- veeam_aiops-0.1.0/server.json +21 -0
- veeam_aiops-0.1.0/skills/veeam-aiops/SKILL.md +158 -0
- veeam_aiops-0.1.0/skills/veeam-aiops/references/capabilities.md +67 -0
- veeam_aiops-0.1.0/skills/veeam-aiops/references/cli-reference.md +56 -0
- veeam_aiops-0.1.0/skills/veeam-aiops/references/setup-guide.md +77 -0
- veeam_aiops-0.1.0/tests/test_smoke.py +227 -0
- veeam_aiops-0.1.0/veeam_aiops/__init__.py +9 -0
- veeam_aiops-0.1.0/veeam_aiops/cli/__init__.py +9 -0
- veeam_aiops-0.1.0/veeam_aiops/cli/_common.py +78 -0
- veeam_aiops-0.1.0/veeam_aiops/cli/_root.py +56 -0
- veeam_aiops-0.1.0/veeam_aiops/cli/backup.py +27 -0
- veeam_aiops-0.1.0/veeam_aiops/cli/doctor.py +21 -0
- veeam_aiops-0.1.0/veeam_aiops/cli/job.py +85 -0
- veeam_aiops-0.1.0/veeam_aiops/cli/repository.py +27 -0
- veeam_aiops-0.1.0/veeam_aiops/cli/restore.py +55 -0
- veeam_aiops-0.1.0/veeam_aiops/cli/session.py +37 -0
- veeam_aiops-0.1.0/veeam_aiops/config.py +131 -0
- veeam_aiops-0.1.0/veeam_aiops/connection.py +195 -0
- veeam_aiops-0.1.0/veeam_aiops/doctor.py +65 -0
- veeam_aiops-0.1.0/veeam_aiops/governance/__init__.py +40 -0
- veeam_aiops-0.1.0/veeam_aiops/governance/audit.py +377 -0
- veeam_aiops-0.1.0/veeam_aiops/governance/budget.py +225 -0
- veeam_aiops-0.1.0/veeam_aiops/governance/decorators.py +474 -0
- veeam_aiops-0.1.0/veeam_aiops/governance/paths.py +23 -0
- veeam_aiops-0.1.0/veeam_aiops/governance/patterns.py +378 -0
- veeam_aiops-0.1.0/veeam_aiops/governance/policy.py +411 -0
- veeam_aiops-0.1.0/veeam_aiops/governance/sanitize.py +39 -0
- veeam_aiops-0.1.0/veeam_aiops/governance/undo.py +218 -0
- veeam_aiops-0.1.0/veeam_aiops/ops/__init__.py +1 -0
- veeam_aiops-0.1.0/veeam_aiops/ops/backups.py +24 -0
- veeam_aiops-0.1.0/veeam_aiops/ops/jobs.py +63 -0
- veeam_aiops-0.1.0/veeam_aiops/ops/repositories.py +24 -0
- veeam_aiops-0.1.0/veeam_aiops/ops/restore.py +49 -0
- veeam_aiops-0.1.0/veeam_aiops/ops/sessions.py +44 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.pytest_cache/
|
|
6
|
+
.ruff_cache/
|
|
7
|
+
|
|
8
|
+
# Virtual envs / build
|
|
9
|
+
.venv/
|
|
10
|
+
dist/
|
|
11
|
+
build/
|
|
12
|
+
|
|
13
|
+
# uv
|
|
14
|
+
uv.lock
|
|
15
|
+
|
|
16
|
+
# Local config / secrets (never commit)
|
|
17
|
+
*.env
|
|
18
|
+
.env
|
|
19
|
+
config.yaml
|
|
20
|
+
|
|
21
|
+
# OS
|
|
22
|
+
.DS_Store
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wei <zhouwei008@gmail.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: veeam-aiops
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Veeam Backup & Replication AI-powered backup operations with a built-in governance harness (audit, budget, undo, risk tiers)
|
|
5
|
+
Author-email: wei <zhouwei008@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: httpx<1.0,>=0.27
|
|
10
|
+
Requires-Dist: mcp[cli]<2.0,>=1.10
|
|
11
|
+
Requires-Dist: python-dotenv<2.0,>=1.0
|
|
12
|
+
Requires-Dist: pyyaml<7.0,>=6.0
|
|
13
|
+
Requires-Dist: rich<16.0,>=13.0
|
|
14
|
+
Requires-Dist: typer<1.0,>=0.12
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
<!-- mcp-name: io.github.AIops-tools/veeam-aiops -->
|
|
18
|
+
|
|
19
|
+
# Veeam AIops (preview)
|
|
20
|
+
|
|
21
|
+
> **Disclaimer**: Community-maintained open-source project. **Not affiliated with, endorsed by, or sponsored by Veeam Software.** "Veeam" is a trademark of its owner. MIT licensed.
|
|
22
|
+
|
|
23
|
+
AI-powered Veeam Backup & Replication operations with a **built-in governance
|
|
24
|
+
harness** — unified audit log, policy engine, token/runaway budget guard,
|
|
25
|
+
undo-token recording, and graduated-autonomy risk tiers. Self-contained: no
|
|
26
|
+
external dependencies beyond `httpx` and the MCP SDK. Preview — not yet full
|
|
27
|
+
coverage of every Veeam operation.
|
|
28
|
+
|
|
29
|
+
## What works
|
|
30
|
+
|
|
31
|
+
- **CLI** (`veeam-aiops ...`): `job list/get/start/stop/enable/disable`, `restore list-points/start`, `repository list`, `session list/get`, `backup list`, `doctor`, `mcp`.
|
|
32
|
+
- **MCP server** (`veeam-aiops mcp` or `veeam-aiops-mcp`): **12 tools** (8 read, 4 write), every one wrapped with the bundled `@governed_tool` harness.
|
|
33
|
+
- **Reversibility**: write ops with a clean inverse (job start/stop, enable/disable) record an inverse undo descriptor; the irreversible VM restore declares none and is tagged `high` risk.
|
|
34
|
+
- **Async sessions**: Veeam jobs and restores run as sessions — poll progress with `session list` / `session get` (the runaway budget guard prevents poll loops from running away).
|
|
35
|
+
|
|
36
|
+
## Quick start
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
uv tool install veeam-aiops
|
|
40
|
+
mkdir -p ~/.veeam-aiops
|
|
41
|
+
# create ~/.veeam-aiops/config.yaml with a targets: list
|
|
42
|
+
# put passwords in ~/.veeam-aiops/.env (chmod 600)
|
|
43
|
+
veeam-aiops doctor
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Example `~/.veeam-aiops/config.yaml`:
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
targets:
|
|
50
|
+
- name: vbr-lab
|
|
51
|
+
host: 10.0.0.20
|
|
52
|
+
username: "DOMAIN\\backup-admin"
|
|
53
|
+
port: 9419
|
|
54
|
+
verify_ssl: false # self-signed lab certs only
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`~/.veeam-aiops/.env` (chmod 600): `VEEAM_VBR_LAB_PASSWORD=<password>`
|
|
58
|
+
|
|
59
|
+
## Audit & safety
|
|
60
|
+
|
|
61
|
+
All operations are logged to a local SQLite audit DB under `~/.veeam-aiops/`
|
|
62
|
+
(relocatable via `VEEAM_AIOPS_HOME`). Every write tool passes through the
|
|
63
|
+
governance harness: policy pre-check, token/runaway budget guard, graduated
|
|
64
|
+
risk-tier gate, and audit logging. Destructive CLI commands (`job stop`,
|
|
65
|
+
`restore start`) require double confirmation and support `--dry-run`.
|
|
66
|
+
API-returned text is run through a prompt-injection sanitizer.
|
|
67
|
+
|
|
68
|
+
License: MIT.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<!-- mcp-name: io.github.AIops-tools/veeam-aiops -->
|
|
2
|
+
|
|
3
|
+
# Veeam AIops (preview)
|
|
4
|
+
|
|
5
|
+
> **Disclaimer**: Community-maintained open-source project. **Not affiliated with, endorsed by, or sponsored by Veeam Software.** "Veeam" is a trademark of its owner. MIT licensed.
|
|
6
|
+
|
|
7
|
+
AI-powered Veeam Backup & Replication operations with a **built-in governance
|
|
8
|
+
harness** — unified audit log, policy engine, token/runaway budget guard,
|
|
9
|
+
undo-token recording, and graduated-autonomy risk tiers. Self-contained: no
|
|
10
|
+
external dependencies beyond `httpx` and the MCP SDK. Preview — not yet full
|
|
11
|
+
coverage of every Veeam operation.
|
|
12
|
+
|
|
13
|
+
## What works
|
|
14
|
+
|
|
15
|
+
- **CLI** (`veeam-aiops ...`): `job list/get/start/stop/enable/disable`, `restore list-points/start`, `repository list`, `session list/get`, `backup list`, `doctor`, `mcp`.
|
|
16
|
+
- **MCP server** (`veeam-aiops mcp` or `veeam-aiops-mcp`): **12 tools** (8 read, 4 write), every one wrapped with the bundled `@governed_tool` harness.
|
|
17
|
+
- **Reversibility**: write ops with a clean inverse (job start/stop, enable/disable) record an inverse undo descriptor; the irreversible VM restore declares none and is tagged `high` risk.
|
|
18
|
+
- **Async sessions**: Veeam jobs and restores run as sessions — poll progress with `session list` / `session get` (the runaway budget guard prevents poll loops from running away).
|
|
19
|
+
|
|
20
|
+
## Quick start
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uv tool install veeam-aiops
|
|
24
|
+
mkdir -p ~/.veeam-aiops
|
|
25
|
+
# create ~/.veeam-aiops/config.yaml with a targets: list
|
|
26
|
+
# put passwords in ~/.veeam-aiops/.env (chmod 600)
|
|
27
|
+
veeam-aiops doctor
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Example `~/.veeam-aiops/config.yaml`:
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
targets:
|
|
34
|
+
- name: vbr-lab
|
|
35
|
+
host: 10.0.0.20
|
|
36
|
+
username: "DOMAIN\\backup-admin"
|
|
37
|
+
port: 9419
|
|
38
|
+
verify_ssl: false # self-signed lab certs only
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`~/.veeam-aiops/.env` (chmod 600): `VEEAM_VBR_LAB_PASSWORD=<password>`
|
|
42
|
+
|
|
43
|
+
## Audit & safety
|
|
44
|
+
|
|
45
|
+
All operations are logged to a local SQLite audit DB under `~/.veeam-aiops/`
|
|
46
|
+
(relocatable via `VEEAM_AIOPS_HOME`). Every write tool passes through the
|
|
47
|
+
governance harness: policy pre-check, token/runaway budget guard, graduated
|
|
48
|
+
risk-tier gate, and audit logging. Destructive CLI commands (`job stop`,
|
|
49
|
+
`restore start`) require double confirmation and support `--dry-run`.
|
|
50
|
+
API-returned text is run through a prompt-injection sanitizer.
|
|
51
|
+
|
|
52
|
+
License: MIT.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Release Notes
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (preview)
|
|
4
|
+
|
|
5
|
+
Initial preview release of **veeam-aiops** — governed Veeam Backup & Replication
|
|
6
|
+
operations for AI agents.
|
|
7
|
+
|
|
8
|
+
- **12 MCP tools** (8 read, 4 write), every one wrapped with the bundled
|
|
9
|
+
`@governed_tool` governance harness (audit, policy, token/runaway budget,
|
|
10
|
+
undo-token recording, graduated risk tiers).
|
|
11
|
+
- **Backup jobs**: list, get, start, stop, enable, disable.
|
|
12
|
+
- **Restore**: list restore points, start a VM restore (high-risk skeleton).
|
|
13
|
+
- **Repositories** and **stored backups**: list.
|
|
14
|
+
- **Sessions**: list and get, for polling async job/restore progress.
|
|
15
|
+
- **CLI** (`veeam-aiops ...`) with `--dry-run` and double-confirm on destructive
|
|
16
|
+
ops, plus `doctor` and an `mcp` stdio subcommand.
|
|
17
|
+
- Veeam VBR REST API connection layer (OAuth2 password grant → bearer token,
|
|
18
|
+
`x-api-version: 1.1-rev1`, central HTTP-error translation into
|
|
19
|
+
`VeeamApiError` teaching messages).
|
|
20
|
+
- Self-contained: bundled governance harness, no external skill-family
|
|
21
|
+
dependency. Dependencies: `httpx`, `typer`, `rich`, `pyyaml`,
|
|
22
|
+
`python-dotenv`, `mcp[cli]`.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Disclaimer
|
|
4
|
+
|
|
5
|
+
Community-maintained open-source project. **Not affiliated with, endorsed by, or
|
|
6
|
+
sponsored by Veeam Software.** "Veeam" is a trademark of its owner. Source is
|
|
7
|
+
publicly auditable under the MIT license.
|
|
8
|
+
|
|
9
|
+
## Reporting Vulnerabilities
|
|
10
|
+
|
|
11
|
+
Report privately via a GitHub Security Advisory on
|
|
12
|
+
[github.com/AIops-tools/Veeam-AIops](https://github.com/AIops-tools/Veeam-AIops/security/advisories)
|
|
13
|
+
or email zhouwei008@gmail.com. Please do not open public issues for security
|
|
14
|
+
reports.
|
|
15
|
+
|
|
16
|
+
## Security Design
|
|
17
|
+
|
|
18
|
+
### Credential Management
|
|
19
|
+
- Per-target passwords live in `~/.veeam-aiops/.env` (chmod 600), never in
|
|
20
|
+
`config.yaml` and never in source. Variable pattern:
|
|
21
|
+
`VEEAM_<TARGET_NAME_UPPER>_PASSWORD`.
|
|
22
|
+
- Passwords are exchanged for a short-lived OAuth2 bearer token at connect time;
|
|
23
|
+
the token is held only in memory (side-stored by connection id, never set as
|
|
24
|
+
an attribute on the HTTP client). Secrets are never logged or echoed; the
|
|
25
|
+
config file holds only host, port, username, and TLS settings.
|
|
26
|
+
|
|
27
|
+
### Governed Operations
|
|
28
|
+
Every MCP tool runs through the bundled `@governed_tool` harness
|
|
29
|
+
(`veeam_aiops.governance`):
|
|
30
|
+
- **Audit** — every call logged to a local SQLite DB under `~/.veeam-aiops/`
|
|
31
|
+
(relocatable via `VEEAM_AIOPS_HOME`), agent-attributed, secret-redacted.
|
|
32
|
+
- **Token/runaway budget** — hard ceilings (`VEEAM_MAX_TOOL_CALLS` /
|
|
33
|
+
`VEEAM_MAX_TOOL_SECONDS`) plus an on-by-default guard that trips a tight
|
|
34
|
+
poll/retry loop, preventing unbounded API consumption (e.g. polling a slow
|
|
35
|
+
session).
|
|
36
|
+
- **Graduated risk tiers** — `~/.veeam-aiops/rules.yaml` `risk_tiers` gate
|
|
37
|
+
writes by environment/tag; the highest tiers require a recorded approver.
|
|
38
|
+
- **Undo-token recording** — reversible writes (job start/stop, enable/disable)
|
|
39
|
+
record an inverse descriptor so a change can be rolled back.
|
|
40
|
+
|
|
41
|
+
### Destructive Operations
|
|
42
|
+
`job stop` and `restore start` require double confirmation at the CLI layer and
|
|
43
|
+
support `--dry-run`. The VM restore is irreversible (overwrites/creates a VM),
|
|
44
|
+
tagged `risk_level=high`, and records no undo token.
|
|
45
|
+
|
|
46
|
+
### SSL/TLS Verification
|
|
47
|
+
`verify_ssl` defaults to true; disable only for self-signed lab certificates.
|
|
48
|
+
|
|
49
|
+
### Prompt-Injection Protection
|
|
50
|
+
All Veeam-API-returned text (job names, session results, descriptions) is passed
|
|
51
|
+
through a `sanitize()` truncate + control-character strip before reaching the
|
|
52
|
+
agent.
|
|
53
|
+
|
|
54
|
+
### Network Scope
|
|
55
|
+
No webhooks, no telemetry, no outbound calls beyond the configured Veeam B&R
|
|
56
|
+
REST API endpoint. No post-install scripts or background services.
|
|
57
|
+
|
|
58
|
+
## Static Analysis
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
uvx bandit -r veeam_aiops/ mcp_server/
|
|
62
|
+
uv run ruff check .
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Supported Versions
|
|
66
|
+
|
|
67
|
+
The latest released version receives security fixes. This is a preview (0.x);
|
|
68
|
+
pin a version in production.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""MCP server package for veeam-aiops."""
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Shared MCP server primitives: the FastMCP instance, connection helper,
|
|
2
|
+
error sanitisation, and the ``@tool_errors`` decorator.
|
|
3
|
+
|
|
4
|
+
Tool modules under ``mcp_server/tools/`` import ``mcp`` from here and register
|
|
5
|
+
their ``@mcp.tool()`` functions onto it. ``mcp_server/server.py`` then imports
|
|
6
|
+
those modules and runs the server.
|
|
7
|
+
|
|
8
|
+
Keep ``Optional[X]`` (never PEP 604 ``X | None``) in any FastMCP-reflected
|
|
9
|
+
tool signature — on older mcp/pydantic the union eval'd to ``types.UnionType``
|
|
10
|
+
crashes FastMCP's ``issubclass`` check.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import functools
|
|
14
|
+
import logging
|
|
15
|
+
import os
|
|
16
|
+
from collections.abc import Callable
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any, Optional
|
|
19
|
+
|
|
20
|
+
from mcp.server.fastmcp import FastMCP
|
|
21
|
+
|
|
22
|
+
from veeam_aiops.config import load_config
|
|
23
|
+
from veeam_aiops.connection import ConnectionManager, VeeamApiError
|
|
24
|
+
from veeam_aiops.governance import sanitize
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
_DOCTOR_HINT = "Run 'veeam-aiops doctor' to verify connectivity and credentials."
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _safe_error(exc: Exception, tool: str) -> str:
|
|
32
|
+
"""Return an agent-safe error string; log full detail server-side only."""
|
|
33
|
+
logger.error("Tool %s failed", tool, exc_info=True)
|
|
34
|
+
_passthrough = (
|
|
35
|
+
ValueError,
|
|
36
|
+
FileNotFoundError,
|
|
37
|
+
KeyError,
|
|
38
|
+
PermissionError,
|
|
39
|
+
TimeoutError,
|
|
40
|
+
ConnectionError,
|
|
41
|
+
VeeamApiError,
|
|
42
|
+
)
|
|
43
|
+
if isinstance(exc, _passthrough):
|
|
44
|
+
return sanitize(str(exc), 300)
|
|
45
|
+
return f"{type(exc).__name__}: operation failed."
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def tool_errors(shape: str = "dict") -> Callable:
|
|
49
|
+
"""Wrap a tool body in the canonical try/except → ``_safe_error`` pattern.
|
|
50
|
+
|
|
51
|
+
Place this *between* ``@governed_tool`` and the function so the audit
|
|
52
|
+
decorator and FastMCP still see the original signature.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def decorator(func: Callable) -> Callable:
|
|
56
|
+
name = func.__name__
|
|
57
|
+
|
|
58
|
+
@functools.wraps(func)
|
|
59
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
60
|
+
try:
|
|
61
|
+
return func(*args, **kwargs)
|
|
62
|
+
except Exception as e: # noqa: BLE001 — sanitised below
|
|
63
|
+
msg = _safe_error(e, name)
|
|
64
|
+
if shape == "list":
|
|
65
|
+
return [{"error": msg, "hint": _DOCTOR_HINT}]
|
|
66
|
+
if shape == "str":
|
|
67
|
+
return f"Error: {msg} {_DOCTOR_HINT}"
|
|
68
|
+
return {"error": msg, "hint": _DOCTOR_HINT}
|
|
69
|
+
|
|
70
|
+
return wrapper
|
|
71
|
+
|
|
72
|
+
return decorator
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
mcp = FastMCP(
|
|
76
|
+
"veeam-aiops",
|
|
77
|
+
instructions=(
|
|
78
|
+
"Veeam Backup & Replication operations (preview): backup jobs "
|
|
79
|
+
"(list/get, start/stop, enable/disable), restore points + VM restore, "
|
|
80
|
+
"backup repositories, stored backups, and async sessions for polling "
|
|
81
|
+
"job/restore progress. Every tool runs through the veeam-aiops governance "
|
|
82
|
+
"harness (audit / budget / risk-tier / undo)."
|
|
83
|
+
),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
_conn_mgr: Optional[ConnectionManager] = None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _get_connection(target: Optional[str] = None) -> Any:
|
|
90
|
+
"""Return a Veeam connection, lazily initialising the manager."""
|
|
91
|
+
global _conn_mgr # noqa: PLW0603
|
|
92
|
+
if _conn_mgr is None:
|
|
93
|
+
config_path_str = os.environ.get("VEEAM_AIOPS_CONFIG")
|
|
94
|
+
config_path = Path(config_path_str) if config_path_str else None
|
|
95
|
+
_conn_mgr = ConnectionManager(load_config(config_path))
|
|
96
|
+
return _conn_mgr.connect(target)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""MCP server wrapping Veeam AIops operations (stdio transport).
|
|
2
|
+
|
|
3
|
+
Thin adapter layer: each ``@mcp.tool()`` function (in ``mcp_server/tools/``)
|
|
4
|
+
delegates to the ``veeam_aiops`` ops package and is wrapped with the
|
|
5
|
+
veeam-aiops ``@governed_tool`` harness (audit / budget / undo / risk-tier).
|
|
6
|
+
|
|
7
|
+
Standalone, self-governed Veeam Backup & Replication operations (preview).
|
|
8
|
+
For Veeam Backup & Replication only.
|
|
9
|
+
|
|
10
|
+
Source: https://github.com/AIops-tools/Veeam-AIops
|
|
11
|
+
License: MIT
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
|
|
16
|
+
from mcp_server._shared import _safe_error, mcp, tool_errors
|
|
17
|
+
|
|
18
|
+
# Importing the tool modules registers every @mcp.tool() onto the shared
|
|
19
|
+
# `mcp` instance. Order does not matter; each module is self-contained.
|
|
20
|
+
from mcp_server.tools import ( # noqa: F401 — side effects
|
|
21
|
+
backups,
|
|
22
|
+
jobs,
|
|
23
|
+
repositories,
|
|
24
|
+
restore,
|
|
25
|
+
sessions,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
__all__ = ["mcp", "main", "_safe_error", "tool_errors"]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def main() -> None:
|
|
32
|
+
"""Run the MCP server over stdio."""
|
|
33
|
+
logging.basicConfig(level=logging.INFO)
|
|
34
|
+
mcp.run(transport="stdio")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""MCP tool modules. Importing each registers its @mcp.tool() functions."""
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Stored-backup MCP tools (read-only)."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from mcp_server._shared import _get_connection, mcp, tool_errors
|
|
6
|
+
from veeam_aiops.governance import governed_tool
|
|
7
|
+
from veeam_aiops.ops import backups as ops
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@mcp.tool()
|
|
11
|
+
@governed_tool(risk_level="low")
|
|
12
|
+
@tool_errors("list")
|
|
13
|
+
def backup_list(target: Optional[str] = None) -> list:
|
|
14
|
+
"""[READ] List stored backups with id, name, type, creationTime.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
target: Veeam target name from config; omit to use the default.
|
|
18
|
+
"""
|
|
19
|
+
return ops.list_backups(_get_connection(target))
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Backup job MCP tools: list/get, start/stop, enable/disable.
|
|
2
|
+
|
|
3
|
+
Every tool is wrapped with ``@governed_tool`` (the veeam-aiops harness):
|
|
4
|
+
policy pre-check, budget/runaway guard, graduated-autonomy risk-tier gate,
|
|
5
|
+
audit logging to ~/.veeam-aiops/audit.db, and undo-token recording. Write tools
|
|
6
|
+
with a clean inverse pass an ``undo=`` lambda so the harness records a reversal
|
|
7
|
+
descriptor to the undo store (start↔stop, enable↔disable).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
from mcp_server._shared import _get_connection, mcp, tool_errors
|
|
13
|
+
from veeam_aiops.governance import governed_tool
|
|
14
|
+
from veeam_aiops.ops import jobs as ops
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@mcp.tool()
|
|
18
|
+
@governed_tool(risk_level="low")
|
|
19
|
+
@tool_errors("list")
|
|
20
|
+
def job_list(target: Optional[str] = None) -> list:
|
|
21
|
+
"""[READ] List backup jobs with id, name, type, status, lastResult.
|
|
22
|
+
|
|
23
|
+
Use job_get for full detail of a single job.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
target: Veeam target name from config; omit to use the default.
|
|
27
|
+
"""
|
|
28
|
+
return ops.list_jobs(_get_connection(target))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@mcp.tool()
|
|
32
|
+
@governed_tool(risk_level="low")
|
|
33
|
+
@tool_errors("dict")
|
|
34
|
+
def job_get(job_id: str, target: Optional[str] = None) -> dict:
|
|
35
|
+
"""[READ] Return detail for a single backup job by id.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
job_id: Veeam job id (see job_list).
|
|
39
|
+
target: Veeam target name from config.
|
|
40
|
+
"""
|
|
41
|
+
return ops.get_job(_get_connection(target), job_id)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@mcp.tool()
|
|
45
|
+
@governed_tool(
|
|
46
|
+
risk_level="medium",
|
|
47
|
+
undo=lambda params, result: {
|
|
48
|
+
"tool": "job_stop",
|
|
49
|
+
"params": {"job_id": params.get("job_id")},
|
|
50
|
+
"skill": "veeam-aiops",
|
|
51
|
+
"note": "Inverse of job_start: stop the running job.",
|
|
52
|
+
},
|
|
53
|
+
)
|
|
54
|
+
@tool_errors("dict")
|
|
55
|
+
def job_start(job_id: str, target: Optional[str] = None) -> dict:
|
|
56
|
+
"""[WRITE] Start a backup job. Runs as an async session. Inverse: job_stop.
|
|
57
|
+
|
|
58
|
+
Poll progress with session_list / session_get; do not re-issue.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
job_id: Veeam job id.
|
|
62
|
+
target: Veeam target name from config.
|
|
63
|
+
"""
|
|
64
|
+
return ops.start_job(_get_connection(target), job_id)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@mcp.tool()
|
|
68
|
+
@governed_tool(
|
|
69
|
+
risk_level="medium",
|
|
70
|
+
undo=lambda params, result: {
|
|
71
|
+
"tool": "job_start",
|
|
72
|
+
"params": {"job_id": params.get("job_id")},
|
|
73
|
+
"skill": "veeam-aiops",
|
|
74
|
+
"note": "Inverse of job_stop: start the job again.",
|
|
75
|
+
},
|
|
76
|
+
)
|
|
77
|
+
@tool_errors("dict")
|
|
78
|
+
def job_stop(job_id: str, target: Optional[str] = None) -> dict:
|
|
79
|
+
"""[WRITE] Stop a running backup job. Inverse: job_start.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
job_id: Veeam job id.
|
|
83
|
+
target: Veeam target name from config.
|
|
84
|
+
"""
|
|
85
|
+
return ops.stop_job(_get_connection(target), job_id)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@mcp.tool()
|
|
89
|
+
@governed_tool(
|
|
90
|
+
risk_level="medium",
|
|
91
|
+
undo=lambda params, result: {
|
|
92
|
+
"tool": "job_disable",
|
|
93
|
+
"params": {"job_id": params.get("job_id")},
|
|
94
|
+
"skill": "veeam-aiops",
|
|
95
|
+
"note": "Inverse of job_enable: disable the job again.",
|
|
96
|
+
},
|
|
97
|
+
)
|
|
98
|
+
@tool_errors("dict")
|
|
99
|
+
def job_enable(job_id: str, target: Optional[str] = None) -> dict:
|
|
100
|
+
"""[WRITE] Enable a backup job (clears the disabled flag). Inverse: job_disable.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
job_id: Veeam job id.
|
|
104
|
+
target: Veeam target name from config.
|
|
105
|
+
"""
|
|
106
|
+
return ops.enable_job(_get_connection(target), job_id)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@mcp.tool()
|
|
110
|
+
@governed_tool(
|
|
111
|
+
risk_level="medium",
|
|
112
|
+
undo=lambda params, result: {
|
|
113
|
+
"tool": "job_enable",
|
|
114
|
+
"params": {"job_id": params.get("job_id")},
|
|
115
|
+
"skill": "veeam-aiops",
|
|
116
|
+
"note": "Inverse of job_disable: enable the job again.",
|
|
117
|
+
},
|
|
118
|
+
)
|
|
119
|
+
@tool_errors("dict")
|
|
120
|
+
def job_disable(job_id: str, target: Optional[str] = None) -> dict:
|
|
121
|
+
"""[WRITE] Disable a backup job (skips scheduled runs). Inverse: job_enable.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
job_id: Veeam job id.
|
|
125
|
+
target: Veeam target name from config.
|
|
126
|
+
"""
|
|
127
|
+
return ops.disable_job(_get_connection(target), job_id)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Backup repository MCP tools (read-only)."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from mcp_server._shared import _get_connection, mcp, tool_errors
|
|
6
|
+
from veeam_aiops.governance import governed_tool
|
|
7
|
+
from veeam_aiops.ops import repositories as ops
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@mcp.tool()
|
|
11
|
+
@governed_tool(risk_level="low")
|
|
12
|
+
@tool_errors("list")
|
|
13
|
+
def repository_list(target: Optional[str] = None) -> list:
|
|
14
|
+
"""[READ] List backup repositories with id, name, type, path.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
target: Veeam target name from config; omit to use the default.
|
|
18
|
+
"""
|
|
19
|
+
return ops.list_repositories(_get_connection(target))
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Restore MCP tools: list restore points, start a VM restore.
|
|
2
|
+
|
|
3
|
+
start_vm_restore is high-risk with NO undo token — restoring a VM overwrites
|
|
4
|
+
or creates one and cannot be reversed by the harness.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from mcp_server._shared import _get_connection, mcp, tool_errors
|
|
10
|
+
from veeam_aiops.governance import governed_tool
|
|
11
|
+
from veeam_aiops.ops import restore as ops
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@mcp.tool()
|
|
15
|
+
@governed_tool(risk_level="low")
|
|
16
|
+
@tool_errors("list")
|
|
17
|
+
def restore_list_points(target: Optional[str] = None) -> list:
|
|
18
|
+
"""[READ] List available restore points (id, name, creationTime, type).
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
target: Veeam target name from config; omit to use the default.
|
|
22
|
+
"""
|
|
23
|
+
return ops.list_restore_points(_get_connection(target))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@mcp.tool()
|
|
27
|
+
@governed_tool(risk_level="high")
|
|
28
|
+
@tool_errors("dict")
|
|
29
|
+
def start_vm_restore(restore_point_id: str, target: Optional[str] = None) -> dict:
|
|
30
|
+
"""[WRITE] Start a VM restore from a restore point. IRREVERSIBLE — no undo token.
|
|
31
|
+
|
|
32
|
+
Overwrites or creates a VM; confirm with the user before calling. Runs as an
|
|
33
|
+
async session — poll with session_list / session_get.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
restore_point_id: Restore point id (see restore_list_points).
|
|
37
|
+
target: Veeam target name from config.
|
|
38
|
+
"""
|
|
39
|
+
return ops.start_vm_restore(_get_connection(target), restore_point_id)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Session MCP tools (read-only): poll async job/restore progress."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from mcp_server._shared import _get_connection, mcp, tool_errors
|
|
6
|
+
from veeam_aiops.governance import governed_tool
|
|
7
|
+
from veeam_aiops.ops import sessions as ops
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@mcp.tool()
|
|
11
|
+
@governed_tool(risk_level="low")
|
|
12
|
+
@tool_errors("list")
|
|
13
|
+
def session_list(target: Optional[str] = None) -> list:
|
|
14
|
+
"""[READ] List recent sessions with id, name, type, state, result.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
target: Veeam target name from config; omit to use the default.
|
|
18
|
+
"""
|
|
19
|
+
return ops.list_sessions(_get_connection(target))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@mcp.tool()
|
|
23
|
+
@governed_tool(risk_level="low")
|
|
24
|
+
@tool_errors("dict")
|
|
25
|
+
def session_get(session_id: str, target: Optional[str] = None) -> dict:
|
|
26
|
+
"""[READ] Poll one session by id to check job/restore progress.
|
|
27
|
+
|
|
28
|
+
Use after job_start or start_vm_restore to follow the operation instead of
|
|
29
|
+
re-issuing it.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
session_id: Veeam session id (see session_list).
|
|
33
|
+
target: Veeam target name from config.
|
|
34
|
+
"""
|
|
35
|
+
return ops.get_session(_get_connection(target), session_id)
|