agentdomain-mcp 0.1.0__tar.gz
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.
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__pycache__/
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentdomain-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for AgentDomain — domain registration API for AI agents
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: httpx>=0.27.0
|
|
8
|
+
Requires-Dist: mcp>=1.0.0
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# AgentDomain MCP Server
|
|
12
|
+
|
|
13
|
+
MCP server for [AgentDomain](https://agentdomain.cloud) — register and manage domain names for AI agents.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Via uvx (recommended)
|
|
19
|
+
uvx agentdomain-mcp
|
|
20
|
+
|
|
21
|
+
# Via pip
|
|
22
|
+
pip install agentdomain-mcp
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Configure
|
|
26
|
+
|
|
27
|
+
Set your API key as an environment variable:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
export AGENTDOMAIN_API_KEY="da_your_api_key_here"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Get a free API key at [agentdomain.cloud/dashboard](https://agentdomain.cloud/dashboard/).
|
|
34
|
+
|
|
35
|
+
## Use with Claude Desktop
|
|
36
|
+
|
|
37
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"mcpServers": {
|
|
42
|
+
"agentdomain": {
|
|
43
|
+
"command": "uvx",
|
|
44
|
+
"args": ["agentdomain-mcp"],
|
|
45
|
+
"env": {
|
|
46
|
+
"AGENTDOMAIN_API_KEY": "da_your_api_key_here"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Use with Hermes Agent
|
|
54
|
+
|
|
55
|
+
Add to `~/.hermes/config.yaml`:
|
|
56
|
+
|
|
57
|
+
```yaml
|
|
58
|
+
mcp_servers:
|
|
59
|
+
agentdomain:
|
|
60
|
+
command: "uvx"
|
|
61
|
+
args: ["agentdomain-mcp"]
|
|
62
|
+
env:
|
|
63
|
+
AGENTDOMAIN_API_KEY: "da_your_api_key_here"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Tools
|
|
67
|
+
|
|
68
|
+
| Tool | Description |
|
|
69
|
+
|------|-------------|
|
|
70
|
+
| `domain_search` | Search for available domains |
|
|
71
|
+
| `domain_check` | Check availability + price |
|
|
72
|
+
| `domain_buy` | Register a domain |
|
|
73
|
+
| `domain_list` | List your domains |
|
|
74
|
+
| `domain_dns_get` | Get DNS records |
|
|
75
|
+
| `domain_dns_update` | Update DNS records |
|
|
76
|
+
| `domain_transfer` | Get EPP auth code |
|
|
77
|
+
| `wallet_balance` | Check balance |
|
|
78
|
+
| `wallet_topup` | Create crypto top-up invoice |
|
|
79
|
+
| `wallet_transactions` | List recent transactions |
|
|
80
|
+
| `account_info` | Get account info |
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
MIT
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# AgentDomain MCP Server
|
|
2
|
+
|
|
3
|
+
MCP server for [AgentDomain](https://agentdomain.cloud) — register and manage domain names for AI agents.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Via uvx (recommended)
|
|
9
|
+
uvx agentdomain-mcp
|
|
10
|
+
|
|
11
|
+
# Via pip
|
|
12
|
+
pip install agentdomain-mcp
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Configure
|
|
16
|
+
|
|
17
|
+
Set your API key as an environment variable:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
export AGENTDOMAIN_API_KEY="da_your_api_key_here"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Get a free API key at [agentdomain.cloud/dashboard](https://agentdomain.cloud/dashboard/).
|
|
24
|
+
|
|
25
|
+
## Use with Claude Desktop
|
|
26
|
+
|
|
27
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"agentdomain": {
|
|
33
|
+
"command": "uvx",
|
|
34
|
+
"args": ["agentdomain-mcp"],
|
|
35
|
+
"env": {
|
|
36
|
+
"AGENTDOMAIN_API_KEY": "da_your_api_key_here"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Use with Hermes Agent
|
|
44
|
+
|
|
45
|
+
Add to `~/.hermes/config.yaml`:
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
mcp_servers:
|
|
49
|
+
agentdomain:
|
|
50
|
+
command: "uvx"
|
|
51
|
+
args: ["agentdomain-mcp"]
|
|
52
|
+
env:
|
|
53
|
+
AGENTDOMAIN_API_KEY: "da_your_api_key_here"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Tools
|
|
57
|
+
|
|
58
|
+
| Tool | Description |
|
|
59
|
+
|------|-------------|
|
|
60
|
+
| `domain_search` | Search for available domains |
|
|
61
|
+
| `domain_check` | Check availability + price |
|
|
62
|
+
| `domain_buy` | Register a domain |
|
|
63
|
+
| `domain_list` | List your domains |
|
|
64
|
+
| `domain_dns_get` | Get DNS records |
|
|
65
|
+
| `domain_dns_update` | Update DNS records |
|
|
66
|
+
| `domain_transfer` | Get EPP auth code |
|
|
67
|
+
| `wallet_balance` | Check balance |
|
|
68
|
+
| `wallet_topup` | Create crypto top-up invoice |
|
|
69
|
+
| `wallet_transactions` | List recent transactions |
|
|
70
|
+
| `account_info` | Get account info |
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
MIT
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "agentdomain-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "MCP server for AgentDomain — domain registration API for AI agents"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"mcp>=1.0.0",
|
|
14
|
+
"httpx>=0.27.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
agentdomain-mcp = "server:mcp.run"
|
|
19
|
+
|
|
20
|
+
[tool.hatch.build.targets.wheel]
|
|
21
|
+
packages = ["."]
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""AgentDomain MCP Server — Domain registration tools for AI agents."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import httpx
|
|
5
|
+
from mcp.server.fastmcp import FastMCP
|
|
6
|
+
|
|
7
|
+
API_BASE = os.environ.get("AGENTDOMAIN_API_URL", "https://api.agentdomain.cloud")
|
|
8
|
+
API_KEY = os.environ.get("AGENTDOMAIN_API_KEY", "")
|
|
9
|
+
|
|
10
|
+
mcp = FastMCP(
|
|
11
|
+
"AgentDomain",
|
|
12
|
+
instructions="Domain registration API for AI agents. Search, register, and manage domains autonomously.",
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _headers():
|
|
17
|
+
return {
|
|
18
|
+
"Authorization": f"Bearer {API_KEY}",
|
|
19
|
+
"Content-Type": "application/json",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def _get(path: str) -> dict:
|
|
24
|
+
async with httpx.AsyncClient(timeout=30) as c:
|
|
25
|
+
r = await c.get(f"{API_BASE}{path}", headers=_headers())
|
|
26
|
+
if r.status_code >= 400:
|
|
27
|
+
try:
|
|
28
|
+
detail = r.json().get("detail", r.text)
|
|
29
|
+
except Exception:
|
|
30
|
+
detail = r.text
|
|
31
|
+
return {"error": f"API error {r.status_code}: {detail}"}
|
|
32
|
+
return r.json()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def _post(path: str, data: dict = None) -> dict:
|
|
36
|
+
async with httpx.AsyncClient(timeout=30) as c:
|
|
37
|
+
r = await c.post(f"{API_BASE}{path}", headers=_headers(), json=data or {})
|
|
38
|
+
if r.status_code >= 400:
|
|
39
|
+
try:
|
|
40
|
+
detail = r.json().get("detail", r.text)
|
|
41
|
+
except Exception:
|
|
42
|
+
detail = r.text
|
|
43
|
+
return {"error": f"API error {r.status_code}: {detail}"}
|
|
44
|
+
return r.json()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# ── Domains ──
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@mcp.tool()
|
|
51
|
+
async def domain_search(query: str) -> str:
|
|
52
|
+
"""Search for available domain names matching a query.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
query: Search term (e.g. 'awesome', 'mybrand')
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
List of domains with availability and pricing.
|
|
59
|
+
"""
|
|
60
|
+
data = await _get(f"/v1/domains/search?q={query}")
|
|
61
|
+
if "error" in data:
|
|
62
|
+
return f"Error: {data['error']}"
|
|
63
|
+
domains = data.get("domains", [])
|
|
64
|
+
if not domains:
|
|
65
|
+
return "No domains found."
|
|
66
|
+
lines = []
|
|
67
|
+
for d in domains:
|
|
68
|
+
status = "✅ available" if d.get("available") else "❌ taken"
|
|
69
|
+
price = f"${d['price']:.2f}" if d.get("price") else ""
|
|
70
|
+
lines.append(f" {d['name']} {status} {price}")
|
|
71
|
+
return "Search results:\n" + "\n".join(lines)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@mcp.tool()
|
|
75
|
+
async def domain_check(domain: str) -> str:
|
|
76
|
+
"""Check if a specific domain is available for registration and get its price.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
domain: Full domain name (e.g. 'awesome.com')
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Availability status and price.
|
|
83
|
+
"""
|
|
84
|
+
data = await _post("/v1/domains/check", {"domain": domain})
|
|
85
|
+
if "error" in data:
|
|
86
|
+
return f"Error: {data['error']}"
|
|
87
|
+
if data.get("registrable"):
|
|
88
|
+
price = data.get("price_cents", 0) / 100
|
|
89
|
+
return f"✅ {domain} is available — ${price:.2f}/yr"
|
|
90
|
+
else:
|
|
91
|
+
reason = data.get("reason", "already taken")
|
|
92
|
+
return f"❌ {domain} is not available ({reason})"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@mcp.tool()
|
|
96
|
+
async def domain_buy(domain: str, years: int = 1) -> str:
|
|
97
|
+
"""Register/buy a domain name.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
domain: Full domain name to register (e.g. 'awesome.com')
|
|
101
|
+
years: Registration period in years (default: 1)
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Registration result with status and next steps.
|
|
105
|
+
"""
|
|
106
|
+
data = await _post("/v1/domains/buy", {"domain": domain, "years": years})
|
|
107
|
+
if "error" in data:
|
|
108
|
+
return f"Error: {data['error']}"
|
|
109
|
+
status = data.get("status", "unknown")
|
|
110
|
+
|
|
111
|
+
if status == "active":
|
|
112
|
+
cost = data.get("cost_cents", 0) / 100
|
|
113
|
+
return f"✅ {domain} registered successfully! Cost: ${cost:.2f}"
|
|
114
|
+
elif status == "payment_required":
|
|
115
|
+
url = data.get("payment_url", "")
|
|
116
|
+
cost = data.get("cost_cents", 0) / 100
|
|
117
|
+
return f"💳 Payment needed (${cost:.2f}): {url}"
|
|
118
|
+
elif status == "approval_needed":
|
|
119
|
+
url = data.get("approval_url", "")
|
|
120
|
+
cost = data.get("cost_cents", 0) / 100
|
|
121
|
+
return f"⏳ Approval needed (${cost:.2f}): {url}"
|
|
122
|
+
elif status == "email_not_verified":
|
|
123
|
+
return "⚠️ Email not verified. Please verify your email first."
|
|
124
|
+
else:
|
|
125
|
+
msg = data.get("message", str(data))
|
|
126
|
+
return f"Result: {msg}"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@mcp.tool()
|
|
130
|
+
async def domain_list() -> str:
|
|
131
|
+
"""List all domains registered to your account.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
List of domains with status, expiry, and cost.
|
|
135
|
+
"""
|
|
136
|
+
data = await _get("/v1/domains")
|
|
137
|
+
if "error" in data:
|
|
138
|
+
return f"Error: {data['error']}"
|
|
139
|
+
domains = data.get("domains", [])
|
|
140
|
+
if not domains:
|
|
141
|
+
return "No domains registered yet."
|
|
142
|
+
lines = []
|
|
143
|
+
for d in domains:
|
|
144
|
+
expires = d.get("expires_at", "—")[:10] if d.get("expires_at") else "—"
|
|
145
|
+
cost = f"${d['cost_cents'] / 100:.2f}" if d.get("cost_cents") else "—"
|
|
146
|
+
lines.append(f" {d['domain']} [{d['status']}] expires: {expires} cost: {cost}")
|
|
147
|
+
return "Your domains:\n" + "\n".join(lines)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@mcp.tool()
|
|
151
|
+
async def domain_dns_get(domain: str) -> str:
|
|
152
|
+
"""Get DNS records for a domain.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
domain: The domain to query (e.g. 'awesome.com')
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
List of DNS records.
|
|
159
|
+
"""
|
|
160
|
+
data = await _get(f"/v1/domains/{domain}/dns")
|
|
161
|
+
if "error" in data:
|
|
162
|
+
return f"Error: {data['error']}"
|
|
163
|
+
records = data.get("records", [])
|
|
164
|
+
if not records:
|
|
165
|
+
return f"No DNS records for {domain}."
|
|
166
|
+
lines = [f"DNS records for {domain}:"]
|
|
167
|
+
for r in records:
|
|
168
|
+
lines.append(f" {r.get('type', '?')} {r.get('name', '@')} {r.get('content', '')} TTL:{r.get('ttl', 300)}")
|
|
169
|
+
return "\n".join(lines)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@mcp.tool()
|
|
173
|
+
async def domain_dns_update(domain: str, records_json: str) -> str:
|
|
174
|
+
"""Update DNS records for a domain. Replaces ALL records with the provided set.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
domain: The domain to update (e.g. 'awesome.com')
|
|
178
|
+
records_json: JSON array of DNS records. Each record has: type (A/CNAME/MX/TXT), name, content, ttl.
|
|
179
|
+
Example: [{"type": "A", "name": "@", "content": "1.2.3.4", "ttl": 300}]
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Confirmation of the update.
|
|
183
|
+
"""
|
|
184
|
+
import json
|
|
185
|
+
records = json.loads(records_json)
|
|
186
|
+
data = await _post(f"/v1/domains/{domain}/dns", {"records": records})
|
|
187
|
+
if "error" in data:
|
|
188
|
+
return f"Error: {data['error']}"
|
|
189
|
+
return f"✅ DNS records updated for {domain}. {len(records)} record(s) applied."
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@mcp.tool()
|
|
193
|
+
async def domain_transfer(domain: str) -> str:
|
|
194
|
+
"""Get the EPP auth code to transfer a domain out.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
domain: The domain to transfer (e.g. 'awesome.com')
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
EPP authorization code and transfer instructions.
|
|
201
|
+
"""
|
|
202
|
+
data = await _post(f"/v1/domains/{domain}/transfer")
|
|
203
|
+
if "error" in data:
|
|
204
|
+
return f"Error: {data['error']}"
|
|
205
|
+
code = data.get("epp_code", "")
|
|
206
|
+
msg = data.get("message", "")
|
|
207
|
+
return f"EPP auth code for {domain}: {code}\n\n{msg}"
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# ── Wallet ──
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@mcp.tool()
|
|
214
|
+
async def wallet_balance() -> str:
|
|
215
|
+
"""Check your wallet balance and total spending.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Current balance and total spent.
|
|
219
|
+
"""
|
|
220
|
+
data = await _get("/v1/wallet")
|
|
221
|
+
if "error" in data:
|
|
222
|
+
return f"Error: {data['error']}"
|
|
223
|
+
balance = data.get("balance_cents", 0) / 100
|
|
224
|
+
spent = data.get("total_spent_cents", 0) / 100
|
|
225
|
+
return f"Balance: ${balance:.2f}\nTotal spent: ${spent:.2f}"
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@mcp.tool()
|
|
229
|
+
async def wallet_topup(amount_usd: float, currency: str = "USDC") -> str:
|
|
230
|
+
"""Create a crypto top-up invoice for your wallet.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
amount_usd: Amount in USD to add
|
|
234
|
+
currency: Crypto currency (USDC, BTC, ETH). Default: USDC.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Payment invoice URL.
|
|
238
|
+
"""
|
|
239
|
+
amount_cents = int(amount_usd * 100)
|
|
240
|
+
data = await _post("/v1/wallet/topup/crypto", {
|
|
241
|
+
"amount_cents": amount_cents,
|
|
242
|
+
"crypto_currency": currency,
|
|
243
|
+
})
|
|
244
|
+
if "error" in data:
|
|
245
|
+
return f"Error: {data['error']}"
|
|
246
|
+
url = data.get("invoice_url", "")
|
|
247
|
+
return f"💳 Top-up invoice (${amount_usd:.2f} via {currency}): {url}"
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@mcp.tool()
|
|
251
|
+
async def wallet_transactions(limit: int = 10) -> str:
|
|
252
|
+
"""List recent wallet transactions.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
limit: Max number of transactions to return (default: 10)
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
Recent transactions with type, amount, and status.
|
|
259
|
+
"""
|
|
260
|
+
data = await _get("/v1/wallet/transactions")
|
|
261
|
+
if "error" in data:
|
|
262
|
+
return f"Error: {data['error']}"
|
|
263
|
+
txs = data.get("transactions", [])[:limit]
|
|
264
|
+
if not txs:
|
|
265
|
+
return "No transactions yet."
|
|
266
|
+
lines = ["Recent transactions:"]
|
|
267
|
+
for t in txs:
|
|
268
|
+
amount = f"${t['amount_cents'] / 100:.2f}" if t.get("amount_cents") is not None else "—"
|
|
269
|
+
domain = t.get("domain", "")
|
|
270
|
+
date = t.get("created_at", "")[:10]
|
|
271
|
+
lines.append(f" {t.get('type', '?')} {amount} {domain} [{t.get('status', '?')}] {date}")
|
|
272
|
+
return "\n".join(lines)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# ── Account ──
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@mcp.tool()
|
|
279
|
+
async def account_info() -> str:
|
|
280
|
+
"""Get your account information.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
Agent name, email, and account ID.
|
|
284
|
+
"""
|
|
285
|
+
data = await _get("/v1/auth/me")
|
|
286
|
+
if "error" in data:
|
|
287
|
+
return f"Error: {data['error']}"
|
|
288
|
+
return f"Name: {data.get('name', '—')}\nEmail: {data.get('email', '—')}\nAgent ID: {data.get('id', '—')}"
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
if __name__ == "__main__":
|
|
292
|
+
mcp.run(transport="stdio")
|