mcp-scraper 0.3.10 → 0.3.12
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/README.md +4 -3
- package/dist/bin/api-server.cjs +115 -40
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +2 -2
- package/dist/bin/browser-agent-stdio-server.cjs +206 -31
- package/dist/bin/browser-agent-stdio-server.cjs.map +1 -1
- package/dist/bin/browser-agent-stdio-server.js +3 -2
- package/dist/bin/browser-agent-stdio-server.js.map +1 -1
- 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-combined-stdio-server.cjs +262 -60
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.js +4 -3
- package/dist/bin/mcp-scraper-combined-stdio-server.js.map +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 +38 -11
- 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 +21 -1
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +1 -1
- package/dist/chunk-3YGKXXUG.js +133 -0
- package/dist/chunk-3YGKXXUG.js.map +1 -0
- package/dist/{chunk-UWSG3C5J.js → chunk-4OPKIDON.js} +22 -2
- package/dist/chunk-4OPKIDON.js.map +1 -0
- package/dist/{chunk-CED7X4WB.js → chunk-7R7VBQRV.js} +39 -12
- package/dist/chunk-7R7VBQRV.js.map +1 -0
- package/dist/{chunk-QPFF3V2R.js → chunk-BW3DGFNQ.js} +36 -9
- package/dist/chunk-BW3DGFNQ.js.map +1 -0
- package/dist/chunk-FB5ZJGFN.js +7 -0
- package/dist/chunk-FB5ZJGFN.js.map +1 -0
- package/dist/index.cjs +21 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{server-QTV2EUKA.js → server-CUPDJLWM.js} +44 -142
- package/dist/server-CUPDJLWM.js.map +1 -0
- package/dist/{worker-56IXWOQU.js → worker-FG7ZWEGA.js} +2 -2
- package/docs/mcp-tool-craft-lint.generated.md +1 -1
- package/docs/mcp-tool-manifest.generated.json +3 -3
- package/package.json +1 -1
- package/dist/chunk-CED7X4WB.js.map +0 -1
- package/dist/chunk-NEGW2ZEJ.js +0 -7
- package/dist/chunk-NEGW2ZEJ.js.map +0 -1
- package/dist/chunk-QPFF3V2R.js.map +0 -1
- package/dist/chunk-UWSG3C5J.js.map +0 -1
- package/dist/server-QTV2EUKA.js.map +0 -1
- /package/dist/{worker-56IXWOQU.js.map → worker-FG7ZWEGA.js.map} +0 -0
|
@@ -1,10 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
3
25
|
|
|
4
26
|
// bin/mcp-scraper-combined-stdio-server.ts
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
27
|
+
var import_node_fs5 = require("fs");
|
|
28
|
+
var import_node_os5 = require("os");
|
|
29
|
+
var import_node_path6 = require("path");
|
|
8
30
|
var import_mcp3 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
9
31
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
10
32
|
|
|
@@ -44,6 +66,29 @@ function youtubeVideoIdFromUrl(url) {
|
|
|
44
66
|
}
|
|
45
67
|
return null;
|
|
46
68
|
}
|
|
69
|
+
async function readResponseData(res) {
|
|
70
|
+
const text = await res.text();
|
|
71
|
+
if (!text.trim()) return null;
|
|
72
|
+
try {
|
|
73
|
+
return JSON.parse(text);
|
|
74
|
+
} catch {
|
|
75
|
+
return text;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function httpErrorPayload(path, res, data) {
|
|
79
|
+
const objectData = data && typeof data === "object" && !Array.isArray(data) ? data : null;
|
|
80
|
+
const bodyMessage = objectData ? objectData.message ?? objectData.error ?? objectData.error_code : data;
|
|
81
|
+
return {
|
|
82
|
+
...objectData ?? { body: data },
|
|
83
|
+
error: objectData?.error ?? objectData?.error_code ?? "mcp_http_error",
|
|
84
|
+
error_type: "http",
|
|
85
|
+
retryable: res.status === 429 || res.status >= 500,
|
|
86
|
+
status: res.status,
|
|
87
|
+
statusText: res.statusText,
|
|
88
|
+
path,
|
|
89
|
+
message: typeof bodyMessage === "string" && bodyMessage.trim() ? bodyMessage : `MCP Scraper HTTP ${res.status}${res.statusText ? ` ${res.statusText}` : ""} for ${path}`
|
|
90
|
+
};
|
|
91
|
+
}
|
|
47
92
|
var HttpMcpToolExecutor = class {
|
|
48
93
|
baseUrl;
|
|
49
94
|
apiKey;
|
|
@@ -71,9 +116,9 @@ var HttpMcpToolExecutor = class {
|
|
|
71
116
|
body: JSON.stringify(body),
|
|
72
117
|
signal: AbortSignal.timeout(timeoutMs)
|
|
73
118
|
});
|
|
74
|
-
const data = await res
|
|
119
|
+
const data = await readResponseData(res);
|
|
75
120
|
if (!res.ok) {
|
|
76
|
-
return { content: [{ type: "text", text: JSON.stringify(data) }], isError: true };
|
|
121
|
+
return { content: [{ type: "text", text: JSON.stringify(httpErrorPayload(path, res, data)) }], isError: true };
|
|
77
122
|
}
|
|
78
123
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
79
124
|
} catch (err) {
|
|
@@ -106,9 +151,9 @@ var HttpMcpToolExecutor = class {
|
|
|
106
151
|
},
|
|
107
152
|
signal: AbortSignal.timeout(timeoutMs)
|
|
108
153
|
});
|
|
109
|
-
const data = await res
|
|
154
|
+
const data = await readResponseData(res);
|
|
110
155
|
if (!res.ok) {
|
|
111
|
-
return { content: [{ type: "text", text: JSON.stringify(data) }], isError: true };
|
|
156
|
+
return { content: [{ type: "text", text: JSON.stringify(httpErrorPayload(path, res, data)) }], isError: true };
|
|
112
157
|
}
|
|
113
158
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
114
159
|
} catch (err) {
|
|
@@ -252,9 +297,9 @@ var HttpMcpToolExecutor = class {
|
|
|
252
297
|
|
|
253
298
|
// src/mcp/browser-agent-mcp-server.ts
|
|
254
299
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
255
|
-
var
|
|
256
|
-
var
|
|
257
|
-
var
|
|
300
|
+
var import_node_fs2 = require("fs");
|
|
301
|
+
var import_node_os3 = require("os");
|
|
302
|
+
var import_node_path3 = require("path");
|
|
258
303
|
|
|
259
304
|
// src/lib/browser-service-env.ts
|
|
260
305
|
function browserServiceProfileName() {
|
|
@@ -269,8 +314,137 @@ function browserServiceProfileSaveChanges() {
|
|
|
269
314
|
return void 0;
|
|
270
315
|
}
|
|
271
316
|
|
|
317
|
+
// src/services/fanout/export.ts
|
|
318
|
+
var import_node_fs = require("fs");
|
|
319
|
+
var import_node_os = require("os");
|
|
320
|
+
var import_node_path = require("path");
|
|
321
|
+
var import_papaparse = __toESM(require("papaparse"), 1);
|
|
322
|
+
function outputBaseDir() {
|
|
323
|
+
return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || (0, import_node_path.join)((0, import_node_os.homedir)(), "Downloads", "mcp-scraper");
|
|
324
|
+
}
|
|
325
|
+
function safe(value) {
|
|
326
|
+
return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "capture";
|
|
327
|
+
}
|
|
328
|
+
function writeTable(path, rows, delimiter) {
|
|
329
|
+
(0, import_node_fs.writeFileSync)(path, import_papaparse.default.unparse(rows.length ? rows : [{}], { delimiter }));
|
|
330
|
+
}
|
|
331
|
+
function exportFanout(enriched) {
|
|
332
|
+
const stamp = safe(enriched.capturedAt.replace(/[:.]/g, "-"));
|
|
333
|
+
const outputDir = outputBaseDir();
|
|
334
|
+
const relativeDir = (0, import_node_path.join)("fanout", `${stamp}-${safe(enriched.platform)}`);
|
|
335
|
+
const dir = (0, import_node_path.join)(outputDir, relativeDir);
|
|
336
|
+
(0, import_node_fs.mkdirSync)(dir, { recursive: true });
|
|
337
|
+
const queryRows = enriched.queries.map((q, i) => ({ index: i + 1, query: q }));
|
|
338
|
+
const citationRows = enriched.aggregates.citationOrder.map((c) => {
|
|
339
|
+
const src = enriched.citedUrls.find((s) => s.url === c.url);
|
|
340
|
+
return { rank: c.rank, domain: c.domain, url: c.url, timesCited: c.timesCited, siteType: src?.siteType ?? "", title: src?.title ?? "" };
|
|
341
|
+
});
|
|
342
|
+
const sourceRows = enriched.browsedUrls.map((s) => ({ domain: s.domain, url: s.url, cited: s.cited, timesCited: s.timesCited, siteType: s.siteType, title: s.title, round: s.round ?? "" }));
|
|
343
|
+
const browsedOnlyRows = enriched.browsedOnly.map((s) => ({ domain: s.domain, url: s.url, cited: s.cited, timesCited: s.timesCited, siteType: s.siteType, title: s.title, round: s.round ?? "" }));
|
|
344
|
+
const snippetRows = enriched.snippets.map((s) => ({ domain: s.domain, url: s.url, title: s.title, text: s.text }));
|
|
345
|
+
const domainRows = enriched.aggregates.topSites.map((d) => ({ domain: d.domain, count: d.count, cited: d.cited, timesCited: d.timesCited, siteType: d.siteType }));
|
|
346
|
+
const relativePaths = {
|
|
347
|
+
relativeTo: "MCP_SCRAPER_OUTPUT_DIR or ~/Downloads/mcp-scraper",
|
|
348
|
+
dir: relativeDir,
|
|
349
|
+
json: (0, import_node_path.join)(relativeDir, "fanout.json"),
|
|
350
|
+
queriesCsv: (0, import_node_path.join)(relativeDir, "queries.csv"),
|
|
351
|
+
queriesTsv: (0, import_node_path.join)(relativeDir, "queries.tsv"),
|
|
352
|
+
citationsCsv: (0, import_node_path.join)(relativeDir, "citations.csv"),
|
|
353
|
+
sourcesCsv: (0, import_node_path.join)(relativeDir, "sources.csv"),
|
|
354
|
+
browsedOnlyCsv: (0, import_node_path.join)(relativeDir, "browsed-only.csv"),
|
|
355
|
+
snippetsCsv: (0, import_node_path.join)(relativeDir, "snippets.csv"),
|
|
356
|
+
domainsCsv: (0, import_node_path.join)(relativeDir, "domains.csv"),
|
|
357
|
+
report: (0, import_node_path.join)(relativeDir, "report.html")
|
|
358
|
+
};
|
|
359
|
+
(0, import_node_fs.writeFileSync)((0, import_node_path.join)(outputDir, relativePaths.json), JSON.stringify(enriched, null, 2));
|
|
360
|
+
writeTable((0, import_node_path.join)(outputDir, relativePaths.queriesCsv), queryRows, ",");
|
|
361
|
+
writeTable((0, import_node_path.join)(outputDir, relativePaths.queriesTsv), queryRows, " ");
|
|
362
|
+
writeTable((0, import_node_path.join)(outputDir, relativePaths.citationsCsv), citationRows, ",");
|
|
363
|
+
writeTable((0, import_node_path.join)(outputDir, relativePaths.sourcesCsv), sourceRows, ",");
|
|
364
|
+
writeTable((0, import_node_path.join)(outputDir, relativePaths.browsedOnlyCsv), browsedOnlyRows, ",");
|
|
365
|
+
writeTable((0, import_node_path.join)(outputDir, relativePaths.snippetsCsv), snippetRows, ",");
|
|
366
|
+
writeTable((0, import_node_path.join)(outputDir, relativePaths.domainsCsv), domainRows, ",");
|
|
367
|
+
(0, import_node_fs.writeFileSync)((0, import_node_path.join)(outputDir, relativePaths.report), renderReportHtml(enriched));
|
|
368
|
+
return relativePaths;
|
|
369
|
+
}
|
|
370
|
+
function esc(s) {
|
|
371
|
+
return String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
372
|
+
}
|
|
373
|
+
function renderReportHtml(enriched) {
|
|
374
|
+
const data = JSON.stringify(enriched).replace(/</g, "\\u003c");
|
|
375
|
+
return `<!doctype html>
|
|
376
|
+
<html lang="en">
|
|
377
|
+
<head>
|
|
378
|
+
<meta charset="utf-8" />
|
|
379
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
380
|
+
<title>AI Search Fan-Out \u2014 ${esc(enriched.platform)}</title>
|
|
381
|
+
<style>
|
|
382
|
+
:root{--bg:#0b0d12;--panel:#151922;--line:#252b38;--ink:#e8ecf3;--mut:#8b95a7;--acc:#5b9dff;--cite:#39d98a;--browse:#5a6677}
|
|
383
|
+
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.5 -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}
|
|
384
|
+
.wrap{max-width:980px;margin:0 auto;padding:28px 20px 80px}
|
|
385
|
+
h1{font-size:20px;margin:0 0 2px}.sub{color:var(--mut);margin:0 0 20px;font-size:13px}
|
|
386
|
+
.stat{font-size:30px;font-weight:700}.stat small{font-size:13px;font-weight:400;color:var(--mut)}
|
|
387
|
+
.cards{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin:18px 0}
|
|
388
|
+
.card{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:12px 14px}
|
|
389
|
+
.card b{font-size:22px;display:block}.card span{color:var(--mut);font-size:12px}
|
|
390
|
+
.bars{margin:8px 0 22px}.bar{display:flex;align-items:center;gap:8px;margin:4px 0}
|
|
391
|
+
.bar .lab{width:150px;color:var(--mut);font-size:12px;text-align:right}.bar .track{flex:1;background:#10141c;border-radius:5px;overflow:hidden}
|
|
392
|
+
.bar .fill{background:var(--acc);height:16px}.bar .n{width:34px;font-size:12px;color:var(--mut)}
|
|
393
|
+
.tabs{display:flex;gap:8px;margin:22px 0 12px}.tab{background:var(--panel);border:1px solid var(--line);color:var(--ink);padding:7px 14px;border-radius:8px;cursor:pointer}
|
|
394
|
+
.tab.on{background:var(--acc);border-color:var(--acc);color:#04101f;font-weight:600}
|
|
395
|
+
.chips{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:12px}
|
|
396
|
+
.chip{background:var(--panel);border:1px solid var(--line);color:var(--mut);padding:4px 10px;border-radius:20px;cursor:pointer;font-size:12px}
|
|
397
|
+
.chip.on{border-color:var(--acc);color:var(--ink)}.chip b{color:var(--ink)}
|
|
398
|
+
.row{background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:10px 12px;margin:6px 0;display:flex;gap:10px;align-items:flex-start}
|
|
399
|
+
.row .idx{color:var(--mut);min-width:22px}.row .main{flex:1}
|
|
400
|
+
.tag{font-size:11px;padding:2px 7px;border-radius:6px;border:1px solid var(--line);color:var(--mut);margin-right:5px}
|
|
401
|
+
.dom{font-weight:600}.url{color:var(--mut);font-size:12px;word-break:break-all}
|
|
402
|
+
.pill{font-size:11px;padding:2px 7px;border-radius:6px}.pill.cite{background:rgba(57,217,138,.15);color:var(--cite)}.pill.brow{background:rgba(90,102,119,.2);color:#aeb8c8}
|
|
403
|
+
.copy{background:none;border:1px solid var(--line);color:var(--mut);border-radius:6px;cursor:pointer;font-size:11px;padding:2px 8px}
|
|
404
|
+
.hide{display:none}
|
|
405
|
+
</style>
|
|
406
|
+
</head>
|
|
407
|
+
<body><div class="wrap">
|
|
408
|
+
<h1>AI Search Fan-Out</h1>
|
|
409
|
+
<p class="sub" id="sub"></p>
|
|
410
|
+
<div class="stat" id="stat"></div>
|
|
411
|
+
<div class="cards" id="cards"></div>
|
|
412
|
+
<div id="charts"></div>
|
|
413
|
+
<div class="tabs"><button class="tab on" data-tab="q">Queries</button><button class="tab" data-tab="u">URLs</button></div>
|
|
414
|
+
<div class="chips" id="chips"></div>
|
|
415
|
+
<div id="list"></div>
|
|
416
|
+
</div>
|
|
417
|
+
<script>
|
|
418
|
+
const D=${data};
|
|
419
|
+
const el=(t,c,h)=>{const e=document.createElement(t);if(c)e.className=c;if(h!=null)e.innerHTML=h;return e};
|
|
420
|
+
document.getElementById('sub').textContent=D.platform+' \xB7 '+(D.meta.model||'model n/a')+' \xB7 '+D.meta.rounds+' search round(s) \xB7 prompt: '+(D.prompt||'');
|
|
421
|
+
document.getElementById('stat').innerHTML='Total Fan-Out: '+D.counts.subQueries+' <small>sub-queries \xB7 '+D.counts.browsed+' URLs researched \xB7 '+D.counts.cited+' cited</small>';
|
|
422
|
+
const cards=[['Sub-queries',D.counts.subQueries],['Researched',D.counts.browsed],['Cited',D.counts.cited],['Browsed-only',D.counts.browsedOnly]];
|
|
423
|
+
const cc=document.getElementById('cards');cards.forEach(([k,v])=>{const c=el('div','card');c.appendChild(el('b',null,v));c.appendChild(el('span',null,k));cc.appendChild(c)});
|
|
424
|
+
function barChart(title,obj){const max=Math.max(1,...Object.values(obj));const box=el('div','bars');box.appendChild(el('div',null,'<b>'+title+'</b>'));Object.entries(obj).forEach(([k,v])=>{const r=el('div','bar');r.appendChild(el('div','lab',k));const t=el('div','track');const f=el('div','fill');f.style.width=(v/max*100)+'%';t.appendChild(f);r.appendChild(t);r.appendChild(el('div','n',v));box.appendChild(r)});return box}
|
|
425
|
+
const charts=document.getElementById('charts');
|
|
426
|
+
charts.appendChild(barChart('URL Categories',D.aggregates.byCategory));
|
|
427
|
+
let tab='q',filter=null;
|
|
428
|
+
const chips=document.getElementById('chips'),list=document.getElementById('list');
|
|
429
|
+
function render(){
|
|
430
|
+
chips.innerHTML='';list.innerHTML='';
|
|
431
|
+
if(tab==='q'){
|
|
432
|
+
D.queries.forEach((q,i)=>{const r=el('div','row');r.appendChild(el('div','idx',i+1));const m=el('div','main');m.appendChild(el('div',null,esc(q)));r.appendChild(m);const b=el('button','copy','copy');b.onclick=()=>navigator.clipboard.writeText(q);r.appendChild(b);list.appendChild(r)});
|
|
433
|
+
} else {
|
|
434
|
+
const counts={};D.browsedUrls.forEach(s=>{counts[s.siteType]=(counts[s.siteType]||0)+1});
|
|
435
|
+
Object.entries(counts).forEach(([k,v])=>{const c=el('button','chip'+(filter===k?' on':''),k+' <b>'+v+'</b>');c.onclick=()=>{filter=filter===k?null:k;render()};chips.appendChild(c)});
|
|
436
|
+
D.browsedUrls.forEach(s=>{if(filter&&s.siteType!==filter)return;const r=el('div','row');const m=el('div','main');m.appendChild(el('div',null,'<span class="dom">'+esc(s.domain)+'</span> <span class="pill '+(s.cited?'cite':'brow')+'">'+(s.cited?'cited '+s.timesCited+'\xD7':'browsed')+'</span> <span class="tag">'+s.siteType+'</span>'));m.appendChild(el('div','url',esc(s.url)));r.appendChild(m);list.appendChild(r)});
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
function esc(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')}
|
|
440
|
+
document.querySelectorAll('.tab').forEach(t=>t.onclick=()=>{document.querySelectorAll('.tab').forEach(x=>x.classList.remove('on'));t.classList.add('on');tab=t.dataset.tab;filter=null;render()});
|
|
441
|
+
render();
|
|
442
|
+
</script>
|
|
443
|
+
</body></html>`;
|
|
444
|
+
}
|
|
445
|
+
|
|
272
446
|
// src/version.ts
|
|
273
|
-
var PACKAGE_VERSION = "0.3.
|
|
447
|
+
var PACKAGE_VERSION = "0.3.12";
|
|
274
448
|
|
|
275
449
|
// src/mcp/browser-agent-tool-schemas.ts
|
|
276
450
|
var import_zod = require("zod");
|
|
@@ -407,7 +581,7 @@ var BrowserCaptureFanoutInputSchema = {
|
|
|
407
581
|
wait_ms: import_zod.z.number().int().min(0).max(18e4).optional().describe("How long to wait for the answer stream to finish before parsing. Defaults to 90000 when a prompt is sent, 8000 for passive capture. Capture ends as soon as the answer stream finishes."),
|
|
408
582
|
first_party_domain: import_zod.z.string().optional().describe("The brand/site being researched, for example example.com. Sources on this domain are tagged First-party/vendor. Set this when the prompt is about a specific business."),
|
|
409
583
|
reset: import_zod.z.boolean().default(false).describe("Clear any previously buffered stream for this page before capturing. Use when re-running to avoid mixing turns."),
|
|
410
|
-
export: import_zod.z.boolean().default(false).describe("When true, write JSON, CSV, TSV exports (queries, citations, sources, domains) and a self-contained HTML report to MCP_SCRAPER_OUTPUT_DIR/fanout
|
|
584
|
+
export: import_zod.z.boolean().default(false).describe("When true, write JSON, CSV, TSV exports (queries, citations, all sources, browsed-only sources, snippets, domains) and a self-contained HTML report to MCP_SCRAPER_OUTPUT_DIR/fanout, returning paths relative to MCP_SCRAPER_OUTPUT_DIR.")
|
|
411
585
|
};
|
|
412
586
|
var FanoutSourceOutput = import_zod.z.object({
|
|
413
587
|
url: import_zod.z.string(),
|
|
@@ -449,15 +623,18 @@ var BrowserCaptureFanoutOutputSchema = {
|
|
|
449
623
|
}).describe("Objective aggregates: top sourced sites by frequency, citation order, and URL-category counts."),
|
|
450
624
|
first_party_domain: import_zod.z.string().nullable(),
|
|
451
625
|
exports: import_zod.z.object({
|
|
626
|
+
relativeTo: import_zod.z.string(),
|
|
452
627
|
dir: import_zod.z.string(),
|
|
453
628
|
json: import_zod.z.string(),
|
|
454
629
|
queriesCsv: import_zod.z.string(),
|
|
455
630
|
queriesTsv: import_zod.z.string(),
|
|
456
631
|
citationsCsv: import_zod.z.string(),
|
|
457
632
|
sourcesCsv: import_zod.z.string(),
|
|
633
|
+
browsedOnlyCsv: import_zod.z.string(),
|
|
634
|
+
snippetsCsv: import_zod.z.string(),
|
|
458
635
|
domainsCsv: import_zod.z.string(),
|
|
459
636
|
report: import_zod.z.string()
|
|
460
|
-
}).nullable().describe("
|
|
637
|
+
}).nullable().describe("Relative export paths when export=true, otherwise null. Paths are relative to MCP_SCRAPER_OUTPUT_DIR, or ~/Downloads/mcp-scraper when that env var is not set."),
|
|
461
638
|
debug: import_zod.z.object({
|
|
462
639
|
interceptorReady: import_zod.z.boolean(),
|
|
463
640
|
rawBytes: import_zod.z.number().int().min(0),
|
|
@@ -609,8 +786,8 @@ var BrowserListSessionsOutputSchema = {
|
|
|
609
786
|
// src/mcp/replay-annotator.ts
|
|
610
787
|
var import_node_child_process = require("child_process");
|
|
611
788
|
var import_promises = require("fs/promises");
|
|
612
|
-
var
|
|
613
|
-
var
|
|
789
|
+
var import_node_os2 = require("os");
|
|
790
|
+
var import_node_path2 = require("path");
|
|
614
791
|
var import_node_util = require("util");
|
|
615
792
|
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
616
793
|
function finiteNumber(value) {
|
|
@@ -620,11 +797,11 @@ function clamp(value, min, max) {
|
|
|
620
797
|
return Math.min(max, Math.max(min, value));
|
|
621
798
|
}
|
|
622
799
|
function formatAssTime(seconds) {
|
|
623
|
-
const
|
|
624
|
-
const hours = Math.floor(
|
|
625
|
-
const minutes = Math.floor(
|
|
626
|
-
const wholeSeconds = Math.floor(
|
|
627
|
-
const centiseconds = Math.floor((
|
|
800
|
+
const safe2 = Math.max(0, seconds);
|
|
801
|
+
const hours = Math.floor(safe2 / 3600);
|
|
802
|
+
const minutes = Math.floor(safe2 % 3600 / 60);
|
|
803
|
+
const wholeSeconds = Math.floor(safe2 % 60);
|
|
804
|
+
const centiseconds = Math.floor((safe2 - Math.floor(safe2)) * 100);
|
|
628
805
|
return `${hours}:${String(minutes).padStart(2, "0")}:${String(wholeSeconds).padStart(2, "0")}.${String(centiseconds).padStart(2, "0")}`;
|
|
629
806
|
}
|
|
630
807
|
function cssHexToAssColor(color) {
|
|
@@ -833,8 +1010,8 @@ function ffmpegFilterPath(path) {
|
|
|
833
1010
|
async function annotateReplayVideo(inputFilePath, outputFilePath, options) {
|
|
834
1011
|
if (!options.annotations.length) throw new Error("annotations must include at least one item");
|
|
835
1012
|
const size = await videoSize(inputFilePath);
|
|
836
|
-
const tmp = await (0, import_promises.mkdtemp)((0,
|
|
837
|
-
const assPath = (0,
|
|
1013
|
+
const tmp = await (0, import_promises.mkdtemp)((0, import_node_path2.join)((0, import_node_os2.tmpdir)(), "mcp-scraper-ass-"));
|
|
1014
|
+
const assPath = (0, import_node_path2.join)(tmp, "annotations.ass");
|
|
838
1015
|
try {
|
|
839
1016
|
await (0, import_promises.writeFile)(assPath, buildAssSubtitle(options, size), "utf8");
|
|
840
1017
|
await execFileAsync("ffmpeg", [
|
|
@@ -902,8 +1079,8 @@ function actionResult(tool, sessionId, ok, data, nextRecommendedTool = "browser_
|
|
|
902
1079
|
nextRecommendedTool
|
|
903
1080
|
});
|
|
904
1081
|
}
|
|
905
|
-
function
|
|
906
|
-
return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || (0,
|
|
1082
|
+
function outputBaseDir2() {
|
|
1083
|
+
return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || (0, import_node_path3.join)((0, import_node_os3.homedir)(), "Downloads", "mcp-scraper");
|
|
907
1084
|
}
|
|
908
1085
|
function safeFilePart(value) {
|
|
909
1086
|
return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 120) || "replay";
|
|
@@ -912,7 +1089,7 @@ function replayFilePath(sessionId, replayId, filename) {
|
|
|
912
1089
|
const requested = filename?.trim();
|
|
913
1090
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
914
1091
|
const name = requested ? safeFilePart(requested).replace(/\.mp4$/i, "") : `${stamp}-${safeFilePart(sessionId)}-${safeFilePart(replayId)}`;
|
|
915
|
-
return (0,
|
|
1092
|
+
return (0, import_node_path3.join)(outputBaseDir2(), "browser-replays", `${name}.mp4`);
|
|
916
1093
|
}
|
|
917
1094
|
function slugPart(value) {
|
|
918
1095
|
const trimmed = value?.trim().toLowerCase();
|
|
@@ -925,6 +1102,11 @@ function savedProfileNameFromEmail(email, domain) {
|
|
|
925
1102
|
const domainPart = slugPart(domain);
|
|
926
1103
|
return `${value}${domainPart ? `-${domainPart}` : ""}`.slice(0, 80) || null;
|
|
927
1104
|
}
|
|
1105
|
+
function isEnrichedFanout(value) {
|
|
1106
|
+
if (!value || typeof value !== "object") return false;
|
|
1107
|
+
const candidate = value;
|
|
1108
|
+
return typeof candidate.platform === "string" && typeof candidate.capturedAt === "string" && typeof candidate.prompt === "string" && Array.isArray(candidate.queries) && Array.isArray(candidate.browsedUrls) && Array.isArray(candidate.citedUrls) && Array.isArray(candidate.browsedOnly) && Array.isArray(candidate.snippets) && !!candidate.counts && !!candidate.aggregates;
|
|
1109
|
+
}
|
|
928
1110
|
function annotatedReplayFilePath(sessionId, replayId, filename) {
|
|
929
1111
|
const requested = filename?.trim();
|
|
930
1112
|
if (requested) return replayFilePath(sessionId, replayId, requested);
|
|
@@ -982,8 +1164,8 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
982
1164
|
}
|
|
983
1165
|
const bytes = Buffer.from(await res.arrayBuffer());
|
|
984
1166
|
const filePath = replayFilePath(sessionId, replayId, filename);
|
|
985
|
-
(0,
|
|
986
|
-
(0,
|
|
1167
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path3.join)(outputBaseDir2(), "browser-replays"), { recursive: true });
|
|
1168
|
+
(0, import_node_fs2.writeFileSync)(filePath, bytes);
|
|
987
1169
|
return {
|
|
988
1170
|
ok: true,
|
|
989
1171
|
data: {
|
|
@@ -1442,7 +1624,7 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1442
1624
|
try {
|
|
1443
1625
|
const sourcePath = String(downloaded.data.file_path);
|
|
1444
1626
|
const outputPath = annotatedReplayFilePath(input.session_id, input.replay_id, input.filename);
|
|
1445
|
-
(0,
|
|
1627
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path3.join)(outputBaseDir2(), "browser-replays"), { recursive: true });
|
|
1446
1628
|
const result = await annotateReplayVideo(sourcePath, outputPath, {
|
|
1447
1629
|
annotations: input.annotations,
|
|
1448
1630
|
sourceWidth: input.source_width,
|
|
@@ -1515,13 +1697,13 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1515
1697
|
"browser_capture_fanout",
|
|
1516
1698
|
{
|
|
1517
1699
|
title: "Capture AI Search Fan-Out",
|
|
1518
|
-
description: `Capture the query fan-out behind a ChatGPT or Claude web-search answer for Answer Engine Optimization (AEO): the exact sub-queries the model issued, every researched URL split into cited vs browsed-only (with citation frequency and snippet), each source tagged by category (First-party/vendor, News/media, Reddit, Social/video, Encyclopedia, Review site, Docs, Blog), plus top sourced sites and citation order. This returns the raw structured data; YOU analyze it \u2014 classify each sub-query by funnel stage (Problem-aware, Solution-aware, Decision-aware, Retention) and type (BoFu, Branded, Comparison, How-to, Search operator), name the brands the model researched, and give AEO insights. Sample asks: "capture the fan-out for this ChatGPT answer", "what did Claude search and cite for best CRM", "show the sub-queries and sources behind this AI answer". WRITE NOTE: passing prompt submits a real message in the user's logged-in account (a new conversation turn) \u2014 only send when the user wants that; omit prompt to capture a prompt the user just ran. Setup, if not already connected or if the profile disconnected: call browser_profile_onboard, give the user the watch_url, let them complete login, then call browser_profile_status until status is AUTHENTICATED. After that, browser_open the saved direct no-proxy profile and go to chatgpt.com or claude.ai. Fan-out is captured only as it streams, so the session must be open when the prompt runs. NOT for Google AI Overview citations \u2014 use harvest_paa for those; this tool is ChatGPT and Claude only.`,
|
|
1700
|
+
description: `Capture the query fan-out behind a ChatGPT or Claude web-search answer for Answer Engine Optimization (AEO): the exact sub-queries the model issued, every researched URL split into cited vs browsed-only (with citation frequency and snippet), each source tagged by category (First-party/vendor, News/media, Reddit, Social/video, Encyclopedia, Review site, Docs, Blog), plus top sourced sites and citation order. Set export=true for durable JSON, CSV, TSV, and HTML artifacts; export paths are returned relative to MCP_SCRAPER_OUTPUT_DIR. This returns the raw structured data; YOU analyze it \u2014 classify each sub-query by funnel stage (Problem-aware, Solution-aware, Decision-aware, Retention) and type (BoFu, Branded, Comparison, How-to, Search operator), name the brands the model researched, and give AEO insights. Sample asks: "capture the fan-out for this ChatGPT answer", "what did Claude search and cite for best CRM", "show the sub-queries and sources behind this AI answer". WRITE NOTE: passing prompt submits a real message in the user's logged-in account (a new conversation turn) \u2014 only send when the user wants that; omit prompt to capture a prompt the user just ran. Setup, if not already connected or if the profile disconnected: call browser_profile_onboard, give the user the watch_url, let them complete login, then call browser_profile_status until status is AUTHENTICATED. After that, browser_open the saved direct no-proxy profile and go to chatgpt.com or claude.ai. Fan-out is captured only as it streams, so the session must be open when the prompt runs. NOT for Google AI Overview citations \u2014 use harvest_paa for those; this tool is ChatGPT and Claude only.`,
|
|
1519
1701
|
inputSchema: BrowserCaptureFanoutInputSchema,
|
|
1520
1702
|
outputSchema: BrowserCaptureFanoutOutputSchema,
|
|
1521
1703
|
annotations: annotations("Capture AI Search Fan-Out")
|
|
1522
1704
|
},
|
|
1523
1705
|
async (input) => {
|
|
1524
|
-
const emit = (result,
|
|
1706
|
+
const emit = (result, exports3) => structuredResult({
|
|
1525
1707
|
ok: true,
|
|
1526
1708
|
tool: "browser_capture_fanout",
|
|
1527
1709
|
session_id: input.session_id,
|
|
@@ -1537,31 +1719,47 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1537
1719
|
counts: result.counts,
|
|
1538
1720
|
aggregates: result.aggregates,
|
|
1539
1721
|
first_party_domain: result.firstPartyDomain,
|
|
1540
|
-
exports:
|
|
1722
|
+
exports: exports3 ?? null,
|
|
1541
1723
|
...result.debug ? { debug: result.debug } : {}
|
|
1542
1724
|
});
|
|
1543
1725
|
const res = await req("POST", `/agent/sessions/${input.session_id}/capture-fanout`, {
|
|
1544
1726
|
prompt: input.prompt,
|
|
1545
1727
|
wait_ms: input.wait_ms,
|
|
1546
1728
|
first_party_domain: input.first_party_domain,
|
|
1547
|
-
|
|
1729
|
+
reset: input.reset,
|
|
1730
|
+
export: input.export
|
|
1548
1731
|
});
|
|
1549
1732
|
if (!res.ok) return errorResult("browser_capture_fanout", res.data, input.session_id);
|
|
1550
1733
|
const hosted = res.data?.result ?? res.data;
|
|
1551
|
-
|
|
1734
|
+
let exports2 = res.data?.exports ?? null;
|
|
1735
|
+
if (input.export && !exports2 && isEnrichedFanout(hosted)) {
|
|
1736
|
+
try {
|
|
1737
|
+
exports2 = exportFanout(hosted);
|
|
1738
|
+
} catch (err) {
|
|
1739
|
+
return errorResult(
|
|
1740
|
+
"browser_capture_fanout",
|
|
1741
|
+
{
|
|
1742
|
+
error: `Fan-out captured but local export failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1743
|
+
result: hosted
|
|
1744
|
+
},
|
|
1745
|
+
input.session_id
|
|
1746
|
+
);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
return emit(hosted, exports2);
|
|
1552
1750
|
}
|
|
1553
1751
|
);
|
|
1554
1752
|
}
|
|
1555
1753
|
|
|
1556
1754
|
// src/mcp/paa-mcp-server.ts
|
|
1557
1755
|
var import_mcp2 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
1558
|
-
var
|
|
1559
|
-
var
|
|
1756
|
+
var import_node_fs4 = require("fs");
|
|
1757
|
+
var import_node_path5 = require("path");
|
|
1560
1758
|
|
|
1561
1759
|
// src/mcp/mcp-response-formatter.ts
|
|
1562
|
-
var
|
|
1563
|
-
var
|
|
1564
|
-
var
|
|
1760
|
+
var import_node_fs3 = require("fs");
|
|
1761
|
+
var import_node_os4 = require("os");
|
|
1762
|
+
var import_node_path4 = require("path");
|
|
1565
1763
|
|
|
1566
1764
|
// src/errors.ts
|
|
1567
1765
|
function sanitizeVendorName(message) {
|
|
@@ -1697,17 +1895,17 @@ function reportTitle(full) {
|
|
|
1697
1895
|
const title = full.split("\n").find((line) => line.startsWith("# "));
|
|
1698
1896
|
return title?.replace(/^#\s+/, "").trim() || "MCP Scraper Report";
|
|
1699
1897
|
}
|
|
1700
|
-
function
|
|
1701
|
-
return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || (0,
|
|
1898
|
+
function outputBaseDir3() {
|
|
1899
|
+
return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || (0, import_node_path4.join)((0, import_node_os4.homedir)(), "Downloads", "mcp-scraper");
|
|
1702
1900
|
}
|
|
1703
1901
|
function saveFullReport(full) {
|
|
1704
1902
|
if (!reportSavingEnabled || process.env.MCP_SCRAPER_SAVE_REPORTS === "false") return null;
|
|
1705
|
-
const outDir =
|
|
1903
|
+
const outDir = outputBaseDir3();
|
|
1706
1904
|
try {
|
|
1707
|
-
(0,
|
|
1905
|
+
(0, import_node_fs3.mkdirSync)(outDir, { recursive: true });
|
|
1708
1906
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1709
|
-
const file = (0,
|
|
1710
|
-
(0,
|
|
1907
|
+
const file = (0, import_node_path4.join)(outDir, `${stamp}-${slugifyReportName(reportTitle(full))}.md`);
|
|
1908
|
+
(0, import_node_fs3.writeFileSync)(file, full, "utf8");
|
|
1711
1909
|
return file;
|
|
1712
1910
|
} catch {
|
|
1713
1911
|
return null;
|
|
@@ -1716,12 +1914,12 @@ function saveFullReport(full) {
|
|
|
1716
1914
|
function persistScreenshotLocally(base64, url) {
|
|
1717
1915
|
if (!reportSavingEnabled || process.env.MCP_SCRAPER_SAVE_REPORTS === "false") return null;
|
|
1718
1916
|
try {
|
|
1719
|
-
const dir = (0,
|
|
1720
|
-
(0,
|
|
1917
|
+
const dir = (0, import_node_path4.join)(outputBaseDir3(), "screenshots");
|
|
1918
|
+
(0, import_node_fs3.mkdirSync)(dir, { recursive: true });
|
|
1721
1919
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1722
1920
|
const slug = url.replace(/^https?:\/\//, "").replace(/[^a-z0-9]+/gi, "-").replace(/^-+|-+$/g, "").slice(0, 60);
|
|
1723
|
-
const filePath = (0,
|
|
1724
|
-
(0,
|
|
1921
|
+
const filePath = (0, import_node_path4.join)(dir, `${stamp}-${slug}.png`);
|
|
1922
|
+
(0, import_node_fs3.writeFileSync)(filePath, Buffer.from(base64, "base64"));
|
|
1725
1923
|
return filePath;
|
|
1726
1924
|
} catch {
|
|
1727
1925
|
return null;
|
|
@@ -3062,7 +3260,7 @@ function formatInstagramProfileContent(raw, input) {
|
|
|
3062
3260
|
const itemRows = items.slice(0, 100).map(
|
|
3063
3261
|
(item, i) => `| ${i + 1} | ${item.type} | \`${item.shortcode}\` | ${item.url} | ${cell(item.firstSeenStage ?? "")} |`
|
|
3064
3262
|
).join("\n");
|
|
3065
|
-
const browserLabel = "hosted browser";
|
|
3263
|
+
const browserLabel = browser.profileName ? `hosted browser profile ${browser.profileName}` : "hosted browser";
|
|
3066
3264
|
const full = [
|
|
3067
3265
|
`# Instagram Profile Content: ${d.handle ?? input.handle ?? input.url ?? "profile"}`,
|
|
3068
3266
|
`**Collected:** ${items.length} items \xB7 posts ${typeCounts?.post ?? 0} \xB7 reels ${typeCounts?.reel ?? 0} \xB7 tv ${typeCounts?.tv ?? 0}`,
|
|
@@ -3137,7 +3335,7 @@ function formatInstagramMediaDownload(raw, input) {
|
|
|
3137
3335
|
const transcriptText = transcript?.text ?? "";
|
|
3138
3336
|
const chunks = transcript?.chunks ?? [];
|
|
3139
3337
|
const browser = structuredInstagramBrowser(d.browser);
|
|
3140
|
-
const browserLabel = "hosted browser";
|
|
3338
|
+
const browserLabel = browser.profileName ? `hosted browser profile ${browser.profileName}` : "hosted browser";
|
|
3141
3339
|
const downloadRows = downloads.map((download, i) => {
|
|
3142
3340
|
const status = download.error ? `error: ${cell(download.error)}` : `${download.sizeBytes ?? 0} bytes`;
|
|
3143
3341
|
return `| ${i + 1} | ${download.kind} | ${download.savedPath ? `\`${download.savedPath}\`` : "\u2014"} | ${status} |`;
|
|
@@ -3382,13 +3580,17 @@ var FacebookVideoTranscribeInputSchema = {
|
|
|
3382
3580
|
var InstagramProfileContentInputSchema = {
|
|
3383
3581
|
handle: import_zod3.z.string().min(1).optional().describe("Instagram handle, with or without @. Provide handle or url."),
|
|
3384
3582
|
url: import_zod3.z.string().url().optional().describe("Instagram profile URL, e.g. https://www.instagram.com/nasaartemis/. Provide handle or url."),
|
|
3385
|
-
|
|
3386
|
-
|
|
3583
|
+
profile: import_zod3.z.string().min(1).optional().describe("Optional saved hosted browser profile name to load authenticated Instagram access. If omitted, the server uses its configured default profile when present."),
|
|
3584
|
+
saveProfileChanges: import_zod3.z.boolean().optional().describe("Whether to save browser changes back to the hosted profile. Leave unset unless intentionally updating the saved login session."),
|
|
3585
|
+
maxItems: import_zod3.z.number().int().min(1).max(2e3).default(50).describe("Maximum profile grid post/reel/tv URLs to collect. Default 50, maximum 2000. Use higher values only when the user asks for a fuller archive."),
|
|
3586
|
+
maxScrolls: import_zod3.z.number().int().min(0).max(250).default(10).describe("Maximum pagination scroll attempts. Default 10, maximum 250. Increase for long profiles when Instagram continues loading more grid links."),
|
|
3387
3587
|
scrollDelayMs: import_zod3.z.number().int().min(250).max(5e3).default(1200).describe("Delay after each pagination scroll before collecting newly loaded links. Default 1200ms. Increase to 2000-3000ms when Instagram loads slowly."),
|
|
3388
3588
|
stableScrollLimit: import_zod3.z.number().int().min(1).max(10).default(4).describe("Stop after this many consecutive scrolls with no new links or scroll progress. Default 4.")
|
|
3389
3589
|
};
|
|
3390
3590
|
var InstagramMediaDownloadInputSchema = {
|
|
3391
3591
|
url: import_zod3.z.string().url().describe("Instagram post, reel, or tv URL, e.g. https://www.instagram.com/reel/SHORTCODE/. The tool renders the page, extracts text, image metadata, and Instagram CDN media tracks."),
|
|
3592
|
+
profile: import_zod3.z.string().min(1).optional().describe("Optional saved hosted browser profile name to load authenticated Instagram access. If omitted, the server uses its configured default profile when present."),
|
|
3593
|
+
saveProfileChanges: import_zod3.z.boolean().optional().describe("Whether to save browser changes back to the hosted profile. Leave unset unless intentionally updating the saved login session."),
|
|
3392
3594
|
mediaTypes: import_zod3.z.array(import_zod3.z.enum(["image", "video", "audio"])).default(["image", "video", "audio"]).describe("Which media types to download when downloadMedia is true. Reels commonly expose separate video-only and audio-only MP4 tracks."),
|
|
3393
3595
|
downloadMedia: import_zod3.z.boolean().default(true).describe("Download extracted text/media files to the MCP Scraper output directory when the API server can write files. Always returns extracted media URLs even when false."),
|
|
3394
3596
|
downloadAllTracks: import_zod3.z.boolean().default(false).describe("Download every captured Instagram MP4 track instead of only the selected best video and audio tracks. Use false by default to avoid duplicate bitrates."),
|
|
@@ -3800,8 +4002,8 @@ var InstagramBrowserOutput = import_zod3.z.object({
|
|
|
3800
4002
|
executablePathConfigured: import_zod3.z.boolean()
|
|
3801
4003
|
});
|
|
3802
4004
|
var InstagramPaginationOutput = import_zod3.z.object({
|
|
3803
|
-
maxItems: import_zod3.z.number().int().min(1).max(
|
|
3804
|
-
maxScrolls: import_zod3.z.number().int().min(0).max(
|
|
4005
|
+
maxItems: import_zod3.z.number().int().min(1).max(2e3),
|
|
4006
|
+
maxScrolls: import_zod3.z.number().int().min(0).max(250),
|
|
3805
4007
|
attemptedScrolls: import_zod3.z.number().int().min(0),
|
|
3806
4008
|
stableScrolls: import_zod3.z.number().int().min(0),
|
|
3807
4009
|
stableScrollLimit: import_zod3.z.number().int().min(1).max(10),
|
|
@@ -4416,8 +4618,8 @@ function localPlanningToolAnnotations(title) {
|
|
|
4416
4618
|
}
|
|
4417
4619
|
function listSavedReports() {
|
|
4418
4620
|
try {
|
|
4419
|
-
const dir =
|
|
4420
|
-
return (0,
|
|
4621
|
+
const dir = outputBaseDir3();
|
|
4622
|
+
return (0, import_node_fs4.readdirSync)(dir).filter((f) => f.endsWith(".md")).map((f) => ({ filename: f, mtimeMs: (0, import_node_fs4.statSync)((0, import_node_path5.join)(dir, f)).mtimeMs })).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, 100);
|
|
4421
4623
|
} catch {
|
|
4422
4624
|
return [];
|
|
4423
4625
|
}
|
|
@@ -4441,9 +4643,9 @@ function registerSavedReportResources(server2) {
|
|
|
4441
4643
|
},
|
|
4442
4644
|
async (uri, variables) => {
|
|
4443
4645
|
const requested = Array.isArray(variables.filename) ? variables.filename[0] : variables.filename;
|
|
4444
|
-
const filename = (0,
|
|
4646
|
+
const filename = (0, import_node_path5.basename)(decodeURIComponent(String(requested ?? "")));
|
|
4445
4647
|
if (!filename.endsWith(".md")) throw new Error("Only saved .md reports can be read");
|
|
4446
|
-
const text = (0,
|
|
4648
|
+
const text = (0, import_node_fs4.readFileSync)((0, import_node_path5.join)(outputBaseDir3(), filename), "utf8");
|
|
4447
4649
|
return { contents: [{ uri: uri.href, mimeType: "text/markdown", text }] };
|
|
4448
4650
|
}
|
|
4449
4651
|
);
|
|
@@ -4743,10 +4945,10 @@ if (!forceStdio && (interactiveTerminal || wantsHelp)) {
|
|
|
4743
4945
|
}
|
|
4744
4946
|
function readApiKeyFile() {
|
|
4745
4947
|
const explicitPath = process.env.MCP_SCRAPER_KEY_PATH?.trim();
|
|
4746
|
-
const paths = [explicitPath, (0,
|
|
4948
|
+
const paths = [explicitPath, (0, import_node_path6.join)((0, import_node_os5.homedir)(), ".mcp-scraper-key")].filter(Boolean);
|
|
4747
4949
|
for (const path of paths) {
|
|
4748
4950
|
try {
|
|
4749
|
-
const value = (0,
|
|
4951
|
+
const value = (0, import_node_fs5.readFileSync)(path, "utf8").trim();
|
|
4750
4952
|
if (value) return value;
|
|
4751
4953
|
} catch {
|
|
4752
4954
|
}
|