domain-search-mcp 1.2.9 → 1.2.10
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 +886 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2378,6 +2378,513 @@ ALLOWED_TLDS=com,io,dev,app,co,net,org,xyz,ai,sh,me,cc
|
|
|
2378
2378
|
DENY_TLDS=localhost,internal,test,local
|
|
2379
2379
|
```
|
|
2380
2380
|
|
|
2381
|
+
### Programmatic Environment Variable Configuration
|
|
2382
|
+
|
|
2383
|
+
For applications that need to configure API keys programmatically (without .env files), use these patterns:
|
|
2384
|
+
|
|
2385
|
+
#### Setting Environment Variables in Application Code
|
|
2386
|
+
|
|
2387
|
+
```typescript
|
|
2388
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2389
|
+
// PROGRAMMATIC CONFIGURATION MODULE
|
|
2390
|
+
// Use this when you need to configure Domain Search MCP in code
|
|
2391
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2392
|
+
|
|
2393
|
+
import { searchDomain, bulkSearch, compareRegistrars, tldInfo, checkSocials, suggestDomains } from 'domain-search-mcp';
|
|
2394
|
+
|
|
2395
|
+
/**
|
|
2396
|
+
* Configuration interface for Domain Search MCP
|
|
2397
|
+
*/
|
|
2398
|
+
interface DomainSearchConfig {
|
|
2399
|
+
porkbun?: {
|
|
2400
|
+
apiKey: string;
|
|
2401
|
+
apiSecret: string;
|
|
2402
|
+
};
|
|
2403
|
+
namecheap?: {
|
|
2404
|
+
apiKey: string;
|
|
2405
|
+
apiUser: string;
|
|
2406
|
+
clientIp?: string; // Auto-detected if not provided
|
|
2407
|
+
};
|
|
2408
|
+
cache?: {
|
|
2409
|
+
availabilityTtl?: number; // seconds, default 300
|
|
2410
|
+
pricingTtl?: number; // seconds, default 3600
|
|
2411
|
+
};
|
|
2412
|
+
rateLimitPerMinute?: number; // default 60
|
|
2413
|
+
logLevel?: 'debug' | 'info' | 'warn' | 'error';
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
/**
|
|
2417
|
+
* Configure Domain Search MCP programmatically
|
|
2418
|
+
* Call this BEFORE any domain search operations
|
|
2419
|
+
*/
|
|
2420
|
+
function configureDomainSearch(config: DomainSearchConfig): void {
|
|
2421
|
+
// Set Porkbun credentials
|
|
2422
|
+
if (config.porkbun) {
|
|
2423
|
+
process.env.PORKBUN_API_KEY = config.porkbun.apiKey;
|
|
2424
|
+
process.env.PORKBUN_API_SECRET = config.porkbun.apiSecret;
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
// Set Namecheap credentials
|
|
2428
|
+
if (config.namecheap) {
|
|
2429
|
+
process.env.NAMECHEAP_API_KEY = config.namecheap.apiKey;
|
|
2430
|
+
process.env.NAMECHEAP_API_USER = config.namecheap.apiUser;
|
|
2431
|
+
if (config.namecheap.clientIp) {
|
|
2432
|
+
process.env.NAMECHEAP_CLIENT_IP = config.namecheap.clientIp;
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
// Set cache configuration
|
|
2437
|
+
if (config.cache) {
|
|
2438
|
+
if (config.cache.availabilityTtl) {
|
|
2439
|
+
process.env.CACHE_TTL_AVAILABILITY = String(config.cache.availabilityTtl);
|
|
2440
|
+
}
|
|
2441
|
+
if (config.cache.pricingTtl) {
|
|
2442
|
+
process.env.CACHE_TTL_PRICING = String(config.cache.pricingTtl);
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
// Set rate limiting
|
|
2447
|
+
if (config.rateLimitPerMinute) {
|
|
2448
|
+
process.env.RATE_LIMIT_PER_MINUTE = String(config.rateLimitPerMinute);
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
// Set log level
|
|
2452
|
+
if (config.logLevel) {
|
|
2453
|
+
process.env.LOG_LEVEL = config.logLevel;
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2458
|
+
// USAGE EXAMPLES
|
|
2459
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2460
|
+
|
|
2461
|
+
// Example 1: Configure with Porkbun only (recommended)
|
|
2462
|
+
configureDomainSearch({
|
|
2463
|
+
porkbun: {
|
|
2464
|
+
apiKey: 'pk1_abc123def456ghi789jkl012mno345pqr678stu901vwx234',
|
|
2465
|
+
apiSecret: 'sk1_xyz789abc123def456ghi789jkl012mno345pqr678stu901'
|
|
2466
|
+
},
|
|
2467
|
+
logLevel: 'info'
|
|
2468
|
+
});
|
|
2469
|
+
|
|
2470
|
+
// Example 2: Configure with both Porkbun and Namecheap for price comparison
|
|
2471
|
+
configureDomainSearch({
|
|
2472
|
+
porkbun: {
|
|
2473
|
+
apiKey: process.env.MY_APP_PORKBUN_KEY!,
|
|
2474
|
+
apiSecret: process.env.MY_APP_PORKBUN_SECRET!
|
|
2475
|
+
},
|
|
2476
|
+
namecheap: {
|
|
2477
|
+
apiKey: process.env.MY_APP_NAMECHEAP_KEY!,
|
|
2478
|
+
apiUser: process.env.MY_APP_NAMECHEAP_USER!
|
|
2479
|
+
},
|
|
2480
|
+
cache: {
|
|
2481
|
+
availabilityTtl: 600, // 10 minutes
|
|
2482
|
+
pricingTtl: 7200 // 2 hours
|
|
2483
|
+
},
|
|
2484
|
+
rateLimitPerMinute: 100,
|
|
2485
|
+
logLevel: 'debug'
|
|
2486
|
+
});
|
|
2487
|
+
|
|
2488
|
+
// Example 3: No API keys (uses RDAP/WHOIS fallback)
|
|
2489
|
+
configureDomainSearch({
|
|
2490
|
+
cache: { availabilityTtl: 300 },
|
|
2491
|
+
logLevel: 'warn'
|
|
2492
|
+
});
|
|
2493
|
+
|
|
2494
|
+
// Now use the configured service
|
|
2495
|
+
const result = await searchDomain({
|
|
2496
|
+
domain_name: "myproject",
|
|
2497
|
+
tlds: ["com", "io", "dev"]
|
|
2498
|
+
});
|
|
2499
|
+
```
|
|
2500
|
+
|
|
2501
|
+
#### Complete Initialization Module Pattern
|
|
2502
|
+
|
|
2503
|
+
```typescript
|
|
2504
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2505
|
+
// domain-search-init.ts - Reusable initialization module
|
|
2506
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2507
|
+
|
|
2508
|
+
import { searchDomain, tldInfo, checkSocials, compareRegistrars } from 'domain-search-mcp';
|
|
2509
|
+
|
|
2510
|
+
// Configuration types
|
|
2511
|
+
interface RegistrarCredentials {
|
|
2512
|
+
type: 'porkbun' | 'namecheap';
|
|
2513
|
+
apiKey: string;
|
|
2514
|
+
apiSecret?: string; // Required for Porkbun
|
|
2515
|
+
apiUser?: string; // Required for Namecheap
|
|
2516
|
+
clientIp?: string; // Optional for Namecheap
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
interface InitializationResult {
|
|
2520
|
+
success: boolean;
|
|
2521
|
+
activeSource: 'porkbun_api' | 'namecheap_api' | 'godaddy_mcp' | 'rdap' | 'whois';
|
|
2522
|
+
pricingAvailable: boolean;
|
|
2523
|
+
estimatedResponseTime: string;
|
|
2524
|
+
errors: string[];
|
|
2525
|
+
warnings: string[];
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
/**
|
|
2529
|
+
* Initialize Domain Search MCP with full validation
|
|
2530
|
+
*
|
|
2531
|
+
* @param credentials - Optional registrar credentials
|
|
2532
|
+
* @returns Initialization result with status and diagnostics
|
|
2533
|
+
*/
|
|
2534
|
+
async function initializeDomainSearch(
|
|
2535
|
+
credentials?: RegistrarCredentials[]
|
|
2536
|
+
): Promise<InitializationResult> {
|
|
2537
|
+
const result: InitializationResult = {
|
|
2538
|
+
success: false,
|
|
2539
|
+
activeSource: 'rdap',
|
|
2540
|
+
pricingAvailable: false,
|
|
2541
|
+
estimatedResponseTime: '2-5 seconds',
|
|
2542
|
+
errors: [],
|
|
2543
|
+
warnings: []
|
|
2544
|
+
};
|
|
2545
|
+
|
|
2546
|
+
// Step 1: Set environment variables from credentials
|
|
2547
|
+
if (credentials) {
|
|
2548
|
+
for (const cred of credentials) {
|
|
2549
|
+
if (cred.type === 'porkbun') {
|
|
2550
|
+
if (!cred.apiKey || !cred.apiSecret) {
|
|
2551
|
+
result.errors.push('Porkbun requires both apiKey and apiSecret');
|
|
2552
|
+
continue;
|
|
2553
|
+
}
|
|
2554
|
+
process.env.PORKBUN_API_KEY = cred.apiKey;
|
|
2555
|
+
process.env.PORKBUN_API_SECRET = cred.apiSecret;
|
|
2556
|
+
} else if (cred.type === 'namecheap') {
|
|
2557
|
+
if (!cred.apiKey || !cred.apiUser) {
|
|
2558
|
+
result.errors.push('Namecheap requires both apiKey and apiUser');
|
|
2559
|
+
continue;
|
|
2560
|
+
}
|
|
2561
|
+
process.env.NAMECHEAP_API_KEY = cred.apiKey;
|
|
2562
|
+
process.env.NAMECHEAP_API_USER = cred.apiUser;
|
|
2563
|
+
if (cred.clientIp) {
|
|
2564
|
+
process.env.NAMECHEAP_CLIENT_IP = cred.clientIp;
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
// Step 2: Run validation search
|
|
2571
|
+
try {
|
|
2572
|
+
const testResult = await searchDomain({
|
|
2573
|
+
domain_name: `init-test-${Date.now()}`,
|
|
2574
|
+
tlds: ['com']
|
|
2575
|
+
});
|
|
2576
|
+
|
|
2577
|
+
if (testResult.results && testResult.results.length > 0) {
|
|
2578
|
+
const source = testResult.results[0].source;
|
|
2579
|
+
result.activeSource = source as InitializationResult['activeSource'];
|
|
2580
|
+
result.pricingAvailable = testResult.results[0].price_first_year !== null;
|
|
2581
|
+
|
|
2582
|
+
// Set response time estimate based on source
|
|
2583
|
+
switch (source) {
|
|
2584
|
+
case 'porkbun_api':
|
|
2585
|
+
result.estimatedResponseTime = '100-200ms';
|
|
2586
|
+
break;
|
|
2587
|
+
case 'namecheap_api':
|
|
2588
|
+
result.estimatedResponseTime = '150-300ms';
|
|
2589
|
+
break;
|
|
2590
|
+
case 'godaddy_mcp':
|
|
2591
|
+
result.estimatedResponseTime = '200-400ms';
|
|
2592
|
+
break;
|
|
2593
|
+
default:
|
|
2594
|
+
result.estimatedResponseTime = '2-5 seconds';
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
result.success = true;
|
|
2598
|
+
}
|
|
2599
|
+
} catch (error) {
|
|
2600
|
+
result.errors.push(`Validation failed: ${error.message}`);
|
|
2601
|
+
|
|
2602
|
+
// Provide specific remediation based on error
|
|
2603
|
+
if (error.message.includes('AUTH') || error.message.includes('401')) {
|
|
2604
|
+
result.errors.push('API key authentication failed. Check your credentials.');
|
|
2605
|
+
}
|
|
2606
|
+
if (error.message.includes('IP') || error.message.includes('whitelist')) {
|
|
2607
|
+
result.errors.push('IP not whitelisted for Namecheap. Add your IP at https://ap.www.namecheap.com/settings/tools/apiaccess');
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
// Step 3: Add warnings for suboptimal configuration
|
|
2612
|
+
if (!result.pricingAvailable) {
|
|
2613
|
+
result.warnings.push(
|
|
2614
|
+
'Pricing data not available. Add Porkbun API keys for pricing information.'
|
|
2615
|
+
);
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
if (result.activeSource === 'rdap' || result.activeSource === 'whois') {
|
|
2619
|
+
result.warnings.push(
|
|
2620
|
+
'Using fallback source. Response times will be 10-20x slower than with API keys.'
|
|
2621
|
+
);
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
return result;
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2628
|
+
// USAGE EXAMPLE: Complete Application Bootstrap
|
|
2629
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2630
|
+
|
|
2631
|
+
async function bootstrapApplication() {
|
|
2632
|
+
console.log('Initializing Domain Search MCP...');
|
|
2633
|
+
console.log('─'.repeat(60));
|
|
2634
|
+
|
|
2635
|
+
// Initialize with credentials (or empty array for fallback mode)
|
|
2636
|
+
const initResult = await initializeDomainSearch([
|
|
2637
|
+
{
|
|
2638
|
+
type: 'porkbun',
|
|
2639
|
+
apiKey: process.env.PORKBUN_API_KEY || '',
|
|
2640
|
+
apiSecret: process.env.PORKBUN_API_SECRET || ''
|
|
2641
|
+
}
|
|
2642
|
+
]);
|
|
2643
|
+
|
|
2644
|
+
// Report initialization status
|
|
2645
|
+
console.log(`\nInitialization ${initResult.success ? '✅ SUCCESS' : '❌ FAILED'}`);
|
|
2646
|
+
console.log(`Active source: ${initResult.activeSource}`);
|
|
2647
|
+
console.log(`Pricing available: ${initResult.pricingAvailable ? 'Yes' : 'No'}`);
|
|
2648
|
+
console.log(`Expected response time: ${initResult.estimatedResponseTime}`);
|
|
2649
|
+
|
|
2650
|
+
if (initResult.errors.length > 0) {
|
|
2651
|
+
console.log('\n❌ Errors:');
|
|
2652
|
+
initResult.errors.forEach(e => console.log(` - ${e}`));
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
if (initResult.warnings.length > 0) {
|
|
2656
|
+
console.log('\n⚠️ Warnings:');
|
|
2657
|
+
initResult.warnings.forEach(w => console.log(` - ${w}`));
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
if (!initResult.success) {
|
|
2661
|
+
throw new Error('Domain Search MCP initialization failed');
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2664
|
+
console.log('\n' + '─'.repeat(60));
|
|
2665
|
+
console.log('Ready to search domains!\n');
|
|
2666
|
+
|
|
2667
|
+
return initResult;
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
// Bootstrap the application
|
|
2671
|
+
bootstrapApplication()
|
|
2672
|
+
.then(() => {
|
|
2673
|
+
// Application is ready, proceed with domain operations
|
|
2674
|
+
console.log('Application started successfully');
|
|
2675
|
+
})
|
|
2676
|
+
.catch((error) => {
|
|
2677
|
+
console.error('Failed to start application:', error.message);
|
|
2678
|
+
process.exit(1);
|
|
2679
|
+
});
|
|
2680
|
+
```
|
|
2681
|
+
|
|
2682
|
+
#### Invalid API Key Error Handling Patterns
|
|
2683
|
+
|
|
2684
|
+
```typescript
|
|
2685
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2686
|
+
// COMPREHENSIVE API KEY ERROR HANDLING
|
|
2687
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2688
|
+
|
|
2689
|
+
import { searchDomain, DomainSearchError } from 'domain-search-mcp';
|
|
2690
|
+
|
|
2691
|
+
// Error codes returned by Domain Search MCP
|
|
2692
|
+
type DomainSearchErrorCode =
|
|
2693
|
+
| 'AUTH_ERROR' // Invalid API key or secret
|
|
2694
|
+
| 'API_KEY_EXPIRED' // API key has expired
|
|
2695
|
+
| 'IP_NOT_WHITELISTED' // Namecheap IP whitelist issue
|
|
2696
|
+
| 'RATE_LIMIT_EXCEEDED' // Too many requests
|
|
2697
|
+
| 'NETWORK_ERROR' // Connection failed
|
|
2698
|
+
| 'TIMEOUT' // Request timed out
|
|
2699
|
+
| 'INVALID_TLD' // TLD not supported
|
|
2700
|
+
| 'INVALID_DOMAIN' // Domain name format invalid
|
|
2701
|
+
| 'REGISTRAR_UNAVAILABLE'; // Registrar API down
|
|
2702
|
+
|
|
2703
|
+
interface ErrorRecoveryAction {
|
|
2704
|
+
action: 'retry' | 'fallback' | 'abort' | 'reconfigure';
|
|
2705
|
+
delay?: number; // ms to wait before retry
|
|
2706
|
+
fallbackRegistrars?: string[]; // Alternative registrars to try
|
|
2707
|
+
message: string; // User-friendly message
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
/**
|
|
2711
|
+
* Determine recovery action for API key errors
|
|
2712
|
+
*/
|
|
2713
|
+
function getRecoveryAction(error: DomainSearchError): ErrorRecoveryAction {
|
|
2714
|
+
switch (error.code) {
|
|
2715
|
+
case 'AUTH_ERROR':
|
|
2716
|
+
return {
|
|
2717
|
+
action: 'reconfigure',
|
|
2718
|
+
message: `Authentication failed for ${error.registrar}. ` +
|
|
2719
|
+
(error.registrar === 'porkbun'
|
|
2720
|
+
? 'Verify PORKBUN_API_KEY and PORKBUN_API_SECRET at https://porkbun.com/account/api'
|
|
2721
|
+
: 'Verify NAMECHEAP_API_KEY and check IP whitelist at https://ap.www.namecheap.com/settings/tools/apiaccess')
|
|
2722
|
+
};
|
|
2723
|
+
|
|
2724
|
+
case 'API_KEY_EXPIRED':
|
|
2725
|
+
return {
|
|
2726
|
+
action: 'reconfigure',
|
|
2727
|
+
message: `API key expired for ${error.registrar}. Generate new keys from registrar dashboard.`
|
|
2728
|
+
};
|
|
2729
|
+
|
|
2730
|
+
case 'IP_NOT_WHITELISTED':
|
|
2731
|
+
return {
|
|
2732
|
+
action: 'fallback',
|
|
2733
|
+
fallbackRegistrars: ['porkbun', 'godaddy'], // Exclude Namecheap
|
|
2734
|
+
message: `IP ${error.detectedIp} not whitelisted for Namecheap. Falling back to other sources.`
|
|
2735
|
+
};
|
|
2736
|
+
|
|
2737
|
+
case 'RATE_LIMIT_EXCEEDED':
|
|
2738
|
+
return {
|
|
2739
|
+
action: 'retry',
|
|
2740
|
+
delay: error.retryAfter || 60000, // Default 60 seconds
|
|
2741
|
+
message: `Rate limit exceeded. Retrying in ${(error.retryAfter || 60000) / 1000}s.`
|
|
2742
|
+
};
|
|
2743
|
+
|
|
2744
|
+
case 'NETWORK_ERROR':
|
|
2745
|
+
case 'TIMEOUT':
|
|
2746
|
+
return {
|
|
2747
|
+
action: 'retry',
|
|
2748
|
+
delay: 5000,
|
|
2749
|
+
message: `Network issue. Retrying in 5 seconds.`
|
|
2750
|
+
};
|
|
2751
|
+
|
|
2752
|
+
case 'REGISTRAR_UNAVAILABLE':
|
|
2753
|
+
return {
|
|
2754
|
+
action: 'fallback',
|
|
2755
|
+
fallbackRegistrars: error.registrar === 'porkbun'
|
|
2756
|
+
? ['namecheap', 'godaddy']
|
|
2757
|
+
: ['porkbun', 'godaddy'],
|
|
2758
|
+
message: `${error.registrar} API unavailable. Using fallback registrars.`
|
|
2759
|
+
};
|
|
2760
|
+
|
|
2761
|
+
default:
|
|
2762
|
+
return {
|
|
2763
|
+
action: 'abort',
|
|
2764
|
+
message: `Unrecoverable error: ${error.message}`
|
|
2765
|
+
};
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
/**
|
|
2770
|
+
* Execute domain search with full error recovery
|
|
2771
|
+
*/
|
|
2772
|
+
async function searchWithErrorRecovery(
|
|
2773
|
+
domainName: string,
|
|
2774
|
+
tlds: string[],
|
|
2775
|
+
maxRetries: number = 3
|
|
2776
|
+
): Promise<SearchResult> {
|
|
2777
|
+
let lastError: DomainSearchError | null = null;
|
|
2778
|
+
let currentRegistrars: string[] | undefined = undefined; // Auto-select initially
|
|
2779
|
+
|
|
2780
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
2781
|
+
try {
|
|
2782
|
+
const result = await searchDomain({
|
|
2783
|
+
domain_name: domainName,
|
|
2784
|
+
tlds,
|
|
2785
|
+
registrars: currentRegistrars
|
|
2786
|
+
});
|
|
2787
|
+
return result;
|
|
2788
|
+
|
|
2789
|
+
} catch (error) {
|
|
2790
|
+
lastError = error as DomainSearchError;
|
|
2791
|
+
const recovery = getRecoveryAction(lastError);
|
|
2792
|
+
|
|
2793
|
+
console.log(`Attempt ${attempt}/${maxRetries}: ${recovery.message}`);
|
|
2794
|
+
|
|
2795
|
+
switch (recovery.action) {
|
|
2796
|
+
case 'retry':
|
|
2797
|
+
if (attempt < maxRetries && recovery.delay) {
|
|
2798
|
+
await new Promise(resolve => setTimeout(resolve, recovery.delay));
|
|
2799
|
+
}
|
|
2800
|
+
break;
|
|
2801
|
+
|
|
2802
|
+
case 'fallback':
|
|
2803
|
+
if (recovery.fallbackRegistrars) {
|
|
2804
|
+
currentRegistrars = recovery.fallbackRegistrars;
|
|
2805
|
+
console.log(`Switching to fallback registrars: ${currentRegistrars.join(', ')}`);
|
|
2806
|
+
}
|
|
2807
|
+
break;
|
|
2808
|
+
|
|
2809
|
+
case 'reconfigure':
|
|
2810
|
+
// Log the issue and try without the problematic registrar
|
|
2811
|
+
console.error(recovery.message);
|
|
2812
|
+
if (lastError.registrar === 'porkbun') {
|
|
2813
|
+
currentRegistrars = ['namecheap', 'godaddy'];
|
|
2814
|
+
} else if (lastError.registrar === 'namecheap') {
|
|
2815
|
+
currentRegistrars = ['porkbun', 'godaddy'];
|
|
2816
|
+
}
|
|
2817
|
+
break;
|
|
2818
|
+
|
|
2819
|
+
case 'abort':
|
|
2820
|
+
throw new Error(`Search failed: ${recovery.message}`);
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
|
|
2825
|
+
throw lastError || new Error('Search failed after maximum retries');
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2829
|
+
// USAGE EXAMPLE
|
|
2830
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2831
|
+
|
|
2832
|
+
async function main() {
|
|
2833
|
+
try {
|
|
2834
|
+
const result = await searchWithErrorRecovery('myproject', ['com', 'io', 'dev']);
|
|
2835
|
+
console.log('Search completed successfully:', result.results.length, 'results');
|
|
2836
|
+
} catch (error) {
|
|
2837
|
+
console.error('Search failed:', error.message);
|
|
2838
|
+
// Provide user guidance based on error
|
|
2839
|
+
console.log('\nTroubleshooting steps:');
|
|
2840
|
+
console.log('1. Check your .env file for correct API keys');
|
|
2841
|
+
console.log('2. Verify your IP is whitelisted (for Namecheap)');
|
|
2842
|
+
console.log('3. Try running without API keys to use RDAP/WHOIS fallback');
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
main();
|
|
2847
|
+
```
|
|
2848
|
+
|
|
2849
|
+
### API Keys Benefits Comparison
|
|
2850
|
+
|
|
2851
|
+
Detailed comparison of using API keys vs RDAP/WHOIS fallback:
|
|
2852
|
+
|
|
2853
|
+
| Feature | With Porkbun API | With Namecheap API | RDAP/WHOIS Fallback |
|
|
2854
|
+
|---------|------------------|-------------------|---------------------|
|
|
2855
|
+
| **Setup Time** | 2 minutes | 10+ minutes | None |
|
|
2856
|
+
| **Cost** | Free | Free | Free |
|
|
2857
|
+
| **Response Time** | 100-200ms | 150-300ms | 2-5 seconds |
|
|
2858
|
+
| **Rate Limit** | 1000+ req/min | 500+ req/min | 10-50 req/min |
|
|
2859
|
+
| **Pricing Data** | ✅ Full | ✅ Full | ❌ Not available |
|
|
2860
|
+
| **Renewal Prices** | ✅ Yes | ✅ Yes | ❌ No |
|
|
2861
|
+
| **WHOIS Privacy Info** | ✅ Yes | ✅ Yes | ❌ No |
|
|
2862
|
+
| **Bulk Operations** | 100 domains | 100 domains | ~50 domains max |
|
|
2863
|
+
| **TLD Coverage** | All major TLDs | All major TLDs | Varies by TLD |
|
|
2864
|
+
| **Reliability** | 99.9% | 99.5% | 85-95% |
|
|
2865
|
+
| **IP Whitelist Required** | ❌ No | ✅ Yes | ❌ No |
|
|
2866
|
+
| **Account Required** | Free account | Account + domain/$50 | None |
|
|
2867
|
+
|
|
2868
|
+
**Decision Matrix:**
|
|
2869
|
+
|
|
2870
|
+
```
|
|
2871
|
+
Need pricing data?
|
|
2872
|
+
├── YES → Use Porkbun API (fastest setup)
|
|
2873
|
+
└── NO → RDAP/WHOIS is sufficient
|
|
2874
|
+
|
|
2875
|
+
Need price comparison?
|
|
2876
|
+
├── YES → Configure BOTH Porkbun + Namecheap
|
|
2877
|
+
└── NO → Porkbun only
|
|
2878
|
+
|
|
2879
|
+
High-volume usage (>50 req/min)?
|
|
2880
|
+
├── YES → API keys required
|
|
2881
|
+
└── NO → Either works
|
|
2882
|
+
|
|
2883
|
+
Development/testing?
|
|
2884
|
+
├── YES → RDAP/WHOIS is fine
|
|
2885
|
+
└── NO → Production should use API keys
|
|
2886
|
+
```
|
|
2887
|
+
|
|
2381
2888
|
### IDE Integration
|
|
2382
2889
|
|
|
2383
2890
|
#### Cursor
|
|
@@ -4745,32 +5252,145 @@ report.availableDomains.slice(0, 5).forEach(d =>
|
|
|
4745
5252
|
|
|
4746
5253
|
### Workflow 8: Domain Research Pipeline
|
|
4747
5254
|
|
|
4748
|
-
Comprehensive domain research combining
|
|
5255
|
+
Comprehensive domain research for startups combining `search_domain`, `tld_info`, and `suggest_domains`.
|
|
5256
|
+
|
|
5257
|
+
#### Complete Implementation with Explicit Imports
|
|
4749
5258
|
|
|
4750
5259
|
```typescript
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
5260
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
5261
|
+
// DOMAIN RESEARCH PIPELINE FOR STARTUPS
|
|
5262
|
+
// Combines search_domain, tld_info, and suggest_domains for comprehensive reports
|
|
5263
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
5264
|
+
|
|
5265
|
+
// Required imports from domain-search-mcp
|
|
5266
|
+
import {
|
|
5267
|
+
searchDomain,
|
|
5268
|
+
suggestDomains,
|
|
5269
|
+
suggestDomainsSmart,
|
|
5270
|
+
tldInfo,
|
|
5271
|
+
checkSocials,
|
|
5272
|
+
compareRegistrars,
|
|
5273
|
+
bulkSearch
|
|
5274
|
+
} from 'domain-search-mcp';
|
|
5275
|
+
|
|
5276
|
+
// Type definitions for pipeline results
|
|
5277
|
+
import type {
|
|
5278
|
+
SearchDomainResponse,
|
|
5279
|
+
TldInfoResponse,
|
|
5280
|
+
SuggestDomainsResponse,
|
|
5281
|
+
CheckSocialsResponse,
|
|
5282
|
+
CompareRegistrarsResponse
|
|
5283
|
+
} from 'domain-search-mcp';
|
|
5284
|
+
|
|
5285
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
5286
|
+
// TLD_INFO API STRUCTURE (Complete Reference)
|
|
5287
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
5288
|
+
|
|
5289
|
+
/**
|
|
5290
|
+
* Complete TldInfoResponse interface as returned by tld_info tool
|
|
5291
|
+
* This is the exact structure you receive when calling tldInfo()
|
|
5292
|
+
*/
|
|
5293
|
+
interface TldInfoResponse {
|
|
5294
|
+
// Core fields (always present)
|
|
5295
|
+
tld: string; // e.g., "io", "com", "dev"
|
|
5296
|
+
description: string; // e.g., "Popular with tech startups"
|
|
5297
|
+
typical_use: string; // e.g., "Tech startups, SaaS products"
|
|
5298
|
+
price_range: {
|
|
5299
|
+
min: number; // Minimum price in USD (e.g., 29.88)
|
|
5300
|
+
max: number; // Maximum price in USD (e.g., 59.99)
|
|
5301
|
+
currency: "USD"; // Always USD
|
|
5302
|
+
};
|
|
5303
|
+
restrictions: string[]; // e.g., ["Requires HTTPS"] or []
|
|
5304
|
+
popularity: "high" | "medium" | "low";
|
|
5305
|
+
recommendation: string; // Usage recommendation text
|
|
5306
|
+
|
|
5307
|
+
// Extended fields (only when detailed: true)
|
|
5308
|
+
registry?: string; // e.g., "Internet Computer Bureau"
|
|
5309
|
+
introduced?: number; // Year introduced, e.g., 1997
|
|
5310
|
+
type?: "gTLD" | "ccTLD" | "newTLD";
|
|
5311
|
+
dnssec_required?: boolean;
|
|
5312
|
+
whois_server?: string; // e.g., "whois.nic.io"
|
|
5313
|
+
rdap_server?: string; // e.g., "https://rdap.nic.io/domain/"
|
|
5314
|
+
}
|
|
5315
|
+
|
|
5316
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
5317
|
+
// PIPELINE IMPLEMENTATION
|
|
5318
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
5319
|
+
|
|
5320
|
+
interface StartupDomainReport {
|
|
5321
|
+
businessIdea: string;
|
|
5322
|
+
tldAnalysis: {
|
|
5323
|
+
tld: string;
|
|
5324
|
+
description: string;
|
|
5325
|
+
priceRange: { min: number; max: number };
|
|
5326
|
+
recommendation: string;
|
|
5327
|
+
restrictions: string[];
|
|
5328
|
+
popularity: string;
|
|
5329
|
+
};
|
|
5330
|
+
topRecommendations: Array<{
|
|
5331
|
+
domain: string;
|
|
5332
|
+
price: number | null;
|
|
5333
|
+
bestRegistrar: string;
|
|
5334
|
+
socialAvailability: string;
|
|
5335
|
+
}>;
|
|
5336
|
+
allSuggestions: string[];
|
|
5337
|
+
researchTimestamp: string;
|
|
5338
|
+
}
|
|
5339
|
+
|
|
5340
|
+
/**
|
|
5341
|
+
* Execute complete domain research pipeline for startups
|
|
5342
|
+
*
|
|
5343
|
+
* @param businessIdea - Description of the startup or business
|
|
5344
|
+
* @returns Comprehensive domain research report
|
|
5345
|
+
*/
|
|
5346
|
+
async function domainResearchPipeline(businessIdea: string): Promise<StartupDomainReport> {
|
|
5347
|
+
console.log(`Starting domain research for: "${businessIdea}"`);
|
|
5348
|
+
|
|
5349
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
5350
|
+
// Step 1: Get TLD information FIRST (provides context for decisions)
|
|
5351
|
+
// Using tld_info tool directly with detailed: true for full data
|
|
5352
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
5353
|
+
|
|
5354
|
+
const tldData: TldInfoResponse = await tldInfo({
|
|
5355
|
+
tld: "com",
|
|
5356
|
+
detailed: true // Get registry, whois_server, etc.
|
|
5357
|
+
});
|
|
5358
|
+
|
|
5359
|
+
console.log(`TLD context: ${tldData.tld} - ${tldData.description}`);
|
|
5360
|
+
console.log(`Price range: $${tldData.price_range.min}-$${tldData.price_range.max}`);
|
|
5361
|
+
|
|
5362
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
5363
|
+
// Step 2: Generate smart domain suggestions using suggest_domains
|
|
5364
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
5365
|
+
|
|
5366
|
+
const suggestions: SuggestDomainsResponse = await suggestDomainsSmart({
|
|
4754
5367
|
query: businessIdea,
|
|
4755
5368
|
tld: "com",
|
|
4756
5369
|
style: "brandable",
|
|
4757
|
-
|
|
5370
|
+
industry: "tech",
|
|
5371
|
+
max_suggestions: 15,
|
|
5372
|
+
include_premium: false
|
|
4758
5373
|
});
|
|
4759
5374
|
|
|
4760
|
-
|
|
4761
|
-
const tldInfo = await getTldInfo({ tld: "com", detailed: true });
|
|
5375
|
+
console.log(`Generated ${suggestions.results.available.length} available suggestions`);
|
|
4762
5376
|
|
|
5377
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
4763
5378
|
// Step 3: For top suggestions, compare registrar pricing
|
|
5379
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
5380
|
+
|
|
4764
5381
|
const topDomains = suggestions.results.available.slice(0, 5);
|
|
4765
|
-
const priceComparisons = await Promise.all(
|
|
5382
|
+
const priceComparisons: CompareRegistrarsResponse[] = await Promise.all(
|
|
4766
5383
|
topDomains.map(d => compareRegistrars({
|
|
4767
5384
|
domain: d.domain.replace('.com', ''),
|
|
4768
5385
|
tld: "com"
|
|
4769
5386
|
}))
|
|
4770
5387
|
);
|
|
4771
5388
|
|
|
4772
|
-
//
|
|
4773
|
-
|
|
5389
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
5390
|
+
// Step 4: Check social media availability for top picks
|
|
5391
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
5392
|
+
|
|
5393
|
+
const socialChecks: CheckSocialsResponse[] = await Promise.all(
|
|
4774
5394
|
topDomains.slice(0, 3).map(d => {
|
|
4775
5395
|
const name = d.domain.replace('.com', '');
|
|
4776
5396
|
return checkSocials({
|
|
@@ -4780,27 +5400,272 @@ async function domainResearchPipeline(businessIdea: string) {
|
|
|
4780
5400
|
})
|
|
4781
5401
|
);
|
|
4782
5402
|
|
|
4783
|
-
//
|
|
5403
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
5404
|
+
// Step 5: Compile final research report
|
|
5405
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
5406
|
+
|
|
4784
5407
|
return {
|
|
4785
5408
|
businessIdea,
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
5409
|
+
tldAnalysis: {
|
|
5410
|
+
tld: tldData.tld,
|
|
5411
|
+
description: tldData.description,
|
|
5412
|
+
priceRange: {
|
|
5413
|
+
min: tldData.price_range.min,
|
|
5414
|
+
max: tldData.price_range.max
|
|
5415
|
+
},
|
|
5416
|
+
recommendation: tldData.recommendation,
|
|
5417
|
+
restrictions: tldData.restrictions,
|
|
5418
|
+
popularity: tldData.popularity
|
|
4790
5419
|
},
|
|
4791
5420
|
topRecommendations: topDomains.map((d, i) => ({
|
|
4792
5421
|
domain: d.domain,
|
|
4793
5422
|
price: d.price_first_year,
|
|
4794
|
-
bestRegistrar: priceComparisons[i]?.best_first_year?.registrar,
|
|
4795
|
-
socialAvailability: socialChecks[i]?.summary
|
|
5423
|
+
bestRegistrar: priceComparisons[i]?.best_first_year?.registrar || 'unknown',
|
|
5424
|
+
socialAvailability: socialChecks[i]?.summary || 'not checked'
|
|
4796
5425
|
})),
|
|
4797
|
-
allSuggestions: suggestions.results.available,
|
|
4798
|
-
|
|
5426
|
+
allSuggestions: suggestions.results.available.map(d => d.domain),
|
|
5427
|
+
researchTimestamp: new Date().toISOString()
|
|
4799
5428
|
};
|
|
4800
5429
|
}
|
|
4801
5430
|
|
|
4802
|
-
//
|
|
4803
|
-
|
|
5431
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
5432
|
+
// USAGE EXAMPLE WITH FULL OUTPUT
|
|
5433
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
5434
|
+
|
|
5435
|
+
async function main() {
|
|
5436
|
+
const report = await domainResearchPipeline("ai-powered code review tool");
|
|
5437
|
+
|
|
5438
|
+
console.log("\n" + "═".repeat(60));
|
|
5439
|
+
console.log("DOMAIN RESEARCH REPORT");
|
|
5440
|
+
console.log("═".repeat(60));
|
|
5441
|
+
|
|
5442
|
+
console.log(`\nBusiness: ${report.businessIdea}`);
|
|
5443
|
+
|
|
5444
|
+
console.log(`\n📊 TLD Analysis (.${report.tldAnalysis.tld}):`);
|
|
5445
|
+
console.log(` ${report.tldAnalysis.description}`);
|
|
5446
|
+
console.log(` Price: $${report.tldAnalysis.priceRange.min}-$${report.tldAnalysis.priceRange.max}/yr`);
|
|
5447
|
+
console.log(` Popularity: ${report.tldAnalysis.popularity}`);
|
|
5448
|
+
|
|
5449
|
+
console.log(`\n🏆 Top Recommendations:`);
|
|
5450
|
+
report.topRecommendations.forEach((rec, i) => {
|
|
5451
|
+
console.log(` ${i + 1}. ${rec.domain}`);
|
|
5452
|
+
console.log(` Price: $${rec.price}/yr via ${rec.bestRegistrar}`);
|
|
5453
|
+
console.log(` Social: ${rec.socialAvailability}`);
|
|
5454
|
+
});
|
|
5455
|
+
|
|
5456
|
+
console.log(`\n📝 All Suggestions (${report.allSuggestions.length} domains):`);
|
|
5457
|
+
console.log(` ${report.allSuggestions.slice(0, 10).join(', ')}...`);
|
|
5458
|
+
}
|
|
5459
|
+
|
|
5460
|
+
main().catch(console.error);
|
|
5461
|
+
```
|
|
5462
|
+
|
|
5463
|
+
#### Rate Limiting and Retry Logic for Production
|
|
5464
|
+
|
|
5465
|
+
```typescript
|
|
5466
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
5467
|
+
// PRODUCTION-READY DOMAIN RESEARCH WITH RATE LIMITING
|
|
5468
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
5469
|
+
|
|
5470
|
+
import {
|
|
5471
|
+
searchDomain,
|
|
5472
|
+
tldInfo,
|
|
5473
|
+
suggestDomains,
|
|
5474
|
+
checkSocials,
|
|
5475
|
+
compareRegistrars
|
|
5476
|
+
} from 'domain-search-mcp';
|
|
5477
|
+
|
|
5478
|
+
// Rate limiter configuration
|
|
5479
|
+
interface RateLimiterConfig {
|
|
5480
|
+
maxRequestsPerMinute: number;
|
|
5481
|
+
maxConcurrent: number;
|
|
5482
|
+
retryAttempts: number;
|
|
5483
|
+
retryDelayMs: number;
|
|
5484
|
+
backoffMultiplier: number;
|
|
5485
|
+
}
|
|
5486
|
+
|
|
5487
|
+
const DEFAULT_RATE_CONFIG: RateLimiterConfig = {
|
|
5488
|
+
maxRequestsPerMinute: 50, // Conservative limit for production
|
|
5489
|
+
maxConcurrent: 5, // Max parallel requests
|
|
5490
|
+
retryAttempts: 3, // Number of retries on failure
|
|
5491
|
+
retryDelayMs: 1000, // Initial retry delay
|
|
5492
|
+
backoffMultiplier: 2 // Exponential backoff multiplier
|
|
5493
|
+
};
|
|
5494
|
+
|
|
5495
|
+
/**
|
|
5496
|
+
* Token bucket rate limiter for API calls
|
|
5497
|
+
*/
|
|
5498
|
+
class RateLimiter {
|
|
5499
|
+
private tokens: number;
|
|
5500
|
+
private lastRefill: number;
|
|
5501
|
+
private readonly config: RateLimiterConfig;
|
|
5502
|
+
|
|
5503
|
+
constructor(config: RateLimiterConfig = DEFAULT_RATE_CONFIG) {
|
|
5504
|
+
this.config = config;
|
|
5505
|
+
this.tokens = config.maxRequestsPerMinute;
|
|
5506
|
+
this.lastRefill = Date.now();
|
|
5507
|
+
}
|
|
5508
|
+
|
|
5509
|
+
async acquire(): Promise<void> {
|
|
5510
|
+
// Refill tokens based on time elapsed
|
|
5511
|
+
const now = Date.now();
|
|
5512
|
+
const elapsed = now - this.lastRefill;
|
|
5513
|
+
const refill = Math.floor(elapsed / 60000 * this.config.maxRequestsPerMinute);
|
|
5514
|
+
|
|
5515
|
+
if (refill > 0) {
|
|
5516
|
+
this.tokens = Math.min(this.config.maxRequestsPerMinute, this.tokens + refill);
|
|
5517
|
+
this.lastRefill = now;
|
|
5518
|
+
}
|
|
5519
|
+
|
|
5520
|
+
// Wait if no tokens available
|
|
5521
|
+
if (this.tokens <= 0) {
|
|
5522
|
+
const waitTime = 60000 / this.config.maxRequestsPerMinute;
|
|
5523
|
+
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
5524
|
+
return this.acquire(); // Retry after waiting
|
|
5525
|
+
}
|
|
5526
|
+
|
|
5527
|
+
this.tokens--;
|
|
5528
|
+
}
|
|
5529
|
+
}
|
|
5530
|
+
|
|
5531
|
+
/**
|
|
5532
|
+
* Execute function with retry and exponential backoff
|
|
5533
|
+
*/
|
|
5534
|
+
async function withRetry<T>(
|
|
5535
|
+
fn: () => Promise<T>,
|
|
5536
|
+
config: RateLimiterConfig = DEFAULT_RATE_CONFIG,
|
|
5537
|
+
attempt: number = 1
|
|
5538
|
+
): Promise<T> {
|
|
5539
|
+
try {
|
|
5540
|
+
return await fn();
|
|
5541
|
+
} catch (error) {
|
|
5542
|
+
if (attempt >= config.retryAttempts) {
|
|
5543
|
+
throw error;
|
|
5544
|
+
}
|
|
5545
|
+
|
|
5546
|
+
// Check if error is retryable
|
|
5547
|
+
const isRetryable = error.code === 'RATE_LIMIT_EXCEEDED' ||
|
|
5548
|
+
error.code === 'NETWORK_ERROR' ||
|
|
5549
|
+
error.code === 'TIMEOUT';
|
|
5550
|
+
|
|
5551
|
+
if (!isRetryable) {
|
|
5552
|
+
throw error;
|
|
5553
|
+
}
|
|
5554
|
+
|
|
5555
|
+
// Calculate backoff delay
|
|
5556
|
+
const delay = config.retryDelayMs * Math.pow(config.backoffMultiplier, attempt - 1);
|
|
5557
|
+
console.log(`Retry ${attempt}/${config.retryAttempts} after ${delay}ms...`);
|
|
5558
|
+
|
|
5559
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
5560
|
+
return withRetry(fn, config, attempt + 1);
|
|
5561
|
+
}
|
|
5562
|
+
}
|
|
5563
|
+
|
|
5564
|
+
/**
|
|
5565
|
+
* Production-ready domain research pipeline with rate limiting
|
|
5566
|
+
*/
|
|
5567
|
+
async function productionDomainResearch(
|
|
5568
|
+
businessIdea: string,
|
|
5569
|
+
rateConfig: RateLimiterConfig = DEFAULT_RATE_CONFIG
|
|
5570
|
+
): Promise<StartupDomainReport> {
|
|
5571
|
+
const rateLimiter = new RateLimiter(rateConfig);
|
|
5572
|
+
|
|
5573
|
+
// Step 1: Get TLD info with rate limiting
|
|
5574
|
+
await rateLimiter.acquire();
|
|
5575
|
+
const tldData = await withRetry(
|
|
5576
|
+
() => tldInfo({ tld: "com", detailed: true }),
|
|
5577
|
+
rateConfig
|
|
5578
|
+
);
|
|
5579
|
+
|
|
5580
|
+
// Step 2: Generate suggestions with rate limiting
|
|
5581
|
+
await rateLimiter.acquire();
|
|
5582
|
+
const suggestions = await withRetry(
|
|
5583
|
+
() => suggestDomainsSmart({
|
|
5584
|
+
query: businessIdea,
|
|
5585
|
+
tld: "com",
|
|
5586
|
+
style: "brandable",
|
|
5587
|
+
max_suggestions: 15
|
|
5588
|
+
}),
|
|
5589
|
+
rateConfig
|
|
5590
|
+
);
|
|
5591
|
+
|
|
5592
|
+
// Step 3: Rate-limited parallel operations
|
|
5593
|
+
const topDomains = suggestions.results.available.slice(0, 5);
|
|
5594
|
+
|
|
5595
|
+
// Process in batches respecting concurrency limit
|
|
5596
|
+
const batchSize = rateConfig.maxConcurrent;
|
|
5597
|
+
const priceResults: CompareRegistrarsResponse[] = [];
|
|
5598
|
+
|
|
5599
|
+
for (let i = 0; i < topDomains.length; i += batchSize) {
|
|
5600
|
+
const batch = topDomains.slice(i, i + batchSize);
|
|
5601
|
+
|
|
5602
|
+
// Acquire tokens for batch
|
|
5603
|
+
await Promise.all(batch.map(() => rateLimiter.acquire()));
|
|
5604
|
+
|
|
5605
|
+
// Execute batch with retry
|
|
5606
|
+
const batchResults = await Promise.all(
|
|
5607
|
+
batch.map(d => withRetry(
|
|
5608
|
+
() => compareRegistrars({
|
|
5609
|
+
domain: d.domain.replace('.com', ''),
|
|
5610
|
+
tld: "com"
|
|
5611
|
+
}),
|
|
5612
|
+
rateConfig
|
|
5613
|
+
))
|
|
5614
|
+
);
|
|
5615
|
+
|
|
5616
|
+
priceResults.push(...batchResults);
|
|
5617
|
+
}
|
|
5618
|
+
|
|
5619
|
+
// Step 4: Social checks with rate limiting
|
|
5620
|
+
const socialResults: CheckSocialsResponse[] = [];
|
|
5621
|
+
for (const d of topDomains.slice(0, 3)) {
|
|
5622
|
+
await rateLimiter.acquire();
|
|
5623
|
+
const result = await withRetry(
|
|
5624
|
+
() => checkSocials({
|
|
5625
|
+
name: d.domain.replace('.com', ''),
|
|
5626
|
+
platforms: ["github", "twitter", "npm"]
|
|
5627
|
+
}),
|
|
5628
|
+
rateConfig
|
|
5629
|
+
);
|
|
5630
|
+
socialResults.push(result);
|
|
5631
|
+
}
|
|
5632
|
+
|
|
5633
|
+
// Compile report
|
|
5634
|
+
return {
|
|
5635
|
+
businessIdea,
|
|
5636
|
+
tldAnalysis: {
|
|
5637
|
+
tld: tldData.tld,
|
|
5638
|
+
description: tldData.description,
|
|
5639
|
+
priceRange: tldData.price_range,
|
|
5640
|
+
recommendation: tldData.recommendation,
|
|
5641
|
+
restrictions: tldData.restrictions,
|
|
5642
|
+
popularity: tldData.popularity
|
|
5643
|
+
},
|
|
5644
|
+
topRecommendations: topDomains.map((d, i) => ({
|
|
5645
|
+
domain: d.domain,
|
|
5646
|
+
price: d.price_first_year,
|
|
5647
|
+
bestRegistrar: priceResults[i]?.best_first_year?.registrar || 'unknown',
|
|
5648
|
+
socialAvailability: socialResults[i]?.summary || 'not checked'
|
|
5649
|
+
})),
|
|
5650
|
+
allSuggestions: suggestions.results.available.map(d => d.domain),
|
|
5651
|
+
researchTimestamp: new Date().toISOString()
|
|
5652
|
+
};
|
|
5653
|
+
}
|
|
5654
|
+
|
|
5655
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
5656
|
+
// USAGE: Production mode with custom rate limits
|
|
5657
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
5658
|
+
|
|
5659
|
+
// Conservative rate limiting for production
|
|
5660
|
+
const productionConfig: RateLimiterConfig = {
|
|
5661
|
+
maxRequestsPerMinute: 30, // Conservative for shared API
|
|
5662
|
+
maxConcurrent: 3, // Low concurrency
|
|
5663
|
+
retryAttempts: 5, // More retries for reliability
|
|
5664
|
+
retryDelayMs: 2000, // Longer initial delay
|
|
5665
|
+
backoffMultiplier: 2 // Standard exponential backoff
|
|
5666
|
+
};
|
|
5667
|
+
|
|
5668
|
+
const report = await productionDomainResearch("fintech payment platform", productionConfig);
|
|
4804
5669
|
```
|
|
4805
5670
|
|
|
4806
5671
|
#### Tool Parameter Optimization Reference
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "domain-search-mcp",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.10",
|
|
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",
|