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.
- package/README.md +57 -1
- package/context7.json +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/registrars/godaddy-mcp.d.ts +67 -0
- package/dist/registrars/godaddy-mcp.d.ts.map +1 -0
- package/dist/registrars/godaddy-mcp.js +301 -0
- package/dist/registrars/godaddy-mcp.js.map +1 -0
- package/dist/registrars/index.d.ts +1 -0
- package/dist/registrars/index.d.ts.map +1 -1
- package/dist/registrars/index.js +4 -1
- package/dist/registrars/index.js.map +1 -1
- 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/services/domain-search.d.ts +5 -4
- package/dist/services/domain-search.d.ts.map +1 -1
- package/dist/services/domain-search.js +24 -8
- package/dist/services/domain-search.js.map +1 -1
- package/dist/tools/check_socials.d.ts.map +1 -1
- package/dist/tools/check_socials.js +21 -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/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- 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/config.ts +1 -0
- package/src/registrars/godaddy-mcp.ts +380 -0
- package/src/registrars/index.ts +1 -0
- package/src/server.ts +16 -0
- package/src/services/domain-search.ts +25 -9
- package/src/tools/check_socials.ts +22 -6
- package/src/tools/index.ts +7 -0
- package/src/tools/suggest_domains_smart.ts +392 -0
- package/src/types.ts +2 -0
- package/src/utils/semantic-engine.ts +483 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GoDaddy MCP Adapter.
|
|
3
|
+
*
|
|
4
|
+
* Uses GoDaddy's public MCP endpoint for domain availability checks.
|
|
5
|
+
* No API key or reseller account required!
|
|
6
|
+
*
|
|
7
|
+
* Endpoint: https://api.godaddy.com/v1/domains/mcp
|
|
8
|
+
* Protocol: JSON-RPC 2.0 over HTTP (Streamable HTTP transport)
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Free availability checking (no auth)
|
|
12
|
+
* - Bulk checking up to 1000 domains
|
|
13
|
+
* - Premium/auction domain detection
|
|
14
|
+
*
|
|
15
|
+
* Limitations:
|
|
16
|
+
* - No pricing information
|
|
17
|
+
* - Rate limits not documented (be conservative)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { z } from 'zod';
|
|
21
|
+
import { RegistrarAdapter, RateLimiter } from './base.js';
|
|
22
|
+
import type { DomainResult, TLDInfo } from '../types.js';
|
|
23
|
+
import { logger } from '../utils/logger.js';
|
|
24
|
+
import { RegistrarApiError } from '../utils/errors.js';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* GoDaddy MCP endpoint.
|
|
28
|
+
*/
|
|
29
|
+
const GODADDY_MCP_ENDPOINT = 'https://api.godaddy.com/v1/domains/mcp';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* JSON-RPC request ID counter.
|
|
33
|
+
*/
|
|
34
|
+
let jsonRpcId = 1;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Response schema for MCP tool call.
|
|
38
|
+
*/
|
|
39
|
+
const McpResponseSchema = z.object({
|
|
40
|
+
jsonrpc: z.literal('2.0'),
|
|
41
|
+
id: z.number(),
|
|
42
|
+
result: z.object({
|
|
43
|
+
content: z.array(z.object({
|
|
44
|
+
type: z.string(),
|
|
45
|
+
text: z.string(),
|
|
46
|
+
})),
|
|
47
|
+
isError: z.boolean().optional(),
|
|
48
|
+
}).optional(),
|
|
49
|
+
error: z.object({
|
|
50
|
+
code: z.number(),
|
|
51
|
+
message: z.string(),
|
|
52
|
+
}).optional(),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Parse availability from GoDaddy MCP text response.
|
|
57
|
+
* The response is markdown-formatted text with different formats for single vs bulk queries.
|
|
58
|
+
*/
|
|
59
|
+
interface ParsedAvailability {
|
|
60
|
+
available: boolean;
|
|
61
|
+
premium: boolean;
|
|
62
|
+
auction: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parseAvailabilityResponse(text: string, domain: string): ParsedAvailability {
|
|
66
|
+
const normalizedDomain = domain.toLowerCase();
|
|
67
|
+
const normalizedText = text.toLowerCase();
|
|
68
|
+
|
|
69
|
+
// Default: unavailable
|
|
70
|
+
const result: ParsedAvailability = {
|
|
71
|
+
available: false,
|
|
72
|
+
premium: false,
|
|
73
|
+
auction: false,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// ==== SINGLE DOMAIN FORMAT ====
|
|
77
|
+
// Format: "STATUS: ✅ AVAILABLE" or "AVAILABILITY: Standard registration available"
|
|
78
|
+
if (normalizedText.includes('status:') || normalizedText.includes('availability:')) {
|
|
79
|
+
// Check for explicit availability indicators
|
|
80
|
+
if (
|
|
81
|
+
normalizedText.includes('status: ✅ available') ||
|
|
82
|
+
normalizedText.includes('✅ available') ||
|
|
83
|
+
normalizedText.includes('standard registration available') ||
|
|
84
|
+
normalizedText.includes('purchasable: yes')
|
|
85
|
+
) {
|
|
86
|
+
result.available = true;
|
|
87
|
+
|
|
88
|
+
// Check if premium
|
|
89
|
+
if (normalizedText.includes('type: premium') || normalizedText.includes('premium domain')) {
|
|
90
|
+
result.premium = true;
|
|
91
|
+
}
|
|
92
|
+
// Check if auction
|
|
93
|
+
if (normalizedText.includes('type: auction') || normalizedText.includes('auction domain')) {
|
|
94
|
+
result.auction = true;
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Explicit unavailable
|
|
100
|
+
if (
|
|
101
|
+
normalizedText.includes('status: ❌') ||
|
|
102
|
+
normalizedText.includes('not available') ||
|
|
103
|
+
normalizedText.includes('already registered') ||
|
|
104
|
+
normalizedText.includes('purchasable: no')
|
|
105
|
+
) {
|
|
106
|
+
result.available = false;
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ==== BULK DOMAIN FORMAT ====
|
|
112
|
+
// Check if domain appears in available section
|
|
113
|
+
// GoDaddy formats: "✅ **AVAILABLE DOMAINS" or "✅ **STANDARD SUGGESTIONS"
|
|
114
|
+
const availableMatch = text.match(/✅\s*\*\*(?:AVAILABLE|STANDARD)[^]*?(?=(?:💎|⚠️|❌|\*\*[A-Z])|$)/i);
|
|
115
|
+
if (availableMatch && availableMatch[0].toLowerCase().includes(normalizedDomain)) {
|
|
116
|
+
result.available = true;
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Check premium section
|
|
121
|
+
// GoDaddy format: "💎 **PREMIUM DOMAINS"
|
|
122
|
+
const premiumMatch = text.match(/💎\s*\*\*PREMIUM[^]*?(?=(?:⚠️|❌|\*\*[A-Z])|$)/i);
|
|
123
|
+
if (premiumMatch && premiumMatch[0].toLowerCase().includes(normalizedDomain)) {
|
|
124
|
+
result.available = true;
|
|
125
|
+
result.premium = true;
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Check auction section
|
|
130
|
+
// GoDaddy format: "🔨 **AUCTION DOMAINS" or similar
|
|
131
|
+
const auctionMatch = text.match(/🔨\s*\*\*AUCTION[^]*?(?=(?:💎|⚠️|❌|\*\*[A-Z])|$)/i);
|
|
132
|
+
if (auctionMatch && auctionMatch[0].toLowerCase().includes(normalizedDomain)) {
|
|
133
|
+
result.available = true;
|
|
134
|
+
result.auction = true;
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Check unavailable section
|
|
139
|
+
// GoDaddy format: "❌ **UNAVAILABLE DOMAINS"
|
|
140
|
+
const unavailableMatch = text.match(/❌\s*\*\*UNAVAILABLE[^]*?(?=(?:💎|⚠️|\*\*[A-Z])|$)/i);
|
|
141
|
+
if (unavailableMatch && unavailableMatch[0].toLowerCase().includes(normalizedDomain)) {
|
|
142
|
+
result.available = false;
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ==== FALLBACK: LINE-BY-LINE ANALYSIS ====
|
|
147
|
+
const lines = text.split('\n');
|
|
148
|
+
for (const line of lines) {
|
|
149
|
+
const lowerLine = line.toLowerCase();
|
|
150
|
+
|
|
151
|
+
// Check for domain-specific lines or general status
|
|
152
|
+
if (lowerLine.includes(normalizedDomain) || lowerLine.includes('status') || lowerLine.includes('available')) {
|
|
153
|
+
// Premium indicators
|
|
154
|
+
if (lowerLine.includes('premium')) {
|
|
155
|
+
result.available = true;
|
|
156
|
+
result.premium = true;
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
// Auction indicators
|
|
160
|
+
if (lowerLine.includes('auction')) {
|
|
161
|
+
result.available = true;
|
|
162
|
+
result.auction = true;
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
// Available indicators (must check before unavailable since "unavailable" contains "available")
|
|
166
|
+
if (
|
|
167
|
+
(lowerLine.includes('✅') && lowerLine.includes('available')) ||
|
|
168
|
+
lowerLine.includes('register at') ||
|
|
169
|
+
lowerLine.includes('can be registered')
|
|
170
|
+
) {
|
|
171
|
+
result.available = true;
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
// Unavailable indicators
|
|
175
|
+
if (lowerLine.includes('❌') || lowerLine.includes('unavailable') || lowerLine.includes('not available')) {
|
|
176
|
+
result.available = false;
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* GoDaddy MCP Adapter.
|
|
187
|
+
*
|
|
188
|
+
* Uses GoDaddy's public MCP endpoint - no authentication required!
|
|
189
|
+
*/
|
|
190
|
+
export class GodaddyMcpAdapter extends RegistrarAdapter {
|
|
191
|
+
readonly name = 'GoDaddy';
|
|
192
|
+
readonly id = 'godaddy';
|
|
193
|
+
|
|
194
|
+
constructor() {
|
|
195
|
+
// Conservative rate limit - GoDaddy doesn't document their limits
|
|
196
|
+
// Using 30/min to be safe (they say "excessive requests may be throttled")
|
|
197
|
+
super(30);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Check if GoDaddy MCP is enabled.
|
|
202
|
+
* Always enabled since no API key needed!
|
|
203
|
+
*/
|
|
204
|
+
isEnabled(): boolean {
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Search for domain availability using GoDaddy MCP.
|
|
210
|
+
*/
|
|
211
|
+
async search(domain: string, tld: string): Promise<DomainResult> {
|
|
212
|
+
const fullDomain = `${domain}.${tld}`;
|
|
213
|
+
|
|
214
|
+
return this.retryWithBackoff(async () => {
|
|
215
|
+
const text = await this.callMcpTool('domains_check_availability', {
|
|
216
|
+
domains: fullDomain,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const parsed = parseAvailabilityResponse(text, fullDomain);
|
|
220
|
+
|
|
221
|
+
return this.createResult(domain, tld, {
|
|
222
|
+
available: parsed.available,
|
|
223
|
+
premium: parsed.premium,
|
|
224
|
+
price_first_year: null, // GoDaddy MCP doesn't provide pricing
|
|
225
|
+
price_renewal: null,
|
|
226
|
+
privacy_included: false, // Unknown
|
|
227
|
+
source: 'godaddy_api',
|
|
228
|
+
premium_reason: parsed.premium
|
|
229
|
+
? 'Premium domain (GoDaddy)'
|
|
230
|
+
: parsed.auction
|
|
231
|
+
? 'Auction domain (GoDaddy)'
|
|
232
|
+
: undefined,
|
|
233
|
+
});
|
|
234
|
+
}, `check ${fullDomain}`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Bulk check multiple domains at once.
|
|
239
|
+
* GoDaddy MCP supports up to 1000 domains per request!
|
|
240
|
+
*/
|
|
241
|
+
async bulkSearch(domains: string[]): Promise<Map<string, ParsedAvailability>> {
|
|
242
|
+
const results = new Map<string, ParsedAvailability>();
|
|
243
|
+
|
|
244
|
+
// GoDaddy accepts comma-separated domains
|
|
245
|
+
const domainList = domains.join(', ');
|
|
246
|
+
|
|
247
|
+
const text = await this.callMcpTool('domains_check_availability', {
|
|
248
|
+
domains: domainList,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Parse results for each domain
|
|
252
|
+
for (const domain of domains) {
|
|
253
|
+
const parsed = parseAvailabilityResponse(text, domain);
|
|
254
|
+
results.set(domain.toLowerCase(), parsed);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return results;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Get TLD info - not supported by GoDaddy MCP.
|
|
262
|
+
*/
|
|
263
|
+
async getTldInfo(_tld: string): Promise<TLDInfo | null> {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Call a GoDaddy MCP tool via JSON-RPC.
|
|
269
|
+
*/
|
|
270
|
+
private async callMcpTool(
|
|
271
|
+
toolName: string,
|
|
272
|
+
args: Record<string, unknown>,
|
|
273
|
+
): Promise<string> {
|
|
274
|
+
const requestId = jsonRpcId++;
|
|
275
|
+
|
|
276
|
+
const payload = {
|
|
277
|
+
jsonrpc: '2.0',
|
|
278
|
+
method: 'tools/call',
|
|
279
|
+
params: {
|
|
280
|
+
name: toolName,
|
|
281
|
+
arguments: args,
|
|
282
|
+
},
|
|
283
|
+
id: requestId,
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
logger.debug('GoDaddy MCP request', {
|
|
287
|
+
tool: toolName,
|
|
288
|
+
args,
|
|
289
|
+
request_id: requestId,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
const response = await this.withTimeout(
|
|
294
|
+
fetch(GODADDY_MCP_ENDPOINT, {
|
|
295
|
+
method: 'POST',
|
|
296
|
+
headers: {
|
|
297
|
+
'Content-Type': 'application/json',
|
|
298
|
+
'Accept': 'application/json, text/event-stream',
|
|
299
|
+
},
|
|
300
|
+
body: JSON.stringify(payload),
|
|
301
|
+
}),
|
|
302
|
+
`GoDaddy MCP ${toolName}`,
|
|
303
|
+
15000, // 15 second timeout
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
if (!response.ok) {
|
|
307
|
+
throw new RegistrarApiError(
|
|
308
|
+
'GoDaddy MCP',
|
|
309
|
+
`HTTP ${response.status}: ${response.statusText}`,
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Response is SSE format: "event: message\ndata: {...}"
|
|
314
|
+
const rawText = await response.text();
|
|
315
|
+
|
|
316
|
+
// Extract JSON from SSE format
|
|
317
|
+
const dataMatch = rawText.match(/data:\s*(\{.*\})/s);
|
|
318
|
+
if (!dataMatch) {
|
|
319
|
+
throw new RegistrarApiError(
|
|
320
|
+
'GoDaddy MCP',
|
|
321
|
+
'Invalid response format - expected SSE',
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const jsonStr = dataMatch[1];
|
|
326
|
+
const parsed = JSON.parse(jsonStr!);
|
|
327
|
+
|
|
328
|
+
// Validate response
|
|
329
|
+
const validated = McpResponseSchema.parse(parsed);
|
|
330
|
+
|
|
331
|
+
if (validated.error) {
|
|
332
|
+
throw new RegistrarApiError(
|
|
333
|
+
'GoDaddy MCP',
|
|
334
|
+
`RPC Error ${validated.error.code}: ${validated.error.message}`,
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (!validated.result || validated.result.isError) {
|
|
339
|
+
throw new RegistrarApiError(
|
|
340
|
+
'GoDaddy MCP',
|
|
341
|
+
'Tool call returned error',
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Extract text content
|
|
346
|
+
const textContent = validated.result.content.find(c => c.type === 'text');
|
|
347
|
+
if (!textContent) {
|
|
348
|
+
throw new RegistrarApiError(
|
|
349
|
+
'GoDaddy MCP',
|
|
350
|
+
'No text content in response',
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
logger.debug('GoDaddy MCP response', {
|
|
355
|
+
request_id: requestId,
|
|
356
|
+
text_length: textContent.text.length,
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
return textContent.text;
|
|
360
|
+
} catch (error) {
|
|
361
|
+
if (error instanceof RegistrarApiError) {
|
|
362
|
+
throw error;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (error instanceof Error) {
|
|
366
|
+
if (error.name === 'AbortError' || error.message.includes('timeout')) {
|
|
367
|
+
throw error;
|
|
368
|
+
}
|
|
369
|
+
throw new RegistrarApiError('GoDaddy MCP', error.message);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
throw new RegistrarApiError('GoDaddy MCP', 'Unknown network error');
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Singleton instance.
|
|
379
|
+
*/
|
|
380
|
+
export const godaddyMcpAdapter = new GodaddyMcpAdapter();
|
package/src/registrars/index.ts
CHANGED
package/src/server.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* - bulk_search: Check many domains at once
|
|
11
11
|
* - compare_registrars: Compare pricing across registrars
|
|
12
12
|
* - suggest_domains: Generate available name variations
|
|
13
|
+
* - suggest_domains_smart: AI-powered domain suggestions with semantic analysis
|
|
13
14
|
* - tld_info: Get TLD information and recommendations
|
|
14
15
|
* - check_socials: Check social handle availability
|
|
15
16
|
*
|
|
@@ -36,6 +37,8 @@ import {
|
|
|
36
37
|
executeCompareRegistrars,
|
|
37
38
|
suggestDomainsTool,
|
|
38
39
|
executeSuggestDomains,
|
|
40
|
+
suggestDomainsSmartTool,
|
|
41
|
+
executeSuggestDomainsSmart,
|
|
39
42
|
tldInfoTool,
|
|
40
43
|
executeTldInfo,
|
|
41
44
|
checkSocialsTool,
|
|
@@ -57,6 +60,7 @@ const TOOLS: Tool[] = [
|
|
|
57
60
|
bulkSearchTool as Tool,
|
|
58
61
|
compareRegistrarsTool as Tool,
|
|
59
62
|
suggestDomainsTool as Tool,
|
|
63
|
+
suggestDomainsSmartTool as Tool,
|
|
60
64
|
tldInfoTool as Tool,
|
|
61
65
|
checkSocialsTool as Tool,
|
|
62
66
|
];
|
|
@@ -187,6 +191,18 @@ async function executeToolCall(
|
|
|
187
191
|
max_suggestions: (args.max_suggestions as number) || 10,
|
|
188
192
|
});
|
|
189
193
|
|
|
194
|
+
case 'suggest_domains_smart':
|
|
195
|
+
return executeSuggestDomainsSmart({
|
|
196
|
+
query: args.query as string,
|
|
197
|
+
tld: (args.tld as string) || 'com',
|
|
198
|
+
industry: args.industry as
|
|
199
|
+
| 'tech' | 'startup' | 'finance' | 'health' | 'food' | 'creative' | 'ecommerce' | 'education' | 'gaming' | 'social'
|
|
200
|
+
| undefined,
|
|
201
|
+
style: (args.style as 'brandable' | 'descriptive' | 'short' | 'creative') || 'brandable',
|
|
202
|
+
max_suggestions: (args.max_suggestions as number) || 15,
|
|
203
|
+
include_premium: (args.include_premium as boolean) || false,
|
|
204
|
+
});
|
|
205
|
+
|
|
190
206
|
case 'tld_info':
|
|
191
207
|
return executeTldInfo({
|
|
192
208
|
tld: args.tld as string,
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* Domain Search Service.
|
|
3
3
|
*
|
|
4
4
|
* Orchestrates domain availability checks across multiple sources:
|
|
5
|
-
* 1. Porkbun (primary, if configured)
|
|
6
|
-
* 2. Namecheap (secondary, if configured)
|
|
7
|
-
* 3.
|
|
8
|
-
* 4.
|
|
5
|
+
* 1. Porkbun (primary, if configured - has pricing)
|
|
6
|
+
* 2. Namecheap (secondary, if configured - has pricing)
|
|
7
|
+
* 3. GoDaddy MCP (always available - no auth, no pricing, great availability data)
|
|
8
|
+
* 4. RDAP (fallback, always available)
|
|
9
|
+
* 5. WHOIS (last resort, always available)
|
|
9
10
|
*
|
|
10
11
|
* Handles:
|
|
11
12
|
* - Smart source selection based on availability and configuration
|
|
@@ -28,7 +29,7 @@ import {
|
|
|
28
29
|
buildDomain,
|
|
29
30
|
} from '../utils/validators.js';
|
|
30
31
|
import { domainCache, domainCacheKey, getOrCompute } from '../utils/cache.js';
|
|
31
|
-
import { porkbunAdapter, namecheapAdapter } from '../registrars/index.js';
|
|
32
|
+
import { porkbunAdapter, namecheapAdapter, godaddyMcpAdapter } from '../registrars/index.js';
|
|
32
33
|
import { checkRdap, isRdapAvailable } from '../fallbacks/rdap.js';
|
|
33
34
|
import { checkWhois, isWhoisAvailable } from '../fallbacks/whois.js';
|
|
34
35
|
import {
|
|
@@ -125,7 +126,7 @@ async function searchSingleDomain(
|
|
|
125
126
|
const triedSources: string[] = [];
|
|
126
127
|
|
|
127
128
|
// Check cache first
|
|
128
|
-
for (const source of ['porkbun', 'namecheap', 'rdap', 'whois'] as const) {
|
|
129
|
+
for (const source of ['porkbun', 'namecheap', 'godaddy', 'rdap', 'whois'] as const) {
|
|
129
130
|
const cacheKey = domainCacheKey(fullDomain, source);
|
|
130
131
|
const cached = domainCache.get(cacheKey);
|
|
131
132
|
if (cached) {
|
|
@@ -182,6 +183,14 @@ async function searchSingleDomain(
|
|
|
182
183
|
|
|
183
184
|
/**
|
|
184
185
|
* Build the priority list of sources to try.
|
|
186
|
+
*
|
|
187
|
+
* Priority order:
|
|
188
|
+
* 1. Preferred registrars (if specified)
|
|
189
|
+
* 2. Porkbun (has pricing, best API)
|
|
190
|
+
* 3. Namecheap (has pricing)
|
|
191
|
+
* 4. GoDaddy MCP (free, no pricing but good availability data)
|
|
192
|
+
* 5. RDAP (free, no pricing)
|
|
193
|
+
* 6. WHOIS (slowest fallback)
|
|
185
194
|
*/
|
|
186
195
|
function buildSourcePriority(
|
|
187
196
|
tld: string,
|
|
@@ -196,21 +205,25 @@ function buildSourcePriority(
|
|
|
196
205
|
sources.push('porkbun');
|
|
197
206
|
} else if (registrar === 'namecheap' && config.namecheap.enabled) {
|
|
198
207
|
sources.push('namecheap');
|
|
208
|
+
} else if (registrar === 'godaddy') {
|
|
209
|
+
sources.push('godaddy');
|
|
199
210
|
}
|
|
200
211
|
}
|
|
201
212
|
} else {
|
|
202
|
-
// Default priority: Porkbun first (
|
|
213
|
+
// Default priority: Porkbun first (best API with pricing), then Namecheap, then GoDaddy
|
|
203
214
|
if (config.porkbun.enabled) sources.push('porkbun');
|
|
204
215
|
if (config.namecheap.enabled) sources.push('namecheap');
|
|
216
|
+
// GoDaddy MCP is always available (no auth needed)
|
|
217
|
+
sources.push('godaddy');
|
|
205
218
|
}
|
|
206
219
|
|
|
207
220
|
// Always add fallbacks
|
|
208
221
|
if (isRdapAvailable(tld)) sources.push('rdap');
|
|
209
222
|
if (isWhoisAvailable(tld)) sources.push('whois');
|
|
210
223
|
|
|
211
|
-
// If no registrar APIs, RDAP should be first
|
|
224
|
+
// If no registrar APIs, GoDaddy MCP and RDAP should be first
|
|
212
225
|
if (sources.length === 0) {
|
|
213
|
-
sources.push('rdap', 'whois');
|
|
226
|
+
sources.push('godaddy', 'rdap', 'whois');
|
|
214
227
|
}
|
|
215
228
|
|
|
216
229
|
return sources;
|
|
@@ -231,6 +244,9 @@ async function trySource(
|
|
|
231
244
|
case 'namecheap':
|
|
232
245
|
return namecheapAdapter.search(domain, tld);
|
|
233
246
|
|
|
247
|
+
case 'godaddy':
|
|
248
|
+
return godaddyMcpAdapter.search(domain, tld);
|
|
249
|
+
|
|
234
250
|
case 'rdap':
|
|
235
251
|
return checkRdap(domain, tld);
|
|
236
252
|
|
|
@@ -112,14 +112,15 @@ const PLATFORM_CONFIGS: Record<SocialPlatform, PlatformConfig> = {
|
|
|
112
112
|
// MEDIUM CONFIDENCE (Status codes, some edge cases)
|
|
113
113
|
// ─────────────────────────────────────────────────────────────────────────
|
|
114
114
|
twitter: {
|
|
115
|
-
|
|
115
|
+
// Use oembed API - Twitter SPA returns 200 for all direct requests
|
|
116
|
+
url: 'https://publish.twitter.com/oembed?url=https://twitter.com/{}',
|
|
116
117
|
profileUrl: 'https://twitter.com/{}',
|
|
117
118
|
errorType: 'status_code',
|
|
118
119
|
errorCode: 404,
|
|
119
|
-
confidence: '
|
|
120
|
-
method: '
|
|
120
|
+
confidence: 'high', // oembed API is reliable
|
|
121
|
+
method: 'GET',
|
|
121
122
|
headers: {
|
|
122
|
-
'User-Agent': '
|
|
123
|
+
'User-Agent': 'Domain-Search-MCP/1.0',
|
|
123
124
|
},
|
|
124
125
|
regexCheck: /^[A-Za-z0-9_]{1,15}$/,
|
|
125
126
|
},
|
|
@@ -226,8 +227,8 @@ export const checkSocialsTool = {
|
|
|
226
227
|
description: `Check if a username is available on social media and developer platforms.
|
|
227
228
|
|
|
228
229
|
Supports 10 platforms with varying confidence levels:
|
|
229
|
-
- HIGH: GitHub, npm, PyPI, Reddit (reliable public APIs)
|
|
230
|
-
- MEDIUM:
|
|
230
|
+
- HIGH: GitHub, npm, PyPI, Reddit, Twitter/X (reliable public APIs)
|
|
231
|
+
- MEDIUM: YouTube, ProductHunt (status code based)
|
|
231
232
|
- LOW: Instagram, LinkedIn, TikTok (block automated checks - verify manually)
|
|
232
233
|
|
|
233
234
|
Returns availability status with confidence indicator.
|
|
@@ -295,6 +296,20 @@ async function checkPlatform(
|
|
|
295
296
|
maxRedirects: 0,
|
|
296
297
|
});
|
|
297
298
|
|
|
299
|
+
// Handle rate limiting (429) - return uncertain instead of false negative
|
|
300
|
+
if (response.status === 429) {
|
|
301
|
+
logger.debug(`Rate limited on ${platform}`, { username });
|
|
302
|
+
return {
|
|
303
|
+
platform,
|
|
304
|
+
handle: username,
|
|
305
|
+
available: false,
|
|
306
|
+
url: profileUrl,
|
|
307
|
+
checked_at: new Date().toISOString(),
|
|
308
|
+
confidence: 'low', // Can't be sure due to rate limit
|
|
309
|
+
error: 'Rate limited - please try again later',
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
298
313
|
let available = false;
|
|
299
314
|
|
|
300
315
|
// Determine availability based on errorType
|
|
@@ -342,6 +357,7 @@ async function checkPlatform(
|
|
|
342
357
|
url: profileUrl,
|
|
343
358
|
checked_at: new Date().toISOString(),
|
|
344
359
|
confidence: 'low',
|
|
360
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
345
361
|
};
|
|
346
362
|
}
|
|
347
363
|
}
|
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,
|