agentpay-dns-lookup-mcp 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -0
- package/server.py +145 -0
package/package.json
ADDED
package/server.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""DNS Lookup MCP — Resolve DNS records for any domain.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python3 server.py # Free tier (50 calls/instance)
|
|
6
|
+
python3 server.py --pro-key PROL_XXX # Pro tier (unlimited)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json, socket, sys
|
|
10
|
+
from mcp.server import Server, stdio_server
|
|
11
|
+
import httpx
|
|
12
|
+
|
|
13
|
+
server = Server("dns-lookup-mcp")
|
|
14
|
+
GOOGLE_DNS = "https://dns.google/resolve"
|
|
15
|
+
|
|
16
|
+
# ─── Rate Limiting & Pro Key ───────────────────────────────────────────
|
|
17
|
+
FREE_LIMIT = 50
|
|
18
|
+
PRO_KEYS = {"PROL_AGENTPAY_DEMO": "demo"} # Demo key for testing
|
|
19
|
+
|
|
20
|
+
# Parse --pro-key from command line
|
|
21
|
+
PRO_KEY = None
|
|
22
|
+
for i, arg in enumerate(sys.argv):
|
|
23
|
+
if arg == "--pro-key" and i + 1 < len(sys.argv):
|
|
24
|
+
PRO_KEY = sys.argv[i + 1]
|
|
25
|
+
break
|
|
26
|
+
|
|
27
|
+
IS_PRO = PRO_KEY in PRO_KEYS
|
|
28
|
+
call_counter = 0
|
|
29
|
+
|
|
30
|
+
STRIPE_LINK = "https://buy.stripe.com/5kQ3cxflRabW9PW1AD1oI0r" # $19/mo
|
|
31
|
+
|
|
32
|
+
def check_rate_limit():
|
|
33
|
+
"""Check if free tier has exceeded limit. Returns error dict or None."""
|
|
34
|
+
global call_counter
|
|
35
|
+
if IS_PRO:
|
|
36
|
+
return None
|
|
37
|
+
call_counter += 1
|
|
38
|
+
if call_counter > FREE_LIMIT:
|
|
39
|
+
remaining = call_counter - FREE_LIMIT
|
|
40
|
+
return {
|
|
41
|
+
"error": f"Free tier limit reached ({FREE_LIMIT} calls). Upgrade to Pro for unlimited access.",
|
|
42
|
+
"isError": True,
|
|
43
|
+
"next_steps": [
|
|
44
|
+
f"Purchase Pro at {STRIPE_LINK} ($19/mo, unlimited)",
|
|
45
|
+
"Restart the server to reset the free counter",
|
|
46
|
+
"Use --pro-key PROL_XXX to run in Pro mode"
|
|
47
|
+
],
|
|
48
|
+
"calls_used": call_counter,
|
|
49
|
+
"limit": FREE_LIMIT,
|
|
50
|
+
"over_by": remaining
|
|
51
|
+
}
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
async def _dns_lookup(domain, rtype):
|
|
55
|
+
params = {"name": domain, "type": rtype}
|
|
56
|
+
async with httpx.AsyncClient(timeout=10) as client:
|
|
57
|
+
resp = await client.get(GOOGLE_DNS, params=params)
|
|
58
|
+
resp.raise_for_status()
|
|
59
|
+
return resp.json()
|
|
60
|
+
|
|
61
|
+
@server.tool(
|
|
62
|
+
name="dns_lookup_record",
|
|
63
|
+
description="Lookup a specific DNS record type for a domain",
|
|
64
|
+
input_schema={
|
|
65
|
+
"type": "object",
|
|
66
|
+
"properties": {
|
|
67
|
+
"domain": {"type": "string", "description": "Domain name (e.g. example.com)"},
|
|
68
|
+
"record_type": {"type": "string", "enum": ["A", "AAAA", "MX", "NS", "CNAME", "TXT", "SOA"],
|
|
69
|
+
"description": "DNS record type", "default": "A"}
|
|
70
|
+
},
|
|
71
|
+
"required": ["domain"]
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
async def dns_lookup_record(domain: str, record_type: str = "A") -> str:
|
|
75
|
+
limit_check = check_rate_limit()
|
|
76
|
+
if limit_check:
|
|
77
|
+
return json.dumps(limit_check, indent=2)
|
|
78
|
+
try:
|
|
79
|
+
data = await _dns_lookup(domain, record_type)
|
|
80
|
+
answers = data.get("Answer", [])
|
|
81
|
+
if not answers:
|
|
82
|
+
return json.dumps({"domain": domain, "type": record_type, "records": [], "status": "no_records"}, indent=2)
|
|
83
|
+
records = []
|
|
84
|
+
for a in answers:
|
|
85
|
+
records.append({"name": a.get("name", domain), "type": record_type, "ttl": a.get("TTL", 0), "value": a.get("data", "")})
|
|
86
|
+
return json.dumps({"domain": domain, "type": record_type, "records": records}, indent=2)
|
|
87
|
+
except Exception as e:
|
|
88
|
+
return json.dumps({"error": str(e), "isError": True, "next_steps": ["Verify the domain exists", "Check network connectivity"]}, indent=2)
|
|
89
|
+
|
|
90
|
+
@server.tool(
|
|
91
|
+
name="dns_get_all_records",
|
|
92
|
+
description="Get all common DNS records for a domain (A, AAAA, MX, NS, TXT)",
|
|
93
|
+
input_schema={
|
|
94
|
+
"type": "object",
|
|
95
|
+
"properties": {
|
|
96
|
+
"domain": {"type": "string", "description": "Domain name"}
|
|
97
|
+
},
|
|
98
|
+
"required": ["domain"]
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
async def dns_get_all_records(domain: str) -> str:
|
|
102
|
+
limit_check = check_rate_limit()
|
|
103
|
+
if limit_check:
|
|
104
|
+
return json.dumps(limit_check, indent=2)
|
|
105
|
+
types = ["A", "AAAA", "MX", "NS", "TXT", "SOA"]
|
|
106
|
+
result = {"domain": domain, "records": {}}
|
|
107
|
+
for t in types:
|
|
108
|
+
try:
|
|
109
|
+
data = await _dns_lookup(domain, t)
|
|
110
|
+
answers = data.get("Answer", [])
|
|
111
|
+
result["records"][t] = [a.get("data", "") for a in answers]
|
|
112
|
+
except:
|
|
113
|
+
result["records"][t] = []
|
|
114
|
+
return json.dumps(result, indent=2)
|
|
115
|
+
|
|
116
|
+
@server.tool(
|
|
117
|
+
name="dns_reverse_lookup",
|
|
118
|
+
description="Reverse DNS lookup for an IP address",
|
|
119
|
+
input_schema={
|
|
120
|
+
"type": "object",
|
|
121
|
+
"properties": {
|
|
122
|
+
"ip": {"type": "string", "description": "IP address"}
|
|
123
|
+
},
|
|
124
|
+
"required": ["ip"]
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
async def dns_reverse_lookup(ip: str) -> str:
|
|
128
|
+
limit_check = check_rate_limit()
|
|
129
|
+
if limit_check:
|
|
130
|
+
return json.dumps(limit_check, indent=2)
|
|
131
|
+
try:
|
|
132
|
+
hostname = socket.gethostbyaddr(ip)
|
|
133
|
+
return json.dumps({"ip": ip, "hostname": hostname[0], "aliases": hostname[1]}, indent=2)
|
|
134
|
+
except Exception as e:
|
|
135
|
+
return json.dumps({"error": str(e), "isError": True}, indent=2)
|
|
136
|
+
|
|
137
|
+
def main():
|
|
138
|
+
import anyio
|
|
139
|
+
async def run():
|
|
140
|
+
async with stdio_server() as streams:
|
|
141
|
+
await server.run(streams[0], streams[1], server.create_initialization_options())
|
|
142
|
+
anyio.run(run)
|
|
143
|
+
|
|
144
|
+
if __name__ == "__main__":
|
|
145
|
+
main()
|