"""Domain management tools for FastMCP.""" from typing import Any, Dict, List from pydantic import BaseModel, Field from fastmcp import FastMCP from .client import NameCheapClient from .server import NameCheapAPIError # Create domain tools instance domains_mcp = FastMCP("domains") # Global client instance _client: NameCheapClient = None def get_client() -> NameCheapClient: """Get or create the Name Cheap client.""" global _client if _client is None: _client = NameCheapClient() return _client # Pydantic models for validation class ContactInfo(BaseModel): """Contact information for domain registration.""" first_name: str = Field(..., description="First name") last_name: str = Field(..., description="Last name") address1: str = Field(..., description="Street address") city: str = Field(..., description="City") state_province: str = Field(..., description="State or province") postal_code: str = Field(..., description="Postal/ZIP code") country: str = Field(..., description="Country code (e.g., US, CA)") phone: str = Field(..., description="Phone number with country code") email_address: str = Field(..., description="Email address") address2: str = Field("", description="Apartment/suite number") organization_name: str = Field("", description="Organization name") class DomainCheckRequest(BaseModel): """Request model for domain availability check.""" domains: List[str] = Field(..., min_items=1, description="List of domain names to check") class DomainRegisterRequest(BaseModel): """Request model for domain registration.""" domain_name: str = Field(..., description="Domain name to register") years: int = Field(1, ge=1, le=10, description="Registration period in years") contacts: ContactInfo = Field(..., description="Contact information") class DomainRenewRequest(BaseModel): """Request model for domain renewal.""" domain_identifier: str = Field(..., description="Domain name or identifier") years: int = Field(1, ge=1, le=10, description="Renewal period in years") # Domain management tools @domains_mcp.tool() async def list_domains() -> Dict[str, Any]: """List all domains in the account with enhanced information. Returns: Success response with domain list or error details """ try: client = get_client() domains = await client.list_domains() return { "success": True, "data": domains, "message": f"Found {len(domains)} domains in account" } except NameCheapAPIError as e: return { "success": False, "error": str(e), "message": "Failed to list domains" } except Exception as e: return { "success": False, "error": f"Unexpected error: {str(e)}", "message": "Failed to list domains" } @domains_mcp.tool() async def get_domain_info(domain_identifier: str) -> Dict[str, Any]: """Get detailed information about a domain. Args: domain_identifier: Domain name (supports exact domain name matching) Returns: Success response with domain details or error details """ try: client = get_client() domain_info = await client.get_domain_info(domain_identifier) return { "success": True, "data": domain_info, "message": f"Retrieved information for domain {domain_identifier}" } except NameCheapAPIError as e: return { "success": False, "error": str(e), "message": f"Failed to get domain info for {domain_identifier}" } except Exception as e: return { "success": False, "error": f"Unexpected error: {str(e)}", "message": f"Failed to get domain info for {domain_identifier}" } @domains_mcp.tool() async def check_domain_availability(request: DomainCheckRequest) -> Dict[str, Any]: """Check if domains are available for registration. Args: request: Domain check request with list of domains Returns: Success response with availability results or error details """ try: client = get_client() results = await client.check_domain_availability(request.domains) return { "success": True, "data": results, "message": f"Checked availability for {len(request.domains)} domains" } except NameCheapAPIError as e: return { "success": False, "error": str(e), "message": "Failed to check domain availability" } except Exception as e: return { "success": False, "error": f"Unexpected error: {str(e)}", "message": "Failed to check domain availability" } @domains_mcp.tool() async def register_domain(request: DomainRegisterRequest) -> Dict[str, Any]: """Register a new domain. Args: request: Domain registration request with domain, years, and contact info Returns: Success response with registration details or error details """ try: client = get_client() # Convert ContactInfo to API format contact_data = { "FirstName": request.contacts.first_name, "LastName": request.contacts.last_name, "Address1": request.contacts.address1, "City": request.contacts.city, "StateProvince": request.contacts.state_province, "PostalCode": request.contacts.postal_code, "Country": request.contacts.country, "Phone": request.contacts.phone, "EmailAddress": request.contacts.email_address, } if request.contacts.address2: contact_data["Address2"] = request.contacts.address2 if request.contacts.organization_name: contact_data["OrganizationName"] = request.contacts.organization_name result = await client.register_domain( request.domain_name, request.years, contact_data ) return { "success": True, "data": result, "message": f"Successfully registered domain {request.domain_name} for {request.years} years" } except NameCheapAPIError as e: return { "success": False, "error": str(e), "message": f"Failed to register domain {request.domain_name}" } except Exception as e: return { "success": False, "error": f"Unexpected error: {str(e)}", "message": f"Failed to register domain {request.domain_name}" } @domains_mcp.tool() async def renew_domain(request: DomainRenewRequest) -> Dict[str, Any]: """Renew an existing domain. Args: request: Domain renewal request with identifier and years Returns: Success response with renewal details or error details """ try: client = get_client() result = await client.renew_domain(request.domain_identifier, request.years) return { "success": True, "data": result, "message": f"Successfully renewed domain {request.domain_identifier} for {request.years} years" } except NameCheapAPIError as e: return { "success": False, "error": str(e), "message": f"Failed to renew domain {request.domain_identifier}" } except Exception as e: return { "success": False, "error": f"Unexpected error: {str(e)}", "message": f"Failed to renew domain {request.domain_identifier}" } # Resource for domain listings @domains_mcp.resource("namecheap://domains") async def list_domains_resource() -> str: """List all domains as a resource.""" try: client = get_client() domains = await client.list_domains() # Format as readable text if not domains: return "No domains found in account." output = ["Domains in Account:", "=" * 20, ""] for domain in domains: name = domain.get("@Name", "Unknown") expires = domain.get("@Expires", "Unknown") is_expired = domain.get("_is_expired", False) is_locked = domain.get("_is_locked", False) status_flags = [] if is_expired: status_flags.append("EXPIRED") if is_locked: status_flags.append("LOCKED") status = f" [{', '.join(status_flags)}]" if status_flags else "" output.append(f"• {name} (expires: {expires}){status}") return "\n".join(output) except Exception as e: return f"Error listing domains: {str(e)}" @domains_mcp.resource("namecheap://domain/{domain_name}") async def get_domain_resource(domain_name: str) -> str: """Get detailed domain information as a resource.""" try: client = get_client() domain_info = await client.get_domain_info(domain_name) # Format as readable text output = [f"Domain Information: {domain_name}", "=" * 40, ""] # Extract key information if "DomainGetInfoResult" in domain_info: info = domain_info["DomainGetInfoResult"] output.append(f"Status: {info.get('@Status', 'Unknown')}") output.append(f"ID: {info.get('@ID', 'Unknown')}") output.append(f"Owner Name: {info.get('@OwnerName', 'Unknown')}") output.append(f"Created: {info.get('@DomainDetails', {}).get('@CreatedDate', 'Unknown')}") output.append(f"Expires: {info.get('@DomainDetails', {}).get('@ExpiredDate', 'Unknown')}") output.append("") # Nameservers nameservers = info.get("DnsDetails", {}).get("Nameserver", []) if nameservers: output.append("Nameservers:") if isinstance(nameservers, list): for ns in nameservers: output.append(f" • {ns}") else: output.append(f" • {nameservers}") return "\n".join(output) except Exception as e: return f"Error getting domain info: {str(e)}"