Migrate from fastmcp to official MCP package
This commit is contained in:
parent
5c87097158
commit
b8fd6e4632
@ -10,7 +10,9 @@ import re
|
|||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from fastmcp import FastMCP
|
from mcp.server import Server
|
||||||
|
from mcp.server.stdio import stdio_server
|
||||||
|
from mcp.types import Resource, Tool, TextContent
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
@ -143,15 +145,15 @@ class VultrDNSServer:
|
|||||||
return await self._make_request("DELETE", f"/domains/{domain}/records/{record_id}")
|
return await self._make_request("DELETE", f"/domains/{domain}/records/{record_id}")
|
||||||
|
|
||||||
|
|
||||||
def create_mcp_server(api_key: Optional[str] = None) -> FastMCP:
|
def create_mcp_server(api_key: Optional[str] = None) -> Server:
|
||||||
"""
|
"""
|
||||||
Create and configure a FastMCP server for Vultr DNS management.
|
Create and configure an MCP server for Vultr DNS management.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
api_key: Vultr API key. If not provided, will read from VULTR_API_KEY env var.
|
api_key: Vultr API key. If not provided, will read from VULTR_API_KEY env var.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Configured FastMCP server instance
|
Configured MCP server instance
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: If API key is not provided and not found in environment
|
ValueError: If API key is not provided and not found in environment
|
||||||
@ -164,43 +166,43 @@ def create_mcp_server(api_key: Optional[str] = None) -> FastMCP:
|
|||||||
"VULTR_API_KEY must be provided either as parameter or environment variable"
|
"VULTR_API_KEY must be provided either as parameter or environment variable"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Initialize FastMCP server
|
# Initialize MCP server
|
||||||
mcp = FastMCP("Vultr DNS Manager")
|
server = Server("vultr-dns-mcp")
|
||||||
|
|
||||||
# Initialize Vultr client
|
# Initialize Vultr client
|
||||||
vultr_client = VultrDNSServer(api_key)
|
vultr_client = VultrDNSServer(api_key)
|
||||||
|
|
||||||
# Add resources for client discovery
|
# Add resources for client discovery
|
||||||
@mcp.resource("vultr://domains")
|
@server.list_resources()
|
||||||
async def get_domains_resource():
|
async def list_resources() -> List[Resource]:
|
||||||
"""Resource listing all available domains."""
|
"""List available resources."""
|
||||||
try:
|
return [
|
||||||
domains = await vultr_client.list_domains()
|
Resource(
|
||||||
return {
|
uri="vultr://domains",
|
||||||
"uri": "vultr://domains",
|
name="DNS Domains",
|
||||||
"name": "DNS Domains",
|
description="All DNS domains in your Vultr account",
|
||||||
"description": "All DNS domains in your Vultr account",
|
mimeType="application/json"
|
||||||
"mimeType": "application/json",
|
),
|
||||||
"content": domains
|
Resource(
|
||||||
}
|
uri="vultr://capabilities",
|
||||||
except Exception as e:
|
name="Server Capabilities",
|
||||||
return {
|
description="Vultr DNS server capabilities and supported features",
|
||||||
"uri": "vultr://domains",
|
mimeType="application/json"
|
||||||
"name": "DNS Domains",
|
)
|
||||||
"description": f"Error loading domains: {str(e)}",
|
]
|
||||||
"mimeType": "application/json",
|
|
||||||
"content": {"error": str(e)}
|
|
||||||
}
|
|
||||||
|
|
||||||
@mcp.resource("vultr://capabilities")
|
@server.read_resource()
|
||||||
async def get_capabilities_resource():
|
async def read_resource(uri: str) -> str:
|
||||||
"""Resource describing server capabilities and supported record types."""
|
"""Read a specific resource."""
|
||||||
return {
|
if uri == "vultr://domains":
|
||||||
"uri": "vultr://capabilities",
|
try:
|
||||||
"name": "Server Capabilities",
|
domains = await vultr_client.list_domains()
|
||||||
"description": "Vultr DNS server capabilities and supported features",
|
return str(domains)
|
||||||
"mimeType": "application/json",
|
except Exception as e:
|
||||||
"content": {
|
return f"Error loading domains: {str(e)}"
|
||||||
|
|
||||||
|
elif uri == "vultr://capabilities":
|
||||||
|
capabilities = {
|
||||||
"supported_record_types": [
|
"supported_record_types": [
|
||||||
{
|
{
|
||||||
"type": "A",
|
"type": "A",
|
||||||
@ -253,356 +255,470 @@ def create_mcp_server(api_key: Optional[str] = None) -> FastMCP:
|
|||||||
"min_ttl": 60,
|
"min_ttl": 60,
|
||||||
"max_ttl": 86400
|
"max_ttl": 86400
|
||||||
}
|
}
|
||||||
}
|
return str(capabilities)
|
||||||
|
|
||||||
@mcp.resource("vultr://records/{domain}")
|
elif uri.startswith("vultr://records/"):
|
||||||
async def get_domain_records_resource(domain: str):
|
domain = uri.replace("vultr://records/", "")
|
||||||
"""Resource listing all records for a specific domain."""
|
try:
|
||||||
try:
|
records = await vultr_client.list_records(domain)
|
||||||
records = await vultr_client.list_records(domain)
|
return str({
|
||||||
return {
|
|
||||||
"uri": f"vultr://records/{domain}",
|
|
||||||
"name": f"DNS Records for {domain}",
|
|
||||||
"description": f"All DNS records configured for domain {domain}",
|
|
||||||
"mimeType": "application/json",
|
|
||||||
"content": {
|
|
||||||
"domain": domain,
|
"domain": domain,
|
||||||
"records": records,
|
"records": records,
|
||||||
"record_count": len(records)
|
"record_count": len(records)
|
||||||
}
|
})
|
||||||
}
|
except Exception as e:
|
||||||
except Exception as e:
|
return f"Error loading records for {domain}: {str(e)}"
|
||||||
return {
|
|
||||||
"uri": f"vultr://records/{domain}",
|
return "Resource not found"
|
||||||
"name": f"DNS Records for {domain}",
|
|
||||||
"description": f"Error loading records for {domain}: {str(e)}",
|
|
||||||
"mimeType": "application/json",
|
|
||||||
"content": {"error": str(e), "domain": domain}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Define MCP tools
|
# Define MCP tools
|
||||||
@mcp.tool()
|
@server.list_tools()
|
||||||
async def list_dns_domains() -> List[Dict[str, Any]]:
|
async def list_tools() -> List[Tool]:
|
||||||
"""
|
"""List available tools."""
|
||||||
List all DNS domains in your Vultr account.
|
return [
|
||||||
|
Tool(
|
||||||
|
name="list_dns_domains",
|
||||||
|
description="List all DNS domains in your Vultr account",
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {},
|
||||||
|
"required": []
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Tool(
|
||||||
|
name="get_dns_domain",
|
||||||
|
description="Get detailed information for a specific DNS domain",
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"domain": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The domain name to retrieve (e.g., 'example.com')"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["domain"]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Tool(
|
||||||
|
name="create_dns_domain",
|
||||||
|
description="Create a new DNS domain with a default A record",
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"domain": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The domain name to create (e.g., 'newdomain.com')"
|
||||||
|
},
|
||||||
|
"ip": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "IPv4 address for the default A record (e.g., '192.168.1.100')"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["domain", "ip"]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Tool(
|
||||||
|
name="delete_dns_domain",
|
||||||
|
description="Delete a DNS domain and ALL its associated records",
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"domain": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The domain name to delete (e.g., 'example.com')"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["domain"]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Tool(
|
||||||
|
name="list_dns_records",
|
||||||
|
description="List all DNS records for a specific domain",
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"domain": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The domain name (e.g., 'example.com')"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["domain"]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Tool(
|
||||||
|
name="get_dns_record",
|
||||||
|
description="Get detailed information for a specific DNS record",
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"domain": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The domain name (e.g., 'example.com')"
|
||||||
|
},
|
||||||
|
"record_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The unique record identifier"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["domain", "record_id"]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Tool(
|
||||||
|
name="create_dns_record",
|
||||||
|
description="Create a new DNS record for a domain",
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"domain": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The domain name (e.g., 'example.com')"
|
||||||
|
},
|
||||||
|
"record_type": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Record type (A, AAAA, CNAME, MX, TXT, NS, SRV)"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Record name/subdomain"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Record value"
|
||||||
|
},
|
||||||
|
"ttl": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Time to live in seconds (60-86400, default: 300)"
|
||||||
|
},
|
||||||
|
"priority": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Priority for MX/SRV records (0-65535)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["domain", "record_type", "name", "data"]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Tool(
|
||||||
|
name="update_dns_record",
|
||||||
|
description="Update an existing DNS record with new configuration",
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"domain": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The domain name (e.g., 'example.com')"
|
||||||
|
},
|
||||||
|
"record_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The unique identifier of the record to update"
|
||||||
|
},
|
||||||
|
"record_type": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "New record type (A, AAAA, CNAME, MX, TXT, NS, SRV)"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "New record name/subdomain"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "New record value"
|
||||||
|
},
|
||||||
|
"ttl": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "New TTL in seconds (60-86400, optional)"
|
||||||
|
},
|
||||||
|
"priority": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "New priority for MX/SRV records (optional)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["domain", "record_id", "record_type", "name", "data"]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Tool(
|
||||||
|
name="delete_dns_record",
|
||||||
|
description="Delete a specific DNS record",
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"domain": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The domain name (e.g., 'example.com')"
|
||||||
|
},
|
||||||
|
"record_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The unique identifier of the record to delete"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["domain", "record_id"]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Tool(
|
||||||
|
name="validate_dns_record",
|
||||||
|
description="Validate DNS record parameters before creation",
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"record_type": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The record type (A, AAAA, CNAME, MX, TXT, NS, SRV)"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The record name/subdomain"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The record data/value"
|
||||||
|
},
|
||||||
|
"ttl": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Time to live in seconds (optional)"
|
||||||
|
},
|
||||||
|
"priority": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Priority for MX/SRV records (optional)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["record_type", "name", "data"]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Tool(
|
||||||
|
name="analyze_dns_records",
|
||||||
|
description="Analyze DNS configuration for a domain and provide insights",
|
||||||
|
inputSchema={
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"domain": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The domain name to analyze (e.g., 'example.com')"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["domain"]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
This tool retrieves all domains currently managed through Vultr DNS.
|
@server.call_tool()
|
||||||
Each domain object includes domain name, creation date, and status information.
|
async def call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
|
||||||
"""
|
"""Handle tool calls."""
|
||||||
try:
|
try:
|
||||||
domains = await vultr_client.list_domains()
|
if name == "list_dns_domains":
|
||||||
return domains
|
domains = await vultr_client.list_domains()
|
||||||
except Exception as e:
|
return [TextContent(type="text", text=str(domains))]
|
||||||
return [{"error": str(e)}]
|
|
||||||
|
|
||||||
@mcp.tool()
|
elif name == "get_dns_domain":
|
||||||
async def get_dns_domain(domain: str) -> Dict[str, Any]:
|
domain = arguments["domain"]
|
||||||
"""
|
result = await vultr_client.get_domain(domain)
|
||||||
Get detailed information for a specific DNS domain.
|
return [TextContent(type="text", text=str(result))]
|
||||||
|
|
||||||
Args:
|
elif name == "create_dns_domain":
|
||||||
domain: The domain name to retrieve (e.g., "example.com")
|
domain = arguments["domain"]
|
||||||
"""
|
ip = arguments["ip"]
|
||||||
try:
|
result = await vultr_client.create_domain(domain, ip)
|
||||||
return await vultr_client.get_domain(domain)
|
return [TextContent(type="text", text=str(result))]
|
||||||
except Exception as e:
|
|
||||||
return {"error": str(e)}
|
|
||||||
|
|
||||||
@mcp.tool()
|
elif name == "delete_dns_domain":
|
||||||
async def create_dns_domain(domain: str, ip: str) -> Dict[str, Any]:
|
domain = arguments["domain"]
|
||||||
"""
|
await vultr_client.delete_domain(domain)
|
||||||
Create a new DNS domain with a default A record.
|
return [TextContent(type="text", text=f"Domain {domain} deleted successfully")]
|
||||||
|
|
||||||
Args:
|
elif name == "list_dns_records":
|
||||||
domain: The domain name to create (e.g., "newdomain.com")
|
domain = arguments["domain"]
|
||||||
ip: IPv4 address for the default A record (e.g., "192.168.1.100")
|
records = await vultr_client.list_records(domain)
|
||||||
"""
|
return [TextContent(type="text", text=str(records))]
|
||||||
try:
|
|
||||||
return await vultr_client.create_domain(domain, ip)
|
|
||||||
except Exception as e:
|
|
||||||
return {"error": str(e)}
|
|
||||||
|
|
||||||
@mcp.tool()
|
elif name == "get_dns_record":
|
||||||
async def delete_dns_domain(domain: str) -> Dict[str, Any]:
|
domain = arguments["domain"]
|
||||||
"""
|
record_id = arguments["record_id"]
|
||||||
Delete a DNS domain and ALL its associated records.
|
result = await vultr_client.get_record(domain, record_id)
|
||||||
|
return [TextContent(type="text", text=str(result))]
|
||||||
|
|
||||||
⚠️ WARNING: This permanently deletes the domain and all DNS records.
|
elif name == "create_dns_record":
|
||||||
|
domain = arguments["domain"]
|
||||||
|
record_type = arguments["record_type"]
|
||||||
|
name = arguments["name"]
|
||||||
|
data = arguments["data"]
|
||||||
|
ttl = arguments.get("ttl")
|
||||||
|
priority = arguments.get("priority")
|
||||||
|
result = await vultr_client.create_record(domain, record_type, name, data, ttl, priority)
|
||||||
|
return [TextContent(type="text", text=str(result))]
|
||||||
|
|
||||||
Args:
|
elif name == "update_dns_record":
|
||||||
domain: The domain name to delete (e.g., "example.com")
|
domain = arguments["domain"]
|
||||||
"""
|
record_id = arguments["record_id"]
|
||||||
try:
|
record_type = arguments["record_type"]
|
||||||
await vultr_client.delete_domain(domain)
|
name = arguments["name"]
|
||||||
return {"success": f"Domain {domain} deleted successfully"}
|
data = arguments["data"]
|
||||||
except Exception as e:
|
ttl = arguments.get("ttl")
|
||||||
return {"error": str(e)}
|
priority = arguments.get("priority")
|
||||||
|
result = await vultr_client.update_record(domain, record_id, record_type, name, data, ttl, priority)
|
||||||
|
return [TextContent(type="text", text=str(result))]
|
||||||
|
|
||||||
@mcp.tool()
|
elif name == "delete_dns_record":
|
||||||
async def list_dns_records(domain: str) -> List[Dict[str, Any]]:
|
domain = arguments["domain"]
|
||||||
"""
|
record_id = arguments["record_id"]
|
||||||
List all DNS records for a specific domain.
|
await vultr_client.delete_record(domain, record_id)
|
||||||
|
return [TextContent(type="text", text=f"DNS record {record_id} deleted successfully")]
|
||||||
|
|
||||||
Args:
|
elif name == "validate_dns_record":
|
||||||
domain: The domain name (e.g., "example.com")
|
record_type = arguments["record_type"]
|
||||||
"""
|
name = arguments["name"]
|
||||||
try:
|
data = arguments["data"]
|
||||||
records = await vultr_client.list_records(domain)
|
ttl = arguments.get("ttl")
|
||||||
return records
|
priority = arguments.get("priority")
|
||||||
except Exception as e:
|
|
||||||
return [{"error": str(e)}]
|
|
||||||
|
|
||||||
@mcp.tool()
|
validation_result = {
|
||||||
async def get_dns_record(domain: str, record_id: str) -> Dict[str, Any]:
|
"valid": True,
|
||||||
"""
|
"errors": [],
|
||||||
Get detailed information for a specific DNS record.
|
"warnings": [],
|
||||||
|
"suggestions": []
|
||||||
|
}
|
||||||
|
|
||||||
Args:
|
# Validate record type
|
||||||
domain: The domain name (e.g., "example.com")
|
valid_types = ['A', 'AAAA', 'CNAME', 'MX', 'TXT', 'NS', 'SRV']
|
||||||
record_id: The unique record identifier
|
if record_type.upper() not in valid_types:
|
||||||
"""
|
validation_result["valid"] = False
|
||||||
try:
|
validation_result["errors"].append(f"Invalid record type. Must be one of: {', '.join(valid_types)}")
|
||||||
return await vultr_client.get_record(domain, record_id)
|
|
||||||
except Exception as e:
|
|
||||||
return {"error": str(e)}
|
|
||||||
|
|
||||||
@mcp.tool()
|
record_type = record_type.upper()
|
||||||
async def create_dns_record(
|
|
||||||
domain: str,
|
|
||||||
record_type: str,
|
|
||||||
name: str,
|
|
||||||
data: str,
|
|
||||||
ttl: Optional[int] = None,
|
|
||||||
priority: Optional[int] = None
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Create a new DNS record for a domain.
|
|
||||||
|
|
||||||
Args:
|
# Validate TTL
|
||||||
domain: The domain name (e.g., "example.com")
|
if ttl is not None:
|
||||||
record_type: Record type (A, AAAA, CNAME, MX, TXT, NS, SRV)
|
if ttl < 60 or ttl > 86400:
|
||||||
name: Record name/subdomain
|
validation_result["warnings"].append("TTL should be between 60 and 86400 seconds")
|
||||||
data: Record value
|
elif ttl < 300:
|
||||||
ttl: Time to live in seconds (60-86400, default: 300)
|
validation_result["warnings"].append("Low TTL values may impact DNS performance")
|
||||||
priority: Priority for MX/SRV records (0-65535)
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return await vultr_client.create_record(domain, record_type, name, data, ttl, priority)
|
|
||||||
except Exception as e:
|
|
||||||
return {"error": str(e)}
|
|
||||||
|
|
||||||
@mcp.tool()
|
# Record-specific validation
|
||||||
async def update_dns_record(
|
if record_type == 'A':
|
||||||
domain: str,
|
ipv4_pattern = r'^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'
|
||||||
record_id: str,
|
if not re.match(ipv4_pattern, data):
|
||||||
record_type: str,
|
validation_result["valid"] = False
|
||||||
name: str,
|
validation_result["errors"].append("Invalid IPv4 address format")
|
||||||
data: str,
|
|
||||||
ttl: Optional[int] = None,
|
|
||||||
priority: Optional[int] = None
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Update an existing DNS record with new configuration.
|
|
||||||
|
|
||||||
Args:
|
elif record_type == 'AAAA':
|
||||||
domain: The domain name (e.g., "example.com")
|
if '::' in data and data.count('::') > 1:
|
||||||
record_id: The unique identifier of the record to update
|
validation_result["valid"] = False
|
||||||
record_type: New record type (A, AAAA, CNAME, MX, TXT, NS, SRV)
|
validation_result["errors"].append("Invalid IPv6 address: multiple :: sequences")
|
||||||
name: New record name/subdomain
|
|
||||||
data: New record value
|
|
||||||
ttl: New TTL in seconds (60-86400, optional)
|
|
||||||
priority: New priority for MX/SRV records (optional)
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return await vultr_client.update_record(domain, record_id, record_type, name, data, ttl, priority)
|
|
||||||
except Exception as e:
|
|
||||||
return {"error": str(e)}
|
|
||||||
|
|
||||||
@mcp.tool()
|
elif record_type == 'CNAME':
|
||||||
async def delete_dns_record(domain: str, record_id: str) -> Dict[str, Any]:
|
if name == '@' or name == '':
|
||||||
"""
|
validation_result["valid"] = False
|
||||||
Delete a specific DNS record.
|
validation_result["errors"].append("CNAME records cannot be used for root domain (@)")
|
||||||
|
|
||||||
Args:
|
elif record_type == 'MX':
|
||||||
domain: The domain name (e.g., "example.com")
|
if priority is None:
|
||||||
record_id: The unique identifier of the record to delete
|
validation_result["valid"] = False
|
||||||
"""
|
validation_result["errors"].append("MX records require a priority value")
|
||||||
try:
|
elif priority < 0 or priority > 65535:
|
||||||
await vultr_client.delete_record(domain, record_id)
|
validation_result["valid"] = False
|
||||||
return {"success": f"DNS record {record_id} deleted successfully"}
|
validation_result["errors"].append("MX priority must be between 0 and 65535")
|
||||||
except Exception as e:
|
|
||||||
return {"error": str(e)}
|
|
||||||
|
|
||||||
@mcp.tool()
|
elif record_type == 'SRV':
|
||||||
async def validate_dns_record(
|
if priority is None:
|
||||||
record_type: str,
|
validation_result["valid"] = False
|
||||||
name: str,
|
validation_result["errors"].append("SRV records require a priority value")
|
||||||
data: str,
|
srv_parts = data.split()
|
||||||
ttl: Optional[int] = None,
|
if len(srv_parts) != 3:
|
||||||
priority: Optional[int] = None
|
validation_result["valid"] = False
|
||||||
) -> Dict[str, Any]:
|
validation_result["errors"].append("SRV data must be in format: 'weight port target'")
|
||||||
"""
|
|
||||||
Validate DNS record parameters before creation.
|
|
||||||
|
|
||||||
Args:
|
result = {
|
||||||
record_type: The record type (A, AAAA, CNAME, MX, TXT, NS, SRV)
|
"record_type": record_type,
|
||||||
name: The record name/subdomain
|
"name": name,
|
||||||
data: The record data/value
|
"data": data,
|
||||||
ttl: Time to live in seconds (optional)
|
"ttl": ttl,
|
||||||
priority: Priority for MX/SRV records (optional)
|
"priority": priority,
|
||||||
"""
|
"validation": validation_result
|
||||||
validation_result = {
|
}
|
||||||
"valid": True,
|
return [TextContent(type="text", text=str(result))]
|
||||||
"errors": [],
|
|
||||||
"warnings": [],
|
|
||||||
"suggestions": []
|
|
||||||
}
|
|
||||||
|
|
||||||
# Validate record type
|
elif name == "analyze_dns_records":
|
||||||
valid_types = ['A', 'AAAA', 'CNAME', 'MX', 'TXT', 'NS', 'SRV']
|
domain = arguments["domain"]
|
||||||
if record_type.upper() not in valid_types:
|
records = await vultr_client.list_records(domain)
|
||||||
validation_result["valid"] = False
|
|
||||||
validation_result["errors"].append(f"Invalid record type. Must be one of: {', '.join(valid_types)}")
|
|
||||||
|
|
||||||
record_type = record_type.upper()
|
# Analyze records
|
||||||
|
record_types = {}
|
||||||
|
total_records = len(records)
|
||||||
|
ttl_values = []
|
||||||
|
has_root_a = False
|
||||||
|
has_www = False
|
||||||
|
has_mx = False
|
||||||
|
has_spf = False
|
||||||
|
|
||||||
# Validate TTL
|
for record in records:
|
||||||
if ttl is not None:
|
record_type = record.get('type', 'UNKNOWN')
|
||||||
if ttl < 60 or ttl > 86400:
|
record_name = record.get('name', '')
|
||||||
validation_result["warnings"].append("TTL should be between 60 and 86400 seconds")
|
record_data = record.get('data', '')
|
||||||
elif ttl < 300:
|
ttl = record.get('ttl', 300)
|
||||||
validation_result["warnings"].append("Low TTL values may impact DNS performance")
|
|
||||||
|
|
||||||
# Record-specific validation
|
record_types[record_type] = record_types.get(record_type, 0) + 1
|
||||||
if record_type == 'A':
|
ttl_values.append(ttl)
|
||||||
ipv4_pattern = r'^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'
|
|
||||||
if not re.match(ipv4_pattern, data):
|
|
||||||
validation_result["valid"] = False
|
|
||||||
validation_result["errors"].append("Invalid IPv4 address format")
|
|
||||||
|
|
||||||
elif record_type == 'AAAA':
|
if record_type == 'A' and record_name in ['@', domain]:
|
||||||
if '::' in data and data.count('::') > 1:
|
has_root_a = True
|
||||||
validation_result["valid"] = False
|
if record_name == 'www':
|
||||||
validation_result["errors"].append("Invalid IPv6 address: multiple :: sequences")
|
has_www = True
|
||||||
|
if record_type == 'MX':
|
||||||
|
has_mx = True
|
||||||
|
if record_type == 'TXT' and 'spf1' in record_data.lower():
|
||||||
|
has_spf = True
|
||||||
|
|
||||||
elif record_type == 'CNAME':
|
# Generate recommendations
|
||||||
if name == '@' or name == '':
|
recommendations = []
|
||||||
validation_result["valid"] = False
|
issues = []
|
||||||
validation_result["errors"].append("CNAME records cannot be used for root domain (@)")
|
|
||||||
|
|
||||||
elif record_type == 'MX':
|
if not has_root_a:
|
||||||
if priority is None:
|
recommendations.append("Consider adding an A record for the root domain (@)")
|
||||||
validation_result["valid"] = False
|
if not has_www:
|
||||||
validation_result["errors"].append("MX records require a priority value")
|
recommendations.append("Consider adding a www subdomain (A or CNAME record)")
|
||||||
elif priority < 0 or priority > 65535:
|
if not has_mx and total_records > 1:
|
||||||
validation_result["valid"] = False
|
recommendations.append("Consider adding MX records if you plan to use email")
|
||||||
validation_result["errors"].append("MX priority must be between 0 and 65535")
|
if has_mx and not has_spf:
|
||||||
|
recommendations.append("Add SPF record (TXT) to prevent email spoofing")
|
||||||
|
|
||||||
elif record_type == 'SRV':
|
avg_ttl = sum(ttl_values) / len(ttl_values) if ttl_values else 0
|
||||||
if priority is None:
|
low_ttl_count = sum(1 for ttl in ttl_values if ttl < 300)
|
||||||
validation_result["valid"] = False
|
|
||||||
validation_result["errors"].append("SRV records require a priority value")
|
|
||||||
srv_parts = data.split()
|
|
||||||
if len(srv_parts) != 3:
|
|
||||||
validation_result["valid"] = False
|
|
||||||
validation_result["errors"].append("SRV data must be in format: 'weight port target'")
|
|
||||||
|
|
||||||
return {
|
if low_ttl_count > total_records * 0.5:
|
||||||
"record_type": record_type,
|
issues.append("Many records have very low TTL values, which may impact performance")
|
||||||
"name": name,
|
|
||||||
"data": data,
|
|
||||||
"ttl": ttl,
|
|
||||||
"priority": priority,
|
|
||||||
"validation": validation_result
|
|
||||||
}
|
|
||||||
|
|
||||||
@mcp.tool()
|
result = {
|
||||||
async def analyze_dns_records(domain: str) -> Dict[str, Any]:
|
"domain": domain,
|
||||||
"""
|
"analysis": {
|
||||||
Analyze DNS configuration for a domain and provide insights.
|
"total_records": total_records,
|
||||||
|
"record_types": record_types,
|
||||||
|
"average_ttl": round(avg_ttl),
|
||||||
|
"configuration_status": {
|
||||||
|
"has_root_domain": has_root_a,
|
||||||
|
"has_www_subdomain": has_www,
|
||||||
|
"has_email_mx": has_mx,
|
||||||
|
"has_spf_protection": has_spf
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"recommendations": recommendations,
|
||||||
|
"potential_issues": issues,
|
||||||
|
"records_detail": records
|
||||||
|
}
|
||||||
|
return [TextContent(type="text", text=str(result))]
|
||||||
|
|
||||||
Args:
|
else:
|
||||||
domain: The domain name to analyze (e.g., "example.com")
|
return [TextContent(type="text", text=f"Unknown tool: {name}")]
|
||||||
"""
|
|
||||||
try:
|
|
||||||
records = await vultr_client.list_records(domain)
|
|
||||||
|
|
||||||
# Analyze records
|
|
||||||
record_types = {}
|
|
||||||
total_records = len(records)
|
|
||||||
ttl_values = []
|
|
||||||
has_root_a = False
|
|
||||||
has_www = False
|
|
||||||
has_mx = False
|
|
||||||
has_spf = False
|
|
||||||
|
|
||||||
for record in records:
|
|
||||||
record_type = record.get('type', 'UNKNOWN')
|
|
||||||
record_name = record.get('name', '')
|
|
||||||
record_data = record.get('data', '')
|
|
||||||
ttl = record.get('ttl', 300)
|
|
||||||
|
|
||||||
record_types[record_type] = record_types.get(record_type, 0) + 1
|
|
||||||
ttl_values.append(ttl)
|
|
||||||
|
|
||||||
if record_type == 'A' and record_name in ['@', domain]:
|
|
||||||
has_root_a = True
|
|
||||||
if record_name == 'www':
|
|
||||||
has_www = True
|
|
||||||
if record_type == 'MX':
|
|
||||||
has_mx = True
|
|
||||||
if record_type == 'TXT' and 'spf1' in record_data.lower():
|
|
||||||
has_spf = True
|
|
||||||
|
|
||||||
# Generate recommendations
|
|
||||||
recommendations = []
|
|
||||||
issues = []
|
|
||||||
|
|
||||||
if not has_root_a:
|
|
||||||
recommendations.append("Consider adding an A record for the root domain (@)")
|
|
||||||
if not has_www:
|
|
||||||
recommendations.append("Consider adding a www subdomain (A or CNAME record)")
|
|
||||||
if not has_mx and total_records > 1:
|
|
||||||
recommendations.append("Consider adding MX records if you plan to use email")
|
|
||||||
if has_mx and not has_spf:
|
|
||||||
recommendations.append("Add SPF record (TXT) to prevent email spoofing")
|
|
||||||
|
|
||||||
avg_ttl = sum(ttl_values) / len(ttl_values) if ttl_values else 0
|
|
||||||
low_ttl_count = sum(1 for ttl in ttl_values if ttl < 300)
|
|
||||||
|
|
||||||
if low_ttl_count > total_records * 0.5:
|
|
||||||
issues.append("Many records have very low TTL values, which may impact performance")
|
|
||||||
|
|
||||||
return {
|
|
||||||
"domain": domain,
|
|
||||||
"analysis": {
|
|
||||||
"total_records": total_records,
|
|
||||||
"record_types": record_types,
|
|
||||||
"average_ttl": round(avg_ttl),
|
|
||||||
"configuration_status": {
|
|
||||||
"has_root_domain": has_root_a,
|
|
||||||
"has_www_subdomain": has_www,
|
|
||||||
"has_email_mx": has_mx,
|
|
||||||
"has_spf_protection": has_spf
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"recommendations": recommendations,
|
|
||||||
"potential_issues": issues,
|
|
||||||
"records_detail": records
|
|
||||||
}
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"error": str(e), "domain": domain}
|
return [TextContent(type="text", text=f"Error: {str(e)}")]
|
||||||
|
|
||||||
return mcp
|
return server
|
||||||
|
|
||||||
|
|
||||||
def run_server(api_key: Optional[str] = None) -> None:
|
async def run_server(api_key: Optional[str] = None) -> None:
|
||||||
"""
|
"""
|
||||||
Create and run a Vultr DNS MCP server.
|
Create and run a Vultr DNS MCP server.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
api_key: Vultr API key. If not provided, will read from VULTR_API_KEY env var.
|
api_key: Vultr API key. If not provided, will read from VULTR_API_KEY env var.
|
||||||
"""
|
"""
|
||||||
mcp = create_mcp_server(api_key)
|
server = create_mcp_server(api_key)
|
||||||
mcp.run()
|
async with stdio_server() as (read_stream, write_stream):
|
||||||
|
await server.run(read_stream, write_stream, None)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user