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.
Files changed (2) hide show
  1. package/README.md +886 -21
  2. 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 multiple tools:
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
- async function domainResearchPipeline(businessIdea: string) {
4752
- // Step 1: Generate smart suggestions from business description
4753
- const suggestions = await suggestDomainsSmart({
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
- max_suggestions: 15
5370
+ industry: "tech",
5371
+ max_suggestions: 15,
5372
+ include_premium: false
4758
5373
  });
4759
5374
 
4760
- // Step 2: Get TLD information for context
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
- // Step 4: Check social media for top picks
4773
- const socialChecks = await Promise.all(
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
- // Compile research report
5403
+ // ─────────────────────────────────────────────────────────────────────────
5404
+ // Step 5: Compile final research report
5405
+ // ─────────────────────────────────────────────────────────────────────────
5406
+
4784
5407
  return {
4785
5408
  businessIdea,
4786
- tldContext: {
4787
- description: tldInfo.description,
4788
- priceRange: tldInfo.price_range,
4789
- recommendation: tldInfo.recommendation
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
- relatedTerms: suggestions.related_terms
5426
+ allSuggestions: suggestions.results.available.map(d => d.domain),
5427
+ researchTimestamp: new Date().toISOString()
4799
5428
  };
4800
5429
  }
4801
5430
 
4802
- // Usage
4803
- const research = await domainResearchPipeline("ai-powered code review tool");
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.9",
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",