domain-search-mcp 1.2.1 → 1.2.2
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/README.md +939 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -185,6 +185,118 @@ Find the best deal:
|
|
|
185
185
|
}
|
|
186
186
|
```
|
|
187
187
|
|
|
188
|
+
**Error Handling and User Presentation:**
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
// Robust comparison with error handling and formatted output
|
|
192
|
+
async function comparePricingWithPresentation(domain: string, tld: string) {
|
|
193
|
+
try {
|
|
194
|
+
const result = await compareRegistrars({
|
|
195
|
+
domain: domain,
|
|
196
|
+
tld: tld,
|
|
197
|
+
registrars: ["porkbun", "namecheap"]
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Format for user presentation
|
|
201
|
+
const presentation = formatPriceComparison(result);
|
|
202
|
+
return { success: true, data: result, formatted: presentation };
|
|
203
|
+
|
|
204
|
+
} catch (error) {
|
|
205
|
+
// Handle domain not available
|
|
206
|
+
if (error.code === "DOMAIN_UNAVAILABLE") {
|
|
207
|
+
return {
|
|
208
|
+
success: false,
|
|
209
|
+
error: `${domain}.${tld} is not available for registration`,
|
|
210
|
+
suggestion: "Try suggest_domains to find alternatives"
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Handle registrar API errors
|
|
215
|
+
if (error.code === "REGISTRAR_API_ERROR") {
|
|
216
|
+
// Try with remaining registrars
|
|
217
|
+
const workingRegistrars = error.failedRegistrars
|
|
218
|
+
? ["porkbun", "namecheap"].filter(r => !error.failedRegistrars.includes(r))
|
|
219
|
+
: [];
|
|
220
|
+
|
|
221
|
+
if (workingRegistrars.length > 0) {
|
|
222
|
+
const partialResult = await compareRegistrars({
|
|
223
|
+
domain, tld,
|
|
224
|
+
registrars: workingRegistrars
|
|
225
|
+
});
|
|
226
|
+
return {
|
|
227
|
+
success: true,
|
|
228
|
+
partial: true,
|
|
229
|
+
data: partialResult,
|
|
230
|
+
note: `Some registrars unavailable. Showing ${workingRegistrars.join(', ')} only.`
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Handle rate limiting
|
|
236
|
+
if (error.code === "RATE_LIMIT") {
|
|
237
|
+
await new Promise(r => setTimeout(r, error.retryAfter * 1000));
|
|
238
|
+
return comparePricingWithPresentation(domain, tld);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Format comparison for display
|
|
246
|
+
function formatPriceComparison(result) {
|
|
247
|
+
const lines = [
|
|
248
|
+
`Domain: ${result.domain}`,
|
|
249
|
+
``,
|
|
250
|
+
`💰 PRICING COMPARISON`,
|
|
251
|
+
`${'─'.repeat(40)}`,
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
// Add each registrar's pricing
|
|
255
|
+
if (result.registrar_prices) {
|
|
256
|
+
for (const [registrar, prices] of Object.entries(result.registrar_prices)) {
|
|
257
|
+
lines.push(`${registrar.toUpperCase()}`);
|
|
258
|
+
lines.push(` First year: $${prices.first_year}`);
|
|
259
|
+
lines.push(` Renewal: $${prices.renewal}`);
|
|
260
|
+
lines.push(``);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
lines.push(`${'─'.repeat(40)}`);
|
|
265
|
+
lines.push(`RECOMMENDATION`);
|
|
266
|
+
lines.push(` Best first year: ${result.best_first_year.registrar} ($${result.best_first_year.price})`);
|
|
267
|
+
lines.push(` Best renewal: ${result.best_renewal.registrar} ($${result.best_renewal.price})`);
|
|
268
|
+
lines.push(``);
|
|
269
|
+
lines.push(`💡 ${result.recommendation}`);
|
|
270
|
+
|
|
271
|
+
return lines.join('\n');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Usage
|
|
275
|
+
const comparison = await comparePricingWithPresentation("startup", "io");
|
|
276
|
+
if (comparison.success) {
|
|
277
|
+
console.log(comparison.formatted);
|
|
278
|
+
}
|
|
279
|
+
// Output:
|
|
280
|
+
// Domain: startup.io
|
|
281
|
+
//
|
|
282
|
+
// 💰 PRICING COMPARISON
|
|
283
|
+
// ────────────────────────────────────────
|
|
284
|
+
// PORKBUN
|
|
285
|
+
// First year: $29.88
|
|
286
|
+
// Renewal: $29.88
|
|
287
|
+
//
|
|
288
|
+
// NAMECHEAP
|
|
289
|
+
// First year: $32.98
|
|
290
|
+
// Renewal: $32.98
|
|
291
|
+
//
|
|
292
|
+
// ────────────────────────────────────────
|
|
293
|
+
// RECOMMENDATION
|
|
294
|
+
// Best first year: porkbun ($29.88)
|
|
295
|
+
// Best renewal: porkbun ($29.88)
|
|
296
|
+
//
|
|
297
|
+
// 💡 Porkbun offers the best price for both first year and renewal
|
|
298
|
+
```
|
|
299
|
+
|
|
188
300
|
### suggest_domains
|
|
189
301
|
|
|
190
302
|
Get variations when your preferred name is taken:
|
|
@@ -194,7 +306,8 @@ Get variations when your preferred name is taken:
|
|
|
194
306
|
{
|
|
195
307
|
"base_name": "vibecoding",
|
|
196
308
|
"tld": "com",
|
|
197
|
-
"max_suggestions": 5
|
|
309
|
+
"max_suggestions": 5,
|
|
310
|
+
"variants": ["prefixes", "suffixes", "hyphen", "abbreviations"]
|
|
198
311
|
}
|
|
199
312
|
|
|
200
313
|
// Output
|
|
@@ -211,6 +324,86 @@ Get variations when your preferred name is taken:
|
|
|
211
324
|
}
|
|
212
325
|
```
|
|
213
326
|
|
|
327
|
+
**Handling Edge Cases:**
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
// Handle scenarios when no alternatives are available
|
|
331
|
+
async function getSuggestionsWithFallback(baseName: string, tld: string) {
|
|
332
|
+
try {
|
|
333
|
+
const result = await suggestDomains({
|
|
334
|
+
base_name: baseName,
|
|
335
|
+
tld: tld,
|
|
336
|
+
max_suggestions: 10,
|
|
337
|
+
variants: ["prefixes", "suffixes", "hyphen", "abbreviations", "numbers"]
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Case 1: No suggestions found
|
|
341
|
+
if (result.suggestions.length === 0) {
|
|
342
|
+
console.log(`No variations available for ${baseName}.${tld}`);
|
|
343
|
+
|
|
344
|
+
// Try different TLDs
|
|
345
|
+
const altTlds = ["io", "dev", "app", "co"].filter(t => t !== tld);
|
|
346
|
+
for (const altTld of altTlds) {
|
|
347
|
+
const altResult = await suggestDomains({
|
|
348
|
+
base_name: baseName,
|
|
349
|
+
tld: altTld,
|
|
350
|
+
max_suggestions: 5
|
|
351
|
+
});
|
|
352
|
+
if (altResult.suggestions.length > 0) {
|
|
353
|
+
return {
|
|
354
|
+
originalTld: tld,
|
|
355
|
+
alternativeTld: altTld,
|
|
356
|
+
suggestions: altResult.suggestions,
|
|
357
|
+
message: `No ${tld} available, but found options in .${altTld}`
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Try smart suggestions as last resort
|
|
363
|
+
const smartResult = await suggestDomainsSmart({
|
|
364
|
+
query: baseName,
|
|
365
|
+
tld: tld,
|
|
366
|
+
style: "short",
|
|
367
|
+
max_suggestions: 10
|
|
368
|
+
});
|
|
369
|
+
return {
|
|
370
|
+
originalTld: tld,
|
|
371
|
+
suggestions: smartResult.results.available,
|
|
372
|
+
message: "Used AI-powered suggestions for creative alternatives"
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Case 2: All suggestions are premium (expensive)
|
|
377
|
+
const affordable = result.suggestions.filter(s => s.price_first_year < 50);
|
|
378
|
+
const premium = result.suggestions.filter(s => s.price_first_year >= 50);
|
|
379
|
+
|
|
380
|
+
if (affordable.length === 0 && premium.length > 0) {
|
|
381
|
+
return {
|
|
382
|
+
suggestions: [],
|
|
383
|
+
premiumOnly: premium,
|
|
384
|
+
message: `Only premium domains available (starting at $${premium[0].price_first_year})`
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return { suggestions: result.suggestions };
|
|
389
|
+
|
|
390
|
+
} catch (error) {
|
|
391
|
+
// Handle rate limiting during suggestion generation
|
|
392
|
+
if (error.code === "RATE_LIMIT") {
|
|
393
|
+
await new Promise(r => setTimeout(r, error.retryAfter * 1000));
|
|
394
|
+
return getSuggestionsWithFallback(baseName, tld);
|
|
395
|
+
}
|
|
396
|
+
throw error;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Usage
|
|
401
|
+
const suggestions = await getSuggestionsWithFallback("techapp", "com");
|
|
402
|
+
if (suggestions.alternativeTld) {
|
|
403
|
+
console.log(suggestions.message);
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
214
407
|
### suggest_domains_smart
|
|
215
408
|
|
|
216
409
|
AI-powered domain suggestions using semantic analysis:
|
|
@@ -314,13 +507,212 @@ Verify username availability across 10 platforms:
|
|
|
314
507
|
}
|
|
315
508
|
```
|
|
316
509
|
|
|
317
|
-
**v1.2.
|
|
510
|
+
**v1.2.2 Improvements:**
|
|
318
511
|
- **Twitter**: Uses oembed API for reliable detection (no more false positives)
|
|
319
512
|
- **Smart Caching**: Taken usernames cached 24h, available 1h, errors 5min
|
|
320
513
|
- **Rate Limit Handling**: Automatic 429 detection with graceful error reporting
|
|
321
514
|
|
|
515
|
+
**Error Handling for check_socials:**
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
// Handle various error scenarios when checking social platforms
|
|
519
|
+
async function robustSocialCheck(username: string) {
|
|
520
|
+
try {
|
|
521
|
+
const result = await checkSocials({
|
|
522
|
+
name: username,
|
|
523
|
+
platforms: ["github", "twitter", "instagram", "npm", "linkedin"]
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
// Categorize results by confidence and availability
|
|
527
|
+
const report = {
|
|
528
|
+
definitelyAvailable: result.results
|
|
529
|
+
.filter(r => r.available && r.confidence === "high")
|
|
530
|
+
.map(r => r.platform),
|
|
531
|
+
probablyAvailable: result.results
|
|
532
|
+
.filter(r => r.available && r.confidence === "medium")
|
|
533
|
+
.map(r => r.platform),
|
|
534
|
+
definitelyTaken: result.results
|
|
535
|
+
.filter(r => !r.available && r.confidence === "high")
|
|
536
|
+
.map(r => r.platform),
|
|
537
|
+
probablyTaken: result.results
|
|
538
|
+
.filter(r => !r.available && r.confidence === "medium")
|
|
539
|
+
.map(r => r.platform),
|
|
540
|
+
checkManually: result.results
|
|
541
|
+
.filter(r => r.confidence === "low")
|
|
542
|
+
.map(r => ({ platform: r.platform, url: r.url })),
|
|
543
|
+
errors: result.results
|
|
544
|
+
.filter(r => r.error)
|
|
545
|
+
.map(r => ({ platform: r.platform, error: r.error }))
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
return report;
|
|
549
|
+
} catch (error) {
|
|
550
|
+
// Handle rate limiting
|
|
551
|
+
if (error.code === "RATE_LIMIT") {
|
|
552
|
+
console.log(`Rate limited. Retry after ${error.retryAfter} seconds`);
|
|
553
|
+
await new Promise(r => setTimeout(r, error.retryAfter * 1000));
|
|
554
|
+
return robustSocialCheck(username); // Retry
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Handle network errors
|
|
558
|
+
if (error.code === "TIMEOUT" || error.code === "NETWORK_ERROR") {
|
|
559
|
+
console.log("Network issue. Some platforms may not have been checked.");
|
|
560
|
+
return { error: "Partial check - network issues", platforms: [] };
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
throw error;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Usage
|
|
568
|
+
const socialReport = await robustSocialCheck("myproject");
|
|
569
|
+
console.log("Secure these now:", socialReport.definitelyAvailable);
|
|
570
|
+
console.log("Verify manually:", socialReport.checkManually);
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
**Confidence Levels Explained:**
|
|
574
|
+
|
|
575
|
+
| Platform | Confidence | Detection Method |
|
|
576
|
+
|----------|------------|------------------|
|
|
577
|
+
| GitHub | High | Public API check |
|
|
578
|
+
| Twitter/X | High | oembed API (v1.2.1+) |
|
|
579
|
+
| npm | High | Registry API |
|
|
580
|
+
| PyPI | High | Package API |
|
|
581
|
+
| Reddit | High | Profile check |
|
|
582
|
+
| YouTube | Medium | Channel page check |
|
|
583
|
+
| ProductHunt | Medium | Profile page check |
|
|
584
|
+
| Instagram | Low | Blocked by platform |
|
|
585
|
+
| LinkedIn | Low | Blocked by platform |
|
|
586
|
+
| TikTok | Low | Blocked by platform |
|
|
587
|
+
|
|
322
588
|
## Configuration
|
|
323
589
|
|
|
590
|
+
### API Keys Setup and Benefits
|
|
591
|
+
|
|
592
|
+
Domain Search MCP works without API keys using RDAP/WHOIS fallbacks, but configuring registrar API keys provides significant benefits:
|
|
593
|
+
|
|
594
|
+
#### Performance Comparison: API Keys vs Fallbacks
|
|
595
|
+
|
|
596
|
+
| Metric | Without API Keys | With Porkbun API | With Namecheap API |
|
|
597
|
+
|--------|-----------------|------------------|-------------------|
|
|
598
|
+
| **Response Time** | 2-5 seconds | 100-200ms | 150-300ms |
|
|
599
|
+
| **Rate Limit** | 10-50 req/min | 1000+ req/min | 500+ req/min |
|
|
600
|
+
| **Pricing Data** | Not available | Full pricing | Full pricing |
|
|
601
|
+
| **Bulk Operations** | ~50 domains max | 100 domains | 100 domains |
|
|
602
|
+
| **Reliability** | Varies by TLD | 99.9% uptime | 99.9% uptime |
|
|
603
|
+
| **WHOIS Privacy Info** | No | Yes | Yes |
|
|
604
|
+
|
|
605
|
+
#### Configuring Porkbun API (Recommended)
|
|
606
|
+
|
|
607
|
+
```typescript
|
|
608
|
+
// Step 1: Get free API keys from https://porkbun.com/account/api
|
|
609
|
+
|
|
610
|
+
// Step 2: Add to your .env file
|
|
611
|
+
PORKBUN_API_KEY=pk1_abc123...
|
|
612
|
+
PORKBUN_SECRET_KEY=sk1_xyz789...
|
|
613
|
+
|
|
614
|
+
// Step 3: The server automatically detects and uses these keys
|
|
615
|
+
// No code changes needed - just set environment variables
|
|
616
|
+
|
|
617
|
+
// Verification: Check if API keys are working
|
|
618
|
+
const result = await searchDomain({
|
|
619
|
+
domain_name: "example",
|
|
620
|
+
tlds: ["com"]
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
// With API keys, you'll see:
|
|
624
|
+
// - source: "porkbun_api" (not "rdap" or "whois")
|
|
625
|
+
// - price_first_year: 8.95 (actual pricing)
|
|
626
|
+
// - privacy_included: true (WHOIS privacy info)
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
#### Configuring Namecheap API
|
|
630
|
+
|
|
631
|
+
```typescript
|
|
632
|
+
// Step 1: Enable API access at https://ap.www.namecheap.com/settings/tools/apiaccess
|
|
633
|
+
// Step 2: Whitelist your IP address in Namecheap dashboard
|
|
634
|
+
|
|
635
|
+
// Step 3: Add to your .env file
|
|
636
|
+
NAMECHEAP_API_KEY=your_api_key
|
|
637
|
+
NAMECHEAP_API_USER=your_username
|
|
638
|
+
NAMECHEAP_CLIENT_IP=your_whitelisted_ip // Optional, auto-detected
|
|
639
|
+
|
|
640
|
+
// The server uses Namecheap as secondary source after Porkbun
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
#### Registrar Selection Strategy
|
|
644
|
+
|
|
645
|
+
The server automatically selects the best available source:
|
|
646
|
+
|
|
647
|
+
```typescript
|
|
648
|
+
// Priority order (highest to lowest):
|
|
649
|
+
// 1. Porkbun API (if configured) - fastest, most reliable
|
|
650
|
+
// 2. Namecheap API (if configured) - good alternative
|
|
651
|
+
// 3. GoDaddy MCP (if available) - no API key needed, has pricing
|
|
652
|
+
// 4. RDAP (always available) - fast but no pricing
|
|
653
|
+
// 5. WHOIS (fallback) - slowest, rate-limited
|
|
654
|
+
|
|
655
|
+
// Example: How source selection works
|
|
656
|
+
const result = await searchDomain({ domain_name: "startup", tlds: ["com"] });
|
|
657
|
+
|
|
658
|
+
// Result shows which source was used:
|
|
659
|
+
console.log(result.results[0].source);
|
|
660
|
+
// "porkbun_api" - if Porkbun keys configured
|
|
661
|
+
// "namecheap_api" - if only Namecheap configured
|
|
662
|
+
// "godaddy_mcp" - if GoDaddy MCP available
|
|
663
|
+
// "rdap" - if no API keys, RDAP successful
|
|
664
|
+
// "whois" - fallback when RDAP fails
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
#### Handling Missing API Credentials
|
|
668
|
+
|
|
669
|
+
```typescript
|
|
670
|
+
// The server gracefully handles missing credentials
|
|
671
|
+
try {
|
|
672
|
+
const result = await searchDomain({
|
|
673
|
+
domain_name: "example",
|
|
674
|
+
tlds: ["com"]
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
// Check which source was used
|
|
678
|
+
if (result.results[0].source === "rdap" || result.results[0].source === "whois") {
|
|
679
|
+
console.log("Note: Using fallback. Configure API keys for pricing data.");
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Check if pricing is available
|
|
683
|
+
if (result.results[0].price_first_year === null) {
|
|
684
|
+
console.log("Pricing not available. Add Porkbun API key for pricing.");
|
|
685
|
+
}
|
|
686
|
+
} catch (error) {
|
|
687
|
+
if (error.code === "AUTH_ERROR") {
|
|
688
|
+
console.log("API key invalid. Check your credentials.");
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
#### Complete Configuration Example
|
|
694
|
+
|
|
695
|
+
```typescript
|
|
696
|
+
// Full .env configuration for optimal performance
|
|
697
|
+
// ================================================
|
|
698
|
+
|
|
699
|
+
// Required for pricing data (choose at least one)
|
|
700
|
+
PORKBUN_API_KEY=pk1_your_key
|
|
701
|
+
PORKBUN_SECRET_KEY=sk1_your_secret
|
|
702
|
+
|
|
703
|
+
// Optional: Additional registrar for price comparison
|
|
704
|
+
NAMECHEAP_API_KEY=your_namecheap_key
|
|
705
|
+
NAMECHEAP_API_USER=your_username
|
|
706
|
+
|
|
707
|
+
// Optional: Performance tuning
|
|
708
|
+
CACHE_TTL_AVAILABILITY=300 // Cache results for 5 minutes
|
|
709
|
+
CACHE_TTL_PRICING=3600 // Cache pricing for 1 hour
|
|
710
|
+
RATE_LIMIT_PER_MINUTE=60 // Max requests per minute
|
|
711
|
+
|
|
712
|
+
// Optional: Logging
|
|
713
|
+
LOG_LEVEL=info // debug | info | warn | error
|
|
714
|
+
```
|
|
715
|
+
|
|
324
716
|
### Environment Variables
|
|
325
717
|
|
|
326
718
|
Create a `.env` file based on `.env.example`:
|
|
@@ -470,6 +862,81 @@ When operating without API keys, Domain Search MCP uses WHOIS and RDAP protocols
|
|
|
470
862
|
| **RDAP** | 10-50 req/min per TLD | Returns 429 or connection refused |
|
|
471
863
|
| **WHOIS** | 5-20 req/min per server | Connection timeout or ban |
|
|
472
864
|
|
|
865
|
+
#### WHOIS/RDAP Protocol Details
|
|
866
|
+
|
|
867
|
+
**RDAP (Registration Data Access Protocol):**
|
|
868
|
+
- Modern replacement for WHOIS with JSON responses
|
|
869
|
+
- Each TLD has its own RDAP server (e.g., rdap.verisign.com for .com)
|
|
870
|
+
- Rate limits are per-TLD, not global
|
|
871
|
+
- Supports HTTPS with structured responses
|
|
872
|
+
|
|
873
|
+
```typescript
|
|
874
|
+
// RDAP servers by TLD
|
|
875
|
+
const rdapServers = {
|
|
876
|
+
"com": "https://rdap.verisign.com/com/v1/domain/",
|
|
877
|
+
"net": "https://rdap.verisign.com/net/v1/domain/",
|
|
878
|
+
"io": "https://rdap.nic.io/domain/",
|
|
879
|
+
"dev": "https://rdap.nic.google/domain/",
|
|
880
|
+
"app": "https://rdap.nic.google/domain/"
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
// RDAP response indicates availability
|
|
884
|
+
// - 200 OK: Domain is registered (taken)
|
|
885
|
+
// - 404 Not Found: Domain is available
|
|
886
|
+
// - 429 Too Many Requests: Rate limited
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
**WHOIS Protocol:**
|
|
890
|
+
- Legacy text-based protocol on port 43
|
|
891
|
+
- Different servers have different response formats
|
|
892
|
+
- Some servers ban IPs after repeated queries
|
|
893
|
+
- No standard rate limit headers
|
|
894
|
+
|
|
895
|
+
```typescript
|
|
896
|
+
// WHOIS rate limit strategies
|
|
897
|
+
const whoisStrategies = {
|
|
898
|
+
// Spread requests across time
|
|
899
|
+
delayBetweenRequests: 2000, // 2 seconds minimum
|
|
900
|
+
|
|
901
|
+
// Use different query patterns to avoid detection
|
|
902
|
+
randomizeQueryTiming: true,
|
|
903
|
+
|
|
904
|
+
// Fallback to RDAP when WHOIS fails
|
|
905
|
+
rdapFallback: true,
|
|
906
|
+
|
|
907
|
+
// Cache responses aggressively
|
|
908
|
+
cacheTTL: 300 // 5 minutes
|
|
909
|
+
};
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
#### Monitoring WHOIS/RDAP Health
|
|
913
|
+
|
|
914
|
+
```typescript
|
|
915
|
+
// Monitor rate limit status across sources
|
|
916
|
+
async function checkSourceHealth() {
|
|
917
|
+
const sources = ["rdap", "whois", "porkbun", "namecheap"];
|
|
918
|
+
const health = {};
|
|
919
|
+
|
|
920
|
+
for (const source of sources) {
|
|
921
|
+
try {
|
|
922
|
+
const start = Date.now();
|
|
923
|
+
await searchDomain({ domain_name: "test" + Date.now(), tlds: ["com"] });
|
|
924
|
+
health[source] = {
|
|
925
|
+
status: "healthy",
|
|
926
|
+
latency: Date.now() - start
|
|
927
|
+
};
|
|
928
|
+
} catch (error) {
|
|
929
|
+
health[source] = {
|
|
930
|
+
status: error.code === "RATE_LIMIT" ? "rate_limited" : "error",
|
|
931
|
+
retryAfter: error.retryAfter || null
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
return health;
|
|
937
|
+
}
|
|
938
|
+
```
|
|
939
|
+
|
|
473
940
|
### Automatic Rate Limit Handling
|
|
474
941
|
|
|
475
942
|
The server implements intelligent rate limit handling:
|
|
@@ -604,7 +1071,133 @@ try {
|
|
|
604
1071
|
|
|
605
1072
|
## Workflow Examples
|
|
606
1073
|
|
|
607
|
-
### Workflow 1: Domain
|
|
1074
|
+
### Workflow 1: Complete Domain Acquisition with Partial Availability Handling
|
|
1075
|
+
|
|
1076
|
+
A comprehensive workflow that handles scenarios where domains are available on some registrars but not others, or when some checks succeed while others fail:
|
|
1077
|
+
|
|
1078
|
+
```typescript
|
|
1079
|
+
async function completeDomainAcquisition(brandName: string) {
|
|
1080
|
+
// Step 1: Run parallel checks across domains and social media
|
|
1081
|
+
const [domainResults, socialResults] = await Promise.all([
|
|
1082
|
+
searchDomain({
|
|
1083
|
+
domain_name: brandName,
|
|
1084
|
+
tlds: ["com", "io", "dev", "app", "co"]
|
|
1085
|
+
}),
|
|
1086
|
+
checkSocials({
|
|
1087
|
+
name: brandName,
|
|
1088
|
+
platforms: ["github", "twitter", "instagram", "npm", "linkedin"]
|
|
1089
|
+
})
|
|
1090
|
+
]);
|
|
1091
|
+
|
|
1092
|
+
// Step 2: Handle partial availability - some TLDs available, some taken
|
|
1093
|
+
const available = domainResults.results.filter(r => r.available && !r.error);
|
|
1094
|
+
const taken = domainResults.results.filter(r => !r.available && !r.error);
|
|
1095
|
+
const failed = domainResults.results.filter(r => r.error);
|
|
1096
|
+
|
|
1097
|
+
// Step 3: Retry failed checks with exponential backoff
|
|
1098
|
+
const retriedResults = [];
|
|
1099
|
+
for (const failedDomain of failed) {
|
|
1100
|
+
const tld = failedDomain.domain.split('.').pop();
|
|
1101
|
+
let delay = 1000;
|
|
1102
|
+
|
|
1103
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
1104
|
+
await new Promise(r => setTimeout(r, delay));
|
|
1105
|
+
try {
|
|
1106
|
+
const retry = await searchDomain({
|
|
1107
|
+
domain_name: brandName,
|
|
1108
|
+
tlds: [tld]
|
|
1109
|
+
});
|
|
1110
|
+
if (!retry.results[0].error) {
|
|
1111
|
+
retriedResults.push(retry.results[0]);
|
|
1112
|
+
break;
|
|
1113
|
+
}
|
|
1114
|
+
} catch (e) {
|
|
1115
|
+
delay *= 2; // Exponential backoff
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// Step 4: If preferred .com is taken, generate alternatives
|
|
1121
|
+
let suggestions = [];
|
|
1122
|
+
const comDomain = [...available, ...retriedResults].find(d => d.domain.endsWith('.com'));
|
|
1123
|
+
if (!comDomain) {
|
|
1124
|
+
const suggestResult = await suggestDomains({
|
|
1125
|
+
base_name: brandName,
|
|
1126
|
+
tld: "com",
|
|
1127
|
+
max_suggestions: 10,
|
|
1128
|
+
variants: ["prefixes", "suffixes", "hyphen"]
|
|
1129
|
+
});
|
|
1130
|
+
suggestions = suggestResult.suggestions;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// Step 5: Compare pricing for available domains
|
|
1134
|
+
const priceComparisons = await Promise.all(
|
|
1135
|
+
available.slice(0, 3).map(d => {
|
|
1136
|
+
const [name, tld] = d.domain.split('.');
|
|
1137
|
+
return compareRegistrars({ domain: name, tld }).catch(() => null);
|
|
1138
|
+
})
|
|
1139
|
+
);
|
|
1140
|
+
|
|
1141
|
+
// Step 6: Compile comprehensive report
|
|
1142
|
+
return {
|
|
1143
|
+
brandName,
|
|
1144
|
+
summary: {
|
|
1145
|
+
domainsChecked: domainResults.results.length,
|
|
1146
|
+
available: available.length + retriedResults.length,
|
|
1147
|
+
taken: taken.length,
|
|
1148
|
+
failedChecks: failed.length - retriedResults.length,
|
|
1149
|
+
socialsAvailable: socialResults.results.filter(r => r.available).length
|
|
1150
|
+
},
|
|
1151
|
+
domains: {
|
|
1152
|
+
available: [...available, ...retriedResults].map(d => ({
|
|
1153
|
+
domain: d.domain,
|
|
1154
|
+
price: d.price_first_year,
|
|
1155
|
+
registrar: d.registrar,
|
|
1156
|
+
source: d.source
|
|
1157
|
+
})),
|
|
1158
|
+
taken: taken.map(d => d.domain),
|
|
1159
|
+
alternatives: suggestions.map(s => s.domain)
|
|
1160
|
+
},
|
|
1161
|
+
socials: {
|
|
1162
|
+
available: socialResults.results
|
|
1163
|
+
.filter(r => r.available && r.confidence !== "low")
|
|
1164
|
+
.map(r => r.platform),
|
|
1165
|
+
taken: socialResults.results
|
|
1166
|
+
.filter(r => !r.available)
|
|
1167
|
+
.map(r => r.platform),
|
|
1168
|
+
needsManualCheck: socialResults.results
|
|
1169
|
+
.filter(r => r.confidence === "low")
|
|
1170
|
+
.map(r => r.platform)
|
|
1171
|
+
},
|
|
1172
|
+
pricing: priceComparisons.filter(Boolean).map(p => ({
|
|
1173
|
+
domain: p.domain,
|
|
1174
|
+
bestPrice: p.best_first_year,
|
|
1175
|
+
recommendation: p.recommendation
|
|
1176
|
+
})),
|
|
1177
|
+
nextSteps: generateNextSteps(available, socialResults, suggestions)
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
function generateNextSteps(available, socialResults, suggestions) {
|
|
1182
|
+
const steps = [];
|
|
1183
|
+
if (available.length > 0) {
|
|
1184
|
+
steps.push(`Register ${available[0].domain} at ${available[0].registrar}`);
|
|
1185
|
+
} else if (suggestions.length > 0) {
|
|
1186
|
+
steps.push(`Consider alternative: ${suggestions[0].domain}`);
|
|
1187
|
+
}
|
|
1188
|
+
const availableSocials = socialResults.results.filter(r => r.available);
|
|
1189
|
+
if (availableSocials.length > 0) {
|
|
1190
|
+
steps.push(`Secure username on: ${availableSocials.map(r => r.platform).join(', ')}`);
|
|
1191
|
+
}
|
|
1192
|
+
return steps;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// Usage
|
|
1196
|
+
const acquisition = await completeDomainAcquisition("techstartup");
|
|
1197
|
+
// Returns comprehensive report with partial availability handled
|
|
1198
|
+
```
|
|
1199
|
+
|
|
1200
|
+
### Workflow 2: Domain Suggestion When Preferred Name is Taken
|
|
608
1201
|
|
|
609
1202
|
When a user's preferred domain is unavailable, use `suggest_domains` to find alternatives:
|
|
610
1203
|
|
|
@@ -789,7 +1382,349 @@ async function robustDomainSearch(domainName: string, tlds: string[]) {
|
|
|
789
1382
|
}
|
|
790
1383
|
```
|
|
791
1384
|
|
|
792
|
-
### Workflow 5:
|
|
1385
|
+
### Workflow 5: Bulk Validation with Compare and Suggest (100 Domains)
|
|
1386
|
+
|
|
1387
|
+
Complete workflow that validates 100 domains using bulk_search, finds best pricing with compare_registrars for available ones, and generates alternatives for unavailable ones using suggest_domains:
|
|
1388
|
+
|
|
1389
|
+
```typescript
|
|
1390
|
+
async function bulkDomainValidationPipeline(domainNames: string[], tld: string = "com") {
|
|
1391
|
+
// Step 1: Bulk search all domains (handles up to 100)
|
|
1392
|
+
console.log(`Checking ${domainNames.length} domains...`);
|
|
1393
|
+
|
|
1394
|
+
const bulkResults = await bulkSearch({
|
|
1395
|
+
domains: domainNames,
|
|
1396
|
+
tld: tld,
|
|
1397
|
+
concurrency: 10 // Process 10 at a time for optimal speed
|
|
1398
|
+
});
|
|
1399
|
+
|
|
1400
|
+
// Step 2: Separate available and unavailable domains
|
|
1401
|
+
const available = bulkResults.results.filter(r => r.available && !r.error);
|
|
1402
|
+
const unavailable = bulkResults.results.filter(r => !r.available && !r.error);
|
|
1403
|
+
const errors = bulkResults.results.filter(r => r.error);
|
|
1404
|
+
|
|
1405
|
+
console.log(`Results: ${available.length} available, ${unavailable.length} taken, ${errors.length} errors`);
|
|
1406
|
+
|
|
1407
|
+
// Step 3: Compare registrar pricing for available domains (top 10)
|
|
1408
|
+
const topAvailable = available
|
|
1409
|
+
.sort((a, b) => (a.price_first_year || 999) - (b.price_first_year || 999))
|
|
1410
|
+
.slice(0, 10);
|
|
1411
|
+
|
|
1412
|
+
const priceComparisons = await Promise.all(
|
|
1413
|
+
topAvailable.map(async (domain) => {
|
|
1414
|
+
try {
|
|
1415
|
+
const name = domain.domain.replace(`.${tld}`, '');
|
|
1416
|
+
const comparison = await compareRegistrars({
|
|
1417
|
+
domain: name,
|
|
1418
|
+
tld: tld,
|
|
1419
|
+
registrars: ["porkbun", "namecheap"]
|
|
1420
|
+
});
|
|
1421
|
+
return { domain: domain.domain, comparison };
|
|
1422
|
+
} catch (error) {
|
|
1423
|
+
return { domain: domain.domain, comparison: null, error: error.message };
|
|
1424
|
+
}
|
|
1425
|
+
})
|
|
1426
|
+
);
|
|
1427
|
+
|
|
1428
|
+
// Step 4: Generate alternatives for unavailable domains (top 5)
|
|
1429
|
+
const topUnavailable = unavailable.slice(0, 5);
|
|
1430
|
+
const alternatives = await Promise.all(
|
|
1431
|
+
topUnavailable.map(async (domain) => {
|
|
1432
|
+
try {
|
|
1433
|
+
const name = domain.domain.replace(`.${tld}`, '');
|
|
1434
|
+
const suggestions = await suggestDomains({
|
|
1435
|
+
base_name: name,
|
|
1436
|
+
tld: tld,
|
|
1437
|
+
max_suggestions: 5,
|
|
1438
|
+
variants: ["prefixes", "suffixes", "hyphen"]
|
|
1439
|
+
});
|
|
1440
|
+
return {
|
|
1441
|
+
originalDomain: domain.domain,
|
|
1442
|
+
alternatives: suggestions.suggestions
|
|
1443
|
+
};
|
|
1444
|
+
} catch (error) {
|
|
1445
|
+
return { originalDomain: domain.domain, alternatives: [], error: error.message };
|
|
1446
|
+
}
|
|
1447
|
+
})
|
|
1448
|
+
);
|
|
1449
|
+
|
|
1450
|
+
// Step 5: Compile comprehensive report
|
|
1451
|
+
return {
|
|
1452
|
+
summary: {
|
|
1453
|
+
totalSearched: domainNames.length,
|
|
1454
|
+
available: available.length,
|
|
1455
|
+
unavailable: unavailable.length,
|
|
1456
|
+
errors: errors.length
|
|
1457
|
+
},
|
|
1458
|
+
availableDomains: available.map(d => ({
|
|
1459
|
+
domain: d.domain,
|
|
1460
|
+
price: d.price_first_year,
|
|
1461
|
+
registrar: d.registrar
|
|
1462
|
+
})),
|
|
1463
|
+
bestDeals: priceComparisons
|
|
1464
|
+
.filter(p => p.comparison)
|
|
1465
|
+
.map(p => ({
|
|
1466
|
+
domain: p.domain,
|
|
1467
|
+
bestFirstYear: p.comparison.best_first_year,
|
|
1468
|
+
bestRenewal: p.comparison.best_renewal,
|
|
1469
|
+
recommendation: p.comparison.recommendation
|
|
1470
|
+
})),
|
|
1471
|
+
alternativesForTaken: alternatives.filter(a => a.alternatives.length > 0),
|
|
1472
|
+
failedChecks: errors.map(e => ({ domain: e.domain, error: e.error }))
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
// Usage: Validate 50 startup name ideas
|
|
1477
|
+
const startupNames = [
|
|
1478
|
+
"techflow", "datawise", "cloudpeak", "aiforge", "bytecraft",
|
|
1479
|
+
"codestream", "devpulse", "syncwave", "logiclab", "pixelcraft",
|
|
1480
|
+
// ... add more names up to 100
|
|
1481
|
+
];
|
|
1482
|
+
|
|
1483
|
+
const report = await bulkDomainValidationPipeline(startupNames, "io");
|
|
1484
|
+
console.log(`Found ${report.summary.available} available domains`);
|
|
1485
|
+
console.log(`Best deal: ${report.bestDeals[0]?.domain} at $${report.bestDeals[0]?.bestFirstYear?.price}`);
|
|
1486
|
+
```
|
|
1487
|
+
|
|
1488
|
+
### Workflow 6: Domain Research with TLD Info (search + tld_info + suggest)
|
|
1489
|
+
|
|
1490
|
+
A research-focused workflow using search_domain, tld_info, and suggest_domains to provide comprehensive domain options analysis:
|
|
1491
|
+
|
|
1492
|
+
```typescript
|
|
1493
|
+
async function domainResearchWithTldAnalysis(baseName: string, preferredTlds: string[] = ["com", "io", "dev"]) {
|
|
1494
|
+
// Step 1: Get detailed information about each TLD
|
|
1495
|
+
const tldDetails = await Promise.all(
|
|
1496
|
+
preferredTlds.map(async (tld) => {
|
|
1497
|
+
const info = await tldInfo({ tld, detailed: true });
|
|
1498
|
+
return { tld, ...info };
|
|
1499
|
+
})
|
|
1500
|
+
);
|
|
1501
|
+
|
|
1502
|
+
// Step 2: Search domain availability across all TLDs
|
|
1503
|
+
const searchResults = await searchDomain({
|
|
1504
|
+
domain_name: baseName,
|
|
1505
|
+
tlds: preferredTlds
|
|
1506
|
+
});
|
|
1507
|
+
|
|
1508
|
+
// Step 3: For each unavailable TLD, generate suggestions
|
|
1509
|
+
const unavailableTlds = searchResults.results
|
|
1510
|
+
.filter(r => !r.available)
|
|
1511
|
+
.map(r => r.domain.split('.').pop());
|
|
1512
|
+
|
|
1513
|
+
const suggestions = {};
|
|
1514
|
+
for (const tld of unavailableTlds) {
|
|
1515
|
+
try {
|
|
1516
|
+
const result = await suggestDomains({
|
|
1517
|
+
base_name: baseName,
|
|
1518
|
+
tld: tld,
|
|
1519
|
+
max_suggestions: 5,
|
|
1520
|
+
variants: ["prefixes", "suffixes"]
|
|
1521
|
+
});
|
|
1522
|
+
suggestions[tld] = result.suggestions;
|
|
1523
|
+
} catch (error) {
|
|
1524
|
+
suggestions[tld] = [];
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
// Step 4: Compile research report with TLD context
|
|
1529
|
+
return {
|
|
1530
|
+
baseName,
|
|
1531
|
+
tldAnalysis: tldDetails.map(tld => ({
|
|
1532
|
+
tld: tld.tld,
|
|
1533
|
+
description: tld.description,
|
|
1534
|
+
typicalUse: tld.typical_use,
|
|
1535
|
+
priceRange: tld.price_range,
|
|
1536
|
+
restrictions: tld.restrictions || [],
|
|
1537
|
+
popularity: tld.popularity,
|
|
1538
|
+
recommendation: tld.recommendation
|
|
1539
|
+
})),
|
|
1540
|
+
availability: searchResults.results.map(r => ({
|
|
1541
|
+
domain: r.domain,
|
|
1542
|
+
available: r.available,
|
|
1543
|
+
price: r.price_first_year,
|
|
1544
|
+
tldInfo: tldDetails.find(t => r.domain.endsWith(`.${t.tld}`))
|
|
1545
|
+
})),
|
|
1546
|
+
suggestions: Object.entries(suggestions).map(([tld, suggs]) => ({
|
|
1547
|
+
forTld: tld,
|
|
1548
|
+
alternatives: suggs.map(s => s.domain)
|
|
1549
|
+
})),
|
|
1550
|
+
recommendation: generateTldRecommendation(searchResults.results, tldDetails)
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
function generateTldRecommendation(results, tldDetails) {
|
|
1555
|
+
const available = results.filter(r => r.available);
|
|
1556
|
+
if (available.length === 0) {
|
|
1557
|
+
return "No preferred TLDs available. Consider the suggested alternatives.";
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
const cheapest = available.sort((a, b) => a.price_first_year - b.price_first_year)[0];
|
|
1561
|
+
const tldInfo = tldDetails.find(t => cheapest.domain.endsWith(`.${t.tld}`));
|
|
1562
|
+
|
|
1563
|
+
return `Recommended: ${cheapest.domain} ($${cheapest.price_first_year}/yr) - ${tldInfo?.recommendation || 'Good choice'}`;
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
// Usage
|
|
1567
|
+
const research = await domainResearchWithTldAnalysis("myproject", ["com", "io", "dev", "app"]);
|
|
1568
|
+
console.log(research.recommendation);
|
|
1569
|
+
// Output: "Recommended: myproject.com ($8.95/yr) - Classic, universal choice"
|
|
1570
|
+
```
|
|
1571
|
+
|
|
1572
|
+
### Workflow 7: Validate 50 Domains with Result Aggregation
|
|
1573
|
+
|
|
1574
|
+
End-to-end workflow for validating exactly 50 domain names with comprehensive result handling:
|
|
1575
|
+
|
|
1576
|
+
```typescript
|
|
1577
|
+
async function validate50Domains(domainNames: string[], tld: string = "com") {
|
|
1578
|
+
// Ensure we have exactly 50 domains
|
|
1579
|
+
const domains = domainNames.slice(0, 50);
|
|
1580
|
+
if (domains.length < 50) {
|
|
1581
|
+
console.log(`Note: Only ${domains.length} domains provided`);
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
console.log(`Starting validation of ${domains.length} domains...`);
|
|
1585
|
+
const startTime = Date.now();
|
|
1586
|
+
|
|
1587
|
+
// Step 1: Bulk search with optimized concurrency
|
|
1588
|
+
const bulkResult = await bulkSearch({
|
|
1589
|
+
domains: domains,
|
|
1590
|
+
tld: tld,
|
|
1591
|
+
concurrency: 10 // Optimal for rate limit avoidance
|
|
1592
|
+
});
|
|
1593
|
+
|
|
1594
|
+
// Step 2: Aggregate results by status
|
|
1595
|
+
const aggregation = {
|
|
1596
|
+
available: [],
|
|
1597
|
+
taken: [],
|
|
1598
|
+
errors: [],
|
|
1599
|
+
bySource: {},
|
|
1600
|
+
byPrice: { under10: [], under25: [], under50: [], premium: [] }
|
|
1601
|
+
};
|
|
1602
|
+
|
|
1603
|
+
for (const result of bulkResult.results) {
|
|
1604
|
+
// Categorize by availability
|
|
1605
|
+
if (result.error) {
|
|
1606
|
+
aggregation.errors.push({
|
|
1607
|
+
domain: result.domain,
|
|
1608
|
+
error: result.error,
|
|
1609
|
+
retryable: result.retryable || false
|
|
1610
|
+
});
|
|
1611
|
+
} else if (result.available) {
|
|
1612
|
+
aggregation.available.push(result);
|
|
1613
|
+
|
|
1614
|
+
// Categorize by price
|
|
1615
|
+
const price = result.price_first_year;
|
|
1616
|
+
if (price && price < 10) aggregation.byPrice.under10.push(result);
|
|
1617
|
+
else if (price && price < 25) aggregation.byPrice.under25.push(result);
|
|
1618
|
+
else if (price && price < 50) aggregation.byPrice.under50.push(result);
|
|
1619
|
+
else if (price) aggregation.byPrice.premium.push(result);
|
|
1620
|
+
} else {
|
|
1621
|
+
aggregation.taken.push(result);
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
// Track by source
|
|
1625
|
+
const source = result.source || "unknown";
|
|
1626
|
+
if (!aggregation.bySource[source]) {
|
|
1627
|
+
aggregation.bySource[source] = { count: 0, avgLatency: 0 };
|
|
1628
|
+
}
|
|
1629
|
+
aggregation.bySource[source].count++;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
// Step 3: Retry failed domains with exponential backoff
|
|
1633
|
+
if (aggregation.errors.length > 0) {
|
|
1634
|
+
console.log(`Retrying ${aggregation.errors.length} failed domains...`);
|
|
1635
|
+
|
|
1636
|
+
const retryResults = [];
|
|
1637
|
+
for (const failed of aggregation.errors.filter(e => e.retryable)) {
|
|
1638
|
+
const domainName = failed.domain.replace(`.${tld}`, '');
|
|
1639
|
+
let delay = 2000;
|
|
1640
|
+
|
|
1641
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
1642
|
+
await new Promise(r => setTimeout(r, delay));
|
|
1643
|
+
try {
|
|
1644
|
+
const retry = await searchDomain({
|
|
1645
|
+
domain_name: domainName,
|
|
1646
|
+
tlds: [tld]
|
|
1647
|
+
});
|
|
1648
|
+
if (!retry.results[0].error) {
|
|
1649
|
+
retryResults.push(retry.results[0]);
|
|
1650
|
+
// Remove from errors, add to appropriate category
|
|
1651
|
+
const idx = aggregation.errors.findIndex(e => e.domain === failed.domain);
|
|
1652
|
+
if (idx > -1) aggregation.errors.splice(idx, 1);
|
|
1653
|
+
if (retry.results[0].available) {
|
|
1654
|
+
aggregation.available.push(retry.results[0]);
|
|
1655
|
+
} else {
|
|
1656
|
+
aggregation.taken.push(retry.results[0]);
|
|
1657
|
+
}
|
|
1658
|
+
break;
|
|
1659
|
+
}
|
|
1660
|
+
} catch (e) {
|
|
1661
|
+
delay *= 2;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
// Step 4: Generate summary report
|
|
1668
|
+
const duration = Date.now() - startTime;
|
|
1669
|
+
const report = {
|
|
1670
|
+
summary: {
|
|
1671
|
+
totalDomains: domains.length,
|
|
1672
|
+
available: aggregation.available.length,
|
|
1673
|
+
taken: aggregation.taken.length,
|
|
1674
|
+
errors: aggregation.errors.length,
|
|
1675
|
+
duration: `${(duration / 1000).toFixed(1)}s`,
|
|
1676
|
+
avgTimePerDomain: `${(duration / domains.length).toFixed(0)}ms`
|
|
1677
|
+
},
|
|
1678
|
+
availableDomains: aggregation.available
|
|
1679
|
+
.sort((a, b) => (a.price_first_year || 999) - (b.price_first_year || 999))
|
|
1680
|
+
.map(d => ({
|
|
1681
|
+
domain: d.domain,
|
|
1682
|
+
price: d.price_first_year,
|
|
1683
|
+
registrar: d.registrar,
|
|
1684
|
+
source: d.source
|
|
1685
|
+
})),
|
|
1686
|
+
priceBreakdown: {
|
|
1687
|
+
budget: aggregation.byPrice.under10.map(d => d.domain),
|
|
1688
|
+
moderate: aggregation.byPrice.under25.map(d => d.domain),
|
|
1689
|
+
standard: aggregation.byPrice.under50.map(d => d.domain),
|
|
1690
|
+
premium: aggregation.byPrice.premium.map(d => d.domain)
|
|
1691
|
+
},
|
|
1692
|
+
sourceStats: aggregation.bySource,
|
|
1693
|
+
takenDomains: aggregation.taken.map(d => d.domain),
|
|
1694
|
+
failedChecks: aggregation.errors
|
|
1695
|
+
};
|
|
1696
|
+
|
|
1697
|
+
return report;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
// Usage: Validate 50 startup name ideas
|
|
1701
|
+
const startupIdeas = [
|
|
1702
|
+
"codeforge", "devpulse", "techwave", "dataflow", "cloudpeak",
|
|
1703
|
+
"aibridge", "synclab", "bytecraft", "logicbox", "pixelsmith",
|
|
1704
|
+
"neuralnet", "quantumbit", "cyberlink", "smartnode", "deepcore",
|
|
1705
|
+
"faststack", "cleancode", "agiledev", "swiftbyte", "codestream",
|
|
1706
|
+
"datawise", "techspark", "cloudsync", "aistack", "devforge",
|
|
1707
|
+
"bytewise", "logicflow", "pixelwave", "neuralhub", "quantumai",
|
|
1708
|
+
"cyberpulse", "smartflow", "deeptech", "fastcode", "cleanstack",
|
|
1709
|
+
"agilebit", "swiftdev", "streamcode", "wisebyte", "sparktech",
|
|
1710
|
+
"syncwave", "forgeai", "pulsedev", "wavetech", "flowdata",
|
|
1711
|
+
"peakcloud", "bridgeai", "labsync", "craftbyte", "boxlogic"
|
|
1712
|
+
];
|
|
1713
|
+
|
|
1714
|
+
const report = await validate50Domains(startupIdeas, "io");
|
|
1715
|
+
|
|
1716
|
+
console.log(`\n=== 50 DOMAIN VALIDATION REPORT ===`);
|
|
1717
|
+
console.log(`Completed in ${report.summary.duration}`);
|
|
1718
|
+
console.log(`Available: ${report.summary.available} | Taken: ${report.summary.taken} | Errors: ${report.summary.errors}`);
|
|
1719
|
+
console.log(`\nBest deals (under $10):`);
|
|
1720
|
+
report.priceBreakdown.budget.forEach(d => console.log(` ${d}`));
|
|
1721
|
+
console.log(`\nTop 5 available domains:`);
|
|
1722
|
+
report.availableDomains.slice(0, 5).forEach(d =>
|
|
1723
|
+
console.log(` ${d.domain} - $${d.price}/yr (${d.registrar})`)
|
|
1724
|
+
);
|
|
1725
|
+
```
|
|
1726
|
+
|
|
1727
|
+
### Workflow 8: Domain Research Pipeline
|
|
793
1728
|
|
|
794
1729
|
Comprehensive domain research combining multiple tools:
|
|
795
1730
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "domain-search-mcp",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Fast domain availability aggregator MCP server. Check availability across Porkbun, Namecheap, RDAP, and WHOIS. Compare pricing. Get suggestions.",
|
|
5
5
|
"main": "dist/server.js",
|
|
6
6
|
"types": "dist/server.d.ts",
|