domainagent-mcp 1.0.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.
Files changed (3) hide show
  1. package/README.md +174 -0
  2. package/mcp-server.js +1147 -0
  3. package/package.json +53 -0
package/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # domainagent-mcp
2
+
3
+ MCP server for [domainagent.dev](https://domainagent.dev) — the domain registrar built for AI agents.
4
+
5
+ Search, register, deploy, and manage domains in one prompt. Pay with USDC on Base via x402.
6
+
7
+ ## What is domainagent?
8
+
9
+ domainagent is an agent-first domain registrar. Instead of clicking through GoDaddy, your AI agent calls an API and gets:
10
+
11
+ - **Domain search + registration** via Name.com
12
+ - **Static site deployment** via Cloudflare Pages
13
+ - **DNS management** (A, CNAME, MX, TXT records)
14
+ - **SSL** (automatic via Cloudflare)
15
+ - **Health checks** (DNS, HTTP, SSL, response time)
16
+ - **Renewals** with expiry tracking
17
+ - **Free transfer-out** with auth/EPP codes (no lock-in)
18
+ - **x402 payment** — USDC on Base mainnet, no credit card needed
19
+
20
+ ## Install
21
+
22
+ ### npx (no install)
23
+ ```bash
24
+ npx domainagent-mcp
25
+ ```
26
+
27
+ ### Claude Desktop / Cursor / MCP-compatible tools
28
+
29
+ Add to your MCP config:
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "domainagent": {
35
+ "command": "npx",
36
+ "args": ["domainagent-mcp"],
37
+ "env": {
38
+ "SHIPAGENT_API": "https://domainagent.dev"
39
+ }
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ ### Global install
46
+ ```bash
47
+ npm install -g domainagent-mcp
48
+ domainagent-mcp
49
+ ```
50
+
51
+ ## Tools
52
+
53
+ | Tool | Description | Auth | Payment |
54
+ |------|-------------|------|---------|
55
+ | `domainagent_search` | Search available domains | — | Free |
56
+ | `domainagent_recommend` | AI domain suggestions from a description | — | Free |
57
+ | `domainagent_pricing` | TLD price list | — | Free |
58
+ | `domainagent_status` | Check domain deployment status | — | Free |
59
+ | `domainagent_health` | DNS + HTTP + SSL health check | — | Free |
60
+ | `domainagent_renewals_due` | Domains expiring within 30 days | — | Free |
61
+ | `domainagent_deploy` | Register + deploy + DNS + SSL | Wallet | x402 USDC |
62
+ | `domainagent_redeploy` | Update existing deployment | Wallet | x402 USDC |
63
+ | `domainagent_renew` | Renew domain registration | Wallet | x402 USDC |
64
+ | `domainagent_auth_challenge` | Get wallet auth challenge | — | Free |
65
+ | `domainagent_auth_verify` | Verify signed challenge → session token | — | Free |
66
+ | `domainagent_list_domains` | List your domains | Token | Free |
67
+ | `domainagent_dns_list` | List DNS records | Token | Free |
68
+ | `domainagent_dns_add` | Add DNS record | Token | Free |
69
+ | `domainagent_dns_delete` | Delete DNS record | Token | Free |
70
+ | `domainagent_renewal_email` | Set renewal reminder email | Token | Free |
71
+ | `domainagent_connect` | Connect your Cloudflare account | Token | Free |
72
+ | `domainagent_transfer_out` | Get auth/EPP code for transfer | Token | Free |
73
+
74
+ ## Quick Start
75
+
76
+ **Search for a domain:**
77
+ ```
78
+ "Find me a domain for my fitness tracking app"
79
+ → domainagent_search({ query: "fitness tracker" })
80
+ → fittrack.dev — $14.99 (available)
81
+ ```
82
+
83
+ **Deploy:**
84
+ ```
85
+ "Deploy my app to fittrack.dev"
86
+ → domainagent_deploy({ domain: "fittrack.dev", owner: "0x...", filesBase64: "..." })
87
+ → 402 Payment Required: $16.99 USDC (domain + deploy fee)
88
+ → Agent signs USDC authorization, retries
89
+ → ✓ fittrack.dev is live with SSL
90
+ ```
91
+
92
+ **Check health:**
93
+ ```
94
+ "Is fittrack.dev working?"
95
+ → domainagent_health({ domain: "fittrack.dev" })
96
+ → DNS: ✓ | HTTP: 200 | SSL: valid | Response: 45ms
97
+ ```
98
+
99
+ ## Authentication
100
+
101
+ Free tools (search, pricing, health) require no auth.
102
+
103
+ Sensitive operations use wallet-based authentication:
104
+ 1. Call `domainagent_auth_challenge` with your wallet address
105
+ 2. Sign the returned message with your wallet
106
+ 3. Call `domainagent_auth_verify` with the signature
107
+ 4. Use the returned session token as `authToken` in subsequent calls
108
+
109
+ Paid operations (deploy, redeploy, renew) use x402 — sign a USDC authorization on Base mainnet.
110
+
111
+ ## x402 Payment Flow
112
+
113
+ Paid endpoints return `402 Payment Required` with details:
114
+ - Network: Base mainnet (eip155:8453)
115
+ - Token: USDC (`0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`)
116
+ - Amount: varies by operation
117
+
118
+ Your agent signs a USDC authorization and retries with the `X-Payment` header. The [x402 protocol](https://www.x402.org/) handles the rest.
119
+
120
+ ## No Lock-in
121
+
122
+ - **Free transfer-out** — get your auth/EPP code anytime via `domainagent_transfer_out`
123
+ - **You own your domain** — registered in your name via Name.com (ICANN-accredited)
124
+ - **Connect your own Cloudflare** — deploy to your infrastructure, not ours
125
+ - **Open MCP** — this repo is MIT licensed
126
+
127
+ ## x402 Payment Signing
128
+
129
+ Some MCP tools (deploy, redeploy, renew) require x402 USDC payment on Base mainnet.
130
+
131
+ The `examples/` directory includes tools to make this easy:
132
+
133
+ ### x402-signer.js
134
+
135
+ Signs x402 payment headers from your wallet. Use it when an MCP tool returns a 402:
136
+
137
+ ```bash
138
+ # Install dependencies
139
+ npm install viem @x402/core @x402/evm
140
+
141
+ # Sign a payment from a saved 402 response
142
+ node examples/x402-signer.js --payment-file payment-402.json --key 0xYOUR_KEY
143
+
144
+ # Or pipe it
145
+ echo '{"paymentDetails":{...}}' | node examples/x402-signer.js --key 0xYOUR_KEY
146
+
147
+ # Use with env var
148
+ export WALLET_PRIVATE_KEY=0x...
149
+ node examples/x402-signer.js --payment-file payment-402.json
150
+ ```
151
+
152
+ Output: a base64-encoded `PAYMENT-SIGNATURE` value ready to pass as `paymentSignature` in any MCP tool.
153
+
154
+ ### Full deploy flow example
155
+
156
+ ```bash
157
+ export WALLET_PRIVATE_KEY=0x...
158
+ export WALLET_ADDRESS=0x...
159
+ export CONTACT_EMAIL=you@example.com
160
+
161
+ ./examples/agent-deploy-flow.sh mydomain.xyz ./my-site.zip
162
+ ```
163
+
164
+ This script handles the complete flow: search → deploy attempt → sign payment → retry with signature → verify status.
165
+
166
+ ## API
167
+
168
+ Full OpenAPI spec: [domainagent.dev/openapi.yaml](https://domainagent.dev/openapi.yaml)
169
+
170
+ Agent discovery: [domainagent.dev/.well-known/agent.json](https://domainagent.dev/.well-known/agent.json)
171
+
172
+ ## License
173
+
174
+ MIT
package/mcp-server.js ADDED
@@ -0,0 +1,1147 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ShipAgent MCP Server
4
+ * Exposes ShipAgent domain registration and deployment as MCP tools.
5
+ *
6
+ * Usage: node mcp-server.js
7
+ * Config: set SHIPAGENT_API env var to override API base URL (default: http://127.0.0.1:3001)
8
+ */
9
+
10
+ "use strict";
11
+
12
+ const { Server } = require("@modelcontextprotocol/sdk/server/index.js");
13
+ const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
14
+ const {
15
+ CallToolRequestSchema,
16
+ ListToolsRequestSchema,
17
+ } = require("@modelcontextprotocol/sdk/types.js");
18
+ const https = require("https");
19
+ const http = require("http");
20
+ const { URL } = require("url");
21
+
22
+ const API_BASE = (process.env.SHIPAGENT_API || "http://127.0.0.1:3001").replace(/\/$/, "");
23
+
24
+ // --- HTTP helper ---
25
+
26
+ function apiRequest(method, path, body, headers) {
27
+ return new Promise((resolve, reject) => {
28
+ const url = new URL(API_BASE + path);
29
+ const isHttps = url.protocol === "https:";
30
+ const transport = isHttps ? https : http;
31
+
32
+ const payload = body ? JSON.stringify(body) : null;
33
+ const options = {
34
+ hostname: url.hostname,
35
+ port: url.port || (isHttps ? 443 : 80),
36
+ path: url.pathname + url.search,
37
+ method,
38
+ headers: {
39
+ "Content-Type": "application/json",
40
+ "Accept": "application/json",
41
+ ...(payload ? { "Content-Length": Buffer.byteLength(payload) } : {}),
42
+ ...(headers || {}),
43
+ },
44
+ };
45
+
46
+ const req = transport.request(options, (res) => {
47
+ let data = "";
48
+ res.on("data", (chunk) => (data += chunk));
49
+ res.on("end", () => {
50
+ try {
51
+ resolve({ status: res.statusCode, headers: res.headers, body: JSON.parse(data) });
52
+ } catch {
53
+ resolve({ status: res.statusCode, headers: res.headers, body: data });
54
+ }
55
+ });
56
+ });
57
+
58
+ req.on("error", reject);
59
+ if (payload) req.write(payload);
60
+ req.end();
61
+ });
62
+ }
63
+
64
+ function formatResult(response) {
65
+ const { status, body } = response;
66
+
67
+ if (status === 402) {
68
+ // x402 payment required — give the agent everything it needs
69
+ const paymentInfo = response.headers["x-payment-required"] || response.headers["payment-required"];
70
+ let paymentDetails = {};
71
+ if (paymentInfo) {
72
+ try {
73
+ paymentDetails = JSON.parse(Buffer.from(paymentInfo, "base64").toString());
74
+ } catch {
75
+ paymentDetails = { raw: paymentInfo };
76
+ }
77
+ }
78
+ return {
79
+ isError: false,
80
+ content: [{
81
+ type: "text",
82
+ text: JSON.stringify({
83
+ paymentRequired: true,
84
+ statusCode: 402,
85
+ message: "x402 payment required. Sign a USDC authorization and retry with X-Payment header.",
86
+ paymentDetails,
87
+ bodyDetails: body,
88
+ }, null, 2),
89
+ }],
90
+ };
91
+ }
92
+
93
+ if (status >= 400) {
94
+ const msg = (typeof body === "object" && body.error) ? body.error : JSON.stringify(body);
95
+ return { isError: true, content: [{ type: "text", text: `Error ${status}: ${msg}` }] };
96
+ }
97
+
98
+ return { content: [{ type: "text", text: JSON.stringify(body, null, 2) }] };
99
+ }
100
+
101
+ // --- Tool definitions ---
102
+
103
+ const TOOLS = [
104
+ {
105
+ name: "domainagent_auth_challenge",
106
+ description:
107
+ "Get a challenge message to sign with your wallet. First step of authentication. Returns a message containing a nonce that must be signed.",
108
+ inputSchema: {
109
+ type: "object",
110
+ required: ["owner"],
111
+ properties: {
112
+ owner: { type: "string", description: "Wallet address (0x...)" },
113
+ },
114
+ },
115
+ },
116
+ {
117
+ name: "domainagent_auth_verify",
118
+ description:
119
+ "Verify a signed challenge and get a session token. Second step of authentication. Use the token as Bearer auth for subsequent calls.",
120
+ inputSchema: {
121
+ type: "object",
122
+ required: ["owner", "signature", "message"],
123
+ properties: {
124
+ owner: { type: "string", description: "Wallet address" },
125
+ signature: { type: "string", description: "Signature of the challenge message" },
126
+ message: { type: "string", description: "The exact challenge message that was signed" },
127
+ },
128
+ },
129
+ },
130
+ {
131
+ name: "domainagent_list_domains",
132
+ description:
133
+ "List all domains owned by the authenticated wallet. Requires auth token.",
134
+ inputSchema: {
135
+ type: "object",
136
+ properties: {
137
+ authToken: { type: "string", description: "Session token from auth/verify" },
138
+ },
139
+ required: ["authToken"],
140
+ },
141
+ },
142
+ {
143
+ name: "domainagent_health",
144
+ description:
145
+ "Check if a deployed domain is healthy — DNS resolution, HTTP status, SSL, response time. Free, no auth required.",
146
+ inputSchema: {
147
+ type: "object",
148
+ required: ["domain"],
149
+ properties: {
150
+ domain: { type: "string", description: "Domain to health-check (e.g. fittrack.dev)" },
151
+ },
152
+ },
153
+ },
154
+ {
155
+ name: "domainagent_dns_list",
156
+ description:
157
+ "List DNS records for a domain. Requires auth.",
158
+ inputSchema: {
159
+ type: "object",
160
+ required: ["domain", "authToken"],
161
+ properties: {
162
+ domain: { type: "string", description: "Domain name" },
163
+ authToken: { type: "string", description: "Session token" },
164
+ },
165
+ },
166
+ },
167
+ {
168
+ name: "domainagent_dns_add",
169
+ description:
170
+ "Add a DNS record (MX, TXT, CNAME, A, etc.) to a domain. Requires auth.",
171
+ inputSchema: {
172
+ type: "object",
173
+ required: ["domain", "type", "answer", "authToken"],
174
+ properties: {
175
+ domain: { type: "string", description: "Domain name" },
176
+ host: { type: "string", description: "Subdomain or empty for apex" },
177
+ type: { type: "string", description: "Record type (A, CNAME, MX, TXT, etc.)" },
178
+ answer: { type: "string", description: "Record value" },
179
+ ttl: { type: "number", description: "TTL in seconds (default 300)" },
180
+ authToken: { type: "string", description: "Session token" },
181
+ },
182
+ },
183
+ },
184
+ {
185
+ name: "domainagent_dns_delete",
186
+ description:
187
+ "Delete a DNS record by ID. Get record IDs from domainagent_dns_list. Requires auth.",
188
+ inputSchema: {
189
+ type: "object",
190
+ required: ["domain", "recordId", "authToken"],
191
+ properties: {
192
+ domain: { type: "string", description: "Domain name" },
193
+ recordId: { type: "string", description: "DNS record ID to delete" },
194
+ authToken: { type: "string", description: "Session token" },
195
+ },
196
+ },
197
+ },
198
+ {
199
+ name: "domainagent_renewal_email",
200
+ description:
201
+ "Set a renewal reminder email address for a domain. We'll email 30 days before expiry. Requires auth.",
202
+ inputSchema: {
203
+ type: "object",
204
+ required: ["domain", "email", "authToken"],
205
+ properties: {
206
+ domain: { type: "string", description: "Domain name" },
207
+ email: { type: "string", description: "Email address for renewal reminders" },
208
+ authToken: { type: "string", description: "Session token" },
209
+ },
210
+ },
211
+ },
212
+ {
213
+ name: "domainagent_connect",
214
+ description:
215
+ "Connect a Cloudflare account so deployments go to the customer's own infrastructure. One-time setup. Requires a Cloudflare API token with Pages:Edit and DNS:Edit permissions. Requires auth.",
216
+ inputSchema: {
217
+ type: "object",
218
+ required: ["cf_api_token", "authToken"],
219
+ properties: {
220
+ cf_api_token: { type: "string", description: "Cloudflare API token with Pages:Edit and DNS:Edit permissions" },
221
+ authToken: { type: "string", description: "Session token" },
222
+ },
223
+ },
224
+ },
225
+ {
226
+ name: "domainagent_transfer_out",
227
+ description:
228
+ "Get an auth/EPP code to transfer a domain to another registrar. Free — no charge. The domain will be unlocked and the auth code returned. Requires auth.",
229
+ inputSchema: {
230
+ type: "object",
231
+ required: ["domain", "authToken"],
232
+ properties: {
233
+ domain: { type: "string", description: "Domain to transfer out (e.g. fittrack.dev)" },
234
+ authToken: { type: "string", description: "Session token" },
235
+ },
236
+ },
237
+ },
238
+ {
239
+ name: "domainagent_renewals_due",
240
+ description:
241
+ "Check which domains are expiring within 30 days. Free. Optionally filter by owner wallet.",
242
+ inputSchema: {
243
+ type: "object",
244
+ properties: {
245
+ owner: { type: "string", description: "Filter by wallet address (optional)" },
246
+ },
247
+ },
248
+ },
249
+ {
250
+ name: "domainagent_renew",
251
+ description:
252
+ "Renew a domain registration. Protected by x402 — costs domain renewal price + $2 fee in USDC.",
253
+ inputSchema: {
254
+ type: "object",
255
+ required: ["domain", "owner"],
256
+ properties: {
257
+ domain: { type: "string", description: "Domain to renew" },
258
+ owner: { type: "string", description: "Wallet address of the domain owner" },
259
+ paymentSignature: { type: "string", description: "x402 payment signature (after receiving payment details)" },
260
+ },
261
+ },
262
+ },
263
+ {
264
+ name: "domainagent_search",
265
+ description:
266
+ "Search for available domain names matching a keyword or phrase. Returns domain availability and pricing. Use this before deploying to find a good domain.",
267
+ inputSchema: {
268
+ type: "object",
269
+ required: ["query"],
270
+ properties: {
271
+ query: {
272
+ type: "string",
273
+ description: "Keyword or phrase to search for (e.g. 'fitness app', 'fittrack')",
274
+ },
275
+ tlds: {
276
+ type: "array",
277
+ description: "TLDs to search. Default: .com, .dev, .io, .app, .net, .xyz, .co",
278
+ items: { type: "string" },
279
+ example: [".com", ".dev"],
280
+ },
281
+ },
282
+ },
283
+ },
284
+ {
285
+ name: "domainagent_recommend",
286
+ description:
287
+ "Get AI-powered domain suggestions based on a natural language description of your project. Extracts keywords and searches for the best available options within your budget.",
288
+ inputSchema: {
289
+ type: "object",
290
+ required: ["description"],
291
+ properties: {
292
+ description: {
293
+ type: "string",
294
+ description: "Natural language description of your project (e.g. 'a fitness tracking app for runners')",
295
+ },
296
+ budget: {
297
+ type: "number",
298
+ description: "Maximum domain price in USD (default: 100)",
299
+ },
300
+ },
301
+ },
302
+ },
303
+ {
304
+ name: "domainagent_pricing",
305
+ description:
306
+ "Get the current TLD price list and fee structure for deploying via ShipAgent. Returns domain registration costs by TLD, plus deploy and redeploy fees.",
307
+ inputSchema: {
308
+ type: "object",
309
+ properties: {},
310
+ },
311
+ },
312
+ {
313
+ name: "domainagent_status",
314
+ description:
315
+ "Check the registration, deployment, DNS, and SSL status of a domain deployed through ShipAgent.",
316
+ inputSchema: {
317
+ type: "object",
318
+ required: ["domain"],
319
+ properties: {
320
+ domain: {
321
+ type: "string",
322
+ description: "Domain to check status for (e.g. fittrack.dev)",
323
+ },
324
+ },
325
+ },
326
+ },
327
+ {
328
+ name: "domainagent_deploy",
329
+ description: `Register a domain and deploy your app.
330
+ Protected by x402 — you pay $2 USDC on Base mainnet, domain cost is included.
331
+
332
+ This tool uses a two-step flow:
333
+ 1. Call with domain + owner + filesBase64 (+ optionally paymentSignature)
334
+ 2. If payment needed: you receive payment details — sign USDC authorization and retry with paymentSignature
335
+ 3. On success: domain is registered and files are deployed automatically
336
+
337
+ Alternatively, if you want to separate payment from upload:
338
+ 1. Call without filesBase64 to get an uploadToken after payment
339
+ 2. Then call domainagent_upload with the token and your zip file (valid 30 min)
340
+
341
+ The tool handles:
342
+ - Domain availability check
343
+ - Domain registration via Name.com
344
+ - Deployment to Fly.io (or Cloudflare Pages if connected)
345
+ - DNS configuration
346
+ - SSL (automatic)
347
+
348
+ Supported runtimes (auto-detected from your zip contents):
349
+ - Static sites (HTML/CSS/JS) → served via Caddy
350
+ - Node.js (package.json detected) → node:20-alpine
351
+ - Python (requirements.txt detected) → python:3.12-slim with gunicorn/uvicorn
352
+ - Docker (Dockerfile detected) → custom build
353
+
354
+ Hosting tiers:
355
+ - Static: starter ($3/mo), growth ($8/mo), pro ($20/mo)
356
+ - App: app_sm ($12/mo, 256MB), app_md ($25/mo, 512MB), app_lg ($50/mo, 1GB)
357
+
358
+ For Python/Node apps, use app_sm or higher. Static sites work on any tier.
359
+
360
+ ⚠️ By default, apps auto-stop after ~5 min idle and cold-start on the next request (~2-5s delay).
361
+ Set alwaysOn: true to keep your app running 24/7 with zero cold starts.
362
+ Always-on is free on app tiers (app_sm/md/lg). Static tiers (starter/growth/pro) have a +$2/mo surcharge.`,
363
+ inputSchema: {
364
+ type: "object",
365
+ required: ["domain", "owner", "filesBase64", "email"],
366
+ properties: {
367
+ domain: {
368
+ type: "string",
369
+ description: "Domain to register and deploy to (e.g. fittrack.dev)",
370
+ },
371
+ owner: {
372
+ type: "string",
373
+ description: "Your wallet address (used for ownership verification on redeployment)",
374
+ },
375
+ filesBase64: {
376
+ type: "string",
377
+ description: "Base64-encoded zip file of your site (must include index.html at root for static, or package.json/requirements.txt for apps)",
378
+ },
379
+ contacts: {
380
+ type: "object",
381
+ description: "ICANN registrant contact info. If omitted, account defaults are used. Required fields: firstName, lastName, email, phone, address1, city, state, zip, country",
382
+ properties: {
383
+ firstName: { type: "string" },
384
+ lastName: { type: "string" },
385
+ email: { type: "string" },
386
+ phone: { type: "string", description: "E.164 format, e.g. +1.5551234567" },
387
+ address1: { type: "string" },
388
+ city: { type: "string" },
389
+ state: { type: "string" },
390
+ zip: { type: "string" },
391
+ country: { type: "string", description: "2-letter ISO country code, e.g. US" },
392
+ },
393
+ },
394
+ alwaysOn: {
395
+ type: "boolean",
396
+ description: "Keep app running 24/7 with no cold starts. Default false (app sleeps after ~5 min idle). Recommended true for production apps.",
397
+ },
398
+ paymentSignature: {
399
+ type: "string",
400
+ description: "x402 payment signature (X-Payment header value) from a signed USDC authorization. Required after receiving payment details.",
401
+ },
402
+ },
403
+ },
404
+ },
405
+ {
406
+ name: "domainagent_upload",
407
+ description: `Upload files to complete a domain deployment after paying via domainagent_deploy.
408
+
409
+ Use this when:
410
+ - domainagent_deploy returned an uploadToken (payment succeeded, files not yet uploaded)
411
+ - You have a base64-encoded zip file to deploy
412
+
413
+ The uploadToken is valid for 30 minutes.`,
414
+ inputSchema: {
415
+ type: "object",
416
+ required: ["uploadToken", "filesBase64"],
417
+ properties: {
418
+ uploadToken: { type: "string", description: "Token returned by domainagent_deploy after payment" },
419
+ filesBase64: { type: "string", description: "Base64-encoded zip file of your site" },
420
+ },
421
+ },
422
+ },
423
+ {
424
+ name: "domainagent_redeploy",
425
+ description: `Update an existing ShipAgent deployment with new files.
426
+ The domain must already be registered via domainagent_deploy.
427
+ Costs $2 USDC via x402 payment.
428
+
429
+ Steps:
430
+ 1. Call this tool with your domain, owner address, and updated zip file
431
+ 2. Receive x402 payment details
432
+ 3. Sign USDC authorization and retry with payment signature`,
433
+ inputSchema: {
434
+ type: "object",
435
+ required: ["domain", "owner", "filesBase64"],
436
+ properties: {
437
+ domain: {
438
+ type: "string",
439
+ description: "Domain to redeploy (must already be deployed)",
440
+ },
441
+ owner: {
442
+ type: "string",
443
+ description: "Your wallet address (must match original deployer)",
444
+ },
445
+ filesBase64: {
446
+ type: "string",
447
+ description: "Base64-encoded zip file with updated static site",
448
+ },
449
+ paymentSignature: {
450
+ type: "string",
451
+ description: "x402 payment signature after receiving payment details",
452
+ },
453
+ },
454
+ },
455
+ },
456
+ {
457
+ name: "domainagent_hosting_status",
458
+ description:
459
+ "Check hosting status, tier, usage, and billing info for a deployed domain.",
460
+ inputSchema: {
461
+ type: "object",
462
+ required: ["domain"],
463
+ properties: {
464
+ domain: { type: "string", description: "Domain to check hosting for" },
465
+ },
466
+ },
467
+ },
468
+ {
469
+ name: "domainagent_hosting_pay",
470
+ description:
471
+ "Pay hosting fee for a domain. Protected by x402. Send tier (starter/growth/pro) and billing (monthly/annual). Also resumes paused sites.",
472
+ inputSchema: {
473
+ type: "object",
474
+ required: ["domain"],
475
+ properties: {
476
+ domain: { type: "string", description: "Domain to pay hosting for" },
477
+ tier: { type: "string", description: "Hosting tier: starter, growth, or pro (default: current tier)" },
478
+ billing: { type: "string", description: "Billing cycle: monthly or annual (default: monthly)" },
479
+ paymentSignature: { type: "string", description: "x402 payment signature" },
480
+ },
481
+ },
482
+ },
483
+ {
484
+ name: "domainagent_hosting_pay_overage",
485
+ description:
486
+ "Pay outstanding bandwidth overage for a domain. Protected by x402. Amount is calculated from actual usage. Also resumes paused sites.",
487
+ inputSchema: {
488
+ type: "object",
489
+ required: ["domain"],
490
+ properties: {
491
+ domain: { type: "string", description: "Domain with outstanding overage" },
492
+ paymentSignature: { type: "string", description: "x402 payment signature" },
493
+ },
494
+ },
495
+ },
496
+ {
497
+ name: "domainagent_hosting_upgrade",
498
+ description:
499
+ "Upgrade the hosting tier for a domain. Requires auth. Does not charge — use domainagent_hosting_pay to pay for the new tier.",
500
+ inputSchema: {
501
+ type: "object",
502
+ required: ["domain", "tier", "authToken"],
503
+ properties: {
504
+ domain: { type: "string", description: "Domain to upgrade" },
505
+ tier: { type: "string", description: "New tier: starter, growth, or pro" },
506
+ billing: { type: "string", description: "Billing cycle: monthly or annual" },
507
+ authToken: { type: "string", description: "Session token" },
508
+ },
509
+ },
510
+ },
511
+ {
512
+ name: "domainagent_env_list",
513
+ description:
514
+ "List environment variable names (values masked) for a deployed domain. Requires auth.",
515
+ inputSchema: {
516
+ type: "object",
517
+ required: ["domain", "authToken"],
518
+ properties: {
519
+ domain: { type: "string", description: "Domain to list env vars for" },
520
+ authToken: { type: "string", description: "Session token" },
521
+ },
522
+ },
523
+ },
524
+ {
525
+ name: "domainagent_env_set",
526
+ description:
527
+ "Set one or more environment variables for a deployed domain. Restarts the app automatically. Requires auth.",
528
+ inputSchema: {
529
+ type: "object",
530
+ required: ["domain", "vars", "authToken"],
531
+ properties: {
532
+ domain: { type: "string", description: "Domain to set env vars for" },
533
+ vars: { type: "object", description: "Key-value pairs to set, e.g. { DATABASE_URL: 'postgres://...' }" },
534
+ authToken: { type: "string", description: "Session token" },
535
+ },
536
+ },
537
+ },
538
+ {
539
+ name: "domainagent_env_delete",
540
+ description:
541
+ "Delete an environment variable by key from a deployed domain. Restarts the app automatically. Requires auth.",
542
+ inputSchema: {
543
+ type: "object",
544
+ required: ["domain", "key", "authToken"],
545
+ properties: {
546
+ domain: { type: "string", description: "Domain to delete env var from" },
547
+ key: { type: "string", description: "Environment variable key to delete" },
548
+ authToken: { type: "string", description: "Session token" },
549
+ },
550
+ },
551
+ },
552
+ {
553
+ name: "domainagent_logs",
554
+ description:
555
+ "Fetch recent logs for a deployed domain. Only available for Fly-hosted apps. Requires auth.",
556
+ inputSchema: {
557
+ type: "object",
558
+ required: ["domain", "authToken"],
559
+ properties: {
560
+ domain: { type: "string", description: "Domain to fetch logs for" },
561
+ lines: { type: "number", description: "Number of log lines to return (default: 100)" },
562
+ authToken: { type: "string", description: "Session token" },
563
+ },
564
+ },
565
+ },
566
+ {
567
+ name: "domainagent_deploy_preview",
568
+ description:
569
+ `Deploy files to a preview URL without a custom domain. Good for testing before going live. Protected by x402 — $2 fee.
570
+
571
+ Supported runtimes (auto-detected from your zip contents):
572
+ - Static sites (HTML/CSS/JS) → served via Caddy
573
+ - Node.js (package.json detected) → node:20-alpine
574
+ - Python (requirements.txt detected) → python:3.12-slim with gunicorn/uvicorn
575
+ - Docker (Dockerfile detected) → custom build
576
+
577
+ Hosting tiers:
578
+ - Static: starter ($3/mo), growth ($8/mo), pro ($20/mo)
579
+ - App: app_sm ($12/mo, 256MB), app_md ($25/mo, 512MB), app_lg ($50/mo, 1GB)
580
+
581
+ For Python/Node apps, use app_sm or higher. Static sites work on any tier.`,
582
+ inputSchema: {
583
+ type: "object",
584
+ required: ["owner", "filesBase64"],
585
+ properties: {
586
+ owner: { type: "string", description: "Wallet address" },
587
+ domain: { type: "string", description: "Optional domain to associate the preview with" },
588
+ hostingTier: { type: "string", description: "Hosting tier (default: starter)" },
589
+ filesBase64: { type: "string", description: "Base64-encoded zip file of your site" },
590
+ paymentSignature: { type: "string", description: "x402 payment signature" },
591
+ },
592
+ },
593
+ },
594
+ {
595
+ name: "domainagent_deploy_promote",
596
+ description:
597
+ "Promote a preview deployment to production on a custom domain. Requires auth.",
598
+ inputSchema: {
599
+ type: "object",
600
+ required: ["domain", "previewId", "authToken"],
601
+ properties: {
602
+ domain: { type: "string", description: "Domain to promote the preview to" },
603
+ previewId: { type: "string", description: "Preview app ID from domainagent_deploy_preview" },
604
+ authToken: { type: "string", description: "Session token" },
605
+ },
606
+ },
607
+ },
608
+ {
609
+ name: "domainagent_rollback",
610
+ description:
611
+ "Roll back a domain to its previous deployment. Requires at least 2 deploys in history. Requires auth.",
612
+ inputSchema: {
613
+ type: "object",
614
+ required: ["domain", "authToken"],
615
+ properties: {
616
+ domain: { type: "string", description: "Domain to roll back" },
617
+ authToken: { type: "string", description: "Session token" },
618
+ },
619
+ },
620
+ },
621
+ {
622
+ name: "domainagent_deploy_resume",
623
+ description: "Resume a failed deploy. If a deploy crashed after domain registration but before the app went live, this re-deploys from the saved zip without re-purchasing the domain.",
624
+ inputSchema: {
625
+ type: "object",
626
+ properties: {
627
+ domain: { type: "string", description: "Domain to resume deploy for" },
628
+ authToken: { type: "string", description: "Session token from auth/verify" },
629
+ },
630
+ required: ["domain", "authToken"],
631
+ },
632
+ },
633
+ {
634
+ name: "domainagent_deploy_from_git",
635
+ description:
636
+ `Deploy directly from a GitHub repo URL. Clones the repo, auto-detects runtime (Node.js, Python, Docker, static), builds, and deploys. Protected by x402 — $2 fee.
637
+
638
+ Supported runtimes (auto-detected from your repo contents):
639
+ - Static sites (HTML/CSS/JS) → served via Caddy
640
+ - Node.js (package.json detected) → node:20-alpine
641
+ - Python (requirements.txt detected) → python:3.12-slim with gunicorn/uvicorn
642
+ - Docker (Dockerfile detected) → custom build
643
+
644
+ Hosting tiers:
645
+ - Static: starter ($3/mo), growth ($8/mo), pro ($20/mo)
646
+ - App: app_sm ($12/mo, 256MB), app_md ($25/mo, 512MB), app_lg ($50/mo, 1GB)
647
+
648
+ For Python/Node apps, use app_sm or higher. Static sites work on any tier.`,
649
+ inputSchema: {
650
+ type: "object",
651
+ required: ["domain", "repoUrl", "owner", "email"],
652
+ properties: {
653
+ domain: { type: "string", description: "Domain to deploy to" },
654
+ repoUrl: { type: "string", description: "Git repository URL (e.g. https://github.com/user/repo)" },
655
+ branch: { type: "string", description: "Branch to deploy (default: main)" },
656
+ owner: { type: "string", description: "Wallet address" },
657
+ email: { type: "string", description: "Contact email for hosting notices" },
658
+ hostingTier: { type: "string", description: "Hosting tier (default: starter)" },
659
+ hostingBilling: { type: "string", description: "Billing cycle: monthly or annual" },
660
+ paymentSignature: { type: "string", description: "x402 payment signature" },
661
+ },
662
+ },
663
+ },
664
+ ];
665
+
666
+ // --- Multipart form builder (for file upload endpoints) ---
667
+
668
+ function buildMultipartForm(fields, fileFieldName, fileBuffer, fileName) {
669
+ const boundary = `----ShipAgentBoundary${Date.now()}`;
670
+ const parts = [];
671
+
672
+ for (const [name, value] of Object.entries(fields)) {
673
+ if (value == null) continue;
674
+ parts.push(
675
+ `--${boundary}\r\nContent-Disposition: form-data; name="${name}"\r\n\r\n${value}\r\n`
676
+ );
677
+ }
678
+
679
+ if (fileBuffer) {
680
+ parts.push(
681
+ `--${boundary}\r\nContent-Disposition: form-data; name="${fileFieldName}"; filename="${fileName || "files.zip"}"\r\nContent-Type: application/zip\r\n\r\n`
682
+ );
683
+ }
684
+
685
+ const textPart = Buffer.from(parts.join(""));
686
+ const endPart = Buffer.from(`\r\n--${boundary}--\r\n`);
687
+
688
+ let body;
689
+ if (fileBuffer) {
690
+ body = Buffer.concat([textPart, fileBuffer, endPart]);
691
+ } else {
692
+ body = Buffer.concat([textPart, endPart]);
693
+ }
694
+
695
+ return { body, contentType: `multipart/form-data; boundary=${boundary}` };
696
+ }
697
+
698
+ function multipartRequest(method, path, fields, fileBuffer, extraHeaders) {
699
+ return new Promise((resolve, reject) => {
700
+ const url = new URL(API_BASE + path);
701
+ const isHttps = url.protocol === "https:";
702
+ const transport = isHttps ? https : http;
703
+
704
+ const { body, contentType } = buildMultipartForm(
705
+ fields,
706
+ "files",
707
+ fileBuffer,
708
+ "files.zip"
709
+ );
710
+
711
+ const options = {
712
+ hostname: url.hostname,
713
+ port: url.port || (isHttps ? 443 : 80),
714
+ path: url.pathname + url.search,
715
+ method,
716
+ headers: {
717
+ "Content-Type": contentType,
718
+ "Content-Length": body.length,
719
+ "Accept": "application/json",
720
+ ...(extraHeaders || {}),
721
+ },
722
+ };
723
+
724
+ const req = transport.request(options, (res) => {
725
+ let data = "";
726
+ res.on("data", (chunk) => (data += chunk));
727
+ res.on("end", () => {
728
+ try {
729
+ resolve({ status: res.statusCode, headers: res.headers, body: JSON.parse(data) });
730
+ } catch {
731
+ resolve({ status: res.statusCode, headers: res.headers, body: data });
732
+ }
733
+ });
734
+ });
735
+
736
+ req.on("error", reject);
737
+ req.write(body);
738
+ req.end();
739
+ });
740
+ }
741
+
742
+ // --- Tool handlers ---
743
+
744
+ async function handleTool(name, args) {
745
+ switch (name) {
746
+ case "domainagent_auth_challenge": {
747
+ const res = await apiRequest("POST", "/api/auth/challenge", { owner: args.owner });
748
+ return formatResult(res);
749
+ }
750
+
751
+ case "domainagent_auth_verify": {
752
+ const res = await apiRequest("POST", "/api/auth/verify", {
753
+ owner: args.owner,
754
+ signature: args.signature,
755
+ message: args.message,
756
+ });
757
+ return formatResult(res);
758
+ }
759
+
760
+ case "domainagent_list_domains": {
761
+ const res = await apiRequest("GET", "/api/domains", null, {
762
+ Authorization: `Bearer ${args.authToken}`,
763
+ });
764
+ return formatResult(res);
765
+ }
766
+
767
+ case "domainagent_health": {
768
+ const res = await apiRequest("GET", `/api/health/${encodeURIComponent(args.domain)}`);
769
+ return formatResult(res);
770
+ }
771
+
772
+ case "domainagent_dns_list": {
773
+ const res = await apiRequest("GET", `/api/dns/${encodeURIComponent(args.domain)}`, null, {
774
+ Authorization: `Bearer ${args.authToken}`,
775
+ });
776
+ return formatResult(res);
777
+ }
778
+
779
+ case "domainagent_dns_add": {
780
+ const res = await apiRequest("POST", `/api/dns/${encodeURIComponent(args.domain)}`, {
781
+ host: args.host || "",
782
+ type: args.type,
783
+ answer: args.answer,
784
+ ttl: args.ttl || 300,
785
+ }, { Authorization: `Bearer ${args.authToken}` });
786
+ return formatResult(res);
787
+ }
788
+
789
+ case "domainagent_dns_delete": {
790
+ const res = await apiRequest("DELETE", `/api/dns/${encodeURIComponent(args.domain)}/${args.recordId}`, null, {
791
+ Authorization: `Bearer ${args.authToken}`,
792
+ });
793
+ return formatResult(res);
794
+ }
795
+
796
+ case "domainagent_renewal_email": {
797
+ const res = await apiRequest("POST", "/api/renewal-email", {
798
+ domain: args.domain,
799
+ email: args.email,
800
+ }, { Authorization: `Bearer ${args.authToken}` });
801
+ return formatResult(res);
802
+ }
803
+
804
+ case "domainagent_connect": {
805
+ const res = await apiRequest("POST", "/api/connect", {
806
+ cf_api_token: args.cf_api_token,
807
+ }, { Authorization: `Bearer ${args.authToken}` });
808
+ return formatResult(res);
809
+ }
810
+
811
+ case "domainagent_transfer_out": {
812
+ const res = await apiRequest("POST", "/api/transfer-out", {
813
+ domain: args.domain,
814
+ }, { Authorization: `Bearer ${args.authToken}` });
815
+ return formatResult(res);
816
+ }
817
+
818
+ case "domainagent_renewals_due": {
819
+ const ownerParam = args.owner ? `?owner=${encodeURIComponent(args.owner)}` : "";
820
+ const res = await apiRequest("GET", `/api/renewals-due${ownerParam}`);
821
+ return formatResult(res);
822
+ }
823
+
824
+ case "domainagent_renew": {
825
+ const extraHeaders = {};
826
+ if (args.paymentSignature) extraHeaders["X-Payment"] = args.paymentSignature;
827
+ extraHeaders["PAYMENT-SIGNATURE"] = args.paymentSignature;
828
+ const res = await apiRequest("POST", "/api/renew", {
829
+ domain: args.domain,
830
+ owner: args.owner,
831
+ }, extraHeaders);
832
+ return formatResult(res);
833
+ }
834
+
835
+ case "domainagent_search": {
836
+ const res = await apiRequest("POST", "/api/search", {
837
+ query: args.query,
838
+ tlds: args.tlds,
839
+ });
840
+ return formatResult(res);
841
+ }
842
+
843
+ case "domainagent_recommend": {
844
+ const res = await apiRequest("POST", "/api/recommend", {
845
+ description: args.description,
846
+ budget: args.budget,
847
+ });
848
+ return formatResult(res);
849
+ }
850
+
851
+ case "domainagent_pricing": {
852
+ const res = await apiRequest("GET", "/api/pricing");
853
+ return formatResult(res);
854
+ }
855
+
856
+ case "domainagent_status": {
857
+ const domain = encodeURIComponent(args.domain);
858
+ const res = await apiRequest("GET", `/api/status/${domain}`);
859
+ return formatResult(res);
860
+ }
861
+
862
+ case "domainagent_deploy": {
863
+ const { domain, owner, filesBase64, paymentSignature, email, hostingTier, hostingBilling, contacts, alwaysOn } = args;
864
+
865
+ // Two-step deploy flow:
866
+ // Step 1: POST /api/deploy/prepare (JSON + x402) — registers domain, returns upload token
867
+ // Step 2: POST /api/deploy/upload (multipart) — uploads files, completes deploy
868
+
869
+ let fileBuffer = null;
870
+ if (filesBase64) {
871
+ try {
872
+ fileBuffer = Buffer.from(filesBase64, "base64");
873
+ } catch (err) {
874
+ return { isError: true, content: [{ type: "text", text: `Invalid base64 for filesBase64: ${err.message}` }] };
875
+ }
876
+ }
877
+
878
+ const prepareBody = { domain, owner, renewalEmail: email, hostingTier, hostingBilling };
879
+ if (contacts) prepareBody.contacts = contacts;
880
+
881
+ const extraHeaders = {};
882
+ if (paymentSignature) {
883
+ extraHeaders["X-Payment"] = paymentSignature;
884
+ extraHeaders["x-payment"] = paymentSignature;
885
+ }
886
+
887
+ // Step 1: Pay + register domain
888
+ const prepareRes = await apiRequest("POST", "/api/deploy/prepare", prepareBody, extraHeaders);
889
+
890
+ // If 402, return payment details to the agent
891
+ if (prepareRes.status === 402) return formatResult(prepareRes);
892
+
893
+ // If error, return it
894
+ if (prepareRes.status >= 400) return formatResult(prepareRes);
895
+
896
+ const uploadToken = prepareRes.body?.uploadToken;
897
+ if (!uploadToken) {
898
+ return { isError: true, content: [{ type: "text", text: `Prepare succeeded but no uploadToken returned: ${JSON.stringify(prepareRes.body)}` }] };
899
+ }
900
+
901
+ // If no files provided, return the token so agent can upload separately
902
+ if (!fileBuffer) {
903
+ return {
904
+ content: [{ type: "text", text: JSON.stringify({
905
+ success: true,
906
+ domain,
907
+ uploadToken,
908
+ message: "Domain registered and payment received. Now call domainagent_upload with this token and your zip file to complete deployment.",
909
+ expiresInMinutes: 30,
910
+ }, null, 2) }],
911
+ };
912
+ }
913
+
914
+ // Step 2: Upload files
915
+ const uploadRes = await multipartRequest(
916
+ "POST",
917
+ "/api/deploy/upload",
918
+ { uploadToken },
919
+ fileBuffer,
920
+ {}
921
+ );
922
+
923
+ return formatResult(uploadRes);
924
+ }
925
+
926
+ case "domainagent_upload": {
927
+ const { uploadToken, filesBase64 } = args;
928
+ let fileBuffer;
929
+ try {
930
+ fileBuffer = Buffer.from(filesBase64, "base64");
931
+ } catch (err) {
932
+ return { isError: true, content: [{ type: "text", text: `Invalid base64: ${err.message}` }] };
933
+ }
934
+ const res = await multipartRequest("POST", "/api/deploy/upload", { uploadToken }, fileBuffer, {});
935
+ return formatResult(res);
936
+ }
937
+
938
+ case "domainagent_redeploy": {
939
+ const { domain, owner, filesBase64, paymentSignature } = args;
940
+
941
+ let fileBuffer = null;
942
+ if (filesBase64) {
943
+ try {
944
+ fileBuffer = Buffer.from(filesBase64, "base64");
945
+ } catch (err) {
946
+ return {
947
+ isError: true,
948
+ content: [{ type: "text", text: `Invalid base64 for filesBase64: ${err.message}` }],
949
+ };
950
+ }
951
+ }
952
+
953
+ const extraHeaders = {};
954
+ if (paymentSignature) {
955
+ extraHeaders["X-Payment"] = paymentSignature;
956
+ extraHeaders["PAYMENT-SIGNATURE"] = paymentSignature;
957
+ }
958
+
959
+ const res = await multipartRequest(
960
+ "POST",
961
+ "/api/redeploy",
962
+ { domain, owner },
963
+ fileBuffer,
964
+ extraHeaders
965
+ );
966
+
967
+ return formatResult(res);
968
+ }
969
+
970
+ case "domainagent_hosting_status": {
971
+ const res = await apiRequest("GET", `/api/hosting/${encodeURIComponent(args.domain)}`);
972
+ return formatResult(res);
973
+ }
974
+
975
+ case "domainagent_hosting_pay": {
976
+ const extraHeaders = {};
977
+ if (args.paymentSignature) extraHeaders["X-Payment"] = args.paymentSignature;
978
+ extraHeaders["PAYMENT-SIGNATURE"] = args.paymentSignature;
979
+ const res = await apiRequest("POST", "/api/hosting/pay", {
980
+ domain: args.domain,
981
+ tier: args.tier,
982
+ billing: args.billing,
983
+ }, extraHeaders);
984
+ return formatResult(res);
985
+ }
986
+
987
+ case "domainagent_hosting_pay_overage": {
988
+ const extraHeaders = {};
989
+ if (args.paymentSignature) extraHeaders["X-Payment"] = args.paymentSignature;
990
+ extraHeaders["PAYMENT-SIGNATURE"] = args.paymentSignature;
991
+ const res = await apiRequest("POST", "/api/hosting/pay-overage", {
992
+ domain: args.domain,
993
+ }, extraHeaders);
994
+ return formatResult(res);
995
+ }
996
+
997
+ case "domainagent_hosting_upgrade": {
998
+ const res = await apiRequest("POST", `/api/hosting/${encodeURIComponent(args.domain)}/upgrade`, {
999
+ tier: args.tier,
1000
+ billing: args.billing,
1001
+ }, { Authorization: `Bearer ${args.authToken}` });
1002
+ return formatResult(res);
1003
+ }
1004
+
1005
+ case "domainagent_env_list": {
1006
+ const res = await apiRequest("GET", `/api/hosting/${encodeURIComponent(args.domain)}/env`, null, {
1007
+ Authorization: `Bearer ${args.authToken}`,
1008
+ });
1009
+ return formatResult(res);
1010
+ }
1011
+
1012
+ case "domainagent_env_set": {
1013
+ const res = await apiRequest("POST", `/api/hosting/${encodeURIComponent(args.domain)}/env`, {
1014
+ vars: args.vars,
1015
+ }, { Authorization: `Bearer ${args.authToken}` });
1016
+ return formatResult(res);
1017
+ }
1018
+
1019
+ case "domainagent_env_delete": {
1020
+ const res = await apiRequest("DELETE", `/api/hosting/${encodeURIComponent(args.domain)}/env/${encodeURIComponent(args.key)}`, null, {
1021
+ Authorization: `Bearer ${args.authToken}`,
1022
+ });
1023
+ return formatResult(res);
1024
+ }
1025
+
1026
+ case "domainagent_logs": {
1027
+ const linesParam = args.lines ? `?lines=${args.lines}` : "";
1028
+ const res = await apiRequest("GET", `/api/hosting/${encodeURIComponent(args.domain)}/logs${linesParam}`, null, {
1029
+ Authorization: `Bearer ${args.authToken}`,
1030
+ });
1031
+ return formatResult(res);
1032
+ }
1033
+
1034
+ case "domainagent_deploy_preview": {
1035
+ const { owner, domain, hostingTier, filesBase64, paymentSignature } = args;
1036
+
1037
+ let fileBuffer = null;
1038
+ if (filesBase64) {
1039
+ try {
1040
+ fileBuffer = Buffer.from(filesBase64, "base64");
1041
+ } catch (err) {
1042
+ return {
1043
+ isError: true,
1044
+ content: [{ type: "text", text: `Invalid base64 for filesBase64: ${err.message}` }],
1045
+ };
1046
+ }
1047
+ }
1048
+
1049
+ const extraHeaders = {};
1050
+ if (paymentSignature) extraHeaders["X-Payment"] = paymentSignature;
1051
+ extraHeaders["PAYMENT-SIGNATURE"] = paymentSignature;
1052
+
1053
+ const fields = { owner };
1054
+ if (domain) fields.domain = domain;
1055
+ if (hostingTier) fields.hostingTier = hostingTier;
1056
+
1057
+ const res = await multipartRequest(
1058
+ "POST",
1059
+ "/api/deploy/preview",
1060
+ fields,
1061
+ fileBuffer,
1062
+ extraHeaders
1063
+ );
1064
+
1065
+ return formatResult(res);
1066
+ }
1067
+
1068
+ case "domainagent_deploy_promote": {
1069
+ const res = await apiRequest("POST", "/api/deploy/promote", {
1070
+ domain: args.domain,
1071
+ previewId: args.previewId,
1072
+ }, { Authorization: `Bearer ${args.authToken}` });
1073
+ return formatResult(res);
1074
+ }
1075
+
1076
+ case "domainagent_rollback": {
1077
+ const res = await apiRequest("POST", `/api/hosting/${encodeURIComponent(args.domain)}/rollback`, null, {
1078
+ Authorization: `Bearer ${args.authToken}`,
1079
+ });
1080
+ return formatResult(res);
1081
+ }
1082
+
1083
+ case "domainagent_deploy_resume": {
1084
+ const res = await apiRequest("POST", "/api/deploy/resume", {
1085
+ domain: args.domain,
1086
+ }, { Authorization: `Bearer ${args.authToken}` });
1087
+ return formatResult(res);
1088
+ }
1089
+
1090
+ case "domainagent_deploy_from_git": {
1091
+ const extraHeaders = {};
1092
+ if (args.paymentSignature) extraHeaders["X-Payment"] = args.paymentSignature;
1093
+ extraHeaders["PAYMENT-SIGNATURE"] = args.paymentSignature;
1094
+ const res = await apiRequest("POST", "/api/deploy/from-git", {
1095
+ domain: args.domain,
1096
+ repoUrl: args.repoUrl,
1097
+ branch: args.branch,
1098
+ owner: args.owner,
1099
+ email: args.email,
1100
+ hostingTier: args.hostingTier,
1101
+ hostingBilling: args.hostingBilling,
1102
+ }, extraHeaders);
1103
+ return formatResult(res);
1104
+ }
1105
+
1106
+ default:
1107
+ return {
1108
+ isError: true,
1109
+ content: [{ type: "text", text: `Unknown tool: ${name}` }],
1110
+ };
1111
+ }
1112
+ }
1113
+
1114
+ // --- MCP server setup ---
1115
+
1116
+ async function main() {
1117
+ const server = new Server(
1118
+ { name: "domainagent", version: "1.0.0" },
1119
+ { capabilities: { tools: {} } }
1120
+ );
1121
+
1122
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
1123
+ tools: TOOLS,
1124
+ }));
1125
+
1126
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1127
+ const { name, arguments: args } = request.params;
1128
+ try {
1129
+ return await handleTool(name, args || {});
1130
+ } catch (err) {
1131
+ return {
1132
+ isError: true,
1133
+ content: [{ type: "text", text: `Tool error: ${err.message}` }],
1134
+ };
1135
+ }
1136
+ });
1137
+
1138
+ const transport = new StdioServerTransport();
1139
+ await server.connect(transport);
1140
+
1141
+ process.stderr.write(`domainagent MCP server running (API: ${API_BASE})\n`);
1142
+ }
1143
+
1144
+ main().catch((err) => {
1145
+ process.stderr.write(`Fatal: ${err.message}\n`);
1146
+ process.exit(1);
1147
+ });
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "domainagent-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for domainagent.dev — register domains, deploy apps, and manage DNS with x402 payments",
5
+ "main": "mcp-server.js",
6
+ "bin": {
7
+ "domainagent-mcp": "./mcp-server.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node server.js"
11
+ },
12
+ "keywords": [
13
+ "mcp",
14
+ "domain",
15
+ "dns",
16
+ "deploy",
17
+ "x402",
18
+ "ai-agent",
19
+ "model-context-protocol"
20
+ ],
21
+ "author": "domainagent.dev",
22
+ "license": "MIT",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/mswat/domainagent-mcp"
26
+ },
27
+ "homepage": "https://domainagent.dev",
28
+ "type": "commonjs",
29
+ "engines": {
30
+ "node": ">=20.0.0 <23.0.0"
31
+ },
32
+ "overrides": {
33
+ "jose": "5.10.0"
34
+ },
35
+ "dependencies": {
36
+ "@coinbase/cdp-sdk": "^1.46.1",
37
+ "@coinbase/x402": "^2.1.0",
38
+ "@modelcontextprotocol/sdk": "^1.29.0",
39
+ "@supabase/supabase-js": "^2.101.1",
40
+ "@x402/core": "^2.9.0",
41
+ "@x402/evm": "^2.9.0",
42
+ "@x402/express": "^2.9.0",
43
+ "adm-zip": "^0.5.17",
44
+ "bs58": "^6.0.0",
45
+ "cors": "^2.8.6",
46
+ "dotenv": "^17.4.1",
47
+ "ethers": "^6.16.0",
48
+ "express": "^5.2.1",
49
+ "jose": "5.10.0",
50
+ "multer": "^2.1.1",
51
+ "siwe": "^3.0.0"
52
+ }
53
+ }