@ironclads/namecheap-mcp
Version:
MCP server for Namecheap API integration - domain management, DNS, and domain suggestions
812 lines (802 loc) β’ 37.8 kB
JavaScript
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