mcp-scraper 0.3.9 → 0.3.11
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 +6 -4
- package/dist/bin/api-server.cjs +138 -19
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +1 -1
- package/dist/bin/browser-agent-stdio-server.cjs +104 -28
- package/dist/bin/browser-agent-stdio-server.cjs.map +1 -1
- package/dist/bin/browser-agent-stdio-server.js +2 -2
- 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 +110 -34
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.js +4 -4
- package/dist/bin/mcp-scraper-install.cjs +7 -7
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +2 -2
- package/dist/bin/mcp-stdio-server.cjs +1 -1
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +2 -2
- package/dist/{chunk-ELI6NDEG.js → chunk-KPXMPAJ3.js} +7 -7
- package/dist/chunk-KPXMPAJ3.js.map +1 -0
- package/dist/{chunk-BRUVQRJK.js → chunk-L27GJQV7.js} +105 -29
- package/dist/chunk-L27GJQV7.js.map +1 -0
- package/dist/chunk-RRE7WVHQ.js +7 -0
- package/dist/chunk-RRE7WVHQ.js.map +1 -0
- package/dist/{chunk-KDKQ5Y7V.js → chunk-VMH7SRWY.js} +2 -2
- package/dist/{server-ZSHE4R5Z.js → server-3SA5Q4OF.js} +140 -21
- package/dist/{server-ZSHE4R5Z.js.map → server-3SA5Q4OF.js.map} +1 -1
- package/docs/mcp-tool-craft-lint.generated.md +3 -2
- package/docs/mcp-tool-manifest.generated.json +43 -13
- package/package.json +1 -1
- package/dist/chunk-7VNTN4Q7.js +0 -7
- package/dist/chunk-7VNTN4Q7.js.map +0 -1
- package/dist/chunk-BRUVQRJK.js.map +0 -1
- package/dist/chunk-ELI6NDEG.js.map +0 -1
- /package/dist/{chunk-KDKQ5Y7V.js.map → chunk-VMH7SRWY.js.map} +0 -0
package/dist/bin/api-server.js
CHANGED
|
@@ -17,7 +17,7 @@ loadDotEnv();
|
|
|
17
17
|
async function main() {
|
|
18
18
|
const [{ serve }, { app }, { startWorker }, { migrate }] = await Promise.all([
|
|
19
19
|
import("@hono/node-server"),
|
|
20
|
-
import("../server-
|
|
20
|
+
import("../server-3SA5Q4OF.js"),
|
|
21
21
|
import("../worker-56IXWOQU.js"),
|
|
22
22
|
import("../db-BE4JVB3V.js")
|
|
23
23
|
]);
|
|
@@ -27,7 +27,7 @@ function browserServiceProfileSaveChanges() {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// src/version.ts
|
|
30
|
-
var PACKAGE_VERSION = "0.3.
|
|
30
|
+
var PACKAGE_VERSION = "0.3.11";
|
|
31
31
|
|
|
32
32
|
// src/mcp/browser-agent-tool-schemas.ts
|
|
33
33
|
var import_zod = require("zod");
|
|
@@ -53,10 +53,17 @@ var BrowserOpenInputSchema = {
|
|
|
53
53
|
var BrowserProfileOnboardInputSchema = {
|
|
54
54
|
email: import_zod.z.string().optional().describe("Optional account email used to derive a stable MCP Scraper profile name. This does not import cookies or browser state."),
|
|
55
55
|
profile: import_zod.z.string().optional().describe("Optional saved MCP Scraper hosted browser profile name to create or load. If omitted, email is used to derive one."),
|
|
56
|
-
|
|
56
|
+
domain: import_zod.z.string().optional().describe("Authenticated site domain to connect to the profile. Defaults to chatgpt.com for AI visibility fan-out tracking."),
|
|
57
|
+
login_url: import_zod.z.string().url().optional().describe("Optional login URL for the target domain. Defaults to https://chatgpt.com/ when domain is omitted."),
|
|
58
|
+
url: import_zod.z.string().url().optional().describe("Deprecated alias for login_url. Use login_url for the target site login page."),
|
|
57
59
|
label: import_zod.z.string().optional().describe("Optional human label for this setup session."),
|
|
58
60
|
timeout_seconds: import_zod.z.number().int().min(60).max(259200).optional().describe("How long the direct no-proxy setup session may live before auto-termination. Defaults to 600.")
|
|
59
61
|
};
|
|
62
|
+
var BrowserProfileStatusInputSchema = {
|
|
63
|
+
connection_id: import_zod.z.string().optional().describe("Auth connection id returned by browser_profile_onboard. Prefer this when available."),
|
|
64
|
+
profile: import_zod.z.string().optional().describe("Saved hosted profile name to check when connection_id is unavailable."),
|
|
65
|
+
domain: import_zod.z.string().optional().describe("Authenticated site domain to check with profile. Defaults to chatgpt.com when omitted.")
|
|
66
|
+
};
|
|
60
67
|
var BrowserSessionInputSchema = {
|
|
61
68
|
session_id: import_zod.z.string().describe("The session id returned by browser_open or browser_list_sessions. Use only a returned session_id; do not construct one yourself.")
|
|
62
69
|
};
|
|
@@ -157,7 +164,7 @@ var BrowserCaptureFanoutInputSchema = {
|
|
|
157
164
|
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."),
|
|
158
165
|
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."),
|
|
159
166
|
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."),
|
|
160
|
-
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
|
|
167
|
+
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.")
|
|
161
168
|
};
|
|
162
169
|
var FanoutSourceOutput = import_zod.z.object({
|
|
163
170
|
url: import_zod.z.string(),
|
|
@@ -199,15 +206,18 @@ var BrowserCaptureFanoutOutputSchema = {
|
|
|
199
206
|
}).describe("Objective aggregates: top sourced sites by frequency, citation order, and URL-category counts."),
|
|
200
207
|
first_party_domain: import_zod.z.string().nullable(),
|
|
201
208
|
exports: import_zod.z.object({
|
|
209
|
+
relativeTo: import_zod.z.string(),
|
|
202
210
|
dir: import_zod.z.string(),
|
|
203
211
|
json: import_zod.z.string(),
|
|
204
212
|
queriesCsv: import_zod.z.string(),
|
|
205
213
|
queriesTsv: import_zod.z.string(),
|
|
206
214
|
citationsCsv: import_zod.z.string(),
|
|
207
215
|
sourcesCsv: import_zod.z.string(),
|
|
216
|
+
browsedOnlyCsv: import_zod.z.string(),
|
|
217
|
+
snippetsCsv: import_zod.z.string(),
|
|
208
218
|
domainsCsv: import_zod.z.string(),
|
|
209
219
|
report: import_zod.z.string()
|
|
210
|
-
}).nullable().describe("
|
|
220
|
+
}).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."),
|
|
211
221
|
debug: import_zod.z.object({
|
|
212
222
|
interceptorReady: import_zod.z.boolean(),
|
|
213
223
|
rawBytes: import_zod.z.number().int().min(0),
|
|
@@ -228,15 +238,36 @@ var BrowserOpenOutputSchema = {
|
|
|
228
238
|
var BrowserProfileOnboardOutputSchema = {
|
|
229
239
|
ok: import_zod.z.boolean(),
|
|
230
240
|
tool: import_zod.z.literal("browser_profile_onboard"),
|
|
231
|
-
session_id:
|
|
241
|
+
session_id: NullableString,
|
|
242
|
+
auth_connection_id: import_zod.z.string(),
|
|
232
243
|
watch_url: import_zod.z.string(),
|
|
233
244
|
live_view_url: NullableString,
|
|
234
245
|
profile: import_zod.z.string(),
|
|
246
|
+
domain: import_zod.z.string(),
|
|
235
247
|
setup_url: import_zod.z.string(),
|
|
236
248
|
account_email: NullableString,
|
|
249
|
+
status: import_zod.z.string(),
|
|
250
|
+
flow_status: NullableString,
|
|
251
|
+
flow_step: NullableString,
|
|
252
|
+
flow_expires_at: NullableString,
|
|
253
|
+
post_login_url: NullableString,
|
|
237
254
|
next_steps: import_zod.z.array(import_zod.z.string()),
|
|
238
255
|
raw: BrowserRawObject.optional()
|
|
239
256
|
};
|
|
257
|
+
var BrowserProfileStatusOutputSchema = {
|
|
258
|
+
ok: import_zod.z.boolean(),
|
|
259
|
+
tool: import_zod.z.literal("browser_profile_status"),
|
|
260
|
+
session_id: NullableString,
|
|
261
|
+
auth_connection_id: import_zod.z.string(),
|
|
262
|
+
profile: import_zod.z.string(),
|
|
263
|
+
domain: import_zod.z.string(),
|
|
264
|
+
status: import_zod.z.string(),
|
|
265
|
+
flow_status: NullableString,
|
|
266
|
+
flow_step: NullableString,
|
|
267
|
+
flow_expires_at: NullableString,
|
|
268
|
+
post_login_url: NullableString,
|
|
269
|
+
raw: BrowserRawObject.optional()
|
|
270
|
+
};
|
|
240
271
|
var BrowserScreenshotOutputSchema = {
|
|
241
272
|
...BrowserBaseOutput,
|
|
242
273
|
tool: import_zod.z.literal("browser_screenshot"),
|
|
@@ -643,10 +674,16 @@ function replayFilePath(sessionId, replayId, filename) {
|
|
|
643
674
|
const name = requested ? safeFilePart(requested).replace(/\.mp4$/i, "") : `${stamp}-${safeFilePart(sessionId)}-${safeFilePart(replayId)}`;
|
|
644
675
|
return (0, import_node_path2.join)(outputBaseDir(), "browser-replays", `${name}.mp4`);
|
|
645
676
|
}
|
|
646
|
-
function
|
|
647
|
-
const
|
|
677
|
+
function slugPart(value) {
|
|
678
|
+
const trimmed = value?.trim().toLowerCase();
|
|
679
|
+
if (!trimmed) return null;
|
|
680
|
+
return trimmed.replace(/^https?:\/\//, "").replace(/^www\./, "").replace(/\/.*$/, "").replace(/[^a-z0-9._-]+/g, "-").replace(/[._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
681
|
+
}
|
|
682
|
+
function savedProfileNameFromEmail(email, domain) {
|
|
683
|
+
const value = slugPart(email);
|
|
648
684
|
if (!value) return null;
|
|
649
|
-
|
|
685
|
+
const domainPart = slugPart(domain);
|
|
686
|
+
return `${value}${domainPart ? `-${domainPart}` : ""}`.slice(0, 80) || null;
|
|
650
687
|
}
|
|
651
688
|
function annotatedReplayFilePath(sessionId, replayId, filename) {
|
|
652
689
|
const requested = filename?.trim();
|
|
@@ -737,45 +774,83 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
737
774
|
"browser_profile_onboard",
|
|
738
775
|
{
|
|
739
776
|
title: "Connect Hosted Browser Profile",
|
|
740
|
-
description: "Create or load a saved MCP Scraper hosted browser profile and
|
|
777
|
+
description: "Create or load a saved MCP Scraper hosted browser profile auth connection and return a user takeover login URL. This is the customer auth setup and reconnect flow for AI visibility tracking: give the user the watch_url, let them complete login for the target domain, then call browser_profile_status until status is AUTHENTICATED. If a saved profile later shows login controls, run this same setup flow again.",
|
|
741
778
|
inputSchema: BrowserProfileOnboardInputSchema,
|
|
742
779
|
outputSchema: BrowserProfileOnboardOutputSchema,
|
|
743
780
|
annotations: annotations("Connect Hosted Browser Profile")
|
|
744
781
|
},
|
|
745
782
|
async (input) => {
|
|
746
|
-
const
|
|
783
|
+
const domain = slugPart(input.domain) || "chatgpt.com";
|
|
784
|
+
const setupUrl = input.login_url ?? input.url ?? (domain === "chatgpt.com" ? "https://chatgpt.com/" : `https://${domain}/`);
|
|
785
|
+
const profile = input.profile?.trim() || savedProfileNameFromEmail(input.email, domain) || browserServiceProfileName();
|
|
747
786
|
if (!profile) {
|
|
748
787
|
return errorResult("browser_profile_onboard", {
|
|
749
788
|
error: "profile or email is required when BROWSER_AGENT_PROFILE_NAME is not available"
|
|
750
789
|
});
|
|
751
790
|
}
|
|
752
|
-
const
|
|
753
|
-
const open = await req("POST", "/agent/sessions", {
|
|
791
|
+
const open = await req("POST", "/agent/profiles/onboard", {
|
|
754
792
|
label: input.label ?? `Profile setup: ${profile}`,
|
|
755
793
|
profile,
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
timeout_seconds: input.timeout_seconds,
|
|
759
|
-
url: setupUrl
|
|
794
|
+
domain,
|
|
795
|
+
login_url: setupUrl
|
|
760
796
|
});
|
|
761
797
|
if (!open.ok) return errorResult("browser_profile_onboard", open.data);
|
|
762
|
-
const session = open.data;
|
|
763
798
|
return structuredResult({
|
|
764
799
|
ok: true,
|
|
765
800
|
tool: "browser_profile_onboard",
|
|
766
|
-
session_id:
|
|
767
|
-
|
|
768
|
-
|
|
801
|
+
session_id: null,
|
|
802
|
+
auth_connection_id: open.data.connection_id,
|
|
803
|
+
watch_url: open.data.hosted_url,
|
|
804
|
+
live_view_url: open.data.live_view_url ?? null,
|
|
769
805
|
profile,
|
|
806
|
+
domain,
|
|
770
807
|
setup_url: setupUrl,
|
|
771
808
|
account_email: input.email?.trim() || null,
|
|
809
|
+
status: open.data.status ?? null,
|
|
810
|
+
flow_status: open.data.flow_status ?? null,
|
|
811
|
+
flow_step: open.data.flow_step ?? null,
|
|
812
|
+
flow_expires_at: open.data.flow_expires_at ?? null,
|
|
813
|
+
post_login_url: open.data.post_login_url ?? null,
|
|
772
814
|
next_steps: [
|
|
773
|
-
"Open the watch_url and let the user
|
|
774
|
-
"
|
|
775
|
-
"
|
|
776
|
-
"
|
|
815
|
+
"Open the watch_url and let the user complete login for the target domain.",
|
|
816
|
+
"Call browser_profile_status with auth_connection_id until status is AUTHENTICATED.",
|
|
817
|
+
"After authentication, call browser_open with this profile name and the target site URL.",
|
|
818
|
+
"If the saved profile later shows login controls or disconnects, repeat browser_profile_onboard."
|
|
777
819
|
],
|
|
778
|
-
raw:
|
|
820
|
+
raw: open.data
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
);
|
|
824
|
+
server2.registerTool(
|
|
825
|
+
"browser_profile_status",
|
|
826
|
+
{
|
|
827
|
+
title: "Check Browser Profile Auth Status",
|
|
828
|
+
description: "Check whether a hosted browser profile auth connection is authenticated after browser_profile_onboard. Use auth_connection_id when available. Once status is AUTHENTICATED, use browser_open with the returned profile.",
|
|
829
|
+
inputSchema: BrowserProfileStatusInputSchema,
|
|
830
|
+
outputSchema: BrowserProfileStatusOutputSchema,
|
|
831
|
+
annotations: annotations("Check Browser Profile Auth Status", true)
|
|
832
|
+
},
|
|
833
|
+
async (input) => {
|
|
834
|
+
const domain = slugPart(input.domain) || "chatgpt.com";
|
|
835
|
+
const res = await req("POST", "/agent/profiles/status", {
|
|
836
|
+
...input.connection_id ? { connection_id: input.connection_id } : {},
|
|
837
|
+
...input.profile ? { profile: input.profile } : {},
|
|
838
|
+
domain
|
|
839
|
+
});
|
|
840
|
+
if (!res.ok) return errorResult("browser_profile_status", res.data);
|
|
841
|
+
return structuredResult({
|
|
842
|
+
ok: true,
|
|
843
|
+
tool: "browser_profile_status",
|
|
844
|
+
session_id: null,
|
|
845
|
+
auth_connection_id: res.data.connection_id,
|
|
846
|
+
profile: res.data.profile,
|
|
847
|
+
domain: res.data.domain,
|
|
848
|
+
status: res.data.status,
|
|
849
|
+
flow_status: res.data.flow_status ?? null,
|
|
850
|
+
flow_step: res.data.flow_step ?? null,
|
|
851
|
+
flow_expires_at: res.data.flow_expires_at ?? null,
|
|
852
|
+
post_login_url: res.data.post_login_url ?? null,
|
|
853
|
+
raw: res.data
|
|
779
854
|
});
|
|
780
855
|
}
|
|
781
856
|
);
|
|
@@ -783,7 +858,7 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
783
858
|
"browser_open",
|
|
784
859
|
{
|
|
785
860
|
title: "Open Browser Session",
|
|
786
|
-
description: "Open a direct no-proxy hosted browser session you can drive. Pass a saved profile name to load an authenticated session for a site. For customer login setup or reconnects, use browser_profile_onboard first
|
|
861
|
+
description: "Open a direct no-proxy hosted browser session you can drive. Pass a saved profile name to load an authenticated session for a site. For customer login setup or reconnects, use browser_profile_onboard first, then call browser_profile_status until status is AUTHENTICATED. Returns a session_id used by all other browser_* tools.",
|
|
787
862
|
inputSchema: BrowserOpenInputSchema,
|
|
788
863
|
outputSchema: BrowserOpenOutputSchema,
|
|
789
864
|
annotations: annotations("Open Browser Session")
|
|
@@ -1205,7 +1280,7 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1205
1280
|
"browser_capture_fanout",
|
|
1206
1281
|
{
|
|
1207
1282
|
title: "Capture AI Search Fan-Out",
|
|
1208
|
-
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
|
|
1283
|
+
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.`,
|
|
1209
1284
|
inputSchema: BrowserCaptureFanoutInputSchema,
|
|
1210
1285
|
outputSchema: BrowserCaptureFanoutOutputSchema,
|
|
1211
1286
|
annotations: annotations("Capture AI Search Fan-Out")
|
|
@@ -1234,7 +1309,8 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
1234
1309
|
prompt: input.prompt,
|
|
1235
1310
|
wait_ms: input.wait_ms,
|
|
1236
1311
|
first_party_domain: input.first_party_domain,
|
|
1237
|
-
|
|
1312
|
+
reset: input.reset,
|
|
1313
|
+
export: input.export
|
|
1238
1314
|
});
|
|
1239
1315
|
if (!res.ok) return errorResult("browser_capture_fanout", res.data, input.session_id);
|
|
1240
1316
|
const hosted = res.data?.result ?? res.data;
|