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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PACKAGE_VERSION
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-WT6OT2T3.js";
|
|
4
4
|
import {
|
|
5
5
|
browserServiceProfileName,
|
|
6
6
|
browserServiceProfileSaveChanges,
|
|
@@ -91,9 +91,13 @@ function sanitizeHarvestResult(result) {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
// src/mcp/server-instructions.ts
|
|
94
|
-
|
|
94
|
+
function serverInstructions(savesReportsLocally) {
|
|
95
|
+
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.";
|
|
96
|
+
return `
|
|
95
97
|
# MCP Scraper
|
|
96
98
|
|
|
99
|
+
${reportLine}
|
|
100
|
+
|
|
97
101
|
Scrapes and analyzes web, search, maps, social, and site data. **Pick a tool by NAME from the map
|
|
98
102
|
below, then load its schema before calling.** Where a tool needs a value another tool produces, the
|
|
99
103
|
seam is noted so you can chain them.
|
|
@@ -158,25 +162,41 @@ Multi-step orchestrations \u2014 prefer these over hand-chaining primitives when
|
|
|
158
162
|
- Stand up a rank-tracking blueprint (database + cron + ingestion plan) -> **rank_tracker_workflow**.
|
|
159
163
|
- Find which sources an AI answer cites for a query (AEO) -> **query_fanout_workflow** (open a browser
|
|
160
164
|
session on chatgpt.com or claude.ai FIRST, then run it against that session).
|
|
165
|
+
- Not sure which workflow fits a goal -> **workflow_suggest**; to just see what's available -> **workflow_list**.
|
|
166
|
+
- Hosted/stepwise chain: **workflow_run** starts and returns \`runId\` (+ \`nextStep\` when multi-leg) ->
|
|
167
|
+
**workflow_step** with that \`runId\` advances one leg at a time until \`done\` -> **workflow_status**
|
|
168
|
+
re-opens a run to check state or recover artifact ids -> **workflow_artifact_read** pulls one artifact's
|
|
169
|
+
full content by \`runId\` + \`artifactId\`.
|
|
170
|
+
- Browser replay recording: **browser_replay_start** begins it -> **browser_replay_mark** (while
|
|
171
|
+
recording) locates a DOM target and returns a ready-to-use timed annotation -> **browser_replay_stop**
|
|
172
|
+
ends it -> feed collected annotations to **browser_replay_annotate**, or just
|
|
173
|
+
**browser_replay_download** the plain MP4. **browser_list_replays** recovers replay ids.
|
|
174
|
+
- Video breakdown is async: **video_frame_analysis** takes a DIRECT media file URL (resolve
|
|
175
|
+
YouTube/Facebook/Instagram page URLs to one first, e.g. via \`facebook_page_intel\`'s \`videoUrl\`) and
|
|
176
|
+
returns a \`runId\` immediately -> poll **video_frame_analysis_status** with that \`runId\` until \`status\`
|
|
177
|
+
is \`done\`.
|
|
161
178
|
|
|
162
179
|
## Notes
|
|
163
180
|
- Bulk / full-site crawls: call \`extract_site\` with \`rotateProxies:true\` for blocked or rate-limited
|
|
164
181
|
sites. It returns a saved folder/artifact plus a summary, not the full content inline.
|
|
165
182
|
- Browser sessions and media transcription cost credits and take longer \u2014 prefer the cheapest tool that
|
|
166
183
|
answers the question (plain search/extract before browser agents).
|
|
167
|
-
- Large results are saved to disk or an artifact and returned as a summary plus a path
|
|
168
|
-
full detail rather than expecting the whole payload inline.
|
|
184
|
+
- Large results are saved to disk or an artifact and returned as a summary plus a path or artifactId;
|
|
185
|
+
read it back for full detail rather than expecting the whole payload inline.
|
|
169
186
|
`.trim();
|
|
187
|
+
}
|
|
188
|
+
var SERVER_INSTRUCTIONS = serverInstructions(true);
|
|
170
189
|
|
|
171
190
|
// src/mcp/paa-mcp-server.ts
|
|
172
191
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
173
192
|
import { readdirSync, readFileSync, statSync } from "fs";
|
|
174
|
-
import { basename, join as
|
|
193
|
+
import { basename, join as join3 } from "path";
|
|
194
|
+
import { createHash } from "crypto";
|
|
175
195
|
|
|
176
196
|
// src/mcp/mcp-response-formatter.ts
|
|
177
|
-
import { mkdirSync, writeFileSync } from "fs";
|
|
178
|
-
import { homedir } from "os";
|
|
179
|
-
import { join } from "path";
|
|
197
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
198
|
+
import { homedir as homedir2 } from "os";
|
|
199
|
+
import { join as join2 } from "path";
|
|
180
200
|
|
|
181
201
|
// src/mcp/workflow-catalog.ts
|
|
182
202
|
var WORKFLOW_RECIPES = [
|
|
@@ -713,7 +733,129 @@ function renderImageSection(audit) {
|
|
|
713
733
|
return lines.join("\n");
|
|
714
734
|
}
|
|
715
735
|
|
|
736
|
+
// src/mcp/report-artifact-offload.ts
|
|
737
|
+
import { randomBytes } from "crypto";
|
|
738
|
+
|
|
739
|
+
// src/api/blob-store.ts
|
|
740
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
741
|
+
import { homedir } from "os";
|
|
742
|
+
import { join, dirname } from "path";
|
|
743
|
+
function byteLength(data) {
|
|
744
|
+
return Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data);
|
|
745
|
+
}
|
|
746
|
+
var LocalBlobStore = class {
|
|
747
|
+
constructor(baseDir) {
|
|
748
|
+
this.baseDir = baseDir;
|
|
749
|
+
}
|
|
750
|
+
baseDir;
|
|
751
|
+
kind = "local";
|
|
752
|
+
async put(key, data, contentType = "application/octet-stream") {
|
|
753
|
+
const path = join(this.baseDir, "blobs", key);
|
|
754
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
755
|
+
writeFileSync(path, data);
|
|
756
|
+
return { key, url: `file://${path}`, bytes: byteLength(data), contentType };
|
|
757
|
+
}
|
|
758
|
+
async get(key) {
|
|
759
|
+
const path = join(this.baseDir, "blobs", key);
|
|
760
|
+
try {
|
|
761
|
+
const { readFileSync: readFileSync2 } = await import("fs");
|
|
762
|
+
return readFileSync2(path);
|
|
763
|
+
} catch {
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
function localBaseDir() {
|
|
769
|
+
return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || join(homedir(), "Downloads", "mcp-scraper");
|
|
770
|
+
}
|
|
771
|
+
var cached = null;
|
|
772
|
+
function getBlobStore() {
|
|
773
|
+
if (cached) return cached;
|
|
774
|
+
if (process.env.BLOB_READ_WRITE_TOKEN) {
|
|
775
|
+
cached = new VercelBlobStore(process.env.BLOB_READ_WRITE_TOKEN);
|
|
776
|
+
} else {
|
|
777
|
+
cached = new LocalBlobStore(localBaseDir());
|
|
778
|
+
}
|
|
779
|
+
return cached;
|
|
780
|
+
}
|
|
781
|
+
var VercelBlobStore = class {
|
|
782
|
+
constructor(token) {
|
|
783
|
+
this.token = token;
|
|
784
|
+
}
|
|
785
|
+
token;
|
|
786
|
+
kind = "vercel-blob";
|
|
787
|
+
async put(key, data, contentType = "application/octet-stream") {
|
|
788
|
+
const { put } = await import("@vercel/blob");
|
|
789
|
+
const body = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
790
|
+
const result = await put(key, body, {
|
|
791
|
+
access: "public",
|
|
792
|
+
token: this.token,
|
|
793
|
+
contentType,
|
|
794
|
+
addRandomSuffix: true
|
|
795
|
+
});
|
|
796
|
+
return { key, url: result.url, bytes: byteLength(data), contentType };
|
|
797
|
+
}
|
|
798
|
+
async get(key) {
|
|
799
|
+
try {
|
|
800
|
+
const { list } = await import("@vercel/blob");
|
|
801
|
+
const res = await list({ prefix: key, token: this.token, limit: 1 });
|
|
802
|
+
const match = res.blobs.find((b) => b.pathname === key || b.pathname.startsWith(key));
|
|
803
|
+
if (!match) return null;
|
|
804
|
+
const resp = await fetch(match.url);
|
|
805
|
+
if (!resp.ok) return null;
|
|
806
|
+
return Buffer.from(await resp.arrayBuffer());
|
|
807
|
+
} catch {
|
|
808
|
+
return null;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
// src/mcp/report-artifact-offload.ts
|
|
814
|
+
var REPORT_BLOB_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
815
|
+
var REPORT_BLOB_PREFIX = "mcp-reports/";
|
|
816
|
+
var PREVIEW_CHARS = 2e3;
|
|
817
|
+
var ARTIFACT_OFFLOAD_ENABLED = process.env.MCP_SCRAPER_ARTIFACT_OFFLOAD !== "false";
|
|
818
|
+
async function offloadReport(toolName, ownerId, report) {
|
|
819
|
+
const timestamp = Date.now();
|
|
820
|
+
const random = randomBytes(6).toString("hex");
|
|
821
|
+
const key = `${REPORT_BLOB_PREFIX}${ownerId}/${toolName}/${timestamp}-${random}.md`;
|
|
822
|
+
const stored = await getBlobStore().put(key, report, "text/markdown");
|
|
823
|
+
return {
|
|
824
|
+
artifactId: stored.key,
|
|
825
|
+
bytes: stored.bytes,
|
|
826
|
+
expiresAt: new Date(timestamp + REPORT_BLOB_TTL_MS).toISOString(),
|
|
827
|
+
preview: report.slice(0, PREVIEW_CHARS)
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
function artifactOwnerId(artifactId) {
|
|
831
|
+
if (!artifactId.startsWith(REPORT_BLOB_PREFIX)) return null;
|
|
832
|
+
const rest = artifactId.slice(REPORT_BLOB_PREFIX.length);
|
|
833
|
+
const segment = rest.split("/")[0];
|
|
834
|
+
return segment || null;
|
|
835
|
+
}
|
|
836
|
+
async function readArtifactWindow(artifactId, offset, maxBytes) {
|
|
837
|
+
const buf = await getBlobStore().get(artifactId);
|
|
838
|
+
if (!buf) return null;
|
|
839
|
+
const totalBytes = buf.length;
|
|
840
|
+
const start = Math.max(0, offset);
|
|
841
|
+
const end = Math.min(totalBytes, start + maxBytes);
|
|
842
|
+
const slice = buf.subarray(start, end);
|
|
843
|
+
const nextOffset = end < totalBytes ? end : null;
|
|
844
|
+
return { text: slice.toString("utf8"), totalBytes, nextOffset };
|
|
845
|
+
}
|
|
846
|
+
function summaryEnvelope(executiveSummary, offloaded) {
|
|
847
|
+
return [
|
|
848
|
+
executiveSummary.trim(),
|
|
849
|
+
"",
|
|
850
|
+
"--- Full report stored as artifact ---",
|
|
851
|
+
`artifactId: ${offloaded.artifactId} \xB7 ${offloaded.bytes} bytes \xB7 expires ${offloaded.expiresAt}`,
|
|
852
|
+
"Read it with report_artifact_read (supports offset/maxBytes windowing)."
|
|
853
|
+
].join("\n");
|
|
854
|
+
}
|
|
855
|
+
|
|
716
856
|
// src/mcp/mcp-response-formatter.ts
|
|
857
|
+
var INLINE_BUDGET_BYTES = Number(process.env.MCP_SCRAPER_INLINE_BUDGET ?? 5e4);
|
|
858
|
+
var STRUCTURED_ARRAY_CAP = 25;
|
|
717
859
|
var reportSavingEnabled = true;
|
|
718
860
|
function configureReportSaving(enabled) {
|
|
719
861
|
reportSavingEnabled = enabled;
|
|
@@ -731,16 +873,16 @@ function reportTitle(full) {
|
|
|
731
873
|
return title?.replace(/^#\s+/, "").trim() || "MCP Scraper Report";
|
|
732
874
|
}
|
|
733
875
|
function outputBaseDir() {
|
|
734
|
-
return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() ||
|
|
876
|
+
return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || join2(homedir2(), "Downloads", "mcp-scraper");
|
|
735
877
|
}
|
|
736
878
|
function saveFullReport(full) {
|
|
737
879
|
if (!reportSavingEnabled || process.env.MCP_SCRAPER_SAVE_REPORTS === "false") return null;
|
|
738
880
|
const outDir = outputBaseDir();
|
|
739
881
|
try {
|
|
740
|
-
|
|
882
|
+
mkdirSync2(outDir, { recursive: true });
|
|
741
883
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
742
|
-
const file =
|
|
743
|
-
|
|
884
|
+
const file = join2(outDir, `${stamp}-${slugifyReportName(reportTitle(full))}.md`);
|
|
885
|
+
writeFileSync2(file, full, "utf8");
|
|
744
886
|
return file;
|
|
745
887
|
} catch {
|
|
746
888
|
return null;
|
|
@@ -755,9 +897,9 @@ function saveBulkSite(siteUrl, pages, seo, imageAudit) {
|
|
|
755
897
|
if (!reportSavingActive()) return null;
|
|
756
898
|
try {
|
|
757
899
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
758
|
-
const dir =
|
|
759
|
-
const pagesDir =
|
|
760
|
-
|
|
900
|
+
const dir = join2(outputBaseDir(), `extract-${slugifyReportName(siteUrl)}-${stamp}`);
|
|
901
|
+
const pagesDir = join2(dir, "pages");
|
|
902
|
+
mkdirSync2(pagesDir, { recursive: true });
|
|
761
903
|
const indexRows = pages.map((p, i) => {
|
|
762
904
|
const num = String(i + 1).padStart(4, "0");
|
|
763
905
|
const slug = slugifyReportName(p.url.replace(/^https?:\/\//, "")).slice(0, 60) || "page";
|
|
@@ -771,7 +913,7 @@ function saveBulkSite(siteUrl, pages, seo, imageAudit) {
|
|
|
771
913
|
"",
|
|
772
914
|
body || "_(no content extracted)_"
|
|
773
915
|
].filter(Boolean).join("\n");
|
|
774
|
-
|
|
916
|
+
writeFileSync2(join2(pagesDir, fname), content, "utf8");
|
|
775
917
|
return `| ${i + 1} | ${cell(p.title ?? "Untitled")} | ${p.url} | pages/${fname} |`;
|
|
776
918
|
});
|
|
777
919
|
const dataFilesSection = seo ? [
|
|
@@ -803,40 +945,40 @@ function saveBulkSite(siteUrl, pages, seo, imageAudit) {
|
|
|
803
945
|
|---|-------|-----|------|
|
|
804
946
|
${indexRows.join("\n")}`
|
|
805
947
|
].filter(Boolean).join("\n");
|
|
806
|
-
const indexFile =
|
|
807
|
-
|
|
948
|
+
const indexFile = join2(dir, "index.md");
|
|
949
|
+
writeFileSync2(indexFile, index, "utf8");
|
|
808
950
|
let seoFiles;
|
|
809
951
|
if (seo) {
|
|
810
952
|
const toJsonl = (rows) => rows.map((r) => JSON.stringify(r)).join("\n");
|
|
811
|
-
const pagesJsonl =
|
|
812
|
-
const linksJsonl =
|
|
813
|
-
const metricsJsonl =
|
|
814
|
-
const issuesFile =
|
|
815
|
-
const reportFile =
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
953
|
+
const pagesJsonl = join2(dir, "pages.jsonl");
|
|
954
|
+
const linksJsonl = join2(dir, "links.jsonl");
|
|
955
|
+
const metricsJsonl = join2(dir, "link-metrics.jsonl");
|
|
956
|
+
const issuesFile = join2(dir, "issues.json");
|
|
957
|
+
const reportFile = join2(dir, "report.md");
|
|
958
|
+
writeFileSync2(pagesJsonl, toJsonl(seo.pageRows), "utf8");
|
|
959
|
+
writeFileSync2(linksJsonl, toJsonl(seo.edges), "utf8");
|
|
960
|
+
writeFileSync2(metricsJsonl, toJsonl(seo.metrics), "utf8");
|
|
961
|
+
writeFileSync2(issuesFile, JSON.stringify(seo.issues, null, 2), "utf8");
|
|
962
|
+
writeFileSync2(reportFile, seo.reportMd + (imageAudit ? `
|
|
821
963
|
|
|
822
964
|
${renderImageSection(imageAudit)}` : ""), "utf8");
|
|
823
|
-
const linkReportFile =
|
|
824
|
-
const linksSummaryFile =
|
|
825
|
-
const externalDomainsFile =
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
965
|
+
const linkReportFile = join2(dir, "link-report.md");
|
|
966
|
+
const linksSummaryFile = join2(dir, "links-summary.json");
|
|
967
|
+
const externalDomainsFile = join2(dir, "external-domains.json");
|
|
968
|
+
writeFileSync2(linkReportFile, renderLinkReport(seo.linkReport), "utf8");
|
|
969
|
+
writeFileSync2(linksSummaryFile, JSON.stringify(seo.linkReport.summary, null, 2), "utf8");
|
|
970
|
+
writeFileSync2(externalDomainsFile, JSON.stringify(seo.linkReport.externalDomains, null, 2), "utf8");
|
|
829
971
|
seoFiles = [pagesJsonl, linksJsonl, metricsJsonl, issuesFile, reportFile, linkReportFile, linksSummaryFile, externalDomainsFile];
|
|
830
972
|
if (imageAudit) {
|
|
831
|
-
const imagesJsonl =
|
|
832
|
-
const imagesSummary =
|
|
833
|
-
|
|
834
|
-
|
|
973
|
+
const imagesJsonl = join2(dir, "images.jsonl");
|
|
974
|
+
const imagesSummary = join2(dir, "images-summary.json");
|
|
975
|
+
writeFileSync2(imagesJsonl, imageAudit.rows.map((r) => JSON.stringify(r)).join("\n"), "utf8");
|
|
976
|
+
writeFileSync2(imagesSummary, JSON.stringify(imageAudit.summary, null, 2), "utf8");
|
|
835
977
|
seoFiles.push(imagesJsonl, imagesSummary);
|
|
836
978
|
}
|
|
837
979
|
if (seo.branding) {
|
|
838
|
-
const brandingFile =
|
|
839
|
-
|
|
980
|
+
const brandingFile = join2(dir, "branding.json");
|
|
981
|
+
writeFileSync2(brandingFile, JSON.stringify(seo.branding, null, 2), "utf8");
|
|
840
982
|
seoFiles.push(brandingFile);
|
|
841
983
|
}
|
|
842
984
|
}
|
|
@@ -849,12 +991,12 @@ function saveUrlInventory(siteUrl, urls) {
|
|
|
849
991
|
if (!reportSavingActive()) return null;
|
|
850
992
|
try {
|
|
851
993
|
const outDir = outputBaseDir();
|
|
852
|
-
|
|
994
|
+
mkdirSync2(outDir, { recursive: true });
|
|
853
995
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
854
|
-
const file =
|
|
996
|
+
const file = join2(outDir, `${stamp}-urlmap-${slugifyReportName(siteUrl.replace(/^https?:\/\//, ""))}.csv`);
|
|
855
997
|
const csv = (v) => /[",\n]/.test(v) ? `"${v.replace(/"/g, '""')}"` : v;
|
|
856
998
|
const rows = ["url,status", ...urls.map((u) => `${csv(u.url)},${u.status ?? ""}`)];
|
|
857
|
-
|
|
999
|
+
writeFileSync2(file, rows.join("\n"), "utf8");
|
|
858
1000
|
return file;
|
|
859
1001
|
} catch {
|
|
860
1002
|
return null;
|
|
@@ -863,12 +1005,12 @@ function saveUrlInventory(siteUrl, urls) {
|
|
|
863
1005
|
function persistScreenshotLocally(base64, url) {
|
|
864
1006
|
if (!reportSavingEnabled || process.env.MCP_SCRAPER_SAVE_REPORTS === "false") return null;
|
|
865
1007
|
try {
|
|
866
|
-
const dir =
|
|
867
|
-
|
|
1008
|
+
const dir = join2(outputBaseDir(), "screenshots");
|
|
1009
|
+
mkdirSync2(dir, { recursive: true });
|
|
868
1010
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
869
1011
|
const slug = url.replace(/^https?:\/\//, "").replace(/[^a-z0-9]+/gi, "-").replace(/^-+|-+$/g, "").slice(0, 60);
|
|
870
|
-
const filePath =
|
|
871
|
-
|
|
1012
|
+
const filePath = join2(dir, `${stamp}-${slug}.png`);
|
|
1013
|
+
writeFileSync2(filePath, Buffer.from(base64, "base64"));
|
|
872
1014
|
return filePath;
|
|
873
1015
|
} catch {
|
|
874
1016
|
return null;
|
|
@@ -881,6 +1023,20 @@ function oneBlock(content, diskContent) {
|
|
|
881
1023
|
\u{1F4C4} Saved: \`${filePath}\`` : content;
|
|
882
1024
|
return { content: [{ type: "text", text }] };
|
|
883
1025
|
}
|
|
1026
|
+
async function maybeOffload(toolName, ctx, fullText, summaryText, structuredContent) {
|
|
1027
|
+
if (!ctx?.hosted || !ARTIFACT_OFFLOAD_ENABLED) return null;
|
|
1028
|
+
const bytes = Buffer.byteLength(fullText);
|
|
1029
|
+
if (bytes <= INLINE_BUDGET_BYTES) return null;
|
|
1030
|
+
const offloaded = await offloadReport(toolName, ctx.ownerId, fullText);
|
|
1031
|
+
return {
|
|
1032
|
+
content: [{ type: "text", text: summaryEnvelope(summaryText, offloaded) }],
|
|
1033
|
+
structuredContent: { ...structuredContent, artifact: offloaded }
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
function capArray(items, cap) {
|
|
1037
|
+
if (items.length <= cap) return { items };
|
|
1038
|
+
return { items: items.slice(0, cap), truncatedCount: items.length - cap };
|
|
1039
|
+
}
|
|
884
1040
|
function workflowRecipeTable(recipes) {
|
|
885
1041
|
if (!recipes.length) return "";
|
|
886
1042
|
return [
|
|
@@ -1232,7 +1388,7 @@ ${headingSection}${kpoSection}${brandingSection}${bodySectionFull}${memSection}$
|
|
|
1232
1388
|
}
|
|
1233
1389
|
return { ...textResult, structuredContent };
|
|
1234
1390
|
}
|
|
1235
|
-
function formatMapSiteUrls(raw, input) {
|
|
1391
|
+
async function formatMapSiteUrls(raw, input, ctx) {
|
|
1236
1392
|
const parsed = parseData(raw);
|
|
1237
1393
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
1238
1394
|
const d = parsed.data;
|
|
@@ -1273,20 +1429,28 @@ ${broken.map((u) => `- ${u.url} (${u.status})`).join("\n")}` : "",
|
|
|
1273
1429
|
- Extract content from all pages: use \`extract_site\`
|
|
1274
1430
|
- Scrape a single page: use \`extract_url\``
|
|
1275
1431
|
].filter(Boolean).join("\n");
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
urls: urls.map((u) => ({ url: u.url, status: u.status ?? null })),
|
|
1287
|
-
durationMs: d.durationMs ?? 0
|
|
1288
|
-
}
|
|
1432
|
+
const structuredContent = {
|
|
1433
|
+
startUrl: d.startUrl ?? input.url,
|
|
1434
|
+
totalFound: d.totalFound ?? urls.length,
|
|
1435
|
+
truncated: d.truncated === true,
|
|
1436
|
+
okCount: ok.length,
|
|
1437
|
+
redirectCount: redirects.length,
|
|
1438
|
+
brokenCount: broken.length,
|
|
1439
|
+
inventoryFile: inventoryFile ?? null,
|
|
1440
|
+
urls: urls.map((u) => ({ url: u.url, status: u.status ?? null })),
|
|
1441
|
+
durationMs: d.durationMs ?? 0
|
|
1289
1442
|
};
|
|
1443
|
+
const summary = `# URL Map: ${input.url}
|
|
1444
|
+
**${d.totalFound} URLs** \xB7 ${(d.durationMs / 1e3).toFixed(1)}s
|
|
1445
|
+
|
|
1446
|
+
## Summary
|
|
1447
|
+
- 2xx: ${ok.length}
|
|
1448
|
+
- 3xx: ${redirects.length}
|
|
1449
|
+
- 4xx+: ${broken.length}`;
|
|
1450
|
+
const capped = capArray(structuredContent.urls, STRUCTURED_ARRAY_CAP);
|
|
1451
|
+
const offloaded = await maybeOffload("map_site_urls", ctx, full, summary, { ...structuredContent, urls: capped.items, truncatedCount: capped.truncatedCount });
|
|
1452
|
+
if (offloaded) return offloaded;
|
|
1453
|
+
return { ...oneBlock(full), structuredContent };
|
|
1290
1454
|
}
|
|
1291
1455
|
function buildSeoExport(siteUrl, pages, branding) {
|
|
1292
1456
|
const { edges, metrics } = buildLinkGraph(pages, siteUrl);
|
|
@@ -1306,7 +1470,7 @@ function buildSeoExport(siteUrl, pages, branding) {
|
|
|
1306
1470
|
branding: branding ?? void 0
|
|
1307
1471
|
};
|
|
1308
1472
|
}
|
|
1309
|
-
function formatExtractSite(raw, input) {
|
|
1473
|
+
async function formatExtractSite(raw, input, ctx) {
|
|
1310
1474
|
const parsed = parseData(raw);
|
|
1311
1475
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
1312
1476
|
const d = parsed.data;
|
|
@@ -1341,6 +1505,36 @@ function formatExtractSite(raw, input) {
|
|
|
1341
1505
|
schemaTypes: schemaTypesOf(p)
|
|
1342
1506
|
})));
|
|
1343
1507
|
const preview = pages.slice(0, BULK_PAGE_THRESHOLD).map(pageRow).join("\n");
|
|
1508
|
+
const summaryHeader = [
|
|
1509
|
+
`# Site Extract: ${input.url}`,
|
|
1510
|
+
durationLine,
|
|
1511
|
+
`
|
|
1512
|
+
## Pages (first ${Math.min(BULK_PAGE_THRESHOLD, pages.length)} of ${pages.length})
|
|
1513
|
+
| # | Title | URL | Schema |
|
|
1514
|
+
|---|-------|-----|--------|
|
|
1515
|
+
${preview}`
|
|
1516
|
+
].join("\n");
|
|
1517
|
+
if (!bulk && ctx?.hosted) {
|
|
1518
|
+
const pageDetails2 = pages.map((p, i) => {
|
|
1519
|
+
const body = (p.bodyMarkdown ?? "").trim();
|
|
1520
|
+
return [
|
|
1521
|
+
`
|
|
1522
|
+
## ${i + 1}. ${p.title ?? "Untitled"}`,
|
|
1523
|
+
`- **URL:** ${p.url}`,
|
|
1524
|
+
p.metaDescription ? `- **Description:** ${p.metaDescription}` : "",
|
|
1525
|
+
body ? `
|
|
1526
|
+
${body}` : "_(no content extracted)_"
|
|
1527
|
+
].filter(Boolean).join("\n");
|
|
1528
|
+
}).join("\n");
|
|
1529
|
+
const fullForOffload = `${summaryHeader}
|
|
1530
|
+
|
|
1531
|
+
---
|
|
1532
|
+
# Full Page Content
|
|
1533
|
+
${pageDetails2}${tips}`;
|
|
1534
|
+
const capped = capArray(structuredContent.pages, STRUCTURED_ARRAY_CAP);
|
|
1535
|
+
const offloaded = await maybeOffload("extract_site", ctx, fullForOffload, `${summaryHeader}${tips}`, { ...structuredContent, pages: capped.items, truncatedCount: capped.truncatedCount });
|
|
1536
|
+
if (offloaded) return offloaded;
|
|
1537
|
+
}
|
|
1344
1538
|
const location = bulk ? `
|
|
1345
1539
|
## \u{1F4C1} Bulk scrape saved
|
|
1346
1540
|
- **Folder:** \`${bulk.dir}\` \u2190 all scraped page content is here
|
|
@@ -1393,7 +1587,7 @@ ${body}` : "_(no content extracted)_"
|
|
|
1393
1587
|
${pageDetails}${tips}`;
|
|
1394
1588
|
return { ...oneBlock(full, diskReport), structuredContent };
|
|
1395
1589
|
}
|
|
1396
|
-
async function formatAuditSite(raw, input) {
|
|
1590
|
+
async function formatAuditSite(raw, input, ctx) {
|
|
1397
1591
|
const parsed = parseData(raw);
|
|
1398
1592
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
1399
1593
|
const d = parsed.data;
|
|
@@ -1424,6 +1618,24 @@ async function formatAuditSite(raw, input) {
|
|
|
1424
1618
|
images: { unique: img.unique, totalBytes: img.totalBytes, over100kb: img.over100kb, legacyFormat: img.legacyFormat },
|
|
1425
1619
|
links: { internal: lr.internal.totalLinks, external: lr.external.totalLinks, orphans: lr.internal.orphans, brokenInternal: lr.internal.brokenInternal, externalDomains: lr.external.uniqueDomains }
|
|
1426
1620
|
};
|
|
1621
|
+
const summary = [
|
|
1622
|
+
`# Technical SEO Audit: ${input.url}`,
|
|
1623
|
+
durationLine,
|
|
1624
|
+
`
|
|
1625
|
+
## Top issues
|
|
1626
|
+
${topIssues || "_none found_"}`,
|
|
1627
|
+
`
|
|
1628
|
+
## Links
|
|
1629
|
+
${linkLine}`,
|
|
1630
|
+
`
|
|
1631
|
+
## Images
|
|
1632
|
+
${imgLine}`
|
|
1633
|
+
].join("\n");
|
|
1634
|
+
if (!bulk && ctx?.hosted) {
|
|
1635
|
+
const fullForOffload = [summary, "\n---\n# Full Audit Report", seo.reportMd, renderImageSection(imageAudit), renderLinkReport(seo.linkReport)].join("\n");
|
|
1636
|
+
const offloaded = await maybeOffload("audit_site", ctx, fullForOffload, summary, structuredContent);
|
|
1637
|
+
if (offloaded) return offloaded;
|
|
1638
|
+
}
|
|
1427
1639
|
const location = bulk ? `
|
|
1428
1640
|
## \u{1F4C1} Technical audit saved
|
|
1429
1641
|
- **Folder:** \`${bulk.dir}\` \u2190 full audit + page content here
|
|
@@ -2194,7 +2406,7 @@ ${rows}`,
|
|
|
2194
2406
|
}
|
|
2195
2407
|
};
|
|
2196
2408
|
}
|
|
2197
|
-
function formatDirectoryWorkflow(raw, input) {
|
|
2409
|
+
async function formatDirectoryWorkflow(raw, input, ctx) {
|
|
2198
2410
|
const parsed = parseData(raw);
|
|
2199
2411
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
2200
2412
|
const d = parsed.data;
|
|
@@ -2246,26 +2458,29 @@ ${businessRows}` : null,
|
|
|
2246
2458
|
durationMs != null ? `
|
|
2247
2459
|
*Completed in ${(durationMs / 1e3).toFixed(1)}s*` : null
|
|
2248
2460
|
].filter(Boolean).join("\n");
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
cities,
|
|
2266
|
-
durationMs: durationMs ?? 0
|
|
2267
|
-
}
|
|
2461
|
+
const structuredContent = {
|
|
2462
|
+
query: d.query,
|
|
2463
|
+
state: d.state,
|
|
2464
|
+
minPopulation: d.minPopulation,
|
|
2465
|
+
populationYear: d.populationYear,
|
|
2466
|
+
maxResultsPerCity: d.maxResultsPerCity,
|
|
2467
|
+
concurrency: d.concurrency,
|
|
2468
|
+
censusSourceUrl: d.censusSourceUrl,
|
|
2469
|
+
usZipsSourcePath: d.usZipsSourcePath ?? null,
|
|
2470
|
+
warnings,
|
|
2471
|
+
extractedAt: d.extractedAt,
|
|
2472
|
+
selectedCityCount: d.selectedCityCount,
|
|
2473
|
+
totalResultCount,
|
|
2474
|
+
csvPath,
|
|
2475
|
+
cities,
|
|
2476
|
+
durationMs: durationMs ?? 0
|
|
2268
2477
|
};
|
|
2478
|
+
const summary = `# Directory Workflow: ${input.query}
|
|
2479
|
+
**Markets:** ${cities.length} \xB7 **Maps results:** ${totalResultCount} \xB7 **State:** ${d.state ?? input.state ?? "US"}`;
|
|
2480
|
+
const capped = capArray(cities, STRUCTURED_ARRAY_CAP);
|
|
2481
|
+
const offloaded = await maybeOffload("directory_workflow", ctx, full, summary, { ...structuredContent, cities: capped.items, truncatedCount: capped.truncatedCount });
|
|
2482
|
+
if (offloaded) return offloaded;
|
|
2483
|
+
return { ...oneBlock(full), structuredContent };
|
|
2269
2484
|
}
|
|
2270
2485
|
function formatMapsPlaceIntel(raw, input) {
|
|
2271
2486
|
const parsed = parseData(raw);
|
|
@@ -2786,28 +3001,36 @@ ${rows}` : ""
|
|
|
2786
3001
|
};
|
|
2787
3002
|
}
|
|
2788
3003
|
|
|
3004
|
+
// src/mcp/output-schema-registry.ts
|
|
3005
|
+
var ADVERTISE_OUTPUT_SCHEMAS = process.env.MCP_SCRAPER_ADVERTISE_OUTPUT_SCHEMAS === "true";
|
|
3006
|
+
var OUTPUT_SCHEMAS = {};
|
|
3007
|
+
function recordOutputSchema(name, schema) {
|
|
3008
|
+
OUTPUT_SCHEMAS[name] = schema;
|
|
3009
|
+
return ADVERTISE_OUTPUT_SCHEMAS ? schema : void 0;
|
|
3010
|
+
}
|
|
3011
|
+
|
|
2789
3012
|
// src/mcp/mcp-tool-schemas.ts
|
|
2790
3013
|
import { z } from "zod";
|
|
2791
3014
|
var HarvestPaaInputSchema = {
|
|
2792
|
-
query: z.string().min(1).describe('The search query.
|
|
2793
|
-
location: z.string().optional().describe('City, region, or country for geo signals
|
|
2794
|
-
maxQuestions: z.number().int().min(1).max(200).default(30).describe("
|
|
2795
|
-
gl: z.string().length(2).default("us").describe("Google country code inferred from location or user language.
|
|
2796
|
-
hl: z.string().default("en").describe("Google interface/content language inferred from the user request.
|
|
2797
|
-
device: z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use
|
|
2798
|
-
proxyMode: z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe('Leave unset (clean egress
|
|
2799
|
-
proxyZip: z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location"
|
|
2800
|
-
debug: z.boolean().default(false).describe("Include sanitized
|
|
3015
|
+
query: 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.'),
|
|
3016
|
+
location: 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.'),
|
|
3017
|
+
maxQuestions: 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."),
|
|
3018
|
+
gl: z.string().length(2).default("us").describe("Google country code inferred from location or user language."),
|
|
3019
|
+
hl: z.string().default("en").describe("Google interface/content language inferred from the user request."),
|
|
3020
|
+
device: z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only for mobile rankings."),
|
|
3021
|
+
proxyMode: 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.'),
|
|
3022
|
+
proxyZip: z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location".'),
|
|
3023
|
+
debug: z.boolean().default(false).describe("Include sanitized diagnostics for debugging localization, CAPTCHA, or proxy behavior.")
|
|
2801
3024
|
};
|
|
2802
3025
|
var ExtractUrlInputSchema = {
|
|
2803
|
-
url: z.string().url().describe("Public http/https URL to extract.
|
|
2804
|
-
screenshot: z.boolean().default(false).describe("
|
|
2805
|
-
screenshotDevice: z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport for screenshot. desktop = 1440\xD7900
|
|
2806
|
-
extractBranding: z.boolean().default(false).describe("Extract brand colors, fonts, logo, and favicon
|
|
2807
|
-
downloadMedia: z.boolean().default(false).describe("Extract and download
|
|
3026
|
+
url: z.string().url().describe("Public http/https URL to extract."),
|
|
3027
|
+
screenshot: z.boolean().default(false).describe("Capture a full-page screenshot, saved to ~/Downloads/mcp-scraper/screenshots/ and returned inline."),
|
|
3028
|
+
screenshotDevice: z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport for screenshot. desktop = 1440\xD7900, mobile = 390\xD7844."),
|
|
3029
|
+
extractBranding: z.boolean().default(false).describe("Extract brand colors, fonts, logo, and favicon via a rendered browser session."),
|
|
3030
|
+
downloadMedia: z.boolean().default(false).describe("Extract and download page media (images/video/audio) to ~/Downloads/mcp-scraper/media/. Ad/tracking noise is filtered automatically."),
|
|
2808
3031
|
mediaTypes: z.array(z.enum(["image", "video", "audio"])).default(["image", "video", "audio"]).describe("Which media types to download. Default all three."),
|
|
2809
|
-
allowLocal: z.boolean().default(false).describe("Allow localhost and private-network URLs.
|
|
2810
|
-
depositToVault: z.boolean().default(false).describe("
|
|
3032
|
+
allowLocal: z.boolean().default(false).describe("Allow localhost and private-network URLs. Local development only."),
|
|
3033
|
+
depositToVault: 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."),
|
|
2811
3034
|
vaultName: z.string().trim().min(1).max(120).optional().describe("Optional vault to deposit into. Defaults to the user's personal vault.")
|
|
2812
3035
|
};
|
|
2813
3036
|
var MapSiteUrlsInputSchema = {
|
|
@@ -2815,34 +3038,34 @@ var MapSiteUrlsInputSchema = {
|
|
|
2815
3038
|
maxUrls: 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.")
|
|
2816
3039
|
};
|
|
2817
3040
|
var ExtractSiteInputSchema = {
|
|
2818
|
-
url: z.string().url().describe("Public website URL or domain to crawl for page CONTENT
|
|
2819
|
-
maxPages: z.number().int().min(1).max(1e4).optional().describe("Maximum pages to extract.
|
|
2820
|
-
rotateProxies: z.boolean().optional().describe("Route page fetches through rotating residential proxies
|
|
2821
|
-
rotateProxyEvery: z.number().int().min(1).max(100).optional().describe("When rotateProxies is on,
|
|
2822
|
-
formats: z.array(z.enum(["markdown", "links", "json", "images", "branding"])).optional().describe("
|
|
3041
|
+
url: 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."),
|
|
3042
|
+
maxPages: 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."),
|
|
3043
|
+
rotateProxies: 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."),
|
|
3044
|
+
rotateProxyEvery: z.number().int().min(1).max(100).optional().describe("When rotateProxies is on, pages fetched per proxy before rotating. Default 30."),
|
|
3045
|
+
formats: z.array(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.")
|
|
2823
3046
|
};
|
|
2824
3047
|
var AuditSiteInputSchema = {
|
|
2825
|
-
url: z.string().url().describe("Public website URL or domain
|
|
2826
|
-
maxPages: z.number().int().min(1).max(1e4).optional().describe("Maximum pages to crawl and audit.
|
|
2827
|
-
rotateProxies: z.boolean().optional().describe("Route page fetches through rotating residential proxies to defeat rate-limiting and bot blocks
|
|
2828
|
-
rotateProxyEvery: z.number().int().min(1).max(100).optional().describe("When rotateProxies is on,
|
|
3048
|
+
url: 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."),
|
|
3049
|
+
maxPages: 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."),
|
|
3050
|
+
rotateProxies: 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."),
|
|
3051
|
+
rotateProxyEvery: z.number().int().min(1).max(100).optional().describe("When rotateProxies is on, pages fetched per proxy before rotating. Default 30.")
|
|
2829
3052
|
};
|
|
2830
3053
|
var YoutubeHarvestInputSchema = {
|
|
2831
3054
|
mode: z.enum(["search", "channel"]).describe("Use search for topic/keyword requests. Use channel when the user provides @handle, channel ID, or channel URL."),
|
|
2832
3055
|
query: z.string().optional().describe("Required when mode is search. The YouTube search topic in the user\u2019s words."),
|
|
2833
3056
|
channelHandle: z.string().optional().describe("YouTube channel handle, channel ID, or URL. Examples: @mkbhd, UC..., https://youtube.com/@mkbhd."),
|
|
2834
|
-
maxVideos: z.number().int().min(1).max(500).default(50).describe("Number of videos to return. Default 50, maximum 500.
|
|
3057
|
+
maxVideos: z.number().int().min(1).max(500).default(50).describe("Number of videos to return. Default 50, maximum 500.")
|
|
2835
3058
|
};
|
|
2836
3059
|
var YoutubeTranscribeInputSchema = {
|
|
2837
3060
|
videoId: 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."),
|
|
2838
|
-
url: z.string().url().optional().describe("Full YouTube URL
|
|
3061
|
+
url: z.string().url().optional().describe("Full YouTube URL. Use when the user pasted a URL instead of an ID. Provide videoId or url.")
|
|
2839
3062
|
};
|
|
2840
3063
|
var FacebookPageIntelInputSchema = {
|
|
2841
|
-
pageId: z.string().optional().describe("Facebook advertiser/page ID. Use only a
|
|
2842
|
-
libraryId: z.string().optional().describe("Facebook Ad Library archive ID
|
|
3064
|
+
pageId: z.string().optional().describe("Facebook advertiser/page ID. Use only a value returned by facebook_ad_search or copied from Ad Library."),
|
|
3065
|
+
libraryId: 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."),
|
|
2843
3066
|
query: z.string().optional().describe("Advertiser or brand name when pageId/libraryId is not known. One of pageId, libraryId, or query is required."),
|
|
2844
|
-
maxAds: z.number().int().min(1).max(200).default(50).describe("Maximum ads to inspect. Default 50, maximum 200.
|
|
2845
|
-
country: z.string().length(2).default("US").describe("Two-letter Ad Library country code. Default US.
|
|
3067
|
+
maxAds: z.number().int().min(1).max(200).default(50).describe("Maximum ads to inspect. Default 50, maximum 200."),
|
|
3068
|
+
country: z.string().length(2).default("US").describe("Two-letter Ad Library country code. Default US.")
|
|
2846
3069
|
};
|
|
2847
3070
|
var FacebookAdSearchInputSchema = {
|
|
2848
3071
|
query: z.string().min(1).describe("Advertiser, brand, competitor, niche, or keyword to search in Facebook Ad Library."),
|
|
@@ -2850,7 +3073,7 @@ var FacebookAdSearchInputSchema = {
|
|
|
2850
3073
|
maxResults: z.number().int().min(1).max(20).default(10).describe("Maximum advertisers to return. Default 10, maximum 20. Prefer tighter search terms over maxing this out.")
|
|
2851
3074
|
};
|
|
2852
3075
|
var RedditThreadInputSchema = {
|
|
2853
|
-
url: z.string().min(1).describe("A reddit.com thread/post URL (www, old,
|
|
3076
|
+
url: z.string().min(1).describe("A reddit.com thread/post URL (www, old, new Reddit, or redd.it)."),
|
|
2854
3077
|
maxComments: z.number().int().min(1).max(2e3).optional().describe("Optional cap on comments returned. Omit to return all captured comments.")
|
|
2855
3078
|
};
|
|
2856
3079
|
var VideoFrameAnalysisInputSchema = {
|
|
@@ -2864,14 +3087,14 @@ var VideoFrameAnalysisStatusInputSchema = {
|
|
|
2864
3087
|
runId: z.string().min(1).describe("The runId returned by video_frame_analysis.")
|
|
2865
3088
|
};
|
|
2866
3089
|
var FacebookAdTranscribeInputSchema = {
|
|
2867
|
-
videoUrl: z.string().url().describe("Direct Facebook CDN video URL from
|
|
3090
|
+
videoUrl: 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.")
|
|
2868
3091
|
};
|
|
2869
3092
|
var FacebookVideoTranscribeInputSchema = {
|
|
2870
|
-
url: z.string().url().describe("Organic Facebook reel
|
|
3093
|
+
url: z.string().url().describe("Organic Facebook reel/video/watch/post/share URL from facebook.com, m.facebook.com, or fb.watch."),
|
|
2871
3094
|
quality: 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.")
|
|
2872
3095
|
};
|
|
2873
3096
|
var GoogleAdsSearchInputSchema = {
|
|
2874
|
-
query: z.string().min(1).describe("A domain (e.g. getviktor.com) or advertiser/brand name to look up in
|
|
3097
|
+
query: z.string().min(1).describe("A domain (e.g. getviktor.com) or advertiser/brand name to look up in Google Ads Transparency Center."),
|
|
2875
3098
|
region: z.string().length(2).default("US").describe("Two-letter region code for where the ads are shown. Default US. Examples: US, CA, GB, AU."),
|
|
2876
3099
|
maxResults: z.number().int().min(1).max(20).default(10).describe("Maximum advertisers to return. Default 10, maximum 20.")
|
|
2877
3100
|
};
|
|
@@ -2882,78 +3105,84 @@ var GoogleAdsPageIntelInputSchema = {
|
|
|
2882
3105
|
maxAds: 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.")
|
|
2883
3106
|
};
|
|
2884
3107
|
var GoogleAdsTranscribeInputSchema = {
|
|
2885
|
-
videoUrl: z.string().url().describe("Direct googlevideo.com playback URL from
|
|
3108
|
+
videoUrl: z.string().url().describe("Direct googlevideo.com playback URL from google_ads_page_intel. For YouTube-hosted ads use youtube_transcribe instead.")
|
|
2886
3109
|
};
|
|
2887
3110
|
var InstagramProfileContentInputSchema = {
|
|
2888
3111
|
handle: z.string().min(1).optional().describe("Instagram handle, with or without @. Provide handle or url."),
|
|
2889
|
-
url: z.string().url().optional().describe("Instagram profile URL
|
|
2890
|
-
profile: z.string().min(1).optional().describe("Optional saved hosted browser profile name
|
|
2891
|
-
saveProfileChanges: z.boolean().optional().describe("
|
|
2892
|
-
maxItems: z.number().int().min(1).max(2e3).default(50).describe("Maximum
|
|
2893
|
-
maxScrolls: z.number().int().min(0).max(250).default(10).describe("Maximum pagination scroll attempts. Default 10, maximum 250.
|
|
2894
|
-
scrollDelayMs: z.number().int().min(250).max(5e3).default(1200).describe("Delay after each
|
|
2895
|
-
stableScrollLimit: z.number().int().min(1).max(10).default(4).describe("Stop after this many consecutive scrolls with no new links
|
|
3112
|
+
url: z.string().url().optional().describe("Instagram profile URL. Provide handle or url."),
|
|
3113
|
+
profile: z.string().min(1).optional().describe("Optional saved hosted browser profile name for authenticated Instagram access."),
|
|
3114
|
+
saveProfileChanges: z.boolean().optional().describe("Save browser changes back to the hosted profile. Leave unset unless intentionally updating the saved login."),
|
|
3115
|
+
maxItems: z.number().int().min(1).max(2e3).default(50).describe("Maximum grid URLs to collect. Default 50, maximum 2000."),
|
|
3116
|
+
maxScrolls: z.number().int().min(0).max(250).default(10).describe("Maximum pagination scroll attempts. Default 10, maximum 250."),
|
|
3117
|
+
scrollDelayMs: z.number().int().min(250).max(5e3).default(1200).describe("Delay after each scroll before collecting new links. Default 1200ms."),
|
|
3118
|
+
stableScrollLimit: z.number().int().min(1).max(10).default(4).describe("Stop after this many consecutive scrolls with no new links.")
|
|
2896
3119
|
};
|
|
2897
3120
|
var InstagramMediaDownloadInputSchema = {
|
|
2898
|
-
url: z.string().url().describe("Instagram post, reel, or tv URL, e.g. https://www.instagram.com/reel/SHORTCODE/.
|
|
2899
|
-
profile: z.string().min(1).optional().describe("Optional saved hosted browser profile name
|
|
2900
|
-
saveProfileChanges: z.boolean().optional().describe("
|
|
2901
|
-
mediaTypes: z.array(z.enum(["image", "video", "audio"])).default(["image", "video", "audio"]).describe("Which media types to download when downloadMedia is true.
|
|
2902
|
-
downloadMedia: z.boolean().default(true).describe("Download extracted text/media files to the
|
|
2903
|
-
downloadAllTracks: z.boolean().default(false).describe("Download every captured
|
|
2904
|
-
includeTranscript: z.boolean().default(false).describe("Transcribe the selected audio track
|
|
2905
|
-
mux: z.boolean().default(true).describe("
|
|
3121
|
+
url: z.string().url().describe("Instagram post, reel, or tv URL, e.g. https://www.instagram.com/reel/SHORTCODE/."),
|
|
3122
|
+
profile: z.string().min(1).optional().describe("Optional saved hosted browser profile name for authenticated Instagram access."),
|
|
3123
|
+
saveProfileChanges: z.boolean().optional().describe("Save browser changes back to the hosted profile. Leave unset unless intentionally updating the saved login."),
|
|
3124
|
+
mediaTypes: z.array(z.enum(["image", "video", "audio"])).default(["image", "video", "audio"]).describe("Which media types to download when downloadMedia is true."),
|
|
3125
|
+
downloadMedia: z.boolean().default(true).describe("Download extracted text/media files to the output directory. Media URLs are always returned even when false."),
|
|
3126
|
+
downloadAllTracks: z.boolean().default(false).describe("Download every captured MP4 track instead of only the best video/audio pair."),
|
|
3127
|
+
includeTranscript: z.boolean().default(false).describe("Transcribe the selected audio track. Adds transcription cost and time."),
|
|
3128
|
+
mux: z.boolean().default(true).describe("Mux separately downloaded video/audio tracks into one MP4 if ffmpeg is available.")
|
|
2906
3129
|
};
|
|
2907
3130
|
var MapsPlaceIntelInputSchema = {
|
|
2908
|
-
businessName: z.string().min(1).describe('Business name only.
|
|
2909
|
-
location: z.string().min(1).describe('City/region/country where the business should be searched, e.g. "Denver, CO".
|
|
3131
|
+
businessName: z.string().min(1).describe('Business name only, e.g. "Elite Roofing" (not "Elite Roofing Denver CO" \u2014 put the city in location).'),
|
|
3132
|
+
location: z.string().min(1).describe('City/region/country where the business should be searched, e.g. "Denver, CO".'),
|
|
2910
3133
|
gl: z.string().length(2).default("us").describe("Google country code inferred from location."),
|
|
2911
3134
|
hl: z.string().length(2).default("en").describe("Language inferred from user request."),
|
|
2912
|
-
includeReviews: z.boolean().default(false).describe("
|
|
2913
|
-
maxReviews: z.number().int().min(1).max(500).default(50).describe("Max review cards
|
|
3135
|
+
includeReviews: z.boolean().default(false).describe("Fetch individual review cards \u2014 for reviews, customer pain, complaints, or praise themes."),
|
|
3136
|
+
maxReviews: z.number().int().min(1).max(500).default(50).describe("Max review cards when includeReviews is true. Default 50, maximum 500.")
|
|
2914
3137
|
};
|
|
2915
3138
|
var MapsSearchInputSchema = {
|
|
2916
|
-
query: z.string().min(1).describe('Business category, niche,
|
|
2917
|
-
location: z.string().optional().describe('City, region, country, or service area
|
|
3139
|
+
query: z.string().min(1).describe('Business category, niche, or search term, e.g. "roofers". Do not include location here \u2014 use location instead.'),
|
|
3140
|
+
location: z.string().optional().describe('City, region, country, or service area, e.g. "Denver, CO".'),
|
|
2918
3141
|
gl: z.string().length(2).default("us").describe("Google country code inferred from location."),
|
|
2919
3142
|
hl: z.string().length(2).default("en").describe("Language inferred from user request."),
|
|
2920
|
-
maxResults: z.number().int().min(1).max(50).default(10).describe("Number of
|
|
2921
|
-
proxyMode: z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("
|
|
2922
|
-
proxyZip: z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential
|
|
2923
|
-
debug: z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics
|
|
3143
|
+
maxResults: z.number().int().min(1).max(50).default(10).describe("Number of candidates to return. Default 10, maximum 50."),
|
|
3144
|
+
proxyMode: 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."),
|
|
3145
|
+
proxyZip: z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential proxy targeting."),
|
|
3146
|
+
debug: z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics.")
|
|
2924
3147
|
};
|
|
2925
3148
|
var DirectoryWorkflowInputSchema = {
|
|
2926
|
-
query: z.string().min(1).describe("Business category, niche, or keyword to search on Google Maps for every
|
|
2927
|
-
state: z.string().min(2).default("TN").describe("US state abbreviation or
|
|
2928
|
-
minPopulation: z.number().int().min(0).default(1e5).describe(
|
|
2929
|
-
populationYear: z.number().int().min(2020).max(2025).default(2025).describe("Census population estimate year
|
|
2930
|
-
maxCities: z.number().int().min(1).max(100).default(25).describe("Maximum
|
|
2931
|
-
maxResultsPerCity: z.number().int().min(1).max(50).default(50).describe("Google Maps
|
|
2932
|
-
concurrency: z.number().int().min(1).max(5).default(5).describe("
|
|
2933
|
-
includeZipGroups: z.boolean().default(true).describe("Attach ZIP groups from a configured US ZIPS CSV when available
|
|
2934
|
-
usZipsCsvPath: z.string().optional().describe("Local/test-only path to a US ZIPS CSV
|
|
2935
|
-
saveCsv: z.boolean().default(true).describe("Save a directory-ready CSV to the MCP Scraper output directory and return its path.
|
|
2936
|
-
proxyMode: z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Proxy targeting
|
|
2937
|
-
proxyZip: z.string().regex(/^\d{5}$/).optional().describe("Optional ZIP override for proxy targeting
|
|
2938
|
-
debug: z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics
|
|
3149
|
+
query: z.string().min(1).describe("Business category, niche, or keyword to search on Google Maps for every market. Do not include the city."),
|
|
3150
|
+
state: z.string().min(2).default("TN").describe("US state abbreviation or name used to select Census places, e.g. TN."),
|
|
3151
|
+
minPopulation: z.number().int().min(0).default(1e5).describe("Minimum Census place population for market selection."),
|
|
3152
|
+
populationYear: z.number().int().min(2020).max(2025).default(2025).describe("Census population estimate year (2020-2025 Population Estimates Program)."),
|
|
3153
|
+
maxCities: z.number().int().min(1).max(100).default(25).describe("Maximum markets to process after sorting by population descending."),
|
|
3154
|
+
maxResultsPerCity: z.number().int().min(1).max(50).default(50).describe("Google Maps candidates to collect per city."),
|
|
3155
|
+
concurrency: z.number().int().min(1).max(5).default(5).describe("City Maps searches to run in parallel."),
|
|
3156
|
+
includeZipGroups: z.boolean().default(true).describe("Attach ZIP groups from a configured US ZIPS CSV when available (MCP_SCRAPER_USZIPS_CSV_PATH or usZipsCsvPath)."),
|
|
3157
|
+
usZipsCsvPath: 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."),
|
|
3158
|
+
saveCsv: z.boolean().default(true).describe("Save a directory-ready CSV of results to the MCP Scraper output directory and return its path."),
|
|
3159
|
+
proxyMode: 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."),
|
|
3160
|
+
proxyZip: z.string().regex(/^\d{5}$/).optional().describe("Optional ZIP override for proxy targeting; normally omitted."),
|
|
3161
|
+
debug: z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics.")
|
|
2939
3162
|
};
|
|
3163
|
+
var ArtifactPointerOutputSchema = z.object({
|
|
3164
|
+
artifactId: z.string(),
|
|
3165
|
+
bytes: z.number().int().min(0),
|
|
3166
|
+
expiresAt: z.string(),
|
|
3167
|
+
preview: z.string()
|
|
3168
|
+
});
|
|
2940
3169
|
var RankTrackerModeSchema = z.enum(["maps", "organic", "ai_overview", "paa"]);
|
|
2941
3170
|
var RankTrackerBlueprintInputSchema = {
|
|
2942
3171
|
projectName: z.string().min(1).optional().describe("Optional name for the rank tracker project, client, or campaign."),
|
|
2943
|
-
targetDomain: z.string().min(1).optional().describe("Primary domain to track in organic results, AI Overview citations, and PAA sources
|
|
2944
|
-
targetBusinessName: z.string().min(1).optional().describe("Primary Google Business Profile
|
|
2945
|
-
trackingModes: z.array(RankTrackerModeSchema).min(1).max(4).default(["maps", "organic", "ai_overview", "paa"]).describe("Rank tracker surfaces to build
|
|
2946
|
-
keywords: z.array(z.string().min(1)).max(200).default([]).describe("Seed keywords or service queries to track. Leave empty
|
|
2947
|
-
locations: z.array(z.string().min(1)).max(100).default([]).describe(
|
|
3172
|
+
targetDomain: z.string().min(1).optional().describe("Primary domain to track in organic results, AI Overview citations, and PAA sources."),
|
|
3173
|
+
targetBusinessName: z.string().min(1).optional().describe("Primary Google Business Profile/brand name to match in Maps results. Required for Maps tracking."),
|
|
3174
|
+
trackingModes: z.array(RankTrackerModeSchema).min(1).max(4).default(["maps", "organic", "ai_overview", "paa"]).describe("Rank tracker surfaces to build: maps, organic, ai_overview, paa."),
|
|
3175
|
+
keywords: z.array(z.string().min(1)).max(200).default([]).describe("Seed keywords or service queries to track. Leave empty to derive from user input downstream."),
|
|
3176
|
+
locations: z.array(z.string().min(1)).max(100).default([]).describe('Markets, cities, ZIPs, or service areas to track, e.g. "Denver, CO".'),
|
|
2948
3177
|
competitors: z.array(z.string().min(1)).max(100).default([]).describe("Optional competitor domains or business names to persist as comparison targets."),
|
|
2949
|
-
database: z.enum(["postgres", "neon", "supabase", "sqlite", "mysql"]).default("postgres").describe("Database family
|
|
2950
|
-
scheduleCadence: z.enum(["daily", "weekly", "monthly", "custom"]).default("weekly").describe("Default recurring rank check cadence
|
|
3178
|
+
database: z.enum(["postgres", "neon", "supabase", "sqlite", "mysql"]).default("postgres").describe("Database family to target when generating migrations."),
|
|
3179
|
+
scheduleCadence: z.enum(["daily", "weekly", "monthly", "custom"]).default("weekly").describe("Default recurring rank check cadence."),
|
|
2951
3180
|
customCron: z.string().min(1).optional().describe("Cron expression to use when scheduleCadence is custom."),
|
|
2952
|
-
timezone: z.string().min(1).default("UTC").describe("IANA timezone for scheduled rank checks
|
|
2953
|
-
includeCron: z.boolean().default(true).describe("Include a cron
|
|
2954
|
-
includeDashboard: z.boolean().default(true).describe("Include dashboard/reporting requirements
|
|
2955
|
-
includeAlerts: z.boolean().default(true).describe("Include alert rules for rank movement and SERP feature
|
|
2956
|
-
notes: z.string().max(4e3).optional().describe("Extra product, client, stack, or hosting requirements
|
|
3181
|
+
timezone: z.string().min(1).default("UTC").describe("IANA timezone for scheduled rank checks."),
|
|
3182
|
+
includeCron: z.boolean().default(true).describe("Include a cron/heartbeat worker plan."),
|
|
3183
|
+
includeDashboard: z.boolean().default(true).describe("Include dashboard/reporting requirements."),
|
|
3184
|
+
includeAlerts: z.boolean().default(true).describe("Include alert rules for rank movement and SERP feature changes."),
|
|
3185
|
+
notes: z.string().max(4e3).optional().describe("Extra product, client, stack, or hosting requirements.")
|
|
2957
3186
|
};
|
|
2958
3187
|
var NullableString = z.string().nullable();
|
|
2959
3188
|
var MapsSearchAttemptOutput = z.object({
|
|
@@ -3050,7 +3279,9 @@ var DirectoryWorkflowOutputSchema = {
|
|
|
3050
3279
|
attempts: z.array(MapsSearchAttemptOutput),
|
|
3051
3280
|
results: z.array(DirectoryMapsBusinessOutput)
|
|
3052
3281
|
})),
|
|
3053
|
-
durationMs: z.number().int().min(0)
|
|
3282
|
+
durationMs: z.number().int().min(0),
|
|
3283
|
+
truncatedCount: z.number().int().min(0).optional(),
|
|
3284
|
+
artifact: ArtifactPointerOutputSchema.optional()
|
|
3054
3285
|
};
|
|
3055
3286
|
var RankTrackerToolPlanOutput = z.object({
|
|
3056
3287
|
tool: z.string(),
|
|
@@ -3103,7 +3334,7 @@ var EntityIdsOutput = z.object({
|
|
|
3103
3334
|
kgId: z.string().nullable(),
|
|
3104
3335
|
cid: z.string().nullable(),
|
|
3105
3336
|
gcid: z.string().nullable()
|
|
3106
|
-
})).describe("
|
|
3337
|
+
})).describe("Entities named on the page with their kgId/cid/gcid. Flat lists below are the same IDs deduplicated, kept for backward compatibility."),
|
|
3107
3338
|
kgIds: z.array(z.string()),
|
|
3108
3339
|
cids: z.array(z.string()),
|
|
3109
3340
|
gcids: z.array(z.string())
|
|
@@ -3170,7 +3401,9 @@ var ExtractSiteOutputSchema = {
|
|
|
3170
3401
|
title: NullableString,
|
|
3171
3402
|
schemaTypes: z.array(z.string())
|
|
3172
3403
|
})),
|
|
3173
|
-
durationMs: z.number().min(0)
|
|
3404
|
+
durationMs: z.number().min(0),
|
|
3405
|
+
truncatedCount: z.number().int().min(0).optional(),
|
|
3406
|
+
artifact: ArtifactPointerOutputSchema.optional()
|
|
3174
3407
|
};
|
|
3175
3408
|
var AuditSiteOutputSchema = {
|
|
3176
3409
|
url: z.string(),
|
|
@@ -3190,7 +3423,8 @@ var AuditSiteOutputSchema = {
|
|
|
3190
3423
|
orphans: z.number().int().min(0),
|
|
3191
3424
|
brokenInternal: z.number().int().min(0),
|
|
3192
3425
|
externalDomains: z.number().int().min(0)
|
|
3193
|
-
})
|
|
3426
|
+
}),
|
|
3427
|
+
artifact: ArtifactPointerOutputSchema.optional()
|
|
3194
3428
|
};
|
|
3195
3429
|
var MapsPlaceIntelOutputSchema = {
|
|
3196
3430
|
name: z.string(),
|
|
@@ -3262,7 +3496,9 @@ var MapSiteUrlsOutputSchema = {
|
|
|
3262
3496
|
url: z.string(),
|
|
3263
3497
|
status: z.number().int().nullable()
|
|
3264
3498
|
})),
|
|
3265
|
-
durationMs: z.number().min(0)
|
|
3499
|
+
durationMs: z.number().min(0),
|
|
3500
|
+
truncatedCount: z.number().int().min(0).optional(),
|
|
3501
|
+
artifact: ArtifactPointerOutputSchema.optional()
|
|
3266
3502
|
};
|
|
3267
3503
|
var YoutubeHarvestOutputSchema = {
|
|
3268
3504
|
mode: z.string(),
|
|
@@ -3570,7 +3806,7 @@ var WorkflowIdSchema = z.enum([
|
|
|
3570
3806
|
"ai-overview-language"
|
|
3571
3807
|
]);
|
|
3572
3808
|
var WorkflowListInputSchema = {
|
|
3573
|
-
includeRecipes: z.boolean().default(true).describe("Include high-level AI-facing recipes
|
|
3809
|
+
includeRecipes: z.boolean().default(true).describe("Include high-level AI-facing recipes (market analysis, ICP research, CRO audits, content gaps, etc).")
|
|
3574
3810
|
};
|
|
3575
3811
|
var WorkflowSuggestInputSchema = {
|
|
3576
3812
|
goal: 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".'),
|
|
@@ -3583,12 +3819,12 @@ var WorkflowSuggestInputSchema = {
|
|
|
3583
3819
|
maxSuggestions: z.number().int().min(1).max(8).default(3).describe("Number of matching workflow recipes to return.")
|
|
3584
3820
|
};
|
|
3585
3821
|
var WorkflowRunInputSchema = {
|
|
3586
|
-
workflowId: WorkflowIdSchema.describe("Workflow to run
|
|
3587
|
-
input: z.record(z.unknown()).default({}).describe("Workflow-specific input object
|
|
3822
|
+
workflowId: WorkflowIdSchema.describe("Workflow to run. Call workflow_list or workflow_suggest first when unsure."),
|
|
3823
|
+
input: z.record(z.unknown()).default({}).describe("Workflow-specific input object; shape depends on workflowId. Call workflow_list or workflow_suggest to see required fields."),
|
|
3588
3824
|
webhookUrl: z.string().url().optional().describe("Optional HTTPS webhook to receive the completed hosted workflow run event.")
|
|
3589
3825
|
};
|
|
3590
3826
|
var WorkflowStepInputSchema = {
|
|
3591
|
-
runId: z.string().min(1).describe("Workflow run id returned by workflow_run
|
|
3827
|
+
runId: z.string().min(1).describe("Workflow run id returned by workflow_run/workflow_step/workflow_status. Advances the run by exactly one step.")
|
|
3592
3828
|
};
|
|
3593
3829
|
var WorkflowStatusInputSchema = {
|
|
3594
3830
|
runId: 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.")
|
|
@@ -3596,7 +3832,7 @@ var WorkflowStatusInputSchema = {
|
|
|
3596
3832
|
var WorkflowArtifactReadInputSchema = {
|
|
3597
3833
|
runId: 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."),
|
|
3598
3834
|
artifactId: 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."),
|
|
3599
|
-
maxBytes: z.number().int().min(1e3).max(1e6).default(2e5).describe("Maximum bytes of artifact text to return inline.
|
|
3835
|
+
maxBytes: z.number().int().min(1e3).max(1e6).default(2e5).describe("Maximum bytes of artifact text to return inline.")
|
|
3600
3836
|
};
|
|
3601
3837
|
var WorkflowRecipeOutput = z.object({
|
|
3602
3838
|
id: z.string(),
|
|
@@ -3655,28 +3891,28 @@ var WorkflowArtifactReadOutputSchema = {
|
|
|
3655
3891
|
text: z.string()
|
|
3656
3892
|
};
|
|
3657
3893
|
var SearchSerpInputSchema = {
|
|
3658
|
-
query: z.string().min(1).describe('The search query.
|
|
3659
|
-
location: z.string().optional().describe("City, region, or country for geo signals
|
|
3894
|
+
query: 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.'),
|
|
3895
|
+
location: z.string().optional().describe("City, region, or country for geo signals. Set alongside city-in-query wording; alone it does NOT reliably localize."),
|
|
3660
3896
|
gl: z.string().length(2).default("us").describe("Google country code inferred from location or user language."),
|
|
3661
3897
|
hl: z.string().default("en").describe("Google interface/content language inferred from user request."),
|
|
3662
|
-
device: z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use
|
|
3663
|
-
proxyMode: z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe('Leave unset (clean egress
|
|
3664
|
-
proxyZip: z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location"
|
|
3665
|
-
debug: z.boolean().default(false).describe("Include sanitized
|
|
3666
|
-
pages: z.number().int().min(1).max(2).default(1).describe("Number of result pages to fetch (1\u20132)")
|
|
3898
|
+
device: z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only for mobile rankings."),
|
|
3899
|
+
proxyMode: 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.'),
|
|
3900
|
+
proxyZip: z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location".'),
|
|
3901
|
+
debug: z.boolean().default(false).describe("Include sanitized diagnostics for debugging localization, CAPTCHA, or proxy behavior."),
|
|
3902
|
+
pages: z.number().int().min(1).max(2).default(1).describe("Number of result pages to fetch (1\u20132).")
|
|
3667
3903
|
};
|
|
3668
3904
|
var CaptureSerpSnapshotInputSchema = {
|
|
3669
|
-
query: z.string().min(1).describe('Search query to capture
|
|
3670
|
-
location: z.string().optional().describe("City, region, country, or service area
|
|
3671
|
-
gl: z.string().length(2).default("us").describe("Google country code inferred from the requested market
|
|
3905
|
+
query: 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.'),
|
|
3906
|
+
location: z.string().optional().describe("City, region, country, or service area for localized Google results."),
|
|
3907
|
+
gl: z.string().length(2).default("us").describe("Google country code inferred from the requested market."),
|
|
3672
3908
|
hl: z.string().default("en").describe("Google interface/content language inferred from the user request."),
|
|
3673
|
-
device: z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only
|
|
3674
|
-
proxyMode: z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe('Leave unset (clean egress
|
|
3675
|
-
proxyZip: z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location"
|
|
3676
|
-
pages: z.number().int().min(1).max(2).default(1).describe("
|
|
3677
|
-
debug: z.boolean().default(false).describe("Include sanitized browser
|
|
3678
|
-
includePageSnapshots: z.boolean().default(false).describe("Also capture ranking-page snapshots for selected SERP URLs
|
|
3679
|
-
pageSnapshotLimit: z.number().int().min(0).max(10).default(0).describe("Maximum ranking-page snapshots
|
|
3909
|
+
device: z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only for mobile rankings/evidence."),
|
|
3910
|
+
proxyMode: 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.'),
|
|
3911
|
+
proxyZip: z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location".'),
|
|
3912
|
+
pages: z.number().int().min(1).max(2).default(1).describe("Google result pages to capture. Use 2 only for deeper ranking evidence."),
|
|
3913
|
+
debug: z.boolean().default(false).describe("Include sanitized browser/proxy/location diagnostics."),
|
|
3914
|
+
includePageSnapshots: z.boolean().default(false).describe("Also capture ranking-page snapshots for selected SERP URLs."),
|
|
3915
|
+
pageSnapshotLimit: z.number().int().min(0).max(10).default(0).describe("Maximum ranking-page snapshots when includePageSnapshots is true.")
|
|
3680
3916
|
};
|
|
3681
3917
|
var ScreenshotInputSchema = {
|
|
3682
3918
|
url: 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."),
|
|
@@ -3684,15 +3920,26 @@ var ScreenshotInputSchema = {
|
|
|
3684
3920
|
allowLocal: 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.")
|
|
3685
3921
|
};
|
|
3686
3922
|
var CaptureSerpPageSnapshotsInputSchema = {
|
|
3687
|
-
urls: z.array(z.string().url()).min(1).max(25).describe("Public HTTP/HTTPS URLs to capture
|
|
3923
|
+
urls: z.array(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."),
|
|
3688
3924
|
targets: z.array(z.object({
|
|
3689
3925
|
url: z.string().url().describe("Public HTTP/HTTPS URL to capture."),
|
|
3690
|
-
sourceKind: z.enum(["organic", "ai_citation", "local_pack_website", "configured_target", "site_subject"]).default("configured_target").describe("Why this page is being captured
|
|
3926
|
+
sourceKind: z.enum(["organic", "ai_citation", "local_pack_website", "configured_target", "site_subject"]).default("configured_target").describe("Why this page is being captured."),
|
|
3691
3927
|
sourcePosition: z.number().int().min(1).optional().describe("Ranking or citation position when the page came from SERP evidence.")
|
|
3692
|
-
}).strict()).min(1).max(25).optional().describe("Structured
|
|
3693
|
-
maxConcurrency: z.number().int().min(1).max(5).default(2).describe("Parallel page captures.
|
|
3694
|
-
timeoutMs: z.number().int().min(1e3).max(6e4).default(15e3).describe("Per-page capture timeout in milliseconds
|
|
3695
|
-
debug: z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics
|
|
3928
|
+
}).strict()).min(1).max(25).optional().describe("Structured targets. Use instead of urls when source kind or position should be preserved."),
|
|
3929
|
+
maxConcurrency: z.number().int().min(1).max(5).default(2).describe("Parallel page captures."),
|
|
3930
|
+
timeoutMs: z.number().int().min(1e3).max(6e4).default(15e3).describe("Per-page capture timeout in milliseconds; timeouts return as structured capture failures."),
|
|
3931
|
+
debug: z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics.")
|
|
3932
|
+
};
|
|
3933
|
+
var ReportArtifactReadInputSchema = {
|
|
3934
|
+
artifactId: 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."),
|
|
3935
|
+
offset: z.number().int().min(0).default(0).describe("Byte offset to start reading from. Pass the previous call's nextOffset to continue."),
|
|
3936
|
+
maxBytes: z.number().int().min(1e3).max(1e5).default(2e4).describe("Maximum bytes of artifact text to return in this window.")
|
|
3937
|
+
};
|
|
3938
|
+
var ReportArtifactReadOutputSchema = {
|
|
3939
|
+
artifactId: z.string(),
|
|
3940
|
+
text: z.string(),
|
|
3941
|
+
totalBytes: z.number().int().min(0),
|
|
3942
|
+
nextOffset: z.number().int().min(0).nullable()
|
|
3696
3943
|
};
|
|
3697
3944
|
|
|
3698
3945
|
// src/mcp/rank-tracker-blueprint.ts
|
|
@@ -4019,6 +4266,9 @@ function buildRankTrackerBlueprint(input) {
|
|
|
4019
4266
|
}
|
|
4020
4267
|
|
|
4021
4268
|
// src/mcp/paa-mcp-server.ts
|
|
4269
|
+
function hashOwnerId(callerKey) {
|
|
4270
|
+
return createHash("sha256").update(callerKey).digest("hex").slice(0, 24);
|
|
4271
|
+
}
|
|
4022
4272
|
function liveWebToolAnnotations(title) {
|
|
4023
4273
|
return {
|
|
4024
4274
|
title,
|
|
@@ -4031,16 +4281,16 @@ function liveWebToolAnnotations(title) {
|
|
|
4031
4281
|
function registerSerpIntelligenceCaptureTools(server, executor) {
|
|
4032
4282
|
server.registerTool("capture_serp_snapshot", {
|
|
4033
4283
|
title: "SERP Intelligence Snapshot",
|
|
4034
|
-
description: "Capture a structured SERP Intelligence
|
|
4284
|
+
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.",
|
|
4035
4285
|
inputSchema: CaptureSerpSnapshotInputSchema,
|
|
4036
|
-
outputSchema: CaptureSerpSnapshotOutputSchema,
|
|
4286
|
+
outputSchema: recordOutputSchema("capture_serp_snapshot", CaptureSerpSnapshotOutputSchema),
|
|
4037
4287
|
annotations: liveWebToolAnnotations("SERP Intelligence Snapshot")
|
|
4038
4288
|
}, async (input) => formatCaptureSerpSnapshot(await executor.captureSerpSnapshot(input), input));
|
|
4039
4289
|
server.registerTool("capture_serp_page_snapshots", {
|
|
4040
4290
|
title: "SERP Intelligence Page Snapshots",
|
|
4041
|
-
description: "Capture public ranking
|
|
4291
|
+
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.",
|
|
4042
4292
|
inputSchema: CaptureSerpPageSnapshotsInputSchema,
|
|
4043
|
-
outputSchema: CaptureSerpPageSnapshotsOutputSchema,
|
|
4293
|
+
outputSchema: recordOutputSchema("capture_serp_page_snapshots", CaptureSerpPageSnapshotsOutputSchema),
|
|
4044
4294
|
annotations: liveWebToolAnnotations("SERP Intelligence Page Snapshots")
|
|
4045
4295
|
}, async (input) => formatCaptureSerpPageSnapshots(await executor.captureSerpPageSnapshots(input), input));
|
|
4046
4296
|
}
|
|
@@ -4056,7 +4306,7 @@ function localPlanningToolAnnotations(title) {
|
|
|
4056
4306
|
function listSavedReports() {
|
|
4057
4307
|
try {
|
|
4058
4308
|
const dir = outputBaseDir();
|
|
4059
|
-
return readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => ({ filename: f, mtimeMs: statSync(
|
|
4309
|
+
return readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => ({ filename: f, mtimeMs: statSync(join3(dir, f)).mtimeMs })).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, 100);
|
|
4060
4310
|
} catch {
|
|
4061
4311
|
return [];
|
|
4062
4312
|
}
|
|
@@ -4082,236 +4332,257 @@ function registerSavedReportResources(server) {
|
|
|
4082
4332
|
const requested = Array.isArray(variables.filename) ? variables.filename[0] : variables.filename;
|
|
4083
4333
|
const filename = basename(decodeURIComponent(String(requested ?? "")));
|
|
4084
4334
|
if (!filename.endsWith(".md")) throw new Error("Only saved .md reports can be read");
|
|
4085
|
-
const text = readFileSync(
|
|
4335
|
+
const text = readFileSync(join3(outputBaseDir(), filename), "utf8");
|
|
4086
4336
|
return { contents: [{ uri: uri.href, mimeType: "text/markdown", text }] };
|
|
4087
4337
|
}
|
|
4088
4338
|
);
|
|
4089
4339
|
}
|
|
4090
4340
|
function buildPaaExtractorMcpServer(executor, options = {}) {
|
|
4091
|
-
const server = new McpServer({ name: "mcp-scraper", version: PACKAGE_VERSION }, { instructions:
|
|
4341
|
+
const server = new McpServer({ name: "mcp-scraper", version: PACKAGE_VERSION }, { instructions: serverInstructions(options.savesReportsLocally !== false) });
|
|
4092
4342
|
registerPaaExtractorMcpTools(server, executor, options);
|
|
4093
4343
|
return server;
|
|
4094
4344
|
}
|
|
4095
4345
|
function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
4096
4346
|
const savesReports = options.savesReportsLocally !== false;
|
|
4097
|
-
const
|
|
4098
|
-
const
|
|
4347
|
+
const fileBehavior = (local, hosted) => savesReports ? local : hosted;
|
|
4348
|
+
const ownerId = options.ownerId ?? "local";
|
|
4349
|
+
const ctx = { hosted: !savesReports, ownerId };
|
|
4099
4350
|
if (savesReports) registerSavedReportResources(server);
|
|
4100
4351
|
server.registerTool("harvest_paa", {
|
|
4101
4352
|
title: "Google PAA + SERP Harvest",
|
|
4102
|
-
description:
|
|
4353
|
+
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.",
|
|
4103
4354
|
inputSchema: HarvestPaaInputSchema,
|
|
4104
|
-
outputSchema: HarvestPaaOutputSchema,
|
|
4355
|
+
outputSchema: recordOutputSchema("harvest_paa", HarvestPaaOutputSchema),
|
|
4105
4356
|
annotations: liveWebToolAnnotations("Google PAA + SERP Harvest")
|
|
4106
4357
|
}, async (input) => formatHarvestPaa(await executor.harvestPaa(input), input));
|
|
4107
4358
|
server.registerTool("search_serp", {
|
|
4108
4359
|
title: "Google SERP Lookup",
|
|
4109
|
-
description:
|
|
4360
|
+
description: "Fast Google SERP lookup without PAA expansion \u2014 rankings, organic results, local pack, positions. Split topic from location; leave proxyMode unset.",
|
|
4110
4361
|
inputSchema: SearchSerpInputSchema,
|
|
4111
|
-
outputSchema: SearchSerpOutputSchema,
|
|
4362
|
+
outputSchema: recordOutputSchema("search_serp", SearchSerpOutputSchema),
|
|
4112
4363
|
annotations: liveWebToolAnnotations("Google SERP Lookup")
|
|
4113
4364
|
}, async (input) => formatSearchSerp(await executor.searchSerp(input), input));
|
|
4114
4365
|
server.registerTool("extract_url", {
|
|
4115
4366
|
title: "Single URL Extract",
|
|
4116
|
-
description:
|
|
4367
|
+
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).",
|
|
4117
4368
|
inputSchema: ExtractUrlInputSchema,
|
|
4118
|
-
outputSchema: ExtractUrlOutputSchema,
|
|
4369
|
+
outputSchema: recordOutputSchema("extract_url", ExtractUrlOutputSchema),
|
|
4119
4370
|
annotations: liveWebToolAnnotations("Single URL Extract")
|
|
4120
4371
|
}, async (input) => formatExtractUrl(await executor.extractUrl(input), input));
|
|
4121
4372
|
server.registerTool("map_site_urls", {
|
|
4122
4373
|
title: "Site URL Map",
|
|
4123
|
-
description:
|
|
4374
|
+
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.")}`,
|
|
4124
4375
|
inputSchema: MapSiteUrlsInputSchema,
|
|
4125
|
-
outputSchema: MapSiteUrlsOutputSchema,
|
|
4376
|
+
outputSchema: recordOutputSchema("map_site_urls", MapSiteUrlsOutputSchema),
|
|
4126
4377
|
annotations: liveWebToolAnnotations("Site URL Map")
|
|
4127
|
-
}, async (input) => formatMapSiteUrls(await executor.mapSiteUrls(input), input));
|
|
4378
|
+
}, async (input) => formatMapSiteUrls(await executor.mapSiteUrls(input), input, ctx));
|
|
4128
4379
|
server.registerTool("extract_site", {
|
|
4129
4380
|
title: "Multi-Page Site Content Crawl",
|
|
4130
|
-
description:
|
|
4381
|
+
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.`,
|
|
4131
4382
|
inputSchema: ExtractSiteInputSchema,
|
|
4132
|
-
outputSchema: ExtractSiteOutputSchema,
|
|
4383
|
+
outputSchema: recordOutputSchema("extract_site", ExtractSiteOutputSchema),
|
|
4133
4384
|
annotations: liveWebToolAnnotations("Multi-Page Site Content Crawl")
|
|
4134
|
-
}, async (input) => formatExtractSite(await executor.extractSite(input), input));
|
|
4385
|
+
}, async (input) => formatExtractSite(await executor.extractSite(input), input, ctx));
|
|
4135
4386
|
server.registerTool("audit_site", {
|
|
4136
4387
|
title: "Technical SEO Audit",
|
|
4137
|
-
description:
|
|
4388
|
+
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.`,
|
|
4138
4389
|
inputSchema: AuditSiteInputSchema,
|
|
4139
|
-
outputSchema: AuditSiteOutputSchema,
|
|
4390
|
+
outputSchema: recordOutputSchema("audit_site", AuditSiteOutputSchema),
|
|
4140
4391
|
annotations: liveWebToolAnnotations("Technical SEO Audit")
|
|
4141
|
-
}, async (input) => formatAuditSite(await executor.auditSite(input), input));
|
|
4392
|
+
}, async (input) => formatAuditSite(await executor.auditSite(input), input, ctx));
|
|
4142
4393
|
server.registerTool("youtube_harvest", {
|
|
4143
4394
|
title: "YouTube Video Harvest",
|
|
4144
|
-
description:
|
|
4395
|
+
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.',
|
|
4145
4396
|
inputSchema: YoutubeHarvestInputSchema,
|
|
4146
|
-
outputSchema: YoutubeHarvestOutputSchema,
|
|
4397
|
+
outputSchema: recordOutputSchema("youtube_harvest", YoutubeHarvestOutputSchema),
|
|
4147
4398
|
annotations: liveWebToolAnnotations("YouTube Video Harvest")
|
|
4148
4399
|
}, async (input) => formatYoutubeHarvest(await executor.youtubeHarvest(input), input));
|
|
4149
4400
|
server.registerTool("youtube_transcribe", {
|
|
4150
4401
|
title: "YouTube Transcription",
|
|
4151
|
-
description:
|
|
4402
|
+
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.",
|
|
4152
4403
|
inputSchema: YoutubeTranscribeInputSchema,
|
|
4153
|
-
outputSchema: YoutubeTranscribeOutputSchema,
|
|
4404
|
+
outputSchema: recordOutputSchema("youtube_transcribe", YoutubeTranscribeOutputSchema),
|
|
4154
4405
|
annotations: liveWebToolAnnotations("YouTube Transcription")
|
|
4155
4406
|
}, async (input) => formatYoutubeTranscribe(await executor.youtubeTranscribe(input), input));
|
|
4156
4407
|
server.registerTool("facebook_page_intel", {
|
|
4157
4408
|
title: "Facebook Advertiser Ad Intel",
|
|
4158
|
-
description:
|
|
4409
|
+
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.",
|
|
4159
4410
|
inputSchema: FacebookPageIntelInputSchema,
|
|
4160
|
-
outputSchema: FacebookPageIntelOutputSchema,
|
|
4411
|
+
outputSchema: recordOutputSchema("facebook_page_intel", FacebookPageIntelOutputSchema),
|
|
4161
4412
|
annotations: liveWebToolAnnotations("Facebook Advertiser Ad Intel")
|
|
4162
4413
|
}, async (input) => formatFacebookPageIntel(await executor.facebookPageIntel(input), input));
|
|
4163
4414
|
server.registerTool("facebook_ad_search", {
|
|
4164
4415
|
title: "Facebook Ad Library Search",
|
|
4165
|
-
description:
|
|
4416
|
+
description: "Search Facebook Ad Library to find advertisers by brand, competitor, niche, or keyword. Returns advertisers with ad counts and library IDs.",
|
|
4166
4417
|
inputSchema: FacebookAdSearchInputSchema,
|
|
4167
|
-
outputSchema: FacebookAdSearchOutputSchema,
|
|
4418
|
+
outputSchema: recordOutputSchema("facebook_ad_search", FacebookAdSearchOutputSchema),
|
|
4168
4419
|
annotations: liveWebToolAnnotations("Facebook Ad Library Search")
|
|
4169
4420
|
}, async (input) => formatFacebookAdSearch(await executor.facebookAdSearch(input), input));
|
|
4170
4421
|
server.registerTool("reddit_thread", {
|
|
4171
4422
|
title: "Reddit Thread + Comments",
|
|
4172
|
-
description:
|
|
4423
|
+
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.",
|
|
4173
4424
|
inputSchema: RedditThreadInputSchema,
|
|
4174
|
-
outputSchema: RedditThreadOutputSchema,
|
|
4425
|
+
outputSchema: recordOutputSchema("reddit_thread", RedditThreadOutputSchema),
|
|
4175
4426
|
annotations: liveWebToolAnnotations("Reddit Thread + Comments")
|
|
4176
4427
|
}, async (input) => formatRedditThread(await executor.redditThread(input), input));
|
|
4177
4428
|
server.registerTool("video_frame_analysis", {
|
|
4178
4429
|
title: "Video Breakdown (frame-by-frame + transcript)",
|
|
4179
|
-
description:
|
|
4430
|
+
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.",
|
|
4180
4431
|
inputSchema: VideoFrameAnalysisInputSchema,
|
|
4181
|
-
outputSchema: VideoFrameAnalysisOutputSchema,
|
|
4432
|
+
outputSchema: recordOutputSchema("video_frame_analysis", VideoFrameAnalysisOutputSchema),
|
|
4182
4433
|
annotations: { title: "Video Breakdown", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
4183
4434
|
}, async (input) => executor.videoFrameAnalysis(input));
|
|
4184
4435
|
server.registerTool("video_frame_analysis_status", {
|
|
4185
4436
|
title: "Video Breakdown Status",
|
|
4186
|
-
description: 'Check
|
|
4437
|
+
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".',
|
|
4187
4438
|
inputSchema: VideoFrameAnalysisStatusInputSchema,
|
|
4188
|
-
outputSchema: VideoFrameAnalysisStatusOutputSchema,
|
|
4439
|
+
outputSchema: recordOutputSchema("video_frame_analysis_status", VideoFrameAnalysisStatusOutputSchema),
|
|
4189
4440
|
annotations: { title: "Video Breakdown Status", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
4190
4441
|
}, async (input) => executor.videoFrameAnalysisStatus(input));
|
|
4191
4442
|
server.registerTool("facebook_ad_transcribe", {
|
|
4192
4443
|
title: "Facebook Ad Transcription",
|
|
4193
|
-
description: "Transcribe audio from a Facebook ad video CDN URL
|
|
4444
|
+
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).",
|
|
4194
4445
|
inputSchema: FacebookAdTranscribeInputSchema,
|
|
4195
|
-
outputSchema: FacebookAdTranscribeOutputSchema,
|
|
4446
|
+
outputSchema: recordOutputSchema("facebook_ad_transcribe", FacebookAdTranscribeOutputSchema),
|
|
4196
4447
|
annotations: liveWebToolAnnotations("Facebook Ad Transcription")
|
|
4197
4448
|
}, async (input) => formatFacebookAdTranscribe(await executor.facebookAdTranscribe(input), input));
|
|
4198
4449
|
server.registerTool("google_ads_search", {
|
|
4199
4450
|
title: "Google Ads Transparency Search",
|
|
4200
|
-
description:
|
|
4451
|
+
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.",
|
|
4201
4452
|
inputSchema: GoogleAdsSearchInputSchema,
|
|
4202
|
-
outputSchema: GoogleAdsSearchOutputSchema,
|
|
4453
|
+
outputSchema: recordOutputSchema("google_ads_search", GoogleAdsSearchOutputSchema),
|
|
4203
4454
|
annotations: liveWebToolAnnotations("Google Ads Transparency Search")
|
|
4204
4455
|
}, async (input) => formatGoogleAdsSearch(await executor.googleAdsSearch(input), input));
|
|
4205
4456
|
server.registerTool("google_ads_page_intel", {
|
|
4206
4457
|
title: "Google Ads Advertiser Intel",
|
|
4207
|
-
description:
|
|
4458
|
+
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.",
|
|
4208
4459
|
inputSchema: GoogleAdsPageIntelInputSchema,
|
|
4209
|
-
outputSchema: GoogleAdsPageIntelOutputSchema,
|
|
4460
|
+
outputSchema: recordOutputSchema("google_ads_page_intel", GoogleAdsPageIntelOutputSchema),
|
|
4210
4461
|
annotations: liveWebToolAnnotations("Google Ads Advertiser Intel")
|
|
4211
4462
|
}, async (input) => formatGoogleAdsPageIntel(await executor.googleAdsPageIntel(input), input));
|
|
4212
4463
|
server.registerTool("google_ads_transcribe", {
|
|
4213
4464
|
title: "Google Ad Video Transcription",
|
|
4214
|
-
description: "Transcribe audio from a Google video ad
|
|
4465
|
+
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.",
|
|
4215
4466
|
inputSchema: GoogleAdsTranscribeInputSchema,
|
|
4216
|
-
outputSchema: GoogleAdsTranscribeOutputSchema,
|
|
4467
|
+
outputSchema: recordOutputSchema("google_ads_transcribe", GoogleAdsTranscribeOutputSchema),
|
|
4217
4468
|
annotations: liveWebToolAnnotations("Google Ad Video Transcription")
|
|
4218
4469
|
}, async (input) => formatGoogleAdsTranscribe(await executor.googleAdsTranscribe(input), input));
|
|
4219
4470
|
server.registerTool("facebook_video_transcribe", {
|
|
4220
4471
|
title: "Facebook Organic Video Transcription",
|
|
4221
|
-
description:
|
|
4472
|
+
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.",
|
|
4222
4473
|
inputSchema: FacebookVideoTranscribeInputSchema,
|
|
4223
|
-
outputSchema: FacebookVideoTranscribeOutputSchema,
|
|
4474
|
+
outputSchema: recordOutputSchema("facebook_video_transcribe", FacebookVideoTranscribeOutputSchema),
|
|
4224
4475
|
annotations: liveWebToolAnnotations("Facebook Organic Video Transcription")
|
|
4225
4476
|
}, async (input) => formatFacebookVideoTranscribe(await executor.facebookVideoTranscribe(input), input));
|
|
4226
4477
|
server.registerTool("instagram_profile_content", {
|
|
4227
4478
|
title: "Instagram Profile Content Discovery",
|
|
4228
|
-
description:
|
|
4479
|
+
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.",
|
|
4229
4480
|
inputSchema: InstagramProfileContentInputSchema,
|
|
4230
|
-
outputSchema: InstagramProfileContentOutputSchema,
|
|
4481
|
+
outputSchema: recordOutputSchema("instagram_profile_content", InstagramProfileContentOutputSchema),
|
|
4231
4482
|
annotations: liveWebToolAnnotations("Instagram Profile Content Discovery")
|
|
4232
4483
|
}, async (input) => formatInstagramProfileContent(await executor.instagramProfileContent(input), input));
|
|
4233
4484
|
server.registerTool("instagram_media_download", {
|
|
4234
4485
|
title: "Instagram Post/Reel Media Download",
|
|
4235
|
-
description:
|
|
4486
|
+
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.",
|
|
4236
4487
|
inputSchema: InstagramMediaDownloadInputSchema,
|
|
4237
|
-
outputSchema: InstagramMediaDownloadOutputSchema,
|
|
4488
|
+
outputSchema: recordOutputSchema("instagram_media_download", InstagramMediaDownloadOutputSchema),
|
|
4238
4489
|
annotations: liveWebToolAnnotations("Instagram Post/Reel Media Download")
|
|
4239
4490
|
}, async (input) => formatInstagramMediaDownload(await executor.instagramMediaDownload(input), input));
|
|
4240
4491
|
server.registerTool("maps_place_intel", {
|
|
4241
4492
|
title: "Google Maps Business Profile Details",
|
|
4242
|
-
description:
|
|
4493
|
+
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.",
|
|
4243
4494
|
inputSchema: MapsPlaceIntelInputSchema,
|
|
4244
|
-
outputSchema: MapsPlaceIntelOutputSchema,
|
|
4495
|
+
outputSchema: recordOutputSchema("maps_place_intel", MapsPlaceIntelOutputSchema),
|
|
4245
4496
|
annotations: liveWebToolAnnotations("Google Maps Business Profile Details")
|
|
4246
4497
|
}, async (input) => formatMapsPlaceIntel(await executor.mapsPlaceIntel(input), input));
|
|
4247
4498
|
server.registerTool("maps_search", {
|
|
4248
4499
|
title: "Google Maps Business Search",
|
|
4249
|
-
description:
|
|
4500
|
+
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.",
|
|
4250
4501
|
inputSchema: MapsSearchInputSchema,
|
|
4251
|
-
outputSchema: MapsSearchOutputSchema,
|
|
4502
|
+
outputSchema: recordOutputSchema("maps_search", MapsSearchOutputSchema),
|
|
4252
4503
|
annotations: liveWebToolAnnotations("Google Maps Business Search")
|
|
4253
4504
|
}, async (input) => formatMapsSearch(await executor.mapsSearch(input), input));
|
|
4254
4505
|
server.registerTool("directory_workflow", {
|
|
4255
4506
|
title: "Directory Workflow: Markets + Maps",
|
|
4256
|
-
description:
|
|
4507
|
+
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.")}`,
|
|
4257
4508
|
inputSchema: DirectoryWorkflowInputSchema,
|
|
4258
|
-
outputSchema: DirectoryWorkflowOutputSchema,
|
|
4509
|
+
outputSchema: recordOutputSchema("directory_workflow", DirectoryWorkflowOutputSchema),
|
|
4259
4510
|
annotations: liveWebToolAnnotations("Directory Workflow: Markets + Maps")
|
|
4260
|
-
}, async (input) => formatDirectoryWorkflow(await executor.directoryWorkflow(input), input));
|
|
4511
|
+
}, async (input) => formatDirectoryWorkflow(await executor.directoryWorkflow(input), input, ctx));
|
|
4261
4512
|
server.registerTool("workflow_list", {
|
|
4262
4513
|
title: "Workflow Catalog",
|
|
4263
|
-
description: "List MCP Scraper higher-level workflows and
|
|
4514
|
+
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.",
|
|
4264
4515
|
inputSchema: WorkflowListInputSchema,
|
|
4265
|
-
outputSchema: WorkflowListOutputSchema,
|
|
4516
|
+
outputSchema: recordOutputSchema("workflow_list", WorkflowListOutputSchema),
|
|
4266
4517
|
annotations: localPlanningToolAnnotations("Workflow Catalog")
|
|
4267
4518
|
}, async (input) => formatWorkflowList(await executor.workflowList(input), input));
|
|
4268
4519
|
server.registerTool("workflow_suggest", {
|
|
4269
4520
|
title: "Workflow Intent Router",
|
|
4270
|
-
description:
|
|
4521
|
+
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.",
|
|
4271
4522
|
inputSchema: WorkflowSuggestInputSchema,
|
|
4272
|
-
outputSchema: WorkflowSuggestOutputSchema,
|
|
4523
|
+
outputSchema: recordOutputSchema("workflow_suggest", WorkflowSuggestOutputSchema),
|
|
4273
4524
|
annotations: localPlanningToolAnnotations("Workflow Intent Router")
|
|
4274
4525
|
}, async (input) => formatWorkflowSuggest(input));
|
|
4275
4526
|
server.registerTool("workflow_run", {
|
|
4276
4527
|
title: "Run Workflow",
|
|
4277
|
-
description:
|
|
4528
|
+
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.",
|
|
4278
4529
|
inputSchema: WorkflowRunInputSchema,
|
|
4279
|
-
outputSchema: WorkflowRunOutputSchema,
|
|
4530
|
+
outputSchema: recordOutputSchema("workflow_run", WorkflowRunOutputSchema),
|
|
4280
4531
|
annotations: liveWebToolAnnotations("Run Workflow")
|
|
4281
4532
|
}, async (input) => formatWorkflowRun(await executor.workflowRun(input), input));
|
|
4282
4533
|
server.registerTool("workflow_step", {
|
|
4283
4534
|
title: "Advance Workflow Step",
|
|
4284
|
-
description:
|
|
4535
|
+
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.",
|
|
4285
4536
|
inputSchema: WorkflowStepInputSchema,
|
|
4286
|
-
outputSchema: WorkflowStepOutputSchema,
|
|
4537
|
+
outputSchema: recordOutputSchema("workflow_step", WorkflowStepOutputSchema),
|
|
4287
4538
|
annotations: liveWebToolAnnotations("Advance Workflow Step")
|
|
4288
4539
|
}, async (input) => formatWorkflowStep(await executor.workflowStep(input), input));
|
|
4289
4540
|
server.registerTool("workflow_status", {
|
|
4290
4541
|
title: "Workflow Status",
|
|
4291
|
-
description: "Fetch a hosted workflow run by id and list its current status and artifacts
|
|
4542
|
+
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.",
|
|
4292
4543
|
inputSchema: WorkflowStatusInputSchema,
|
|
4293
|
-
outputSchema: WorkflowStatusOutputSchema,
|
|
4544
|
+
outputSchema: recordOutputSchema("workflow_status", WorkflowStatusOutputSchema),
|
|
4294
4545
|
annotations: liveWebToolAnnotations("Workflow Status")
|
|
4295
4546
|
}, async (input) => formatWorkflowStatus(await executor.workflowStatus(input), input));
|
|
4296
4547
|
server.registerTool("workflow_artifact_read", {
|
|
4297
4548
|
title: "Read Workflow Artifact",
|
|
4298
|
-
description: "Read a workflow artifact back into
|
|
4549
|
+
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.",
|
|
4299
4550
|
inputSchema: WorkflowArtifactReadInputSchema,
|
|
4300
|
-
outputSchema: WorkflowArtifactReadOutputSchema,
|
|
4551
|
+
outputSchema: recordOutputSchema("workflow_artifact_read", WorkflowArtifactReadOutputSchema),
|
|
4301
4552
|
annotations: liveWebToolAnnotations("Read Workflow Artifact")
|
|
4302
4553
|
}, async (input) => formatWorkflowArtifactRead(await executor.workflowArtifactRead(input), input));
|
|
4554
|
+
server.registerTool("report_artifact_read", {
|
|
4555
|
+
title: "Read Report Artifact",
|
|
4556
|
+
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.",
|
|
4557
|
+
inputSchema: ReportArtifactReadInputSchema,
|
|
4558
|
+
outputSchema: recordOutputSchema("report_artifact_read", ReportArtifactReadOutputSchema),
|
|
4559
|
+
annotations: liveWebToolAnnotations("Read Report Artifact")
|
|
4560
|
+
}, async (input) => {
|
|
4561
|
+
const owner = artifactOwnerId(input.artifactId);
|
|
4562
|
+
if (!owner || owner !== ownerId) {
|
|
4563
|
+
return { content: [{ type: "text", text: "Artifact not found or not owned by this caller." }], isError: true };
|
|
4564
|
+
}
|
|
4565
|
+
const window = await readArtifactWindow(input.artifactId, input.offset ?? 0, input.maxBytes ?? 2e4);
|
|
4566
|
+
if (!window) {
|
|
4567
|
+
return { content: [{ type: "text", text: "Artifact not found or expired." }], isError: true };
|
|
4568
|
+
}
|
|
4569
|
+
return {
|
|
4570
|
+
content: [{ type: "text", text: window.text }],
|
|
4571
|
+
structuredContent: { artifactId: input.artifactId, text: window.text, totalBytes: window.totalBytes, nextOffset: window.nextOffset }
|
|
4572
|
+
};
|
|
4573
|
+
});
|
|
4303
4574
|
server.registerTool("rank_tracker_workflow", {
|
|
4304
4575
|
title: "Rank Tracker Blueprint Builder",
|
|
4305
|
-
description: "Generate a build-ready database, cron
|
|
4576
|
+
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.",
|
|
4306
4577
|
inputSchema: RankTrackerBlueprintInputSchema,
|
|
4307
|
-
outputSchema: RankTrackerBlueprintOutputSchema,
|
|
4578
|
+
outputSchema: recordOutputSchema("rank_tracker_workflow", RankTrackerBlueprintOutputSchema),
|
|
4308
4579
|
annotations: localPlanningToolAnnotations("Rank Tracker Blueprint Builder")
|
|
4309
4580
|
}, async (input) => buildRankTrackerBlueprint(input));
|
|
4310
4581
|
server.registerTool("credits_info", {
|
|
4311
4582
|
title: "MCP Scraper Credits & Costs",
|
|
4312
|
-
description: "Answer questions about MCP Scraper credits, usage limits, and concurrency upgrades
|
|
4583
|
+
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.",
|
|
4313
4584
|
inputSchema: CreditsInfoInputSchema,
|
|
4314
|
-
outputSchema: CreditsInfoOutputSchema,
|
|
4585
|
+
outputSchema: recordOutputSchema("credits_info", CreditsInfoOutputSchema),
|
|
4315
4586
|
annotations: {
|
|
4316
4587
|
title: "MCP Scraper Credits & Costs",
|
|
4317
4588
|
readOnlyHint: true,
|
|
@@ -4605,30 +4876,30 @@ var HttpMcpToolExecutor = class {
|
|
|
4605
4876
|
|
|
4606
4877
|
// src/mcp/browser-agent-mcp-server.ts
|
|
4607
4878
|
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4608
|
-
import { mkdirSync as
|
|
4609
|
-
import { homedir as
|
|
4610
|
-
import { join as
|
|
4879
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
4880
|
+
import { homedir as homedir4 } from "os";
|
|
4881
|
+
import { join as join6 } from "path";
|
|
4611
4882
|
|
|
4612
4883
|
// src/services/fanout/export.ts
|
|
4613
|
-
import { mkdirSync as
|
|
4614
|
-
import { homedir as
|
|
4615
|
-
import { join as
|
|
4884
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
4885
|
+
import { homedir as homedir3 } from "os";
|
|
4886
|
+
import { join as join4 } from "path";
|
|
4616
4887
|
import Papa from "papaparse";
|
|
4617
4888
|
function outputBaseDir2() {
|
|
4618
|
-
return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() ||
|
|
4889
|
+
return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || join4(homedir3(), "Downloads", "mcp-scraper");
|
|
4619
4890
|
}
|
|
4620
4891
|
function safe(value) {
|
|
4621
4892
|
return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "capture";
|
|
4622
4893
|
}
|
|
4623
4894
|
function writeTable(path, rows, delimiter) {
|
|
4624
|
-
|
|
4895
|
+
writeFileSync3(path, Papa.unparse(rows.length ? rows : [{}], { delimiter }));
|
|
4625
4896
|
}
|
|
4626
4897
|
function exportFanout(enriched) {
|
|
4627
4898
|
const stamp = safe(enriched.capturedAt.replace(/[:.]/g, "-"));
|
|
4628
4899
|
const outputDir = outputBaseDir2();
|
|
4629
|
-
const relativeDir =
|
|
4630
|
-
const dir =
|
|
4631
|
-
|
|
4900
|
+
const relativeDir = join4("fanout", `${stamp}-${safe(enriched.platform)}`);
|
|
4901
|
+
const dir = join4(outputDir, relativeDir);
|
|
4902
|
+
mkdirSync3(dir, { recursive: true });
|
|
4632
4903
|
const queryRows = enriched.queries.map((q, i) => ({ index: i + 1, query: q }));
|
|
4633
4904
|
const citationRows = enriched.aggregates.citationOrder.map((c) => {
|
|
4634
4905
|
const src = enriched.citedUrls.find((s) => s.url === c.url);
|
|
@@ -4641,25 +4912,25 @@ function exportFanout(enriched) {
|
|
|
4641
4912
|
const relativePaths = {
|
|
4642
4913
|
relativeTo: "MCP_SCRAPER_OUTPUT_DIR or ~/Downloads/mcp-scraper",
|
|
4643
4914
|
dir: relativeDir,
|
|
4644
|
-
json:
|
|
4645
|
-
queriesCsv:
|
|
4646
|
-
queriesTsv:
|
|
4647
|
-
citationsCsv:
|
|
4648
|
-
sourcesCsv:
|
|
4649
|
-
browsedOnlyCsv:
|
|
4650
|
-
snippetsCsv:
|
|
4651
|
-
domainsCsv:
|
|
4652
|
-
report:
|
|
4915
|
+
json: join4(relativeDir, "fanout.json"),
|
|
4916
|
+
queriesCsv: join4(relativeDir, "queries.csv"),
|
|
4917
|
+
queriesTsv: join4(relativeDir, "queries.tsv"),
|
|
4918
|
+
citationsCsv: join4(relativeDir, "citations.csv"),
|
|
4919
|
+
sourcesCsv: join4(relativeDir, "sources.csv"),
|
|
4920
|
+
browsedOnlyCsv: join4(relativeDir, "browsed-only.csv"),
|
|
4921
|
+
snippetsCsv: join4(relativeDir, "snippets.csv"),
|
|
4922
|
+
domainsCsv: join4(relativeDir, "domains.csv"),
|
|
4923
|
+
report: join4(relativeDir, "report.html")
|
|
4653
4924
|
};
|
|
4654
|
-
|
|
4655
|
-
writeTable(
|
|
4656
|
-
writeTable(
|
|
4657
|
-
writeTable(
|
|
4658
|
-
writeTable(
|
|
4659
|
-
writeTable(
|
|
4660
|
-
writeTable(
|
|
4661
|
-
writeTable(
|
|
4662
|
-
|
|
4925
|
+
writeFileSync3(join4(outputDir, relativePaths.json), JSON.stringify(enriched, null, 2));
|
|
4926
|
+
writeTable(join4(outputDir, relativePaths.queriesCsv), queryRows, ",");
|
|
4927
|
+
writeTable(join4(outputDir, relativePaths.queriesTsv), queryRows, " ");
|
|
4928
|
+
writeTable(join4(outputDir, relativePaths.citationsCsv), citationRows, ",");
|
|
4929
|
+
writeTable(join4(outputDir, relativePaths.sourcesCsv), sourceRows, ",");
|
|
4930
|
+
writeTable(join4(outputDir, relativePaths.browsedOnlyCsv), browsedOnlyRows, ",");
|
|
4931
|
+
writeTable(join4(outputDir, relativePaths.snippetsCsv), snippetRows, ",");
|
|
4932
|
+
writeTable(join4(outputDir, relativePaths.domainsCsv), domainRows, ",");
|
|
4933
|
+
writeFileSync3(join4(outputDir, relativePaths.report), renderReportHtml(enriched));
|
|
4663
4934
|
return relativePaths;
|
|
4664
4935
|
}
|
|
4665
4936
|
function esc(s) {
|
|
@@ -4756,18 +5027,18 @@ var BrowserOpenInputSchema = {
|
|
|
4756
5027
|
label: z2.string().optional().describe("Optional human label for this session, shown in the watch console."),
|
|
4757
5028
|
url: z2.string().url().optional().describe("Optional URL to navigate to immediately after opening."),
|
|
4758
5029
|
profile: z2.string().optional().describe("Optional saved hosted profile name to load a logged-in session for a site."),
|
|
4759
|
-
save_profile_changes: z2.boolean().optional().describe("Persist cookies
|
|
4760
|
-
timeout_seconds: z2.number().int().min(60).max(259200).optional().describe("
|
|
5030
|
+
save_profile_changes: z2.boolean().optional().describe("Persist cookies/storage back to the named profile on close. Avoid parallel sessions writing to the same profile."),
|
|
5031
|
+
timeout_seconds: z2.number().int().min(60).max(259200).optional().describe("Session lifetime before auto-termination. Defaults to 600.")
|
|
4761
5032
|
};
|
|
4762
5033
|
var BrowserProfileConnectInputSchema = {
|
|
4763
|
-
email: z2.string().optional().describe("Account email for the login
|
|
4764
|
-
profile: z2.string().optional().describe("Profile to add this login to. Omit to derive
|
|
4765
|
-
domain: z2.string().optional().describe("Site to log into, e.g. chatgpt.com, claude.ai, reddit.com. Defaults to chatgpt.com.
|
|
4766
|
-
login_url: z2.string().url().optional().describe("Login page for the domain. Defaults to https://<domain
|
|
4767
|
-
url: z2.string().url().optional().describe("Deprecated alias for login_url.
|
|
4768
|
-
note: z2.string().optional().describe(
|
|
5034
|
+
email: z2.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."),
|
|
5035
|
+
profile: z2.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."),
|
|
5036
|
+
domain: z2.string().optional().describe("Site to log into, e.g. chatgpt.com, claude.ai, reddit.com. Defaults to chatgpt.com."),
|
|
5037
|
+
login_url: z2.string().url().optional().describe("Login page for the domain. Defaults to https://<domain>/."),
|
|
5038
|
+
url: z2.string().url().optional().describe("Deprecated alias for login_url."),
|
|
5039
|
+
note: z2.string().optional().describe("Free-text note describing this login. Surfaced by browser_profile_list."),
|
|
4769
5040
|
label: z2.string().optional().describe("Optional human label for this sign-in setup session."),
|
|
4770
|
-
timeout_seconds: z2.number().int().min(60).max(259200).optional().describe("
|
|
5041
|
+
timeout_seconds: z2.number().int().min(60).max(259200).optional().describe("Sign-in session lifetime before auto-termination. Defaults to 600.")
|
|
4771
5042
|
};
|
|
4772
5043
|
var BrowserProfileListInputSchema = {
|
|
4773
5044
|
profile: z2.string().optional().describe("Profile whose saved logins to list. Omit to derive from email."),
|
|
@@ -4776,7 +5047,7 @@ var BrowserProfileListInputSchema = {
|
|
|
4776
5047
|
connection_id: z2.string().optional().describe("A specific login connection id returned by browser_profile_connect, to poll just that one.")
|
|
4777
5048
|
};
|
|
4778
5049
|
var BrowserSessionInputSchema = {
|
|
4779
|
-
session_id: z2.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
5050
|
+
session_id: z2.string().describe("The session id returned by browser_open or browser_list_sessions.")
|
|
4780
5051
|
};
|
|
4781
5052
|
var BrowserLocateTargetSchema = z2.object({
|
|
4782
5053
|
name: z2.string().optional().describe("Optional label for this target, echoed in the result."),
|
|
@@ -4788,94 +5059,94 @@ var BrowserLocateTargetSchema = z2.object({
|
|
|
4788
5059
|
message: "target requires selector or text"
|
|
4789
5060
|
});
|
|
4790
5061
|
var BrowserLocateInputSchema = {
|
|
4791
|
-
session_id: z2.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
5062
|
+
session_id: z2.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
4792
5063
|
targets: z2.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.")
|
|
4793
5064
|
};
|
|
4794
5065
|
var BrowserGotoInputSchema = {
|
|
4795
|
-
session_id: z2.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
5066
|
+
session_id: z2.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
4796
5067
|
url: z2.string().url().describe("URL to navigate the browser to.")
|
|
4797
5068
|
};
|
|
4798
5069
|
var BrowserClickInputSchema = {
|
|
4799
|
-
session_id: z2.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
5070
|
+
session_id: z2.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
4800
5071
|
x: z2.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."),
|
|
4801
5072
|
y: z2.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."),
|
|
4802
5073
|
button: z2.enum(["left", "right", "middle"]).default("left").describe("Mouse button."),
|
|
4803
5074
|
num_clicks: z2.number().int().min(1).max(3).optional().describe("Number of clicks, e.g. 2 for double-click.")
|
|
4804
5075
|
};
|
|
4805
5076
|
var BrowserTypeInputSchema = {
|
|
4806
|
-
session_id: z2.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
5077
|
+
session_id: z2.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
4807
5078
|
text: z2.string().describe("Text to type at the current focus. Click a field first to focus it."),
|
|
4808
5079
|
delay: z2.number().int().min(0).max(500).optional().describe("Optional per-keystroke delay in ms for human-like typing.")
|
|
4809
5080
|
};
|
|
4810
5081
|
var BrowserScrollInputSchema = {
|
|
4811
|
-
session_id: z2.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
5082
|
+
session_id: z2.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
4812
5083
|
delta_y: z2.number().default(5).describe("Vertical scroll in wheel units. Positive scrolls down, negative up."),
|
|
4813
5084
|
delta_x: z2.number().default(0).describe("Horizontal scroll in wheel units."),
|
|
4814
5085
|
x: z2.number().optional().describe("X position to scroll at. Defaults to screen center."),
|
|
4815
5086
|
y: z2.number().optional().describe("Y position to scroll at. Defaults to screen center.")
|
|
4816
5087
|
};
|
|
4817
5088
|
var BrowserPressInputSchema = {
|
|
4818
|
-
session_id: z2.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
5089
|
+
session_id: z2.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
4819
5090
|
keys: z2.array(z2.string()).min(1).describe('Keys or combinations to press, e.g. ["Return"], ["Ctrl+a"], ["Ctrl+Shift+Tab"].')
|
|
4820
5091
|
};
|
|
4821
5092
|
var BrowserReplayStopInputSchema = {
|
|
4822
|
-
session_id: z2.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
4823
|
-
replay_id: z2.string().describe("The replay id returned by browser_replay_start or browser_list_replays.
|
|
5093
|
+
session_id: z2.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
5094
|
+
replay_id: z2.string().describe("The replay id returned by browser_replay_start or browser_list_replays.")
|
|
4824
5095
|
};
|
|
4825
5096
|
var BrowserReplayDownloadInputSchema = {
|
|
4826
|
-
session_id: z2.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
4827
|
-
replay_id: z2.string().describe("The replay id returned by browser_replay_start or browser_list_replays.
|
|
5097
|
+
session_id: z2.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
5098
|
+
replay_id: z2.string().describe("The replay id returned by browser_replay_start or browser_list_replays."),
|
|
4828
5099
|
filename: z2.string().optional().describe("Optional local MP4 filename. Defaults to a timestamped replay filename.")
|
|
4829
5100
|
};
|
|
4830
5101
|
var BrowserReplayAnnotationSchema = z2.object({
|
|
4831
|
-
type: z2.enum(["box", "circle", "underline", "arrow", "label"]).default("box").describe("Annotation style
|
|
4832
|
-
start_seconds: z2.number().min(0).default(0).describe("When the annotation should appear
|
|
4833
|
-
end_seconds: z2.number().min(0).optional().describe("When
|
|
4834
|
-
left: z2.number().optional().describe("Target left edge in
|
|
4835
|
-
top: z2.number().optional().describe("Target top edge in
|
|
4836
|
-
width: z2.number().positive().optional().describe("Target width in
|
|
4837
|
-
height: z2.number().positive().optional().describe("Target height in
|
|
4838
|
-
x: z2.number().optional().describe("Point target x coordinate
|
|
4839
|
-
y: z2.number().optional().describe("Point target y coordinate
|
|
4840
|
-
from_x: z2.number().optional().describe("Arrow start x coordinate
|
|
4841
|
-
from_y: z2.number().optional().describe("Arrow start y coordinate
|
|
4842
|
-
to_x: z2.number().optional().describe("Arrow
|
|
4843
|
-
to_y: z2.number().optional().describe("Arrow
|
|
4844
|
-
label: z2.string().max(120).optional().describe("Optional text callout
|
|
4845
|
-
color: z2.string().regex(/^#?[0-9a-fA-F]{6}$/).optional().describe("Annotation color as hex,
|
|
5102
|
+
type: z2.enum(["box", "circle", "underline", "arrow", "label"]).default("box").describe("Annotation style."),
|
|
5103
|
+
start_seconds: z2.number().min(0).default(0).describe("When the annotation should appear."),
|
|
5104
|
+
end_seconds: z2.number().min(0).optional().describe("When it disappears. Defaults to 2s after start_seconds."),
|
|
5105
|
+
left: z2.number().optional().describe("Target left edge in screenshot pixels (element.left)."),
|
|
5106
|
+
top: z2.number().optional().describe("Target top edge in screenshot pixels (element.top)."),
|
|
5107
|
+
width: z2.number().positive().optional().describe("Target width in screenshot pixels (element.width)."),
|
|
5108
|
+
height: z2.number().positive().optional().describe("Target height in screenshot pixels (element.height)."),
|
|
5109
|
+
x: z2.number().optional().describe("Point target x coordinate when no box is available."),
|
|
5110
|
+
y: z2.number().optional().describe("Point target y coordinate when no box is available."),
|
|
5111
|
+
from_x: z2.number().optional().describe("Arrow start x coordinate. Defaults near the target."),
|
|
5112
|
+
from_y: z2.number().optional().describe("Arrow start y coordinate. Defaults near the target."),
|
|
5113
|
+
to_x: z2.number().optional().describe("Arrow end x coordinate. Defaults to the target box center."),
|
|
5114
|
+
to_y: z2.number().optional().describe("Arrow end y coordinate. Defaults to the target box center."),
|
|
5115
|
+
label: z2.string().max(120).optional().describe("Optional text callout."),
|
|
5116
|
+
color: z2.string().regex(/^#?[0-9a-fA-F]{6}$/).optional().describe("Annotation color as hex, e.g. #ff3b30."),
|
|
4846
5117
|
thickness: z2.number().min(1).max(24).optional().describe("Stroke thickness in pixels. Defaults to 5.")
|
|
4847
5118
|
});
|
|
4848
5119
|
var BrowserReplayMarkInputSchema = {
|
|
4849
|
-
session_id: z2.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
5120
|
+
session_id: z2.string().describe("The session id returned by browser_open or browser_list_sessions. A replay must already be recording."),
|
|
4850
5121
|
target: BrowserLocateTargetSchema.describe("The exact DOM element or text range to mark in the current viewport."),
|
|
4851
|
-
type: z2.enum(["box", "circle", "underline", "arrow"]).default("box").describe("Annotation style to generate.
|
|
5122
|
+
type: z2.enum(["box", "circle", "underline", "arrow"]).default("box").describe("Annotation style to generate."),
|
|
4852
5123
|
label: z2.string().max(120).optional().describe("Optional callout text to render near the target."),
|
|
4853
|
-
color: z2.string().regex(/^#?[0-9a-fA-F]{6}$/).optional().describe("Annotation color as hex,
|
|
5124
|
+
color: z2.string().regex(/^#?[0-9a-fA-F]{6}$/).optional().describe("Annotation color as hex, e.g. #ff3b30."),
|
|
4854
5125
|
thickness: z2.number().min(1).max(24).optional().describe("Stroke thickness in pixels. Defaults to 5."),
|
|
4855
5126
|
padding: z2.number().min(0).max(80).default(8).describe("Pixels to expand the DOM bounds so the highlight does not touch the text edge."),
|
|
4856
|
-
start_offset_seconds: z2.number().min(-5).max(10).default(-0.25).describe("Offset from the current replay time
|
|
4857
|
-
duration_seconds: z2.number().min(0.5).max(30).default(4).describe("How long the
|
|
5127
|
+
start_offset_seconds: z2.number().min(-5).max(10).default(-0.25).describe("Offset from the current replay time; negative appears just before the mark action."),
|
|
5128
|
+
duration_seconds: z2.number().min(0.5).max(30).default(4).describe("How long the annotation should remain visible.")
|
|
4858
5129
|
};
|
|
4859
5130
|
var BrowserReplayAnnotateInputSchema = {
|
|
4860
|
-
session_id: z2.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
4861
|
-
replay_id: z2.string().describe("The replay id returned by browser_replay_start or browser_list_replays.
|
|
4862
|
-
annotations: z2.array(BrowserReplayAnnotationSchema).min(1).max(50).describe("Timed overlay annotations
|
|
4863
|
-
filename: z2.string().optional().describe("Optional output MP4 filename. Defaults to a timestamped
|
|
5131
|
+
session_id: z2.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
5132
|
+
replay_id: z2.string().describe("The replay id returned by browser_replay_start or browser_list_replays."),
|
|
5133
|
+
annotations: z2.array(BrowserReplayAnnotationSchema).min(1).max(50).describe("Timed overlay annotations. Prefer ones from browser_replay_mark; otherwise use exact DOM bounds from browser_locate."),
|
|
5134
|
+
filename: z2.string().optional().describe("Optional output MP4 filename. Defaults to a timestamped filename."),
|
|
4864
5135
|
source_width: z2.number().positive().optional().describe("Width of the screenshot coordinate space used for annotations. Defaults to the replay video width."),
|
|
4865
|
-
source_height: z2.number().positive().optional().describe("Height of the coordinate space
|
|
4866
|
-
source_left_offset: z2.number().min(0).optional().describe("
|
|
4867
|
-
source_top_offset: z2.number().min(0).optional().describe("
|
|
5136
|
+
source_height: z2.number().positive().optional().describe("Height of the annotation coordinate space; if smaller than the replay video height, the browser chrome offset is inferred."),
|
|
5137
|
+
source_left_offset: z2.number().min(0).optional().describe("Explicit X offset from annotation to replay video coordinates. Usually omitted."),
|
|
5138
|
+
source_top_offset: z2.number().min(0).optional().describe("Explicit Y offset from annotation to replay video coordinates. Usually omitted.")
|
|
4868
5139
|
};
|
|
4869
5140
|
var BrowserListInputSchema = {
|
|
4870
5141
|
include_closed: z2.boolean().default(false).describe("Include closed sessions in the list.")
|
|
4871
5142
|
};
|
|
4872
5143
|
var BrowserCaptureFanoutInputSchema = {
|
|
4873
|
-
session_id: z2.string().describe("
|
|
4874
|
-
prompt: z2.string().optional().describe("Optional prompt to type
|
|
4875
|
-
wait_ms: z2.number().int().min(0).max(18e4).optional().describe("How long to wait for the answer stream to finish
|
|
4876
|
-
first_party_domain: z2.string().optional().describe("The brand/site being researched,
|
|
4877
|
-
reset: z2.boolean().default(false).describe("Clear any previously buffered stream for this page before capturing.
|
|
4878
|
-
export: z2.boolean().default(false).describe("
|
|
5144
|
+
session_id: z2.string().describe("Session id from browser_open. Must be on chatgpt.com or claude.ai, logged in via a saved hosted profile."),
|
|
5145
|
+
prompt: z2.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."),
|
|
5146
|
+
wait_ms: z2.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."),
|
|
5147
|
+
first_party_domain: z2.string().optional().describe("The brand/site being researched, e.g. example.com \u2014 sources on this domain are tagged First-party/vendor."),
|
|
5148
|
+
reset: z2.boolean().default(false).describe("Clear any previously buffered stream for this page before capturing."),
|
|
5149
|
+
export: z2.boolean().default(false).describe("Write JSON/CSV/TSV/HTML exports to MCP_SCRAPER_OUTPUT_DIR/fanout, returning relative paths.")
|
|
4879
5150
|
};
|
|
4880
5151
|
var FanoutSourceOutput = z2.object({
|
|
4881
5152
|
url: z2.string(),
|
|
@@ -4899,10 +5170,10 @@ var BrowserCaptureFanoutOutputSchema = {
|
|
|
4899
5170
|
title: z2.string(),
|
|
4900
5171
|
rounds: z2.number().int().min(0)
|
|
4901
5172
|
}),
|
|
4902
|
-
queries: z2.array(z2.string()).describe("Every web-search sub-query
|
|
4903
|
-
browsed_urls: z2.array(FanoutSourceOutput).describe("Every researched URL
|
|
4904
|
-
cited_urls: z2.array(FanoutSourceOutput).describe("
|
|
4905
|
-
browsed_only: z2.array(FanoutSourceOutput).describe("Researched URLs
|
|
5173
|
+
queries: z2.array(z2.string()).describe("Every web-search sub-query issued, in capture order."),
|
|
5174
|
+
browsed_urls: z2.array(FanoutSourceOutput).describe("Every researched URL, cited first."),
|
|
5175
|
+
cited_urls: z2.array(FanoutSourceOutput).describe("Researched URLs cited in the final answer."),
|
|
5176
|
+
browsed_only: z2.array(FanoutSourceOutput).describe("Researched URLs pulled but not cited."),
|
|
4906
5177
|
snippets: z2.array(z2.object({ url: z2.string(), domain: z2.string(), title: z2.string(), text: z2.string() })),
|
|
4907
5178
|
counts: z2.object({
|
|
4908
5179
|
subQueries: z2.number().int().min(0),
|
|
@@ -5086,7 +5357,7 @@ var BrowserListSessionsOutputSchema = {
|
|
|
5086
5357
|
import { execFile } from "child_process";
|
|
5087
5358
|
import { mkdtemp, rm, stat, writeFile } from "fs/promises";
|
|
5088
5359
|
import { tmpdir } from "os";
|
|
5089
|
-
import { join as
|
|
5360
|
+
import { join as join5 } from "path";
|
|
5090
5361
|
import { promisify } from "util";
|
|
5091
5362
|
var execFileAsync = promisify(execFile);
|
|
5092
5363
|
function finiteNumber(value) {
|
|
@@ -5309,8 +5580,8 @@ function ffmpegFilterPath(path) {
|
|
|
5309
5580
|
async function annotateReplayVideo(inputFilePath, outputFilePath, options) {
|
|
5310
5581
|
if (!options.annotations.length) throw new Error("annotations must include at least one item");
|
|
5311
5582
|
const size = await videoSize(inputFilePath);
|
|
5312
|
-
const tmp = await mkdtemp(
|
|
5313
|
-
const assPath =
|
|
5583
|
+
const tmp = await mkdtemp(join5(tmpdir(), "mcp-scraper-ass-"));
|
|
5584
|
+
const assPath = join5(tmp, "annotations.ass");
|
|
5314
5585
|
try {
|
|
5315
5586
|
await writeFile(assPath, buildAssSubtitle(options, size), "utf8");
|
|
5316
5587
|
await execFileAsync("ffmpeg", [
|
|
@@ -5380,7 +5651,7 @@ function actionResult(tool, sessionId, ok, data, nextRecommendedTool = "browser_
|
|
|
5380
5651
|
});
|
|
5381
5652
|
}
|
|
5382
5653
|
function outputBaseDir3() {
|
|
5383
|
-
return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() ||
|
|
5654
|
+
return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || join6(homedir4(), "Downloads", "mcp-scraper");
|
|
5384
5655
|
}
|
|
5385
5656
|
function safeFilePart(value) {
|
|
5386
5657
|
return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 120) || "replay";
|
|
@@ -5389,7 +5660,7 @@ function replayFilePath(sessionId, replayId, filename) {
|
|
|
5389
5660
|
const requested = filename?.trim();
|
|
5390
5661
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
5391
5662
|
const name = requested ? safeFilePart(requested).replace(/\.mp4$/i, "") : `${stamp}-${safeFilePart(sessionId)}-${safeFilePart(replayId)}`;
|
|
5392
|
-
return
|
|
5663
|
+
return join6(outputBaseDir3(), "browser-replays", `${name}.mp4`);
|
|
5393
5664
|
}
|
|
5394
5665
|
function slugPart(value) {
|
|
5395
5666
|
const trimmed = value?.trim().toLowerCase();
|
|
@@ -5463,8 +5734,8 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
5463
5734
|
}
|
|
5464
5735
|
const bytes = Buffer.from(await res.arrayBuffer());
|
|
5465
5736
|
const filePath = replayFilePath(sessionId, replayId, filename);
|
|
5466
|
-
|
|
5467
|
-
|
|
5737
|
+
mkdirSync4(join6(outputBaseDir3(), "browser-replays"), { recursive: true });
|
|
5738
|
+
writeFileSync4(filePath, bytes);
|
|
5468
5739
|
return {
|
|
5469
5740
|
ok: true,
|
|
5470
5741
|
data: {
|
|
@@ -5505,9 +5776,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
5505
5776
|
"browser_profile_connect",
|
|
5506
5777
|
{
|
|
5507
5778
|
title: "Save a Site Login to a Profile",
|
|
5508
|
-
description:
|
|
5779
|
+
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.",
|
|
5509
5780
|
inputSchema: BrowserProfileConnectInputSchema,
|
|
5510
|
-
outputSchema: BrowserProfileConnectOutputSchema,
|
|
5781
|
+
outputSchema: recordOutputSchema("browser_profile_connect", BrowserProfileConnectOutputSchema),
|
|
5511
5782
|
annotations: annotations("Save a Site Login to a Profile")
|
|
5512
5783
|
},
|
|
5513
5784
|
async (input) => {
|
|
@@ -5563,9 +5834,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
5563
5834
|
"browser_profile_list",
|
|
5564
5835
|
{
|
|
5565
5836
|
title: "List Saved Logins in a Profile",
|
|
5566
|
-
description:
|
|
5837
|
+
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.",
|
|
5567
5838
|
inputSchema: BrowserProfileListInputSchema,
|
|
5568
|
-
outputSchema: BrowserProfileListOutputSchema,
|
|
5839
|
+
outputSchema: recordOutputSchema("browser_profile_list", BrowserProfileListOutputSchema),
|
|
5569
5840
|
annotations: annotations("List Saved Logins in a Profile", true)
|
|
5570
5841
|
},
|
|
5571
5842
|
async (input) => {
|
|
@@ -5597,9 +5868,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
5597
5868
|
"browser_open",
|
|
5598
5869
|
{
|
|
5599
5870
|
title: "Open Browser Session",
|
|
5600
|
-
description: "Open a direct no-proxy hosted browser session you can drive. Pass a saved profile name to load a session
|
|
5871
|
+
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.",
|
|
5601
5872
|
inputSchema: BrowserOpenInputSchema,
|
|
5602
|
-
outputSchema: BrowserOpenOutputSchema,
|
|
5873
|
+
outputSchema: recordOutputSchema("browser_open", BrowserOpenOutputSchema),
|
|
5603
5874
|
annotations: annotations("Open Browser Session")
|
|
5604
5875
|
},
|
|
5605
5876
|
async (input) => {
|
|
@@ -5631,9 +5902,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
5631
5902
|
"browser_screenshot",
|
|
5632
5903
|
{
|
|
5633
5904
|
title: "See Page (Screenshot + Elements)",
|
|
5634
|
-
description: "Capture what the browser currently shows
|
|
5905
|
+
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.",
|
|
5635
5906
|
inputSchema: BrowserSessionInputSchema,
|
|
5636
|
-
outputSchema: BrowserScreenshotOutputSchema,
|
|
5907
|
+
outputSchema: recordOutputSchema("browser_screenshot", BrowserScreenshotOutputSchema),
|
|
5637
5908
|
annotations: annotations("See Page", true)
|
|
5638
5909
|
},
|
|
5639
5910
|
async (input) => {
|
|
@@ -5663,9 +5934,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
5663
5934
|
"browser_read",
|
|
5664
5935
|
{
|
|
5665
5936
|
title: "Read Page Text + Elements",
|
|
5666
|
-
description: "Return the page url, title, visible text, and
|
|
5937
|
+
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.",
|
|
5667
5938
|
inputSchema: BrowserSessionInputSchema,
|
|
5668
|
-
outputSchema: BrowserReadOutputSchema,
|
|
5939
|
+
outputSchema: recordOutputSchema("browser_read", BrowserReadOutputSchema),
|
|
5669
5940
|
annotations: annotations("Read Page", true)
|
|
5670
5941
|
},
|
|
5671
5942
|
async (input) => {
|
|
@@ -5687,9 +5958,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
5687
5958
|
"browser_locate",
|
|
5688
5959
|
{
|
|
5689
5960
|
title: "Locate DOM Targets",
|
|
5690
|
-
description: "Locate exact visible DOM elements or text ranges
|
|
5961
|
+
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.",
|
|
5691
5962
|
inputSchema: BrowserLocateInputSchema,
|
|
5692
|
-
outputSchema: BrowserLocateOutputSchema,
|
|
5963
|
+
outputSchema: recordOutputSchema("browser_locate", BrowserLocateOutputSchema),
|
|
5693
5964
|
annotations: annotations("Locate DOM Targets", true)
|
|
5694
5965
|
},
|
|
5695
5966
|
async (input) => {
|
|
@@ -5712,9 +5983,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
5712
5983
|
"browser_goto",
|
|
5713
5984
|
{
|
|
5714
5985
|
title: "Navigate To URL",
|
|
5715
|
-
description: "Navigate an existing browser session to a URL
|
|
5986
|
+
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.",
|
|
5716
5987
|
inputSchema: BrowserGotoInputSchema,
|
|
5717
|
-
outputSchema: BrowserActionOutputSchema,
|
|
5988
|
+
outputSchema: recordOutputSchema("browser_goto", BrowserActionOutputSchema),
|
|
5718
5989
|
annotations: annotations("Navigate To URL")
|
|
5719
5990
|
},
|
|
5720
5991
|
async (input) => {
|
|
@@ -5726,9 +5997,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
5726
5997
|
"browser_click",
|
|
5727
5998
|
{
|
|
5728
5999
|
title: "Click",
|
|
5729
|
-
description: "Click a visible page target using screenshot pixel coordinates. Use
|
|
6000
|
+
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.",
|
|
5730
6001
|
inputSchema: BrowserClickInputSchema,
|
|
5731
|
-
outputSchema: BrowserActionOutputSchema,
|
|
6002
|
+
outputSchema: recordOutputSchema("browser_click", BrowserActionOutputSchema),
|
|
5732
6003
|
annotations: annotations("Click")
|
|
5733
6004
|
},
|
|
5734
6005
|
async (input) => {
|
|
@@ -5745,9 +6016,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
5745
6016
|
"browser_type",
|
|
5746
6017
|
{
|
|
5747
6018
|
title: "Type Text",
|
|
5748
|
-
description: 'Type text into the currently focused browser field.
|
|
6019
|
+
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.',
|
|
5749
6020
|
inputSchema: BrowserTypeInputSchema,
|
|
5750
|
-
outputSchema: BrowserActionOutputSchema,
|
|
6021
|
+
outputSchema: recordOutputSchema("browser_type", BrowserActionOutputSchema),
|
|
5751
6022
|
annotations: annotations("Type Text")
|
|
5752
6023
|
},
|
|
5753
6024
|
async (input) => {
|
|
@@ -5759,9 +6030,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
5759
6030
|
"browser_scroll",
|
|
5760
6031
|
{
|
|
5761
6032
|
title: "Scroll",
|
|
5762
|
-
description: "Scroll the page to reveal more content
|
|
6033
|
+
description: "Scroll the page to reveal more content. Positive delta_y scrolls down; negative scrolls up.",
|
|
5763
6034
|
inputSchema: BrowserScrollInputSchema,
|
|
5764
|
-
outputSchema: BrowserActionOutputSchema,
|
|
6035
|
+
outputSchema: recordOutputSchema("browser_scroll", BrowserActionOutputSchema),
|
|
5765
6036
|
annotations: annotations("Scroll")
|
|
5766
6037
|
},
|
|
5767
6038
|
async (input) => {
|
|
@@ -5778,9 +6049,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
5778
6049
|
"browser_press",
|
|
5779
6050
|
{
|
|
5780
6051
|
title: "Press Keys",
|
|
5781
|
-
description:
|
|
6052
|
+
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.",
|
|
5782
6053
|
inputSchema: BrowserPressInputSchema,
|
|
5783
|
-
outputSchema: BrowserActionOutputSchema,
|
|
6054
|
+
outputSchema: recordOutputSchema("browser_press", BrowserActionOutputSchema),
|
|
5784
6055
|
annotations: annotations("Press Keys")
|
|
5785
6056
|
},
|
|
5786
6057
|
async (input) => {
|
|
@@ -5792,9 +6063,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
5792
6063
|
"browser_replay_start",
|
|
5793
6064
|
{
|
|
5794
6065
|
title: "Start Recording",
|
|
5795
|
-
description: "Start recording an MP4 replay of the session. Returns replay_id
|
|
6066
|
+
description: "Start recording an MP4 replay of the session. Returns replay_id and a download_url. Stop with browser_replay_stop.",
|
|
5796
6067
|
inputSchema: BrowserSessionInputSchema,
|
|
5797
|
-
outputSchema: BrowserReplayStartOutputSchema,
|
|
6068
|
+
outputSchema: recordOutputSchema("browser_replay_start", BrowserReplayStartOutputSchema),
|
|
5798
6069
|
annotations: annotations("Start Recording")
|
|
5799
6070
|
},
|
|
5800
6071
|
async (input) => {
|
|
@@ -5815,9 +6086,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
5815
6086
|
"browser_replay_stop",
|
|
5816
6087
|
{
|
|
5817
6088
|
title: "Stop Recording",
|
|
5818
|
-
description: "Stop a replay recording and expose
|
|
6089
|
+
description: "Stop a replay recording and expose its final view_url/download_url. Use browser_replay_download to save the MP4.",
|
|
5819
6090
|
inputSchema: BrowserReplayStopInputSchema,
|
|
5820
|
-
outputSchema: BrowserReplayStopOutputSchema,
|
|
6091
|
+
outputSchema: recordOutputSchema("browser_replay_stop", BrowserReplayStopOutputSchema),
|
|
5821
6092
|
annotations: annotations("Stop Recording")
|
|
5822
6093
|
},
|
|
5823
6094
|
async (input) => {
|
|
@@ -5838,9 +6109,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
5838
6109
|
"browser_list_replays",
|
|
5839
6110
|
{
|
|
5840
6111
|
title: "List Replay Videos",
|
|
5841
|
-
description: "List replay recordings for a browser session, including
|
|
6112
|
+
description: "List replay recordings for a browser session, including view_url and download_url when available.",
|
|
5842
6113
|
inputSchema: BrowserSessionInputSchema,
|
|
5843
|
-
outputSchema: BrowserListReplaysOutputSchema,
|
|
6114
|
+
outputSchema: recordOutputSchema("browser_list_replays", BrowserListReplaysOutputSchema),
|
|
5844
6115
|
annotations: annotations("List Replay Videos", true)
|
|
5845
6116
|
},
|
|
5846
6117
|
async (input) => {
|
|
@@ -5860,9 +6131,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
5860
6131
|
"browser_replay_download",
|
|
5861
6132
|
{
|
|
5862
6133
|
title: "Download Replay MP4",
|
|
5863
|
-
description: "Download a replay recording
|
|
6134
|
+
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.",
|
|
5864
6135
|
inputSchema: BrowserReplayDownloadInputSchema,
|
|
5865
|
-
outputSchema: BrowserReplayDownloadOutputSchema,
|
|
6136
|
+
outputSchema: recordOutputSchema("browser_replay_download", BrowserReplayDownloadOutputSchema),
|
|
5866
6137
|
annotations: annotations("Download Replay MP4")
|
|
5867
6138
|
},
|
|
5868
6139
|
async (input) => {
|
|
@@ -5884,9 +6155,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
5884
6155
|
"browser_replay_mark",
|
|
5885
6156
|
{
|
|
5886
6157
|
title: "Mark Replay Annotation",
|
|
5887
|
-
description: "While a replay is actively recording, locate one exact DOM target and return a ready-to-use annotation
|
|
6158
|
+
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.",
|
|
5888
6159
|
inputSchema: BrowserReplayMarkInputSchema,
|
|
5889
|
-
outputSchema: BrowserReplayMarkOutputSchema,
|
|
6160
|
+
outputSchema: recordOutputSchema("browser_replay_mark", BrowserReplayMarkOutputSchema),
|
|
5890
6161
|
annotations: annotations("Mark Replay Annotation", true)
|
|
5891
6162
|
},
|
|
5892
6163
|
async (input) => {
|
|
@@ -5934,9 +6205,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
5934
6205
|
"browser_replay_annotate",
|
|
5935
6206
|
{
|
|
5936
6207
|
title: "Annotate Replay MP4",
|
|
5937
|
-
description: "Download a browser replay MP4, render visual annotations over it, and save a new annotated MP4
|
|
6208
|
+
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.",
|
|
5938
6209
|
inputSchema: BrowserReplayAnnotateInputSchema,
|
|
5939
|
-
outputSchema: BrowserReplayAnnotateOutputSchema,
|
|
6210
|
+
outputSchema: recordOutputSchema("browser_replay_annotate", BrowserReplayAnnotateOutputSchema),
|
|
5940
6211
|
annotations: annotations("Annotate Replay MP4")
|
|
5941
6212
|
},
|
|
5942
6213
|
async (input) => {
|
|
@@ -5946,7 +6217,7 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
5946
6217
|
try {
|
|
5947
6218
|
const sourcePath = String(downloaded.data.file_path);
|
|
5948
6219
|
const outputPath = annotatedReplayFilePath(input.session_id, input.replay_id, input.filename);
|
|
5949
|
-
|
|
6220
|
+
mkdirSync4(join6(outputBaseDir3(), "browser-replays"), { recursive: true });
|
|
5950
6221
|
const result = await annotateReplayVideo(sourcePath, outputPath, {
|
|
5951
6222
|
annotations: input.annotations,
|
|
5952
6223
|
sourceWidth: input.source_width,
|
|
@@ -5976,9 +6247,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
5976
6247
|
"browser_close",
|
|
5977
6248
|
{
|
|
5978
6249
|
title: "Close Browser Session",
|
|
5979
|
-
description: "Close and release a browser session when the task is done,
|
|
6250
|
+
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.",
|
|
5980
6251
|
inputSchema: BrowserSessionInputSchema,
|
|
5981
|
-
outputSchema: BrowserCloseOutputSchema,
|
|
6252
|
+
outputSchema: recordOutputSchema("browser_close", BrowserCloseOutputSchema),
|
|
5982
6253
|
annotations: { title: "Close Browser Session", readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
5983
6254
|
},
|
|
5984
6255
|
async (input) => {
|
|
@@ -5997,9 +6268,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
5997
6268
|
"browser_list_sessions",
|
|
5998
6269
|
{
|
|
5999
6270
|
title: "List Browser Sessions",
|
|
6000
|
-
description: "List browser sessions and their status, with a watch_url for each. Use
|
|
6271
|
+
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.",
|
|
6001
6272
|
inputSchema: BrowserListInputSchema,
|
|
6002
|
-
outputSchema: BrowserListSessionsOutputSchema,
|
|
6273
|
+
outputSchema: recordOutputSchema("browser_list_sessions", BrowserListSessionsOutputSchema),
|
|
6003
6274
|
annotations: annotations("List Browser Sessions", true)
|
|
6004
6275
|
},
|
|
6005
6276
|
async (input) => {
|
|
@@ -6019,9 +6290,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
6019
6290
|
"query_fanout_workflow",
|
|
6020
6291
|
{
|
|
6021
6292
|
title: "Capture AI Search Fan-Out",
|
|
6022
|
-
description:
|
|
6293
|
+
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.",
|
|
6023
6294
|
inputSchema: BrowserCaptureFanoutInputSchema,
|
|
6024
|
-
outputSchema: BrowserCaptureFanoutOutputSchema,
|
|
6295
|
+
outputSchema: recordOutputSchema("query_fanout_workflow", BrowserCaptureFanoutOutputSchema),
|
|
6025
6296
|
annotations: annotations("Capture AI Search Fan-Out")
|
|
6026
6297
|
},
|
|
6027
6298
|
async (input) => {
|
|
@@ -6084,9 +6355,11 @@ export {
|
|
|
6084
6355
|
renderIssueReport,
|
|
6085
6356
|
auditImages,
|
|
6086
6357
|
renderImageSection,
|
|
6358
|
+
getBlobStore,
|
|
6087
6359
|
configureReportSaving,
|
|
6088
6360
|
outputBaseDir,
|
|
6089
6361
|
SERVER_INSTRUCTIONS,
|
|
6362
|
+
hashOwnerId,
|
|
6090
6363
|
registerSerpIntelligenceCaptureTools,
|
|
6091
6364
|
buildPaaExtractorMcpServer,
|
|
6092
6365
|
registerPaaExtractorMcpTools,
|
|
@@ -6094,4 +6367,4 @@ export {
|
|
|
6094
6367
|
exportFanout,
|
|
6095
6368
|
registerBrowserAgentMcpTools
|
|
6096
6369
|
};
|
|
6097
|
-
//# sourceMappingURL=chunk-
|
|
6370
|
+
//# sourceMappingURL=chunk-SS5YBZVI.js.map
|