mcp-scraper 0.3.24 → 0.3.25
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/dist/bin/api-server.cjs +8140 -5941
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +3 -3
- package/dist/bin/mcp-scraper-cli.cjs +320 -58
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +4 -3
- package/dist/bin/mcp-scraper-cli.js.map +1 -1
- package/dist/bin/mcp-scraper-install.cjs +3 -3
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +2 -2
- package/dist/bin/mcp-stdio-server.cjs +121 -24
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +5 -5
- package/dist/bin/paa-harvest.cjs +284 -10
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +5 -3
- package/dist/bin/paa-harvest.js.map +1 -1
- package/dist/{chunk-ZAP7MYVO.js → chunk-3PRO376E.js} +2 -3
- package/dist/chunk-3PRO376E.js.map +1 -0
- package/dist/{chunk-LFATOGDF.js → chunk-BP27CZ5Q.js} +4 -3
- package/dist/chunk-BP27CZ5Q.js.map +1 -0
- package/dist/{chunk-DE7WP4UU.js → chunk-DOBQN3EY.js} +3 -3
- package/dist/chunk-DOBQN3EY.js.map +1 -0
- package/dist/{chunk-GZ4W72WF.js → chunk-FE2WC4JR.js} +119 -23
- package/dist/chunk-FE2WC4JR.js.map +1 -0
- package/dist/{chunk-IGOEAKC7.js → chunk-G7PQ64LM.js} +274 -9
- package/dist/chunk-G7PQ64LM.js.map +1 -0
- package/dist/chunk-K443GQY5.js +24 -0
- package/dist/chunk-K443GQY5.js.map +1 -0
- package/dist/{chunk-H2R232HK.js → chunk-NNEIXK5L.js} +307 -62
- package/dist/chunk-NNEIXK5L.js.map +1 -0
- package/dist/{chunk-3KYRG7O7.js → chunk-TRNSXR46.js} +185 -2
- package/dist/chunk-TRNSXR46.js.map +1 -0
- package/dist/chunk-WPHAMNT7.js +7 -0
- package/dist/chunk-WPHAMNT7.js.map +1 -0
- package/dist/{chunk-JTWQGPKF.js → chunk-WYCER2HW.js} +33 -10
- package/dist/chunk-WYCER2HW.js.map +1 -0
- package/dist/{db-EG5ETPTY.js → db-FP2ABUU4.js} +32 -2
- package/dist/index.cjs +284 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/{server-2SRARAO5.js → server-RRCKVBHG.js} +1563 -265
- package/dist/server-RRCKVBHG.js.map +1 -0
- package/dist/{worker-UM5UM6O5.js → worker-UC6D2756.js} +7 -6
- package/dist/{worker-UM5UM6O5.js.map → worker-UC6D2756.js.map} +1 -1
- package/docs/kernel-proxy-future-enhancements.md +80 -0
- package/docs/spec-browser-routing.md +108 -0
- package/docs/spec-mcp-tools.md +146 -0
- package/docs/specs/audit-visual-demo.md +309 -0
- package/package.json +5 -2
- package/dist/chunk-3KYRG7O7.js.map +0 -1
- package/dist/chunk-DE7WP4UU.js.map +0 -1
- package/dist/chunk-GZ4W72WF.js.map +0 -1
- package/dist/chunk-H2R232HK.js.map +0 -1
- package/dist/chunk-H7R4PPMG.js +0 -7
- package/dist/chunk-H7R4PPMG.js.map +0 -1
- package/dist/chunk-IGOEAKC7.js.map +0 -1
- package/dist/chunk-JTWQGPKF.js.map +0 -1
- package/dist/chunk-LFATOGDF.js.map +0 -1
- package/dist/chunk-ZAP7MYVO.js.map +0 -1
- package/dist/server-2SRARAO5.js.map +0 -1
- /package/dist/{db-EG5ETPTY.js.map → db-FP2ABUU4.js.map} +0 -0
|
@@ -36,10 +36,10 @@ var CLIENT_OVER_SERVER_MARGIN_MS = 15e3;
|
|
|
36
36
|
function harvestTimeoutBudget(maxQuestions, serpOnly = false) {
|
|
37
37
|
const requested = Number.isFinite(maxQuestions) && maxQuestions > 0 ? Math.trunc(maxQuestions) : 30;
|
|
38
38
|
let serverMs;
|
|
39
|
-
if (serpOnly
|
|
40
|
-
else if (requested <=
|
|
41
|
-
else if (requested <=
|
|
42
|
-
else serverMs =
|
|
39
|
+
if (serpOnly) serverMs = 11e4;
|
|
40
|
+
else if (requested <= 50) serverMs = 17e4;
|
|
41
|
+
else if (requested <= 100) serverMs = 23e4;
|
|
42
|
+
else serverMs = 285e3;
|
|
43
43
|
const clientMs = Math.min(serverMs + CLIENT_OVER_SERVER_MARGIN_MS, VERCEL_FUNCTION_MAX_MS - 5e3);
|
|
44
44
|
return { serverMs, clientMs };
|
|
45
45
|
}
|
|
@@ -261,6 +261,9 @@ var HttpMcpToolExecutor = class {
|
|
|
261
261
|
facebookAdSearch(input) {
|
|
262
262
|
return this.call("/facebook/search", input);
|
|
263
263
|
}
|
|
264
|
+
redditThread(input) {
|
|
265
|
+
return this.call("/reddit/thread", input, this.httpTimeoutOverrideMs ?? 24e4);
|
|
266
|
+
}
|
|
264
267
|
facebookAdTranscribe(input) {
|
|
265
268
|
return this.call("/facebook/transcribe", input);
|
|
266
269
|
}
|
|
@@ -456,7 +459,7 @@ render();
|
|
|
456
459
|
}
|
|
457
460
|
|
|
458
461
|
// src/version.ts
|
|
459
|
-
var PACKAGE_VERSION = "0.3.
|
|
462
|
+
var PACKAGE_VERSION = "0.3.25";
|
|
460
463
|
|
|
461
464
|
// src/mcp/browser-agent-tool-schemas.ts
|
|
462
465
|
var import_zod = require("zod");
|
|
@@ -1872,6 +1875,17 @@ var WORKFLOW_RECIPES = [
|
|
|
1872
1875
|
produces: ["city summary", "competitor CSV", "review insight CSV", "market difficulty/opportunity notes", "HTML report"],
|
|
1873
1876
|
runHint: "Use workflow_run with workflowId local-competitive-audit for state-wide market batches, or map-comparison for one city/location comparison."
|
|
1874
1877
|
},
|
|
1878
|
+
{
|
|
1879
|
+
id: "lead_generation",
|
|
1880
|
+
title: "Lead Generation",
|
|
1881
|
+
description: "Build an outreach-ready local lead list for a niche in one market: Google Maps businesses with confirmed review counts and booking URLs, enriched with email and social links scraped from each business website (proxy/browser-backed so blocked sites still resolve).",
|
|
1882
|
+
primaryWorkflowId: "get-leads",
|
|
1883
|
+
recommendedTools: ["workflow_run", "maps_search", "maps_place_intel", "extract_url"],
|
|
1884
|
+
requiredInputs: ["query", "location"],
|
|
1885
|
+
optionalInputs: ["maxResults", "enrichWebsites", "hydrateReviewCounts", "concurrency", "proxyMode"],
|
|
1886
|
+
produces: ["leads CSV (name, phone, website, email, socials, review count, booking URL)", "evidence JSON", "HTML report", "outreach task list"],
|
|
1887
|
+
runHint: 'Use workflow_run with workflowId get-leads. Put the niche in query (e.g. "roofers") and the market in location (e.g. "Houston, TX"); do not combine them.'
|
|
1888
|
+
},
|
|
1875
1889
|
{
|
|
1876
1890
|
id: "icp_research",
|
|
1877
1891
|
title: "ICP Research",
|
|
@@ -2817,6 +2831,16 @@ ${bodyMd.slice(0, 3e3)}${bodyMd.length > 3e3 ? `
|
|
|
2817
2831
|
const bodySectionFull = bodyMd ? `
|
|
2818
2832
|
## Page Content
|
|
2819
2833
|
${bodyMd}` : "";
|
|
2834
|
+
const mem = d.memory ?? null;
|
|
2835
|
+
const memSection = mem?.deposited ? `
|
|
2836
|
+
## \u2705 Saved to memory
|
|
2837
|
+
Full content stored in vault \`${mem.vault ?? "\u2014"}\` as note \`${mem.noteId ?? "\u2014"}\`${mem.chunks !== void 0 ? ` (${mem.chunks} chunks indexed)` : ""}. Recall it later with memory search \u2014 no need to re-scrape.` : mem?.fileUrl ? `
|
|
2838
|
+
## \u{1F4C4} Saved as a temporary file
|
|
2839
|
+
Memory vault was unavailable${mem.error ? ` (${mem.error})` : ""}, so the full content was saved to a download link instead:
|
|
2840
|
+
- **Link:** ${mem.fileUrl}
|
|
2841
|
+
- **Expires:** ${mem.fileExpiresAt ?? "in 24 hours"} (auto-deleted)` : mem ? `
|
|
2842
|
+
## \u26A0\uFE0F Memory deposit skipped
|
|
2843
|
+
${mem.error ?? "unknown error"} \u2014 the page content is still in the truncated preview above.` : "";
|
|
2820
2844
|
const screenshotSection = screenshotMeta ? `
|
|
2821
2845
|
## Screenshot
|
|
2822
2846
|
- **File:** ${screenshotPath ?? "(returned inline only \u2014 disk write unavailable in this environment)"}
|
|
@@ -2846,10 +2870,10 @@ ${bodyMd}` : "";
|
|
|
2846
2870
|
- ${schemaCount} JSON-LD schema block(s) detected`;
|
|
2847
2871
|
const full = `# URL Extract: ${url}
|
|
2848
2872
|
**${title}**
|
|
2849
|
-
${headingSection}${kpoSection}${brandingSection}${bodySection}${screenshotSection}${mediaSection}${tips}`;
|
|
2873
|
+
${headingSection}${kpoSection}${brandingSection}${bodySection}${memSection}${screenshotSection}${mediaSection}${tips}`;
|
|
2850
2874
|
const diskReport = `# URL Extract: ${url}
|
|
2851
2875
|
**${title}**
|
|
2852
|
-
${headingSection}${kpoSection}${brandingSection}${bodySectionFull}${screenshotSection}${mediaSection}${tips}`;
|
|
2876
|
+
${headingSection}${kpoSection}${brandingSection}${bodySectionFull}${memSection}${screenshotSection}${mediaSection}${tips}`;
|
|
2853
2877
|
const textResult = oneBlock(full, diskReport);
|
|
2854
2878
|
const structuredContent = {
|
|
2855
2879
|
url,
|
|
@@ -2862,7 +2886,8 @@ ${headingSection}${kpoSection}${brandingSection}${bodySectionFull}${screenshotSe
|
|
|
2862
2886
|
missingSchemaFields: kpo?.missingFields ?? [],
|
|
2863
2887
|
screenshotSaved: screenshotPath ?? null,
|
|
2864
2888
|
branding: branding ?? null,
|
|
2865
|
-
mediaAssets: media?.assets ?? null
|
|
2889
|
+
mediaAssets: media?.assets ?? null,
|
|
2890
|
+
memory: mem ?? void 0
|
|
2866
2891
|
};
|
|
2867
2892
|
if (screenshotMeta?.base64) {
|
|
2868
2893
|
return {
|
|
@@ -3248,6 +3273,38 @@ ${adBlocks}`,
|
|
|
3248
3273
|
}
|
|
3249
3274
|
};
|
|
3250
3275
|
}
|
|
3276
|
+
function formatRedditThread(raw, input) {
|
|
3277
|
+
const parsed = parseData(raw);
|
|
3278
|
+
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
3279
|
+
const d = parsed.data;
|
|
3280
|
+
const comments = d.comments ?? [];
|
|
3281
|
+
const commentMd = comments.map((cm) => {
|
|
3282
|
+
const indent = " ".repeat(Math.min(Number(cm.depth ?? 0), 6));
|
|
3283
|
+
return `${indent}- **u/${cm.author || "[unknown]"}** (${cm.score || "\u2014"}): ${(cm.body || "").replace(/\s+/g, " ").trim()}`;
|
|
3284
|
+
}).join("\n");
|
|
3285
|
+
const full = [
|
|
3286
|
+
`# ${d.title || "Reddit thread"}`,
|
|
3287
|
+
`**u/${d.author || "[unknown]"}** \xB7 ${d.score || "\u2014"} \xB7 ${comments.length} comments captured`,
|
|
3288
|
+
d.postBody ? `
|
|
3289
|
+
${d.postBody}` : "",
|
|
3290
|
+
`
|
|
3291
|
+
## Comments
|
|
3292
|
+
${commentMd || "_No comments captured._"}`
|
|
3293
|
+
].join("\n");
|
|
3294
|
+
return {
|
|
3295
|
+
...oneBlock(full),
|
|
3296
|
+
structuredContent: {
|
|
3297
|
+
sourceUrl: d.sourceUrl ?? input.url ?? null,
|
|
3298
|
+
oldRedditUrl: d.oldRedditUrl ?? null,
|
|
3299
|
+
title: d.title ?? null,
|
|
3300
|
+
author: d.author ?? null,
|
|
3301
|
+
score: d.score ?? null,
|
|
3302
|
+
postBody: d.postBody ?? null,
|
|
3303
|
+
numComments: comments.length,
|
|
3304
|
+
comments: comments.map((cm) => ({ author: cm.author ?? null, score: cm.score ?? null, depth: Number(cm.depth ?? 0), body: cm.body ?? "" }))
|
|
3305
|
+
}
|
|
3306
|
+
};
|
|
3307
|
+
}
|
|
3251
3308
|
function formatFacebookAdSearch(raw, input) {
|
|
3252
3309
|
const parsed = parseData(raw);
|
|
3253
3310
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
@@ -4213,7 +4270,7 @@ var import_zod3 = require("zod");
|
|
|
4213
4270
|
|
|
4214
4271
|
// src/schemas.ts
|
|
4215
4272
|
var import_zod2 = require("zod");
|
|
4216
|
-
var DEFAULT_PROXY_MODE = "
|
|
4273
|
+
var DEFAULT_PROXY_MODE = "none";
|
|
4217
4274
|
var DEFAULT_MAPS_PROXY_MODE = "location";
|
|
4218
4275
|
var HarvestOptionsSchema = import_zod2.z.object({
|
|
4219
4276
|
query: import_zod2.z.string().min(1),
|
|
@@ -4235,7 +4292,8 @@ var HarvestOptionsSchema = import_zod2.z.object({
|
|
|
4235
4292
|
outputDir: import_zod2.z.string().default("./paa-output"),
|
|
4236
4293
|
format: import_zod2.z.enum(["json", "csv", "both"]).default("both"),
|
|
4237
4294
|
serpOnly: import_zod2.z.boolean().default(false),
|
|
4238
|
-
pages: import_zod2.z.number().int().min(1).max(2).default(1)
|
|
4295
|
+
pages: import_zod2.z.number().int().min(1).max(2).default(1),
|
|
4296
|
+
softDeadlineMs: import_zod2.z.number().optional()
|
|
4239
4297
|
});
|
|
4240
4298
|
var MapsPlaceOptionsSchema = import_zod2.z.object({
|
|
4241
4299
|
businessName: import_zod2.z.string().min(1),
|
|
@@ -4317,7 +4375,7 @@ var HarvestPaaInputSchema = {
|
|
|
4317
4375
|
gl: import_zod3.z.string().length(2).default("us").describe("Google country code inferred from location or user language. Examples: United States us, United Kingdom gb, Japan jp, Canada ca, Australia au."),
|
|
4318
4376
|
hl: import_zod3.z.string().default("en").describe("Google interface/content language inferred from the user request. Use en unless the user asks for another language or locale."),
|
|
4319
4377
|
device: import_zod3.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
|
|
4320
|
-
proxyMode: import_zod3.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe(
|
|
4378
|
+
proxyMode: import_zod3.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe('Leave unset \u2014 routing is automatic (clean egress by default; residential only when a location is given, with fallback). Set "location" only to force local-pack targeting.'),
|
|
4321
4379
|
proxyZip: import_zod3.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use only with proxyMode location when the user gives a specific ZIP or city-center targeting needs to be forced."),
|
|
4322
4380
|
debug: import_zod3.z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior.")
|
|
4323
4381
|
};
|
|
@@ -4328,7 +4386,9 @@ var ExtractUrlInputSchema = {
|
|
|
4328
4386
|
extractBranding: import_zod3.z.boolean().default(false).describe("Extract brand colors, fonts, logo, and favicon using a rendered browser session. Returns colorScheme (light/dark), colors (primary/accent/background/text/heading as hex), fonts (heading/body family names), and assets (logo URL, favicon URL). Use when the user asks about brand colors, site theme, or brand assets."),
|
|
4329
4387
|
downloadMedia: import_zod3.z.boolean().default(false).describe("Extract and download all page media (images, video, audio) to ~/Downloads/mcp-scraper/media/. Ad networks, tracking pixels, and noise URLs are filtered automatically. Use when the user asks to download or harvest assets from a page."),
|
|
4330
4388
|
mediaTypes: import_zod3.z.array(import_zod3.z.enum(["image", "video", "audio"])).default(["image", "video", "audio"]).describe("Which media types to download. Default all three."),
|
|
4331
|
-
allowLocal: import_zod3.z.boolean().default(false).describe("Allow localhost and private-network URLs. For local development only.")
|
|
4389
|
+
allowLocal: import_zod3.z.boolean().default(false).describe("Allow localhost and private-network URLs. For local development only."),
|
|
4390
|
+
depositToVault: import_zod3.z.boolean().default(false).describe("Also save the full page content into the user's MCP Memory Library vault, server-side. The complete body is stored and embedded for semantic recall WITHOUT passing through this conversation \u2014 the tool result only confirms the saved note. Use when the user asks to save/remember/store a page, or to build a knowledge base from scrapes."),
|
|
4391
|
+
vaultName: import_zod3.z.string().trim().min(1).max(120).optional().describe("Optional vault to deposit into. Defaults to the user's personal vault.")
|
|
4332
4392
|
};
|
|
4333
4393
|
var MapSiteUrlsInputSchema = {
|
|
4334
4394
|
url: import_zod3.z.string().url().describe("Public website URL or domain to crawl for internal URLs. Use before extract_site when the user asks to audit/map/crawl a site."),
|
|
@@ -4369,6 +4429,10 @@ var FacebookAdSearchInputSchema = {
|
|
|
4369
4429
|
country: import_zod3.z.string().length(2).default("US").describe("Two-letter Ad Library country code. Default US. Examples: US, CA, GB, AU."),
|
|
4370
4430
|
maxResults: import_zod3.z.number().int().min(1).max(20).default(10).describe("Maximum advertisers to return. Default 10, maximum 20. Prefer tighter search terms over maxing this out.")
|
|
4371
4431
|
};
|
|
4432
|
+
var RedditThreadInputSchema = {
|
|
4433
|
+
url: import_zod3.z.string().min(1).describe("A reddit.com thread/post URL (www, old, or new Reddit, or a redd.it link). The service fetches it via old.reddit through a residential proxy and returns the post plus its comment tree."),
|
|
4434
|
+
maxComments: import_zod3.z.number().int().min(1).max(2e3).optional().describe("Optional cap on comments returned. Omit to return all captured comments.")
|
|
4435
|
+
};
|
|
4372
4436
|
var FacebookAdTranscribeInputSchema = {
|
|
4373
4437
|
videoUrl: import_zod3.z.string().url().describe("Direct Facebook CDN video URL from a facebook_page_intel ad result. Do not pass a public Facebook reel/post/share URL here; use facebook_video_transcribe for organic Facebook URLs.")
|
|
4374
4438
|
};
|
|
@@ -4635,7 +4699,17 @@ var ExtractUrlOutputSchema = {
|
|
|
4635
4699
|
entityTypes: import_zod3.z.array(import_zod3.z.string()),
|
|
4636
4700
|
napScore: import_zod3.z.number().nullable(),
|
|
4637
4701
|
missingSchemaFields: import_zod3.z.array(import_zod3.z.string()),
|
|
4638
|
-
screenshotSaved: NullableString2
|
|
4702
|
+
screenshotSaved: NullableString2,
|
|
4703
|
+
memory: import_zod3.z.object({
|
|
4704
|
+
deposited: import_zod3.z.boolean(),
|
|
4705
|
+
vault: import_zod3.z.string().optional(),
|
|
4706
|
+
noteId: import_zod3.z.string().optional(),
|
|
4707
|
+
path: import_zod3.z.string().optional(),
|
|
4708
|
+
chunks: import_zod3.z.number().int().optional(),
|
|
4709
|
+
fileUrl: import_zod3.z.string().optional(),
|
|
4710
|
+
fileExpiresAt: import_zod3.z.string().optional(),
|
|
4711
|
+
error: import_zod3.z.string().optional()
|
|
4712
|
+
}).optional()
|
|
4639
4713
|
};
|
|
4640
4714
|
var ExtractSiteOutputSchema = {
|
|
4641
4715
|
url: import_zod3.z.string(),
|
|
@@ -4767,6 +4841,21 @@ var FacebookAdSearchOutputSchema = {
|
|
|
4767
4841
|
sampleLibraryId: NullableString2
|
|
4768
4842
|
}))
|
|
4769
4843
|
};
|
|
4844
|
+
var RedditThreadOutputSchema = {
|
|
4845
|
+
sourceUrl: NullableString2,
|
|
4846
|
+
oldRedditUrl: NullableString2,
|
|
4847
|
+
title: NullableString2,
|
|
4848
|
+
author: NullableString2,
|
|
4849
|
+
score: NullableString2,
|
|
4850
|
+
postBody: NullableString2,
|
|
4851
|
+
numComments: import_zod3.z.number().int().min(0),
|
|
4852
|
+
comments: import_zod3.z.array(import_zod3.z.object({
|
|
4853
|
+
author: NullableString2,
|
|
4854
|
+
score: NullableString2,
|
|
4855
|
+
depth: import_zod3.z.number().int().min(0),
|
|
4856
|
+
body: import_zod3.z.string()
|
|
4857
|
+
}))
|
|
4858
|
+
};
|
|
4770
4859
|
var FacebookPageIntelOutputSchema = {
|
|
4771
4860
|
advertiserName: NullableString2,
|
|
4772
4861
|
totalAds: import_zod3.z.number().int().min(0),
|
|
@@ -4960,6 +5049,7 @@ var CreditsInfoInputSchema = {
|
|
|
4960
5049
|
};
|
|
4961
5050
|
var WorkflowIdSchema = import_zod3.z.enum([
|
|
4962
5051
|
"directory",
|
|
5052
|
+
"get-leads",
|
|
4963
5053
|
"agent-packet",
|
|
4964
5054
|
"local-competitive-audit",
|
|
4965
5055
|
"map-comparison",
|
|
@@ -4981,8 +5071,8 @@ var WorkflowSuggestInputSchema = {
|
|
|
4981
5071
|
maxSuggestions: import_zod3.z.number().int().min(1).max(8).default(3).describe("Number of matching workflow recipes to return.")
|
|
4982
5072
|
};
|
|
4983
5073
|
var WorkflowRunInputSchema = {
|
|
4984
|
-
workflowId: WorkflowIdSchema.describe("Workflow to run: directory, agent-packet, local-competitive-audit, map-comparison, serp-comparison, paa-expansion-brief, ai-overview-language. Use only these values; call workflow_list or workflow_suggest first when unsure."),
|
|
4985
|
-
input: import_zod3.z.record(import_zod3.z.unknown()).default({}).describe("Workflow-specific input object. Examples: agent-packet uses {keyword, domain?, location?, maxQuestions?}; local-competitive-audit uses {query, state, minPopulation?, maxCities?, maxResultsPerCity?, hydrateTop?, maxReviews?}; serp-comparison uses {keyword, domain?, url?, location?, extractTop?}."),
|
|
5074
|
+
workflowId: WorkflowIdSchema.describe("Workflow to run: directory, get-leads, agent-packet, local-competitive-audit, map-comparison, serp-comparison, paa-expansion-brief, ai-overview-language. Use only these values; call workflow_list or workflow_suggest first when unsure."),
|
|
5075
|
+
input: import_zod3.z.record(import_zod3.z.unknown()).default({}).describe("Workflow-specific input object. Examples: get-leads uses {query, location, maxResults?, enrichWebsites?, hydrateReviewCounts?}; agent-packet uses {keyword, domain?, location?, maxQuestions?}; local-competitive-audit uses {query, state, minPopulation?, maxCities?, maxResultsPerCity?, hydrateTop?, maxReviews?}; serp-comparison uses {keyword, domain?, url?, location?, extractTop?}."),
|
|
4986
5076
|
webhookUrl: import_zod3.z.string().url().optional().describe("Optional HTTPS webhook to receive the completed hosted workflow run event.")
|
|
4987
5077
|
};
|
|
4988
5078
|
var WorkflowStepInputSchema = {
|
|
@@ -5058,7 +5148,7 @@ var SearchSerpInputSchema = {
|
|
|
5058
5148
|
gl: import_zod3.z.string().length(2).default("us").describe("Google country code inferred from location or user language."),
|
|
5059
5149
|
hl: import_zod3.z.string().default("en").describe("Google interface/content language inferred from user request."),
|
|
5060
5150
|
device: import_zod3.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
|
|
5061
|
-
proxyMode: import_zod3.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe(
|
|
5151
|
+
proxyMode: import_zod3.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe('Leave unset \u2014 routing is automatic. Set "location" only to force local-pack targeting.'),
|
|
5062
5152
|
proxyZip: import_zod3.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use only with proxyMode location when the user gives a specific ZIP or city-center targeting needs to be forced."),
|
|
5063
5153
|
debug: import_zod3.z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior."),
|
|
5064
5154
|
pages: import_zod3.z.number().int().min(1).max(2).default(1).describe("Number of result pages to fetch (1\u20132)")
|
|
@@ -5069,7 +5159,7 @@ var CaptureSerpSnapshotInputSchema = {
|
|
|
5069
5159
|
gl: import_zod3.z.string().length(2).default("us").describe("Google country code inferred from the requested market, e.g. us, gb, ca, au."),
|
|
5070
5160
|
hl: import_zod3.z.string().default("en").describe("Google interface/content language inferred from the user request."),
|
|
5071
5161
|
device: import_zod3.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only when the user asks for mobile rankings or mobile SERP evidence."),
|
|
5072
|
-
proxyMode: import_zod3.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe(
|
|
5162
|
+
proxyMode: import_zod3.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe('Leave unset \u2014 routing is automatic. Set "location" only to force city/ZIP targeting.'),
|
|
5073
5163
|
proxyZip: import_zod3.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use only with proxyMode location when a precise city-center or ZIP proxy is needed."),
|
|
5074
5164
|
pages: import_zod3.z.number().int().min(1).max(2).default(1).describe("Number of Google result pages to capture. Use 1 normally and 2 only when the user needs deeper ranking evidence."),
|
|
5075
5165
|
debug: import_zod3.z.boolean().default(false).describe("Include sanitized browser, proxy, and location diagnostics. Use true when debugging localization, CAPTCHA, proxy selection, or capture reliability."),
|
|
@@ -5476,21 +5566,21 @@ function registerPaaExtractorMcpTools(server2, executor, options = {}) {
|
|
|
5476
5566
|
if (savesReports) registerSavedReportResources(server2);
|
|
5477
5567
|
server2.registerTool("harvest_paa", {
|
|
5478
5568
|
title: "Google PAA + SERP Harvest",
|
|
5479
|
-
description: withReportNote('Best default tool for Google search research. Extracts People Also Ask questions plus answers/source URLs, organic SERP, local pack when present, entity IDs (CID/GCID/KG MID), and AI Overview. Infer the user language: split topic from location (e.g. "best hvac company in Denver CO" => query "best hvac company", location "Denver, CO", gl "us", hl "en").
|
|
5569
|
+
description: withReportNote('Best default tool for Google search research. Extracts People Also Ask questions plus answers/source URLs, organic SERP, local pack when present, entity IDs (CID/GCID/KG MID), and AI Overview. Infer the user language: split topic from location (e.g. "best hvac company in Denver CO" => query "best hvac company", location "Denver, CO", gl "us", hl "en"). Routing is automatic \u2014 leave proxyMode unset. If a deep harvest exceeds the time budget it returns the questions gathered so far (partial), billed per returned question. Use maxQuestions 30 normally, 100-200 for "full", "deep", "all", or comprehensive research. Deep harvests above 100 questions can run for several minutes with no interim progress \u2014 warn the user before starting one and keep maxQuestions at or below 100 unless they explicitly want a deep harvest. Credits are charged by extracted question; unused request hold is refunded.'),
|
|
5480
5570
|
inputSchema: HarvestPaaInputSchema,
|
|
5481
5571
|
outputSchema: HarvestPaaOutputSchema,
|
|
5482
5572
|
annotations: liveWebToolAnnotations("Google PAA + SERP Harvest")
|
|
5483
5573
|
}, async (input) => formatHarvestPaa(await executor.harvestPaa(input), input));
|
|
5484
5574
|
server2.registerTool("search_serp", {
|
|
5485
5575
|
title: "Google SERP Lookup",
|
|
5486
|
-
description: withReportNote("Fast Google SERP lookup without PAA expansion. Use when the user asks for rankings, organic results, local pack, quick SERP, or positions. Split topic from location and infer gl/hl from the user request.
|
|
5576
|
+
description: withReportNote("Fast Google SERP lookup without PAA expansion. Use when the user asks for rankings, organic results, local pack, quick SERP, or positions. Split topic from location and infer gl/hl from the user request. Routing is automatic \u2014 leave proxyMode unset; set location only to force local-pack targeting."),
|
|
5487
5577
|
inputSchema: SearchSerpInputSchema,
|
|
5488
5578
|
outputSchema: SearchSerpOutputSchema,
|
|
5489
5579
|
annotations: liveWebToolAnnotations("Google SERP Lookup")
|
|
5490
5580
|
}, async (input) => formatSearchSerp(await executor.searchSerp(input), input));
|
|
5491
5581
|
server2.registerTool("extract_url", {
|
|
5492
5582
|
title: "Single URL Extract",
|
|
5493
|
-
description: withReportNote("Extract structured data from one public URL when the user provides one page, asks to inspect/scrape a page, or needs page content, schema, headings, metadata, screenshots, branding, or media assets. Returns structured page fields plus artifact handles for saved reports/screenshots/media when requested. Use map_site_urls before extracting a site inventory; use extract_site for multi-page crawling."),
|
|
5583
|
+
description: withReportNote("Extract structured data from one public URL when the user provides one page, asks to inspect/scrape a page, or needs page content, schema, headings, metadata, screenshots, branding, or media assets. Returns structured page fields plus artifact handles for saved reports/screenshots/media when requested. Set depositToVault:true to also save the full page into the user's MCP Memory vault server-side (the full body is NOT returned to chat). Use map_site_urls before extracting a site inventory; use extract_site for multi-page crawling."),
|
|
5494
5584
|
inputSchema: ExtractUrlInputSchema,
|
|
5495
5585
|
outputSchema: ExtractUrlOutputSchema,
|
|
5496
5586
|
annotations: liveWebToolAnnotations("Single URL Extract")
|
|
@@ -5544,6 +5634,13 @@ function registerPaaExtractorMcpTools(server2, executor, options = {}) {
|
|
|
5544
5634
|
outputSchema: FacebookAdSearchOutputSchema,
|
|
5545
5635
|
annotations: liveWebToolAnnotations("Facebook Ad Library Search")
|
|
5546
5636
|
}, async (input) => formatFacebookAdSearch(await executor.facebookAdSearch(input), input));
|
|
5637
|
+
server2.registerTool("reddit_thread", {
|
|
5638
|
+
title: "Reddit Thread + Comments",
|
|
5639
|
+
description: withReportNote("Capture a Reddit post and its comment tree from any reddit.com thread URL. Use this when the user wants Reddit comments, opinions, discussion, or audience voice from a specific thread (find threads first with search_serp, which surfaces reddit.com/comments links). Returns the post title, author, score, body, and a list of comments with author, score, nesting depth, and text. Handles Reddit's bot protection automatically (fetches via old.reddit through a residential proxy with retries); pass maxComments to cap the list."),
|
|
5640
|
+
inputSchema: RedditThreadInputSchema,
|
|
5641
|
+
outputSchema: RedditThreadOutputSchema,
|
|
5642
|
+
annotations: liveWebToolAnnotations("Reddit Thread + Comments")
|
|
5643
|
+
}, async (input) => formatRedditThread(await executor.redditThread(input), input));
|
|
5547
5644
|
server2.registerTool("facebook_ad_transcribe", {
|
|
5548
5645
|
title: "Facebook Ad Transcription",
|
|
5549
5646
|
description: "Transcribe audio from a Facebook ad video CDN URL. Use this when facebook_page_intel returned a direct videoUrl and the user asks what the ad says, what claims it makes, or wants ad-message extraction. Returns full transcript, timestamped chunks, word count, and resolvedInputs. Use only with the direct videoUrl value from facebook_page_intel results; do not pass public Facebook post/reel/share URLs. Use facebook_video_transcribe for organic Facebook URLs, and facebook_page_intel first when you only have a brand/page/ad library handle.",
|
|
@@ -5581,14 +5678,14 @@ function registerPaaExtractorMcpTools(server2, executor, options = {}) {
|
|
|
5581
5678
|
}, async (input) => formatMapsPlaceIntel(await executor.mapsPlaceIntel(input), input));
|
|
5582
5679
|
server2.registerTool("maps_search", {
|
|
5583
5680
|
title: "Google Maps Business Search",
|
|
5584
|
-
description: withReportNote('Search Google Maps for multiple businesses/profiles by category, niche, keyword, or local market. Use this when the user asks for several Google Business Profiles, GMBs, GBPs, leads, prospects, competitors, or "more than the 3-pack."
|
|
5681
|
+
description: withReportNote('Search Google Maps for multiple businesses/profiles by category, niche, keyword, or local market. Use this when the user asks for several Google Business Profiles, GMBs, GBPs, leads, prospects, competitors, or "more than the 3-pack." Routing is automatic and location-aware \u2014 leave proxyMode unset; pass proxyZip only to force a specific ZIP. Returns up to 50 candidates with names, place URLs, CIDs when available, ratings, review counts, profile metadata, and sanitized attempt telemetry. Default maxResults is 10; maximum is 50. Use maps_place_intel afterward only when a selected business needs full details and reviews.'),
|
|
5585
5682
|
inputSchema: MapsSearchInputSchema,
|
|
5586
5683
|
outputSchema: MapsSearchOutputSchema,
|
|
5587
5684
|
annotations: liveWebToolAnnotations("Google Maps Business Search")
|
|
5588
5685
|
}, async (input) => formatMapsSearch(await executor.mapsSearch(input), input));
|
|
5589
5686
|
server2.registerTool("directory_workflow", {
|
|
5590
5687
|
title: "Directory Workflow: Markets + Maps",
|
|
5591
|
-
description: withReportNote('Build directory/prospecting datasets by selecting US city markets from the free Census Population Estimates city/place dataset, optionally joining configured US ZIPS/Lead Magician ZIP groups, then running Google Maps business searches for each city in parallel. Use this when the user wants "all cities over 100k population in a state", "build a directory CSV", "find markets then get Maps data", or similar location-database + Maps workflows. Set minPopulation, state, query, maxResultsPerCity, and concurrency.
|
|
5688
|
+
description: withReportNote('Build directory/prospecting datasets by selecting US city markets from the free Census Population Estimates city/place dataset, optionally joining configured US ZIPS/Lead Magician ZIP groups, then running Google Maps business searches for each city in parallel. Use this when the user wants "all cities over 100k population in a state", "build a directory CSV", "find markets then get Maps data", or similar location-database + Maps workflows. Set minPopulation, state, query, maxResultsPerCity, and concurrency. Routing is automatic per city. Saved CSV rows include source_location, result_position, business_name, review_stars, review_count, category, address, phone, hours_status, website_url, directions_url, place_url, cid, cid_decimal, city population, and ZIP groups. Structured city results include sanitized attempt telemetry. Use maps_place_intel only when a selected profile needs deeper review topics, profile review count confirmation, or review cards. For local Lead Magician ZIP enrichment, set MCP_SCRAPER_USZIPS_CSV_PATH on the API server or pass usZipsCsvPath only in local/test mode.'),
|
|
5592
5689
|
inputSchema: DirectoryWorkflowInputSchema,
|
|
5593
5690
|
outputSchema: DirectoryWorkflowOutputSchema,
|
|
5594
5691
|
annotations: liveWebToolAnnotations("Directory Workflow: Markets + Maps")
|
|
@@ -5709,11 +5806,11 @@ function renderInstallTerminal(options) {
|
|
|
5709
5806
|
"1/1 install surfaces ready",
|
|
5710
5807
|
colorize("Newest: saved browser profiles that hold MANY site logins. Share the watch_url, user signs in, then verify with browser_profile_list. Browser sessions are direct/no-proxy by default.", "lime", color),
|
|
5711
5808
|
"",
|
|
5712
|
-
`${colorize("Tools", "cyan", color)} ${colorize("(
|
|
5809
|
+
`${colorize("Tools", "cyan", color)} ${colorize("(46 MCP tools)", "muted", color)}`,
|
|
5713
5810
|
toolRow("search", ["harvest_paa", "search_serp", "maps_search", "maps_place_intel"], color),
|
|
5714
5811
|
toolRow("extract", ["extract_url", "map_site_urls", "extract_site", "audit_site", "directory_workflow"], color),
|
|
5715
5812
|
toolRow("build", ["rank_tracker_workflow", "cron plan", "database prompt"], color),
|
|
5716
|
-
toolRow("media", ["youtube_harvest", "youtube_transcribe", "facebook_ad_search", "facebook_page_intel", "facebook_ad_transcribe", "facebook_video_transcribe", "instagram_profile_content", "instagram_media_download"], color),
|
|
5813
|
+
toolRow("media", ["youtube_harvest", "youtube_transcribe", "facebook_ad_search", "facebook_page_intel", "facebook_ad_transcribe", "facebook_video_transcribe", "instagram_profile_content", "instagram_media_download", "reddit_thread"], color),
|
|
5717
5814
|
toolRow("browser", ["browser_open", "browser_profile_connect", "browser_profile_list", "browser_close", "browser_screenshot", "browser_read", "browser_locate", "browser_replay_mark", "browser_replay_annotate"], color),
|
|
5718
5815
|
toolRow("account", ["credits_info", "reports", "MCP resources"], color),
|
|
5719
5816
|
`${colorize("Workflows", "cyan", color)} ${colorize("(MCP + CLI + API)", "muted", color)}`,
|