domain-search-mcp 1.1.5 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +394 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -89,6 +89,7 @@ nano .env
|
|
|
89
89
|
|-----------|----------|---------|-------|
|
|
90
90
|
| **Porkbun** | JSON | Free | Fast, includes WHOIS privacy |
|
|
91
91
|
| **Namecheap** | XML | Free | Requires IP whitelist |
|
|
92
|
+
| **GoDaddy** | MCP | Free | Via GoDaddy MCP server (no API key needed) |
|
|
92
93
|
|
|
93
94
|
### Fallback Protocols
|
|
94
95
|
|
|
@@ -257,6 +258,7 @@ AI-powered domain suggestions using semantic analysis:
|
|
|
257
258
|
```
|
|
258
259
|
|
|
259
260
|
**Features:**
|
|
261
|
+
- **Dual-Source Suggestions**: Combines semantic analysis + GoDaddy AI recommendations
|
|
260
262
|
- Understands natural language queries ("coffee shop in seattle")
|
|
261
263
|
- Auto-detects industry for contextual suggestions
|
|
262
264
|
- Generates portmanteau/blended names
|
|
@@ -404,7 +406,8 @@ domain-search-mcp/
|
|
|
404
406
|
│ ├── registrars/ # Registrar adapters
|
|
405
407
|
│ │ ├── base.ts
|
|
406
408
|
│ │ ├── porkbun.ts
|
|
407
|
-
│ │
|
|
409
|
+
│ │ ├── namecheap.ts
|
|
410
|
+
│ │ └── godaddy-mcp.ts # GoDaddy via MCP server
|
|
408
411
|
│ ├── fallbacks/ # RDAP and WHOIS fallbacks
|
|
409
412
|
│ │ ├── rdap.ts
|
|
410
413
|
│ │ └── whois.ts
|
|
@@ -451,6 +454,396 @@ The server provides user-friendly error messages with suggested actions:
|
|
|
451
454
|
| `NO_SOURCE_AVAILABLE` | All sources failed | Yes |
|
|
452
455
|
| `TIMEOUT` | Request timed out | Yes |
|
|
453
456
|
|
|
457
|
+
## Rate Limiting & Performance Optimization
|
|
458
|
+
|
|
459
|
+
### Understanding WHOIS/RDAP Rate Limits
|
|
460
|
+
|
|
461
|
+
When operating without API keys, Domain Search MCP uses WHOIS and RDAP protocols as fallbacks. These protocols have important rate limiting considerations:
|
|
462
|
+
|
|
463
|
+
| Protocol | Typical Rate Limit | Behavior When Exceeded |
|
|
464
|
+
|----------|-------------------|------------------------|
|
|
465
|
+
| **RDAP** | 10-50 req/min per TLD | Returns 429 or connection refused |
|
|
466
|
+
| **WHOIS** | 5-20 req/min per server | Connection timeout or ban |
|
|
467
|
+
|
|
468
|
+
### Automatic Rate Limit Handling
|
|
469
|
+
|
|
470
|
+
The server implements intelligent rate limit handling:
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
// Built-in protections
|
|
474
|
+
{
|
|
475
|
+
// Automatic exponential backoff
|
|
476
|
+
retryStrategy: {
|
|
477
|
+
initialDelay: 1000, // Start with 1 second
|
|
478
|
+
maxDelay: 30000, // Cap at 30 seconds
|
|
479
|
+
backoffMultiplier: 2, // Double each retry
|
|
480
|
+
maxRetries: 3 // Give up after 3 attempts
|
|
481
|
+
},
|
|
482
|
+
|
|
483
|
+
// Per-source rate limiting
|
|
484
|
+
rateLimits: {
|
|
485
|
+
rdap: { requestsPerMinute: 30, burstLimit: 5 },
|
|
486
|
+
whois: { requestsPerMinute: 10, burstLimit: 3 }
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### Strategies for High-Volume Searches
|
|
492
|
+
|
|
493
|
+
When performing bulk searches without API keys, use these optimization strategies:
|
|
494
|
+
|
|
495
|
+
#### 1. Use Caching Effectively
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
// Results are cached automatically
|
|
499
|
+
// - Availability: 5 minutes (CACHE_TTL_AVAILABILITY)
|
|
500
|
+
// - Pricing: 1 hour (CACHE_TTL_PRICING)
|
|
501
|
+
// - TLD info: 24 hours
|
|
502
|
+
|
|
503
|
+
// Subsequent checks for the same domain are instant
|
|
504
|
+
const first = await searchDomain("example.com"); // API call
|
|
505
|
+
const second = await searchDomain("example.com"); // Cache hit (no API call)
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
#### 2. Batch Domains by TLD
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
// GOOD: Group by TLD to minimize server switches
|
|
512
|
+
const comDomains = ["app1", "app2", "app3"];
|
|
513
|
+
const ioDomains = ["startup1", "startup2"];
|
|
514
|
+
|
|
515
|
+
await bulkSearch({ domains: comDomains, tld: "com" }); // One .com server
|
|
516
|
+
await bulkSearch({ domains: ioDomains, tld: "io" }); // One .io server
|
|
517
|
+
|
|
518
|
+
// BAD: Mixed TLDs cause more server connections
|
|
519
|
+
await Promise.all([
|
|
520
|
+
searchDomain("app1.com"),
|
|
521
|
+
searchDomain("startup1.io"),
|
|
522
|
+
searchDomain("app2.com"), // Back to .com server
|
|
523
|
+
]);
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
#### 3. Control Concurrency
|
|
527
|
+
|
|
528
|
+
```typescript
|
|
529
|
+
// bulk_search has built-in concurrency control
|
|
530
|
+
{
|
|
531
|
+
"domains": ["name1", "name2", ..., "name50"],
|
|
532
|
+
"tld": "com",
|
|
533
|
+
"concurrency": 5 // Process 5 at a time (default)
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
#### 4. Implement Request Queuing
|
|
538
|
+
|
|
539
|
+
For very high volumes, implement client-side queuing:
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
// Example: Rate-limited queue for 100+ domains
|
|
543
|
+
async function queuedBulkSearch(domains: string[], tld: string) {
|
|
544
|
+
const BATCH_SIZE = 25;
|
|
545
|
+
const DELAY_BETWEEN_BATCHES = 5000; // 5 seconds
|
|
546
|
+
|
|
547
|
+
const results = [];
|
|
548
|
+
for (let i = 0; i < domains.length; i += BATCH_SIZE) {
|
|
549
|
+
const batch = domains.slice(i, i + BATCH_SIZE);
|
|
550
|
+
const batchResults = await bulkSearch({ domains: batch, tld });
|
|
551
|
+
results.push(...batchResults.results);
|
|
552
|
+
|
|
553
|
+
// Wait between batches to avoid rate limits
|
|
554
|
+
if (i + BATCH_SIZE < domains.length) {
|
|
555
|
+
await new Promise(r => setTimeout(r, DELAY_BETWEEN_BATCHES));
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return results;
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### Why API Keys Are Recommended
|
|
563
|
+
|
|
564
|
+
| Feature | Without API Keys | With API Keys | With GoDaddy MCP |
|
|
565
|
+
|---------|-----------------|---------------|------------------|
|
|
566
|
+
| Speed | 2-5 sec/domain | 100-200ms/domain | 200-500ms/domain |
|
|
567
|
+
| Rate Limits | Strict (10-50/min) | Generous (1000+/min) | Moderate |
|
|
568
|
+
| Pricing Data | Not available | Full pricing info | Full pricing info |
|
|
569
|
+
| Reliability | Varies by server | Consistent | Consistent |
|
|
570
|
+
| Bulk Operations | Limited to ~50/batch | Up to 100/batch | Supported |
|
|
571
|
+
| Setup Required | None | API key setup | MCP server only |
|
|
572
|
+
|
|
573
|
+
> **Tip**: GoDaddy MCP provides a middle ground - no API key needed but still gives pricing data!
|
|
574
|
+
|
|
575
|
+
### Handling Rate Limit Errors
|
|
576
|
+
|
|
577
|
+
```typescript
|
|
578
|
+
// The server returns structured errors for rate limits
|
|
579
|
+
{
|
|
580
|
+
"error": true,
|
|
581
|
+
"code": "RATE_LIMIT",
|
|
582
|
+
"message": "WHOIS rate limit exceeded for .com TLD",
|
|
583
|
+
"retryable": true,
|
|
584
|
+
"retryAfter": 30, // Seconds to wait
|
|
585
|
+
"suggestedAction": "Wait 30 seconds or use Porkbun API for faster results"
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Your code should handle these gracefully
|
|
589
|
+
try {
|
|
590
|
+
const result = await searchDomain("example.com");
|
|
591
|
+
} catch (error) {
|
|
592
|
+
if (error.code === "RATE_LIMIT" && error.retryable) {
|
|
593
|
+
await sleep(error.retryAfter * 1000);
|
|
594
|
+
return searchDomain("example.com"); // Retry
|
|
595
|
+
}
|
|
596
|
+
throw error;
|
|
597
|
+
}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
## Workflow Examples
|
|
601
|
+
|
|
602
|
+
### Workflow 1: Domain Suggestion When Preferred Name is Taken
|
|
603
|
+
|
|
604
|
+
When a user's preferred domain is unavailable, use `suggest_domains` to find alternatives:
|
|
605
|
+
|
|
606
|
+
```typescript
|
|
607
|
+
// Step 1: Check if preferred domain is available
|
|
608
|
+
const preferred = await searchDomain({
|
|
609
|
+
domain_name: "techapp",
|
|
610
|
+
tlds: ["com"]
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
// Step 2: If taken, generate suggestions
|
|
614
|
+
if (!preferred.results[0].available) {
|
|
615
|
+
const suggestions = await suggestDomains({
|
|
616
|
+
base_name: "techapp",
|
|
617
|
+
tld: "com",
|
|
618
|
+
max_suggestions: 10,
|
|
619
|
+
variants: ["prefixes", "suffixes", "hyphen", "abbreviations"]
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
// Step 3: Present alternatives to user
|
|
623
|
+
console.log("techapp.com is taken. Available alternatives:");
|
|
624
|
+
suggestions.suggestions.forEach(s => {
|
|
625
|
+
console.log(` ${s.domain} - $${s.price_first_year}/year`);
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// Output:
|
|
629
|
+
// techapp.com is taken. Available alternatives:
|
|
630
|
+
// gettechapp.com - $8.95/year
|
|
631
|
+
// techappnow.com - $8.95/year
|
|
632
|
+
// techapp-io.com - $8.95/year
|
|
633
|
+
// mytechapp.com - $8.95/year
|
|
634
|
+
}
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
### Workflow 2: Simultaneous Social Media Verification
|
|
638
|
+
|
|
639
|
+
Check username availability across multiple platforms at once:
|
|
640
|
+
|
|
641
|
+
```typescript
|
|
642
|
+
// Check if "myproject" is available on GitHub, Twitter, and Instagram
|
|
643
|
+
const socialCheck = await checkSocials({
|
|
644
|
+
name: "myproject",
|
|
645
|
+
platforms: ["github", "twitter", "instagram"]
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
// Handle results by confidence level
|
|
649
|
+
const highConfidence = socialCheck.results.filter(r => r.confidence === "high");
|
|
650
|
+
const mediumConfidence = socialCheck.results.filter(r => r.confidence === "medium");
|
|
651
|
+
const lowConfidence = socialCheck.results.filter(r => r.confidence === "low");
|
|
652
|
+
|
|
653
|
+
// Report findings
|
|
654
|
+
console.log("Verified available:", highConfidence.filter(r => r.available).map(r => r.platform));
|
|
655
|
+
console.log("Likely available:", mediumConfidence.filter(r => r.available).map(r => r.platform));
|
|
656
|
+
console.log("Check manually:", lowConfidence.map(r => r.platform));
|
|
657
|
+
|
|
658
|
+
// Output:
|
|
659
|
+
// Verified available: ["github"]
|
|
660
|
+
// Likely available: ["twitter"]
|
|
661
|
+
// Check manually: ["instagram"]
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
### Workflow 3: Complete Brand Validation Pipeline
|
|
665
|
+
|
|
666
|
+
Comprehensive brand name validation across domains and social media:
|
|
667
|
+
|
|
668
|
+
```typescript
|
|
669
|
+
async function validateBrandName(brandName: string) {
|
|
670
|
+
// Run domain and social checks in parallel
|
|
671
|
+
const [domainResults, socialResults] = await Promise.all([
|
|
672
|
+
searchDomain({
|
|
673
|
+
domain_name: brandName,
|
|
674
|
+
tlds: ["com", "io", "dev", "app"]
|
|
675
|
+
}),
|
|
676
|
+
checkSocials({
|
|
677
|
+
name: brandName,
|
|
678
|
+
platforms: ["github", "twitter", "instagram", "linkedin"]
|
|
679
|
+
})
|
|
680
|
+
]);
|
|
681
|
+
|
|
682
|
+
// Analyze domain availability
|
|
683
|
+
const availableDomains = domainResults.results.filter(r => r.available);
|
|
684
|
+
const bestDomain = availableDomains.sort((a, b) =>
|
|
685
|
+
a.price_first_year - b.price_first_year
|
|
686
|
+
)[0];
|
|
687
|
+
|
|
688
|
+
// Analyze social availability
|
|
689
|
+
const availableSocials = socialResults.results.filter(r =>
|
|
690
|
+
r.available && r.confidence !== "low"
|
|
691
|
+
);
|
|
692
|
+
|
|
693
|
+
// Calculate brand score
|
|
694
|
+
const domainScore = availableDomains.length / domainResults.results.length;
|
|
695
|
+
const socialScore = availableSocials.length / socialResults.results.length;
|
|
696
|
+
const overallScore = (domainScore + socialScore) / 2;
|
|
697
|
+
|
|
698
|
+
return {
|
|
699
|
+
brandName,
|
|
700
|
+
overallScore: Math.round(overallScore * 100),
|
|
701
|
+
domains: {
|
|
702
|
+
available: availableDomains.map(d => d.domain),
|
|
703
|
+
bestOption: bestDomain?.domain,
|
|
704
|
+
bestPrice: bestDomain?.price_first_year
|
|
705
|
+
},
|
|
706
|
+
socials: {
|
|
707
|
+
available: availableSocials.map(s => s.platform),
|
|
708
|
+
needsManualCheck: socialResults.results
|
|
709
|
+
.filter(r => r.confidence === "low")
|
|
710
|
+
.map(s => s.platform)
|
|
711
|
+
},
|
|
712
|
+
recommendation: overallScore > 0.7
|
|
713
|
+
? "Strong brand availability - proceed with registration"
|
|
714
|
+
: overallScore > 0.4
|
|
715
|
+
? "Partial availability - consider alternatives"
|
|
716
|
+
: "Limited availability - try a different name"
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Usage
|
|
721
|
+
const result = await validateBrandName("vibecoding");
|
|
722
|
+
// Output:
|
|
723
|
+
// {
|
|
724
|
+
// brandName: "vibecoding",
|
|
725
|
+
// overallScore: 85,
|
|
726
|
+
// domains: {
|
|
727
|
+
// available: ["vibecoding.com", "vibecoding.io", "vibecoding.dev"],
|
|
728
|
+
// bestOption: "vibecoding.com",
|
|
729
|
+
// bestPrice: 8.95
|
|
730
|
+
// },
|
|
731
|
+
// socials: {
|
|
732
|
+
// available: ["github", "twitter"],
|
|
733
|
+
// needsManualCheck: ["instagram", "linkedin"]
|
|
734
|
+
// },
|
|
735
|
+
// recommendation: "Strong brand availability - proceed with registration"
|
|
736
|
+
// }
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
### Workflow 4: Handling Partial Availability Scenarios
|
|
740
|
+
|
|
741
|
+
When some sources succeed and others fail:
|
|
742
|
+
|
|
743
|
+
```typescript
|
|
744
|
+
async function robustDomainSearch(domainName: string, tlds: string[]) {
|
|
745
|
+
const results = await searchDomain({ domain_name: domainName, tlds });
|
|
746
|
+
|
|
747
|
+
// Separate successful and failed checks
|
|
748
|
+
const successful = results.results.filter(r => !r.error);
|
|
749
|
+
const failed = results.results.filter(r => r.error);
|
|
750
|
+
|
|
751
|
+
// Handle partial failures
|
|
752
|
+
if (failed.length > 0) {
|
|
753
|
+
console.log(`Warning: ${failed.length} TLDs could not be checked:`);
|
|
754
|
+
failed.forEach(f => console.log(` ${f.domain}: ${f.error}`));
|
|
755
|
+
|
|
756
|
+
// Retry failed ones with exponential backoff
|
|
757
|
+
for (const failedResult of failed) {
|
|
758
|
+
const tld = failedResult.domain.split('.').pop();
|
|
759
|
+
let retryDelay = 1000;
|
|
760
|
+
|
|
761
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
762
|
+
await new Promise(r => setTimeout(r, retryDelay));
|
|
763
|
+
try {
|
|
764
|
+
const retry = await searchDomain({
|
|
765
|
+
domain_name: domainName,
|
|
766
|
+
tlds: [tld]
|
|
767
|
+
});
|
|
768
|
+
if (!retry.results[0].error) {
|
|
769
|
+
successful.push(retry.results[0]);
|
|
770
|
+
break;
|
|
771
|
+
}
|
|
772
|
+
} catch (e) {
|
|
773
|
+
retryDelay *= 2; // Exponential backoff
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
return {
|
|
780
|
+
results: successful,
|
|
781
|
+
partialFailure: failed.length > 0,
|
|
782
|
+
failedTlds: failed.map(f => f.domain.split('.').pop())
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
### Workflow 5: Domain Research Pipeline
|
|
788
|
+
|
|
789
|
+
Comprehensive domain research combining multiple tools:
|
|
790
|
+
|
|
791
|
+
```typescript
|
|
792
|
+
async function domainResearchPipeline(businessIdea: string) {
|
|
793
|
+
// Step 1: Generate smart suggestions from business description
|
|
794
|
+
const suggestions = await suggestDomainsSmart({
|
|
795
|
+
query: businessIdea,
|
|
796
|
+
tld: "com",
|
|
797
|
+
style: "brandable",
|
|
798
|
+
max_suggestions: 15
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
// Step 2: Get TLD information for context
|
|
802
|
+
const tldInfo = await getTldInfo({ tld: "com", detailed: true });
|
|
803
|
+
|
|
804
|
+
// Step 3: For top suggestions, compare registrar pricing
|
|
805
|
+
const topDomains = suggestions.results.available.slice(0, 5);
|
|
806
|
+
const priceComparisons = await Promise.all(
|
|
807
|
+
topDomains.map(d => compareRegistrars({
|
|
808
|
+
domain: d.domain.replace('.com', ''),
|
|
809
|
+
tld: "com"
|
|
810
|
+
}))
|
|
811
|
+
);
|
|
812
|
+
|
|
813
|
+
// Step 4: Check social media for top picks
|
|
814
|
+
const socialChecks = await Promise.all(
|
|
815
|
+
topDomains.slice(0, 3).map(d => {
|
|
816
|
+
const name = d.domain.replace('.com', '');
|
|
817
|
+
return checkSocials({
|
|
818
|
+
name,
|
|
819
|
+
platforms: ["github", "twitter", "npm"]
|
|
820
|
+
});
|
|
821
|
+
})
|
|
822
|
+
);
|
|
823
|
+
|
|
824
|
+
// Compile research report
|
|
825
|
+
return {
|
|
826
|
+
businessIdea,
|
|
827
|
+
tldContext: {
|
|
828
|
+
description: tldInfo.description,
|
|
829
|
+
priceRange: tldInfo.price_range,
|
|
830
|
+
recommendation: tldInfo.recommendation
|
|
831
|
+
},
|
|
832
|
+
topRecommendations: topDomains.map((d, i) => ({
|
|
833
|
+
domain: d.domain,
|
|
834
|
+
price: d.price_first_year,
|
|
835
|
+
bestRegistrar: priceComparisons[i]?.best_first_year?.registrar,
|
|
836
|
+
socialAvailability: socialChecks[i]?.summary
|
|
837
|
+
})),
|
|
838
|
+
allSuggestions: suggestions.results.available,
|
|
839
|
+
relatedTerms: suggestions.related_terms
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// Usage
|
|
844
|
+
const research = await domainResearchPipeline("ai-powered code review tool");
|
|
845
|
+
```
|
|
846
|
+
|
|
454
847
|
## Security
|
|
455
848
|
|
|
456
849
|
- API keys are never logged (automatic secret masking)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "domain-search-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
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",
|