Migrate from fastmcp to official MCP package

This commit is contained in:
Ryan Malloy 2025-06-11 17:53:43 -06:00
parent 5c87097158
commit b8fd6e4632

View File

@ -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."""
return [
Resource(
uri="vultr://domains",
name="DNS Domains",
description="All DNS domains in your Vultr account",
mimeType="application/json"
),
Resource(
uri="vultr://capabilities",
name="Server Capabilities",
description="Vultr DNS server capabilities and supported features",
mimeType="application/json"
)
]
@server.read_resource()
async def read_resource(uri: str) -> str:
"""Read a specific resource."""
if uri == "vultr://domains":
try: try:
domains = await vultr_client.list_domains() domains = await vultr_client.list_domains()
return { return str(domains)
"uri": "vultr://domains",
"name": "DNS Domains",
"description": "All DNS domains in your Vultr account",
"mimeType": "application/json",
"content": domains
}
except Exception as e: except Exception as e:
return { return f"Error loading domains: {str(e)}"
"uri": "vultr://domains",
"name": "DNS Domains",
"description": f"Error loading domains: {str(e)}",
"mimeType": "application/json",
"content": {"error": str(e)}
}
@mcp.resource("vultr://capabilities") elif uri == "vultr://capabilities":
async def get_capabilities_resource(): capabilities = {
"""Resource describing server capabilities and supported record types."""
return {
"uri": "vultr://capabilities",
"name": "Server Capabilities",
"description": "Vultr DNS server capabilities and supported features",
"mimeType": "application/json",
"content": {
"supported_record_types": [ "supported_record_types": [
{ {
"type": "A", "type": "A",
@ -253,204 +255,319 @@ 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 { return str({
"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 { return f"Error loading records for {domain}: {str(e)}"
"uri": f"vultr://records/{domain}",
"name": f"DNS Records for {domain}", return "Resource not found"
"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:
if name == "list_dns_domains":
domains = await vultr_client.list_domains() domains = await vultr_client.list_domains()
return domains return [TextContent(type="text", text=str(domains))]
except Exception as e:
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"]
"""
Create a new DNS domain with a default A record.
Args:
domain: The domain name to create (e.g., "newdomain.com")
ip: IPv4 address for the default A record (e.g., "192.168.1.100")
"""
try:
return await vultr_client.create_domain(domain, ip)
except Exception as e:
return {"error": str(e)}
@mcp.tool()
async def delete_dns_domain(domain: str) -> Dict[str, Any]:
"""
Delete a DNS domain and ALL its associated records.
WARNING: This permanently deletes the domain and all DNS records.
Args:
domain: The domain name to delete (e.g., "example.com")
"""
try:
await vultr_client.delete_domain(domain) await vultr_client.delete_domain(domain)
return {"success": f"Domain {domain} deleted successfully"} return [TextContent(type="text", text=f"Domain {domain} deleted successfully")]
except Exception as e:
return {"error": str(e)}
@mcp.tool() elif name == "list_dns_records":
async def list_dns_records(domain: str) -> List[Dict[str, Any]]: domain = arguments["domain"]
"""
List all DNS records for a specific domain.
Args:
domain: The domain name (e.g., "example.com")
"""
try:
records = await vultr_client.list_records(domain) records = await vultr_client.list_records(domain)
return records return [TextContent(type="text", text=str(records))]
except Exception as e:
return [{"error": str(e)}]
@mcp.tool() elif name == "get_dns_record":
async def get_dns_record(domain: str, record_id: str) -> Dict[str, Any]: domain = arguments["domain"]
""" record_id = arguments["record_id"]
Get detailed information for a specific DNS record. result = await vultr_client.get_record(domain, record_id)
return [TextContent(type="text", text=str(result))]
Args: elif name == "create_dns_record":
domain: The domain name (e.g., "example.com") domain = arguments["domain"]
record_id: The unique record identifier record_type = arguments["record_type"]
""" name = arguments["name"]
try: data = arguments["data"]
return await vultr_client.get_record(domain, record_id) ttl = arguments.get("ttl")
except Exception as e: priority = arguments.get("priority")
return {"error": str(e)} result = await vultr_client.create_record(domain, record_type, name, data, ttl, priority)
return [TextContent(type="text", text=str(result))]
@mcp.tool() elif name == "update_dns_record":
async def create_dns_record( domain = arguments["domain"]
domain: str, record_id = arguments["record_id"]
record_type: str, record_type = arguments["record_type"]
name: str, name = arguments["name"]
data: str, data = arguments["data"]
ttl: Optional[int] = None, ttl = arguments.get("ttl")
priority: Optional[int] = None priority = arguments.get("priority")
) -> Dict[str, Any]: result = await vultr_client.update_record(domain, record_id, record_type, name, data, ttl, priority)
""" return [TextContent(type="text", text=str(result))]
Create a new DNS record for a domain.
Args: elif name == "delete_dns_record":
domain: The domain name (e.g., "example.com") domain = arguments["domain"]
record_type: Record type (A, AAAA, CNAME, MX, TXT, NS, SRV) record_id = arguments["record_id"]
name: Record name/subdomain
data: Record value
ttl: Time to live in seconds (60-86400, default: 300)
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()
async def update_dns_record(
domain: str,
record_id: str,
record_type: str,
name: str,
data: str,
ttl: Optional[int] = None,
priority: Optional[int] = None
) -> Dict[str, Any]:
"""
Update an existing DNS record with new configuration.
Args:
domain: The domain name (e.g., "example.com")
record_id: The unique identifier of the record to update
record_type: New record type (A, AAAA, CNAME, MX, TXT, NS, SRV)
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()
async def delete_dns_record(domain: str, record_id: str) -> Dict[str, Any]:
"""
Delete a specific DNS record.
Args:
domain: The domain name (e.g., "example.com")
record_id: The unique identifier of the record to delete
"""
try:
await vultr_client.delete_record(domain, record_id) await vultr_client.delete_record(domain, record_id)
return {"success": f"DNS record {record_id} deleted successfully"} return [TextContent(type="text", text=f"DNS record {record_id} deleted successfully")]
except Exception as e:
return {"error": str(e)}
@mcp.tool() elif name == "validate_dns_record":
async def validate_dns_record( record_type = arguments["record_type"]
record_type: str, name = arguments["name"]
name: str, data = arguments["data"]
data: str, ttl = arguments.get("ttl")
ttl: Optional[int] = None, priority = arguments.get("priority")
priority: Optional[int] = None
) -> Dict[str, Any]:
"""
Validate DNS record parameters before creation.
Args:
record_type: The record type (A, AAAA, CNAME, MX, TXT, NS, SRV)
name: The record name/subdomain
data: The record data/value
ttl: Time to live in seconds (optional)
priority: Priority for MX/SRV records (optional)
"""
validation_result = { validation_result = {
"valid": True, "valid": True,
"errors": [], "errors": [],
@ -507,7 +624,7 @@ def create_mcp_server(api_key: Optional[str] = None) -> FastMCP:
validation_result["valid"] = False validation_result["valid"] = False
validation_result["errors"].append("SRV data must be in format: 'weight port target'") validation_result["errors"].append("SRV data must be in format: 'weight port target'")
return { result = {
"record_type": record_type, "record_type": record_type,
"name": name, "name": name,
"data": data, "data": data,
@ -515,16 +632,10 @@ def create_mcp_server(api_key: Optional[str] = None) -> FastMCP:
"priority": priority, "priority": priority,
"validation": validation_result "validation": validation_result
} }
return [TextContent(type="text", text=str(result))]
@mcp.tool() elif name == "analyze_dns_records":
async def analyze_dns_records(domain: str) -> Dict[str, Any]: domain = arguments["domain"]
"""
Analyze DNS configuration for a domain and provide insights.
Args:
domain: The domain name to analyze (e.g., "example.com")
"""
try:
records = await vultr_client.list_records(domain) records = await vultr_client.list_records(domain)
# Analyze records # Analyze records
@ -573,7 +684,7 @@ def create_mcp_server(api_key: Optional[str] = None) -> FastMCP:
if low_ttl_count > total_records * 0.5: if low_ttl_count > total_records * 0.5:
issues.append("Many records have very low TTL values, which may impact performance") issues.append("Many records have very low TTL values, which may impact performance")
return { result = {
"domain": domain, "domain": domain,
"analysis": { "analysis": {
"total_records": total_records, "total_records": total_records,
@ -590,19 +701,24 @@ def create_mcp_server(api_key: Optional[str] = None) -> FastMCP:
"potential_issues": issues, "potential_issues": issues,
"records_detail": records "records_detail": records
} }
return [TextContent(type="text", text=str(result))]
else:
return [TextContent(type="text", text=f"Unknown tool: {name}")]
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)