mcp-scraper 0.4.5 → 0.4.6
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 +315 -6
- 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 +89 -1
- 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-5BB6Y3BB.js +7 -0
- package/dist/chunk-5BB6Y3BB.js.map +1 -0
- package/dist/{chunk-NJK5BTUK.js → chunk-5GAD2AN2.js} +2 -2
- package/dist/{chunk-FUVQR6GK.js → chunk-GL4BW4CP.js} +49 -1
- package/dist/chunk-GL4BW4CP.js.map +1 -0
- package/dist/{chunk-3UH3BEIZ.js → chunk-HE2LQPJ2.js} +2 -2
- package/dist/{chunk-WI4A3KFQ.js → chunk-IFXACD4K.js} +90 -2
- package/dist/chunk-IFXACD4K.js.map +1 -0
- package/dist/{chunk-5LO6EHEH.js → chunk-IVQYNY45.js} +14 -3
- package/dist/chunk-IVQYNY45.js.map +1 -0
- package/dist/{chunk-4ZZWFI6L.js → chunk-NYAWN7CZ.js} +3 -3
- package/dist/{db-H3S3M6KK.js → db-LIOTIWVN.js} +6 -2
- package/dist/{extract-bundle-UKE273TV.js → extract-bundle-COS56ZDO.js} +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/{server-XCDFHHLE.js → server-R3KVQNOT.js} +170 -13
- package/dist/server-R3KVQNOT.js.map +1 -0
- package/dist/{site-extract-repository-THBVEXMP.js → site-extract-repository-LI2S3S6E.js} +4 -4
- package/dist/{worker-YHKUJR5P.js → worker-VASAJ7FZ.js} +5 -5
- package/package.json +2 -1
- package/dist/chunk-5LO6EHEH.js.map +0 -1
- package/dist/chunk-FUVQR6GK.js.map +0 -1
- package/dist/chunk-UGQC2FOX.js +0 -7
- package/dist/chunk-UGQC2FOX.js.map +0 -1
- package/dist/chunk-WI4A3KFQ.js.map +0 -1
- package/dist/server-XCDFHHLE.js.map +0 -1
- /package/dist/{chunk-NJK5BTUK.js.map → chunk-5GAD2AN2.js.map} +0 -0
- /package/dist/{chunk-3UH3BEIZ.js.map → chunk-HE2LQPJ2.js.map} +0 -0
- /package/dist/{chunk-4ZZWFI6L.js.map → chunk-NYAWN7CZ.js.map} +0 -0
- /package/dist/{db-H3S3M6KK.js.map → db-LIOTIWVN.js.map} +0 -0
- /package/dist/{extract-bundle-UKE273TV.js.map → extract-bundle-COS56ZDO.js.map} +0 -0
- /package/dist/{site-extract-repository-THBVEXMP.js.map → site-extract-repository-LI2S3S6E.js.map} +0 -0
- /package/dist/{worker-YHKUJR5P.js.map → worker-VASAJ7FZ.js.map} +0 -0
package/dist/bin/api-server.cjs
CHANGED
|
@@ -3617,6 +3617,7 @@ __export(db_exports, {
|
|
|
3617
3617
|
getLedger: () => getLedger,
|
|
3618
3618
|
getMemoryPlan: () => getMemoryPlan,
|
|
3619
3619
|
getMemoryProvisioned: () => getMemoryProvisioned,
|
|
3620
|
+
getPageSnapshot: () => getPageSnapshot,
|
|
3620
3621
|
getRefresh: () => getRefresh,
|
|
3621
3622
|
getUserByApiKey: () => getUserByApiKey,
|
|
3622
3623
|
getUserByEmail: () => getUserByEmail,
|
|
@@ -3672,6 +3673,7 @@ __export(db_exports, {
|
|
|
3672
3673
|
startHarvestAttempt: () => startHarvestAttempt,
|
|
3673
3674
|
stripeEventAlreadyProcessed: () => stripeEventAlreadyProcessed,
|
|
3674
3675
|
updateKpoJobState: () => updateKpoJobState,
|
|
3676
|
+
upsertPageSnapshot: () => upsertPageSnapshot,
|
|
3675
3677
|
verifyPassword: () => verifyPassword
|
|
3676
3678
|
});
|
|
3677
3679
|
function getDb() {
|
|
@@ -4109,6 +4111,20 @@ async function migrate() {
|
|
|
4109
4111
|
} catch {
|
|
4110
4112
|
}
|
|
4111
4113
|
}
|
|
4114
|
+
await db.execute(`
|
|
4115
|
+
CREATE TABLE IF NOT EXISTS page_snapshots (
|
|
4116
|
+
user_id INTEGER NOT NULL REFERENCES users(id),
|
|
4117
|
+
url TEXT NOT NULL,
|
|
4118
|
+
content_hash TEXT NOT NULL,
|
|
4119
|
+
content TEXT NOT NULL,
|
|
4120
|
+
title TEXT,
|
|
4121
|
+
content_bytes INTEGER NOT NULL,
|
|
4122
|
+
truncated INTEGER NOT NULL DEFAULT 0,
|
|
4123
|
+
checked_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
4124
|
+
PRIMARY KEY (user_id, url)
|
|
4125
|
+
)
|
|
4126
|
+
`);
|
|
4127
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS page_snapshots_user_checked_at ON page_snapshots(user_id, checked_at DESC)`);
|
|
4112
4128
|
}
|
|
4113
4129
|
async function registerClient(clientId, redirectUris, name) {
|
|
4114
4130
|
await getDb().execute({
|
|
@@ -4539,6 +4555,38 @@ async function listJobs(userId) {
|
|
|
4539
4555
|
const res = await getDb().execute({ sql: "SELECT * FROM jobs WHERE user_id = ? ORDER BY created_at DESC LIMIT 50", args: [userId] });
|
|
4540
4556
|
return res.rows.map((r) => deserialize(rowToRawJob(r)));
|
|
4541
4557
|
}
|
|
4558
|
+
async function getPageSnapshot(userId, url) {
|
|
4559
|
+
const res = await getDb().execute({
|
|
4560
|
+
sql: "SELECT content_hash, content, title, content_bytes, truncated, checked_at FROM page_snapshots WHERE user_id = ? AND url = ?",
|
|
4561
|
+
args: [userId, url]
|
|
4562
|
+
});
|
|
4563
|
+
const row = res.rows[0];
|
|
4564
|
+
if (!row) return null;
|
|
4565
|
+
return {
|
|
4566
|
+
contentHash: String(row.content_hash),
|
|
4567
|
+
content: String(row.content),
|
|
4568
|
+
title: row.title == null ? null : String(row.title),
|
|
4569
|
+
contentBytes: Number(row.content_bytes),
|
|
4570
|
+
truncated: Number(row.truncated) === 1,
|
|
4571
|
+
checkedAt: String(row.checked_at)
|
|
4572
|
+
};
|
|
4573
|
+
}
|
|
4574
|
+
async function upsertPageSnapshot(userId, url, snapshot) {
|
|
4575
|
+
await getDb().execute({
|
|
4576
|
+
sql: `
|
|
4577
|
+
INSERT INTO page_snapshots (user_id, url, content_hash, content, title, content_bytes, truncated, checked_at)
|
|
4578
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))
|
|
4579
|
+
ON CONFLICT(user_id, url) DO UPDATE SET
|
|
4580
|
+
content_hash = excluded.content_hash,
|
|
4581
|
+
content = excluded.content,
|
|
4582
|
+
title = excluded.title,
|
|
4583
|
+
content_bytes = excluded.content_bytes,
|
|
4584
|
+
truncated = excluded.truncated,
|
|
4585
|
+
checked_at = excluded.checked_at
|
|
4586
|
+
`,
|
|
4587
|
+
args: [userId, url, snapshot.contentHash, snapshot.content, snapshot.title, snapshot.contentBytes, snapshot.truncated ? 1 : 0]
|
|
4588
|
+
});
|
|
4589
|
+
}
|
|
4542
4590
|
function rowToRequestEvent(row) {
|
|
4543
4591
|
const rawResult = row.result != null ? String(row.result) : null;
|
|
4544
4592
|
return {
|
|
@@ -10046,7 +10094,8 @@ var init_rates = __esm({
|
|
|
10046
10094
|
trustpilot_reviews: 500,
|
|
10047
10095
|
trustpilot_review: 100,
|
|
10048
10096
|
g2_reviews: 500,
|
|
10049
|
-
g2_review: 150
|
|
10097
|
+
g2_review: 150,
|
|
10098
|
+
diff_page: 100
|
|
10050
10099
|
};
|
|
10051
10100
|
MC_PER_BROWSER_MS = MC_COSTS.browser_minute / 6e4;
|
|
10052
10101
|
BROWSER_OPEN_MIN_BALANCE_MC = 1e3;
|
|
@@ -10249,6 +10298,14 @@ var init_rates = __esm({
|
|
|
10249
10298
|
credits: mcToCredits(MC_COSTS.g2_review),
|
|
10250
10299
|
unit: "per review card",
|
|
10251
10300
|
notes: "Charged per review actually extracted (each card carries up to 3 Q&A sections), billed down automatically if the balance runs out mid-page."
|
|
10301
|
+
},
|
|
10302
|
+
{
|
|
10303
|
+
key: "diff_page",
|
|
10304
|
+
label: "Page change check",
|
|
10305
|
+
aliases: ["diff_page", "page diff", "change detection"],
|
|
10306
|
+
credits: mcToCredits(MC_COSTS.diff_page),
|
|
10307
|
+
unit: "per check",
|
|
10308
|
+
notes: "Same cost as a single page extract \u2014 one scrape is performed per check, then compared against your last stored snapshot for that URL."
|
|
10252
10309
|
}
|
|
10253
10310
|
];
|
|
10254
10311
|
CONCURRENCY_PRICE_ID = "price_1Ta1NRS8aAcsk3TGwsRnYbix";
|
|
@@ -10334,7 +10391,9 @@ var init_rates = __esm({
|
|
|
10334
10391
|
G2_REVIEWS: "g2_reviews",
|
|
10335
10392
|
G2_REVIEWS_REFUND: "g2_reviews_refund",
|
|
10336
10393
|
G2_REVIEW: "g2_review",
|
|
10337
|
-
G2_REVIEW_REFUND: "g2_review_refund"
|
|
10394
|
+
G2_REVIEW_REFUND: "g2_review_refund",
|
|
10395
|
+
DIFF_PAGE: "diff_page",
|
|
10396
|
+
DIFF_PAGE_REFUND: "diff_page_refund"
|
|
10338
10397
|
};
|
|
10339
10398
|
MEMORY_AI_MARGIN_MULTIPLE = 3;
|
|
10340
10399
|
MC_PER_USD = SUBSCRIPTION_TIERS["price_1TmiHRS8aAcsk3TGwmSNfNIa"].credits_mc / SUBSCRIPTION_TIERS["price_1TmiHRS8aAcsk3TGwmSNfNIa"].monthly_usd;
|
|
@@ -13113,7 +13172,7 @@ var init_api_auth = __esm({
|
|
|
13113
13172
|
});
|
|
13114
13173
|
|
|
13115
13174
|
// src/api/server-schemas.ts
|
|
13116
|
-
var import_zod13, HarvestBodySchema, ExtractUrlBodySchema, MapUrlsBodySchema, ExtractSiteBodySchema, YoutubeHarvestBodySchema, YoutubeTranscribeBodySchema;
|
|
13175
|
+
var import_zod13, HarvestBodySchema, ExtractUrlBodySchema, DiffPageBodySchema, MapUrlsBodySchema, ExtractSiteBodySchema, YoutubeHarvestBodySchema, YoutubeTranscribeBodySchema;
|
|
13117
13176
|
var init_server_schemas = __esm({
|
|
13118
13177
|
"src/api/server-schemas.ts"() {
|
|
13119
13178
|
"use strict";
|
|
@@ -13144,6 +13203,11 @@ var init_server_schemas = __esm({
|
|
|
13144
13203
|
depositToVault: import_zod13.z.boolean().optional(),
|
|
13145
13204
|
vaultName: import_zod13.z.string().trim().min(1).max(120).optional()
|
|
13146
13205
|
});
|
|
13206
|
+
DiffPageBodySchema = import_zod13.z.object({
|
|
13207
|
+
url: import_zod13.z.string().min(1, "url is required"),
|
|
13208
|
+
allowLocal: import_zod13.z.boolean().optional(),
|
|
13209
|
+
resetBaseline: import_zod13.z.boolean().optional()
|
|
13210
|
+
});
|
|
13147
13211
|
MapUrlsBodySchema = import_zod13.z.object({
|
|
13148
13212
|
url: import_zod13.z.string().min(1, "url is required"),
|
|
13149
13213
|
maxUrls: import_zod13.z.number().int().min(1).max(2e3).optional(),
|
|
@@ -19330,6 +19394,55 @@ ${headingSection}${kpoSection}${brandingSection}${bodySectionFull}${memSection}$
|
|
|
19330
19394
|
}
|
|
19331
19395
|
return { ...textResult, structuredContent };
|
|
19332
19396
|
}
|
|
19397
|
+
function formatDiffPage(raw, input) {
|
|
19398
|
+
const parsed = parseData(raw);
|
|
19399
|
+
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
19400
|
+
const d = parsed.data;
|
|
19401
|
+
const url = d.url ?? input.url;
|
|
19402
|
+
const title = d.title ?? "Untitled";
|
|
19403
|
+
const statusLine = d.status === "baseline" ? d.isReset ? "\u{1F195} **Baseline reset** \u2014 prior history discarded, this check is now the new baseline." : "\u{1F195} **Baseline captured** \u2014 first check for this URL, nothing to compare yet." : d.status === "unchanged" ? `\u2705 **No changes** since ${d.previousCheckedAt ?? "the last check"}.` : `\u{1F504} **Changed** since ${d.previousCheckedAt ?? "the last check"} \u2014 +${d.summary.linesAdded}/-${d.summary.linesRemoved} lines (${d.summary.percentChanged ?? 0}% of content).`;
|
|
19404
|
+
const previewHunks = d.hunks.slice(0, DIFF_PAGE_PREVIEW_HUNKS);
|
|
19405
|
+
const diffBlock = previewHunks.length ? [
|
|
19406
|
+
"\n## Diff",
|
|
19407
|
+
"```diff",
|
|
19408
|
+
...previewHunks.flatMap((h) => h.lines.map((l) => `${h.type === "added" ? "+" : "-"} ${l}`)),
|
|
19409
|
+
"```",
|
|
19410
|
+
previewHunks.length < d.hunks.length ? `*(showing ${previewHunks.length} of ${d.hunks.length} returned hunks)*` : ""
|
|
19411
|
+
].filter(Boolean).join("\n") : "";
|
|
19412
|
+
const truncationNotes = [
|
|
19413
|
+
d.contentTruncated ? "\n\u26A0\uFE0F Page content exceeded the storable cap and was truncated before hashing/diffing \u2014 changes past that point are not visible to this comparison." : "",
|
|
19414
|
+
d.hunksTruncatedReason ? `
|
|
19415
|
+
\u26A0\uFE0F ${d.hunksTruncatedReason}` : ""
|
|
19416
|
+
].filter(Boolean).join("\n");
|
|
19417
|
+
const tips = `
|
|
19418
|
+
---
|
|
19419
|
+
\u{1F4A1} **Tips**
|
|
19420
|
+
- Call \`diff_page\` again anytime to re-check \u2014 this tool is on-demand, not automatic.
|
|
19421
|
+
- Want the page's current content with nothing to compare? use \`extract_url\`.`;
|
|
19422
|
+
const full = `# Page Diff: ${url}
|
|
19423
|
+
**${title}**
|
|
19424
|
+
|
|
19425
|
+
${statusLine}${diffBlock}${truncationNotes}${tips}`;
|
|
19426
|
+
return {
|
|
19427
|
+
...oneBlock(full),
|
|
19428
|
+
structuredContent: {
|
|
19429
|
+
url,
|
|
19430
|
+
title: d.title,
|
|
19431
|
+
status: d.status,
|
|
19432
|
+
isReset: d.isReset,
|
|
19433
|
+
previousCheckedAt: d.previousCheckedAt,
|
|
19434
|
+
currentCheckedAt: d.currentCheckedAt,
|
|
19435
|
+
contentHash: d.contentHash,
|
|
19436
|
+
previousContentHash: d.previousContentHash,
|
|
19437
|
+
summary: d.summary,
|
|
19438
|
+
hunks: d.hunks,
|
|
19439
|
+
contentTruncated: d.contentTruncated,
|
|
19440
|
+
hunksTruncated: d.hunksTruncated,
|
|
19441
|
+
hunksTruncatedReason: d.hunksTruncatedReason,
|
|
19442
|
+
totalChangedLineCount: d.totalChangedLineCount
|
|
19443
|
+
}
|
|
19444
|
+
};
|
|
19445
|
+
}
|
|
19333
19446
|
async function formatMapSiteUrls(raw, input, ctx) {
|
|
19334
19447
|
const parsed = parseData(raw);
|
|
19335
19448
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
@@ -21054,7 +21167,7 @@ ${rows}` : ""
|
|
|
21054
21167
|
}
|
|
21055
21168
|
};
|
|
21056
21169
|
}
|
|
21057
|
-
var import_node_fs6, import_node_os6, import_node_path8, INLINE_BUDGET_BYTES, STRUCTURED_ARRAY_CAP, reportSavingEnabled, BULK_PAGE_THRESHOLD, BULK_URL_THRESHOLD;
|
|
21170
|
+
var import_node_fs6, import_node_os6, import_node_path8, INLINE_BUDGET_BYTES, STRUCTURED_ARRAY_CAP, reportSavingEnabled, BULK_PAGE_THRESHOLD, BULK_URL_THRESHOLD, DIFF_PAGE_PREVIEW_HUNKS;
|
|
21058
21171
|
var init_mcp_response_formatter = __esm({
|
|
21059
21172
|
"src/mcp/mcp-response-formatter.ts"() {
|
|
21060
21173
|
"use strict";
|
|
@@ -21073,6 +21186,7 @@ var init_mcp_response_formatter = __esm({
|
|
|
21073
21186
|
reportSavingEnabled = true;
|
|
21074
21187
|
BULK_PAGE_THRESHOLD = 25;
|
|
21075
21188
|
BULK_URL_THRESHOLD = 500;
|
|
21189
|
+
DIFF_PAGE_PREVIEW_HUNKS = 20;
|
|
21076
21190
|
}
|
|
21077
21191
|
});
|
|
21078
21192
|
|
|
@@ -27003,7 +27117,7 @@ var PACKAGE_VERSION;
|
|
|
27003
27117
|
var init_version = __esm({
|
|
27004
27118
|
"src/version.ts"() {
|
|
27005
27119
|
"use strict";
|
|
27006
|
-
PACKAGE_VERSION = "0.4.
|
|
27120
|
+
PACKAGE_VERSION = "0.4.6";
|
|
27007
27121
|
}
|
|
27008
27122
|
});
|
|
27009
27123
|
|
|
@@ -27142,7 +27256,7 @@ var init_output_schema_registry = __esm({
|
|
|
27142
27256
|
});
|
|
27143
27257
|
|
|
27144
27258
|
// src/mcp/mcp-tool-schemas.ts
|
|
27145
|
-
var import_zod33, HarvestPaaInputSchema, ExtractUrlInputSchema, MapSiteUrlsInputSchema, ExtractSiteInputSchema, AuditSiteInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, RedditThreadInputSchema, VideoFrameAnalysisInputSchema, VideoFrameAnalysisStatusInputSchema, FacebookAdTranscribeInputSchema, FacebookVideoTranscribeInputSchema, GoogleAdsSearchInputSchema, GoogleAdsPageIntelInputSchema, GoogleAdsTranscribeInputSchema, InstagramProfileContentInputSchema, InstagramMediaDownloadInputSchema, MapsPlaceIntelInputSchema, TrustpilotReviewsInputSchema, G2ReviewsInputSchema, ReviewCardSchema, MapsSearchInputSchema, DirectoryWorkflowInputSchema, ArtifactPointerOutputSchema, RankTrackerModeSchema, RankTrackerBlueprintInputSchema, NullableString, MapsSearchAttemptOutput, MapsSearchOutputSchema, DirectoryMapsBusinessOutput, DirectoryWorkflowOutputSchema, RankTrackerToolPlanOutput, RankTrackerTableOutput, RankTrackerCronJobOutput, RankTrackerBlueprintOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, ExtractUrlOutputSchema, ExtractSiteOutputSchema, AuditSiteOutputSchema, MapsPlaceIntelOutputSchema, TrustpilotReviewsOutputSchema, G2ReviewsOutputSchema, 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;
|
|
27259
|
+
var import_zod33, HarvestPaaInputSchema, ExtractUrlInputSchema, DiffPageInputSchema, MapSiteUrlsInputSchema, ExtractSiteInputSchema, AuditSiteInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, RedditThreadInputSchema, VideoFrameAnalysisInputSchema, VideoFrameAnalysisStatusInputSchema, FacebookAdTranscribeInputSchema, FacebookVideoTranscribeInputSchema, GoogleAdsSearchInputSchema, GoogleAdsPageIntelInputSchema, GoogleAdsTranscribeInputSchema, InstagramProfileContentInputSchema, InstagramMediaDownloadInputSchema, MapsPlaceIntelInputSchema, TrustpilotReviewsInputSchema, G2ReviewsInputSchema, ReviewCardSchema, MapsSearchInputSchema, DirectoryWorkflowInputSchema, ArtifactPointerOutputSchema, RankTrackerModeSchema, RankTrackerBlueprintInputSchema, NullableString, MapsSearchAttemptOutput, MapsSearchOutputSchema, DirectoryMapsBusinessOutput, DirectoryWorkflowOutputSchema, RankTrackerToolPlanOutput, RankTrackerTableOutput, RankTrackerCronJobOutput, RankTrackerBlueprintOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, ExtractUrlOutputSchema, DiffPageOutputSchema, ExtractSiteOutputSchema, AuditSiteOutputSchema, MapsPlaceIntelOutputSchema, TrustpilotReviewsOutputSchema, G2ReviewsOutputSchema, 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;
|
|
27146
27260
|
var init_mcp_tool_schemas = __esm({
|
|
27147
27261
|
"src/mcp/mcp-tool-schemas.ts"() {
|
|
27148
27262
|
"use strict";
|
|
@@ -27170,6 +27284,11 @@ var init_mcp_tool_schemas = __esm({
|
|
|
27170
27284
|
depositToVault: import_zod33.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."),
|
|
27171
27285
|
vaultName: import_zod33.z.string().trim().min(1).max(120).optional().describe("Optional vault to deposit into. Defaults to the user's personal vault.")
|
|
27172
27286
|
};
|
|
27287
|
+
DiffPageInputSchema = {
|
|
27288
|
+
url: import_zod33.z.string().url().describe("Public http/https URL to check for changes since the last diff_page call."),
|
|
27289
|
+
allowLocal: import_zod33.z.boolean().default(false).describe("Allow localhost and private-network URLs. Local development only."),
|
|
27290
|
+
resetBaseline: import_zod33.z.boolean().default(false).describe("Discard any previously stored snapshot for this URL and capture the current content as a fresh baseline instead of diffing against history. Use when you deliberately want to restart change tracking.")
|
|
27291
|
+
};
|
|
27173
27292
|
MapSiteUrlsInputSchema = {
|
|
27174
27293
|
url: import_zod33.z.string().url().describe("Public website URL or domain to crawl for internal URLs. Use before extract_site when the user asks to audit/map/crawl a site."),
|
|
27175
27294
|
maxUrls: import_zod33.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.")
|
|
@@ -27563,6 +27682,29 @@ var init_mcp_tool_schemas = __esm({
|
|
|
27563
27682
|
error: import_zod33.z.string().optional()
|
|
27564
27683
|
}).optional()
|
|
27565
27684
|
};
|
|
27685
|
+
DiffPageOutputSchema = {
|
|
27686
|
+
url: import_zod33.z.string(),
|
|
27687
|
+
title: NullableString,
|
|
27688
|
+
status: import_zod33.z.enum(["baseline", "unchanged", "changed"]).describe('"baseline" = first-ever check for this URL, or resetBaseline was used \u2014 nothing to compare against. "unchanged" = content hash matched the stored snapshot. "changed" = a diff was computed.'),
|
|
27689
|
+
isReset: import_zod33.z.boolean().describe("True only when resetBaseline discarded a real prior snapshot \u2014 distinguishes an explicit reset from a URL's true first-ever check."),
|
|
27690
|
+
previousCheckedAt: NullableString.describe("ISO timestamp of the snapshot this was compared against, or null if there was none."),
|
|
27691
|
+
currentCheckedAt: import_zod33.z.string().describe("ISO timestamp of this check, now stored as the new snapshot."),
|
|
27692
|
+
contentHash: import_zod33.z.string().describe("sha256 of the full (untruncated) page content just captured."),
|
|
27693
|
+
previousContentHash: NullableString,
|
|
27694
|
+
summary: import_zod33.z.object({
|
|
27695
|
+
linesAdded: import_zod33.z.number().int().min(0),
|
|
27696
|
+
linesRemoved: import_zod33.z.number().int().min(0),
|
|
27697
|
+
percentChanged: import_zod33.z.number().min(0).max(100).nullable().describe('Proportion of changed lines relative to the larger of the two versions. Null when status is "baseline".')
|
|
27698
|
+
}),
|
|
27699
|
+
hunks: import_zod33.z.array(import_zod33.z.object({
|
|
27700
|
+
type: import_zod33.z.enum(["added", "removed"]).describe("Unchanged context lines are omitted \u2014 only added/removed lines are returned."),
|
|
27701
|
+
lines: import_zod33.z.array(import_zod33.z.string())
|
|
27702
|
+
})).describe("Ordered added/removed line hunks, capped for response size \u2014 see hunksTruncated."),
|
|
27703
|
+
contentTruncated: import_zod33.z.boolean().describe("True if the scraped page exceeded the 250,000-character storable cap and was truncated before hashing/diffing/storing \u2014 changes past that point are invisible to this comparison."),
|
|
27704
|
+
hunksTruncated: import_zod33.z.boolean().describe("True if the hunks list above was capped for response size \u2014 see hunksTruncatedReason."),
|
|
27705
|
+
hunksTruncatedReason: NullableString,
|
|
27706
|
+
totalChangedLineCount: import_zod33.z.number().int().min(0).describe("Total changed lines found before any hunksTruncated capping was applied.")
|
|
27707
|
+
};
|
|
27566
27708
|
ExtractSiteOutputSchema = {
|
|
27567
27709
|
url: import_zod33.z.string(),
|
|
27568
27710
|
pageCount: import_zod33.z.number().int().min(0),
|
|
@@ -28575,6 +28717,13 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
|
|
|
28575
28717
|
outputSchema: recordOutputSchema("extract_url", ExtractUrlOutputSchema),
|
|
28576
28718
|
annotations: liveWebToolAnnotations("Single URL Extract")
|
|
28577
28719
|
}, async (input) => formatExtractUrl(await executor.extractUrl(input), input));
|
|
28720
|
+
server.registerTool("diff_page", {
|
|
28721
|
+
title: "Page Change Check",
|
|
28722
|
+
description: "Check whether a public URL has changed since you last checked it with this tool: scrapes the current page, diffs it against your last stored snapshot for that URL, and returns what was added or removed (or confirms no change). Stores the new snapshot as the baseline for next time \u2014 on-demand only, no automatic recurring checks. Use extract_url instead when you just want the page's current content with nothing to compare against.",
|
|
28723
|
+
inputSchema: DiffPageInputSchema,
|
|
28724
|
+
outputSchema: recordOutputSchema("diff_page", DiffPageOutputSchema),
|
|
28725
|
+
annotations: liveWebToolAnnotations("Page Change Check")
|
|
28726
|
+
}, async (input) => formatDiffPage(await executor.diffPage(input), input));
|
|
28578
28727
|
server.registerTool("map_site_urls", {
|
|
28579
28728
|
title: "Site URL Map",
|
|
28580
28729
|
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.")}`,
|
|
@@ -29006,6 +29155,9 @@ var init_http_mcp_tool_executor = __esm({
|
|
|
29006
29155
|
extractUrl(input) {
|
|
29007
29156
|
return this.call("/extract-url", input);
|
|
29008
29157
|
}
|
|
29158
|
+
diffPage(input) {
|
|
29159
|
+
return this.call("/diff-page", input);
|
|
29160
|
+
}
|
|
29009
29161
|
mapSiteUrls(input) {
|
|
29010
29162
|
return this.call("/map-urls", input);
|
|
29011
29163
|
}
|
|
@@ -37392,6 +37544,77 @@ var init_site_audit_worker = __esm({
|
|
|
37392
37544
|
}
|
|
37393
37545
|
});
|
|
37394
37546
|
|
|
37547
|
+
// src/api/page-diff.ts
|
|
37548
|
+
function sha256Hex(value) {
|
|
37549
|
+
return (0, import_node_crypto14.createHash)("sha256").update(value).digest("hex");
|
|
37550
|
+
}
|
|
37551
|
+
function truncateForStorage(value, maxChars = MAX_SNAPSHOT_CONTENT_CHARS) {
|
|
37552
|
+
if (value.length <= maxChars) return { value, truncated: false };
|
|
37553
|
+
return { value: value.slice(0, maxChars), truncated: true };
|
|
37554
|
+
}
|
|
37555
|
+
function changeLines(change) {
|
|
37556
|
+
const lines = change.value.split("\n");
|
|
37557
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
37558
|
+
return lines;
|
|
37559
|
+
}
|
|
37560
|
+
function diffPageContent(oldContent, newContent) {
|
|
37561
|
+
const changes = (0, import_diff.diffLines)(oldContent, newContent);
|
|
37562
|
+
const rawHunks = [];
|
|
37563
|
+
let linesAdded = 0;
|
|
37564
|
+
let linesRemoved = 0;
|
|
37565
|
+
let totalChangedLineCount = 0;
|
|
37566
|
+
for (const change of changes) {
|
|
37567
|
+
if (!change.added && !change.removed) continue;
|
|
37568
|
+
const lines = changeLines(change);
|
|
37569
|
+
totalChangedLineCount += lines.length;
|
|
37570
|
+
if (change.added) linesAdded += lines.length;
|
|
37571
|
+
if (change.removed) linesRemoved += lines.length;
|
|
37572
|
+
rawHunks.push({ type: change.added ? "added" : "removed", lines });
|
|
37573
|
+
}
|
|
37574
|
+
let hunksTruncated = false;
|
|
37575
|
+
const hunksWithinCount = rawHunks.length > MAX_DIFF_HUNKS ? rawHunks.slice(0, MAX_DIFF_HUNKS) : rawHunks;
|
|
37576
|
+
if (rawHunks.length > MAX_DIFF_HUNKS) hunksTruncated = true;
|
|
37577
|
+
const hunks = [];
|
|
37578
|
+
let emittedLines = 0;
|
|
37579
|
+
for (const hunk of hunksWithinCount) {
|
|
37580
|
+
if (emittedLines >= MAX_DIFF_LINES_PER_RESPONSE) {
|
|
37581
|
+
hunksTruncated = true;
|
|
37582
|
+
break;
|
|
37583
|
+
}
|
|
37584
|
+
const remaining = MAX_DIFF_LINES_PER_RESPONSE - emittedLines;
|
|
37585
|
+
if (hunk.lines.length > remaining) {
|
|
37586
|
+
hunks.push({ type: hunk.type, lines: hunk.lines.slice(0, remaining) });
|
|
37587
|
+
emittedLines += remaining;
|
|
37588
|
+
hunksTruncated = true;
|
|
37589
|
+
break;
|
|
37590
|
+
}
|
|
37591
|
+
hunks.push(hunk);
|
|
37592
|
+
emittedLines += hunk.lines.length;
|
|
37593
|
+
}
|
|
37594
|
+
const denominator = Math.max(oldContent.split("\n").length, newContent.split("\n").length, 1);
|
|
37595
|
+
const percentChanged = totalChangedLineCount > 0 ? Math.min(100, Math.round(totalChangedLineCount / denominator * 1e3) / 10) : 0;
|
|
37596
|
+
return {
|
|
37597
|
+
hunks,
|
|
37598
|
+
linesAdded,
|
|
37599
|
+
linesRemoved,
|
|
37600
|
+
percentChanged,
|
|
37601
|
+
hunksTruncated,
|
|
37602
|
+
hunksTruncatedReason: hunksTruncated ? `Showing ${emittedLines} of ${totalChangedLineCount} changed lines (capped at ${MAX_DIFF_HUNKS} hunks / ${MAX_DIFF_LINES_PER_RESPONSE} lines). Use contentHash/previousContentHash to confirm the full scope of change.` : null,
|
|
37603
|
+
totalChangedLineCount
|
|
37604
|
+
};
|
|
37605
|
+
}
|
|
37606
|
+
var import_node_crypto14, import_diff, MAX_SNAPSHOT_CONTENT_CHARS, MAX_DIFF_HUNKS, MAX_DIFF_LINES_PER_RESPONSE;
|
|
37607
|
+
var init_page_diff = __esm({
|
|
37608
|
+
"src/api/page-diff.ts"() {
|
|
37609
|
+
"use strict";
|
|
37610
|
+
import_node_crypto14 = require("crypto");
|
|
37611
|
+
import_diff = require("diff");
|
|
37612
|
+
MAX_SNAPSHOT_CONTENT_CHARS = 25e4;
|
|
37613
|
+
MAX_DIFF_HUNKS = 200;
|
|
37614
|
+
MAX_DIFF_LINES_PER_RESPONSE = 2e3;
|
|
37615
|
+
}
|
|
37616
|
+
});
|
|
37617
|
+
|
|
37395
37618
|
// src/api/scrape-vault-sink.ts
|
|
37396
37619
|
async function depositScrapeToVault(user, opts) {
|
|
37397
37620
|
try {
|
|
@@ -38089,6 +38312,7 @@ var init_server = __esm({
|
|
|
38089
38312
|
import_stripe2 = __toESM(require("stripe"), 1);
|
|
38090
38313
|
init_rates();
|
|
38091
38314
|
init_server_schemas();
|
|
38315
|
+
init_page_diff();
|
|
38092
38316
|
init_scrape_vault_sink();
|
|
38093
38317
|
init_scrape_blob_cleanup();
|
|
38094
38318
|
init_schemas3();
|
|
@@ -38988,6 +39212,91 @@ var init_server = __esm({
|
|
|
38988
39212
|
await releaseConcurrencyGate(gate.lockId);
|
|
38989
39213
|
}
|
|
38990
39214
|
});
|
|
39215
|
+
app.post("/diff-page", auth2, async (c) => {
|
|
39216
|
+
const raw = await c.req.json().catch(() => ({}));
|
|
39217
|
+
const bodyResult = DiffPageBodySchema.safeParse(raw);
|
|
39218
|
+
if (!bodyResult.success) return c.json({ error: bodyResult.error.issues[0]?.message ?? "Invalid request" }, 400);
|
|
39219
|
+
const { url, allowLocal, resetBaseline } = bodyResult.data;
|
|
39220
|
+
if (!allowLocal) {
|
|
39221
|
+
const checked = await validatePublicHttpUrl(url, { field: "URL" });
|
|
39222
|
+
if (checked.error || !checked.parsed) return c.json({ error: checked.error ?? "Invalid URL" }, 400);
|
|
39223
|
+
} else {
|
|
39224
|
+
try {
|
|
39225
|
+
new URL(url);
|
|
39226
|
+
} catch {
|
|
39227
|
+
return c.json({ error: "Invalid URL" }, 400);
|
|
39228
|
+
}
|
|
39229
|
+
}
|
|
39230
|
+
const canonicalUrl = (() => {
|
|
39231
|
+
try {
|
|
39232
|
+
return new URL(url).href;
|
|
39233
|
+
} catch {
|
|
39234
|
+
return url;
|
|
39235
|
+
}
|
|
39236
|
+
})();
|
|
39237
|
+
const user = c.get("user");
|
|
39238
|
+
const gate = await acquireConcurrencyGate(user, "diff_page", {
|
|
39239
|
+
reuseLockId: c.req.header("x-mcp-scraper-concurrency-lock"),
|
|
39240
|
+
metadata: { url: canonicalUrl }
|
|
39241
|
+
});
|
|
39242
|
+
if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
|
|
39243
|
+
let debited = false;
|
|
39244
|
+
try {
|
|
39245
|
+
const { ok: dpOk, balance_mc: dpBal } = await debitMc(user.id, MC_COSTS.diff_page, LedgerOperation.DIFF_PAGE, new URL(canonicalUrl).hostname);
|
|
39246
|
+
if (!dpOk) return c.json(insufficientBalanceResponse(dpBal, MC_COSTS.diff_page), 402);
|
|
39247
|
+
debited = true;
|
|
39248
|
+
const existingSnapshot = await getPageSnapshot(user.id, canonicalUrl);
|
|
39249
|
+
const previous = resetBaseline ? null : existingSnapshot;
|
|
39250
|
+
const kernelApiKey = browserServiceApiKey();
|
|
39251
|
+
const result = await extractKpo({ url: canonicalUrl, kernelApiKey });
|
|
39252
|
+
const currentContent = result.bodyMarkdown ?? "";
|
|
39253
|
+
const currentHash = sha256Hex(currentContent);
|
|
39254
|
+
const { value: storedContent, truncated: contentTruncated } = truncateForStorage(currentContent);
|
|
39255
|
+
const currentCheckedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
39256
|
+
await upsertPageSnapshot(user.id, canonicalUrl, {
|
|
39257
|
+
contentHash: currentHash,
|
|
39258
|
+
content: storedContent,
|
|
39259
|
+
title: result.title ?? null,
|
|
39260
|
+
contentBytes: Buffer.byteLength(currentContent, "utf8"),
|
|
39261
|
+
truncated: contentTruncated
|
|
39262
|
+
});
|
|
39263
|
+
let status;
|
|
39264
|
+
let diff = { hunks: [], linesAdded: 0, linesRemoved: 0, percentChanged: 0, hunksTruncated: false, hunksTruncatedReason: null, totalChangedLineCount: 0 };
|
|
39265
|
+
if (!previous) {
|
|
39266
|
+
status = "baseline";
|
|
39267
|
+
diff.percentChanged = null;
|
|
39268
|
+
} else if (previous.contentHash === currentHash) {
|
|
39269
|
+
status = "unchanged";
|
|
39270
|
+
} else {
|
|
39271
|
+
status = "changed";
|
|
39272
|
+
diff = diffPageContent(previous.content, storedContent);
|
|
39273
|
+
}
|
|
39274
|
+
await logRequestEvent({ userId: user.id, source: "diff_page", status: "done", query: canonicalUrl, result: { status } });
|
|
39275
|
+
return c.json({
|
|
39276
|
+
url: canonicalUrl,
|
|
39277
|
+
title: result.title ?? null,
|
|
39278
|
+
status,
|
|
39279
|
+
isReset: !!resetBaseline && !!existingSnapshot,
|
|
39280
|
+
previousCheckedAt: previous?.checkedAt ?? null,
|
|
39281
|
+
currentCheckedAt,
|
|
39282
|
+
contentHash: currentHash,
|
|
39283
|
+
previousContentHash: previous?.contentHash ?? null,
|
|
39284
|
+
summary: { linesAdded: diff.linesAdded, linesRemoved: diff.linesRemoved, percentChanged: diff.percentChanged },
|
|
39285
|
+
hunks: diff.hunks,
|
|
39286
|
+
contentTruncated,
|
|
39287
|
+
hunksTruncated: diff.hunksTruncated,
|
|
39288
|
+
hunksTruncatedReason: diff.hunksTruncatedReason,
|
|
39289
|
+
totalChangedLineCount: diff.totalChangedLineCount
|
|
39290
|
+
});
|
|
39291
|
+
} catch (err) {
|
|
39292
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
39293
|
+
if (debited) await creditMc(user.id, MC_COSTS.diff_page, LedgerOperation.DIFF_PAGE, "failed call");
|
|
39294
|
+
await logRequestEvent({ userId: user.id, source: "diff_page", status: "failed", query: canonicalUrl, error: msg });
|
|
39295
|
+
return c.json({ error: msg }, 500);
|
|
39296
|
+
} finally {
|
|
39297
|
+
await releaseConcurrencyGate(gate.lockId);
|
|
39298
|
+
}
|
|
39299
|
+
});
|
|
38991
39300
|
app.post("/map-urls", auth2, async (c) => {
|
|
38992
39301
|
const raw = await c.req.json().catch(() => ({}));
|
|
38993
39302
|
const bodyResult = MapUrlsBodySchema.safeParse(raw);
|