domain-search-mcp 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +57 -1
  2. package/context7.json +4 -0
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +1 -0
  5. package/dist/config.js.map +1 -1
  6. package/dist/registrars/godaddy-mcp.d.ts +67 -0
  7. package/dist/registrars/godaddy-mcp.d.ts.map +1 -0
  8. package/dist/registrars/godaddy-mcp.js +301 -0
  9. package/dist/registrars/godaddy-mcp.js.map +1 -0
  10. package/dist/registrars/index.d.ts +1 -0
  11. package/dist/registrars/index.d.ts.map +1 -1
  12. package/dist/registrars/index.js +4 -1
  13. package/dist/registrars/index.js.map +1 -1
  14. package/dist/server.d.ts +1 -0
  15. package/dist/server.d.ts.map +1 -1
  16. package/dist/server.js +11 -0
  17. package/dist/server.js.map +1 -1
  18. package/dist/services/domain-search.d.ts +5 -4
  19. package/dist/services/domain-search.d.ts.map +1 -1
  20. package/dist/services/domain-search.js +24 -8
  21. package/dist/services/domain-search.js.map +1 -1
  22. package/dist/tools/check_socials.d.ts.map +1 -1
  23. package/dist/tools/check_socials.js +21 -6
  24. package/dist/tools/check_socials.js.map +1 -1
  25. package/dist/tools/index.d.ts +1 -0
  26. package/dist/tools/index.d.ts.map +1 -1
  27. package/dist/tools/index.js +5 -1
  28. package/dist/tools/index.js.map +1 -1
  29. package/dist/tools/suggest_domains_smart.d.ts +113 -0
  30. package/dist/tools/suggest_domains_smart.d.ts.map +1 -0
  31. package/dist/tools/suggest_domains_smart.js +311 -0
  32. package/dist/tools/suggest_domains_smart.js.map +1 -0
  33. package/dist/types.d.ts +2 -0
  34. package/dist/types.d.ts.map +1 -1
  35. package/dist/utils/semantic-engine.d.ts +59 -0
  36. package/dist/utils/semantic-engine.d.ts.map +1 -0
  37. package/dist/utils/semantic-engine.js +427 -0
  38. package/dist/utils/semantic-engine.js.map +1 -0
  39. package/package.json +1 -1
  40. package/src/config.ts +1 -0
  41. package/src/registrars/godaddy-mcp.ts +380 -0
  42. package/src/registrars/index.ts +1 -0
  43. package/src/server.ts +16 -0
  44. package/src/services/domain-search.ts +25 -9
  45. package/src/tools/check_socials.ts +22 -6
  46. package/src/tools/index.ts +7 -0
  47. package/src/tools/suggest_domains_smart.ts +392 -0
  48. package/src/types.ts +2 -0
  49. package/src/utils/semantic-engine.ts +483 -0
@@ -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
+ }
package/src/types.ts CHANGED
@@ -166,6 +166,8 @@ export interface SocialHandleResult {
166
166
  checked_at: string;
167
167
  /** Some platforms can't be reliably checked */
168
168
  confidence: 'high' | 'medium' | 'low';
169
+ /** Error message if check failed (rate limit, timeout, etc.) */
170
+ error?: string;
169
171
  }
170
172
 
171
173
  // ═══════════════════════════════════════════════════════════════════════════