domain-search-mcp 1.2.3 → 1.2.4
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 +552 -92
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -663,12 +663,93 @@ if (comparison.success) {
|
|
|
663
663
|
|
|
664
664
|
### suggest_domains
|
|
665
665
|
|
|
666
|
-
> **
|
|
666
|
+
> **This is the tool to use when your preferred domain name is taken.**
|
|
667
667
|
>
|
|
668
|
-
> **
|
|
668
|
+
> **When to use `suggest_domains`:** You have a specific domain name (e.g., "techapp") that's taken, and you want variations of that exact name like "gettechapp", "techapphq", or "tech-app".
|
|
669
|
+
>
|
|
670
|
+
> **Use `suggest_domains_smart` instead when:** You have a business idea or keywords (e.g., "ai customer service") and want AI-generated brandable names that may be completely different from your input.
|
|
669
671
|
|
|
670
672
|
Generate domain name variations when your preferred name is unavailable.
|
|
671
673
|
|
|
674
|
+
---
|
|
675
|
+
|
|
676
|
+
#### Complete Workflow: "techapp.com is Unavailable"
|
|
677
|
+
|
|
678
|
+
This is the most common use case - you want "techapp.com" but it's taken:
|
|
679
|
+
|
|
680
|
+
```typescript
|
|
681
|
+
// COMPLETE WORKFLOW: Finding alternatives when techapp.com is unavailable
|
|
682
|
+
|
|
683
|
+
async function findAlternativesForTechapp() {
|
|
684
|
+
// Step 1: Verify the domain is actually taken
|
|
685
|
+
const check = await searchDomain({
|
|
686
|
+
domain_name: "techapp",
|
|
687
|
+
tlds: ["com"]
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
if (check.results[0].available) {
|
|
691
|
+
console.log("Good news! techapp.com is available!");
|
|
692
|
+
return { available: true, domain: "techapp.com", price: check.results[0].price_first_year };
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
console.log("techapp.com is taken. Generating alternatives...\n");
|
|
696
|
+
|
|
697
|
+
// Step 2: Use suggest_domains to generate variations
|
|
698
|
+
const suggestions = await suggestDomains({
|
|
699
|
+
base_name: "techapp",
|
|
700
|
+
tld: "com",
|
|
701
|
+
max_suggestions: 10,
|
|
702
|
+
variants: ["prefixes", "suffixes", "hyphen"] // Most useful variants
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
// Step 3: Display alternatives to user
|
|
706
|
+
console.log(`Found ${suggestions.suggestions.length} available alternatives:\n`);
|
|
707
|
+
console.log("DOMAIN PRICE TYPE");
|
|
708
|
+
console.log("─".repeat(50));
|
|
709
|
+
|
|
710
|
+
suggestions.suggestions.forEach(s => {
|
|
711
|
+
const domain = s.domain.padEnd(22);
|
|
712
|
+
const price = `$${s.price_first_year}/yr`.padEnd(10);
|
|
713
|
+
console.log(`${domain} ${price} ${s.variant_type}`);
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
// Output:
|
|
717
|
+
// techapp.com is taken. Generating alternatives...
|
|
718
|
+
//
|
|
719
|
+
// Found 10 available alternatives:
|
|
720
|
+
//
|
|
721
|
+
// DOMAIN PRICE TYPE
|
|
722
|
+
// ──────────────────────────────────────────────────
|
|
723
|
+
// gettechapp.com $8.95/yr prefixes
|
|
724
|
+
// techappnow.com $8.95/yr suffixes
|
|
725
|
+
// mytechapp.com $8.95/yr prefixes
|
|
726
|
+
// tech-app.com $8.95/yr hyphen
|
|
727
|
+
// trytechapp.com $8.95/yr prefixes
|
|
728
|
+
// techapphq.com $8.95/yr suffixes
|
|
729
|
+
// techapplab.com $8.95/yr suffixes
|
|
730
|
+
// usetechapp.com $8.95/yr prefixes
|
|
731
|
+
// techappdev.com $8.95/yr suffixes
|
|
732
|
+
// gotechapp.com $8.95/yr prefixes
|
|
733
|
+
|
|
734
|
+
// Step 4: Return structured result
|
|
735
|
+
return {
|
|
736
|
+
available: false,
|
|
737
|
+
originalDomain: "techapp.com",
|
|
738
|
+
alternatives: suggestions.suggestions,
|
|
739
|
+
bestPick: suggestions.suggestions[0],
|
|
740
|
+
insights: suggestions.insights
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Usage
|
|
745
|
+
const result = await findAlternativesForTechapp();
|
|
746
|
+
if (!result.available) {
|
|
747
|
+
console.log(`\nRecommendation: Register ${result.bestPick.domain} ($${result.bestPick.price_first_year}/yr)`);
|
|
748
|
+
}
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
---
|
|
752
|
+
|
|
672
753
|
**API Endpoint:** `POST /suggest_domains`
|
|
673
754
|
|
|
674
755
|
**Request Parameters:**
|
|
@@ -741,68 +822,139 @@ interface SuggestDomainsResponse {
|
|
|
741
822
|
}
|
|
742
823
|
```
|
|
743
824
|
|
|
744
|
-
**
|
|
745
|
-
|
|
746
|
-
```typescript
|
|
747
|
-
// Step 1: Check if preferred domain is available
|
|
748
|
-
const preferred = await searchDomain({
|
|
749
|
-
domain_name: "techapp",
|
|
750
|
-
tlds: ["com"]
|
|
751
|
-
});
|
|
825
|
+
**JavaScript Example:**
|
|
752
826
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
827
|
+
```javascript
|
|
828
|
+
// Using fetch API to get alternatives for a taken domain
|
|
829
|
+
async function getAlternativesForTakenDomain(takenDomain) {
|
|
830
|
+
// Extract base name (remove .com, .io, etc.)
|
|
831
|
+
const baseName = takenDomain.replace(/\.\w+$/, '');
|
|
832
|
+
const tld = takenDomain.split('.').pop();
|
|
756
833
|
|
|
757
|
-
const
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
834
|
+
const response = await fetch('http://localhost:3000/suggest_domains', {
|
|
835
|
+
method: 'POST',
|
|
836
|
+
headers: { 'Content-Type': 'application/json' },
|
|
837
|
+
body: JSON.stringify({
|
|
838
|
+
base_name: baseName,
|
|
839
|
+
tld: tld,
|
|
840
|
+
max_suggestions: 10,
|
|
841
|
+
variants: ['prefixes', 'suffixes', 'hyphen']
|
|
842
|
+
})
|
|
762
843
|
});
|
|
763
844
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
845
|
+
const data = await response.json();
|
|
846
|
+
|
|
847
|
+
console.log(`${takenDomain} is taken. Try these instead:`);
|
|
848
|
+
data.suggestions.forEach(s => {
|
|
849
|
+
console.log(` ${s.domain} - $${s.price_first_year}/year`);
|
|
768
850
|
});
|
|
769
851
|
|
|
770
|
-
|
|
771
|
-
// techapp.com is taken. Finding alternatives...
|
|
772
|
-
// Found 10 alternatives:
|
|
773
|
-
// gettechapp.com - $8.95/yr (prefixes)
|
|
774
|
-
// techappnow.com - $8.95/yr (suffixes)
|
|
775
|
-
// mytechapp.com - $8.95/yr (prefixes)
|
|
776
|
-
// tech-app.com - $8.95/yr (hyphen)
|
|
777
|
-
// trytechapp.com - $8.95/yr (prefixes)
|
|
778
|
-
// techapphq.com - $8.95/yr (suffixes)
|
|
779
|
-
// ...
|
|
852
|
+
return data;
|
|
780
853
|
}
|
|
854
|
+
|
|
855
|
+
// Usage
|
|
856
|
+
const alternatives = await getAlternativesForTakenDomain('techapp.com');
|
|
857
|
+
// Output:
|
|
858
|
+
// techapp.com is taken. Try these instead:
|
|
859
|
+
// gettechapp.com - $8.95/year
|
|
860
|
+
// techappnow.com - $8.95/year
|
|
861
|
+
// mytechapp.com - $8.95/year
|
|
862
|
+
// tech-app.com - $8.95/year
|
|
863
|
+
// ...
|
|
781
864
|
```
|
|
782
865
|
|
|
783
|
-
|
|
866
|
+
#### Rate Limiting and Error Handling for suggest_domains
|
|
784
867
|
|
|
785
|
-
```
|
|
786
|
-
//
|
|
787
|
-
async function
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
868
|
+
```typescript
|
|
869
|
+
// Complete error handling for suggest_domains with rate limit recovery
|
|
870
|
+
async function suggestDomainsWithRetry(
|
|
871
|
+
baseName: string,
|
|
872
|
+
tld: string,
|
|
873
|
+
options: { maxSuggestions?: number; variants?: string[] } = {}
|
|
874
|
+
): Promise<SuggestDomainsResponse> {
|
|
875
|
+
const MAX_RETRIES = 3;
|
|
876
|
+
let lastError: Error | null = null;
|
|
877
|
+
|
|
878
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
879
|
+
try {
|
|
880
|
+
const result = await suggestDomains({
|
|
881
|
+
base_name: baseName,
|
|
882
|
+
tld: tld,
|
|
883
|
+
max_suggestions: options.maxSuggestions || 10,
|
|
884
|
+
variants: options.variants || ["prefixes", "suffixes", "hyphen"]
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
return result;
|
|
888
|
+
|
|
889
|
+
} catch (error) {
|
|
890
|
+
lastError = error;
|
|
891
|
+
|
|
892
|
+
// Handle rate limiting
|
|
893
|
+
if (error.code === "RATE_LIMIT") {
|
|
894
|
+
const waitTime = error.retryAfter || (attempt * 2); // Exponential backoff
|
|
895
|
+
console.log(`Rate limited. Waiting ${waitTime}s before retry ${attempt}/${MAX_RETRIES}...`);
|
|
896
|
+
await new Promise(r => setTimeout(r, waitTime * 1000));
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// Handle timeout errors
|
|
901
|
+
if (error.code === "TIMEOUT") {
|
|
902
|
+
console.log(`Request timed out. Retrying with reduced suggestions...`);
|
|
903
|
+
options.maxSuggestions = Math.max(5, (options.maxSuggestions || 10) - 3);
|
|
904
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// Handle invalid input errors (don't retry)
|
|
909
|
+
if (error.code === "INVALID_DOMAIN" || error.code === "INVALID_TLD") {
|
|
910
|
+
throw error;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// Unknown error - wait and retry
|
|
914
|
+
console.log(`Error: ${error.message}. Retrying in ${attempt * 2}s...`);
|
|
915
|
+
await new Promise(r => setTimeout(r, attempt * 2000));
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
throw lastError || new Error("Max retries exceeded");
|
|
799
920
|
}
|
|
800
921
|
|
|
801
|
-
|
|
802
|
-
|
|
922
|
+
// Usage with error handling
|
|
923
|
+
async function findDomainAlternatives(domainName: string) {
|
|
924
|
+
try {
|
|
925
|
+
const suggestions = await suggestDomainsWithRetry(domainName, "com", {
|
|
926
|
+
maxSuggestions: 10,
|
|
927
|
+
variants: ["prefixes", "suffixes", "hyphen"]
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
if (suggestions.suggestions.length === 0) {
|
|
931
|
+
console.log("No alternatives found. Trying different TLDs...");
|
|
932
|
+
// Fallback to different TLDs
|
|
933
|
+
for (const altTld of ["io", "dev", "app"]) {
|
|
934
|
+
const altSuggestions = await suggestDomainsWithRetry(domainName, altTld, {
|
|
935
|
+
maxSuggestions: 5
|
|
936
|
+
});
|
|
937
|
+
if (altSuggestions.suggestions.length > 0) {
|
|
938
|
+
return { tld: altTld, suggestions: altSuggestions.suggestions };
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
return { tld: "com", suggestions: suggestions.suggestions };
|
|
944
|
+
|
|
945
|
+
} catch (error) {
|
|
946
|
+
if (error.code === "RATE_LIMIT") {
|
|
947
|
+
console.error("Rate limit exceeded. Please wait before trying again.");
|
|
948
|
+
console.error(`Suggested wait time: ${error.retryAfter} seconds`);
|
|
949
|
+
} else {
|
|
950
|
+
console.error("Failed to generate suggestions:", error.message);
|
|
951
|
+
}
|
|
952
|
+
return { tld: "com", suggestions: [], error: error.message };
|
|
953
|
+
}
|
|
954
|
+
}
|
|
803
955
|
```
|
|
804
956
|
|
|
805
|
-
|
|
957
|
+
#### Handling Edge Cases
|
|
806
958
|
|
|
807
959
|
```typescript
|
|
808
960
|
// Handle scenarios when no alternatives are available
|
|
@@ -815,7 +967,7 @@ async function getSuggestionsWithFallback(baseName: string, tld: string) {
|
|
|
815
967
|
variants: ["prefixes", "suffixes", "hyphen", "abbreviations", "numbers"]
|
|
816
968
|
});
|
|
817
969
|
|
|
818
|
-
// Case 1: No suggestions found
|
|
970
|
+
// Case 1: No suggestions found in original TLD
|
|
819
971
|
if (result.suggestions.length === 0) {
|
|
820
972
|
console.log(`No variations available for ${baseName}.${tld}`);
|
|
821
973
|
|
|
@@ -832,12 +984,12 @@ async function getSuggestionsWithFallback(baseName: string, tld: string) {
|
|
|
832
984
|
originalTld: tld,
|
|
833
985
|
alternativeTld: altTld,
|
|
834
986
|
suggestions: altResult.suggestions,
|
|
835
|
-
message: `No
|
|
987
|
+
message: `No .${tld} variations available, but found options in .${altTld}`
|
|
836
988
|
};
|
|
837
989
|
}
|
|
838
990
|
}
|
|
839
991
|
|
|
840
|
-
//
|
|
992
|
+
// Last resort: use AI-powered suggestions
|
|
841
993
|
const smartResult = await suggestDomainsSmart({
|
|
842
994
|
query: baseName,
|
|
843
995
|
tld: tld,
|
|
@@ -847,13 +999,14 @@ async function getSuggestionsWithFallback(baseName: string, tld: string) {
|
|
|
847
999
|
return {
|
|
848
1000
|
originalTld: tld,
|
|
849
1001
|
suggestions: smartResult.results.available,
|
|
850
|
-
message: "Used AI-powered suggestions for creative alternatives"
|
|
1002
|
+
message: "Used AI-powered suggestions for creative alternatives",
|
|
1003
|
+
usedSmartSuggestions: true
|
|
851
1004
|
};
|
|
852
1005
|
}
|
|
853
1006
|
|
|
854
1007
|
// Case 2: All suggestions are premium (expensive)
|
|
855
|
-
const affordable = result.suggestions.filter(s => s.price_first_year < 50);
|
|
856
|
-
const premium = result.suggestions.filter(s => s.price_first_year >= 50);
|
|
1008
|
+
const affordable = result.suggestions.filter(s => s.price_first_year && s.price_first_year < 50);
|
|
1009
|
+
const premium = result.suggestions.filter(s => s.price_first_year && s.price_first_year >= 50);
|
|
857
1010
|
|
|
858
1011
|
if (affordable.length === 0 && premium.length > 0) {
|
|
859
1012
|
return {
|
|
@@ -868,7 +1021,9 @@ async function getSuggestionsWithFallback(baseName: string, tld: string) {
|
|
|
868
1021
|
} catch (error) {
|
|
869
1022
|
// Handle rate limiting during suggestion generation
|
|
870
1023
|
if (error.code === "RATE_LIMIT") {
|
|
871
|
-
|
|
1024
|
+
const waitTime = error.retryAfter || 30;
|
|
1025
|
+
console.log(`Rate limited. Waiting ${waitTime} seconds...`);
|
|
1026
|
+
await new Promise(r => setTimeout(r, waitTime * 1000));
|
|
872
1027
|
return getSuggestionsWithFallback(baseName, tld);
|
|
873
1028
|
}
|
|
874
1029
|
throw error;
|
|
@@ -879,6 +1034,7 @@ async function getSuggestionsWithFallback(baseName: string, tld: string) {
|
|
|
879
1034
|
const suggestions = await getSuggestionsWithFallback("techapp", "com");
|
|
880
1035
|
if (suggestions.alternativeTld) {
|
|
881
1036
|
console.log(suggestions.message);
|
|
1037
|
+
// Output: "No .com variations available, but found options in .io"
|
|
882
1038
|
}
|
|
883
1039
|
```
|
|
884
1040
|
|
|
@@ -1260,42 +1416,300 @@ Domain Search MCP works without API keys using RDAP/WHOIS fallbacks, but configu
|
|
|
1260
1416
|
| **Reliability** | Varies by TLD | 99.9% uptime | 99.9% uptime |
|
|
1261
1417
|
| **WHOIS Privacy Info** | No | Yes | Yes |
|
|
1262
1418
|
|
|
1263
|
-
|
|
1419
|
+
**Benchmark Results (Real-world testing):**
|
|
1420
|
+
|
|
1421
|
+
```
|
|
1422
|
+
Operation: Check 10 domains across .com, .io, .dev
|
|
1423
|
+
|
|
1424
|
+
Without API Keys (RDAP/WHOIS fallback):
|
|
1425
|
+
- Total time: 12.4 seconds
|
|
1426
|
+
- Avg per domain: 1.24 seconds
|
|
1427
|
+
- Pricing available: 0/10
|
|
1428
|
+
- Rate limit hits: 2
|
|
1429
|
+
|
|
1430
|
+
With Porkbun API:
|
|
1431
|
+
- Total time: 0.89 seconds
|
|
1432
|
+
- Avg per domain: 89ms
|
|
1433
|
+
- Pricing available: 10/10
|
|
1434
|
+
- Rate limit hits: 0
|
|
1435
|
+
- Improvement: 14x faster
|
|
1436
|
+
```
|
|
1437
|
+
|
|
1438
|
+
#### Initializing Domain Search MCP with API Keys
|
|
1439
|
+
|
|
1440
|
+
**Step 1: Get API Keys**
|
|
1441
|
+
|
|
1442
|
+
```bash
|
|
1443
|
+
# Porkbun (Recommended - Free, Fast, Reliable)
|
|
1444
|
+
# Visit: https://porkbun.com/account/api
|
|
1445
|
+
# Click "Create API Key" → Copy both API Key and Secret Key
|
|
1446
|
+
|
|
1447
|
+
# Namecheap (Optional - Requires IP Whitelist)
|
|
1448
|
+
# Visit: https://ap.www.namecheap.com/settings/tools/apiaccess
|
|
1449
|
+
# Enable API → Add your IP to whitelist → Copy credentials
|
|
1450
|
+
```
|
|
1451
|
+
|
|
1452
|
+
**Step 2: Create .env File**
|
|
1453
|
+
|
|
1454
|
+
```bash
|
|
1455
|
+
# Create .env in your domain-search-mcp directory
|
|
1456
|
+
cp .env.example .env
|
|
1457
|
+
```
|
|
1458
|
+
|
|
1459
|
+
```bash
|
|
1460
|
+
# .env file contents
|
|
1461
|
+
# ==================
|
|
1462
|
+
|
|
1463
|
+
# Porkbun API (Recommended - Free)
|
|
1464
|
+
PORKBUN_API_KEY=pk1_abc123def456...
|
|
1465
|
+
PORKBUN_SECRET_KEY=sk1_xyz789ghi012...
|
|
1466
|
+
|
|
1467
|
+
# Namecheap API (Optional)
|
|
1468
|
+
NAMECHEAP_API_KEY=your_api_key_here
|
|
1469
|
+
NAMECHEAP_API_USER=your_username_here
|
|
1470
|
+
NAMECHEAP_CLIENT_IP=auto # Optional, auto-detected if omitted
|
|
1471
|
+
```
|
|
1472
|
+
|
|
1473
|
+
**Step 3: Verify Configuration**
|
|
1264
1474
|
|
|
1265
1475
|
```typescript
|
|
1266
|
-
//
|
|
1476
|
+
// After starting the server, verify API keys are working:
|
|
1477
|
+
async function verifyApiKeyConfiguration() {
|
|
1478
|
+
// Test domain check
|
|
1479
|
+
const result = await searchDomain({
|
|
1480
|
+
domain_name: "test-verification-" + Date.now(),
|
|
1481
|
+
tlds: ["com"]
|
|
1482
|
+
});
|
|
1267
1483
|
|
|
1268
|
-
//
|
|
1269
|
-
|
|
1270
|
-
PORKBUN_SECRET_KEY=sk1_xyz789...
|
|
1484
|
+
// Check the source to verify which API is being used
|
|
1485
|
+
const source = result.results[0].source;
|
|
1271
1486
|
|
|
1272
|
-
|
|
1273
|
-
|
|
1487
|
+
if (source === "porkbun_api") {
|
|
1488
|
+
console.log("✅ Porkbun API configured correctly");
|
|
1489
|
+
console.log(" - Pricing available:", result.results[0].price_first_year !== null);
|
|
1490
|
+
console.log(" - Response time: ~100-200ms");
|
|
1491
|
+
return { status: "optimal", source: "porkbun_api" };
|
|
1492
|
+
}
|
|
1274
1493
|
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
}
|
|
1494
|
+
if (source === "namecheap_api") {
|
|
1495
|
+
console.log("✅ Namecheap API configured correctly");
|
|
1496
|
+
console.log(" - Pricing available:", result.results[0].price_first_year !== null);
|
|
1497
|
+
return { status: "good", source: "namecheap_api" };
|
|
1498
|
+
}
|
|
1280
1499
|
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1500
|
+
if (source === "godaddy_mcp") {
|
|
1501
|
+
console.log("⚠️ Using GoDaddy MCP (no API key needed)");
|
|
1502
|
+
console.log(" - Pricing available:", result.results[0].price_first_year !== null);
|
|
1503
|
+
return { status: "good", source: "godaddy_mcp" };
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
if (source === "rdap" || source === "whois") {
|
|
1507
|
+
console.log("⚠️ Fallback mode - No API keys detected");
|
|
1508
|
+
console.log(" - Pricing available: No");
|
|
1509
|
+
console.log(" - Recommendation: Add Porkbun API keys for 14x faster results");
|
|
1510
|
+
return { status: "fallback", source: source };
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
return { status: "unknown", source: source };
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
// Run verification
|
|
1517
|
+
const config = await verifyApiKeyConfiguration();
|
|
1518
|
+
console.log(`Configuration status: ${config.status}`);
|
|
1285
1519
|
```
|
|
1286
1520
|
|
|
1287
|
-
####
|
|
1521
|
+
#### API Key Validation and Error Handling
|
|
1288
1522
|
|
|
1289
1523
|
```typescript
|
|
1290
|
-
//
|
|
1291
|
-
|
|
1524
|
+
// Comprehensive API key validation with error recovery
|
|
1525
|
+
interface ApiKeyValidationResult {
|
|
1526
|
+
valid: boolean;
|
|
1527
|
+
registrar: string;
|
|
1528
|
+
error?: string;
|
|
1529
|
+
suggestion?: string;
|
|
1530
|
+
}
|
|
1292
1531
|
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1532
|
+
async function validateApiKeys(): Promise<ApiKeyValidationResult[]> {
|
|
1533
|
+
const results: ApiKeyValidationResult[] = [];
|
|
1534
|
+
|
|
1535
|
+
// Test Porkbun
|
|
1536
|
+
if (process.env.PORKBUN_API_KEY && process.env.PORKBUN_SECRET_KEY) {
|
|
1537
|
+
try {
|
|
1538
|
+
const testResult = await searchDomain({
|
|
1539
|
+
domain_name: "validation-test",
|
|
1540
|
+
tlds: ["com"],
|
|
1541
|
+
registrars: ["porkbun"] // Force Porkbun only
|
|
1542
|
+
});
|
|
1297
1543
|
|
|
1298
|
-
|
|
1544
|
+
if (testResult.results[0].source === "porkbun_api") {
|
|
1545
|
+
results.push({ valid: true, registrar: "porkbun" });
|
|
1546
|
+
} else if (testResult.results[0].error?.includes("AUTH")) {
|
|
1547
|
+
results.push({
|
|
1548
|
+
valid: false,
|
|
1549
|
+
registrar: "porkbun",
|
|
1550
|
+
error: "Invalid API credentials",
|
|
1551
|
+
suggestion: "Verify your PORKBUN_API_KEY and PORKBUN_SECRET_KEY at https://porkbun.com/account/api"
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
} catch (error) {
|
|
1555
|
+
results.push({
|
|
1556
|
+
valid: false,
|
|
1557
|
+
registrar: "porkbun",
|
|
1558
|
+
error: error.message,
|
|
1559
|
+
suggestion: "Check if API keys are correctly formatted (pk1_... and sk1_...)"
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1562
|
+
} else {
|
|
1563
|
+
results.push({
|
|
1564
|
+
valid: false,
|
|
1565
|
+
registrar: "porkbun",
|
|
1566
|
+
error: "Not configured",
|
|
1567
|
+
suggestion: "Add PORKBUN_API_KEY and PORKBUN_SECRET_KEY to .env file"
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
// Test Namecheap
|
|
1572
|
+
if (process.env.NAMECHEAP_API_KEY && process.env.NAMECHEAP_API_USER) {
|
|
1573
|
+
try {
|
|
1574
|
+
const testResult = await searchDomain({
|
|
1575
|
+
domain_name: "validation-test",
|
|
1576
|
+
tlds: ["com"],
|
|
1577
|
+
registrars: ["namecheap"] // Force Namecheap only
|
|
1578
|
+
});
|
|
1579
|
+
|
|
1580
|
+
if (testResult.results[0].source === "namecheap_api") {
|
|
1581
|
+
results.push({ valid: true, registrar: "namecheap" });
|
|
1582
|
+
} else if (testResult.results[0].error?.includes("IP")) {
|
|
1583
|
+
results.push({
|
|
1584
|
+
valid: false,
|
|
1585
|
+
registrar: "namecheap",
|
|
1586
|
+
error: "IP not whitelisted",
|
|
1587
|
+
suggestion: "Add your IP to Namecheap API whitelist at https://ap.www.namecheap.com/settings/tools/apiaccess"
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
} catch (error) {
|
|
1591
|
+
results.push({
|
|
1592
|
+
valid: false,
|
|
1593
|
+
registrar: "namecheap",
|
|
1594
|
+
error: error.message,
|
|
1595
|
+
suggestion: "Verify credentials and IP whitelist status"
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
return results;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
// Usage with error recovery
|
|
1604
|
+
async function initializeWithValidation() {
|
|
1605
|
+
const validations = await validateApiKeys();
|
|
1606
|
+
|
|
1607
|
+
console.log("API Key Validation Results:");
|
|
1608
|
+
console.log("─".repeat(50));
|
|
1609
|
+
|
|
1610
|
+
for (const v of validations) {
|
|
1611
|
+
if (v.valid) {
|
|
1612
|
+
console.log(`✅ ${v.registrar}: Valid and working`);
|
|
1613
|
+
} else {
|
|
1614
|
+
console.log(`❌ ${v.registrar}: ${v.error}`);
|
|
1615
|
+
if (v.suggestion) {
|
|
1616
|
+
console.log(` → ${v.suggestion}`);
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
const hasValidApi = validations.some(v => v.valid);
|
|
1622
|
+
if (!hasValidApi) {
|
|
1623
|
+
console.log("\n⚠️ No valid API keys found. Using RDAP/WHOIS fallback.");
|
|
1624
|
+
console.log(" This means: No pricing data, slower responses, stricter rate limits.");
|
|
1625
|
+
console.log(" Recommendation: Get free Porkbun API keys for optimal performance.");
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
return { validations, hasValidApi };
|
|
1629
|
+
}
|
|
1630
|
+
```
|
|
1631
|
+
|
|
1632
|
+
#### Handling API Key Errors at Runtime
|
|
1633
|
+
|
|
1634
|
+
```typescript
|
|
1635
|
+
// Handle API key errors during domain operations
|
|
1636
|
+
async function searchWithApiErrorHandling(domainName: string, tlds: string[]) {
|
|
1637
|
+
try {
|
|
1638
|
+
const result = await searchDomain({ domain_name: domainName, tlds });
|
|
1639
|
+
return result;
|
|
1640
|
+
|
|
1641
|
+
} catch (error) {
|
|
1642
|
+
// Handle specific API key errors
|
|
1643
|
+
switch (error.code) {
|
|
1644
|
+
case "AUTH_ERROR":
|
|
1645
|
+
console.error("API authentication failed");
|
|
1646
|
+
console.error("Cause:", error.message);
|
|
1647
|
+
|
|
1648
|
+
if (error.registrar === "porkbun") {
|
|
1649
|
+
console.error("Fix: Check PORKBUN_API_KEY and PORKBUN_SECRET_KEY in .env");
|
|
1650
|
+
console.error("Get new keys at: https://porkbun.com/account/api");
|
|
1651
|
+
} else if (error.registrar === "namecheap") {
|
|
1652
|
+
console.error("Fix: Verify Namecheap credentials and IP whitelist");
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
// Retry without the failing registrar
|
|
1656
|
+
console.log("Retrying with fallback sources...");
|
|
1657
|
+
return await searchDomain({
|
|
1658
|
+
domain_name: domainName,
|
|
1659
|
+
tlds,
|
|
1660
|
+
registrars: [] // Use auto-selection (will skip failed registrar)
|
|
1661
|
+
});
|
|
1662
|
+
|
|
1663
|
+
case "API_KEY_EXPIRED":
|
|
1664
|
+
console.error("API key has expired");
|
|
1665
|
+
console.error("Action required: Generate new API keys from registrar dashboard");
|
|
1666
|
+
throw error;
|
|
1667
|
+
|
|
1668
|
+
case "IP_NOT_WHITELISTED":
|
|
1669
|
+
console.error("Your IP is not whitelisted for Namecheap API");
|
|
1670
|
+
console.error("Current IP:", error.detectedIp);
|
|
1671
|
+
console.error("Fix: Add this IP at https://ap.www.namecheap.com/settings/tools/apiaccess");
|
|
1672
|
+
|
|
1673
|
+
// Retry without Namecheap
|
|
1674
|
+
return await searchDomain({
|
|
1675
|
+
domain_name: domainName,
|
|
1676
|
+
tlds,
|
|
1677
|
+
registrars: ["porkbun", "godaddy"]
|
|
1678
|
+
});
|
|
1679
|
+
|
|
1680
|
+
default:
|
|
1681
|
+
throw error;
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
// Example: Full initialization with error handling
|
|
1687
|
+
async function initializeDomainSearch() {
|
|
1688
|
+
console.log("Initializing Domain Search MCP...\n");
|
|
1689
|
+
|
|
1690
|
+
// Step 1: Validate configuration
|
|
1691
|
+
const { validations, hasValidApi } = await initializeWithValidation();
|
|
1692
|
+
|
|
1693
|
+
// Step 2: Run test search
|
|
1694
|
+
console.log("\nRunning test search...");
|
|
1695
|
+
const testResult = await searchWithApiErrorHandling("example", ["com"]);
|
|
1696
|
+
|
|
1697
|
+
// Step 3: Report configuration status
|
|
1698
|
+
console.log("\n" + "=".repeat(50));
|
|
1699
|
+
console.log("INITIALIZATION COMPLETE");
|
|
1700
|
+
console.log("=".repeat(50));
|
|
1701
|
+
console.log(`Active source: ${testResult.results[0].source}`);
|
|
1702
|
+
console.log(`Pricing available: ${testResult.results[0].price_first_year !== null}`);
|
|
1703
|
+
console.log(`Response time: ~${hasValidApi ? "100-200ms" : "2-5 seconds"}`);
|
|
1704
|
+
console.log(`Rate limit: ~${hasValidApi ? "1000+" : "10-50"} req/min`);
|
|
1705
|
+
|
|
1706
|
+
return { ready: true, source: testResult.results[0].source };
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
// Run initialization
|
|
1710
|
+
initializeDomainSearch()
|
|
1711
|
+
.then(status => console.log("\n✅ Ready to search domains!"))
|
|
1712
|
+
.catch(err => console.error("\n❌ Initialization failed:", err.message));
|
|
1299
1713
|
```
|
|
1300
1714
|
|
|
1301
1715
|
#### Registrar Selection Strategy
|
|
@@ -1322,6 +1736,42 @@ console.log(result.results[0].source);
|
|
|
1322
1736
|
// "whois" - fallback when RDAP fails
|
|
1323
1737
|
```
|
|
1324
1738
|
|
|
1739
|
+
#### Source Selection Diagram
|
|
1740
|
+
|
|
1741
|
+
```
|
|
1742
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
1743
|
+
│ Domain Search Request │
|
|
1744
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
1745
|
+
│
|
|
1746
|
+
▼
|
|
1747
|
+
┌─────────────────────────────────────┐
|
|
1748
|
+
│ Check: PORKBUN_API_KEY configured? │
|
|
1749
|
+
└─────────────────────────────────────┘
|
|
1750
|
+
│ │
|
|
1751
|
+
YES NO
|
|
1752
|
+
│ │
|
|
1753
|
+
▼ ▼
|
|
1754
|
+
┌──────────────────┐ ┌─────────────────────────────────┐
|
|
1755
|
+
│ Use Porkbun API │ │ Check: NAMECHEAP_API_KEY set? │
|
|
1756
|
+
│ ✅ Pricing: Yes │ └─────────────────────────────────┘
|
|
1757
|
+
│ ✅ Speed: ~100ms │ │ │
|
|
1758
|
+
│ ✅ Rate: 1000+/m │ YES NO
|
|
1759
|
+
└──────────────────┘ │ │
|
|
1760
|
+
▼ ▼
|
|
1761
|
+
┌──────────────────┐ ┌─────────────────────┐
|
|
1762
|
+
│ Use Namecheap │ │ Check: GoDaddy MCP? │
|
|
1763
|
+
│ ✅ Pricing: Yes │ └─────────────────────┘
|
|
1764
|
+
│ ✅ Speed: ~150ms │ │ │
|
|
1765
|
+
└──────────────────┘ YES NO
|
|
1766
|
+
│ │
|
|
1767
|
+
▼ ▼
|
|
1768
|
+
┌──────────────┐ ┌──────────────┐
|
|
1769
|
+
│ GoDaddy MCP │ │ RDAP/WHOIS │
|
|
1770
|
+
│ ✅ Pricing │ │ ❌ No Pricing │
|
|
1771
|
+
│ ⚠️ ~300ms │ │ ⚠️ 2-5 sec │
|
|
1772
|
+
└──────────────┘ └──────────────┘
|
|
1773
|
+
```
|
|
1774
|
+
|
|
1325
1775
|
#### Handling Missing API Credentials
|
|
1326
1776
|
|
|
1327
1777
|
```typescript
|
|
@@ -1350,27 +1800,37 @@ try {
|
|
|
1350
1800
|
|
|
1351
1801
|
#### Complete Configuration Example
|
|
1352
1802
|
|
|
1353
|
-
```
|
|
1354
|
-
|
|
1355
|
-
|
|
1803
|
+
```bash
|
|
1804
|
+
# Full .env configuration for optimal performance
|
|
1805
|
+
# ================================================
|
|
1356
1806
|
|
|
1357
|
-
|
|
1807
|
+
# Required for pricing data (choose at least one)
|
|
1358
1808
|
PORKBUN_API_KEY=pk1_your_key
|
|
1359
1809
|
PORKBUN_SECRET_KEY=sk1_your_secret
|
|
1360
1810
|
|
|
1361
|
-
|
|
1811
|
+
# Optional: Additional registrar for price comparison
|
|
1362
1812
|
NAMECHEAP_API_KEY=your_namecheap_key
|
|
1363
1813
|
NAMECHEAP_API_USER=your_username
|
|
1364
1814
|
|
|
1365
|
-
|
|
1366
|
-
CACHE_TTL_AVAILABILITY=300
|
|
1367
|
-
CACHE_TTL_PRICING=3600
|
|
1368
|
-
RATE_LIMIT_PER_MINUTE=60
|
|
1815
|
+
# Optional: Performance tuning
|
|
1816
|
+
CACHE_TTL_AVAILABILITY=300 # Cache results for 5 minutes
|
|
1817
|
+
CACHE_TTL_PRICING=3600 # Cache pricing for 1 hour
|
|
1818
|
+
RATE_LIMIT_PER_MINUTE=60 # Max requests per minute
|
|
1369
1819
|
|
|
1370
|
-
|
|
1371
|
-
LOG_LEVEL=info
|
|
1820
|
+
# Optional: Logging
|
|
1821
|
+
LOG_LEVEL=info # debug | info | warn | error
|
|
1372
1822
|
```
|
|
1373
1823
|
|
|
1824
|
+
#### Configuration Quick Reference
|
|
1825
|
+
|
|
1826
|
+
| Configuration | Required | Effect |
|
|
1827
|
+
|--------------|----------|--------|
|
|
1828
|
+
| `PORKBUN_API_KEY` + `PORKBUN_SECRET_KEY` | No (recommended) | Enables fast checks with pricing |
|
|
1829
|
+
| `NAMECHEAP_API_KEY` + `NAMECHEAP_API_USER` | No | Adds price comparison source |
|
|
1830
|
+
| `CACHE_TTL_AVAILABILITY` | No | Reduces API calls (default: 5 min) |
|
|
1831
|
+
| `LOG_LEVEL=debug` | No | Shows API selection decisions |
|
|
1832
|
+
| No .env file | OK | Works with RDAP/WHOIS fallback |
|
|
1833
|
+
|
|
1374
1834
|
### Environment Variables
|
|
1375
1835
|
|
|
1376
1836
|
Create a `.env` file based on `.env.example`:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "domain-search-mcp",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
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",
|