domain-search-mcp 1.0.0
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/.env.example +52 -0
- package/Dockerfile +15 -0
- package/LICENSE +21 -0
- package/README.md +426 -0
- package/SECURITY.md +252 -0
- package/dist/config.d.ts +25 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +117 -0
- package/dist/config.js.map +1 -0
- package/dist/fallbacks/index.d.ts +6 -0
- package/dist/fallbacks/index.d.ts.map +1 -0
- package/dist/fallbacks/index.js +14 -0
- package/dist/fallbacks/index.js.map +1 -0
- package/dist/fallbacks/rdap.d.ts +18 -0
- package/dist/fallbacks/rdap.d.ts.map +1 -0
- package/dist/fallbacks/rdap.js +339 -0
- package/dist/fallbacks/rdap.js.map +1 -0
- package/dist/fallbacks/whois.d.ts +27 -0
- package/dist/fallbacks/whois.d.ts.map +1 -0
- package/dist/fallbacks/whois.js +219 -0
- package/dist/fallbacks/whois.js.map +1 -0
- package/dist/registrars/base.d.ts +89 -0
- package/dist/registrars/base.d.ts.map +1 -0
- package/dist/registrars/base.js +203 -0
- package/dist/registrars/base.js.map +1 -0
- package/dist/registrars/index.d.ts +7 -0
- package/dist/registrars/index.d.ts.map +1 -0
- package/dist/registrars/index.js +15 -0
- package/dist/registrars/index.js.map +1 -0
- package/dist/registrars/namecheap.d.ts +69 -0
- package/dist/registrars/namecheap.d.ts.map +1 -0
- package/dist/registrars/namecheap.js +307 -0
- package/dist/registrars/namecheap.js.map +1 -0
- package/dist/registrars/porkbun.d.ts +63 -0
- package/dist/registrars/porkbun.d.ts.map +1 -0
- package/dist/registrars/porkbun.js +299 -0
- package/dist/registrars/porkbun.js.map +1 -0
- package/dist/server.d.ts +19 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +209 -0
- package/dist/server.js.map +1 -0
- package/dist/services/domain-search.d.ts +40 -0
- package/dist/services/domain-search.d.ts.map +1 -0
- package/dist/services/domain-search.js +438 -0
- package/dist/services/domain-search.js.map +1 -0
- package/dist/services/index.d.ts +5 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +11 -0
- package/dist/services/index.js.map +1 -0
- package/dist/tools/bulk_search.d.ts +72 -0
- package/dist/tools/bulk_search.d.ts.map +1 -0
- package/dist/tools/bulk_search.js +108 -0
- package/dist/tools/bulk_search.js.map +1 -0
- package/dist/tools/check_socials.d.ts +71 -0
- package/dist/tools/check_socials.d.ts.map +1 -0
- package/dist/tools/check_socials.js +357 -0
- package/dist/tools/check_socials.js.map +1 -0
- package/dist/tools/compare_registrars.d.ts +80 -0
- package/dist/tools/compare_registrars.d.ts.map +1 -0
- package/dist/tools/compare_registrars.js +116 -0
- package/dist/tools/compare_registrars.js.map +1 -0
- package/dist/tools/index.d.ts +10 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +31 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/search_domain.d.ts +61 -0
- package/dist/tools/search_domain.d.ts.map +1 -0
- package/dist/tools/search_domain.js +81 -0
- package/dist/tools/search_domain.js.map +1 -0
- package/dist/tools/suggest_domains.d.ts +82 -0
- package/dist/tools/suggest_domains.d.ts.map +1 -0
- package/dist/tools/suggest_domains.js +227 -0
- package/dist/tools/suggest_domains.js.map +1 -0
- package/dist/tools/tld_info.d.ts +56 -0
- package/dist/tools/tld_info.d.ts.map +1 -0
- package/dist/tools/tld_info.js +273 -0
- package/dist/tools/tld_info.js.map +1 -0
- package/dist/types.d.ts +193 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/cache.d.ts +81 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +192 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/errors.d.ts +87 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +191 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +24 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +27 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +132 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/premium-analyzer.d.ts +33 -0
- package/dist/utils/premium-analyzer.d.ts.map +1 -0
- package/dist/utils/premium-analyzer.js +273 -0
- package/dist/utils/premium-analyzer.js.map +1 -0
- package/dist/utils/validators.d.ts +53 -0
- package/dist/utils/validators.d.ts.map +1 -0
- package/dist/utils/validators.js +159 -0
- package/dist/utils/validators.js.map +1 -0
- package/docs/marketing/devto-post.md +135 -0
- package/docs/marketing/hackernews.md +42 -0
- package/docs/marketing/producthunt.md +109 -0
- package/docs/marketing/reddit-post.md +59 -0
- package/docs/marketing/twitter-thread.md +105 -0
- package/examples/bulk-search-50-domains.ts +131 -0
- package/examples/cli-interactive.ts +280 -0
- package/examples/compare-registrars.ts +78 -0
- package/examples/search-single-domain.ts +54 -0
- package/examples/suggest-names.ts +110 -0
- package/glama.json +6 -0
- package/jest.config.js +35 -0
- package/package.json +62 -0
- package/smithery.yaml +36 -0
- package/src/config.ts +121 -0
- package/src/fallbacks/index.ts +6 -0
- package/src/fallbacks/rdap.ts +407 -0
- package/src/fallbacks/whois.ts +250 -0
- package/src/registrars/base.ts +264 -0
- package/src/registrars/index.ts +7 -0
- package/src/registrars/namecheap.ts +378 -0
- package/src/registrars/porkbun.ts +380 -0
- package/src/server.ts +276 -0
- package/src/services/domain-search.ts +567 -0
- package/src/services/index.ts +9 -0
- package/src/tools/bulk_search.ts +142 -0
- package/src/tools/check_socials.ts +467 -0
- package/src/tools/compare_registrars.ts +162 -0
- package/src/tools/index.ts +45 -0
- package/src/tools/search_domain.ts +93 -0
- package/src/tools/suggest_domains.ts +284 -0
- package/src/tools/tld_info.ts +294 -0
- package/src/types.ts +289 -0
- package/src/utils/cache.ts +238 -0
- package/src/utils/errors.ts +262 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/logger.ts +162 -0
- package/src/utils/premium-analyzer.ts +303 -0
- package/src/utils/validators.ts +193 -0
- package/tests/premium-analyzer.test.ts +310 -0
- package/tests/unit/cache.test.ts +123 -0
- package/tests/unit/errors.test.ts +190 -0
- package/tests/unit/tld-info.test.ts +62 -0
- package/tests/unit/tools.test.ts +200 -0
- package/tests/unit/validators.test.ts +146 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* search_domain Tool - Primary Domain Search.
|
|
3
|
+
*
|
|
4
|
+
* This is the main entry point for domain availability checks.
|
|
5
|
+
* Designed for magic first moment: works with zero configuration.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import type { SearchResponse } from '../types.js';
|
|
10
|
+
import { searchDomain } from '../services/domain-search.js';
|
|
11
|
+
import { wrapError } from '../utils/errors.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Input schema for search_domain.
|
|
15
|
+
*/
|
|
16
|
+
export const searchDomainSchema = z.object({
|
|
17
|
+
domain_name: z
|
|
18
|
+
.string()
|
|
19
|
+
.min(1)
|
|
20
|
+
.max(63)
|
|
21
|
+
.describe(
|
|
22
|
+
"The domain name you want to check (e.g., 'vibecoding', 'myapp'). Don't include the extension (.com, .io, etc.)",
|
|
23
|
+
),
|
|
24
|
+
tlds: z
|
|
25
|
+
.array(z.string())
|
|
26
|
+
.optional()
|
|
27
|
+
.describe(
|
|
28
|
+
"TLD extensions to check (e.g., ['com', 'io', 'dev']). Defaults to ['com', 'io', 'dev'] if not specified.",
|
|
29
|
+
),
|
|
30
|
+
registrars: z
|
|
31
|
+
.array(z.string())
|
|
32
|
+
.optional()
|
|
33
|
+
.describe(
|
|
34
|
+
"Optional: specific registrars to check (e.g., ['porkbun', 'namecheap']). Leave empty to auto-select.",
|
|
35
|
+
),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export type SearchDomainInput = z.infer<typeof searchDomainSchema>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Tool definition for MCP.
|
|
42
|
+
*/
|
|
43
|
+
export const searchDomainTool = {
|
|
44
|
+
name: 'search_domain',
|
|
45
|
+
description: `Search for domain availability and pricing across multiple TLDs.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
- Availability status for each domain
|
|
49
|
+
- Pricing (first year and renewal)
|
|
50
|
+
- Whether WHOIS privacy is included
|
|
51
|
+
- Human-readable insights and next steps
|
|
52
|
+
|
|
53
|
+
Examples:
|
|
54
|
+
- search_domain("vibecoding") → checks vibecoding.com, .io, .dev
|
|
55
|
+
- search_domain("myapp", ["com", "io"]) → checks specific TLDs`,
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: 'object',
|
|
58
|
+
properties: {
|
|
59
|
+
domain_name: {
|
|
60
|
+
type: 'string',
|
|
61
|
+
description:
|
|
62
|
+
"The domain name to search (e.g., 'vibecoding'). No extension needed.",
|
|
63
|
+
},
|
|
64
|
+
tlds: {
|
|
65
|
+
type: 'array',
|
|
66
|
+
items: { type: 'string' },
|
|
67
|
+
description:
|
|
68
|
+
"TLD extensions to check (e.g., ['com', 'io', 'dev']). Defaults to ['com', 'io', 'dev'].",
|
|
69
|
+
},
|
|
70
|
+
registrars: {
|
|
71
|
+
type: 'array',
|
|
72
|
+
items: { type: 'string' },
|
|
73
|
+
description:
|
|
74
|
+
"Optional: specific registrars to check. Leave empty to auto-select.",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
required: ['domain_name'],
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Execute the search_domain tool.
|
|
83
|
+
*/
|
|
84
|
+
export async function executeSearchDomain(
|
|
85
|
+
input: SearchDomainInput,
|
|
86
|
+
): Promise<SearchResponse> {
|
|
87
|
+
try {
|
|
88
|
+
const { domain_name, tlds, registrars } = searchDomainSchema.parse(input);
|
|
89
|
+
return await searchDomain(domain_name, tlds, registrars);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
throw wrapError(error);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* suggest_domains Tool - Domain Name Suggestions.
|
|
3
|
+
*
|
|
4
|
+
* Generate and check variations of a base domain name.
|
|
5
|
+
* Helps find available alternatives when the primary name is taken.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { searchDomain } from '../services/domain-search.js';
|
|
10
|
+
import { validateDomainName } from '../utils/validators.js';
|
|
11
|
+
import { wrapError } from '../utils/errors.js';
|
|
12
|
+
import type { DomainResult } from '../types.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Input schema for suggest_domains.
|
|
16
|
+
*/
|
|
17
|
+
export const suggestDomainsSchema = z.object({
|
|
18
|
+
base_name: z
|
|
19
|
+
.string()
|
|
20
|
+
.min(1)
|
|
21
|
+
.describe("The base domain name to generate variations from (e.g., 'vibecoding')."),
|
|
22
|
+
tld: z
|
|
23
|
+
.string()
|
|
24
|
+
.optional()
|
|
25
|
+
.default('com')
|
|
26
|
+
.describe("TLD to check suggestions against (e.g., 'com'). Defaults to 'com'."),
|
|
27
|
+
variants: z
|
|
28
|
+
.array(z.enum(['hyphen', 'numbers', 'abbreviations', 'synonyms', 'prefixes', 'suffixes']))
|
|
29
|
+
.optional()
|
|
30
|
+
.describe(
|
|
31
|
+
"Types of variations to generate. Defaults to all types.",
|
|
32
|
+
),
|
|
33
|
+
max_suggestions: z
|
|
34
|
+
.number()
|
|
35
|
+
.int()
|
|
36
|
+
.min(1)
|
|
37
|
+
.max(50)
|
|
38
|
+
.optional()
|
|
39
|
+
.default(10)
|
|
40
|
+
.describe("Maximum number of suggestions to return (1-50). Defaults to 10."),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export type SuggestDomainsInput = z.infer<typeof suggestDomainsSchema>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Tool definition for MCP.
|
|
47
|
+
*/
|
|
48
|
+
export const suggestDomainsTool = {
|
|
49
|
+
name: 'suggest_domains',
|
|
50
|
+
description: `Generate and check availability of domain name variations.
|
|
51
|
+
|
|
52
|
+
Creates variations like:
|
|
53
|
+
- Hyphenated: vibe-coding
|
|
54
|
+
- With numbers: vibecoding1, vibecoding2
|
|
55
|
+
- Prefixes: getvibecoding, tryvibecoding
|
|
56
|
+
- Suffixes: vibecodingapp, vibecodinghq
|
|
57
|
+
|
|
58
|
+
Returns only available suggestions, ranked by quality.
|
|
59
|
+
|
|
60
|
+
Example:
|
|
61
|
+
- suggest_domains("vibecoding") → finds available variations`,
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
properties: {
|
|
65
|
+
base_name: {
|
|
66
|
+
type: 'string',
|
|
67
|
+
description: "The base domain name to generate variations from.",
|
|
68
|
+
},
|
|
69
|
+
tld: {
|
|
70
|
+
type: 'string',
|
|
71
|
+
description: "TLD to check (e.g., 'com'). Defaults to 'com'.",
|
|
72
|
+
},
|
|
73
|
+
variants: {
|
|
74
|
+
type: 'array',
|
|
75
|
+
items: {
|
|
76
|
+
type: 'string',
|
|
77
|
+
enum: ['hyphen', 'numbers', 'abbreviations', 'synonyms', 'prefixes', 'suffixes'],
|
|
78
|
+
},
|
|
79
|
+
description: "Types of variations to generate. Defaults to all.",
|
|
80
|
+
},
|
|
81
|
+
max_suggestions: {
|
|
82
|
+
type: 'number',
|
|
83
|
+
description: "Maximum suggestions to return (1-50). Defaults to 10.",
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
required: ['base_name'],
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Generate domain name variations.
|
|
92
|
+
*/
|
|
93
|
+
function generateVariations(
|
|
94
|
+
baseName: string,
|
|
95
|
+
variantTypes: string[],
|
|
96
|
+
): string[] {
|
|
97
|
+
const variations = new Set<string>();
|
|
98
|
+
|
|
99
|
+
// Always include the original
|
|
100
|
+
variations.add(baseName);
|
|
101
|
+
|
|
102
|
+
if (variantTypes.includes('hyphen') || variantTypes.length === 0) {
|
|
103
|
+
// Add hyphens at word boundaries (heuristic: before capital letters or common words)
|
|
104
|
+
const hyphenated = baseName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
105
|
+
if (hyphenated !== baseName) {
|
|
106
|
+
variations.add(hyphenated);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Try hyphen in the middle
|
|
110
|
+
if (baseName.length >= 6) {
|
|
111
|
+
const mid = Math.floor(baseName.length / 2);
|
|
112
|
+
variations.add(baseName.slice(0, mid) + '-' + baseName.slice(mid));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (variantTypes.includes('numbers') || variantTypes.length === 0) {
|
|
117
|
+
// Add numbers
|
|
118
|
+
for (let i = 1; i <= 3; i++) {
|
|
119
|
+
variations.add(`${baseName}${i}`);
|
|
120
|
+
}
|
|
121
|
+
variations.add(`${baseName}io`);
|
|
122
|
+
variations.add(`${baseName}app`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (variantTypes.includes('prefixes') || variantTypes.length === 0) {
|
|
126
|
+
const prefixes = ['get', 'try', 'use', 'my', 'the', 'go', 'hey', 'hi'];
|
|
127
|
+
for (const prefix of prefixes) {
|
|
128
|
+
variations.add(`${prefix}${baseName}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (variantTypes.includes('suffixes') || variantTypes.length === 0) {
|
|
133
|
+
const suffixes = ['app', 'hq', 'io', 'labs', 'dev', 'ai', 'hub', 'now'];
|
|
134
|
+
for (const suffix of suffixes) {
|
|
135
|
+
variations.add(`${baseName}${suffix}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (variantTypes.includes('abbreviations') || variantTypes.length === 0) {
|
|
140
|
+
// Remove vowels (except first letter)
|
|
141
|
+
const abbreviated = baseName[0] + baseName.slice(1).replace(/[aeiou]/gi, '');
|
|
142
|
+
if (abbreviated.length >= 3 && abbreviated !== baseName) {
|
|
143
|
+
variations.add(abbreviated);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return Array.from(variations);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Score a domain suggestion.
|
|
152
|
+
* Higher score = better suggestion.
|
|
153
|
+
*/
|
|
154
|
+
function scoreSuggestion(
|
|
155
|
+
original: string,
|
|
156
|
+
suggestion: string,
|
|
157
|
+
result: DomainResult,
|
|
158
|
+
): number {
|
|
159
|
+
let score = 50; // Base score
|
|
160
|
+
|
|
161
|
+
// Prefer shorter names
|
|
162
|
+
score -= suggestion.length;
|
|
163
|
+
|
|
164
|
+
// Prefer exact match
|
|
165
|
+
if (suggestion === original) score += 20;
|
|
166
|
+
|
|
167
|
+
// Prefer no hyphens
|
|
168
|
+
if (!suggestion.includes('-')) score += 5;
|
|
169
|
+
|
|
170
|
+
// Prefer no numbers
|
|
171
|
+
if (!/\d/.test(suggestion)) score += 5;
|
|
172
|
+
|
|
173
|
+
// Prefer cheaper prices
|
|
174
|
+
if (result.price_first_year !== null) {
|
|
175
|
+
if (result.price_first_year <= 10) score += 10;
|
|
176
|
+
else if (result.price_first_year <= 15) score += 5;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Penalize premiums
|
|
180
|
+
if (result.premium) score -= 20;
|
|
181
|
+
|
|
182
|
+
// Bonus for privacy included
|
|
183
|
+
if (result.privacy_included) score += 5;
|
|
184
|
+
|
|
185
|
+
return score;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Response format for suggestions.
|
|
190
|
+
*/
|
|
191
|
+
interface SuggestDomainsResponse {
|
|
192
|
+
base_name: string;
|
|
193
|
+
tld: string;
|
|
194
|
+
total_checked: number;
|
|
195
|
+
available_count: number;
|
|
196
|
+
suggestions: Array<{
|
|
197
|
+
domain: string;
|
|
198
|
+
price_first_year: number | null;
|
|
199
|
+
registrar: string;
|
|
200
|
+
score: number;
|
|
201
|
+
}>;
|
|
202
|
+
insights: string[];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Execute the suggest_domains tool.
|
|
207
|
+
*/
|
|
208
|
+
export async function executeSuggestDomains(
|
|
209
|
+
input: SuggestDomainsInput,
|
|
210
|
+
): Promise<SuggestDomainsResponse> {
|
|
211
|
+
try {
|
|
212
|
+
const { base_name, tld, variants, max_suggestions } =
|
|
213
|
+
suggestDomainsSchema.parse(input);
|
|
214
|
+
|
|
215
|
+
const normalizedBase = validateDomainName(base_name);
|
|
216
|
+
const variantTypes = variants || [];
|
|
217
|
+
|
|
218
|
+
// Generate variations
|
|
219
|
+
const variations = generateVariations(normalizedBase, variantTypes);
|
|
220
|
+
|
|
221
|
+
// Limit to max + some buffer for unavailable ones
|
|
222
|
+
const toCheck = variations.slice(0, max_suggestions * 2);
|
|
223
|
+
|
|
224
|
+
// Check availability for all variations
|
|
225
|
+
const results: Array<{ name: string; result: DomainResult | null }> = [];
|
|
226
|
+
|
|
227
|
+
for (const name of toCheck) {
|
|
228
|
+
try {
|
|
229
|
+
const response = await searchDomain(name, [tld]);
|
|
230
|
+
const result = response.results.find((r) => r.domain === `${name}.${tld}`);
|
|
231
|
+
results.push({ name, result: result || null });
|
|
232
|
+
} catch {
|
|
233
|
+
results.push({ name, result: null });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Filter to available and score them
|
|
238
|
+
const available = results
|
|
239
|
+
.filter((r) => r.result?.available)
|
|
240
|
+
.map((r) => ({
|
|
241
|
+
name: r.name,
|
|
242
|
+
result: r.result!,
|
|
243
|
+
score: scoreSuggestion(normalizedBase, r.name, r.result!),
|
|
244
|
+
}))
|
|
245
|
+
.sort((a, b) => b.score - a.score)
|
|
246
|
+
.slice(0, max_suggestions);
|
|
247
|
+
|
|
248
|
+
const insights: string[] = [];
|
|
249
|
+
|
|
250
|
+
if (available.length > 0) {
|
|
251
|
+
insights.push(
|
|
252
|
+
`✅ Found ${available.length} available variation${available.length > 1 ? 's' : ''}`,
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
const best = available[0]!;
|
|
256
|
+
insights.push(
|
|
257
|
+
`⭐ Top suggestion: ${best.name}.${tld} ($${best.result.price_first_year ?? 'unknown'}/year)`,
|
|
258
|
+
);
|
|
259
|
+
} else {
|
|
260
|
+
insights.push(
|
|
261
|
+
`❌ No available variations found for ${normalizedBase}.${tld}`,
|
|
262
|
+
);
|
|
263
|
+
insights.push(
|
|
264
|
+
'💡 Try a different TLD or modify the base name more significantly',
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
base_name: normalizedBase,
|
|
270
|
+
tld,
|
|
271
|
+
total_checked: toCheck.length,
|
|
272
|
+
available_count: available.length,
|
|
273
|
+
suggestions: available.map((a) => ({
|
|
274
|
+
domain: `${a.name}.${tld}`,
|
|
275
|
+
price_first_year: a.result.price_first_year,
|
|
276
|
+
registrar: a.result.registrar,
|
|
277
|
+
score: a.score,
|
|
278
|
+
})),
|
|
279
|
+
insights,
|
|
280
|
+
};
|
|
281
|
+
} catch (error) {
|
|
282
|
+
throw wrapError(error);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tld_info Tool - TLD Information.
|
|
3
|
+
*
|
|
4
|
+
* Get information about a Top Level Domain (TLD).
|
|
5
|
+
* Includes pricing, restrictions, and recommendations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import type { TLDInfo } from '../types.js';
|
|
10
|
+
import { validateTld } from '../utils/validators.js';
|
|
11
|
+
import { wrapError } from '../utils/errors.js';
|
|
12
|
+
import { tldCache, tldCacheKey } from '../utils/cache.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Input schema for tld_info.
|
|
16
|
+
*/
|
|
17
|
+
export const tldInfoSchema = z.object({
|
|
18
|
+
tld: z
|
|
19
|
+
.string()
|
|
20
|
+
.min(2)
|
|
21
|
+
.max(63)
|
|
22
|
+
.describe("The TLD to get information about (e.g., 'com', 'io', 'dev')."),
|
|
23
|
+
detailed: z
|
|
24
|
+
.boolean()
|
|
25
|
+
.optional()
|
|
26
|
+
.default(false)
|
|
27
|
+
.describe("Whether to include detailed information. Defaults to false."),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export type TldInfoInput = z.infer<typeof tldInfoSchema>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Tool definition for MCP.
|
|
34
|
+
*/
|
|
35
|
+
export const tldInfoTool = {
|
|
36
|
+
name: 'tld_info',
|
|
37
|
+
description: `Get information about a Top Level Domain (TLD).
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
- Description and typical use case
|
|
41
|
+
- Price range
|
|
42
|
+
- Any special restrictions
|
|
43
|
+
- Popularity and recommendations
|
|
44
|
+
|
|
45
|
+
Example:
|
|
46
|
+
- tld_info("io") → info about .io domains`,
|
|
47
|
+
inputSchema: {
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {
|
|
50
|
+
tld: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
description: "The TLD to get info about (e.g., 'com', 'io', 'dev').",
|
|
53
|
+
},
|
|
54
|
+
detailed: {
|
|
55
|
+
type: 'boolean',
|
|
56
|
+
description: "Include detailed information. Defaults to false.",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
required: ['tld'],
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Static TLD database.
|
|
65
|
+
*/
|
|
66
|
+
const TLD_DATABASE: Record<string, TLDInfo> = {
|
|
67
|
+
com: {
|
|
68
|
+
tld: 'com',
|
|
69
|
+
description: 'Commercial - the most recognized TLD worldwide',
|
|
70
|
+
typical_use: 'Businesses, commercial websites, general purpose',
|
|
71
|
+
price_range: { min: 8.88, max: 15.99, currency: 'USD' },
|
|
72
|
+
renewal_price_typical: 12.99,
|
|
73
|
+
restrictions: [],
|
|
74
|
+
popularity: 'high',
|
|
75
|
+
category: 'generic',
|
|
76
|
+
},
|
|
77
|
+
io: {
|
|
78
|
+
tld: 'io',
|
|
79
|
+
description: 'British Indian Ocean Territory - popular with tech startups',
|
|
80
|
+
typical_use: 'Tech startups, SaaS products, developer tools',
|
|
81
|
+
price_range: { min: 29.88, max: 59.99, currency: 'USD' },
|
|
82
|
+
renewal_price_typical: 44.99,
|
|
83
|
+
restrictions: [],
|
|
84
|
+
popularity: 'high',
|
|
85
|
+
category: 'country',
|
|
86
|
+
},
|
|
87
|
+
dev: {
|
|
88
|
+
tld: 'dev',
|
|
89
|
+
description: 'Developer - for software developers and their projects',
|
|
90
|
+
typical_use: 'Developer portfolios, tools, documentation sites',
|
|
91
|
+
price_range: { min: 10.18, max: 19.99, currency: 'USD' },
|
|
92
|
+
renewal_price_typical: 14.99,
|
|
93
|
+
restrictions: ['Requires HTTPS (HSTS preloaded)'],
|
|
94
|
+
popularity: 'medium',
|
|
95
|
+
category: 'new',
|
|
96
|
+
},
|
|
97
|
+
app: {
|
|
98
|
+
tld: 'app',
|
|
99
|
+
description: 'Application - for mobile and web applications',
|
|
100
|
+
typical_use: 'Mobile apps, web applications, software products',
|
|
101
|
+
price_range: { min: 11.18, max: 19.99, currency: 'USD' },
|
|
102
|
+
renewal_price_typical: 14.99,
|
|
103
|
+
restrictions: ['Requires HTTPS (HSTS preloaded)'],
|
|
104
|
+
popularity: 'medium',
|
|
105
|
+
category: 'new',
|
|
106
|
+
},
|
|
107
|
+
co: {
|
|
108
|
+
tld: 'co',
|
|
109
|
+
description: 'Colombia / Company - popular alternative to .com',
|
|
110
|
+
typical_use: 'Companies, startups, short URLs',
|
|
111
|
+
price_range: { min: 9.48, max: 29.99, currency: 'USD' },
|
|
112
|
+
renewal_price_typical: 24.99,
|
|
113
|
+
restrictions: [],
|
|
114
|
+
popularity: 'high',
|
|
115
|
+
category: 'country',
|
|
116
|
+
},
|
|
117
|
+
net: {
|
|
118
|
+
tld: 'net',
|
|
119
|
+
description: 'Network - originally for network providers',
|
|
120
|
+
typical_use: 'Technology companies, network services, ISPs',
|
|
121
|
+
price_range: { min: 9.88, max: 14.99, currency: 'USD' },
|
|
122
|
+
renewal_price_typical: 12.99,
|
|
123
|
+
restrictions: [],
|
|
124
|
+
popularity: 'high',
|
|
125
|
+
category: 'generic',
|
|
126
|
+
},
|
|
127
|
+
org: {
|
|
128
|
+
tld: 'org',
|
|
129
|
+
description: 'Organization - for non-profits and communities',
|
|
130
|
+
typical_use: 'Non-profit organizations, open source projects, communities',
|
|
131
|
+
price_range: { min: 9.88, max: 14.99, currency: 'USD' },
|
|
132
|
+
renewal_price_typical: 12.99,
|
|
133
|
+
restrictions: [],
|
|
134
|
+
popularity: 'high',
|
|
135
|
+
category: 'generic',
|
|
136
|
+
},
|
|
137
|
+
ai: {
|
|
138
|
+
tld: 'ai',
|
|
139
|
+
description: 'Anguilla / Artificial Intelligence - trending for AI projects',
|
|
140
|
+
typical_use: 'AI/ML projects, tech startups, research',
|
|
141
|
+
price_range: { min: 49.88, max: 99.99, currency: 'USD' },
|
|
142
|
+
renewal_price_typical: 79.99,
|
|
143
|
+
restrictions: [],
|
|
144
|
+
popularity: 'medium',
|
|
145
|
+
category: 'country',
|
|
146
|
+
},
|
|
147
|
+
xyz: {
|
|
148
|
+
tld: 'xyz',
|
|
149
|
+
description: 'XYZ - for the next generation of internet users',
|
|
150
|
+
typical_use: 'Creative projects, personal sites, unconventional brands',
|
|
151
|
+
price_range: { min: 1.99, max: 12.99, currency: 'USD' },
|
|
152
|
+
renewal_price_typical: 12.99,
|
|
153
|
+
restrictions: [],
|
|
154
|
+
popularity: 'medium',
|
|
155
|
+
category: 'new',
|
|
156
|
+
},
|
|
157
|
+
me: {
|
|
158
|
+
tld: 'me',
|
|
159
|
+
description: 'Montenegro - popular for personal brands',
|
|
160
|
+
typical_use: 'Personal websites, portfolios, URL shorteners',
|
|
161
|
+
price_range: { min: 9.88, max: 19.99, currency: 'USD' },
|
|
162
|
+
renewal_price_typical: 17.99,
|
|
163
|
+
restrictions: [],
|
|
164
|
+
popularity: 'medium',
|
|
165
|
+
category: 'country',
|
|
166
|
+
},
|
|
167
|
+
sh: {
|
|
168
|
+
tld: 'sh',
|
|
169
|
+
description: 'Saint Helena - popular with developers (shell scripts)',
|
|
170
|
+
typical_use: 'Developer tools, CLI applications, tech projects',
|
|
171
|
+
price_range: { min: 29.88, max: 49.99, currency: 'USD' },
|
|
172
|
+
renewal_price_typical: 44.99,
|
|
173
|
+
restrictions: [],
|
|
174
|
+
popularity: 'low',
|
|
175
|
+
category: 'country',
|
|
176
|
+
},
|
|
177
|
+
cc: {
|
|
178
|
+
tld: 'cc',
|
|
179
|
+
description: 'Cocos Islands - often used as "Creative Commons"',
|
|
180
|
+
typical_use: 'Creative projects, alternative to .com',
|
|
181
|
+
price_range: { min: 9.88, max: 24.99, currency: 'USD' },
|
|
182
|
+
renewal_price_typical: 19.99,
|
|
183
|
+
restrictions: [],
|
|
184
|
+
popularity: 'low',
|
|
185
|
+
category: 'country',
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Response format for TLD info.
|
|
191
|
+
*/
|
|
192
|
+
interface TldInfoResponse extends TLDInfo {
|
|
193
|
+
insights: string[];
|
|
194
|
+
recommendation: string;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Execute the tld_info tool.
|
|
199
|
+
*/
|
|
200
|
+
export async function executeTldInfo(
|
|
201
|
+
input: TldInfoInput,
|
|
202
|
+
): Promise<TldInfoResponse> {
|
|
203
|
+
try {
|
|
204
|
+
const { tld, detailed } = tldInfoSchema.parse(input);
|
|
205
|
+
const normalizedTld = validateTld(tld);
|
|
206
|
+
|
|
207
|
+
// Check cache
|
|
208
|
+
const cacheKey = tldCacheKey(normalizedTld);
|
|
209
|
+
const cached = tldCache.get(cacheKey);
|
|
210
|
+
if (cached) {
|
|
211
|
+
return formatResponse(cached, detailed);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Look up in database
|
|
215
|
+
const info = TLD_DATABASE[normalizedTld];
|
|
216
|
+
|
|
217
|
+
if (!info) {
|
|
218
|
+
// Return generic info for unknown TLDs
|
|
219
|
+
const genericInfo: TLDInfo = {
|
|
220
|
+
tld: normalizedTld,
|
|
221
|
+
description: `${normalizedTld.toUpperCase()} domain extension`,
|
|
222
|
+
typical_use: 'General purpose',
|
|
223
|
+
price_range: { min: 10, max: 50, currency: 'USD' },
|
|
224
|
+
renewal_price_typical: 20,
|
|
225
|
+
restrictions: ['Check registrar for specific restrictions'],
|
|
226
|
+
popularity: 'low',
|
|
227
|
+
category: 'generic',
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
return formatResponse(genericInfo, detailed);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Cache the result
|
|
234
|
+
tldCache.set(cacheKey, info);
|
|
235
|
+
|
|
236
|
+
return formatResponse(info, detailed);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
throw wrapError(error);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Format the response with insights.
|
|
244
|
+
*/
|
|
245
|
+
function formatResponse(info: TLDInfo, detailed: boolean): TldInfoResponse {
|
|
246
|
+
const insights: string[] = [];
|
|
247
|
+
let recommendation = '';
|
|
248
|
+
|
|
249
|
+
// Generate insights
|
|
250
|
+
if (info.popularity === 'high') {
|
|
251
|
+
insights.push(`✅ .${info.tld} is highly recognized and trusted`);
|
|
252
|
+
} else if (info.popularity === 'medium') {
|
|
253
|
+
insights.push(`💡 .${info.tld} is gaining popularity in specific niches`);
|
|
254
|
+
} else {
|
|
255
|
+
insights.push(`⚠️ .${info.tld} is less common - may need more brand building`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (info.restrictions.length > 0) {
|
|
259
|
+
insights.push(`⚠️ Special requirements: ${info.restrictions.join(', ')}`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (info.price_range.min <= 10) {
|
|
263
|
+
insights.push(`💰 Budget-friendly starting at $${info.price_range.min}/year`);
|
|
264
|
+
} else if (info.price_range.min >= 40) {
|
|
265
|
+
insights.push(`💸 Premium pricing starting at $${info.price_range.min}/year`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Generate recommendation
|
|
269
|
+
switch (info.tld) {
|
|
270
|
+
case 'com':
|
|
271
|
+
recommendation = 'Best for mainstream businesses and maximum recognition';
|
|
272
|
+
break;
|
|
273
|
+
case 'io':
|
|
274
|
+
recommendation = 'Perfect for tech startups and SaaS products';
|
|
275
|
+
break;
|
|
276
|
+
case 'dev':
|
|
277
|
+
recommendation = 'Ideal for developers and tech portfolios (requires HTTPS)';
|
|
278
|
+
break;
|
|
279
|
+
case 'app':
|
|
280
|
+
recommendation = 'Great for mobile/web applications (requires HTTPS)';
|
|
281
|
+
break;
|
|
282
|
+
case 'ai':
|
|
283
|
+
recommendation = 'Trending choice for AI/ML projects, but pricey';
|
|
284
|
+
break;
|
|
285
|
+
default:
|
|
286
|
+
recommendation = `Good choice for ${info.typical_use.toLowerCase()}`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
...info,
|
|
291
|
+
insights,
|
|
292
|
+
recommendation,
|
|
293
|
+
};
|
|
294
|
+
}
|