mcp-scraper 0.2.16 → 0.2.18
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 +12 -5
- package/dist/bin/api-server.cjs +479 -7
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +1 -1
- package/dist/bin/browser-agent-stdio-server.cjs +1 -1
- package/dist/bin/browser-agent-stdio-server.cjs.map +1 -1
- package/dist/bin/browser-agent-stdio-server.js +2 -2
- package/dist/bin/mcp-scraper-cli.cjs +1 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +1 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs +466 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-scraper-combined-stdio-server.js +3 -3
- package/dist/bin/mcp-scraper-install.cjs +9 -7
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +9 -7
- package/dist/bin/mcp-scraper-install.js.map +1 -1
- package/dist/bin/mcp-stdio-server.cjs +466 -1
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +2 -2
- package/dist/chunk-M5SAUO4K.js +7 -0
- package/dist/chunk-M5SAUO4K.js.map +1 -0
- package/dist/{chunk-ATJAINML.js → chunk-P5PLQU3H.js} +2 -2
- package/dist/{chunk-FMC3V54I.js → chunk-RJMCASQH.js} +469 -2
- package/dist/chunk-RJMCASQH.js.map +1 -0
- package/dist/{server-KSEQLZNP.js → server-VVY5K44O.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-FMC3V54I.js.map +0 -1
- package/dist/chunk-KE4JZDLV.js +0 -7
- package/dist/chunk-KE4JZDLV.js.map +0 -1
- /package/dist/{chunk-ATJAINML.js.map → chunk-P5PLQU3H.js.map} +0 -0
- /package/dist/{server-KSEQLZNP.js.map → server-VVY5K44O.js.map} +0 -0
|
@@ -75,6 +75,57 @@ var HttpMcpToolExecutor = class {
|
|
|
75
75
|
return { content: [{ type: "text", text: msg }], isError: true };
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
+
async getJson(path, timeoutMs = this.timeoutMs) {
|
|
79
|
+
try {
|
|
80
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
81
|
+
method: "GET",
|
|
82
|
+
headers: {
|
|
83
|
+
"x-api-key": this.apiKey
|
|
84
|
+
},
|
|
85
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
86
|
+
});
|
|
87
|
+
const data = await res.json();
|
|
88
|
+
if (!res.ok) {
|
|
89
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }], isError: true };
|
|
90
|
+
}
|
|
91
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
92
|
+
} catch (err) {
|
|
93
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
94
|
+
return { content: [{ type: "text", text: msg }], isError: true };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async getTextArtifact(path, maxBytes, timeoutMs = this.timeoutMs) {
|
|
98
|
+
try {
|
|
99
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
100
|
+
method: "GET",
|
|
101
|
+
headers: {
|
|
102
|
+
"x-api-key": this.apiKey
|
|
103
|
+
},
|
|
104
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
105
|
+
});
|
|
106
|
+
if (!res.ok) {
|
|
107
|
+
const data = await res.json().catch(async () => ({ error: await res.text().catch(() => `HTTP ${res.status}`) }));
|
|
108
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }], isError: true };
|
|
109
|
+
}
|
|
110
|
+
const bytes = Buffer.from(await res.arrayBuffer());
|
|
111
|
+
const sliced = bytes.subarray(0, Math.min(maxBytes, bytes.length));
|
|
112
|
+
return {
|
|
113
|
+
content: [{
|
|
114
|
+
type: "text",
|
|
115
|
+
text: JSON.stringify({
|
|
116
|
+
contentType: res.headers.get("content-type") ?? "application/octet-stream",
|
|
117
|
+
bytes: bytes.length,
|
|
118
|
+
truncated: sliced.length < bytes.length,
|
|
119
|
+
maxBytes,
|
|
120
|
+
text: sliced.toString("utf8")
|
|
121
|
+
})
|
|
122
|
+
}]
|
|
123
|
+
};
|
|
124
|
+
} catch (err) {
|
|
125
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
126
|
+
return { content: [{ type: "text", text: msg }], isError: true };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
78
129
|
harvestPaa(input) {
|
|
79
130
|
const timeoutMs = this.httpTimeoutOverrideMs ?? harvestTimeoutBudget(input.maxQuestions ?? 30).clientMs;
|
|
80
131
|
return this.call("/harvest/sync", input, timeoutMs);
|
|
@@ -122,6 +173,26 @@ var HttpMcpToolExecutor = class {
|
|
|
122
173
|
const timeoutMs = this.httpTimeoutOverrideMs ?? Math.min(9e5, Math.max(18e4, Math.ceil(cityCount / concurrency) * 12e4));
|
|
123
174
|
return this.call("/directory/run", input, timeoutMs);
|
|
124
175
|
}
|
|
176
|
+
workflowList(_input) {
|
|
177
|
+
return this.getJson("/workflows/definitions");
|
|
178
|
+
}
|
|
179
|
+
workflowRun(input) {
|
|
180
|
+
const timeoutMs = this.httpTimeoutOverrideMs ?? Number(process.env.MCP_SCRAPER_WORKFLOW_TIMEOUT_MS ?? 9e5);
|
|
181
|
+
return this.call("/workflows/run", {
|
|
182
|
+
workflowId: input.workflowId,
|
|
183
|
+
input: input.input ?? {},
|
|
184
|
+
webhookUrl: input.webhookUrl
|
|
185
|
+
}, Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 9e5);
|
|
186
|
+
}
|
|
187
|
+
workflowStatus(input) {
|
|
188
|
+
return this.getJson(`/workflows/runs/${encodeURIComponent(input.runId)}`);
|
|
189
|
+
}
|
|
190
|
+
workflowArtifactRead(input) {
|
|
191
|
+
return this.getTextArtifact(
|
|
192
|
+
`/workflows/runs/${encodeURIComponent(input.runId)}/artifacts/${encodeURIComponent(input.artifactId)}`,
|
|
193
|
+
input.maxBytes ?? 2e5
|
|
194
|
+
);
|
|
195
|
+
}
|
|
125
196
|
creditsInfo(input) {
|
|
126
197
|
return this.call("/billing/credits", input);
|
|
127
198
|
}
|
|
@@ -139,7 +210,7 @@ var import_node_fs2 = require("fs");
|
|
|
139
210
|
var import_node_path2 = require("path");
|
|
140
211
|
|
|
141
212
|
// src/version.ts
|
|
142
|
-
var PACKAGE_VERSION = "0.2.
|
|
213
|
+
var PACKAGE_VERSION = "0.2.18";
|
|
143
214
|
|
|
144
215
|
// src/mcp/mcp-response-formatter.ts
|
|
145
216
|
var import_node_fs = require("fs");
|
|
@@ -151,6 +222,121 @@ function sanitizeVendorName(message) {
|
|
|
151
222
|
return message.replace(/kernel\.sh\s+sessions?/gi, "sessions").replace(/kernel\.sh\s+session/gi, "this session").replace(/kernel\.sh/gi, "the service").replace(/kernel\s+sessions?/gi, "sessions").replace(/kernel\s+session/gi, "this session").replace(/\bkernel\b/gi, "the service").replace(/ +/g, " ").trim();
|
|
152
223
|
}
|
|
153
224
|
|
|
225
|
+
// src/mcp/workflow-catalog.ts
|
|
226
|
+
var WORKFLOW_RECIPES = [
|
|
227
|
+
{
|
|
228
|
+
id: "market_analysis",
|
|
229
|
+
title: "Market Analysis",
|
|
230
|
+
description: "Compare a niche across a city, state, or market set using Google Maps competitors, SERP evidence, review counts, categories, and opportunity signals.",
|
|
231
|
+
primaryWorkflowId: "local-competitive-audit",
|
|
232
|
+
recommendedTools: ["workflow_run", "directory_workflow", "maps_search", "maps_place_intel", "search_serp", "harvest_paa"],
|
|
233
|
+
requiredInputs: ["query", "state or location"],
|
|
234
|
+
optionalInputs: ["domain", "minPopulation", "maxCities", "maxResultsPerCity", "hydrateTop", "maxReviews"],
|
|
235
|
+
produces: ["city summary", "competitor CSV", "review insight CSV", "market difficulty/opportunity notes", "HTML report"],
|
|
236
|
+
runHint: "Use workflow_run with workflowId local-competitive-audit for state-wide market batches, or map-comparison for one city/location comparison."
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
id: "icp_research",
|
|
240
|
+
title: "ICP Research",
|
|
241
|
+
description: "Build an evidence packet for ideal-customer-profile research from real search demand, PAA language, competing pages, AI Overview citations, and source domains.",
|
|
242
|
+
primaryWorkflowId: "agent-packet",
|
|
243
|
+
recommendedTools: ["workflow_run", "harvest_paa", "search_serp", "extract_url", "youtube_harvest", "facebook_ad_search"],
|
|
244
|
+
requiredInputs: ["keyword or audience problem"],
|
|
245
|
+
optionalInputs: ["domain", "location", "maxQuestions"],
|
|
246
|
+
produces: ["evidence JSON", "sources CSV", "competitor CSV", "brief markdown", "agent task list"],
|
|
247
|
+
runHint: "Use workflow_run with workflowId agent-packet. Treat keyword as the buyer problem, job-to-be-done, niche, or service category."
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
id: "forum_review_research",
|
|
251
|
+
title: "Forum And Review Research",
|
|
252
|
+
description: "Acquire customer language from Google PAA, forum/Perspectives SERP surfaces, Google Maps reviews, review topics, Facebook ads, and video transcripts.",
|
|
253
|
+
primaryWorkflowId: "local-competitive-audit",
|
|
254
|
+
recommendedTools: ["workflow_run", "harvest_paa", "maps_search", "maps_place_intel", "facebook_page_intel", "facebook_video_transcribe", "youtube_transcribe"],
|
|
255
|
+
requiredInputs: ["query", "state or location"],
|
|
256
|
+
optionalInputs: ["maxReviews", "maxQuestions", "competitors", "facebookUrl", "youtubeVideoId"],
|
|
257
|
+
produces: ["review insight CSV", "PAA/source language", "competitor review topics", "transcripts where supplied", "pain/theme summary"],
|
|
258
|
+
runHint: "Use local-competitive-audit for review acquisition, then harvest_paa for forum/Perspectives language and transcript tools for supplied videos."
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
id: "brand_design_brief",
|
|
262
|
+
title: "Brand Design Brief",
|
|
263
|
+
description: "Create a design briefing grounded in a brand site, competitor pages, SERP language, audience pains, visual assets, colors, fonts, screenshots, and positioning evidence.",
|
|
264
|
+
primaryWorkflowId: "serp-comparison",
|
|
265
|
+
recommendedTools: ["workflow_run", "extract_url", "extract_site", "capture_serp_page_snapshots", "search_serp", "harvest_paa", "browser_open"],
|
|
266
|
+
requiredInputs: ["url or domain", "keyword or market category"],
|
|
267
|
+
optionalInputs: ["competitorUrls", "location", "extractTop"],
|
|
268
|
+
produces: ["page comparison CSV", "content gaps", "branding/asset evidence", "SERP positioning evidence", "designer-ready brief"],
|
|
269
|
+
runHint: "Use workflow_run with workflowId serp-comparison for market/page evidence, then extract_url with extractBranding:true for brand assets and colors."
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
id: "cro_audit",
|
|
273
|
+
title: "CRO Audit",
|
|
274
|
+
description: "Audit conversion clarity using page extraction, screenshots, browser DOM inspection, competitor SERP/page comparisons, forms/buttons, proof, offer, trust, and friction evidence.",
|
|
275
|
+
primaryWorkflowId: "serp-comparison",
|
|
276
|
+
recommendedTools: ["workflow_run", "extract_url", "extract_site", "capture_serp_page_snapshots", "browser_open", "browser_screenshot", "browser_locate"],
|
|
277
|
+
requiredInputs: ["url"],
|
|
278
|
+
optionalInputs: ["keyword", "domain", "location", "competitorUrls"],
|
|
279
|
+
produces: ["page extraction", "SERP/page comparison", "screenshot evidence", "friction checklist", "CRO recommendations"],
|
|
280
|
+
runHint: "Use workflow_run with workflowId serp-comparison when a keyword/domain is known; use extract_url and browser tools for page-level CRO evidence."
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
id: "competitive_positioning_brief",
|
|
284
|
+
title: "Competitive Positioning Brief",
|
|
285
|
+
description: "Compare competitors across SERP, PAA, AI Overview citations, page headings, Facebook ads, YouTube angles, Maps proof, and website claims.",
|
|
286
|
+
primaryWorkflowId: "serp-comparison",
|
|
287
|
+
recommendedTools: ["workflow_run", "facebook_ad_search", "facebook_page_intel", "youtube_harvest", "youtube_transcribe", "maps_search", "extract_url"],
|
|
288
|
+
requiredInputs: ["keyword or query", "domain or url"],
|
|
289
|
+
optionalInputs: ["location", "extractTop", "competitorUrls", "brandNames"],
|
|
290
|
+
produces: ["SERP comparison", "content gap CSV", "AI Overview citation table", "competitor message map", "positioning brief"],
|
|
291
|
+
runHint: "Use workflow_run with workflowId serp-comparison, then enrich with Facebook, YouTube, and Maps tools when the user asks for campaign or offer positioning."
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
id: "content_gap_brief",
|
|
295
|
+
title: "Content Gap Brief",
|
|
296
|
+
description: "Turn live SERP, page extraction, PAA questions, AI Overview citations, and source-domain evidence into a writer-ready content brief.",
|
|
297
|
+
primaryWorkflowId: "serp-comparison",
|
|
298
|
+
recommendedTools: ["workflow_run", "paa-expansion-brief", "harvest_paa", "extract_url"],
|
|
299
|
+
requiredInputs: ["keyword"],
|
|
300
|
+
optionalInputs: ["domain", "url", "location", "maxQuestions", "extractTop"],
|
|
301
|
+
produces: ["content gaps", "page comparison CSV", "PAA questions CSV", "section map", "writer brief"],
|
|
302
|
+
runHint: "Use workflow_run with workflowId serp-comparison for competitor gaps or paa-expansion-brief when the user mainly wants a section map from PAA data."
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
id: "ai_search_visibility_audit",
|
|
306
|
+
title: "AI Search Visibility Audit",
|
|
307
|
+
description: "Audit whether a brand/domain appears in AI Overview citations, PAA sources, organic results, local pack, and source pages, then produce language guidance.",
|
|
308
|
+
primaryWorkflowId: "ai-overview-language",
|
|
309
|
+
recommendedTools: ["workflow_run", "search_serp", "harvest_paa", "extract_url", "capture_serp_snapshot"],
|
|
310
|
+
requiredInputs: ["keyword", "domain or url"],
|
|
311
|
+
optionalInputs: ["location", "maxQuestions", "extractTop"],
|
|
312
|
+
produces: ["AI Overview citations CSV", "claim patterns", "language guidance", "citation hooks", "visibility gaps"],
|
|
313
|
+
runHint: "Use workflow_run with workflowId ai-overview-language. Use serp-comparison when the user also wants ranking-page gaps."
|
|
314
|
+
}
|
|
315
|
+
];
|
|
316
|
+
function normalize(value) {
|
|
317
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
318
|
+
}
|
|
319
|
+
function suggestWorkflowRecipes(goal, limit = 3) {
|
|
320
|
+
const normalized = normalize(goal);
|
|
321
|
+
const scored = WORKFLOW_RECIPES.map((recipe) => {
|
|
322
|
+
const haystack = normalize([
|
|
323
|
+
recipe.id,
|
|
324
|
+
recipe.title,
|
|
325
|
+
recipe.description,
|
|
326
|
+
recipe.requiredInputs.join(" "),
|
|
327
|
+
recipe.optionalInputs.join(" "),
|
|
328
|
+
recipe.produces.join(" ")
|
|
329
|
+
].join(" "));
|
|
330
|
+
let score = 0;
|
|
331
|
+
for (const token of normalized.split(/\s+/).filter(Boolean)) {
|
|
332
|
+
if (haystack.includes(token)) score += 1;
|
|
333
|
+
}
|
|
334
|
+
if (haystack.includes(normalized)) score += 5;
|
|
335
|
+
return { recipe, score };
|
|
336
|
+
});
|
|
337
|
+
return scored.sort((a, b) => b.score - a.score || a.recipe.title.localeCompare(b.recipe.title)).slice(0, Math.max(1, limit)).map((item) => item.recipe);
|
|
338
|
+
}
|
|
339
|
+
|
|
154
340
|
// src/mcp/mcp-response-formatter.ts
|
|
155
341
|
var reportSavingEnabled = true;
|
|
156
342
|
function sanitizeVendorText(text) {
|
|
@@ -202,6 +388,14 @@ function oneBlock(content) {
|
|
|
202
388
|
\u{1F4C4} Saved: \`${filePath}\`` : content;
|
|
203
389
|
return { content: [{ type: "text", text }] };
|
|
204
390
|
}
|
|
391
|
+
function workflowRecipeTable(recipes) {
|
|
392
|
+
if (!recipes.length) return "";
|
|
393
|
+
return [
|
|
394
|
+
"| Recipe | Best workflow | What it produces |",
|
|
395
|
+
"|---|---|---|",
|
|
396
|
+
...recipes.map((recipe) => `| ${cell(recipe.title)} | ${recipe.primaryWorkflowId ? `\`${recipe.primaryWorkflowId}\`` : "tool chain"} | ${cell(recipe.produces.slice(0, 4).join(", "))} |`)
|
|
397
|
+
].join("\n");
|
|
398
|
+
}
|
|
205
399
|
function formatStructuredError(body, fallback) {
|
|
206
400
|
if (body.error === "insufficient_balance") {
|
|
207
401
|
return `Insufficient credits. Balance: ${body.balance_credits} credits. This call requires ${body.required_credits} credits. Top up at ${body.topup_url}`;
|
|
@@ -774,6 +968,163 @@ function normalizeMapsAttempts(value) {
|
|
|
774
968
|
observedRegion: attempt.observedRegion ?? attempt.observed_region ?? null
|
|
775
969
|
}));
|
|
776
970
|
}
|
|
971
|
+
function workflowArtifactsFrom(run) {
|
|
972
|
+
return Array.isArray(run?.artifacts) ? run.artifacts : [];
|
|
973
|
+
}
|
|
974
|
+
function workflowArtifactRows(artifacts) {
|
|
975
|
+
if (!artifacts.length) return "";
|
|
976
|
+
return [
|
|
977
|
+
"| Artifact | Type | ID | Rows/Bytes |",
|
|
978
|
+
"|---|---|---|---|",
|
|
979
|
+
...artifacts.map((artifact) => {
|
|
980
|
+
const rows = artifact.rows_count ?? artifact.rows ?? "";
|
|
981
|
+
const bytes = artifact.bytes ?? "";
|
|
982
|
+
const size = [rows ? `${rows} rows` : "", bytes ? `${bytes} bytes` : ""].filter(Boolean).join(" / ");
|
|
983
|
+
return `| ${cell(String(artifact.label ?? artifact.path ?? "artifact"))} | ${cell(String(artifact.kind ?? artifact.content_type ?? "file"))} | \`${String(artifact.id ?? "")}\` | ${cell(size || "\u2014")} |`;
|
|
984
|
+
})
|
|
985
|
+
].join("\n");
|
|
986
|
+
}
|
|
987
|
+
function formatWorkflowList(raw, input) {
|
|
988
|
+
const parsed = parseData(raw);
|
|
989
|
+
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
990
|
+
const workflows = Array.isArray(parsed.data.workflows) ? parsed.data.workflows : [];
|
|
991
|
+
const workflowRows = [
|
|
992
|
+
"| Workflow ID | Title | Use |",
|
|
993
|
+
"|---|---|---|",
|
|
994
|
+
...workflows.map((workflow) => `| \`${String(workflow.id ?? "")}\` | ${cell(String(workflow.title ?? ""))} | ${cell(String(workflow.description ?? ""))} |`)
|
|
995
|
+
].join("\n");
|
|
996
|
+
const recipes = input.includeRecipes === false ? [] : WORKFLOW_RECIPES;
|
|
997
|
+
const full = [
|
|
998
|
+
"# MCP Scraper Workflows",
|
|
999
|
+
"Use `workflow_suggest` when the user describes a high-level job. Use `workflow_run` when the workflow id and input are known. Use `workflow_status` and `workflow_artifact_read` to inspect completed runs and pull evidence back into context.",
|
|
1000
|
+
workflows.length ? `
|
|
1001
|
+
## Runnable Workflows
|
|
1002
|
+
${workflowRows}` : "",
|
|
1003
|
+
recipes.length ? `
|
|
1004
|
+
## High-Level Recipes
|
|
1005
|
+
${workflowRecipeTable(recipes)}` : "",
|
|
1006
|
+
recipes.length ? "\nThese recipes cover market analysis, ICP research, forum and review acquisition, brand design briefings, CRO audits, competitive positioning, content gaps, and AI search visibility audits." : ""
|
|
1007
|
+
].filter(Boolean).join("\n");
|
|
1008
|
+
return {
|
|
1009
|
+
...oneBlock(full),
|
|
1010
|
+
structuredContent: {
|
|
1011
|
+
workflows: workflows.map((workflow) => ({
|
|
1012
|
+
id: String(workflow.id ?? ""),
|
|
1013
|
+
title: String(workflow.title ?? ""),
|
|
1014
|
+
description: String(workflow.description ?? "")
|
|
1015
|
+
})),
|
|
1016
|
+
recipes
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
function formatWorkflowSuggest(input) {
|
|
1021
|
+
const suggestions = suggestWorkflowRecipes(input.goal, input.maxSuggestions ?? 3);
|
|
1022
|
+
const full = [
|
|
1023
|
+
`# Workflow Suggestions`,
|
|
1024
|
+
`**Goal:** ${input.goal}`,
|
|
1025
|
+
"",
|
|
1026
|
+
workflowRecipeTable(suggestions),
|
|
1027
|
+
"",
|
|
1028
|
+
"## How To Execute",
|
|
1029
|
+
"1. Pick the closest recipe.",
|
|
1030
|
+
"2. If it has a `primaryWorkflowId`, call `workflow_run` with that id and the required inputs.",
|
|
1031
|
+
"3. After the run completes, use `workflow_artifact_read` on the most relevant CSV/JSON/Markdown artifacts before writing the final answer."
|
|
1032
|
+
].join("\n");
|
|
1033
|
+
return {
|
|
1034
|
+
...oneBlock(full),
|
|
1035
|
+
structuredContent: {
|
|
1036
|
+
goal: input.goal,
|
|
1037
|
+
suggestions
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
function formatWorkflowRun(raw, input) {
|
|
1042
|
+
const parsed = parseData(raw);
|
|
1043
|
+
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
1044
|
+
const run = parsed.data.run;
|
|
1045
|
+
const summary = parsed.data.summary;
|
|
1046
|
+
const artifacts = workflowArtifactsFrom(run);
|
|
1047
|
+
const runId = String(run?.id ?? "");
|
|
1048
|
+
const status = String(run?.status ?? summary?.status ?? "unknown");
|
|
1049
|
+
const full = [
|
|
1050
|
+
`# Workflow Run: ${input.workflowId}`,
|
|
1051
|
+
`**Run ID:** \`${runId || "unknown"}\``,
|
|
1052
|
+
`**Status:** ${status}`,
|
|
1053
|
+
summary?.title ? `**Title:** ${summary.title}` : "",
|
|
1054
|
+
summary?.summary ? `
|
|
1055
|
+
## Summary
|
|
1056
|
+
${summary.summary}` : "",
|
|
1057
|
+
artifacts.length ? `
|
|
1058
|
+
## Artifacts
|
|
1059
|
+
${workflowArtifactRows(artifacts)}` : "",
|
|
1060
|
+
artifacts.length ? "\nUse `workflow_artifact_read` with the run id and artifact id to pull CSV, JSON, Markdown, or report content into context." : ""
|
|
1061
|
+
].filter(Boolean).join("\n");
|
|
1062
|
+
return {
|
|
1063
|
+
...oneBlock(full),
|
|
1064
|
+
structuredContent: {
|
|
1065
|
+
workflowId: input.workflowId,
|
|
1066
|
+
input: input.input ?? {},
|
|
1067
|
+
run,
|
|
1068
|
+
summary,
|
|
1069
|
+
artifacts
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
function formatWorkflowStatus(raw, input) {
|
|
1074
|
+
const parsed = parseData(raw);
|
|
1075
|
+
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
1076
|
+
const run = parsed.data.run;
|
|
1077
|
+
const artifacts = workflowArtifactsFrom(run);
|
|
1078
|
+
const full = [
|
|
1079
|
+
`# Workflow Status`,
|
|
1080
|
+
`**Run ID:** \`${input.runId}\``,
|
|
1081
|
+
`**Workflow:** ${run?.workflow_id ?? "unknown"}`,
|
|
1082
|
+
`**Status:** ${run?.status ?? "unknown"}`,
|
|
1083
|
+
run?.error_message ? `
|
|
1084
|
+
## Error
|
|
1085
|
+
${run.error_message}` : "",
|
|
1086
|
+
artifacts.length ? `
|
|
1087
|
+
## Artifacts
|
|
1088
|
+
${workflowArtifactRows(artifacts)}` : ""
|
|
1089
|
+
].filter(Boolean).join("\n");
|
|
1090
|
+
return {
|
|
1091
|
+
...oneBlock(full),
|
|
1092
|
+
structuredContent: {
|
|
1093
|
+
run,
|
|
1094
|
+
artifacts
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
function formatWorkflowArtifactRead(raw, input) {
|
|
1099
|
+
const parsed = parseData(raw);
|
|
1100
|
+
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
1101
|
+
const text = typeof parsed.data.text === "string" ? parsed.data.text : "";
|
|
1102
|
+
const contentType = String(parsed.data.contentType ?? "text/plain");
|
|
1103
|
+
const bytes = Number(parsed.data.bytes ?? 0);
|
|
1104
|
+
const truncated = parsed.data.truncated === true;
|
|
1105
|
+
const full = [
|
|
1106
|
+
`# Workflow Artifact`,
|
|
1107
|
+
`**Run ID:** \`${input.runId}\``,
|
|
1108
|
+
`**Artifact ID:** \`${input.artifactId}\``,
|
|
1109
|
+
`**Content-Type:** ${contentType}`,
|
|
1110
|
+
`**Bytes:** ${bytes}${truncated ? ` (truncated to ${input.maxBytes ?? 2e5})` : ""}`,
|
|
1111
|
+
"",
|
|
1112
|
+
"```",
|
|
1113
|
+
text,
|
|
1114
|
+
"```"
|
|
1115
|
+
].join("\n");
|
|
1116
|
+
return {
|
|
1117
|
+
...oneBlock(full),
|
|
1118
|
+
structuredContent: {
|
|
1119
|
+
runId: input.runId,
|
|
1120
|
+
artifactId: input.artifactId,
|
|
1121
|
+
contentType,
|
|
1122
|
+
bytes,
|
|
1123
|
+
truncated,
|
|
1124
|
+
text
|
|
1125
|
+
}
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
777
1128
|
function formatCreditsInfo(raw, input) {
|
|
778
1129
|
const parsed = parseData(raw);
|
|
779
1130
|
if ("error" in parsed) return { content: [{ type: "text", text: parsed.error }], isError: true };
|
|
@@ -1644,6 +1995,85 @@ var CreditsInfoInputSchema = {
|
|
|
1644
1995
|
item: import_zod.z.string().optional().describe('Optional tool, action, or feature to look up, e.g. "maps reviews", "extract_url", "YouTube transcription", or "concurrency"'),
|
|
1645
1996
|
includeLedger: import_zod.z.boolean().default(false).describe("Whether to include recent credit ledger entries")
|
|
1646
1997
|
};
|
|
1998
|
+
var WorkflowIdSchema = import_zod.z.enum([
|
|
1999
|
+
"directory",
|
|
2000
|
+
"agent-packet",
|
|
2001
|
+
"local-competitive-audit",
|
|
2002
|
+
"map-comparison",
|
|
2003
|
+
"serp-comparison",
|
|
2004
|
+
"paa-expansion-brief",
|
|
2005
|
+
"ai-overview-language"
|
|
2006
|
+
]);
|
|
2007
|
+
var WorkflowListInputSchema = {
|
|
2008
|
+
includeRecipes: import_zod.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.")
|
|
2009
|
+
};
|
|
2010
|
+
var WorkflowSuggestInputSchema = {
|
|
2011
|
+
goal: import_zod.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".'),
|
|
2012
|
+
query: import_zod.z.string().optional().describe("Business category, niche, or Maps query when known."),
|
|
2013
|
+
keyword: import_zod.z.string().optional().describe("Search keyword, audience problem, or content topic when known."),
|
|
2014
|
+
domain: import_zod.z.string().optional().describe("Target domain or brand domain when known."),
|
|
2015
|
+
url: import_zod.z.string().url().optional().describe("Target URL when the workflow should inspect a specific page."),
|
|
2016
|
+
location: import_zod.z.string().optional().describe("City/region/country for localized research, e.g. Denver, CO."),
|
|
2017
|
+
state: import_zod.z.string().optional().describe("US state abbreviation or name for state-wide market research."),
|
|
2018
|
+
maxSuggestions: import_zod.z.number().int().min(1).max(8).default(3).describe("Number of matching workflow recipes to return.")
|
|
2019
|
+
};
|
|
2020
|
+
var WorkflowRunInputSchema = {
|
|
2021
|
+
workflowId: WorkflowIdSchema.describe("Workflow to run. Use workflow_list or workflow_suggest first when unsure."),
|
|
2022
|
+
input: import_zod.z.record(import_zod.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?}."),
|
|
2023
|
+
webhookUrl: import_zod.z.string().url().optional().describe("Optional HTTPS webhook to receive the completed hosted workflow run event.")
|
|
2024
|
+
};
|
|
2025
|
+
var WorkflowStatusInputSchema = {
|
|
2026
|
+
runId: import_zod.z.string().min(1).describe("Workflow run id returned by workflow_run.")
|
|
2027
|
+
};
|
|
2028
|
+
var WorkflowArtifactReadInputSchema = {
|
|
2029
|
+
runId: import_zod.z.string().min(1).describe("Workflow run id returned by workflow_run or workflow_status."),
|
|
2030
|
+
artifactId: import_zod.z.string().min(1).describe("Artifact id from the run artifact list."),
|
|
2031
|
+
maxBytes: import_zod.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.")
|
|
2032
|
+
};
|
|
2033
|
+
var WorkflowRecipeOutput = import_zod.z.object({
|
|
2034
|
+
id: import_zod.z.string(),
|
|
2035
|
+
title: import_zod.z.string(),
|
|
2036
|
+
description: import_zod.z.string(),
|
|
2037
|
+
primaryWorkflowId: import_zod.z.string().nullable(),
|
|
2038
|
+
recommendedTools: import_zod.z.array(import_zod.z.string()),
|
|
2039
|
+
requiredInputs: import_zod.z.array(import_zod.z.string()),
|
|
2040
|
+
optionalInputs: import_zod.z.array(import_zod.z.string()),
|
|
2041
|
+
produces: import_zod.z.array(import_zod.z.string()),
|
|
2042
|
+
runHint: import_zod.z.string()
|
|
2043
|
+
});
|
|
2044
|
+
var WorkflowDefinitionOutput = import_zod.z.object({
|
|
2045
|
+
id: import_zod.z.string(),
|
|
2046
|
+
title: import_zod.z.string(),
|
|
2047
|
+
description: import_zod.z.string()
|
|
2048
|
+
});
|
|
2049
|
+
var WorkflowArtifactOutput = import_zod.z.record(import_zod.z.unknown());
|
|
2050
|
+
var WorkflowListOutputSchema = {
|
|
2051
|
+
workflows: import_zod.z.array(WorkflowDefinitionOutput),
|
|
2052
|
+
recipes: import_zod.z.array(WorkflowRecipeOutput)
|
|
2053
|
+
};
|
|
2054
|
+
var WorkflowSuggestOutputSchema = {
|
|
2055
|
+
goal: import_zod.z.string(),
|
|
2056
|
+
suggestions: import_zod.z.array(WorkflowRecipeOutput)
|
|
2057
|
+
};
|
|
2058
|
+
var WorkflowRunOutputSchema = {
|
|
2059
|
+
workflowId: import_zod.z.string(),
|
|
2060
|
+
input: import_zod.z.record(import_zod.z.unknown()),
|
|
2061
|
+
run: import_zod.z.record(import_zod.z.unknown()).optional(),
|
|
2062
|
+
summary: import_zod.z.record(import_zod.z.unknown()).optional(),
|
|
2063
|
+
artifacts: import_zod.z.array(WorkflowArtifactOutput)
|
|
2064
|
+
};
|
|
2065
|
+
var WorkflowStatusOutputSchema = {
|
|
2066
|
+
run: import_zod.z.record(import_zod.z.unknown()).optional(),
|
|
2067
|
+
artifacts: import_zod.z.array(WorkflowArtifactOutput)
|
|
2068
|
+
};
|
|
2069
|
+
var WorkflowArtifactReadOutputSchema = {
|
|
2070
|
+
runId: import_zod.z.string(),
|
|
2071
|
+
artifactId: import_zod.z.string(),
|
|
2072
|
+
contentType: import_zod.z.string(),
|
|
2073
|
+
bytes: import_zod.z.number().int().min(0),
|
|
2074
|
+
truncated: import_zod.z.boolean(),
|
|
2075
|
+
text: import_zod.z.string()
|
|
2076
|
+
};
|
|
1647
2077
|
var SearchSerpInputSchema = {
|
|
1648
2078
|
query: import_zod.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".'),
|
|
1649
2079
|
location: import_zod.z.string().optional().describe("City, region, or country for geo-targeted results, inferred from user request when present."),
|
|
@@ -2167,6 +2597,41 @@ function registerPaaExtractorMcpTools(server2, executor2, options = {}) {
|
|
|
2167
2597
|
outputSchema: DirectoryWorkflowOutputSchema,
|
|
2168
2598
|
annotations: liveWebToolAnnotations("Directory Workflow: Markets + Maps")
|
|
2169
2599
|
}, async (input) => formatDirectoryWorkflow(await executor2.directoryWorkflow(input), input));
|
|
2600
|
+
server2.registerTool("workflow_list", {
|
|
2601
|
+
title: "Workflow Catalog",
|
|
2602
|
+
description: "List MCP Scraper higher-level workflows and AI-facing recipes. Use this when the user asks what MCP Scraper can do beyond single tools, or asks for market analysis, ICP research, forum/review acquisition, full brand design briefings, CRO audits, competitive positioning, content gap briefs, or AI search visibility audits. Returns runnable workflow ids plus recipe guidance for the best tool chain.",
|
|
2603
|
+
inputSchema: WorkflowListInputSchema,
|
|
2604
|
+
outputSchema: WorkflowListOutputSchema,
|
|
2605
|
+
annotations: localPlanningToolAnnotations("Workflow Catalog")
|
|
2606
|
+
}, async (input) => formatWorkflowList(await executor2.workflowList(input), input));
|
|
2607
|
+
server2.registerTool("workflow_suggest", {
|
|
2608
|
+
title: "Workflow Intent Router",
|
|
2609
|
+
description: 'Route a high-level business or research goal to the right MCP Scraper workflow/tool chain. Use before running work when the user says things like "do market analysis", "research my ICP", "acquire forum and review language", "build a brand design brief", "run a CRO audit", "compare competitors", "make a content gap brief", or "audit AI search visibility". This tool does not spend credits; it tells the AI exactly which workflow/tool chain to run next.',
|
|
2610
|
+
inputSchema: WorkflowSuggestInputSchema,
|
|
2611
|
+
outputSchema: WorkflowSuggestOutputSchema,
|
|
2612
|
+
annotations: localPlanningToolAnnotations("Workflow Intent Router")
|
|
2613
|
+
}, async (input) => formatWorkflowSuggest(input));
|
|
2614
|
+
server2.registerTool("workflow_run", {
|
|
2615
|
+
title: "Run Workflow",
|
|
2616
|
+
description: withReportNote("Run a higher-level MCP Scraper workflow and return the run id, summary, and artifacts. 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 executing market analysis, ICP evidence packets, local competitive audits, Maps/SERP comparisons, content gap briefs, and AI Overview language guidance."),
|
|
2617
|
+
inputSchema: WorkflowRunInputSchema,
|
|
2618
|
+
outputSchema: WorkflowRunOutputSchema,
|
|
2619
|
+
annotations: liveWebToolAnnotations("Run Workflow")
|
|
2620
|
+
}, async (input) => formatWorkflowRun(await executor2.workflowRun(input), input));
|
|
2621
|
+
server2.registerTool("workflow_status", {
|
|
2622
|
+
title: "Workflow Status",
|
|
2623
|
+
description: "Fetch a hosted workflow run by id and list its current status and artifacts. Use after workflow_run when the model needs to re-open a run, inspect artifact ids, or recover from a long workflow.",
|
|
2624
|
+
inputSchema: WorkflowStatusInputSchema,
|
|
2625
|
+
outputSchema: WorkflowStatusOutputSchema,
|
|
2626
|
+
annotations: liveWebToolAnnotations("Workflow Status")
|
|
2627
|
+
}, async (input) => formatWorkflowStatus(await executor2.workflowStatus(input), input));
|
|
2628
|
+
server2.registerTool("workflow_artifact_read", {
|
|
2629
|
+
title: "Read Workflow Artifact",
|
|
2630
|
+
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.",
|
|
2631
|
+
inputSchema: WorkflowArtifactReadInputSchema,
|
|
2632
|
+
outputSchema: WorkflowArtifactReadOutputSchema,
|
|
2633
|
+
annotations: liveWebToolAnnotations("Read Workflow Artifact")
|
|
2634
|
+
}, async (input) => formatWorkflowArtifactRead(await executor2.workflowArtifactRead(input), input));
|
|
2170
2635
|
server2.registerTool("rank_tracker_blueprint", {
|
|
2171
2636
|
title: "Rank Tracker Blueprint Builder",
|
|
2172
2637
|
description: "Generate a build-ready database, cron/heartbeat, ingestion, metrics, and AI implementation prompt for a rank tracker powered by MCP Scraper. Supports Maps rankings through directory_workflow/maps_search, organic rankings through search_serp, AI Overview citation tracking, and People Also Ask source presence tracking. This tool is local planning only; it does not call the web or spend credits.",
|