mcp-scraper 0.2.19 → 0.2.20
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 +3 -3
- package/dist/bin/api-server.cjs +1020 -348
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +3 -3
- package/dist/bin/browser-agent-stdio-server.cjs +314 -55
- 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 +321 -167
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +3 -2
- package/dist/bin/mcp-scraper-cli.js.map +1 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs +1128 -530
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.js +5 -4
- package/dist/bin/mcp-scraper-combined-stdio-server.js.map +1 -1
- package/dist/bin/mcp-scraper-install.cjs +3 -3
- 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 +726 -387
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +3 -2
- package/dist/bin/mcp-stdio-server.js.map +1 -1
- package/dist/bin/paa-harvest.cjs +5 -3
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +2 -1
- package/dist/bin/paa-harvest.js.map +1 -1
- package/dist/{chunk-TL7YTFLH.js → chunk-DBQDG7EH.js} +99 -27
- package/dist/chunk-DBQDG7EH.js.map +1 -0
- package/dist/{chunk-F44RBOJ5.js → chunk-H2R232HK.js} +216 -108
- package/dist/chunk-H2R232HK.js.map +1 -0
- package/dist/{chunk-76P4DKWR.js → chunk-L4OWOUGR.js} +315 -56
- package/dist/chunk-L4OWOUGR.js.map +1 -0
- package/dist/chunk-LFATOGDF.js +111 -0
- package/dist/chunk-LFATOGDF.js.map +1 -0
- package/dist/{chunk-MCBOZ2JF.js → chunk-MLNCKQ2B.js} +3 -3
- package/dist/chunk-MLNCKQ2B.js.map +1 -0
- package/dist/{chunk-4UEVYYDW.js → chunk-O5J7E4VX.js} +394 -57
- package/dist/chunk-O5J7E4VX.js.map +1 -0
- package/dist/{chunk-BSYPATSM.js → chunk-SXTXMFEQ.js} +2 -2
- package/dist/{chunk-CQTAKXBN.js → chunk-XGUDTDZ2.js} +7 -104
- package/dist/chunk-XGUDTDZ2.js.map +1 -0
- package/dist/chunk-XKUDVN2E.js +7 -0
- package/dist/chunk-XKUDVN2E.js.map +1 -0
- package/dist/{db-P5X6UQ3E.js → db-BE4JVB3V.js} +8 -2
- package/dist/index.cjs +5 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/{server-CRUSZA2Q.js → server-7NE45K2A.js} +211 -37
- package/dist/server-7NE45K2A.js.map +1 -0
- package/dist/{worker-OZSWIS3F.js → worker-MIYG2B2I.js} +5 -4
- package/dist/{worker-OZSWIS3F.js.map → worker-MIYG2B2I.js.map} +1 -1
- package/docs/adr/0001-in-page-graphql-interception-for-anti-bot-scraping.md +2 -2
- package/docs/mcp-tool-craft-lint.generated.md +51 -0
- package/docs/mcp-tool-manifest.generated.json +1406 -0
- package/docs/specs/local-competitive-audit-spec.md +1 -2
- package/docs/specs/mcp-tool-definition-quality-audit-spec.md +1602 -0
- package/package.json +4 -1
- package/dist/chunk-4UEVYYDW.js.map +0 -1
- package/dist/chunk-76P4DKWR.js.map +0 -1
- package/dist/chunk-CQTAKXBN.js.map +0 -1
- package/dist/chunk-F44RBOJ5.js.map +0 -1
- package/dist/chunk-MCBOZ2JF.js.map +0 -1
- package/dist/chunk-RU2HZD55.js +0 -7
- package/dist/chunk-RU2HZD55.js.map +0 -1
- package/dist/chunk-TL7YTFLH.js.map +0 -1
- package/dist/server-CRUSZA2Q.js.map +0 -1
- /package/dist/{chunk-BSYPATSM.js.map → chunk-SXTXMFEQ.js.map} +0 -0
- /package/dist/{db-P5X6UQ3E.js.map → db-BE4JVB3V.js.map} +0 -0
|
@@ -23,6 +23,27 @@ function harvestTimeoutBudget(maxQuestions, serpOnly = false) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
// src/mcp/http-mcp-tool-executor.ts
|
|
26
|
+
function youtubeVideoIdFromUrl(url) {
|
|
27
|
+
if (!url) return null;
|
|
28
|
+
try {
|
|
29
|
+
const parsed = new URL(url);
|
|
30
|
+
const host = parsed.hostname.replace(/^www\./, "").replace(/^m\./, "");
|
|
31
|
+
if (host === "youtu.be") {
|
|
32
|
+
const id = parsed.pathname.split("/").filter(Boolean)[0];
|
|
33
|
+
return id || null;
|
|
34
|
+
}
|
|
35
|
+
if (host === "youtube.com" || host === "music.youtube.com") {
|
|
36
|
+
const watchId = parsed.searchParams.get("v");
|
|
37
|
+
if (watchId) return watchId;
|
|
38
|
+
const parts = parsed.pathname.split("/").filter(Boolean);
|
|
39
|
+
const markerIndex = parts.findIndex((part) => ["shorts", "embed", "live"].includes(part));
|
|
40
|
+
if (markerIndex >= 0 && parts[markerIndex + 1]) return parts[markerIndex + 1];
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
26
47
|
var HttpMcpToolExecutor = class {
|
|
27
48
|
baseUrl;
|
|
28
49
|
apiKey;
|
|
@@ -148,7 +169,21 @@ var HttpMcpToolExecutor = class {
|
|
|
148
169
|
return this.call("/youtube/harvest", input);
|
|
149
170
|
}
|
|
150
171
|
youtubeTranscribe(input) {
|
|
151
|
-
|
|
172
|
+
const videoId = input.videoId?.trim() || youtubeVideoIdFromUrl(input.url);
|
|
173
|
+
if (!videoId) {
|
|
174
|
+
return Promise.resolve({
|
|
175
|
+
content: [{
|
|
176
|
+
type: "text",
|
|
177
|
+
text: JSON.stringify({
|
|
178
|
+
error_code: "youtube_video_id_required",
|
|
179
|
+
error: "Pass videoId from youtube_harvest or a YouTube url that contains a video id.",
|
|
180
|
+
retryable: false
|
|
181
|
+
})
|
|
182
|
+
}],
|
|
183
|
+
isError: true
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return this.call("/youtube/transcribe", { videoId });
|
|
152
187
|
}
|
|
153
188
|
facebookPageIntel(input) {
|
|
154
189
|
return this.call("/facebook/page-intel", input);
|
|
@@ -185,6 +220,10 @@ var HttpMcpToolExecutor = class {
|
|
|
185
220
|
webhookUrl: input.webhookUrl
|
|
186
221
|
}, Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 9e5);
|
|
187
222
|
}
|
|
223
|
+
workflowStep(input) {
|
|
224
|
+
const timeoutMs = this.httpTimeoutOverrideMs ?? Number(process.env.MCP_SCRAPER_WORKFLOW_TIMEOUT_MS ?? 9e5);
|
|
225
|
+
return this.call(`/workflows/runs/${encodeURIComponent(input.runId)}/step`, {}, Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 9e5);
|
|
226
|
+
}
|
|
188
227
|
workflowStatus(input) {
|
|
189
228
|
return this.getJson(`/workflows/runs/${encodeURIComponent(input.runId)}`);
|
|
190
229
|
}
|
|
@@ -212,10 +251,22 @@ var import_node_os2 = require("os");
|
|
|
212
251
|
var import_node_path2 = require("path");
|
|
213
252
|
|
|
214
253
|
// src/version.ts
|
|
215
|
-
var PACKAGE_VERSION = "0.2.
|
|
254
|
+
var PACKAGE_VERSION = "0.2.20";
|
|
216
255
|
|
|
217
256
|
// src/mcp/browser-agent-tool-schemas.ts
|
|
218
257
|
var import_zod = require("zod");
|
|
258
|
+
var NullableString = import_zod.z.string().nullable();
|
|
259
|
+
var BrowserRawObject = import_zod.z.record(import_zod.z.unknown());
|
|
260
|
+
var BrowserElementOutput = import_zod.z.record(import_zod.z.unknown());
|
|
261
|
+
var BrowserBaseOutput = {
|
|
262
|
+
ok: import_zod.z.boolean().describe("Whether the browser-agent action succeeded."),
|
|
263
|
+
tool: import_zod.z.string().describe("Browser Agent MCP tool that produced this response."),
|
|
264
|
+
session_id: NullableString.describe("Browser session id when the response is scoped to a session.")
|
|
265
|
+
};
|
|
266
|
+
var BrowserReplayBaseOutput = {
|
|
267
|
+
...BrowserBaseOutput,
|
|
268
|
+
replay_id: NullableString.describe("Replay id when the response is scoped to a replay.")
|
|
269
|
+
};
|
|
219
270
|
var BrowserOpenInputSchema = {
|
|
220
271
|
label: import_zod.z.string().optional().describe("Optional human label for this session, shown in the watch console."),
|
|
221
272
|
url: import_zod.z.string().url().optional().describe("Optional URL to navigate to immediately after opening."),
|
|
@@ -223,7 +274,7 @@ var BrowserOpenInputSchema = {
|
|
|
223
274
|
timeout_seconds: import_zod.z.number().int().min(60).max(259200).optional().describe("How long the session may live before auto-termination. Defaults to 600. The browser idles into a zero-cost standby between actions, so a longer timeout is cheap.")
|
|
224
275
|
};
|
|
225
276
|
var BrowserSessionInputSchema = {
|
|
226
|
-
session_id: import_zod.z.string().describe("The session id returned by browser_open.")
|
|
277
|
+
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.")
|
|
227
278
|
};
|
|
228
279
|
var BrowserLocateTargetSchema = import_zod.z.object({
|
|
229
280
|
name: import_zod.z.string().optional().describe("Optional label for this target, echoed in the result."),
|
|
@@ -235,43 +286,43 @@ var BrowserLocateTargetSchema = import_zod.z.object({
|
|
|
235
286
|
message: "target requires selector or text"
|
|
236
287
|
});
|
|
237
288
|
var BrowserLocateInputSchema = {
|
|
238
|
-
session_id: import_zod.z.string().describe("The session id returned by browser_open."),
|
|
289
|
+
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."),
|
|
239
290
|
targets: import_zod.z.array(BrowserLocateTargetSchema).min(1).max(20).describe("DOM targets to locate in the current viewport. Use selectors for exact elements, or text for visible text ranges.")
|
|
240
291
|
};
|
|
241
292
|
var BrowserGotoInputSchema = {
|
|
242
|
-
session_id: import_zod.z.string().describe("The session id returned by browser_open."),
|
|
293
|
+
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."),
|
|
243
294
|
url: import_zod.z.string().url().describe("URL to navigate the browser to.")
|
|
244
295
|
};
|
|
245
296
|
var BrowserClickInputSchema = {
|
|
246
|
-
session_id: import_zod.z.string().describe("The session id returned by browser_open."),
|
|
247
|
-
x: import_zod.z.number().describe("X coordinate to click, in screenshot pixels. Use the
|
|
248
|
-
y: import_zod.z.number().describe("Y coordinate to click, in screenshot pixels."),
|
|
297
|
+
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."),
|
|
298
|
+
x: import_zod.z.number().describe("X coordinate to click, in screenshot pixels. Use only coordinates from the latest browser_screenshot, browser_read, or browser_locate result; do not guess."),
|
|
299
|
+
y: import_zod.z.number().describe("Y coordinate to click, in screenshot pixels. Use only coordinates from the latest browser_screenshot, browser_read, or browser_locate result; do not guess."),
|
|
249
300
|
button: import_zod.z.enum(["left", "right", "middle"]).default("left").describe("Mouse button."),
|
|
250
301
|
num_clicks: import_zod.z.number().int().min(1).max(3).optional().describe("Number of clicks, e.g. 2 for double-click.")
|
|
251
302
|
};
|
|
252
303
|
var BrowserTypeInputSchema = {
|
|
253
|
-
session_id: import_zod.z.string().describe("The session id returned by browser_open."),
|
|
304
|
+
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."),
|
|
254
305
|
text: import_zod.z.string().describe("Text to type at the current focus. Click a field first to focus it."),
|
|
255
306
|
delay: import_zod.z.number().int().min(0).max(500).optional().describe("Optional per-keystroke delay in ms for human-like typing.")
|
|
256
307
|
};
|
|
257
308
|
var BrowserScrollInputSchema = {
|
|
258
|
-
session_id: import_zod.z.string().describe("The session id returned by browser_open."),
|
|
309
|
+
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."),
|
|
259
310
|
delta_y: import_zod.z.number().default(5).describe("Vertical scroll in wheel units. Positive scrolls down, negative up."),
|
|
260
311
|
delta_x: import_zod.z.number().default(0).describe("Horizontal scroll in wheel units."),
|
|
261
312
|
x: import_zod.z.number().optional().describe("X position to scroll at. Defaults to screen center."),
|
|
262
313
|
y: import_zod.z.number().optional().describe("Y position to scroll at. Defaults to screen center.")
|
|
263
314
|
};
|
|
264
315
|
var BrowserPressInputSchema = {
|
|
265
|
-
session_id: import_zod.z.string().describe("The session id returned by browser_open."),
|
|
316
|
+
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."),
|
|
266
317
|
keys: import_zod.z.array(import_zod.z.string()).min(1).describe('Keys or combinations to press, e.g. ["Return"], ["Ctrl+a"], ["Ctrl+Shift+Tab"].')
|
|
267
318
|
};
|
|
268
319
|
var BrowserReplayStopInputSchema = {
|
|
269
|
-
session_id: import_zod.z.string().describe("The session id returned by browser_open."),
|
|
270
|
-
replay_id: import_zod.z.string().describe("The replay id returned by browser_replay_start.")
|
|
320
|
+
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."),
|
|
321
|
+
replay_id: import_zod.z.string().describe("The replay id returned by browser_replay_start or browser_list_replays. Use only a returned replay_id; do not construct one yourself.")
|
|
271
322
|
};
|
|
272
323
|
var BrowserReplayDownloadInputSchema = {
|
|
273
|
-
session_id: import_zod.z.string().describe("The session id returned by browser_open."),
|
|
274
|
-
replay_id: import_zod.z.string().describe("The replay id returned by browser_replay_start or browser_list_replays."),
|
|
324
|
+
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."),
|
|
325
|
+
replay_id: import_zod.z.string().describe("The replay id returned by browser_replay_start or browser_list_replays. Use only a returned replay_id; do not construct one yourself."),
|
|
275
326
|
filename: import_zod.z.string().optional().describe("Optional local MP4 filename. Defaults to a timestamped replay filename.")
|
|
276
327
|
};
|
|
277
328
|
var BrowserReplayAnnotationSchema = import_zod.z.object({
|
|
@@ -293,7 +344,7 @@ var BrowserReplayAnnotationSchema = import_zod.z.object({
|
|
|
293
344
|
thickness: import_zod.z.number().min(1).max(24).optional().describe("Stroke thickness in pixels. Defaults to 5.")
|
|
294
345
|
});
|
|
295
346
|
var BrowserReplayMarkInputSchema = {
|
|
296
|
-
session_id: import_zod.z.string().describe("The session id returned by browser_open. A replay must already be recording."),
|
|
347
|
+
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. A replay must already be recording."),
|
|
297
348
|
target: BrowserLocateTargetSchema.describe("The exact DOM element or text range to mark in the current viewport."),
|
|
298
349
|
type: import_zod.z.enum(["box", "circle", "underline", "arrow"]).default("box").describe("Annotation style to generate. Labels are included on the returned annotation when label is provided."),
|
|
299
350
|
label: import_zod.z.string().max(120).optional().describe("Optional callout text to render near the target."),
|
|
@@ -304,8 +355,8 @@ var BrowserReplayMarkInputSchema = {
|
|
|
304
355
|
duration_seconds: import_zod.z.number().min(0.5).max(30).default(4).describe("How long the generated annotation should remain visible.")
|
|
305
356
|
};
|
|
306
357
|
var BrowserReplayAnnotateInputSchema = {
|
|
307
|
-
session_id: import_zod.z.string().describe("The session id returned by browser_open."),
|
|
308
|
-
replay_id: import_zod.z.string().describe("The replay id returned by browser_replay_start or browser_list_replays."),
|
|
358
|
+
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."),
|
|
359
|
+
replay_id: import_zod.z.string().describe("The replay id returned by browser_replay_start or browser_list_replays. Use only a returned replay_id; do not construct one yourself."),
|
|
309
360
|
annotations: import_zod.z.array(BrowserReplayAnnotationSchema).min(1).max(50).describe("Timed overlay annotations to render on the replay. Prefer annotations returned by browser_replay_mark; otherwise use exact DOM bounds from browser_locate/browser_screenshot/browser_read."),
|
|
310
361
|
filename: import_zod.z.string().optional().describe("Optional output MP4 filename. Defaults to a timestamped annotated replay filename."),
|
|
311
362
|
source_width: import_zod.z.number().positive().optional().describe("Width of the screenshot coordinate space used for annotations. Defaults to the replay video width."),
|
|
@@ -316,6 +367,113 @@ var BrowserReplayAnnotateInputSchema = {
|
|
|
316
367
|
var BrowserListInputSchema = {
|
|
317
368
|
include_closed: import_zod.z.boolean().default(false).describe("Include closed sessions in the list.")
|
|
318
369
|
};
|
|
370
|
+
var BrowserOpenOutputSchema = {
|
|
371
|
+
ok: import_zod.z.boolean(),
|
|
372
|
+
tool: import_zod.z.literal("browser_open"),
|
|
373
|
+
session_id: import_zod.z.string().describe("Session id returned by browser_open. Use only this exact value in later browser_* calls; do not construct one yourself."),
|
|
374
|
+
watch_url: import_zod.z.string().describe("Human watch/takeover URL for this browser session."),
|
|
375
|
+
live_view_url: NullableString.describe("Raw live-view URL from the browser service when available."),
|
|
376
|
+
url: NullableString.describe("Initial URL requested by the caller, when provided."),
|
|
377
|
+
hint: import_zod.z.string(),
|
|
378
|
+
raw: BrowserRawObject.optional()
|
|
379
|
+
};
|
|
380
|
+
var BrowserScreenshotOutputSchema = {
|
|
381
|
+
...BrowserBaseOutput,
|
|
382
|
+
tool: import_zod.z.literal("browser_screenshot"),
|
|
383
|
+
url: NullableString,
|
|
384
|
+
title: NullableString,
|
|
385
|
+
text: import_zod.z.string(),
|
|
386
|
+
elements: import_zod.z.array(BrowserElementOutput),
|
|
387
|
+
screenshot: import_zod.z.object({
|
|
388
|
+
mime_type: import_zod.z.string(),
|
|
389
|
+
inline: import_zod.z.boolean()
|
|
390
|
+
}).nullable()
|
|
391
|
+
};
|
|
392
|
+
var BrowserReadOutputSchema = {
|
|
393
|
+
...BrowserBaseOutput,
|
|
394
|
+
tool: import_zod.z.literal("browser_read"),
|
|
395
|
+
url: NullableString,
|
|
396
|
+
title: NullableString,
|
|
397
|
+
text: import_zod.z.string(),
|
|
398
|
+
elements: import_zod.z.array(BrowserElementOutput),
|
|
399
|
+
raw: BrowserRawObject.optional()
|
|
400
|
+
};
|
|
401
|
+
var BrowserLocateOutputSchema = {
|
|
402
|
+
...BrowserBaseOutput,
|
|
403
|
+
tool: import_zod.z.literal("browser_locate"),
|
|
404
|
+
url: NullableString,
|
|
405
|
+
title: NullableString,
|
|
406
|
+
viewport: BrowserRawObject.nullable(),
|
|
407
|
+
replay: BrowserRawObject.nullable(),
|
|
408
|
+
targets: import_zod.z.array(BrowserRawObject),
|
|
409
|
+
raw: BrowserRawObject.optional()
|
|
410
|
+
};
|
|
411
|
+
var BrowserActionOutputSchema = {
|
|
412
|
+
...BrowserBaseOutput,
|
|
413
|
+
result: BrowserRawObject.describe("Provider action result. Check ok and follow with browser_screenshot/browser_read when page state matters."),
|
|
414
|
+
nextRecommendedTool: import_zod.z.string().nullable()
|
|
415
|
+
};
|
|
416
|
+
var BrowserReplayStartOutputSchema = {
|
|
417
|
+
...BrowserReplayBaseOutput,
|
|
418
|
+
tool: import_zod.z.literal("browser_replay_start"),
|
|
419
|
+
view_url: NullableString,
|
|
420
|
+
download_url: NullableString,
|
|
421
|
+
raw: BrowserRawObject.optional()
|
|
422
|
+
};
|
|
423
|
+
var BrowserReplayStopOutputSchema = {
|
|
424
|
+
...BrowserReplayBaseOutput,
|
|
425
|
+
tool: import_zod.z.literal("browser_replay_stop"),
|
|
426
|
+
view_url: NullableString,
|
|
427
|
+
download_url: NullableString,
|
|
428
|
+
raw: BrowserRawObject.optional()
|
|
429
|
+
};
|
|
430
|
+
var BrowserListReplaysOutputSchema = {
|
|
431
|
+
...BrowserBaseOutput,
|
|
432
|
+
tool: import_zod.z.literal("browser_list_replays"),
|
|
433
|
+
replays: import_zod.z.array(BrowserRawObject),
|
|
434
|
+
count: import_zod.z.number().int().min(0)
|
|
435
|
+
};
|
|
436
|
+
var BrowserReplayDownloadOutputSchema = {
|
|
437
|
+
...BrowserReplayBaseOutput,
|
|
438
|
+
tool: import_zod.z.literal("browser_replay_download"),
|
|
439
|
+
file_path: NullableString,
|
|
440
|
+
bytes: import_zod.z.number().int().min(0).nullable(),
|
|
441
|
+
mime_type: NullableString,
|
|
442
|
+
download_url: NullableString
|
|
443
|
+
};
|
|
444
|
+
var BrowserReplayMarkOutputSchema = {
|
|
445
|
+
...BrowserReplayBaseOutput,
|
|
446
|
+
tool: import_zod.z.literal("browser_replay_mark"),
|
|
447
|
+
annotation: BrowserRawObject,
|
|
448
|
+
source_width: import_zod.z.number().nullable(),
|
|
449
|
+
source_height: import_zod.z.number().nullable(),
|
|
450
|
+
target: BrowserRawObject.nullable(),
|
|
451
|
+
hint: import_zod.z.string()
|
|
452
|
+
};
|
|
453
|
+
var BrowserReplayAnnotateOutputSchema = {
|
|
454
|
+
...BrowserReplayBaseOutput,
|
|
455
|
+
tool: import_zod.z.literal("browser_replay_annotate"),
|
|
456
|
+
source_file_path: NullableString,
|
|
457
|
+
annotated_file_path: NullableString,
|
|
458
|
+
bytes: import_zod.z.number().int().min(0).nullable(),
|
|
459
|
+
width: import_zod.z.number().int().min(0).nullable(),
|
|
460
|
+
height: import_zod.z.number().int().min(0).nullable(),
|
|
461
|
+
annotation_count: import_zod.z.number().int().min(0).nullable(),
|
|
462
|
+
mime_type: NullableString
|
|
463
|
+
};
|
|
464
|
+
var BrowserCloseOutputSchema = {
|
|
465
|
+
...BrowserBaseOutput,
|
|
466
|
+
tool: import_zod.z.literal("browser_close"),
|
|
467
|
+
closed: import_zod.z.boolean(),
|
|
468
|
+
raw: BrowserRawObject.optional()
|
|
469
|
+
};
|
|
470
|
+
var BrowserListSessionsOutputSchema = {
|
|
471
|
+
ok: import_zod.z.boolean(),
|
|
472
|
+
tool: import_zod.z.literal("browser_list_sessions"),
|
|
473
|
+
session_id: import_zod.z.null(),
|
|
474
|
+
sessions: import_zod.z.array(BrowserRawObject),
|
|
475
|
+
count: import_zod.z.number().int().min(0)
|
|
476
|
+
};
|
|
319
477
|
|
|
320
478
|
// src/mcp/replay-annotator.ts
|
|
321
479
|
var import_node_child_process = require("child_process");
|
|
@@ -578,8 +736,40 @@ async function annotateReplayVideo(inputFilePath, outputFilePath, options) {
|
|
|
578
736
|
}
|
|
579
737
|
|
|
580
738
|
// src/mcp/browser-agent-mcp-server.ts
|
|
581
|
-
function
|
|
582
|
-
return {
|
|
739
|
+
function structuredResult(value, isError = false) {
|
|
740
|
+
return {
|
|
741
|
+
content: [{ type: "text", text: JSON.stringify(value) }],
|
|
742
|
+
structuredContent: value,
|
|
743
|
+
isError
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
function errorMessage(value) {
|
|
747
|
+
if (value && typeof value === "object") {
|
|
748
|
+
const data = value;
|
|
749
|
+
if (typeof data.error === "string") return data.error;
|
|
750
|
+
if (typeof data.message === "string") return data.message;
|
|
751
|
+
}
|
|
752
|
+
return typeof value === "string" ? value : "Browser Agent request failed";
|
|
753
|
+
}
|
|
754
|
+
function errorResult(tool, value, sessionId = null, replayId = null) {
|
|
755
|
+
return structuredResult({
|
|
756
|
+
ok: false,
|
|
757
|
+
tool,
|
|
758
|
+
session_id: sessionId,
|
|
759
|
+
...replayId !== null ? { replay_id: replayId } : {},
|
|
760
|
+
error: errorMessage(value),
|
|
761
|
+
raw: value && typeof value === "object" ? value : { value }
|
|
762
|
+
}, true);
|
|
763
|
+
}
|
|
764
|
+
function actionResult(tool, sessionId, ok, data, nextRecommendedTool = "browser_screenshot") {
|
|
765
|
+
if (!ok) return errorResult(tool, data, sessionId);
|
|
766
|
+
return structuredResult({
|
|
767
|
+
ok: true,
|
|
768
|
+
tool,
|
|
769
|
+
session_id: sessionId,
|
|
770
|
+
result: data && typeof data === "object" ? data : { value: data },
|
|
771
|
+
nextRecommendedTool
|
|
772
|
+
});
|
|
583
773
|
}
|
|
584
774
|
function outputBaseDir() {
|
|
585
775
|
return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || (0, import_node_path2.join)((0, import_node_os2.homedir)(), "Downloads", "mcp-scraper");
|
|
@@ -679,6 +869,7 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
679
869
|
title: "Open Browser Session",
|
|
680
870
|
description: "Open a fresh cloud browser you can drive. Returns a session_id used by all other browser_* tools, and a watch_url where a human can watch live or take over. Anti-bot stealth and automatic CAPTCHA/Cloudflare solving are on by default: if a Cloudflare or CAPTCHA challenge appears, do NOT click it \u2014 wait a few seconds and call browser_screenshot again; it is solved automatically. Billing: metered per second of active browser work at ~4 credits per minute; idle and standby time are free. Call browser_close when done to stop the meter. After opening, call browser_screenshot to see the page.",
|
|
681
871
|
inputSchema: BrowserOpenInputSchema,
|
|
872
|
+
outputSchema: BrowserOpenOutputSchema,
|
|
682
873
|
annotations: annotations("Open Browser Session")
|
|
683
874
|
},
|
|
684
875
|
async (input) => {
|
|
@@ -687,16 +878,20 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
687
878
|
profile: input.profile,
|
|
688
879
|
timeout_seconds: input.timeout_seconds
|
|
689
880
|
});
|
|
690
|
-
if (!open.ok) return
|
|
881
|
+
if (!open.ok) return errorResult("browser_open", open.data);
|
|
691
882
|
const session = open.data;
|
|
692
883
|
if (input.url) {
|
|
693
884
|
await req("POST", `/agent/sessions/${session.session_id}/goto`, { url: input.url });
|
|
694
885
|
}
|
|
695
|
-
return
|
|
886
|
+
return structuredResult({
|
|
887
|
+
ok: true,
|
|
888
|
+
tool: "browser_open",
|
|
696
889
|
session_id: session.session_id,
|
|
697
890
|
watch_url: `${consoleBase}/console/${session.session_id}`,
|
|
698
891
|
live_view_url: session.live_view_url ?? null,
|
|
699
|
-
|
|
892
|
+
url: input.url ?? null,
|
|
893
|
+
hint: "Call browser_screenshot to see the page. Click by the x,y of an element from the snapshot.",
|
|
894
|
+
raw: session
|
|
700
895
|
});
|
|
701
896
|
}
|
|
702
897
|
);
|
|
@@ -706,19 +901,30 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
706
901
|
title: "See Page (Screenshot + Elements)",
|
|
707
902
|
description: "Capture what the browser currently shows. Returns a screenshot image PLUS a text snapshot listing interactive elements with their center x,y coordinates, the page url and title, and visible text. This is your primary way to perceive the page. Click elements by their listed x,y. If a Cloudflare/CAPTCHA challenge is visible, wait and screenshot again rather than clicking it.",
|
|
708
903
|
inputSchema: BrowserSessionInputSchema,
|
|
904
|
+
outputSchema: BrowserScreenshotOutputSchema,
|
|
709
905
|
annotations: annotations("See Page", true)
|
|
710
906
|
},
|
|
711
907
|
async (input) => {
|
|
712
908
|
const res = await req("POST", `/agent/sessions/${input.session_id}/screenshot`);
|
|
713
|
-
if (!res.ok) return
|
|
909
|
+
if (!res.ok) return errorResult("browser_screenshot", res.data, input.session_id);
|
|
714
910
|
const { image_base64, mime_type, url, title, elements, text } = res.data;
|
|
715
911
|
const content = [];
|
|
716
912
|
if (image_base64) content.push({ type: "image", data: image_base64, mimeType: mime_type ?? "image/png" });
|
|
913
|
+
const structured = {
|
|
914
|
+
ok: true,
|
|
915
|
+
tool: "browser_screenshot",
|
|
916
|
+
session_id: input.session_id,
|
|
917
|
+
url: url ?? null,
|
|
918
|
+
title: title ?? null,
|
|
919
|
+
text: typeof text === "string" ? text : "",
|
|
920
|
+
elements: Array.isArray(elements) ? elements : [],
|
|
921
|
+
screenshot: image_base64 ? { mime_type: mime_type ?? "image/png", inline: true } : null
|
|
922
|
+
};
|
|
717
923
|
content.push({
|
|
718
924
|
type: "text",
|
|
719
|
-
text: JSON.stringify(
|
|
925
|
+
text: JSON.stringify(structured)
|
|
720
926
|
});
|
|
721
|
-
return { content };
|
|
927
|
+
return { content, structuredContent: structured };
|
|
722
928
|
}
|
|
723
929
|
);
|
|
724
930
|
server2.registerTool(
|
|
@@ -727,11 +933,22 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
727
933
|
title: "Read Page Text + Elements",
|
|
728
934
|
description: "Return the page url, title, visible text, and the list of interactive elements (with x,y) without an image. Cheaper than browser_screenshot when you only need to read content or find a target element to click.",
|
|
729
935
|
inputSchema: BrowserSessionInputSchema,
|
|
936
|
+
outputSchema: BrowserReadOutputSchema,
|
|
730
937
|
annotations: annotations("Read Page", true)
|
|
731
938
|
},
|
|
732
939
|
async (input) => {
|
|
733
940
|
const res = await req("POST", `/agent/sessions/${input.session_id}/read`);
|
|
734
|
-
return
|
|
941
|
+
if (!res.ok) return errorResult("browser_read", res.data, input.session_id);
|
|
942
|
+
return structuredResult({
|
|
943
|
+
ok: true,
|
|
944
|
+
tool: "browser_read",
|
|
945
|
+
session_id: input.session_id,
|
|
946
|
+
url: res.data?.url ?? null,
|
|
947
|
+
title: res.data?.title ?? null,
|
|
948
|
+
text: typeof res.data?.text === "string" ? res.data.text : "",
|
|
949
|
+
elements: Array.isArray(res.data?.elements) ? res.data.elements : [],
|
|
950
|
+
raw: res.data
|
|
951
|
+
});
|
|
735
952
|
}
|
|
736
953
|
);
|
|
737
954
|
server2.registerTool(
|
|
@@ -740,32 +957,46 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
740
957
|
title: "Locate DOM Targets",
|
|
741
958
|
description: "Locate exact visible DOM elements or text ranges in the current browser viewport and return left/top/width/height bounds in screenshot pixels. Use this before drawing annotations or when a callout must literally circle, box, underline, or point to a real element. Prefer CSS selectors for exact UI elements; use text when selector is unknown. When a replay is actively recording, the result includes replay_elapsed_seconds for timing.",
|
|
742
959
|
inputSchema: BrowserLocateInputSchema,
|
|
960
|
+
outputSchema: BrowserLocateOutputSchema,
|
|
743
961
|
annotations: annotations("Locate DOM Targets", true)
|
|
744
962
|
},
|
|
745
963
|
async (input) => {
|
|
746
964
|
const res = await req("POST", `/agent/sessions/${input.session_id}/locate`, { targets: input.targets });
|
|
747
|
-
return
|
|
965
|
+
if (!res.ok) return errorResult("browser_locate", res.data, input.session_id);
|
|
966
|
+
return structuredResult({
|
|
967
|
+
ok: true,
|
|
968
|
+
tool: "browser_locate",
|
|
969
|
+
session_id: input.session_id,
|
|
970
|
+
url: res.data?.url ?? null,
|
|
971
|
+
title: res.data?.title ?? null,
|
|
972
|
+
viewport: res.data?.viewport ?? null,
|
|
973
|
+
replay: res.data?.replay ?? null,
|
|
974
|
+
targets: Array.isArray(res.data?.targets) ? res.data.targets : [],
|
|
975
|
+
raw: res.data
|
|
976
|
+
});
|
|
748
977
|
}
|
|
749
978
|
);
|
|
750
979
|
server2.registerTool(
|
|
751
980
|
"browser_goto",
|
|
752
981
|
{
|
|
753
982
|
title: "Navigate To URL",
|
|
754
|
-
description: "Navigate
|
|
983
|
+
description: "Navigate an existing browser session to a URL when the user asks you to go to another page or continue browsing in the same session. Use browser_open first if no session exists. Follow with browser_screenshot to see redirects, login walls, CAPTCHA/Cloudflare state, or the loaded page.",
|
|
755
984
|
inputSchema: BrowserGotoInputSchema,
|
|
985
|
+
outputSchema: BrowserActionOutputSchema,
|
|
756
986
|
annotations: annotations("Navigate To URL")
|
|
757
987
|
},
|
|
758
988
|
async (input) => {
|
|
759
989
|
const res = await req("POST", `/agent/sessions/${input.session_id}/goto`, { url: input.url });
|
|
760
|
-
return
|
|
990
|
+
return actionResult("browser_goto", input.session_id, res.ok, res.data, "browser_screenshot");
|
|
761
991
|
}
|
|
762
992
|
);
|
|
763
993
|
server2.registerTool(
|
|
764
994
|
"browser_click",
|
|
765
995
|
{
|
|
766
996
|
title: "Click",
|
|
767
|
-
description: "Click
|
|
997
|
+
description: "Click a visible page target using screenshot pixel coordinates. Use this when the user asks you to press a button, open a menu, choose a result, or activate a visible UI element. Use x/y only from the latest browser_screenshot, browser_read, or browser_locate result; do not guess coordinates. Follow with browser_screenshot when the click may change the page.",
|
|
768
998
|
inputSchema: BrowserClickInputSchema,
|
|
999
|
+
outputSchema: BrowserActionOutputSchema,
|
|
769
1000
|
annotations: annotations("Click")
|
|
770
1001
|
},
|
|
771
1002
|
async (input) => {
|
|
@@ -775,28 +1006,30 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
775
1006
|
button: input.button,
|
|
776
1007
|
num_clicks: input.num_clicks
|
|
777
1008
|
});
|
|
778
|
-
return
|
|
1009
|
+
return actionResult("browser_click", input.session_id, res.ok, res.data, "browser_screenshot");
|
|
779
1010
|
}
|
|
780
1011
|
);
|
|
781
1012
|
server2.registerTool(
|
|
782
1013
|
"browser_type",
|
|
783
1014
|
{
|
|
784
1015
|
title: "Type Text",
|
|
785
|
-
description: 'Type text
|
|
1016
|
+
description: 'Type text into the currently focused browser field. Use this when the user asks you to fill a search box, form field, login field, or editable text area. Click or Tab to the field first if focus is uncertain. Use browser_press with ["Return"] to submit, and browser_screenshot afterward when page state matters.',
|
|
786
1017
|
inputSchema: BrowserTypeInputSchema,
|
|
1018
|
+
outputSchema: BrowserActionOutputSchema,
|
|
787
1019
|
annotations: annotations("Type Text")
|
|
788
1020
|
},
|
|
789
1021
|
async (input) => {
|
|
790
1022
|
const res = await req("POST", `/agent/sessions/${input.session_id}/type`, { text: input.text, delay: input.delay });
|
|
791
|
-
return
|
|
1023
|
+
return actionResult("browser_type", input.session_id, res.ok, res.data, "browser_screenshot");
|
|
792
1024
|
}
|
|
793
1025
|
);
|
|
794
1026
|
server2.registerTool(
|
|
795
1027
|
"browser_scroll",
|
|
796
1028
|
{
|
|
797
1029
|
title: "Scroll",
|
|
798
|
-
description: "Scroll the page. Positive delta_y scrolls down. Follow with browser_screenshot to
|
|
1030
|
+
description: "Scroll the page to reveal more content before reading, clicking, or locating elements. Positive delta_y scrolls down; negative delta_y scrolls up. Follow with browser_screenshot or browser_read to inspect newly revealed content.",
|
|
799
1031
|
inputSchema: BrowserScrollInputSchema,
|
|
1032
|
+
outputSchema: BrowserActionOutputSchema,
|
|
800
1033
|
annotations: annotations("Scroll")
|
|
801
1034
|
},
|
|
802
1035
|
async (input) => {
|
|
@@ -806,20 +1039,21 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
806
1039
|
x: input.x,
|
|
807
1040
|
y: input.y
|
|
808
1041
|
});
|
|
809
|
-
return
|
|
1042
|
+
return actionResult("browser_scroll", input.session_id, res.ok, res.data, "browser_screenshot");
|
|
810
1043
|
}
|
|
811
1044
|
);
|
|
812
1045
|
server2.registerTool(
|
|
813
1046
|
"browser_press",
|
|
814
1047
|
{
|
|
815
1048
|
title: "Press Keys",
|
|
816
|
-
description: 'Press keys or combinations,
|
|
1049
|
+
description: 'Press keyboard keys or combinations in the active browser session. Use this for submit, Escape, Tab navigation, select-all, or keyboard shortcuts. Examples: ["Return"], ["Escape"], ["Tab"], ["Ctrl+a"], ["Ctrl+Shift+Tab"]. Use browser_type for text entry and browser_screenshot after keypresses that may change the page.',
|
|
817
1050
|
inputSchema: BrowserPressInputSchema,
|
|
1051
|
+
outputSchema: BrowserActionOutputSchema,
|
|
818
1052
|
annotations: annotations("Press Keys")
|
|
819
1053
|
},
|
|
820
1054
|
async (input) => {
|
|
821
1055
|
const res = await req("POST", `/agent/sessions/${input.session_id}/press`, { keys: input.keys });
|
|
822
|
-
return
|
|
1056
|
+
return actionResult("browser_press", input.session_id, res.ok, res.data, "browser_screenshot");
|
|
823
1057
|
}
|
|
824
1058
|
);
|
|
825
1059
|
server2.registerTool(
|
|
@@ -828,11 +1062,21 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
828
1062
|
title: "Start Recording",
|
|
829
1063
|
description: "Start recording an MP4 replay of the session. Returns replay_id, view_url when available, and a download_url. Use to capture a task for later review; stop with browser_replay_stop.",
|
|
830
1064
|
inputSchema: BrowserSessionInputSchema,
|
|
1065
|
+
outputSchema: BrowserReplayStartOutputSchema,
|
|
831
1066
|
annotations: annotations("Start Recording")
|
|
832
1067
|
},
|
|
833
1068
|
async (input) => {
|
|
834
1069
|
const res = await req("POST", `/agent/sessions/${input.session_id}/replay/start`);
|
|
835
|
-
return
|
|
1070
|
+
if (!res.ok) return errorResult("browser_replay_start", res.data, input.session_id);
|
|
1071
|
+
return structuredResult({
|
|
1072
|
+
ok: true,
|
|
1073
|
+
tool: "browser_replay_start",
|
|
1074
|
+
session_id: input.session_id,
|
|
1075
|
+
replay_id: res.data?.replay_id ?? res.data?.replayId ?? null,
|
|
1076
|
+
view_url: res.data?.view_url ?? res.data?.viewUrl ?? null,
|
|
1077
|
+
download_url: res.data?.download_url ?? res.data?.downloadUrl ?? null,
|
|
1078
|
+
raw: res.data
|
|
1079
|
+
});
|
|
836
1080
|
}
|
|
837
1081
|
);
|
|
838
1082
|
server2.registerTool(
|
|
@@ -841,11 +1085,21 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
841
1085
|
title: "Stop Recording",
|
|
842
1086
|
description: "Stop a replay recording and expose the final view_url and download_url. Use browser_replay_download to save the MP4 locally.",
|
|
843
1087
|
inputSchema: BrowserReplayStopInputSchema,
|
|
1088
|
+
outputSchema: BrowserReplayStopOutputSchema,
|
|
844
1089
|
annotations: annotations("Stop Recording")
|
|
845
1090
|
},
|
|
846
1091
|
async (input) => {
|
|
847
1092
|
const res = await req("POST", `/agent/sessions/${input.session_id}/replay/stop`, { replay_id: input.replay_id });
|
|
848
|
-
return
|
|
1093
|
+
if (!res.ok) return errorResult("browser_replay_stop", res.data, input.session_id, input.replay_id);
|
|
1094
|
+
return structuredResult({
|
|
1095
|
+
ok: true,
|
|
1096
|
+
tool: "browser_replay_stop",
|
|
1097
|
+
session_id: input.session_id,
|
|
1098
|
+
replay_id: res.data?.replay_id ?? res.data?.replayId ?? input.replay_id,
|
|
1099
|
+
view_url: res.data?.view_url ?? res.data?.viewUrl ?? null,
|
|
1100
|
+
download_url: res.data?.download_url ?? res.data?.downloadUrl ?? null,
|
|
1101
|
+
raw: res.data
|
|
1102
|
+
});
|
|
849
1103
|
}
|
|
850
1104
|
);
|
|
851
1105
|
server2.registerTool(
|
|
@@ -854,11 +1108,20 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
854
1108
|
title: "List Replay Videos",
|
|
855
1109
|
description: "List replay recordings for a browser session, including final view_url and authenticated download_url values when available.",
|
|
856
1110
|
inputSchema: BrowserSessionInputSchema,
|
|
1111
|
+
outputSchema: BrowserListReplaysOutputSchema,
|
|
857
1112
|
annotations: annotations("List Replay Videos", true)
|
|
858
1113
|
},
|
|
859
1114
|
async (input) => {
|
|
860
1115
|
const res = await req("GET", `/agent/sessions/${input.session_id}/replays`);
|
|
861
|
-
return
|
|
1116
|
+
if (!res.ok) return errorResult("browser_list_replays", res.data, input.session_id);
|
|
1117
|
+
const replays = Array.isArray(res.data?.replays) ? res.data.replays : [];
|
|
1118
|
+
return structuredResult({
|
|
1119
|
+
ok: true,
|
|
1120
|
+
tool: "browser_list_replays",
|
|
1121
|
+
session_id: input.session_id,
|
|
1122
|
+
replays,
|
|
1123
|
+
count: replays.length
|
|
1124
|
+
});
|
|
862
1125
|
}
|
|
863
1126
|
);
|
|
864
1127
|
server2.registerTool(
|
|
@@ -867,11 +1130,22 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
867
1130
|
title: "Download Replay MP4",
|
|
868
1131
|
description: "Download a replay recording through MCP Scraper and save the MP4 locally under MCP_SCRAPER_OUTPUT_DIR/browser-replays. Use after browser_replay_stop or browser_list_replays.",
|
|
869
1132
|
inputSchema: BrowserReplayDownloadInputSchema,
|
|
870
|
-
|
|
1133
|
+
outputSchema: BrowserReplayDownloadOutputSchema,
|
|
1134
|
+
annotations: annotations("Download Replay MP4")
|
|
871
1135
|
},
|
|
872
1136
|
async (input) => {
|
|
873
1137
|
const res = await downloadReplay(input.session_id, input.replay_id, input.filename);
|
|
874
|
-
return
|
|
1138
|
+
if (!res.ok) return errorResult("browser_replay_download", res.data, input.session_id, input.replay_id);
|
|
1139
|
+
return structuredResult({
|
|
1140
|
+
ok: true,
|
|
1141
|
+
tool: "browser_replay_download",
|
|
1142
|
+
session_id: input.session_id,
|
|
1143
|
+
replay_id: input.replay_id,
|
|
1144
|
+
file_path: res.data?.file_path ?? null,
|
|
1145
|
+
bytes: typeof res.data?.bytes === "number" ? res.data.bytes : null,
|
|
1146
|
+
mime_type: res.data?.mime_type ?? null,
|
|
1147
|
+
download_url: res.data?.download_url ?? null
|
|
1148
|
+
});
|
|
875
1149
|
}
|
|
876
1150
|
);
|
|
877
1151
|
server2.registerTool(
|
|
@@ -880,19 +1154,20 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
880
1154
|
title: "Mark Replay Annotation",
|
|
881
1155
|
description: "While a replay is actively recording, locate one exact DOM target and return a ready-to-use annotation object with DOM bounds and replay-relative timing. Use this instead of guessing start_seconds or drawing rough rectangles. Workflow: start browser_replay_start, navigate until the target is visible and stable, call browser_replay_mark for each callout, then stop the replay and pass the returned annotations to browser_replay_annotate.",
|
|
882
1156
|
inputSchema: BrowserReplayMarkInputSchema,
|
|
1157
|
+
outputSchema: BrowserReplayMarkOutputSchema,
|
|
883
1158
|
annotations: annotations("Mark Replay Annotation", true)
|
|
884
1159
|
},
|
|
885
1160
|
async (input) => {
|
|
886
1161
|
const res = await req("POST", `/agent/sessions/${input.session_id}/locate`, { targets: [input.target] });
|
|
887
|
-
if (!res.ok) return
|
|
1162
|
+
if (!res.ok) return errorResult("browser_replay_mark", res.data, input.session_id);
|
|
888
1163
|
const target = res.data?.targets?.[0];
|
|
889
1164
|
const element = target?.element;
|
|
890
1165
|
const elapsed = res.data?.replay?.replay_elapsed_seconds;
|
|
891
1166
|
if (!target?.found || !element) {
|
|
892
|
-
return
|
|
1167
|
+
return errorResult("browser_replay_mark", { error: target?.error ?? "target not found in current viewport", target }, input.session_id);
|
|
893
1168
|
}
|
|
894
1169
|
if (!finiteNumber2(elapsed)) {
|
|
895
|
-
return
|
|
1170
|
+
return errorResult("browser_replay_mark", { error: "no active replay clock found; call browser_replay_start before browser_replay_mark" }, input.session_id);
|
|
896
1171
|
}
|
|
897
1172
|
const padded = expandElementBounds(element, res.data?.viewport, input.padding ?? 8);
|
|
898
1173
|
const start = Math.max(0, elapsed + (input.start_offset_seconds ?? -0.25));
|
|
@@ -909,7 +1184,11 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
909
1184
|
...input.color ? { color: input.color } : {},
|
|
910
1185
|
...input.thickness ? { thickness: input.thickness } : {}
|
|
911
1186
|
};
|
|
912
|
-
return
|
|
1187
|
+
return structuredResult({
|
|
1188
|
+
ok: true,
|
|
1189
|
+
tool: "browser_replay_mark",
|
|
1190
|
+
session_id: input.session_id,
|
|
1191
|
+
replay_id: res.data?.replay?.replay_id ?? res.data?.replay?.replayId ?? null,
|
|
913
1192
|
annotation,
|
|
914
1193
|
source_width: padded.sourceWidth,
|
|
915
1194
|
source_height: padded.sourceHeight,
|
|
@@ -925,12 +1204,13 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
925
1204
|
title: "Annotate Replay MP4",
|
|
926
1205
|
description: "Download a browser replay MP4, render visual annotations over it, and save a new annotated MP4 locally. Use this after browser_replay_stop when the user wants proof videos with circles, boxes, arrows, underlines, or labels. For accurate timing and placement, prefer annotations returned by browser_replay_mark while the replay is recording; otherwise use exact left/top/width/height bounds from browser_locate. If the replay video size differs from the screenshot coordinate space, pass source_width and source_height.",
|
|
927
1206
|
inputSchema: BrowserReplayAnnotateInputSchema,
|
|
928
|
-
|
|
1207
|
+
outputSchema: BrowserReplayAnnotateOutputSchema,
|
|
1208
|
+
annotations: annotations("Annotate Replay MP4")
|
|
929
1209
|
},
|
|
930
1210
|
async (input) => {
|
|
931
1211
|
const sourceName = input.filename ? `${input.filename}-source` : void 0;
|
|
932
1212
|
const downloaded = await downloadReplay(input.session_id, input.replay_id, sourceName);
|
|
933
|
-
if (!downloaded.ok) return
|
|
1213
|
+
if (!downloaded.ok) return errorResult("browser_replay_annotate", downloaded.data, input.session_id, input.replay_id);
|
|
934
1214
|
try {
|
|
935
1215
|
const sourcePath = String(downloaded.data.file_path);
|
|
936
1216
|
const outputPath = annotatedReplayFilePath(input.session_id, input.replay_id, input.filename);
|
|
@@ -942,7 +1222,10 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
942
1222
|
sourceLeftOffset: input.source_left_offset,
|
|
943
1223
|
sourceTopOffset: input.source_top_offset
|
|
944
1224
|
});
|
|
945
|
-
return
|
|
1225
|
+
return structuredResult({
|
|
1226
|
+
ok: true,
|
|
1227
|
+
tool: "browser_replay_annotate",
|
|
1228
|
+
session_id: input.session_id,
|
|
946
1229
|
replay_id: input.replay_id,
|
|
947
1230
|
source_file_path: sourcePath,
|
|
948
1231
|
annotated_file_path: result.filePath,
|
|
@@ -953,7 +1236,7 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
953
1236
|
mime_type: "video/mp4"
|
|
954
1237
|
});
|
|
955
1238
|
} catch (err) {
|
|
956
|
-
return
|
|
1239
|
+
return errorResult("browser_replay_annotate", { error: err instanceof Error ? err.message : String(err) }, input.session_id, input.replay_id);
|
|
957
1240
|
}
|
|
958
1241
|
}
|
|
959
1242
|
);
|
|
@@ -961,28 +1244,43 @@ function registerBrowserAgentMcpTools(server2, opts) {
|
|
|
961
1244
|
"browser_close",
|
|
962
1245
|
{
|
|
963
1246
|
title: "Close Browser Session",
|
|
964
|
-
description: "Close and release
|
|
1247
|
+
description: "Close and release a browser session when the task is done, when the user asks to stop the browser, or when active browser billing should end. Use browser_list_sessions first if you need to recover the session_id.",
|
|
965
1248
|
inputSchema: BrowserSessionInputSchema,
|
|
1249
|
+
outputSchema: BrowserCloseOutputSchema,
|
|
966
1250
|
annotations: { title: "Close Browser Session", readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
967
1251
|
},
|
|
968
1252
|
async (input) => {
|
|
969
1253
|
const res = await req("DELETE", `/agent/sessions/${input.session_id}`);
|
|
970
|
-
return
|
|
1254
|
+
if (!res.ok) return errorResult("browser_close", res.data, input.session_id);
|
|
1255
|
+
return structuredResult({
|
|
1256
|
+
ok: true,
|
|
1257
|
+
tool: "browser_close",
|
|
1258
|
+
session_id: input.session_id,
|
|
1259
|
+
closed: true,
|
|
1260
|
+
raw: res.data
|
|
1261
|
+
});
|
|
971
1262
|
}
|
|
972
1263
|
);
|
|
973
1264
|
server2.registerTool(
|
|
974
1265
|
"browser_list_sessions",
|
|
975
1266
|
{
|
|
976
1267
|
title: "List Browser Sessions",
|
|
977
|
-
description: "List
|
|
1268
|
+
description: "List browser sessions and their status, with a watch_url for each. Use this to recover a session_id, find an active browser, or decide which session to close. Use browser_open to create a new session.",
|
|
978
1269
|
inputSchema: BrowserListInputSchema,
|
|
1270
|
+
outputSchema: BrowserListSessionsOutputSchema,
|
|
979
1271
|
annotations: annotations("List Browser Sessions", true)
|
|
980
1272
|
},
|
|
981
1273
|
async (input) => {
|
|
982
1274
|
const res = await req("GET", `/agent/sessions${input.include_closed ? "?all=1" : ""}`);
|
|
983
|
-
if (!res.ok) return
|
|
1275
|
+
if (!res.ok) return errorResult("browser_list_sessions", res.data);
|
|
984
1276
|
const sessions = (res.data.sessions ?? []).map((s) => ({ ...s, watch_url: `${consoleBase}/console/${s.session_id}` }));
|
|
985
|
-
return
|
|
1277
|
+
return structuredResult({
|
|
1278
|
+
ok: true,
|
|
1279
|
+
tool: "browser_list_sessions",
|
|
1280
|
+
session_id: null,
|
|
1281
|
+
sessions,
|
|
1282
|
+
count: sessions.length
|
|
1283
|
+
});
|
|
986
1284
|
}
|
|
987
1285
|
);
|
|
988
1286
|
}
|
|
@@ -1462,7 +1760,7 @@ ${bodyMd.slice(0, 3e3)}${bodyMd.length > 3e3 ? "\n\n*(truncated)*" : ""}` : "";
|
|
|
1462
1760
|
const full = `# URL Extract: ${url}
|
|
1463
1761
|
**${title}**
|
|
1464
1762
|
${headingSection}${kpoSection}${brandingSection}${bodySection}${screenshotSection}${mediaSection}${tips}`;
|
|
1465
|
-
const
|
|
1763
|
+
const textResult = oneBlock(full);
|
|
1466
1764
|
const structuredContent = {
|
|
1467
1765
|
url,
|
|
1468
1766
|
title: d.title ?? null,
|
|
@@ -1477,13 +1775,13 @@ ${headingSection}${kpoSection}${brandingSection}${bodySection}${screenshotSectio
|
|
|
1477
1775
|
if (screenshotMeta?.base64) {
|
|
1478
1776
|
return {
|
|
1479
1777
|
content: [
|
|
1480
|
-
...
|
|
1778
|
+
...textResult.content,
|
|
1481
1779
|
{ type: "image", data: screenshotMeta.base64, mimeType: "image/png" }
|
|
1482
1780
|
],
|
|
1483
1781
|
structuredContent
|
|
1484
1782
|
};
|
|
1485
1783
|
}
|
|
1486
|
-
return { ...
|
|
1784
|
+
return { ...textResult, structuredContent };
|
|
1487
1785
|
}
|
|
1488
1786
|
function formatMapSiteUrls(raw, input) {
|
|
1489
1787
|
const parsed = parseData(raw);
|
|
@@ -1612,13 +1910,25 @@ ${videoRows}`,
|
|
|
1612
1910
|
}
|
|
1613
1911
|
};
|
|
1614
1912
|
}
|
|
1913
|
+
function structuredTranscriptChunks(chunks) {
|
|
1914
|
+
return chunks.map((c) => ({
|
|
1915
|
+
startSec: Number.isFinite(c.timestamp?.[0]) ? c.timestamp[0] : 0,
|
|
1916
|
+
endSec: Number.isFinite(c.timestamp?.[1]) ? c.timestamp[1] : 0,
|
|
1917
|
+
text: c.text
|
|
1918
|
+
}));
|
|
1919
|
+
}
|
|
1920
|
+
function wordCount(text) {
|
|
1921
|
+
return text.trim() ? text.trim().split(/\s+/).length : 0;
|
|
1922
|
+
}
|
|
1615
1923
|
function formatYoutubeTranscribe(raw, input) {
|
|
1616
1924
|
const parsed = parseData(raw);
|
|
1617
1925
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
1618
1926
|
const d = parsed.data;
|
|
1619
1927
|
const text = d.text ?? "";
|
|
1620
1928
|
const chunks = d.chunks ?? [];
|
|
1929
|
+
const videoId = d.videoId ?? input.videoId ?? null;
|
|
1621
1930
|
const durSec = d.durationMs ? (d.durationMs / 1e3).toFixed(0) : "\u2014";
|
|
1931
|
+
const words = wordCount(text);
|
|
1622
1932
|
const chunkRows = chunks.slice(0, 50).map((c) => {
|
|
1623
1933
|
const sec = Number.isFinite(c.timestamp[0]) ? Math.floor(c.timestamp[0]) : 0;
|
|
1624
1934
|
const mm = String(Math.floor(sec / 60)).padStart(2, "0");
|
|
@@ -1626,8 +1936,8 @@ function formatYoutubeTranscribe(raw, input) {
|
|
|
1626
1936
|
return `| ${mm}:${ss} | ${cell(truncate(c.text, 120))} |`;
|
|
1627
1937
|
}).join("\n");
|
|
1628
1938
|
const full = [
|
|
1629
|
-
`# YouTube Transcript: \`${input.
|
|
1630
|
-
`**Duration:** ${durSec}s \xB7 **${
|
|
1939
|
+
`# YouTube Transcript: \`${videoId ?? input.url ?? "video"}\``,
|
|
1940
|
+
`**Duration:** ${durSec}s \xB7 **${words} words**`,
|
|
1631
1941
|
`
|
|
1632
1942
|
## Full Transcript
|
|
1633
1943
|
${text}`,
|
|
@@ -1640,7 +1950,22 @@ ${chunkRows}` : "",
|
|
|
1640
1950
|
---
|
|
1641
1951
|
\u{1F4A1} Harvest more from this channel: use \`youtube_harvest\` with \`mode: "channel"\``
|
|
1642
1952
|
].filter(Boolean).join("\n");
|
|
1643
|
-
return
|
|
1953
|
+
return {
|
|
1954
|
+
...oneBlock(full),
|
|
1955
|
+
structuredContent: {
|
|
1956
|
+
videoId,
|
|
1957
|
+
url: videoId ? `https://www.youtube.com/watch?v=${videoId}` : input.url ?? null,
|
|
1958
|
+
wordCount: words,
|
|
1959
|
+
chunkCount: chunks.length,
|
|
1960
|
+
durationMs: typeof d.durationMs === "number" ? d.durationMs : null,
|
|
1961
|
+
transcriptText: text,
|
|
1962
|
+
chunks: structuredTranscriptChunks(chunks),
|
|
1963
|
+
resolvedInputs: {
|
|
1964
|
+
videoId,
|
|
1965
|
+
url: input.url ?? null
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
};
|
|
1644
1969
|
}
|
|
1645
1970
|
function formatFacebookPageIntel(raw, input) {
|
|
1646
1971
|
const parsed = parseData(raw);
|
|
@@ -1650,12 +1975,13 @@ function formatFacebookPageIntel(raw, input) {
|
|
|
1650
1975
|
const ads = d.ads ?? [];
|
|
1651
1976
|
const s = d.summary ?? { totalAds: 0, activeCount: 0, videoCount: 0, imageCount: 0 };
|
|
1652
1977
|
const adBlocks = ads.map((ad, i) => [
|
|
1653
|
-
`### Ad ${i + 1}${ad.libraryId ? ` \xB7 \`${ad.libraryId}\`` : ""} \u2014 ${ad.status ?? "\u2014"} \xB7 ${ad.creativeType ?? "\u2014"} \xB7 ${ad.startDate ?? "\u2014"}`,
|
|
1978
|
+
`### Ad ${i + 1}${ad.libraryId ? ` \xB7 \`${ad.libraryId}\`` : ""} \u2014 ${ad.status ?? "\u2014"} \xB7 ${ad.creativeType ?? "\u2014"} \xB7 ${ad.startDate ?? ad.started ?? "\u2014"}`,
|
|
1654
1979
|
ad.headline ? `**Headline:** ${ad.headline}` : "",
|
|
1655
1980
|
ad.primaryText ? `**Copy:** ${truncate(ad.primaryText, 200)}` : "",
|
|
1656
1981
|
ad.cta ? `**CTA:** ${ad.cta}` : "",
|
|
1657
|
-
ad.
|
|
1658
|
-
ad.
|
|
1982
|
+
ad.landingUrl ? `**Landing URL:** ${ad.landingUrl}` : "",
|
|
1983
|
+
ad.videoUrl ?? ad.videoSrc ? `**Video URL:** \`${ad.videoUrl ?? ad.videoSrc}\`` : "",
|
|
1984
|
+
ad.variations ?? ad.clusterCount ? `**Variations:** ${ad.variations ?? ad.clusterCount}` : ""
|
|
1659
1985
|
].filter(Boolean).join("\n")).join("\n\n---\n\n");
|
|
1660
1986
|
const full = [
|
|
1661
1987
|
`# Facebook Ad Intel: ${advertiser}`,
|
|
@@ -1681,11 +2007,16 @@ ${adBlocks}`,
|
|
|
1681
2007
|
libraryId: ad.libraryId ?? null,
|
|
1682
2008
|
status: ad.status ?? null,
|
|
1683
2009
|
creativeType: ad.creativeType ?? null,
|
|
2010
|
+
primaryText: ad.primaryText ?? null,
|
|
1684
2011
|
headline: ad.headline ?? null,
|
|
1685
2012
|
cta: ad.cta ?? null,
|
|
1686
|
-
startDate: ad.startDate ?? null,
|
|
1687
|
-
|
|
1688
|
-
|
|
2013
|
+
startDate: ad.startDate ?? ad.started ?? null,
|
|
2014
|
+
landingUrl: ad.landingUrl ?? null,
|
|
2015
|
+
domain: ad.domain ?? null,
|
|
2016
|
+
videoUrl: ad.videoUrl ?? ad.videoSrc ?? null,
|
|
2017
|
+
imageUrl: ad.imageUrl ?? ad.imageSrc ?? null,
|
|
2018
|
+
videoPoster: ad.videoPoster ?? null,
|
|
2019
|
+
variations: typeof ad.variations === "number" ? ad.variations : typeof ad.clusterCount === "number" ? ad.clusterCount : null
|
|
1689
2020
|
}))
|
|
1690
2021
|
}
|
|
1691
2022
|
};
|
|
@@ -1719,8 +2050,11 @@ ${rows}`,
|
|
|
1719
2050
|
advertiserCount: advertisers.length,
|
|
1720
2051
|
advertisers: advertisers.map((a) => ({
|
|
1721
2052
|
name: a.pageName ?? a.name ?? null,
|
|
2053
|
+
pageId: a.pageId ?? null,
|
|
2054
|
+
pageUrl: a.pageUrl ?? null,
|
|
1722
2055
|
adCount: typeof a.adCount === "number" ? a.adCount : null,
|
|
1723
|
-
libraryId: a.sampleLibraryId ?? a.libraryId ?? null
|
|
2056
|
+
libraryId: a.sampleLibraryId ?? a.libraryId ?? null,
|
|
2057
|
+
sampleLibraryId: a.sampleLibraryId ?? null
|
|
1724
2058
|
}))
|
|
1725
2059
|
}
|
|
1726
2060
|
};
|
|
@@ -1818,6 +2152,34 @@ function formatWorkflowSuggest(input) {
|
|
|
1818
2152
|
}
|
|
1819
2153
|
};
|
|
1820
2154
|
}
|
|
2155
|
+
function workflowStepLines(data) {
|
|
2156
|
+
const step = data.step;
|
|
2157
|
+
const nextStep = data.nextStep;
|
|
2158
|
+
const done = data.done === true;
|
|
2159
|
+
const lines = [];
|
|
2160
|
+
if (step) {
|
|
2161
|
+
const index = Number(step.index ?? 0);
|
|
2162
|
+
const totalSteps = step.totalSteps != null ? Number(step.totalSteps) : void 0;
|
|
2163
|
+
const stepLabel = totalSteps ? `${index + 1}/${totalSteps}` : `${index + 1}`;
|
|
2164
|
+
lines.push(`
|
|
2165
|
+
## Step ${stepLabel}: ${step.title ?? step.id ?? ""}`);
|
|
2166
|
+
const output = step.output;
|
|
2167
|
+
if (output && Object.keys(output).length) {
|
|
2168
|
+
lines.push(Object.entries(output).map(([k, v]) => `- ${k}: ${typeof v === "object" ? JSON.stringify(v) : String(v)}`).join("\n"));
|
|
2169
|
+
}
|
|
2170
|
+
const warnings = Array.isArray(step.warnings) ? step.warnings : [];
|
|
2171
|
+
if (warnings.length) lines.push(`
|
|
2172
|
+
**Warnings:**
|
|
2173
|
+
${warnings.map((w) => `- ${w}`).join("\n")}`);
|
|
2174
|
+
}
|
|
2175
|
+
if (done) {
|
|
2176
|
+
lines.push("\n**Done.** All steps complete.");
|
|
2177
|
+
} else if (nextStep && typeof nextStep === "object") {
|
|
2178
|
+
lines.push(`
|
|
2179
|
+
**Next step:** \`${nextStep.id ?? nextStep.index}\` \u2014 call \`workflow_step\` with this run id to continue.`);
|
|
2180
|
+
}
|
|
2181
|
+
return lines;
|
|
2182
|
+
}
|
|
1821
2183
|
function formatWorkflowRun(raw, input) {
|
|
1822
2184
|
const parsed = parseData(raw);
|
|
1823
2185
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
@@ -1831,6 +2193,7 @@ function formatWorkflowRun(raw, input) {
|
|
|
1831
2193
|
`**Run ID:** \`${runId || "unknown"}\``,
|
|
1832
2194
|
`**Status:** ${status}`,
|
|
1833
2195
|
summary?.title ? `**Title:** ${summary.title}` : "",
|
|
2196
|
+
...workflowStepLines(parsed.data),
|
|
1834
2197
|
summary?.summary ? `
|
|
1835
2198
|
## Summary
|
|
1836
2199
|
${summary.summary}` : "",
|
|
@@ -1846,6 +2209,42 @@ ${workflowArtifactRows(artifacts)}` : "",
|
|
|
1846
2209
|
input: input.input ?? {},
|
|
1847
2210
|
run,
|
|
1848
2211
|
summary,
|
|
2212
|
+
step: parsed.data.step,
|
|
2213
|
+
nextStep: parsed.data.nextStep ?? null,
|
|
2214
|
+
done: parsed.data.done === true,
|
|
2215
|
+
artifacts
|
|
2216
|
+
}
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
function formatWorkflowStep(raw, input) {
|
|
2220
|
+
const parsed = parseData(raw);
|
|
2221
|
+
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
2222
|
+
const run = parsed.data.run;
|
|
2223
|
+
const summary = parsed.data.summary;
|
|
2224
|
+
const artifacts = workflowArtifactsFrom(run);
|
|
2225
|
+
const done = parsed.data.done === true;
|
|
2226
|
+
const full = [
|
|
2227
|
+
`# Workflow Step`,
|
|
2228
|
+
`**Run ID:** \`${input.runId}\``,
|
|
2229
|
+
`**Status:** ${run?.status ?? (done ? "done" : "running")}`,
|
|
2230
|
+
...workflowStepLines(parsed.data),
|
|
2231
|
+
done && summary?.summary ? `
|
|
2232
|
+
## Summary
|
|
2233
|
+
${summary.summary}` : "",
|
|
2234
|
+
artifacts.length ? `
|
|
2235
|
+
## Artifacts
|
|
2236
|
+
${workflowArtifactRows(artifacts)}` : "",
|
|
2237
|
+
done && artifacts.length ? "\nUse `workflow_artifact_read` with the run id and artifact id to pull CSV, JSON, Markdown, or report content into context." : ""
|
|
2238
|
+
].filter(Boolean).join("\n");
|
|
2239
|
+
return {
|
|
2240
|
+
...oneBlock(full),
|
|
2241
|
+
structuredContent: {
|
|
2242
|
+
runId: input.runId,
|
|
2243
|
+
run,
|
|
2244
|
+
summary: summary ?? null,
|
|
2245
|
+
step: parsed.data.step,
|
|
2246
|
+
nextStep: parsed.data.nextStep ?? null,
|
|
2247
|
+
done,
|
|
1849
2248
|
artifacts
|
|
1850
2249
|
}
|
|
1851
2250
|
};
|
|
@@ -2250,6 +2649,7 @@ function formatFacebookAdTranscribe(raw, input) {
|
|
|
2250
2649
|
const text = d.text ?? "";
|
|
2251
2650
|
const chunks = d.chunks ?? [];
|
|
2252
2651
|
const durSec = d.durationMs ? (d.durationMs / 1e3).toFixed(0) : "\u2014";
|
|
2652
|
+
const words = wordCount(text);
|
|
2253
2653
|
const chunkRows = chunks.slice(0, 50).map((c) => {
|
|
2254
2654
|
const sec = Number.isFinite(c.timestamp[0]) ? Math.floor(c.timestamp[0]) : 0;
|
|
2255
2655
|
const mm = String(Math.floor(sec / 60)).padStart(2, "0");
|
|
@@ -2258,7 +2658,7 @@ function formatFacebookAdTranscribe(raw, input) {
|
|
|
2258
2658
|
}).join("\n");
|
|
2259
2659
|
const full = [
|
|
2260
2660
|
`# Facebook Ad Transcript`,
|
|
2261
|
-
`**Duration:** ${durSec}s \xB7 **${
|
|
2661
|
+
`**Duration:** ${durSec}s \xB7 **${words} words**`,
|
|
2262
2662
|
`
|
|
2263
2663
|
## Full Transcript
|
|
2264
2664
|
${text}`,
|
|
@@ -2271,7 +2671,20 @@ ${chunkRows}` : "",
|
|
|
2271
2671
|
---
|
|
2272
2672
|
\u{1F4A1} Get more ads from this advertiser: use \`facebook_page_intel\`. For public Facebook reel/post URLs, use \`facebook_video_transcribe\`.`
|
|
2273
2673
|
].filter(Boolean).join("\n");
|
|
2274
|
-
return
|
|
2674
|
+
return {
|
|
2675
|
+
...oneBlock(full),
|
|
2676
|
+
structuredContent: {
|
|
2677
|
+
videoUrl: input.videoUrl,
|
|
2678
|
+
wordCount: words,
|
|
2679
|
+
chunkCount: chunks.length,
|
|
2680
|
+
durationMs: typeof d.durationMs === "number" ? d.durationMs : null,
|
|
2681
|
+
transcriptText: text,
|
|
2682
|
+
chunks: structuredTranscriptChunks(chunks),
|
|
2683
|
+
resolvedInputs: {
|
|
2684
|
+
videoUrl: input.videoUrl
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
};
|
|
2275
2688
|
}
|
|
2276
2689
|
function formatFacebookVideoTranscribe(raw, input) {
|
|
2277
2690
|
const parsed = parseData(raw);
|
|
@@ -2279,7 +2692,7 @@ function formatFacebookVideoTranscribe(raw, input) {
|
|
|
2279
2692
|
const d = parsed.data;
|
|
2280
2693
|
const text = d.text ?? "";
|
|
2281
2694
|
const chunks = d.chunks ?? [];
|
|
2282
|
-
const
|
|
2695
|
+
const wordCount2 = text.trim() ? text.trim().split(/\s+/).length : 0;
|
|
2283
2696
|
const durSec = d.durationMs ? (d.durationMs / 1e3).toFixed(0) : "\u2014";
|
|
2284
2697
|
const videoDuration = typeof d.videoDurationSec === "number" ? `${Math.round(d.videoDurationSec)}s` : "\u2014";
|
|
2285
2698
|
const label = d.videoId ? `Facebook Organic Video \`${d.videoId}\`` : "Facebook Organic Video";
|
|
@@ -2292,7 +2705,7 @@ function formatFacebookVideoTranscribe(raw, input) {
|
|
|
2292
2705
|
const full = [
|
|
2293
2706
|
`# ${label} Transcript`,
|
|
2294
2707
|
d.ownerName ? `**Owner:** ${d.ownerName}` : "",
|
|
2295
|
-
`**Video duration:** ${videoDuration} \xB7 **Transcribed in:** ${durSec}s \xB7 **${
|
|
2708
|
+
`**Video duration:** ${videoDuration} \xB7 **Transcribed in:** ${durSec}s \xB7 **${wordCount2} words**`,
|
|
2296
2709
|
d.pageUrl ? `**Page URL:** ${d.pageUrl}` : `**Page URL:** ${input.url}`,
|
|
2297
2710
|
d.videoUrl ? `**Extracted MP4:** \`${d.videoUrl}\`` : "",
|
|
2298
2711
|
`
|
|
@@ -2318,7 +2731,7 @@ ${chunkRows}` : "",
|
|
|
2318
2731
|
bitrate: typeof d.bitrate === "number" ? d.bitrate : null,
|
|
2319
2732
|
videoDurationSec: typeof d.videoDurationSec === "number" ? d.videoDurationSec : null,
|
|
2320
2733
|
videoUrl: d.videoUrl ?? "",
|
|
2321
|
-
wordCount,
|
|
2734
|
+
wordCount: wordCount2,
|
|
2322
2735
|
chunkCount: chunks.length,
|
|
2323
2736
|
transcriptText: text,
|
|
2324
2737
|
chunks: chunks.map((c) => ({
|
|
@@ -2331,451 +2744,612 @@ ${chunkRows}` : "",
|
|
|
2331
2744
|
}
|
|
2332
2745
|
|
|
2333
2746
|
// src/mcp/mcp-tool-schemas.ts
|
|
2747
|
+
var import_zod3 = require("zod");
|
|
2748
|
+
|
|
2749
|
+
// src/schemas.ts
|
|
2334
2750
|
var import_zod2 = require("zod");
|
|
2751
|
+
var DEFAULT_PROXY_MODE = "configured";
|
|
2752
|
+
var DEFAULT_MAPS_PROXY_MODE = "location";
|
|
2753
|
+
var HarvestOptionsSchema = import_zod2.z.object({
|
|
2754
|
+
query: import_zod2.z.string().min(1),
|
|
2755
|
+
location: import_zod2.z.string().optional(),
|
|
2756
|
+
gl: import_zod2.z.string().length(2).default("us"),
|
|
2757
|
+
hl: import_zod2.z.string().length(2).default("en"),
|
|
2758
|
+
device: import_zod2.z.enum(["desktop", "mobile"]).default("desktop"),
|
|
2759
|
+
proxyMode: import_zod2.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE),
|
|
2760
|
+
proxyZip: import_zod2.z.string().regex(/^\d{5}$/).optional(),
|
|
2761
|
+
debug: import_zod2.z.boolean().default(false),
|
|
2762
|
+
depth: import_zod2.z.number().int().min(1).max(30).default(3),
|
|
2763
|
+
maxQuestions: import_zod2.z.number().int().min(1).max(1e3).default(100),
|
|
2764
|
+
headless: import_zod2.z.boolean().default(false),
|
|
2765
|
+
profileDir: import_zod2.z.string().optional(),
|
|
2766
|
+
proxy: import_zod2.z.string().url().optional(),
|
|
2767
|
+
kernelApiKey: import_zod2.z.string().optional(),
|
|
2768
|
+
kernelProxyId: import_zod2.z.string().optional(),
|
|
2769
|
+
kernelProxyResolution: import_zod2.z.unknown().optional(),
|
|
2770
|
+
outputDir: import_zod2.z.string().default("./paa-output"),
|
|
2771
|
+
format: import_zod2.z.enum(["json", "csv", "both"]).default("both"),
|
|
2772
|
+
serpOnly: import_zod2.z.boolean().default(false),
|
|
2773
|
+
pages: import_zod2.z.number().int().min(1).max(2).default(1)
|
|
2774
|
+
});
|
|
2775
|
+
var MapsPlaceOptionsSchema = import_zod2.z.object({
|
|
2776
|
+
businessName: import_zod2.z.string().min(1),
|
|
2777
|
+
location: import_zod2.z.string().min(1),
|
|
2778
|
+
gl: import_zod2.z.string().length(2).default("us"),
|
|
2779
|
+
hl: import_zod2.z.string().length(2).default("en"),
|
|
2780
|
+
includeReviews: import_zod2.z.boolean().default(false),
|
|
2781
|
+
maxReviews: import_zod2.z.number().int().min(1).max(500).default(50),
|
|
2782
|
+
kernelApiKey: import_zod2.z.string().optional(),
|
|
2783
|
+
kernelProxyId: import_zod2.z.string().optional(),
|
|
2784
|
+
headless: import_zod2.z.boolean().default(true)
|
|
2785
|
+
});
|
|
2786
|
+
var MapsSearchOptionsSchema = import_zod2.z.object({
|
|
2787
|
+
query: import_zod2.z.string().min(1),
|
|
2788
|
+
location: import_zod2.z.string().optional(),
|
|
2789
|
+
gl: import_zod2.z.string().length(2).default("us"),
|
|
2790
|
+
hl: import_zod2.z.string().length(2).default("en"),
|
|
2791
|
+
maxResults: import_zod2.z.number().int().min(1).max(50).default(10),
|
|
2792
|
+
proxyMode: import_zod2.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE),
|
|
2793
|
+
proxyZip: import_zod2.z.string().regex(/^\d{5}$/).optional(),
|
|
2794
|
+
debug: import_zod2.z.boolean().default(false),
|
|
2795
|
+
kernelApiKey: import_zod2.z.string().optional(),
|
|
2796
|
+
kernelProxyId: import_zod2.z.string().optional(),
|
|
2797
|
+
kernelProxyResolution: import_zod2.z.unknown().optional(),
|
|
2798
|
+
headless: import_zod2.z.boolean().default(true)
|
|
2799
|
+
});
|
|
2800
|
+
var RawPAAItemSchema = import_zod2.z.object({
|
|
2801
|
+
question: import_zod2.z.string().min(1),
|
|
2802
|
+
answer: import_zod2.z.string().optional(),
|
|
2803
|
+
sourceTitle: import_zod2.z.string().optional(),
|
|
2804
|
+
sourceSite: import_zod2.z.string().optional(),
|
|
2805
|
+
sourceCite: import_zod2.z.string().optional()
|
|
2806
|
+
});
|
|
2807
|
+
var RawMapsOverviewSchema = import_zod2.z.object({
|
|
2808
|
+
name: import_zod2.z.string().nullable(),
|
|
2809
|
+
rating: import_zod2.z.string().nullable(),
|
|
2810
|
+
reviewCount: import_zod2.z.string().nullable(),
|
|
2811
|
+
category: import_zod2.z.string().nullable(),
|
|
2812
|
+
address: import_zod2.z.string().nullable(),
|
|
2813
|
+
hoursSummary: import_zod2.z.string().nullable(),
|
|
2814
|
+
phone: import_zod2.z.string().nullable(),
|
|
2815
|
+
phoneDisplay: import_zod2.z.string().nullable(),
|
|
2816
|
+
website: import_zod2.z.string().nullable(),
|
|
2817
|
+
plusCode: import_zod2.z.string().nullable(),
|
|
2818
|
+
bookingUrl: import_zod2.z.string().nullable()
|
|
2819
|
+
});
|
|
2820
|
+
var RawMapsHoursRowSchema = import_zod2.z.object({
|
|
2821
|
+
day: import_zod2.z.string(),
|
|
2822
|
+
hours: import_zod2.z.string()
|
|
2823
|
+
});
|
|
2824
|
+
var RawMapsReviewStatsSchema = import_zod2.z.object({
|
|
2825
|
+
reviewHistogram: import_zod2.z.array(import_zod2.z.object({
|
|
2826
|
+
stars: import_zod2.z.number(),
|
|
2827
|
+
count: import_zod2.z.string()
|
|
2828
|
+
})),
|
|
2829
|
+
reviewTopics: import_zod2.z.array(import_zod2.z.object({
|
|
2830
|
+
label: import_zod2.z.string(),
|
|
2831
|
+
count: import_zod2.z.string()
|
|
2832
|
+
}))
|
|
2833
|
+
});
|
|
2834
|
+
var RawMapsReviewCardSchema = import_zod2.z.object({
|
|
2835
|
+
reviewId: import_zod2.z.string(),
|
|
2836
|
+
author: import_zod2.z.string().nullable(),
|
|
2837
|
+
stars: import_zod2.z.string().nullable(),
|
|
2838
|
+
date: import_zod2.z.string().nullable(),
|
|
2839
|
+
text: import_zod2.z.string().nullable(),
|
|
2840
|
+
ownerResponse: import_zod2.z.string().nullable()
|
|
2841
|
+
});
|
|
2842
|
+
var RawMapsAboutAttributeSchema = import_zod2.z.object({
|
|
2843
|
+
section: import_zod2.z.string(),
|
|
2844
|
+
attribute: import_zod2.z.string()
|
|
2845
|
+
});
|
|
2846
|
+
|
|
2847
|
+
// src/mcp/mcp-tool-schemas.ts
|
|
2335
2848
|
var HarvestPaaInputSchema = {
|
|
2336
|
-
query:
|
|
2337
|
-
location:
|
|
2338
|
-
maxQuestions:
|
|
2339
|
-
gl:
|
|
2340
|
-
hl:
|
|
2341
|
-
device:
|
|
2342
|
-
proxyMode:
|
|
2343
|
-
proxyZip:
|
|
2344
|
-
debug:
|
|
2849
|
+
query: import_zod3.z.string().min(1).describe('Core search topic only. If the user says "best hvac company in Denver CO", use query="best hvac company" and location="Denver, CO". Do not include the location in query when it can be separated.'),
|
|
2850
|
+
location: import_zod3.z.string().optional().describe('City, region, or country for geo-targeted results, inferred from the user request when present, e.g. "Denver, CO", "Tokyo, Japan", "London, UK".'),
|
|
2851
|
+
maxQuestions: import_zod3.z.number().int().min(1).max(200).default(30).describe("Number of PAA questions to extract. Default 30. Maximum 200. Use 10 for quick probes, 30 for normal research, 100-200 when the user asks for everything/full/deep research. Larger harvests get a longer server time budget (151-200 questions \u2192 up to 280s). Credits are charged by extracted question; unused request hold is refunded."),
|
|
2852
|
+
gl: import_zod3.z.string().length(2).default("us").describe("Google country code inferred from location or user language. Examples: United States us, United Kingdom gb, Japan jp, Canada ca, Australia au."),
|
|
2853
|
+
hl: import_zod3.z.string().default("en").describe("Google interface/content language inferred from the user request. Use en unless the user asks for another language or locale."),
|
|
2854
|
+
device: import_zod3.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
|
|
2855
|
+
proxyMode: import_zod3.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe("Proxy targeting mode. Default configured uses the service proxy without city/ZIP targeting for the highest general success rate. Use location only when the user explicitly needs city/ZIP-targeted residential proxy evidence. Use none only for direct-network debugging."),
|
|
2856
|
+
proxyZip: import_zod3.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use only with proxyMode location when the user gives a specific ZIP or city-center targeting needs to be forced."),
|
|
2857
|
+
debug: import_zod3.z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior.")
|
|
2345
2858
|
};
|
|
2346
2859
|
var ExtractUrlInputSchema = {
|
|
2347
|
-
url:
|
|
2348
|
-
screenshot:
|
|
2349
|
-
screenshotDevice:
|
|
2350
|
-
extractBranding:
|
|
2351
|
-
downloadMedia:
|
|
2352
|
-
mediaTypes:
|
|
2353
|
-
allowLocal:
|
|
2860
|
+
url: import_zod3.z.string().url().describe("Public http/https URL to extract. Use this when the user provides one specific page URL."),
|
|
2861
|
+
screenshot: import_zod3.z.boolean().default(false).describe("Also capture a full-page screenshot of the URL. Saved to ~/Downloads/mcp-scraper/screenshots/ and returned inline. Use when the user asks to see or capture the page visually."),
|
|
2862
|
+
screenshotDevice: import_zod3.z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport for screenshot. desktop = 1440\xD7900. mobile = 390\xD7844. Default desktop."),
|
|
2863
|
+
extractBranding: import_zod3.z.boolean().default(false).describe("Extract brand colors, fonts, logo, and favicon using a rendered browser session. Returns colorScheme (light/dark), colors (primary/accent/background/text/heading as hex), fonts (heading/body family names), and assets (logo URL, favicon URL). Use when the user asks about brand colors, site theme, or brand assets."),
|
|
2864
|
+
downloadMedia: import_zod3.z.boolean().default(false).describe("Extract and download all page media (images, video, audio) to ~/Downloads/mcp-scraper/media/. Ad networks, tracking pixels, and noise URLs are filtered automatically. Use when the user asks to download or harvest assets from a page."),
|
|
2865
|
+
mediaTypes: import_zod3.z.array(import_zod3.z.enum(["image", "video", "audio"])).default(["image", "video", "audio"]).describe("Which media types to download. Default all three."),
|
|
2866
|
+
allowLocal: import_zod3.z.boolean().default(false).describe("Allow localhost and private-network URLs. For local development only.")
|
|
2354
2867
|
};
|
|
2355
2868
|
var MapSiteUrlsInputSchema = {
|
|
2356
|
-
url:
|
|
2357
|
-
maxUrls:
|
|
2869
|
+
url: import_zod3.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."),
|
|
2870
|
+
maxUrls: import_zod3.z.number().int().min(1).max(500).optional().describe("Maximum URLs to discover. Use 100 for normal maps, higher when the user asks for a full inventory.")
|
|
2358
2871
|
};
|
|
2359
2872
|
var ExtractSiteInputSchema = {
|
|
2360
|
-
url:
|
|
2361
|
-
maxPages:
|
|
2873
|
+
url: import_zod3.z.string().url().describe("Public website URL or domain to extract across multiple pages. Use when the user asks for a site audit, website crawl, or full-site content/schema extraction."),
|
|
2874
|
+
maxPages: import_zod3.z.number().int().min(1).max(50).optional().describe("Maximum pages to extract. Use 50 when the user asks for full results or a complete crawl within MCP limits.")
|
|
2362
2875
|
};
|
|
2363
2876
|
var YoutubeHarvestInputSchema = {
|
|
2364
|
-
mode:
|
|
2365
|
-
query:
|
|
2366
|
-
channelHandle:
|
|
2367
|
-
maxVideos:
|
|
2877
|
+
mode: import_zod3.z.enum(["search", "channel"]).describe("Use search for topic/keyword requests. Use channel when the user provides @handle, channel ID, or channel URL."),
|
|
2878
|
+
query: import_zod3.z.string().optional().describe("Required when mode is search. The YouTube search topic in the user\u2019s words."),
|
|
2879
|
+
channelHandle: import_zod3.z.string().optional().describe("YouTube channel handle, channel ID, or URL. Examples: @mkbhd, UC..., https://youtube.com/@mkbhd."),
|
|
2880
|
+
maxVideos: import_zod3.z.number().int().min(1).max(500).default(50).describe("Number of videos to return. Default 50, maximum 500. Use 10-25 for quick topic discovery, 50 for normal channel/search harvests, and larger values only when the user asks for full channel/history because large responses consume more context.")
|
|
2368
2881
|
};
|
|
2369
2882
|
var YoutubeTranscribeInputSchema = {
|
|
2370
|
-
videoId:
|
|
2883
|
+
videoId: import_zod3.z.string().min(1).optional().describe("YouTube video ID, e.g. dQw4w9WgXcQ. Use only an ID returned by youtube_harvest or visible in a YouTube URL; do not invent one."),
|
|
2884
|
+
url: import_zod3.z.string().url().optional().describe("Full YouTube URL, e.g. https://www.youtube.com/watch?v=dQw4w9WgXcQ or https://youtu.be/dQw4w9WgXcQ. Use this when the user pasted a URL instead of an ID. Provide videoId or url.")
|
|
2371
2885
|
};
|
|
2372
2886
|
var FacebookPageIntelInputSchema = {
|
|
2373
|
-
pageId:
|
|
2374
|
-
libraryId:
|
|
2375
|
-
query:
|
|
2376
|
-
maxAds:
|
|
2377
|
-
country:
|
|
2887
|
+
pageId: import_zod3.z.string().optional().describe("Facebook advertiser/page ID. Use only a pageId returned by facebook_ad_search/facebook_page_intel or copied from Facebook Ad Library; do not construct one yourself."),
|
|
2888
|
+
libraryId: import_zod3.z.string().optional().describe("Facebook Ad Library archive ID for a known ad or advertiser sample. Use a libraryId returned by facebook_ad_search, or a libraryId/adArchiveId visible in Ad Library."),
|
|
2889
|
+
query: import_zod3.z.string().optional().describe("Advertiser or brand name when pageId/libraryId is not known. One of pageId, libraryId, or query is required."),
|
|
2890
|
+
maxAds: import_zod3.z.number().int().min(1).max(200).default(50).describe("Maximum ads to inspect. Default 50, maximum 200. Prefer 25-50 for focused advertiser scans; use 100-200 only when the user asks for a broad ad archive sweep."),
|
|
2891
|
+
country: import_zod3.z.string().length(2).default("US").describe("Two-letter Ad Library country code. Default US. Examples: US, CA, GB, AU. Infer from the user request when they name a country.")
|
|
2378
2892
|
};
|
|
2379
2893
|
var FacebookAdSearchInputSchema = {
|
|
2380
|
-
query:
|
|
2381
|
-
country:
|
|
2382
|
-
maxResults:
|
|
2894
|
+
query: import_zod3.z.string().min(1).describe("Advertiser, brand, competitor, niche, or keyword to search in Facebook Ad Library."),
|
|
2895
|
+
country: import_zod3.z.string().length(2).default("US").describe("Two-letter Ad Library country code. Default US. Examples: US, CA, GB, AU."),
|
|
2896
|
+
maxResults: import_zod3.z.number().int().min(1).max(20).default(10).describe("Maximum advertisers to return. Default 10, maximum 20. Prefer tighter search terms over maxing this out.")
|
|
2383
2897
|
};
|
|
2384
2898
|
var FacebookAdTranscribeInputSchema = {
|
|
2385
|
-
videoUrl:
|
|
2899
|
+
videoUrl: import_zod3.z.string().url().describe("Direct Facebook CDN video URL from a facebook_page_intel ad result. Do not pass a public Facebook reel/post/share URL here; use facebook_video_transcribe for organic Facebook URLs.")
|
|
2386
2900
|
};
|
|
2387
2901
|
var FacebookVideoTranscribeInputSchema = {
|
|
2388
|
-
url:
|
|
2389
|
-
quality:
|
|
2902
|
+
url: import_zod3.z.string().url().describe("Organic Facebook reel, video, watch, post, or share URL from facebook.com, m.facebook.com, or fb.watch. The tool renders the page, extracts the best matching public Facebook CDN MP4 URL, then transcribes it. Use this when the user pastes a normal Facebook video page URL and asks for the transcript or downloadable MP4."),
|
|
2903
|
+
quality: import_zod3.z.enum(["best", "hd", "sd"]).default("best").describe("Preferred progressive MP4 quality. Use best by default; hd prefers the highest HD progressive URL; sd forces the SD URL.")
|
|
2390
2904
|
};
|
|
2391
2905
|
var MapsPlaceIntelInputSchema = {
|
|
2392
|
-
businessName:
|
|
2393
|
-
location:
|
|
2394
|
-
gl:
|
|
2395
|
-
hl:
|
|
2396
|
-
includeReviews:
|
|
2397
|
-
maxReviews:
|
|
2906
|
+
businessName: import_zod3.z.string().min(1).describe('Business name only. If user says "Elite Roofing Denver CO", use businessName="Elite Roofing" and location="Denver, CO".'),
|
|
2907
|
+
location: import_zod3.z.string().min(1).describe('City/region/country where the business should be searched, e.g. "Denver, CO". Infer from the user request when possible.'),
|
|
2908
|
+
gl: import_zod3.z.string().length(2).default("us").describe("Google country code inferred from location."),
|
|
2909
|
+
hl: import_zod3.z.string().length(2).default("en").describe("Language inferred from user request."),
|
|
2910
|
+
includeReviews: import_zod3.z.boolean().default(false).describe("Whether to fetch individual review cards. Use true when the user asks for reviews, customer pain, complaints, praise, themes, or review evidence."),
|
|
2911
|
+
maxReviews: import_zod3.z.number().int().min(1).max(500).default(50).describe("Max review cards to return when includeReviews is true. Default 50, maximum 500. Use 50 for normal review analysis and larger values only for deep review mining.")
|
|
2398
2912
|
};
|
|
2399
2913
|
var MapsSearchInputSchema = {
|
|
2400
|
-
query:
|
|
2401
|
-
location:
|
|
2402
|
-
gl:
|
|
2403
|
-
hl:
|
|
2404
|
-
maxResults:
|
|
2405
|
-
proxyMode:
|
|
2406
|
-
proxyZip:
|
|
2407
|
-
debug:
|
|
2914
|
+
query: import_zod3.z.string().min(1).describe('Business category, niche, keyword, or search term. If the user says "roofers in Denver CO", use query="roofers" and location="Denver, CO". Do not put the location here when it can be separated.'),
|
|
2915
|
+
location: import_zod3.z.string().optional().describe('City, region, country, or service area for the Maps search, e.g. "Denver, CO". Infer from the user request when present.'),
|
|
2916
|
+
gl: import_zod3.z.string().length(2).default("us").describe("Google country code inferred from location."),
|
|
2917
|
+
hl: import_zod3.z.string().length(2).default("en").describe("Language inferred from user request."),
|
|
2918
|
+
maxResults: import_zod3.z.number().int().min(1).max(50).default(10).describe("Number of Google Maps business/profile candidates to return. Default 10. Maximum 50. Use 10 unless the user asks for more."),
|
|
2919
|
+
proxyMode: import_zod3.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Proxy targeting mode. Maps defaults to location so local market searches get city/state residential proxy targeting and rotation. Use configured to force the service proxy without city/ZIP targeting, and none only for local direct-network debugging."),
|
|
2920
|
+
proxyZip: import_zod3.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use when the user gives a specific ZIP or city-center ZIP."),
|
|
2921
|
+
debug: import_zod3.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics when debugging Maps localization, CAPTCHA, or proxy behavior.")
|
|
2408
2922
|
};
|
|
2409
2923
|
var DirectoryWorkflowInputSchema = {
|
|
2410
|
-
query:
|
|
2411
|
-
state:
|
|
2412
|
-
minPopulation:
|
|
2413
|
-
populationYear:
|
|
2414
|
-
maxCities:
|
|
2415
|
-
maxResultsPerCity:
|
|
2416
|
-
concurrency:
|
|
2417
|
-
includeZipGroups:
|
|
2418
|
-
usZipsCsvPath:
|
|
2419
|
-
saveCsv:
|
|
2420
|
-
proxyMode:
|
|
2421
|
-
proxyZip:
|
|
2422
|
-
debug:
|
|
2423
|
-
};
|
|
2424
|
-
var RankTrackerModeSchema =
|
|
2924
|
+
query: import_zod3.z.string().min(1).describe("Business category, niche, or keyword to search on Google Maps for every selected market, e.g. roofers, dentists, med spas. Do not include the city here."),
|
|
2925
|
+
state: import_zod3.z.string().min(2).default("TN").describe("US state abbreviation or state name used to select Census places, e.g. TN or Tennessee."),
|
|
2926
|
+
minPopulation: import_zod3.z.number().int().min(0).default(1e5).describe('Minimum Census place population for market selection. Use 100000 for "cities above 100k population".'),
|
|
2927
|
+
populationYear: import_zod3.z.number().int().min(2020).max(2025).default(2025).describe("Census population estimate year from the 2020-2025 Population Estimates Program city/place dataset."),
|
|
2928
|
+
maxCities: import_zod3.z.number().int().min(1).max(100).default(25).describe("Maximum number of markets to process after sorting by population descending."),
|
|
2929
|
+
maxResultsPerCity: import_zod3.z.number().int().min(1).max(50).default(50).describe("Google Maps business/profile candidates to collect for each city. Maximum 50."),
|
|
2930
|
+
concurrency: import_zod3.z.number().int().min(1).max(5).default(5).describe("How many city Maps searches to run in parallel. Use 5 for broad directory batches unless debugging."),
|
|
2931
|
+
includeZipGroups: import_zod3.z.boolean().default(true).describe("Attach ZIP groups from a configured US ZIPS CSV when available. Set MCP_SCRAPER_USZIPS_CSV_PATH on the API server or pass usZipsCsvPath in local/test mode."),
|
|
2932
|
+
usZipsCsvPath: import_zod3.z.string().optional().describe("Local/test-only path to a US ZIPS CSV with state_abbr, zipcode, county, city columns, such as Lead Magician tools/analytics/data/uszips.csv. Deployed APIs should use MCP_SCRAPER_USZIPS_CSV_PATH instead."),
|
|
2933
|
+
saveCsv: import_zod3.z.boolean().default(true).describe("Save a directory-ready CSV to the MCP Scraper output directory and return its path. CSV rows include source_location, result_position, business_name, review_stars, review_count, category, address, phone, hours_status, website_url, directions_url, place_url, CID fields, population, and ZIP groups."),
|
|
2934
|
+
proxyMode: import_zod3.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Proxy targeting mode for every city Maps search. Maps workflows default to location so each city can use city/state or ZIP-group residential proxy targeting. Use configured to force the service proxy without city/ZIP targeting, and none only for local direct-network debugging."),
|
|
2935
|
+
proxyZip: import_zod3.z.string().regex(/^\d{5}$/).optional().describe("Optional ZIP override for proxy targeting. Normally omit it so each city can use its ZIP group or city/state location."),
|
|
2936
|
+
debug: import_zod3.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics in each Maps browser session when supported.")
|
|
2937
|
+
};
|
|
2938
|
+
var RankTrackerModeSchema = import_zod3.z.enum(["maps", "organic", "ai_overview", "paa"]);
|
|
2425
2939
|
var RankTrackerBlueprintInputSchema = {
|
|
2426
|
-
projectName:
|
|
2427
|
-
targetDomain:
|
|
2428
|
-
targetBusinessName:
|
|
2429
|
-
trackingModes:
|
|
2430
|
-
keywords:
|
|
2431
|
-
locations:
|
|
2432
|
-
competitors:
|
|
2433
|
-
database:
|
|
2434
|
-
scheduleCadence:
|
|
2435
|
-
customCron:
|
|
2436
|
-
timezone:
|
|
2437
|
-
includeCron:
|
|
2438
|
-
includeDashboard:
|
|
2439
|
-
includeAlerts:
|
|
2440
|
-
notes:
|
|
2441
|
-
};
|
|
2442
|
-
var
|
|
2443
|
-
var MapsSearchAttemptOutput =
|
|
2444
|
-
attemptNumber:
|
|
2445
|
-
maxAttempts:
|
|
2446
|
-
status:
|
|
2447
|
-
outcome:
|
|
2448
|
-
willRetry:
|
|
2449
|
-
durationMs:
|
|
2450
|
-
resultCount:
|
|
2451
|
-
error:
|
|
2452
|
-
proxyMode:
|
|
2453
|
-
proxyResolutionSource:
|
|
2454
|
-
proxyIdSuffix:
|
|
2455
|
-
proxyTargetLevel:
|
|
2456
|
-
proxyTargetLocation:
|
|
2457
|
-
proxyTargetZip:
|
|
2458
|
-
browserSessionIdSuffix:
|
|
2459
|
-
observedIp:
|
|
2460
|
-
observedCity:
|
|
2461
|
-
observedRegion:
|
|
2940
|
+
projectName: import_zod3.z.string().min(1).optional().describe("Optional name for the rank tracker project, client, or campaign."),
|
|
2941
|
+
targetDomain: import_zod3.z.string().min(1).optional().describe("Primary domain to track in organic results, AI Overview citations, and PAA sources, e.g. example.com."),
|
|
2942
|
+
targetBusinessName: import_zod3.z.string().min(1).optional().describe("Primary Google Business Profile or brand/business name to match in Maps results. Required for reliable Maps rank tracking."),
|
|
2943
|
+
trackingModes: import_zod3.z.array(RankTrackerModeSchema).min(1).max(4).default(["maps", "organic", "ai_overview", "paa"]).describe("Rank tracker surfaces to build. maps uses directory_workflow/maps_search. organic uses search_serp. ai_overview uses search_serp/harvest_paa. paa uses harvest_paa source presence."),
|
|
2944
|
+
keywords: import_zod3.z.array(import_zod3.z.string().min(1)).max(200).default([]).describe("Seed keywords or service queries to track. Leave empty when the downstream AI should create the keyword table from user input."),
|
|
2945
|
+
locations: import_zod3.z.array(import_zod3.z.string().min(1)).max(100).default([]).describe("Markets, cities, ZIPs, or service areas to track. Use city/state strings like Denver, CO for localized SERP and Maps checks."),
|
|
2946
|
+
competitors: import_zod3.z.array(import_zod3.z.string().min(1)).max(100).default([]).describe("Optional competitor domains or business names to persist as comparison targets."),
|
|
2947
|
+
database: import_zod3.z.enum(["postgres", "neon", "supabase", "sqlite", "mysql"]).default("postgres").describe("Database family the downstream AI should target when generating migrations."),
|
|
2948
|
+
scheduleCadence: import_zod3.z.enum(["daily", "weekly", "monthly", "custom"]).default("weekly").describe("Default recurring rank check cadence for the generated cron/heartbeat plan."),
|
|
2949
|
+
customCron: import_zod3.z.string().min(1).optional().describe("Cron expression to use when scheduleCadence is custom."),
|
|
2950
|
+
timezone: import_zod3.z.string().min(1).default("UTC").describe("IANA timezone for scheduled rank checks, e.g. America/Denver."),
|
|
2951
|
+
includeCron: import_zod3.z.boolean().default(true).describe("Include a cron or heartbeat worker plan. Keep true for production rank trackers."),
|
|
2952
|
+
includeDashboard: import_zod3.z.boolean().default(true).describe("Include dashboard/reporting requirements in the generated prompt."),
|
|
2953
|
+
includeAlerts: import_zod3.z.boolean().default(true).describe("Include alert rules for rank movement and SERP feature gains/losses."),
|
|
2954
|
+
notes: import_zod3.z.string().max(4e3).optional().describe("Extra product, client, stack, or hosting requirements to include in the implementation prompt.")
|
|
2955
|
+
};
|
|
2956
|
+
var NullableString2 = import_zod3.z.string().nullable();
|
|
2957
|
+
var MapsSearchAttemptOutput = import_zod3.z.object({
|
|
2958
|
+
attemptNumber: import_zod3.z.number().int().min(1),
|
|
2959
|
+
maxAttempts: import_zod3.z.number().int().min(1),
|
|
2960
|
+
status: import_zod3.z.enum(["ok", "failed"]),
|
|
2961
|
+
outcome: import_zod3.z.string(),
|
|
2962
|
+
willRetry: import_zod3.z.boolean(),
|
|
2963
|
+
durationMs: import_zod3.z.number().int().min(0),
|
|
2964
|
+
resultCount: import_zod3.z.number().int().min(0),
|
|
2965
|
+
error: NullableString2,
|
|
2966
|
+
proxyMode: import_zod3.z.enum(["location", "configured", "none"]),
|
|
2967
|
+
proxyResolutionSource: import_zod3.z.enum(["disabled", "location_reused", "location_created", "configured_fallback", "unavailable"]).nullable(),
|
|
2968
|
+
proxyIdSuffix: NullableString2,
|
|
2969
|
+
proxyTargetLevel: import_zod3.z.enum(["zip", "city", "state"]).nullable(),
|
|
2970
|
+
proxyTargetLocation: NullableString2,
|
|
2971
|
+
proxyTargetZip: NullableString2,
|
|
2972
|
+
browserSessionIdSuffix: NullableString2,
|
|
2973
|
+
observedIp: NullableString2,
|
|
2974
|
+
observedCity: NullableString2,
|
|
2975
|
+
observedRegion: NullableString2
|
|
2462
2976
|
});
|
|
2463
2977
|
var MapsSearchOutputSchema = {
|
|
2464
|
-
query:
|
|
2465
|
-
location:
|
|
2466
|
-
searchQuery:
|
|
2467
|
-
searchUrl:
|
|
2468
|
-
extractedAt:
|
|
2469
|
-
requestedMaxResults:
|
|
2470
|
-
resultCount:
|
|
2471
|
-
results:
|
|
2472
|
-
position:
|
|
2473
|
-
name:
|
|
2474
|
-
placeUrl:
|
|
2475
|
-
cid:
|
|
2476
|
-
cidDecimal:
|
|
2477
|
-
rating:
|
|
2478
|
-
reviewCount:
|
|
2479
|
-
category:
|
|
2480
|
-
address:
|
|
2481
|
-
phone:
|
|
2482
|
-
hoursStatus:
|
|
2483
|
-
websiteUrl:
|
|
2484
|
-
directionsUrl:
|
|
2485
|
-
metadata:
|
|
2978
|
+
query: import_zod3.z.string(),
|
|
2979
|
+
location: import_zod3.z.string().nullable(),
|
|
2980
|
+
searchQuery: import_zod3.z.string(),
|
|
2981
|
+
searchUrl: import_zod3.z.string().url(),
|
|
2982
|
+
extractedAt: import_zod3.z.string(),
|
|
2983
|
+
requestedMaxResults: import_zod3.z.number().int().min(1).max(50),
|
|
2984
|
+
resultCount: import_zod3.z.number().int().min(0).max(50),
|
|
2985
|
+
results: import_zod3.z.array(import_zod3.z.object({
|
|
2986
|
+
position: import_zod3.z.number().int().min(1),
|
|
2987
|
+
name: import_zod3.z.string(),
|
|
2988
|
+
placeUrl: import_zod3.z.string().url(),
|
|
2989
|
+
cid: NullableString2,
|
|
2990
|
+
cidDecimal: NullableString2,
|
|
2991
|
+
rating: NullableString2,
|
|
2992
|
+
reviewCount: NullableString2,
|
|
2993
|
+
category: NullableString2,
|
|
2994
|
+
address: NullableString2,
|
|
2995
|
+
phone: NullableString2,
|
|
2996
|
+
hoursStatus: NullableString2,
|
|
2997
|
+
websiteUrl: NullableString2,
|
|
2998
|
+
directionsUrl: NullableString2,
|
|
2999
|
+
metadata: import_zod3.z.array(import_zod3.z.string())
|
|
2486
3000
|
})),
|
|
2487
|
-
attempts:
|
|
2488
|
-
durationMs:
|
|
2489
|
-
};
|
|
2490
|
-
var DirectoryMapsBusinessOutput =
|
|
2491
|
-
position:
|
|
2492
|
-
name:
|
|
2493
|
-
placeUrl:
|
|
2494
|
-
cid:
|
|
2495
|
-
cidDecimal:
|
|
2496
|
-
rating:
|
|
2497
|
-
reviewCount:
|
|
2498
|
-
category:
|
|
2499
|
-
address:
|
|
2500
|
-
phone:
|
|
2501
|
-
hoursStatus:
|
|
2502
|
-
websiteUrl:
|
|
2503
|
-
directionsUrl:
|
|
2504
|
-
metadata:
|
|
3001
|
+
attempts: import_zod3.z.array(MapsSearchAttemptOutput),
|
|
3002
|
+
durationMs: import_zod3.z.number().int().min(0)
|
|
3003
|
+
};
|
|
3004
|
+
var DirectoryMapsBusinessOutput = import_zod3.z.object({
|
|
3005
|
+
position: import_zod3.z.number().int().min(1),
|
|
3006
|
+
name: import_zod3.z.string(),
|
|
3007
|
+
placeUrl: import_zod3.z.string().url(),
|
|
3008
|
+
cid: NullableString2,
|
|
3009
|
+
cidDecimal: NullableString2,
|
|
3010
|
+
rating: NullableString2,
|
|
3011
|
+
reviewCount: NullableString2,
|
|
3012
|
+
category: NullableString2,
|
|
3013
|
+
address: NullableString2,
|
|
3014
|
+
phone: NullableString2,
|
|
3015
|
+
hoursStatus: NullableString2,
|
|
3016
|
+
websiteUrl: NullableString2,
|
|
3017
|
+
directionsUrl: NullableString2,
|
|
3018
|
+
metadata: import_zod3.z.array(import_zod3.z.string())
|
|
2505
3019
|
});
|
|
2506
3020
|
var DirectoryWorkflowOutputSchema = {
|
|
2507
|
-
query:
|
|
2508
|
-
state:
|
|
2509
|
-
minPopulation:
|
|
2510
|
-
populationYear:
|
|
2511
|
-
maxResultsPerCity:
|
|
2512
|
-
concurrency:
|
|
2513
|
-
censusSourceUrl:
|
|
2514
|
-
usZipsSourcePath:
|
|
2515
|
-
warnings:
|
|
2516
|
-
extractedAt:
|
|
2517
|
-
selectedCityCount:
|
|
2518
|
-
totalResultCount:
|
|
2519
|
-
csvPath:
|
|
2520
|
-
cities:
|
|
2521
|
-
city:
|
|
2522
|
-
state:
|
|
2523
|
-
location:
|
|
2524
|
-
cityKey:
|
|
2525
|
-
censusName:
|
|
2526
|
-
population:
|
|
2527
|
-
populationYear:
|
|
2528
|
-
zips:
|
|
2529
|
-
counties:
|
|
2530
|
-
status:
|
|
2531
|
-
error:
|
|
2532
|
-
resultCount:
|
|
2533
|
-
durationMs:
|
|
2534
|
-
attempts:
|
|
2535
|
-
results:
|
|
3021
|
+
query: import_zod3.z.string(),
|
|
3022
|
+
state: import_zod3.z.string(),
|
|
3023
|
+
minPopulation: import_zod3.z.number().int().min(0),
|
|
3024
|
+
populationYear: import_zod3.z.number().int().min(2020).max(2025),
|
|
3025
|
+
maxResultsPerCity: import_zod3.z.number().int().min(1).max(50),
|
|
3026
|
+
concurrency: import_zod3.z.number().int().min(1).max(5),
|
|
3027
|
+
censusSourceUrl: import_zod3.z.string().url(),
|
|
3028
|
+
usZipsSourcePath: NullableString2,
|
|
3029
|
+
warnings: import_zod3.z.array(import_zod3.z.string()),
|
|
3030
|
+
extractedAt: import_zod3.z.string(),
|
|
3031
|
+
selectedCityCount: import_zod3.z.number().int().min(0),
|
|
3032
|
+
totalResultCount: import_zod3.z.number().int().min(0),
|
|
3033
|
+
csvPath: NullableString2,
|
|
3034
|
+
cities: import_zod3.z.array(import_zod3.z.object({
|
|
3035
|
+
city: import_zod3.z.string(),
|
|
3036
|
+
state: import_zod3.z.string(),
|
|
3037
|
+
location: import_zod3.z.string(),
|
|
3038
|
+
cityKey: import_zod3.z.string(),
|
|
3039
|
+
censusName: import_zod3.z.string(),
|
|
3040
|
+
population: import_zod3.z.number().int().min(0),
|
|
3041
|
+
populationYear: import_zod3.z.number().int().min(2020).max(2025),
|
|
3042
|
+
zips: import_zod3.z.array(import_zod3.z.string()),
|
|
3043
|
+
counties: import_zod3.z.array(import_zod3.z.string()),
|
|
3044
|
+
status: import_zod3.z.enum(["ok", "empty", "failed"]),
|
|
3045
|
+
error: NullableString2,
|
|
3046
|
+
resultCount: import_zod3.z.number().int().min(0),
|
|
3047
|
+
durationMs: import_zod3.z.number().int().min(0),
|
|
3048
|
+
attempts: import_zod3.z.array(MapsSearchAttemptOutput),
|
|
3049
|
+
results: import_zod3.z.array(DirectoryMapsBusinessOutput)
|
|
2536
3050
|
})),
|
|
2537
|
-
durationMs:
|
|
3051
|
+
durationMs: import_zod3.z.number().int().min(0)
|
|
2538
3052
|
};
|
|
2539
|
-
var RankTrackerToolPlanOutput =
|
|
2540
|
-
tool:
|
|
2541
|
-
purpose:
|
|
3053
|
+
var RankTrackerToolPlanOutput = import_zod3.z.object({
|
|
3054
|
+
tool: import_zod3.z.string(),
|
|
3055
|
+
purpose: import_zod3.z.string()
|
|
2542
3056
|
});
|
|
2543
|
-
var RankTrackerTableOutput =
|
|
2544
|
-
name:
|
|
2545
|
-
purpose:
|
|
2546
|
-
keyColumns:
|
|
3057
|
+
var RankTrackerTableOutput = import_zod3.z.object({
|
|
3058
|
+
name: import_zod3.z.string(),
|
|
3059
|
+
purpose: import_zod3.z.string(),
|
|
3060
|
+
keyColumns: import_zod3.z.array(import_zod3.z.string())
|
|
2547
3061
|
});
|
|
2548
|
-
var RankTrackerCronJobOutput =
|
|
2549
|
-
name:
|
|
2550
|
-
purpose:
|
|
2551
|
-
modes:
|
|
2552
|
-
recommendedTools:
|
|
3062
|
+
var RankTrackerCronJobOutput = import_zod3.z.object({
|
|
3063
|
+
name: import_zod3.z.string(),
|
|
3064
|
+
purpose: import_zod3.z.string(),
|
|
3065
|
+
modes: import_zod3.z.array(RankTrackerModeSchema),
|
|
3066
|
+
recommendedTools: import_zod3.z.array(import_zod3.z.string())
|
|
2553
3067
|
});
|
|
2554
3068
|
var RankTrackerBlueprintOutputSchema = {
|
|
2555
|
-
projectName:
|
|
2556
|
-
targetDomain:
|
|
2557
|
-
targetBusinessName:
|
|
2558
|
-
trackingModes:
|
|
2559
|
-
database:
|
|
2560
|
-
recommendedTools:
|
|
2561
|
-
tables:
|
|
2562
|
-
cron:
|
|
2563
|
-
enabled:
|
|
2564
|
-
cadence:
|
|
2565
|
-
expression:
|
|
2566
|
-
timezone:
|
|
2567
|
-
jobs:
|
|
3069
|
+
projectName: import_zod3.z.string(),
|
|
3070
|
+
targetDomain: NullableString2,
|
|
3071
|
+
targetBusinessName: NullableString2,
|
|
3072
|
+
trackingModes: import_zod3.z.array(RankTrackerModeSchema),
|
|
3073
|
+
database: import_zod3.z.string(),
|
|
3074
|
+
recommendedTools: import_zod3.z.array(RankTrackerToolPlanOutput),
|
|
3075
|
+
tables: import_zod3.z.array(RankTrackerTableOutput),
|
|
3076
|
+
cron: import_zod3.z.object({
|
|
3077
|
+
enabled: import_zod3.z.boolean(),
|
|
3078
|
+
cadence: import_zod3.z.string(),
|
|
3079
|
+
expression: import_zod3.z.string(),
|
|
3080
|
+
timezone: import_zod3.z.string(),
|
|
3081
|
+
jobs: import_zod3.z.array(RankTrackerCronJobOutput)
|
|
2568
3082
|
}),
|
|
2569
|
-
metrics:
|
|
2570
|
-
implementationPrompt:
|
|
2571
|
-
};
|
|
2572
|
-
var OrganicResultOutput =
|
|
2573
|
-
position:
|
|
2574
|
-
title:
|
|
2575
|
-
url:
|
|
2576
|
-
domain:
|
|
2577
|
-
snippet:
|
|
3083
|
+
metrics: import_zod3.z.array(import_zod3.z.string()),
|
|
3084
|
+
implementationPrompt: import_zod3.z.string()
|
|
3085
|
+
};
|
|
3086
|
+
var OrganicResultOutput = import_zod3.z.object({
|
|
3087
|
+
position: import_zod3.z.number().int(),
|
|
3088
|
+
title: import_zod3.z.string(),
|
|
3089
|
+
url: import_zod3.z.string(),
|
|
3090
|
+
domain: import_zod3.z.string(),
|
|
3091
|
+
snippet: NullableString2
|
|
2578
3092
|
});
|
|
2579
|
-
var AiOverviewOutput =
|
|
2580
|
-
detected:
|
|
2581
|
-
text:
|
|
3093
|
+
var AiOverviewOutput = import_zod3.z.object({
|
|
3094
|
+
detected: import_zod3.z.boolean(),
|
|
3095
|
+
text: NullableString2
|
|
2582
3096
|
}).nullable();
|
|
2583
|
-
var EntityIdsOutput =
|
|
2584
|
-
kgIds:
|
|
2585
|
-
cids:
|
|
2586
|
-
gcids:
|
|
3097
|
+
var EntityIdsOutput = import_zod3.z.object({
|
|
3098
|
+
kgIds: import_zod3.z.array(import_zod3.z.string()),
|
|
3099
|
+
cids: import_zod3.z.array(import_zod3.z.string()),
|
|
3100
|
+
gcids: import_zod3.z.array(import_zod3.z.string())
|
|
2587
3101
|
}).nullable();
|
|
2588
3102
|
var HarvestPaaOutputSchema = {
|
|
2589
|
-
query:
|
|
2590
|
-
location:
|
|
2591
|
-
questionCount:
|
|
2592
|
-
completionStatus:
|
|
2593
|
-
questions:
|
|
2594
|
-
question:
|
|
2595
|
-
answer:
|
|
2596
|
-
sourceTitle:
|
|
2597
|
-
sourceSite:
|
|
3103
|
+
query: import_zod3.z.string(),
|
|
3104
|
+
location: NullableString2,
|
|
3105
|
+
questionCount: import_zod3.z.number().int().min(0),
|
|
3106
|
+
completionStatus: NullableString2,
|
|
3107
|
+
questions: import_zod3.z.array(import_zod3.z.object({
|
|
3108
|
+
question: import_zod3.z.string(),
|
|
3109
|
+
answer: NullableString2,
|
|
3110
|
+
sourceTitle: NullableString2,
|
|
3111
|
+
sourceSite: NullableString2
|
|
2598
3112
|
})),
|
|
2599
|
-
organicResults:
|
|
3113
|
+
organicResults: import_zod3.z.array(OrganicResultOutput),
|
|
2600
3114
|
aiOverview: AiOverviewOutput,
|
|
2601
3115
|
entityIds: EntityIdsOutput,
|
|
2602
|
-
durationMs:
|
|
3116
|
+
durationMs: import_zod3.z.number().min(0).nullable()
|
|
2603
3117
|
};
|
|
2604
3118
|
var SearchSerpOutputSchema = {
|
|
2605
|
-
query:
|
|
2606
|
-
location:
|
|
2607
|
-
organicResults:
|
|
2608
|
-
localPack:
|
|
2609
|
-
position:
|
|
2610
|
-
name:
|
|
2611
|
-
rating:
|
|
2612
|
-
reviewCount:
|
|
2613
|
-
websiteUrl:
|
|
3119
|
+
query: import_zod3.z.string(),
|
|
3120
|
+
location: NullableString2,
|
|
3121
|
+
organicResults: import_zod3.z.array(OrganicResultOutput),
|
|
3122
|
+
localPack: import_zod3.z.array(import_zod3.z.object({
|
|
3123
|
+
position: import_zod3.z.number().int(),
|
|
3124
|
+
name: import_zod3.z.string(),
|
|
3125
|
+
rating: NullableString2,
|
|
3126
|
+
reviewCount: NullableString2,
|
|
3127
|
+
websiteUrl: NullableString2
|
|
2614
3128
|
})),
|
|
2615
3129
|
aiOverview: AiOverviewOutput,
|
|
2616
3130
|
entityIds: EntityIdsOutput
|
|
2617
3131
|
};
|
|
2618
3132
|
var ExtractUrlOutputSchema = {
|
|
2619
|
-
url:
|
|
2620
|
-
title:
|
|
2621
|
-
headings:
|
|
2622
|
-
level:
|
|
2623
|
-
text:
|
|
3133
|
+
url: import_zod3.z.string(),
|
|
3134
|
+
title: NullableString2,
|
|
3135
|
+
headings: import_zod3.z.array(import_zod3.z.object({
|
|
3136
|
+
level: import_zod3.z.number().int(),
|
|
3137
|
+
text: import_zod3.z.string()
|
|
2624
3138
|
})),
|
|
2625
|
-
schemaBlockCount:
|
|
2626
|
-
entityName:
|
|
2627
|
-
entityTypes:
|
|
2628
|
-
napScore:
|
|
2629
|
-
missingSchemaFields:
|
|
2630
|
-
screenshotSaved:
|
|
3139
|
+
schemaBlockCount: import_zod3.z.number().int().min(0),
|
|
3140
|
+
entityName: NullableString2,
|
|
3141
|
+
entityTypes: import_zod3.z.array(import_zod3.z.string()),
|
|
3142
|
+
napScore: import_zod3.z.number().nullable(),
|
|
3143
|
+
missingSchemaFields: import_zod3.z.array(import_zod3.z.string()),
|
|
3144
|
+
screenshotSaved: NullableString2
|
|
2631
3145
|
};
|
|
2632
3146
|
var ExtractSiteOutputSchema = {
|
|
2633
|
-
url:
|
|
2634
|
-
pageCount:
|
|
2635
|
-
pages:
|
|
2636
|
-
url:
|
|
2637
|
-
title:
|
|
2638
|
-
schemaTypes:
|
|
3147
|
+
url: import_zod3.z.string(),
|
|
3148
|
+
pageCount: import_zod3.z.number().int().min(0),
|
|
3149
|
+
pages: import_zod3.z.array(import_zod3.z.object({
|
|
3150
|
+
url: import_zod3.z.string(),
|
|
3151
|
+
title: NullableString2,
|
|
3152
|
+
schemaTypes: import_zod3.z.array(import_zod3.z.string())
|
|
2639
3153
|
})),
|
|
2640
|
-
durationMs:
|
|
3154
|
+
durationMs: import_zod3.z.number().min(0)
|
|
2641
3155
|
};
|
|
2642
3156
|
var MapsPlaceIntelOutputSchema = {
|
|
2643
|
-
name:
|
|
2644
|
-
rating:
|
|
2645
|
-
reviewCount:
|
|
2646
|
-
category:
|
|
2647
|
-
address:
|
|
2648
|
-
phone:
|
|
2649
|
-
website:
|
|
2650
|
-
hoursSummary:
|
|
2651
|
-
bookingUrl:
|
|
2652
|
-
kgmid:
|
|
2653
|
-
cidDecimal:
|
|
2654
|
-
cidUrl:
|
|
2655
|
-
lat:
|
|
2656
|
-
lng:
|
|
2657
|
-
reviewsStatus:
|
|
2658
|
-
reviewsCollected:
|
|
2659
|
-
reviewTopics:
|
|
2660
|
-
label:
|
|
2661
|
-
count:
|
|
3157
|
+
name: import_zod3.z.string(),
|
|
3158
|
+
rating: NullableString2,
|
|
3159
|
+
reviewCount: NullableString2,
|
|
3160
|
+
category: NullableString2,
|
|
3161
|
+
address: NullableString2,
|
|
3162
|
+
phone: NullableString2,
|
|
3163
|
+
website: NullableString2,
|
|
3164
|
+
hoursSummary: NullableString2,
|
|
3165
|
+
bookingUrl: NullableString2,
|
|
3166
|
+
kgmid: NullableString2,
|
|
3167
|
+
cidDecimal: NullableString2,
|
|
3168
|
+
cidUrl: NullableString2,
|
|
3169
|
+
lat: import_zod3.z.number().nullable(),
|
|
3170
|
+
lng: import_zod3.z.number().nullable(),
|
|
3171
|
+
reviewsStatus: import_zod3.z.string(),
|
|
3172
|
+
reviewsCollected: import_zod3.z.number().int().min(0),
|
|
3173
|
+
reviewTopics: import_zod3.z.array(import_zod3.z.object({
|
|
3174
|
+
label: import_zod3.z.string(),
|
|
3175
|
+
count: import_zod3.z.string()
|
|
2662
3176
|
}))
|
|
2663
3177
|
};
|
|
2664
3178
|
var CreditsInfoOutputSchema = {
|
|
2665
|
-
balanceCredits:
|
|
2666
|
-
matchedCost:
|
|
2667
|
-
label:
|
|
2668
|
-
credits:
|
|
2669
|
-
unit:
|
|
2670
|
-
notes:
|
|
3179
|
+
balanceCredits: import_zod3.z.number().nullable(),
|
|
3180
|
+
matchedCost: import_zod3.z.object({
|
|
3181
|
+
label: import_zod3.z.string(),
|
|
3182
|
+
credits: import_zod3.z.number(),
|
|
3183
|
+
unit: import_zod3.z.string(),
|
|
3184
|
+
notes: NullableString2
|
|
2671
3185
|
}).nullable(),
|
|
2672
|
-
costs:
|
|
2673
|
-
key:
|
|
2674
|
-
label:
|
|
2675
|
-
credits:
|
|
2676
|
-
unit:
|
|
2677
|
-
notes:
|
|
3186
|
+
costs: import_zod3.z.array(import_zod3.z.object({
|
|
3187
|
+
key: import_zod3.z.string(),
|
|
3188
|
+
label: import_zod3.z.string(),
|
|
3189
|
+
credits: import_zod3.z.number(),
|
|
3190
|
+
unit: import_zod3.z.string(),
|
|
3191
|
+
notes: NullableString2
|
|
2678
3192
|
})),
|
|
2679
|
-
ledger:
|
|
2680
|
-
createdAt:
|
|
2681
|
-
operation:
|
|
2682
|
-
credits:
|
|
2683
|
-
description:
|
|
3193
|
+
ledger: import_zod3.z.array(import_zod3.z.object({
|
|
3194
|
+
createdAt: import_zod3.z.string(),
|
|
3195
|
+
operation: import_zod3.z.string(),
|
|
3196
|
+
credits: import_zod3.z.number(),
|
|
3197
|
+
description: NullableString2
|
|
2684
3198
|
})),
|
|
2685
|
-
concurrency:
|
|
2686
|
-
currentExtraSlots:
|
|
2687
|
-
currentLimit:
|
|
2688
|
-
hasSubscription:
|
|
2689
|
-
upgrade:
|
|
2690
|
-
product:
|
|
2691
|
-
priceLabel:
|
|
2692
|
-
unitAmountUsd:
|
|
2693
|
-
currency:
|
|
2694
|
-
interval:
|
|
2695
|
-
billingUrl:
|
|
2696
|
-
terminalCommand:
|
|
2697
|
-
terminalCommandWithApiKeyEnv:
|
|
3199
|
+
concurrency: import_zod3.z.object({
|
|
3200
|
+
currentExtraSlots: import_zod3.z.number().int().min(0),
|
|
3201
|
+
currentLimit: import_zod3.z.number().int().min(1),
|
|
3202
|
+
hasSubscription: import_zod3.z.boolean(),
|
|
3203
|
+
upgrade: import_zod3.z.object({
|
|
3204
|
+
product: import_zod3.z.string(),
|
|
3205
|
+
priceLabel: import_zod3.z.string(),
|
|
3206
|
+
unitAmountUsd: import_zod3.z.number(),
|
|
3207
|
+
currency: import_zod3.z.string(),
|
|
3208
|
+
interval: import_zod3.z.string(),
|
|
3209
|
+
billingUrl: import_zod3.z.string().url(),
|
|
3210
|
+
terminalCommand: import_zod3.z.string(),
|
|
3211
|
+
terminalCommandWithApiKeyEnv: import_zod3.z.string()
|
|
2698
3212
|
})
|
|
2699
3213
|
}).nullable()
|
|
2700
3214
|
};
|
|
2701
3215
|
var MapSiteUrlsOutputSchema = {
|
|
2702
|
-
startUrl:
|
|
2703
|
-
totalFound:
|
|
2704
|
-
truncated:
|
|
2705
|
-
okCount:
|
|
2706
|
-
redirectCount:
|
|
2707
|
-
brokenCount:
|
|
2708
|
-
urls:
|
|
2709
|
-
url:
|
|
2710
|
-
status:
|
|
3216
|
+
startUrl: import_zod3.z.string(),
|
|
3217
|
+
totalFound: import_zod3.z.number().int().min(0),
|
|
3218
|
+
truncated: import_zod3.z.boolean(),
|
|
3219
|
+
okCount: import_zod3.z.number().int().min(0),
|
|
3220
|
+
redirectCount: import_zod3.z.number().int().min(0),
|
|
3221
|
+
brokenCount: import_zod3.z.number().int().min(0),
|
|
3222
|
+
urls: import_zod3.z.array(import_zod3.z.object({
|
|
3223
|
+
url: import_zod3.z.string(),
|
|
3224
|
+
status: import_zod3.z.number().int().nullable()
|
|
2711
3225
|
})),
|
|
2712
|
-
durationMs:
|
|
3226
|
+
durationMs: import_zod3.z.number().min(0)
|
|
2713
3227
|
};
|
|
2714
3228
|
var YoutubeHarvestOutputSchema = {
|
|
2715
|
-
mode:
|
|
2716
|
-
videoCount:
|
|
2717
|
-
channel:
|
|
2718
|
-
title:
|
|
2719
|
-
subscriberCount:
|
|
3229
|
+
mode: import_zod3.z.string(),
|
|
3230
|
+
videoCount: import_zod3.z.number().int().min(0),
|
|
3231
|
+
channel: import_zod3.z.object({
|
|
3232
|
+
title: NullableString2,
|
|
3233
|
+
subscriberCount: NullableString2
|
|
2720
3234
|
}).nullable(),
|
|
2721
|
-
videos:
|
|
2722
|
-
videoId:
|
|
2723
|
-
title:
|
|
2724
|
-
channelName:
|
|
2725
|
-
views:
|
|
2726
|
-
duration:
|
|
2727
|
-
url:
|
|
3235
|
+
videos: import_zod3.z.array(import_zod3.z.object({
|
|
3236
|
+
videoId: import_zod3.z.string(),
|
|
3237
|
+
title: import_zod3.z.string(),
|
|
3238
|
+
channelName: NullableString2,
|
|
3239
|
+
views: NullableString2,
|
|
3240
|
+
duration: NullableString2,
|
|
3241
|
+
url: NullableString2
|
|
2728
3242
|
}))
|
|
2729
3243
|
};
|
|
2730
3244
|
var FacebookAdSearchOutputSchema = {
|
|
2731
|
-
query:
|
|
2732
|
-
advertiserCount:
|
|
2733
|
-
advertisers:
|
|
2734
|
-
name:
|
|
2735
|
-
|
|
2736
|
-
|
|
3245
|
+
query: import_zod3.z.string(),
|
|
3246
|
+
advertiserCount: import_zod3.z.number().int().min(0),
|
|
3247
|
+
advertisers: import_zod3.z.array(import_zod3.z.object({
|
|
3248
|
+
name: NullableString2,
|
|
3249
|
+
pageId: NullableString2,
|
|
3250
|
+
pageUrl: NullableString2,
|
|
3251
|
+
adCount: import_zod3.z.number().int().nullable(),
|
|
3252
|
+
libraryId: NullableString2,
|
|
3253
|
+
sampleLibraryId: NullableString2
|
|
2737
3254
|
}))
|
|
2738
3255
|
};
|
|
2739
3256
|
var FacebookPageIntelOutputSchema = {
|
|
2740
|
-
advertiserName:
|
|
2741
|
-
totalAds:
|
|
2742
|
-
activeCount:
|
|
2743
|
-
videoCount:
|
|
2744
|
-
imageCount:
|
|
2745
|
-
ads:
|
|
2746
|
-
libraryId:
|
|
2747
|
-
status:
|
|
2748
|
-
creativeType:
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
3257
|
+
advertiserName: NullableString2,
|
|
3258
|
+
totalAds: import_zod3.z.number().int().min(0),
|
|
3259
|
+
activeCount: import_zod3.z.number().int().min(0),
|
|
3260
|
+
videoCount: import_zod3.z.number().int().min(0),
|
|
3261
|
+
imageCount: import_zod3.z.number().int().min(0),
|
|
3262
|
+
ads: import_zod3.z.array(import_zod3.z.object({
|
|
3263
|
+
libraryId: NullableString2,
|
|
3264
|
+
status: NullableString2,
|
|
3265
|
+
creativeType: NullableString2,
|
|
3266
|
+
primaryText: NullableString2,
|
|
3267
|
+
headline: NullableString2,
|
|
3268
|
+
cta: NullableString2,
|
|
3269
|
+
startDate: NullableString2,
|
|
3270
|
+
landingUrl: NullableString2,
|
|
3271
|
+
domain: NullableString2,
|
|
3272
|
+
videoUrl: NullableString2,
|
|
3273
|
+
imageUrl: NullableString2,
|
|
3274
|
+
videoPoster: NullableString2,
|
|
3275
|
+
variations: import_zod3.z.number().int().nullable()
|
|
2754
3276
|
}))
|
|
2755
3277
|
};
|
|
2756
3278
|
var FacebookVideoTranscribeOutputSchema = {
|
|
2757
|
-
sourceUrl:
|
|
2758
|
-
pageUrl:
|
|
2759
|
-
videoId:
|
|
2760
|
-
ownerName:
|
|
2761
|
-
selectedQuality:
|
|
2762
|
-
bitrate:
|
|
2763
|
-
videoDurationSec:
|
|
2764
|
-
videoUrl:
|
|
2765
|
-
wordCount:
|
|
2766
|
-
chunkCount:
|
|
2767
|
-
transcriptText:
|
|
2768
|
-
chunks:
|
|
2769
|
-
startSec:
|
|
2770
|
-
endSec:
|
|
2771
|
-
text:
|
|
3279
|
+
sourceUrl: import_zod3.z.string().url(),
|
|
3280
|
+
pageUrl: import_zod3.z.string().url(),
|
|
3281
|
+
videoId: NullableString2,
|
|
3282
|
+
ownerName: NullableString2,
|
|
3283
|
+
selectedQuality: import_zod3.z.string(),
|
|
3284
|
+
bitrate: import_zod3.z.number().int().nullable(),
|
|
3285
|
+
videoDurationSec: import_zod3.z.number().nullable(),
|
|
3286
|
+
videoUrl: import_zod3.z.string().url(),
|
|
3287
|
+
wordCount: import_zod3.z.number().int().min(0),
|
|
3288
|
+
chunkCount: import_zod3.z.number().int().min(0),
|
|
3289
|
+
transcriptText: import_zod3.z.string(),
|
|
3290
|
+
chunks: import_zod3.z.array(import_zod3.z.object({
|
|
3291
|
+
startSec: import_zod3.z.number(),
|
|
3292
|
+
endSec: import_zod3.z.number(),
|
|
3293
|
+
text: import_zod3.z.string()
|
|
2772
3294
|
}))
|
|
2773
3295
|
};
|
|
3296
|
+
var TranscriptChunkOutput = import_zod3.z.object({
|
|
3297
|
+
startSec: import_zod3.z.number(),
|
|
3298
|
+
endSec: import_zod3.z.number(),
|
|
3299
|
+
text: import_zod3.z.string()
|
|
3300
|
+
});
|
|
3301
|
+
var YoutubeTranscribeOutputSchema = {
|
|
3302
|
+
videoId: NullableString2,
|
|
3303
|
+
url: NullableString2,
|
|
3304
|
+
wordCount: import_zod3.z.number().int().min(0),
|
|
3305
|
+
chunkCount: import_zod3.z.number().int().min(0),
|
|
3306
|
+
durationMs: import_zod3.z.number().nullable(),
|
|
3307
|
+
transcriptText: import_zod3.z.string(),
|
|
3308
|
+
chunks: import_zod3.z.array(TranscriptChunkOutput),
|
|
3309
|
+
resolvedInputs: import_zod3.z.object({
|
|
3310
|
+
videoId: NullableString2,
|
|
3311
|
+
url: NullableString2
|
|
3312
|
+
})
|
|
3313
|
+
};
|
|
3314
|
+
var FacebookAdTranscribeOutputSchema = {
|
|
3315
|
+
videoUrl: import_zod3.z.string().url(),
|
|
3316
|
+
wordCount: import_zod3.z.number().int().min(0),
|
|
3317
|
+
chunkCount: import_zod3.z.number().int().min(0),
|
|
3318
|
+
durationMs: import_zod3.z.number().nullable(),
|
|
3319
|
+
transcriptText: import_zod3.z.string(),
|
|
3320
|
+
chunks: import_zod3.z.array(TranscriptChunkOutput),
|
|
3321
|
+
resolvedInputs: import_zod3.z.object({
|
|
3322
|
+
videoUrl: import_zod3.z.string().url()
|
|
3323
|
+
})
|
|
3324
|
+
};
|
|
3325
|
+
var CaptureSerpSnapshotOutputSchema = {
|
|
3326
|
+
schemaVersion: import_zod3.z.literal("serp-intelligence.capture.v1"),
|
|
3327
|
+
status: import_zod3.z.string(),
|
|
3328
|
+
query: NullableString2,
|
|
3329
|
+
location: NullableString2,
|
|
3330
|
+
capturedAt: NullableString2,
|
|
3331
|
+
resultCount: import_zod3.z.number().int().min(0).nullable(),
|
|
3332
|
+
snapshotId: NullableString2,
|
|
3333
|
+
resolvedInputs: import_zod3.z.record(import_zod3.z.unknown()),
|
|
3334
|
+
artifacts: import_zod3.z.array(import_zod3.z.record(import_zod3.z.unknown())),
|
|
3335
|
+
diagnostics: import_zod3.z.record(import_zod3.z.unknown()).nullable(),
|
|
3336
|
+
providerPayload: import_zod3.z.record(import_zod3.z.unknown())
|
|
3337
|
+
};
|
|
3338
|
+
var CaptureSerpPageSnapshotsOutputSchema = {
|
|
3339
|
+
schemaVersion: import_zod3.z.literal("serp-intelligence.page-snapshots.v1"),
|
|
3340
|
+
status: import_zod3.z.string(),
|
|
3341
|
+
count: import_zod3.z.number().int().min(0),
|
|
3342
|
+
failedCount: import_zod3.z.number().int().min(0),
|
|
3343
|
+
captures: import_zod3.z.array(import_zod3.z.record(import_zod3.z.unknown())),
|
|
3344
|
+
resolvedInputs: import_zod3.z.record(import_zod3.z.unknown()),
|
|
3345
|
+
diagnostics: import_zod3.z.record(import_zod3.z.unknown()).nullable(),
|
|
3346
|
+
providerPayload: import_zod3.z.record(import_zod3.z.unknown())
|
|
3347
|
+
};
|
|
2774
3348
|
var CreditsInfoInputSchema = {
|
|
2775
|
-
item:
|
|
2776
|
-
includeLedger:
|
|
3349
|
+
item: import_zod3.z.string().optional().describe('Optional tool, action, or feature to look up, e.g. "maps reviews", "extract_url", "YouTube transcription", or "concurrency"'),
|
|
3350
|
+
includeLedger: import_zod3.z.boolean().default(false).describe("Whether to include recent credit ledger entries")
|
|
2777
3351
|
};
|
|
2778
|
-
var WorkflowIdSchema =
|
|
3352
|
+
var WorkflowIdSchema = import_zod3.z.enum([
|
|
2779
3353
|
"directory",
|
|
2780
3354
|
"agent-packet",
|
|
2781
3355
|
"local-competitive-audit",
|
|
@@ -2785,114 +3359,129 @@ var WorkflowIdSchema = import_zod2.z.enum([
|
|
|
2785
3359
|
"ai-overview-language"
|
|
2786
3360
|
]);
|
|
2787
3361
|
var WorkflowListInputSchema = {
|
|
2788
|
-
includeRecipes:
|
|
3362
|
+
includeRecipes: import_zod3.z.boolean().default(true).describe("Include high-level AI-facing recipes such as market analysis, ICP research, forum/review research, brand design briefings, CRO audits, positioning briefs, content gaps, and AI search visibility audits.")
|
|
2789
3363
|
};
|
|
2790
3364
|
var WorkflowSuggestInputSchema = {
|
|
2791
|
-
goal:
|
|
2792
|
-
query:
|
|
2793
|
-
keyword:
|
|
2794
|
-
domain:
|
|
2795
|
-
url:
|
|
2796
|
-
location:
|
|
2797
|
-
state:
|
|
2798
|
-
maxSuggestions:
|
|
3365
|
+
goal: import_zod3.z.string().min(1).describe('The user goal or job to route, e.g. "market analysis for roofers in Tennessee", "ICP research for med spas", "CRO audit for this URL", or "brand design briefing".'),
|
|
3366
|
+
query: import_zod3.z.string().optional().describe("Business category, niche, or Maps query when known."),
|
|
3367
|
+
keyword: import_zod3.z.string().optional().describe("Search keyword, audience problem, or content topic when known."),
|
|
3368
|
+
domain: import_zod3.z.string().optional().describe("Target domain or brand domain when known."),
|
|
3369
|
+
url: import_zod3.z.string().url().optional().describe("Target URL when the workflow should inspect a specific page."),
|
|
3370
|
+
location: import_zod3.z.string().optional().describe("City/region/country for localized research, e.g. Denver, CO."),
|
|
3371
|
+
state: import_zod3.z.string().optional().describe("US state abbreviation or name for state-wide market research."),
|
|
3372
|
+
maxSuggestions: import_zod3.z.number().int().min(1).max(8).default(3).describe("Number of matching workflow recipes to return.")
|
|
2799
3373
|
};
|
|
2800
3374
|
var WorkflowRunInputSchema = {
|
|
2801
|
-
workflowId: WorkflowIdSchema.describe("Workflow to run. Use workflow_list or workflow_suggest first when unsure."),
|
|
2802
|
-
input:
|
|
2803
|
-
webhookUrl:
|
|
3375
|
+
workflowId: WorkflowIdSchema.describe("Workflow to run: directory, agent-packet, local-competitive-audit, map-comparison, serp-comparison, paa-expansion-brief, ai-overview-language. Use only these values; call workflow_list or workflow_suggest first when unsure."),
|
|
3376
|
+
input: import_zod3.z.record(import_zod3.z.unknown()).default({}).describe("Workflow-specific input object. Examples: agent-packet uses {keyword, domain?, location?, maxQuestions?}; local-competitive-audit uses {query, state, minPopulation?, maxCities?, maxResultsPerCity?, hydrateTop?, maxReviews?}; serp-comparison uses {keyword, domain?, url?, location?, extractTop?}."),
|
|
3377
|
+
webhookUrl: import_zod3.z.string().url().optional().describe("Optional HTTPS webhook to receive the completed hosted workflow run event.")
|
|
3378
|
+
};
|
|
3379
|
+
var WorkflowStepInputSchema = {
|
|
3380
|
+
runId: import_zod3.z.string().min(1).describe("Workflow run id returned by workflow_run, workflow_step, or workflow_status. Use only a returned runId; do not construct one yourself. Advances the run by exactly one step (one logical leg, e.g. one live harvest).")
|
|
2804
3381
|
};
|
|
2805
3382
|
var WorkflowStatusInputSchema = {
|
|
2806
|
-
runId:
|
|
3383
|
+
runId: import_zod3.z.string().min(1).describe("Workflow run id returned by workflow_run, workflow_step, or workflow_status. Use only a returned runId; do not construct one yourself.")
|
|
2807
3384
|
};
|
|
2808
3385
|
var WorkflowArtifactReadInputSchema = {
|
|
2809
|
-
runId:
|
|
2810
|
-
artifactId:
|
|
2811
|
-
maxBytes:
|
|
2812
|
-
};
|
|
2813
|
-
var WorkflowRecipeOutput =
|
|
2814
|
-
id:
|
|
2815
|
-
title:
|
|
2816
|
-
description:
|
|
2817
|
-
primaryWorkflowId:
|
|
2818
|
-
recommendedTools:
|
|
2819
|
-
requiredInputs:
|
|
2820
|
-
optionalInputs:
|
|
2821
|
-
produces:
|
|
2822
|
-
runHint:
|
|
3386
|
+
runId: import_zod3.z.string().min(1).describe("Workflow run id returned by workflow_run, workflow_step, or workflow_status. Use only a returned runId; do not construct one yourself."),
|
|
3387
|
+
artifactId: import_zod3.z.string().min(1).describe("Artifact id from the run artifact list returned by workflow_run, workflow_step, or workflow_status. Use only a returned artifactId; do not construct one yourself."),
|
|
3388
|
+
maxBytes: import_zod3.z.number().int().min(1e3).max(1e6).default(2e5).describe("Maximum bytes of artifact text to return inline. Use lower values for large CSV/JSON artifacts; call again with the downloadUrl if needed outside MCP.")
|
|
3389
|
+
};
|
|
3390
|
+
var WorkflowRecipeOutput = import_zod3.z.object({
|
|
3391
|
+
id: import_zod3.z.string(),
|
|
3392
|
+
title: import_zod3.z.string(),
|
|
3393
|
+
description: import_zod3.z.string(),
|
|
3394
|
+
primaryWorkflowId: import_zod3.z.string().nullable(),
|
|
3395
|
+
recommendedTools: import_zod3.z.array(import_zod3.z.string()),
|
|
3396
|
+
requiredInputs: import_zod3.z.array(import_zod3.z.string()),
|
|
3397
|
+
optionalInputs: import_zod3.z.array(import_zod3.z.string()),
|
|
3398
|
+
produces: import_zod3.z.array(import_zod3.z.string()),
|
|
3399
|
+
runHint: import_zod3.z.string()
|
|
2823
3400
|
});
|
|
2824
|
-
var WorkflowDefinitionOutput =
|
|
2825
|
-
id:
|
|
2826
|
-
title:
|
|
2827
|
-
description:
|
|
3401
|
+
var WorkflowDefinitionOutput = import_zod3.z.object({
|
|
3402
|
+
id: import_zod3.z.string(),
|
|
3403
|
+
title: import_zod3.z.string(),
|
|
3404
|
+
description: import_zod3.z.string()
|
|
2828
3405
|
});
|
|
2829
|
-
var WorkflowArtifactOutput =
|
|
3406
|
+
var WorkflowArtifactOutput = import_zod3.z.record(import_zod3.z.unknown());
|
|
2830
3407
|
var WorkflowListOutputSchema = {
|
|
2831
|
-
workflows:
|
|
2832
|
-
recipes:
|
|
3408
|
+
workflows: import_zod3.z.array(WorkflowDefinitionOutput),
|
|
3409
|
+
recipes: import_zod3.z.array(WorkflowRecipeOutput)
|
|
2833
3410
|
};
|
|
2834
3411
|
var WorkflowSuggestOutputSchema = {
|
|
2835
|
-
goal:
|
|
2836
|
-
suggestions:
|
|
3412
|
+
goal: import_zod3.z.string(),
|
|
3413
|
+
suggestions: import_zod3.z.array(WorkflowRecipeOutput)
|
|
2837
3414
|
};
|
|
2838
3415
|
var WorkflowRunOutputSchema = {
|
|
2839
|
-
workflowId:
|
|
2840
|
-
input:
|
|
2841
|
-
run:
|
|
2842
|
-
summary:
|
|
2843
|
-
|
|
3416
|
+
workflowId: import_zod3.z.string(),
|
|
3417
|
+
input: import_zod3.z.record(import_zod3.z.unknown()),
|
|
3418
|
+
run: import_zod3.z.record(import_zod3.z.unknown()).optional(),
|
|
3419
|
+
summary: import_zod3.z.record(import_zod3.z.unknown()).optional(),
|
|
3420
|
+
step: import_zod3.z.record(import_zod3.z.unknown()).optional(),
|
|
3421
|
+
nextStep: import_zod3.z.record(import_zod3.z.unknown()).nullable().optional(),
|
|
3422
|
+
done: import_zod3.z.boolean().optional(),
|
|
3423
|
+
artifacts: import_zod3.z.array(WorkflowArtifactOutput)
|
|
3424
|
+
};
|
|
3425
|
+
var WorkflowStepOutputSchema = {
|
|
3426
|
+
runId: import_zod3.z.string(),
|
|
3427
|
+
run: import_zod3.z.record(import_zod3.z.unknown()).optional(),
|
|
3428
|
+
summary: import_zod3.z.record(import_zod3.z.unknown()).nullable().optional(),
|
|
3429
|
+
step: import_zod3.z.record(import_zod3.z.unknown()).optional(),
|
|
3430
|
+
nextStep: import_zod3.z.record(import_zod3.z.unknown()).nullable().optional(),
|
|
3431
|
+
done: import_zod3.z.boolean(),
|
|
3432
|
+
artifacts: import_zod3.z.array(WorkflowArtifactOutput)
|
|
2844
3433
|
};
|
|
2845
3434
|
var WorkflowStatusOutputSchema = {
|
|
2846
|
-
run:
|
|
2847
|
-
artifacts:
|
|
3435
|
+
run: import_zod3.z.record(import_zod3.z.unknown()).optional(),
|
|
3436
|
+
artifacts: import_zod3.z.array(WorkflowArtifactOutput)
|
|
2848
3437
|
};
|
|
2849
3438
|
var WorkflowArtifactReadOutputSchema = {
|
|
2850
|
-
runId:
|
|
2851
|
-
artifactId:
|
|
2852
|
-
contentType:
|
|
2853
|
-
bytes:
|
|
2854
|
-
truncated:
|
|
2855
|
-
text:
|
|
3439
|
+
runId: import_zod3.z.string(),
|
|
3440
|
+
artifactId: import_zod3.z.string(),
|
|
3441
|
+
contentType: import_zod3.z.string(),
|
|
3442
|
+
bytes: import_zod3.z.number().int().min(0),
|
|
3443
|
+
truncated: import_zod3.z.boolean(),
|
|
3444
|
+
text: import_zod3.z.string()
|
|
2856
3445
|
};
|
|
2857
3446
|
var SearchSerpInputSchema = {
|
|
2858
|
-
query:
|
|
2859
|
-
location:
|
|
2860
|
-
gl:
|
|
2861
|
-
hl:
|
|
2862
|
-
device:
|
|
2863
|
-
proxyMode:
|
|
2864
|
-
proxyZip:
|
|
2865
|
-
debug:
|
|
2866
|
-
pages:
|
|
3447
|
+
query: import_zod3.z.string().min(1).describe('Core search topic only. Separate location when possible. If user says "best dentist in Brooklyn NY serp", use query="best dentist" and location="Brooklyn, NY".'),
|
|
3448
|
+
location: import_zod3.z.string().optional().describe("City, region, or country for geo-targeted results, inferred from user request when present."),
|
|
3449
|
+
gl: import_zod3.z.string().length(2).default("us").describe("Google country code inferred from location or user language."),
|
|
3450
|
+
hl: import_zod3.z.string().default("en").describe("Google interface/content language inferred from user request."),
|
|
3451
|
+
device: import_zod3.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
|
|
3452
|
+
proxyMode: import_zod3.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe("Proxy targeting mode. Default configured uses the service proxy without city/ZIP targeting for the highest general SERP success rate. Use location only when the user explicitly needs city/ZIP-targeted residential proxy evidence. Use none only for direct-network debugging."),
|
|
3453
|
+
proxyZip: import_zod3.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use only with proxyMode location when the user gives a specific ZIP or city-center targeting needs to be forced."),
|
|
3454
|
+
debug: import_zod3.z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior."),
|
|
3455
|
+
pages: import_zod3.z.number().int().min(1).max(2).default(1).describe("Number of result pages to fetch (1\u20132)")
|
|
2867
3456
|
};
|
|
2868
3457
|
var CaptureSerpSnapshotInputSchema = {
|
|
2869
|
-
query:
|
|
2870
|
-
location:
|
|
2871
|
-
gl:
|
|
2872
|
-
hl:
|
|
2873
|
-
device:
|
|
2874
|
-
proxyMode:
|
|
2875
|
-
proxyZip:
|
|
2876
|
-
pages:
|
|
2877
|
-
debug:
|
|
2878
|
-
includePageSnapshots:
|
|
2879
|
-
pageSnapshotLimit:
|
|
3458
|
+
query: import_zod3.z.string().min(1).describe("Core search query to capture as a structured SERP Intelligence snapshot. Separate the place into location when the user gives a city, region, country, or ZIP."),
|
|
3459
|
+
location: import_zod3.z.string().optional().describe("City, region, country, or service area used for localized Google results. MCP Scraper records location evidence; UULE alone is not proof of localization."),
|
|
3460
|
+
gl: import_zod3.z.string().length(2).default("us").describe("Google country code inferred from the requested market, e.g. us, gb, ca, au."),
|
|
3461
|
+
hl: import_zod3.z.string().default("en").describe("Google interface/content language inferred from the user request."),
|
|
3462
|
+
device: import_zod3.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only when the user asks for mobile rankings or mobile SERP evidence."),
|
|
3463
|
+
proxyMode: import_zod3.z.enum(["location", "configured", "none"]).default(DEFAULT_PROXY_MODE).describe("Proxy behavior for capture. Default configured uses the service proxy without city/ZIP targeting for the highest general capture success rate. Use location only when the user explicitly needs city/ZIP-targeted residential proxy evidence. Use none only for direct-network debugging."),
|
|
3464
|
+
proxyZip: import_zod3.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use only with proxyMode location when a precise city-center or ZIP proxy is needed."),
|
|
3465
|
+
pages: import_zod3.z.number().int().min(1).max(2).default(1).describe("Number of Google result pages to capture. Use 1 normally and 2 only when the user needs deeper ranking evidence."),
|
|
3466
|
+
debug: import_zod3.z.boolean().default(false).describe("Include sanitized browser, proxy, and location diagnostics. Use true when debugging localization, CAPTCHA, proxy selection, or capture reliability."),
|
|
3467
|
+
includePageSnapshots: import_zod3.z.boolean().default(false).describe("Also capture ranking-page snapshots for selected SERP URLs through the same product capture path."),
|
|
3468
|
+
pageSnapshotLimit: import_zod3.z.number().int().min(0).max(10).default(0).describe("Maximum ranking-page snapshots to capture when includePageSnapshots is true. Use 0 when only SERP evidence is needed.")
|
|
2880
3469
|
};
|
|
2881
3470
|
var ScreenshotInputSchema = {
|
|
2882
|
-
url:
|
|
2883
|
-
device:
|
|
2884
|
-
allowLocal:
|
|
3471
|
+
url: import_zod3.z.string().url().describe("URL to capture as a full-page screenshot. Use http or https. Pass allowLocal: true to capture localhost or private-network URLs during development."),
|
|
3472
|
+
device: import_zod3.z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport profile. desktop = 1440\xD7900. mobile = 390\xD7844. Use desktop by default; use mobile when the user asks for a mobile view."),
|
|
3473
|
+
allowLocal: import_zod3.z.boolean().default(false).describe("Allow localhost and private-network URLs (127.x, 192.168.x, 10.x, etc.). For local development only \u2014 not for production use.")
|
|
2885
3474
|
};
|
|
2886
3475
|
var CaptureSerpPageSnapshotsInputSchema = {
|
|
2887
|
-
urls:
|
|
2888
|
-
targets:
|
|
2889
|
-
url:
|
|
2890
|
-
sourceKind:
|
|
2891
|
-
sourcePosition:
|
|
3476
|
+
urls: import_zod3.z.array(import_zod3.z.string().url()).min(1).max(25).describe("Public HTTP/HTTPS URLs to capture as SERP Intelligence page snapshots. Do not pass localhost, private IPs, file URLs, or internal admin URLs."),
|
|
3477
|
+
targets: import_zod3.z.array(import_zod3.z.object({
|
|
3478
|
+
url: import_zod3.z.string().url().describe("Public HTTP/HTTPS URL to capture."),
|
|
3479
|
+
sourceKind: import_zod3.z.enum(["organic", "ai_citation", "local_pack_website", "configured_target", "site_subject"]).default("configured_target").describe("Why this page is being captured for SERP Intelligence evidence."),
|
|
3480
|
+
sourcePosition: import_zod3.z.number().int().min(1).optional().describe("Ranking or citation position when the page came from SERP evidence.")
|
|
2892
3481
|
}).strict()).min(1).max(25).optional().describe("Structured page snapshot targets. Use this instead of urls when source kind or position should be preserved."),
|
|
2893
|
-
maxConcurrency:
|
|
2894
|
-
timeoutMs:
|
|
2895
|
-
debug:
|
|
3482
|
+
maxConcurrency: import_zod3.z.number().int().min(1).max(5).default(2).describe("Parallel page captures. Use 2 normally; higher values can increase proxy/browser pressure."),
|
|
3483
|
+
timeoutMs: import_zod3.z.number().int().min(1e3).max(6e4).default(15e3).describe("Per-page capture timeout in milliseconds. Increase for slow pages; timeout artifacts are returned as structured capture failures."),
|
|
3484
|
+
debug: import_zod3.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics for page snapshot debugging. Use true for capture, network, or proxy troubleshooting.")
|
|
2896
3485
|
};
|
|
2897
3486
|
|
|
2898
3487
|
// src/mcp/rank-tracker-blueprint.ts
|
|
@@ -3278,70 +3867,72 @@ function registerPaaExtractorMcpTools(server2, executor, options = {}) {
|
|
|
3278
3867
|
if (savesReports) registerSavedReportResources(server2);
|
|
3279
3868
|
server2.registerTool("harvest_paa", {
|
|
3280
3869
|
title: "Google PAA + SERP Harvest",
|
|
3281
|
-
description: withReportNote('Best default tool for Google search research. Extracts People Also Ask questions plus answers/source URLs, organic SERP, local pack when present, entity IDs (CID/GCID/KG MID), and AI Overview. Infer the user language: split topic from location (e.g. "best hvac company in Denver CO" => query "best hvac company", location "Denver, CO", gl "us", hl "en").
|
|
3870
|
+
description: withReportNote('Best default tool for Google search research. Extracts People Also Ask questions plus answers/source URLs, organic SERP, local pack when present, entity IDs (CID/GCID/KG MID), and AI Overview. Infer the user language: split topic from location (e.g. "best hvac company in Denver CO" => query "best hvac company", location "Denver, CO", gl "us", hl "en"). Omit proxyMode for normal use; the service defaults to the configured browser-service proxy without city/ZIP targeting for the highest general success rate. Use proxyMode location only when the user explicitly needs city/ZIP-targeted residential proxy evidence. Use maxQuestions 30 normally, 100-200 for "full", "deep", "all", or comprehensive research. Deep harvests above 100 questions can run for several minutes with no interim progress \u2014 warn the user before starting one and keep maxQuestions at or below 100 unless they explicitly want a deep harvest. Credits are charged by extracted question; unused request hold is refunded.'),
|
|
3282
3871
|
inputSchema: HarvestPaaInputSchema,
|
|
3283
3872
|
outputSchema: HarvestPaaOutputSchema,
|
|
3284
3873
|
annotations: liveWebToolAnnotations("Google PAA + SERP Harvest")
|
|
3285
3874
|
}, async (input) => formatHarvestPaa(await executor.harvestPaa(input), input));
|
|
3286
3875
|
server2.registerTool("search_serp", {
|
|
3287
3876
|
title: "Google SERP Lookup",
|
|
3288
|
-
description: withReportNote("Fast Google SERP lookup without PAA expansion. Use when the user asks for rankings, organic results, local pack, quick SERP, or positions. Split topic from location and infer gl/hl from the user request.
|
|
3877
|
+
description: withReportNote("Fast Google SERP lookup without PAA expansion. Use when the user asks for rankings, organic results, local pack, quick SERP, or positions. Split topic from location and infer gl/hl from the user request. Omit proxyMode for normal use; the service defaults to the configured browser-service proxy without city/ZIP targeting. Use proxyMode location and proxyZip only when the user explicitly needs city/ZIP-targeted residential proxy evidence."),
|
|
3289
3878
|
inputSchema: SearchSerpInputSchema,
|
|
3290
3879
|
outputSchema: SearchSerpOutputSchema,
|
|
3291
3880
|
annotations: liveWebToolAnnotations("Google SERP Lookup")
|
|
3292
3881
|
}, async (input) => formatSearchSerp(await executor.searchSerp(input), input));
|
|
3293
3882
|
server2.registerTool("extract_url", {
|
|
3294
3883
|
title: "Single URL Extract",
|
|
3295
|
-
description: withReportNote("Extract structured data from one public URL
|
|
3884
|
+
description: withReportNote("Extract structured data from one public URL when the user provides one page, asks to inspect/scrape a page, or needs page content, schema, headings, metadata, screenshots, branding, or media assets. Returns structured page fields plus artifact handles for saved reports/screenshots/media when requested. Use map_site_urls before extracting a site inventory; use extract_site for multi-page crawling."),
|
|
3296
3885
|
inputSchema: ExtractUrlInputSchema,
|
|
3297
3886
|
outputSchema: ExtractUrlOutputSchema,
|
|
3298
3887
|
annotations: liveWebToolAnnotations("Single URL Extract")
|
|
3299
3888
|
}, async (input) => formatExtractUrl(await executor.extractUrl(input), input));
|
|
3300
3889
|
server2.registerTool("map_site_urls", {
|
|
3301
3890
|
title: "Site URL Map",
|
|
3302
|
-
description: withReportNote("Map/crawl a public website
|
|
3891
|
+
description: withReportNote("Map/crawl a public website when the user asks for a sitemap, URL inventory, broken-link scan, redirect scan, or crawl planning. Returns internal URLs with HTTP status and truncation metadata. Use this before extract_site when choosing pages for an audit; use extract_url for one known page."),
|
|
3303
3892
|
inputSchema: MapSiteUrlsInputSchema,
|
|
3304
3893
|
outputSchema: MapSiteUrlsOutputSchema,
|
|
3305
3894
|
annotations: liveWebToolAnnotations("Site URL Map")
|
|
3306
3895
|
}, async (input) => formatMapSiteUrls(await executor.mapSiteUrls(input), input));
|
|
3307
3896
|
server2.registerTool("extract_site", {
|
|
3308
3897
|
title: "Multi-Page Site Extract",
|
|
3309
|
-
description: withReportNote("Run multi-page extraction across a public website. Returns per-page titles, H1s, metadata, headings, schema/entity data, canonical URLs, and content. Use
|
|
3898
|
+
description: withReportNote("Run multi-page extraction across a public website when the user asks for a website audit, competitor audit, full-site content crawl, schema inventory, or page metadata review. Returns per-page titles, H1s, metadata, headings, schema/entity data, canonical URLs, and content. Use map_site_urls first when URL selection matters; use extract_url for one page."),
|
|
3310
3899
|
inputSchema: ExtractSiteInputSchema,
|
|
3311
3900
|
outputSchema: ExtractSiteOutputSchema,
|
|
3312
3901
|
annotations: liveWebToolAnnotations("Multi-Page Site Extract")
|
|
3313
3902
|
}, async (input) => formatExtractSite(await executor.extractSite(input), input));
|
|
3314
3903
|
server2.registerTool("youtube_harvest", {
|
|
3315
3904
|
title: "YouTube Video Harvest",
|
|
3316
|
-
description: withReportNote('Harvest YouTube video metadata by
|
|
3905
|
+
description: withReportNote('Harvest YouTube video metadata when the user wants to find videos by topic, inspect a channel library, compare video angles, or get videoIds for later transcription. Use mode "search" for keyword/topic requests and mode "channel" for @handles, channel IDs, or channel URLs. Returns titles, views, durations, URLs, and videoIds for follow-up transcription. Use youtube_transcribe after selecting one video.'),
|
|
3317
3906
|
inputSchema: YoutubeHarvestInputSchema,
|
|
3318
3907
|
outputSchema: YoutubeHarvestOutputSchema,
|
|
3319
3908
|
annotations: liveWebToolAnnotations("YouTube Video Harvest")
|
|
3320
3909
|
}, async (input) => formatYoutubeHarvest(await executor.youtubeHarvest(input), input));
|
|
3321
3910
|
server2.registerTool("youtube_transcribe", {
|
|
3322
3911
|
title: "YouTube Transcription",
|
|
3323
|
-
description: withReportNote("Fetch and transcribe captions from a YouTube video. Returns full transcript, timestamped chunks,
|
|
3912
|
+
description: withReportNote("Fetch and transcribe captions from a YouTube video. Use this when the user asks what was said in a YouTube video, wants claims/offers/lessons extracted from a video, or provides a YouTube URL for transcript work. Returns full transcript, timestamped chunks, word count, and resolvedInputs. Pass videoId from youtube_harvest results, or pass url when the user pasted a YouTube URL. Use youtube_harvest first when you need to discover videos by topic/channel."),
|
|
3324
3913
|
inputSchema: YoutubeTranscribeInputSchema,
|
|
3914
|
+
outputSchema: YoutubeTranscribeOutputSchema,
|
|
3325
3915
|
annotations: liveWebToolAnnotations("YouTube Transcription")
|
|
3326
3916
|
}, async (input) => formatYoutubeTranscribe(await executor.youtubeTranscribe(input), input));
|
|
3327
3917
|
server2.registerTool("facebook_page_intel", {
|
|
3328
3918
|
title: "Facebook Advertiser Ad Intel",
|
|
3329
|
-
description: withReportNote("Harvest ads from a Facebook advertiser. Returns ad copy, headlines, CTAs, creative type, status, landing URLs, and direct ad video URLs ready for facebook_ad_transcribe. Accepts pageId, libraryId, or a brand/advertiser name as query. Use
|
|
3919
|
+
description: withReportNote("Harvest ads from a Facebook advertiser when the user wants current ad copy, creative angles, CTAs, video URLs, or competitive ad intelligence for one brand/page. Returns ad copy, headlines, CTAs, creative type, status, landing URLs, and direct ad video URLs ready for facebook_ad_transcribe. Accepts pageId, libraryId, or a brand/advertiser name as query. Use facebook_ad_search first when the advertiser handle is unknown. For normal public Facebook reels/posts/watch/share URLs, use facebook_video_transcribe instead."),
|
|
3330
3920
|
inputSchema: FacebookPageIntelInputSchema,
|
|
3331
3921
|
outputSchema: FacebookPageIntelOutputSchema,
|
|
3332
3922
|
annotations: liveWebToolAnnotations("Facebook Advertiser Ad Intel")
|
|
3333
3923
|
}, async (input) => formatFacebookPageIntel(await executor.facebookPageIntel(input), input));
|
|
3334
3924
|
server2.registerTool("facebook_ad_search", {
|
|
3335
3925
|
title: "Facebook Ad Library Search",
|
|
3336
|
-
description: withReportNote("Search Facebook Ad Library by brand,
|
|
3926
|
+
description: withReportNote("Search Facebook Ad Library when the user wants to find advertisers by brand, competitor, niche, or keyword. Returns advertisers with ad counts and library IDs. Use this to discover competitor/page handles, then pass libraryId or pageId to facebook_page_intel for ad details."),
|
|
3337
3927
|
inputSchema: FacebookAdSearchInputSchema,
|
|
3338
3928
|
outputSchema: FacebookAdSearchOutputSchema,
|
|
3339
3929
|
annotations: liveWebToolAnnotations("Facebook Ad Library Search")
|
|
3340
3930
|
}, async (input) => formatFacebookAdSearch(await executor.facebookAdSearch(input), input));
|
|
3341
3931
|
server2.registerTool("facebook_ad_transcribe", {
|
|
3342
3932
|
title: "Facebook Ad Transcription",
|
|
3343
|
-
description: "Transcribe audio from a Facebook ad video CDN URL. Returns full transcript
|
|
3933
|
+
description: "Transcribe audio from a Facebook ad video CDN URL. Use this when facebook_page_intel returned a direct videoUrl and the user asks what the ad says, what claims it makes, or wants ad-message extraction. Returns full transcript, timestamped chunks, word count, and resolvedInputs. Use only with the direct videoUrl value from facebook_page_intel results; do not pass public Facebook post/reel/share URLs. Use facebook_video_transcribe for organic Facebook URLs, and facebook_page_intel first when you only have a brand/page/ad library handle.",
|
|
3344
3934
|
inputSchema: FacebookAdTranscribeInputSchema,
|
|
3935
|
+
outputSchema: FacebookAdTranscribeOutputSchema,
|
|
3345
3936
|
annotations: liveWebToolAnnotations("Facebook Ad Transcription")
|
|
3346
3937
|
}, async (input) => formatFacebookAdTranscribe(await executor.facebookAdTranscribe(input), input));
|
|
3347
3938
|
server2.registerTool("facebook_video_transcribe", {
|
|
@@ -3360,14 +3951,14 @@ function registerPaaExtractorMcpTools(server2, executor, options = {}) {
|
|
|
3360
3951
|
}, async (input) => formatMapsPlaceIntel(await executor.mapsPlaceIntel(input), input));
|
|
3361
3952
|
server2.registerTool("maps_search", {
|
|
3362
3953
|
title: "Google Maps Business Search",
|
|
3363
|
-
description: withReportNote('Search Google Maps for multiple businesses/profiles by category, niche, keyword, or local market. Use this when the user asks for several Google Business Profiles, GMBs, GBPs, leads, prospects, competitors, or "more than the 3-pack."
|
|
3954
|
+
description: withReportNote('Search Google Maps for multiple businesses/profiles by category, niche, keyword, or local market. Use this when the user asks for several Google Business Profiles, GMBs, GBPs, leads, prospects, competitors, or "more than the 3-pack." Maps is the default location-targeted surface: omit proxyMode for normal Maps use so the service creates residential proxy evidence for the requested market and rotates on retryable failures. Pass proxyZip only when a specific ZIP or city-center ZIP is known. Use proxyMode configured only when you explicitly do not want city/ZIP proxy targeting. Returns up to 50 candidates with names, place URLs, CIDs when available, ratings, review counts, profile metadata, and sanitized attempt telemetry. Default maxResults is 10; maximum is 50. Use maps_place_intel afterward only when a selected business needs full details and reviews.'),
|
|
3364
3955
|
inputSchema: MapsSearchInputSchema,
|
|
3365
3956
|
outputSchema: MapsSearchOutputSchema,
|
|
3366
3957
|
annotations: liveWebToolAnnotations("Google Maps Business Search")
|
|
3367
3958
|
}, async (input) => formatMapsSearch(await executor.mapsSearch(input), input));
|
|
3368
3959
|
server2.registerTool("directory_workflow", {
|
|
3369
3960
|
title: "Directory Workflow: Markets + Maps",
|
|
3370
|
-
description: withReportNote('Build directory/prospecting datasets by selecting US city markets from the free Census Population Estimates city/place dataset, optionally joining configured US ZIPS/Lead Magician ZIP groups, then running Google Maps business searches for each city in parallel. Use this when the user wants "all cities over 100k population in a state", "build a directory CSV", "find markets then get Maps data", or similar location-database + Maps workflows. Set minPopulation, state, query, maxResultsPerCity, and concurrency.
|
|
3961
|
+
description: withReportNote('Build directory/prospecting datasets by selecting US city markets from the free Census Population Estimates city/place dataset, optionally joining configured US ZIPS/Lead Magician ZIP groups, then running Google Maps business searches for each city in parallel. Use this when the user wants "all cities over 100k population in a state", "build a directory CSV", "find markets then get Maps data", or similar location-database + Maps workflows. Set minPopulation, state, query, maxResultsPerCity, and concurrency. Maps workflows default to location-targeted proxying so each city can use city/state or ZIP-group residential proxy evidence; use proxyMode configured only when you explicitly do not want city/ZIP proxy targeting. Saved CSV rows include source_location, result_position, business_name, review_stars, review_count, category, address, phone, hours_status, website_url, directions_url, place_url, cid, cid_decimal, city population, and ZIP groups. Structured city results include sanitized attempt telemetry. Use maps_place_intel only when a selected profile needs deeper review topics, profile review count confirmation, or review cards. For local Lead Magician ZIP enrichment, set MCP_SCRAPER_USZIPS_CSV_PATH on the API server or pass usZipsCsvPath only in local/test mode.'),
|
|
3371
3962
|
inputSchema: DirectoryWorkflowInputSchema,
|
|
3372
3963
|
outputSchema: DirectoryWorkflowOutputSchema,
|
|
3373
3964
|
annotations: liveWebToolAnnotations("Directory Workflow: Markets + Maps")
|
|
@@ -3388,21 +3979,28 @@ function registerPaaExtractorMcpTools(server2, executor, options = {}) {
|
|
|
3388
3979
|
}, async (input) => formatWorkflowSuggest(input));
|
|
3389
3980
|
server2.registerTool("workflow_run", {
|
|
3390
3981
|
title: "Run Workflow",
|
|
3391
|
-
description: withReportNote("
|
|
3982
|
+
description: withReportNote("Start a higher-level MCP Scraper workflow. Use after workflow_suggest or workflow_list. Runnable workflow ids: directory, agent-packet, local-competitive-audit, map-comparison, serp-comparison, paa-expansion-brief, ai-overview-language. This is the main MCP tool for market analysis, ICP evidence packets, local competitive audits, Maps/SERP comparisons, content gap briefs, and AI Overview language guidance. Stepwise workflows (e.g. agent-packet) run ONE leg per call and return runId, the step output, and nextStep \u2014 when nextStep is present, call workflow_step with the runId to run the next leg, and keep calling it until done is true. This keeps each call short instead of one long blocking run, so report each step result to the user as it arrives."),
|
|
3392
3983
|
inputSchema: WorkflowRunInputSchema,
|
|
3393
3984
|
outputSchema: WorkflowRunOutputSchema,
|
|
3394
3985
|
annotations: liveWebToolAnnotations("Run Workflow")
|
|
3395
3986
|
}, async (input) => formatWorkflowRun(await executor.workflowRun(input), input));
|
|
3987
|
+
server2.registerTool("workflow_step", {
|
|
3988
|
+
title: "Advance Workflow Step",
|
|
3989
|
+
description: withReportNote("Run the next leg of a stepwise MCP Scraper workflow started with workflow_run. Pass the runId. Each call executes exactly one logical step (typically one live harvest), persists that step's artifacts, and returns the step output plus nextStep. Keep calling workflow_step with the same runId until done is true, reporting each step result to the user as it lands. Use this instead of waiting on one long workflow call \u2014 it avoids client timeouts on long multi-step jobs."),
|
|
3990
|
+
inputSchema: WorkflowStepInputSchema,
|
|
3991
|
+
outputSchema: WorkflowStepOutputSchema,
|
|
3992
|
+
annotations: liveWebToolAnnotations("Advance Workflow Step")
|
|
3993
|
+
}, async (input) => formatWorkflowStep(await executor.workflowStep(input), input));
|
|
3396
3994
|
server2.registerTool("workflow_status", {
|
|
3397
3995
|
title: "Workflow Status",
|
|
3398
|
-
description: "Fetch a hosted workflow run by id and list its current status and artifacts. Use
|
|
3996
|
+
description: "Fetch a hosted workflow run by id and list its current status and artifacts. Use when a workflow may still be running, when the model needs to re-open a run, inspect artifact ids, recover from a long workflow, or decide whether to call workflow_step or workflow_artifact_read next. Use only a runId returned by workflow_run/workflow_step/workflow_status; do not construct one yourself.",
|
|
3399
3997
|
inputSchema: WorkflowStatusInputSchema,
|
|
3400
3998
|
outputSchema: WorkflowStatusOutputSchema,
|
|
3401
3999
|
annotations: liveWebToolAnnotations("Workflow Status")
|
|
3402
4000
|
}, async (input) => formatWorkflowStatus(await executor.workflowStatus(input), input));
|
|
3403
4001
|
server2.registerTool("workflow_artifact_read", {
|
|
3404
4002
|
title: "Read Workflow Artifact",
|
|
3405
|
-
description: "Read a workflow artifact back into MCP context by run id and artifact id. Use this before writing final deliverables so the answer is grounded in generated evidence.json, CSVs, Markdown briefs, reports, and task files instead of memory. Use maxBytes to limit large CSV/JSON artifacts.",
|
|
4003
|
+
description: "Read a workflow artifact back into MCP context by run id and artifact id. Use this before writing final deliverables so the answer is grounded in generated evidence.json, CSVs, Markdown briefs, reports, and task files instead of memory. Use workflow_status first when artifact ids are unknown. Use only artifactId values returned by workflow_run/workflow_step/workflow_status; do not construct one yourself. Use maxBytes to limit large CSV/JSON artifacts.",
|
|
3406
4004
|
inputSchema: WorkflowArtifactReadInputSchema,
|
|
3407
4005
|
outputSchema: WorkflowArtifactReadOutputSchema,
|
|
3408
4006
|
annotations: liveWebToolAnnotations("Read Workflow Artifact")
|
|
@@ -3482,7 +4080,7 @@ function renderInstallTerminal(options) {
|
|
|
3482
4080
|
"1/1 install surfaces ready",
|
|
3483
4081
|
colorize("Newest: MCP workflow tools for market analysis, ICP research, CRO audits, brand briefs, and AI search visibility.", "lime", color),
|
|
3484
4082
|
"",
|
|
3485
|
-
`${colorize("Tools", "cyan", color)} ${colorize("(
|
|
4083
|
+
`${colorize("Tools", "cyan", color)} ${colorize("(39 MCP tools)", "muted", color)}`,
|
|
3486
4084
|
toolRow("search", ["harvest_paa", "search_serp", "maps_search", "maps_place_intel"], color),
|
|
3487
4085
|
toolRow("extract", ["extract_url", "map_site_urls", "extract_site", "directory_workflow"], color),
|
|
3488
4086
|
toolRow("build", ["rank_tracker_blueprint", "cron plan", "database prompt"], color),
|
|
@@ -3490,7 +4088,7 @@ function renderInstallTerminal(options) {
|
|
|
3490
4088
|
toolRow("browser", ["browser_open", "browser_screenshot", "browser_read", "browser_locate", "browser_replay_mark", "browser_replay_annotate"], color),
|
|
3491
4089
|
toolRow("account", ["credits_info", "local reports", "MCP resources"], color),
|
|
3492
4090
|
`${colorize("Workflows", "cyan", color)} ${colorize("(MCP + CLI + API)", "muted", color)}`,
|
|
3493
|
-
toolRow("route", ["workflow_list", "workflow_suggest", "workflow_run", "workflow_status", "workflow_artifact_read"], color),
|
|
4091
|
+
toolRow("route", ["workflow_list", "workflow_suggest", "workflow_run", "workflow_step", "workflow_status", "workflow_artifact_read"], color),
|
|
3494
4092
|
toolRow("seo", ["directory", "agent-packet", "local audit", "map/serp comparison", "PAA/AIO briefs", "scheduled runs"], color),
|
|
3495
4093
|
"",
|
|
3496
4094
|
colorize("Usage tips:", "amber", color),
|