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
package/dist/bin/api-server.cjs
CHANGED
|
@@ -4103,6 +4103,12 @@ async function migrate() {
|
|
|
4103
4103
|
)
|
|
4104
4104
|
`);
|
|
4105
4105
|
await db.execute(`CREATE INDEX IF NOT EXISTS oauth_tokens_identity ON oauth_tokens(identity)`);
|
|
4106
|
+
for (const col of ["replaced_by TEXT", "rotated_at TEXT"]) {
|
|
4107
|
+
try {
|
|
4108
|
+
await db.execute(`ALTER TABLE oauth_tokens ADD COLUMN ${col}`);
|
|
4109
|
+
} catch {
|
|
4110
|
+
}
|
|
4111
|
+
}
|
|
4106
4112
|
}
|
|
4107
4113
|
async function registerClient(clientId, redirectUris, name) {
|
|
4108
4114
|
await getDb().execute({
|
|
@@ -4168,15 +4174,20 @@ async function getRefresh(refreshToken) {
|
|
|
4168
4174
|
scope: String(row.scope),
|
|
4169
4175
|
resource: row.resource != null ? String(row.resource) : null,
|
|
4170
4176
|
expires_at: String(row.expires_at),
|
|
4171
|
-
revoked: Number(row.revoked ?? 0)
|
|
4177
|
+
revoked: Number(row.revoked ?? 0),
|
|
4178
|
+
replaced_by: row.replaced_by != null ? String(row.replaced_by) : null,
|
|
4179
|
+
rotated_at: row.rotated_at != null ? String(row.rotated_at) : null
|
|
4172
4180
|
};
|
|
4173
4181
|
}
|
|
4174
4182
|
async function revokeRefresh(refreshToken) {
|
|
4175
4183
|
await getDb().execute({ sql: "UPDATE oauth_tokens SET revoked = 1 WHERE refresh_token = ?", args: [refreshToken] });
|
|
4176
4184
|
}
|
|
4177
4185
|
async function rotateRefresh(oldToken, next) {
|
|
4178
|
-
await revokeRefresh(oldToken);
|
|
4179
4186
|
await putRefresh(next);
|
|
4187
|
+
await getDb().execute({
|
|
4188
|
+
sql: "UPDATE oauth_tokens SET revoked = 1, replaced_by = ?, rotated_at = ? WHERE refresh_token = ?",
|
|
4189
|
+
args: [next.refresh_token, (/* @__PURE__ */ new Date()).toISOString(), oldToken]
|
|
4190
|
+
});
|
|
4180
4191
|
}
|
|
4181
4192
|
async function recordStripeEvent(eventId) {
|
|
4182
4193
|
const res = await getDb().execute({
|
|
@@ -10004,6 +10015,15 @@ var init_blob_store = __esm({
|
|
|
10004
10015
|
(0, import_node_fs2.writeFileSync)(path6, data);
|
|
10005
10016
|
return { key, url: `file://${path6}`, bytes: byteLength(data), contentType };
|
|
10006
10017
|
}
|
|
10018
|
+
async get(key) {
|
|
10019
|
+
const path6 = (0, import_node_path2.join)(this.baseDir, "blobs", key);
|
|
10020
|
+
try {
|
|
10021
|
+
const { readFileSync: readFileSync4 } = await import("fs");
|
|
10022
|
+
return readFileSync4(path6);
|
|
10023
|
+
} catch {
|
|
10024
|
+
return null;
|
|
10025
|
+
}
|
|
10026
|
+
}
|
|
10007
10027
|
};
|
|
10008
10028
|
cached = null;
|
|
10009
10029
|
VercelBlobStore = class {
|
|
@@ -10023,6 +10043,19 @@ var init_blob_store = __esm({
|
|
|
10023
10043
|
});
|
|
10024
10044
|
return { key, url: result.url, bytes: byteLength(data), contentType };
|
|
10025
10045
|
}
|
|
10046
|
+
async get(key) {
|
|
10047
|
+
try {
|
|
10048
|
+
const { list } = await import("@vercel/blob");
|
|
10049
|
+
const res = await list({ prefix: key, token: this.token, limit: 1 });
|
|
10050
|
+
const match = res.blobs.find((b) => b.pathname === key || b.pathname.startsWith(key));
|
|
10051
|
+
if (!match) return null;
|
|
10052
|
+
const resp = await fetch(match.url);
|
|
10053
|
+
if (!resp.ok) return null;
|
|
10054
|
+
return Buffer.from(await resp.arrayBuffer());
|
|
10055
|
+
} catch {
|
|
10056
|
+
return null;
|
|
10057
|
+
}
|
|
10058
|
+
}
|
|
10026
10059
|
};
|
|
10027
10060
|
}
|
|
10028
10061
|
});
|
|
@@ -17828,6 +17861,57 @@ var init_workflow_catalog = __esm({
|
|
|
17828
17861
|
}
|
|
17829
17862
|
});
|
|
17830
17863
|
|
|
17864
|
+
// src/mcp/report-artifact-offload.ts
|
|
17865
|
+
async function offloadReport(toolName, ownerId, report) {
|
|
17866
|
+
const timestamp2 = Date.now();
|
|
17867
|
+
const random = (0, import_node_crypto7.randomBytes)(6).toString("hex");
|
|
17868
|
+
const key = `${REPORT_BLOB_PREFIX}${ownerId}/${toolName}/${timestamp2}-${random}.md`;
|
|
17869
|
+
const stored = await getBlobStore().put(key, report, "text/markdown");
|
|
17870
|
+
return {
|
|
17871
|
+
artifactId: stored.key,
|
|
17872
|
+
bytes: stored.bytes,
|
|
17873
|
+
expiresAt: new Date(timestamp2 + REPORT_BLOB_TTL_MS).toISOString(),
|
|
17874
|
+
preview: report.slice(0, PREVIEW_CHARS)
|
|
17875
|
+
};
|
|
17876
|
+
}
|
|
17877
|
+
function artifactOwnerId(artifactId) {
|
|
17878
|
+
if (!artifactId.startsWith(REPORT_BLOB_PREFIX)) return null;
|
|
17879
|
+
const rest = artifactId.slice(REPORT_BLOB_PREFIX.length);
|
|
17880
|
+
const segment = rest.split("/")[0];
|
|
17881
|
+
return segment || null;
|
|
17882
|
+
}
|
|
17883
|
+
async function readArtifactWindow(artifactId, offset, maxBytes) {
|
|
17884
|
+
const buf = await getBlobStore().get(artifactId);
|
|
17885
|
+
if (!buf) return null;
|
|
17886
|
+
const totalBytes = buf.length;
|
|
17887
|
+
const start = Math.max(0, offset);
|
|
17888
|
+
const end = Math.min(totalBytes, start + maxBytes);
|
|
17889
|
+
const slice = buf.subarray(start, end);
|
|
17890
|
+
const nextOffset = end < totalBytes ? end : null;
|
|
17891
|
+
return { text: slice.toString("utf8"), totalBytes, nextOffset };
|
|
17892
|
+
}
|
|
17893
|
+
function summaryEnvelope(executiveSummary, offloaded) {
|
|
17894
|
+
return [
|
|
17895
|
+
executiveSummary.trim(),
|
|
17896
|
+
"",
|
|
17897
|
+
"--- Full report stored as artifact ---",
|
|
17898
|
+
`artifactId: ${offloaded.artifactId} \xB7 ${offloaded.bytes} bytes \xB7 expires ${offloaded.expiresAt}`,
|
|
17899
|
+
"Read it with report_artifact_read (supports offset/maxBytes windowing)."
|
|
17900
|
+
].join("\n");
|
|
17901
|
+
}
|
|
17902
|
+
var import_node_crypto7, REPORT_BLOB_TTL_MS, REPORT_BLOB_PREFIX, PREVIEW_CHARS, ARTIFACT_OFFLOAD_ENABLED;
|
|
17903
|
+
var init_report_artifact_offload = __esm({
|
|
17904
|
+
"src/mcp/report-artifact-offload.ts"() {
|
|
17905
|
+
"use strict";
|
|
17906
|
+
import_node_crypto7 = require("crypto");
|
|
17907
|
+
init_blob_store();
|
|
17908
|
+
REPORT_BLOB_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
17909
|
+
REPORT_BLOB_PREFIX = "mcp-reports/";
|
|
17910
|
+
PREVIEW_CHARS = 2e3;
|
|
17911
|
+
ARTIFACT_OFFLOAD_ENABLED = process.env.MCP_SCRAPER_ARTIFACT_OFFLOAD !== "false";
|
|
17912
|
+
}
|
|
17913
|
+
});
|
|
17914
|
+
|
|
17831
17915
|
// src/mcp/mcp-response-formatter.ts
|
|
17832
17916
|
function configureReportSaving(enabled) {
|
|
17833
17917
|
reportSavingEnabled = enabled;
|
|
@@ -17993,6 +18077,20 @@ function oneBlock(content, diskContent) {
|
|
|
17993
18077
|
\u{1F4C4} Saved: \`${filePath}\`` : content;
|
|
17994
18078
|
return { content: [{ type: "text", text }] };
|
|
17995
18079
|
}
|
|
18080
|
+
async function maybeOffload(toolName, ctx, fullText, summaryText, structuredContent) {
|
|
18081
|
+
if (!ctx?.hosted || !ARTIFACT_OFFLOAD_ENABLED) return null;
|
|
18082
|
+
const bytes = Buffer.byteLength(fullText);
|
|
18083
|
+
if (bytes <= INLINE_BUDGET_BYTES) return null;
|
|
18084
|
+
const offloaded = await offloadReport(toolName, ctx.ownerId, fullText);
|
|
18085
|
+
return {
|
|
18086
|
+
content: [{ type: "text", text: summaryEnvelope(summaryText, offloaded) }],
|
|
18087
|
+
structuredContent: { ...structuredContent, artifact: offloaded }
|
|
18088
|
+
};
|
|
18089
|
+
}
|
|
18090
|
+
function capArray(items, cap) {
|
|
18091
|
+
if (items.length <= cap) return { items };
|
|
18092
|
+
return { items: items.slice(0, cap), truncatedCount: items.length - cap };
|
|
18093
|
+
}
|
|
17996
18094
|
function workflowRecipeTable(recipes) {
|
|
17997
18095
|
if (!recipes.length) return "";
|
|
17998
18096
|
return [
|
|
@@ -18344,7 +18442,7 @@ ${headingSection}${kpoSection}${brandingSection}${bodySectionFull}${memSection}$
|
|
|
18344
18442
|
}
|
|
18345
18443
|
return { ...textResult, structuredContent };
|
|
18346
18444
|
}
|
|
18347
|
-
function formatMapSiteUrls(raw, input) {
|
|
18445
|
+
async function formatMapSiteUrls(raw, input, ctx) {
|
|
18348
18446
|
const parsed = parseData(raw);
|
|
18349
18447
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
18350
18448
|
const d = parsed.data;
|
|
@@ -18385,20 +18483,28 @@ ${broken.map((u) => `- ${u.url} (${u.status})`).join("\n")}` : "",
|
|
|
18385
18483
|
- Extract content from all pages: use \`extract_site\`
|
|
18386
18484
|
- Scrape a single page: use \`extract_url\``
|
|
18387
18485
|
].filter(Boolean).join("\n");
|
|
18388
|
-
|
|
18389
|
-
|
|
18390
|
-
|
|
18391
|
-
|
|
18392
|
-
|
|
18393
|
-
|
|
18394
|
-
|
|
18395
|
-
|
|
18396
|
-
|
|
18397
|
-
|
|
18398
|
-
urls: urls.map((u) => ({ url: u.url, status: u.status ?? null })),
|
|
18399
|
-
durationMs: d.durationMs ?? 0
|
|
18400
|
-
}
|
|
18486
|
+
const structuredContent = {
|
|
18487
|
+
startUrl: d.startUrl ?? input.url,
|
|
18488
|
+
totalFound: d.totalFound ?? urls.length,
|
|
18489
|
+
truncated: d.truncated === true,
|
|
18490
|
+
okCount: ok.length,
|
|
18491
|
+
redirectCount: redirects.length,
|
|
18492
|
+
brokenCount: broken.length,
|
|
18493
|
+
inventoryFile: inventoryFile ?? null,
|
|
18494
|
+
urls: urls.map((u) => ({ url: u.url, status: u.status ?? null })),
|
|
18495
|
+
durationMs: d.durationMs ?? 0
|
|
18401
18496
|
};
|
|
18497
|
+
const summary = `# URL Map: ${input.url}
|
|
18498
|
+
**${d.totalFound} URLs** \xB7 ${(d.durationMs / 1e3).toFixed(1)}s
|
|
18499
|
+
|
|
18500
|
+
## Summary
|
|
18501
|
+
- 2xx: ${ok.length}
|
|
18502
|
+
- 3xx: ${redirects.length}
|
|
18503
|
+
- 4xx+: ${broken.length}`;
|
|
18504
|
+
const capped = capArray(structuredContent.urls, STRUCTURED_ARRAY_CAP);
|
|
18505
|
+
const offloaded = await maybeOffload("map_site_urls", ctx, full, summary, { ...structuredContent, urls: capped.items, truncatedCount: capped.truncatedCount });
|
|
18506
|
+
if (offloaded) return offloaded;
|
|
18507
|
+
return { ...oneBlock(full), structuredContent };
|
|
18402
18508
|
}
|
|
18403
18509
|
function buildSeoExport(siteUrl, pages, branding) {
|
|
18404
18510
|
const { edges, metrics } = buildLinkGraph(pages, siteUrl);
|
|
@@ -18418,7 +18524,7 @@ function buildSeoExport(siteUrl, pages, branding) {
|
|
|
18418
18524
|
branding: branding ?? void 0
|
|
18419
18525
|
};
|
|
18420
18526
|
}
|
|
18421
|
-
function formatExtractSite(raw, input) {
|
|
18527
|
+
async function formatExtractSite(raw, input, ctx) {
|
|
18422
18528
|
const parsed = parseData(raw);
|
|
18423
18529
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
18424
18530
|
const d = parsed.data;
|
|
@@ -18453,6 +18559,36 @@ function formatExtractSite(raw, input) {
|
|
|
18453
18559
|
schemaTypes: schemaTypesOf(p)
|
|
18454
18560
|
})));
|
|
18455
18561
|
const preview = pages.slice(0, BULK_PAGE_THRESHOLD).map(pageRow).join("\n");
|
|
18562
|
+
const summaryHeader = [
|
|
18563
|
+
`# Site Extract: ${input.url}`,
|
|
18564
|
+
durationLine,
|
|
18565
|
+
`
|
|
18566
|
+
## Pages (first ${Math.min(BULK_PAGE_THRESHOLD, pages.length)} of ${pages.length})
|
|
18567
|
+
| # | Title | URL | Schema |
|
|
18568
|
+
|---|-------|-----|--------|
|
|
18569
|
+
${preview}`
|
|
18570
|
+
].join("\n");
|
|
18571
|
+
if (!bulk && ctx?.hosted) {
|
|
18572
|
+
const pageDetails2 = pages.map((p, i) => {
|
|
18573
|
+
const body = (p.bodyMarkdown ?? "").trim();
|
|
18574
|
+
return [
|
|
18575
|
+
`
|
|
18576
|
+
## ${i + 1}. ${p.title ?? "Untitled"}`,
|
|
18577
|
+
`- **URL:** ${p.url}`,
|
|
18578
|
+
p.metaDescription ? `- **Description:** ${p.metaDescription}` : "",
|
|
18579
|
+
body ? `
|
|
18580
|
+
${body}` : "_(no content extracted)_"
|
|
18581
|
+
].filter(Boolean).join("\n");
|
|
18582
|
+
}).join("\n");
|
|
18583
|
+
const fullForOffload = `${summaryHeader}
|
|
18584
|
+
|
|
18585
|
+
---
|
|
18586
|
+
# Full Page Content
|
|
18587
|
+
${pageDetails2}${tips}`;
|
|
18588
|
+
const capped = capArray(structuredContent.pages, STRUCTURED_ARRAY_CAP);
|
|
18589
|
+
const offloaded = await maybeOffload("extract_site", ctx, fullForOffload, `${summaryHeader}${tips}`, { ...structuredContent, pages: capped.items, truncatedCount: capped.truncatedCount });
|
|
18590
|
+
if (offloaded) return offloaded;
|
|
18591
|
+
}
|
|
18456
18592
|
const location2 = bulk ? `
|
|
18457
18593
|
## \u{1F4C1} Bulk scrape saved
|
|
18458
18594
|
- **Folder:** \`${bulk.dir}\` \u2190 all scraped page content is here
|
|
@@ -18505,7 +18641,7 @@ ${body}` : "_(no content extracted)_"
|
|
|
18505
18641
|
${pageDetails}${tips}`;
|
|
18506
18642
|
return { ...oneBlock(full, diskReport), structuredContent };
|
|
18507
18643
|
}
|
|
18508
|
-
async function formatAuditSite(raw, input) {
|
|
18644
|
+
async function formatAuditSite(raw, input, ctx) {
|
|
18509
18645
|
const parsed = parseData(raw);
|
|
18510
18646
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
18511
18647
|
const d = parsed.data;
|
|
@@ -18536,6 +18672,24 @@ async function formatAuditSite(raw, input) {
|
|
|
18536
18672
|
images: { unique: img.unique, totalBytes: img.totalBytes, over100kb: img.over100kb, legacyFormat: img.legacyFormat },
|
|
18537
18673
|
links: { internal: lr.internal.totalLinks, external: lr.external.totalLinks, orphans: lr.internal.orphans, brokenInternal: lr.internal.brokenInternal, externalDomains: lr.external.uniqueDomains }
|
|
18538
18674
|
};
|
|
18675
|
+
const summary = [
|
|
18676
|
+
`# Technical SEO Audit: ${input.url}`,
|
|
18677
|
+
durationLine,
|
|
18678
|
+
`
|
|
18679
|
+
## Top issues
|
|
18680
|
+
${topIssues || "_none found_"}`,
|
|
18681
|
+
`
|
|
18682
|
+
## Links
|
|
18683
|
+
${linkLine}`,
|
|
18684
|
+
`
|
|
18685
|
+
## Images
|
|
18686
|
+
${imgLine}`
|
|
18687
|
+
].join("\n");
|
|
18688
|
+
if (!bulk && ctx?.hosted) {
|
|
18689
|
+
const fullForOffload = [summary, "\n---\n# Full Audit Report", seo.reportMd, renderImageSection(imageAudit), renderLinkReport(seo.linkReport)].join("\n");
|
|
18690
|
+
const offloaded = await maybeOffload("audit_site", ctx, fullForOffload, summary, structuredContent);
|
|
18691
|
+
if (offloaded) return offloaded;
|
|
18692
|
+
}
|
|
18539
18693
|
const location2 = bulk ? `
|
|
18540
18694
|
## \u{1F4C1} Technical audit saved
|
|
18541
18695
|
- **Folder:** \`${bulk.dir}\` \u2190 full audit + page content here
|
|
@@ -19306,7 +19460,7 @@ ${rows}`,
|
|
|
19306
19460
|
}
|
|
19307
19461
|
};
|
|
19308
19462
|
}
|
|
19309
|
-
function formatDirectoryWorkflow(raw, input) {
|
|
19463
|
+
async function formatDirectoryWorkflow(raw, input, ctx) {
|
|
19310
19464
|
const parsed = parseData(raw);
|
|
19311
19465
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
19312
19466
|
const d = parsed.data;
|
|
@@ -19358,26 +19512,29 @@ ${businessRows}` : null,
|
|
|
19358
19512
|
durationMs != null ? `
|
|
19359
19513
|
*Completed in ${(durationMs / 1e3).toFixed(1)}s*` : null
|
|
19360
19514
|
].filter(Boolean).join("\n");
|
|
19361
|
-
|
|
19362
|
-
|
|
19363
|
-
|
|
19364
|
-
|
|
19365
|
-
|
|
19366
|
-
|
|
19367
|
-
|
|
19368
|
-
|
|
19369
|
-
|
|
19370
|
-
|
|
19371
|
-
|
|
19372
|
-
|
|
19373
|
-
|
|
19374
|
-
|
|
19375
|
-
|
|
19376
|
-
|
|
19377
|
-
cities,
|
|
19378
|
-
durationMs: durationMs ?? 0
|
|
19379
|
-
}
|
|
19515
|
+
const structuredContent = {
|
|
19516
|
+
query: d.query,
|
|
19517
|
+
state: d.state,
|
|
19518
|
+
minPopulation: d.minPopulation,
|
|
19519
|
+
populationYear: d.populationYear,
|
|
19520
|
+
maxResultsPerCity: d.maxResultsPerCity,
|
|
19521
|
+
concurrency: d.concurrency,
|
|
19522
|
+
censusSourceUrl: d.censusSourceUrl,
|
|
19523
|
+
usZipsSourcePath: d.usZipsSourcePath ?? null,
|
|
19524
|
+
warnings,
|
|
19525
|
+
extractedAt: d.extractedAt,
|
|
19526
|
+
selectedCityCount: d.selectedCityCount,
|
|
19527
|
+
totalResultCount,
|
|
19528
|
+
csvPath,
|
|
19529
|
+
cities,
|
|
19530
|
+
durationMs: durationMs ?? 0
|
|
19380
19531
|
};
|
|
19532
|
+
const summary = `# Directory Workflow: ${input.query}
|
|
19533
|
+
**Markets:** ${cities.length} \xB7 **Maps results:** ${totalResultCount} \xB7 **State:** ${d.state ?? input.state ?? "US"}`;
|
|
19534
|
+
const capped = capArray(cities, STRUCTURED_ARRAY_CAP);
|
|
19535
|
+
const offloaded = await maybeOffload("directory_workflow", ctx, full, summary, { ...structuredContent, cities: capped.items, truncatedCount: capped.truncatedCount });
|
|
19536
|
+
if (offloaded) return offloaded;
|
|
19537
|
+
return { ...oneBlock(full), structuredContent };
|
|
19381
19538
|
}
|
|
19382
19539
|
function formatMapsPlaceIntel(raw, input) {
|
|
19383
19540
|
const parsed = parseData(raw);
|
|
@@ -19897,7 +20054,7 @@ ${rows}` : ""
|
|
|
19897
20054
|
}
|
|
19898
20055
|
};
|
|
19899
20056
|
}
|
|
19900
|
-
var import_node_fs5, import_node_os5, import_node_path7, reportSavingEnabled, BULK_PAGE_THRESHOLD, BULK_URL_THRESHOLD;
|
|
20057
|
+
var import_node_fs5, import_node_os5, import_node_path7, INLINE_BUDGET_BYTES, STRUCTURED_ARRAY_CAP, reportSavingEnabled, BULK_PAGE_THRESHOLD, BULK_URL_THRESHOLD;
|
|
19901
20058
|
var init_mcp_response_formatter = __esm({
|
|
19902
20059
|
"src/mcp/mcp-response-formatter.ts"() {
|
|
19903
20060
|
"use strict";
|
|
@@ -19910,6 +20067,9 @@ var init_mcp_response_formatter = __esm({
|
|
|
19910
20067
|
init_seo_link_report();
|
|
19911
20068
|
init_seo_issues();
|
|
19912
20069
|
init_image_audit();
|
|
20070
|
+
init_report_artifact_offload();
|
|
20071
|
+
INLINE_BUDGET_BYTES = Number(process.env.MCP_SCRAPER_INLINE_BUDGET ?? 5e4);
|
|
20072
|
+
STRUCTURED_ARRAY_CAP = 25;
|
|
19913
20073
|
reportSavingEnabled = true;
|
|
19914
20074
|
BULK_PAGE_THRESHOLD = 25;
|
|
19915
20075
|
BULK_URL_THRESHOLD = 500;
|
|
@@ -22471,7 +22631,7 @@ async function readManifestFromSummary(summary) {
|
|
|
22471
22631
|
function webhookSignature(body, timestamp2) {
|
|
22472
22632
|
const secret2 = process.env.MCP_SCRAPER_WEBHOOK_SECRET?.trim();
|
|
22473
22633
|
if (!secret2) return null;
|
|
22474
|
-
return (0,
|
|
22634
|
+
return (0, import_node_crypto8.createHmac)("sha256", secret2).update(`${timestamp2}.${body}`).digest("hex");
|
|
22475
22635
|
}
|
|
22476
22636
|
async function deliverWorkflowWebhook(input) {
|
|
22477
22637
|
if (!input.webhookUrl) return;
|
|
@@ -22678,11 +22838,11 @@ async function dispatchDueWorkflowSchedules(apiUrl, limit = 3) {
|
|
|
22678
22838
|
}
|
|
22679
22839
|
return { dispatched: results.length, results };
|
|
22680
22840
|
}
|
|
22681
|
-
var
|
|
22841
|
+
var import_node_crypto8, import_promises9, import_hono12, import_zod29, workflowApp, WorkflowInputSchema, WorkflowIdSchema, CadenceSchema, ScheduleStatusSchema, RunBodySchema, ScheduleCreateSchema, SchedulePatchSchema, TERMINAL_RUN_STATUSES;
|
|
22682
22842
|
var init_workflow_routes = __esm({
|
|
22683
22843
|
"src/api/workflow-routes.ts"() {
|
|
22684
22844
|
"use strict";
|
|
22685
|
-
|
|
22845
|
+
import_node_crypto8 = require("crypto");
|
|
22686
22846
|
import_promises9 = require("fs/promises");
|
|
22687
22847
|
import_hono12 = require("hono");
|
|
22688
22848
|
import_zod29 = require("zod");
|
|
@@ -22963,7 +23123,7 @@ var init_workflow_routes = __esm({
|
|
|
22963
23123
|
// src/serp-intelligence/page-snapshot-extractor.ts
|
|
22964
23124
|
function sha256(value) {
|
|
22965
23125
|
if (!value) return null;
|
|
22966
|
-
return (0,
|
|
23126
|
+
return (0, import_node_crypto9.createHash)("sha256").update(value).digest("hex");
|
|
22967
23127
|
}
|
|
22968
23128
|
function countWords(markdown) {
|
|
22969
23129
|
const matches = markdown.trim().match(/\b[\p{L}\p{N}][\p{L}\p{N}'-]*\b/gu);
|
|
@@ -23263,11 +23423,11 @@ async function capturePageSnapshots(targets, options = {}) {
|
|
|
23263
23423
|
}
|
|
23264
23424
|
};
|
|
23265
23425
|
}
|
|
23266
|
-
var
|
|
23426
|
+
var import_node_crypto9, import_p_limit3, DEFAULT_TIMEOUT_MS, DEFAULT_MAX_CONCURRENCY, DEFAULT_MAX_CONTENT_CHARS;
|
|
23267
23427
|
var init_page_snapshot_extractor = __esm({
|
|
23268
23428
|
"src/serp-intelligence/page-snapshot-extractor.ts"() {
|
|
23269
23429
|
"use strict";
|
|
23270
|
-
|
|
23430
|
+
import_node_crypto9 = require("crypto");
|
|
23271
23431
|
import_p_limit3 = __toESM(require("p-limit"), 1);
|
|
23272
23432
|
init_kpo_extractor();
|
|
23273
23433
|
init_url_utils();
|
|
@@ -25843,18 +26003,18 @@ var PACKAGE_VERSION;
|
|
|
25843
26003
|
var init_version = __esm({
|
|
25844
26004
|
"src/version.ts"() {
|
|
25845
26005
|
"use strict";
|
|
25846
|
-
PACKAGE_VERSION = "0.3.
|
|
26006
|
+
PACKAGE_VERSION = "0.3.46";
|
|
25847
26007
|
}
|
|
25848
26008
|
});
|
|
25849
26009
|
|
|
25850
26010
|
// src/mcp/server-instructions.ts
|
|
25851
|
-
|
|
25852
|
-
|
|
25853
|
-
|
|
25854
|
-
"use strict";
|
|
25855
|
-
SERVER_INSTRUCTIONS = `
|
|
26011
|
+
function serverInstructions(savesReportsLocally) {
|
|
26012
|
+
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.";
|
|
26013
|
+
return `
|
|
25856
26014
|
# MCP Scraper
|
|
25857
26015
|
|
|
26016
|
+
${reportLine}
|
|
26017
|
+
|
|
25858
26018
|
Scrapes and analyzes web, search, maps, social, and site data. **Pick a tool by NAME from the map
|
|
25859
26019
|
below, then load its schema before calling.** Where a tool needs a value another tool produces, the
|
|
25860
26020
|
seam is noted so you can chain them.
|
|
@@ -25919,45 +26079,78 @@ Multi-step orchestrations \u2014 prefer these over hand-chaining primitives when
|
|
|
25919
26079
|
- Stand up a rank-tracking blueprint (database + cron + ingestion plan) -> **rank_tracker_workflow**.
|
|
25920
26080
|
- Find which sources an AI answer cites for a query (AEO) -> **query_fanout_workflow** (open a browser
|
|
25921
26081
|
session on chatgpt.com or claude.ai FIRST, then run it against that session).
|
|
26082
|
+
- Not sure which workflow fits a goal -> **workflow_suggest**; to just see what's available -> **workflow_list**.
|
|
26083
|
+
- Hosted/stepwise chain: **workflow_run** starts and returns \`runId\` (+ \`nextStep\` when multi-leg) ->
|
|
26084
|
+
**workflow_step** with that \`runId\` advances one leg at a time until \`done\` -> **workflow_status**
|
|
26085
|
+
re-opens a run to check state or recover artifact ids -> **workflow_artifact_read** pulls one artifact's
|
|
26086
|
+
full content by \`runId\` + \`artifactId\`.
|
|
26087
|
+
- Browser replay recording: **browser_replay_start** begins it -> **browser_replay_mark** (while
|
|
26088
|
+
recording) locates a DOM target and returns a ready-to-use timed annotation -> **browser_replay_stop**
|
|
26089
|
+
ends it -> feed collected annotations to **browser_replay_annotate**, or just
|
|
26090
|
+
**browser_replay_download** the plain MP4. **browser_list_replays** recovers replay ids.
|
|
26091
|
+
- Video breakdown is async: **video_frame_analysis** takes a DIRECT media file URL (resolve
|
|
26092
|
+
YouTube/Facebook/Instagram page URLs to one first, e.g. via \`facebook_page_intel\`'s \`videoUrl\`) and
|
|
26093
|
+
returns a \`runId\` immediately -> poll **video_frame_analysis_status** with that \`runId\` until \`status\`
|
|
26094
|
+
is \`done\`.
|
|
25922
26095
|
|
|
25923
26096
|
## Notes
|
|
25924
26097
|
- Bulk / full-site crawls: call \`extract_site\` with \`rotateProxies:true\` for blocked or rate-limited
|
|
25925
26098
|
sites. It returns a saved folder/artifact plus a summary, not the full content inline.
|
|
25926
26099
|
- Browser sessions and media transcription cost credits and take longer \u2014 prefer the cheapest tool that
|
|
25927
26100
|
answers the question (plain search/extract before browser agents).
|
|
25928
|
-
- Large results are saved to disk or an artifact and returned as a summary plus a path
|
|
25929
|
-
full detail rather than expecting the whole payload inline.
|
|
26101
|
+
- Large results are saved to disk or an artifact and returned as a summary plus a path or artifactId;
|
|
26102
|
+
read it back for full detail rather than expecting the whole payload inline.
|
|
25930
26103
|
`.trim();
|
|
26104
|
+
}
|
|
26105
|
+
var SERVER_INSTRUCTIONS;
|
|
26106
|
+
var init_server_instructions = __esm({
|
|
26107
|
+
"src/mcp/server-instructions.ts"() {
|
|
26108
|
+
"use strict";
|
|
26109
|
+
SERVER_INSTRUCTIONS = serverInstructions(true);
|
|
26110
|
+
}
|
|
26111
|
+
});
|
|
26112
|
+
|
|
26113
|
+
// src/mcp/output-schema-registry.ts
|
|
26114
|
+
function recordOutputSchema(name, schema) {
|
|
26115
|
+
OUTPUT_SCHEMAS[name] = schema;
|
|
26116
|
+
return ADVERTISE_OUTPUT_SCHEMAS ? schema : void 0;
|
|
26117
|
+
}
|
|
26118
|
+
var ADVERTISE_OUTPUT_SCHEMAS, OUTPUT_SCHEMAS;
|
|
26119
|
+
var init_output_schema_registry = __esm({
|
|
26120
|
+
"src/mcp/output-schema-registry.ts"() {
|
|
26121
|
+
"use strict";
|
|
26122
|
+
ADVERTISE_OUTPUT_SCHEMAS = process.env.MCP_SCRAPER_ADVERTISE_OUTPUT_SCHEMAS === "true";
|
|
26123
|
+
OUTPUT_SCHEMAS = {};
|
|
25931
26124
|
}
|
|
25932
26125
|
});
|
|
25933
26126
|
|
|
25934
26127
|
// src/mcp/mcp-tool-schemas.ts
|
|
25935
|
-
var import_zod31, HarvestPaaInputSchema, ExtractUrlInputSchema, MapSiteUrlsInputSchema, ExtractSiteInputSchema, AuditSiteInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, RedditThreadInputSchema, VideoFrameAnalysisInputSchema, VideoFrameAnalysisStatusInputSchema, FacebookAdTranscribeInputSchema, FacebookVideoTranscribeInputSchema, GoogleAdsSearchInputSchema, GoogleAdsPageIntelInputSchema, GoogleAdsTranscribeInputSchema, InstagramProfileContentInputSchema, InstagramMediaDownloadInputSchema, MapsPlaceIntelInputSchema, MapsSearchInputSchema, DirectoryWorkflowInputSchema, RankTrackerModeSchema, RankTrackerBlueprintInputSchema, NullableString, MapsSearchAttemptOutput, MapsSearchOutputSchema, DirectoryMapsBusinessOutput, DirectoryWorkflowOutputSchema, RankTrackerToolPlanOutput, RankTrackerTableOutput, RankTrackerCronJobOutput, RankTrackerBlueprintOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, ExtractUrlOutputSchema, ExtractSiteOutputSchema, AuditSiteOutputSchema, MapsPlaceIntelOutputSchema, CreditsInfoOutputSchema, MapSiteUrlsOutputSchema, YoutubeHarvestOutputSchema, FacebookAdSearchOutputSchema, VideoFrameAnalysisOutputSchema, VideoFrameAnalysisStatusOutputSchema, RedditThreadOutputSchema, FacebookPageIntelOutputSchema, GoogleAdsSearchOutputSchema, GoogleAdsPageIntelOutputSchema, FacebookVideoTranscribeOutputSchema, TranscriptChunkOutput, InstagramBrowserOutput, InstagramPaginationOutput, InstagramProfileContentOutputSchema, InstagramMediaTrackOutput, InstagramDownloadOutput, InstagramMediaDownloadOutputSchema, YoutubeTranscribeOutputSchema, FacebookAdTranscribeOutputSchema, GoogleAdsTranscribeOutputSchema, CaptureSerpSnapshotOutputSchema, CaptureSerpPageSnapshotsOutputSchema, CreditsInfoInputSchema, WorkflowIdSchema2, WorkflowListInputSchema, WorkflowSuggestInputSchema, WorkflowRunInputSchema, WorkflowStepInputSchema, WorkflowStatusInputSchema, WorkflowArtifactReadInputSchema, WorkflowRecipeOutput, WorkflowDefinitionOutput, WorkflowArtifactOutput, WorkflowListOutputSchema, WorkflowSuggestOutputSchema, WorkflowRunOutputSchema, WorkflowStepOutputSchema, WorkflowStatusOutputSchema, WorkflowArtifactReadOutputSchema, SearchSerpInputSchema, CaptureSerpSnapshotInputSchema, ScreenshotInputSchema, CaptureSerpPageSnapshotsInputSchema;
|
|
26128
|
+
var import_zod31, HarvestPaaInputSchema, ExtractUrlInputSchema, MapSiteUrlsInputSchema, ExtractSiteInputSchema, AuditSiteInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, RedditThreadInputSchema, VideoFrameAnalysisInputSchema, VideoFrameAnalysisStatusInputSchema, FacebookAdTranscribeInputSchema, FacebookVideoTranscribeInputSchema, GoogleAdsSearchInputSchema, GoogleAdsPageIntelInputSchema, GoogleAdsTranscribeInputSchema, InstagramProfileContentInputSchema, InstagramMediaDownloadInputSchema, MapsPlaceIntelInputSchema, MapsSearchInputSchema, DirectoryWorkflowInputSchema, ArtifactPointerOutputSchema, RankTrackerModeSchema, RankTrackerBlueprintInputSchema, NullableString, MapsSearchAttemptOutput, MapsSearchOutputSchema, DirectoryMapsBusinessOutput, DirectoryWorkflowOutputSchema, RankTrackerToolPlanOutput, RankTrackerTableOutput, RankTrackerCronJobOutput, RankTrackerBlueprintOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, ExtractUrlOutputSchema, ExtractSiteOutputSchema, AuditSiteOutputSchema, MapsPlaceIntelOutputSchema, CreditsInfoOutputSchema, MapSiteUrlsOutputSchema, YoutubeHarvestOutputSchema, FacebookAdSearchOutputSchema, VideoFrameAnalysisOutputSchema, VideoFrameAnalysisStatusOutputSchema, RedditThreadOutputSchema, FacebookPageIntelOutputSchema, GoogleAdsSearchOutputSchema, GoogleAdsPageIntelOutputSchema, FacebookVideoTranscribeOutputSchema, TranscriptChunkOutput, InstagramBrowserOutput, InstagramPaginationOutput, InstagramProfileContentOutputSchema, InstagramMediaTrackOutput, InstagramDownloadOutput, InstagramMediaDownloadOutputSchema, YoutubeTranscribeOutputSchema, FacebookAdTranscribeOutputSchema, GoogleAdsTranscribeOutputSchema, CaptureSerpSnapshotOutputSchema, CaptureSerpPageSnapshotsOutputSchema, CreditsInfoInputSchema, WorkflowIdSchema2, WorkflowListInputSchema, WorkflowSuggestInputSchema, WorkflowRunInputSchema, WorkflowStepInputSchema, WorkflowStatusInputSchema, WorkflowArtifactReadInputSchema, WorkflowRecipeOutput, WorkflowDefinitionOutput, WorkflowArtifactOutput, WorkflowListOutputSchema, WorkflowSuggestOutputSchema, WorkflowRunOutputSchema, WorkflowStepOutputSchema, WorkflowStatusOutputSchema, WorkflowArtifactReadOutputSchema, SearchSerpInputSchema, CaptureSerpSnapshotInputSchema, ScreenshotInputSchema, CaptureSerpPageSnapshotsInputSchema, ReportArtifactReadInputSchema, ReportArtifactReadOutputSchema;
|
|
25936
26129
|
var init_mcp_tool_schemas = __esm({
|
|
25937
26130
|
"src/mcp/mcp-tool-schemas.ts"() {
|
|
25938
26131
|
"use strict";
|
|
25939
26132
|
import_zod31 = require("zod");
|
|
25940
26133
|
init_schemas3();
|
|
25941
26134
|
HarvestPaaInputSchema = {
|
|
25942
|
-
query: import_zod31.z.string().min(1).describe('The search query.
|
|
25943
|
-
location: import_zod31.z.string().optional().describe('City, region, or country for geo signals
|
|
25944
|
-
maxQuestions: import_zod31.z.number().int().min(1).max(200).default(30).describe("
|
|
25945
|
-
gl: import_zod31.z.string().length(2).default("us").describe("Google country code inferred from location or user language.
|
|
25946
|
-
hl: import_zod31.z.string().default("en").describe("Google interface/content language inferred from the user request.
|
|
25947
|
-
device: import_zod31.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use
|
|
25948
|
-
proxyMode: import_zod31.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe('Leave unset (clean egress
|
|
25949
|
-
proxyZip: import_zod31.z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location"
|
|
25950
|
-
debug: import_zod31.z.boolean().default(false).describe("Include sanitized
|
|
26135
|
+
query: import_zod31.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.'),
|
|
26136
|
+
location: import_zod31.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.'),
|
|
26137
|
+
maxQuestions: import_zod31.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."),
|
|
26138
|
+
gl: import_zod31.z.string().length(2).default("us").describe("Google country code inferred from location or user language."),
|
|
26139
|
+
hl: import_zod31.z.string().default("en").describe("Google interface/content language inferred from the user request."),
|
|
26140
|
+
device: import_zod31.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only for mobile rankings."),
|
|
26141
|
+
proxyMode: import_zod31.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.'),
|
|
26142
|
+
proxyZip: import_zod31.z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location".'),
|
|
26143
|
+
debug: import_zod31.z.boolean().default(false).describe("Include sanitized diagnostics for debugging localization, CAPTCHA, or proxy behavior.")
|
|
25951
26144
|
};
|
|
25952
26145
|
ExtractUrlInputSchema = {
|
|
25953
|
-
url: import_zod31.z.string().url().describe("Public http/https URL to extract.
|
|
25954
|
-
screenshot: import_zod31.z.boolean().default(false).describe("
|
|
25955
|
-
screenshotDevice: import_zod31.z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport for screenshot. desktop = 1440\xD7900
|
|
25956
|
-
extractBranding: import_zod31.z.boolean().default(false).describe("Extract brand colors, fonts, logo, and favicon
|
|
25957
|
-
downloadMedia: import_zod31.z.boolean().default(false).describe("Extract and download
|
|
26146
|
+
url: import_zod31.z.string().url().describe("Public http/https URL to extract."),
|
|
26147
|
+
screenshot: import_zod31.z.boolean().default(false).describe("Capture a full-page screenshot, saved to ~/Downloads/mcp-scraper/screenshots/ and returned inline."),
|
|
26148
|
+
screenshotDevice: import_zod31.z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport for screenshot. desktop = 1440\xD7900, mobile = 390\xD7844."),
|
|
26149
|
+
extractBranding: import_zod31.z.boolean().default(false).describe("Extract brand colors, fonts, logo, and favicon via a rendered browser session."),
|
|
26150
|
+
downloadMedia: import_zod31.z.boolean().default(false).describe("Extract and download page media (images/video/audio) to ~/Downloads/mcp-scraper/media/. Ad/tracking noise is filtered automatically."),
|
|
25958
26151
|
mediaTypes: import_zod31.z.array(import_zod31.z.enum(["image", "video", "audio"])).default(["image", "video", "audio"]).describe("Which media types to download. Default all three."),
|
|
25959
|
-
allowLocal: import_zod31.z.boolean().default(false).describe("Allow localhost and private-network URLs.
|
|
25960
|
-
depositToVault: import_zod31.z.boolean().default(false).describe("
|
|
26152
|
+
allowLocal: import_zod31.z.boolean().default(false).describe("Allow localhost and private-network URLs. Local development only."),
|
|
26153
|
+
depositToVault: import_zod31.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."),
|
|
25961
26154
|
vaultName: import_zod31.z.string().trim().min(1).max(120).optional().describe("Optional vault to deposit into. Defaults to the user's personal vault.")
|
|
25962
26155
|
};
|
|
25963
26156
|
MapSiteUrlsInputSchema = {
|
|
@@ -25965,34 +26158,34 @@ var init_mcp_tool_schemas = __esm({
|
|
|
25965
26158
|
maxUrls: import_zod31.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.")
|
|
25966
26159
|
};
|
|
25967
26160
|
ExtractSiteInputSchema = {
|
|
25968
|
-
url: import_zod31.z.string().url().describe("Public website URL or domain to crawl for page CONTENT
|
|
25969
|
-
maxPages: import_zod31.z.number().int().min(1).max(1e4).optional().describe("Maximum pages to extract.
|
|
25970
|
-
rotateProxies: import_zod31.z.boolean().optional().describe("Route page fetches through rotating residential proxies
|
|
25971
|
-
rotateProxyEvery: import_zod31.z.number().int().min(1).max(100).optional().describe("When rotateProxies is on,
|
|
25972
|
-
formats: import_zod31.z.array(import_zod31.z.enum(["markdown", "links", "json", "images", "branding"])).optional().describe("
|
|
26161
|
+
url: import_zod31.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."),
|
|
26162
|
+
maxPages: import_zod31.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."),
|
|
26163
|
+
rotateProxies: import_zod31.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."),
|
|
26164
|
+
rotateProxyEvery: import_zod31.z.number().int().min(1).max(100).optional().describe("When rotateProxies is on, pages fetched per proxy before rotating. Default 30."),
|
|
26165
|
+
formats: import_zod31.z.array(import_zod31.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.")
|
|
25973
26166
|
};
|
|
25974
26167
|
AuditSiteInputSchema = {
|
|
25975
|
-
url: import_zod31.z.string().url().describe("Public website URL or domain
|
|
25976
|
-
maxPages: import_zod31.z.number().int().min(1).max(1e4).optional().describe("Maximum pages to crawl and audit.
|
|
25977
|
-
rotateProxies: import_zod31.z.boolean().optional().describe("Route page fetches through rotating residential proxies to defeat rate-limiting and bot blocks
|
|
25978
|
-
rotateProxyEvery: import_zod31.z.number().int().min(1).max(100).optional().describe("When rotateProxies is on,
|
|
26168
|
+
url: import_zod31.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."),
|
|
26169
|
+
maxPages: import_zod31.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."),
|
|
26170
|
+
rotateProxies: import_zod31.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."),
|
|
26171
|
+
rotateProxyEvery: import_zod31.z.number().int().min(1).max(100).optional().describe("When rotateProxies is on, pages fetched per proxy before rotating. Default 30.")
|
|
25979
26172
|
};
|
|
25980
26173
|
YoutubeHarvestInputSchema = {
|
|
25981
26174
|
mode: import_zod31.z.enum(["search", "channel"]).describe("Use search for topic/keyword requests. Use channel when the user provides @handle, channel ID, or channel URL."),
|
|
25982
26175
|
query: import_zod31.z.string().optional().describe("Required when mode is search. The YouTube search topic in the user\u2019s words."),
|
|
25983
26176
|
channelHandle: import_zod31.z.string().optional().describe("YouTube channel handle, channel ID, or URL. Examples: @mkbhd, UC..., https://youtube.com/@mkbhd."),
|
|
25984
|
-
maxVideos: import_zod31.z.number().int().min(1).max(500).default(50).describe("Number of videos to return. Default 50, maximum 500.
|
|
26177
|
+
maxVideos: import_zod31.z.number().int().min(1).max(500).default(50).describe("Number of videos to return. Default 50, maximum 500.")
|
|
25985
26178
|
};
|
|
25986
26179
|
YoutubeTranscribeInputSchema = {
|
|
25987
26180
|
videoId: import_zod31.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."),
|
|
25988
|
-
url: import_zod31.z.string().url().optional().describe("Full YouTube URL
|
|
26181
|
+
url: import_zod31.z.string().url().optional().describe("Full YouTube URL. Use when the user pasted a URL instead of an ID. Provide videoId or url.")
|
|
25989
26182
|
};
|
|
25990
26183
|
FacebookPageIntelInputSchema = {
|
|
25991
|
-
pageId: import_zod31.z.string().optional().describe("Facebook advertiser/page ID. Use only a
|
|
25992
|
-
libraryId: import_zod31.z.string().optional().describe("Facebook Ad Library archive ID
|
|
26184
|
+
pageId: import_zod31.z.string().optional().describe("Facebook advertiser/page ID. Use only a value returned by facebook_ad_search or copied from Ad Library."),
|
|
26185
|
+
libraryId: import_zod31.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."),
|
|
25993
26186
|
query: import_zod31.z.string().optional().describe("Advertiser or brand name when pageId/libraryId is not known. One of pageId, libraryId, or query is required."),
|
|
25994
|
-
maxAds: import_zod31.z.number().int().min(1).max(200).default(50).describe("Maximum ads to inspect. Default 50, maximum 200.
|
|
25995
|
-
country: import_zod31.z.string().length(2).default("US").describe("Two-letter Ad Library country code. Default US.
|
|
26187
|
+
maxAds: import_zod31.z.number().int().min(1).max(200).default(50).describe("Maximum ads to inspect. Default 50, maximum 200."),
|
|
26188
|
+
country: import_zod31.z.string().length(2).default("US").describe("Two-letter Ad Library country code. Default US.")
|
|
25996
26189
|
};
|
|
25997
26190
|
FacebookAdSearchInputSchema = {
|
|
25998
26191
|
query: import_zod31.z.string().min(1).describe("Advertiser, brand, competitor, niche, or keyword to search in Facebook Ad Library."),
|
|
@@ -26000,7 +26193,7 @@ var init_mcp_tool_schemas = __esm({
|
|
|
26000
26193
|
maxResults: import_zod31.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.")
|
|
26001
26194
|
};
|
|
26002
26195
|
RedditThreadInputSchema = {
|
|
26003
|
-
url: import_zod31.z.string().min(1).describe("A reddit.com thread/post URL (www, old,
|
|
26196
|
+
url: import_zod31.z.string().min(1).describe("A reddit.com thread/post URL (www, old, new Reddit, or redd.it)."),
|
|
26004
26197
|
maxComments: import_zod31.z.number().int().min(1).max(2e3).optional().describe("Optional cap on comments returned. Omit to return all captured comments.")
|
|
26005
26198
|
};
|
|
26006
26199
|
VideoFrameAnalysisInputSchema = {
|
|
@@ -26014,14 +26207,14 @@ var init_mcp_tool_schemas = __esm({
|
|
|
26014
26207
|
runId: import_zod31.z.string().min(1).describe("The runId returned by video_frame_analysis.")
|
|
26015
26208
|
};
|
|
26016
26209
|
FacebookAdTranscribeInputSchema = {
|
|
26017
|
-
videoUrl: import_zod31.z.string().url().describe("Direct Facebook CDN video URL from
|
|
26210
|
+
videoUrl: import_zod31.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.")
|
|
26018
26211
|
};
|
|
26019
26212
|
FacebookVideoTranscribeInputSchema = {
|
|
26020
|
-
url: import_zod31.z.string().url().describe("Organic Facebook reel
|
|
26213
|
+
url: import_zod31.z.string().url().describe("Organic Facebook reel/video/watch/post/share URL from facebook.com, m.facebook.com, or fb.watch."),
|
|
26021
26214
|
quality: import_zod31.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.")
|
|
26022
26215
|
};
|
|
26023
26216
|
GoogleAdsSearchInputSchema = {
|
|
26024
|
-
query: import_zod31.z.string().min(1).describe("A domain (e.g. getviktor.com) or advertiser/brand name to look up in
|
|
26217
|
+
query: import_zod31.z.string().min(1).describe("A domain (e.g. getviktor.com) or advertiser/brand name to look up in Google Ads Transparency Center."),
|
|
26025
26218
|
region: import_zod31.z.string().length(2).default("US").describe("Two-letter region code for where the ads are shown. Default US. Examples: US, CA, GB, AU."),
|
|
26026
26219
|
maxResults: import_zod31.z.number().int().min(1).max(20).default(10).describe("Maximum advertisers to return. Default 10, maximum 20.")
|
|
26027
26220
|
};
|
|
@@ -26032,78 +26225,84 @@ var init_mcp_tool_schemas = __esm({
|
|
|
26032
26225
|
maxAds: import_zod31.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.")
|
|
26033
26226
|
};
|
|
26034
26227
|
GoogleAdsTranscribeInputSchema = {
|
|
26035
|
-
videoUrl: import_zod31.z.string().url().describe("Direct googlevideo.com playback URL from
|
|
26228
|
+
videoUrl: import_zod31.z.string().url().describe("Direct googlevideo.com playback URL from google_ads_page_intel. For YouTube-hosted ads use youtube_transcribe instead.")
|
|
26036
26229
|
};
|
|
26037
26230
|
InstagramProfileContentInputSchema = {
|
|
26038
26231
|
handle: import_zod31.z.string().min(1).optional().describe("Instagram handle, with or without @. Provide handle or url."),
|
|
26039
|
-
url: import_zod31.z.string().url().optional().describe("Instagram profile URL
|
|
26040
|
-
profile: import_zod31.z.string().min(1).optional().describe("Optional saved hosted browser profile name
|
|
26041
|
-
saveProfileChanges: import_zod31.z.boolean().optional().describe("
|
|
26042
|
-
maxItems: import_zod31.z.number().int().min(1).max(2e3).default(50).describe("Maximum
|
|
26043
|
-
maxScrolls: import_zod31.z.number().int().min(0).max(250).default(10).describe("Maximum pagination scroll attempts. Default 10, maximum 250.
|
|
26044
|
-
scrollDelayMs: import_zod31.z.number().int().min(250).max(5e3).default(1200).describe("Delay after each
|
|
26045
|
-
stableScrollLimit: import_zod31.z.number().int().min(1).max(10).default(4).describe("Stop after this many consecutive scrolls with no new links
|
|
26232
|
+
url: import_zod31.z.string().url().optional().describe("Instagram profile URL. Provide handle or url."),
|
|
26233
|
+
profile: import_zod31.z.string().min(1).optional().describe("Optional saved hosted browser profile name for authenticated Instagram access."),
|
|
26234
|
+
saveProfileChanges: import_zod31.z.boolean().optional().describe("Save browser changes back to the hosted profile. Leave unset unless intentionally updating the saved login."),
|
|
26235
|
+
maxItems: import_zod31.z.number().int().min(1).max(2e3).default(50).describe("Maximum grid URLs to collect. Default 50, maximum 2000."),
|
|
26236
|
+
maxScrolls: import_zod31.z.number().int().min(0).max(250).default(10).describe("Maximum pagination scroll attempts. Default 10, maximum 250."),
|
|
26237
|
+
scrollDelayMs: import_zod31.z.number().int().min(250).max(5e3).default(1200).describe("Delay after each scroll before collecting new links. Default 1200ms."),
|
|
26238
|
+
stableScrollLimit: import_zod31.z.number().int().min(1).max(10).default(4).describe("Stop after this many consecutive scrolls with no new links.")
|
|
26046
26239
|
};
|
|
26047
26240
|
InstagramMediaDownloadInputSchema = {
|
|
26048
|
-
url: import_zod31.z.string().url().describe("Instagram post, reel, or tv URL, e.g. https://www.instagram.com/reel/SHORTCODE/.
|
|
26049
|
-
profile: import_zod31.z.string().min(1).optional().describe("Optional saved hosted browser profile name
|
|
26050
|
-
saveProfileChanges: import_zod31.z.boolean().optional().describe("
|
|
26051
|
-
mediaTypes: import_zod31.z.array(import_zod31.z.enum(["image", "video", "audio"])).default(["image", "video", "audio"]).describe("Which media types to download when downloadMedia is true.
|
|
26052
|
-
downloadMedia: import_zod31.z.boolean().default(true).describe("Download extracted text/media files to the
|
|
26053
|
-
downloadAllTracks: import_zod31.z.boolean().default(false).describe("Download every captured
|
|
26054
|
-
includeTranscript: import_zod31.z.boolean().default(false).describe("Transcribe the selected audio track
|
|
26055
|
-
mux: import_zod31.z.boolean().default(true).describe("
|
|
26241
|
+
url: import_zod31.z.string().url().describe("Instagram post, reel, or tv URL, e.g. https://www.instagram.com/reel/SHORTCODE/."),
|
|
26242
|
+
profile: import_zod31.z.string().min(1).optional().describe("Optional saved hosted browser profile name for authenticated Instagram access."),
|
|
26243
|
+
saveProfileChanges: import_zod31.z.boolean().optional().describe("Save browser changes back to the hosted profile. Leave unset unless intentionally updating the saved login."),
|
|
26244
|
+
mediaTypes: import_zod31.z.array(import_zod31.z.enum(["image", "video", "audio"])).default(["image", "video", "audio"]).describe("Which media types to download when downloadMedia is true."),
|
|
26245
|
+
downloadMedia: import_zod31.z.boolean().default(true).describe("Download extracted text/media files to the output directory. Media URLs are always returned even when false."),
|
|
26246
|
+
downloadAllTracks: import_zod31.z.boolean().default(false).describe("Download every captured MP4 track instead of only the best video/audio pair."),
|
|
26247
|
+
includeTranscript: import_zod31.z.boolean().default(false).describe("Transcribe the selected audio track. Adds transcription cost and time."),
|
|
26248
|
+
mux: import_zod31.z.boolean().default(true).describe("Mux separately downloaded video/audio tracks into one MP4 if ffmpeg is available.")
|
|
26056
26249
|
};
|
|
26057
26250
|
MapsPlaceIntelInputSchema = {
|
|
26058
|
-
businessName: import_zod31.z.string().min(1).describe('Business name only.
|
|
26059
|
-
location: import_zod31.z.string().min(1).describe('City/region/country where the business should be searched, e.g. "Denver, CO".
|
|
26251
|
+
businessName: import_zod31.z.string().min(1).describe('Business name only, e.g. "Elite Roofing" (not "Elite Roofing Denver CO" \u2014 put the city in location).'),
|
|
26252
|
+
location: import_zod31.z.string().min(1).describe('City/region/country where the business should be searched, e.g. "Denver, CO".'),
|
|
26060
26253
|
gl: import_zod31.z.string().length(2).default("us").describe("Google country code inferred from location."),
|
|
26061
26254
|
hl: import_zod31.z.string().length(2).default("en").describe("Language inferred from user request."),
|
|
26062
|
-
includeReviews: import_zod31.z.boolean().default(false).describe("
|
|
26063
|
-
maxReviews: import_zod31.z.number().int().min(1).max(500).default(50).describe("Max review cards
|
|
26255
|
+
includeReviews: import_zod31.z.boolean().default(false).describe("Fetch individual review cards \u2014 for reviews, customer pain, complaints, or praise themes."),
|
|
26256
|
+
maxReviews: import_zod31.z.number().int().min(1).max(500).default(50).describe("Max review cards when includeReviews is true. Default 50, maximum 500.")
|
|
26064
26257
|
};
|
|
26065
26258
|
MapsSearchInputSchema = {
|
|
26066
|
-
query: import_zod31.z.string().min(1).describe('Business category, niche,
|
|
26067
|
-
location: import_zod31.z.string().optional().describe('City, region, country, or service area
|
|
26259
|
+
query: import_zod31.z.string().min(1).describe('Business category, niche, or search term, e.g. "roofers". Do not include location here \u2014 use location instead.'),
|
|
26260
|
+
location: import_zod31.z.string().optional().describe('City, region, country, or service area, e.g. "Denver, CO".'),
|
|
26068
26261
|
gl: import_zod31.z.string().length(2).default("us").describe("Google country code inferred from location."),
|
|
26069
26262
|
hl: import_zod31.z.string().length(2).default("en").describe("Language inferred from user request."),
|
|
26070
|
-
maxResults: import_zod31.z.number().int().min(1).max(50).default(10).describe("Number of
|
|
26071
|
-
proxyMode: import_zod31.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("
|
|
26072
|
-
proxyZip: import_zod31.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential
|
|
26073
|
-
debug: import_zod31.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics
|
|
26263
|
+
maxResults: import_zod31.z.number().int().min(1).max(50).default(10).describe("Number of candidates to return. Default 10, maximum 50."),
|
|
26264
|
+
proxyMode: import_zod31.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."),
|
|
26265
|
+
proxyZip: import_zod31.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential proxy targeting."),
|
|
26266
|
+
debug: import_zod31.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics.")
|
|
26074
26267
|
};
|
|
26075
26268
|
DirectoryWorkflowInputSchema = {
|
|
26076
|
-
query: import_zod31.z.string().min(1).describe("Business category, niche, or keyword to search on Google Maps for every
|
|
26077
|
-
state: import_zod31.z.string().min(2).default("TN").describe("US state abbreviation or
|
|
26078
|
-
minPopulation: import_zod31.z.number().int().min(0).default(1e5).describe(
|
|
26079
|
-
populationYear: import_zod31.z.number().int().min(2020).max(2025).default(2025).describe("Census population estimate year
|
|
26080
|
-
maxCities: import_zod31.z.number().int().min(1).max(100).default(25).describe("Maximum
|
|
26081
|
-
maxResultsPerCity: import_zod31.z.number().int().min(1).max(50).default(50).describe("Google Maps
|
|
26082
|
-
concurrency: import_zod31.z.number().int().min(1).max(5).default(5).describe("
|
|
26083
|
-
includeZipGroups: import_zod31.z.boolean().default(true).describe("Attach ZIP groups from a configured US ZIPS CSV when available
|
|
26084
|
-
usZipsCsvPath: import_zod31.z.string().optional().describe("Local/test-only path to a US ZIPS CSV
|
|
26085
|
-
saveCsv: import_zod31.z.boolean().default(true).describe("Save a directory-ready CSV to the MCP Scraper output directory and return its path.
|
|
26086
|
-
proxyMode: import_zod31.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Proxy targeting
|
|
26087
|
-
proxyZip: import_zod31.z.string().regex(/^\d{5}$/).optional().describe("Optional ZIP override for proxy targeting
|
|
26088
|
-
debug: import_zod31.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics
|
|
26269
|
+
query: import_zod31.z.string().min(1).describe("Business category, niche, or keyword to search on Google Maps for every market. Do not include the city."),
|
|
26270
|
+
state: import_zod31.z.string().min(2).default("TN").describe("US state abbreviation or name used to select Census places, e.g. TN."),
|
|
26271
|
+
minPopulation: import_zod31.z.number().int().min(0).default(1e5).describe("Minimum Census place population for market selection."),
|
|
26272
|
+
populationYear: import_zod31.z.number().int().min(2020).max(2025).default(2025).describe("Census population estimate year (2020-2025 Population Estimates Program)."),
|
|
26273
|
+
maxCities: import_zod31.z.number().int().min(1).max(100).default(25).describe("Maximum markets to process after sorting by population descending."),
|
|
26274
|
+
maxResultsPerCity: import_zod31.z.number().int().min(1).max(50).default(50).describe("Google Maps candidates to collect per city."),
|
|
26275
|
+
concurrency: import_zod31.z.number().int().min(1).max(5).default(5).describe("City Maps searches to run in parallel."),
|
|
26276
|
+
includeZipGroups: import_zod31.z.boolean().default(true).describe("Attach ZIP groups from a configured US ZIPS CSV when available (MCP_SCRAPER_USZIPS_CSV_PATH or usZipsCsvPath)."),
|
|
26277
|
+
usZipsCsvPath: import_zod31.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."),
|
|
26278
|
+
saveCsv: import_zod31.z.boolean().default(true).describe("Save a directory-ready CSV of results to the MCP Scraper output directory and return its path."),
|
|
26279
|
+
proxyMode: import_zod31.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."),
|
|
26280
|
+
proxyZip: import_zod31.z.string().regex(/^\d{5}$/).optional().describe("Optional ZIP override for proxy targeting; normally omitted."),
|
|
26281
|
+
debug: import_zod31.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics.")
|
|
26089
26282
|
};
|
|
26283
|
+
ArtifactPointerOutputSchema = import_zod31.z.object({
|
|
26284
|
+
artifactId: import_zod31.z.string(),
|
|
26285
|
+
bytes: import_zod31.z.number().int().min(0),
|
|
26286
|
+
expiresAt: import_zod31.z.string(),
|
|
26287
|
+
preview: import_zod31.z.string()
|
|
26288
|
+
});
|
|
26090
26289
|
RankTrackerModeSchema = import_zod31.z.enum(["maps", "organic", "ai_overview", "paa"]);
|
|
26091
26290
|
RankTrackerBlueprintInputSchema = {
|
|
26092
26291
|
projectName: import_zod31.z.string().min(1).optional().describe("Optional name for the rank tracker project, client, or campaign."),
|
|
26093
|
-
targetDomain: import_zod31.z.string().min(1).optional().describe("Primary domain to track in organic results, AI Overview citations, and PAA sources
|
|
26094
|
-
targetBusinessName: import_zod31.z.string().min(1).optional().describe("Primary Google Business Profile
|
|
26095
|
-
trackingModes: import_zod31.z.array(RankTrackerModeSchema).min(1).max(4).default(["maps", "organic", "ai_overview", "paa"]).describe("Rank tracker surfaces to build
|
|
26096
|
-
keywords: import_zod31.z.array(import_zod31.z.string().min(1)).max(200).default([]).describe("Seed keywords or service queries to track. Leave empty
|
|
26097
|
-
locations: import_zod31.z.array(import_zod31.z.string().min(1)).max(100).default([]).describe(
|
|
26292
|
+
targetDomain: import_zod31.z.string().min(1).optional().describe("Primary domain to track in organic results, AI Overview citations, and PAA sources."),
|
|
26293
|
+
targetBusinessName: import_zod31.z.string().min(1).optional().describe("Primary Google Business Profile/brand name to match in Maps results. Required for Maps tracking."),
|
|
26294
|
+
trackingModes: import_zod31.z.array(RankTrackerModeSchema).min(1).max(4).default(["maps", "organic", "ai_overview", "paa"]).describe("Rank tracker surfaces to build: maps, organic, ai_overview, paa."),
|
|
26295
|
+
keywords: import_zod31.z.array(import_zod31.z.string().min(1)).max(200).default([]).describe("Seed keywords or service queries to track. Leave empty to derive from user input downstream."),
|
|
26296
|
+
locations: import_zod31.z.array(import_zod31.z.string().min(1)).max(100).default([]).describe('Markets, cities, ZIPs, or service areas to track, e.g. "Denver, CO".'),
|
|
26098
26297
|
competitors: import_zod31.z.array(import_zod31.z.string().min(1)).max(100).default([]).describe("Optional competitor domains or business names to persist as comparison targets."),
|
|
26099
|
-
database: import_zod31.z.enum(["postgres", "neon", "supabase", "sqlite", "mysql"]).default("postgres").describe("Database family
|
|
26100
|
-
scheduleCadence: import_zod31.z.enum(["daily", "weekly", "monthly", "custom"]).default("weekly").describe("Default recurring rank check cadence
|
|
26298
|
+
database: import_zod31.z.enum(["postgres", "neon", "supabase", "sqlite", "mysql"]).default("postgres").describe("Database family to target when generating migrations."),
|
|
26299
|
+
scheduleCadence: import_zod31.z.enum(["daily", "weekly", "monthly", "custom"]).default("weekly").describe("Default recurring rank check cadence."),
|
|
26101
26300
|
customCron: import_zod31.z.string().min(1).optional().describe("Cron expression to use when scheduleCadence is custom."),
|
|
26102
|
-
timezone: import_zod31.z.string().min(1).default("UTC").describe("IANA timezone for scheduled rank checks
|
|
26103
|
-
includeCron: import_zod31.z.boolean().default(true).describe("Include a cron
|
|
26104
|
-
includeDashboard: import_zod31.z.boolean().default(true).describe("Include dashboard/reporting requirements
|
|
26105
|
-
includeAlerts: import_zod31.z.boolean().default(true).describe("Include alert rules for rank movement and SERP feature
|
|
26106
|
-
notes: import_zod31.z.string().max(4e3).optional().describe("Extra product, client, stack, or hosting requirements
|
|
26301
|
+
timezone: import_zod31.z.string().min(1).default("UTC").describe("IANA timezone for scheduled rank checks."),
|
|
26302
|
+
includeCron: import_zod31.z.boolean().default(true).describe("Include a cron/heartbeat worker plan."),
|
|
26303
|
+
includeDashboard: import_zod31.z.boolean().default(true).describe("Include dashboard/reporting requirements."),
|
|
26304
|
+
includeAlerts: import_zod31.z.boolean().default(true).describe("Include alert rules for rank movement and SERP feature changes."),
|
|
26305
|
+
notes: import_zod31.z.string().max(4e3).optional().describe("Extra product, client, stack, or hosting requirements.")
|
|
26107
26306
|
};
|
|
26108
26307
|
NullableString = import_zod31.z.string().nullable();
|
|
26109
26308
|
MapsSearchAttemptOutput = import_zod31.z.object({
|
|
@@ -26200,7 +26399,9 @@ var init_mcp_tool_schemas = __esm({
|
|
|
26200
26399
|
attempts: import_zod31.z.array(MapsSearchAttemptOutput),
|
|
26201
26400
|
results: import_zod31.z.array(DirectoryMapsBusinessOutput)
|
|
26202
26401
|
})),
|
|
26203
|
-
durationMs: import_zod31.z.number().int().min(0)
|
|
26402
|
+
durationMs: import_zod31.z.number().int().min(0),
|
|
26403
|
+
truncatedCount: import_zod31.z.number().int().min(0).optional(),
|
|
26404
|
+
artifact: ArtifactPointerOutputSchema.optional()
|
|
26204
26405
|
};
|
|
26205
26406
|
RankTrackerToolPlanOutput = import_zod31.z.object({
|
|
26206
26407
|
tool: import_zod31.z.string(),
|
|
@@ -26253,7 +26454,7 @@ var init_mcp_tool_schemas = __esm({
|
|
|
26253
26454
|
kgId: import_zod31.z.string().nullable(),
|
|
26254
26455
|
cid: import_zod31.z.string().nullable(),
|
|
26255
26456
|
gcid: import_zod31.z.string().nullable()
|
|
26256
|
-
})).describe("
|
|
26457
|
+
})).describe("Entities named on the page with their kgId/cid/gcid. Flat lists below are the same IDs deduplicated, kept for backward compatibility."),
|
|
26257
26458
|
kgIds: import_zod31.z.array(import_zod31.z.string()),
|
|
26258
26459
|
cids: import_zod31.z.array(import_zod31.z.string()),
|
|
26259
26460
|
gcids: import_zod31.z.array(import_zod31.z.string())
|
|
@@ -26320,7 +26521,9 @@ var init_mcp_tool_schemas = __esm({
|
|
|
26320
26521
|
title: NullableString,
|
|
26321
26522
|
schemaTypes: import_zod31.z.array(import_zod31.z.string())
|
|
26322
26523
|
})),
|
|
26323
|
-
durationMs: import_zod31.z.number().min(0)
|
|
26524
|
+
durationMs: import_zod31.z.number().min(0),
|
|
26525
|
+
truncatedCount: import_zod31.z.number().int().min(0).optional(),
|
|
26526
|
+
artifact: ArtifactPointerOutputSchema.optional()
|
|
26324
26527
|
};
|
|
26325
26528
|
AuditSiteOutputSchema = {
|
|
26326
26529
|
url: import_zod31.z.string(),
|
|
@@ -26340,7 +26543,8 @@ var init_mcp_tool_schemas = __esm({
|
|
|
26340
26543
|
orphans: import_zod31.z.number().int().min(0),
|
|
26341
26544
|
brokenInternal: import_zod31.z.number().int().min(0),
|
|
26342
26545
|
externalDomains: import_zod31.z.number().int().min(0)
|
|
26343
|
-
})
|
|
26546
|
+
}),
|
|
26547
|
+
artifact: ArtifactPointerOutputSchema.optional()
|
|
26344
26548
|
};
|
|
26345
26549
|
MapsPlaceIntelOutputSchema = {
|
|
26346
26550
|
name: import_zod31.z.string(),
|
|
@@ -26412,7 +26616,9 @@ var init_mcp_tool_schemas = __esm({
|
|
|
26412
26616
|
url: import_zod31.z.string(),
|
|
26413
26617
|
status: import_zod31.z.number().int().nullable()
|
|
26414
26618
|
})),
|
|
26415
|
-
durationMs: import_zod31.z.number().min(0)
|
|
26619
|
+
durationMs: import_zod31.z.number().min(0),
|
|
26620
|
+
truncatedCount: import_zod31.z.number().int().min(0).optional(),
|
|
26621
|
+
artifact: ArtifactPointerOutputSchema.optional()
|
|
26416
26622
|
};
|
|
26417
26623
|
YoutubeHarvestOutputSchema = {
|
|
26418
26624
|
mode: import_zod31.z.string(),
|
|
@@ -26720,7 +26926,7 @@ var init_mcp_tool_schemas = __esm({
|
|
|
26720
26926
|
"ai-overview-language"
|
|
26721
26927
|
]);
|
|
26722
26928
|
WorkflowListInputSchema = {
|
|
26723
|
-
includeRecipes: import_zod31.z.boolean().default(true).describe("Include high-level AI-facing recipes
|
|
26929
|
+
includeRecipes: import_zod31.z.boolean().default(true).describe("Include high-level AI-facing recipes (market analysis, ICP research, CRO audits, content gaps, etc).")
|
|
26724
26930
|
};
|
|
26725
26931
|
WorkflowSuggestInputSchema = {
|
|
26726
26932
|
goal: import_zod31.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".'),
|
|
@@ -26733,12 +26939,12 @@ var init_mcp_tool_schemas = __esm({
|
|
|
26733
26939
|
maxSuggestions: import_zod31.z.number().int().min(1).max(8).default(3).describe("Number of matching workflow recipes to return.")
|
|
26734
26940
|
};
|
|
26735
26941
|
WorkflowRunInputSchema = {
|
|
26736
|
-
workflowId: WorkflowIdSchema2.describe("Workflow to run
|
|
26737
|
-
input: import_zod31.z.record(import_zod31.z.unknown()).default({}).describe("Workflow-specific input object
|
|
26942
|
+
workflowId: WorkflowIdSchema2.describe("Workflow to run. Call workflow_list or workflow_suggest first when unsure."),
|
|
26943
|
+
input: import_zod31.z.record(import_zod31.z.unknown()).default({}).describe("Workflow-specific input object; shape depends on workflowId. Call workflow_list or workflow_suggest to see required fields."),
|
|
26738
26944
|
webhookUrl: import_zod31.z.string().url().optional().describe("Optional HTTPS webhook to receive the completed hosted workflow run event.")
|
|
26739
26945
|
};
|
|
26740
26946
|
WorkflowStepInputSchema = {
|
|
26741
|
-
runId: import_zod31.z.string().min(1).describe("Workflow run id returned by workflow_run
|
|
26947
|
+
runId: import_zod31.z.string().min(1).describe("Workflow run id returned by workflow_run/workflow_step/workflow_status. Advances the run by exactly one step.")
|
|
26742
26948
|
};
|
|
26743
26949
|
WorkflowStatusInputSchema = {
|
|
26744
26950
|
runId: import_zod31.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.")
|
|
@@ -26746,7 +26952,7 @@ var init_mcp_tool_schemas = __esm({
|
|
|
26746
26952
|
WorkflowArtifactReadInputSchema = {
|
|
26747
26953
|
runId: import_zod31.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."),
|
|
26748
26954
|
artifactId: import_zod31.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."),
|
|
26749
|
-
maxBytes: import_zod31.z.number().int().min(1e3).max(1e6).default(2e5).describe("Maximum bytes of artifact text to return inline.
|
|
26955
|
+
maxBytes: import_zod31.z.number().int().min(1e3).max(1e6).default(2e5).describe("Maximum bytes of artifact text to return inline.")
|
|
26750
26956
|
};
|
|
26751
26957
|
WorkflowRecipeOutput = import_zod31.z.object({
|
|
26752
26958
|
id: import_zod31.z.string(),
|
|
@@ -26805,28 +27011,28 @@ var init_mcp_tool_schemas = __esm({
|
|
|
26805
27011
|
text: import_zod31.z.string()
|
|
26806
27012
|
};
|
|
26807
27013
|
SearchSerpInputSchema = {
|
|
26808
|
-
query: import_zod31.z.string().min(1).describe('The search query.
|
|
26809
|
-
location: import_zod31.z.string().optional().describe("City, region, or country for geo signals
|
|
27014
|
+
query: import_zod31.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.'),
|
|
27015
|
+
location: import_zod31.z.string().optional().describe("City, region, or country for geo signals. Set alongside city-in-query wording; alone it does NOT reliably localize."),
|
|
26810
27016
|
gl: import_zod31.z.string().length(2).default("us").describe("Google country code inferred from location or user language."),
|
|
26811
27017
|
hl: import_zod31.z.string().default("en").describe("Google interface/content language inferred from user request."),
|
|
26812
|
-
device: import_zod31.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use
|
|
26813
|
-
proxyMode: import_zod31.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe('Leave unset (clean egress
|
|
26814
|
-
proxyZip: import_zod31.z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location"
|
|
26815
|
-
debug: import_zod31.z.boolean().default(false).describe("Include sanitized
|
|
26816
|
-
pages: import_zod31.z.number().int().min(1).max(2).default(1).describe("Number of result pages to fetch (1\u20132)")
|
|
27018
|
+
device: import_zod31.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only for mobile rankings."),
|
|
27019
|
+
proxyMode: import_zod31.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.'),
|
|
27020
|
+
proxyZip: import_zod31.z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location".'),
|
|
27021
|
+
debug: import_zod31.z.boolean().default(false).describe("Include sanitized diagnostics for debugging localization, CAPTCHA, or proxy behavior."),
|
|
27022
|
+
pages: import_zod31.z.number().int().min(1).max(2).default(1).describe("Number of result pages to fetch (1\u20132).")
|
|
26817
27023
|
};
|
|
26818
27024
|
CaptureSerpSnapshotInputSchema = {
|
|
26819
|
-
query: import_zod31.z.string().min(1).describe('Search query to capture
|
|
26820
|
-
location: import_zod31.z.string().optional().describe("City, region, country, or service area
|
|
26821
|
-
gl: import_zod31.z.string().length(2).default("us").describe("Google country code inferred from the requested market
|
|
27025
|
+
query: import_zod31.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.'),
|
|
27026
|
+
location: import_zod31.z.string().optional().describe("City, region, country, or service area for localized Google results."),
|
|
27027
|
+
gl: import_zod31.z.string().length(2).default("us").describe("Google country code inferred from the requested market."),
|
|
26822
27028
|
hl: import_zod31.z.string().default("en").describe("Google interface/content language inferred from the user request."),
|
|
26823
|
-
device: import_zod31.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only
|
|
26824
|
-
proxyMode: import_zod31.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe('Leave unset (clean egress
|
|
26825
|
-
proxyZip: import_zod31.z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location"
|
|
26826
|
-
pages: import_zod31.z.number().int().min(1).max(2).default(1).describe("
|
|
26827
|
-
debug: import_zod31.z.boolean().default(false).describe("Include sanitized browser
|
|
26828
|
-
includePageSnapshots: import_zod31.z.boolean().default(false).describe("Also capture ranking-page snapshots for selected SERP URLs
|
|
26829
|
-
pageSnapshotLimit: import_zod31.z.number().int().min(0).max(10).default(0).describe("Maximum ranking-page snapshots
|
|
27029
|
+
device: import_zod31.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only for mobile rankings/evidence."),
|
|
27030
|
+
proxyMode: import_zod31.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.'),
|
|
27031
|
+
proxyZip: import_zod31.z.string().regex(/^\d{5}$/).optional().describe('US ZIP for residential geo-IP targeting. Only meaningful with proxyMode "location".'),
|
|
27032
|
+
pages: import_zod31.z.number().int().min(1).max(2).default(1).describe("Google result pages to capture. Use 2 only for deeper ranking evidence."),
|
|
27033
|
+
debug: import_zod31.z.boolean().default(false).describe("Include sanitized browser/proxy/location diagnostics."),
|
|
27034
|
+
includePageSnapshots: import_zod31.z.boolean().default(false).describe("Also capture ranking-page snapshots for selected SERP URLs."),
|
|
27035
|
+
pageSnapshotLimit: import_zod31.z.number().int().min(0).max(10).default(0).describe("Maximum ranking-page snapshots when includePageSnapshots is true.")
|
|
26830
27036
|
};
|
|
26831
27037
|
ScreenshotInputSchema = {
|
|
26832
27038
|
url: import_zod31.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."),
|
|
@@ -26834,15 +27040,26 @@ var init_mcp_tool_schemas = __esm({
|
|
|
26834
27040
|
allowLocal: import_zod31.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.")
|
|
26835
27041
|
};
|
|
26836
27042
|
CaptureSerpPageSnapshotsInputSchema = {
|
|
26837
|
-
urls: import_zod31.z.array(import_zod31.z.string().url()).min(1).max(25).describe("Public HTTP/HTTPS URLs to capture
|
|
27043
|
+
urls: import_zod31.z.array(import_zod31.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."),
|
|
26838
27044
|
targets: import_zod31.z.array(import_zod31.z.object({
|
|
26839
27045
|
url: import_zod31.z.string().url().describe("Public HTTP/HTTPS URL to capture."),
|
|
26840
|
-
sourceKind: import_zod31.z.enum(["organic", "ai_citation", "local_pack_website", "configured_target", "site_subject"]).default("configured_target").describe("Why this page is being captured
|
|
27046
|
+
sourceKind: import_zod31.z.enum(["organic", "ai_citation", "local_pack_website", "configured_target", "site_subject"]).default("configured_target").describe("Why this page is being captured."),
|
|
26841
27047
|
sourcePosition: import_zod31.z.number().int().min(1).optional().describe("Ranking or citation position when the page came from SERP evidence.")
|
|
26842
|
-
}).strict()).min(1).max(25).optional().describe("Structured
|
|
26843
|
-
maxConcurrency: import_zod31.z.number().int().min(1).max(5).default(2).describe("Parallel page captures.
|
|
26844
|
-
timeoutMs: import_zod31.z.number().int().min(1e3).max(6e4).default(15e3).describe("Per-page capture timeout in milliseconds
|
|
26845
|
-
debug: import_zod31.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics
|
|
27048
|
+
}).strict()).min(1).max(25).optional().describe("Structured targets. Use instead of urls when source kind or position should be preserved."),
|
|
27049
|
+
maxConcurrency: import_zod31.z.number().int().min(1).max(5).default(2).describe("Parallel page captures."),
|
|
27050
|
+
timeoutMs: import_zod31.z.number().int().min(1e3).max(6e4).default(15e3).describe("Per-page capture timeout in milliseconds; timeouts return as structured capture failures."),
|
|
27051
|
+
debug: import_zod31.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics.")
|
|
27052
|
+
};
|
|
27053
|
+
ReportArtifactReadInputSchema = {
|
|
27054
|
+
artifactId: import_zod31.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."),
|
|
27055
|
+
offset: import_zod31.z.number().int().min(0).default(0).describe("Byte offset to start reading from. Pass the previous call's nextOffset to continue."),
|
|
27056
|
+
maxBytes: import_zod31.z.number().int().min(1e3).max(1e5).default(2e4).describe("Maximum bytes of artifact text to return in this window.")
|
|
27057
|
+
};
|
|
27058
|
+
ReportArtifactReadOutputSchema = {
|
|
27059
|
+
artifactId: import_zod31.z.string(),
|
|
27060
|
+
text: import_zod31.z.string(),
|
|
27061
|
+
totalBytes: import_zod31.z.number().int().min(0),
|
|
27062
|
+
nextOffset: import_zod31.z.number().int().min(0).nullable()
|
|
26846
27063
|
};
|
|
26847
27064
|
}
|
|
26848
27065
|
});
|
|
@@ -27177,6 +27394,9 @@ var init_rank_tracker_blueprint = __esm({
|
|
|
27177
27394
|
});
|
|
27178
27395
|
|
|
27179
27396
|
// src/mcp/paa-mcp-server.ts
|
|
27397
|
+
function hashOwnerId(callerKey) {
|
|
27398
|
+
return (0, import_node_crypto10.createHash)("sha256").update(callerKey).digest("hex").slice(0, 24);
|
|
27399
|
+
}
|
|
27180
27400
|
function liveWebToolAnnotations(title) {
|
|
27181
27401
|
return {
|
|
27182
27402
|
title,
|
|
@@ -27189,16 +27409,16 @@ function liveWebToolAnnotations(title) {
|
|
|
27189
27409
|
function registerSerpIntelligenceCaptureTools(server, executor) {
|
|
27190
27410
|
server.registerTool("capture_serp_snapshot", {
|
|
27191
27411
|
title: "SERP Intelligence Snapshot",
|
|
27192
|
-
description: "Capture a structured SERP Intelligence
|
|
27412
|
+
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.",
|
|
27193
27413
|
inputSchema: CaptureSerpSnapshotInputSchema,
|
|
27194
|
-
outputSchema: CaptureSerpSnapshotOutputSchema,
|
|
27414
|
+
outputSchema: recordOutputSchema("capture_serp_snapshot", CaptureSerpSnapshotOutputSchema),
|
|
27195
27415
|
annotations: liveWebToolAnnotations("SERP Intelligence Snapshot")
|
|
27196
27416
|
}, async (input) => formatCaptureSerpSnapshot(await executor.captureSerpSnapshot(input), input));
|
|
27197
27417
|
server.registerTool("capture_serp_page_snapshots", {
|
|
27198
27418
|
title: "SERP Intelligence Page Snapshots",
|
|
27199
|
-
description: "Capture public ranking
|
|
27419
|
+
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.",
|
|
27200
27420
|
inputSchema: CaptureSerpPageSnapshotsInputSchema,
|
|
27201
|
-
outputSchema: CaptureSerpPageSnapshotsOutputSchema,
|
|
27421
|
+
outputSchema: recordOutputSchema("capture_serp_page_snapshots", CaptureSerpPageSnapshotsOutputSchema),
|
|
27202
27422
|
annotations: liveWebToolAnnotations("SERP Intelligence Page Snapshots")
|
|
27203
27423
|
}, async (input) => formatCaptureSerpPageSnapshots(await executor.captureSerpPageSnapshots(input), input));
|
|
27204
27424
|
}
|
|
@@ -27246,230 +27466,251 @@ function registerSavedReportResources(server) {
|
|
|
27246
27466
|
);
|
|
27247
27467
|
}
|
|
27248
27468
|
function buildPaaExtractorMcpServer(executor, options = {}) {
|
|
27249
|
-
const server = new import_mcp.McpServer({ name: "mcp-scraper", version: PACKAGE_VERSION }, { instructions:
|
|
27469
|
+
const server = new import_mcp.McpServer({ name: "mcp-scraper", version: PACKAGE_VERSION }, { instructions: serverInstructions(options.savesReportsLocally !== false) });
|
|
27250
27470
|
registerPaaExtractorMcpTools(server, executor, options);
|
|
27251
27471
|
return server;
|
|
27252
27472
|
}
|
|
27253
27473
|
function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
27254
27474
|
const savesReports = options.savesReportsLocally !== false;
|
|
27255
|
-
const
|
|
27256
|
-
const
|
|
27475
|
+
const fileBehavior = (local, hosted) => savesReports ? local : hosted;
|
|
27476
|
+
const ownerId = options.ownerId ?? "local";
|
|
27477
|
+
const ctx = { hosted: !savesReports, ownerId };
|
|
27257
27478
|
if (savesReports) registerSavedReportResources(server);
|
|
27258
27479
|
server.registerTool("harvest_paa", {
|
|
27259
27480
|
title: "Google PAA + SERP Harvest",
|
|
27260
|
-
description:
|
|
27481
|
+
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.",
|
|
27261
27482
|
inputSchema: HarvestPaaInputSchema,
|
|
27262
|
-
outputSchema: HarvestPaaOutputSchema,
|
|
27483
|
+
outputSchema: recordOutputSchema("harvest_paa", HarvestPaaOutputSchema),
|
|
27263
27484
|
annotations: liveWebToolAnnotations("Google PAA + SERP Harvest")
|
|
27264
27485
|
}, async (input) => formatHarvestPaa(await executor.harvestPaa(input), input));
|
|
27265
27486
|
server.registerTool("search_serp", {
|
|
27266
27487
|
title: "Google SERP Lookup",
|
|
27267
|
-
description:
|
|
27488
|
+
description: "Fast Google SERP lookup without PAA expansion \u2014 rankings, organic results, local pack, positions. Split topic from location; leave proxyMode unset.",
|
|
27268
27489
|
inputSchema: SearchSerpInputSchema,
|
|
27269
|
-
outputSchema: SearchSerpOutputSchema,
|
|
27490
|
+
outputSchema: recordOutputSchema("search_serp", SearchSerpOutputSchema),
|
|
27270
27491
|
annotations: liveWebToolAnnotations("Google SERP Lookup")
|
|
27271
27492
|
}, async (input) => formatSearchSerp(await executor.searchSerp(input), input));
|
|
27272
27493
|
server.registerTool("extract_url", {
|
|
27273
27494
|
title: "Single URL Extract",
|
|
27274
|
-
description:
|
|
27495
|
+
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).",
|
|
27275
27496
|
inputSchema: ExtractUrlInputSchema,
|
|
27276
|
-
outputSchema: ExtractUrlOutputSchema,
|
|
27497
|
+
outputSchema: recordOutputSchema("extract_url", ExtractUrlOutputSchema),
|
|
27277
27498
|
annotations: liveWebToolAnnotations("Single URL Extract")
|
|
27278
27499
|
}, async (input) => formatExtractUrl(await executor.extractUrl(input), input));
|
|
27279
27500
|
server.registerTool("map_site_urls", {
|
|
27280
27501
|
title: "Site URL Map",
|
|
27281
|
-
description:
|
|
27502
|
+
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.")}`,
|
|
27282
27503
|
inputSchema: MapSiteUrlsInputSchema,
|
|
27283
|
-
outputSchema: MapSiteUrlsOutputSchema,
|
|
27504
|
+
outputSchema: recordOutputSchema("map_site_urls", MapSiteUrlsOutputSchema),
|
|
27284
27505
|
annotations: liveWebToolAnnotations("Site URL Map")
|
|
27285
|
-
}, async (input) => formatMapSiteUrls(await executor.mapSiteUrls(input), input));
|
|
27506
|
+
}, async (input) => formatMapSiteUrls(await executor.mapSiteUrls(input), input, ctx));
|
|
27286
27507
|
server.registerTool("extract_site", {
|
|
27287
27508
|
title: "Multi-Page Site Content Crawl",
|
|
27288
|
-
description:
|
|
27509
|
+
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.`,
|
|
27289
27510
|
inputSchema: ExtractSiteInputSchema,
|
|
27290
|
-
outputSchema: ExtractSiteOutputSchema,
|
|
27511
|
+
outputSchema: recordOutputSchema("extract_site", ExtractSiteOutputSchema),
|
|
27291
27512
|
annotations: liveWebToolAnnotations("Multi-Page Site Content Crawl")
|
|
27292
|
-
}, async (input) => formatExtractSite(await executor.extractSite(input), input));
|
|
27513
|
+
}, async (input) => formatExtractSite(await executor.extractSite(input), input, ctx));
|
|
27293
27514
|
server.registerTool("audit_site", {
|
|
27294
27515
|
title: "Technical SEO Audit",
|
|
27295
|
-
description:
|
|
27516
|
+
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.`,
|
|
27296
27517
|
inputSchema: AuditSiteInputSchema,
|
|
27297
|
-
outputSchema: AuditSiteOutputSchema,
|
|
27518
|
+
outputSchema: recordOutputSchema("audit_site", AuditSiteOutputSchema),
|
|
27298
27519
|
annotations: liveWebToolAnnotations("Technical SEO Audit")
|
|
27299
|
-
}, async (input) => formatAuditSite(await executor.auditSite(input), input));
|
|
27520
|
+
}, async (input) => formatAuditSite(await executor.auditSite(input), input, ctx));
|
|
27300
27521
|
server.registerTool("youtube_harvest", {
|
|
27301
27522
|
title: "YouTube Video Harvest",
|
|
27302
|
-
description:
|
|
27523
|
+
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.',
|
|
27303
27524
|
inputSchema: YoutubeHarvestInputSchema,
|
|
27304
|
-
outputSchema: YoutubeHarvestOutputSchema,
|
|
27525
|
+
outputSchema: recordOutputSchema("youtube_harvest", YoutubeHarvestOutputSchema),
|
|
27305
27526
|
annotations: liveWebToolAnnotations("YouTube Video Harvest")
|
|
27306
27527
|
}, async (input) => formatYoutubeHarvest(await executor.youtubeHarvest(input), input));
|
|
27307
27528
|
server.registerTool("youtube_transcribe", {
|
|
27308
27529
|
title: "YouTube Transcription",
|
|
27309
|
-
description:
|
|
27530
|
+
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.",
|
|
27310
27531
|
inputSchema: YoutubeTranscribeInputSchema,
|
|
27311
|
-
outputSchema: YoutubeTranscribeOutputSchema,
|
|
27532
|
+
outputSchema: recordOutputSchema("youtube_transcribe", YoutubeTranscribeOutputSchema),
|
|
27312
27533
|
annotations: liveWebToolAnnotations("YouTube Transcription")
|
|
27313
27534
|
}, async (input) => formatYoutubeTranscribe(await executor.youtubeTranscribe(input), input));
|
|
27314
27535
|
server.registerTool("facebook_page_intel", {
|
|
27315
27536
|
title: "Facebook Advertiser Ad Intel",
|
|
27316
|
-
description:
|
|
27537
|
+
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.",
|
|
27317
27538
|
inputSchema: FacebookPageIntelInputSchema,
|
|
27318
|
-
outputSchema: FacebookPageIntelOutputSchema,
|
|
27539
|
+
outputSchema: recordOutputSchema("facebook_page_intel", FacebookPageIntelOutputSchema),
|
|
27319
27540
|
annotations: liveWebToolAnnotations("Facebook Advertiser Ad Intel")
|
|
27320
27541
|
}, async (input) => formatFacebookPageIntel(await executor.facebookPageIntel(input), input));
|
|
27321
27542
|
server.registerTool("facebook_ad_search", {
|
|
27322
27543
|
title: "Facebook Ad Library Search",
|
|
27323
|
-
description:
|
|
27544
|
+
description: "Search Facebook Ad Library to find advertisers by brand, competitor, niche, or keyword. Returns advertisers with ad counts and library IDs.",
|
|
27324
27545
|
inputSchema: FacebookAdSearchInputSchema,
|
|
27325
|
-
outputSchema: FacebookAdSearchOutputSchema,
|
|
27546
|
+
outputSchema: recordOutputSchema("facebook_ad_search", FacebookAdSearchOutputSchema),
|
|
27326
27547
|
annotations: liveWebToolAnnotations("Facebook Ad Library Search")
|
|
27327
27548
|
}, async (input) => formatFacebookAdSearch(await executor.facebookAdSearch(input), input));
|
|
27328
27549
|
server.registerTool("reddit_thread", {
|
|
27329
27550
|
title: "Reddit Thread + Comments",
|
|
27330
|
-
description:
|
|
27551
|
+
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.",
|
|
27331
27552
|
inputSchema: RedditThreadInputSchema,
|
|
27332
|
-
outputSchema: RedditThreadOutputSchema,
|
|
27553
|
+
outputSchema: recordOutputSchema("reddit_thread", RedditThreadOutputSchema),
|
|
27333
27554
|
annotations: liveWebToolAnnotations("Reddit Thread + Comments")
|
|
27334
27555
|
}, async (input) => formatRedditThread(await executor.redditThread(input), input));
|
|
27335
27556
|
server.registerTool("video_frame_analysis", {
|
|
27336
27557
|
title: "Video Breakdown (frame-by-frame + transcript)",
|
|
27337
|
-
description:
|
|
27558
|
+
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.",
|
|
27338
27559
|
inputSchema: VideoFrameAnalysisInputSchema,
|
|
27339
|
-
outputSchema: VideoFrameAnalysisOutputSchema,
|
|
27560
|
+
outputSchema: recordOutputSchema("video_frame_analysis", VideoFrameAnalysisOutputSchema),
|
|
27340
27561
|
annotations: { title: "Video Breakdown", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
27341
27562
|
}, async (input) => executor.videoFrameAnalysis(input));
|
|
27342
27563
|
server.registerTool("video_frame_analysis_status", {
|
|
27343
27564
|
title: "Video Breakdown Status",
|
|
27344
|
-
description: 'Check
|
|
27565
|
+
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".',
|
|
27345
27566
|
inputSchema: VideoFrameAnalysisStatusInputSchema,
|
|
27346
|
-
outputSchema: VideoFrameAnalysisStatusOutputSchema,
|
|
27567
|
+
outputSchema: recordOutputSchema("video_frame_analysis_status", VideoFrameAnalysisStatusOutputSchema),
|
|
27347
27568
|
annotations: { title: "Video Breakdown Status", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
27348
27569
|
}, async (input) => executor.videoFrameAnalysisStatus(input));
|
|
27349
27570
|
server.registerTool("facebook_ad_transcribe", {
|
|
27350
27571
|
title: "Facebook Ad Transcription",
|
|
27351
|
-
description: "Transcribe audio from a Facebook ad video CDN URL
|
|
27572
|
+
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).",
|
|
27352
27573
|
inputSchema: FacebookAdTranscribeInputSchema,
|
|
27353
|
-
outputSchema: FacebookAdTranscribeOutputSchema,
|
|
27574
|
+
outputSchema: recordOutputSchema("facebook_ad_transcribe", FacebookAdTranscribeOutputSchema),
|
|
27354
27575
|
annotations: liveWebToolAnnotations("Facebook Ad Transcription")
|
|
27355
27576
|
}, async (input) => formatFacebookAdTranscribe(await executor.facebookAdTranscribe(input), input));
|
|
27356
27577
|
server.registerTool("google_ads_search", {
|
|
27357
27578
|
title: "Google Ads Transparency Search",
|
|
27358
|
-
description:
|
|
27579
|
+
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.",
|
|
27359
27580
|
inputSchema: GoogleAdsSearchInputSchema,
|
|
27360
|
-
outputSchema: GoogleAdsSearchOutputSchema,
|
|
27581
|
+
outputSchema: recordOutputSchema("google_ads_search", GoogleAdsSearchOutputSchema),
|
|
27361
27582
|
annotations: liveWebToolAnnotations("Google Ads Transparency Search")
|
|
27362
27583
|
}, async (input) => formatGoogleAdsSearch(await executor.googleAdsSearch(input), input));
|
|
27363
27584
|
server.registerTool("google_ads_page_intel", {
|
|
27364
27585
|
title: "Google Ads Advertiser Intel",
|
|
27365
|
-
description:
|
|
27586
|
+
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.",
|
|
27366
27587
|
inputSchema: GoogleAdsPageIntelInputSchema,
|
|
27367
|
-
outputSchema: GoogleAdsPageIntelOutputSchema,
|
|
27588
|
+
outputSchema: recordOutputSchema("google_ads_page_intel", GoogleAdsPageIntelOutputSchema),
|
|
27368
27589
|
annotations: liveWebToolAnnotations("Google Ads Advertiser Intel")
|
|
27369
27590
|
}, async (input) => formatGoogleAdsPageIntel(await executor.googleAdsPageIntel(input), input));
|
|
27370
27591
|
server.registerTool("google_ads_transcribe", {
|
|
27371
27592
|
title: "Google Ad Video Transcription",
|
|
27372
|
-
description: "Transcribe audio from a Google video ad
|
|
27593
|
+
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.",
|
|
27373
27594
|
inputSchema: GoogleAdsTranscribeInputSchema,
|
|
27374
|
-
outputSchema: GoogleAdsTranscribeOutputSchema,
|
|
27595
|
+
outputSchema: recordOutputSchema("google_ads_transcribe", GoogleAdsTranscribeOutputSchema),
|
|
27375
27596
|
annotations: liveWebToolAnnotations("Google Ad Video Transcription")
|
|
27376
27597
|
}, async (input) => formatGoogleAdsTranscribe(await executor.googleAdsTranscribe(input), input));
|
|
27377
27598
|
server.registerTool("facebook_video_transcribe", {
|
|
27378
27599
|
title: "Facebook Organic Video Transcription",
|
|
27379
|
-
description:
|
|
27600
|
+
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.",
|
|
27380
27601
|
inputSchema: FacebookVideoTranscribeInputSchema,
|
|
27381
|
-
outputSchema: FacebookVideoTranscribeOutputSchema,
|
|
27602
|
+
outputSchema: recordOutputSchema("facebook_video_transcribe", FacebookVideoTranscribeOutputSchema),
|
|
27382
27603
|
annotations: liveWebToolAnnotations("Facebook Organic Video Transcription")
|
|
27383
27604
|
}, async (input) => formatFacebookVideoTranscribe(await executor.facebookVideoTranscribe(input), input));
|
|
27384
27605
|
server.registerTool("instagram_profile_content", {
|
|
27385
27606
|
title: "Instagram Profile Content Discovery",
|
|
27386
|
-
description:
|
|
27607
|
+
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.",
|
|
27387
27608
|
inputSchema: InstagramProfileContentInputSchema,
|
|
27388
|
-
outputSchema: InstagramProfileContentOutputSchema,
|
|
27609
|
+
outputSchema: recordOutputSchema("instagram_profile_content", InstagramProfileContentOutputSchema),
|
|
27389
27610
|
annotations: liveWebToolAnnotations("Instagram Profile Content Discovery")
|
|
27390
27611
|
}, async (input) => formatInstagramProfileContent(await executor.instagramProfileContent(input), input));
|
|
27391
27612
|
server.registerTool("instagram_media_download", {
|
|
27392
27613
|
title: "Instagram Post/Reel Media Download",
|
|
27393
|
-
description:
|
|
27614
|
+
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.",
|
|
27394
27615
|
inputSchema: InstagramMediaDownloadInputSchema,
|
|
27395
|
-
outputSchema: InstagramMediaDownloadOutputSchema,
|
|
27616
|
+
outputSchema: recordOutputSchema("instagram_media_download", InstagramMediaDownloadOutputSchema),
|
|
27396
27617
|
annotations: liveWebToolAnnotations("Instagram Post/Reel Media Download")
|
|
27397
27618
|
}, async (input) => formatInstagramMediaDownload(await executor.instagramMediaDownload(input), input));
|
|
27398
27619
|
server.registerTool("maps_place_intel", {
|
|
27399
27620
|
title: "Google Maps Business Profile Details",
|
|
27400
|
-
description:
|
|
27621
|
+
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.",
|
|
27401
27622
|
inputSchema: MapsPlaceIntelInputSchema,
|
|
27402
|
-
outputSchema: MapsPlaceIntelOutputSchema,
|
|
27623
|
+
outputSchema: recordOutputSchema("maps_place_intel", MapsPlaceIntelOutputSchema),
|
|
27403
27624
|
annotations: liveWebToolAnnotations("Google Maps Business Profile Details")
|
|
27404
27625
|
}, async (input) => formatMapsPlaceIntel(await executor.mapsPlaceIntel(input), input));
|
|
27405
27626
|
server.registerTool("maps_search", {
|
|
27406
27627
|
title: "Google Maps Business Search",
|
|
27407
|
-
description:
|
|
27628
|
+
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.",
|
|
27408
27629
|
inputSchema: MapsSearchInputSchema,
|
|
27409
|
-
outputSchema: MapsSearchOutputSchema,
|
|
27630
|
+
outputSchema: recordOutputSchema("maps_search", MapsSearchOutputSchema),
|
|
27410
27631
|
annotations: liveWebToolAnnotations("Google Maps Business Search")
|
|
27411
27632
|
}, async (input) => formatMapsSearch(await executor.mapsSearch(input), input));
|
|
27412
27633
|
server.registerTool("directory_workflow", {
|
|
27413
27634
|
title: "Directory Workflow: Markets + Maps",
|
|
27414
|
-
description:
|
|
27635
|
+
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.")}`,
|
|
27415
27636
|
inputSchema: DirectoryWorkflowInputSchema,
|
|
27416
|
-
outputSchema: DirectoryWorkflowOutputSchema,
|
|
27637
|
+
outputSchema: recordOutputSchema("directory_workflow", DirectoryWorkflowOutputSchema),
|
|
27417
27638
|
annotations: liveWebToolAnnotations("Directory Workflow: Markets + Maps")
|
|
27418
|
-
}, async (input) => formatDirectoryWorkflow(await executor.directoryWorkflow(input), input));
|
|
27639
|
+
}, async (input) => formatDirectoryWorkflow(await executor.directoryWorkflow(input), input, ctx));
|
|
27419
27640
|
server.registerTool("workflow_list", {
|
|
27420
27641
|
title: "Workflow Catalog",
|
|
27421
|
-
description: "List MCP Scraper higher-level workflows and
|
|
27642
|
+
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.",
|
|
27422
27643
|
inputSchema: WorkflowListInputSchema,
|
|
27423
|
-
outputSchema: WorkflowListOutputSchema,
|
|
27644
|
+
outputSchema: recordOutputSchema("workflow_list", WorkflowListOutputSchema),
|
|
27424
27645
|
annotations: localPlanningToolAnnotations("Workflow Catalog")
|
|
27425
27646
|
}, async (input) => formatWorkflowList(await executor.workflowList(input), input));
|
|
27426
27647
|
server.registerTool("workflow_suggest", {
|
|
27427
27648
|
title: "Workflow Intent Router",
|
|
27428
|
-
description:
|
|
27649
|
+
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.",
|
|
27429
27650
|
inputSchema: WorkflowSuggestInputSchema,
|
|
27430
|
-
outputSchema: WorkflowSuggestOutputSchema,
|
|
27651
|
+
outputSchema: recordOutputSchema("workflow_suggest", WorkflowSuggestOutputSchema),
|
|
27431
27652
|
annotations: localPlanningToolAnnotations("Workflow Intent Router")
|
|
27432
27653
|
}, async (input) => formatWorkflowSuggest(input));
|
|
27433
27654
|
server.registerTool("workflow_run", {
|
|
27434
27655
|
title: "Run Workflow",
|
|
27435
|
-
description:
|
|
27656
|
+
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.",
|
|
27436
27657
|
inputSchema: WorkflowRunInputSchema,
|
|
27437
|
-
outputSchema: WorkflowRunOutputSchema,
|
|
27658
|
+
outputSchema: recordOutputSchema("workflow_run", WorkflowRunOutputSchema),
|
|
27438
27659
|
annotations: liveWebToolAnnotations("Run Workflow")
|
|
27439
27660
|
}, async (input) => formatWorkflowRun(await executor.workflowRun(input), input));
|
|
27440
27661
|
server.registerTool("workflow_step", {
|
|
27441
27662
|
title: "Advance Workflow Step",
|
|
27442
|
-
description:
|
|
27663
|
+
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.",
|
|
27443
27664
|
inputSchema: WorkflowStepInputSchema,
|
|
27444
|
-
outputSchema: WorkflowStepOutputSchema,
|
|
27665
|
+
outputSchema: recordOutputSchema("workflow_step", WorkflowStepOutputSchema),
|
|
27445
27666
|
annotations: liveWebToolAnnotations("Advance Workflow Step")
|
|
27446
27667
|
}, async (input) => formatWorkflowStep(await executor.workflowStep(input), input));
|
|
27447
27668
|
server.registerTool("workflow_status", {
|
|
27448
27669
|
title: "Workflow Status",
|
|
27449
|
-
description: "Fetch a hosted workflow run by id and list its current status and artifacts
|
|
27670
|
+
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.",
|
|
27450
27671
|
inputSchema: WorkflowStatusInputSchema,
|
|
27451
|
-
outputSchema: WorkflowStatusOutputSchema,
|
|
27672
|
+
outputSchema: recordOutputSchema("workflow_status", WorkflowStatusOutputSchema),
|
|
27452
27673
|
annotations: liveWebToolAnnotations("Workflow Status")
|
|
27453
27674
|
}, async (input) => formatWorkflowStatus(await executor.workflowStatus(input), input));
|
|
27454
27675
|
server.registerTool("workflow_artifact_read", {
|
|
27455
27676
|
title: "Read Workflow Artifact",
|
|
27456
|
-
description: "Read a workflow artifact back into
|
|
27677
|
+
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.",
|
|
27457
27678
|
inputSchema: WorkflowArtifactReadInputSchema,
|
|
27458
|
-
outputSchema: WorkflowArtifactReadOutputSchema,
|
|
27679
|
+
outputSchema: recordOutputSchema("workflow_artifact_read", WorkflowArtifactReadOutputSchema),
|
|
27459
27680
|
annotations: liveWebToolAnnotations("Read Workflow Artifact")
|
|
27460
27681
|
}, async (input) => formatWorkflowArtifactRead(await executor.workflowArtifactRead(input), input));
|
|
27682
|
+
server.registerTool("report_artifact_read", {
|
|
27683
|
+
title: "Read Report Artifact",
|
|
27684
|
+
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.",
|
|
27685
|
+
inputSchema: ReportArtifactReadInputSchema,
|
|
27686
|
+
outputSchema: recordOutputSchema("report_artifact_read", ReportArtifactReadOutputSchema),
|
|
27687
|
+
annotations: liveWebToolAnnotations("Read Report Artifact")
|
|
27688
|
+
}, async (input) => {
|
|
27689
|
+
const owner = artifactOwnerId(input.artifactId);
|
|
27690
|
+
if (!owner || owner !== ownerId) {
|
|
27691
|
+
return { content: [{ type: "text", text: "Artifact not found or not owned by this caller." }], isError: true };
|
|
27692
|
+
}
|
|
27693
|
+
const window2 = await readArtifactWindow(input.artifactId, input.offset ?? 0, input.maxBytes ?? 2e4);
|
|
27694
|
+
if (!window2) {
|
|
27695
|
+
return { content: [{ type: "text", text: "Artifact not found or expired." }], isError: true };
|
|
27696
|
+
}
|
|
27697
|
+
return {
|
|
27698
|
+
content: [{ type: "text", text: window2.text }],
|
|
27699
|
+
structuredContent: { artifactId: input.artifactId, text: window2.text, totalBytes: window2.totalBytes, nextOffset: window2.nextOffset }
|
|
27700
|
+
};
|
|
27701
|
+
});
|
|
27461
27702
|
server.registerTool("rank_tracker_workflow", {
|
|
27462
27703
|
title: "Rank Tracker Blueprint Builder",
|
|
27463
|
-
description: "Generate a build-ready database, cron
|
|
27704
|
+
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.",
|
|
27464
27705
|
inputSchema: RankTrackerBlueprintInputSchema,
|
|
27465
|
-
outputSchema: RankTrackerBlueprintOutputSchema,
|
|
27706
|
+
outputSchema: recordOutputSchema("rank_tracker_workflow", RankTrackerBlueprintOutputSchema),
|
|
27466
27707
|
annotations: localPlanningToolAnnotations("Rank Tracker Blueprint Builder")
|
|
27467
27708
|
}, async (input) => buildRankTrackerBlueprint(input));
|
|
27468
27709
|
server.registerTool("credits_info", {
|
|
27469
27710
|
title: "MCP Scraper Credits & Costs",
|
|
27470
|
-
description: "Answer questions about MCP Scraper credits, usage limits, and concurrency upgrades
|
|
27711
|
+
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.",
|
|
27471
27712
|
inputSchema: CreditsInfoInputSchema,
|
|
27472
|
-
outputSchema: CreditsInfoOutputSchema,
|
|
27713
|
+
outputSchema: recordOutputSchema("credits_info", CreditsInfoOutputSchema),
|
|
27473
27714
|
annotations: {
|
|
27474
27715
|
title: "MCP Scraper Credits & Costs",
|
|
27475
27716
|
readOnlyHint: true,
|
|
@@ -27479,16 +27720,19 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
27479
27720
|
}
|
|
27480
27721
|
}, async (input) => formatCreditsInfo(await executor.creditsInfo(input), input));
|
|
27481
27722
|
}
|
|
27482
|
-
var import_mcp, import_node_fs8, import_node_path11;
|
|
27723
|
+
var import_mcp, import_node_fs8, import_node_path11, import_node_crypto10;
|
|
27483
27724
|
var init_paa_mcp_server = __esm({
|
|
27484
27725
|
"src/mcp/paa-mcp-server.ts"() {
|
|
27485
27726
|
"use strict";
|
|
27486
27727
|
import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
27487
27728
|
import_node_fs8 = require("fs");
|
|
27488
27729
|
import_node_path11 = require("path");
|
|
27730
|
+
import_node_crypto10 = require("crypto");
|
|
27489
27731
|
init_version();
|
|
27490
27732
|
init_mcp_response_formatter();
|
|
27491
27733
|
init_server_instructions();
|
|
27734
|
+
init_output_schema_registry();
|
|
27735
|
+
init_report_artifact_offload();
|
|
27492
27736
|
init_mcp_tool_schemas();
|
|
27493
27737
|
init_rank_tracker_blueprint();
|
|
27494
27738
|
init_mcp_response_formatter();
|
|
@@ -27941,18 +28185,18 @@ var init_browser_agent_tool_schemas = __esm({
|
|
|
27941
28185
|
label: import_zod32.z.string().optional().describe("Optional human label for this session, shown in the watch console."),
|
|
27942
28186
|
url: import_zod32.z.string().url().optional().describe("Optional URL to navigate to immediately after opening."),
|
|
27943
28187
|
profile: import_zod32.z.string().optional().describe("Optional saved hosted profile name to load a logged-in session for a site."),
|
|
27944
|
-
save_profile_changes: import_zod32.z.boolean().optional().describe("Persist cookies
|
|
27945
|
-
timeout_seconds: import_zod32.z.number().int().min(60).max(259200).optional().describe("
|
|
28188
|
+
save_profile_changes: import_zod32.z.boolean().optional().describe("Persist cookies/storage back to the named profile on close. Avoid parallel sessions writing to the same profile."),
|
|
28189
|
+
timeout_seconds: import_zod32.z.number().int().min(60).max(259200).optional().describe("Session lifetime before auto-termination. Defaults to 600.")
|
|
27946
28190
|
};
|
|
27947
28191
|
BrowserProfileConnectInputSchema = {
|
|
27948
|
-
email: import_zod32.z.string().optional().describe("Account email for the login
|
|
27949
|
-
profile: import_zod32.z.string().optional().describe("Profile to add this login to. Omit to derive
|
|
27950
|
-
domain: import_zod32.z.string().optional().describe("Site to log into, e.g. chatgpt.com, claude.ai, reddit.com. Defaults to chatgpt.com.
|
|
27951
|
-
login_url: import_zod32.z.string().url().optional().describe("Login page for the domain. Defaults to https://<domain
|
|
27952
|
-
url: import_zod32.z.string().url().optional().describe("Deprecated alias for login_url.
|
|
27953
|
-
note: import_zod32.z.string().optional().describe(
|
|
28192
|
+
email: import_zod32.z.string().optional().describe("Account email for the login. Derives a stable profile name and is recorded as a note. Does NOT import existing cookies \u2014 the user signs in fresh."),
|
|
28193
|
+
profile: import_zod32.z.string().optional().describe("Profile to add this login to. Omit to derive from email. A single profile holds MANY logins \u2014 pass the same name with a different domain to stack accounts."),
|
|
28194
|
+
domain: import_zod32.z.string().optional().describe("Site to log into, e.g. chatgpt.com, claude.ai, reddit.com. Defaults to chatgpt.com."),
|
|
28195
|
+
login_url: import_zod32.z.string().url().optional().describe("Login page for the domain. Defaults to https://<domain>/."),
|
|
28196
|
+
url: import_zod32.z.string().url().optional().describe("Deprecated alias for login_url."),
|
|
28197
|
+
note: import_zod32.z.string().optional().describe("Free-text note describing this login. Surfaced by browser_profile_list."),
|
|
27954
28198
|
label: import_zod32.z.string().optional().describe("Optional human label for this sign-in setup session."),
|
|
27955
|
-
timeout_seconds: import_zod32.z.number().int().min(60).max(259200).optional().describe("
|
|
28199
|
+
timeout_seconds: import_zod32.z.number().int().min(60).max(259200).optional().describe("Sign-in session lifetime before auto-termination. Defaults to 600.")
|
|
27956
28200
|
};
|
|
27957
28201
|
BrowserProfileListInputSchema = {
|
|
27958
28202
|
profile: import_zod32.z.string().optional().describe("Profile whose saved logins to list. Omit to derive from email."),
|
|
@@ -27961,7 +28205,7 @@ var init_browser_agent_tool_schemas = __esm({
|
|
|
27961
28205
|
connection_id: import_zod32.z.string().optional().describe("A specific login connection id returned by browser_profile_connect, to poll just that one.")
|
|
27962
28206
|
};
|
|
27963
28207
|
BrowserSessionInputSchema = {
|
|
27964
|
-
session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
28208
|
+
session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions.")
|
|
27965
28209
|
};
|
|
27966
28210
|
BrowserLocateTargetSchema = import_zod32.z.object({
|
|
27967
28211
|
name: import_zod32.z.string().optional().describe("Optional label for this target, echoed in the result."),
|
|
@@ -27973,94 +28217,94 @@ var init_browser_agent_tool_schemas = __esm({
|
|
|
27973
28217
|
message: "target requires selector or text"
|
|
27974
28218
|
});
|
|
27975
28219
|
BrowserLocateInputSchema = {
|
|
27976
|
-
session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
28220
|
+
session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
27977
28221
|
targets: import_zod32.z.array(BrowserLocateTargetSchema).min(1).max(20).describe("DOM targets to locate in the current viewport. Use selectors for exact elements, or text for visible text ranges.")
|
|
27978
28222
|
};
|
|
27979
28223
|
BrowserGotoInputSchema = {
|
|
27980
|
-
session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
28224
|
+
session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
27981
28225
|
url: import_zod32.z.string().url().describe("URL to navigate the browser to.")
|
|
27982
28226
|
};
|
|
27983
28227
|
BrowserClickInputSchema = {
|
|
27984
|
-
session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
28228
|
+
session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
27985
28229
|
x: import_zod32.z.number().describe("X coordinate to click, in screenshot pixels. Use only coordinates from the latest browser_screenshot, browser_read, or browser_locate result; do not guess."),
|
|
27986
28230
|
y: import_zod32.z.number().describe("Y coordinate to click, in screenshot pixels. Use only coordinates from the latest browser_screenshot, browser_read, or browser_locate result; do not guess."),
|
|
27987
28231
|
button: import_zod32.z.enum(["left", "right", "middle"]).default("left").describe("Mouse button."),
|
|
27988
28232
|
num_clicks: import_zod32.z.number().int().min(1).max(3).optional().describe("Number of clicks, e.g. 2 for double-click.")
|
|
27989
28233
|
};
|
|
27990
28234
|
BrowserTypeInputSchema = {
|
|
27991
|
-
session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
28235
|
+
session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
27992
28236
|
text: import_zod32.z.string().describe("Text to type at the current focus. Click a field first to focus it."),
|
|
27993
28237
|
delay: import_zod32.z.number().int().min(0).max(500).optional().describe("Optional per-keystroke delay in ms for human-like typing.")
|
|
27994
28238
|
};
|
|
27995
28239
|
BrowserScrollInputSchema = {
|
|
27996
|
-
session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
28240
|
+
session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
27997
28241
|
delta_y: import_zod32.z.number().default(5).describe("Vertical scroll in wheel units. Positive scrolls down, negative up."),
|
|
27998
28242
|
delta_x: import_zod32.z.number().default(0).describe("Horizontal scroll in wheel units."),
|
|
27999
28243
|
x: import_zod32.z.number().optional().describe("X position to scroll at. Defaults to screen center."),
|
|
28000
28244
|
y: import_zod32.z.number().optional().describe("Y position to scroll at. Defaults to screen center.")
|
|
28001
28245
|
};
|
|
28002
28246
|
BrowserPressInputSchema = {
|
|
28003
|
-
session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
28247
|
+
session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
28004
28248
|
keys: import_zod32.z.array(import_zod32.z.string()).min(1).describe('Keys or combinations to press, e.g. ["Return"], ["Ctrl+a"], ["Ctrl+Shift+Tab"].')
|
|
28005
28249
|
};
|
|
28006
28250
|
BrowserReplayStopInputSchema = {
|
|
28007
|
-
session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
28008
|
-
replay_id: import_zod32.z.string().describe("The replay id returned by browser_replay_start or browser_list_replays.
|
|
28251
|
+
session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
28252
|
+
replay_id: import_zod32.z.string().describe("The replay id returned by browser_replay_start or browser_list_replays.")
|
|
28009
28253
|
};
|
|
28010
28254
|
BrowserReplayDownloadInputSchema = {
|
|
28011
|
-
session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
28012
|
-
replay_id: import_zod32.z.string().describe("The replay id returned by browser_replay_start or browser_list_replays.
|
|
28255
|
+
session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
28256
|
+
replay_id: import_zod32.z.string().describe("The replay id returned by browser_replay_start or browser_list_replays."),
|
|
28013
28257
|
filename: import_zod32.z.string().optional().describe("Optional local MP4 filename. Defaults to a timestamped replay filename.")
|
|
28014
28258
|
};
|
|
28015
28259
|
BrowserReplayAnnotationSchema = import_zod32.z.object({
|
|
28016
|
-
type: import_zod32.z.enum(["box", "circle", "underline", "arrow", "label"]).default("box").describe("Annotation style
|
|
28017
|
-
start_seconds: import_zod32.z.number().min(0).default(0).describe("When the annotation should appear
|
|
28018
|
-
end_seconds: import_zod32.z.number().min(0).optional().describe("When
|
|
28019
|
-
left: import_zod32.z.number().optional().describe("Target left edge in
|
|
28020
|
-
top: import_zod32.z.number().optional().describe("Target top edge in
|
|
28021
|
-
width: import_zod32.z.number().positive().optional().describe("Target width in
|
|
28022
|
-
height: import_zod32.z.number().positive().optional().describe("Target height in
|
|
28023
|
-
x: import_zod32.z.number().optional().describe("Point target x coordinate
|
|
28024
|
-
y: import_zod32.z.number().optional().describe("Point target y coordinate
|
|
28025
|
-
from_x: import_zod32.z.number().optional().describe("Arrow start x coordinate
|
|
28026
|
-
from_y: import_zod32.z.number().optional().describe("Arrow start y coordinate
|
|
28027
|
-
to_x: import_zod32.z.number().optional().describe("Arrow
|
|
28028
|
-
to_y: import_zod32.z.number().optional().describe("Arrow
|
|
28029
|
-
label: import_zod32.z.string().max(120).optional().describe("Optional text callout
|
|
28030
|
-
color: import_zod32.z.string().regex(/^#?[0-9a-fA-F]{6}$/).optional().describe("Annotation color as hex,
|
|
28260
|
+
type: import_zod32.z.enum(["box", "circle", "underline", "arrow", "label"]).default("box").describe("Annotation style."),
|
|
28261
|
+
start_seconds: import_zod32.z.number().min(0).default(0).describe("When the annotation should appear."),
|
|
28262
|
+
end_seconds: import_zod32.z.number().min(0).optional().describe("When it disappears. Defaults to 2s after start_seconds."),
|
|
28263
|
+
left: import_zod32.z.number().optional().describe("Target left edge in screenshot pixels (element.left)."),
|
|
28264
|
+
top: import_zod32.z.number().optional().describe("Target top edge in screenshot pixels (element.top)."),
|
|
28265
|
+
width: import_zod32.z.number().positive().optional().describe("Target width in screenshot pixels (element.width)."),
|
|
28266
|
+
height: import_zod32.z.number().positive().optional().describe("Target height in screenshot pixels (element.height)."),
|
|
28267
|
+
x: import_zod32.z.number().optional().describe("Point target x coordinate when no box is available."),
|
|
28268
|
+
y: import_zod32.z.number().optional().describe("Point target y coordinate when no box is available."),
|
|
28269
|
+
from_x: import_zod32.z.number().optional().describe("Arrow start x coordinate. Defaults near the target."),
|
|
28270
|
+
from_y: import_zod32.z.number().optional().describe("Arrow start y coordinate. Defaults near the target."),
|
|
28271
|
+
to_x: import_zod32.z.number().optional().describe("Arrow end x coordinate. Defaults to the target box center."),
|
|
28272
|
+
to_y: import_zod32.z.number().optional().describe("Arrow end y coordinate. Defaults to the target box center."),
|
|
28273
|
+
label: import_zod32.z.string().max(120).optional().describe("Optional text callout."),
|
|
28274
|
+
color: import_zod32.z.string().regex(/^#?[0-9a-fA-F]{6}$/).optional().describe("Annotation color as hex, e.g. #ff3b30."),
|
|
28031
28275
|
thickness: import_zod32.z.number().min(1).max(24).optional().describe("Stroke thickness in pixels. Defaults to 5.")
|
|
28032
28276
|
});
|
|
28033
28277
|
BrowserReplayMarkInputSchema = {
|
|
28034
|
-
session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
28278
|
+
session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions. A replay must already be recording."),
|
|
28035
28279
|
target: BrowserLocateTargetSchema.describe("The exact DOM element or text range to mark in the current viewport."),
|
|
28036
|
-
type: import_zod32.z.enum(["box", "circle", "underline", "arrow"]).default("box").describe("Annotation style to generate.
|
|
28280
|
+
type: import_zod32.z.enum(["box", "circle", "underline", "arrow"]).default("box").describe("Annotation style to generate."),
|
|
28037
28281
|
label: import_zod32.z.string().max(120).optional().describe("Optional callout text to render near the target."),
|
|
28038
|
-
color: import_zod32.z.string().regex(/^#?[0-9a-fA-F]{6}$/).optional().describe("Annotation color as hex,
|
|
28282
|
+
color: import_zod32.z.string().regex(/^#?[0-9a-fA-F]{6}$/).optional().describe("Annotation color as hex, e.g. #ff3b30."),
|
|
28039
28283
|
thickness: import_zod32.z.number().min(1).max(24).optional().describe("Stroke thickness in pixels. Defaults to 5."),
|
|
28040
28284
|
padding: import_zod32.z.number().min(0).max(80).default(8).describe("Pixels to expand the DOM bounds so the highlight does not touch the text edge."),
|
|
28041
|
-
start_offset_seconds: import_zod32.z.number().min(-5).max(10).default(-0.25).describe("Offset from the current replay time
|
|
28042
|
-
duration_seconds: import_zod32.z.number().min(0.5).max(30).default(4).describe("How long the
|
|
28285
|
+
start_offset_seconds: import_zod32.z.number().min(-5).max(10).default(-0.25).describe("Offset from the current replay time; negative appears just before the mark action."),
|
|
28286
|
+
duration_seconds: import_zod32.z.number().min(0.5).max(30).default(4).describe("How long the annotation should remain visible.")
|
|
28043
28287
|
};
|
|
28044
28288
|
BrowserReplayAnnotateInputSchema = {
|
|
28045
|
-
session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions.
|
|
28046
|
-
replay_id: import_zod32.z.string().describe("The replay id returned by browser_replay_start or browser_list_replays.
|
|
28047
|
-
annotations: import_zod32.z.array(BrowserReplayAnnotationSchema).min(1).max(50).describe("Timed overlay annotations
|
|
28048
|
-
filename: import_zod32.z.string().optional().describe("Optional output MP4 filename. Defaults to a timestamped
|
|
28289
|
+
session_id: import_zod32.z.string().describe("The session id returned by browser_open or browser_list_sessions."),
|
|
28290
|
+
replay_id: import_zod32.z.string().describe("The replay id returned by browser_replay_start or browser_list_replays."),
|
|
28291
|
+
annotations: import_zod32.z.array(BrowserReplayAnnotationSchema).min(1).max(50).describe("Timed overlay annotations. Prefer ones from browser_replay_mark; otherwise use exact DOM bounds from browser_locate."),
|
|
28292
|
+
filename: import_zod32.z.string().optional().describe("Optional output MP4 filename. Defaults to a timestamped filename."),
|
|
28049
28293
|
source_width: import_zod32.z.number().positive().optional().describe("Width of the screenshot coordinate space used for annotations. Defaults to the replay video width."),
|
|
28050
|
-
source_height: import_zod32.z.number().positive().optional().describe("Height of the coordinate space
|
|
28051
|
-
source_left_offset: import_zod32.z.number().min(0).optional().describe("
|
|
28052
|
-
source_top_offset: import_zod32.z.number().min(0).optional().describe("
|
|
28294
|
+
source_height: import_zod32.z.number().positive().optional().describe("Height of the annotation coordinate space; if smaller than the replay video height, the browser chrome offset is inferred."),
|
|
28295
|
+
source_left_offset: import_zod32.z.number().min(0).optional().describe("Explicit X offset from annotation to replay video coordinates. Usually omitted."),
|
|
28296
|
+
source_top_offset: import_zod32.z.number().min(0).optional().describe("Explicit Y offset from annotation to replay video coordinates. Usually omitted.")
|
|
28053
28297
|
};
|
|
28054
28298
|
BrowserListInputSchema = {
|
|
28055
28299
|
include_closed: import_zod32.z.boolean().default(false).describe("Include closed sessions in the list.")
|
|
28056
28300
|
};
|
|
28057
28301
|
BrowserCaptureFanoutInputSchema = {
|
|
28058
|
-
session_id: import_zod32.z.string().describe("
|
|
28059
|
-
prompt: import_zod32.z.string().optional().describe("Optional prompt to type
|
|
28060
|
-
wait_ms: import_zod32.z.number().int().min(0).max(18e4).optional().describe("How long to wait for the answer stream to finish
|
|
28061
|
-
first_party_domain: import_zod32.z.string().optional().describe("The brand/site being researched,
|
|
28062
|
-
reset: import_zod32.z.boolean().default(false).describe("Clear any previously buffered stream for this page before capturing.
|
|
28063
|
-
export: import_zod32.z.boolean().default(false).describe("
|
|
28302
|
+
session_id: import_zod32.z.string().describe("Session id from browser_open. Must be on chatgpt.com or claude.ai, logged in via a saved hosted profile."),
|
|
28303
|
+
prompt: import_zod32.z.string().optional().describe("Optional prompt to type and submit before capturing. Omit to passively capture a prompt the user just ran. Must trigger web search to produce a fan-out."),
|
|
28304
|
+
wait_ms: import_zod32.z.number().int().min(0).max(18e4).optional().describe("How long to wait for the answer stream to finish. Defaults to 90000 when a prompt is sent, 8000 for passive capture."),
|
|
28305
|
+
first_party_domain: import_zod32.z.string().optional().describe("The brand/site being researched, e.g. example.com \u2014 sources on this domain are tagged First-party/vendor."),
|
|
28306
|
+
reset: import_zod32.z.boolean().default(false).describe("Clear any previously buffered stream for this page before capturing."),
|
|
28307
|
+
export: import_zod32.z.boolean().default(false).describe("Write JSON/CSV/TSV/HTML exports to MCP_SCRAPER_OUTPUT_DIR/fanout, returning relative paths.")
|
|
28064
28308
|
};
|
|
28065
28309
|
FanoutSourceOutput = import_zod32.z.object({
|
|
28066
28310
|
url: import_zod32.z.string(),
|
|
@@ -28084,10 +28328,10 @@ var init_browser_agent_tool_schemas = __esm({
|
|
|
28084
28328
|
title: import_zod32.z.string(),
|
|
28085
28329
|
rounds: import_zod32.z.number().int().min(0)
|
|
28086
28330
|
}),
|
|
28087
|
-
queries: import_zod32.z.array(import_zod32.z.string()).describe("Every web-search sub-query
|
|
28088
|
-
browsed_urls: import_zod32.z.array(FanoutSourceOutput).describe("Every researched URL
|
|
28089
|
-
cited_urls: import_zod32.z.array(FanoutSourceOutput).describe("
|
|
28090
|
-
browsed_only: import_zod32.z.array(FanoutSourceOutput).describe("Researched URLs
|
|
28331
|
+
queries: import_zod32.z.array(import_zod32.z.string()).describe("Every web-search sub-query issued, in capture order."),
|
|
28332
|
+
browsed_urls: import_zod32.z.array(FanoutSourceOutput).describe("Every researched URL, cited first."),
|
|
28333
|
+
cited_urls: import_zod32.z.array(FanoutSourceOutput).describe("Researched URLs cited in the final answer."),
|
|
28334
|
+
browsed_only: import_zod32.z.array(FanoutSourceOutput).describe("Researched URLs pulled but not cited."),
|
|
28091
28335
|
snippets: import_zod32.z.array(import_zod32.z.object({ url: import_zod32.z.string(), domain: import_zod32.z.string(), title: import_zod32.z.string(), text: import_zod32.z.string() })),
|
|
28092
28336
|
counts: import_zod32.z.object({
|
|
28093
28337
|
subQueries: import_zod32.z.number().int().min(0),
|
|
@@ -28698,9 +28942,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
28698
28942
|
"browser_profile_connect",
|
|
28699
28943
|
{
|
|
28700
28944
|
title: "Save a Site Login to a Profile",
|
|
28701
|
-
description:
|
|
28945
|
+
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.",
|
|
28702
28946
|
inputSchema: BrowserProfileConnectInputSchema,
|
|
28703
|
-
outputSchema: BrowserProfileConnectOutputSchema,
|
|
28947
|
+
outputSchema: recordOutputSchema("browser_profile_connect", BrowserProfileConnectOutputSchema),
|
|
28704
28948
|
annotations: annotations("Save a Site Login to a Profile")
|
|
28705
28949
|
},
|
|
28706
28950
|
async (input) => {
|
|
@@ -28756,9 +29000,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
28756
29000
|
"browser_profile_list",
|
|
28757
29001
|
{
|
|
28758
29002
|
title: "List Saved Logins in a Profile",
|
|
28759
|
-
description:
|
|
29003
|
+
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.",
|
|
28760
29004
|
inputSchema: BrowserProfileListInputSchema,
|
|
28761
|
-
outputSchema: BrowserProfileListOutputSchema,
|
|
29005
|
+
outputSchema: recordOutputSchema("browser_profile_list", BrowserProfileListOutputSchema),
|
|
28762
29006
|
annotations: annotations("List Saved Logins in a Profile", true)
|
|
28763
29007
|
},
|
|
28764
29008
|
async (input) => {
|
|
@@ -28790,9 +29034,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
28790
29034
|
"browser_open",
|
|
28791
29035
|
{
|
|
28792
29036
|
title: "Open Browser Session",
|
|
28793
|
-
description: "Open a direct no-proxy hosted browser session you can drive. Pass a saved profile name to load a session
|
|
29037
|
+
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.",
|
|
28794
29038
|
inputSchema: BrowserOpenInputSchema,
|
|
28795
|
-
outputSchema: BrowserOpenOutputSchema,
|
|
29039
|
+
outputSchema: recordOutputSchema("browser_open", BrowserOpenOutputSchema),
|
|
28796
29040
|
annotations: annotations("Open Browser Session")
|
|
28797
29041
|
},
|
|
28798
29042
|
async (input) => {
|
|
@@ -28824,9 +29068,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
28824
29068
|
"browser_screenshot",
|
|
28825
29069
|
{
|
|
28826
29070
|
title: "See Page (Screenshot + Elements)",
|
|
28827
|
-
description: "Capture what the browser currently shows
|
|
29071
|
+
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.",
|
|
28828
29072
|
inputSchema: BrowserSessionInputSchema,
|
|
28829
|
-
outputSchema: BrowserScreenshotOutputSchema,
|
|
29073
|
+
outputSchema: recordOutputSchema("browser_screenshot", BrowserScreenshotOutputSchema),
|
|
28830
29074
|
annotations: annotations("See Page", true)
|
|
28831
29075
|
},
|
|
28832
29076
|
async (input) => {
|
|
@@ -28856,9 +29100,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
28856
29100
|
"browser_read",
|
|
28857
29101
|
{
|
|
28858
29102
|
title: "Read Page Text + Elements",
|
|
28859
|
-
description: "Return the page url, title, visible text, and
|
|
29103
|
+
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.",
|
|
28860
29104
|
inputSchema: BrowserSessionInputSchema,
|
|
28861
|
-
outputSchema: BrowserReadOutputSchema,
|
|
29105
|
+
outputSchema: recordOutputSchema("browser_read", BrowserReadOutputSchema),
|
|
28862
29106
|
annotations: annotations("Read Page", true)
|
|
28863
29107
|
},
|
|
28864
29108
|
async (input) => {
|
|
@@ -28880,9 +29124,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
28880
29124
|
"browser_locate",
|
|
28881
29125
|
{
|
|
28882
29126
|
title: "Locate DOM Targets",
|
|
28883
|
-
description: "Locate exact visible DOM elements or text ranges
|
|
29127
|
+
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.",
|
|
28884
29128
|
inputSchema: BrowserLocateInputSchema,
|
|
28885
|
-
outputSchema: BrowserLocateOutputSchema,
|
|
29129
|
+
outputSchema: recordOutputSchema("browser_locate", BrowserLocateOutputSchema),
|
|
28886
29130
|
annotations: annotations("Locate DOM Targets", true)
|
|
28887
29131
|
},
|
|
28888
29132
|
async (input) => {
|
|
@@ -28905,9 +29149,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
28905
29149
|
"browser_goto",
|
|
28906
29150
|
{
|
|
28907
29151
|
title: "Navigate To URL",
|
|
28908
|
-
description: "Navigate an existing browser session to a URL
|
|
29152
|
+
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.",
|
|
28909
29153
|
inputSchema: BrowserGotoInputSchema,
|
|
28910
|
-
outputSchema: BrowserActionOutputSchema,
|
|
29154
|
+
outputSchema: recordOutputSchema("browser_goto", BrowserActionOutputSchema),
|
|
28911
29155
|
annotations: annotations("Navigate To URL")
|
|
28912
29156
|
},
|
|
28913
29157
|
async (input) => {
|
|
@@ -28919,9 +29163,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
28919
29163
|
"browser_click",
|
|
28920
29164
|
{
|
|
28921
29165
|
title: "Click",
|
|
28922
|
-
description: "Click a visible page target using screenshot pixel coordinates. Use
|
|
29166
|
+
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.",
|
|
28923
29167
|
inputSchema: BrowserClickInputSchema,
|
|
28924
|
-
outputSchema: BrowserActionOutputSchema,
|
|
29168
|
+
outputSchema: recordOutputSchema("browser_click", BrowserActionOutputSchema),
|
|
28925
29169
|
annotations: annotations("Click")
|
|
28926
29170
|
},
|
|
28927
29171
|
async (input) => {
|
|
@@ -28938,9 +29182,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
28938
29182
|
"browser_type",
|
|
28939
29183
|
{
|
|
28940
29184
|
title: "Type Text",
|
|
28941
|
-
description: 'Type text into the currently focused browser field.
|
|
29185
|
+
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.',
|
|
28942
29186
|
inputSchema: BrowserTypeInputSchema,
|
|
28943
|
-
outputSchema: BrowserActionOutputSchema,
|
|
29187
|
+
outputSchema: recordOutputSchema("browser_type", BrowserActionOutputSchema),
|
|
28944
29188
|
annotations: annotations("Type Text")
|
|
28945
29189
|
},
|
|
28946
29190
|
async (input) => {
|
|
@@ -28952,9 +29196,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
28952
29196
|
"browser_scroll",
|
|
28953
29197
|
{
|
|
28954
29198
|
title: "Scroll",
|
|
28955
|
-
description: "Scroll the page to reveal more content
|
|
29199
|
+
description: "Scroll the page to reveal more content. Positive delta_y scrolls down; negative scrolls up.",
|
|
28956
29200
|
inputSchema: BrowserScrollInputSchema,
|
|
28957
|
-
outputSchema: BrowserActionOutputSchema,
|
|
29201
|
+
outputSchema: recordOutputSchema("browser_scroll", BrowserActionOutputSchema),
|
|
28958
29202
|
annotations: annotations("Scroll")
|
|
28959
29203
|
},
|
|
28960
29204
|
async (input) => {
|
|
@@ -28971,9 +29215,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
28971
29215
|
"browser_press",
|
|
28972
29216
|
{
|
|
28973
29217
|
title: "Press Keys",
|
|
28974
|
-
description:
|
|
29218
|
+
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.",
|
|
28975
29219
|
inputSchema: BrowserPressInputSchema,
|
|
28976
|
-
outputSchema: BrowserActionOutputSchema,
|
|
29220
|
+
outputSchema: recordOutputSchema("browser_press", BrowserActionOutputSchema),
|
|
28977
29221
|
annotations: annotations("Press Keys")
|
|
28978
29222
|
},
|
|
28979
29223
|
async (input) => {
|
|
@@ -28985,9 +29229,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
28985
29229
|
"browser_replay_start",
|
|
28986
29230
|
{
|
|
28987
29231
|
title: "Start Recording",
|
|
28988
|
-
description: "Start recording an MP4 replay of the session. Returns replay_id
|
|
29232
|
+
description: "Start recording an MP4 replay of the session. Returns replay_id and a download_url. Stop with browser_replay_stop.",
|
|
28989
29233
|
inputSchema: BrowserSessionInputSchema,
|
|
28990
|
-
outputSchema: BrowserReplayStartOutputSchema,
|
|
29234
|
+
outputSchema: recordOutputSchema("browser_replay_start", BrowserReplayStartOutputSchema),
|
|
28991
29235
|
annotations: annotations("Start Recording")
|
|
28992
29236
|
},
|
|
28993
29237
|
async (input) => {
|
|
@@ -29008,9 +29252,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
29008
29252
|
"browser_replay_stop",
|
|
29009
29253
|
{
|
|
29010
29254
|
title: "Stop Recording",
|
|
29011
|
-
description: "Stop a replay recording and expose
|
|
29255
|
+
description: "Stop a replay recording and expose its final view_url/download_url. Use browser_replay_download to save the MP4.",
|
|
29012
29256
|
inputSchema: BrowserReplayStopInputSchema,
|
|
29013
|
-
outputSchema: BrowserReplayStopOutputSchema,
|
|
29257
|
+
outputSchema: recordOutputSchema("browser_replay_stop", BrowserReplayStopOutputSchema),
|
|
29014
29258
|
annotations: annotations("Stop Recording")
|
|
29015
29259
|
},
|
|
29016
29260
|
async (input) => {
|
|
@@ -29031,9 +29275,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
29031
29275
|
"browser_list_replays",
|
|
29032
29276
|
{
|
|
29033
29277
|
title: "List Replay Videos",
|
|
29034
|
-
description: "List replay recordings for a browser session, including
|
|
29278
|
+
description: "List replay recordings for a browser session, including view_url and download_url when available.",
|
|
29035
29279
|
inputSchema: BrowserSessionInputSchema,
|
|
29036
|
-
outputSchema: BrowserListReplaysOutputSchema,
|
|
29280
|
+
outputSchema: recordOutputSchema("browser_list_replays", BrowserListReplaysOutputSchema),
|
|
29037
29281
|
annotations: annotations("List Replay Videos", true)
|
|
29038
29282
|
},
|
|
29039
29283
|
async (input) => {
|
|
@@ -29053,9 +29297,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
29053
29297
|
"browser_replay_download",
|
|
29054
29298
|
{
|
|
29055
29299
|
title: "Download Replay MP4",
|
|
29056
|
-
description: "Download a replay recording
|
|
29300
|
+
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.",
|
|
29057
29301
|
inputSchema: BrowserReplayDownloadInputSchema,
|
|
29058
|
-
outputSchema: BrowserReplayDownloadOutputSchema,
|
|
29302
|
+
outputSchema: recordOutputSchema("browser_replay_download", BrowserReplayDownloadOutputSchema),
|
|
29059
29303
|
annotations: annotations("Download Replay MP4")
|
|
29060
29304
|
},
|
|
29061
29305
|
async (input) => {
|
|
@@ -29077,9 +29321,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
29077
29321
|
"browser_replay_mark",
|
|
29078
29322
|
{
|
|
29079
29323
|
title: "Mark Replay Annotation",
|
|
29080
|
-
description: "While a replay is actively recording, locate one exact DOM target and return a ready-to-use annotation
|
|
29324
|
+
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.",
|
|
29081
29325
|
inputSchema: BrowserReplayMarkInputSchema,
|
|
29082
|
-
outputSchema: BrowserReplayMarkOutputSchema,
|
|
29326
|
+
outputSchema: recordOutputSchema("browser_replay_mark", BrowserReplayMarkOutputSchema),
|
|
29083
29327
|
annotations: annotations("Mark Replay Annotation", true)
|
|
29084
29328
|
},
|
|
29085
29329
|
async (input) => {
|
|
@@ -29127,9 +29371,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
29127
29371
|
"browser_replay_annotate",
|
|
29128
29372
|
{
|
|
29129
29373
|
title: "Annotate Replay MP4",
|
|
29130
|
-
description: "Download a browser replay MP4, render visual annotations over it, and save a new annotated MP4
|
|
29374
|
+
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.",
|
|
29131
29375
|
inputSchema: BrowserReplayAnnotateInputSchema,
|
|
29132
|
-
outputSchema: BrowserReplayAnnotateOutputSchema,
|
|
29376
|
+
outputSchema: recordOutputSchema("browser_replay_annotate", BrowserReplayAnnotateOutputSchema),
|
|
29133
29377
|
annotations: annotations("Annotate Replay MP4")
|
|
29134
29378
|
},
|
|
29135
29379
|
async (input) => {
|
|
@@ -29169,9 +29413,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
29169
29413
|
"browser_close",
|
|
29170
29414
|
{
|
|
29171
29415
|
title: "Close Browser Session",
|
|
29172
|
-
description: "Close and release a browser session when the task is done,
|
|
29416
|
+
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.",
|
|
29173
29417
|
inputSchema: BrowserSessionInputSchema,
|
|
29174
|
-
outputSchema: BrowserCloseOutputSchema,
|
|
29418
|
+
outputSchema: recordOutputSchema("browser_close", BrowserCloseOutputSchema),
|
|
29175
29419
|
annotations: { title: "Close Browser Session", readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
29176
29420
|
},
|
|
29177
29421
|
async (input) => {
|
|
@@ -29190,9 +29434,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
29190
29434
|
"browser_list_sessions",
|
|
29191
29435
|
{
|
|
29192
29436
|
title: "List Browser Sessions",
|
|
29193
|
-
description: "List browser sessions and their status, with a watch_url for each. Use
|
|
29437
|
+
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.",
|
|
29194
29438
|
inputSchema: BrowserListInputSchema,
|
|
29195
|
-
outputSchema: BrowserListSessionsOutputSchema,
|
|
29439
|
+
outputSchema: recordOutputSchema("browser_list_sessions", BrowserListSessionsOutputSchema),
|
|
29196
29440
|
annotations: annotations("List Browser Sessions", true)
|
|
29197
29441
|
},
|
|
29198
29442
|
async (input) => {
|
|
@@ -29212,9 +29456,9 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
29212
29456
|
"query_fanout_workflow",
|
|
29213
29457
|
{
|
|
29214
29458
|
title: "Capture AI Search Fan-Out",
|
|
29215
|
-
description:
|
|
29459
|
+
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.",
|
|
29216
29460
|
inputSchema: BrowserCaptureFanoutInputSchema,
|
|
29217
|
-
outputSchema: BrowserCaptureFanoutOutputSchema,
|
|
29461
|
+
outputSchema: recordOutputSchema("query_fanout_workflow", BrowserCaptureFanoutOutputSchema),
|
|
29218
29462
|
annotations: annotations("Capture AI Search Fan-Out")
|
|
29219
29463
|
},
|
|
29220
29464
|
async (input) => {
|
|
@@ -29279,6 +29523,7 @@ var init_browser_agent_mcp_server = __esm({
|
|
|
29279
29523
|
init_browser_agent_tool_schemas();
|
|
29280
29524
|
init_replay_annotator();
|
|
29281
29525
|
init_outbound_sanitize();
|
|
29526
|
+
init_output_schema_registry();
|
|
29282
29527
|
}
|
|
29283
29528
|
});
|
|
29284
29529
|
|
|
@@ -29394,9 +29639,9 @@ var init_mcp_routes = __esm({
|
|
|
29394
29639
|
enableJsonResponse: true
|
|
29395
29640
|
});
|
|
29396
29641
|
const consoleBaseUrl = process.env.BROWSER_AGENT_CONSOLE_URL?.trim() || baseUrl;
|
|
29397
|
-
const server = buildPaaExtractorMcpServer(executor, { savesReportsLocally: false });
|
|
29642
|
+
const server = buildPaaExtractorMcpServer(executor, { savesReportsLocally: false, ownerId: hashOwnerId(callerKey) });
|
|
29398
29643
|
registerSerpIntelligenceCaptureTools(server, executor);
|
|
29399
|
-
registerBrowserAgentMcpTools(server, { baseUrl, apiKey: callerKey, consoleBaseUrl });
|
|
29644
|
+
registerBrowserAgentMcpTools(server, { baseUrl, apiKey: callerKey, consoleBaseUrl, savesReportsLocally: false });
|
|
29400
29645
|
await server.connect(transport);
|
|
29401
29646
|
return transport.handleRequest(c.req.raw);
|
|
29402
29647
|
} catch {
|
|
@@ -29517,7 +29762,7 @@ async function listAuthConnectionsByProfile(profile) {
|
|
|
29517
29762
|
}
|
|
29518
29763
|
async function createSessionRow(input) {
|
|
29519
29764
|
const db = getDb();
|
|
29520
|
-
const id = `bas_${(0,
|
|
29765
|
+
const id = `bas_${(0, import_node_crypto11.randomUUID)().replace(/-/g, "").slice(0, 20)}`;
|
|
29521
29766
|
await db.execute({
|
|
29522
29767
|
sql: `INSERT INTO browser_agent_sessions (id, runtime_session_id, live_view_url, cdp_ws_url, status, label, user_id, concurrency_lock_id, last_action_at)
|
|
29523
29768
|
VALUES (?, ?, ?, ?, 'open', ?, ?, ?, datetime('now'))`,
|
|
@@ -29586,7 +29831,7 @@ async function recordAction(input) {
|
|
|
29586
29831
|
sql: `INSERT INTO browser_agent_actions (id, session_id, type, params_json, ok, error)
|
|
29587
29832
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
29588
29833
|
args: [
|
|
29589
|
-
`baa_${(0,
|
|
29834
|
+
`baa_${(0, import_node_crypto11.randomUUID)().replace(/-/g, "").slice(0, 20)}`,
|
|
29590
29835
|
input.sessionId,
|
|
29591
29836
|
input.type,
|
|
29592
29837
|
input.params == null ? null : JSON.stringify(input.params),
|
|
@@ -29626,11 +29871,11 @@ async function listReplayRows(sessionId) {
|
|
|
29626
29871
|
});
|
|
29627
29872
|
return res.rows;
|
|
29628
29873
|
}
|
|
29629
|
-
var
|
|
29874
|
+
var import_node_crypto11, _ready2;
|
|
29630
29875
|
var init_browser_agent_db = __esm({
|
|
29631
29876
|
"src/api/browser-agent-db.ts"() {
|
|
29632
29877
|
"use strict";
|
|
29633
|
-
|
|
29878
|
+
import_node_crypto11 = require("crypto");
|
|
29634
29879
|
init_db();
|
|
29635
29880
|
_ready2 = false;
|
|
29636
29881
|
}
|
|
@@ -31756,7 +32001,7 @@ async function getKeys() {
|
|
|
31756
32001
|
const privateKey = await (0, import_jose2.importPKCS8)(pem, "RS256", { extractable: true });
|
|
31757
32002
|
const full = await (0, import_jose2.exportJWK)(privateKey);
|
|
31758
32003
|
const publicJwk = { kty: full.kty, n: full.n, e: full.e };
|
|
31759
|
-
const kid = (0,
|
|
32004
|
+
const kid = (0, import_node_crypto12.createHash)("sha256").update(JSON.stringify({ e: publicJwk.e, kty: publicJwk.kty, n: publicJwk.n })).digest("base64url").slice(0, 16);
|
|
31760
32005
|
publicJwk.kid = kid;
|
|
31761
32006
|
publicJwk.alg = "RS256";
|
|
31762
32007
|
publicJwk.use = "sig";
|
|
@@ -31935,23 +32180,23 @@ async function validateAuthRequest(p) {
|
|
|
31935
32180
|
}
|
|
31936
32181
|
function pkceMatches(verifier, challenge) {
|
|
31937
32182
|
if (!verifier) return false;
|
|
31938
|
-
const computed = (0,
|
|
32183
|
+
const computed = (0, import_node_crypto12.createHash)("sha256").update(verifier).digest("base64url");
|
|
31939
32184
|
return computed === challenge;
|
|
31940
32185
|
}
|
|
31941
32186
|
async function mintAccessToken(identity, scope, plan, audience) {
|
|
31942
32187
|
const { privateKey, kid } = await getKeys();
|
|
31943
|
-
return new import_jose2.SignJWT({ scope, plan }).setProtectedHeader({ alg: "RS256", kid }).setIssuer(ISSUER).setSubject(identity).setAudience(audience).setIssuedAt().setJti((0,
|
|
32188
|
+
return new import_jose2.SignJWT({ scope, plan }).setProtectedHeader({ alg: "RS256", kid }).setIssuer(ISSUER).setSubject(identity).setAudience(audience).setIssuedAt().setJti((0, import_node_crypto12.randomUUID)()).setExpirationTime(`${ACCESS_TTL_SECONDS}s`).sign(privateKey);
|
|
31944
32189
|
}
|
|
31945
32190
|
function tokenErrorResponse(c, error, description, status) {
|
|
31946
32191
|
return c.json({ error, error_description: description }, status);
|
|
31947
32192
|
}
|
|
31948
|
-
var import_hono17, import_cookie,
|
|
32193
|
+
var import_hono17, import_cookie, import_node_crypto12, import_jose2, ISSUER, RESOURCE, SCRAPER_RESOURCE, MEMORY_SCOPES, SCRAPER_SCOPES, SUPPORTED_SCOPES, ACCESS_TTL_SECONDS, REFRESH_TTL_SECONDS, CODE_TTL_SECONDS, ROTATION_GRACE_SECONDS, secureCookies, sessionCookieOptions, cachedKeys, oauthApp;
|
|
31949
32194
|
var init_oauth_routes = __esm({
|
|
31950
32195
|
"src/api/oauth-routes.ts"() {
|
|
31951
32196
|
"use strict";
|
|
31952
32197
|
import_hono17 = require("hono");
|
|
31953
32198
|
import_cookie = require("hono/cookie");
|
|
31954
|
-
|
|
32199
|
+
import_node_crypto12 = require("crypto");
|
|
31955
32200
|
import_jose2 = require("jose");
|
|
31956
32201
|
init_session();
|
|
31957
32202
|
init_db();
|
|
@@ -31966,6 +32211,7 @@ var init_oauth_routes = __esm({
|
|
|
31966
32211
|
ACCESS_TTL_SECONDS = 3600;
|
|
31967
32212
|
REFRESH_TTL_SECONDS = 60 * 60 * 24 * 30;
|
|
31968
32213
|
CODE_TTL_SECONDS = 60;
|
|
32214
|
+
ROTATION_GRACE_SECONDS = 120;
|
|
31969
32215
|
secureCookies = process.env.NODE_ENV === "production" || process.env.VERCEL === "1";
|
|
31970
32216
|
sessionCookieOptions = {
|
|
31971
32217
|
httpOnly: true,
|
|
@@ -32036,7 +32282,7 @@ var init_oauth_routes = __esm({
|
|
|
32036
32282
|
}
|
|
32037
32283
|
}
|
|
32038
32284
|
const clientName = typeof body.client_name === "string" ? body.client_name : null;
|
|
32039
|
-
const clientId = `client_${(0,
|
|
32285
|
+
const clientId = `client_${(0, import_node_crypto12.randomBytes)(16).toString("hex")}`;
|
|
32040
32286
|
await registerClient(clientId, redirectUris, clientName);
|
|
32041
32287
|
console.log("[oauth-dcr] register OK client_id=%s redirect_uris=%s", clientId, JSON.stringify(redirectUris));
|
|
32042
32288
|
return c.json({
|
|
@@ -32089,7 +32335,7 @@ var init_oauth_routes = __esm({
|
|
|
32089
32335
|
if (action === "deny") return redirectWithError(p.redirect_uri, p.state, "access_denied");
|
|
32090
32336
|
if (action !== "approve") return c.text("unsupported action", 400);
|
|
32091
32337
|
const scope = negotiateScope(p.scope, user, p.resource);
|
|
32092
|
-
const code = `code_${(0,
|
|
32338
|
+
const code = `code_${(0, import_node_crypto12.randomBytes)(32).toString("base64url")}`;
|
|
32093
32339
|
const expiresAt = new Date(Date.now() + CODE_TTL_SECONDS * 1e3).toISOString();
|
|
32094
32340
|
await putCode({
|
|
32095
32341
|
code,
|
|
@@ -32128,7 +32374,7 @@ var init_oauth_routes = __esm({
|
|
|
32128
32374
|
const plan = user ? resolvePlan(user) : "free";
|
|
32129
32375
|
const audience = record.resource ?? RESOURCE();
|
|
32130
32376
|
const accessToken = await mintAccessToken(record.identity, record.scope, plan, audience);
|
|
32131
|
-
const refreshToken = `rt_${(0,
|
|
32377
|
+
const refreshToken = `rt_${(0, import_node_crypto12.randomBytes)(40).toString("base64url")}`;
|
|
32132
32378
|
await putRefresh({
|
|
32133
32379
|
refresh_token: refreshToken,
|
|
32134
32380
|
client_id: clientId,
|
|
@@ -32144,6 +32390,13 @@ var init_oauth_routes = __esm({
|
|
|
32144
32390
|
const refreshToken = get("refresh_token") ?? "";
|
|
32145
32391
|
const clientId = get("client_id") ?? "";
|
|
32146
32392
|
const record = await getRefresh(refreshToken);
|
|
32393
|
+
if (record && record.revoked && record.replaced_by && record.rotated_at && Date.now() - new Date(record.rotated_at).getTime() < ROTATION_GRACE_SECONDS * 1e3 && (!clientId || record.client_id === clientId)) {
|
|
32394
|
+
const graceUser = await getUserByEmail(record.identity);
|
|
32395
|
+
const gracePlan = graceUser ? resolvePlan(graceUser) : "free";
|
|
32396
|
+
const graceAudience = record.resource ?? RESOURCE();
|
|
32397
|
+
const graceAccess = await mintAccessToken(record.identity, record.scope, gracePlan, graceAudience);
|
|
32398
|
+
return c.json({ access_token: graceAccess, token_type: "Bearer", expires_in: ACCESS_TTL_SECONDS, refresh_token: record.replaced_by, scope: record.scope });
|
|
32399
|
+
}
|
|
32147
32400
|
if (!record || record.revoked) return tokenErrorResponse(c, "invalid_grant", "Refresh token is invalid or revoked", 400);
|
|
32148
32401
|
if (new Date(record.expires_at).getTime() < Date.now()) return tokenErrorResponse(c, "invalid_grant", "Refresh token has expired", 400);
|
|
32149
32402
|
if (clientId && record.client_id !== clientId) return tokenErrorResponse(c, "invalid_grant", "client_id mismatch", 400);
|
|
@@ -32151,7 +32404,7 @@ var init_oauth_routes = __esm({
|
|
|
32151
32404
|
const plan = user ? resolvePlan(user) : "free";
|
|
32152
32405
|
const audience = record.resource ?? RESOURCE();
|
|
32153
32406
|
const accessToken = await mintAccessToken(record.identity, record.scope, plan, audience);
|
|
32154
|
-
const nextRefresh = `rt_${(0,
|
|
32407
|
+
const nextRefresh = `rt_${(0, import_node_crypto12.randomBytes)(40).toString("base64url")}`;
|
|
32155
32408
|
await rotateRefresh(refreshToken, {
|
|
32156
32409
|
refresh_token: nextRefresh,
|
|
32157
32410
|
client_id: record.client_id,
|