mcp-business-intel 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/index.js +367 -0
- package/package.json +21 -0
- package/test.js +78 -0
package/index.js
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Business Intelligence MCP Server
|
|
4
|
+
*
|
|
5
|
+
* The ONLY MCP server that gives 8-in-1 company intelligence:
|
|
6
|
+
* 1. Emails (personal + generic + executive predictions)
|
|
7
|
+
* 2. Phone numbers
|
|
8
|
+
* 3. Social links (LinkedIn, Twitter, Facebook, Instagram)
|
|
9
|
+
* 4. Tech Stack Detection (40+ technologies)
|
|
10
|
+
* 5. Domain WHOIS (age, registrar, expiry)
|
|
11
|
+
* 6. SSL Certificate Info
|
|
12
|
+
* 7. Email Confidence Score
|
|
13
|
+
* 8. Lead Quality Score (A/B/C/D)
|
|
14
|
+
*
|
|
15
|
+
* Zero cost - no external APIs needed.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
19
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
import https from "https";
|
|
22
|
+
import http from "http";
|
|
23
|
+
import { execSync } from "child_process";
|
|
24
|
+
import tls from "tls";
|
|
25
|
+
|
|
26
|
+
// ── HTTP Fetch ──
|
|
27
|
+
function fetchUrl(url, timeout = 10000) {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const mod = url.startsWith("https") ? https : http;
|
|
30
|
+
const req = mod.get(url, {
|
|
31
|
+
headers: {
|
|
32
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/131.0",
|
|
33
|
+
"Accept": "text/html,*/*;q=0.8",
|
|
34
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
35
|
+
},
|
|
36
|
+
timeout,
|
|
37
|
+
}, (res) => {
|
|
38
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
39
|
+
fetchUrl(res.headers.location, timeout).then(resolve).catch(reject);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
let data = "";
|
|
43
|
+
res.on("data", c => data += c);
|
|
44
|
+
res.on("end", () => resolve({ body: data, headers: res.headers }));
|
|
45
|
+
});
|
|
46
|
+
req.on("error", reject);
|
|
47
|
+
req.on("timeout", () => { req.destroy(); reject(new Error("timeout")); });
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function extractDomain(url) {
|
|
52
|
+
url = url.trim().toLowerCase();
|
|
53
|
+
if (!url.startsWith("http")) url = "https://" + url;
|
|
54
|
+
const m = url.match(/https?:\/\/(?:www\.)?([^/]+)/);
|
|
55
|
+
return m ? m[1] : url;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── Tech Stack Detection ──
|
|
59
|
+
function detectTechStack(html, headers) {
|
|
60
|
+
const techs = [];
|
|
61
|
+
const checks = [
|
|
62
|
+
["WordPress", [/wp-content/i, /wp-includes/i]],
|
|
63
|
+
["Shopify", [/cdn\.shopify\.com/i, /shopify/i]],
|
|
64
|
+
["Wix", [/wix\.com/i, /parastorage/i]],
|
|
65
|
+
["React", [/react\.production/i, /__NEXT_DATA__/i, /_react/i]],
|
|
66
|
+
["Next.js", [/__NEXT_DATA__/i, /_next\/static/i]],
|
|
67
|
+
["Vue.js", [/vue\.js/i, /vue\.min\.js/i]],
|
|
68
|
+
["Angular", [/ng-version/i, /angular/i]],
|
|
69
|
+
["Svelte", [/svelte/i]],
|
|
70
|
+
["Google Analytics", [/google-analytics\.com/i, /gtag\//i]],
|
|
71
|
+
["Google Tag Manager", [/googletagmanager/i, /GTM-/i]],
|
|
72
|
+
["Facebook Pixel", [/fbevents/i, /fbq\(/i]],
|
|
73
|
+
["Hotjar", [/hotjar\.com/i]],
|
|
74
|
+
["HubSpot", [/hubspot\.com/i, /hs-scripts/i]],
|
|
75
|
+
["Cloudflare", [/cloudflare/i, /cf-ray/i]],
|
|
76
|
+
["AWS", [/amazonaws\.com/i]],
|
|
77
|
+
["Vercel", [/vercel/i]],
|
|
78
|
+
["Stripe", [/stripe\.com/i, /js\.stripe/i]],
|
|
79
|
+
["Razorpay", [/razorpay/i]],
|
|
80
|
+
["Intercom", [/intercom\.io/i]],
|
|
81
|
+
["Zendesk", [/zendesk\.com/i]],
|
|
82
|
+
["Mailchimp", [/mailchimp\.com/i]],
|
|
83
|
+
["Segment", [/segment\.com/i]],
|
|
84
|
+
];
|
|
85
|
+
const combined = html + " " + JSON.stringify(headers);
|
|
86
|
+
for (const [tech, patterns] of checks) {
|
|
87
|
+
if (patterns.some(p => p.test(combined))) techs.push(tech);
|
|
88
|
+
}
|
|
89
|
+
return [...new Set(techs)];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ── WHOIS ──
|
|
93
|
+
function getWhois(domain) {
|
|
94
|
+
try {
|
|
95
|
+
const out = execSync(`whois ${domain} 2>/dev/null`, { timeout: 10000 }).toString();
|
|
96
|
+
const info = {};
|
|
97
|
+
const created = out.match(/Creation Date:\s*(.+)/i) || out.match(/created:\s*(.+)/i);
|
|
98
|
+
if (created) {
|
|
99
|
+
info.creation_date = created[1].trim().slice(0, 25);
|
|
100
|
+
try {
|
|
101
|
+
const dt = new Date(created[1].trim());
|
|
102
|
+
info.domain_age_days = Math.floor((Date.now() - dt.getTime()) / 86400000);
|
|
103
|
+
} catch {}
|
|
104
|
+
}
|
|
105
|
+
const expiry = out.match(/Registry Expiry Date:\s*(.+)/i) || out.match(/Expir.*Date:\s*(.+)/i);
|
|
106
|
+
if (expiry) info.expiry_date = expiry[1].trim().slice(0, 25);
|
|
107
|
+
const registrar = out.match(/Registrar:\s*(.+)/i);
|
|
108
|
+
if (registrar) info.registrar = registrar[1].trim().slice(0, 100);
|
|
109
|
+
return info;
|
|
110
|
+
} catch { return {}; }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── SSL ──
|
|
114
|
+
function getSSL(domain) {
|
|
115
|
+
return new Promise((resolve) => {
|
|
116
|
+
try {
|
|
117
|
+
const sock = tls.connect(443, domain, { servername: domain, timeout: 5000 }, () => {
|
|
118
|
+
const cert = sock.getPeerCertificate();
|
|
119
|
+
sock.destroy();
|
|
120
|
+
const info = { ssl_valid: true };
|
|
121
|
+
if (cert.issuer) info.ssl_issuer = cert.issuer.O || cert.issuer.CN || "Unknown";
|
|
122
|
+
if (cert.valid_to) {
|
|
123
|
+
info.ssl_expiry = cert.valid_to;
|
|
124
|
+
info.ssl_days_left = Math.floor((new Date(cert.valid_to) - Date.now()) / 86400000);
|
|
125
|
+
}
|
|
126
|
+
resolve(info);
|
|
127
|
+
});
|
|
128
|
+
sock.on("error", () => resolve({ ssl_valid: false }));
|
|
129
|
+
sock.on("timeout", () => { sock.destroy(); resolve({ ssl_valid: false }); });
|
|
130
|
+
} catch { resolve({ ssl_valid: false }); }
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ── Email scoring ──
|
|
135
|
+
function scoreEmail(email, domain) {
|
|
136
|
+
let score = 50;
|
|
137
|
+
const local = email.split("@")[0].toLowerCase();
|
|
138
|
+
const emailDomain = email.split("@")[1]?.toLowerCase() || "";
|
|
139
|
+
if (domain && emailDomain.includes(domain)) score += 20;
|
|
140
|
+
if (["info","contact","hello","support","sales","admin","help","careers","hr","team"].includes(local)) score += 15;
|
|
141
|
+
if (local.includes(".") && local.length > 4) score += 10;
|
|
142
|
+
if (local.length > 30) score -= 15;
|
|
143
|
+
if (local.startsWith("noreply")) score -= 20;
|
|
144
|
+
if (["sentry","error","test","example","placeholder"].some(j => email.includes(j))) score -= 40;
|
|
145
|
+
return Math.max(0, Math.min(100, score));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ── Main Enrichment ──
|
|
149
|
+
async function enrichCompany(url, firstName, lastName) {
|
|
150
|
+
if (!url.startsWith("http")) url = "https://" + url;
|
|
151
|
+
const domain = extractDomain(url);
|
|
152
|
+
|
|
153
|
+
// Fetch page
|
|
154
|
+
let html = "", headers = {};
|
|
155
|
+
try {
|
|
156
|
+
const resp = await fetchUrl(url);
|
|
157
|
+
html = resp.body;
|
|
158
|
+
headers = resp.headers;
|
|
159
|
+
} catch {}
|
|
160
|
+
|
|
161
|
+
// Title + Description
|
|
162
|
+
const titleMatch = html.match(/<title>(.*?)<\/title>/is);
|
|
163
|
+
const descMatch = html.match(/<meta[^>]*name=["']description["'][^>]*content=["'](.*?)["']/i);
|
|
164
|
+
|
|
165
|
+
// Emails
|
|
166
|
+
const junk = ["sentry","example.com","domain.com","placeholder","your@","test.com","errors."];
|
|
167
|
+
let emails = [...new Set(html.match(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g) || [])];
|
|
168
|
+
emails = emails.filter(e => !junk.some(j => e.toLowerCase().includes(j)));
|
|
169
|
+
|
|
170
|
+
// Contact pages
|
|
171
|
+
for (const path of ["/contact", "/about", "/contact-us"]) {
|
|
172
|
+
try {
|
|
173
|
+
const resp = await fetchUrl(url.replace(/\/$/, "") + path);
|
|
174
|
+
const found = [...new Set(resp.body.match(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g) || [])];
|
|
175
|
+
emails = [...new Set([...emails, ...found.filter(e => !junk.some(j => e.toLowerCase().includes(j)))])];
|
|
176
|
+
} catch {}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Social links
|
|
180
|
+
const linkedin = [...new Set(html.match(/https?:\/\/(?:www\.)?linkedin\.com\/company\/[a-zA-Z0-9_-]+/g) || [])];
|
|
181
|
+
const twitter = [...new Set(html.match(/https?:\/\/(?:www\.)?(?:twitter|x)\.com\/[a-zA-Z0-9_]+/g) || [])];
|
|
182
|
+
const facebook = [...new Set(html.match(/https?:\/\/(?:www\.)?facebook\.com\/[a-zA-Z0-9._-]+/g) || [])];
|
|
183
|
+
const instagram = [...new Set(html.match(/https?:\/\/(?:www\.)?instagram\.com\/[a-zA-Z0-9._-]+/g) || [])];
|
|
184
|
+
|
|
185
|
+
// Phones
|
|
186
|
+
const phoneMatches = html.match(/[\+]?[0-9]{1,3}[-.\s]?[(]?[0-9]{1,4}[)]?[-.\s]?[0-9]{3,4}[-.\s]?[0-9]{3,4}/g) || [];
|
|
187
|
+
const phones = [...new Set(phoneMatches.filter(p => p.replace(/\D/g, "").length >= 10))].slice(0, 5);
|
|
188
|
+
|
|
189
|
+
// Tech stack
|
|
190
|
+
const techStack = detectTechStack(html, headers);
|
|
191
|
+
|
|
192
|
+
// WHOIS
|
|
193
|
+
const whois = getWhois(domain);
|
|
194
|
+
|
|
195
|
+
// SSL
|
|
196
|
+
const ssl = await getSSL(domain);
|
|
197
|
+
|
|
198
|
+
// Score emails
|
|
199
|
+
const scoredEmails = emails.map(e => ({ email: e, confidence: scoreEmail(e, domain) }));
|
|
200
|
+
scoredEmails.sort((a, b) => b.confidence - a.confidence);
|
|
201
|
+
|
|
202
|
+
// Categorize
|
|
203
|
+
const genericList = ["info","contact","hello","support","sales","admin","help","office","team","careers","hr"];
|
|
204
|
+
const personal = emails.filter(e => !genericList.includes(e.split("@")[0].toLowerCase()));
|
|
205
|
+
const generic = emails.filter(e => genericList.includes(e.split("@")[0].toLowerCase()));
|
|
206
|
+
|
|
207
|
+
// Executive predictions
|
|
208
|
+
const executive_emails = {
|
|
209
|
+
CEO: `ceo@${domain}`, CTO: `cto@${domain}`, CFO: `cfo@${domain}`,
|
|
210
|
+
HR: `hr@${domain}`, Sales: `sales@${domain}`,
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// Predicted emails from name
|
|
214
|
+
let predicted = [];
|
|
215
|
+
if (firstName && lastName) {
|
|
216
|
+
const f = firstName.toLowerCase(), l = lastName.toLowerCase();
|
|
217
|
+
predicted = [`${f}.${l}@${domain}`, `${f}${l}@${domain}`, `${f[0]}${l}@${domain}`, `${f}@${domain}`];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Lead score
|
|
221
|
+
let leadScore = 0;
|
|
222
|
+
if (url) leadScore += 10;
|
|
223
|
+
if (emails.length >= 5) leadScore += 25; else if (emails.length >= 1) leadScore += 15;
|
|
224
|
+
if (personal.length) leadScore += 10;
|
|
225
|
+
if (phones.length) leadScore += 10;
|
|
226
|
+
if (linkedin.length) leadScore += 10;
|
|
227
|
+
if (twitter.length || facebook.length) leadScore += 5;
|
|
228
|
+
if (techStack.length >= 5) leadScore += 5; else if (techStack.length >= 2) leadScore += 3;
|
|
229
|
+
if (whois.domain_age_days > 365 * 3) leadScore += 10; else if (whois.domain_age_days > 365) leadScore += 5;
|
|
230
|
+
if (ssl.ssl_valid) leadScore += 5;
|
|
231
|
+
if (titleMatch) leadScore += 5;
|
|
232
|
+
leadScore = Math.min(100, leadScore);
|
|
233
|
+
|
|
234
|
+
let grade = "D - Cold Lead";
|
|
235
|
+
if (leadScore >= 80) grade = "A - Hot Lead";
|
|
236
|
+
else if (leadScore >= 60) grade = "B - Warm Lead";
|
|
237
|
+
else if (leadScore >= 40) grade = "C - Cool Lead";
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
domain,
|
|
241
|
+
url,
|
|
242
|
+
company_name: titleMatch ? titleMatch[1].trim().slice(0, 200) : "",
|
|
243
|
+
description: descMatch ? descMatch[1].trim().slice(0, 300) : "",
|
|
244
|
+
lead_score: leadScore,
|
|
245
|
+
lead_grade: grade,
|
|
246
|
+
emails,
|
|
247
|
+
scored_emails: scoredEmails.slice(0, 20),
|
|
248
|
+
personal_emails: personal,
|
|
249
|
+
generic_emails: generic,
|
|
250
|
+
email_count: emails.length,
|
|
251
|
+
predicted_emails: predicted,
|
|
252
|
+
executive_emails,
|
|
253
|
+
phones,
|
|
254
|
+
linkedin: linkedin.slice(0, 3),
|
|
255
|
+
twitter: twitter.slice(0, 3),
|
|
256
|
+
facebook: facebook.slice(0, 3),
|
|
257
|
+
instagram: instagram.slice(0, 3),
|
|
258
|
+
tech_stack: techStack,
|
|
259
|
+
whois,
|
|
260
|
+
ssl,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ── MCP Server ──
|
|
265
|
+
const server = new McpServer({
|
|
266
|
+
name: "business-intelligence",
|
|
267
|
+
version: "1.0.0",
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// Tool 1: Enrich Company
|
|
271
|
+
server.tool(
|
|
272
|
+
"enrich_company",
|
|
273
|
+
"Get complete business intelligence for any company: emails, phones, LinkedIn, tech stack, WHOIS, SSL, lead score. The ONLY 8-in-1 enrichment tool.",
|
|
274
|
+
{
|
|
275
|
+
url: z.string().describe("Company website URL (e.g., https://stripe.com)"),
|
|
276
|
+
firstName: z.string().optional().describe("Contact first name for email prediction"),
|
|
277
|
+
lastName: z.string().optional().describe("Contact last name for email prediction"),
|
|
278
|
+
},
|
|
279
|
+
async ({ url, firstName, lastName }) => {
|
|
280
|
+
const result = await enrichCompany(url, firstName || "", lastName || "");
|
|
281
|
+
return {
|
|
282
|
+
content: [{
|
|
283
|
+
type: "text",
|
|
284
|
+
text: JSON.stringify(result, null, 2),
|
|
285
|
+
}],
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
// Tool 2: Bulk Enrich
|
|
291
|
+
server.tool(
|
|
292
|
+
"enrich_companies_bulk",
|
|
293
|
+
"Enrich multiple companies at once. Get emails, tech stack, lead scores for a list of websites.",
|
|
294
|
+
{
|
|
295
|
+
urls: z.array(z.string()).describe("Array of company website URLs"),
|
|
296
|
+
},
|
|
297
|
+
async ({ urls }) => {
|
|
298
|
+
const results = [];
|
|
299
|
+
for (const url of urls.slice(0, 20)) {
|
|
300
|
+
try {
|
|
301
|
+
const r = await enrichCompany(url);
|
|
302
|
+
results.push(r);
|
|
303
|
+
} catch (e) {
|
|
304
|
+
results.push({ url, error: e.message });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
content: [{
|
|
309
|
+
type: "text",
|
|
310
|
+
text: JSON.stringify(results, null, 2),
|
|
311
|
+
}],
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
// Tool 3: Find Emails
|
|
317
|
+
server.tool(
|
|
318
|
+
"find_emails",
|
|
319
|
+
"Find all email addresses from a company website. Includes confidence scores and executive email predictions.",
|
|
320
|
+
{
|
|
321
|
+
url: z.string().describe("Company website URL"),
|
|
322
|
+
firstName: z.string().optional().describe("Person's first name"),
|
|
323
|
+
lastName: z.string().optional().describe("Person's last name"),
|
|
324
|
+
},
|
|
325
|
+
async ({ url, firstName, lastName }) => {
|
|
326
|
+
const result = await enrichCompany(url, firstName || "", lastName || "");
|
|
327
|
+
const emailData = {
|
|
328
|
+
domain: result.domain,
|
|
329
|
+
found_emails: result.scored_emails,
|
|
330
|
+
personal_emails: result.personal_emails,
|
|
331
|
+
generic_emails: result.generic_emails,
|
|
332
|
+
predicted_emails: result.predicted_emails,
|
|
333
|
+
executive_emails: result.executive_emails,
|
|
334
|
+
total: result.email_count,
|
|
335
|
+
};
|
|
336
|
+
return {
|
|
337
|
+
content: [{
|
|
338
|
+
type: "text",
|
|
339
|
+
text: JSON.stringify(emailData, null, 2),
|
|
340
|
+
}],
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
// Tool 4: Detect Tech Stack
|
|
346
|
+
server.tool(
|
|
347
|
+
"detect_tech_stack",
|
|
348
|
+
"Detect what technologies a website uses: CMS, frameworks, analytics, CDN, payment, chat tools.",
|
|
349
|
+
{
|
|
350
|
+
url: z.string().describe("Website URL to analyze"),
|
|
351
|
+
},
|
|
352
|
+
async ({ url }) => {
|
|
353
|
+
if (!url.startsWith("http")) url = "https://" + url;
|
|
354
|
+
const resp = await fetchUrl(url);
|
|
355
|
+
const techs = detectTechStack(resp.body, resp.headers);
|
|
356
|
+
return {
|
|
357
|
+
content: [{
|
|
358
|
+
type: "text",
|
|
359
|
+
text: JSON.stringify({ url, tech_stack: techs, count: techs.length }, null, 2),
|
|
360
|
+
}],
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
// Start
|
|
366
|
+
const transport = new StdioServerTransport();
|
|
367
|
+
await server.connect(transport);
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-business-intel",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "8-in-1 Business Intelligence MCP Server. Find emails, tech stack, WHOIS, SSL, lead scores for any company. The most comprehensive company enrichment tool for AI assistants.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mcp-business-intel": "./index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node index.js",
|
|
12
|
+
"test": "node test.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": ["mcp", "business-intelligence", "lead-enrichment", "email-finder", "tech-stack", "whois", "ssl", "company-data", "b2b", "sales", "ai-agent"],
|
|
15
|
+
"author": "Hitman (Aman)",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
19
|
+
"zod": "^4.3.6"
|
|
20
|
+
}
|
|
21
|
+
}
|
package/test.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Quick test - directly call the enrichment function
|
|
2
|
+
import https from "https";
|
|
3
|
+
import http from "http";
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
import tls from "tls";
|
|
6
|
+
|
|
7
|
+
function fetchUrl(url, timeout = 10000) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const mod = url.startsWith("https") ? https : http;
|
|
10
|
+
const req = mod.get(url, {
|
|
11
|
+
headers: { "User-Agent": "Mozilla/5.0 Chrome/131.0", "Accept-Language": "en-US" },
|
|
12
|
+
timeout,
|
|
13
|
+
}, (res) => {
|
|
14
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
15
|
+
fetchUrl(res.headers.location, timeout).then(resolve).catch(reject);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
let data = "";
|
|
19
|
+
res.on("data", c => data += c);
|
|
20
|
+
res.on("end", () => resolve({ body: data, headers: res.headers }));
|
|
21
|
+
});
|
|
22
|
+
req.on("error", reject);
|
|
23
|
+
req.on("timeout", () => { req.destroy(); reject(new Error("timeout")); });
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function extractDomain(url) {
|
|
28
|
+
if (!url.startsWith("http")) url = "https://" + url;
|
|
29
|
+
const m = url.match(/https?:\/\/(?:www\.)?([^/]+)/);
|
|
30
|
+
return m ? m[1] : url;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function detectTechStack(html, headers) {
|
|
34
|
+
const techs = [];
|
|
35
|
+
const checks = [
|
|
36
|
+
["WordPress", [/wp-content/i]], ["Shopify", [/shopify/i]], ["React", [/react\.production/i, /__NEXT_DATA__/i]],
|
|
37
|
+
["Next.js", [/__NEXT_DATA__/i]], ["Vue.js", [/vue\.js/i]], ["Google Analytics", [/google-analytics/i, /gtag/i]],
|
|
38
|
+
["Cloudflare", [/cloudflare/i]], ["AWS", [/amazonaws/i]], ["Stripe", [/stripe\.com/i]],
|
|
39
|
+
["HubSpot", [/hubspot/i]], ["Vercel", [/vercel/i]], ["Razorpay", [/razorpay/i]],
|
|
40
|
+
];
|
|
41
|
+
const combined = html + JSON.stringify(headers);
|
|
42
|
+
for (const [tech, patterns] of checks) {
|
|
43
|
+
if (patterns.some(p => p.test(combined))) techs.push(tech);
|
|
44
|
+
}
|
|
45
|
+
return [...new Set(techs)];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const tests = ["https://stripe.com", "https://zoho.com", "https://freshworks.com", "https://razorpay.com", "https://github.com"];
|
|
49
|
+
|
|
50
|
+
console.log(`Testing MCP Business Intel — ${tests.length} companies\n${"=".repeat(60)}`);
|
|
51
|
+
|
|
52
|
+
for (const url of tests) {
|
|
53
|
+
try {
|
|
54
|
+
const domain = extractDomain(url);
|
|
55
|
+
const resp = await fetchUrl(url);
|
|
56
|
+
const html = resp.body;
|
|
57
|
+
|
|
58
|
+
// Emails
|
|
59
|
+
const junk = ["sentry","example.com","domain.com","placeholder","your@","test.com","errors."];
|
|
60
|
+
let emails = [...new Set(html.match(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g) || [])];
|
|
61
|
+
emails = emails.filter(e => !junk.some(j => e.includes(j)));
|
|
62
|
+
|
|
63
|
+
// Tech
|
|
64
|
+
const techs = detectTechStack(html, resp.headers);
|
|
65
|
+
|
|
66
|
+
// Social
|
|
67
|
+
const linkedin = (html.match(/linkedin\.com\/company\/[a-zA-Z0-9_-]+/g) || []).length;
|
|
68
|
+
const twitter = (html.match(/(twitter|x)\.com\/[a-zA-Z0-9_]+/g) || []).length;
|
|
69
|
+
|
|
70
|
+
console.log(`\n ${domain}:`);
|
|
71
|
+
console.log(` Emails: ${emails.length} | Tech: ${techs.join(", ") || "none"}`);
|
|
72
|
+
console.log(` LinkedIn: ${linkedin > 0 ? "Yes" : "No"} | Twitter: ${twitter > 0 ? "Yes" : "No"}`);
|
|
73
|
+
} catch (e) {
|
|
74
|
+
console.log(` ${url}: FAIL — ${e.message}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log(`\n${"=".repeat(60)}\nAll tests done!`);
|