proxmox-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.
- proxmox_aiops-0.1.0/.gitignore +22 -0
- proxmox_aiops-0.1.0/LICENSE +21 -0
- proxmox_aiops-0.1.0/PKG-INFO +70 -0
- proxmox_aiops-0.1.0/README.md +54 -0
- proxmox_aiops-0.1.0/RELEASE_NOTES.md +33 -0
- proxmox_aiops-0.1.0/SECURITY.md +63 -0
- proxmox_aiops-0.1.0/mcp_server/__init__.py +1 -0
- proxmox_aiops-0.1.0/mcp_server/_shared.py +102 -0
- proxmox_aiops-0.1.0/mcp_server/server.py +28 -0
- proxmox_aiops-0.1.0/mcp_server/tools/__init__.py +1 -0
- proxmox_aiops-0.1.0/mcp_server/tools/cluster.py +51 -0
- proxmox_aiops-0.1.0/mcp_server/tools/lxc.py +64 -0
- proxmox_aiops-0.1.0/mcp_server/tools/storage.py +43 -0
- proxmox_aiops-0.1.0/mcp_server/tools/vm.py +353 -0
- proxmox_aiops-0.1.0/proxmox_aiops/__init__.py +9 -0
- proxmox_aiops-0.1.0/proxmox_aiops/cli/__init__.py +9 -0
- proxmox_aiops-0.1.0/proxmox_aiops/cli/_common.py +81 -0
- proxmox_aiops-0.1.0/proxmox_aiops/cli/_root.py +54 -0
- proxmox_aiops-0.1.0/proxmox_aiops/cli/cluster.py +49 -0
- proxmox_aiops-0.1.0/proxmox_aiops/cli/doctor.py +21 -0
- proxmox_aiops-0.1.0/proxmox_aiops/cli/lxc.py +62 -0
- proxmox_aiops-0.1.0/proxmox_aiops/cli/storage.py +51 -0
- proxmox_aiops-0.1.0/proxmox_aiops/cli/vm.py +250 -0
- proxmox_aiops-0.1.0/proxmox_aiops/config.py +134 -0
- proxmox_aiops-0.1.0/proxmox_aiops/connection.py +101 -0
- proxmox_aiops-0.1.0/proxmox_aiops/doctor.py +65 -0
- proxmox_aiops-0.1.0/proxmox_aiops/governance/__init__.py +40 -0
- proxmox_aiops-0.1.0/proxmox_aiops/governance/audit.py +377 -0
- proxmox_aiops-0.1.0/proxmox_aiops/governance/budget.py +225 -0
- proxmox_aiops-0.1.0/proxmox_aiops/governance/decorators.py +474 -0
- proxmox_aiops-0.1.0/proxmox_aiops/governance/paths.py +23 -0
- proxmox_aiops-0.1.0/proxmox_aiops/governance/patterns.py +378 -0
- proxmox_aiops-0.1.0/proxmox_aiops/governance/policy.py +411 -0
- proxmox_aiops-0.1.0/proxmox_aiops/governance/sanitize.py +39 -0
- proxmox_aiops-0.1.0/proxmox_aiops/governance/undo.py +218 -0
- proxmox_aiops-0.1.0/proxmox_aiops/ops/__init__.py +1 -0
- proxmox_aiops-0.1.0/proxmox_aiops/ops/cluster.py +82 -0
- proxmox_aiops-0.1.0/proxmox_aiops/ops/lxc.py +69 -0
- proxmox_aiops-0.1.0/proxmox_aiops/ops/storage.py +71 -0
- proxmox_aiops-0.1.0/proxmox_aiops/ops/vm_lifecycle.py +247 -0
- proxmox_aiops-0.1.0/pyproject.toml +59 -0
- proxmox_aiops-0.1.0/server.json +21 -0
- proxmox_aiops-0.1.0/skills/proxmox-aiops/SKILL.md +143 -0
- proxmox_aiops-0.1.0/skills/proxmox-aiops/references/capabilities.md +60 -0
- proxmox_aiops-0.1.0/skills/proxmox-aiops/references/cli-reference.md +62 -0
- proxmox_aiops-0.1.0/skills/proxmox-aiops/references/setup-guide.md +74 -0
- proxmox_aiops-0.1.0/tests/test_smoke.py +222 -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,70 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: proxmox-aiops
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Proxmox VE AI-powered VM/container lifecycle 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: mcp[cli]<2.0,>=1.10
|
|
10
|
+
Requires-Dist: proxmoxer<3.0,>=2.0
|
|
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/proxmox-aiops -->
|
|
18
|
+
|
|
19
|
+
# Proxmox AIops (preview)
|
|
20
|
+
|
|
21
|
+
> **Disclaimer**: Community-maintained open-source project. **Not affiliated with, endorsed by, or sponsored by Proxmox Server Solutions GmbH.** "Proxmox" is a trademark of its owner. MIT licensed.
|
|
22
|
+
|
|
23
|
+
AI-powered Proxmox VE VM and container lifecycle operations with a **built-in
|
|
24
|
+
governance harness** — unified audit log, policy engine, token/runaway budget
|
|
25
|
+
guard, undo-token recording, and graduated-autonomy risk tiers. Self-contained:
|
|
26
|
+
no external dependencies beyond `proxmoxer` and the MCP SDK. Preview — not yet
|
|
27
|
+
full coverage of every Proxmox operation.
|
|
28
|
+
|
|
29
|
+
## What works
|
|
30
|
+
|
|
31
|
+
- **CLI** (`proxmox-aiops ...`): `vm list/get/config/start/stop/shutdown/reboot/reconfigure/clone/delete/migrate`, `vm snapshot-create/snapshot-delete/snapshot-list/snapshot-rollback`, `ct list/start/stop`, `cluster nodes/status/task-status`, `storage list/content`, `doctor`, `mcp`.
|
|
32
|
+
- **MCP server** (`proxmox-aiops mcp` or `proxmox-aiops-mcp`): **23 tools**, every one wrapped with the bundled `@governed_tool` harness.
|
|
33
|
+
- **Reversibility**: write ops with a clean inverse (start/stop/shutdown/reconfigure/clone/migrate/snapshot-create, container start/stop) record an inverse undo descriptor; irreversible ops (delete, snapshot-rollback) declare none and are tagged `high` risk.
|
|
34
|
+
- **Async tasks**: Proxmox writes return a task UPID — poll completion with `cluster task-status` (the runaway budget guard prevents poll loops from running away).
|
|
35
|
+
|
|
36
|
+
## Quick start
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
uv tool install proxmox-aiops
|
|
40
|
+
mkdir -p ~/.proxmox-aiops
|
|
41
|
+
# create ~/.proxmox-aiops/config.yaml with a targets: list
|
|
42
|
+
# put secrets in ~/.proxmox-aiops/.env (chmod 600)
|
|
43
|
+
proxmox-aiops doctor
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Example `~/.proxmox-aiops/config.yaml`:
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
targets:
|
|
50
|
+
- name: pve-lab
|
|
51
|
+
host: 10.0.0.10
|
|
52
|
+
user: "root@pam!claude" # API token: user@realm!tokenid
|
|
53
|
+
node: pve1
|
|
54
|
+
auth_kind: token
|
|
55
|
+
verify_ssl: false # self-signed lab certs only
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`~/.proxmox-aiops/.env` (chmod 600): `PROXMOX_PVE_LAB_SECRET=<token-uuid>`
|
|
59
|
+
|
|
60
|
+
## Audit & safety
|
|
61
|
+
|
|
62
|
+
All operations are logged to a local SQLite audit DB under `~/.proxmox-aiops/`
|
|
63
|
+
(relocatable via `PROXMOX_AIOPS_HOME`). Every write tool passes through the
|
|
64
|
+
governance harness: policy pre-check, token/runaway budget guard, graduated
|
|
65
|
+
risk-tier gate, and audit logging. Destructive CLI commands (`vm stop`,
|
|
66
|
+
`vm delete`, `vm snapshot-delete`, `vm snapshot-rollback`, `ct stop`) require
|
|
67
|
+
double confirmation and support `--dry-run`. API-returned text is run through a
|
|
68
|
+
prompt-injection sanitizer.
|
|
69
|
+
|
|
70
|
+
License: MIT.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<!-- mcp-name: io.github.AIops-tools/proxmox-aiops -->
|
|
2
|
+
|
|
3
|
+
# Proxmox AIops (preview)
|
|
4
|
+
|
|
5
|
+
> **Disclaimer**: Community-maintained open-source project. **Not affiliated with, endorsed by, or sponsored by Proxmox Server Solutions GmbH.** "Proxmox" is a trademark of its owner. MIT licensed.
|
|
6
|
+
|
|
7
|
+
AI-powered Proxmox VE VM and container lifecycle operations with a **built-in
|
|
8
|
+
governance harness** — unified audit log, policy engine, token/runaway budget
|
|
9
|
+
guard, undo-token recording, and graduated-autonomy risk tiers. Self-contained:
|
|
10
|
+
no external dependencies beyond `proxmoxer` and the MCP SDK. Preview — not yet
|
|
11
|
+
full coverage of every Proxmox operation.
|
|
12
|
+
|
|
13
|
+
## What works
|
|
14
|
+
|
|
15
|
+
- **CLI** (`proxmox-aiops ...`): `vm list/get/config/start/stop/shutdown/reboot/reconfigure/clone/delete/migrate`, `vm snapshot-create/snapshot-delete/snapshot-list/snapshot-rollback`, `ct list/start/stop`, `cluster nodes/status/task-status`, `storage list/content`, `doctor`, `mcp`.
|
|
16
|
+
- **MCP server** (`proxmox-aiops mcp` or `proxmox-aiops-mcp`): **23 tools**, every one wrapped with the bundled `@governed_tool` harness.
|
|
17
|
+
- **Reversibility**: write ops with a clean inverse (start/stop/shutdown/reconfigure/clone/migrate/snapshot-create, container start/stop) record an inverse undo descriptor; irreversible ops (delete, snapshot-rollback) declare none and are tagged `high` risk.
|
|
18
|
+
- **Async tasks**: Proxmox writes return a task UPID — poll completion with `cluster task-status` (the runaway budget guard prevents poll loops from running away).
|
|
19
|
+
|
|
20
|
+
## Quick start
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uv tool install proxmox-aiops
|
|
24
|
+
mkdir -p ~/.proxmox-aiops
|
|
25
|
+
# create ~/.proxmox-aiops/config.yaml with a targets: list
|
|
26
|
+
# put secrets in ~/.proxmox-aiops/.env (chmod 600)
|
|
27
|
+
proxmox-aiops doctor
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Example `~/.proxmox-aiops/config.yaml`:
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
targets:
|
|
34
|
+
- name: pve-lab
|
|
35
|
+
host: 10.0.0.10
|
|
36
|
+
user: "root@pam!claude" # API token: user@realm!tokenid
|
|
37
|
+
node: pve1
|
|
38
|
+
auth_kind: token
|
|
39
|
+
verify_ssl: false # self-signed lab certs only
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`~/.proxmox-aiops/.env` (chmod 600): `PROXMOX_PVE_LAB_SECRET=<token-uuid>`
|
|
43
|
+
|
|
44
|
+
## Audit & safety
|
|
45
|
+
|
|
46
|
+
All operations are logged to a local SQLite audit DB under `~/.proxmox-aiops/`
|
|
47
|
+
(relocatable via `PROXMOX_AIOPS_HOME`). Every write tool passes through the
|
|
48
|
+
governance harness: policy pre-check, token/runaway budget guard, graduated
|
|
49
|
+
risk-tier gate, and audit logging. Destructive CLI commands (`vm stop`,
|
|
50
|
+
`vm delete`, `vm snapshot-delete`, `vm snapshot-rollback`, `ct stop`) require
|
|
51
|
+
double confirmation and support `--dry-run`. API-returned text is run through a
|
|
52
|
+
prompt-injection sanitizer.
|
|
53
|
+
|
|
54
|
+
License: MIT.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Release notes
|
|
2
|
+
|
|
3
|
+
## v0.1.0 (2026-06-22) — first release (preview)
|
|
4
|
+
|
|
5
|
+
First public release of **proxmox-aiops** — governed Proxmox VE VM and container
|
|
6
|
+
lifecycle operations for AI agents. Standalone and self-contained.
|
|
7
|
+
|
|
8
|
+
### Highlights
|
|
9
|
+
- **23 MCP tools** (8 read / 15 write):
|
|
10
|
+
- VM lifecycle: list/get/config, start/stop/shutdown/reboot, reconfigure,
|
|
11
|
+
clone, delete, migrate
|
|
12
|
+
- Snapshots: create/delete/list/rollback
|
|
13
|
+
- LXC containers: list/start/stop
|
|
14
|
+
- Cluster/tasks: node list, cluster status, async task (UPID) polling
|
|
15
|
+
- Storage: pool list, content list
|
|
16
|
+
- **CLI + MCP server** (`proxmox-aiops` and `proxmox-aiops mcp`).
|
|
17
|
+
- **Built-in governance harness** (`proxmox_aiops.governance`, no external
|
|
18
|
+
dependency): unified audit log under `~/.proxmox-aiops/`, policy engine,
|
|
19
|
+
token/runaway budget guard, undo-token recording, and graduated-autonomy
|
|
20
|
+
risk tiers. State dir relocatable via `PROXMOX_AIOPS_HOME`.
|
|
21
|
+
- **Reversibility**: write ops with a clean inverse (start/stop/shutdown/
|
|
22
|
+
reconfigure/clone/migrate/snapshot-create, container start/stop) record an
|
|
23
|
+
undo descriptor; irreversible ops (delete, snapshot-rollback) declare none
|
|
24
|
+
and are tagged `high` risk.
|
|
25
|
+
- **Safety**: destructive CLI commands require double confirmation + `--dry-run`;
|
|
26
|
+
all API text is sanitized; TLS verification on by default.
|
|
27
|
+
|
|
28
|
+
### Notes
|
|
29
|
+
- Preview (0.x): broad coverage of common operations, not yet exhaustive — see
|
|
30
|
+
`references/capabilities.md` for the "not yet covered" list.
|
|
31
|
+
- Proxmox writes are asynchronous; poll completion with `cluster task-status`.
|
|
32
|
+
- Verified with a mocked Proxmox API (9 smoke tests); not yet exercised against
|
|
33
|
+
a live PVE cluster.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Disclaimer
|
|
4
|
+
|
|
5
|
+
Community-maintained open-source project. **Not affiliated with, endorsed by, or
|
|
6
|
+
sponsored by Proxmox Server Solutions GmbH.** "Proxmox" is a trademark of its
|
|
7
|
+
owner. Source is 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/Proxmox-AIops](https://github.com/AIops-tools/Proxmox-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 secrets live in `~/.proxmox-aiops/.env` (chmod 600), never in
|
|
20
|
+
`config.yaml` and never in source. Variable pattern:
|
|
21
|
+
`PROXMOX_<TARGET_NAME_UPPER>_SECRET` (API token UUID, or login password).
|
|
22
|
+
- Secrets are never logged or echoed; the config file holds only host, user,
|
|
23
|
+
node, and TLS settings.
|
|
24
|
+
|
|
25
|
+
### Governed Operations
|
|
26
|
+
Every MCP tool runs through the bundled `@governed_tool` harness
|
|
27
|
+
(`proxmox_aiops.governance`):
|
|
28
|
+
- **Audit** — every call logged to a local SQLite DB under `~/.proxmox-aiops/`
|
|
29
|
+
(relocatable via `PROXMOX_AIOPS_HOME`), agent-attributed, secret-redacted.
|
|
30
|
+
- **Token/runaway budget** — hard ceilings (`PROXMOX_MAX_TOOL_CALLS` /
|
|
31
|
+
`PROXMOX_MAX_TOOL_SECONDS`) plus an on-by-default guard that trips a tight
|
|
32
|
+
poll/retry loop, preventing unbounded API consumption.
|
|
33
|
+
- **Graduated risk tiers** — `~/.proxmox-aiops/rules.yaml` `risk_tiers` gate
|
|
34
|
+
writes by environment/tag; the highest tiers require a recorded approver.
|
|
35
|
+
- **Undo-token recording** — reversible writes record an inverse descriptor so
|
|
36
|
+
a change can be rolled back.
|
|
37
|
+
|
|
38
|
+
### Destructive Operations
|
|
39
|
+
`vm stop`, `vm delete`, `vm snapshot-delete`, `vm snapshot-rollback`, and
|
|
40
|
+
`ct stop` require double confirmation at the CLI layer and support `--dry-run`.
|
|
41
|
+
|
|
42
|
+
### SSL/TLS Verification
|
|
43
|
+
`verify_ssl` defaults to true; disable only for self-signed lab certificates.
|
|
44
|
+
|
|
45
|
+
### Prompt-Injection Protection
|
|
46
|
+
All Proxmox-API-returned text (names, UPIDs, descriptions) is passed through a
|
|
47
|
+
`sanitize()` truncate + control-character strip before reaching the agent.
|
|
48
|
+
|
|
49
|
+
### Network Scope
|
|
50
|
+
No webhooks, no telemetry, no outbound calls beyond the configured Proxmox API
|
|
51
|
+
endpoint. No post-install scripts or background services.
|
|
52
|
+
|
|
53
|
+
## Static Analysis
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
uvx bandit -r proxmox_aiops/ mcp_server/
|
|
57
|
+
uv run ruff check .
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Supported Versions
|
|
61
|
+
|
|
62
|
+
The latest released version receives security fixes. This is a preview (0.x);
|
|
63
|
+
pin a version in production.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""MCP server package for proxmox-aiops."""
|
|
@@ -0,0 +1,102 @@
|
|
|
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 (踩坑 #33).
|
|
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 proxmox_aiops.config import load_config
|
|
23
|
+
from proxmox_aiops.connection import ConnectionManager
|
|
24
|
+
from proxmox_aiops.governance import sanitize
|
|
25
|
+
from proxmox_aiops.ops.lxc import ContainerNotFoundError
|
|
26
|
+
from proxmox_aiops.ops.storage import NodeRequiredError
|
|
27
|
+
from proxmox_aiops.ops.vm_lifecycle import VMNotFoundError
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
_DOCTOR_HINT = "Run 'proxmox-aiops doctor' to verify connectivity and credentials."
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _safe_error(exc: Exception, tool: str) -> str:
|
|
35
|
+
"""Return an agent-safe error string; log full detail server-side only."""
|
|
36
|
+
logger.error("Tool %s failed", tool, exc_info=True)
|
|
37
|
+
_passthrough = (
|
|
38
|
+
ValueError,
|
|
39
|
+
FileNotFoundError,
|
|
40
|
+
KeyError,
|
|
41
|
+
PermissionError,
|
|
42
|
+
TimeoutError,
|
|
43
|
+
ConnectionError,
|
|
44
|
+
VMNotFoundError,
|
|
45
|
+
NodeRequiredError,
|
|
46
|
+
ContainerNotFoundError,
|
|
47
|
+
)
|
|
48
|
+
if isinstance(exc, _passthrough):
|
|
49
|
+
return sanitize(str(exc), 300)
|
|
50
|
+
return f"{type(exc).__name__}: operation failed."
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def tool_errors(shape: str = "dict") -> Callable:
|
|
54
|
+
"""Wrap a tool body in the canonical try/except → ``_safe_error`` pattern.
|
|
55
|
+
|
|
56
|
+
Place this *between* ``@governed_tool`` and the function so the audit
|
|
57
|
+
decorator and FastMCP still see the original signature.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def decorator(func: Callable) -> Callable:
|
|
61
|
+
name = func.__name__
|
|
62
|
+
|
|
63
|
+
@functools.wraps(func)
|
|
64
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
65
|
+
try:
|
|
66
|
+
return func(*args, **kwargs)
|
|
67
|
+
except Exception as e: # noqa: BLE001 — sanitised below
|
|
68
|
+
msg = _safe_error(e, name)
|
|
69
|
+
if shape == "list":
|
|
70
|
+
return [{"error": msg, "hint": _DOCTOR_HINT}]
|
|
71
|
+
if shape == "str":
|
|
72
|
+
return f"Error: {msg} {_DOCTOR_HINT}"
|
|
73
|
+
return {"error": msg, "hint": _DOCTOR_HINT}
|
|
74
|
+
|
|
75
|
+
return wrapper
|
|
76
|
+
|
|
77
|
+
return decorator
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
mcp = FastMCP(
|
|
81
|
+
"proxmox-aiops",
|
|
82
|
+
instructions=(
|
|
83
|
+
"Proxmox VE operations (preview): QEMU VM lifecycle (list/get/config, "
|
|
84
|
+
"start/stop/shutdown/reboot, reconfigure, clone, delete, migrate), "
|
|
85
|
+
"snapshots (create/delete/list/rollback), LXC containers (list/start/stop), "
|
|
86
|
+
"cluster/nodes + async task polling, and storage listing. Every tool runs "
|
|
87
|
+
"through the proxmox-aiops governance harness (audit / budget / risk-tier / "
|
|
88
|
+
"undo)."
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
_conn_mgr: Optional[ConnectionManager] = None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _get_connection(target: Optional[str] = None) -> Any:
|
|
96
|
+
"""Return a proxmoxer connection, lazily initialising the manager."""
|
|
97
|
+
global _conn_mgr # noqa: PLW0603
|
|
98
|
+
if _conn_mgr is None:
|
|
99
|
+
config_path_str = os.environ.get("PROXMOX_AIOPS_CONFIG")
|
|
100
|
+
config_path = Path(config_path_str) if config_path_str else None
|
|
101
|
+
_conn_mgr = ConnectionManager(load_config(config_path))
|
|
102
|
+
return _conn_mgr.connect(target)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""MCP server wrapping Proxmox AIops operations (stdio transport).
|
|
2
|
+
|
|
3
|
+
Thin adapter layer: each ``@mcp.tool()`` function (in ``mcp_server/tools/``)
|
|
4
|
+
delegates to the ``proxmox_aiops`` ops package and is wrapped with the
|
|
5
|
+
proxmox-aiops ``@governed_tool`` harness (audit / budget / undo / risk-tier).
|
|
6
|
+
|
|
7
|
+
Standalone, self-governed Proxmox VE operations (preview).
|
|
8
|
+
For Proxmox VE only.
|
|
9
|
+
|
|
10
|
+
Source: https://github.com/AIops-tools/Proxmox-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 cluster, lxc, storage, vm # noqa: F401 — side effects
|
|
21
|
+
|
|
22
|
+
__all__ = ["mcp", "main", "_safe_error", "tool_errors"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def main() -> None:
|
|
26
|
+
"""Run the MCP server over stdio."""
|
|
27
|
+
logging.basicConfig(level=logging.INFO)
|
|
28
|
+
mcp.run(transport="stdio")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""MCP tool modules. Importing each registers its @mcp.tool() functions."""
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Cluster / node / async-task MCP tools (all read-only)."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from mcp_server._shared import _get_connection, mcp, tool_errors
|
|
6
|
+
from proxmox_aiops.governance import governed_tool
|
|
7
|
+
from proxmox_aiops.ops import cluster as cl
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@mcp.tool()
|
|
11
|
+
@governed_tool(risk_level="low")
|
|
12
|
+
@tool_errors("list")
|
|
13
|
+
def node_list(target: Optional[str] = None) -> list:
|
|
14
|
+
"""[READ] List Proxmox cluster nodes with status, cpu load, and memory.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
target: Proxmox target name from config; omit to use the default.
|
|
18
|
+
"""
|
|
19
|
+
return cl.list_nodes(_get_connection(target))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@mcp.tool()
|
|
23
|
+
@governed_tool(risk_level="low")
|
|
24
|
+
@tool_errors("list")
|
|
25
|
+
def cluster_status(target: Optional[str] = None) -> list:
|
|
26
|
+
"""[READ] Return cluster membership + quorum status.
|
|
27
|
+
|
|
28
|
+
The ``type=cluster`` row's ``quorate`` field indicates whether the cluster
|
|
29
|
+
currently has quorum.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
target: Proxmox target name from config.
|
|
33
|
+
"""
|
|
34
|
+
return cl.cluster_status(_get_connection(target))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@mcp.tool()
|
|
38
|
+
@governed_tool(risk_level="low")
|
|
39
|
+
@tool_errors("dict")
|
|
40
|
+
def task_status(upid: str, target: Optional[str] = None, node: Optional[str] = None) -> dict:
|
|
41
|
+
"""[READ] Poll a Proxmox async task (clone / migrate / backup) by its UPID.
|
|
42
|
+
|
|
43
|
+
Use after a write that returned a task UPID to check completion instead of
|
|
44
|
+
re-issuing the operation. The node is parsed from the UPID when omitted.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
upid: The task UPID returned by an async write tool.
|
|
48
|
+
target: Proxmox target name from config.
|
|
49
|
+
node: Node the task runs on; omit to parse it from the UPID.
|
|
50
|
+
"""
|
|
51
|
+
return cl.get_task_status(_get_connection(target), upid, node=node)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""LXC container MCP tools: list / start / stop (start/stop carry undo tokens)."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from mcp_server._shared import _get_connection, mcp, tool_errors
|
|
6
|
+
from proxmox_aiops.governance import governed_tool
|
|
7
|
+
from proxmox_aiops.ops import lxc
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@mcp.tool()
|
|
11
|
+
@governed_tool(risk_level="low")
|
|
12
|
+
@tool_errors("list")
|
|
13
|
+
def ct_list(target: Optional[str] = None, node: Optional[str] = None) -> list:
|
|
14
|
+
"""[READ] List LXC containers with name, vmid, status, cpu, mem.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
target: Proxmox target name from config; omit to use the default.
|
|
18
|
+
node: Node name; omit to use the configured default / all nodes.
|
|
19
|
+
"""
|
|
20
|
+
return lxc.list_cts(_get_connection(target), node=node)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@mcp.tool()
|
|
24
|
+
@governed_tool(
|
|
25
|
+
risk_level="medium",
|
|
26
|
+
undo=lambda params, result: {
|
|
27
|
+
"tool": "ct_stop",
|
|
28
|
+
"params": {"vmid": params.get("vmid"), "node": params.get("node")},
|
|
29
|
+
"skill": "proxmox-aiops",
|
|
30
|
+
"note": "Inverse of ct_start: stop the container again.",
|
|
31
|
+
},
|
|
32
|
+
)
|
|
33
|
+
@tool_errors("dict")
|
|
34
|
+
def ct_start(vmid: int, target: Optional[str] = None, node: Optional[str] = None) -> dict:
|
|
35
|
+
"""[WRITE] Start an LXC container. Returns the task UPID. Inverse: ct_stop.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
vmid: Numeric container id (see ct_list).
|
|
39
|
+
target: Proxmox target name from config.
|
|
40
|
+
node: Node name; omit to auto-locate the container.
|
|
41
|
+
"""
|
|
42
|
+
return lxc.start_ct(_get_connection(target), vmid, node=node)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@mcp.tool()
|
|
46
|
+
@governed_tool(
|
|
47
|
+
risk_level="medium",
|
|
48
|
+
undo=lambda params, result: {
|
|
49
|
+
"tool": "ct_start",
|
|
50
|
+
"params": {"vmid": params.get("vmid"), "node": params.get("node")},
|
|
51
|
+
"skill": "proxmox-aiops",
|
|
52
|
+
"note": "Inverse of ct_stop: start the container again.",
|
|
53
|
+
},
|
|
54
|
+
)
|
|
55
|
+
@tool_errors("dict")
|
|
56
|
+
def ct_stop(vmid: int, target: Optional[str] = None, node: Optional[str] = None) -> dict:
|
|
57
|
+
"""[WRITE] Stop an LXC container. Returns the task UPID. Inverse: ct_start.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
vmid: Numeric container id (see ct_list).
|
|
61
|
+
target: Proxmox target name from config.
|
|
62
|
+
node: Node name; omit to auto-locate the container.
|
|
63
|
+
"""
|
|
64
|
+
return lxc.stop_ct(_get_connection(target), vmid, node=node)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Storage read MCP tools for Proxmox VE."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from mcp_server._shared import _get_connection, mcp, tool_errors
|
|
7
|
+
from proxmox_aiops.governance import governed_tool
|
|
8
|
+
from proxmox_aiops.ops import storage as st
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@mcp.tool()
|
|
12
|
+
@governed_tool(risk_level="low")
|
|
13
|
+
@tool_errors("list")
|
|
14
|
+
def storage_list(target: Optional[str] = None, node: Optional[str] = None) -> list:
|
|
15
|
+
"""[READ] List storage pools on a node (id, type, total/used/avail bytes).
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
target: Proxmox target name from config; omit to use the default.
|
|
19
|
+
node: Node name; omit to use the target's configured default node.
|
|
20
|
+
"""
|
|
21
|
+
return st.list_storage(_get_connection(target), node=node)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@mcp.tool()
|
|
25
|
+
@governed_tool(risk_level="low")
|
|
26
|
+
@tool_errors("list")
|
|
27
|
+
def storage_content(
|
|
28
|
+
storage: str,
|
|
29
|
+
content: Optional[str] = None,
|
|
30
|
+
target: Optional[str] = None,
|
|
31
|
+
node: Optional[str] = None,
|
|
32
|
+
) -> list:
|
|
33
|
+
"""[READ] List volumes on a storage pool (ISOs, disk images, backups, templates).
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
storage: Storage pool id (see storage_list).
|
|
37
|
+
content: Optional filter — 'iso', 'images', 'backup', 'vztmpl'.
|
|
38
|
+
target: Proxmox target name from config.
|
|
39
|
+
node: Node name; omit to use the configured default node.
|
|
40
|
+
"""
|
|
41
|
+
return st.list_storage_content(
|
|
42
|
+
_get_connection(target), storage, node=node, content=content
|
|
43
|
+
)
|