mcp-scraper 0.3.24 → 0.3.26
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 +8220 -5991
- 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 +186 -49
- 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-GZ4W72WF.js → chunk-2GHI2D6Q.js} +184 -48
- package/dist/chunk-2GHI2D6Q.js.map +1 -0
- 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-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-MS2UZ6HR.js +7 -0
- package/dist/chunk-MS2UZ6HR.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-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-HIGHKTEV.js} +1563 -275
- package/dist/server-HIGHKTEV.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/spec-server-instructions.md +94 -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.26";
|
|
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 };
|
|
@@ -4179,33 +4236,73 @@ ${limitations.map((l) => `- ${l}`).join("\n")}` : "",
|
|
|
4179
4236
|
|
|
4180
4237
|
// src/mcp/server-instructions.ts
|
|
4181
4238
|
var SERVER_INSTRUCTIONS = `
|
|
4182
|
-
|
|
4183
|
-
|
|
4239
|
+
# MCP Scraper
|
|
4240
|
+
|
|
4241
|
+
Scrapes and analyzes web, search, maps, social, and site data. **Pick a tool by NAME from the map
|
|
4242
|
+
below, then load its schema before calling.** Where a tool needs a value another tool produces, the
|
|
4243
|
+
seam is noted so you can chain them.
|
|
4244
|
+
|
|
4245
|
+
## Search & research
|
|
4246
|
+
- Organic Google results, rankings, local pack -> **search_serp**.
|
|
4247
|
+
- Full SERP plus People-Also-Ask and AI-Overview detail -> **harvest_paa**.
|
|
4248
|
+
- \`search_serp\` returns \`organicResults[].url\` (often including reddit.com threads) \u2014 feed those into
|
|
4249
|
+
\`extract_url\` or \`reddit_thread\`.
|
|
4250
|
+
|
|
4251
|
+
## Pages & sites
|
|
4252
|
+
- One page -> **extract_url** (takes a url).
|
|
4253
|
+
- Whole site, crawl + SEO report -> **extract_site** (takes a url).
|
|
4254
|
+
- Just the URL list/inventory -> **map_site_urls** (takes a url).
|
|
4255
|
+
- \`map_site_urls\` returns urls you can feed straight into \`extract_url\`.
|
|
4256
|
+
|
|
4257
|
+
## Google Maps
|
|
4258
|
+
- Find multiple places/competitors/prospects -> **maps_search** (returns \`results[]\` with name,
|
|
4259
|
+
placeUrl, cid).
|
|
4260
|
+
- One business deep-dive + reviews -> **maps_place_intel** (takes businessName + location, NOT an id \u2014
|
|
4261
|
+
call it directly with a name from a \`maps_search\` result or from the user).
|
|
4262
|
+
|
|
4263
|
+
## YouTube
|
|
4264
|
+
- Find or list videos -> **youtube_harvest** (returns \`videos[].videoId\`).
|
|
4265
|
+
- Transcribe one video -> **youtube_transcribe** (takes a videoId from \`youtube_harvest\`, or a url).
|
|
4266
|
+
|
|
4267
|
+
## Facebook
|
|
4268
|
+
- Find advertisers -> **facebook_ad_search** (returns \`advertisers[]\` with pageId, libraryId).
|
|
4269
|
+
- One advertiser's ads -> **facebook_page_intel** (takes pageId, libraryId, or query; returns
|
|
4270
|
+
\`ads[].videoUrl\`).
|
|
4271
|
+
- Transcribe an ad video -> **facebook_ad_transcribe** (takes a videoUrl from \`facebook_page_intel\`).
|
|
4272
|
+
- Transcribe an organic reel/video/post -> **facebook_video_transcribe** (takes the Facebook url directly).
|
|
4273
|
+
|
|
4274
|
+
## Instagram
|
|
4275
|
+
- Profile inventory -> **instagram_profile_content** (returns post/reel/tv urls).
|
|
4276
|
+
- One post or reel -> **instagram_media_download** (takes a url from \`instagram_profile_content\`, or a
|
|
4277
|
+
url the user gives).
|
|
4184
4278
|
|
|
4185
|
-
|
|
4186
|
-
-
|
|
4187
|
-
|
|
4188
|
-
- Google Maps: find places -> maps_search; one place deep-dive + reviews -> maps_place_intel.
|
|
4189
|
-
- YouTube: find or list videos -> youtube_harvest; transcribe one video -> youtube_transcribe.
|
|
4190
|
-
- Facebook: find ads -> facebook_ad_search; transcribe an ad -> facebook_ad_transcribe; transcribe a
|
|
4191
|
-
video -> facebook_video_transcribe; page profile/intel -> facebook_page_intel.
|
|
4192
|
-
- Instagram: profile inventory -> instagram_profile_content; one post or reel -> instagram_media_download.
|
|
4193
|
-
- Workflows (multi-step): local directory build -> directory_workflow; rank blueprint -> rank_tracker_workflow;
|
|
4194
|
-
AI-answer citation fan-out (AEO) -> query_fanout_workflow; deep research -> deep_research_workflow.
|
|
4195
|
-
- Anything without a dedicated tool (e.g. Reddit, arbitrary logged-in sites) -> the browser_* agent
|
|
4196
|
-
(browser_open, then navigate/read).
|
|
4197
|
-
- Logged-in sites (ChatGPT, Claude, Reddit, any account): save the login first -> browser_profile_connect
|
|
4198
|
-
(returns an mcpscraper.dev sign-in link for the user; one profile holds MANY logins, call it again with
|
|
4199
|
-
the same profile + a new domain to add accounts) -> poll browser_profile_list until AUTHENTICATED ->
|
|
4200
|
-
browser_open with that profile. browser_profile_list also shows what a profile is connected to.
|
|
4279
|
+
## Reddit
|
|
4280
|
+
- A reddit.com thread/post URL -> **reddit_thread**. Returns the post plus its comment tree and handles
|
|
4281
|
+
Reddit's bot wall itself (no login needed). Find threads first with \`search_serp\`.
|
|
4201
4282
|
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4283
|
+
## Other sites & logins (browser agent)
|
|
4284
|
+
For an arbitrary site or a logged-in dashboard with no dedicated tool, use the browser_* agent. **First
|
|
4285
|
+
decide whether the site needs a login:**
|
|
4286
|
+
- **Needs a login** (ChatGPT, Claude, any account) -> save it first: **browser_profile_connect** returns
|
|
4287
|
+
an mcpscraper.dev sign-in link for the user; one profile holds MANY logins (call it again with the same
|
|
4288
|
+
profile + a new domain to add accounts). Poll **browser_profile_list** until AUTHENTICATED, then
|
|
4289
|
+
**browser_open** with that profile.
|
|
4290
|
+
- **No login** -> **browser_open**, then navigate/read.
|
|
4291
|
+
|
|
4292
|
+
## Workflows
|
|
4293
|
+
Multi-step orchestrations \u2014 prefer these over hand-chaining primitives when the whole job is one of these:
|
|
4294
|
+
- Build a local business directory for a niche across a state/region -> **directory_workflow**.
|
|
4295
|
+
- Stand up a rank-tracking blueprint (database + cron + ingestion plan) -> **rank_tracker_workflow**.
|
|
4296
|
+
- Find which sources an AI answer cites for a query (AEO) -> **query_fanout_workflow** (open a browser
|
|
4297
|
+
session on chatgpt.com or claude.ai FIRST, then run it against that session).
|
|
4298
|
+
|
|
4299
|
+
## Notes
|
|
4300
|
+
- Bulk / full-site crawls: call \`extract_site\` with \`rotateProxies:true\` for blocked or rate-limited
|
|
4301
|
+
sites. It returns a saved folder/artifact plus a summary, not the full content inline.
|
|
4302
|
+
- Browser sessions and media transcription cost credits and take longer \u2014 prefer the cheapest tool that
|
|
4303
|
+
answers the question (plain search/extract before browser agents).
|
|
4304
|
+
- Large results are saved to disk or an artifact and returned as a summary plus a path; read the path for
|
|
4305
|
+
full detail rather than expecting the whole payload inline.
|
|
4209
4306
|
`.trim();
|
|
4210
4307
|
|
|
4211
4308
|
// src/mcp/mcp-tool-schemas.ts
|
|
@@ -4213,7 +4310,7 @@ var import_zod3 = require("zod");
|
|
|
4213
4310
|
|
|
4214
4311
|
// src/schemas.ts
|
|
4215
4312
|
var import_zod2 = require("zod");
|
|
4216
|
-
var DEFAULT_PROXY_MODE = "
|
|
4313
|
+
var DEFAULT_PROXY_MODE = "none";
|
|
4217
4314
|
var DEFAULT_MAPS_PROXY_MODE = "location";
|
|
4218
4315
|
var HarvestOptionsSchema = import_zod2.z.object({
|
|
4219
4316
|
query: import_zod2.z.string().min(1),
|
|
@@ -4235,7 +4332,8 @@ var HarvestOptionsSchema = import_zod2.z.object({
|
|
|
4235
4332
|
outputDir: import_zod2.z.string().default("./paa-output"),
|
|
4236
4333
|
format: import_zod2.z.enum(["json", "csv", "both"]).default("both"),
|
|
4237
4334
|
serpOnly: import_zod2.z.boolean().default(false),
|
|
4238
|
-
pages: import_zod2.z.number().int().min(1).max(2).default(1)
|
|
4335
|
+
pages: import_zod2.z.number().int().min(1).max(2).default(1),
|
|
4336
|
+
softDeadlineMs: import_zod2.z.number().optional()
|
|
4239
4337
|
});
|
|
4240
4338
|
var MapsPlaceOptionsSchema = import_zod2.z.object({
|
|
4241
4339
|
businessName: import_zod2.z.string().min(1),
|
|
@@ -4317,7 +4415,7 @@ var HarvestPaaInputSchema = {
|
|
|
4317
4415
|
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
4416
|
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
4417
|
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(
|
|
4418
|
+
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
4419
|
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
4420
|
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
4421
|
};
|
|
@@ -4328,7 +4426,9 @@ var ExtractUrlInputSchema = {
|
|
|
4328
4426
|
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
4427
|
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
4428
|
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.")
|
|
4429
|
+
allowLocal: import_zod3.z.boolean().default(false).describe("Allow localhost and private-network URLs. For local development only."),
|
|
4430
|
+
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."),
|
|
4431
|
+
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
4432
|
};
|
|
4333
4433
|
var MapSiteUrlsInputSchema = {
|
|
4334
4434
|
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 +4469,10 @@ var FacebookAdSearchInputSchema = {
|
|
|
4369
4469
|
country: import_zod3.z.string().length(2).default("US").describe("Two-letter Ad Library country code. Default US. Examples: US, CA, GB, AU."),
|
|
4370
4470
|
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
4471
|
};
|
|
4472
|
+
var RedditThreadInputSchema = {
|
|
4473
|
+
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."),
|
|
4474
|
+
maxComments: import_zod3.z.number().int().min(1).max(2e3).optional().describe("Optional cap on comments returned. Omit to return all captured comments.")
|
|
4475
|
+
};
|
|
4372
4476
|
var FacebookAdTranscribeInputSchema = {
|
|
4373
4477
|
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
4478
|
};
|
|
@@ -4635,7 +4739,17 @@ var ExtractUrlOutputSchema = {
|
|
|
4635
4739
|
entityTypes: import_zod3.z.array(import_zod3.z.string()),
|
|
4636
4740
|
napScore: import_zod3.z.number().nullable(),
|
|
4637
4741
|
missingSchemaFields: import_zod3.z.array(import_zod3.z.string()),
|
|
4638
|
-
screenshotSaved: NullableString2
|
|
4742
|
+
screenshotSaved: NullableString2,
|
|
4743
|
+
memory: import_zod3.z.object({
|
|
4744
|
+
deposited: import_zod3.z.boolean(),
|
|
4745
|
+
vault: import_zod3.z.string().optional(),
|
|
4746
|
+
noteId: import_zod3.z.string().optional(),
|
|
4747
|
+
path: import_zod3.z.string().optional(),
|
|
4748
|
+
chunks: import_zod3.z.number().int().optional(),
|
|
4749
|
+
fileUrl: import_zod3.z.string().optional(),
|
|
4750
|
+
fileExpiresAt: import_zod3.z.string().optional(),
|
|
4751
|
+
error: import_zod3.z.string().optional()
|
|
4752
|
+
}).optional()
|
|
4639
4753
|
};
|
|
4640
4754
|
var ExtractSiteOutputSchema = {
|
|
4641
4755
|
url: import_zod3.z.string(),
|
|
@@ -4767,6 +4881,21 @@ var FacebookAdSearchOutputSchema = {
|
|
|
4767
4881
|
sampleLibraryId: NullableString2
|
|
4768
4882
|
}))
|
|
4769
4883
|
};
|
|
4884
|
+
var RedditThreadOutputSchema = {
|
|
4885
|
+
sourceUrl: NullableString2,
|
|
4886
|
+
oldRedditUrl: NullableString2,
|
|
4887
|
+
title: NullableString2,
|
|
4888
|
+
author: NullableString2,
|
|
4889
|
+
score: NullableString2,
|
|
4890
|
+
postBody: NullableString2,
|
|
4891
|
+
numComments: import_zod3.z.number().int().min(0),
|
|
4892
|
+
comments: import_zod3.z.array(import_zod3.z.object({
|
|
4893
|
+
author: NullableString2,
|
|
4894
|
+
score: NullableString2,
|
|
4895
|
+
depth: import_zod3.z.number().int().min(0),
|
|
4896
|
+
body: import_zod3.z.string()
|
|
4897
|
+
}))
|
|
4898
|
+
};
|
|
4770
4899
|
var FacebookPageIntelOutputSchema = {
|
|
4771
4900
|
advertiserName: NullableString2,
|
|
4772
4901
|
totalAds: import_zod3.z.number().int().min(0),
|
|
@@ -4960,6 +5089,7 @@ var CreditsInfoInputSchema = {
|
|
|
4960
5089
|
};
|
|
4961
5090
|
var WorkflowIdSchema = import_zod3.z.enum([
|
|
4962
5091
|
"directory",
|
|
5092
|
+
"get-leads",
|
|
4963
5093
|
"agent-packet",
|
|
4964
5094
|
"local-competitive-audit",
|
|
4965
5095
|
"map-comparison",
|
|
@@ -4981,8 +5111,8 @@ var WorkflowSuggestInputSchema = {
|
|
|
4981
5111
|
maxSuggestions: import_zod3.z.number().int().min(1).max(8).default(3).describe("Number of matching workflow recipes to return.")
|
|
4982
5112
|
};
|
|
4983
5113
|
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?}."),
|
|
5114
|
+
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."),
|
|
5115
|
+
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
5116
|
webhookUrl: import_zod3.z.string().url().optional().describe("Optional HTTPS webhook to receive the completed hosted workflow run event.")
|
|
4987
5117
|
};
|
|
4988
5118
|
var WorkflowStepInputSchema = {
|
|
@@ -5058,7 +5188,7 @@ var SearchSerpInputSchema = {
|
|
|
5058
5188
|
gl: import_zod3.z.string().length(2).default("us").describe("Google country code inferred from location or user language."),
|
|
5059
5189
|
hl: import_zod3.z.string().default("en").describe("Google interface/content language inferred from user request."),
|
|
5060
5190
|
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(
|
|
5191
|
+
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
5192
|
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
5193
|
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
5194
|
pages: import_zod3.z.number().int().min(1).max(2).default(1).describe("Number of result pages to fetch (1\u20132)")
|
|
@@ -5069,7 +5199,7 @@ var CaptureSerpSnapshotInputSchema = {
|
|
|
5069
5199
|
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
5200
|
hl: import_zod3.z.string().default("en").describe("Google interface/content language inferred from the user request."),
|
|
5071
5201
|
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(
|
|
5202
|
+
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
5203
|
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
5204
|
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
5205
|
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 +5606,21 @@ function registerPaaExtractorMcpTools(server2, executor, options = {}) {
|
|
|
5476
5606
|
if (savesReports) registerSavedReportResources(server2);
|
|
5477
5607
|
server2.registerTool("harvest_paa", {
|
|
5478
5608
|
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").
|
|
5609
|
+
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
5610
|
inputSchema: HarvestPaaInputSchema,
|
|
5481
5611
|
outputSchema: HarvestPaaOutputSchema,
|
|
5482
5612
|
annotations: liveWebToolAnnotations("Google PAA + SERP Harvest")
|
|
5483
5613
|
}, async (input) => formatHarvestPaa(await executor.harvestPaa(input), input));
|
|
5484
5614
|
server2.registerTool("search_serp", {
|
|
5485
5615
|
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.
|
|
5616
|
+
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
5617
|
inputSchema: SearchSerpInputSchema,
|
|
5488
5618
|
outputSchema: SearchSerpOutputSchema,
|
|
5489
5619
|
annotations: liveWebToolAnnotations("Google SERP Lookup")
|
|
5490
5620
|
}, async (input) => formatSearchSerp(await executor.searchSerp(input), input));
|
|
5491
5621
|
server2.registerTool("extract_url", {
|
|
5492
5622
|
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."),
|
|
5623
|
+
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
5624
|
inputSchema: ExtractUrlInputSchema,
|
|
5495
5625
|
outputSchema: ExtractUrlOutputSchema,
|
|
5496
5626
|
annotations: liveWebToolAnnotations("Single URL Extract")
|
|
@@ -5544,6 +5674,13 @@ function registerPaaExtractorMcpTools(server2, executor, options = {}) {
|
|
|
5544
5674
|
outputSchema: FacebookAdSearchOutputSchema,
|
|
5545
5675
|
annotations: liveWebToolAnnotations("Facebook Ad Library Search")
|
|
5546
5676
|
}, async (input) => formatFacebookAdSearch(await executor.facebookAdSearch(input), input));
|
|
5677
|
+
server2.registerTool("reddit_thread", {
|
|
5678
|
+
title: "Reddit Thread + Comments",
|
|
5679
|
+
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."),
|
|
5680
|
+
inputSchema: RedditThreadInputSchema,
|
|
5681
|
+
outputSchema: RedditThreadOutputSchema,
|
|
5682
|
+
annotations: liveWebToolAnnotations("Reddit Thread + Comments")
|
|
5683
|
+
}, async (input) => formatRedditThread(await executor.redditThread(input), input));
|
|
5547
5684
|
server2.registerTool("facebook_ad_transcribe", {
|
|
5548
5685
|
title: "Facebook Ad Transcription",
|
|
5549
5686
|
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 +5718,14 @@ function registerPaaExtractorMcpTools(server2, executor, options = {}) {
|
|
|
5581
5718
|
}, async (input) => formatMapsPlaceIntel(await executor.mapsPlaceIntel(input), input));
|
|
5582
5719
|
server2.registerTool("maps_search", {
|
|
5583
5720
|
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."
|
|
5721
|
+
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
5722
|
inputSchema: MapsSearchInputSchema,
|
|
5586
5723
|
outputSchema: MapsSearchOutputSchema,
|
|
5587
5724
|
annotations: liveWebToolAnnotations("Google Maps Business Search")
|
|
5588
5725
|
}, async (input) => formatMapsSearch(await executor.mapsSearch(input), input));
|
|
5589
5726
|
server2.registerTool("directory_workflow", {
|
|
5590
5727
|
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.
|
|
5728
|
+
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
5729
|
inputSchema: DirectoryWorkflowInputSchema,
|
|
5593
5730
|
outputSchema: DirectoryWorkflowOutputSchema,
|
|
5594
5731
|
annotations: liveWebToolAnnotations("Directory Workflow: Markets + Maps")
|
|
@@ -5709,11 +5846,11 @@ function renderInstallTerminal(options) {
|
|
|
5709
5846
|
"1/1 install surfaces ready",
|
|
5710
5847
|
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
5848
|
"",
|
|
5712
|
-
`${colorize("Tools", "cyan", color)} ${colorize("(
|
|
5849
|
+
`${colorize("Tools", "cyan", color)} ${colorize("(46 MCP tools)", "muted", color)}`,
|
|
5713
5850
|
toolRow("search", ["harvest_paa", "search_serp", "maps_search", "maps_place_intel"], color),
|
|
5714
5851
|
toolRow("extract", ["extract_url", "map_site_urls", "extract_site", "audit_site", "directory_workflow"], color),
|
|
5715
5852
|
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),
|
|
5853
|
+
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
5854
|
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
5855
|
toolRow("account", ["credits_info", "reports", "MCP resources"], color),
|
|
5719
5856
|
`${colorize("Workflows", "cyan", color)} ${colorize("(MCP + CLI + API)", "muted", color)}`,
|