fix: implement proxmox_list_qemu and proxmox_list_lxc tools
This commit is contained in:
@@ -1,11 +1,15 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import httpx
|
||||||
from mcp.server.fastmcp import FastMCP
|
from mcp.server.fastmcp import FastMCP
|
||||||
|
|
||||||
from proxmox_mcp import __version__
|
from proxmox_mcp import __version__
|
||||||
from proxmox_mcp.config import settings
|
from proxmox_mcp.config import settings
|
||||||
|
from proxmox_mcp.targets import registry
|
||||||
from proxmox_mcp.tools import register_all
|
from proxmox_mcp.tools import register_all
|
||||||
|
|
||||||
mcp = FastMCP(
|
mcp = FastMCP(
|
||||||
@@ -18,6 +22,131 @@ mcp = FastMCP(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_active_client() -> httpx.Client | None:
|
||||||
|
"""Return an authenticated httpx client for the active target, or None."""
|
||||||
|
target = registry.active()
|
||||||
|
if not target:
|
||||||
|
return None
|
||||||
|
verify = os.environ.get("PROXMOX_VERIFY_SSL", "true").lower() in ("1", "true", "yes")
|
||||||
|
client = httpx.Client(
|
||||||
|
base_url=target.url,
|
||||||
|
verify=verify,
|
||||||
|
headers={"Authorization": f"PVEAPIToken={target.token_id}={target.token_secret}"},
|
||||||
|
timeout=30.0,
|
||||||
|
)
|
||||||
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def proxmox_list_all_guests() -> str:
|
||||||
|
"""List all QEMU VMs and LXC containers across all Proxmox cluster nodes.
|
||||||
|
|
||||||
|
This tool uses the cluster resources endpoint, which does not require a node
|
||||||
|
parameter. It returns a combined list of all VMs and containers regardless
|
||||||
|
of which node they reside on.
|
||||||
|
"""
|
||||||
|
client = _get_active_client()
|
||||||
|
if not client:
|
||||||
|
return json.dumps({"error": "No active target. Call proxmox_connect or proxmox_use_target first."})
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Query cluster resources for both VMs and containers
|
||||||
|
resp = client.get("/api2/json/cluster/resources", params={"type": "vm"})
|
||||||
|
resp.raise_for_status()
|
||||||
|
data = resp.json().get("data", [])
|
||||||
|
|
||||||
|
# Filter: vmid with status and type
|
||||||
|
guests = []
|
||||||
|
for entry in data:
|
||||||
|
if entry.get("type") in ("qemu", "lxc"):
|
||||||
|
guests.append({
|
||||||
|
"vmid": entry.get("vmid"),
|
||||||
|
"node": entry.get("node"),
|
||||||
|
"name": entry.get("name"),
|
||||||
|
"type": entry.get("type"),
|
||||||
|
"status": entry.get("status"),
|
||||||
|
"mem": entry.get("mem"),
|
||||||
|
"maxmem": entry.get("maxmem"),
|
||||||
|
"cpu": entry.get("cpu"),
|
||||||
|
"maxcpu": entry.get("maxcpu"),
|
||||||
|
"disk": entry.get("disk"),
|
||||||
|
"maxdisk": entry.get("maxdisk"),
|
||||||
|
"uptime": entry.get("uptime"),
|
||||||
|
})
|
||||||
|
return json.dumps(guests, indent=2)
|
||||||
|
except Exception as exc:
|
||||||
|
return json.dumps({"error": str(exc)})
|
||||||
|
finally:
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def proxmox_debug_guest_inventory(node: str | None = None) -> str:
|
||||||
|
"""Return the raw JSON from the Proxmox API for debugging guest listing.
|
||||||
|
|
||||||
|
If a node is given, queries /nodes/{node}/qemu and /nodes/{node}/lxc separately.
|
||||||
|
Otherwise, queries the cluster resources endpoint.
|
||||||
|
"""
|
||||||
|
client = _get_active_client()
|
||||||
|
if not client:
|
||||||
|
return json.dumps({"error": "No active target. Call proxmox_connect or proxmox_use_target first."})
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = {}
|
||||||
|
if node:
|
||||||
|
# Per-node endpoints
|
||||||
|
for gtype in ("qemu", "lxc"):
|
||||||
|
resp = client.get(f"/api2/json/nodes/{node}/{gtype}")
|
||||||
|
resp.raise_for_status()
|
||||||
|
result[gtype] = resp.json().get("data", [])
|
||||||
|
else:
|
||||||
|
# Cluster resources
|
||||||
|
resp = client.get("/api2/json/cluster/resources", params={"type": "vm"})
|
||||||
|
resp.raise_for_status()
|
||||||
|
result = resp.json()
|
||||||
|
return json.dumps(result, indent=2, default=str)
|
||||||
|
except Exception as exc:
|
||||||
|
return json.dumps({"error": str(exc)})
|
||||||
|
finally:
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def proxmox_list_qemu(node: str) -> str:
|
||||||
|
"""List all QEMU VMs on a specific Proxmox node."""
|
||||||
|
client = _get_active_client()
|
||||||
|
if not client:
|
||||||
|
return json.dumps({"error": "No active target. Call proxmox_connect or proxmox_use_target first."})
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = client.get(f"/api2/json/nodes/{node}/qemu")
|
||||||
|
resp.raise_for_status()
|
||||||
|
vms = resp.json().get("data", [])
|
||||||
|
return json.dumps(vms, indent=2)
|
||||||
|
except Exception as exc:
|
||||||
|
return json.dumps({"error": str(exc)})
|
||||||
|
finally:
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def proxmox_list_lxc(node: str) -> str:
|
||||||
|
"""List all LXC containers on a specific Proxmox node."""
|
||||||
|
client = _get_active_client()
|
||||||
|
if not client:
|
||||||
|
return json.dumps({"error": "No active target. Call proxmox_connect or proxmox_use_target first."})
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = client.get(f"/api2/json/nodes/{node}/lxc")
|
||||||
|
resp.raise_for_status()
|
||||||
|
lxcs = resp.json().get("data", [])
|
||||||
|
return json.dumps(lxcs, indent=2)
|
||||||
|
except Exception as exc:
|
||||||
|
return json.dumps({"error": str(exc)})
|
||||||
|
finally:
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
def proxmox_get_config() -> str:
|
def proxmox_get_config() -> str:
|
||||||
from proxmox_mcp.targets import registry
|
from proxmox_mcp.targets import registry
|
||||||
|
|||||||
Reference in New Issue
Block a user