UNPKG

@ironclads/namecheap-mcp

Version:

MCP server for Namecheap API integration - domain management, DNS, and domain suggestions

812 lines (802 loc) β€’ 37.8 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; import { NamecheapClient } from './namecheap-client.js'; import { NamecheapConfigSchema, DomainSuggestionOptionsSchema } from './types.js'; import { allTools } from './tools.js'; import { DomainNameSchema, BulkDomainsSchema } from './validation.js'; import { z } from 'zod'; class NamecheapMCPServer { server; namecheapClient = null; constructor() { this.server = new Server({ name: 'namecheap-mcp', version: '1.6.1', }); this.setupToolHandlers(); this.setupErrorHandling(); this.initializeClient(); // Initialize client at startup } setupErrorHandling() { this.server.onerror = (error) => { console.error('[MCP Error]', error); }; process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: allTools, }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { // Client should already be initialized in constructor switch (name) { case 'check_domain': return await this.handleCheckDomain(args); case 'check_domains_bulk': return await this.handleCheckDomainsBulk(args); case 'get_domain_info': return await this.handleGetDomainInfo(args); case 'register_domain': return await this.handleRegisterDomain(args); case 'renew_domain': return await this.handleRenewDomain(args); case 'get_nameservers': return await this.handleGetNameservers(args); case 'set_nameservers': return await this.handleSetNameservers(args); case 'list_domains': return await this.handleListDomains(args); case 'suggest_domains': return await this.handleSuggestDomains(args); case 'get_contacts': return await this.handleGetContacts(args); case 'set_contacts': return await this.handleSetContacts(args); case 'get_tld_list': return await this.handleGetTldList(args); case 'reactivate_domain': return await this.handleReactivateDomain(args); case 'get_registrar_lock': return await this.handleGetRegistrarLock(args); case 'set_registrar_lock': return await this.handleSetRegistrarLock(args); case 'get_balances': return await this.handleGetBalances(args); case 'get_pricing': return await this.handleGetPricing(args); case 'get_domain_pricing': return await this.handleGetDomainPricing(args); case 'get_all_tld_pricing': return await this.handleGetAllTldPricing(args); default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); } } catch (error) { if (error instanceof McpError) { throw error; } const errorMessage = error instanceof Error ? `${error.message}\nStack: ${error.stack}` : String(error); console.error('Full error details for MCP:', errorMessage); throw new McpError(ErrorCode.InternalError, `Tool execution failed: ${errorMessage}`); } }); } parseCliArgs() { const args = {}; const cliArgs = process.argv.slice(2); for (let i = 0; i < cliArgs.length; i++) { const arg = cliArgs[i]; if (arg.startsWith('--')) { const key = arg.slice(2); const nextArg = cliArgs[i + 1]; if (nextArg && !nextArg.startsWith('--')) { args[key] = nextArg; i++; // Skip next argument since we used it as value } else { // Handle boolean flags args[key] = 'true'; } } } return args; } initializeClient() { // Parse CLI arguments first const cliArgs = this.parseCliArgs(); console.error('CLI arguments:', cliArgs); // CLI arguments override environment variables const config = { apiUser: cliArgs['api-user'] || process.env.NAMECHEAP_API_USER, apiKey: cliArgs['api-key'] || process.env.NAMECHEAP_API_KEY, username: cliArgs['username'] || process.env.NAMECHEAP_USERNAME, clientIp: cliArgs['client-ip'] || process.env.NAMECHEAP_CLIENT_IP, sandbox: (cliArgs['sandbox'] || process.env.NAMECHEAP_SANDBOX || 'true') === 'true', }; console.error('Config:', config); // Check for required configuration const missingFields = []; if (!config.apiUser) missingFields.push('api-user'); if (!config.apiKey) missingFields.push('api-key'); if (!config.username) missingFields.push('username'); if (!config.clientIp) missingFields.push('client-ip'); if (missingFields.length > 0) { console.error(`❌ Missing required configuration: ${missingFields.join(', ')}`); console.error(''); console.error('πŸ’‘ Provide via CLI arguments:'); console.error(` namecheap-mcp --api-user YOUR_USER --api-key YOUR_KEY --username YOUR_USER --client-ip YOUR_IP`); console.error(''); console.error('πŸ’‘ Or via environment variables:'); console.error(' NAMECHEAP_API_USER, NAMECHEAP_API_KEY, NAMECHEAP_USERNAME, NAMECHEAP_CLIENT_IP'); console.error(''); console.error('πŸ’‘ Run --help for more information'); throw new McpError(ErrorCode.InvalidRequest, `Missing required configuration: ${missingFields.join(', ')}`); } // Validate configuration const result = NamecheapConfigSchema.safeParse(config); console.error('Validation result:', result); if (!result.success) { console.error('Validation error:', JSON.stringify(result.error.issues, null, 2)); throw new McpError(ErrorCode.InvalidRequest, `Invalid Namecheap configuration: ${JSON.stringify(result.error.issues)}`); } this.namecheapClient = new NamecheapClient(result.data); } async handleCheckDomain(args) { try { const { domain } = args; // Validate domain format const validatedDomain = DomainNameSchema.parse(domain.toLowerCase().trim()); console.error(`Checking domain: ${validatedDomain}`); if (!this.namecheapClient) { console.error('NamecheapClient is null!'); throw new Error('NamecheapClient not initialized'); } console.error('NamecheapClient exists, calling checkDomain...'); const result = await this.namecheapClient.checkDomain(validatedDomain); console.error(`Check result: ${JSON.stringify(result)}`); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { console.error(`Error in handleCheckDomain: ${error}`); console.error(`Error stack: ${error instanceof Error ? error.stack : 'No stack'}`); if (error instanceof z.ZodError) { throw new McpError(ErrorCode.InvalidRequest, `Invalid domain format: ${error.errors.map(e => e.message).join(', ')}`); } throw error; } } async handleCheckDomainsBulk(args) { try { const { domains } = args; // Validate domains format const validatedDomains = BulkDomainsSchema.parse(domains.map(d => d.toLowerCase().trim())); console.error(`Checking bulk domains: ${validatedDomains.join(', ')}`); if (!this.namecheapClient) { throw new Error('NamecheapClient not initialized'); } const results = await this.namecheapClient.checkDomainsBulk(validatedDomains); // Format results in a table-like structure const formattedResults = results.map(result => ({ domain: result.domain, available: result.available ? 'βœ… Available' : '❌ Taken', premium: result.premium ? 'Yes' : 'No', price: `$${result.price}/year` })); return { content: [ { type: 'text', text: `## Bulk Domain Check Results\n\n` + `Checked ${results.length} domains:\n\n` + formattedResults .map(r => `**${r.domain}** - ${r.available}\n` + ` - Premium: ${r.premium}\n` + ` - Price: ${r.price}\n`) .join('\n') + `\n**Available domains:** ${results.filter(r => r.available).length}/${results.length}` }, ], }; } catch (error) { console.error(`Error in handleCheckDomainsBulk: ${error}`); if (error instanceof z.ZodError) { throw new McpError(ErrorCode.InvalidRequest, `Invalid domains: ${error.errors.map(e => e.message).join(', ')}`); } throw error; } } async handleGetDomainInfo(args) { try { const { domain } = args; const result = await this.namecheapClient.getDomainInfo(domain); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { if (error instanceof Error && error.message.includes('Domain Locked reason:')) { // Extract the suspension reason and contact information const message = error.message; const reasonMatch = message.match(/Domain Locked reason: (.+?)(?:\n|$)/); const contactMatch = message.match(/please contact ([^\s\n]+)/); const reason = reasonMatch ? reasonMatch[1] : 'Domain is suspended'; const contact = contactMatch ? contactMatch[1] : 'registry support'; return { content: [ { type: 'text', text: `## Domain Status: Suspended\n\n` + `**Domain:** ${args.domain}\n` + `**Status:** 🚫 Suspended by Registry\n` + `**Reason:** ${reason}\n` + `**Contact:** ${contact}\n\n` + `**Next Steps:**\n` + `1. Contact the registry at: ${contact}\n` + `2. Request specific suspension reason and appeal process\n` + `3. Provide necessary documentation for appeal\n\n` + `**Note:** This domain is currently inaccessible due to registry suspension.` }, ], }; } // Re-throw other errors throw error; } } async handleRegisterDomain(args) { const { domain, years = 1 } = args; const result = await this.namecheapClient.registerDomain(domain, years); return { content: [ { type: 'text', text: `Domain ${domain} registration ${result ? 'successful' : 'failed'}`, }, ], }; } async handleRenewDomain(args) { const { domain, years = 1 } = args; const result = await this.namecheapClient.renewDomain(domain, years); return { content: [ { type: 'text', text: `Domain ${domain} renewal ${result ? 'successful' : 'failed'}`, }, ], }; } async handleGetNameservers(args) { const { domain } = args; const result = await this.namecheapClient.getNameservers(domain); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } async handleSetNameservers(args) { const { domain, nameservers } = args; const result = await this.namecheapClient.setNameservers(domain, nameservers); return { content: [ { type: 'text', text: `Nameservers update ${result ? 'successful' : 'failed'}`, }, ], }; } async handleListDomains(args) { try { const options = args; const result = await this.namecheapClient.listDomains(options); // Format domains for display const formattedDomains = result.domains.map(domain => ({ domain: domain.domain, id: domain.id, created: domain.created, expires: domain.expires, expired: domain.isExpired ? '❌ Expired' : 'βœ… Active', locked: domain.isLocked ? 'πŸ”’ Locked' : 'πŸ”“ Unlocked', autoRenew: domain.autoRenew ? 'πŸ”„ Auto-renew' : '⏸️ Manual', whoisGuard: domain.whoisGuard === 'ENABLED' ? 'πŸ›‘οΈ Protected' : 'πŸ‘οΈ Public', })); return { content: [ { type: 'text', text: `## Your Domains (${result.paging.totalItems} total)\n\n` + `Page ${result.paging.currentPage} of ${Math.ceil(result.paging.totalItems / result.paging.pageSize)}\n\n` + (formattedDomains.length > 0 ? formattedDomains .map(d => `**${d.domain}** (ID: ${d.id})\n` + ` - Created: ${d.created}\n` + ` - Expires: ${d.expires}\n` + ` - Status: ${d.expired}\n` + ` - Lock: ${d.locked}\n` + ` - Renewal: ${d.autoRenew}\n` + ` - Privacy: ${d.whoisGuard}\n`) .join('\n') : 'No domains found.') + `\n\n**Summary:** ${result.domains.filter(d => !d.isExpired).length} active, ${result.domains.filter(d => d.isExpired).length} expired` }, ], }; } catch (error) { throw new McpError(ErrorCode.InvalidRequest, `Failed to list domains: ${error instanceof Error ? error.message : String(error)}`); } } async handleSuggestDomains(args) { try { const options = DomainSuggestionOptionsSchema.parse(args); const suggestions = await this.namecheapClient.suggestDomains(options); // Format suggestions for display const formattedSuggestions = suggestions.map(s => ({ domain: s.domain, available: s.available, premium: s.premium, price: s.price, type: s.suggestion_type, score: s.score, status: s.available ? 'βœ… Available' : '❌ Taken' })); return { content: [ { type: 'text', text: `## Domain Suggestions for "${options.keyword}"\n\n` + `Found ${suggestions.length} suggestions:\n\n` + formattedSuggestions .map(s => `**${s.domain}** - ${s.status}\n` + ` - Type: ${s.type}\n` + ` - Price: $${s.price}/year${s.premium ? ' (Premium)' : ''}\n` + ` - Score: ${s.score}/100\n`) .join('\n') + `\n\n**Available domains:** ${suggestions.filter(s => s.available).length}/${suggestions.length}` }, ], }; } catch (error) { throw new McpError(ErrorCode.InvalidRequest, `Invalid domain suggestion options: ${error instanceof Error ? error.message : String(error)}`); } } async handleGetContacts(args) { try { const { domain } = args; const result = await this.namecheapClient.getContacts(domain); return { content: [ { type: 'text', text: `## Domain Contacts for ${domain}\n\n` + `**Registrant Contact:**\n` + `- Name: ${result.registrant.FirstName} ${result.registrant.LastName}\n` + `- Organization: ${result.registrant.OrganizationName || 'N/A'}\n` + `- Email: ${result.registrant.EmailAddress}\n` + `- Phone: ${result.registrant.Phone}\n` + `- Address: ${result.registrant.Address1}${result.registrant.Address2 ? ', ' + result.registrant.Address2 : ''}\n` + `- City: ${result.registrant.City}, ${result.registrant.StateProvince} ${result.registrant.PostalCode}\n` + `- Country: ${result.registrant.Country}\n\n` + `**Technical Contact:**\n` + `- Name: ${result.tech.FirstName} ${result.tech.LastName}\n` + `- Organization: ${result.tech.OrganizationName || 'N/A'}\n` + `- Email: ${result.tech.EmailAddress}\n` + `- Phone: ${result.tech.Phone}\n\n` + `**Administrative Contact:**\n` + `- Name: ${result.admin.FirstName} ${result.admin.LastName}\n` + `- Organization: ${result.admin.OrganizationName || 'N/A'}\n` + `- Email: ${result.admin.EmailAddress}\n` + `- Phone: ${result.admin.Phone}\n\n` + `**Billing Contact:**\n` + `- Name: ${result.auxBilling.FirstName} ${result.auxBilling.LastName}\n` + `- Organization: ${result.auxBilling.OrganizationName || 'N/A'}\n` + `- Email: ${result.auxBilling.EmailAddress}\n` + `- Phone: ${result.auxBilling.Phone}` }, ], }; } catch (error) { throw new McpError(ErrorCode.InvalidRequest, `Failed to get domain contacts: ${error instanceof Error ? error.message : String(error)}`); } } async handleSetContacts(args) { try { const { domain, contacts } = args; const result = await this.namecheapClient.setContacts(domain, contacts); return { content: [ { type: 'text', text: `Domain contacts update for ${domain}: ${result ? 'βœ… Successful' : '❌ Failed'}` }, ], }; } catch (error) { throw new McpError(ErrorCode.InvalidRequest, `Failed to set domain contacts: ${error instanceof Error ? error.message : String(error)}`); } } async handleGetTldList(args) { try { const result = await this.namecheapClient.getTldList(); // Show only the most popular/common TLDs to avoid token limit const popularTlds = result.filter(tld => tld.isApiRegisterable && ['com', 'net', 'org', 'info', 'biz', 'me', 'co', 'io', 'tv', 'cc', 'ws', 'in', 'mx', 'my', 'cv'].includes(tld.name)); const formattedTlds = popularTlds.map(tld => ({ name: tld.name, registerable: tld.isApiRegisterable ? 'βœ… Yes' : '❌ No', renewable: tld.isApiRenewable ? 'βœ… Yes' : '❌ No', transferrable: tld.isApiTransferrable ? 'βœ… Yes' : '❌ No', minYears: tld.minRegisterYears || 1, maxYears: tld.maxRegisterYears || 10, realTime: tld.nonRealTime ? '❌ No' : 'βœ… Yes', categories: (tld.categories || []).join(', ') || 'General', })); return { content: [ { type: 'text', text: `## Popular Top-Level Domains (TLDs)\n\n` + `Found ${result.length} total TLDs, showing ${popularTlds.length} popular ones:\n\n` + formattedTlds .map(tld => `**${tld.name}**\n` + ` - API Registerable: ${tld.registerable}\n` + ` - API Renewable: ${tld.renewable}\n` + ` - API Transferrable: ${tld.transferrable}\n` + ` - Registration Years: ${tld.minYears}-${tld.maxYears}\n` + ` - Real-time Check: ${tld.realTime}\n` + ``) .join('\n') + `\n\n**Note:** This shows only popular TLDs. Total available: ${result.length} TLDs.` }, ], }; } catch (error) { throw new McpError(ErrorCode.InvalidRequest, `Failed to get TLD list: ${error instanceof Error ? error.message : String(error)}`); } } async handleReactivateDomain(args) { try { const { domain } = args; const result = await this.namecheapClient.reactivate(domain); return { content: [ { type: 'text', text: `Domain ${domain} reactivation: ${result ? 'βœ… Successful' : '❌ Failed'}` }, ], }; } catch (error) { throw new McpError(ErrorCode.InvalidRequest, `Failed to reactivate domain: ${error instanceof Error ? error.message : String(error)}`); } } async handleGetRegistrarLock(args) { try { const { domain } = args; const result = await this.namecheapClient.getRegistrarLock(domain); return { content: [ { type: 'text', text: `## Registrar Lock Status for ${domain}\n\n` + `**Status:** ${result.registrarLock ? 'πŸ”’ Locked' : 'πŸ”“ Unlocked'}\n\n` + `${result.registrarLock ? 'Domain is locked and protected from unauthorized transfers.' : 'Domain is unlocked and can be transferred.'}` }, ], }; } catch (error) { throw new McpError(ErrorCode.InvalidRequest, `Failed to get registrar lock status: ${error instanceof Error ? error.message : String(error)}`); } } async handleSetRegistrarLock(args) { try { const { domain, lock } = args; const result = await this.namecheapClient.setRegistrarLock(domain, lock); const action = lock ? 'Lock' : 'Unlock'; const status = lock ? 'πŸ”’ Locked' : 'πŸ”“ Unlocked'; return { content: [ { type: 'text', text: `Domain ${domain} registrar ${action.toLowerCase()}: ${result ? `βœ… Successful - ${status}` : '❌ Failed'}` }, ], }; } catch (error) { throw new McpError(ErrorCode.InvalidRequest, `Failed to set registrar lock: ${error instanceof Error ? error.message : String(error)}`); } } async handleGetBalances(args) { try { const result = await this.namecheapClient.getBalances(); return { content: [ { type: 'text', text: `## Account Balances\n\n` + `**Available Balance:** $${result.availableBalance.toFixed(2)}\n` + `**Account Balance:** $${result.accountBalance.toFixed(2)}\n` + `**Earned Amount:** $${result.earnedAmount.toFixed(2)}\n` + `**Withdrawable Amount:** $${result.withdrawableAmount.toFixed(2)}\n` + `**Funds Required for Auto-Renew:** $${result.fundsRequiredForAutoRenew.toFixed(2)}\n\n` + `${result.fundsRequiredForAutoRenew > result.availableBalance ? '⚠️ **Warning:** Insufficient funds for auto-renewal!' : 'βœ… **Status:** Sufficient funds for auto-renewal'}` }, ], }; } catch (error) { throw new McpError(ErrorCode.InvalidRequest, `Failed to get account balances: ${error instanceof Error ? error.message : String(error)}`); } } async handleGetPricing(args) { try { const options = args; const result = await this.namecheapClient.getPricing({ productType: options.productType, productCategory: options.productCategory, actionType: options.actionType, productName: options.productName, promotionCode: options.promotionCode, }); if (result.length === 0) { return { content: [ { type: 'text', text: `No pricing information found for ${options.productType}${options.productName ? ` (${options.productName})` : ''}.` }, ], }; } const formattedPricing = result.map(p => ({ product: p.product, duration: `${p.duration} ${p.durationType.toLowerCase()}${p.duration > 1 ? 's' : ''}`, yourPrice: `$${p.yourPrice.toFixed(2)}`, regularPrice: `$${p.regularPrice.toFixed(2)}`, savings: p.regularPrice > p.yourPrice ? `Save $${(p.regularPrice - p.yourPrice).toFixed(2)}` : '', })); return { content: [ { type: 'text', text: `## ${options.productType} Pricing${options.actionType ? ` (${options.actionType})` : ''}\n\n` + `Found ${result.length} pricing options:\n\n` + formattedPricing .map(p => `**${p.product}** (${p.duration})\n` + ` - Your Price: ${p.yourPrice}\n` + ` - Regular Price: ${p.regularPrice}\n` + (p.savings ? ` - ${p.savings}\n` : '')) .join('\n') }, ], }; } catch (error) { throw new McpError(ErrorCode.InvalidRequest, `Failed to get pricing: ${error instanceof Error ? error.message : String(error)}`); } } async handleGetDomainPricing(args) { try { const { tlds, actionType = 'REGISTER' } = args; const pricingMap = await this.namecheapClient.getDomainPricing(tlds, actionType); if (pricingMap.size === 0) { return { content: [ { type: 'text', text: `No pricing information found for the requested TLDs: ${tlds.join(', ')}` }, ], }; } const pricingData = []; for (const [tld, pricing] of pricingMap.entries()) { const oneDayPricing = pricing.find(p => p.duration === 1) || pricing[0]; if (oneDayPricing) { pricingData.push({ tld, price: oneDayPricing.yourPrice, duration: `${oneDayPricing.duration} ${oneDayPricing.durationType.toLowerCase()}${oneDayPricing.duration > 1 ? 's' : ''}`, }); } } // Sort by price (cheapest first) pricingData.sort((a, b) => a.price - b.price); return { content: [ { type: 'text', text: `## Domain ${actionType} Pricing πŸ’°\n\n` + `Found pricing for ${pricingData.length}/${tlds.length} TLDs (sorted by price):\n\n` + pricingData .map((p, index) => `${index === 0 ? 'πŸ₯‡' : index === 1 ? 'πŸ₯ˆ' : index === 2 ? 'πŸ₯‰' : 'πŸ’Ž'} **${p.tld}** - $${p.price.toFixed(2)}/${p.duration}`) .join('\n') + `\n\nπŸ’‘ **Cheapest option:** ${pricingData[0]?.tld} at $${pricingData[0]?.price.toFixed(2)}` }, ], }; } catch (error) { throw new McpError(ErrorCode.InvalidRequest, `Failed to get domain pricing: ${error instanceof Error ? error.message : String(error)}`); } } async handleGetAllTldPricing(args) { try { const { actionType = 'REGISTER', batchSize = 20, maxBatches = 10 } = args; // First get all available TLDs const allTlds = await this.namecheapClient.getTldList(); const registerableTlds = allTlds .filter(tld => tld.isApiRegisterable) .map(tld => tld.name); const totalTlds = registerableTlds.length; const totalBatches = Math.min(maxBatches, Math.ceil(totalTlds / batchSize)); const actualTldsToProcess = Math.min(totalBatches * batchSize, totalTlds); console.error(`Processing ${actualTldsToProcess}/${totalTlds} TLDs in ${totalBatches} batches...`); const allPricingData = []; for (let batch = 0; batch < totalBatches; batch++) { const start = batch * batchSize; const end = Math.min(start + batchSize, totalTlds); const batchTlds = registerableTlds.slice(start, end); console.error(`Processing batch ${batch + 1}/${totalBatches}: ${batchTlds.join(', ')}`); try { const pricingMap = await this.namecheapClient.getDomainPricing(batchTlds, actionType); for (const [tld, pricing] of pricingMap.entries()) { const oneDayPricing = pricing.find(p => p.duration === 1) || pricing[0]; if (oneDayPricing) { allPricingData.push({ tld, price: oneDayPricing.yourPrice, duration: `${oneDayPricing.duration} ${oneDayPricing.durationType.toLowerCase()}${oneDayPricing.duration > 1 ? 's' : ''}`, }); } } // Add delay between batches to respect API rate limits (50/min) if (batch < totalBatches - 1) { await new Promise(resolve => setTimeout(resolve, 1200)); // ~1.2 second delay } } catch (error) { console.error(`Error processing batch ${batch + 1}:`, error); // Continue with next batch } } if (allPricingData.length === 0) { return { content: [ { type: 'text', text: `No pricing data retrieved. This may be due to API rate limits or errors.` }, ], }; } // Sort by price (cheapest first) allPricingData.sort((a, b) => a.price - b.price); // Show top 50 cheapest TLDs to avoid token limits const topCheapest = allPricingData.slice(0, 50); return { content: [ { type: 'text', text: `## All TLD ${actionType} Pricing πŸ’°\\n\\n` + `Retrieved pricing for ${allPricingData.length}/${actualTldsToProcess} TLDs (showing top 50 cheapest):\\n\\n` + topCheapest .map((p, index) => `${index === 0 ? 'πŸ₯‡' : index === 1 ? 'πŸ₯ˆ' : index === 2 ? 'πŸ₯‰' : `${index + 1}.`} **${p.tld}** - $${p.price.toFixed(2)}/${p.duration}`) .join('\\n') + `\\n\\nπŸ’‘ **Cheapest TLD:** ${allPricingData[0]?.tld} at $${allPricingData[0]?.price.toFixed(2)}\\n` + `πŸ“Š **Price Range:** $${allPricingData[0]?.price.toFixed(2)} - $${allPricingData[allPricingData.length - 1]?.price.toFixed(2)}\\n` + `⏱️ **Processing Time:** ~${Math.ceil(totalBatches * 1.2)} seconds (${totalBatches} batches)` }, ], }; } catch (error) { throw new McpError(ErrorCode.InternalError, `Failed to get all TLD pricing: ${error instanceof Error ? error.message : String(error)}`); } } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Namecheap MCP Server running on stdio'); } static showHelp() { console.log(` Namecheap MCP Server v1.6.1 USAGE: namecheap-mcp [OPTIONS] OPTIONS: --api-user <USER> Namecheap API user --api-key <KEY> Namecheap API key --username <USER> Namecheap username --client-ip <IP> Whitelisted IP address --sandbox Use sandbox mode (default: true) --help, -h Show this help message EXAMPLES: # Using CLI arguments namecheap-mcp --api-user myuser --api-key mykey --username myuser --client-ip 1.2.3.4 # Using environment variables export NAMECHEAP_API_USER=myuser export NAMECHEAP_API_KEY=mykey export NAMECHEAP_USERNAME=myuser export NAMECHEAP_CLIENT_IP=1.2.3.4 namecheap-mcp --sandbox # Mix of both (CLI overrides environment) export NAMECHEAP_API_USER=myuser namecheap-mcp --api-key mykey --username myuser --client-ip 1.2.3.4 CLAUDE DESKTOP CONFIGURATION: Configuration: { "mcpServers": { "namecheap": { "command": "namecheap-mcp", "args": [ "--api-user", "your-api-user", "--api-key", "your-api-key", "--username", "your-username", "--client-ip", "your-ip", "--sandbox" ] } } } Config File Location: - macOS: ~/Library/Application Support/Claude/claude_desktop_config.json - Windows: %APPDATA%\Claude\claude_desktop_config.json For more information: https://www.npmjs.com/package/namecheap-mcp `); } } // Check for help flag before starting server if (process.argv.includes('--help') || process.argv.includes('-h')) { NamecheapMCPServer.showHelp(); process.exit(0); } const server = new NamecheapMCPServer(); server.run().catch(console.error); //# sourceMappingURL=index.js.map