diff --git a/README.md b/README.md index 4f9391c..304bf30 100644 --- a/README.md +++ b/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 diff --git a/src/proxmox_mcp/server.py b/src/proxmox_mcp/server.py index c9412fe..b5c35cb 100644 --- a/src/proxmox_mcp/server.py +++ b/src/proxmox_mcp/server.py @@ -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.""" diff --git a/src/proxmox_mcp/tools/vm_console.py b/src/proxmox_mcp/tools/vm_console.py index 96f91f2..37dd8f2 100644 --- a/src/proxmox_mcp/tools/vm_console.py +++ b/src/proxmox_mcp/tools/vm_console.py @@ -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: