mcp-scraper 0.4.3 → 0.4.5

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.
Files changed (32) hide show
  1. package/dist/bin/api-server.cjs +2394 -1851
  2. package/dist/bin/api-server.cjs.map +1 -1
  3. package/dist/bin/api-server.js +2 -2
  4. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  5. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  6. package/dist/bin/mcp-scraper-cli.js +1 -1
  7. package/dist/bin/mcp-scraper-install.cjs +1 -1
  8. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  9. package/dist/bin/mcp-scraper-install.js +1 -1
  10. package/dist/bin/mcp-stdio-server.cjs +179 -3
  11. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  12. package/dist/bin/mcp-stdio-server.js +2 -2
  13. package/dist/{chunk-NVUBFCJK.js → chunk-4ZZWFI6L.js} +2 -2
  14. package/dist/{chunk-4MFYMUIF.js → chunk-5LO6EHEH.js} +25 -3
  15. package/dist/chunk-5LO6EHEH.js.map +1 -0
  16. package/dist/chunk-UGQC2FOX.js +7 -0
  17. package/dist/chunk-UGQC2FOX.js.map +1 -0
  18. package/dist/{chunk-BAMVP57P.js → chunk-WI4A3KFQ.js} +180 -4
  19. package/dist/chunk-WI4A3KFQ.js.map +1 -0
  20. package/dist/{server-LBEYRKM2.js → server-XCDFHHLE.js} +551 -220
  21. package/dist/server-XCDFHHLE.js.map +1 -0
  22. package/dist/{site-extract-repository-4NVO5KQA.js → site-extract-repository-THBVEXMP.js} +3 -3
  23. package/dist/{worker-7XRZNLKB.js → worker-YHKUJR5P.js} +2 -2
  24. package/package.json +1 -1
  25. package/dist/chunk-4MFYMUIF.js.map +0 -1
  26. package/dist/chunk-BAMVP57P.js.map +0 -1
  27. package/dist/chunk-SRMGSCC3.js +0 -7
  28. package/dist/chunk-SRMGSCC3.js.map +0 -1
  29. package/dist/server-LBEYRKM2.js.map +0 -1
  30. /package/dist/{chunk-NVUBFCJK.js.map → chunk-4ZZWFI6L.js.map} +0 -0
  31. /package/dist/{site-extract-repository-4NVO5KQA.js.map → site-extract-repository-THBVEXMP.js.map} +0 -0
  32. /package/dist/{worker-7XRZNLKB.js.map → worker-YHKUJR5P.js.map} +0 -0
@@ -11,7 +11,7 @@ import "../chunk-K443GQY5.js";
11
11
  import "../chunk-XGIPATLV.js";
12
12
  import {
13
13
  PACKAGE_VERSION
14
- } from "../chunk-SRMGSCC3.js";
14
+ } from "../chunk-UGQC2FOX.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.3";
105
+ var PACKAGE_VERSION = "0.4.5";
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.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":[]}
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.5'\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-SRMGSCC3.js";
7
+ } from "../chunk-UGQC2FOX.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;
@@ -300,6 +300,9 @@ var HttpMcpToolExecutor = class {
300
300
  trustpilotReviews(input) {
301
301
  return this.call("/trustpilot/reviews", input, this.httpTimeoutOverrideMs ?? 3e5);
302
302
  }
303
+ g2Reviews(input) {
304
+ return this.call("/g2/reviews", input, this.httpTimeoutOverrideMs ?? 3e5);
305
+ }
303
306
  directoryWorkflow(input) {
304
307
  const cityCount = typeof input.maxCities === "number" ? input.maxCities : 25;
305
308
  const concurrency = typeof input.concurrency === "number" && input.concurrency > 0 ? input.concurrency : 5;
@@ -477,7 +480,7 @@ render();
477
480
  }
478
481
 
479
482
  // src/version.ts
480
- var PACKAGE_VERSION = "0.4.3";
483
+ var PACKAGE_VERSION = "0.4.5";
481
484
 
482
485
  // src/mcp/browser-agent-tool-schemas.ts
483
486
  var import_zod = require("zod");
@@ -498,7 +501,10 @@ var BrowserOpenInputSchema = {
498
501
  url: import_zod.z.string().url().optional().describe("Optional URL to navigate to immediately after opening."),
499
502
  profile: import_zod.z.string().optional().describe("Optional saved hosted profile name to load a logged-in session for a site."),
500
503
  save_profile_changes: import_zod.z.boolean().optional().describe("Persist cookies/storage back to the named profile on close. Avoid parallel sessions writing to the same profile."),
501
- timeout_seconds: import_zod.z.number().int().min(60).max(259200).optional().describe("Session lifetime before auto-termination. Defaults to 600.")
504
+ timeout_seconds: import_zod.z.number().int().min(60).max(259200).optional().describe("Session lifetime before auto-termination. Defaults to 600."),
505
+ extension_names: import_zod.z.array(import_zod.z.string()).optional().describe(
506
+ "Names of extensions previously added with browser_extension_import (see browser_extension_list for what's available) to load into this session. Loading extensions restarts the browser, adding a few seconds to startup."
507
+ )
502
508
  };
503
509
  var BrowserProfileConnectInputSchema = {
504
510
  email: import_zod.z.string().optional().describe("Account email for the login. Derives a stable profile name and is recorded as a note. Does NOT import existing cookies \u2014 the user signs in fresh."),
@@ -725,6 +731,43 @@ var BrowserProfileListOutputSchema = {
725
731
  connections: import_zod.z.array(BrowserProfileLogin).describe("All site logins saved in this profile, each with its current auth status and note."),
726
732
  count: import_zod.z.number().int().min(0)
727
733
  };
734
+ var BrowserExtensionImportInputSchema = {
735
+ store_url: import_zod.z.string().url().describe("Chrome Web Store URL of the extension to add, e.g. https://chromewebstore.google.com/detail/<slug>/<id>."),
736
+ name: import_zod.z.string().min(1).max(64).describe('Short name to save this extension under, e.g. "ani-ai". Reuse it later in extension_names on browser_open.')
737
+ };
738
+ var BrowserExtensionImportOutputSchema = {
739
+ ok: import_zod.z.boolean(),
740
+ tool: import_zod.z.literal("browser_extension_import"),
741
+ session_id: import_zod.z.null(),
742
+ name: import_zod.z.string().describe("The name this extension was saved under."),
743
+ source_url: import_zod.z.string().describe("The store URL this extension was imported from."),
744
+ size_bytes: import_zod.z.number().nullable().describe("Size of the extension package in bytes.")
745
+ };
746
+ var BrowserExtensionSummary = import_zod.z.object({
747
+ name: import_zod.z.string(),
748
+ source: import_zod.z.string().describe('Always "store" for extensions added via browser_extension_import.'),
749
+ source_url: NullableString,
750
+ size_bytes: import_zod.z.number().nullable(),
751
+ created_at: import_zod.z.string()
752
+ });
753
+ var BrowserExtensionListInputSchema = {};
754
+ var BrowserExtensionListOutputSchema = {
755
+ ok: import_zod.z.boolean(),
756
+ tool: import_zod.z.literal("browser_extension_list"),
757
+ session_id: import_zod.z.null(),
758
+ extensions: import_zod.z.array(BrowserExtensionSummary).describe("Every extension available to load via extension_names on browser_open."),
759
+ count: import_zod.z.number().int().min(0)
760
+ };
761
+ var BrowserExtensionDeleteInputSchema = {
762
+ name: import_zod.z.string().min(1).describe("Name of the extension to remove, as returned by browser_extension_list.")
763
+ };
764
+ var BrowserExtensionDeleteOutputSchema = {
765
+ ok: import_zod.z.boolean(),
766
+ tool: import_zod.z.literal("browser_extension_delete"),
767
+ session_id: import_zod.z.null(),
768
+ name: import_zod.z.string(),
769
+ deleted: import_zod.z.boolean()
770
+ };
728
771
  var BrowserScreenshotOutputSchema = {
729
772
  ...BrowserBaseOutput,
730
773
  tool: import_zod.z.literal("browser_screenshot"),
@@ -1404,6 +1447,71 @@ function registerBrowserAgentMcpTools(server2, opts) {
1404
1447
  });
1405
1448
  }
1406
1449
  );
1450
+ server2.registerTool(
1451
+ "browser_extension_import",
1452
+ {
1453
+ title: "Add Browser Extension",
1454
+ description: "Add a Chrome extension from its Chrome Web Store page so it can be loaded into browser_open sessions via extension_names. One-time setup per extension \u2014 check what's already added with browser_extension_list first. The extension starts logged out in any session; sign into it once inside a session, pairing with a saved profile (browser_open's profile + save_profile_changes) to keep it signed in on future opens.",
1455
+ inputSchema: BrowserExtensionImportInputSchema,
1456
+ outputSchema: recordOutputSchema("browser_extension_import", BrowserExtensionImportOutputSchema),
1457
+ annotations: annotations("Add Browser Extension")
1458
+ },
1459
+ async (input) => {
1460
+ const res = await req("POST", "/agent/extensions/import", { store_url: input.store_url, name: input.name });
1461
+ if (!res.ok) return errorResult("browser_extension_import", res.data);
1462
+ return structuredResult({
1463
+ ok: true,
1464
+ tool: "browser_extension_import",
1465
+ session_id: null,
1466
+ name: res.data.name,
1467
+ source_url: res.data.source_url,
1468
+ size_bytes: res.data.size_bytes ?? null
1469
+ });
1470
+ }
1471
+ );
1472
+ server2.registerTool(
1473
+ "browser_extension_list",
1474
+ {
1475
+ title: "List Browser Extensions",
1476
+ description: "List extensions added via browser_extension_import, for use as extension_names on browser_open. Read-only, no cost.",
1477
+ inputSchema: BrowserExtensionListInputSchema,
1478
+ outputSchema: recordOutputSchema("browser_extension_list", BrowserExtensionListOutputSchema),
1479
+ annotations: annotations("List Browser Extensions", true)
1480
+ },
1481
+ async () => {
1482
+ const res = await req("GET", "/agent/extensions");
1483
+ if (!res.ok) return errorResult("browser_extension_list", res.data);
1484
+ const extensions = Array.isArray(res.data?.extensions) ? res.data.extensions : [];
1485
+ return structuredResult({
1486
+ ok: true,
1487
+ tool: "browser_extension_list",
1488
+ session_id: null,
1489
+ extensions,
1490
+ count: extensions.length
1491
+ });
1492
+ }
1493
+ );
1494
+ server2.registerTool(
1495
+ "browser_extension_delete",
1496
+ {
1497
+ title: "Remove Browser Extension",
1498
+ description: "Remove a previously added extension by name so it can no longer be loaded via extension_names.",
1499
+ inputSchema: BrowserExtensionDeleteInputSchema,
1500
+ outputSchema: recordOutputSchema("browser_extension_delete", BrowserExtensionDeleteOutputSchema),
1501
+ annotations: { title: "Remove Browser Extension", readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
1502
+ },
1503
+ async (input) => {
1504
+ const res = await req("DELETE", `/agent/extensions/${encodeURIComponent(input.name)}`);
1505
+ if (!res.ok) return errorResult("browser_extension_delete", res.data);
1506
+ return structuredResult({
1507
+ ok: true,
1508
+ tool: "browser_extension_delete",
1509
+ session_id: null,
1510
+ name: input.name,
1511
+ deleted: true
1512
+ });
1513
+ }
1514
+ );
1407
1515
  server2.registerTool(
1408
1516
  "browser_open",
1409
1517
  {
@@ -1422,7 +1530,8 @@ function registerBrowserAgentMcpTools(server2, opts) {
1422
1530
  ...profile && typeof saveProfileChanges === "boolean" ? { save_profile_changes: saveProfileChanges } : {},
1423
1531
  disable_default_proxy: true,
1424
1532
  timeout_seconds: input.timeout_seconds,
1425
- ...input.url ? { url: input.url } : {}
1533
+ ...input.url ? { url: input.url } : {},
1534
+ ...input.extension_names?.length ? { extension_names: input.extension_names } : {}
1426
1535
  });
1427
1536
  if (!open.ok) return errorResult("browser_open", open.data);
1428
1537
  const session = open.data;
@@ -4159,6 +4268,52 @@ ${rows}`,
4159
4268
  }
4160
4269
  };
4161
4270
  }
4271
+ function formatG2Reviews(raw, input) {
4272
+ const parsed = parseData(raw);
4273
+ if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
4274
+ const d = parsed.data;
4275
+ const product = d.product ?? input.product;
4276
+ const reviewUrl = d.reviewUrl;
4277
+ const extractedAt = d.extractedAt;
4278
+ const requestedMaxPages = d.requestedMaxPages ?? input.maxPages ?? 5;
4279
+ const pagesFetched = d.pagesFetched;
4280
+ const reviews = d.reviews ?? [];
4281
+ const durationMs = d.durationMs;
4282
+ const rows = reviews.map((r) => {
4283
+ const stars = r.rating != null ? "\u2605".repeat(Math.round(r.rating)) + "\u2606".repeat(r.ratingScale - Math.round(r.rating)) : "\u2014";
4284
+ const verified = [r.flags.currentUser ? "current-user" : null, r.flags.validated ? "validated" : null, r.flags.incentivized ? "incentivized" : null].filter(Boolean).join(", ") || "\u2014";
4285
+ const firstAnswer = r.body[0]?.answer ?? "";
4286
+ return `| ${cell(r.reviewer.name ?? "Anonymous")} | ${cell(r.reviewer.title)} | ${cell(r.reviewer.companySegment)} | ${stars} | ${cell(r.title)} | ${cell(r.date ? r.date.slice(0, 10) : null)} | ${r.flags.origin ?? "\u2014"} | ${verified} | ${cell(truncate(firstAnswer, 100))} |`;
4287
+ }).join("\n");
4288
+ const full = [
4289
+ `# G2 Reviews: ${product}`,
4290
+ `**Collected:** ${reviews.length} review${reviews.length === 1 ? "" : "s"} across ${pagesFetched ?? 0}/${requestedMaxPages} page${requestedMaxPages === 1 ? "" : "s"}`,
4291
+ reviewUrl ? `**Source:** ${reviewUrl}` : null,
4292
+ `
4293
+ ## Reviews
4294
+ | Reviewer | Title | Company Size | Rating | Review Title | Date | Origin | Verification | Excerpt |
4295
+ |----------|-------|---------------|--------|--------------|------|--------|---------------|---------|
4296
+ ${rows}`,
4297
+ `
4298
+ ---
4299
+ \u{1F4A1} **Note:** sampling tool, default 5 pages (~50 reviews), max 50. Each review's full body has up to 3 Q&A sections (like/dislike/problems solved) \u2014 see structuredContent.reviews[].body for the complete text. For full-corpus export use G2's official API.`,
4300
+ durationMs != null ? `
4301
+ *Extracted in ${(durationMs / 1e3).toFixed(1)}s*` : null
4302
+ ].filter(Boolean).join("\n");
4303
+ return {
4304
+ ...oneBlock(full),
4305
+ structuredContent: {
4306
+ product,
4307
+ reviewUrl: reviewUrl ?? null,
4308
+ extractedAt: extractedAt ?? null,
4309
+ requestedMaxPages,
4310
+ pagesFetched: pagesFetched ?? 0,
4311
+ reviewCount: reviews.length,
4312
+ reviews,
4313
+ durationMs: durationMs ?? 0
4314
+ }
4315
+ };
4316
+ }
4162
4317
  async function formatDirectoryWorkflow(raw, input, ctx) {
4163
4318
  const parsed = parseData(raw);
4164
4319
  if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
@@ -5122,6 +5277,10 @@ var TrustpilotReviewsInputSchema = {
5122
5277
  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
5278
  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
5279
  };
5280
+ var G2ReviewsInputSchema = {
5281
+ product: import_zod3.z.string().min(1).describe(`The product's G2 URL slug, e.g. "notion" from g2.com/products/notion/reviews (also accepts a full g2.com product URL).`),
5282
+ maxPages: import_zod3.z.number().int().min(1).max(50).default(5).describe("Review pages to fetch (~10 reviews per page). Default 5 (~50 reviews). Maximum 50 \u2014 this tool is for sampling, not full-corpus export.")
5283
+ };
5125
5284
  var ReviewCardSchema = import_zod3.z.object({
5126
5285
  source: import_zod3.z.enum(["trustpilot", "g2"]),
5127
5286
  sourceReviewId: import_zod3.z.string().nullable(),
@@ -5472,6 +5631,16 @@ var TrustpilotReviewsOutputSchema = {
5472
5631
  reviews: import_zod3.z.array(ReviewCardSchema),
5473
5632
  durationMs: import_zod3.z.number()
5474
5633
  };
5634
+ var G2ReviewsOutputSchema = {
5635
+ product: import_zod3.z.string(),
5636
+ reviewUrl: import_zod3.z.string(),
5637
+ extractedAt: import_zod3.z.string(),
5638
+ requestedMaxPages: import_zod3.z.number().int(),
5639
+ pagesFetched: import_zod3.z.number().int(),
5640
+ reviewCount: import_zod3.z.number().int(),
5641
+ reviews: import_zod3.z.array(ReviewCardSchema),
5642
+ durationMs: import_zod3.z.number()
5643
+ };
5475
5644
  var CreditsInfoOutputSchema = {
5476
5645
  balanceCredits: import_zod3.z.number().nullable(),
5477
5646
  matchedCost: import_zod3.z.object({
@@ -6530,6 +6699,13 @@ function registerPaaExtractorMcpTools(server2, executor, options = {}) {
6530
6699
  outputSchema: recordOutputSchema("trustpilot_reviews", TrustpilotReviewsOutputSchema),
6531
6700
  annotations: liveWebToolAnnotations("Trustpilot Review Harvest")
6532
6701
  }, async (input) => formatTrustpilotReviews(await executor.trustpilotReviews(input), input));
6702
+ server2.registerTool("g2_reviews", {
6703
+ title: "G2 Review Harvest",
6704
+ description: `Extract customer reviews for a software product from G2 \u2014 reviewer (name, job title, company size), rating, title, date, structured like/dislike/problems-solved Q&A body, and verification flags (incentivized, validated, current user, invite source). Sampling tool, not a full-corpus export: default 5 pages (~50 reviews), max 50 pages. Requires the product's G2 URL slug (e.g. "notion"), not a company name. For bulk/complete extraction, use G2's official API instead.`,
6705
+ inputSchema: G2ReviewsInputSchema,
6706
+ outputSchema: recordOutputSchema("g2_reviews", G2ReviewsOutputSchema),
6707
+ annotations: liveWebToolAnnotations("G2 Review Harvest")
6708
+ }, async (input) => formatG2Reviews(await executor.g2Reviews(input), input));
6533
6709
  server2.registerTool("directory_workflow", {
6534
6710
  title: "Directory Workflow: Markets + Maps",
6535
6711
  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.")}`,