fixed connnection issues and tool usage
This commit is contained in:
@@ -24,11 +24,10 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
"Topic :: System :: Systems Administration",
|
||||
"Topic :: System :: Virtualization",
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
"modelcontextprotocol-sdk>=1.0.0,<2.0.0",
|
||||
"mcp>=0.12.0,<2.0.0",
|
||||
"proxmoxer>=2.0.1,<3.0.0",
|
||||
"requests>=2.31.0,<3.0.0",
|
||||
"pydantic>=2.0.0,<3.0.0",
|
||||
|
||||
@@ -11,8 +11,38 @@ from mcp.server.fastmcp.tools.base import Tool as BaseTool
|
||||
from mcp.types import CallToolResult as Response, TextContent as Content
|
||||
from proxmoxer import ProxmoxAPI
|
||||
from pydantic import BaseModel
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from .tools.vm_console import VMConsoleManager
|
||||
class CustomProxmoxAPI(ProxmoxAPI):
|
||||
def __init__(self, host, **kwargs):
|
||||
self._host = host # Store the host
|
||||
super().__init__(host, **kwargs)
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
try:
|
||||
# Always ensure base_url is set correctly
|
||||
self._store['base_url'] = f'https://{self._host}:{self._store.get("port", "8006")}/api2/json'
|
||||
print(f"Using base_url: {self._store['base_url']}")
|
||||
|
||||
# Handle both dotted and string notation
|
||||
if args and isinstance(args[0], str):
|
||||
path = args[0]
|
||||
print(f"Making request to path: {path}")
|
||||
full_url = f"{self._store['base_url']}/{path}"
|
||||
print(f"Full URL: {full_url}")
|
||||
return self._backend.get(full_url)
|
||||
|
||||
print("Using dotted notation")
|
||||
return super().get(*args, **kwargs)
|
||||
except Exception as e:
|
||||
print(f"Error in CustomProxmoxAPI.get: {e}")
|
||||
raise
|
||||
|
||||
# Import from the same directory
|
||||
import sys
|
||||
import os.path
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
from tools.vm_console import VMConsoleManager
|
||||
|
||||
class ProxmoxConfig(BaseModel):
|
||||
host: str
|
||||
@@ -46,31 +76,25 @@ class ProxmoxMCPServer:
|
||||
|
||||
def _load_config(self, config_path: Optional[str]) -> Config:
|
||||
"""Load configuration from file or environment variables."""
|
||||
if config_path:
|
||||
print(f"Loading config from path: {config_path}")
|
||||
if not config_path:
|
||||
raise ValueError("PROXMOX_MCP_CONFIG environment variable must be set")
|
||||
|
||||
try:
|
||||
with open(config_path) as f:
|
||||
config_data = json.load(f)
|
||||
else:
|
||||
# Load from environment variables
|
||||
config_data = {
|
||||
"proxmox": {
|
||||
"host": os.getenv("PROXMOX_HOST", ""),
|
||||
"port": int(os.getenv("PROXMOX_PORT", "8006")),
|
||||
"verify_ssl": os.getenv("PROXMOX_VERIFY_SSL", "true").lower() == "true",
|
||||
"service": os.getenv("PROXMOX_SERVICE", "PVE"),
|
||||
},
|
||||
"auth": {
|
||||
"user": os.getenv("PROXMOX_USER", ""),
|
||||
"token_name": os.getenv("PROXMOX_TOKEN_NAME", ""),
|
||||
"token_value": os.getenv("PROXMOX_TOKEN_VALUE", ""),
|
||||
},
|
||||
"logging": {
|
||||
"level": os.getenv("LOG_LEVEL", "INFO"),
|
||||
"format": os.getenv("LOG_FORMAT", "%(asctime)s - %(name)s - %(levelname)s - %(message)s"),
|
||||
"file": os.getenv("LOG_FILE"),
|
||||
},
|
||||
}
|
||||
|
||||
return Config(**config_data)
|
||||
print(f"Raw config data: {config_data}")
|
||||
|
||||
# Ensure host is not empty
|
||||
if not config_data.get('proxmox', {}).get('host'):
|
||||
raise ValueError("Proxmox host cannot be empty")
|
||||
|
||||
print(f"Using host: {config_data['proxmox']['host']}")
|
||||
return Config(**config_data)
|
||||
except json.JSONDecodeError as e:
|
||||
raise ValueError(f"Invalid JSON in config file: {e}")
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to load config: {e}")
|
||||
|
||||
def _setup_logging(self) -> None:
|
||||
"""Configure logging based on settings."""
|
||||
@@ -84,15 +108,35 @@ class ProxmoxMCPServer:
|
||||
def _setup_proxmox(self) -> ProxmoxAPI:
|
||||
"""Initialize Proxmox API connection."""
|
||||
try:
|
||||
return ProxmoxAPI(
|
||||
host=self.config.proxmox.host,
|
||||
port=self.config.proxmox.port,
|
||||
user=self.config.auth.user,
|
||||
token_name=self.config.auth.token_name,
|
||||
token_value=self.config.auth.token_value,
|
||||
verify_ssl=self.config.proxmox.verify_ssl,
|
||||
service=self.config.proxmox.service,
|
||||
)
|
||||
self.logger.info(f"Connecting to Proxmox with config: {self.config.proxmox}")
|
||||
print(f"Initializing ProxmoxAPI with host={self.config.proxmox.host}, port={self.config.proxmox.port}")
|
||||
|
||||
print(f"Creating ProxmoxAPI with host={self.config.proxmox.host}")
|
||||
|
||||
# Store the working configuration for reuse
|
||||
self.proxmox_config = {
|
||||
'host': self.config.proxmox.host,
|
||||
'user': self.config.auth.user,
|
||||
'token_name': self.config.auth.token_name,
|
||||
'token_value': self.config.auth.token_value,
|
||||
'verify_ssl': self.config.proxmox.verify_ssl,
|
||||
'service': 'PVE'
|
||||
}
|
||||
|
||||
# Create CustomProxmoxAPI instance with stored config
|
||||
api = CustomProxmoxAPI(**self.proxmox_config)
|
||||
print("ProxmoxAPI initialized successfully")
|
||||
# Test the connection by making a simple request
|
||||
print("Testing API connection...")
|
||||
test_result = api.version.get()
|
||||
print(f"Connection test result: {test_result}")
|
||||
|
||||
# Test nodes endpoint specifically
|
||||
print("Testing nodes endpoint...")
|
||||
nodes_result = api.nodes.get()
|
||||
print(f"Nodes test result: {nodes_result}")
|
||||
|
||||
return api
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to connect to Proxmox: {e}")
|
||||
raise
|
||||
@@ -104,7 +148,10 @@ class ProxmoxMCPServer:
|
||||
def get_nodes() -> List[Content]:
|
||||
"""List all nodes in the Proxmox cluster."""
|
||||
try:
|
||||
print(f"Using ProxmoxAPI instance with config: {self.proxmox_config}")
|
||||
print("Getting nodes using dotted notation...")
|
||||
result = self.proxmox.nodes.get()
|
||||
print(f"Raw nodes result: {result}")
|
||||
nodes = [{"node": node["node"], "status": node["status"]} for node in result]
|
||||
return [Content(type="text", text=json.dumps(nodes))]
|
||||
except Exception as e:
|
||||
@@ -198,26 +245,40 @@ class ProxmoxMCPServer:
|
||||
self.logger.error(f"Failed to execute VM command: {e}")
|
||||
raise
|
||||
|
||||
async def run(self) -> None:
|
||||
def start(self) -> None:
|
||||
"""Start the MCP server."""
|
||||
import anyio
|
||||
import signal
|
||||
import sys
|
||||
|
||||
def signal_handler(signum, frame):
|
||||
print("Received signal to shutdown...")
|
||||
sys.exit(0)
|
||||
|
||||
# Set up signal handlers
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
|
||||
try:
|
||||
await self.mcp.run()
|
||||
self.logger.info("Proxmox MCP server running")
|
||||
print("Starting MCP server...")
|
||||
anyio.run(self.mcp.run_stdio_async)
|
||||
except Exception as e:
|
||||
print(f"Server error: {e}")
|
||||
self.logger.error(f"Server error: {e}")
|
||||
raise
|
||||
|
||||
def main():
|
||||
"""Entry point for the MCP server."""
|
||||
import asyncio
|
||||
|
||||
config_path = os.getenv("PROXMOX_MCP_CONFIG")
|
||||
server = ProxmoxMCPServer(config_path)
|
||||
|
||||
try:
|
||||
asyncio.run(server.run())
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
config_path = os.getenv("PROXMOX_MCP_CONFIG")
|
||||
if not config_path:
|
||||
print("PROXMOX_MCP_CONFIG environment variable must be set")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
server = ProxmoxMCPServer(config_path)
|
||||
server.start()
|
||||
except KeyboardInterrupt:
|
||||
print("\nShutting down gracefully...")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
Reference in New Issue
Block a user