mcp-scraper 0.4.1 → 0.4.3
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 +2976 -2023
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +2 -2
- package/dist/bin/mcp-scraper-cli.cjs +2 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +3 -3
- package/dist/bin/mcp-scraper-install.cjs +1 -1
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +1 -1
- package/dist/bin/mcp-stdio-server.cjs +158 -22
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +3 -3
- package/dist/bin/paa-harvest.cjs +1 -0
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +2 -2
- package/dist/{chunk-6TDUY7CA.js → chunk-4MFYMUIF.js} +25 -3
- package/dist/chunk-4MFYMUIF.js.map +1 -0
- package/dist/{chunk-QUOZ3LZG.js → chunk-BAMVP57P.js} +157 -22
- package/dist/chunk-BAMVP57P.js.map +1 -0
- package/dist/{chunk-NNEIXK5L.js → chunk-FUWZWKGO.js} +2 -2
- package/dist/{chunk-HBC4C75S.js → chunk-NJK5BTUK.js} +2 -2
- package/dist/{chunk-JOEZJL4I.js → chunk-NVUBFCJK.js} +2 -2
- package/dist/chunk-SRMGSCC3.js +7 -0
- package/dist/chunk-SRMGSCC3.js.map +1 -0
- package/dist/{chunk-BP27CZ5Q.js → chunk-XGIPATLV.js} +2 -1
- package/dist/chunk-XGIPATLV.js.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/{server-U7V6DUSY.js → server-LBEYRKM2.js} +1134 -400
- package/dist/server-LBEYRKM2.js.map +1 -0
- package/dist/{site-extract-repository-2MQBAOX5.js → site-extract-repository-4NVO5KQA.js} +3 -3
- package/dist/{worker-RVZXIALX.js → worker-7XRZNLKB.js} +4 -4
- package/package.json +2 -1
- package/dist/chunk-2ULBZRQQ.js +0 -7
- package/dist/chunk-2ULBZRQQ.js.map +0 -1
- package/dist/chunk-6TDUY7CA.js.map +0 -1
- package/dist/chunk-BP27CZ5Q.js.map +0 -1
- package/dist/chunk-QUOZ3LZG.js.map +0 -1
- package/dist/server-U7V6DUSY.js.map +0 -1
- /package/dist/{chunk-NNEIXK5L.js.map → chunk-FUWZWKGO.js.map} +0 -0
- /package/dist/{chunk-HBC4C75S.js.map → chunk-NJK5BTUK.js.map} +0 -0
- /package/dist/{chunk-JOEZJL4I.js.map → chunk-NVUBFCJK.js.map} +0 -0
- /package/dist/{site-extract-repository-2MQBAOX5.js.map → site-extract-repository-4NVO5KQA.js.map} +0 -0
- /package/dist/{worker-RVZXIALX.js.map → worker-7XRZNLKB.js.map} +0 -0
|
@@ -6,12 +6,12 @@ import {
|
|
|
6
6
|
openWorkflowReport,
|
|
7
7
|
runWorkflow,
|
|
8
8
|
workflowOutputBaseDir
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-FUWZWKGO.js";
|
|
10
10
|
import "../chunk-K443GQY5.js";
|
|
11
|
-
import "../chunk-
|
|
11
|
+
import "../chunk-XGIPATLV.js";
|
|
12
12
|
import {
|
|
13
13
|
PACKAGE_VERSION
|
|
14
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-SRMGSCC3.js";
|
|
15
15
|
|
|
16
16
|
// src/cli/human-cli.ts
|
|
17
17
|
import { Command } from "commander";
|
|
@@ -102,7 +102,7 @@ function renderInstallTerminal(options) {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
// src/version.ts
|
|
105
|
-
var PACKAGE_VERSION = "0.4.
|
|
105
|
+
var PACKAGE_VERSION = "0.4.3";
|
|
106
106
|
|
|
107
107
|
// bin/mcp-scraper-install.ts
|
|
108
108
|
var noColor = process.argv.includes("--no-color") || process.env.NO_COLOR !== void 0 || process.env.FORCE_COLOR === "0" || !process.stdout.isTTY;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/install-terminal.ts","../../src/version.ts","../../bin/mcp-scraper-install.ts"],"sourcesContent":["export interface InstallTerminalOptions {\n color?: boolean\n version: string\n apiKeyConfigured?: boolean\n}\n\ntype Tone = 'cyan' | 'lime' | 'amber' | 'red' | 'muted' | 'bold'\n\nconst CODES: Record<Tone | 'reset', string> = {\n reset: '\\x1b[0m',\n cyan: '\\x1b[36m',\n lime: '\\x1b[32m',\n amber: '\\x1b[33m',\n red: '\\x1b[31m',\n muted: '\\x1b[90m',\n bold: '\\x1b[1m',\n}\n\nfunction colorize(value: string, tone: Tone, enabled: boolean): string {\n if (!enabled) return value\n return `${CODES[tone]}${value}${CODES.reset}`\n}\n\nfunction toolRow(label: string, tools: string[], enabled: boolean): string {\n const padded = label.padEnd(9, ' ')\n return ` ${colorize(padded, 'muted', enabled)} ${tools.join(colorize(' . ', 'muted', enabled))}`\n}\n\nexport function renderInstallTerminal(options: InstallTerminalOptions): string {\n const color = options.color ?? true\n const apiKeyValue = options.apiKeyConfigured ? '$MCP_SCRAPER_API_KEY' : 'sk_live_your_key'\n const ascii = String.raw`\n __ __ ____ ____ \n| \\/ |/ ___| _ \\\n| |\\/| | | | |_) |\n| | | | |___| __/\n|_| |_|\\____|_|\n\n ____ ____ ____ _ ____ _____ ____\n/ ___| / ___| _ \\ / \\ | _ \\| ____| _ \\\n\\___ \\| | | |_) | / _ \\ | |_) | _| | |_) |\n ___) | |___| _ < / ___ \\| __/| |___| _ <\n|____/ \\____|_| \\_\\/_/ \\_\\_| |_____|_| \\_\\\n`\n\n const claudeCommand = [\n `MCP_SCRAPER_API_KEY=${apiKeyValue} npx -y -p mcp-scraper@latest \\\\`,\n ' mcp-scraper-cli agent install claude --apply',\n ].join('\\n')\n\n const codexConfig = [\n '[mcp_servers.mcp-scraper]',\n 'command = \"npx\"',\n 'args = [\"-y\", \"-p\", \"mcp-scraper@latest\", \"mcp-scraper\"]',\n `env = { MCP_SCRAPER_API_KEY = \"${apiKeyValue}\" }`,\n ].join('\\n')\n\n return [\n colorize(`mcp-scraper v${options.version}`, 'bold', color),\n colorize('> mcp-scraper-install', 'muted', color),\n colorize(ascii, 'amber', color),\n `${colorize('MCP Scraper Agent', 'cyan', color)} . v${options.version} . mcpscraper.dev`,\n '1/1 install surfaces ready',\n 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),\n '',\n `${colorize('Tools', 'cyan', color)} ${colorize('(128 MCP tools)', 'muted', color)}`,\n toolRow('search', ['harvest_paa', 'search_serp', 'maps_search', 'maps_place_intel'], color),\n toolRow('extract', ['extract_url', 'map_site_urls', 'extract_site', 'audit_site', 'directory_workflow'], color),\n toolRow('build', ['rank_tracker_workflow', 'cron plan', 'database prompt'], color),\n 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),\n 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),\n toolRow('account', ['credits_info', 'reports', 'MCP resources'], color),\n toolRow('memory', ['memory-put', 'memory-get', 'memory-search', 'list-vaults', 'record-fact', 'list-scheduled-actions'], color),\n `${colorize('Workflows', 'cyan', color)} ${colorize('(MCP + CLI + API)', 'muted', color)}`,\n toolRow('route', ['workflow_list', 'workflow_suggest', 'workflow_run', 'workflow_step', 'workflow_status', 'workflow_artifact_read'], color),\n toolRow('seo', ['directory', 'agent-packet', 'competitive audit', 'map/serp comparison', 'PAA/AIO briefs', 'scheduled runs'], color),\n '',\n colorize('Usage tips:', 'amber', color),\n 'Run mcp-scraper-install for this visible card. Run mcp-scraper-cli for setup utilities and subcommands.',\n 'Run mcp-scraper in a human terminal to print this card; MCP clients get the same command as a silent stdio server.',\n 'Explicit card command: npx -y -p mcp-scraper@latest mcp-scraper-install',\n 'Customer auth setup: run browser_profile_connect, send the watch_url, let the user sign in, then call browser_profile_list until AUTHENTICATED.',\n 'Stack logins / reconnect: run browser_profile_connect again with the same profile name and another domain to add accounts or refresh a login.',\n 'Start with workflow_suggest for broad jobs like market analysis, ICP research, CRO audits, brand briefs, content gaps, and AI visibility.',\n 'For MCP clients, use mcp-scraper so one install can mix SERP, Maps, browser, reports, and saved MCP resources.',\n 'If you hit the concurrency limit, add an extra slot for $5/month with mcp-scraper-cli billing concurrency checkout.',\n '',\n `${colorize('Ready.', 'lime', color)} Install the combined MCP server with one command:`,\n '',\n colorize('Setup doctor', 'amber', color),\n 'npx -y -p mcp-scraper@latest mcp-scraper-cli doctor',\n '',\n colorize('Hosted profile setup', 'amber', color),\n 'In your MCP client, call browser_profile_connect with email=\"seo@example.com\" and domain=\"chatgpt.com\".',\n 'Give the returned watch_url to the user. After they sign in, call browser_profile_list, then browser_open with the returned profile. Add more logins by calling browser_profile_connect again with the same profile and a new domain.',\n '',\n colorize('Claude Code one-command setup', 'amber', color),\n claudeCommand,\n 'Then fully exit Claude Code and open a new Claude terminal. Check with: claude mcp list',\n '',\n colorize('Codex config', 'amber', color),\n codexConfig,\n '',\n colorize('Claude Desktop Extension', 'amber', color),\n 'Download: https://mcpscraper.dev/downloads/mcp-scraper.mcpb',\n '',\n colorize('Safety note:', 'muted', color),\n 'mcp-scraper prints this card only when stdin/stdout are an interactive TTY. In MCP clients it writes only JSON-RPC to stdout.',\n 'Use --stdio or MCP_SCRAPER_FORCE_STDIO=1 to force server mode from a terminal.',\n '',\n ].join('\\n')\n}\n","export const PACKAGE_VERSION = '0.4.1'\n","#!/usr/bin/env node\nimport { renderInstallTerminal } from '../src/install-terminal.js'\nimport { PACKAGE_VERSION } from '../src/version.js'\n\nconst noColor =\n process.argv.includes('--no-color') ||\n process.env.NO_COLOR !== undefined ||\n process.env.FORCE_COLOR === '0' ||\n !process.stdout.isTTY\n\nconst help = process.argv.includes('--help') || process.argv.includes('-h')\nif (help) {\n process.stdout.write([\n 'Usage: mcp-scraper-install [--no-color]',\n '',\n 'Prints the branded MCP Scraper terminal install card and copyable install commands.',\n 'mcp-scraper prints the same card in a human terminal and runs as the MCP stdio server in clients.',\n '',\n ].join('\\n'))\n process.exit(0)\n}\n\nprocess.stdout.write(renderInstallTerminal({\n version: PACKAGE_VERSION,\n color: !noColor,\n apiKeyConfigured: Boolean(process.env.MCP_SCRAPER_API_KEY?.trim()),\n}))\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/install-terminal.ts","../../src/version.ts","../../bin/mcp-scraper-install.ts"],"sourcesContent":["export interface InstallTerminalOptions {\n color?: boolean\n version: string\n apiKeyConfigured?: boolean\n}\n\ntype Tone = 'cyan' | 'lime' | 'amber' | 'red' | 'muted' | 'bold'\n\nconst CODES: Record<Tone | 'reset', string> = {\n reset: '\\x1b[0m',\n cyan: '\\x1b[36m',\n lime: '\\x1b[32m',\n amber: '\\x1b[33m',\n red: '\\x1b[31m',\n muted: '\\x1b[90m',\n bold: '\\x1b[1m',\n}\n\nfunction colorize(value: string, tone: Tone, enabled: boolean): string {\n if (!enabled) return value\n return `${CODES[tone]}${value}${CODES.reset}`\n}\n\nfunction toolRow(label: string, tools: string[], enabled: boolean): string {\n const padded = label.padEnd(9, ' ')\n return ` ${colorize(padded, 'muted', enabled)} ${tools.join(colorize(' . ', 'muted', enabled))}`\n}\n\nexport function renderInstallTerminal(options: InstallTerminalOptions): string {\n const color = options.color ?? true\n const apiKeyValue = options.apiKeyConfigured ? '$MCP_SCRAPER_API_KEY' : 'sk_live_your_key'\n const ascii = String.raw`\n __ __ ____ ____ \n| \\/ |/ ___| _ \\\n| |\\/| | | | |_) |\n| | | | |___| __/\n|_| |_|\\____|_|\n\n ____ ____ ____ _ ____ _____ ____\n/ ___| / ___| _ \\ / \\ | _ \\| ____| _ \\\n\\___ \\| | | |_) | / _ \\ | |_) | _| | |_) |\n ___) | |___| _ < / ___ \\| __/| |___| _ <\n|____/ \\____|_| \\_\\/_/ \\_\\_| |_____|_| \\_\\\n`\n\n const claudeCommand = [\n `MCP_SCRAPER_API_KEY=${apiKeyValue} npx -y -p mcp-scraper@latest \\\\`,\n ' mcp-scraper-cli agent install claude --apply',\n ].join('\\n')\n\n const codexConfig = [\n '[mcp_servers.mcp-scraper]',\n 'command = \"npx\"',\n 'args = [\"-y\", \"-p\", \"mcp-scraper@latest\", \"mcp-scraper\"]',\n `env = { MCP_SCRAPER_API_KEY = \"${apiKeyValue}\" }`,\n ].join('\\n')\n\n return [\n colorize(`mcp-scraper v${options.version}`, 'bold', color),\n colorize('> mcp-scraper-install', 'muted', color),\n colorize(ascii, 'amber', color),\n `${colorize('MCP Scraper Agent', 'cyan', color)} . v${options.version} . mcpscraper.dev`,\n '1/1 install surfaces ready',\n 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),\n '',\n `${colorize('Tools', 'cyan', color)} ${colorize('(128 MCP tools)', 'muted', color)}`,\n toolRow('search', ['harvest_paa', 'search_serp', 'maps_search', 'maps_place_intel'], color),\n toolRow('extract', ['extract_url', 'map_site_urls', 'extract_site', 'audit_site', 'directory_workflow'], color),\n toolRow('build', ['rank_tracker_workflow', 'cron plan', 'database prompt'], color),\n 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),\n 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),\n toolRow('account', ['credits_info', 'reports', 'MCP resources'], color),\n toolRow('memory', ['memory-put', 'memory-get', 'memory-search', 'list-vaults', 'record-fact', 'list-scheduled-actions'], color),\n `${colorize('Workflows', 'cyan', color)} ${colorize('(MCP + CLI + API)', 'muted', color)}`,\n toolRow('route', ['workflow_list', 'workflow_suggest', 'workflow_run', 'workflow_step', 'workflow_status', 'workflow_artifact_read'], color),\n toolRow('seo', ['directory', 'agent-packet', 'competitive audit', 'map/serp comparison', 'PAA/AIO briefs', 'scheduled runs'], color),\n '',\n colorize('Usage tips:', 'amber', color),\n 'Run mcp-scraper-install for this visible card. Run mcp-scraper-cli for setup utilities and subcommands.',\n 'Run mcp-scraper in a human terminal to print this card; MCP clients get the same command as a silent stdio server.',\n 'Explicit card command: npx -y -p mcp-scraper@latest mcp-scraper-install',\n 'Customer auth setup: run browser_profile_connect, send the watch_url, let the user sign in, then call browser_profile_list until AUTHENTICATED.',\n 'Stack logins / reconnect: run browser_profile_connect again with the same profile name and another domain to add accounts or refresh a login.',\n 'Start with workflow_suggest for broad jobs like market analysis, ICP research, CRO audits, brand briefs, content gaps, and AI visibility.',\n 'For MCP clients, use mcp-scraper so one install can mix SERP, Maps, browser, reports, and saved MCP resources.',\n 'If you hit the concurrency limit, add an extra slot for $5/month with mcp-scraper-cli billing concurrency checkout.',\n '',\n `${colorize('Ready.', 'lime', color)} Install the combined MCP server with one command:`,\n '',\n colorize('Setup doctor', 'amber', color),\n 'npx -y -p mcp-scraper@latest mcp-scraper-cli doctor',\n '',\n colorize('Hosted profile setup', 'amber', color),\n 'In your MCP client, call browser_profile_connect with email=\"seo@example.com\" and domain=\"chatgpt.com\".',\n 'Give the returned watch_url to the user. After they sign in, call browser_profile_list, then browser_open with the returned profile. Add more logins by calling browser_profile_connect again with the same profile and a new domain.',\n '',\n colorize('Claude Code one-command setup', 'amber', color),\n claudeCommand,\n 'Then fully exit Claude Code and open a new Claude terminal. Check with: claude mcp list',\n '',\n colorize('Codex config', 'amber', color),\n codexConfig,\n '',\n colorize('Claude Desktop Extension', 'amber', color),\n 'Download: https://mcpscraper.dev/downloads/mcp-scraper.mcpb',\n '',\n colorize('Safety note:', 'muted', color),\n 'mcp-scraper prints this card only when stdin/stdout are an interactive TTY. In MCP clients it writes only JSON-RPC to stdout.',\n 'Use --stdio or MCP_SCRAPER_FORCE_STDIO=1 to force server mode from a terminal.',\n '',\n ].join('\\n')\n}\n","export const PACKAGE_VERSION = '0.4.3'\n","#!/usr/bin/env node\nimport { renderInstallTerminal } from '../src/install-terminal.js'\nimport { PACKAGE_VERSION } from '../src/version.js'\n\nconst noColor =\n process.argv.includes('--no-color') ||\n process.env.NO_COLOR !== undefined ||\n process.env.FORCE_COLOR === '0' ||\n !process.stdout.isTTY\n\nconst help = process.argv.includes('--help') || process.argv.includes('-h')\nif (help) {\n process.stdout.write([\n 'Usage: mcp-scraper-install [--no-color]',\n '',\n 'Prints the branded MCP Scraper terminal install card and copyable install commands.',\n 'mcp-scraper prints the same card in a human terminal and runs as the MCP stdio server in clients.',\n '',\n ].join('\\n'))\n process.exit(0)\n}\n\nprocess.stdout.write(renderInstallTerminal({\n version: PACKAGE_VERSION,\n color: !noColor,\n apiKeyConfigured: Boolean(process.env.MCP_SCRAPER_API_KEY?.trim()),\n}))\n"],"mappings":"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","names":[]}
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "../chunk-5HNHYUJ5.js";
|
|
5
5
|
import {
|
|
6
6
|
PACKAGE_VERSION
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-SRMGSCC3.js";
|
|
8
8
|
|
|
9
9
|
// bin/mcp-scraper-install.ts
|
|
10
10
|
var noColor = process.argv.includes("--no-color") || process.env.NO_COLOR !== void 0 || process.env.FORCE_COLOR === "0" || !process.stdout.isTTY;
|
|
@@ -297,6 +297,9 @@ var HttpMcpToolExecutor = class {
|
|
|
297
297
|
mapsSearch(input) {
|
|
298
298
|
return this.call("/maps/search", input);
|
|
299
299
|
}
|
|
300
|
+
trustpilotReviews(input) {
|
|
301
|
+
return this.call("/trustpilot/reviews", input, this.httpTimeoutOverrideMs ?? 3e5);
|
|
302
|
+
}
|
|
300
303
|
directoryWorkflow(input) {
|
|
301
304
|
const cityCount = typeof input.maxCities === "number" ? input.maxCities : 25;
|
|
302
305
|
const concurrency = typeof input.concurrency === "number" && input.concurrency > 0 ? input.concurrency : 5;
|
|
@@ -474,7 +477,7 @@ render();
|
|
|
474
477
|
}
|
|
475
478
|
|
|
476
479
|
// src/version.ts
|
|
477
|
-
var PACKAGE_VERSION = "0.4.
|
|
480
|
+
var PACKAGE_VERSION = "0.4.3";
|
|
478
481
|
|
|
479
482
|
// src/mcp/browser-agent-tool-schemas.ts
|
|
480
483
|
var import_zod = require("zod");
|
|
@@ -696,7 +699,7 @@ var BrowserProfileLogin = import_zod.z.object({
|
|
|
696
699
|
var BrowserProfileConnectOutputSchema = {
|
|
697
700
|
ok: import_zod.z.boolean(),
|
|
698
701
|
tool: import_zod.z.literal("browser_profile_connect"),
|
|
699
|
-
session_id: NullableString,
|
|
702
|
+
session_id: NullableString.describe("The underlying live browser session id backing this sign-in."),
|
|
700
703
|
auth_connection_id: import_zod.z.string(),
|
|
701
704
|
watch_url: import_zod.z.string().describe("mcpscraper.dev sign-in link to give the user so they can complete this login."),
|
|
702
705
|
live_view_url: NullableString.describe("Deprecated; always null. Open watch_url to view the sign-in."),
|
|
@@ -706,10 +709,10 @@ var BrowserProfileConnectOutputSchema = {
|
|
|
706
709
|
account_email: NullableString,
|
|
707
710
|
note: NullableString,
|
|
708
711
|
status: import_zod.z.string(),
|
|
709
|
-
flow_status: NullableString,
|
|
710
|
-
flow_step: NullableString,
|
|
711
|
-
flow_expires_at: NullableString,
|
|
712
|
-
post_login_url: NullableString,
|
|
712
|
+
flow_status: NullableString.describe("Deprecated; always null."),
|
|
713
|
+
flow_step: NullableString.describe("Deprecated; always null."),
|
|
714
|
+
flow_expires_at: NullableString.describe("Deprecated; always null."),
|
|
715
|
+
post_login_url: NullableString.describe("Deprecated; always null."),
|
|
713
716
|
connected_logins: import_zod.z.array(BrowserProfileLogin).describe("Every login currently saved in this profile, so you can see what else is connected."),
|
|
714
717
|
next_steps: import_zod.z.array(import_zod.z.string()),
|
|
715
718
|
raw: BrowserRawObject.optional()
|
|
@@ -1198,6 +1201,11 @@ function slugPart(value) {
|
|
|
1198
1201
|
if (!trimmed) return null;
|
|
1199
1202
|
return trimmed.replace(/^https?:\/\//, "").replace(/^www\./, "").replace(/\/.*$/, "").replace(/[^a-z0-9._-]+/g, "-").replace(/[._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1200
1203
|
}
|
|
1204
|
+
function normalizeDomain(value) {
|
|
1205
|
+
const trimmed = value?.trim().toLowerCase();
|
|
1206
|
+
if (!trimmed) return null;
|
|
1207
|
+
return trimmed.replace(/^https?:\/\//, "").replace(/^www\./, "").replace(/\/.*$/, "").replace(/[^a-z0-9.-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
1208
|
+
}
|
|
1201
1209
|
function savedProfileNameFromEmail(email) {
|
|
1202
1210
|
const value = slugPart(email);
|
|
1203
1211
|
if (!value) return null;
|
|
@@ -1307,13 +1315,13 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1307
1315
|
"browser_profile_connect",
|
|
1308
1316
|
{
|
|
1309
1317
|
title: "Save a Site Login to a Profile",
|
|
1310
|
-
description:
|
|
1318
|
+
description: 'Open a live hosted browser session so the user can sign into a site (ChatGPT, Claude, Reddit, any account-gated site) directly in their own browser tab, then save the login to a named profile. Returns a watch_url \u2014 give it to the user; they sign in fresh on the real site (existing browser cookies are NOT imported), then click "Done" on that page to save the session and close it. ONE profile holds MANY logins \u2014 call again with the same profile and a different domain to stack another account. NOT for one-off scraping (use extract_url) or driving the browser (use browser_open). Billed at the standard live-browser rate while the sign-in session stays open. After the user clicks Done, poll browser_profile_list until AUTHENTICATED, then browser_open with the profile.',
|
|
1311
1319
|
inputSchema: BrowserProfileConnectInputSchema,
|
|
1312
1320
|
outputSchema: recordOutputSchema("browser_profile_connect", BrowserProfileConnectOutputSchema),
|
|
1313
1321
|
annotations: annotations("Save a Site Login to a Profile")
|
|
1314
1322
|
},
|
|
1315
1323
|
async (input) => {
|
|
1316
|
-
const domain =
|
|
1324
|
+
const domain = normalizeDomain(input.domain) || "chatgpt.com";
|
|
1317
1325
|
const setupUrl = input.login_url ?? input.url ?? (domain === "chatgpt.com" ? "https://chatgpt.com/" : `https://${domain}/`);
|
|
1318
1326
|
const profile = input.profile?.trim() || savedProfileNameFromEmail(input.email) || browserServiceProfileName();
|
|
1319
1327
|
if (!profile) {
|
|
@@ -1329,14 +1337,15 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1329
1337
|
domain,
|
|
1330
1338
|
login_url: setupUrl,
|
|
1331
1339
|
...accountEmail ? { account_email: accountEmail } : {},
|
|
1332
|
-
...note ? { note } : {}
|
|
1340
|
+
...note ? { note } : {},
|
|
1341
|
+
...typeof input.timeout_seconds === "number" ? { timeout_seconds: input.timeout_seconds } : {}
|
|
1333
1342
|
});
|
|
1334
1343
|
if (!open.ok) return errorResult("browser_profile_connect", open.data);
|
|
1335
1344
|
const connectedLogins = await fetchConnectedLogins(profile).catch(() => []);
|
|
1336
1345
|
return structuredResult({
|
|
1337
1346
|
ok: true,
|
|
1338
1347
|
tool: "browser_profile_connect",
|
|
1339
|
-
session_id: null,
|
|
1348
|
+
session_id: open.data.browser_session_id ?? null,
|
|
1340
1349
|
auth_connection_id: open.data.connection_id,
|
|
1341
1350
|
watch_url: `${consoleBase}/console/auth/${open.data.connection_id}`,
|
|
1342
1351
|
live_view_url: null,
|
|
@@ -1346,13 +1355,13 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1346
1355
|
account_email: accountEmail,
|
|
1347
1356
|
note,
|
|
1348
1357
|
status: open.data.status ?? null,
|
|
1349
|
-
flow_status:
|
|
1350
|
-
flow_step:
|
|
1351
|
-
flow_expires_at:
|
|
1352
|
-
post_login_url:
|
|
1358
|
+
flow_status: null,
|
|
1359
|
+
flow_step: null,
|
|
1360
|
+
flow_expires_at: null,
|
|
1361
|
+
post_login_url: null,
|
|
1353
1362
|
connected_logins: connectedLogins,
|
|
1354
1363
|
next_steps: [
|
|
1355
|
-
`Give the user watch_url so they can sign in to ${domain}.`,
|
|
1364
|
+
`Give the user watch_url so they can sign in to ${domain} directly in that page, then click "Done \u2014 I'm signed in".`,
|
|
1356
1365
|
"Poll browser_profile_list (with this profile) until the login reads AUTHENTICATED.",
|
|
1357
1366
|
"To add another account to this same profile, call browser_profile_connect again with the same profile and a different domain.",
|
|
1358
1367
|
"When authenticated, call browser_open with this profile to drive the logged-in session."
|
|
@@ -1377,7 +1386,7 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1377
1386
|
error: "profile or email is required when BROWSER_AGENT_PROFILE_NAME is not available"
|
|
1378
1387
|
});
|
|
1379
1388
|
}
|
|
1380
|
-
const domain =
|
|
1389
|
+
const domain = normalizeDomain(input.domain) || void 0;
|
|
1381
1390
|
const res = await req("POST", "/agent/profiles/list", {
|
|
1382
1391
|
profile,
|
|
1383
1392
|
...domain ? { domain } : {},
|
|
@@ -1976,6 +1985,17 @@ var WORKFLOW_RECIPES = [
|
|
|
1976
1985
|
produces: ["content gaps", "page comparison CSV", "PAA questions CSV", "section map", "writer brief"],
|
|
1977
1986
|
runHint: "Use workflow_run with workflowId serp-comparison for competitor gaps or paa-expansion-brief when the user mainly wants a section map from PAA data."
|
|
1978
1987
|
},
|
|
1988
|
+
{
|
|
1989
|
+
id: "recurring_tracking_and_memory",
|
|
1990
|
+
title: "Recurring Tracking & Memory",
|
|
1991
|
+
description: "Run a check on a daily, weekly, or custom cadence with no external cron needed, and save each result somewhere searchable so you can compare against past runs later - rankings, prices, competitor changes, anything you would otherwise check by hand and forget to log.",
|
|
1992
|
+
primaryWorkflowId: null,
|
|
1993
|
+
recommendedTools: ["create-scheduled-action", "search_serp", "table-create", "table-insert-rows", "table-query", "memory-put", "memory-search", "list-scheduled-actions"],
|
|
1994
|
+
requiredInputs: ["plain-language description of what to check and how often"],
|
|
1995
|
+
optionalInputs: ["vault to save into", "timeOfDay", "timezone"],
|
|
1996
|
+
produces: ["a live scheduled action that runs automatically on cadence", "a table row or note per run for structured history", "a searchable trend you can compare run-over-run"],
|
|
1997
|
+
runHint: "Use create-scheduled-action with a plain description and cadence; the run itself checks list-vaults, uses table-insert-rows for repeating structured data (e.g. a keyword rank check) or memory-put for a one-off note, and reads prior runs with table-query or memory-search before writing the new one. For a one-time blueprint to build your own external cron/DB instead of a live scheduled action, use rank_tracker_workflow."
|
|
1998
|
+
},
|
|
1979
1999
|
{
|
|
1980
2000
|
id: "ai_search_visibility_audit",
|
|
1981
2001
|
title: "AI Search Visibility Audit",
|
|
@@ -4094,6 +4114,51 @@ ${rows}`,
|
|
|
4094
4114
|
}
|
|
4095
4115
|
};
|
|
4096
4116
|
}
|
|
4117
|
+
function formatTrustpilotReviews(raw, input) {
|
|
4118
|
+
const parsed = parseData(raw);
|
|
4119
|
+
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
4120
|
+
const d = parsed.data;
|
|
4121
|
+
const domain = d.domain ?? input.domain;
|
|
4122
|
+
const reviewUrl = d.reviewUrl;
|
|
4123
|
+
const extractedAt = d.extractedAt;
|
|
4124
|
+
const requestedMaxPages = d.requestedMaxPages ?? input.maxPages ?? 5;
|
|
4125
|
+
const pagesFetched = d.pagesFetched;
|
|
4126
|
+
const reviews = d.reviews ?? [];
|
|
4127
|
+
const durationMs = d.durationMs;
|
|
4128
|
+
const rows = reviews.map((r) => {
|
|
4129
|
+
const stars = r.rating != null ? "\u2605".repeat(r.rating) + "\u2606".repeat(r.ratingScale - r.rating) : "\u2014";
|
|
4130
|
+
const answer = r.body[0]?.answer ?? "";
|
|
4131
|
+
return `| ${cell(r.reviewer.name ?? "Anonymous")} | ${stars} | ${cell(r.title)} | ${cell(r.date ? r.date.slice(0, 10) : null)} | ${r.flags.origin ?? "\u2014"} | ${r.flags.companyReplied ? "yes" : "no"} | ${cell(truncate(answer, 100))} |`;
|
|
4132
|
+
}).join("\n");
|
|
4133
|
+
const full = [
|
|
4134
|
+
`# Trustpilot Reviews: ${domain}`,
|
|
4135
|
+
`**Collected:** ${reviews.length} review${reviews.length === 1 ? "" : "s"} across ${pagesFetched ?? 0}/${requestedMaxPages} page${requestedMaxPages === 1 ? "" : "s"}`,
|
|
4136
|
+
reviewUrl ? `**Source:** ${reviewUrl}` : null,
|
|
4137
|
+
`
|
|
4138
|
+
## Reviews
|
|
4139
|
+
| Reviewer | Rating | Title | Date | Origin | Company Replied | Excerpt |
|
|
4140
|
+
|----------|--------|-------|------|--------|------------------|---------|
|
|
4141
|
+
${rows}`,
|
|
4142
|
+
`
|
|
4143
|
+
---
|
|
4144
|
+
\u{1F4A1} **Note:** sampling tool, default 5 pages (~100 reviews), max 50. For full-corpus export use Trustpilot's official Business API.`,
|
|
4145
|
+
durationMs != null ? `
|
|
4146
|
+
*Extracted in ${(durationMs / 1e3).toFixed(1)}s*` : null
|
|
4147
|
+
].filter(Boolean).join("\n");
|
|
4148
|
+
return {
|
|
4149
|
+
...oneBlock(full),
|
|
4150
|
+
structuredContent: {
|
|
4151
|
+
domain,
|
|
4152
|
+
reviewUrl: reviewUrl ?? null,
|
|
4153
|
+
extractedAt: extractedAt ?? null,
|
|
4154
|
+
requestedMaxPages,
|
|
4155
|
+
pagesFetched: pagesFetched ?? 0,
|
|
4156
|
+
reviewCount: reviews.length,
|
|
4157
|
+
reviews,
|
|
4158
|
+
durationMs: durationMs ?? 0
|
|
4159
|
+
}
|
|
4160
|
+
};
|
|
4161
|
+
}
|
|
4097
4162
|
async function formatDirectoryWorkflow(raw, input, ctx) {
|
|
4098
4163
|
const parsed = parseData(raw);
|
|
4099
4164
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
@@ -4195,6 +4260,9 @@ function formatMapsPlaceIntel(raw, input) {
|
|
|
4195
4260
|
const about = d.aboutAttributes ?? [];
|
|
4196
4261
|
const reviews = d.reviews ?? [];
|
|
4197
4262
|
const reviewsStatus = d.reviewsStatus ?? "not_requested";
|
|
4263
|
+
const services = d.services ?? [];
|
|
4264
|
+
const areasServed = d.areasServed ?? [];
|
|
4265
|
+
const servicesStatus = d.servicesStatus ?? "not_requested";
|
|
4198
4266
|
const hoursTable = d.hoursTable ?? [];
|
|
4199
4267
|
const ratingLine = [rating, reviewCount ? `(${reviewCount} reviews)` : null].filter(Boolean).join(" ");
|
|
4200
4268
|
const basicLines = [
|
|
@@ -4249,6 +4317,20 @@ ${reviews.map((r, i) => {
|
|
|
4249
4317
|
${r.text ?? ""}`;
|
|
4250
4318
|
}).join("\n\n")}`;
|
|
4251
4319
|
})();
|
|
4320
|
+
const servicesSection = (() => {
|
|
4321
|
+
if (servicesStatus === "not_requested") return "";
|
|
4322
|
+
if (servicesStatus === "unavailable") return "\n## Services & Areas Served\n> Could not be retrieved this run \u2014 retry with `includeServices: true`.";
|
|
4323
|
+
if (servicesStatus === "none_exist") return "\n## Services & Areas Served\n*Not listed on this business's profile.*";
|
|
4324
|
+
const parts = [
|
|
4325
|
+
services.length ? `**Services**
|
|
4326
|
+
${services.map((s) => `- ${s}`).join("\n")}` : null,
|
|
4327
|
+
areasServed.length ? `**Areas Served**
|
|
4328
|
+
${areasServed.map((a) => `- ${a}`).join("\n")}` : null
|
|
4329
|
+
].filter(Boolean);
|
|
4330
|
+
return parts.length ? `
|
|
4331
|
+
## Services & Areas Served
|
|
4332
|
+
${parts.join("\n\n")}` : "";
|
|
4333
|
+
})();
|
|
4252
4334
|
const full = [
|
|
4253
4335
|
`# ${name}`,
|
|
4254
4336
|
category ? `*${category}*` : null,
|
|
@@ -4264,6 +4346,7 @@ ${basicLines}` : null,
|
|
|
4264
4346
|
## Entity IDs
|
|
4265
4347
|
${entitySection}` : null,
|
|
4266
4348
|
reviewsSection,
|
|
4349
|
+
servicesSection,
|
|
4267
4350
|
durationMs != null ? `
|
|
4268
4351
|
---
|
|
4269
4352
|
*Extracted in ${(durationMs / 1e3).toFixed(1)}s*` : null
|
|
@@ -4287,7 +4370,10 @@ ${entitySection}` : null,
|
|
|
4287
4370
|
lng: lng ?? null,
|
|
4288
4371
|
reviewsStatus,
|
|
4289
4372
|
reviewsCollected: reviews.length,
|
|
4290
|
-
reviewTopics: topics.map((t) => ({ label: String(t.label ?? ""), count: String(t.count ?? "") }))
|
|
4373
|
+
reviewTopics: topics.map((t) => ({ label: String(t.label ?? ""), count: String(t.count ?? "") })),
|
|
4374
|
+
services,
|
|
4375
|
+
areasServed,
|
|
4376
|
+
servicesStatus
|
|
4291
4377
|
}
|
|
4292
4378
|
};
|
|
4293
4379
|
}
|
|
@@ -4840,6 +4926,7 @@ var MapsPlaceOptionsSchema = import_zod2.z.object({
|
|
|
4840
4926
|
hl: import_zod2.z.string().length(2).default("en"),
|
|
4841
4927
|
includeReviews: import_zod2.z.boolean().default(false),
|
|
4842
4928
|
maxReviews: import_zod2.z.number().int().min(1).max(500).default(50),
|
|
4929
|
+
includeServices: import_zod2.z.boolean().default(false),
|
|
4843
4930
|
kernelApiKey: import_zod2.z.string().optional(),
|
|
4844
4931
|
kernelProxyId: import_zod2.z.string().optional(),
|
|
4845
4932
|
headless: import_zod2.z.boolean().default(true)
|
|
@@ -5028,8 +5115,37 @@ var MapsPlaceIntelInputSchema = {
|
|
|
5028
5115
|
gl: import_zod3.z.string().length(2).default("us").describe("Google country code inferred from location."),
|
|
5029
5116
|
hl: import_zod3.z.string().length(2).default("en").describe("Language inferred from user request."),
|
|
5030
5117
|
includeReviews: import_zod3.z.boolean().default(false).describe("Fetch individual review cards \u2014 for reviews, customer pain, complaints, or praise themes."),
|
|
5031
|
-
maxReviews: import_zod3.z.number().int().min(1).max(500).default(50).describe("Max review cards when includeReviews is true. Default 50, maximum 500.")
|
|
5032
|
-
|
|
5118
|
+
maxReviews: import_zod3.z.number().int().min(1).max(500).default(50).describe("Max review cards when includeReviews is true. Default 50, maximum 500."),
|
|
5119
|
+
includeServices: import_zod3.z.boolean().default(false).describe("Fetch the business's configured services list and areas-served list, when the profile has them. Adds one extra page visit; not present for every business.")
|
|
5120
|
+
};
|
|
5121
|
+
var TrustpilotReviewsInputSchema = {
|
|
5122
|
+
domain: import_zod3.z.string().min(1).describe(`The business's domain as it appears in its Trustpilot URL, e.g. "www.bhphotovideo.com" (include the www. if the site uses it \u2014 pass the domain as-is, do not guess).`),
|
|
5123
|
+
maxPages: import_zod3.z.number().int().min(1).max(50).default(5).describe("Review pages to fetch (~20 reviews per page). Default 5 (~100 reviews). Maximum 50 \u2014 large companies can have 1,000+ pages; this tool is for sampling, not full-corpus export.")
|
|
5124
|
+
};
|
|
5125
|
+
var ReviewCardSchema = import_zod3.z.object({
|
|
5126
|
+
source: import_zod3.z.enum(["trustpilot", "g2"]),
|
|
5127
|
+
sourceReviewId: import_zod3.z.string().nullable(),
|
|
5128
|
+
reviewUrl: import_zod3.z.string().nullable(),
|
|
5129
|
+
reviewer: import_zod3.z.object({
|
|
5130
|
+
name: import_zod3.z.string().nullable(),
|
|
5131
|
+
profileId: import_zod3.z.string().nullable(),
|
|
5132
|
+
title: import_zod3.z.string().nullable(),
|
|
5133
|
+
companySegment: import_zod3.z.string().nullable()
|
|
5134
|
+
}),
|
|
5135
|
+
rating: import_zod3.z.number().nullable(),
|
|
5136
|
+
ratingScale: import_zod3.z.number(),
|
|
5137
|
+
title: import_zod3.z.string().nullable(),
|
|
5138
|
+
date: import_zod3.z.string().nullable(),
|
|
5139
|
+
body: import_zod3.z.array(import_zod3.z.object({ question: import_zod3.z.string().nullable(), answer: import_zod3.z.string() })),
|
|
5140
|
+
truncated: import_zod3.z.boolean(),
|
|
5141
|
+
flags: import_zod3.z.object({
|
|
5142
|
+
origin: import_zod3.z.string().nullable(),
|
|
5143
|
+
incentivized: import_zod3.z.boolean().nullable(),
|
|
5144
|
+
validated: import_zod3.z.boolean().nullable(),
|
|
5145
|
+
currentUser: import_zod3.z.boolean().nullable(),
|
|
5146
|
+
companyReplied: import_zod3.z.boolean().nullable()
|
|
5147
|
+
})
|
|
5148
|
+
});
|
|
5033
5149
|
var MapsSearchInputSchema = {
|
|
5034
5150
|
query: import_zod3.z.string().min(1).describe('Business category, niche, or search term, e.g. "roofers". Do not include location here \u2014 use location instead.'),
|
|
5035
5151
|
location: import_zod3.z.string().optional().describe('City, region, country, or service area, e.g. "Denver, CO".'),
|
|
@@ -5341,7 +5457,20 @@ var MapsPlaceIntelOutputSchema = {
|
|
|
5341
5457
|
reviewTopics: import_zod3.z.array(import_zod3.z.object({
|
|
5342
5458
|
label: import_zod3.z.string(),
|
|
5343
5459
|
count: import_zod3.z.string()
|
|
5344
|
-
}))
|
|
5460
|
+
})),
|
|
5461
|
+
services: import_zod3.z.array(import_zod3.z.string()),
|
|
5462
|
+
areasServed: import_zod3.z.array(import_zod3.z.string()),
|
|
5463
|
+
servicesStatus: import_zod3.z.string()
|
|
5464
|
+
};
|
|
5465
|
+
var TrustpilotReviewsOutputSchema = {
|
|
5466
|
+
domain: import_zod3.z.string(),
|
|
5467
|
+
reviewUrl: import_zod3.z.string(),
|
|
5468
|
+
extractedAt: import_zod3.z.string(),
|
|
5469
|
+
requestedMaxPages: import_zod3.z.number().int(),
|
|
5470
|
+
pagesFetched: import_zod3.z.number().int(),
|
|
5471
|
+
reviewCount: import_zod3.z.number().int(),
|
|
5472
|
+
reviews: import_zod3.z.array(ReviewCardSchema),
|
|
5473
|
+
durationMs: import_zod3.z.number()
|
|
5345
5474
|
};
|
|
5346
5475
|
var CreditsInfoOutputSchema = {
|
|
5347
5476
|
balanceCredits: import_zod3.z.number().nullable(),
|
|
@@ -5844,7 +5973,7 @@ var ReportArtifactReadOutputSchema = {
|
|
|
5844
5973
|
|
|
5845
5974
|
// src/mcp/rank-tracker-blueprint.ts
|
|
5846
5975
|
var DEFAULT_MODES = ["maps", "organic", "ai_overview", "paa"];
|
|
5847
|
-
function
|
|
5976
|
+
function normalizeDomain2(value) {
|
|
5848
5977
|
const trimmed = value?.trim();
|
|
5849
5978
|
if (!trimmed) return null;
|
|
5850
5979
|
try {
|
|
@@ -6106,7 +6235,7 @@ function buildPrompt(input, modes, tools, tables, jobs, metrics, expression, tar
|
|
|
6106
6235
|
}
|
|
6107
6236
|
function buildRankTrackerBlueprint(input) {
|
|
6108
6237
|
const trackingModes = uniqueModes(input.trackingModes);
|
|
6109
|
-
const targetDomain =
|
|
6238
|
+
const targetDomain = normalizeDomain2(input.targetDomain);
|
|
6110
6239
|
const targetBusinessName = input.targetBusinessName?.trim() || null;
|
|
6111
6240
|
const projectName = input.projectName?.trim() || "MCP Scraper Rank Tracker";
|
|
6112
6241
|
const database = input.database || "postgres";
|
|
@@ -6394,6 +6523,13 @@ function registerPaaExtractorMcpTools(server2, executor, options = {}) {
|
|
|
6394
6523
|
outputSchema: recordOutputSchema("maps_search", MapsSearchOutputSchema),
|
|
6395
6524
|
annotations: liveWebToolAnnotations("Google Maps Business Search")
|
|
6396
6525
|
}, async (input) => formatMapsSearch(await executor.mapsSearch(input), input));
|
|
6526
|
+
server2.registerTool("trustpilot_reviews", {
|
|
6527
|
+
title: "Trustpilot Review Harvest",
|
|
6528
|
+
description: "Extract customer reviews for a business from Trustpilot \u2014 reviewer, rating, title, body, date, invited/organic origin, company-reply flag. Sampling tool, not a full-corpus export: default 5 pages (~100 reviews), max 50 pages. For bulk/complete extraction across thousands of pages, use Trustpilot's official Business API instead.",
|
|
6529
|
+
inputSchema: TrustpilotReviewsInputSchema,
|
|
6530
|
+
outputSchema: recordOutputSchema("trustpilot_reviews", TrustpilotReviewsOutputSchema),
|
|
6531
|
+
annotations: liveWebToolAnnotations("Trustpilot Review Harvest")
|
|
6532
|
+
}, async (input) => formatTrustpilotReviews(await executor.trustpilotReviews(input), input));
|
|
6397
6533
|
server2.registerTool("directory_workflow", {
|
|
6398
6534
|
title: "Directory Workflow: Markets + Maps",
|
|
6399
6535
|
description: `Build directory/prospecting datasets: selects US city markets from Census population data, optionally joins configured ZIP groups, then runs Google Maps business searches per city in parallel. Use for "all cities over 100k population in a state" or market+Maps workflows. ${fileBehavior("Saves a CSV of results per city.", "Large results are stored as a retrievable artifact \u2014 you get an inline summary plus an artifactId for report_artifact_read.")}`,
|