domain-search-mcp 1.0.0 → 1.1.1
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/context7.json +4 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +11 -0
- package/dist/server.js.map +1 -1
- package/dist/tools/check_socials.d.ts.map +1 -1
- package/dist/tools/check_socials.js +7 -6
- package/dist/tools/check_socials.js.map +1 -1
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +5 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/suggest_domains_smart.d.ts +113 -0
- package/dist/tools/suggest_domains_smart.d.ts.map +1 -0
- package/dist/tools/suggest_domains_smart.js +311 -0
- package/dist/tools/suggest_domains_smart.js.map +1 -0
- package/dist/utils/semantic-engine.d.ts +59 -0
- package/dist/utils/semantic-engine.d.ts.map +1 -0
- package/dist/utils/semantic-engine.js +427 -0
- package/dist/utils/semantic-engine.js.map +1 -0
- package/package.json +1 -1
- package/src/server.ts +16 -0
- package/src/tools/check_socials.ts +7 -6
- package/src/tools/index.ts +7 -0
- package/src/tools/suggest_domains_smart.ts +392 -0
- package/src/utils/semantic-engine.ts +483 -0
package/src/tools/index.ts
CHANGED
|
@@ -30,6 +30,13 @@ export {
|
|
|
30
30
|
type SuggestDomainsInput,
|
|
31
31
|
} from './suggest_domains.js';
|
|
32
32
|
|
|
33
|
+
export {
|
|
34
|
+
suggestDomainsSmartTool,
|
|
35
|
+
suggestDomainsSmartSchema,
|
|
36
|
+
executeSuggestDomainsSmart,
|
|
37
|
+
type SuggestDomainsSmartInput,
|
|
38
|
+
} from './suggest_domains_smart.js';
|
|
39
|
+
|
|
33
40
|
export {
|
|
34
41
|
tldInfoTool,
|
|
35
42
|
tldInfoSchema,
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* suggest_domains_smart Tool - AI-like Domain Name Suggestions.
|
|
3
|
+
*
|
|
4
|
+
* Advanced domain suggestion engine using semantic analysis,
|
|
5
|
+
* synonym expansion, industry detection, and creative algorithms.
|
|
6
|
+
* No external AI dependencies - fully native implementation.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
import { searchDomain } from '../services/domain-search.js';
|
|
11
|
+
import { validateDomainName } from '../utils/validators.js';
|
|
12
|
+
import { wrapError } from '../utils/errors.js';
|
|
13
|
+
import {
|
|
14
|
+
generateSmartSuggestions,
|
|
15
|
+
segmentWords,
|
|
16
|
+
detectIndustry,
|
|
17
|
+
scoreDomainName,
|
|
18
|
+
getSynonyms,
|
|
19
|
+
getIndustryTerms,
|
|
20
|
+
} from '../utils/semantic-engine.js';
|
|
21
|
+
import type { DomainResult } from '../types.js';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Premium price thresholds by TLD (first year price in USD).
|
|
25
|
+
* If price exceeds threshold, domain is marked as premium.
|
|
26
|
+
*/
|
|
27
|
+
const PREMIUM_THRESHOLDS: Record<string, number> = {
|
|
28
|
+
com: 15,
|
|
29
|
+
net: 15,
|
|
30
|
+
org: 15,
|
|
31
|
+
io: 50,
|
|
32
|
+
co: 35,
|
|
33
|
+
ai: 80,
|
|
34
|
+
dev: 20,
|
|
35
|
+
app: 20,
|
|
36
|
+
xyz: 15,
|
|
37
|
+
tech: 50,
|
|
38
|
+
default: 30,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Detect if a domain is premium based on price.
|
|
43
|
+
*/
|
|
44
|
+
function isPremiumPrice(tld: string, price: number | null): boolean {
|
|
45
|
+
if (price === null) return false;
|
|
46
|
+
const threshold = PREMIUM_THRESHOLDS[tld] || PREMIUM_THRESHOLDS.default!;
|
|
47
|
+
return price > threshold;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Input schema for suggest_domains_smart.
|
|
52
|
+
*/
|
|
53
|
+
export const suggestDomainsSmartSchema = z.object({
|
|
54
|
+
query: z
|
|
55
|
+
.string()
|
|
56
|
+
.min(1)
|
|
57
|
+
.max(200)
|
|
58
|
+
.describe(
|
|
59
|
+
"Search query - can be keywords, business description, or domain name. " +
|
|
60
|
+
"Examples: 'coffee shop seattle', 'ai startup', 'vibecoding'"
|
|
61
|
+
),
|
|
62
|
+
tld: z
|
|
63
|
+
.string()
|
|
64
|
+
.optional()
|
|
65
|
+
.default('com')
|
|
66
|
+
.describe("Primary TLD to check. Defaults to 'com'."),
|
|
67
|
+
industry: z
|
|
68
|
+
.enum(['tech', 'startup', 'finance', 'health', 'food', 'creative', 'ecommerce', 'education', 'gaming', 'social'])
|
|
69
|
+
.optional()
|
|
70
|
+
.describe("Industry context for better suggestions. Auto-detected if not provided."),
|
|
71
|
+
style: z
|
|
72
|
+
.enum(['brandable', 'descriptive', 'short', 'creative'])
|
|
73
|
+
.optional()
|
|
74
|
+
.default('brandable')
|
|
75
|
+
.describe(
|
|
76
|
+
"Suggestion style: 'brandable' (unique names), 'descriptive' (keyword-based), " +
|
|
77
|
+
"'short' (minimal length), 'creative' (playful combinations)."
|
|
78
|
+
),
|
|
79
|
+
max_suggestions: z
|
|
80
|
+
.number()
|
|
81
|
+
.int()
|
|
82
|
+
.min(1)
|
|
83
|
+
.max(50)
|
|
84
|
+
.optional()
|
|
85
|
+
.default(15)
|
|
86
|
+
.describe("Maximum suggestions to return (1-50). Defaults to 15."),
|
|
87
|
+
include_premium: z
|
|
88
|
+
.boolean()
|
|
89
|
+
.optional()
|
|
90
|
+
.default(false)
|
|
91
|
+
.describe("Include premium-priced domains in results. Defaults to false."),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
export type SuggestDomainsSmartInput = z.infer<typeof suggestDomainsSmartSchema>;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Tool definition for MCP.
|
|
98
|
+
*/
|
|
99
|
+
export const suggestDomainsSmartTool = {
|
|
100
|
+
name: 'suggest_domains_smart',
|
|
101
|
+
description: `AI-powered domain name suggestion engine.
|
|
102
|
+
|
|
103
|
+
Generate creative, brandable domain names from keywords or business descriptions.
|
|
104
|
+
Uses semantic analysis, synonym expansion, and industry-specific vocabulary.
|
|
105
|
+
|
|
106
|
+
Features:
|
|
107
|
+
- Understands natural language queries ("coffee shop in seattle")
|
|
108
|
+
- Auto-detects industry for contextual suggestions
|
|
109
|
+
- Generates portmanteau/blended names (instagram = instant + telegram)
|
|
110
|
+
- Applies modern naming patterns (ly, ify, io, hub, etc.)
|
|
111
|
+
- Filters premium domains by default
|
|
112
|
+
|
|
113
|
+
Examples:
|
|
114
|
+
- suggest_domains_smart("ai customer service") → AI-themed suggestions
|
|
115
|
+
- suggest_domains_smart("organic coffee", industry="food") → Food-focused names
|
|
116
|
+
- suggest_domains_smart("vibecoding", style="short") → Minimal length names`,
|
|
117
|
+
inputSchema: {
|
|
118
|
+
type: 'object',
|
|
119
|
+
properties: {
|
|
120
|
+
query: {
|
|
121
|
+
type: 'string',
|
|
122
|
+
description: "Keywords, business description, or base domain name.",
|
|
123
|
+
},
|
|
124
|
+
tld: {
|
|
125
|
+
type: 'string',
|
|
126
|
+
description: "TLD to check (e.g., 'com'). Defaults to 'com'.",
|
|
127
|
+
},
|
|
128
|
+
industry: {
|
|
129
|
+
type: 'string',
|
|
130
|
+
enum: ['tech', 'startup', 'finance', 'health', 'food', 'creative', 'ecommerce', 'education', 'gaming', 'social'],
|
|
131
|
+
description: "Industry for contextual suggestions. Auto-detected if omitted.",
|
|
132
|
+
},
|
|
133
|
+
style: {
|
|
134
|
+
type: 'string',
|
|
135
|
+
enum: ['brandable', 'descriptive', 'short', 'creative'],
|
|
136
|
+
description: "Suggestion style preference.",
|
|
137
|
+
},
|
|
138
|
+
max_suggestions: {
|
|
139
|
+
type: 'number',
|
|
140
|
+
description: "Maximum suggestions to return (1-50). Defaults to 15.",
|
|
141
|
+
},
|
|
142
|
+
include_premium: {
|
|
143
|
+
type: 'boolean',
|
|
144
|
+
description: "Include premium domains. Defaults to false.",
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
required: ['query'],
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Apply style-specific filtering and scoring adjustments.
|
|
153
|
+
*/
|
|
154
|
+
function applyStyleFilter(
|
|
155
|
+
suggestions: string[],
|
|
156
|
+
style: string,
|
|
157
|
+
originalQuery: string,
|
|
158
|
+
): string[] {
|
|
159
|
+
switch (style) {
|
|
160
|
+
case 'short':
|
|
161
|
+
return suggestions
|
|
162
|
+
.filter(s => s.length <= 8)
|
|
163
|
+
.sort((a, b) => a.length - b.length);
|
|
164
|
+
|
|
165
|
+
case 'descriptive':
|
|
166
|
+
// Prefer suggestions that contain original words
|
|
167
|
+
const words = segmentWords(originalQuery);
|
|
168
|
+
return suggestions.sort((a, b) => {
|
|
169
|
+
const aMatches = words.filter(w => a.includes(w)).length;
|
|
170
|
+
const bMatches = words.filter(w => b.includes(w)).length;
|
|
171
|
+
return bMatches - aMatches;
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
case 'creative':
|
|
175
|
+
// Prefer longer, more unique combinations
|
|
176
|
+
return suggestions
|
|
177
|
+
.filter(s => s.length >= 6)
|
|
178
|
+
.sort((a, b) => {
|
|
179
|
+
const aScore = a.length + (a.match(/[aeiouy]/g)?.length || 0) * 2;
|
|
180
|
+
const bScore = b.length + (b.match(/[aeiouy]/g)?.length || 0) * 2;
|
|
181
|
+
return bScore - aScore;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
case 'brandable':
|
|
185
|
+
default:
|
|
186
|
+
// Balanced approach - pronounceable, medium length
|
|
187
|
+
return suggestions.sort((a, b) => {
|
|
188
|
+
const aScore = scoreDomainName(a, originalQuery);
|
|
189
|
+
const bScore = scoreDomainName(b, originalQuery);
|
|
190
|
+
return bScore - aScore;
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Suggestion result with extended metadata.
|
|
197
|
+
*/
|
|
198
|
+
interface SmartSuggestion {
|
|
199
|
+
domain: string;
|
|
200
|
+
available: boolean;
|
|
201
|
+
price_first_year: number | null;
|
|
202
|
+
price_renewal: number | null;
|
|
203
|
+
registrar: string;
|
|
204
|
+
premium: boolean;
|
|
205
|
+
premium_detected: boolean; // Our detection based on price
|
|
206
|
+
privacy_included: boolean;
|
|
207
|
+
score: number;
|
|
208
|
+
category: 'standard' | 'premium' | 'auction' | 'unavailable';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Response format for smart suggestions.
|
|
213
|
+
*/
|
|
214
|
+
interface SuggestDomainsSmartResponse {
|
|
215
|
+
query: string;
|
|
216
|
+
detected_words: string[];
|
|
217
|
+
detected_industry: string | null;
|
|
218
|
+
tld: string;
|
|
219
|
+
style: string;
|
|
220
|
+
total_generated: number;
|
|
221
|
+
total_checked: number;
|
|
222
|
+
results: {
|
|
223
|
+
available: SmartSuggestion[];
|
|
224
|
+
premium: SmartSuggestion[];
|
|
225
|
+
unavailable_count: number;
|
|
226
|
+
};
|
|
227
|
+
insights: string[];
|
|
228
|
+
related_terms: string[];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Execute the suggest_domains_smart tool.
|
|
233
|
+
*/
|
|
234
|
+
export async function executeSuggestDomainsSmart(
|
|
235
|
+
input: SuggestDomainsSmartInput,
|
|
236
|
+
): Promise<SuggestDomainsSmartResponse> {
|
|
237
|
+
try {
|
|
238
|
+
const { query, tld, industry, style, max_suggestions, include_premium } =
|
|
239
|
+
suggestDomainsSmartSchema.parse(input);
|
|
240
|
+
|
|
241
|
+
// Normalize and analyze input
|
|
242
|
+
const normalizedQuery = query.toLowerCase().trim();
|
|
243
|
+
const detectedWords = segmentWords(normalizedQuery);
|
|
244
|
+
const detectedIndustry = industry || detectIndustry(detectedWords);
|
|
245
|
+
|
|
246
|
+
// Generate smart suggestions
|
|
247
|
+
const rawSuggestions = generateSmartSuggestions(normalizedQuery, {
|
|
248
|
+
maxSuggestions: max_suggestions * 4, // Generate extra for filtering
|
|
249
|
+
includePortmanteau: style === 'creative' || style === 'brandable',
|
|
250
|
+
includeSynonyms: style !== 'short',
|
|
251
|
+
includeIndustryTerms: !!detectedIndustry,
|
|
252
|
+
industry: detectedIndustry || undefined,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Apply style filter
|
|
256
|
+
const styledSuggestions = applyStyleFilter(rawSuggestions, style, normalizedQuery);
|
|
257
|
+
|
|
258
|
+
// Limit candidates for availability check
|
|
259
|
+
const candidates = styledSuggestions.slice(0, max_suggestions * 2);
|
|
260
|
+
|
|
261
|
+
// Check availability in parallel batches
|
|
262
|
+
const BATCH_SIZE = 5;
|
|
263
|
+
const results: Array<{ name: string; result: DomainResult | null }> = [];
|
|
264
|
+
|
|
265
|
+
for (let i = 0; i < candidates.length; i += BATCH_SIZE) {
|
|
266
|
+
const batch = candidates.slice(i, i + BATCH_SIZE);
|
|
267
|
+
const batchResults = await Promise.all(
|
|
268
|
+
batch.map(async (name) => {
|
|
269
|
+
try {
|
|
270
|
+
const response = await searchDomain(name, [tld]);
|
|
271
|
+
const result = response.results.find((r) => r.domain === `${name}.${tld}`);
|
|
272
|
+
return { name, result: result || null };
|
|
273
|
+
} catch {
|
|
274
|
+
return { name, result: null };
|
|
275
|
+
}
|
|
276
|
+
}),
|
|
277
|
+
);
|
|
278
|
+
results.push(...batchResults);
|
|
279
|
+
|
|
280
|
+
// Early exit if we have enough available
|
|
281
|
+
const availableCount = results.filter(r => r.result?.available && !r.result?.premium).length;
|
|
282
|
+
if (availableCount >= max_suggestions && !include_premium) {
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Categorize results
|
|
288
|
+
const available: SmartSuggestion[] = [];
|
|
289
|
+
const premium: SmartSuggestion[] = [];
|
|
290
|
+
let unavailableCount = 0;
|
|
291
|
+
|
|
292
|
+
for (const { name, result } of results) {
|
|
293
|
+
if (!result) {
|
|
294
|
+
unavailableCount++;
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const isPremium = result.premium || isPremiumPrice(tld, result.price_first_year);
|
|
299
|
+
|
|
300
|
+
const suggestion: SmartSuggestion = {
|
|
301
|
+
domain: `${name}.${tld}`,
|
|
302
|
+
available: result.available,
|
|
303
|
+
price_first_year: result.price_first_year,
|
|
304
|
+
price_renewal: result.price_renewal,
|
|
305
|
+
registrar: result.registrar,
|
|
306
|
+
premium: result.premium || false,
|
|
307
|
+
premium_detected: isPremiumPrice(tld, result.price_first_year),
|
|
308
|
+
privacy_included: result.privacy_included || false,
|
|
309
|
+
score: scoreDomainName(name, normalizedQuery),
|
|
310
|
+
category: !result.available
|
|
311
|
+
? 'unavailable'
|
|
312
|
+
: isPremium
|
|
313
|
+
? 'premium'
|
|
314
|
+
: 'standard',
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
if (!result.available) {
|
|
318
|
+
unavailableCount++;
|
|
319
|
+
} else if (isPremium) {
|
|
320
|
+
premium.push(suggestion);
|
|
321
|
+
} else {
|
|
322
|
+
available.push(suggestion);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Sort by score
|
|
327
|
+
available.sort((a, b) => b.score - a.score);
|
|
328
|
+
premium.sort((a, b) => b.score - a.score);
|
|
329
|
+
|
|
330
|
+
// Limit results
|
|
331
|
+
const finalAvailable = available.slice(0, max_suggestions);
|
|
332
|
+
const finalPremium = include_premium ? premium.slice(0, Math.floor(max_suggestions / 2)) : [];
|
|
333
|
+
|
|
334
|
+
// Generate insights
|
|
335
|
+
const insights: string[] = [];
|
|
336
|
+
|
|
337
|
+
if (detectedIndustry) {
|
|
338
|
+
insights.push(`🎯 Detected industry: ${detectedIndustry}`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (detectedWords.length > 1) {
|
|
342
|
+
insights.push(`📝 Parsed keywords: ${detectedWords.join(', ')}`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (finalAvailable.length > 0) {
|
|
346
|
+
insights.push(`✅ Found ${finalAvailable.length} available domain${finalAvailable.length > 1 ? 's' : ''}`);
|
|
347
|
+
const best = finalAvailable[0]!;
|
|
348
|
+
const priceStr = best.price_first_year !== null ? `$${best.price_first_year}/yr` : 'price unknown';
|
|
349
|
+
insights.push(`⭐ Top pick: ${best.domain} (${priceStr})`);
|
|
350
|
+
} else {
|
|
351
|
+
insights.push(`❌ No standard-priced domains available`);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (premium.length > 0) {
|
|
355
|
+
insights.push(`💎 ${premium.length} premium domain${premium.length > 1 ? 's' : ''} available`);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (finalAvailable.length < 3) {
|
|
359
|
+
insights.push(`💡 Try different keywords or a different TLD (.io, .co, .dev)`);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Get related terms for user reference
|
|
363
|
+
const relatedTerms: string[] = [];
|
|
364
|
+
for (const word of detectedWords.slice(0, 3)) {
|
|
365
|
+
const synonyms = getSynonyms(word);
|
|
366
|
+
relatedTerms.push(...synonyms.slice(0, 2));
|
|
367
|
+
}
|
|
368
|
+
if (detectedIndustry) {
|
|
369
|
+
const industryTerms = getIndustryTerms(detectedIndustry);
|
|
370
|
+
relatedTerms.push(...industryTerms.slice(0, 4));
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
query: normalizedQuery,
|
|
375
|
+
detected_words: detectedWords,
|
|
376
|
+
detected_industry: detectedIndustry,
|
|
377
|
+
tld,
|
|
378
|
+
style,
|
|
379
|
+
total_generated: rawSuggestions.length,
|
|
380
|
+
total_checked: results.length,
|
|
381
|
+
results: {
|
|
382
|
+
available: finalAvailable,
|
|
383
|
+
premium: finalPremium,
|
|
384
|
+
unavailable_count: unavailableCount,
|
|
385
|
+
},
|
|
386
|
+
insights,
|
|
387
|
+
related_terms: [...new Set(relatedTerms)].slice(0, 10),
|
|
388
|
+
};
|
|
389
|
+
} catch (error) {
|
|
390
|
+
throw wrapError(error);
|
|
391
|
+
}
|
|
392
|
+
}
|