Spaces:
Sleeping
Sleeping
#!/usr/bin/env python | |
""" | |
Standalone FastAPI MCP server for Hyper-V management. | |
Server will be available at http://localhost:8000 | |
""" | |
import asyncio | |
import json | |
import logging | |
import subprocess | |
import sys | |
import platform | |
from typing import Dict, Any, List, Optional | |
from dataclasses import dataclass, asdict | |
from fastapi import FastAPI, HTTPException | |
from pydantic import BaseModel | |
import uvicorn | |
# Ensure SelectorEventLoopPolicy on Windows | |
if platform.system() == "Windows": | |
try: | |
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) | |
except AttributeError: | |
pass | |
# Setup logging | |
logging.basicConfig(level=logging.INFO) | |
logger = logging.getLogger("hyperv_mcp_server") | |
# Pydantic models for API | |
class ToolCallRequest(BaseModel): | |
name: str | |
arguments: Dict[str, Any] = {} | |
class ToolInfo(BaseModel): | |
name: str | |
description: str | |
inputSchema: Dict[str, Any] | |
class ToolsListResponse(BaseModel): | |
tools: List[ToolInfo] | |
class ToolCallResponse(BaseModel): | |
success: bool | |
result: Any = None | |
error: str = None | |
class VirtualMachine: | |
name: str | |
state: str | |
status: str | |
class HyperVManager: | |
def __init__(self, host: str = "localhost", username: Optional[str] = None, password: Optional[str] = None): | |
self.host = host | |
self.username = username | |
self.password = password | |
def _run_powershell(self, command: str) -> str: | |
"""Execute PowerShell command and return output""" | |
try: | |
if self.host == "localhost": | |
proc = subprocess.run( | |
["powershell", "-Command", command], | |
capture_output=True, text=True, shell=True, timeout=30 | |
) | |
if proc.returncode != 0: | |
raise RuntimeError(f"PowerShell error: {proc.stderr}") | |
return proc.stdout.strip() | |
else: | |
raise NotImplementedError("Remote host not supported in this server") | |
except subprocess.TimeoutExpired: | |
raise RuntimeError("PowerShell command timed out") | |
except Exception as e: | |
logger.error(f"PowerShell execution error: {e}") | |
raise | |
async def list_vms(self) -> List[Dict[str, Any]]: | |
"""List all virtual machines""" | |
try: | |
cmd = ( | |
"Get-VM | Select-Object Name,State,Status | " | |
"ConvertTo-Json -Depth 2" | |
) | |
output = await asyncio.get_event_loop().run_in_executor( | |
None, self._run_powershell, cmd | |
) | |
if not output: | |
return [] | |
data = json.loads(output) | |
if isinstance(data, dict): | |
data = [data] | |
vms = [] | |
for item in data: | |
vm = VirtualMachine( | |
name=item.get('Name', ''), | |
state=item.get('State', ''), | |
status=item.get('Status', ''), | |
) | |
vms.append(asdict(vm)) | |
return vms | |
except Exception as e: | |
logger.error(f"Failed to list VMs: {e}") | |
raise | |
async def get_vm_status(self, vm_name: str) -> Dict[str, Any]: | |
"""Get status of a specific virtual machine""" | |
try: | |
cmd = ( | |
f"$vm = Get-VM -Name '{vm_name}' -ErrorAction Stop; " | |
"$vm | Select-Object Name,State,Status | " | |
"ConvertTo-Json -Depth 2" | |
) | |
output = await asyncio.get_event_loop().run_in_executor( | |
None, self._run_powershell, cmd | |
) | |
if not output: | |
return {} | |
return json.loads(output) | |
except Exception as e: | |
logger.error(f"Failed to get VM status for {vm_name}: {e}") | |
raise | |
async def start_vm(self, vm_name: str) -> Dict[str, Any]: | |
"""Start a virtual machine""" | |
try: | |
cmd = f"Start-VM -Name '{vm_name}' -ErrorAction Stop" | |
await asyncio.get_event_loop().run_in_executor( | |
None, self._run_powershell, cmd | |
) | |
return {"success": True, "message": f"VM '{vm_name}' started successfully"} | |
except Exception as e: | |
logger.error(f"Failed to start VM {vm_name}: {e}") | |
raise | |
async def stop_vm(self, vm_name: str, force: bool = False) -> Dict[str, Any]: | |
"""Stop a virtual machine""" | |
try: | |
force_flag = "-Force" if force else "" | |
cmd = f"Stop-VM -Name '{vm_name}' {force_flag} -ErrorAction Stop" | |
await asyncio.get_event_loop().run_in_executor( | |
None, self._run_powershell, cmd | |
) | |
return {"success": True, "message": f"VM '{vm_name}' stopped successfully"} | |
except Exception as e: | |
logger.error(f"Failed to stop VM {vm_name}: {e}") | |
raise | |
async def restart_vm(self, vm_name: str, force: bool = False) -> Dict[str, Any]: | |
"""Restart a virtual machine""" | |
try: | |
force_flag = "-Force" if force else "" | |
cmd = f"Restart-VM -Name '{vm_name}' {force_flag} -ErrorAction Stop" | |
await asyncio.get_event_loop().run_in_executor( | |
None, self._run_powershell, cmd | |
) | |
return {"success": True, "message": f"VM '{vm_name}' restarted successfully"} | |
except Exception as e: | |
logger.error(f"Failed to restart VM {vm_name}: {e}") | |
raise | |
# Initialize FastAPI app and Hyper-V manager | |
app = FastAPI(title="Hyper-V MCP Server", version="1.0.0") | |
hyperv_manager = HyperVManager() | |
# Tool definitions | |
TOOLS = { | |
"list_vms": { | |
"name": "list_vms", | |
"description": "List all virtual machines on the Hyper-V host", | |
"inputSchema": { | |
"type": "object", | |
"properties": {}, | |
"required": [] | |
} | |
}, | |
"get_vm_status": { | |
"name": "get_vm_status", | |
"description": "Get detailed status information for a specific virtual machine", | |
"inputSchema": { | |
"type": "object", | |
"properties": { | |
"vm_name": {"type": "string", "description": "Name of the virtual machine"} | |
}, | |
"required": ["vm_name"] | |
} | |
}, | |
"start_vm": { | |
"name": "start_vm", | |
"description": "Start a virtual machine", | |
"inputSchema": { | |
"type": "object", | |
"properties": { | |
"vm_name": {"type": "string", "description": "Name of the virtual machine to start"} | |
}, | |
"required": ["vm_name"] | |
} | |
}, | |
"stop_vm": { | |
"name": "stop_vm", | |
"description": "Stop a virtual machine", | |
"inputSchema": { | |
"type": "object", | |
"properties": { | |
"vm_name": {"type": "string", "description": "Name of the virtual machine to stop"}, | |
"force": {"type": "boolean", "description": "Force stop the VM", "default": False} | |
}, | |
"required": ["vm_name"] | |
} | |
}, | |
"restart_vm": { | |
"name": "restart_vm", | |
"description": "Restart a virtual machine", | |
"inputSchema": { | |
"type": "object", | |
"properties": { | |
"vm_name": {"type": "string", "description": "Name of the virtual machine to restart"}, | |
"force": {"type": "boolean", "description": "Force restart the VM", "default": False} | |
}, | |
"required": ["vm_name"] | |
} | |
}, | |
} | |
# API Endpoints | |
async def root(): | |
"""Health check endpoint""" | |
return {"status": "Hyper-V MCP Server is running", "version": "1.0.0"} | |
async def list_tools(): | |
"""List all available tools""" | |
tools = [ToolInfo(**tool_info) for tool_info in TOOLS.values()] | |
return ToolsListResponse(tools=tools) | |
async def call_tool(request: ToolCallRequest): | |
"""Execute a tool with given arguments""" | |
try: | |
tool_name = request.name | |
arguments = request.arguments | |
if tool_name not in TOOLS: | |
raise HTTPException(status_code=404, detail=f"Tool '{tool_name}' not found") | |
# Get the corresponding method from HyperVManager | |
if not hasattr(hyperv_manager, tool_name): | |
raise HTTPException(status_code=500, detail=f"Method '{tool_name}' not implemented") | |
method = getattr(hyperv_manager, tool_name) | |
# Call the method with arguments | |
if arguments: | |
result = await method(**arguments) | |
else: | |
result = await method() | |
return ToolCallResponse(success=True, result=result) | |
except Exception as e: | |
logger.error(f"Tool execution error: {e}") | |
return ToolCallResponse(success=False, error=str(e)) | |
# Additional convenience endpoints | |
async def get_vms(): | |
"""Convenience endpoint to list VMs""" | |
try: | |
result = await hyperv_manager.list_vms() | |
return {"success": True, "vms": result} | |
except Exception as e: | |
raise HTTPException(status_code=500, detail=str(e)) | |
async def get_vm(vm_name: str): | |
"""Convenience endpoint to get VM status""" | |
try: | |
result = await hyperv_manager.get_vm_status(vm_name) | |
return {"success": True, "vm": result} | |
except Exception as e: | |
raise HTTPException(status_code=500, detail=str(e)) | |
async def start_vm_endpoint(vm_name: str): | |
"""Convenience endpoint to start a VM""" | |
try: | |
result = await hyperv_manager.start_vm(vm_name) | |
return result | |
except Exception as e: | |
raise HTTPException(status_code=500, detail=str(e)) | |
async def stop_vm_endpoint(vm_name: str, force: bool = False): | |
"""Convenience endpoint to stop a VM""" | |
try: | |
result = await hyperv_manager.stop_vm(vm_name, force) | |
return result | |
except Exception as e: | |
raise HTTPException(status_code=500, detail=str(e)) | |
if __name__ == "__main__": | |
print("Starting Hyper-V MCP Server...") | |
print("Server will be available at: http://localhost:8000") | |
print("API documentation at: http://localhost:8000/docs") | |
uvicorn.run( | |
app, | |
host="0.0.0.0", | |
port=8000, | |
log_level="info" | |
) |