Console & logging
This commit is contained in:
25
README.md
25
README.md
@@ -12,9 +12,7 @@ A Python-based Model Context Protocol (MCP) server for interacting with Proxmox
|
||||
- Type-safe implementation with Pydantic
|
||||
- Full integration with Claude Desktop
|
||||
|
||||
## Installation for Cline Users
|
||||
|
||||
If you're using this MCP server with Cline, follow these steps for installation:
|
||||
## Installation
|
||||
|
||||
1. Create a directory for your MCP servers (if you haven't already):
|
||||
```bash
|
||||
@@ -31,7 +29,7 @@ If you're using this MCP server with Cline, follow these steps for installation:
|
||||
pip install -e "proxmox-mcp[dev]"
|
||||
```
|
||||
|
||||
3. Create your configuration:
|
||||
3. Create and verify your configuration:
|
||||
```bash
|
||||
# Create config directory
|
||||
mkdir proxmox-config
|
||||
@@ -42,7 +40,7 @@ If you're using this MCP server with Cline, follow these steps for installation:
|
||||
```json
|
||||
{
|
||||
"proxmox": {
|
||||
"host": "your-proxmox-host",
|
||||
"host": "your-proxmox-host", # Must be a valid hostname or IP
|
||||
"port": 8006,
|
||||
"verify_ssl": true,
|
||||
"service": "PVE"
|
||||
@@ -60,15 +58,16 @@ If you're using this MCP server with Cline, follow these steps for installation:
|
||||
}
|
||||
```
|
||||
|
||||
4. Install in Claude Desktop:
|
||||
```bash
|
||||
cd ~/Documents/Cline/MCP
|
||||
mcp install proxmox-mcp/src/proxmox_mcp/server.py \
|
||||
--name "Proxmox Manager" \
|
||||
-v PROXMOX_MCP_CONFIG=./proxmox-config/config.json
|
||||
```
|
||||
Important: Verify your configuration:
|
||||
- Ensure the config file exists at the specified path
|
||||
- The `host` field must be properly set to your Proxmox server's hostname or IP address
|
||||
- You can validate your config by running:
|
||||
```bash
|
||||
python3 -c "import json; print(json.load(open('config.json'))['proxmox']['host'])"
|
||||
```
|
||||
- This should print your Proxmox host address. If it prints an empty string or raises an error, your config needs to be fixed.
|
||||
|
||||
5. The server will now be available in Cline with these tools:
|
||||
4. The server provides these tools:
|
||||
- `get_nodes`: List all nodes in the cluster
|
||||
- `get_node_status`: Get detailed status of a node
|
||||
- `get_vms`: List all VMs
|
||||
|
||||
@@ -98,12 +98,25 @@ class ProxmoxMCPServer:
|
||||
|
||||
def _setup_logging(self) -> None:
|
||||
"""Configure logging based on settings."""
|
||||
import os
|
||||
|
||||
# Convert relative path to absolute
|
||||
log_file = self.config.logging.file
|
||||
if log_file and not os.path.isabs(log_file):
|
||||
log_file = os.path.join(os.getcwd(), log_file)
|
||||
|
||||
# Configure root logger
|
||||
logging.basicConfig(
|
||||
level=getattr(logging, self.config.logging.level.upper()),
|
||||
format=self.config.logging.format,
|
||||
filename=self.config.logging.file,
|
||||
handlers=[
|
||||
logging.FileHandler(log_file),
|
||||
logging.StreamHandler() # Also log to console
|
||||
]
|
||||
)
|
||||
|
||||
self.logger = logging.getLogger("proxmox-mcp")
|
||||
self.logger.info(f"Logging initialized. File: {log_file}, Level: {self.config.logging.level}")
|
||||
|
||||
def _setup_proxmox(self) -> ProxmoxAPI:
|
||||
"""Initialize Proxmox API connection."""
|
||||
|
||||
@@ -40,17 +40,84 @@ class VMConsoleManager:
|
||||
raise ValueError(f"VM {vmid} on node {node} is not running")
|
||||
|
||||
# Get VM's console
|
||||
console = self.proxmox.nodes(node).qemu(vmid).agent.exec.post(
|
||||
command=command
|
||||
)
|
||||
self.logger.info(f"Executing command on VM {vmid} (node: {node}): {command}")
|
||||
|
||||
# Get the API endpoint
|
||||
# Use the guest agent exec endpoint
|
||||
endpoint = self.proxmox.nodes(node).qemu(vmid).agent
|
||||
self.logger.debug(f"Using API endpoint: {endpoint}")
|
||||
|
||||
# Execute the command using two-step process
|
||||
try:
|
||||
# Start command execution
|
||||
self.logger.info("Starting command execution...")
|
||||
try:
|
||||
print(f"Executing command via agent: {command}")
|
||||
exec_result = endpoint("exec").post(command=command)
|
||||
print(f"Raw exec response: {exec_result}")
|
||||
self.logger.info(f"Command started with result: {exec_result}")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to start command: {str(e)}")
|
||||
raise RuntimeError(f"Failed to start command: {str(e)}")
|
||||
|
||||
if 'pid' not in exec_result:
|
||||
raise RuntimeError("No PID returned from command execution")
|
||||
|
||||
pid = exec_result['pid']
|
||||
self.logger.info(f"Waiting for command completion (PID: {pid})...")
|
||||
|
||||
# Add a small delay to allow command to complete
|
||||
import asyncio
|
||||
await asyncio.sleep(1)
|
||||
|
||||
# Get command output using exec-status
|
||||
try:
|
||||
print(f"Getting status for PID {pid}...")
|
||||
console = endpoint("exec-status").get(pid=pid)
|
||||
print(f"Raw exec-status response: {console}")
|
||||
if not console:
|
||||
raise RuntimeError("No response from exec-status")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to get command status: {str(e)}")
|
||||
raise RuntimeError(f"Failed to get command status: {str(e)}")
|
||||
self.logger.info(f"Command completed with status: {console}")
|
||||
print(f"Command completed with status: {console}")
|
||||
except Exception as e:
|
||||
self.logger.error(f"API call failed: {str(e)}")
|
||||
print(f"API call error: {str(e)}") # Print error for immediate feedback
|
||||
raise RuntimeError(f"API call failed: {str(e)}")
|
||||
self.logger.info(f"Raw API response type: {type(console)}")
|
||||
self.logger.info(f"Raw API response: {console}")
|
||||
print(f"Raw API response: {console}") # Print to stdout for immediate feedback
|
||||
|
||||
# Handle different response structures
|
||||
if isinstance(console, dict):
|
||||
# Handle exec-status response format
|
||||
output = console.get("out-data", "")
|
||||
error = console.get("err-data", "")
|
||||
exit_code = console.get("exitcode", 0)
|
||||
exited = console.get("exited", 0)
|
||||
|
||||
if not exited:
|
||||
self.logger.warning("Command may not have completed")
|
||||
else:
|
||||
# Some versions might return data differently
|
||||
self.logger.debug(f"Unexpected response type: {type(console)}")
|
||||
output = str(console)
|
||||
error = ""
|
||||
exit_code = 0
|
||||
|
||||
self.logger.debug(f"Processed output: {output}")
|
||||
self.logger.debug(f"Processed error: {error}")
|
||||
self.logger.debug(f"Processed exit code: {exit_code}")
|
||||
|
||||
self.logger.debug(f"Executed command '{command}' on VM {vmid} (node: {node})")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"output": console.get("out", ""),
|
||||
"error": console.get("err", ""),
|
||||
"exit_code": console.get("exitcode", 0)
|
||||
"output": output,
|
||||
"error": error,
|
||||
"exit_code": exit_code
|
||||
}
|
||||
|
||||
except ValueError:
|
||||
|
||||
Reference in New Issue
Block a user