mcp-scraper 0.3.44 → 0.3.46
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 +612 -359
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +3 -3
- package/dist/bin/mcp-scraper-cli.cjs +1 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +1 -1
- 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 +648 -380
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +2 -2
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +2 -2
- package/dist/{chunk-X53SOJSL.js → chunk-BRVX6RDN.js} +2 -2
- package/dist/{chunk-DVRPXPYH.js → chunk-FUVQR6GK.js} +14 -3
- package/dist/chunk-FUVQR6GK.js.map +1 -0
- package/dist/{chunk-GMTS35L6.js → chunk-RXNHY3TF.js} +2 -2
- package/dist/{chunk-NONBSIES.js → chunk-SS5YBZVI.js} +685 -412
- package/dist/chunk-SS5YBZVI.js.map +1 -0
- package/dist/chunk-WT6OT2T3.js +7 -0
- package/dist/chunk-WT6OT2T3.js.map +1 -0
- package/dist/{db-LYQENFPW.js → db-H3S3M6KK.js} +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/{server-AN6QUH6C.js → server-MGE3GYJW.js} +38 -80
- package/dist/server-MGE3GYJW.js.map +1 -0
- package/dist/{worker-H4EUD2Q5.js → worker-4CVMSUZR.js} +4 -4
- package/package.json +1 -1
- package/dist/chunk-3Q2NSHSR.js +0 -7
- package/dist/chunk-3Q2NSHSR.js.map +0 -1
- package/dist/chunk-DVRPXPYH.js.map +0 -1
- package/dist/chunk-NONBSIES.js.map +0 -1
- package/dist/server-AN6QUH6C.js.map +0 -1
- /package/dist/{chunk-X53SOJSL.js.map → chunk-BRVX6RDN.js.map} +0 -0
- /package/dist/{chunk-GMTS35L6.js.map → chunk-RXNHY3TF.js.map} +0 -0
- /package/dist/{db-LYQENFPW.js.map → db-H3S3M6KK.js.map} +0 -0
- /package/dist/{worker-H4EUD2Q5.js.map → worker-4CVMSUZR.js.map} +0 -0
|
@@ -24,9 +24,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// bin/mcp-stdio-server.ts
|
|
27
|
-
var
|
|
28
|
-
var
|
|
29
|
-
var
|
|
27
|
+
var import_node_fs6 = require("fs");
|
|
28
|
+
var import_node_os6 = require("os");
|
|
29
|
+
var import_node_path7 = require("path");
|
|
30
30
|
var import_mcp3 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
31
31
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
32
32
|
|
|
@@ -474,7 +474,7 @@ render();
|
|
|
474
474
|
}
|
|
475
475
|
|
|
476
476
|
// src/version.ts
|
|
477
|
-
var PACKAGE_VERSION = "0.3.
|
|
477
|
+
var PACKAGE_VERSION = "0.3.46";
|
|
478
478
|
|
|
479
479
|
// src/mcp/browser-agent-tool-schemas.ts
|
|
480
480
|
var import_zod = require("zod");
|
|
@@ -494,18 +494,18 @@ var BrowserOpenInputSchema = {
|
|
|
494
494
|
label: import_zod.z.string().optional().describe("Optional human label for this session, shown in the watch console."),
|
|
495
495
|
url: import_zod.z.string().url().optional().describe("Optional URL to navigate to immediately after opening."),
|
|
496
496
|
profile: import_zod.z.string().optional().describe("Optional saved hosted profile name to load a logged-in session for a site."),
|
|
497
|
-
save_profile_changes: import_zod.z.boolean().optional().describe("Persist cookies
|
|
498
|
-
timeout_seconds: import_zod.z.number().int().min(60).max(259200).optional().describe("
|
|
497
|
+
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."),
|
|
498
|
+
timeout_seconds: import_zod.z.number().int().min(60).max(259200).optional().describe("Session lifetime before auto-termination. Defaults to 600.")
|
|
499
499
|
};
|
|
500
500
|
var BrowserProfileConnectInputSchema = {
|
|
501
|
-
email: import_zod.z.string().optional().describe("Account email for the login
|
|
502
|
-
profile: import_zod.z.string().optional().describe("Profile to add this login to. Omit to derive
|
|
503
|
-
domain: import_zod.z.string().optional().describe("Site to log into, e.g. chatgpt.com, claude.ai, reddit.com. Defaults to chatgpt.com.
|
|
504
|
-
login_url: import_zod.z.string().url().optional().describe("Login page for the domain. Defaults to https://<domain
|
|
505
|
-
url: import_zod.z.string().url().optional().describe("Deprecated alias for login_url.
|
|
506
|
-
note: import_zod.z.string().optional().describe(
|
|
501
|
+
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."),
|
|
502
|
+
profile: import_zod.z.string().optional().describe("Profile to add this login to. Omit to derive from email. A single profile holds MANY logins \u2014 pass the same name with a different domain to stack accounts."),
|
|
503
|
+
domain: import_zod.z.string().optional().describe("Site to log into, e.g. chatgpt.com, claude.ai, reddit.com. Defaults to chatgpt.com."),
|
|
504
|
+
login_url: import_zod.z.string().url().optional().describe("Login page for the domain. Defaults to https://<domain>/."),
|
|
505
|
+
url: import_zod.z.string().url().optional().describe("Deprecated alias for login_url."),
|
|
506
|
+
note: import_zod.z.string().optional().describe("Free-text note describing this login. Surfaced by browser_profile_list."),
|
|
507
507
|
label: import_zod.z.string().optional().describe("Optional human label for this sign-in setup session."),
|
|
508
|
-
timeout_seconds: import_zod.z.number().int().min(60).max(259200).optional().describe("
|
|
508
|
+
timeout_seconds: import_zod.z.number().int().min(60).max(259200).optional().describe("Sign-in session lifetime before auto-termination. Defaults to 600.")
|
|
509
509
|
};
|
|
510
510
|
var BrowserProfileListInputSchema = {
|
|
511
511
|
profile: import_zod.z.string().optional().describe("Profile whose saved logins to list. Omit to derive from email."),
|
|
@@ -514,7 +514,7 @@ var BrowserProfileListInputSchema = {
|
|
|
514
514
|
connection_id: import_zod.z.string().optional().describe("A specific login connection id returned by browser_profile_connect, to poll just that one.")
|
|
515
515
|
};
|
|
516
516
|
var BrowserSessionInputSchema = {
|
|
517
|
-
session_id: import_zod.z.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
517
|
+
session_id: import_zod.z.string().describe("The session id returned by browser_open or browser_list_sessions.")
|
|
518
518
|
};
|
|
519
519
|
var BrowserLocateTargetSchema = import_zod.z.object({
|
|
520
520
|
name: import_zod.z.string().optional().describe("Optional label for this target, echoed in the result."),
|
|
@@ -526,94 +526,94 @@ var BrowserLocateTargetSchema = import_zod.z.object({
|
|
|
526
526
|
message: "target requires selector or text"
|
|
527
527
|
});
|
|
528
528
|
var BrowserLocateInputSchema = {
|
|
529
|
-
session_id: import_zod.z.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
529
|
+
session_id: import_zod.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
530
530
|
targets: import_zod.z.array(BrowserLocateTargetSchema).min(1).max(20).describe("DOM targets to locate in the current viewport. Use selectors for exact elements, or text for visible text ranges.")
|
|
531
531
|
};
|
|
532
532
|
var BrowserGotoInputSchema = {
|
|
533
|
-
session_id: import_zod.z.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
533
|
+
session_id: import_zod.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
534
534
|
url: import_zod.z.string().url().describe("URL to navigate the browser to.")
|
|
535
535
|
};
|
|
536
536
|
var BrowserClickInputSchema = {
|
|
537
|
-
session_id: import_zod.z.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
537
|
+
session_id: import_zod.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
538
538
|
x: import_zod.z.number().describe("X coordinate to click, in screenshot pixels. Use only coordinates from the latest browser_screenshot, browser_read, or browser_locate result; do not guess."),
|
|
539
539
|
y: import_zod.z.number().describe("Y coordinate to click, in screenshot pixels. Use only coordinates from the latest browser_screenshot, browser_read, or browser_locate result; do not guess."),
|
|
540
540
|
button: import_zod.z.enum(["left", "right", "middle"]).default("left").describe("Mouse button."),
|
|
541
541
|
num_clicks: import_zod.z.number().int().min(1).max(3).optional().describe("Number of clicks, e.g. 2 for double-click.")
|
|
542
542
|
};
|
|
543
543
|
var BrowserTypeInputSchema = {
|
|
544
|
-
session_id: import_zod.z.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
544
|
+
session_id: import_zod.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
545
545
|
text: import_zod.z.string().describe("Text to type at the current focus. Click a field first to focus it."),
|
|
546
546
|
delay: import_zod.z.number().int().min(0).max(500).optional().describe("Optional per-keystroke delay in ms for human-like typing.")
|
|
547
547
|
};
|
|
548
548
|
var BrowserScrollInputSchema = {
|
|
549
|
-
session_id: import_zod.z.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
549
|
+
session_id: import_zod.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
550
550
|
delta_y: import_zod.z.number().default(5).describe("Vertical scroll in wheel units. Positive scrolls down, negative up."),
|
|
551
551
|
delta_x: import_zod.z.number().default(0).describe("Horizontal scroll in wheel units."),
|
|
552
552
|
x: import_zod.z.number().optional().describe("X position to scroll at. Defaults to screen center."),
|
|
553
553
|
y: import_zod.z.number().optional().describe("Y position to scroll at. Defaults to screen center.")
|
|
554
554
|
};
|
|
555
555
|
var BrowserPressInputSchema = {
|
|
556
|
-
session_id: import_zod.z.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
556
|
+
session_id: import_zod.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
557
557
|
keys: import_zod.z.array(import_zod.z.string()).min(1).describe('Keys or combinations to press, e.g. ["Return"], ["Ctrl+a"], ["Ctrl+Shift+Tab"].')
|
|
558
558
|
};
|
|
559
559
|
var BrowserReplayStopInputSchema = {
|
|
560
|
-
session_id: import_zod.z.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
561
|
-
replay_id: import_zod.z.string().describe("The replay id returned by browser_replay_start or browser_list_replays.
|
|
560
|
+
session_id: import_zod.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
561
|
+
replay_id: import_zod.z.string().describe("The replay id returned by browser_replay_start or browser_list_replays.")
|
|
562
562
|
};
|
|
563
563
|
var BrowserReplayDownloadInputSchema = {
|
|
564
|
-
session_id: import_zod.z.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
565
|
-
replay_id: import_zod.z.string().describe("The replay id returned by browser_replay_start or browser_list_replays.
|
|
564
|
+
session_id: import_zod.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
565
|
+
replay_id: import_zod.z.string().describe("The replay id returned by browser_replay_start or browser_list_replays."),
|
|
566
566
|
filename: import_zod.z.string().optional().describe("Optional local MP4 filename. Defaults to a timestamped replay filename.")
|
|
567
567
|
};
|
|
568
568
|
var BrowserReplayAnnotationSchema = import_zod.z.object({
|
|
569
|
-
type: import_zod.z.enum(["box", "circle", "underline", "arrow", "label"]).default("box").describe("Annotation style
|
|
570
|
-
start_seconds: import_zod.z.number().min(0).default(0).describe("When the annotation should appear
|
|
571
|
-
end_seconds: import_zod.z.number().min(0).optional().describe("When
|
|
572
|
-
left: import_zod.z.number().optional().describe("Target left edge in
|
|
573
|
-
top: import_zod.z.number().optional().describe("Target top edge in
|
|
574
|
-
width: import_zod.z.number().positive().optional().describe("Target width in
|
|
575
|
-
height: import_zod.z.number().positive().optional().describe("Target height in
|
|
576
|
-
x: import_zod.z.number().optional().describe("Point target x coordinate
|
|
577
|
-
y: import_zod.z.number().optional().describe("Point target y coordinate
|
|
578
|
-
from_x: import_zod.z.number().optional().describe("Arrow start x coordinate
|
|
579
|
-
from_y: import_zod.z.number().optional().describe("Arrow start y coordinate
|
|
580
|
-
to_x: import_zod.z.number().optional().describe("Arrow
|
|
581
|
-
to_y: import_zod.z.number().optional().describe("Arrow
|
|
582
|
-
label: import_zod.z.string().max(120).optional().describe("Optional text callout
|
|
583
|
-
color: import_zod.z.string().regex(/^#?[0-9a-fA-F]{6}$/).optional().describe("Annotation color as hex,
|
|
569
|
+
type: import_zod.z.enum(["box", "circle", "underline", "arrow", "label"]).default("box").describe("Annotation style."),
|
|
570
|
+
start_seconds: import_zod.z.number().min(0).default(0).describe("When the annotation should appear."),
|
|
571
|
+
end_seconds: import_zod.z.number().min(0).optional().describe("When it disappears. Defaults to 2s after start_seconds."),
|
|
572
|
+
left: import_zod.z.number().optional().describe("Target left edge in screenshot pixels (element.left)."),
|
|
573
|
+
top: import_zod.z.number().optional().describe("Target top edge in screenshot pixels (element.top)."),
|
|
574
|
+
width: import_zod.z.number().positive().optional().describe("Target width in screenshot pixels (element.width)."),
|
|
575
|
+
height: import_zod.z.number().positive().optional().describe("Target height in screenshot pixels (element.height)."),
|
|
576
|
+
x: import_zod.z.number().optional().describe("Point target x coordinate when no box is available."),
|
|
577
|
+
y: import_zod.z.number().optional().describe("Point target y coordinate when no box is available."),
|
|
578
|
+
from_x: import_zod.z.number().optional().describe("Arrow start x coordinate. Defaults near the target."),
|
|
579
|
+
from_y: import_zod.z.number().optional().describe("Arrow start y coordinate. Defaults near the target."),
|
|
580
|
+
to_x: import_zod.z.number().optional().describe("Arrow end x coordinate. Defaults to the target box center."),
|
|
581
|
+
to_y: import_zod.z.number().optional().describe("Arrow end y coordinate. Defaults to the target box center."),
|
|
582
|
+
label: import_zod.z.string().max(120).optional().describe("Optional text callout."),
|
|
583
|
+
color: import_zod.z.string().regex(/^#?[0-9a-fA-F]{6}$/).optional().describe("Annotation color as hex, e.g. #ff3b30."),
|
|
584
584
|
thickness: import_zod.z.number().min(1).max(24).optional().describe("Stroke thickness in pixels. Defaults to 5.")
|
|
585
585
|
});
|
|
586
586
|
var BrowserReplayMarkInputSchema = {
|
|
587
|
-
session_id: import_zod.z.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
587
|
+
session_id: import_zod.z.string().describe("The session id returned by browser_open or browser_list_sessions. A replay must already be recording."),
|
|
588
588
|
target: BrowserLocateTargetSchema.describe("The exact DOM element or text range to mark in the current viewport."),
|
|
589
|
-
type: import_zod.z.enum(["box", "circle", "underline", "arrow"]).default("box").describe("Annotation style to generate.
|
|
589
|
+
type: import_zod.z.enum(["box", "circle", "underline", "arrow"]).default("box").describe("Annotation style to generate."),
|
|
590
590
|
label: import_zod.z.string().max(120).optional().describe("Optional callout text to render near the target."),
|
|
591
|
-
color: import_zod.z.string().regex(/^#?[0-9a-fA-F]{6}$/).optional().describe("Annotation color as hex,
|
|
591
|
+
color: import_zod.z.string().regex(/^#?[0-9a-fA-F]{6}$/).optional().describe("Annotation color as hex, e.g. #ff3b30."),
|
|
592
592
|
thickness: import_zod.z.number().min(1).max(24).optional().describe("Stroke thickness in pixels. Defaults to 5."),
|
|
593
593
|
padding: import_zod.z.number().min(0).max(80).default(8).describe("Pixels to expand the DOM bounds so the highlight does not touch the text edge."),
|
|
594
|
-
start_offset_seconds: import_zod.z.number().min(-5).max(10).default(-0.25).describe("Offset from the current replay time
|
|
595
|
-
duration_seconds: import_zod.z.number().min(0.5).max(30).default(4).describe("How long the
|
|
594
|
+
start_offset_seconds: import_zod.z.number().min(-5).max(10).default(-0.25).describe("Offset from the current replay time; negative appears just before the mark action."),
|
|
595
|
+
duration_seconds: import_zod.z.number().min(0.5).max(30).default(4).describe("How long the annotation should remain visible.")
|
|
596
596
|
};
|
|
597
597
|
var BrowserReplayAnnotateInputSchema = {
|
|
598
|
-
session_id: import_zod.z.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
599
|
-
replay_id: import_zod.z.string().describe("The replay id returned by browser_replay_start or browser_list_replays.
|
|
600
|
-
annotations: import_zod.z.array(BrowserReplayAnnotationSchema).min(1).max(50).describe("Timed overlay annotations
|
|
601
|
-
filename: import_zod.z.string().optional().describe("Optional output MP4 filename. Defaults to a timestamped
|
|
598
|
+
session_id: import_zod.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
599
|
+
replay_id: import_zod.z.string().describe("The replay id returned by browser_replay_start or browser_list_replays."),
|
|
600
|
+
annotations: import_zod.z.array(BrowserReplayAnnotationSchema).min(1).max(50).describe("Timed overlay annotations. Prefer ones from browser_replay_mark; otherwise use exact DOM bounds from browser_locate."),
|
|
601
|
+
filename: import_zod.z.string().optional().describe("Optional output MP4 filename. Defaults to a timestamped filename."),
|
|
602
602
|
source_width: import_zod.z.number().positive().optional().describe("Width of the screenshot coordinate space used for annotations. Defaults to the replay video width."),
|
|
603
|
-
source_height: import_zod.z.number().positive().optional().describe("Height of the coordinate space
|
|
604
|
-
source_left_offset: import_zod.z.number().min(0).optional().describe("
|
|
605
|
-
source_top_offset: import_zod.z.number().min(0).optional().describe("
|
|
603
|
+
source_height: import_zod.z.number().positive().optional().describe("Height of the annotation coordinate space; if smaller than the replay video height, the browser chrome offset is inferred."),
|
|
604
|
+
source_left_offset: import_zod.z.number().min(0).optional().describe("Explicit X offset from annotation to replay video coordinates. Usually omitted."),
|
|
605
|
+
source_top_offset: import_zod.z.number().min(0).optional().describe("Explicit Y offset from annotation to replay video coordinates. Usually omitted.")
|
|
606
606
|
};
|
|
607
607
|
var BrowserListInputSchema = {
|
|
608
608
|
include_closed: import_zod.z.boolean().default(false).describe("Include closed sessions in the list.")
|
|
609
609
|
};
|
|
610
610
|
var BrowserCaptureFanoutInputSchema = {
|
|
611
|
-
session_id: import_zod.z.string().describe("
|
|
612
|
-
prompt: import_zod.z.string().optional().describe("Optional prompt to type
|
|
613
|
-
wait_ms: import_zod.z.number().int().min(0).max(18e4).optional().describe("How long to wait for the answer stream to finish
|
|
614
|
-
first_party_domain: import_zod.z.string().optional().describe("The brand/site being researched,
|
|
615
|
-
reset: import_zod.z.boolean().default(false).describe("Clear any previously buffered stream for this page before capturing.
|
|
616
|
-
export: import_zod.z.boolean().default(false).describe("
|
|
611
|
+
session_id: import_zod.z.string().describe("Session id from browser_open. Must be on chatgpt.com or claude.ai, logged in via a saved hosted profile."),
|
|
612
|
+
prompt: import_zod.z.string().optional().describe("Optional prompt to type and submit before capturing. Omit to passively capture a prompt the user just ran. Must trigger web search to produce a fan-out."),
|
|
613
|
+
wait_ms: import_zod.z.number().int().min(0).max(18e4).optional().describe("How long to wait for the answer stream to finish. Defaults to 90000 when a prompt is sent, 8000 for passive capture."),
|
|
614
|
+
first_party_domain: import_zod.z.string().optional().describe("The brand/site being researched, e.g. example.com \u2014 sources on this domain are tagged First-party/vendor."),
|
|
615
|
+
reset: import_zod.z.boolean().default(false).describe("Clear any previously buffered stream for this page before capturing."),
|
|
616
|
+
export: import_zod.z.boolean().default(false).describe("Write JSON/CSV/TSV/HTML exports to MCP_SCRAPER_OUTPUT_DIR/fanout, returning relative paths.")
|
|
617
617
|
};
|
|
618
618
|
var FanoutSourceOutput = import_zod.z.object({
|
|
619
619
|
url: import_zod.z.string(),
|
|
@@ -637,10 +637,10 @@ var BrowserCaptureFanoutOutputSchema = {
|
|
|
637
637
|
title: import_zod.z.string(),
|
|
638
638
|
rounds: import_zod.z.number().int().min(0)
|
|
639
639
|
}),
|
|
640
|
-
queries: import_zod.z.array(import_zod.z.string()).describe("Every web-search sub-query
|
|
641
|
-
browsed_urls: import_zod.z.array(FanoutSourceOutput).describe("Every researched URL
|
|
642
|
-
cited_urls: import_zod.z.array(FanoutSourceOutput).describe("
|
|
643
|
-
browsed_only: import_zod.z.array(FanoutSourceOutput).describe("Researched URLs
|
|
640
|
+
queries: import_zod.z.array(import_zod.z.string()).describe("Every web-search sub-query issued, in capture order."),
|
|
641
|
+
browsed_urls: import_zod.z.array(FanoutSourceOutput).describe("Every researched URL, cited first."),
|
|
642
|
+
cited_urls: import_zod.z.array(FanoutSourceOutput).describe("Researched URLs cited in the final answer."),
|
|
643
|
+
browsed_only: import_zod.z.array(FanoutSourceOutput).describe("Researched URLs pulled but not cited."),
|
|
644
644
|
snippets: import_zod.z.array(import_zod.z.object({ url: import_zod.z.string(), domain: import_zod.z.string(), title: import_zod.z.string(), text: import_zod.z.string() })),
|
|
645
645
|
counts: import_zod.z.object({
|
|
646
646
|
subQueries: import_zod.z.number().int().min(0),
|
|
@@ -1136,6 +1136,14 @@ function sanitizeOutboundDiagnostics(value, parentKey = "") {
|
|
|
1136
1136
|
return value;
|
|
1137
1137
|
}
|
|
1138
1138
|
|
|
1139
|
+
// src/mcp/output-schema-registry.ts
|
|
1140
|
+
var ADVERTISE_OUTPUT_SCHEMAS = process.env.MCP_SCRAPER_ADVERTISE_OUTPUT_SCHEMAS === "true";
|
|
1141
|
+
var OUTPUT_SCHEMAS = {};
|
|
1142
|
+
function recordOutputSchema(name, schema) {
|
|
1143
|
+
OUTPUT_SCHEMAS[name] = schema;
|
|
1144
|
+
return ADVERTISE_OUTPUT_SCHEMAS ? schema : void 0;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1139
1147
|
// src/mcp/browser-agent-mcp-server.ts
|
|
1140
1148
|
function structuredResult(value, isError = false) {
|
|
1141
1149
|
const safe2 = sanitizeOutboundDiagnostics(value);
|
|
@@ -1299,9 +1307,9 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1299
1307
|
"browser_profile_connect",
|
|
1300
1308
|
{
|
|
1301
1309
|
title: "Save a Site Login to a Profile",
|
|
1302
|
-
description:
|
|
1310
|
+
description: "Save a logged-in browser session so this server can reuse a site the user is signed into (ChatGPT, Claude, Reddit, any account-gated site). Returns a watch_url; the user signs in fresh (existing browser cookies are NOT imported) and cookies save to a named profile. 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). After sign-in, poll browser_profile_list until AUTHENTICATED, then browser_open with the profile.",
|
|
1303
1311
|
inputSchema: BrowserProfileConnectInputSchema,
|
|
1304
|
-
outputSchema: BrowserProfileConnectOutputSchema,
|
|
1312
|
+
outputSchema: recordOutputSchema("browser_profile_connect", BrowserProfileConnectOutputSchema),
|
|
1305
1313
|
annotations: annotations("Save a Site Login to a Profile")
|
|
1306
1314
|
},
|
|
1307
1315
|
async (input) => {
|
|
@@ -1357,9 +1365,9 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1357
1365
|
"browser_profile_list",
|
|
1358
1366
|
{
|
|
1359
1367
|
title: "List Saved Logins in a Profile",
|
|
1360
|
-
description:
|
|
1368
|
+
description: "List every site login saved in a profile with its auth status (NEEDS_AUTH/AUTHENTICATED), email, and note. Use to check what's connected, or to poll a just-saved login until AUTHENTICATED. Read-only, no cost. Pass profile (or email to derive it); narrow with domain or connection_id.",
|
|
1361
1369
|
inputSchema: BrowserProfileListInputSchema,
|
|
1362
|
-
outputSchema: BrowserProfileListOutputSchema,
|
|
1370
|
+
outputSchema: recordOutputSchema("browser_profile_list", BrowserProfileListOutputSchema),
|
|
1363
1371
|
annotations: annotations("List Saved Logins in a Profile", true)
|
|
1364
1372
|
},
|
|
1365
1373
|
async (input) => {
|
|
@@ -1391,9 +1399,9 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1391
1399
|
"browser_open",
|
|
1392
1400
|
{
|
|
1393
1401
|
title: "Open Browser Session",
|
|
1394
|
-
description: "Open a direct no-proxy hosted browser session you can drive. Pass a saved profile name to load a session
|
|
1402
|
+
description: "Open a direct no-proxy hosted browser session you can drive. Pass a saved profile name to load a session already logged into that profile's sites (set one up first with browser_profile_connect). Returns a session_id used by all other browser_* tools.",
|
|
1395
1403
|
inputSchema: BrowserOpenInputSchema,
|
|
1396
|
-
outputSchema: BrowserOpenOutputSchema,
|
|
1404
|
+
outputSchema: recordOutputSchema("browser_open", BrowserOpenOutputSchema),
|
|
1397
1405
|
annotations: annotations("Open Browser Session")
|
|
1398
1406
|
},
|
|
1399
1407
|
async (input) => {
|
|
@@ -1425,9 +1433,9 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1425
1433
|
"browser_screenshot",
|
|
1426
1434
|
{
|
|
1427
1435
|
title: "See Page (Screenshot + Elements)",
|
|
1428
|
-
description: "Capture what the browser currently shows
|
|
1436
|
+
description: "Capture what the browser currently shows: a screenshot plus a text snapshot of interactive elements with x,y coordinates, page url/title, and visible text. Primary way to perceive the page; click elements by their listed x,y. If a Cloudflare/CAPTCHA challenge is visible, wait and screenshot again rather than clicking it.",
|
|
1429
1437
|
inputSchema: BrowserSessionInputSchema,
|
|
1430
|
-
outputSchema: BrowserScreenshotOutputSchema,
|
|
1438
|
+
outputSchema: recordOutputSchema("browser_screenshot", BrowserScreenshotOutputSchema),
|
|
1431
1439
|
annotations: annotations("See Page", true)
|
|
1432
1440
|
},
|
|
1433
1441
|
async (input) => {
|
|
@@ -1457,9 +1465,9 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1457
1465
|
"browser_read",
|
|
1458
1466
|
{
|
|
1459
1467
|
title: "Read Page Text + Elements",
|
|
1460
|
-
description: "Return the page url, title, visible text, and
|
|
1468
|
+
description: "Return the page url, title, visible text, and interactive elements (with x,y) without an image. Cheaper than browser_screenshot when you only need to read content or find a click target.",
|
|
1461
1469
|
inputSchema: BrowserSessionInputSchema,
|
|
1462
|
-
outputSchema: BrowserReadOutputSchema,
|
|
1470
|
+
outputSchema: recordOutputSchema("browser_read", BrowserReadOutputSchema),
|
|
1463
1471
|
annotations: annotations("Read Page", true)
|
|
1464
1472
|
},
|
|
1465
1473
|
async (input) => {
|
|
@@ -1481,9 +1489,9 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1481
1489
|
"browser_locate",
|
|
1482
1490
|
{
|
|
1483
1491
|
title: "Locate DOM Targets",
|
|
1484
|
-
description: "Locate exact visible DOM elements or text ranges
|
|
1492
|
+
description: "Locate exact visible DOM elements or text ranges and return left/top/width/height bounds in screenshot pixels. Use before drawing annotations that must circle, box, underline, or point to a real element. Prefer CSS selectors; use text when selector is unknown.",
|
|
1485
1493
|
inputSchema: BrowserLocateInputSchema,
|
|
1486
|
-
outputSchema: BrowserLocateOutputSchema,
|
|
1494
|
+
outputSchema: recordOutputSchema("browser_locate", BrowserLocateOutputSchema),
|
|
1487
1495
|
annotations: annotations("Locate DOM Targets", true)
|
|
1488
1496
|
},
|
|
1489
1497
|
async (input) => {
|
|
@@ -1506,9 +1514,9 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1506
1514
|
"browser_goto",
|
|
1507
1515
|
{
|
|
1508
1516
|
title: "Navigate To URL",
|
|
1509
|
-
description: "Navigate an existing browser session to a URL
|
|
1517
|
+
description: "Navigate an existing browser session to a URL. Use browser_open first if no session exists; follow with browser_screenshot to see the loaded page.",
|
|
1510
1518
|
inputSchema: BrowserGotoInputSchema,
|
|
1511
|
-
outputSchema: BrowserActionOutputSchema,
|
|
1519
|
+
outputSchema: recordOutputSchema("browser_goto", BrowserActionOutputSchema),
|
|
1512
1520
|
annotations: annotations("Navigate To URL")
|
|
1513
1521
|
},
|
|
1514
1522
|
async (input) => {
|
|
@@ -1520,9 +1528,9 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1520
1528
|
"browser_click",
|
|
1521
1529
|
{
|
|
1522
1530
|
title: "Click",
|
|
1523
|
-
description: "Click a visible page target using screenshot pixel coordinates. Use
|
|
1531
|
+
description: "Click a visible page target using screenshot pixel coordinates. Use x/y only from the latest browser_screenshot, browser_read, or browser_locate result; do not guess coordinates.",
|
|
1524
1532
|
inputSchema: BrowserClickInputSchema,
|
|
1525
|
-
outputSchema: BrowserActionOutputSchema,
|
|
1533
|
+
outputSchema: recordOutputSchema("browser_click", BrowserActionOutputSchema),
|
|
1526
1534
|
annotations: annotations("Click")
|
|
1527
1535
|
},
|
|
1528
1536
|
async (input) => {
|
|
@@ -1539,9 +1547,9 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1539
1547
|
"browser_type",
|
|
1540
1548
|
{
|
|
1541
1549
|
title: "Type Text",
|
|
1542
|
-
description: 'Type text into the currently focused browser field.
|
|
1550
|
+
description: 'Type text into the currently focused browser field. Click or Tab to the field first if focus is uncertain. Use browser_press with ["Return"] to submit.',
|
|
1543
1551
|
inputSchema: BrowserTypeInputSchema,
|
|
1544
|
-
outputSchema: BrowserActionOutputSchema,
|
|
1552
|
+
outputSchema: recordOutputSchema("browser_type", BrowserActionOutputSchema),
|
|
1545
1553
|
annotations: annotations("Type Text")
|
|
1546
1554
|
},
|
|
1547
1555
|
async (input) => {
|
|
@@ -1553,9 +1561,9 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1553
1561
|
"browser_scroll",
|
|
1554
1562
|
{
|
|
1555
1563
|
title: "Scroll",
|
|
1556
|
-
description: "Scroll the page to reveal more content
|
|
1564
|
+
description: "Scroll the page to reveal more content. Positive delta_y scrolls down; negative scrolls up.",
|
|
1557
1565
|
inputSchema: BrowserScrollInputSchema,
|
|
1558
|
-
outputSchema: BrowserActionOutputSchema,
|
|
1566
|
+
outputSchema: recordOutputSchema("browser_scroll", BrowserActionOutputSchema),
|
|
1559
1567
|
annotations: annotations("Scroll")
|
|
1560
1568
|
},
|
|
1561
1569
|
async (input) => {
|
|
@@ -1572,9 +1580,9 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1572
1580
|
"browser_press",
|
|
1573
1581
|
{
|
|
1574
1582
|
title: "Press Keys",
|
|
1575
|
-
description:
|
|
1583
|
+
description: "Press keyboard keys or combinations in the active browser session \u2014 submit, Escape, Tab navigation, select-all, or shortcuts. Use browser_type for text entry.",
|
|
1576
1584
|
inputSchema: BrowserPressInputSchema,
|
|
1577
|
-
outputSchema: BrowserActionOutputSchema,
|
|
1585
|
+
outputSchema: recordOutputSchema("browser_press", BrowserActionOutputSchema),
|
|
1578
1586
|
annotations: annotations("Press Keys")
|
|
1579
1587
|
},
|
|
1580
1588
|
async (input) => {
|
|
@@ -1586,9 +1594,9 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1586
1594
|
"browser_replay_start",
|
|
1587
1595
|
{
|
|
1588
1596
|
title: "Start Recording",
|
|
1589
|
-
description: "Start recording an MP4 replay of the session. Returns replay_id
|
|
1597
|
+
description: "Start recording an MP4 replay of the session. Returns replay_id and a download_url. Stop with browser_replay_stop.",
|
|
1590
1598
|
inputSchema: BrowserSessionInputSchema,
|
|
1591
|
-
outputSchema: BrowserReplayStartOutputSchema,
|
|
1599
|
+
outputSchema: recordOutputSchema("browser_replay_start", BrowserReplayStartOutputSchema),
|
|
1592
1600
|
annotations: annotations("Start Recording")
|
|
1593
1601
|
},
|
|
1594
1602
|
async (input) => {
|
|
@@ -1609,9 +1617,9 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1609
1617
|
"browser_replay_stop",
|
|
1610
1618
|
{
|
|
1611
1619
|
title: "Stop Recording",
|
|
1612
|
-
description: "Stop a replay recording and expose
|
|
1620
|
+
description: "Stop a replay recording and expose its final view_url/download_url. Use browser_replay_download to save the MP4.",
|
|
1613
1621
|
inputSchema: BrowserReplayStopInputSchema,
|
|
1614
|
-
outputSchema: BrowserReplayStopOutputSchema,
|
|
1622
|
+
outputSchema: recordOutputSchema("browser_replay_stop", BrowserReplayStopOutputSchema),
|
|
1615
1623
|
annotations: annotations("Stop Recording")
|
|
1616
1624
|
},
|
|
1617
1625
|
async (input) => {
|
|
@@ -1632,9 +1640,9 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1632
1640
|
"browser_list_replays",
|
|
1633
1641
|
{
|
|
1634
1642
|
title: "List Replay Videos",
|
|
1635
|
-
description: "List replay recordings for a browser session, including
|
|
1643
|
+
description: "List replay recordings for a browser session, including view_url and download_url when available.",
|
|
1636
1644
|
inputSchema: BrowserSessionInputSchema,
|
|
1637
|
-
outputSchema: BrowserListReplaysOutputSchema,
|
|
1645
|
+
outputSchema: recordOutputSchema("browser_list_replays", BrowserListReplaysOutputSchema),
|
|
1638
1646
|
annotations: annotations("List Replay Videos", true)
|
|
1639
1647
|
},
|
|
1640
1648
|
async (input) => {
|
|
@@ -1654,9 +1662,9 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1654
1662
|
"browser_replay_download",
|
|
1655
1663
|
{
|
|
1656
1664
|
title: "Download Replay MP4",
|
|
1657
|
-
description: "Download a replay recording
|
|
1665
|
+
description: opts.savesReportsLocally === false ? "Download a replay recording. Returns the download_url; fetch it directly (nothing is saved on this hosted endpoint). Use after browser_replay_stop or browser_list_replays." : "Download a replay recording and save the MP4 under MCP_SCRAPER_OUTPUT_DIR/browser-replays. Use after browser_replay_stop or browser_list_replays.",
|
|
1658
1666
|
inputSchema: BrowserReplayDownloadInputSchema,
|
|
1659
|
-
outputSchema: BrowserReplayDownloadOutputSchema,
|
|
1667
|
+
outputSchema: recordOutputSchema("browser_replay_download", BrowserReplayDownloadOutputSchema),
|
|
1660
1668
|
annotations: annotations("Download Replay MP4")
|
|
1661
1669
|
},
|
|
1662
1670
|
async (input) => {
|
|
@@ -1678,9 +1686,9 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1678
1686
|
"browser_replay_mark",
|
|
1679
1687
|
{
|
|
1680
1688
|
title: "Mark Replay Annotation",
|
|
1681
|
-
description: "While a replay is actively recording, locate one exact DOM target and return a ready-to-use annotation
|
|
1689
|
+
description: "While a replay is actively recording, locate one exact DOM target and return a ready-to-use annotation with DOM bounds and replay-relative timing, instead of guessing start_seconds or rectangles. Pass the returned annotations to browser_replay_annotate after stopping the replay.",
|
|
1682
1690
|
inputSchema: BrowserReplayMarkInputSchema,
|
|
1683
|
-
outputSchema: BrowserReplayMarkOutputSchema,
|
|
1691
|
+
outputSchema: recordOutputSchema("browser_replay_mark", BrowserReplayMarkOutputSchema),
|
|
1684
1692
|
annotations: annotations("Mark Replay Annotation", true)
|
|
1685
1693
|
},
|
|
1686
1694
|
async (input) => {
|
|
@@ -1728,9 +1736,9 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1728
1736
|
"browser_replay_annotate",
|
|
1729
1737
|
{
|
|
1730
1738
|
title: "Annotate Replay MP4",
|
|
1731
|
-
description: "Download a browser replay MP4, render visual annotations over it, and save a new annotated MP4
|
|
1739
|
+
description: "Download a browser replay MP4, render visual annotations (circles/boxes/arrows/labels) over it, and save a new annotated MP4. Prefer annotations from browser_replay_mark for accurate timing; otherwise use exact bounds from browser_locate. Pass source_width/source_height if the replay video size differs from the screenshot coordinate space.",
|
|
1732
1740
|
inputSchema: BrowserReplayAnnotateInputSchema,
|
|
1733
|
-
outputSchema: BrowserReplayAnnotateOutputSchema,
|
|
1741
|
+
outputSchema: recordOutputSchema("browser_replay_annotate", BrowserReplayAnnotateOutputSchema),
|
|
1734
1742
|
annotations: annotations("Annotate Replay MP4")
|
|
1735
1743
|
},
|
|
1736
1744
|
async (input) => {
|
|
@@ -1770,9 +1778,9 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1770
1778
|
"browser_close",
|
|
1771
1779
|
{
|
|
1772
1780
|
title: "Close Browser Session",
|
|
1773
|
-
description: "Close and release a browser session when the task is done,
|
|
1781
|
+
description: "Close and release a browser session when the task is done, to end active browser billing. Use browser_list_sessions first to recover a session_id.",
|
|
1774
1782
|
inputSchema: BrowserSessionInputSchema,
|
|
1775
|
-
outputSchema: BrowserCloseOutputSchema,
|
|
1783
|
+
outputSchema: recordOutputSchema("browser_close", BrowserCloseOutputSchema),
|
|
1776
1784
|
annotations: { title: "Close Browser Session", readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
1777
1785
|
},
|
|
1778
1786
|
async (input) => {
|
|
@@ -1791,9 +1799,9 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1791
1799
|
"browser_list_sessions",
|
|
1792
1800
|
{
|
|
1793
1801
|
title: "List Browser Sessions",
|
|
1794
|
-
description: "List browser sessions and their status, with a watch_url for each. Use
|
|
1802
|
+
description: "List browser sessions and their status, with a watch_url for each. Use to recover a session_id or decide which session to close.",
|
|
1795
1803
|
inputSchema: BrowserListInputSchema,
|
|
1796
|
-
outputSchema: BrowserListSessionsOutputSchema,
|
|
1804
|
+
outputSchema: recordOutputSchema("browser_list_sessions", BrowserListSessionsOutputSchema),
|
|
1797
1805
|
annotations: annotations("List Browser Sessions", true)
|
|
1798
1806
|
},
|
|
1799
1807
|
async (input) => {
|
|
@@ -1813,9 +1821,9 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1813
1821
|
"query_fanout_workflow",
|
|
1814
1822
|
{
|
|
1815
1823
|
title: "Capture AI Search Fan-Out",
|
|
1816
|
-
description:
|
|
1824
|
+
description: "Capture the query fan-out behind a ChatGPT or Claude web-search answer for AEO: sub-queries issued, every researched URL split into cited vs browsed-only, and top sourced sites. Returns raw structured data for you to classify and analyze. Set export=true for JSON/CSV/TSV/HTML artifacts. WRITE NOTE: passing prompt submits a real message in the user's logged-in account \u2014 only send when the user wants that; omit it to capture a prompt the user just ran. The session must already be open on chatgpt.com or claude.ai (see browser_profile_connect) while the prompt streams. NOT for Google AI Overview \u2014 use harvest_paa for that.",
|
|
1817
1825
|
inputSchema: BrowserCaptureFanoutInputSchema,
|
|
1818
|
-
outputSchema: BrowserCaptureFanoutOutputSchema,
|
|
1826
|
+
outputSchema: recordOutputSchema("query_fanout_workflow", BrowserCaptureFanoutOutputSchema),
|
|
1819
1827
|
annotations: annotations("Capture AI Search Fan-Out")
|
|
1820
1828
|
},
|
|
1821
1829
|
async (input) => {
|
|
@@ -1869,13 +1877,14 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1869
1877
|
|
|
1870
1878
|
// src/mcp/paa-mcp-server.ts
|
|
1871
1879
|
var import_mcp2 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
1872
|
-
var
|
|
1873
|
-
var
|
|
1880
|
+
var import_node_fs5 = require("fs");
|
|
1881
|
+
var import_node_path6 = require("path");
|
|
1882
|
+
var import_node_crypto2 = require("crypto");
|
|
1874
1883
|
|
|
1875
1884
|
// src/mcp/mcp-response-formatter.ts
|
|
1876
|
-
var
|
|
1877
|
-
var
|
|
1878
|
-
var
|
|
1885
|
+
var import_node_fs4 = require("fs");
|
|
1886
|
+
var import_node_os5 = require("os");
|
|
1887
|
+
var import_node_path5 = require("path");
|
|
1879
1888
|
|
|
1880
1889
|
// src/mcp/workflow-catalog.ts
|
|
1881
1890
|
var WORKFLOW_RECIPES = [
|
|
@@ -2412,7 +2421,129 @@ function renderImageSection(audit) {
|
|
|
2412
2421
|
return lines.join("\n");
|
|
2413
2422
|
}
|
|
2414
2423
|
|
|
2424
|
+
// src/mcp/report-artifact-offload.ts
|
|
2425
|
+
var import_node_crypto = require("crypto");
|
|
2426
|
+
|
|
2427
|
+
// src/api/blob-store.ts
|
|
2428
|
+
var import_node_fs3 = require("fs");
|
|
2429
|
+
var import_node_os4 = require("os");
|
|
2430
|
+
var import_node_path4 = require("path");
|
|
2431
|
+
function byteLength(data) {
|
|
2432
|
+
return Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data);
|
|
2433
|
+
}
|
|
2434
|
+
var LocalBlobStore = class {
|
|
2435
|
+
constructor(baseDir) {
|
|
2436
|
+
this.baseDir = baseDir;
|
|
2437
|
+
}
|
|
2438
|
+
baseDir;
|
|
2439
|
+
kind = "local";
|
|
2440
|
+
async put(key, data, contentType = "application/octet-stream") {
|
|
2441
|
+
const path = (0, import_node_path4.join)(this.baseDir, "blobs", key);
|
|
2442
|
+
(0, import_node_fs3.mkdirSync)((0, import_node_path4.dirname)(path), { recursive: true });
|
|
2443
|
+
(0, import_node_fs3.writeFileSync)(path, data);
|
|
2444
|
+
return { key, url: `file://${path}`, bytes: byteLength(data), contentType };
|
|
2445
|
+
}
|
|
2446
|
+
async get(key) {
|
|
2447
|
+
const path = (0, import_node_path4.join)(this.baseDir, "blobs", key);
|
|
2448
|
+
try {
|
|
2449
|
+
const { readFileSync: readFileSync3 } = await import("fs");
|
|
2450
|
+
return readFileSync3(path);
|
|
2451
|
+
} catch {
|
|
2452
|
+
return null;
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
};
|
|
2456
|
+
function localBaseDir() {
|
|
2457
|
+
return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || (0, import_node_path4.join)((0, import_node_os4.homedir)(), "Downloads", "mcp-scraper");
|
|
2458
|
+
}
|
|
2459
|
+
var cached = null;
|
|
2460
|
+
function getBlobStore() {
|
|
2461
|
+
if (cached) return cached;
|
|
2462
|
+
if (process.env.BLOB_READ_WRITE_TOKEN) {
|
|
2463
|
+
cached = new VercelBlobStore(process.env.BLOB_READ_WRITE_TOKEN);
|
|
2464
|
+
} else {
|
|
2465
|
+
cached = new LocalBlobStore(localBaseDir());
|
|
2466
|
+
}
|
|
2467
|
+
return cached;
|
|
2468
|
+
}
|
|
2469
|
+
var VercelBlobStore = class {
|
|
2470
|
+
constructor(token) {
|
|
2471
|
+
this.token = token;
|
|
2472
|
+
}
|
|
2473
|
+
token;
|
|
2474
|
+
kind = "vercel-blob";
|
|
2475
|
+
async put(key, data, contentType = "application/octet-stream") {
|
|
2476
|
+
const { put } = await import("@vercel/blob");
|
|
2477
|
+
const body = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
2478
|
+
const result = await put(key, body, {
|
|
2479
|
+
access: "public",
|
|
2480
|
+
token: this.token,
|
|
2481
|
+
contentType,
|
|
2482
|
+
addRandomSuffix: true
|
|
2483
|
+
});
|
|
2484
|
+
return { key, url: result.url, bytes: byteLength(data), contentType };
|
|
2485
|
+
}
|
|
2486
|
+
async get(key) {
|
|
2487
|
+
try {
|
|
2488
|
+
const { list } = await import("@vercel/blob");
|
|
2489
|
+
const res = await list({ prefix: key, token: this.token, limit: 1 });
|
|
2490
|
+
const match = res.blobs.find((b) => b.pathname === key || b.pathname.startsWith(key));
|
|
2491
|
+
if (!match) return null;
|
|
2492
|
+
const resp = await fetch(match.url);
|
|
2493
|
+
if (!resp.ok) return null;
|
|
2494
|
+
return Buffer.from(await resp.arrayBuffer());
|
|
2495
|
+
} catch {
|
|
2496
|
+
return null;
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
};
|
|
2500
|
+
|
|
2501
|
+
// src/mcp/report-artifact-offload.ts
|
|
2502
|
+
var REPORT_BLOB_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2503
|
+
var REPORT_BLOB_PREFIX = "mcp-reports/";
|
|
2504
|
+
var PREVIEW_CHARS = 2e3;
|
|
2505
|
+
var ARTIFACT_OFFLOAD_ENABLED = process.env.MCP_SCRAPER_ARTIFACT_OFFLOAD !== "false";
|
|
2506
|
+
async function offloadReport(toolName, ownerId, report) {
|
|
2507
|
+
const timestamp = Date.now();
|
|
2508
|
+
const random = (0, import_node_crypto.randomBytes)(6).toString("hex");
|
|
2509
|
+
const key = `${REPORT_BLOB_PREFIX}${ownerId}/${toolName}/${timestamp}-${random}.md`;
|
|
2510
|
+
const stored = await getBlobStore().put(key, report, "text/markdown");
|
|
2511
|
+
return {
|
|
2512
|
+
artifactId: stored.key,
|
|
2513
|
+
bytes: stored.bytes,
|
|
2514
|
+
expiresAt: new Date(timestamp + REPORT_BLOB_TTL_MS).toISOString(),
|
|
2515
|
+
preview: report.slice(0, PREVIEW_CHARS)
|
|
2516
|
+
};
|
|
2517
|
+
}
|
|
2518
|
+
function artifactOwnerId(artifactId) {
|
|
2519
|
+
if (!artifactId.startsWith(REPORT_BLOB_PREFIX)) return null;
|
|
2520
|
+
const rest = artifactId.slice(REPORT_BLOB_PREFIX.length);
|
|
2521
|
+
const segment = rest.split("/")[0];
|
|
2522
|
+
return segment || null;
|
|
2523
|
+
}
|
|
2524
|
+
async function readArtifactWindow(artifactId, offset, maxBytes) {
|
|
2525
|
+
const buf = await getBlobStore().get(artifactId);
|
|
2526
|
+
if (!buf) return null;
|
|
2527
|
+
const totalBytes = buf.length;
|
|
2528
|
+
const start = Math.max(0, offset);
|
|
2529
|
+
const end = Math.min(totalBytes, start + maxBytes);
|
|
2530
|
+
const slice = buf.subarray(start, end);
|
|
2531
|
+
const nextOffset = end < totalBytes ? end : null;
|
|
2532
|
+
return { text: slice.toString("utf8"), totalBytes, nextOffset };
|
|
2533
|
+
}
|
|
2534
|
+
function summaryEnvelope(executiveSummary, offloaded) {
|
|
2535
|
+
return [
|
|
2536
|
+
executiveSummary.trim(),
|
|
2537
|
+
"",
|
|
2538
|
+
"--- Full report stored as artifact ---",
|
|
2539
|
+
`artifactId: ${offloaded.artifactId} \xB7 ${offloaded.bytes} bytes \xB7 expires ${offloaded.expiresAt}`,
|
|
2540
|
+
"Read it with report_artifact_read (supports offset/maxBytes windowing)."
|
|
2541
|
+
].join("\n");
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2415
2544
|
// src/mcp/mcp-response-formatter.ts
|
|
2545
|
+
var INLINE_BUDGET_BYTES = Number(process.env.MCP_SCRAPER_INLINE_BUDGET ?? 5e4);
|
|
2546
|
+
var STRUCTURED_ARRAY_CAP = 25;
|
|
2416
2547
|
var reportSavingEnabled = true;
|
|
2417
2548
|
function sanitizeVendorText(text) {
|
|
2418
2549
|
return sanitizeVendorName(
|
|
@@ -2427,16 +2558,16 @@ function reportTitle(full) {
|
|
|
2427
2558
|
return title?.replace(/^#\s+/, "").trim() || "MCP Scraper Report";
|
|
2428
2559
|
}
|
|
2429
2560
|
function outputBaseDir3() {
|
|
2430
|
-
return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || (0,
|
|
2561
|
+
return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || (0, import_node_path5.join)((0, import_node_os5.homedir)(), "Downloads", "mcp-scraper");
|
|
2431
2562
|
}
|
|
2432
2563
|
function saveFullReport(full) {
|
|
2433
2564
|
if (!reportSavingEnabled || process.env.MCP_SCRAPER_SAVE_REPORTS === "false") return null;
|
|
2434
2565
|
const outDir = outputBaseDir3();
|
|
2435
2566
|
try {
|
|
2436
|
-
(0,
|
|
2567
|
+
(0, import_node_fs4.mkdirSync)(outDir, { recursive: true });
|
|
2437
2568
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2438
|
-
const file = (0,
|
|
2439
|
-
(0,
|
|
2569
|
+
const file = (0, import_node_path5.join)(outDir, `${stamp}-${slugifyReportName(reportTitle(full))}.md`);
|
|
2570
|
+
(0, import_node_fs4.writeFileSync)(file, full, "utf8");
|
|
2440
2571
|
return file;
|
|
2441
2572
|
} catch {
|
|
2442
2573
|
return null;
|
|
@@ -2451,9 +2582,9 @@ function saveBulkSite(siteUrl, pages, seo, imageAudit) {
|
|
|
2451
2582
|
if (!reportSavingActive()) return null;
|
|
2452
2583
|
try {
|
|
2453
2584
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2454
|
-
const dir = (0,
|
|
2455
|
-
const pagesDir = (0,
|
|
2456
|
-
(0,
|
|
2585
|
+
const dir = (0, import_node_path5.join)(outputBaseDir3(), `extract-${slugifyReportName(siteUrl)}-${stamp}`);
|
|
2586
|
+
const pagesDir = (0, import_node_path5.join)(dir, "pages");
|
|
2587
|
+
(0, import_node_fs4.mkdirSync)(pagesDir, { recursive: true });
|
|
2457
2588
|
const indexRows = pages.map((p, i) => {
|
|
2458
2589
|
const num = String(i + 1).padStart(4, "0");
|
|
2459
2590
|
const slug = slugifyReportName(p.url.replace(/^https?:\/\//, "")).slice(0, 60) || "page";
|
|
@@ -2467,7 +2598,7 @@ function saveBulkSite(siteUrl, pages, seo, imageAudit) {
|
|
|
2467
2598
|
"",
|
|
2468
2599
|
body || "_(no content extracted)_"
|
|
2469
2600
|
].filter(Boolean).join("\n");
|
|
2470
|
-
(0,
|
|
2601
|
+
(0, import_node_fs4.writeFileSync)((0, import_node_path5.join)(pagesDir, fname), content, "utf8");
|
|
2471
2602
|
return `| ${i + 1} | ${cell(p.title ?? "Untitled")} | ${p.url} | pages/${fname} |`;
|
|
2472
2603
|
});
|
|
2473
2604
|
const dataFilesSection = seo ? [
|
|
@@ -2499,40 +2630,40 @@ function saveBulkSite(siteUrl, pages, seo, imageAudit) {
|
|
|
2499
2630
|
|---|-------|-----|------|
|
|
2500
2631
|
${indexRows.join("\n")}`
|
|
2501
2632
|
].filter(Boolean).join("\n");
|
|
2502
|
-
const indexFile = (0,
|
|
2503
|
-
(0,
|
|
2633
|
+
const indexFile = (0, import_node_path5.join)(dir, "index.md");
|
|
2634
|
+
(0, import_node_fs4.writeFileSync)(indexFile, index, "utf8");
|
|
2504
2635
|
let seoFiles;
|
|
2505
2636
|
if (seo) {
|
|
2506
2637
|
const toJsonl = (rows) => rows.map((r) => JSON.stringify(r)).join("\n");
|
|
2507
|
-
const pagesJsonl = (0,
|
|
2508
|
-
const linksJsonl = (0,
|
|
2509
|
-
const metricsJsonl = (0,
|
|
2510
|
-
const issuesFile = (0,
|
|
2511
|
-
const reportFile = (0,
|
|
2512
|
-
(0,
|
|
2513
|
-
(0,
|
|
2514
|
-
(0,
|
|
2515
|
-
(0,
|
|
2516
|
-
(0,
|
|
2638
|
+
const pagesJsonl = (0, import_node_path5.join)(dir, "pages.jsonl");
|
|
2639
|
+
const linksJsonl = (0, import_node_path5.join)(dir, "links.jsonl");
|
|
2640
|
+
const metricsJsonl = (0, import_node_path5.join)(dir, "link-metrics.jsonl");
|
|
2641
|
+
const issuesFile = (0, import_node_path5.join)(dir, "issues.json");
|
|
2642
|
+
const reportFile = (0, import_node_path5.join)(dir, "report.md");
|
|
2643
|
+
(0, import_node_fs4.writeFileSync)(pagesJsonl, toJsonl(seo.pageRows), "utf8");
|
|
2644
|
+
(0, import_node_fs4.writeFileSync)(linksJsonl, toJsonl(seo.edges), "utf8");
|
|
2645
|
+
(0, import_node_fs4.writeFileSync)(metricsJsonl, toJsonl(seo.metrics), "utf8");
|
|
2646
|
+
(0, import_node_fs4.writeFileSync)(issuesFile, JSON.stringify(seo.issues, null, 2), "utf8");
|
|
2647
|
+
(0, import_node_fs4.writeFileSync)(reportFile, seo.reportMd + (imageAudit ? `
|
|
2517
2648
|
|
|
2518
2649
|
${renderImageSection(imageAudit)}` : ""), "utf8");
|
|
2519
|
-
const linkReportFile = (0,
|
|
2520
|
-
const linksSummaryFile = (0,
|
|
2521
|
-
const externalDomainsFile = (0,
|
|
2522
|
-
(0,
|
|
2523
|
-
(0,
|
|
2524
|
-
(0,
|
|
2650
|
+
const linkReportFile = (0, import_node_path5.join)(dir, "link-report.md");
|
|
2651
|
+
const linksSummaryFile = (0, import_node_path5.join)(dir, "links-summary.json");
|
|
2652
|
+
const externalDomainsFile = (0, import_node_path5.join)(dir, "external-domains.json");
|
|
2653
|
+
(0, import_node_fs4.writeFileSync)(linkReportFile, renderLinkReport(seo.linkReport), "utf8");
|
|
2654
|
+
(0, import_node_fs4.writeFileSync)(linksSummaryFile, JSON.stringify(seo.linkReport.summary, null, 2), "utf8");
|
|
2655
|
+
(0, import_node_fs4.writeFileSync)(externalDomainsFile, JSON.stringify(seo.linkReport.externalDomains, null, 2), "utf8");
|
|
2525
2656
|
seoFiles = [pagesJsonl, linksJsonl, metricsJsonl, issuesFile, reportFile, linkReportFile, linksSummaryFile, externalDomainsFile];
|
|
2526
2657
|
if (imageAudit) {
|
|
2527
|
-
const imagesJsonl = (0,
|
|
2528
|
-
const imagesSummary = (0,
|
|
2529
|
-
(0,
|
|
2530
|
-
(0,
|
|
2658
|
+
const imagesJsonl = (0, import_node_path5.join)(dir, "images.jsonl");
|
|
2659
|
+
const imagesSummary = (0, import_node_path5.join)(dir, "images-summary.json");
|
|
2660
|
+
(0, import_node_fs4.writeFileSync)(imagesJsonl, imageAudit.rows.map((r) => JSON.stringify(r)).join("\n"), "utf8");
|
|
2661
|
+
(0, import_node_fs4.writeFileSync)(imagesSummary, JSON.stringify(imageAudit.summary, null, 2), "utf8");
|
|
2531
2662
|
seoFiles.push(imagesJsonl, imagesSummary);
|
|
2532
2663
|
}
|
|
2533
2664
|
if (seo.branding) {
|
|
2534
|
-
const brandingFile = (0,
|
|
2535
|
-
(0,
|
|
2665
|
+
const brandingFile = (0, import_node_path5.join)(dir, "branding.json");
|
|
2666
|
+
(0, import_node_fs4.writeFileSync)(brandingFile, JSON.stringify(seo.branding, null, 2), "utf8");
|
|
2536
2667
|
seoFiles.push(brandingFile);
|
|
2537
2668
|
}
|
|
2538
2669
|
}
|
|
@@ -2545,12 +2676,12 @@ function saveUrlInventory(siteUrl, urls) {
|
|
|
2545
2676
|
if (!reportSavingActive()) return null;
|
|
2546
2677
|
try {
|
|
2547
2678
|
const outDir = outputBaseDir3();
|
|
2548
|
-
(0,
|
|
2679
|
+
(0, import_node_fs4.mkdirSync)(outDir, { recursive: true });
|
|
2549
2680
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2550
|
-
const file = (0,
|
|
2681
|
+
const file = (0, import_node_path5.join)(outDir, `${stamp}-urlmap-${slugifyReportName(siteUrl.replace(/^https?:\/\//, ""))}.csv`);
|
|
2551
2682
|
const csv = (v) => /[",\n]/.test(v) ? `"${v.replace(/"/g, '""')}"` : v;
|
|
2552
2683
|
const rows = ["url,status", ...urls.map((u) => `${csv(u.url)},${u.status ?? ""}`)];
|
|
2553
|
-
(0,
|
|
2684
|
+
(0, import_node_fs4.writeFileSync)(file, rows.join("\n"), "utf8");
|
|
2554
2685
|
return file;
|
|
2555
2686
|
} catch {
|
|
2556
2687
|
return null;
|
|
@@ -2559,12 +2690,12 @@ function saveUrlInventory(siteUrl, urls) {
|
|
|
2559
2690
|
function persistScreenshotLocally(base64, url) {
|
|
2560
2691
|
if (!reportSavingEnabled || process.env.MCP_SCRAPER_SAVE_REPORTS === "false") return null;
|
|
2561
2692
|
try {
|
|
2562
|
-
const dir = (0,
|
|
2563
|
-
(0,
|
|
2693
|
+
const dir = (0, import_node_path5.join)(outputBaseDir3(), "screenshots");
|
|
2694
|
+
(0, import_node_fs4.mkdirSync)(dir, { recursive: true });
|
|
2564
2695
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2565
2696
|
const slug = url.replace(/^https?:\/\//, "").replace(/[^a-z0-9]+/gi, "-").replace(/^-+|-+$/g, "").slice(0, 60);
|
|
2566
|
-
const filePath = (0,
|
|
2567
|
-
(0,
|
|
2697
|
+
const filePath = (0, import_node_path5.join)(dir, `${stamp}-${slug}.png`);
|
|
2698
|
+
(0, import_node_fs4.writeFileSync)(filePath, Buffer.from(base64, "base64"));
|
|
2568
2699
|
return filePath;
|
|
2569
2700
|
} catch {
|
|
2570
2701
|
return null;
|
|
@@ -2577,6 +2708,20 @@ function oneBlock(content, diskContent) {
|
|
|
2577
2708
|
\u{1F4C4} Saved: \`${filePath}\`` : content;
|
|
2578
2709
|
return { content: [{ type: "text", text }] };
|
|
2579
2710
|
}
|
|
2711
|
+
async function maybeOffload(toolName, ctx, fullText, summaryText, structuredContent) {
|
|
2712
|
+
if (!ctx?.hosted || !ARTIFACT_OFFLOAD_ENABLED) return null;
|
|
2713
|
+
const bytes = Buffer.byteLength(fullText);
|
|
2714
|
+
if (bytes <= INLINE_BUDGET_BYTES) return null;
|
|
2715
|
+
const offloaded = await offloadReport(toolName, ctx.ownerId, fullText);
|
|
2716
|
+
return {
|
|
2717
|
+
content: [{ type: "text", text: summaryEnvelope(summaryText, offloaded) }],
|
|
2718
|
+
structuredContent: { ...structuredContent, artifact: offloaded }
|
|
2719
|
+
};
|
|
2720
|
+
}
|
|
2721
|
+
function capArray(items, cap) {
|
|
2722
|
+
if (items.length <= cap) return { items };
|
|
2723
|
+
return { items: items.slice(0, cap), truncatedCount: items.length - cap };
|
|
2724
|
+
}
|
|
2580
2725
|
function workflowRecipeTable(recipes) {
|
|
2581
2726
|
if (!recipes.length) return "";
|
|
2582
2727
|
return [
|
|
@@ -2928,7 +3073,7 @@ ${headingSection}${kpoSection}${brandingSection}${bodySectionFull}${memSection}$
|
|
|
2928
3073
|
}
|
|
2929
3074
|
return { ...textResult, structuredContent };
|
|
2930
3075
|
}
|
|
2931
|
-
function formatMapSiteUrls(raw, input) {
|
|
3076
|
+
async function formatMapSiteUrls(raw, input, ctx) {
|
|
2932
3077
|
const parsed = parseData(raw);
|
|
2933
3078
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
2934
3079
|
const d = parsed.data;
|
|
@@ -2969,20 +3114,28 @@ ${broken.map((u) => `- ${u.url} (${u.status})`).join("\n")}` : "",
|
|
|
2969
3114
|
- Extract content from all pages: use \`extract_site\`
|
|
2970
3115
|
- Scrape a single page: use \`extract_url\``
|
|
2971
3116
|
].filter(Boolean).join("\n");
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
urls: urls.map((u) => ({ url: u.url, status: u.status ?? null })),
|
|
2983
|
-
durationMs: d.durationMs ?? 0
|
|
2984
|
-
}
|
|
3117
|
+
const structuredContent = {
|
|
3118
|
+
startUrl: d.startUrl ?? input.url,
|
|
3119
|
+
totalFound: d.totalFound ?? urls.length,
|
|
3120
|
+
truncated: d.truncated === true,
|
|
3121
|
+
okCount: ok.length,
|
|
3122
|
+
redirectCount: redirects.length,
|
|
3123
|
+
brokenCount: broken.length,
|
|
3124
|
+
inventoryFile: inventoryFile ?? null,
|
|
3125
|
+
urls: urls.map((u) => ({ url: u.url, status: u.status ?? null })),
|
|
3126
|
+
durationMs: d.durationMs ?? 0
|
|
2985
3127
|
};
|
|
3128
|
+
const summary = `# URL Map: ${input.url}
|
|
3129
|
+
**${d.totalFound} URLs** \xB7 ${(d.durationMs / 1e3).toFixed(1)}s
|
|
3130
|
+
|
|
3131
|
+
## Summary
|
|
3132
|
+
- 2xx: ${ok.length}
|
|
3133
|
+
- 3xx: ${redirects.length}
|
|
3134
|
+
- 4xx+: ${broken.length}`;
|
|
3135
|
+
const capped = capArray(structuredContent.urls, STRUCTURED_ARRAY_CAP);
|
|
3136
|
+
const offloaded = await maybeOffload("map_site_urls", ctx, full, summary, { ...structuredContent, urls: capped.items, truncatedCount: capped.truncatedCount });
|
|
3137
|
+
if (offloaded) return offloaded;
|
|
3138
|
+
return { ...oneBlock(full), structuredContent };
|
|
2986
3139
|
}
|
|
2987
3140
|
function buildSeoExport(siteUrl, pages, branding) {
|
|
2988
3141
|
const { edges, metrics } = buildLinkGraph(pages, siteUrl);
|
|
@@ -3002,7 +3155,7 @@ function buildSeoExport(siteUrl, pages, branding) {
|
|
|
3002
3155
|
branding: branding ?? void 0
|
|
3003
3156
|
};
|
|
3004
3157
|
}
|
|
3005
|
-
function formatExtractSite(raw, input) {
|
|
3158
|
+
async function formatExtractSite(raw, input, ctx) {
|
|
3006
3159
|
const parsed = parseData(raw);
|
|
3007
3160
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
3008
3161
|
const d = parsed.data;
|
|
@@ -3037,6 +3190,36 @@ function formatExtractSite(raw, input) {
|
|
|
3037
3190
|
schemaTypes: schemaTypesOf(p)
|
|
3038
3191
|
})));
|
|
3039
3192
|
const preview = pages.slice(0, BULK_PAGE_THRESHOLD).map(pageRow).join("\n");
|
|
3193
|
+
const summaryHeader = [
|
|
3194
|
+
`# Site Extract: ${input.url}`,
|
|
3195
|
+
durationLine,
|
|
3196
|
+
`
|
|
3197
|
+
## Pages (first ${Math.min(BULK_PAGE_THRESHOLD, pages.length)} of ${pages.length})
|
|
3198
|
+
| # | Title | URL | Schema |
|
|
3199
|
+
|---|-------|-----|--------|
|
|
3200
|
+
${preview}`
|
|
3201
|
+
].join("\n");
|
|
3202
|
+
if (!bulk && ctx?.hosted) {
|
|
3203
|
+
const pageDetails2 = pages.map((p, i) => {
|
|
3204
|
+
const body = (p.bodyMarkdown ?? "").trim();
|
|
3205
|
+
return [
|
|
3206
|
+
`
|
|
3207
|
+
## ${i + 1}. ${p.title ?? "Untitled"}`,
|
|
3208
|
+
`- **URL:** ${p.url}`,
|
|
3209
|
+
p.metaDescription ? `- **Description:** ${p.metaDescription}` : "",
|
|
3210
|
+
body ? `
|
|
3211
|
+
${body}` : "_(no content extracted)_"
|
|
3212
|
+
].filter(Boolean).join("\n");
|
|
3213
|
+
}).join("\n");
|
|
3214
|
+
const fullForOffload = `${summaryHeader}
|
|
3215
|
+
|
|
3216
|
+
---
|
|
3217
|
+
# Full Page Content
|
|
3218
|
+
${pageDetails2}${tips}`;
|
|
3219
|
+
const capped = capArray(structuredContent.pages, STRUCTURED_ARRAY_CAP);
|
|
3220
|
+
const offloaded = await maybeOffload("extract_site", ctx, fullForOffload, `${summaryHeader}${tips}`, { ...structuredContent, pages: capped.items, truncatedCount: capped.truncatedCount });
|
|
3221
|
+
if (offloaded) return offloaded;
|
|
3222
|
+
}
|
|
3040
3223
|
const location = bulk ? `
|
|
3041
3224
|
## \u{1F4C1} Bulk scrape saved
|
|
3042
3225
|
- **Folder:** \`${bulk.dir}\` \u2190 all scraped page content is here
|
|
@@ -3089,7 +3272,7 @@ ${body}` : "_(no content extracted)_"
|
|
|
3089
3272
|
${pageDetails}${tips}`;
|
|
3090
3273
|
return { ...oneBlock(full, diskReport), structuredContent };
|
|
3091
3274
|
}
|
|
3092
|
-
async function formatAuditSite(raw, input) {
|
|
3275
|
+
async function formatAuditSite(raw, input, ctx) {
|
|
3093
3276
|
const parsed = parseData(raw);
|
|
3094
3277
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
3095
3278
|
const d = parsed.data;
|
|
@@ -3120,6 +3303,24 @@ async function formatAuditSite(raw, input) {
|
|
|
3120
3303
|
images: { unique: img.unique, totalBytes: img.totalBytes, over100kb: img.over100kb, legacyFormat: img.legacyFormat },
|
|
3121
3304
|
links: { internal: lr.internal.totalLinks, external: lr.external.totalLinks, orphans: lr.internal.orphans, brokenInternal: lr.internal.brokenInternal, externalDomains: lr.external.uniqueDomains }
|
|
3122
3305
|
};
|
|
3306
|
+
const summary = [
|
|
3307
|
+
`# Technical SEO Audit: ${input.url}`,
|
|
3308
|
+
durationLine,
|
|
3309
|
+
`
|
|
3310
|
+
## Top issues
|
|
3311
|
+
${topIssues || "_none found_"}`,
|
|
3312
|
+
`
|
|
3313
|
+
## Links
|
|
3314
|
+
${linkLine}`,
|
|
3315
|
+
`
|
|
3316
|
+
## Images
|
|
3317
|
+
${imgLine}`
|
|
3318
|
+
].join("\n");
|
|
3319
|
+
if (!bulk && ctx?.hosted) {
|
|
3320
|
+
const fullForOffload = [summary, "\n---\n# Full Audit Report", seo.reportMd, renderImageSection(imageAudit), renderLinkReport(seo.linkReport)].join("\n");
|
|
3321
|
+
const offloaded = await maybeOffload("audit_site", ctx, fullForOffload, summary, structuredContent);
|
|
3322
|
+
if (offloaded) return offloaded;
|
|
3323
|
+
}
|
|
3123
3324
|
const location = bulk ? `
|
|
3124
3325
|
## \u{1F4C1} Technical audit saved
|
|
3125
3326
|
- **Folder:** \`${bulk.dir}\` \u2190 full audit + page content here
|
|
@@ -3890,7 +4091,7 @@ ${rows}`,
|
|
|
3890
4091
|
}
|
|
3891
4092
|
};
|
|
3892
4093
|
}
|
|
3893
|
-
function formatDirectoryWorkflow(raw, input) {
|
|
4094
|
+
async function formatDirectoryWorkflow(raw, input, ctx) {
|
|
3894
4095
|
const parsed = parseData(raw);
|
|
3895
4096
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
3896
4097
|
const d = parsed.data;
|
|
@@ -3942,26 +4143,29 @@ ${businessRows}` : null,
|
|
|
3942
4143
|
durationMs != null ? `
|
|
3943
4144
|
*Completed in ${(durationMs / 1e3).toFixed(1)}s*` : null
|
|
3944
4145
|
].filter(Boolean).join("\n");
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
cities,
|
|
3962
|
-
durationMs: durationMs ?? 0
|
|
3963
|
-
}
|
|
4146
|
+
const structuredContent = {
|
|
4147
|
+
query: d.query,
|
|
4148
|
+
state: d.state,
|
|
4149
|
+
minPopulation: d.minPopulation,
|
|
4150
|
+
populationYear: d.populationYear,
|
|
4151
|
+
maxResultsPerCity: d.maxResultsPerCity,
|
|
4152
|
+
concurrency: d.concurrency,
|
|
4153
|
+
censusSourceUrl: d.censusSourceUrl,
|
|
4154
|
+
usZipsSourcePath: d.usZipsSourcePath ?? null,
|
|
4155
|
+
warnings,
|
|
4156
|
+
extractedAt: d.extractedAt,
|
|
4157
|
+
selectedCityCount: d.selectedCityCount,
|
|
4158
|
+
totalResultCount,
|
|
4159
|
+
csvPath,
|
|
4160
|
+
cities,
|
|
4161
|
+
durationMs: durationMs ?? 0
|
|
3964
4162
|
};
|
|
4163
|
+
const summary = `# Directory Workflow: ${input.query}
|
|
4164
|
+
**Markets:** ${cities.length} \xB7 **Maps results:** ${totalResultCount} \xB7 **State:** ${d.state ?? input.state ?? "US"}`;
|
|
4165
|
+
const capped = capArray(cities, STRUCTURED_ARRAY_CAP);
|
|
4166
|
+
const offloaded = await maybeOffload("directory_workflow", ctx, full, summary, { ...structuredContent, cities: capped.items, truncatedCount: capped.truncatedCount });
|
|
4167
|
+
if (offloaded) return offloaded;
|
|
4168
|
+
return { ...oneBlock(full), structuredContent };
|
|
3965
4169
|
}
|
|
3966
4170
|
function formatMapsPlaceIntel(raw, input) {
|
|
3967
4171
|
const parsed = parseData(raw);
|
|
@@ -4483,9 +4687,13 @@ ${rows}` : ""
|
|
|
4483
4687
|
}
|
|
4484
4688
|
|
|
4485
4689
|
// src/mcp/server-instructions.ts
|
|
4486
|
-
|
|
4690
|
+
function serverInstructions(savesReportsLocally) {
|
|
4691
|
+
const reportLine = savesReportsLocally ? "All report-producing tools also save a full Markdown report to disk." : "On this hosted endpoint, small reports return inline; large reports are stored as artifacts \u2014 read them back with report_artifact_read.";
|
|
4692
|
+
return `
|
|
4487
4693
|
# MCP Scraper
|
|
4488
4694
|
|
|
4695
|
+
${reportLine}
|
|
4696
|
+
|
|
4489
4697
|
Scrapes and analyzes web, search, maps, social, and site data. **Pick a tool by NAME from the map
|
|
4490
4698
|
below, then load its schema before calling.** Where a tool needs a value another tool produces, the
|
|
4491
4699
|
seam is noted so you can chain them.
|
|
@@ -4550,15 +4758,30 @@ Multi-step orchestrations \u2014 prefer these over hand-chaining primitives when
|
|
|
4550
4758
|
- Stand up a rank-tracking blueprint (database + cron + ingestion plan) -> **rank_tracker_workflow**.
|
|
4551
4759
|
- Find which sources an AI answer cites for a query (AEO) -> **query_fanout_workflow** (open a browser
|
|
4552
4760
|
session on chatgpt.com or claude.ai FIRST, then run it against that session).
|
|
4761
|
+
- Not sure which workflow fits a goal -> **workflow_suggest**; to just see what's available -> **workflow_list**.
|
|
4762
|
+
- Hosted/stepwise chain: **workflow_run** starts and returns \`runId\` (+ \`nextStep\` when multi-leg) ->
|
|
4763
|
+
**workflow_step** with that \`runId\` advances one leg at a time until \`done\` -> **workflow_status**
|
|
4764
|
+
re-opens a run to check state or recover artifact ids -> **workflow_artifact_read** pulls one artifact's
|
|
4765
|
+
full content by \`runId\` + \`artifactId\`.
|
|
4766
|
+
- Browser replay recording: **browser_replay_start** begins it -> **browser_replay_mark** (while
|
|
4767
|
+
recording) locates a DOM target and returns a ready-to-use timed annotation -> **browser_replay_stop**
|
|
4768
|
+
ends it -> feed collected annotations to **browser_replay_annotate**, or just
|
|
4769
|
+
**browser_replay_download** the plain MP4. **browser_list_replays** recovers replay ids.
|
|
4770
|
+
- Video breakdown is async: **video_frame_analysis** takes a DIRECT media file URL (resolve
|
|
4771
|
+
YouTube/Facebook/Instagram page URLs to one first, e.g. via \`facebook_page_intel\`'s \`videoUrl\`) and
|
|
4772
|
+
returns a \`runId\` immediately -> poll **video_frame_analysis_status** with that \`runId\` until \`status\`
|
|
4773
|
+
is \`done\`.
|
|
4553
4774
|
|
|
4554
4775
|
## Notes
|
|
4555
4776
|
- Bulk / full-site crawls: call \`extract_site\` with \`rotateProxies:true\` for blocked or rate-limited
|
|
4556
4777
|
sites. It returns a saved folder/artifact plus a summary, not the full content inline.
|
|
4557
4778
|
- Browser sessions and media transcription cost credits and take longer \u2014 prefer the cheapest tool that
|
|
4558
4779
|
answers the question (plain search/extract before browser agents).
|
|
4559
|
-
- Large results are saved to disk or an artifact and returned as a summary plus a path
|
|
4560
|
-
full detail rather than expecting the whole payload inline.
|
|
4780
|
+
- Large results are saved to disk or an artifact and returned as a summary plus a path or artifactId;
|
|
4781
|
+
read it back for full detail rather than expecting the whole payload inline.
|
|
4561
4782
|
`.trim();
|
|
4783
|
+
}
|
|
4784
|
+
var SERVER_INSTRUCTIONS = serverInstructions(true);
|
|
4562
4785
|
|
|
4563
4786
|
// src/mcp/mcp-tool-schemas.ts
|
|
4564
4787
|
var import_zod3 = require("zod");
|
|
@@ -4664,25 +4887,25 @@ var RawMapsAboutAttributeSchema = import_zod2.z.object({
|
|
|
4664
4887
|
|
|
4665
4888
|
// src/mcp/mcp-tool-schemas.ts
|
|
4666
4889
|
var HarvestPaaInputSchema = {
|
|
4667
|
-
query: import_zod3.z.string().min(1).describe('The search query.
|
|
4668
|
-
location: import_zod3.z.string().optional().describe('City, region, or country for geo signals
|
|
4669
|
-
maxQuestions: import_zod3.z.number().int().min(1).max(200).default(30).describe("
|
|
4670
|
-
gl: import_zod3.z.string().length(2).default("us").describe("Google country code inferred from location or user language.
|
|
4671
|
-
hl: import_zod3.z.string().default("en").describe("Google interface/content language inferred from the user request.
|
|
4672
|
-
device: import_zod3.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use
|
|
4673
|
-
proxyMode: import_zod3.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe('Leave unset (clean egress
|
|
4674
|
-
proxyZip: import_zod3.z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location"
|
|
4675
|
-
debug: import_zod3.z.boolean().default(false).describe("Include sanitized
|
|
4890
|
+
query: import_zod3.z.string().min(1).describe('The search query. KEEP the place in the query text for localized results (e.g. "best hvac company Denver CO") and also set location \u2014 city-in-query is what localizes reliably.'),
|
|
4891
|
+
location: import_zod3.z.string().optional().describe('City, region, or country for geo signals, e.g. "Denver, CO". Set alongside city-in-query wording; alone it does NOT reliably localize.'),
|
|
4892
|
+
maxQuestions: import_zod3.z.number().int().min(1).max(200).default(30).describe("PAA questions to extract. Default 30, maximum 200. Use 10 for quick probes, 100-200 for deep research. Billed per extracted question; unused hold refunded."),
|
|
4893
|
+
gl: import_zod3.z.string().length(2).default("us").describe("Google country code inferred from location or user language."),
|
|
4894
|
+
hl: import_zod3.z.string().default("en").describe("Google interface/content language inferred from the user request."),
|
|
4895
|
+
device: import_zod3.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only for mobile rankings."),
|
|
4896
|
+
proxyMode: import_zod3.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe('Leave unset (clean egress). Do NOT set "location" just because a city was named \u2014 that comes from city-in-query wording. "location" forces residential geo-IP for rank-tracking fidelity, is frequently CAPTCHA-blocked, and accepts failures.'),
|
|
4897
|
+
proxyZip: import_zod3.z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location".'),
|
|
4898
|
+
debug: import_zod3.z.boolean().default(false).describe("Include sanitized diagnostics for debugging localization, CAPTCHA, or proxy behavior.")
|
|
4676
4899
|
};
|
|
4677
4900
|
var ExtractUrlInputSchema = {
|
|
4678
|
-
url: import_zod3.z.string().url().describe("Public http/https URL to extract.
|
|
4679
|
-
screenshot: import_zod3.z.boolean().default(false).describe("
|
|
4680
|
-
screenshotDevice: import_zod3.z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport for screenshot. desktop = 1440\xD7900
|
|
4681
|
-
extractBranding: import_zod3.z.boolean().default(false).describe("Extract brand colors, fonts, logo, and favicon
|
|
4682
|
-
downloadMedia: import_zod3.z.boolean().default(false).describe("Extract and download
|
|
4901
|
+
url: import_zod3.z.string().url().describe("Public http/https URL to extract."),
|
|
4902
|
+
screenshot: import_zod3.z.boolean().default(false).describe("Capture a full-page screenshot, saved to ~/Downloads/mcp-scraper/screenshots/ and returned inline."),
|
|
4903
|
+
screenshotDevice: import_zod3.z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport for screenshot. desktop = 1440\xD7900, mobile = 390\xD7844."),
|
|
4904
|
+
extractBranding: import_zod3.z.boolean().default(false).describe("Extract brand colors, fonts, logo, and favicon via a rendered browser session."),
|
|
4905
|
+
downloadMedia: import_zod3.z.boolean().default(false).describe("Extract and download page media (images/video/audio) to ~/Downloads/mcp-scraper/media/. Ad/tracking noise is filtered automatically."),
|
|
4683
4906
|
mediaTypes: import_zod3.z.array(import_zod3.z.enum(["image", "video", "audio"])).default(["image", "video", "audio"]).describe("Which media types to download. Default all three."),
|
|
4684
|
-
allowLocal: import_zod3.z.boolean().default(false).describe("Allow localhost and private-network URLs.
|
|
4685
|
-
depositToVault: import_zod3.z.boolean().default(false).describe("
|
|
4907
|
+
allowLocal: import_zod3.z.boolean().default(false).describe("Allow localhost and private-network URLs. Local development only."),
|
|
4908
|
+
depositToVault: import_zod3.z.boolean().default(false).describe("Save the full page content into the user's MCP Memory vault server-side, embedded for semantic recall \u2014 the full body is NOT returned to chat."),
|
|
4686
4909
|
vaultName: import_zod3.z.string().trim().min(1).max(120).optional().describe("Optional vault to deposit into. Defaults to the user's personal vault.")
|
|
4687
4910
|
};
|
|
4688
4911
|
var MapSiteUrlsInputSchema = {
|
|
@@ -4690,34 +4913,34 @@ var MapSiteUrlsInputSchema = {
|
|
|
4690
4913
|
maxUrls: import_zod3.z.number().int().min(1).max(1e4).optional().describe("Maximum URLs to discover. Use 100 for normal maps, up to 10000 for a full inventory. Large maps (over 500 URLs) write the complete inventory to a local file and return only a summary plus the file path instead of the full list inline.")
|
|
4691
4914
|
};
|
|
4692
4915
|
var ExtractSiteInputSchema = {
|
|
4693
|
-
url: import_zod3.z.string().url().describe("Public website URL or domain to crawl for page CONTENT
|
|
4694
|
-
maxPages: import_zod3.z.number().int().min(1).max(1e4).optional().describe("Maximum pages to extract.
|
|
4695
|
-
rotateProxies: import_zod3.z.boolean().optional().describe("Route page fetches through rotating residential proxies
|
|
4696
|
-
rotateProxyEvery: import_zod3.z.number().int().min(1).max(100).optional().describe("When rotateProxies is on,
|
|
4697
|
-
formats: import_zod3.z.array(import_zod3.z.enum(["markdown", "links", "json", "images", "branding"])).optional().describe("
|
|
4916
|
+
url: import_zod3.z.string().url().describe("Public website URL or domain to crawl for page CONTENT (map + scrape). For a technical SEO audit use audit_site instead \u2014 this returns content only, not analysis."),
|
|
4917
|
+
maxPages: import_zod3.z.number().int().min(1).max(1e4).optional().describe("Maximum pages to extract. Bulk crawls (over 25 pages) switch to folder mode: each page saved as its own Markdown file, with a summary plus folder path returned instead of inlining content."),
|
|
4918
|
+
rotateProxies: import_zod3.z.boolean().optional().describe("Route page fetches through rotating residential proxies to defeat rate-limiting and bot blocks (403/429). Slower and pricier \u2014 use only when a site blocks normal crawling."),
|
|
4919
|
+
rotateProxyEvery: import_zod3.z.number().int().min(1).max(100).optional().describe("When rotateProxies is on, pages fetched per proxy before rotating. Default 30."),
|
|
4920
|
+
formats: import_zod3.z.array(import_zod3.z.enum(["markdown", "links", "json", "images", "branding"])).optional().describe("Per-page output formats: markdown, links, json, images are captured cheaply from HTML; branding (site-level logo/colors/fonts) requires a browser and adds time. Defaults to markdown+links.")
|
|
4698
4921
|
};
|
|
4699
4922
|
var AuditSiteInputSchema = {
|
|
4700
|
-
url: import_zod3.z.string().url().describe("Public website URL or domain
|
|
4701
|
-
maxPages: import_zod3.z.number().int().min(1).max(1e4).optional().describe("Maximum pages to crawl and audit.
|
|
4702
|
-
rotateProxies: import_zod3.z.boolean().optional().describe("Route page fetches through rotating residential proxies to defeat rate-limiting and bot blocks
|
|
4703
|
-
rotateProxyEvery: import_zod3.z.number().int().min(1).max(100).optional().describe("When rotateProxies is on,
|
|
4923
|
+
url: import_zod3.z.string().url().describe("Public website URL or domain for a full technical SEO audit (issues, link graph, indexability, headings, images). For plain content use extract_site instead."),
|
|
4924
|
+
maxPages: import_zod3.z.number().int().min(1).max(1e4).optional().describe("Maximum pages to crawl and audit. Always writes a folder of analysis files plus per-page content, returning a summary plus the folder path."),
|
|
4925
|
+
rotateProxies: import_zod3.z.boolean().optional().describe("Route page fetches through rotating residential proxies to defeat rate-limiting and bot blocks. Slower/pricier \u2014 use only when a site blocks normal crawling."),
|
|
4926
|
+
rotateProxyEvery: import_zod3.z.number().int().min(1).max(100).optional().describe("When rotateProxies is on, pages fetched per proxy before rotating. Default 30.")
|
|
4704
4927
|
};
|
|
4705
4928
|
var YoutubeHarvestInputSchema = {
|
|
4706
4929
|
mode: import_zod3.z.enum(["search", "channel"]).describe("Use search for topic/keyword requests. Use channel when the user provides @handle, channel ID, or channel URL."),
|
|
4707
4930
|
query: import_zod3.z.string().optional().describe("Required when mode is search. The YouTube search topic in the user\u2019s words."),
|
|
4708
4931
|
channelHandle: import_zod3.z.string().optional().describe("YouTube channel handle, channel ID, or URL. Examples: @mkbhd, UC..., https://youtube.com/@mkbhd."),
|
|
4709
|
-
maxVideos: import_zod3.z.number().int().min(1).max(500).default(50).describe("Number of videos to return. Default 50, maximum 500.
|
|
4932
|
+
maxVideos: import_zod3.z.number().int().min(1).max(500).default(50).describe("Number of videos to return. Default 50, maximum 500.")
|
|
4710
4933
|
};
|
|
4711
4934
|
var YoutubeTranscribeInputSchema = {
|
|
4712
4935
|
videoId: import_zod3.z.string().min(1).optional().describe("YouTube video ID, e.g. dQw4w9WgXcQ. Use only an ID returned by youtube_harvest or visible in a YouTube URL; do not invent one."),
|
|
4713
|
-
url: import_zod3.z.string().url().optional().describe("Full YouTube URL
|
|
4936
|
+
url: import_zod3.z.string().url().optional().describe("Full YouTube URL. Use when the user pasted a URL instead of an ID. Provide videoId or url.")
|
|
4714
4937
|
};
|
|
4715
4938
|
var FacebookPageIntelInputSchema = {
|
|
4716
|
-
pageId: import_zod3.z.string().optional().describe("Facebook advertiser/page ID. Use only a
|
|
4717
|
-
libraryId: import_zod3.z.string().optional().describe("Facebook Ad Library archive ID
|
|
4939
|
+
pageId: import_zod3.z.string().optional().describe("Facebook advertiser/page ID. Use only a value returned by facebook_ad_search or copied from Ad Library."),
|
|
4940
|
+
libraryId: import_zod3.z.string().optional().describe("Facebook Ad Library archive ID. Use a value returned by facebook_ad_search, or a libraryId/adArchiveId visible in Ad Library."),
|
|
4718
4941
|
query: import_zod3.z.string().optional().describe("Advertiser or brand name when pageId/libraryId is not known. One of pageId, libraryId, or query is required."),
|
|
4719
|
-
maxAds: import_zod3.z.number().int().min(1).max(200).default(50).describe("Maximum ads to inspect. Default 50, maximum 200.
|
|
4720
|
-
country: import_zod3.z.string().length(2).default("US").describe("Two-letter Ad Library country code. Default US.
|
|
4942
|
+
maxAds: import_zod3.z.number().int().min(1).max(200).default(50).describe("Maximum ads to inspect. Default 50, maximum 200."),
|
|
4943
|
+
country: import_zod3.z.string().length(2).default("US").describe("Two-letter Ad Library country code. Default US.")
|
|
4721
4944
|
};
|
|
4722
4945
|
var FacebookAdSearchInputSchema = {
|
|
4723
4946
|
query: import_zod3.z.string().min(1).describe("Advertiser, brand, competitor, niche, or keyword to search in Facebook Ad Library."),
|
|
@@ -4725,7 +4948,7 @@ var FacebookAdSearchInputSchema = {
|
|
|
4725
4948
|
maxResults: import_zod3.z.number().int().min(1).max(20).default(10).describe("Maximum advertisers to return. Default 10, maximum 20. Prefer tighter search terms over maxing this out.")
|
|
4726
4949
|
};
|
|
4727
4950
|
var RedditThreadInputSchema = {
|
|
4728
|
-
url: import_zod3.z.string().min(1).describe("A reddit.com thread/post URL (www, old,
|
|
4951
|
+
url: import_zod3.z.string().min(1).describe("A reddit.com thread/post URL (www, old, new Reddit, or redd.it)."),
|
|
4729
4952
|
maxComments: import_zod3.z.number().int().min(1).max(2e3).optional().describe("Optional cap on comments returned. Omit to return all captured comments.")
|
|
4730
4953
|
};
|
|
4731
4954
|
var VideoFrameAnalysisInputSchema = {
|
|
@@ -4739,14 +4962,14 @@ var VideoFrameAnalysisStatusInputSchema = {
|
|
|
4739
4962
|
runId: import_zod3.z.string().min(1).describe("The runId returned by video_frame_analysis.")
|
|
4740
4963
|
};
|
|
4741
4964
|
var FacebookAdTranscribeInputSchema = {
|
|
4742
|
-
videoUrl: import_zod3.z.string().url().describe("Direct Facebook CDN video URL from
|
|
4965
|
+
videoUrl: import_zod3.z.string().url().describe("Direct Facebook CDN video URL from facebook_page_intel. Do not pass a public post/reel/share URL \u2014 use facebook_video_transcribe for those.")
|
|
4743
4966
|
};
|
|
4744
4967
|
var FacebookVideoTranscribeInputSchema = {
|
|
4745
|
-
url: import_zod3.z.string().url().describe("Organic Facebook reel
|
|
4968
|
+
url: import_zod3.z.string().url().describe("Organic Facebook reel/video/watch/post/share URL from facebook.com, m.facebook.com, or fb.watch."),
|
|
4746
4969
|
quality: import_zod3.z.enum(["best", "hd", "sd"]).default("best").describe("Preferred progressive MP4 quality. Use best by default; hd prefers the highest HD progressive URL; sd forces the SD URL.")
|
|
4747
4970
|
};
|
|
4748
4971
|
var GoogleAdsSearchInputSchema = {
|
|
4749
|
-
query: import_zod3.z.string().min(1).describe("A domain (e.g. getviktor.com) or advertiser/brand name to look up in
|
|
4972
|
+
query: import_zod3.z.string().min(1).describe("A domain (e.g. getviktor.com) or advertiser/brand name to look up in Google Ads Transparency Center."),
|
|
4750
4973
|
region: import_zod3.z.string().length(2).default("US").describe("Two-letter region code for where the ads are shown. Default US. Examples: US, CA, GB, AU."),
|
|
4751
4974
|
maxResults: import_zod3.z.number().int().min(1).max(20).default(10).describe("Maximum advertisers to return. Default 10, maximum 20.")
|
|
4752
4975
|
};
|
|
@@ -4757,78 +4980,84 @@ var GoogleAdsPageIntelInputSchema = {
|
|
|
4757
4980
|
maxAds: import_zod3.z.number().int().min(1).max(200).default(50).describe("Maximum creatives to inspect and hydrate. Default 50, maximum 200. Prefer 25-50 for focused scans.")
|
|
4758
4981
|
};
|
|
4759
4982
|
var GoogleAdsTranscribeInputSchema = {
|
|
4760
|
-
videoUrl: import_zod3.z.string().url().describe("Direct googlevideo.com playback URL from
|
|
4983
|
+
videoUrl: import_zod3.z.string().url().describe("Direct googlevideo.com playback URL from google_ads_page_intel. For YouTube-hosted ads use youtube_transcribe instead.")
|
|
4761
4984
|
};
|
|
4762
4985
|
var InstagramProfileContentInputSchema = {
|
|
4763
4986
|
handle: import_zod3.z.string().min(1).optional().describe("Instagram handle, with or without @. Provide handle or url."),
|
|
4764
|
-
url: import_zod3.z.string().url().optional().describe("Instagram profile URL
|
|
4765
|
-
profile: import_zod3.z.string().min(1).optional().describe("Optional saved hosted browser profile name
|
|
4766
|
-
saveProfileChanges: import_zod3.z.boolean().optional().describe("
|
|
4767
|
-
maxItems: import_zod3.z.number().int().min(1).max(2e3).default(50).describe("Maximum
|
|
4768
|
-
maxScrolls: import_zod3.z.number().int().min(0).max(250).default(10).describe("Maximum pagination scroll attempts. Default 10, maximum 250.
|
|
4769
|
-
scrollDelayMs: import_zod3.z.number().int().min(250).max(5e3).default(1200).describe("Delay after each
|
|
4770
|
-
stableScrollLimit: import_zod3.z.number().int().min(1).max(10).default(4).describe("Stop after this many consecutive scrolls with no new links
|
|
4987
|
+
url: import_zod3.z.string().url().optional().describe("Instagram profile URL. Provide handle or url."),
|
|
4988
|
+
profile: import_zod3.z.string().min(1).optional().describe("Optional saved hosted browser profile name for authenticated Instagram access."),
|
|
4989
|
+
saveProfileChanges: import_zod3.z.boolean().optional().describe("Save browser changes back to the hosted profile. Leave unset unless intentionally updating the saved login."),
|
|
4990
|
+
maxItems: import_zod3.z.number().int().min(1).max(2e3).default(50).describe("Maximum grid URLs to collect. Default 50, maximum 2000."),
|
|
4991
|
+
maxScrolls: import_zod3.z.number().int().min(0).max(250).default(10).describe("Maximum pagination scroll attempts. Default 10, maximum 250."),
|
|
4992
|
+
scrollDelayMs: import_zod3.z.number().int().min(250).max(5e3).default(1200).describe("Delay after each scroll before collecting new links. Default 1200ms."),
|
|
4993
|
+
stableScrollLimit: import_zod3.z.number().int().min(1).max(10).default(4).describe("Stop after this many consecutive scrolls with no new links.")
|
|
4771
4994
|
};
|
|
4772
4995
|
var InstagramMediaDownloadInputSchema = {
|
|
4773
|
-
url: import_zod3.z.string().url().describe("Instagram post, reel, or tv URL, e.g. https://www.instagram.com/reel/SHORTCODE/.
|
|
4774
|
-
profile: import_zod3.z.string().min(1).optional().describe("Optional saved hosted browser profile name
|
|
4775
|
-
saveProfileChanges: import_zod3.z.boolean().optional().describe("
|
|
4776
|
-
mediaTypes: import_zod3.z.array(import_zod3.z.enum(["image", "video", "audio"])).default(["image", "video", "audio"]).describe("Which media types to download when downloadMedia is true.
|
|
4777
|
-
downloadMedia: import_zod3.z.boolean().default(true).describe("Download extracted text/media files to the
|
|
4778
|
-
downloadAllTracks: import_zod3.z.boolean().default(false).describe("Download every captured
|
|
4779
|
-
includeTranscript: import_zod3.z.boolean().default(false).describe("Transcribe the selected audio track
|
|
4780
|
-
mux: import_zod3.z.boolean().default(true).describe("
|
|
4996
|
+
url: import_zod3.z.string().url().describe("Instagram post, reel, or tv URL, e.g. https://www.instagram.com/reel/SHORTCODE/."),
|
|
4997
|
+
profile: import_zod3.z.string().min(1).optional().describe("Optional saved hosted browser profile name for authenticated Instagram access."),
|
|
4998
|
+
saveProfileChanges: import_zod3.z.boolean().optional().describe("Save browser changes back to the hosted profile. Leave unset unless intentionally updating the saved login."),
|
|
4999
|
+
mediaTypes: import_zod3.z.array(import_zod3.z.enum(["image", "video", "audio"])).default(["image", "video", "audio"]).describe("Which media types to download when downloadMedia is true."),
|
|
5000
|
+
downloadMedia: import_zod3.z.boolean().default(true).describe("Download extracted text/media files to the output directory. Media URLs are always returned even when false."),
|
|
5001
|
+
downloadAllTracks: import_zod3.z.boolean().default(false).describe("Download every captured MP4 track instead of only the best video/audio pair."),
|
|
5002
|
+
includeTranscript: import_zod3.z.boolean().default(false).describe("Transcribe the selected audio track. Adds transcription cost and time."),
|
|
5003
|
+
mux: import_zod3.z.boolean().default(true).describe("Mux separately downloaded video/audio tracks into one MP4 if ffmpeg is available.")
|
|
4781
5004
|
};
|
|
4782
5005
|
var MapsPlaceIntelInputSchema = {
|
|
4783
|
-
businessName: import_zod3.z.string().min(1).describe('Business name only.
|
|
4784
|
-
location: import_zod3.z.string().min(1).describe('City/region/country where the business should be searched, e.g. "Denver, CO".
|
|
5006
|
+
businessName: import_zod3.z.string().min(1).describe('Business name only, e.g. "Elite Roofing" (not "Elite Roofing Denver CO" \u2014 put the city in location).'),
|
|
5007
|
+
location: import_zod3.z.string().min(1).describe('City/region/country where the business should be searched, e.g. "Denver, CO".'),
|
|
4785
5008
|
gl: import_zod3.z.string().length(2).default("us").describe("Google country code inferred from location."),
|
|
4786
5009
|
hl: import_zod3.z.string().length(2).default("en").describe("Language inferred from user request."),
|
|
4787
|
-
includeReviews: import_zod3.z.boolean().default(false).describe("
|
|
4788
|
-
maxReviews: import_zod3.z.number().int().min(1).max(500).default(50).describe("Max review cards
|
|
5010
|
+
includeReviews: import_zod3.z.boolean().default(false).describe("Fetch individual review cards \u2014 for reviews, customer pain, complaints, or praise themes."),
|
|
5011
|
+
maxReviews: import_zod3.z.number().int().min(1).max(500).default(50).describe("Max review cards when includeReviews is true. Default 50, maximum 500.")
|
|
4789
5012
|
};
|
|
4790
5013
|
var MapsSearchInputSchema = {
|
|
4791
|
-
query: import_zod3.z.string().min(1).describe('Business category, niche,
|
|
4792
|
-
location: import_zod3.z.string().optional().describe('City, region, country, or service area
|
|
5014
|
+
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.'),
|
|
5015
|
+
location: import_zod3.z.string().optional().describe('City, region, country, or service area, e.g. "Denver, CO".'),
|
|
4793
5016
|
gl: import_zod3.z.string().length(2).default("us").describe("Google country code inferred from location."),
|
|
4794
5017
|
hl: import_zod3.z.string().length(2).default("en").describe("Language inferred from user request."),
|
|
4795
|
-
maxResults: import_zod3.z.number().int().min(1).max(50).default(10).describe("Number of
|
|
4796
|
-
proxyMode: import_zod3.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("
|
|
4797
|
-
proxyZip: import_zod3.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential
|
|
4798
|
-
debug: import_zod3.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics
|
|
5018
|
+
maxResults: import_zod3.z.number().int().min(1).max(50).default(10).describe("Number of candidates to return. Default 10, maximum 50."),
|
|
5019
|
+
proxyMode: import_zod3.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Defaults to location (city/state residential proxy targeting). configured forces the service proxy without city/ZIP targeting; none is local debugging only."),
|
|
5020
|
+
proxyZip: import_zod3.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential proxy targeting."),
|
|
5021
|
+
debug: import_zod3.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics.")
|
|
4799
5022
|
};
|
|
4800
5023
|
var DirectoryWorkflowInputSchema = {
|
|
4801
|
-
query: import_zod3.z.string().min(1).describe("Business category, niche, or keyword to search on Google Maps for every
|
|
4802
|
-
state: import_zod3.z.string().min(2).default("TN").describe("US state abbreviation or
|
|
4803
|
-
minPopulation: import_zod3.z.number().int().min(0).default(1e5).describe(
|
|
4804
|
-
populationYear: import_zod3.z.number().int().min(2020).max(2025).default(2025).describe("Census population estimate year
|
|
4805
|
-
maxCities: import_zod3.z.number().int().min(1).max(100).default(25).describe("Maximum
|
|
4806
|
-
maxResultsPerCity: import_zod3.z.number().int().min(1).max(50).default(50).describe("Google Maps
|
|
4807
|
-
concurrency: import_zod3.z.number().int().min(1).max(5).default(5).describe("
|
|
4808
|
-
includeZipGroups: import_zod3.z.boolean().default(true).describe("Attach ZIP groups from a configured US ZIPS CSV when available
|
|
4809
|
-
usZipsCsvPath: import_zod3.z.string().optional().describe("Local/test-only path to a US ZIPS CSV
|
|
4810
|
-
saveCsv: import_zod3.z.boolean().default(true).describe("Save a directory-ready CSV to the MCP Scraper output directory and return its path.
|
|
4811
|
-
proxyMode: import_zod3.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Proxy targeting
|
|
4812
|
-
proxyZip: import_zod3.z.string().regex(/^\d{5}$/).optional().describe("Optional ZIP override for proxy targeting
|
|
4813
|
-
debug: import_zod3.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics
|
|
5024
|
+
query: import_zod3.z.string().min(1).describe("Business category, niche, or keyword to search on Google Maps for every market. Do not include the city."),
|
|
5025
|
+
state: import_zod3.z.string().min(2).default("TN").describe("US state abbreviation or name used to select Census places, e.g. TN."),
|
|
5026
|
+
minPopulation: import_zod3.z.number().int().min(0).default(1e5).describe("Minimum Census place population for market selection."),
|
|
5027
|
+
populationYear: import_zod3.z.number().int().min(2020).max(2025).default(2025).describe("Census population estimate year (2020-2025 Population Estimates Program)."),
|
|
5028
|
+
maxCities: import_zod3.z.number().int().min(1).max(100).default(25).describe("Maximum markets to process after sorting by population descending."),
|
|
5029
|
+
maxResultsPerCity: import_zod3.z.number().int().min(1).max(50).default(50).describe("Google Maps candidates to collect per city."),
|
|
5030
|
+
concurrency: import_zod3.z.number().int().min(1).max(5).default(5).describe("City Maps searches to run in parallel."),
|
|
5031
|
+
includeZipGroups: import_zod3.z.boolean().default(true).describe("Attach ZIP groups from a configured US ZIPS CSV when available (MCP_SCRAPER_USZIPS_CSV_PATH or usZipsCsvPath)."),
|
|
5032
|
+
usZipsCsvPath: import_zod3.z.string().optional().describe("Local/test-only path to a US ZIPS CSV (state_abbr, zipcode, county, city columns). Deployed APIs should use MCP_SCRAPER_USZIPS_CSV_PATH instead. For ZIP enrichment, set MCP_SCRAPER_USZIPS_CSV_PATH on the server, or pass this in local/test mode."),
|
|
5033
|
+
saveCsv: import_zod3.z.boolean().default(true).describe("Save a directory-ready CSV of results to the MCP Scraper output directory and return its path."),
|
|
5034
|
+
proxyMode: import_zod3.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Proxy targeting per city search. Defaults to location (city/ZIP-group residential targeting); configured forces the service proxy; none is local debugging only."),
|
|
5035
|
+
proxyZip: import_zod3.z.string().regex(/^\d{5}$/).optional().describe("Optional ZIP override for proxy targeting; normally omitted."),
|
|
5036
|
+
debug: import_zod3.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics.")
|
|
4814
5037
|
};
|
|
5038
|
+
var ArtifactPointerOutputSchema = import_zod3.z.object({
|
|
5039
|
+
artifactId: import_zod3.z.string(),
|
|
5040
|
+
bytes: import_zod3.z.number().int().min(0),
|
|
5041
|
+
expiresAt: import_zod3.z.string(),
|
|
5042
|
+
preview: import_zod3.z.string()
|
|
5043
|
+
});
|
|
4815
5044
|
var RankTrackerModeSchema = import_zod3.z.enum(["maps", "organic", "ai_overview", "paa"]);
|
|
4816
5045
|
var RankTrackerBlueprintInputSchema = {
|
|
4817
5046
|
projectName: import_zod3.z.string().min(1).optional().describe("Optional name for the rank tracker project, client, or campaign."),
|
|
4818
|
-
targetDomain: import_zod3.z.string().min(1).optional().describe("Primary domain to track in organic results, AI Overview citations, and PAA sources
|
|
4819
|
-
targetBusinessName: import_zod3.z.string().min(1).optional().describe("Primary Google Business Profile
|
|
4820
|
-
trackingModes: import_zod3.z.array(RankTrackerModeSchema).min(1).max(4).default(["maps", "organic", "ai_overview", "paa"]).describe("Rank tracker surfaces to build
|
|
4821
|
-
keywords: import_zod3.z.array(import_zod3.z.string().min(1)).max(200).default([]).describe("Seed keywords or service queries to track. Leave empty
|
|
4822
|
-
locations: import_zod3.z.array(import_zod3.z.string().min(1)).max(100).default([]).describe(
|
|
5047
|
+
targetDomain: import_zod3.z.string().min(1).optional().describe("Primary domain to track in organic results, AI Overview citations, and PAA sources."),
|
|
5048
|
+
targetBusinessName: import_zod3.z.string().min(1).optional().describe("Primary Google Business Profile/brand name to match in Maps results. Required for Maps tracking."),
|
|
5049
|
+
trackingModes: import_zod3.z.array(RankTrackerModeSchema).min(1).max(4).default(["maps", "organic", "ai_overview", "paa"]).describe("Rank tracker surfaces to build: maps, organic, ai_overview, paa."),
|
|
5050
|
+
keywords: import_zod3.z.array(import_zod3.z.string().min(1)).max(200).default([]).describe("Seed keywords or service queries to track. Leave empty to derive from user input downstream."),
|
|
5051
|
+
locations: import_zod3.z.array(import_zod3.z.string().min(1)).max(100).default([]).describe('Markets, cities, ZIPs, or service areas to track, e.g. "Denver, CO".'),
|
|
4823
5052
|
competitors: import_zod3.z.array(import_zod3.z.string().min(1)).max(100).default([]).describe("Optional competitor domains or business names to persist as comparison targets."),
|
|
4824
|
-
database: import_zod3.z.enum(["postgres", "neon", "supabase", "sqlite", "mysql"]).default("postgres").describe("Database family
|
|
4825
|
-
scheduleCadence: import_zod3.z.enum(["daily", "weekly", "monthly", "custom"]).default("weekly").describe("Default recurring rank check cadence
|
|
5053
|
+
database: import_zod3.z.enum(["postgres", "neon", "supabase", "sqlite", "mysql"]).default("postgres").describe("Database family to target when generating migrations."),
|
|
5054
|
+
scheduleCadence: import_zod3.z.enum(["daily", "weekly", "monthly", "custom"]).default("weekly").describe("Default recurring rank check cadence."),
|
|
4826
5055
|
customCron: import_zod3.z.string().min(1).optional().describe("Cron expression to use when scheduleCadence is custom."),
|
|
4827
|
-
timezone: import_zod3.z.string().min(1).default("UTC").describe("IANA timezone for scheduled rank checks
|
|
4828
|
-
includeCron: import_zod3.z.boolean().default(true).describe("Include a cron
|
|
4829
|
-
includeDashboard: import_zod3.z.boolean().default(true).describe("Include dashboard/reporting requirements
|
|
4830
|
-
includeAlerts: import_zod3.z.boolean().default(true).describe("Include alert rules for rank movement and SERP feature
|
|
4831
|
-
notes: import_zod3.z.string().max(4e3).optional().describe("Extra product, client, stack, or hosting requirements
|
|
5056
|
+
timezone: import_zod3.z.string().min(1).default("UTC").describe("IANA timezone for scheduled rank checks."),
|
|
5057
|
+
includeCron: import_zod3.z.boolean().default(true).describe("Include a cron/heartbeat worker plan."),
|
|
5058
|
+
includeDashboard: import_zod3.z.boolean().default(true).describe("Include dashboard/reporting requirements."),
|
|
5059
|
+
includeAlerts: import_zod3.z.boolean().default(true).describe("Include alert rules for rank movement and SERP feature changes."),
|
|
5060
|
+
notes: import_zod3.z.string().max(4e3).optional().describe("Extra product, client, stack, or hosting requirements.")
|
|
4832
5061
|
};
|
|
4833
5062
|
var NullableString2 = import_zod3.z.string().nullable();
|
|
4834
5063
|
var MapsSearchAttemptOutput = import_zod3.z.object({
|
|
@@ -4925,7 +5154,9 @@ var DirectoryWorkflowOutputSchema = {
|
|
|
4925
5154
|
attempts: import_zod3.z.array(MapsSearchAttemptOutput),
|
|
4926
5155
|
results: import_zod3.z.array(DirectoryMapsBusinessOutput)
|
|
4927
5156
|
})),
|
|
4928
|
-
durationMs: import_zod3.z.number().int().min(0)
|
|
5157
|
+
durationMs: import_zod3.z.number().int().min(0),
|
|
5158
|
+
truncatedCount: import_zod3.z.number().int().min(0).optional(),
|
|
5159
|
+
artifact: ArtifactPointerOutputSchema.optional()
|
|
4929
5160
|
};
|
|
4930
5161
|
var RankTrackerToolPlanOutput = import_zod3.z.object({
|
|
4931
5162
|
tool: import_zod3.z.string(),
|
|
@@ -4978,7 +5209,7 @@ var EntityIdsOutput = import_zod3.z.object({
|
|
|
4978
5209
|
kgId: import_zod3.z.string().nullable(),
|
|
4979
5210
|
cid: import_zod3.z.string().nullable(),
|
|
4980
5211
|
gcid: import_zod3.z.string().nullable()
|
|
4981
|
-
})).describe("
|
|
5212
|
+
})).describe("Entities named on the page with their kgId/cid/gcid. Flat lists below are the same IDs deduplicated, kept for backward compatibility."),
|
|
4982
5213
|
kgIds: import_zod3.z.array(import_zod3.z.string()),
|
|
4983
5214
|
cids: import_zod3.z.array(import_zod3.z.string()),
|
|
4984
5215
|
gcids: import_zod3.z.array(import_zod3.z.string())
|
|
@@ -5045,7 +5276,9 @@ var ExtractSiteOutputSchema = {
|
|
|
5045
5276
|
title: NullableString2,
|
|
5046
5277
|
schemaTypes: import_zod3.z.array(import_zod3.z.string())
|
|
5047
5278
|
})),
|
|
5048
|
-
durationMs: import_zod3.z.number().min(0)
|
|
5279
|
+
durationMs: import_zod3.z.number().min(0),
|
|
5280
|
+
truncatedCount: import_zod3.z.number().int().min(0).optional(),
|
|
5281
|
+
artifact: ArtifactPointerOutputSchema.optional()
|
|
5049
5282
|
};
|
|
5050
5283
|
var AuditSiteOutputSchema = {
|
|
5051
5284
|
url: import_zod3.z.string(),
|
|
@@ -5065,7 +5298,8 @@ var AuditSiteOutputSchema = {
|
|
|
5065
5298
|
orphans: import_zod3.z.number().int().min(0),
|
|
5066
5299
|
brokenInternal: import_zod3.z.number().int().min(0),
|
|
5067
5300
|
externalDomains: import_zod3.z.number().int().min(0)
|
|
5068
|
-
})
|
|
5301
|
+
}),
|
|
5302
|
+
artifact: ArtifactPointerOutputSchema.optional()
|
|
5069
5303
|
};
|
|
5070
5304
|
var MapsPlaceIntelOutputSchema = {
|
|
5071
5305
|
name: import_zod3.z.string(),
|
|
@@ -5137,7 +5371,9 @@ var MapSiteUrlsOutputSchema = {
|
|
|
5137
5371
|
url: import_zod3.z.string(),
|
|
5138
5372
|
status: import_zod3.z.number().int().nullable()
|
|
5139
5373
|
})),
|
|
5140
|
-
durationMs: import_zod3.z.number().min(0)
|
|
5374
|
+
durationMs: import_zod3.z.number().min(0),
|
|
5375
|
+
truncatedCount: import_zod3.z.number().int().min(0).optional(),
|
|
5376
|
+
artifact: ArtifactPointerOutputSchema.optional()
|
|
5141
5377
|
};
|
|
5142
5378
|
var YoutubeHarvestOutputSchema = {
|
|
5143
5379
|
mode: import_zod3.z.string(),
|
|
@@ -5445,7 +5681,7 @@ var WorkflowIdSchema = import_zod3.z.enum([
|
|
|
5445
5681
|
"ai-overview-language"
|
|
5446
5682
|
]);
|
|
5447
5683
|
var WorkflowListInputSchema = {
|
|
5448
|
-
includeRecipes: import_zod3.z.boolean().default(true).describe("Include high-level AI-facing recipes
|
|
5684
|
+
includeRecipes: import_zod3.z.boolean().default(true).describe("Include high-level AI-facing recipes (market analysis, ICP research, CRO audits, content gaps, etc).")
|
|
5449
5685
|
};
|
|
5450
5686
|
var WorkflowSuggestInputSchema = {
|
|
5451
5687
|
goal: import_zod3.z.string().min(1).describe('The user goal or job to route, e.g. "market analysis for roofers in Tennessee", "ICP research for med spas", "CRO audit for this URL", or "brand design briefing".'),
|
|
@@ -5458,12 +5694,12 @@ var WorkflowSuggestInputSchema = {
|
|
|
5458
5694
|
maxSuggestions: import_zod3.z.number().int().min(1).max(8).default(3).describe("Number of matching workflow recipes to return.")
|
|
5459
5695
|
};
|
|
5460
5696
|
var WorkflowRunInputSchema = {
|
|
5461
|
-
workflowId: WorkflowIdSchema.describe("Workflow to run
|
|
5462
|
-
input: import_zod3.z.record(import_zod3.z.unknown()).default({}).describe("Workflow-specific input object
|
|
5697
|
+
workflowId: WorkflowIdSchema.describe("Workflow to run. Call workflow_list or workflow_suggest first when unsure."),
|
|
5698
|
+
input: import_zod3.z.record(import_zod3.z.unknown()).default({}).describe("Workflow-specific input object; shape depends on workflowId. Call workflow_list or workflow_suggest to see required fields."),
|
|
5463
5699
|
webhookUrl: import_zod3.z.string().url().optional().describe("Optional HTTPS webhook to receive the completed hosted workflow run event.")
|
|
5464
5700
|
};
|
|
5465
5701
|
var WorkflowStepInputSchema = {
|
|
5466
|
-
runId: import_zod3.z.string().min(1).describe("Workflow run id returned by workflow_run
|
|
5702
|
+
runId: import_zod3.z.string().min(1).describe("Workflow run id returned by workflow_run/workflow_step/workflow_status. Advances the run by exactly one step.")
|
|
5467
5703
|
};
|
|
5468
5704
|
var WorkflowStatusInputSchema = {
|
|
5469
5705
|
runId: import_zod3.z.string().min(1).describe("Workflow run id returned by workflow_run, workflow_step, or workflow_status. Use only a returned runId; do not construct one yourself.")
|
|
@@ -5471,7 +5707,7 @@ var WorkflowStatusInputSchema = {
|
|
|
5471
5707
|
var WorkflowArtifactReadInputSchema = {
|
|
5472
5708
|
runId: import_zod3.z.string().min(1).describe("Workflow run id returned by workflow_run, workflow_step, or workflow_status. Use only a returned runId; do not construct one yourself."),
|
|
5473
5709
|
artifactId: import_zod3.z.string().min(1).describe("Artifact id from the run artifact list returned by workflow_run, workflow_step, or workflow_status. Use only a returned artifactId; do not construct one yourself."),
|
|
5474
|
-
maxBytes: import_zod3.z.number().int().min(1e3).max(1e6).default(2e5).describe("Maximum bytes of artifact text to return inline.
|
|
5710
|
+
maxBytes: import_zod3.z.number().int().min(1e3).max(1e6).default(2e5).describe("Maximum bytes of artifact text to return inline.")
|
|
5475
5711
|
};
|
|
5476
5712
|
var WorkflowRecipeOutput = import_zod3.z.object({
|
|
5477
5713
|
id: import_zod3.z.string(),
|
|
@@ -5530,28 +5766,28 @@ var WorkflowArtifactReadOutputSchema = {
|
|
|
5530
5766
|
text: import_zod3.z.string()
|
|
5531
5767
|
};
|
|
5532
5768
|
var SearchSerpInputSchema = {
|
|
5533
|
-
query: import_zod3.z.string().min(1).describe('The search query.
|
|
5534
|
-
location: import_zod3.z.string().optional().describe("City, region, or country for geo signals
|
|
5769
|
+
query: import_zod3.z.string().min(1).describe('The search query. KEEP the place in the query text for localized results (e.g. "best dentist Brooklyn NY") and also set location \u2014 city-in-query is what localizes reliably.'),
|
|
5770
|
+
location: import_zod3.z.string().optional().describe("City, region, or country for geo signals. Set alongside city-in-query wording; alone it does NOT reliably localize."),
|
|
5535
5771
|
gl: import_zod3.z.string().length(2).default("us").describe("Google country code inferred from location or user language."),
|
|
5536
5772
|
hl: import_zod3.z.string().default("en").describe("Google interface/content language inferred from user request."),
|
|
5537
|
-
device: import_zod3.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use
|
|
5538
|
-
proxyMode: import_zod3.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe('Leave unset (clean egress
|
|
5539
|
-
proxyZip: import_zod3.z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location"
|
|
5540
|
-
debug: import_zod3.z.boolean().default(false).describe("Include sanitized
|
|
5541
|
-
pages: import_zod3.z.number().int().min(1).max(2).default(1).describe("Number of result pages to fetch (1\u20132)")
|
|
5773
|
+
device: import_zod3.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only for mobile rankings."),
|
|
5774
|
+
proxyMode: import_zod3.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe('Leave unset (clean egress). Do NOT set "location" just because a city was named \u2014 that comes from city-in-query wording. "location" forces residential geo-IP for rank-tracking fidelity, is frequently CAPTCHA-blocked, and accepts failures.'),
|
|
5775
|
+
proxyZip: import_zod3.z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location".'),
|
|
5776
|
+
debug: import_zod3.z.boolean().default(false).describe("Include sanitized diagnostics for debugging localization, CAPTCHA, or proxy behavior."),
|
|
5777
|
+
pages: import_zod3.z.number().int().min(1).max(2).default(1).describe("Number of result pages to fetch (1\u20132).")
|
|
5542
5778
|
};
|
|
5543
5779
|
var CaptureSerpSnapshotInputSchema = {
|
|
5544
|
-
query: import_zod3.z.string().min(1).describe('Search query to capture
|
|
5545
|
-
location: import_zod3.z.string().optional().describe("City, region, country, or service area
|
|
5546
|
-
gl: import_zod3.z.string().length(2).default("us").describe("Google country code inferred from the requested market
|
|
5780
|
+
query: import_zod3.z.string().min(1).describe('Search query to capture. KEEP the place in the query text for localized captures (e.g. "botox clinic austin tx") and also set location.'),
|
|
5781
|
+
location: import_zod3.z.string().optional().describe("City, region, country, or service area for localized Google results."),
|
|
5782
|
+
gl: import_zod3.z.string().length(2).default("us").describe("Google country code inferred from the requested market."),
|
|
5547
5783
|
hl: import_zod3.z.string().default("en").describe("Google interface/content language inferred from the user request."),
|
|
5548
|
-
device: import_zod3.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only
|
|
5549
|
-
proxyMode: import_zod3.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe('Leave unset (clean egress
|
|
5550
|
-
proxyZip: import_zod3.z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location"
|
|
5551
|
-
pages: import_zod3.z.number().int().min(1).max(2).default(1).describe("
|
|
5552
|
-
debug: import_zod3.z.boolean().default(false).describe("Include sanitized browser
|
|
5553
|
-
includePageSnapshots: import_zod3.z.boolean().default(false).describe("Also capture ranking-page snapshots for selected SERP URLs
|
|
5554
|
-
pageSnapshotLimit: import_zod3.z.number().int().min(0).max(10).default(0).describe("Maximum ranking-page snapshots
|
|
5784
|
+
device: import_zod3.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only for mobile rankings/evidence."),
|
|
5785
|
+
proxyMode: import_zod3.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe('Leave unset (clean egress). Do NOT set "location" just because a city was named \u2014 that comes from city-in-query wording. "location" forces residential geo-IP, is frequently CAPTCHA-blocked, and accepts failures.'),
|
|
5786
|
+
proxyZip: import_zod3.z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location".'),
|
|
5787
|
+
pages: import_zod3.z.number().int().min(1).max(2).default(1).describe("Google result pages to capture. Use 2 only for deeper ranking evidence."),
|
|
5788
|
+
debug: import_zod3.z.boolean().default(false).describe("Include sanitized browser/proxy/location diagnostics."),
|
|
5789
|
+
includePageSnapshots: import_zod3.z.boolean().default(false).describe("Also capture ranking-page snapshots for selected SERP URLs."),
|
|
5790
|
+
pageSnapshotLimit: import_zod3.z.number().int().min(0).max(10).default(0).describe("Maximum ranking-page snapshots when includePageSnapshots is true.")
|
|
5555
5791
|
};
|
|
5556
5792
|
var ScreenshotInputSchema = {
|
|
5557
5793
|
url: import_zod3.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."),
|
|
@@ -5559,15 +5795,26 @@ var ScreenshotInputSchema = {
|
|
|
5559
5795
|
allowLocal: import_zod3.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.")
|
|
5560
5796
|
};
|
|
5561
5797
|
var CaptureSerpPageSnapshotsInputSchema = {
|
|
5562
|
-
urls: import_zod3.z.array(import_zod3.z.string().url()).min(1).max(25).describe("Public HTTP/HTTPS URLs to capture
|
|
5798
|
+
urls: import_zod3.z.array(import_zod3.z.string().url()).min(1).max(25).describe("Public HTTP/HTTPS URLs to capture. Do not pass localhost, private IPs, file URLs, or internal admin URLs."),
|
|
5563
5799
|
targets: import_zod3.z.array(import_zod3.z.object({
|
|
5564
5800
|
url: import_zod3.z.string().url().describe("Public HTTP/HTTPS URL to capture."),
|
|
5565
|
-
sourceKind: import_zod3.z.enum(["organic", "ai_citation", "local_pack_website", "configured_target", "site_subject"]).default("configured_target").describe("Why this page is being captured
|
|
5801
|
+
sourceKind: import_zod3.z.enum(["organic", "ai_citation", "local_pack_website", "configured_target", "site_subject"]).default("configured_target").describe("Why this page is being captured."),
|
|
5566
5802
|
sourcePosition: import_zod3.z.number().int().min(1).optional().describe("Ranking or citation position when the page came from SERP evidence.")
|
|
5567
|
-
}).strict()).min(1).max(25).optional().describe("Structured
|
|
5568
|
-
maxConcurrency: import_zod3.z.number().int().min(1).max(5).default(2).describe("Parallel page captures.
|
|
5569
|
-
timeoutMs: import_zod3.z.number().int().min(1e3).max(6e4).default(15e3).describe("Per-page capture timeout in milliseconds
|
|
5570
|
-
debug: import_zod3.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics
|
|
5803
|
+
}).strict()).min(1).max(25).optional().describe("Structured targets. Use instead of urls when source kind or position should be preserved."),
|
|
5804
|
+
maxConcurrency: import_zod3.z.number().int().min(1).max(5).default(2).describe("Parallel page captures."),
|
|
5805
|
+
timeoutMs: import_zod3.z.number().int().min(1e3).max(6e4).default(15e3).describe("Per-page capture timeout in milliseconds; timeouts return as structured capture failures."),
|
|
5806
|
+
debug: import_zod3.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics.")
|
|
5807
|
+
};
|
|
5808
|
+
var ReportArtifactReadInputSchema = {
|
|
5809
|
+
artifactId: import_zod3.z.string().min(1).describe("Artifact id returned inline by a tool whose result was too large to inline. Use only a returned artifactId; do not construct one yourself."),
|
|
5810
|
+
offset: import_zod3.z.number().int().min(0).default(0).describe("Byte offset to start reading from. Pass the previous call's nextOffset to continue."),
|
|
5811
|
+
maxBytes: import_zod3.z.number().int().min(1e3).max(1e5).default(2e4).describe("Maximum bytes of artifact text to return in this window.")
|
|
5812
|
+
};
|
|
5813
|
+
var ReportArtifactReadOutputSchema = {
|
|
5814
|
+
artifactId: import_zod3.z.string(),
|
|
5815
|
+
text: import_zod3.z.string(),
|
|
5816
|
+
totalBytes: import_zod3.z.number().int().min(0),
|
|
5817
|
+
nextOffset: import_zod3.z.number().int().min(0).nullable()
|
|
5571
5818
|
};
|
|
5572
5819
|
|
|
5573
5820
|
// src/mcp/rank-tracker-blueprint.ts
|
|
@@ -5906,16 +6153,16 @@ function liveWebToolAnnotations(title) {
|
|
|
5906
6153
|
function registerSerpIntelligenceCaptureTools(server2, executor) {
|
|
5907
6154
|
server2.registerTool("capture_serp_snapshot", {
|
|
5908
6155
|
title: "SERP Intelligence Snapshot",
|
|
5909
|
-
description: "Capture a structured SERP Intelligence
|
|
6156
|
+
description: "Capture a structured SERP Intelligence snapshot of a Google query \u2014 the persistent evidence format used by rank-tracking and comparison pipelines. Split query from location; leave proxyMode unset.",
|
|
5910
6157
|
inputSchema: CaptureSerpSnapshotInputSchema,
|
|
5911
|
-
outputSchema: CaptureSerpSnapshotOutputSchema,
|
|
6158
|
+
outputSchema: recordOutputSchema("capture_serp_snapshot", CaptureSerpSnapshotOutputSchema),
|
|
5912
6159
|
annotations: liveWebToolAnnotations("SERP Intelligence Snapshot")
|
|
5913
6160
|
}, async (input) => formatCaptureSerpSnapshot(await executor.captureSerpSnapshot(input), input));
|
|
5914
6161
|
server2.registerTool("capture_serp_page_snapshots", {
|
|
5915
6162
|
title: "SERP Intelligence Page Snapshots",
|
|
5916
|
-
description: "Capture public ranking
|
|
6163
|
+
description: "Capture public ranking pages as SERP Intelligence page snapshots \u2014 persistent page evidence linked to a captured SERP. Provide urls, or targets to preserve source metadata. Private IPs, localhost, file, and internal URLs are rejected.",
|
|
5917
6164
|
inputSchema: CaptureSerpPageSnapshotsInputSchema,
|
|
5918
|
-
outputSchema: CaptureSerpPageSnapshotsOutputSchema,
|
|
6165
|
+
outputSchema: recordOutputSchema("capture_serp_page_snapshots", CaptureSerpPageSnapshotsOutputSchema),
|
|
5919
6166
|
annotations: liveWebToolAnnotations("SERP Intelligence Page Snapshots")
|
|
5920
6167
|
}, async (input) => formatCaptureSerpPageSnapshots(await executor.captureSerpPageSnapshots(input), input));
|
|
5921
6168
|
}
|
|
@@ -5931,7 +6178,7 @@ function localPlanningToolAnnotations(title) {
|
|
|
5931
6178
|
function listSavedReports() {
|
|
5932
6179
|
try {
|
|
5933
6180
|
const dir = outputBaseDir3();
|
|
5934
|
-
return (0,
|
|
6181
|
+
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_path6.join)(dir, f)).mtimeMs })).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, 100);
|
|
5935
6182
|
} catch {
|
|
5936
6183
|
return [];
|
|
5937
6184
|
}
|
|
@@ -5955,233 +6202,254 @@ function registerSavedReportResources(server2) {
|
|
|
5955
6202
|
},
|
|
5956
6203
|
async (uri, variables) => {
|
|
5957
6204
|
const requested = Array.isArray(variables.filename) ? variables.filename[0] : variables.filename;
|
|
5958
|
-
const filename = (0,
|
|
6205
|
+
const filename = (0, import_node_path6.basename)(decodeURIComponent(String(requested ?? "")));
|
|
5959
6206
|
if (!filename.endsWith(".md")) throw new Error("Only saved .md reports can be read");
|
|
5960
|
-
const text = (0,
|
|
6207
|
+
const text = (0, import_node_fs5.readFileSync)((0, import_node_path6.join)(outputBaseDir3(), filename), "utf8");
|
|
5961
6208
|
return { contents: [{ uri: uri.href, mimeType: "text/markdown", text }] };
|
|
5962
6209
|
}
|
|
5963
6210
|
);
|
|
5964
6211
|
}
|
|
5965
6212
|
function registerPaaExtractorMcpTools(server2, executor, options = {}) {
|
|
5966
6213
|
const savesReports = options.savesReportsLocally !== false;
|
|
5967
|
-
const
|
|
5968
|
-
const
|
|
6214
|
+
const fileBehavior = (local, hosted) => savesReports ? local : hosted;
|
|
6215
|
+
const ownerId = options.ownerId ?? "local";
|
|
6216
|
+
const ctx = { hosted: !savesReports, ownerId };
|
|
5969
6217
|
if (savesReports) registerSavedReportResources(server2);
|
|
5970
6218
|
server2.registerTool("harvest_paa", {
|
|
5971
6219
|
title: "Google PAA + SERP Harvest",
|
|
5972
|
-
description:
|
|
6220
|
+
description: "Best default tool for Google search research: People Also Ask questions with answers/sources, organic SERP, local pack, entity IDs, and AI Overview. Split topic from location; leave proxyMode unset. Warn the user before maxQuestions above 100 \u2014 deep harvests can run several minutes with no interim progress, billed per extracted question.",
|
|
5973
6221
|
inputSchema: HarvestPaaInputSchema,
|
|
5974
|
-
outputSchema: HarvestPaaOutputSchema,
|
|
6222
|
+
outputSchema: recordOutputSchema("harvest_paa", HarvestPaaOutputSchema),
|
|
5975
6223
|
annotations: liveWebToolAnnotations("Google PAA + SERP Harvest")
|
|
5976
6224
|
}, async (input) => formatHarvestPaa(await executor.harvestPaa(input), input));
|
|
5977
6225
|
server2.registerTool("search_serp", {
|
|
5978
6226
|
title: "Google SERP Lookup",
|
|
5979
|
-
description:
|
|
6227
|
+
description: "Fast Google SERP lookup without PAA expansion \u2014 rankings, organic results, local pack, positions. Split topic from location; leave proxyMode unset.",
|
|
5980
6228
|
inputSchema: SearchSerpInputSchema,
|
|
5981
|
-
outputSchema: SearchSerpOutputSchema,
|
|
6229
|
+
outputSchema: recordOutputSchema("search_serp", SearchSerpOutputSchema),
|
|
5982
6230
|
annotations: liveWebToolAnnotations("Google SERP Lookup")
|
|
5983
6231
|
}, async (input) => formatSearchSerp(await executor.searchSerp(input), input));
|
|
5984
6232
|
server2.registerTool("extract_url", {
|
|
5985
6233
|
title: "Single URL Extract",
|
|
5986
|
-
description:
|
|
6234
|
+
description: "Extract structured data from one public URL: content, schema, headings, metadata, screenshots, branding, or media assets. Set depositToVault:true to save the full page into the user's MCP Memory vault server-side (not returned to chat).",
|
|
5987
6235
|
inputSchema: ExtractUrlInputSchema,
|
|
5988
|
-
outputSchema: ExtractUrlOutputSchema,
|
|
6236
|
+
outputSchema: recordOutputSchema("extract_url", ExtractUrlOutputSchema),
|
|
5989
6237
|
annotations: liveWebToolAnnotations("Single URL Extract")
|
|
5990
6238
|
}, async (input) => formatExtractUrl(await executor.extractUrl(input), input));
|
|
5991
6239
|
server2.registerTool("map_site_urls", {
|
|
5992
6240
|
title: "Site URL Map",
|
|
5993
|
-
description:
|
|
6241
|
+
description: `Map/crawl a public website for a sitemap, URL inventory, or broken-link scan. Returns internal URLs with HTTP status; ${fileBehavior("maps over 500 URLs are written to a local CSV file instead of inlined.", "large results are stored as a retrievable artifact \u2014 you get an inline summary plus an artifactId for report_artifact_read.")}`,
|
|
5994
6242
|
inputSchema: MapSiteUrlsInputSchema,
|
|
5995
|
-
outputSchema: MapSiteUrlsOutputSchema,
|
|
6243
|
+
outputSchema: recordOutputSchema("map_site_urls", MapSiteUrlsOutputSchema),
|
|
5996
6244
|
annotations: liveWebToolAnnotations("Site URL Map")
|
|
5997
|
-
}, async (input) => formatMapSiteUrls(await executor.mapSiteUrls(input), input));
|
|
6245
|
+
}, async (input) => formatMapSiteUrls(await executor.mapSiteUrls(input), input, ctx));
|
|
5998
6246
|
server2.registerTool("extract_site", {
|
|
5999
6247
|
title: "Multi-Page Site Content Crawl",
|
|
6000
|
-
description:
|
|
6248
|
+
description: `Crawl a public website and return page CONTENT (Markdown) across multiple pages. ${fileBehavior("Bulk crawls over 25 pages are saved as per-page Markdown files in a local folder instead of inlined.", "Large results are stored as a retrievable artifact \u2014 you get an inline summary plus an artifactId for report_artifact_read.")} Content only \u2014 for a technical SEO audit use audit_site instead.`,
|
|
6001
6249
|
inputSchema: ExtractSiteInputSchema,
|
|
6002
|
-
outputSchema: ExtractSiteOutputSchema,
|
|
6250
|
+
outputSchema: recordOutputSchema("extract_site", ExtractSiteOutputSchema),
|
|
6003
6251
|
annotations: liveWebToolAnnotations("Multi-Page Site Content Crawl")
|
|
6004
|
-
}, async (input) => formatExtractSite(await executor.extractSite(input), input));
|
|
6252
|
+
}, async (input) => formatExtractSite(await executor.extractSite(input), input, ctx));
|
|
6005
6253
|
server2.registerTool("audit_site", {
|
|
6006
6254
|
title: "Technical SEO Audit",
|
|
6007
|
-
description:
|
|
6255
|
+
description: `Run a full technical SEO audit (Screaming-Frog-style) on a public website: on-page issues, internal link graph, indexability, heading/image analysis. ${fileBehavior("Writes a folder of analysis files plus per-page content, and returns a summary plus the folder path.", "Large results are stored as a retrievable artifact \u2014 you get an inline summary plus an artifactId for report_artifact_read.")} Use extract_site instead for plain page content.`,
|
|
6008
6256
|
inputSchema: AuditSiteInputSchema,
|
|
6009
|
-
outputSchema: AuditSiteOutputSchema,
|
|
6257
|
+
outputSchema: recordOutputSchema("audit_site", AuditSiteOutputSchema),
|
|
6010
6258
|
annotations: liveWebToolAnnotations("Technical SEO Audit")
|
|
6011
|
-
}, async (input) => formatAuditSite(await executor.auditSite(input), input));
|
|
6259
|
+
}, async (input) => formatAuditSite(await executor.auditSite(input), input, ctx));
|
|
6012
6260
|
server2.registerTool("youtube_harvest", {
|
|
6013
6261
|
title: "YouTube Video Harvest",
|
|
6014
|
-
description:
|
|
6262
|
+
description: 'Harvest YouTube video metadata by topic search or channel library. Use mode "search" for keyword/topic requests, mode "channel" for @handles/channel IDs/URLs. Returns titles, views, durations, and videoIds.',
|
|
6015
6263
|
inputSchema: YoutubeHarvestInputSchema,
|
|
6016
|
-
outputSchema: YoutubeHarvestOutputSchema,
|
|
6264
|
+
outputSchema: recordOutputSchema("youtube_harvest", YoutubeHarvestOutputSchema),
|
|
6017
6265
|
annotations: liveWebToolAnnotations("YouTube Video Harvest")
|
|
6018
6266
|
}, async (input) => formatYoutubeHarvest(await executor.youtubeHarvest(input), input));
|
|
6019
6267
|
server2.registerTool("youtube_transcribe", {
|
|
6020
6268
|
title: "YouTube Transcription",
|
|
6021
|
-
description:
|
|
6269
|
+
description: "Fetch and transcribe captions from a YouTube video. Pass videoId from youtube_harvest, or a url the user pasted. Returns full transcript, timestamped chunks, and word count.",
|
|
6022
6270
|
inputSchema: YoutubeTranscribeInputSchema,
|
|
6023
|
-
outputSchema: YoutubeTranscribeOutputSchema,
|
|
6271
|
+
outputSchema: recordOutputSchema("youtube_transcribe", YoutubeTranscribeOutputSchema),
|
|
6024
6272
|
annotations: liveWebToolAnnotations("YouTube Transcription")
|
|
6025
6273
|
}, async (input) => formatYoutubeTranscribe(await executor.youtubeTranscribe(input), input));
|
|
6026
6274
|
server2.registerTool("facebook_page_intel", {
|
|
6027
6275
|
title: "Facebook Advertiser Ad Intel",
|
|
6028
|
-
description:
|
|
6276
|
+
description: "Harvest ads from a Facebook advertiser: copy, creative angles, CTAs, and direct video URLs ready for facebook_ad_transcribe. Accepts pageId, libraryId, or a brand/advertiser name.",
|
|
6029
6277
|
inputSchema: FacebookPageIntelInputSchema,
|
|
6030
|
-
outputSchema: FacebookPageIntelOutputSchema,
|
|
6278
|
+
outputSchema: recordOutputSchema("facebook_page_intel", FacebookPageIntelOutputSchema),
|
|
6031
6279
|
annotations: liveWebToolAnnotations("Facebook Advertiser Ad Intel")
|
|
6032
6280
|
}, async (input) => formatFacebookPageIntel(await executor.facebookPageIntel(input), input));
|
|
6033
6281
|
server2.registerTool("facebook_ad_search", {
|
|
6034
6282
|
title: "Facebook Ad Library Search",
|
|
6035
|
-
description:
|
|
6283
|
+
description: "Search Facebook Ad Library to find advertisers by brand, competitor, niche, or keyword. Returns advertisers with ad counts and library IDs.",
|
|
6036
6284
|
inputSchema: FacebookAdSearchInputSchema,
|
|
6037
|
-
outputSchema: FacebookAdSearchOutputSchema,
|
|
6285
|
+
outputSchema: recordOutputSchema("facebook_ad_search", FacebookAdSearchOutputSchema),
|
|
6038
6286
|
annotations: liveWebToolAnnotations("Facebook Ad Library Search")
|
|
6039
6287
|
}, async (input) => formatFacebookAdSearch(await executor.facebookAdSearch(input), input));
|
|
6040
6288
|
server2.registerTool("reddit_thread", {
|
|
6041
6289
|
title: "Reddit Thread + Comments",
|
|
6042
|
-
description:
|
|
6290
|
+
description: "Capture a Reddit post and its comment tree from a reddit.com thread URL \u2014 comments, opinions, audience voice. Handles Reddit's bot protection automatically; pass maxComments to cap the list.",
|
|
6043
6291
|
inputSchema: RedditThreadInputSchema,
|
|
6044
|
-
outputSchema: RedditThreadOutputSchema,
|
|
6292
|
+
outputSchema: recordOutputSchema("reddit_thread", RedditThreadOutputSchema),
|
|
6045
6293
|
annotations: liveWebToolAnnotations("Reddit Thread + Comments")
|
|
6046
6294
|
}, async (input) => formatRedditThread(await executor.redditThread(input), input));
|
|
6047
6295
|
server2.registerTool("video_frame_analysis", {
|
|
6048
6296
|
title: "Video Breakdown (frame-by-frame + transcript)",
|
|
6049
|
-
description:
|
|
6297
|
+
description: "Produce a deep frame-by-frame + transcript breakdown of a video \u2014 pacing, hook, visual style, and how to replicate it. Pass a DIRECT video file URL (.mp4/.webm/.mov), not a page URL. Runs asynchronously and costs a flat $1 (refunded on failure): returns a runId immediately; poll video_frame_analysis_status until done.",
|
|
6050
6298
|
inputSchema: VideoFrameAnalysisInputSchema,
|
|
6051
|
-
outputSchema: VideoFrameAnalysisOutputSchema,
|
|
6299
|
+
outputSchema: recordOutputSchema("video_frame_analysis", VideoFrameAnalysisOutputSchema),
|
|
6052
6300
|
annotations: { title: "Video Breakdown", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
6053
6301
|
}, async (input) => executor.videoFrameAnalysis(input));
|
|
6054
6302
|
server2.registerTool("video_frame_analysis_status", {
|
|
6055
6303
|
title: "Video Breakdown Status",
|
|
6056
|
-
description: 'Check
|
|
6304
|
+
description: 'Check progress of a video breakdown started with video_frame_analysis, using its runId. Free to call. When status is "done" it returns the full report and vault path; stop polling on "done" or "failed".',
|
|
6057
6305
|
inputSchema: VideoFrameAnalysisStatusInputSchema,
|
|
6058
|
-
outputSchema: VideoFrameAnalysisStatusOutputSchema,
|
|
6306
|
+
outputSchema: recordOutputSchema("video_frame_analysis_status", VideoFrameAnalysisStatusOutputSchema),
|
|
6059
6307
|
annotations: { title: "Video Breakdown Status", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
6060
6308
|
}, async (input) => executor.videoFrameAnalysisStatus(input));
|
|
6061
6309
|
server2.registerTool("facebook_ad_transcribe", {
|
|
6062
6310
|
title: "Facebook Ad Transcription",
|
|
6063
|
-
description: "Transcribe audio from a Facebook ad video CDN URL
|
|
6311
|
+
description: "Transcribe audio from a Facebook ad video CDN URL returned by facebook_page_intel. Use only that direct videoUrl value \u2014 do not pass public Facebook post/reel/share URLs (use facebook_video_transcribe for those).",
|
|
6064
6312
|
inputSchema: FacebookAdTranscribeInputSchema,
|
|
6065
|
-
outputSchema: FacebookAdTranscribeOutputSchema,
|
|
6313
|
+
outputSchema: recordOutputSchema("facebook_ad_transcribe", FacebookAdTranscribeOutputSchema),
|
|
6066
6314
|
annotations: liveWebToolAnnotations("Facebook Ad Transcription")
|
|
6067
6315
|
}, async (input) => formatFacebookAdTranscribe(await executor.facebookAdTranscribe(input), input));
|
|
6068
6316
|
server2.registerTool("google_ads_search", {
|
|
6069
6317
|
title: "Google Ads Transparency Search",
|
|
6070
|
-
description:
|
|
6318
|
+
description: "Search the Google Ads Transparency Center to find advertisers by domain or brand name. Returns advertisers with advertiser ID and approximate ad count, to pass to google_ads_page_intel.",
|
|
6071
6319
|
inputSchema: GoogleAdsSearchInputSchema,
|
|
6072
|
-
outputSchema: GoogleAdsSearchOutputSchema,
|
|
6320
|
+
outputSchema: recordOutputSchema("google_ads_search", GoogleAdsSearchOutputSchema),
|
|
6073
6321
|
annotations: liveWebToolAnnotations("Google Ads Transparency Search")
|
|
6074
6322
|
}, async (input) => formatGoogleAdsSearch(await executor.googleAdsSearch(input), input));
|
|
6075
6323
|
server2.registerTool("google_ads_page_intel", {
|
|
6076
6324
|
title: "Google Ads Advertiser Intel",
|
|
6077
|
-
description:
|
|
6325
|
+
description: "Harvest an advertiser's ad creatives from the Google Ads Transparency Center: format, image URLs, and \u2014 for video ads \u2014 a YouTube video ID or direct video URL. Accepts advertiserId (from google_ads_search) or a domain.",
|
|
6078
6326
|
inputSchema: GoogleAdsPageIntelInputSchema,
|
|
6079
|
-
outputSchema: GoogleAdsPageIntelOutputSchema,
|
|
6327
|
+
outputSchema: recordOutputSchema("google_ads_page_intel", GoogleAdsPageIntelOutputSchema),
|
|
6080
6328
|
annotations: liveWebToolAnnotations("Google Ads Advertiser Intel")
|
|
6081
6329
|
}, async (input) => formatGoogleAdsPageIntel(await executor.googleAdsPageIntel(input), input));
|
|
6082
6330
|
server2.registerTool("google_ads_transcribe", {
|
|
6083
6331
|
title: "Google Ad Video Transcription",
|
|
6084
|
-
description: "Transcribe audio from a Google video ad
|
|
6332
|
+
description: "Transcribe audio from a Google video ad's direct videoUrl (a googlevideo.com playback URL) returned by google_ads_page_intel. For YouTube-hosted ads, use youtube_transcribe with the returned youtubeVideoId instead.",
|
|
6085
6333
|
inputSchema: GoogleAdsTranscribeInputSchema,
|
|
6086
|
-
outputSchema: GoogleAdsTranscribeOutputSchema,
|
|
6334
|
+
outputSchema: recordOutputSchema("google_ads_transcribe", GoogleAdsTranscribeOutputSchema),
|
|
6087
6335
|
annotations: liveWebToolAnnotations("Google Ad Video Transcription")
|
|
6088
6336
|
}, async (input) => formatGoogleAdsTranscribe(await executor.googleAdsTranscribe(input), input));
|
|
6089
6337
|
server2.registerTool("facebook_video_transcribe", {
|
|
6090
6338
|
title: "Facebook Organic Video Transcription",
|
|
6091
|
-
description:
|
|
6339
|
+
description: "Transcribe audio from an organic Facebook reel/video/post/share URL (including fb.watch). Renders the page, selects the best public CDN MP4, and returns the transcript plus resolved video metadata.",
|
|
6092
6340
|
inputSchema: FacebookVideoTranscribeInputSchema,
|
|
6093
|
-
outputSchema: FacebookVideoTranscribeOutputSchema,
|
|
6341
|
+
outputSchema: recordOutputSchema("facebook_video_transcribe", FacebookVideoTranscribeOutputSchema),
|
|
6094
6342
|
annotations: liveWebToolAnnotations("Facebook Organic Video Transcription")
|
|
6095
6343
|
}, async (input) => formatFacebookVideoTranscribe(await executor.facebookVideoTranscribe(input), input));
|
|
6096
6344
|
server2.registerTool("instagram_profile_content", {
|
|
6097
6345
|
title: "Instagram Profile Content Discovery",
|
|
6098
|
-
description:
|
|
6346
|
+
description: "Discover Instagram profile grid content links (posts/reels/tv) for a handle or profile URL, for later selection with instagram_media_download. Returns profile stats and collected URLs.",
|
|
6099
6347
|
inputSchema: InstagramProfileContentInputSchema,
|
|
6100
|
-
outputSchema: InstagramProfileContentOutputSchema,
|
|
6348
|
+
outputSchema: recordOutputSchema("instagram_profile_content", InstagramProfileContentOutputSchema),
|
|
6101
6349
|
annotations: liveWebToolAnnotations("Instagram Profile Content Discovery")
|
|
6102
6350
|
}, async (input) => formatInstagramProfileContent(await executor.instagramProfileContent(input), input));
|
|
6103
6351
|
server2.registerTool("instagram_media_download", {
|
|
6104
6352
|
title: "Instagram Post/Reel Media Download",
|
|
6105
|
-
description:
|
|
6353
|
+
description: "Extract and download media from one Instagram post, reel, or tv URL \u2014 image, caption, video/audio tracks, optional muxed MP4, or transcript. Selects the best video/audio track pair and muxes when ffmpeg is available.",
|
|
6106
6354
|
inputSchema: InstagramMediaDownloadInputSchema,
|
|
6107
|
-
outputSchema: InstagramMediaDownloadOutputSchema,
|
|
6355
|
+
outputSchema: recordOutputSchema("instagram_media_download", InstagramMediaDownloadOutputSchema),
|
|
6108
6356
|
annotations: liveWebToolAnnotations("Instagram Post/Reel Media Download")
|
|
6109
6357
|
}, async (input) => formatInstagramMediaDownload(await executor.instagramMediaDownload(input), input));
|
|
6110
6358
|
server2.registerTool("maps_place_intel", {
|
|
6111
6359
|
title: "Google Maps Business Profile Details",
|
|
6112
|
-
description:
|
|
6360
|
+
description: "Extract Google Maps business intelligence for one known/named business: rating, reviews, category, address, phone, hours, entity IDs. Not for category searches or multi-business prospect lists \u2014 use maps_search for those. Split business name from location.",
|
|
6113
6361
|
inputSchema: MapsPlaceIntelInputSchema,
|
|
6114
|
-
outputSchema: MapsPlaceIntelOutputSchema,
|
|
6362
|
+
outputSchema: recordOutputSchema("maps_place_intel", MapsPlaceIntelOutputSchema),
|
|
6115
6363
|
annotations: liveWebToolAnnotations("Google Maps Business Profile Details")
|
|
6116
6364
|
}, async (input) => formatMapsPlaceIntel(await executor.mapsPlaceIntel(input), input));
|
|
6117
6365
|
server2.registerTool("maps_search", {
|
|
6118
6366
|
title: "Google Maps Business Search",
|
|
6119
|
-
description:
|
|
6367
|
+
description: "Search Google Maps for multiple businesses by category, niche, or local market \u2014 leads, prospects, competitors, or beyond the 3-pack. Leave proxyMode unset. Returns up to 50 candidates (default 10) with names, place URLs, CIDs, ratings.",
|
|
6120
6368
|
inputSchema: MapsSearchInputSchema,
|
|
6121
|
-
outputSchema: MapsSearchOutputSchema,
|
|
6369
|
+
outputSchema: recordOutputSchema("maps_search", MapsSearchOutputSchema),
|
|
6122
6370
|
annotations: liveWebToolAnnotations("Google Maps Business Search")
|
|
6123
6371
|
}, async (input) => formatMapsSearch(await executor.mapsSearch(input), input));
|
|
6124
6372
|
server2.registerTool("directory_workflow", {
|
|
6125
6373
|
title: "Directory Workflow: Markets + Maps",
|
|
6126
|
-
description:
|
|
6374
|
+
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.")}`,
|
|
6127
6375
|
inputSchema: DirectoryWorkflowInputSchema,
|
|
6128
|
-
outputSchema: DirectoryWorkflowOutputSchema,
|
|
6376
|
+
outputSchema: recordOutputSchema("directory_workflow", DirectoryWorkflowOutputSchema),
|
|
6129
6377
|
annotations: liveWebToolAnnotations("Directory Workflow: Markets + Maps")
|
|
6130
|
-
}, async (input) => formatDirectoryWorkflow(await executor.directoryWorkflow(input), input));
|
|
6378
|
+
}, async (input) => formatDirectoryWorkflow(await executor.directoryWorkflow(input), input, ctx));
|
|
6131
6379
|
server2.registerTool("workflow_list", {
|
|
6132
6380
|
title: "Workflow Catalog",
|
|
6133
|
-
description: "List MCP Scraper higher-level workflows and
|
|
6381
|
+
description: "List MCP Scraper higher-level workflows and recipes \u2014 market analysis, ICP research, CRO audits, competitive positioning, content gap briefs, AI search visibility, and more. Returns runnable workflow ids plus tool-chain guidance.",
|
|
6134
6382
|
inputSchema: WorkflowListInputSchema,
|
|
6135
|
-
outputSchema: WorkflowListOutputSchema,
|
|
6383
|
+
outputSchema: recordOutputSchema("workflow_list", WorkflowListOutputSchema),
|
|
6136
6384
|
annotations: localPlanningToolAnnotations("Workflow Catalog")
|
|
6137
6385
|
}, async (input) => formatWorkflowList(await executor.workflowList(input), input));
|
|
6138
6386
|
server2.registerTool("workflow_suggest", {
|
|
6139
6387
|
title: "Workflow Intent Router",
|
|
6140
|
-
description:
|
|
6388
|
+
description: "Route a high-level business/research goal (market analysis, ICP research, CRO audit, competitor comparison, content gap brief, AI search visibility, etc) to the right MCP Scraper workflow/tool chain. Free; tells you what to run next.",
|
|
6141
6389
|
inputSchema: WorkflowSuggestInputSchema,
|
|
6142
|
-
outputSchema: WorkflowSuggestOutputSchema,
|
|
6390
|
+
outputSchema: recordOutputSchema("workflow_suggest", WorkflowSuggestOutputSchema),
|
|
6143
6391
|
annotations: localPlanningToolAnnotations("Workflow Intent Router")
|
|
6144
6392
|
}, async (input) => formatWorkflowSuggest(input));
|
|
6145
6393
|
server2.registerTool("workflow_run", {
|
|
6146
6394
|
title: "Run Workflow",
|
|
6147
|
-
description:
|
|
6395
|
+
description: "Start a higher-level MCP Scraper workflow (directory, agent-packet, local-competitive-audit, map-comparison, serp-comparison, paa-expansion-brief, ai-overview-language). Use after workflow_suggest or workflow_list. Stepwise workflows return runId + nextStep \u2014 call workflow_step with the runId until done is true.",
|
|
6148
6396
|
inputSchema: WorkflowRunInputSchema,
|
|
6149
|
-
outputSchema: WorkflowRunOutputSchema,
|
|
6397
|
+
outputSchema: recordOutputSchema("workflow_run", WorkflowRunOutputSchema),
|
|
6150
6398
|
annotations: liveWebToolAnnotations("Run Workflow")
|
|
6151
6399
|
}, async (input) => formatWorkflowRun(await executor.workflowRun(input), input));
|
|
6152
6400
|
server2.registerTool("workflow_step", {
|
|
6153
6401
|
title: "Advance Workflow Step",
|
|
6154
|
-
description:
|
|
6402
|
+
description: "Run the next leg of a stepwise workflow started with workflow_run. Pass the runId; each call executes one step and returns its output plus nextStep. Keep calling with the same runId until done is true.",
|
|
6155
6403
|
inputSchema: WorkflowStepInputSchema,
|
|
6156
|
-
outputSchema: WorkflowStepOutputSchema,
|
|
6404
|
+
outputSchema: recordOutputSchema("workflow_step", WorkflowStepOutputSchema),
|
|
6157
6405
|
annotations: liveWebToolAnnotations("Advance Workflow Step")
|
|
6158
6406
|
}, async (input) => formatWorkflowStep(await executor.workflowStep(input), input));
|
|
6159
6407
|
server2.registerTool("workflow_status", {
|
|
6160
6408
|
title: "Workflow Status",
|
|
6161
|
-
description: "Fetch a hosted workflow run by id and list its current status and artifacts
|
|
6409
|
+
description: "Fetch a hosted workflow run by id and list its current status and artifacts, to re-open a run or recover artifact ids. Use only a runId returned by workflow_run/workflow_step/workflow_status.",
|
|
6162
6410
|
inputSchema: WorkflowStatusInputSchema,
|
|
6163
|
-
outputSchema: WorkflowStatusOutputSchema,
|
|
6411
|
+
outputSchema: recordOutputSchema("workflow_status", WorkflowStatusOutputSchema),
|
|
6164
6412
|
annotations: liveWebToolAnnotations("Workflow Status")
|
|
6165
6413
|
}, async (input) => formatWorkflowStatus(await executor.workflowStatus(input), input));
|
|
6166
6414
|
server2.registerTool("workflow_artifact_read", {
|
|
6167
6415
|
title: "Read Workflow Artifact",
|
|
6168
|
-
description: "Read a workflow artifact back into
|
|
6416
|
+
description: "Read a workflow artifact back into context by run id and artifact id, so final deliverables are grounded in generated evidence rather than memory. Use workflow_status first when artifact ids are unknown. Use maxBytes to limit large artifacts.",
|
|
6169
6417
|
inputSchema: WorkflowArtifactReadInputSchema,
|
|
6170
|
-
outputSchema: WorkflowArtifactReadOutputSchema,
|
|
6418
|
+
outputSchema: recordOutputSchema("workflow_artifact_read", WorkflowArtifactReadOutputSchema),
|
|
6171
6419
|
annotations: liveWebToolAnnotations("Read Workflow Artifact")
|
|
6172
6420
|
}, async (input) => formatWorkflowArtifactRead(await executor.workflowArtifactRead(input), input));
|
|
6421
|
+
server2.registerTool("report_artifact_read", {
|
|
6422
|
+
title: "Read Report Artifact",
|
|
6423
|
+
description: "Read back a stored report artifact by artifactId (returned by any tool whose result was too large to inline). Windowed: pass offset/maxBytes and keep reading until nextOffset is null.",
|
|
6424
|
+
inputSchema: ReportArtifactReadInputSchema,
|
|
6425
|
+
outputSchema: recordOutputSchema("report_artifact_read", ReportArtifactReadOutputSchema),
|
|
6426
|
+
annotations: liveWebToolAnnotations("Read Report Artifact")
|
|
6427
|
+
}, async (input) => {
|
|
6428
|
+
const owner = artifactOwnerId(input.artifactId);
|
|
6429
|
+
if (!owner || owner !== ownerId) {
|
|
6430
|
+
return { content: [{ type: "text", text: "Artifact not found or not owned by this caller." }], isError: true };
|
|
6431
|
+
}
|
|
6432
|
+
const window = await readArtifactWindow(input.artifactId, input.offset ?? 0, input.maxBytes ?? 2e4);
|
|
6433
|
+
if (!window) {
|
|
6434
|
+
return { content: [{ type: "text", text: "Artifact not found or expired." }], isError: true };
|
|
6435
|
+
}
|
|
6436
|
+
return {
|
|
6437
|
+
content: [{ type: "text", text: window.text }],
|
|
6438
|
+
structuredContent: { artifactId: input.artifactId, text: window.text, totalBytes: window.totalBytes, nextOffset: window.nextOffset }
|
|
6439
|
+
};
|
|
6440
|
+
});
|
|
6173
6441
|
server2.registerTool("rank_tracker_workflow", {
|
|
6174
6442
|
title: "Rank Tracker Blueprint Builder",
|
|
6175
|
-
description: "Generate a build-ready database, cron
|
|
6443
|
+
description: "Generate a build-ready database schema, cron plan, and implementation prompt for a rank tracker powered by MCP Scraper (Maps, organic, AI Overview, or PAA tracking). Local planning only \u2014 does not call the web or spend credits.",
|
|
6176
6444
|
inputSchema: RankTrackerBlueprintInputSchema,
|
|
6177
|
-
outputSchema: RankTrackerBlueprintOutputSchema,
|
|
6445
|
+
outputSchema: recordOutputSchema("rank_tracker_workflow", RankTrackerBlueprintOutputSchema),
|
|
6178
6446
|
annotations: localPlanningToolAnnotations("Rank Tracker Blueprint Builder")
|
|
6179
6447
|
}, async (input) => buildRankTrackerBlueprint(input));
|
|
6180
6448
|
server2.registerTool("credits_info", {
|
|
6181
6449
|
title: "MCP Scraper Credits & Costs",
|
|
6182
|
-
description: "Answer questions about MCP Scraper credits, usage limits, and concurrency upgrades
|
|
6450
|
+
description: "Answer questions about MCP Scraper credits, usage limits, and concurrency upgrades \u2014 balance, tool costs, concurrency limits, billing URL. Does not expose payment methods or card information.",
|
|
6183
6451
|
inputSchema: CreditsInfoInputSchema,
|
|
6184
|
-
outputSchema: CreditsInfoOutputSchema,
|
|
6452
|
+
outputSchema: recordOutputSchema("credits_info", CreditsInfoOutputSchema),
|
|
6185
6453
|
annotations: {
|
|
6186
6454
|
title: "MCP Scraper Credits & Costs",
|
|
6187
6455
|
readOnlyHint: true,
|
|
@@ -6306,10 +6574,10 @@ if (!forceStdio && (interactiveTerminal || wantsHelp)) {
|
|
|
6306
6574
|
}
|
|
6307
6575
|
function readApiKeyFile() {
|
|
6308
6576
|
const explicitPath = process.env.MCP_SCRAPER_KEY_PATH?.trim();
|
|
6309
|
-
const paths = [explicitPath, (0,
|
|
6577
|
+
const paths = [explicitPath, (0, import_node_path7.join)((0, import_node_os6.homedir)(), ".mcp-scraper-key")].filter(Boolean);
|
|
6310
6578
|
for (const path of paths) {
|
|
6311
6579
|
try {
|
|
6312
|
-
const value = (0,
|
|
6580
|
+
const value = (0, import_node_fs6.readFileSync)(path, "utf8").trim();
|
|
6313
6581
|
if (value) return value;
|
|
6314
6582
|
} catch {
|
|
6315
6583
|
}
|