mcp-scraper 0.1.8 → 0.1.9

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.
@@ -4343,8 +4343,8 @@ async function downloadAsset(url, destDir, filename) {
4343
4343
  }
4344
4344
  const writer = (0, import_node_fs.createWriteStream)(dest);
4345
4345
  await (0, import_promises2.pipeline)(import_node_stream.Readable.fromWeb(res.body), writer);
4346
- const { statSync } = await import("fs");
4347
- const sizeBytes = statSync(dest).size;
4346
+ const { statSync: statSync2 } = await import("fs");
4347
+ const sizeBytes = statSync2(dest).size;
4348
4348
  return { savedPath: dest, sizeBytes, mimeType };
4349
4349
  }
4350
4350
  async function harvestPageMedia(html, pageUrl, options = {}) {
@@ -15276,321 +15276,7 @@ var PACKAGE_VERSION;
15276
15276
  var init_version = __esm({
15277
15277
  "src/version.ts"() {
15278
15278
  "use strict";
15279
- PACKAGE_VERSION = "0.1.8";
15280
- }
15281
- });
15282
-
15283
- // src/mcp/mcp-tool-schemas.ts
15284
- var import_zod19, HarvestPaaInputSchema, ExtractUrlInputSchema, MapSiteUrlsInputSchema, ExtractSiteInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, FacebookAdTranscribeInputSchema, MapsPlaceIntelInputSchema, MapsSearchInputSchema, NullableString, MapsSearchOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, ExtractUrlOutputSchema, ExtractSiteOutputSchema, MapsPlaceIntelOutputSchema, CreditsInfoOutputSchema, MapSiteUrlsOutputSchema, YoutubeHarvestOutputSchema, FacebookAdSearchOutputSchema, FacebookPageIntelOutputSchema, CreditsInfoInputSchema, SearchSerpInputSchema, CaptureSerpSnapshotInputSchema, ScreenshotInputSchema, CaptureSerpPageSnapshotsInputSchema;
15285
- var init_mcp_tool_schemas = __esm({
15286
- "src/mcp/mcp-tool-schemas.ts"() {
15287
- "use strict";
15288
- import_zod19 = require("zod");
15289
- HarvestPaaInputSchema = {
15290
- query: import_zod19.z.string().min(1).describe('Core search topic only. If the user says "best hvac company in Denver CO", use query="best hvac company" and location="Denver, CO". Do not include the location in query when it can be separated.'),
15291
- location: import_zod19.z.string().optional().describe('City, region, or country for geo-targeted results, inferred from the user request when present, e.g. "Denver, CO", "Tokyo, Japan", "London, UK".'),
15292
- maxQuestions: import_zod19.z.number().int().min(1).max(200).default(30).describe("Number of PAA questions to extract. Default 30. Maximum 200. Use 10 for quick probes, 30 for normal research, 100-200 when the user asks for everything/full/deep research. Larger harvests get a longer server time budget (151-200 questions \u2192 up to 280s). Credits are charged by extracted question; unused request hold is refunded."),
15293
- gl: import_zod19.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."),
15294
- hl: import_zod19.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."),
15295
- device: import_zod19.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
15296
- proxyMode: import_zod19.z.enum(["location", "configured", "none"]).default("location").describe("Proxy targeting mode. Use location by default so city/state searches create or reuse a matching residential proxy. Use configured for the static configured proxy. Use none only for direct-network debugging."),
15297
- proxyZip: import_zod19.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use only when the user gives a specific ZIP or city-center proxy targeting needs to be forced."),
15298
- debug: import_zod19.z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior.")
15299
- };
15300
- ExtractUrlInputSchema = {
15301
- url: import_zod19.z.string().url().describe("Public http/https URL to extract. Use this when the user provides one specific page URL."),
15302
- screenshot: import_zod19.z.boolean().default(false).describe("Also capture a full-page screenshot of the URL. Saved to ~/Downloads/mcp-scraper/screenshots/ and returned inline. Use when the user asks to see or capture the page visually."),
15303
- screenshotDevice: import_zod19.z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport for screenshot. desktop = 1440\xD7900. mobile = 390\xD7844. Default desktop."),
15304
- extractBranding: import_zod19.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."),
15305
- downloadMedia: import_zod19.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."),
15306
- mediaTypes: import_zod19.z.array(import_zod19.z.enum(["image", "video", "audio"])).default(["image", "video", "audio"]).describe("Which media types to download. Default all three."),
15307
- allowLocal: import_zod19.z.boolean().default(false).describe("Allow localhost and private-network URLs. For local development only.")
15308
- };
15309
- MapSiteUrlsInputSchema = {
15310
- url: import_zod19.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."),
15311
- maxUrls: import_zod19.z.number().int().min(1).max(500).optional().describe("Maximum URLs to discover. Use 100 for normal maps, higher when the user asks for a full inventory.")
15312
- };
15313
- ExtractSiteInputSchema = {
15314
- url: import_zod19.z.string().url().describe("Public website URL or domain to extract across multiple pages. Use when the user asks for a site audit, website crawl, or full-site content/schema extraction."),
15315
- maxPages: import_zod19.z.number().int().min(1).max(50).optional().describe("Maximum pages to extract. Use 50 when the user asks for full results or a complete crawl within MCP limits.")
15316
- };
15317
- YoutubeHarvestInputSchema = {
15318
- mode: import_zod19.z.enum(["search", "channel"]).describe("Use search for topic/keyword requests. Use channel when the user provides @handle, channel ID, or channel URL."),
15319
- query: import_zod19.z.string().optional().describe("Required when mode is search. The YouTube search topic in the user\u2019s words."),
15320
- channelHandle: import_zod19.z.string().optional().describe("YouTube channel handle, channel ID, or URL. Examples: @mkbhd, UC..., https://youtube.com/@mkbhd."),
15321
- maxVideos: import_zod19.z.number().int().min(1).max(500).default(50).describe("Number of videos to return. Default 50. Increase when user asks for full channel/history.")
15322
- };
15323
- YoutubeTranscribeInputSchema = {
15324
- videoId: import_zod19.z.string().min(1).describe("YouTube video ID, e.g. dQw4w9WgXcQ")
15325
- };
15326
- FacebookPageIntelInputSchema = {
15327
- pageId: import_zod19.z.string().optional(),
15328
- libraryId: import_zod19.z.string().optional(),
15329
- query: import_zod19.z.string().optional().describe("Advertiser or brand name when pageId/libraryId is not known. One of pageId, libraryId, or query is required."),
15330
- maxAds: import_zod19.z.number().int().min(1).max(200).default(50),
15331
- country: import_zod19.z.string().length(2).default("US")
15332
- };
15333
- FacebookAdSearchInputSchema = {
15334
- query: import_zod19.z.string().min(1).describe("Advertiser, brand, competitor, niche, or keyword to search in Facebook Ad Library."),
15335
- country: import_zod19.z.string().length(2).default("US"),
15336
- maxResults: import_zod19.z.number().int().min(1).max(20).default(10)
15337
- };
15338
- FacebookAdTranscribeInputSchema = {
15339
- videoUrl: import_zod19.z.string().url().describe("Facebook CDN video URL from a facebook_page_intel result")
15340
- };
15341
- MapsPlaceIntelInputSchema = {
15342
- businessName: import_zod19.z.string().min(1).describe('Business name only. If user says "Elite Roofing Denver CO", use businessName="Elite Roofing" and location="Denver, CO".'),
15343
- location: import_zod19.z.string().min(1).describe('City/region/country where the business should be searched, e.g. "Denver, CO". Infer from the user request when possible.'),
15344
- gl: import_zod19.z.string().length(2).default("us").describe("Google country code inferred from location."),
15345
- hl: import_zod19.z.string().length(2).default("en").describe("Language inferred from user request."),
15346
- includeReviews: import_zod19.z.boolean().default(false).describe("Whether to fetch individual review cards"),
15347
- maxReviews: import_zod19.z.number().int().min(1).max(500).default(50).describe("Max review cards to return (requires includeReviews: true)")
15348
- };
15349
- MapsSearchInputSchema = {
15350
- query: import_zod19.z.string().min(1).describe('Business category, niche, keyword, or search term. If the user says "roofers in Denver CO", use query="roofers" and location="Denver, CO". Do not put the location here when it can be separated.'),
15351
- location: import_zod19.z.string().optional().describe('City, region, country, or service area for the Maps search, e.g. "Denver, CO". Infer from the user request when present.'),
15352
- gl: import_zod19.z.string().length(2).default("us").describe("Google country code inferred from location."),
15353
- hl: import_zod19.z.string().length(2).default("en").describe("Language inferred from user request."),
15354
- maxResults: import_zod19.z.number().int().min(1).max(50).default(10).describe("Number of Google Maps business/profile candidates to return. Default 10. Maximum 50. Use 10 unless the user asks for more.")
15355
- };
15356
- NullableString = import_zod19.z.string().nullable();
15357
- MapsSearchOutputSchema = {
15358
- query: import_zod19.z.string(),
15359
- location: import_zod19.z.string().nullable(),
15360
- searchQuery: import_zod19.z.string(),
15361
- searchUrl: import_zod19.z.string().url(),
15362
- extractedAt: import_zod19.z.string(),
15363
- requestedMaxResults: import_zod19.z.number().int().min(1).max(50),
15364
- resultCount: import_zod19.z.number().int().min(0).max(50),
15365
- results: import_zod19.z.array(import_zod19.z.object({
15366
- position: import_zod19.z.number().int().min(1),
15367
- name: import_zod19.z.string(),
15368
- placeUrl: import_zod19.z.string().url(),
15369
- cid: NullableString,
15370
- cidDecimal: NullableString,
15371
- rating: NullableString,
15372
- reviewCount: NullableString,
15373
- category: NullableString,
15374
- address: NullableString,
15375
- websiteUrl: NullableString,
15376
- directionsUrl: NullableString,
15377
- metadata: import_zod19.z.array(import_zod19.z.string())
15378
- })),
15379
- durationMs: import_zod19.z.number().int().min(0)
15380
- };
15381
- OrganicResultOutput = import_zod19.z.object({
15382
- position: import_zod19.z.number().int(),
15383
- title: import_zod19.z.string(),
15384
- url: import_zod19.z.string(),
15385
- domain: import_zod19.z.string(),
15386
- snippet: NullableString
15387
- });
15388
- AiOverviewOutput = import_zod19.z.object({
15389
- detected: import_zod19.z.boolean(),
15390
- text: NullableString
15391
- }).nullable();
15392
- EntityIdsOutput = import_zod19.z.object({
15393
- kgIds: import_zod19.z.array(import_zod19.z.string()),
15394
- cids: import_zod19.z.array(import_zod19.z.string()),
15395
- gcids: import_zod19.z.array(import_zod19.z.string())
15396
- }).nullable();
15397
- HarvestPaaOutputSchema = {
15398
- query: import_zod19.z.string(),
15399
- location: NullableString,
15400
- questionCount: import_zod19.z.number().int().min(0),
15401
- completionStatus: NullableString,
15402
- questions: import_zod19.z.array(import_zod19.z.object({
15403
- question: import_zod19.z.string(),
15404
- answer: NullableString,
15405
- sourceTitle: NullableString,
15406
- sourceSite: NullableString
15407
- })),
15408
- organicResults: import_zod19.z.array(OrganicResultOutput),
15409
- aiOverview: AiOverviewOutput,
15410
- entityIds: EntityIdsOutput,
15411
- durationMs: import_zod19.z.number().min(0).nullable()
15412
- };
15413
- SearchSerpOutputSchema = {
15414
- query: import_zod19.z.string(),
15415
- location: NullableString,
15416
- organicResults: import_zod19.z.array(OrganicResultOutput),
15417
- localPack: import_zod19.z.array(import_zod19.z.object({
15418
- position: import_zod19.z.number().int(),
15419
- name: import_zod19.z.string(),
15420
- rating: NullableString,
15421
- reviewCount: NullableString,
15422
- websiteUrl: NullableString
15423
- })),
15424
- aiOverview: AiOverviewOutput,
15425
- entityIds: EntityIdsOutput
15426
- };
15427
- ExtractUrlOutputSchema = {
15428
- url: import_zod19.z.string(),
15429
- title: NullableString,
15430
- headings: import_zod19.z.array(import_zod19.z.object({
15431
- level: import_zod19.z.number().int(),
15432
- text: import_zod19.z.string()
15433
- })),
15434
- schemaBlockCount: import_zod19.z.number().int().min(0),
15435
- entityName: NullableString,
15436
- entityTypes: import_zod19.z.array(import_zod19.z.string()),
15437
- napScore: import_zod19.z.number().nullable(),
15438
- missingSchemaFields: import_zod19.z.array(import_zod19.z.string()),
15439
- screenshotSaved: NullableString
15440
- };
15441
- ExtractSiteOutputSchema = {
15442
- url: import_zod19.z.string(),
15443
- pageCount: import_zod19.z.number().int().min(0),
15444
- pages: import_zod19.z.array(import_zod19.z.object({
15445
- url: import_zod19.z.string(),
15446
- title: NullableString,
15447
- schemaTypes: import_zod19.z.array(import_zod19.z.string())
15448
- })),
15449
- durationMs: import_zod19.z.number().min(0)
15450
- };
15451
- MapsPlaceIntelOutputSchema = {
15452
- name: import_zod19.z.string(),
15453
- rating: NullableString,
15454
- reviewCount: NullableString,
15455
- category: NullableString,
15456
- address: NullableString,
15457
- phone: NullableString,
15458
- website: NullableString,
15459
- hoursSummary: NullableString,
15460
- bookingUrl: NullableString,
15461
- kgmid: NullableString,
15462
- cidDecimal: NullableString,
15463
- cidUrl: NullableString,
15464
- lat: import_zod19.z.number().nullable(),
15465
- lng: import_zod19.z.number().nullable(),
15466
- reviewsStatus: import_zod19.z.string(),
15467
- reviewsCollected: import_zod19.z.number().int().min(0),
15468
- reviewTopics: import_zod19.z.array(import_zod19.z.object({
15469
- label: import_zod19.z.string(),
15470
- count: import_zod19.z.string()
15471
- }))
15472
- };
15473
- CreditsInfoOutputSchema = {
15474
- balanceCredits: import_zod19.z.number().nullable(),
15475
- matchedCost: import_zod19.z.object({
15476
- label: import_zod19.z.string(),
15477
- credits: import_zod19.z.number(),
15478
- unit: import_zod19.z.string(),
15479
- notes: NullableString
15480
- }).nullable(),
15481
- costs: import_zod19.z.array(import_zod19.z.object({
15482
- key: import_zod19.z.string(),
15483
- label: import_zod19.z.string(),
15484
- credits: import_zod19.z.number(),
15485
- unit: import_zod19.z.string(),
15486
- notes: NullableString
15487
- })),
15488
- ledger: import_zod19.z.array(import_zod19.z.object({
15489
- createdAt: import_zod19.z.string(),
15490
- operation: import_zod19.z.string(),
15491
- credits: import_zod19.z.number(),
15492
- description: NullableString
15493
- }))
15494
- };
15495
- MapSiteUrlsOutputSchema = {
15496
- startUrl: import_zod19.z.string(),
15497
- totalFound: import_zod19.z.number().int().min(0),
15498
- truncated: import_zod19.z.boolean(),
15499
- okCount: import_zod19.z.number().int().min(0),
15500
- redirectCount: import_zod19.z.number().int().min(0),
15501
- brokenCount: import_zod19.z.number().int().min(0),
15502
- urls: import_zod19.z.array(import_zod19.z.object({
15503
- url: import_zod19.z.string(),
15504
- status: import_zod19.z.number().int().nullable()
15505
- })),
15506
- durationMs: import_zod19.z.number().min(0)
15507
- };
15508
- YoutubeHarvestOutputSchema = {
15509
- mode: import_zod19.z.string(),
15510
- videoCount: import_zod19.z.number().int().min(0),
15511
- channel: import_zod19.z.object({
15512
- title: NullableString,
15513
- subscriberCount: NullableString
15514
- }).nullable(),
15515
- videos: import_zod19.z.array(import_zod19.z.object({
15516
- videoId: import_zod19.z.string(),
15517
- title: import_zod19.z.string(),
15518
- channelName: NullableString,
15519
- views: NullableString,
15520
- duration: NullableString,
15521
- url: NullableString
15522
- }))
15523
- };
15524
- FacebookAdSearchOutputSchema = {
15525
- query: import_zod19.z.string(),
15526
- advertiserCount: import_zod19.z.number().int().min(0),
15527
- advertisers: import_zod19.z.array(import_zod19.z.object({
15528
- name: NullableString,
15529
- adCount: import_zod19.z.number().int().nullable(),
15530
- libraryId: NullableString
15531
- }))
15532
- };
15533
- FacebookPageIntelOutputSchema = {
15534
- advertiserName: NullableString,
15535
- totalAds: import_zod19.z.number().int().min(0),
15536
- activeCount: import_zod19.z.number().int().min(0),
15537
- videoCount: import_zod19.z.number().int().min(0),
15538
- imageCount: import_zod19.z.number().int().min(0),
15539
- ads: import_zod19.z.array(import_zod19.z.object({
15540
- libraryId: NullableString,
15541
- status: NullableString,
15542
- creativeType: NullableString,
15543
- headline: NullableString,
15544
- cta: NullableString,
15545
- startDate: NullableString,
15546
- videoUrl: NullableString,
15547
- variations: import_zod19.z.number().int().nullable()
15548
- }))
15549
- };
15550
- CreditsInfoInputSchema = {
15551
- item: import_zod19.z.string().optional().describe('Optional tool, action, or feature to look up, e.g. "maps reviews", "extract_url", or "YouTube transcription"'),
15552
- includeLedger: import_zod19.z.boolean().default(false).describe("Whether to include recent credit ledger entries")
15553
- };
15554
- SearchSerpInputSchema = {
15555
- query: import_zod19.z.string().min(1).describe('Core search topic only. Separate location when possible. If user says "best dentist in Brooklyn NY serp", use query="best dentist" and location="Brooklyn, NY".'),
15556
- location: import_zod19.z.string().optional().describe("City, region, or country for geo-targeted results, inferred from user request when present."),
15557
- gl: import_zod19.z.string().length(2).default("us").describe("Google country code inferred from location or user language."),
15558
- hl: import_zod19.z.string().default("en").describe("Google interface/content language inferred from user request."),
15559
- device: import_zod19.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
15560
- proxyMode: import_zod19.z.enum(["location", "configured", "none"]).default("location").describe("Proxy targeting mode. Use location by default so city/state searches create or reuse a matching residential proxy. Use configured for the static configured proxy. Use none only for direct-network debugging."),
15561
- proxyZip: import_zod19.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use only when the user gives a specific ZIP or city-center proxy targeting needs to be forced."),
15562
- debug: import_zod19.z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior."),
15563
- pages: import_zod19.z.number().int().min(1).max(2).default(1).describe("Number of result pages to fetch (1\u20132)")
15564
- };
15565
- CaptureSerpSnapshotInputSchema = {
15566
- query: import_zod19.z.string().min(1).describe("Core search query to capture as a structured SERP Intelligence snapshot. Separate the place into location when the user gives a city, region, country, or ZIP."),
15567
- location: import_zod19.z.string().optional().describe("City, region, country, or service area used for localized Google results. MCP Scraper records location evidence; UULE alone is not proof of localization."),
15568
- gl: import_zod19.z.string().length(2).default("us").describe("Google country code inferred from the requested market, e.g. us, gb, ca, au."),
15569
- hl: import_zod19.z.string().default("en").describe("Google interface/content language inferred from the user request."),
15570
- device: import_zod19.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only when the user asks for mobile rankings or mobile SERP evidence."),
15571
- proxyMode: import_zod19.z.enum(["location", "configured", "none"]).default("location").describe("Proxy behavior for capture. Use location for localized residential proxy targeting, configured for the static residential proxy, and none only for direct-network debugging."),
15572
- proxyZip: import_zod19.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting when a precise city-center or ZIP proxy is needed."),
15573
- pages: import_zod19.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."),
15574
- debug: import_zod19.z.boolean().default(false).describe("Include sanitized browser, proxy, and location diagnostics. Use true when debugging localization, CAPTCHA, proxy selection, or capture reliability."),
15575
- includePageSnapshots: import_zod19.z.boolean().default(false).describe("Also capture ranking-page snapshots for selected SERP URLs through the same product capture path."),
15576
- pageSnapshotLimit: import_zod19.z.number().int().min(0).max(10).default(0).describe("Maximum ranking-page snapshots to capture when includePageSnapshots is true. Use 0 when only SERP evidence is needed.")
15577
- };
15578
- ScreenshotInputSchema = {
15579
- url: import_zod19.z.string().url().describe("URL to capture as a full-page screenshot. Use http or https. Pass allowLocal: true to capture localhost or private-network URLs during development."),
15580
- device: import_zod19.z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport profile. desktop = 1440\xD7900. mobile = 390\xD7844. Use desktop by default; use mobile when the user asks for a mobile view."),
15581
- allowLocal: import_zod19.z.boolean().default(false).describe("Allow localhost and private-network URLs (127.x, 192.168.x, 10.x, etc.). For local development only \u2014 not for production use.")
15582
- };
15583
- CaptureSerpPageSnapshotsInputSchema = {
15584
- urls: import_zod19.z.array(import_zod19.z.string().url()).min(1).max(25).describe("Public HTTP/HTTPS URLs to capture as SERP Intelligence page snapshots. Do not pass localhost, private IPs, file URLs, or internal admin URLs."),
15585
- targets: import_zod19.z.array(import_zod19.z.object({
15586
- url: import_zod19.z.string().url().describe("Public HTTP/HTTPS URL to capture."),
15587
- sourceKind: import_zod19.z.enum(["organic", "ai_citation", "local_pack_website", "configured_target", "site_subject"]).default("configured_target").describe("Why this page is being captured for SERP Intelligence evidence."),
15588
- sourcePosition: import_zod19.z.number().int().min(1).optional().describe("Ranking or citation position when the page came from SERP evidence.")
15589
- }).strict()).min(1).max(25).optional().describe("Structured page snapshot targets. Use this instead of urls when source kind or position should be preserved."),
15590
- maxConcurrency: import_zod19.z.number().int().min(1).max(5).default(2).describe("Parallel page captures. Use 2 normally; higher values can increase proxy/browser pressure."),
15591
- timeoutMs: import_zod19.z.number().int().min(1e3).max(6e4).default(15e3).describe("Per-page capture timeout in milliseconds. Increase for slow pages; timeout artifacts are returned as structured capture failures."),
15592
- debug: import_zod19.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics for page snapshot debugging. Use true for capture, network, or proxy troubleshooting.")
15593
- };
15279
+ PACKAGE_VERSION = "0.1.9";
15594
15280
  }
15595
15281
  });
15596
15282
 
@@ -16461,6 +16147,320 @@ var init_mcp_response_formatter = __esm({
16461
16147
  }
16462
16148
  });
16463
16149
 
16150
+ // src/mcp/mcp-tool-schemas.ts
16151
+ var import_zod19, HarvestPaaInputSchema, ExtractUrlInputSchema, MapSiteUrlsInputSchema, ExtractSiteInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, FacebookAdTranscribeInputSchema, MapsPlaceIntelInputSchema, MapsSearchInputSchema, NullableString, MapsSearchOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, ExtractUrlOutputSchema, ExtractSiteOutputSchema, MapsPlaceIntelOutputSchema, CreditsInfoOutputSchema, MapSiteUrlsOutputSchema, YoutubeHarvestOutputSchema, FacebookAdSearchOutputSchema, FacebookPageIntelOutputSchema, CreditsInfoInputSchema, SearchSerpInputSchema, CaptureSerpSnapshotInputSchema, ScreenshotInputSchema, CaptureSerpPageSnapshotsInputSchema;
16152
+ var init_mcp_tool_schemas = __esm({
16153
+ "src/mcp/mcp-tool-schemas.ts"() {
16154
+ "use strict";
16155
+ import_zod19 = require("zod");
16156
+ HarvestPaaInputSchema = {
16157
+ query: import_zod19.z.string().min(1).describe('Core search topic only. If the user says "best hvac company in Denver CO", use query="best hvac company" and location="Denver, CO". Do not include the location in query when it can be separated.'),
16158
+ location: import_zod19.z.string().optional().describe('City, region, or country for geo-targeted results, inferred from the user request when present, e.g. "Denver, CO", "Tokyo, Japan", "London, UK".'),
16159
+ maxQuestions: import_zod19.z.number().int().min(1).max(200).default(30).describe("Number of PAA questions to extract. Default 30. Maximum 200. Use 10 for quick probes, 30 for normal research, 100-200 when the user asks for everything/full/deep research. Larger harvests get a longer server time budget (151-200 questions \u2192 up to 280s). Credits are charged by extracted question; unused request hold is refunded."),
16160
+ gl: import_zod19.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."),
16161
+ hl: import_zod19.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."),
16162
+ device: import_zod19.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
16163
+ proxyMode: import_zod19.z.enum(["location", "configured", "none"]).default("location").describe("Proxy targeting mode. Use location by default so city/state searches create or reuse a matching residential proxy. Use configured for the static configured proxy. Use none only for direct-network debugging."),
16164
+ proxyZip: import_zod19.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use only when the user gives a specific ZIP or city-center proxy targeting needs to be forced."),
16165
+ debug: import_zod19.z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior.")
16166
+ };
16167
+ ExtractUrlInputSchema = {
16168
+ url: import_zod19.z.string().url().describe("Public http/https URL to extract. Use this when the user provides one specific page URL."),
16169
+ screenshot: import_zod19.z.boolean().default(false).describe("Also capture a full-page screenshot of the URL. Saved to ~/Downloads/mcp-scraper/screenshots/ and returned inline. Use when the user asks to see or capture the page visually."),
16170
+ screenshotDevice: import_zod19.z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport for screenshot. desktop = 1440\xD7900. mobile = 390\xD7844. Default desktop."),
16171
+ extractBranding: import_zod19.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."),
16172
+ downloadMedia: import_zod19.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."),
16173
+ mediaTypes: import_zod19.z.array(import_zod19.z.enum(["image", "video", "audio"])).default(["image", "video", "audio"]).describe("Which media types to download. Default all three."),
16174
+ allowLocal: import_zod19.z.boolean().default(false).describe("Allow localhost and private-network URLs. For local development only.")
16175
+ };
16176
+ MapSiteUrlsInputSchema = {
16177
+ url: import_zod19.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."),
16178
+ maxUrls: import_zod19.z.number().int().min(1).max(500).optional().describe("Maximum URLs to discover. Use 100 for normal maps, higher when the user asks for a full inventory.")
16179
+ };
16180
+ ExtractSiteInputSchema = {
16181
+ url: import_zod19.z.string().url().describe("Public website URL or domain to extract across multiple pages. Use when the user asks for a site audit, website crawl, or full-site content/schema extraction."),
16182
+ maxPages: import_zod19.z.number().int().min(1).max(50).optional().describe("Maximum pages to extract. Use 50 when the user asks for full results or a complete crawl within MCP limits.")
16183
+ };
16184
+ YoutubeHarvestInputSchema = {
16185
+ mode: import_zod19.z.enum(["search", "channel"]).describe("Use search for topic/keyword requests. Use channel when the user provides @handle, channel ID, or channel URL."),
16186
+ query: import_zod19.z.string().optional().describe("Required when mode is search. The YouTube search topic in the user\u2019s words."),
16187
+ channelHandle: import_zod19.z.string().optional().describe("YouTube channel handle, channel ID, or URL. Examples: @mkbhd, UC..., https://youtube.com/@mkbhd."),
16188
+ maxVideos: import_zod19.z.number().int().min(1).max(500).default(50).describe("Number of videos to return. Default 50. Increase when user asks for full channel/history.")
16189
+ };
16190
+ YoutubeTranscribeInputSchema = {
16191
+ videoId: import_zod19.z.string().min(1).describe("YouTube video ID, e.g. dQw4w9WgXcQ")
16192
+ };
16193
+ FacebookPageIntelInputSchema = {
16194
+ pageId: import_zod19.z.string().optional(),
16195
+ libraryId: import_zod19.z.string().optional(),
16196
+ query: import_zod19.z.string().optional().describe("Advertiser or brand name when pageId/libraryId is not known. One of pageId, libraryId, or query is required."),
16197
+ maxAds: import_zod19.z.number().int().min(1).max(200).default(50),
16198
+ country: import_zod19.z.string().length(2).default("US")
16199
+ };
16200
+ FacebookAdSearchInputSchema = {
16201
+ query: import_zod19.z.string().min(1).describe("Advertiser, brand, competitor, niche, or keyword to search in Facebook Ad Library."),
16202
+ country: import_zod19.z.string().length(2).default("US"),
16203
+ maxResults: import_zod19.z.number().int().min(1).max(20).default(10)
16204
+ };
16205
+ FacebookAdTranscribeInputSchema = {
16206
+ videoUrl: import_zod19.z.string().url().describe("Facebook CDN video URL from a facebook_page_intel result")
16207
+ };
16208
+ MapsPlaceIntelInputSchema = {
16209
+ businessName: import_zod19.z.string().min(1).describe('Business name only. If user says "Elite Roofing Denver CO", use businessName="Elite Roofing" and location="Denver, CO".'),
16210
+ location: import_zod19.z.string().min(1).describe('City/region/country where the business should be searched, e.g. "Denver, CO". Infer from the user request when possible.'),
16211
+ gl: import_zod19.z.string().length(2).default("us").describe("Google country code inferred from location."),
16212
+ hl: import_zod19.z.string().length(2).default("en").describe("Language inferred from user request."),
16213
+ includeReviews: import_zod19.z.boolean().default(false).describe("Whether to fetch individual review cards"),
16214
+ maxReviews: import_zod19.z.number().int().min(1).max(500).default(50).describe("Max review cards to return (requires includeReviews: true)")
16215
+ };
16216
+ MapsSearchInputSchema = {
16217
+ query: import_zod19.z.string().min(1).describe('Business category, niche, keyword, or search term. If the user says "roofers in Denver CO", use query="roofers" and location="Denver, CO". Do not put the location here when it can be separated.'),
16218
+ location: import_zod19.z.string().optional().describe('City, region, country, or service area for the Maps search, e.g. "Denver, CO". Infer from the user request when present.'),
16219
+ gl: import_zod19.z.string().length(2).default("us").describe("Google country code inferred from location."),
16220
+ hl: import_zod19.z.string().length(2).default("en").describe("Language inferred from user request."),
16221
+ maxResults: import_zod19.z.number().int().min(1).max(50).default(10).describe("Number of Google Maps business/profile candidates to return. Default 10. Maximum 50. Use 10 unless the user asks for more.")
16222
+ };
16223
+ NullableString = import_zod19.z.string().nullable();
16224
+ MapsSearchOutputSchema = {
16225
+ query: import_zod19.z.string(),
16226
+ location: import_zod19.z.string().nullable(),
16227
+ searchQuery: import_zod19.z.string(),
16228
+ searchUrl: import_zod19.z.string().url(),
16229
+ extractedAt: import_zod19.z.string(),
16230
+ requestedMaxResults: import_zod19.z.number().int().min(1).max(50),
16231
+ resultCount: import_zod19.z.number().int().min(0).max(50),
16232
+ results: import_zod19.z.array(import_zod19.z.object({
16233
+ position: import_zod19.z.number().int().min(1),
16234
+ name: import_zod19.z.string(),
16235
+ placeUrl: import_zod19.z.string().url(),
16236
+ cid: NullableString,
16237
+ cidDecimal: NullableString,
16238
+ rating: NullableString,
16239
+ reviewCount: NullableString,
16240
+ category: NullableString,
16241
+ address: NullableString,
16242
+ websiteUrl: NullableString,
16243
+ directionsUrl: NullableString,
16244
+ metadata: import_zod19.z.array(import_zod19.z.string())
16245
+ })),
16246
+ durationMs: import_zod19.z.number().int().min(0)
16247
+ };
16248
+ OrganicResultOutput = import_zod19.z.object({
16249
+ position: import_zod19.z.number().int(),
16250
+ title: import_zod19.z.string(),
16251
+ url: import_zod19.z.string(),
16252
+ domain: import_zod19.z.string(),
16253
+ snippet: NullableString
16254
+ });
16255
+ AiOverviewOutput = import_zod19.z.object({
16256
+ detected: import_zod19.z.boolean(),
16257
+ text: NullableString
16258
+ }).nullable();
16259
+ EntityIdsOutput = import_zod19.z.object({
16260
+ kgIds: import_zod19.z.array(import_zod19.z.string()),
16261
+ cids: import_zod19.z.array(import_zod19.z.string()),
16262
+ gcids: import_zod19.z.array(import_zod19.z.string())
16263
+ }).nullable();
16264
+ HarvestPaaOutputSchema = {
16265
+ query: import_zod19.z.string(),
16266
+ location: NullableString,
16267
+ questionCount: import_zod19.z.number().int().min(0),
16268
+ completionStatus: NullableString,
16269
+ questions: import_zod19.z.array(import_zod19.z.object({
16270
+ question: import_zod19.z.string(),
16271
+ answer: NullableString,
16272
+ sourceTitle: NullableString,
16273
+ sourceSite: NullableString
16274
+ })),
16275
+ organicResults: import_zod19.z.array(OrganicResultOutput),
16276
+ aiOverview: AiOverviewOutput,
16277
+ entityIds: EntityIdsOutput,
16278
+ durationMs: import_zod19.z.number().min(0).nullable()
16279
+ };
16280
+ SearchSerpOutputSchema = {
16281
+ query: import_zod19.z.string(),
16282
+ location: NullableString,
16283
+ organicResults: import_zod19.z.array(OrganicResultOutput),
16284
+ localPack: import_zod19.z.array(import_zod19.z.object({
16285
+ position: import_zod19.z.number().int(),
16286
+ name: import_zod19.z.string(),
16287
+ rating: NullableString,
16288
+ reviewCount: NullableString,
16289
+ websiteUrl: NullableString
16290
+ })),
16291
+ aiOverview: AiOverviewOutput,
16292
+ entityIds: EntityIdsOutput
16293
+ };
16294
+ ExtractUrlOutputSchema = {
16295
+ url: import_zod19.z.string(),
16296
+ title: NullableString,
16297
+ headings: import_zod19.z.array(import_zod19.z.object({
16298
+ level: import_zod19.z.number().int(),
16299
+ text: import_zod19.z.string()
16300
+ })),
16301
+ schemaBlockCount: import_zod19.z.number().int().min(0),
16302
+ entityName: NullableString,
16303
+ entityTypes: import_zod19.z.array(import_zod19.z.string()),
16304
+ napScore: import_zod19.z.number().nullable(),
16305
+ missingSchemaFields: import_zod19.z.array(import_zod19.z.string()),
16306
+ screenshotSaved: NullableString
16307
+ };
16308
+ ExtractSiteOutputSchema = {
16309
+ url: import_zod19.z.string(),
16310
+ pageCount: import_zod19.z.number().int().min(0),
16311
+ pages: import_zod19.z.array(import_zod19.z.object({
16312
+ url: import_zod19.z.string(),
16313
+ title: NullableString,
16314
+ schemaTypes: import_zod19.z.array(import_zod19.z.string())
16315
+ })),
16316
+ durationMs: import_zod19.z.number().min(0)
16317
+ };
16318
+ MapsPlaceIntelOutputSchema = {
16319
+ name: import_zod19.z.string(),
16320
+ rating: NullableString,
16321
+ reviewCount: NullableString,
16322
+ category: NullableString,
16323
+ address: NullableString,
16324
+ phone: NullableString,
16325
+ website: NullableString,
16326
+ hoursSummary: NullableString,
16327
+ bookingUrl: NullableString,
16328
+ kgmid: NullableString,
16329
+ cidDecimal: NullableString,
16330
+ cidUrl: NullableString,
16331
+ lat: import_zod19.z.number().nullable(),
16332
+ lng: import_zod19.z.number().nullable(),
16333
+ reviewsStatus: import_zod19.z.string(),
16334
+ reviewsCollected: import_zod19.z.number().int().min(0),
16335
+ reviewTopics: import_zod19.z.array(import_zod19.z.object({
16336
+ label: import_zod19.z.string(),
16337
+ count: import_zod19.z.string()
16338
+ }))
16339
+ };
16340
+ CreditsInfoOutputSchema = {
16341
+ balanceCredits: import_zod19.z.number().nullable(),
16342
+ matchedCost: import_zod19.z.object({
16343
+ label: import_zod19.z.string(),
16344
+ credits: import_zod19.z.number(),
16345
+ unit: import_zod19.z.string(),
16346
+ notes: NullableString
16347
+ }).nullable(),
16348
+ costs: import_zod19.z.array(import_zod19.z.object({
16349
+ key: import_zod19.z.string(),
16350
+ label: import_zod19.z.string(),
16351
+ credits: import_zod19.z.number(),
16352
+ unit: import_zod19.z.string(),
16353
+ notes: NullableString
16354
+ })),
16355
+ ledger: import_zod19.z.array(import_zod19.z.object({
16356
+ createdAt: import_zod19.z.string(),
16357
+ operation: import_zod19.z.string(),
16358
+ credits: import_zod19.z.number(),
16359
+ description: NullableString
16360
+ }))
16361
+ };
16362
+ MapSiteUrlsOutputSchema = {
16363
+ startUrl: import_zod19.z.string(),
16364
+ totalFound: import_zod19.z.number().int().min(0),
16365
+ truncated: import_zod19.z.boolean(),
16366
+ okCount: import_zod19.z.number().int().min(0),
16367
+ redirectCount: import_zod19.z.number().int().min(0),
16368
+ brokenCount: import_zod19.z.number().int().min(0),
16369
+ urls: import_zod19.z.array(import_zod19.z.object({
16370
+ url: import_zod19.z.string(),
16371
+ status: import_zod19.z.number().int().nullable()
16372
+ })),
16373
+ durationMs: import_zod19.z.number().min(0)
16374
+ };
16375
+ YoutubeHarvestOutputSchema = {
16376
+ mode: import_zod19.z.string(),
16377
+ videoCount: import_zod19.z.number().int().min(0),
16378
+ channel: import_zod19.z.object({
16379
+ title: NullableString,
16380
+ subscriberCount: NullableString
16381
+ }).nullable(),
16382
+ videos: import_zod19.z.array(import_zod19.z.object({
16383
+ videoId: import_zod19.z.string(),
16384
+ title: import_zod19.z.string(),
16385
+ channelName: NullableString,
16386
+ views: NullableString,
16387
+ duration: NullableString,
16388
+ url: NullableString
16389
+ }))
16390
+ };
16391
+ FacebookAdSearchOutputSchema = {
16392
+ query: import_zod19.z.string(),
16393
+ advertiserCount: import_zod19.z.number().int().min(0),
16394
+ advertisers: import_zod19.z.array(import_zod19.z.object({
16395
+ name: NullableString,
16396
+ adCount: import_zod19.z.number().int().nullable(),
16397
+ libraryId: NullableString
16398
+ }))
16399
+ };
16400
+ FacebookPageIntelOutputSchema = {
16401
+ advertiserName: NullableString,
16402
+ totalAds: import_zod19.z.number().int().min(0),
16403
+ activeCount: import_zod19.z.number().int().min(0),
16404
+ videoCount: import_zod19.z.number().int().min(0),
16405
+ imageCount: import_zod19.z.number().int().min(0),
16406
+ ads: import_zod19.z.array(import_zod19.z.object({
16407
+ libraryId: NullableString,
16408
+ status: NullableString,
16409
+ creativeType: NullableString,
16410
+ headline: NullableString,
16411
+ cta: NullableString,
16412
+ startDate: NullableString,
16413
+ videoUrl: NullableString,
16414
+ variations: import_zod19.z.number().int().nullable()
16415
+ }))
16416
+ };
16417
+ CreditsInfoInputSchema = {
16418
+ item: import_zod19.z.string().optional().describe('Optional tool, action, or feature to look up, e.g. "maps reviews", "extract_url", or "YouTube transcription"'),
16419
+ includeLedger: import_zod19.z.boolean().default(false).describe("Whether to include recent credit ledger entries")
16420
+ };
16421
+ SearchSerpInputSchema = {
16422
+ query: import_zod19.z.string().min(1).describe('Core search topic only. Separate location when possible. If user says "best dentist in Brooklyn NY serp", use query="best dentist" and location="Brooklyn, NY".'),
16423
+ location: import_zod19.z.string().optional().describe("City, region, or country for geo-targeted results, inferred from user request when present."),
16424
+ gl: import_zod19.z.string().length(2).default("us").describe("Google country code inferred from location or user language."),
16425
+ hl: import_zod19.z.string().default("en").describe("Google interface/content language inferred from user request."),
16426
+ device: import_zod19.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
16427
+ proxyMode: import_zod19.z.enum(["location", "configured", "none"]).default("location").describe("Proxy targeting mode. Use location by default so city/state searches create or reuse a matching residential proxy. Use configured for the static configured proxy. Use none only for direct-network debugging."),
16428
+ proxyZip: import_zod19.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use only when the user gives a specific ZIP or city-center proxy targeting needs to be forced."),
16429
+ debug: import_zod19.z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior."),
16430
+ pages: import_zod19.z.number().int().min(1).max(2).default(1).describe("Number of result pages to fetch (1\u20132)")
16431
+ };
16432
+ CaptureSerpSnapshotInputSchema = {
16433
+ query: import_zod19.z.string().min(1).describe("Core search query to capture as a structured SERP Intelligence snapshot. Separate the place into location when the user gives a city, region, country, or ZIP."),
16434
+ location: import_zod19.z.string().optional().describe("City, region, country, or service area used for localized Google results. MCP Scraper records location evidence; UULE alone is not proof of localization."),
16435
+ gl: import_zod19.z.string().length(2).default("us").describe("Google country code inferred from the requested market, e.g. us, gb, ca, au."),
16436
+ hl: import_zod19.z.string().default("en").describe("Google interface/content language inferred from the user request."),
16437
+ device: import_zod19.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only when the user asks for mobile rankings or mobile SERP evidence."),
16438
+ proxyMode: import_zod19.z.enum(["location", "configured", "none"]).default("location").describe("Proxy behavior for capture. Use location for localized residential proxy targeting, configured for the static residential proxy, and none only for direct-network debugging."),
16439
+ proxyZip: import_zod19.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting when a precise city-center or ZIP proxy is needed."),
16440
+ pages: import_zod19.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."),
16441
+ debug: import_zod19.z.boolean().default(false).describe("Include sanitized browser, proxy, and location diagnostics. Use true when debugging localization, CAPTCHA, proxy selection, or capture reliability."),
16442
+ includePageSnapshots: import_zod19.z.boolean().default(false).describe("Also capture ranking-page snapshots for selected SERP URLs through the same product capture path."),
16443
+ pageSnapshotLimit: import_zod19.z.number().int().min(0).max(10).default(0).describe("Maximum ranking-page snapshots to capture when includePageSnapshots is true. Use 0 when only SERP evidence is needed.")
16444
+ };
16445
+ ScreenshotInputSchema = {
16446
+ url: import_zod19.z.string().url().describe("URL to capture as a full-page screenshot. Use http or https. Pass allowLocal: true to capture localhost or private-network URLs during development."),
16447
+ device: import_zod19.z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport profile. desktop = 1440\xD7900. mobile = 390\xD7844. Use desktop by default; use mobile when the user asks for a mobile view."),
16448
+ allowLocal: import_zod19.z.boolean().default(false).describe("Allow localhost and private-network URLs (127.x, 192.168.x, 10.x, etc.). For local development only \u2014 not for production use.")
16449
+ };
16450
+ CaptureSerpPageSnapshotsInputSchema = {
16451
+ urls: import_zod19.z.array(import_zod19.z.string().url()).min(1).max(25).describe("Public HTTP/HTTPS URLs to capture as SERP Intelligence page snapshots. Do not pass localhost, private IPs, file URLs, or internal admin URLs."),
16452
+ targets: import_zod19.z.array(import_zod19.z.object({
16453
+ url: import_zod19.z.string().url().describe("Public HTTP/HTTPS URL to capture."),
16454
+ sourceKind: import_zod19.z.enum(["organic", "ai_citation", "local_pack_website", "configured_target", "site_subject"]).default("configured_target").describe("Why this page is being captured for SERP Intelligence evidence."),
16455
+ sourcePosition: import_zod19.z.number().int().min(1).optional().describe("Ranking or citation position when the page came from SERP evidence.")
16456
+ }).strict()).min(1).max(25).optional().describe("Structured page snapshot targets. Use this instead of urls when source kind or position should be preserved."),
16457
+ maxConcurrency: import_zod19.z.number().int().min(1).max(5).default(2).describe("Parallel page captures. Use 2 normally; higher values can increase proxy/browser pressure."),
16458
+ timeoutMs: import_zod19.z.number().int().min(1e3).max(6e4).default(15e3).describe("Per-page capture timeout in milliseconds. Increase for slow pages; timeout artifacts are returned as structured capture failures."),
16459
+ debug: import_zod19.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics for page snapshot debugging. Use true for capture, network, or proxy troubleshooting.")
16460
+ };
16461
+ }
16462
+ });
16463
+
16464
16464
  // src/mcp/paa-mcp-server.ts
16465
16465
  function liveWebToolAnnotations(title) {
16466
16466
  return {
@@ -16471,14 +16471,49 @@ function liveWebToolAnnotations(title) {
16471
16471
  openWorldHint: true
16472
16472
  };
16473
16473
  }
16474
+ function listSavedReports() {
16475
+ try {
16476
+ const dir = outputBaseDir();
16477
+ return (0, import_node_fs5.readdirSync)(dir).filter((f) => f.endsWith(".md")).map((f) => ({ filename: f, mtimeMs: (0, import_node_fs5.statSync)((0, import_node_path7.join)(dir, f)).mtimeMs })).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, 100);
16478
+ } catch {
16479
+ return [];
16480
+ }
16481
+ }
16482
+ function registerSavedReportResources(server) {
16483
+ server.registerResource(
16484
+ "saved-report",
16485
+ new import_mcp.ResourceTemplate("report://{filename}", {
16486
+ list: () => ({
16487
+ resources: listSavedReports().map((r) => ({
16488
+ uri: `report://${encodeURIComponent(r.filename)}`,
16489
+ name: r.filename,
16490
+ mimeType: "text/markdown"
16491
+ }))
16492
+ })
16493
+ }),
16494
+ {
16495
+ title: "Saved MCP Scraper Reports",
16496
+ description: "Markdown research reports saved by previous MCP Scraper tool calls. Read a report to reuse prior research without re-scraping or spending credits.",
16497
+ mimeType: "text/markdown"
16498
+ },
16499
+ async (uri, variables) => {
16500
+ const requested = Array.isArray(variables.filename) ? variables.filename[0] : variables.filename;
16501
+ const filename = (0, import_node_path7.basename)(decodeURIComponent(String(requested ?? "")));
16502
+ if (!filename.endsWith(".md")) throw new Error("Only saved .md reports can be read");
16503
+ const text = (0, import_node_fs5.readFileSync)((0, import_node_path7.join)(outputBaseDir(), filename), "utf8");
16504
+ return { contents: [{ uri: uri.href, mimeType: "text/markdown", text }] };
16505
+ }
16506
+ );
16507
+ }
16474
16508
  function buildPaaExtractorMcpServer(executor, options = {}) {
16475
16509
  const savesReports = options.savesReportsLocally !== false;
16476
16510
  const reportNote = savesReports ? " Saves a full Markdown report locally." : " Reports are returned inline; no files are saved on this hosted endpoint.";
16477
16511
  const withReportNote = (description) => `${description}${reportNote}`;
16478
16512
  const server = new import_mcp.McpServer({ name: "mcp-scraper", version: PACKAGE_VERSION });
16513
+ if (savesReports) registerSavedReportResources(server);
16479
16514
  server.registerTool("harvest_paa", {
16480
16515
  title: "Google PAA + SERP Harvest",
16481
- 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"). Use maxQuestions 30 normally, 100-150 for "full", "deep", "all", or comprehensive research. Credits are charged by extracted question; unused request hold is refunded.'),
16516
+ 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"). 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.'),
16482
16517
  inputSchema: HarvestPaaInputSchema,
16483
16518
  outputSchema: HarvestPaaOutputSchema,
16484
16519
  annotations: liveWebToolAnnotations("Google PAA + SERP Harvest")
@@ -16573,12 +16608,15 @@ function buildPaaExtractorMcpServer(executor, options = {}) {
16573
16608
  }, async (input) => formatCreditsInfo(await executor.creditsInfo(input), input));
16574
16609
  return server;
16575
16610
  }
16576
- var import_mcp;
16611
+ var import_mcp, import_node_fs5, import_node_path7;
16577
16612
  var init_paa_mcp_server = __esm({
16578
16613
  "src/mcp/paa-mcp-server.ts"() {
16579
16614
  "use strict";
16580
16615
  import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
16616
+ import_node_fs5 = require("fs");
16617
+ import_node_path7 = require("path");
16581
16618
  init_version();
16619
+ init_mcp_response_formatter();
16582
16620
  init_mcp_tool_schemas();
16583
16621
  init_mcp_response_formatter();
16584
16622
  }
@@ -16706,7 +16744,10 @@ function mcpAuthError() {
16706
16744
  });
16707
16745
  return new Response(body, {
16708
16746
  status: 401,
16709
- headers: { "Content-Type": "application/json" }
16747
+ headers: {
16748
+ "Content-Type": "application/json",
16749
+ "WWW-Authenticate": 'Bearer realm="mcp-scraper", error="invalid_token", error_description="Pass an MCP Scraper API key as x-api-key or Bearer token"'
16750
+ }
16710
16751
  });
16711
16752
  }
16712
16753
  async function requireMcpCallerKey(c) {
@@ -18110,10 +18151,10 @@ var init_server = __esm({
18110
18151
  });
18111
18152
 
18112
18153
  // bin/api-server.ts
18113
- var import_node_fs5 = require("fs");
18154
+ var import_node_fs6 = require("fs");
18114
18155
  function loadDotEnv() {
18115
18156
  try {
18116
- for (const line of (0, import_node_fs5.readFileSync)(".env", "utf8").split("\n")) {
18157
+ for (const line of (0, import_node_fs6.readFileSync)(".env", "utf8").split("\n")) {
18117
18158
  const eq = line.indexOf("=");
18118
18159
  if (eq < 1 || line.trimStart().startsWith("#")) continue;
18119
18160
  const k = line.slice(0, eq).trim();