mcp-vultr/src/mcp_vultr/instances.py
Ryan Malloy 566406e33e
Some checks failed
Tests / test (3.10) (push) Has been cancelled
Tests / test (3.11) (push) Has been cancelled
Tests / security (push) Has been cancelled
Tests / test (3.12) (push) Has been cancelled
Tests / test (3.13) (push) Has been cancelled
Tests / test-install (3.13) (push) Has been cancelled
Tests / build (push) Has been cancelled
Tests / test-install (3.10) (push) Has been cancelled
feat: Add universal UUID lookup pattern and update README
- Implement smart identifier resolution across all modules (v1.9.0)
  - Instances: lookup by label or hostname
  - SSH Keys: lookup by name
  - Firewall Groups: lookup by description
  - Snapshots: lookup by description
  - Reserved IPs: lookup by IP address
- All UUID lookups use exact matching for safety
- Update README.md with comprehensive feature documentation
- Add detailed changelog showing version progression
- Enhance examples to demonstrate smart identifier resolution

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-17 13:54:26 -06:00

379 lines
14 KiB
Python

"""
Vultr Instances FastMCP Module.
This module contains FastMCP tools and resources for managing Vultr instances.
"""
from typing import Optional, List, Dict, Any
from fastmcp import FastMCP
def create_instances_mcp(vultr_client) -> FastMCP:
"""
Create a FastMCP instance for Vultr instances management.
Args:
vultr_client: VultrDNSServer instance
Returns:
Configured FastMCP instance with instance management tools
"""
mcp = FastMCP(name="vultr-instances")
# Helper function to check if a string looks like a UUID
def is_uuid_format(s: str) -> bool:
"""Check if a string looks like a UUID."""
if len(s) == 36 and s.count('-') == 4:
return True
return False
# Helper function to get instance ID from label or hostname
async def get_instance_id(identifier: str) -> str:
"""
Get the instance ID from a label, hostname, or UUID.
Args:
identifier: Instance label, hostname, or UUID
Returns:
The instance ID (UUID)
Raises:
ValueError: If the instance is not found
"""
# If it looks like a UUID, return it as-is
if is_uuid_format(identifier):
return identifier
# Otherwise, search for it by label or hostname
instances = await vultr_client.list_instances()
for instance in instances:
if instance.get("label") == identifier or instance.get("hostname") == identifier:
return instance["id"]
raise ValueError(f"Instance '{identifier}' not found (searched by label and hostname)")
# Instance resources
@mcp.resource("instances://list")
async def list_instances_resource() -> List[Dict[str, Any]]:
"""List all instances in your Vultr account."""
return await vultr_client.list_instances()
@mcp.resource("instances://{instance_id}")
async def get_instance_resource(instance_id: str) -> Dict[str, Any]:
"""Get information about a specific instance.
Args:
instance_id: The instance ID, label, or hostname
"""
actual_id = await get_instance_id(instance_id)
return await vultr_client.get_instance(actual_id)
# Instance tools
@mcp.tool
async def list() -> List[Dict[str, Any]]:
"""List all instances in your Vultr account.
Returns:
List of instance objects with details including:
- id: Instance ID
- label: Instance label
- hostname: Instance hostname
- region: Region code
- plan: Plan ID
- os: Operating system
- status: Instance status (active, pending, etc.)
- main_ip: Primary IPv4 address
- v6_main_ip: Primary IPv6 address
- date_created: Creation date
"""
return await vultr_client.list_instances()
@mcp.tool
async def get(instance_id: str) -> Dict[str, Any]:
"""Get detailed information about a specific instance.
Args:
instance_id: The instance ID, label, or hostname (e.g., "web-server", "db.example.com", or UUID)
Returns:
Detailed instance information
"""
actual_id = await get_instance_id(instance_id)
return await vultr_client.get_instance(actual_id)
@mcp.tool
async def create(
region: str,
plan: str,
label: Optional[str] = None,
os_id: Optional[int] = None,
iso_id: Optional[str] = None,
script_id: Optional[str] = None,
snapshot_id: Optional[str] = None,
enable_ipv6: bool = False,
enable_private_network: bool = False,
attach_private_network: Optional[List[str]] = None,
ssh_key_ids: Optional[List[str]] = None,
backups: bool = False,
app_id: Optional[int] = None,
user_data: Optional[str] = None,
ddos_protection: bool = False,
activation_email: bool = False,
hostname: Optional[str] = None,
tag: Optional[str] = None,
firewall_group_id: Optional[str] = None,
reserved_ipv4: Optional[str] = None
) -> Dict[str, Any]:
"""Create a new instance.
Args:
region: Region code (e.g., 'ewr', 'lax')
plan: Plan ID (e.g., 'vc2-1c-1gb')
label: Label for the instance
os_id: Operating System ID (use list_os to get available options)
iso_id: ISO ID for custom installation
script_id: Startup script ID
snapshot_id: Snapshot ID to restore from
enable_ipv6: Enable IPv6
enable_private_network: Enable private networking
attach_private_network: List of private network IDs to attach
ssh_key_ids: List of SSH key IDs to install
backups: Enable automatic backups
app_id: Application ID to install
user_data: Cloud-init user data
ddos_protection: Enable DDoS protection
activation_email: Send activation email
hostname: Hostname for the instance
tag: Tag for the instance
firewall_group_id: Firewall group ID
reserved_ipv4: Reserved IPv4 address to use
Returns:
Created instance information
"""
return await vultr_client.create_instance(
region=region,
plan=plan,
label=label,
os_id=os_id,
iso_id=iso_id,
script_id=script_id,
snapshot_id=snapshot_id,
enable_ipv6=enable_ipv6,
enable_private_network=enable_private_network,
attach_private_network=attach_private_network,
ssh_key_ids=ssh_key_ids,
backups=backups,
app_id=app_id,
user_data=user_data,
ddos_protection=ddos_protection,
activation_email=activation_email,
hostname=hostname,
tag=tag,
firewall_group_id=firewall_group_id,
reserved_ipv4=reserved_ipv4
)
@mcp.tool
async def update(
instance_id: str,
label: Optional[str] = None,
tag: Optional[str] = None,
plan: Optional[str] = None,
enable_ipv6: Optional[bool] = None,
backups: Optional[bool] = None,
ddos_protection: Optional[bool] = None,
firewall_group_id: Optional[str] = None,
user_data: Optional[str] = None
) -> Dict[str, Any]:
"""Update an existing instance.
Args:
instance_id: The instance ID to update
label: New label for the instance
tag: New tag for the instance
plan: New plan ID (for resizing)
enable_ipv6: Enable/disable IPv6
backups: Enable/disable automatic backups
ddos_protection: Enable/disable DDoS protection
firewall_group_id: New firewall group ID
user_data: New cloud-init user data
Returns:
Updated instance information
"""
return await vultr_client.update_instance(
instance_id=instance_id,
label=label,
tag=tag,
plan=plan,
enable_ipv6=enable_ipv6,
backups=backups,
ddos_protection=ddos_protection,
firewall_group_id=firewall_group_id,
user_data=user_data
)
@mcp.tool
async def delete(instance_id: str) -> Dict[str, str]:
"""Delete an instance.
Args:
instance_id: The instance ID, label, or hostname (e.g., "web-server", "db.example.com", or UUID)
Returns:
Status message confirming deletion
"""
actual_id = await get_instance_id(instance_id)
await vultr_client.delete_instance(actual_id)
return {"status": "success", "message": f"Instance {instance_id} deleted successfully"}
@mcp.tool
async def start(instance_id: str) -> Dict[str, str]:
"""Start a stopped instance.
Args:
instance_id: The instance ID, label, or hostname (e.g., "web-server", "db.example.com", or UUID)
Returns:
Status message confirming start
"""
actual_id = await get_instance_id(instance_id)
await vultr_client.start_instance(actual_id)
return {"status": "success", "message": f"Instance {instance_id} started successfully"}
@mcp.tool
async def stop(instance_id: str) -> Dict[str, str]:
"""Stop a running instance.
Args:
instance_id: The instance ID, label, or hostname (e.g., "web-server", "db.example.com", or UUID)
Returns:
Status message confirming stop
"""
actual_id = await get_instance_id(instance_id)
await vultr_client.stop_instance(actual_id)
return {"status": "success", "message": f"Instance {instance_id} stopped successfully"}
@mcp.tool
async def reboot(instance_id: str) -> Dict[str, str]:
"""Reboot an instance.
Args:
instance_id: The instance ID, label, or hostname (e.g., "web-server", "db.example.com", or UUID)
Returns:
Status message confirming reboot
"""
actual_id = await get_instance_id(instance_id)
await vultr_client.reboot_instance(actual_id)
return {"status": "success", "message": f"Instance {instance_id} rebooted successfully"}
@mcp.tool
async def reinstall(instance_id: str, hostname: Optional[str] = None) -> Dict[str, Any]:
"""Reinstall an instance's operating system.
Args:
instance_id: The instance ID, label, or hostname (e.g., "web-server", "db.example.com", or UUID)
hostname: New hostname for the instance (optional)
Returns:
Reinstall status information
"""
actual_id = await get_instance_id(instance_id)
return await vultr_client.reinstall_instance(actual_id, hostname)
# Bandwidth information
@mcp.resource("instances://{instance_id}/bandwidth")
async def get_bandwidth_resource(instance_id: str) -> Dict[str, Any]:
"""Get bandwidth usage for an instance.
Args:
instance_id: The instance ID, label, or hostname
"""
actual_id = await get_instance_id(instance_id)
return await vultr_client.get_instance_bandwidth(actual_id)
@mcp.tool
async def get_bandwidth(instance_id: str) -> Dict[str, Any]:
"""Get bandwidth usage statistics for an instance.
Args:
instance_id: The instance ID, label, or hostname (e.g., "web-server", "db.example.com", or UUID)
Returns:
Bandwidth usage information
"""
actual_id = await get_instance_id(instance_id)
return await vultr_client.get_instance_bandwidth(actual_id)
# IPv4 management
@mcp.tool
async def list_ipv4(instance_id: str) -> List[Dict[str, Any]]:
"""List IPv4 addresses for an instance.
Args:
instance_id: The instance ID, label, or hostname (e.g., "web-server", "db.example.com", or UUID)
Returns:
List of IPv4 addresses
"""
actual_id = await get_instance_id(instance_id)
return await vultr_client.list_instance_ipv4(actual_id)
@mcp.tool
async def create_ipv4(instance_id: str, reboot: bool = True) -> Dict[str, Any]:
"""Create a new IPv4 address for an instance.
Args:
instance_id: The instance ID, label, or hostname (e.g., "web-server", "db.example.com", or UUID)
reboot: Whether to reboot the instance (default: True)
Returns:
Created IPv4 information
"""
actual_id = await get_instance_id(instance_id)
return await vultr_client.create_instance_ipv4(actual_id, reboot)
@mcp.tool
async def delete_ipv4(instance_id: str, ipv4: str) -> Dict[str, str]:
"""Delete an IPv4 address from an instance.
Args:
instance_id: The instance ID, label, or hostname (e.g., "web-server", "db.example.com", or UUID)
ipv4: The IPv4 address to delete
Returns:
Status message confirming deletion
"""
actual_id = await get_instance_id(instance_id)
await vultr_client.delete_instance_ipv4(actual_id, ipv4)
return {"status": "success", "message": f"IPv4 {ipv4} deleted successfully"}
# IPv6 management
@mcp.resource("instances://{instance_id}/ipv6")
async def list_ipv6_resource(instance_id: str) -> List[Dict[str, Any]]:
"""List IPv6 addresses for an instance.
Args:
instance_id: The instance ID, label, or hostname
"""
actual_id = await get_instance_id(instance_id)
return await vultr_client.list_instance_ipv6(actual_id)
@mcp.tool
async def list_ipv6(instance_id: str) -> List[Dict[str, Any]]:
"""List IPv6 addresses for an instance.
Args:
instance_id: The instance ID, label, or hostname (e.g., "web-server", "db.example.com", or UUID)
Returns:
List of IPv6 addresses
"""
actual_id = await get_instance_id(instance_id)
return await vultr_client.list_instance_ipv6(actual_id)
return mcp