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
package/dist/bin/paa-harvest.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
harvest
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-G7PQ64LM.js";
|
|
5
5
|
import {
|
|
6
6
|
browserServiceApiKey
|
|
7
|
-
} from "../chunk-
|
|
8
|
-
import "../chunk-
|
|
7
|
+
} from "../chunk-3PRO376E.js";
|
|
8
|
+
import "../chunk-K443GQY5.js";
|
|
9
|
+
import "../chunk-BP27CZ5Q.js";
|
|
10
|
+
import "../chunk-TRNSXR46.js";
|
|
9
11
|
|
|
10
12
|
// src/cli.ts
|
|
11
13
|
import { Command } from "commander";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cli.ts","../../bin/paa-harvest.ts"],"sourcesContent":["import { Command } from 'commander'\nimport { browserServiceApiKey } from './lib/browser-service-env.js'\nimport { harvest } from './harvest.js'\n\nconst program = new Command()\n\nprogram\n .name('paa-harvest')\n .description('Recursively extract Google People Also Ask questions')\n .requiredOption('-q, --query <query>', 'Seed query')\n .option('-l, --location <location>', 'Location name (e.g. \"austin\" or \"Austin,Texas,United States\")')\n .option('--gl <gl>', 'Google country code', 'us')\n .option('--hl <hl>', 'Google language code', 'en')\n .option('-d, --depth <depth>', 'BFS depth (1-30)', '3')\n .option('-m, --max-questions <n>', 'Max questions to harvest', '100')\n .option('-o, --output <dir>', 'Output directory', './paa-output')\n .option('-f, --format <format>', 'Output format: json, csv, or both', 'both')\n .option('--headless', 'Run browser in headless mode', false)\n .option('--profile <dir>', 'Persistent browser profile directory')\n .option('--proxy <url>', 'Proxy server URL')\n .option('--kernel-api-key <key>', 'Browser service API key (or set BROWSER_SERVICE_API_KEY env var)')\n .action(async (opts) => {\n try {\n const result = await harvest({\n query: opts.query,\n location: opts.location,\n gl: opts.gl,\n hl: opts.hl,\n depth: parseInt(opts.depth, 10),\n maxQuestions: parseInt(opts.maxQuestions, 10),\n outputDir: opts.output,\n format: opts.format,\n headless: opts.headless,\n profileDir: opts.profile,\n proxy: opts.proxy,\n kernelApiKey: opts.kernelApiKey ?? browserServiceApiKey(),\n })\n console.log(JSON.stringify({ totalQuestions: result.totalQuestions, outputDir: result.stats.seed }))\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err))\n process.exit(1)\n }\n })\n\nexport async function runCli(): Promise<void> {\n await program.parseAsync()\n}\n","#!/usr/bin/env node\nimport { runCli } from '../src/cli.js'\nrunCli()\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/cli.ts","../../bin/paa-harvest.ts"],"sourcesContent":["import { Command } from 'commander'\nimport { browserServiceApiKey } from './lib/browser-service-env.js'\nimport { harvest } from './harvest.js'\n\nconst program = new Command()\n\nprogram\n .name('paa-harvest')\n .description('Recursively extract Google People Also Ask questions')\n .requiredOption('-q, --query <query>', 'Seed query')\n .option('-l, --location <location>', 'Location name (e.g. \"austin\" or \"Austin,Texas,United States\")')\n .option('--gl <gl>', 'Google country code', 'us')\n .option('--hl <hl>', 'Google language code', 'en')\n .option('-d, --depth <depth>', 'BFS depth (1-30)', '3')\n .option('-m, --max-questions <n>', 'Max questions to harvest', '100')\n .option('-o, --output <dir>', 'Output directory', './paa-output')\n .option('-f, --format <format>', 'Output format: json, csv, or both', 'both')\n .option('--headless', 'Run browser in headless mode', false)\n .option('--profile <dir>', 'Persistent browser profile directory')\n .option('--proxy <url>', 'Proxy server URL')\n .option('--kernel-api-key <key>', 'Browser service API key (or set BROWSER_SERVICE_API_KEY env var)')\n .action(async (opts) => {\n try {\n const result = await harvest({\n query: opts.query,\n location: opts.location,\n gl: opts.gl,\n hl: opts.hl,\n depth: parseInt(opts.depth, 10),\n maxQuestions: parseInt(opts.maxQuestions, 10),\n outputDir: opts.output,\n format: opts.format,\n headless: opts.headless,\n profileDir: opts.profile,\n proxy: opts.proxy,\n kernelApiKey: opts.kernelApiKey ?? browserServiceApiKey(),\n })\n console.log(JSON.stringify({ totalQuestions: result.totalQuestions, outputDir: result.stats.seed }))\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err))\n process.exit(1)\n }\n })\n\nexport async function runCli(): Promise<void> {\n await program.parseAsync()\n}\n","#!/usr/bin/env node\nimport { runCli } from '../src/cli.js'\nrunCli()\n"],"mappings":";;;;;;;;;;;;AAAA,SAAS,eAAe;AAIxB,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,aAAa,EAClB,YAAY,sDAAsD,EAClE,eAAe,uBAAuB,YAAY,EAClD,OAAO,6BAA6B,+DAA+D,EACnG,OAAO,aAAa,uBAAuB,IAAI,EAC/C,OAAO,aAAa,wBAAwB,IAAI,EAChD,OAAO,uBAAuB,oBAAoB,GAAG,EACrD,OAAO,2BAA2B,4BAA4B,KAAK,EACnE,OAAO,sBAAsB,oBAAoB,cAAc,EAC/D,OAAO,yBAAyB,qCAAqC,MAAM,EAC3E,OAAO,cAAc,gCAAgC,KAAK,EAC1D,OAAO,mBAAmB,sCAAsC,EAChE,OAAO,iBAAiB,kBAAkB,EAC1C,OAAO,0BAA0B,kEAAkE,EACnG,OAAO,OAAO,SAAS;AACtB,MAAI;AACF,UAAM,SAAS,MAAM,QAAQ;AAAA,MAC3B,OAAO,KAAK;AAAA,MACZ,UAAU,KAAK;AAAA,MACf,IAAI,KAAK;AAAA,MACT,IAAI,KAAK;AAAA,MACT,OAAO,SAAS,KAAK,OAAO,EAAE;AAAA,MAC9B,cAAc,SAAS,KAAK,cAAc,EAAE;AAAA,MAC5C,WAAW,KAAK;AAAA,MAChB,QAAQ,KAAK;AAAA,MACb,UAAU,KAAK;AAAA,MACf,YAAY,KAAK;AAAA,MACjB,OAAO,KAAK;AAAA,MACZ,cAAc,KAAK,gBAAgB,qBAAqB;AAAA,IAC1D,CAAC;AACD,YAAQ,IAAI,KAAK,UAAU,EAAE,gBAAgB,OAAO,gBAAgB,WAAW,OAAO,MAAM,KAAK,CAAC,CAAC;AAAA,EACrG,SAAS,KAAK;AACZ,YAAQ,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAC9D,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,eAAsB,SAAwB;AAC5C,QAAM,QAAQ,WAAW;AAC3B;;;AC5CA,OAAO;","names":[]}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PACKAGE_VERSION
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-MS2UZ6HR.js";
|
|
4
4
|
import {
|
|
5
5
|
browserServiceProfileName,
|
|
6
6
|
browserServiceProfileSaveChanges,
|
|
7
7
|
sanitizeVendorName
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-3PRO376E.js";
|
|
9
9
|
import {
|
|
10
10
|
DEFAULT_MAPS_PROXY_MODE,
|
|
11
11
|
DEFAULT_PROXY_MODE
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-BP27CZ5Q.js";
|
|
13
13
|
|
|
14
14
|
// src/harvest-timeout.ts
|
|
15
15
|
var VERCEL_FUNCTION_MAX_MS = 3e5;
|
|
@@ -17,10 +17,10 @@ var CLIENT_OVER_SERVER_MARGIN_MS = 15e3;
|
|
|
17
17
|
function harvestTimeoutBudget(maxQuestions, serpOnly = false) {
|
|
18
18
|
const requested = Number.isFinite(maxQuestions) && maxQuestions > 0 ? Math.trunc(maxQuestions) : 30;
|
|
19
19
|
let serverMs;
|
|
20
|
-
if (serpOnly
|
|
21
|
-
else if (requested <=
|
|
22
|
-
else if (requested <=
|
|
23
|
-
else serverMs =
|
|
20
|
+
if (serpOnly) serverMs = 11e4;
|
|
21
|
+
else if (requested <= 50) serverMs = 17e4;
|
|
22
|
+
else if (requested <= 100) serverMs = 23e4;
|
|
23
|
+
else serverMs = 285e3;
|
|
24
24
|
const clientMs = Math.min(serverMs + CLIENT_OVER_SERVER_MARGIN_MS, VERCEL_FUNCTION_MAX_MS - 5e3);
|
|
25
25
|
return { serverMs, clientMs };
|
|
26
26
|
}
|
|
@@ -92,33 +92,73 @@ function sanitizeHarvestResult(result) {
|
|
|
92
92
|
|
|
93
93
|
// src/mcp/server-instructions.ts
|
|
94
94
|
var SERVER_INSTRUCTIONS = `
|
|
95
|
-
|
|
96
|
-
this routing map, then load its schema before calling.
|
|
95
|
+
# MCP Scraper
|
|
97
96
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
- Google Maps: find places -> maps_search; one place deep-dive + reviews -> maps_place_intel.
|
|
102
|
-
- YouTube: find or list videos -> youtube_harvest; transcribe one video -> youtube_transcribe.
|
|
103
|
-
- Facebook: find ads -> facebook_ad_search; transcribe an ad -> facebook_ad_transcribe; transcribe a
|
|
104
|
-
video -> facebook_video_transcribe; page profile/intel -> facebook_page_intel.
|
|
105
|
-
- Instagram: profile inventory -> instagram_profile_content; one post or reel -> instagram_media_download.
|
|
106
|
-
- Workflows (multi-step): local directory build -> directory_workflow; rank blueprint -> rank_tracker_workflow;
|
|
107
|
-
AI-answer citation fan-out (AEO) -> query_fanout_workflow; deep research -> deep_research_workflow.
|
|
108
|
-
- Anything without a dedicated tool (e.g. Reddit, arbitrary logged-in sites) -> the browser_* agent
|
|
109
|
-
(browser_open, then navigate/read).
|
|
110
|
-
- Logged-in sites (ChatGPT, Claude, Reddit, any account): save the login first -> browser_profile_connect
|
|
111
|
-
(returns an mcpscraper.dev sign-in link for the user; one profile holds MANY logins, call it again with
|
|
112
|
-
the same profile + a new domain to add accounts) -> poll browser_profile_list until AUTHENTICATED ->
|
|
113
|
-
browser_open with that profile. browser_profile_list also shows what a profile is connected to.
|
|
97
|
+
Scrapes and analyzes web, search, maps, social, and site data. **Pick a tool by NAME from the map
|
|
98
|
+
below, then load its schema before calling.** Where a tool needs a value another tool produces, the
|
|
99
|
+
seam is noted so you can chain them.
|
|
114
100
|
|
|
115
|
-
|
|
116
|
-
-
|
|
117
|
-
|
|
118
|
-
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
101
|
+
## Search & research
|
|
102
|
+
- Organic Google results, rankings, local pack -> **search_serp**.
|
|
103
|
+
- Full SERP plus People-Also-Ask and AI-Overview detail -> **harvest_paa**.
|
|
104
|
+
- \`search_serp\` returns \`organicResults[].url\` (often including reddit.com threads) \u2014 feed those into
|
|
105
|
+
\`extract_url\` or \`reddit_thread\`.
|
|
106
|
+
|
|
107
|
+
## Pages & sites
|
|
108
|
+
- One page -> **extract_url** (takes a url).
|
|
109
|
+
- Whole site, crawl + SEO report -> **extract_site** (takes a url).
|
|
110
|
+
- Just the URL list/inventory -> **map_site_urls** (takes a url).
|
|
111
|
+
- \`map_site_urls\` returns urls you can feed straight into \`extract_url\`.
|
|
112
|
+
|
|
113
|
+
## Google Maps
|
|
114
|
+
- Find multiple places/competitors/prospects -> **maps_search** (returns \`results[]\` with name,
|
|
115
|
+
placeUrl, cid).
|
|
116
|
+
- One business deep-dive + reviews -> **maps_place_intel** (takes businessName + location, NOT an id \u2014
|
|
117
|
+
call it directly with a name from a \`maps_search\` result or from the user).
|
|
118
|
+
|
|
119
|
+
## YouTube
|
|
120
|
+
- Find or list videos -> **youtube_harvest** (returns \`videos[].videoId\`).
|
|
121
|
+
- Transcribe one video -> **youtube_transcribe** (takes a videoId from \`youtube_harvest\`, or a url).
|
|
122
|
+
|
|
123
|
+
## Facebook
|
|
124
|
+
- Find advertisers -> **facebook_ad_search** (returns \`advertisers[]\` with pageId, libraryId).
|
|
125
|
+
- One advertiser's ads -> **facebook_page_intel** (takes pageId, libraryId, or query; returns
|
|
126
|
+
\`ads[].videoUrl\`).
|
|
127
|
+
- Transcribe an ad video -> **facebook_ad_transcribe** (takes a videoUrl from \`facebook_page_intel\`).
|
|
128
|
+
- Transcribe an organic reel/video/post -> **facebook_video_transcribe** (takes the Facebook url directly).
|
|
129
|
+
|
|
130
|
+
## Instagram
|
|
131
|
+
- Profile inventory -> **instagram_profile_content** (returns post/reel/tv urls).
|
|
132
|
+
- One post or reel -> **instagram_media_download** (takes a url from \`instagram_profile_content\`, or a
|
|
133
|
+
url the user gives).
|
|
134
|
+
|
|
135
|
+
## Reddit
|
|
136
|
+
- A reddit.com thread/post URL -> **reddit_thread**. Returns the post plus its comment tree and handles
|
|
137
|
+
Reddit's bot wall itself (no login needed). Find threads first with \`search_serp\`.
|
|
138
|
+
|
|
139
|
+
## Other sites & logins (browser agent)
|
|
140
|
+
For an arbitrary site or a logged-in dashboard with no dedicated tool, use the browser_* agent. **First
|
|
141
|
+
decide whether the site needs a login:**
|
|
142
|
+
- **Needs a login** (ChatGPT, Claude, any account) -> save it first: **browser_profile_connect** returns
|
|
143
|
+
an mcpscraper.dev sign-in link for the user; one profile holds MANY logins (call it again with the same
|
|
144
|
+
profile + a new domain to add accounts). Poll **browser_profile_list** until AUTHENTICATED, then
|
|
145
|
+
**browser_open** with that profile.
|
|
146
|
+
- **No login** -> **browser_open**, then navigate/read.
|
|
147
|
+
|
|
148
|
+
## Workflows
|
|
149
|
+
Multi-step orchestrations \u2014 prefer these over hand-chaining primitives when the whole job is one of these:
|
|
150
|
+
- Build a local business directory for a niche across a state/region -> **directory_workflow**.
|
|
151
|
+
- Stand up a rank-tracking blueprint (database + cron + ingestion plan) -> **rank_tracker_workflow**.
|
|
152
|
+
- Find which sources an AI answer cites for a query (AEO) -> **query_fanout_workflow** (open a browser
|
|
153
|
+
session on chatgpt.com or claude.ai FIRST, then run it against that session).
|
|
154
|
+
|
|
155
|
+
## Notes
|
|
156
|
+
- Bulk / full-site crawls: call \`extract_site\` with \`rotateProxies:true\` for blocked or rate-limited
|
|
157
|
+
sites. It returns a saved folder/artifact plus a summary, not the full content inline.
|
|
158
|
+
- Browser sessions and media transcription cost credits and take longer \u2014 prefer the cheapest tool that
|
|
159
|
+
answers the question (plain search/extract before browser agents).
|
|
160
|
+
- Large results are saved to disk or an artifact and returned as a summary plus a path; read the path for
|
|
161
|
+
full detail rather than expecting the whole payload inline.
|
|
122
162
|
`.trim();
|
|
123
163
|
|
|
124
164
|
// src/mcp/paa-mcp-server.ts
|
|
@@ -144,6 +184,17 @@ var WORKFLOW_RECIPES = [
|
|
|
144
184
|
produces: ["city summary", "competitor CSV", "review insight CSV", "market difficulty/opportunity notes", "HTML report"],
|
|
145
185
|
runHint: "Use workflow_run with workflowId local-competitive-audit for state-wide market batches, or map-comparison for one city/location comparison."
|
|
146
186
|
},
|
|
187
|
+
{
|
|
188
|
+
id: "lead_generation",
|
|
189
|
+
title: "Lead Generation",
|
|
190
|
+
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).",
|
|
191
|
+
primaryWorkflowId: "get-leads",
|
|
192
|
+
recommendedTools: ["workflow_run", "maps_search", "maps_place_intel", "extract_url"],
|
|
193
|
+
requiredInputs: ["query", "location"],
|
|
194
|
+
optionalInputs: ["maxResults", "enrichWebsites", "hydrateReviewCounts", "concurrency", "proxyMode"],
|
|
195
|
+
produces: ["leads CSV (name, phone, website, email, socials, review count, booking URL)", "evidence JSON", "HTML report", "outreach task list"],
|
|
196
|
+
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.'
|
|
197
|
+
},
|
|
147
198
|
{
|
|
148
199
|
id: "icp_research",
|
|
149
200
|
title: "ICP Research",
|
|
@@ -1092,6 +1143,16 @@ ${bodyMd.slice(0, 3e3)}${bodyMd.length > 3e3 ? `
|
|
|
1092
1143
|
const bodySectionFull = bodyMd ? `
|
|
1093
1144
|
## Page Content
|
|
1094
1145
|
${bodyMd}` : "";
|
|
1146
|
+
const mem = d.memory ?? null;
|
|
1147
|
+
const memSection = mem?.deposited ? `
|
|
1148
|
+
## \u2705 Saved to memory
|
|
1149
|
+
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 ? `
|
|
1150
|
+
## \u{1F4C4} Saved as a temporary file
|
|
1151
|
+
Memory vault was unavailable${mem.error ? ` (${mem.error})` : ""}, so the full content was saved to a download link instead:
|
|
1152
|
+
- **Link:** ${mem.fileUrl}
|
|
1153
|
+
- **Expires:** ${mem.fileExpiresAt ?? "in 24 hours"} (auto-deleted)` : mem ? `
|
|
1154
|
+
## \u26A0\uFE0F Memory deposit skipped
|
|
1155
|
+
${mem.error ?? "unknown error"} \u2014 the page content is still in the truncated preview above.` : "";
|
|
1095
1156
|
const screenshotSection = screenshotMeta ? `
|
|
1096
1157
|
## Screenshot
|
|
1097
1158
|
- **File:** ${screenshotPath ?? "(returned inline only \u2014 disk write unavailable in this environment)"}
|
|
@@ -1121,10 +1182,10 @@ ${bodyMd}` : "";
|
|
|
1121
1182
|
- ${schemaCount} JSON-LD schema block(s) detected`;
|
|
1122
1183
|
const full = `# URL Extract: ${url}
|
|
1123
1184
|
**${title}**
|
|
1124
|
-
${headingSection}${kpoSection}${brandingSection}${bodySection}${screenshotSection}${mediaSection}${tips}`;
|
|
1185
|
+
${headingSection}${kpoSection}${brandingSection}${bodySection}${memSection}${screenshotSection}${mediaSection}${tips}`;
|
|
1125
1186
|
const diskReport = `# URL Extract: ${url}
|
|
1126
1187
|
**${title}**
|
|
1127
|
-
${headingSection}${kpoSection}${brandingSection}${bodySectionFull}${screenshotSection}${mediaSection}${tips}`;
|
|
1188
|
+
${headingSection}${kpoSection}${brandingSection}${bodySectionFull}${memSection}${screenshotSection}${mediaSection}${tips}`;
|
|
1128
1189
|
const textResult = oneBlock(full, diskReport);
|
|
1129
1190
|
const structuredContent = {
|
|
1130
1191
|
url,
|
|
@@ -1137,7 +1198,8 @@ ${headingSection}${kpoSection}${brandingSection}${bodySectionFull}${screenshotSe
|
|
|
1137
1198
|
missingSchemaFields: kpo?.missingFields ?? [],
|
|
1138
1199
|
screenshotSaved: screenshotPath ?? null,
|
|
1139
1200
|
branding: branding ?? null,
|
|
1140
|
-
mediaAssets: media?.assets ?? null
|
|
1201
|
+
mediaAssets: media?.assets ?? null,
|
|
1202
|
+
memory: mem ?? void 0
|
|
1141
1203
|
};
|
|
1142
1204
|
if (screenshotMeta?.base64) {
|
|
1143
1205
|
return {
|
|
@@ -1523,6 +1585,38 @@ ${adBlocks}`,
|
|
|
1523
1585
|
}
|
|
1524
1586
|
};
|
|
1525
1587
|
}
|
|
1588
|
+
function formatRedditThread(raw, input) {
|
|
1589
|
+
const parsed = parseData(raw);
|
|
1590
|
+
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
1591
|
+
const d = parsed.data;
|
|
1592
|
+
const comments = d.comments ?? [];
|
|
1593
|
+
const commentMd = comments.map((cm) => {
|
|
1594
|
+
const indent = " ".repeat(Math.min(Number(cm.depth ?? 0), 6));
|
|
1595
|
+
return `${indent}- **u/${cm.author || "[unknown]"}** (${cm.score || "\u2014"}): ${(cm.body || "").replace(/\s+/g, " ").trim()}`;
|
|
1596
|
+
}).join("\n");
|
|
1597
|
+
const full = [
|
|
1598
|
+
`# ${d.title || "Reddit thread"}`,
|
|
1599
|
+
`**u/${d.author || "[unknown]"}** \xB7 ${d.score || "\u2014"} \xB7 ${comments.length} comments captured`,
|
|
1600
|
+
d.postBody ? `
|
|
1601
|
+
${d.postBody}` : "",
|
|
1602
|
+
`
|
|
1603
|
+
## Comments
|
|
1604
|
+
${commentMd || "_No comments captured._"}`
|
|
1605
|
+
].join("\n");
|
|
1606
|
+
return {
|
|
1607
|
+
...oneBlock(full),
|
|
1608
|
+
structuredContent: {
|
|
1609
|
+
sourceUrl: d.sourceUrl ?? input.url ?? null,
|
|
1610
|
+
oldRedditUrl: d.oldRedditUrl ?? null,
|
|
1611
|
+
title: d.title ?? null,
|
|
1612
|
+
author: d.author ?? null,
|
|
1613
|
+
score: d.score ?? null,
|
|
1614
|
+
postBody: d.postBody ?? null,
|
|
1615
|
+
numComments: comments.length,
|
|
1616
|
+
comments: comments.map((cm) => ({ author: cm.author ?? null, score: cm.score ?? null, depth: Number(cm.depth ?? 0), body: cm.body ?? "" }))
|
|
1617
|
+
}
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1526
1620
|
function formatFacebookAdSearch(raw, input) {
|
|
1527
1621
|
const parsed = parseData(raw);
|
|
1528
1622
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
@@ -2551,7 +2645,7 @@ var HarvestPaaInputSchema = {
|
|
|
2551
2645
|
gl: 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."),
|
|
2552
2646
|
hl: 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."),
|
|
2553
2647
|
device: z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
|
|
2554
|
-
proxyMode: z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe(
|
|
2648
|
+
proxyMode: 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.'),
|
|
2555
2649
|
proxyZip: 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."),
|
|
2556
2650
|
debug: z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior.")
|
|
2557
2651
|
};
|
|
@@ -2562,7 +2656,9 @@ var ExtractUrlInputSchema = {
|
|
|
2562
2656
|
extractBranding: 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."),
|
|
2563
2657
|
downloadMedia: 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."),
|
|
2564
2658
|
mediaTypes: z.array(z.enum(["image", "video", "audio"])).default(["image", "video", "audio"]).describe("Which media types to download. Default all three."),
|
|
2565
|
-
allowLocal: z.boolean().default(false).describe("Allow localhost and private-network URLs. For local development only.")
|
|
2659
|
+
allowLocal: z.boolean().default(false).describe("Allow localhost and private-network URLs. For local development only."),
|
|
2660
|
+
depositToVault: 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."),
|
|
2661
|
+
vaultName: z.string().trim().min(1).max(120).optional().describe("Optional vault to deposit into. Defaults to the user's personal vault.")
|
|
2566
2662
|
};
|
|
2567
2663
|
var MapSiteUrlsInputSchema = {
|
|
2568
2664
|
url: 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."),
|
|
@@ -2603,6 +2699,10 @@ var FacebookAdSearchInputSchema = {
|
|
|
2603
2699
|
country: z.string().length(2).default("US").describe("Two-letter Ad Library country code. Default US. Examples: US, CA, GB, AU."),
|
|
2604
2700
|
maxResults: 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.")
|
|
2605
2701
|
};
|
|
2702
|
+
var RedditThreadInputSchema = {
|
|
2703
|
+
url: 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."),
|
|
2704
|
+
maxComments: z.number().int().min(1).max(2e3).optional().describe("Optional cap on comments returned. Omit to return all captured comments.")
|
|
2705
|
+
};
|
|
2606
2706
|
var FacebookAdTranscribeInputSchema = {
|
|
2607
2707
|
videoUrl: 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.")
|
|
2608
2708
|
};
|
|
@@ -2869,7 +2969,17 @@ var ExtractUrlOutputSchema = {
|
|
|
2869
2969
|
entityTypes: z.array(z.string()),
|
|
2870
2970
|
napScore: z.number().nullable(),
|
|
2871
2971
|
missingSchemaFields: z.array(z.string()),
|
|
2872
|
-
screenshotSaved: NullableString
|
|
2972
|
+
screenshotSaved: NullableString,
|
|
2973
|
+
memory: z.object({
|
|
2974
|
+
deposited: z.boolean(),
|
|
2975
|
+
vault: z.string().optional(),
|
|
2976
|
+
noteId: z.string().optional(),
|
|
2977
|
+
path: z.string().optional(),
|
|
2978
|
+
chunks: z.number().int().optional(),
|
|
2979
|
+
fileUrl: z.string().optional(),
|
|
2980
|
+
fileExpiresAt: z.string().optional(),
|
|
2981
|
+
error: z.string().optional()
|
|
2982
|
+
}).optional()
|
|
2873
2983
|
};
|
|
2874
2984
|
var ExtractSiteOutputSchema = {
|
|
2875
2985
|
url: z.string(),
|
|
@@ -3001,6 +3111,21 @@ var FacebookAdSearchOutputSchema = {
|
|
|
3001
3111
|
sampleLibraryId: NullableString
|
|
3002
3112
|
}))
|
|
3003
3113
|
};
|
|
3114
|
+
var RedditThreadOutputSchema = {
|
|
3115
|
+
sourceUrl: NullableString,
|
|
3116
|
+
oldRedditUrl: NullableString,
|
|
3117
|
+
title: NullableString,
|
|
3118
|
+
author: NullableString,
|
|
3119
|
+
score: NullableString,
|
|
3120
|
+
postBody: NullableString,
|
|
3121
|
+
numComments: z.number().int().min(0),
|
|
3122
|
+
comments: z.array(z.object({
|
|
3123
|
+
author: NullableString,
|
|
3124
|
+
score: NullableString,
|
|
3125
|
+
depth: z.number().int().min(0),
|
|
3126
|
+
body: z.string()
|
|
3127
|
+
}))
|
|
3128
|
+
};
|
|
3004
3129
|
var FacebookPageIntelOutputSchema = {
|
|
3005
3130
|
advertiserName: NullableString,
|
|
3006
3131
|
totalAds: z.number().int().min(0),
|
|
@@ -3194,6 +3319,7 @@ var CreditsInfoInputSchema = {
|
|
|
3194
3319
|
};
|
|
3195
3320
|
var WorkflowIdSchema = z.enum([
|
|
3196
3321
|
"directory",
|
|
3322
|
+
"get-leads",
|
|
3197
3323
|
"agent-packet",
|
|
3198
3324
|
"local-competitive-audit",
|
|
3199
3325
|
"map-comparison",
|
|
@@ -3215,8 +3341,8 @@ var WorkflowSuggestInputSchema = {
|
|
|
3215
3341
|
maxSuggestions: z.number().int().min(1).max(8).default(3).describe("Number of matching workflow recipes to return.")
|
|
3216
3342
|
};
|
|
3217
3343
|
var WorkflowRunInputSchema = {
|
|
3218
|
-
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."),
|
|
3219
|
-
input: z.record(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?}."),
|
|
3344
|
+
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."),
|
|
3345
|
+
input: z.record(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?}."),
|
|
3220
3346
|
webhookUrl: z.string().url().optional().describe("Optional HTTPS webhook to receive the completed hosted workflow run event.")
|
|
3221
3347
|
};
|
|
3222
3348
|
var WorkflowStepInputSchema = {
|
|
@@ -3292,7 +3418,7 @@ var SearchSerpInputSchema = {
|
|
|
3292
3418
|
gl: z.string().length(2).default("us").describe("Google country code inferred from location or user language."),
|
|
3293
3419
|
hl: z.string().default("en").describe("Google interface/content language inferred from user request."),
|
|
3294
3420
|
device: z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
|
|
3295
|
-
proxyMode: z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe(
|
|
3421
|
+
proxyMode: z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe('Leave unset \u2014 routing is automatic. Set "location" only to force local-pack targeting.'),
|
|
3296
3422
|
proxyZip: 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."),
|
|
3297
3423
|
debug: z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior."),
|
|
3298
3424
|
pages: z.number().int().min(1).max(2).default(1).describe("Number of result pages to fetch (1\u20132)")
|
|
@@ -3303,7 +3429,7 @@ var CaptureSerpSnapshotInputSchema = {
|
|
|
3303
3429
|
gl: z.string().length(2).default("us").describe("Google country code inferred from the requested market, e.g. us, gb, ca, au."),
|
|
3304
3430
|
hl: z.string().default("en").describe("Google interface/content language inferred from the user request."),
|
|
3305
3431
|
device: z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only when the user asks for mobile rankings or mobile SERP evidence."),
|
|
3306
|
-
proxyMode: z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe(
|
|
3432
|
+
proxyMode: z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe('Leave unset \u2014 routing is automatic. Set "location" only to force city/ZIP targeting.'),
|
|
3307
3433
|
proxyZip: 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."),
|
|
3308
3434
|
pages: 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."),
|
|
3309
3435
|
debug: z.boolean().default(false).describe("Include sanitized browser, proxy, and location diagnostics. Use true when debugging localization, CAPTCHA, proxy selection, or capture reliability."),
|
|
@@ -3715,21 +3841,21 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
3715
3841
|
if (savesReports) registerSavedReportResources(server);
|
|
3716
3842
|
server.registerTool("harvest_paa", {
|
|
3717
3843
|
title: "Google PAA + SERP Harvest",
|
|
3718
|
-
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").
|
|
3844
|
+
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.'),
|
|
3719
3845
|
inputSchema: HarvestPaaInputSchema,
|
|
3720
3846
|
outputSchema: HarvestPaaOutputSchema,
|
|
3721
3847
|
annotations: liveWebToolAnnotations("Google PAA + SERP Harvest")
|
|
3722
3848
|
}, async (input) => formatHarvestPaa(await executor.harvestPaa(input), input));
|
|
3723
3849
|
server.registerTool("search_serp", {
|
|
3724
3850
|
title: "Google SERP Lookup",
|
|
3725
|
-
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.
|
|
3851
|
+
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."),
|
|
3726
3852
|
inputSchema: SearchSerpInputSchema,
|
|
3727
3853
|
outputSchema: SearchSerpOutputSchema,
|
|
3728
3854
|
annotations: liveWebToolAnnotations("Google SERP Lookup")
|
|
3729
3855
|
}, async (input) => formatSearchSerp(await executor.searchSerp(input), input));
|
|
3730
3856
|
server.registerTool("extract_url", {
|
|
3731
3857
|
title: "Single URL Extract",
|
|
3732
|
-
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."),
|
|
3858
|
+
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."),
|
|
3733
3859
|
inputSchema: ExtractUrlInputSchema,
|
|
3734
3860
|
outputSchema: ExtractUrlOutputSchema,
|
|
3735
3861
|
annotations: liveWebToolAnnotations("Single URL Extract")
|
|
@@ -3783,6 +3909,13 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
3783
3909
|
outputSchema: FacebookAdSearchOutputSchema,
|
|
3784
3910
|
annotations: liveWebToolAnnotations("Facebook Ad Library Search")
|
|
3785
3911
|
}, async (input) => formatFacebookAdSearch(await executor.facebookAdSearch(input), input));
|
|
3912
|
+
server.registerTool("reddit_thread", {
|
|
3913
|
+
title: "Reddit Thread + Comments",
|
|
3914
|
+
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."),
|
|
3915
|
+
inputSchema: RedditThreadInputSchema,
|
|
3916
|
+
outputSchema: RedditThreadOutputSchema,
|
|
3917
|
+
annotations: liveWebToolAnnotations("Reddit Thread + Comments")
|
|
3918
|
+
}, async (input) => formatRedditThread(await executor.redditThread(input), input));
|
|
3786
3919
|
server.registerTool("facebook_ad_transcribe", {
|
|
3787
3920
|
title: "Facebook Ad Transcription",
|
|
3788
3921
|
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.",
|
|
@@ -3820,14 +3953,14 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
3820
3953
|
}, async (input) => formatMapsPlaceIntel(await executor.mapsPlaceIntel(input), input));
|
|
3821
3954
|
server.registerTool("maps_search", {
|
|
3822
3955
|
title: "Google Maps Business Search",
|
|
3823
|
-
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."
|
|
3956
|
+
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.'),
|
|
3824
3957
|
inputSchema: MapsSearchInputSchema,
|
|
3825
3958
|
outputSchema: MapsSearchOutputSchema,
|
|
3826
3959
|
annotations: liveWebToolAnnotations("Google Maps Business Search")
|
|
3827
3960
|
}, async (input) => formatMapsSearch(await executor.mapsSearch(input), input));
|
|
3828
3961
|
server.registerTool("directory_workflow", {
|
|
3829
3962
|
title: "Directory Workflow: Markets + Maps",
|
|
3830
|
-
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.
|
|
3963
|
+
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.'),
|
|
3831
3964
|
inputSchema: DirectoryWorkflowInputSchema,
|
|
3832
3965
|
outputSchema: DirectoryWorkflowOutputSchema,
|
|
3833
3966
|
annotations: liveWebToolAnnotations("Directory Workflow: Markets + Maps")
|
|
@@ -4100,6 +4233,9 @@ var HttpMcpToolExecutor = class {
|
|
|
4100
4233
|
facebookAdSearch(input) {
|
|
4101
4234
|
return this.call("/facebook/search", input);
|
|
4102
4235
|
}
|
|
4236
|
+
redditThread(input) {
|
|
4237
|
+
return this.call("/reddit/thread", input, this.httpTimeoutOverrideMs ?? 24e4);
|
|
4238
|
+
}
|
|
4103
4239
|
facebookAdTranscribe(input) {
|
|
4104
4240
|
return this.call("/facebook/transcribe", input);
|
|
4105
4241
|
}
|
|
@@ -4315,4 +4451,4 @@ export {
|
|
|
4315
4451
|
HttpMcpToolExecutor,
|
|
4316
4452
|
exportFanout
|
|
4317
4453
|
};
|
|
4318
|
-
//# sourceMappingURL=chunk-
|
|
4454
|
+
//# sourceMappingURL=chunk-2GHI2D6Q.js.map
|