mcp-researchpowerpack 6.0.11 → 6.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![MseeP.ai Security Assessment Badge](https://mseep.net/pr/yigitkonur-mcp-researchpowerpack-badge.png)](https://mseep.ai/app/yigitkonur-mcp-researchpowerpack)
2
+
1
3
  # mcp-researchpowerpack
2
4
 
3
5
  HTTP MCP server for research. Three tools, orientation-first, built for agents that run multi-pass research loops.
package/dist/mcp-use.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "includeInspector": false,
3
- "buildTime": "2026-04-29T11:17:03.370Z",
4
- "buildId": "40a7197a31e9fb29",
3
+ "buildTime": "2026-04-30T14:37:44.640Z",
4
+ "buildId": "3a668f138fddb479",
5
5
  "entryPoint": "dist/index.js",
6
6
  "widgets": {}
7
7
  }
@@ -10,9 +10,9 @@ const scrapeLinksParamsSchema = z.object({
10
10
  )
11
11
  }).strict();
12
12
  const scrapeLinksOutputSchema = z.object({
13
- // `content` deliberately NOT duplicated here — the primary markdown lives in
14
- // the MCP tool result's `content[0].text`. Previously this schema echoed the
15
- // whole extraction output, doubling token cost for clients that forward both.
13
+ content: z.string().describe(
14
+ "Rendered scrape output, including per-URL raw markdown or LLM extraction. Duplicates the MCP content text for clients that only expose structuredContent."
15
+ ),
16
16
  metadata: z.object({
17
17
  total_items: z.number().int().nonnegative().describe("Number of URLs processed."),
18
18
  successful: z.number().int().nonnegative().describe("URLs fetched successfully."),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/schemas/scrape-links.ts"],
4
- "sourcesContent": ["import { z } from 'zod';\n\nconst urlSchema = z\n .string()\n .url({ message: 'scrape-links: Invalid URL format' })\n .refine(\n url => url.startsWith('http://') || url.startsWith('https://'),\n { message: 'scrape-links: URL must use http:// or https://' }\n )\n .describe('A fully-qualified HTTP or HTTPS URL to scrape.');\n\nexport const scrapeLinksParamsSchema = z.object({\n urls: z\n .array(urlSchema)\n .min(1, { message: 'scrape-links: At least 1 URL required' })\n .describe('URLs to fetch and extract in parallel. Reddit post permalinks (`reddit.com/r/<sub>/comments/<id>/...`) are auto-detected and routed through the Reddit API (threaded post + comments); every other URL flows through the HTTP scraper. Mix reddit + non-reddit URLs freely; both branches run concurrently. Prefer contextually grouped batches \u2014 call this tool multiple times in parallel when URL sets are unrelated, instead of one giant mixed batch.'),\n extract: z\n .string()\n .min(1, { message: 'scrape-links: extract cannot be empty' })\n .optional()\n .describe(\n 'OPTIONAL semantic extraction instruction. Describe the SHAPE of what you want, separated by `|`. When provided, the extractor classifies each page (docs / github-thread / reddit / marketing / cve / paper / announcement / qa / blog / changelog / release-notes) and adjusts emphasis per type: preserves numbers/versions/stacktraces verbatim from docs and CVE pages, quotes Reddit/HN with attribution plus sentiment distribution, flags what the page did NOT answer in a \"Not found\" section, and surfaces referenced-but-unscraped URLs in a \"Follow-up signals\" bulletin that feeds the next research loop. Good examples: \"root cause | affected versions | fix | workarounds | timeline\"; \"pricing tiers | rate limits | enterprise contact | free-tier quotas\"; \"maintainer decisions | accepted fix commits | stacktraces | resolved version\". Omit this argument to skip LLM extraction entirely and receive cleaned markdown for each URL (raw mode \u2014 cheaper, faster, and useful when you want the whole page rather than a filtered view).',\n ),\n}).strict();\n\nexport type ScrapeLinksParams = z.infer<typeof scrapeLinksParamsSchema>;\n\nexport const scrapeLinksOutputSchema = z.object({\n // `content` deliberately NOT duplicated here \u2014 the primary markdown lives in\n // the MCP tool result's `content[0].text`. Previously this schema echoed the\n // whole extraction output, doubling token cost for clients that forward both.\n metadata: z.object({\n total_items: z.number().int().nonnegative().describe('Number of URLs processed.'),\n successful: z.number().int().nonnegative().describe('URLs fetched successfully.'),\n failed: z.number().int().nonnegative().describe('URLs that failed.'),\n execution_time_ms: z.number().int().nonnegative().describe('Wall clock time in milliseconds.'),\n total_credits: z.number().int().nonnegative().describe('External scraping credits consumed.'),\n }).strict(),\n}).strict();\n\nexport type ScrapeLinksOutput = z.infer<typeof scrapeLinksOutputSchema>;\n"],
5
- "mappings": "AAAA,SAAS,SAAS;AAElB,MAAM,YAAY,EACf,OAAO,EACP,IAAI,EAAE,SAAS,mCAAmC,CAAC,EACnD;AAAA,EACC,SAAO,IAAI,WAAW,SAAS,KAAK,IAAI,WAAW,UAAU;AAAA,EAC7D,EAAE,SAAS,iDAAiD;AAC9D,EACC,SAAS,gDAAgD;AAErD,MAAM,0BAA0B,EAAE,OAAO;AAAA,EAC9C,MAAM,EACH,MAAM,SAAS,EACf,IAAI,GAAG,EAAE,SAAS,wCAAwC,CAAC,EAC3D,SAAS,icAA4b;AAAA,EACxc,SAAS,EACN,OAAO,EACP,IAAI,GAAG,EAAE,SAAS,wCAAwC,CAAC,EAC3D,SAAS,EACT;AAAA,IACC;AAAA,EACF;AACJ,CAAC,EAAE,OAAO;AAIH,MAAM,0BAA0B,EAAE,OAAO;AAAA;AAAA;AAAA;AAAA,EAI9C,UAAU,EAAE,OAAO;AAAA,IACjB,aAAa,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,2BAA2B;AAAA,IAChF,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,4BAA4B;AAAA,IAChF,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,mBAAmB;AAAA,IACnE,mBAAmB,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,kCAAkC;AAAA,IAC7F,eAAe,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,qCAAqC;AAAA,EAC9F,CAAC,EAAE,OAAO;AACZ,CAAC,EAAE,OAAO;",
4
+ "sourcesContent": ["import { z } from 'zod';\n\nconst urlSchema = z\n .string()\n .url({ message: 'scrape-links: Invalid URL format' })\n .refine(\n url => url.startsWith('http://') || url.startsWith('https://'),\n { message: 'scrape-links: URL must use http:// or https://' }\n )\n .describe('A fully-qualified HTTP or HTTPS URL to scrape.');\n\nexport const scrapeLinksParamsSchema = z.object({\n urls: z\n .array(urlSchema)\n .min(1, { message: 'scrape-links: At least 1 URL required' })\n .describe('URLs to fetch and extract in parallel. Reddit post permalinks (`reddit.com/r/<sub>/comments/<id>/...`) are auto-detected and routed through the Reddit API (threaded post + comments); every other URL flows through the HTTP scraper. Mix reddit + non-reddit URLs freely; both branches run concurrently. Prefer contextually grouped batches \u2014 call this tool multiple times in parallel when URL sets are unrelated, instead of one giant mixed batch.'),\n extract: z\n .string()\n .min(1, { message: 'scrape-links: extract cannot be empty' })\n .optional()\n .describe(\n 'OPTIONAL semantic extraction instruction. Describe the SHAPE of what you want, separated by `|`. When provided, the extractor classifies each page (docs / github-thread / reddit / marketing / cve / paper / announcement / qa / blog / changelog / release-notes) and adjusts emphasis per type: preserves numbers/versions/stacktraces verbatim from docs and CVE pages, quotes Reddit/HN with attribution plus sentiment distribution, flags what the page did NOT answer in a \"Not found\" section, and surfaces referenced-but-unscraped URLs in a \"Follow-up signals\" bulletin that feeds the next research loop. Good examples: \"root cause | affected versions | fix | workarounds | timeline\"; \"pricing tiers | rate limits | enterprise contact | free-tier quotas\"; \"maintainer decisions | accepted fix commits | stacktraces | resolved version\". Omit this argument to skip LLM extraction entirely and receive cleaned markdown for each URL (raw mode \u2014 cheaper, faster, and useful when you want the whole page rather than a filtered view).',\n ),\n}).strict();\n\nexport type ScrapeLinksParams = z.infer<typeof scrapeLinksParamsSchema>;\n\nexport const scrapeLinksOutputSchema = z.object({\n content: z\n .string()\n .describe(\n 'Rendered scrape output, including per-URL raw markdown or LLM extraction. Duplicates the MCP content text for clients that only expose structuredContent.',\n ),\n metadata: z.object({\n total_items: z.number().int().nonnegative().describe('Number of URLs processed.'),\n successful: z.number().int().nonnegative().describe('URLs fetched successfully.'),\n failed: z.number().int().nonnegative().describe('URLs that failed.'),\n execution_time_ms: z.number().int().nonnegative().describe('Wall clock time in milliseconds.'),\n total_credits: z.number().int().nonnegative().describe('External scraping credits consumed.'),\n }).strict(),\n}).strict();\n\nexport type ScrapeLinksOutput = z.infer<typeof scrapeLinksOutputSchema>;\n"],
5
+ "mappings": "AAAA,SAAS,SAAS;AAElB,MAAM,YAAY,EACf,OAAO,EACP,IAAI,EAAE,SAAS,mCAAmC,CAAC,EACnD;AAAA,EACC,SAAO,IAAI,WAAW,SAAS,KAAK,IAAI,WAAW,UAAU;AAAA,EAC7D,EAAE,SAAS,iDAAiD;AAC9D,EACC,SAAS,gDAAgD;AAErD,MAAM,0BAA0B,EAAE,OAAO;AAAA,EAC9C,MAAM,EACH,MAAM,SAAS,EACf,IAAI,GAAG,EAAE,SAAS,wCAAwC,CAAC,EAC3D,SAAS,icAA4b;AAAA,EACxc,SAAS,EACN,OAAO,EACP,IAAI,GAAG,EAAE,SAAS,wCAAwC,CAAC,EAC3D,SAAS,EACT;AAAA,IACC;AAAA,EACF;AACJ,CAAC,EAAE,OAAO;AAIH,MAAM,0BAA0B,EAAE,OAAO;AAAA,EAC9C,SAAS,EACN,OAAO,EACP;AAAA,IACC;AAAA,EACF;AAAA,EACF,UAAU,EAAE,OAAO;AAAA,IACjB,aAAa,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,2BAA2B;AAAA,IAChF,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,4BAA4B;AAAA,IAChF,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,mBAAmB;AAAA,IACnE,mBAAmB,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,kCAAkC;AAAA,IAC7F,eAAe,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,qCAAqC;AAAA,EAC9F,CAAC,EAAE,OAAO;AACZ,CAAC,EAAE,OAAO;",
6
6
  "names": []
7
7
  }
@@ -444,7 +444,7 @@ function buildScrapeResponse(params, contents, metrics, llmErrors, executionTime
444
444
  execution_time_ms: executionTime,
445
445
  total_credits: metrics.totalCredits
446
446
  };
447
- return { content: formattedContent, structuredContent: { metadata } };
447
+ return { content: formattedContent, structuredContent: { content: formattedContent, metadata } };
448
448
  }
449
449
  async function handleScrapeLinks(params, reporter = NOOP_REPORTER) {
450
450
  const startTime = Date.now();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/tools/scrape.ts"],
4
- "sourcesContent": ["/**\n * Scrape Links Tool Handler\n *\n * Scrapes many URLs in parallel. Reddit permalinks (reddit.com/r/.../comments/...)\n * are auto-detected and routed through the Reddit API; all other URLs go through\n * the scraper. Both branches feed the same per-URL LLM extraction pipeline.\n *\n * NEVER throws \u2014 every error is returned as a tool-level failure response.\n */\n\nimport type { MCPServer } from 'mcp-use/server';\n\nimport {\n SCRAPER,\n CONCURRENCY,\n getCapabilities,\n getMissingEnvMessage,\n parseEnv,\n} from '../config/index.js';\nimport {\n scrapeLinksOutputSchema,\n scrapeLinksParamsSchema,\n type ScrapeLinksParams,\n type ScrapeLinksOutput,\n} from '../schemas/scrape-links.js';\nimport { ScraperClient } from '../clients/scraper.js';\nimport { RedditClient, type PostResult } from '../clients/reddit.js';\nimport { JinaClient } from '../clients/jina.js';\nimport { MarkdownCleaner } from '../services/markdown-cleaner.js';\nimport { createLLMProcessor, processContentWithLLM } from '../services/llm-processor.js';\nimport { removeMetaTags } from '../utils/markdown-formatter.js';\nimport { extractReadableContent } from '../utils/content-extractor.js';\nimport { classifyError, ErrorCode } from '../utils/errors.js';\nimport { isDocumentUrl } from '../utils/source-type.js';\nimport { pMap, pMapSettled } from '../utils/concurrency.js';\nimport {\n mcpLog,\n formatSuccess,\n formatError,\n formatBatchHeader,\n formatDuration,\n} from './utils.js';\nimport {\n createToolReporter,\n NOOP_REPORTER,\n toolFailure,\n toolSuccess,\n toToolResponse,\n type ToolExecutionResult,\n type ToolReporter,\n} from './mcp-helpers.js';\n\nconst markdownCleaner = new MarkdownCleaner();\n\nfunction enhanceExtractionInstruction(instruction: string | undefined): string {\n const base = instruction || 'Extract the main content and key information from this page.';\n return `${SCRAPER.EXTRACTION_PREFIX}\\n\\n${base}\\n\\n${SCRAPER.EXTRACTION_SUFFIX}`;\n}\n\n// --- Types ---\n\ninterface ProcessedResult {\n url: string;\n content: string;\n index: number; // original position in params.urls[]\n /**\n * Cleaned markdown captured before LLM extraction. Preserved so the handler\n * can fall back to it when the LLM emits the terse \"Page did not load: X\"\n * escape line and would otherwise nuke the scraped body.\n */\n rawContent?: string;\n}\n\ninterface ScrapeMetrics {\n successful: number;\n failed: number;\n totalCredits: number;\n}\n\ninterface FailedContent {\n content: string;\n index: number;\n}\n\ninterface ScrapePhaseResult {\n successItems: ProcessedResult[];\n failedContents: FailedContent[];\n metrics: ScrapeMetrics;\n}\n\ninterface BranchInput {\n url: string;\n origIndex: number;\n}\n\ninterface ScrapeClients {\n scraperClient?: ScraperClient;\n jinaClient: JinaClient;\n llmProcessor: ReturnType<typeof createLLMProcessor>;\n}\n\n/**\n * Any URL the web branch decides to hand off to Jina Reader \u2014 either because\n * Scrape.do returned a binary content-type, or because Scrape.do failed\n * outright (non-404 error). `scrapeError` is preserved so that, if Jina also\n * fails, the final error message can surface both layers.\n *\n * Genuine 404s are NOT put here \u2014 the URL doesn't exist; Jina won't help.\n */\ninterface JinaFallback {\n url: string;\n origIndex: number;\n reason: 'binary_content' | 'scrape_failed';\n scrapeError?: string;\n}\n\ninterface WebPhaseResult extends ScrapePhaseResult {\n jinaFallbacks: JinaFallback[];\n}\n\n// --- Reddit URL detection ---\n\nconst REDDIT_HOST = /(?:^|\\.)reddit\\.com$/i;\nconst REDDIT_POST_PERMALINK = /\\/r\\/[^/]+\\/comments\\/[a-z0-9]+/i;\n\nfunction isRedditUrl(url: string): boolean {\n try {\n const u = new URL(url);\n return REDDIT_HOST.test(u.hostname);\n } catch {\n return false;\n }\n}\n\nfunction isRedditPostPermalink(url: string): boolean {\n try {\n const u = new URL(url);\n return REDDIT_HOST.test(u.hostname) && REDDIT_POST_PERMALINK.test(u.pathname);\n } catch {\n return false;\n }\n}\n\n// --- Error helper ---\n\nfunction createScrapeErrorResponse(\n code: string,\n message: string,\n startTime: number,\n retryable = false,\n alternatives?: string[],\n): ToolExecutionResult<ScrapeLinksOutput> {\n return toolFailure(\n `${formatError({\n code,\n message,\n retryable,\n toolName: 'scrape-links',\n howToFix: code === 'NO_URLS' ? ['Provide at least one valid URL'] : undefined,\n alternatives,\n })}\\n\\nExecution time: ${formatDuration(Date.now() - startTime)}`,\n );\n}\n\n// --- URL partitioning ---\n\ninterface PartitionedUrls {\n webInputs: BranchInput[];\n redditInputs: BranchInput[];\n documentInputs: BranchInput[];\n invalidEntries: { url: string; origIndex: number }[];\n}\n\nfunction partitionUrls(urls: string[]): PartitionedUrls {\n const webInputs: BranchInput[] = [];\n const redditInputs: BranchInput[] = [];\n const documentInputs: BranchInput[] = [];\n const invalidEntries: { url: string; origIndex: number }[] = [];\n\n for (let i = 0; i < urls.length; i++) {\n const url = urls[i]!;\n try {\n new URL(url);\n } catch {\n invalidEntries.push({ url, origIndex: i });\n continue;\n }\n // Document URLs (.pdf/.docx/.pptx/.xlsx) go straight to Jina Reader \u2014\n // bypassing Scrape.do because it cannot decode binary bodies. Ordered\n // before the Reddit check so a hypothetical PDF on a reddit-adjacent host\n // still takes the document path.\n if (isDocumentUrl(url)) {\n documentInputs.push({ url, origIndex: i });\n } else if (isRedditUrl(url)) {\n redditInputs.push({ url, origIndex: i });\n } else {\n webInputs.push({ url, origIndex: i });\n }\n }\n\n return { webInputs, redditInputs, documentInputs, invalidEntries };\n}\n\n// --- Web branch ---\n\nasync function fetchWebBranch(\n inputs: BranchInput[],\n client: ScraperClient,\n): Promise<WebPhaseResult> {\n if (inputs.length === 0) {\n return {\n successItems: [],\n failedContents: [],\n metrics: { successful: 0, failed: 0, totalCredits: 0 },\n jinaFallbacks: [],\n };\n }\n\n mcpLog('info', `[concurrency] web branch: fanning out ${inputs.length} URL(s) with limit=${CONCURRENCY.SCRAPER}`, 'scrape');\n const urls = inputs.map((i) => i.url);\n const results = await client.scrapeMultiple(urls, { timeout: 60 });\n\n const successItems: ProcessedResult[] = [];\n const failedContents: FailedContent[] = [];\n const jinaFallbacks: JinaFallback[] = [];\n let successful = 0;\n let failed = 0;\n let totalCredits = 0;\n\n for (let i = 0; i < results.length; i++) {\n const result = results[i];\n const input = inputs[i]!;\n const origIndex = input.origIndex;\n if (!result) {\n failed++;\n failedContents.push({ index: origIndex, content: `## ${input.url}\\n\\n\u274C No result returned` });\n continue;\n }\n\n // Binary document detected by content-type \u2014 defer to Jina Reader.\n if (result.error?.code === ErrorCode.UNSUPPORTED_BINARY_CONTENT) {\n jinaFallbacks.push({\n url: result.url,\n origIndex,\n reason: 'binary_content',\n });\n continue;\n }\n\n // Scrape.do failure \u2014 only 404s are treated as hard fails (Jina won't\n // help when the page genuinely doesn't exist). Every other failure mode\n // (302 redirect loops, WAF blocks, timeouts, 5xx, service unavailable)\n // gets a second chance through Jina Reader, which uses different IPs\n // and handles many anti-bot surfaces differently.\n const scrapeFailed = Boolean(result.error) || result.statusCode < 200 || result.statusCode >= 300;\n if (scrapeFailed && result.statusCode !== 404) {\n jinaFallbacks.push({\n url: result.url,\n origIndex,\n reason: 'scrape_failed',\n scrapeError: result.error?.message || result.content || `HTTP ${result.statusCode}`,\n });\n continue;\n }\n if (scrapeFailed) {\n failed++;\n failedContents.push({ index: origIndex, content: `## ${result.url}\\n\\n\u274C Failed to scrape: HTTP 404 \u2014 Page not found` });\n continue;\n }\n\n successful++;\n totalCredits += result.credits;\n\n let content: string;\n try {\n const readable = extractReadableContent(result.content, result.url);\n const sourceForCleaner = readable.extracted ? readable.content : result.content;\n content = markdownCleaner.processContent(sourceForCleaner);\n } catch {\n content = result.content;\n }\n\n successItems.push({ url: result.url, content, index: origIndex, rawContent: content });\n }\n\n return {\n successItems,\n failedContents,\n metrics: { successful, failed, totalCredits },\n jinaFallbacks,\n };\n}\n\nfunction missingScraperWebPhase(inputs: BranchInput[]): WebPhaseResult {\n return {\n successItems: [],\n failedContents: inputs.map((input) => ({\n index: input.origIndex,\n content: `## ${input.url}\\n\\n\u274C Web scraping unavailable. Set \\`SCRAPEDO_API_KEY\\` to enable non-reddit, non-document web URL scraping.`,\n })),\n metrics: { successful: 0, failed: inputs.length, totalCredits: 0 },\n jinaFallbacks: [],\n };\n}\n\n// --- Document branch (Jina Reader) ---\n\n/**\n * Format a Jina-failure line. If the URL was deferred here *after* Scrape.do\n * already failed, surface both layers' errors so the caller can see that this\n * isn't just a Jina glitch \u2014 the primary path failed too.\n *\n * Exported for unit testing.\n */\nexport function formatJinaFailure(url: string, jinaError: string, scrapeError?: string): string {\n if (scrapeError) {\n return `## ${url}\\n\\n\u274C Both scrapers failed. Scrape.do: ${scrapeError}. Jina Reader: ${jinaError}.`;\n }\n return `## ${url}\\n\\n\u274C Document conversion failed: ${jinaError}`;\n}\n\nasync function fetchDocumentBranch(\n inputs: BranchInput[],\n jinaClient: JinaClient,\n /** Optional: map url \u2192 original Scrape.do error, for fallback messaging. */\n scrapeErrorContext?: Map<string, string>,\n): Promise<ScrapePhaseResult> {\n if (inputs.length === 0) {\n return { successItems: [], failedContents: [], metrics: { successful: 0, failed: 0, totalCredits: 0 } };\n }\n\n mcpLog(\n 'info',\n `[concurrency] document branch (jina): converting ${inputs.length} URL(s) with limit=${CONCURRENCY.SCRAPER}`,\n 'scrape',\n );\n\n const results = await pMapSettled(\n inputs,\n (input) => jinaClient.convert({ url: input.url }),\n CONCURRENCY.SCRAPER,\n );\n\n const successItems: ProcessedResult[] = [];\n const failedContents: FailedContent[] = [];\n let successful = 0;\n let failed = 0;\n\n for (let i = 0; i < results.length; i++) {\n const settled = results[i];\n const input = inputs[i]!;\n const scrapeError = scrapeErrorContext?.get(input.url);\n if (!settled) {\n failed++;\n failedContents.push({ index: input.origIndex, content: formatJinaFailure(input.url, 'No result returned', scrapeError) });\n continue;\n }\n if (settled.status === 'rejected') {\n failed++;\n const reason = settled.reason instanceof Error ? settled.reason.message : String(settled.reason);\n failedContents.push({ index: input.origIndex, content: formatJinaFailure(input.url, reason, scrapeError) });\n continue;\n }\n\n const result = settled.value;\n if (result.error || result.statusCode < 200 || result.statusCode >= 300) {\n failed++;\n const errorMsg = result.error?.message || `HTTP ${result.statusCode}`;\n failedContents.push({ index: input.origIndex, content: formatJinaFailure(input.url, errorMsg, scrapeError) });\n continue;\n }\n\n successful++;\n successItems.push({ url: input.url, content: result.content, index: input.origIndex, rawContent: result.content });\n }\n\n return { successItems, failedContents, metrics: { successful, failed, totalCredits: 0 } };\n}\n\n// --- Reddit branch ---\n\nfunction formatRedditPostAsMarkdown(result: PostResult): string {\n const { post, comments } = result;\n const lines: string[] = [];\n lines.push(`# ${post.title}`);\n lines.push('');\n lines.push(`**r/${post.subreddit}** \u2022 u/${post.author} \u2022 \u2B06\uFE0F ${post.score} \u2022 \uD83D\uDCAC ${post.commentCount} comments`);\n lines.push(`\uD83D\uDD17 ${post.url}`);\n lines.push('');\n if (post.body) {\n lines.push('## Post content');\n lines.push('');\n lines.push(post.body);\n lines.push('');\n }\n if (comments.length > 0) {\n lines.push(`## Top comments (${comments.length} total)`);\n lines.push('');\n for (const c of comments) {\n const indent = ' '.repeat(c.depth);\n const op = c.isOP ? ' **[OP]**' : '';\n const score = c.score >= 0 ? `+${c.score}` : `${c.score}`;\n lines.push(`${indent}- **u/${c.author}**${op} _(${score})_`);\n for (const line of c.body.split('\\n')) {\n lines.push(`${indent} ${line}`);\n }\n lines.push('');\n }\n }\n return lines.join('\\n');\n}\n\nasync function fetchRedditBranch(inputs: BranchInput[]): Promise<ScrapePhaseResult> {\n if (inputs.length === 0) {\n return { successItems: [], failedContents: [], metrics: { successful: 0, failed: 0, totalCredits: 0 } };\n }\n\n const env = parseEnv();\n if (!env.REDDIT_CLIENT_ID || !env.REDDIT_CLIENT_SECRET) {\n const failedContents = inputs.map(\n (i) => ({\n index: i.origIndex,\n content: `## ${i.url}\\n\\n\u274C Reddit URL detected, but Reddit API is not configured. Set \\`REDDIT_CLIENT_ID\\` and \\`REDDIT_CLIENT_SECRET\\` in the server env to enable threaded Reddit scraping.`,\n }),\n );\n return {\n successItems: [],\n failedContents,\n metrics: { successful: 0, failed: inputs.length, totalCredits: 0 },\n };\n }\n\n // Warn for non-permalink Reddit URLs (subreddit homepages, /new, /top, /hot,\n // user profiles). The Reddit API path we call requires /r/.../comments/... \u2014\n // reject upfront so the caller sees a helpful message instead of a 404.\n const [postInputs, nonPermalinks] = inputs.reduce<[BranchInput[], BranchInput[]]>(\n ([posts, rest], input) => {\n if (isRedditPostPermalink(input.url)) posts.push(input);\n else rest.push(input);\n return [posts, rest];\n },\n [[], []],\n );\n\n const nonPermalinkFailed = nonPermalinks.map(\n (i) => ({\n index: i.origIndex,\n content: `## ${i.url}\\n\\n\u274C Only Reddit post permalinks (/r/<sub>/comments/<id>/...) are supported. Use web-search with scope:\"reddit\" to discover post permalinks first.`,\n }),\n );\n\n if (postInputs.length === 0) {\n return {\n successItems: [],\n failedContents: nonPermalinkFailed,\n metrics: { successful: 0, failed: nonPermalinks.length, totalCredits: 0 },\n };\n }\n\n mcpLog('info', `[concurrency] reddit branch: fetching ${postInputs.length} post(s) with limit=${CONCURRENCY.REDDIT}`, 'scrape');\n const client = new RedditClient(env.REDDIT_CLIENT_ID, env.REDDIT_CLIENT_SECRET);\n const urls = postInputs.map((i) => i.url);\n const batchResult = await client.batchGetPosts(urls, true);\n const urlToIndex = new Map(postInputs.map((i) => [i.url, i.origIndex]));\n\n const successItems: ProcessedResult[] = [];\n const failedContents: FailedContent[] = [...nonPermalinkFailed];\n let successful = 0;\n let failed = nonPermalinks.length;\n\n for (const [url, result] of batchResult.results) {\n const origIndex = urlToIndex.get(url) ?? -1;\n if (result instanceof Error) {\n failed++;\n failedContents.push({ index: origIndex, content: `## ${url}\\n\\n\u274C Reddit fetch failed: ${result.message}` });\n continue;\n }\n successful++;\n const md = formatRedditPostAsMarkdown(result);\n successItems.push({ url, content: md, index: origIndex, rawContent: md });\n }\n\n return { successItems, failedContents, metrics: { successful, failed, totalCredits: 0 } };\n}\n\n// --- Terse-LLM-escape detection + raw fallback merger ---\n\n/**\n * The LLM extraction prompt tells the model to emit a single terse line when\n * a page \"clearly failed to load\" (login walls, JS-render-empty, paywalls,\n * etc.). In practice the LLM over-triggers this on partially-rendered pages,\n * causing scrape-links to return a one-line verdict and discard the cleaned\n * markdown. This detector + merger keep the verdict but re-attach a capped\n * slice of the raw markdown so the caller always has something to work with.\n */\nconst TERSE_LLM_FAILURE_RE =\n /^\\s*##\\s*Matches\\s*\\n+\\s*_Page did not load:\\s*([a-z0-9_-]+)_\\s*\\.?\\s*$/i;\n\n/** Cap on the raw-markdown slice appended under \"## Raw content ...\" */\nexport const RAW_FALLBACK_CHAR_CAP = 4000;\n\n/**\n * If `llmOutput` is exactly the terse \"## Matches\\n_Page did not load: X_\"\n * line, return the reason token (e.g. \"login-wall\"). Otherwise null.\n */\nexport function detectTerseFailure(llmOutput: string): string | null {\n const m = llmOutput.trim().match(TERSE_LLM_FAILURE_RE);\n return m ? m[1]! : null;\n}\n\n/**\n * When the LLM emitted the terse escape line, append a capped slice of the\n * raw cleaned markdown under a `## Raw content (...)` section so the caller\n * still has the actual scraped body to inspect. No-op otherwise.\n */\nexport function mergeLlmWithRawFallback(\n llmOutput: string,\n rawContent: string | undefined,\n): string {\n const reason = detectTerseFailure(llmOutput);\n if (!reason) return llmOutput;\n const trimmed = rawContent?.trim();\n if (!trimmed) return llmOutput;\n const snippet =\n trimmed.length > RAW_FALLBACK_CHAR_CAP\n ? trimmed.slice(0, RAW_FALLBACK_CHAR_CAP) + '\\n\\n\u2026[raw truncated]'\n : trimmed;\n return `${llmOutput.trim()}\\n\\n## Raw content (LLM flagged page as ${reason})\\n\\n${snippet}`;\n}\n\n// --- LLM extraction (shared by both branches) ---\n\nasync function processItemsWithLlm(\n successItems: ProcessedResult[],\n enhancedInstruction: string | undefined,\n llmProcessor: ReturnType<typeof createLLMProcessor>,\n reporter: ToolReporter,\n): Promise<{ items: ProcessedResult[]; llmErrors: number; llmAttempted: number }> {\n let llmErrors = 0;\n\n // Raw-mode bypass: caller omitted `extract` \u2192 return cleaned markdown as-is.\n if (!enhancedInstruction) {\n if (successItems.length > 0) {\n mcpLog('info', 'Raw mode: extract omitted \u2014 returning cleaned scraped content without LLM pass', 'scrape');\n }\n return { items: successItems, llmErrors, llmAttempted: 0 };\n }\n\n if (!llmProcessor || successItems.length === 0) {\n if (!llmProcessor && successItems.length > 0) {\n mcpLog('warning', 'LLM unavailable (LLM_API_KEY not set). Returning raw scraped content.', 'scrape');\n void reporter.log('warning', 'llm_extractor_unreachable: planner not configured; raw scraped content returned');\n }\n return { items: successItems, llmErrors, llmAttempted: 0 };\n }\n\n mcpLog('info', `[concurrency] llm extraction: fanning out ${successItems.length} item(s) with limit=${CONCURRENCY.LLM_EXTRACTION}`, 'scrape');\n\n const llmResults = await pMap(\n successItems,\n async (item) => {\n mcpLog('debug', `LLM extracting ${item.url}...`, 'scrape');\n\n const llmResult = await processContentWithLLM(\n item.content,\n { enabled: true, extract: enhancedInstruction, url: item.url },\n llmProcessor,\n );\n\n if (llmResult.processed) {\n const merged = mergeLlmWithRawFallback(llmResult.content, item.rawContent);\n if (merged !== llmResult.content) {\n mcpLog('warning', `LLM emitted terse escape line for ${item.url} \u2014 preserved raw fallback`, 'scrape');\n void reporter.log('warning', `llm_terse_escape: ${item.url} \u2014 preserving raw fallback`);\n }\n return { ...item, content: merged };\n }\n\n llmErrors++;\n mcpLog('warning', `LLM extraction failed for ${item.url}: ${llmResult.error || 'unknown reason'}`, 'scrape');\n void reporter.log('warning', `llm_extractor_unreachable: ${item.url} \u2014 ${llmResult.error || 'unknown reason'}`);\n return item;\n },\n CONCURRENCY.LLM_EXTRACTION,\n );\n\n return { items: llmResults, llmErrors, llmAttempted: successItems.length };\n}\n\n// --- Output assembly ---\n\ninterface ContentEntry {\n content: string;\n index: number;\n}\n\nexport function assembleContentEntries(successItems: ProcessedResult[], failedContents: FailedContent[]): string[] {\n const successEntries: ContentEntry[] = successItems.map((item) => {\n let content = item.content;\n try {\n content = removeMetaTags(content);\n } catch {\n // Use content as-is\n }\n return { index: item.index, content: `## ${item.url}\\n\\n${content}` };\n });\n\n return [...failedContents, ...successEntries]\n .sort((a, b) => a.index - b.index)\n .map((entry) => entry.content);\n}\n\nfunction buildScrapeResponse(\n params: ScrapeLinksParams,\n contents: string[],\n metrics: ScrapeMetrics,\n llmErrors: number,\n executionTime: number,\n llmAccounting: { llmAttempted: number; llmSucceeded: boolean },\n): { content: string; structuredContent: ScrapeLinksOutput } {\n const llmExtras: Record<string, string | number> = {};\n if (llmAccounting.llmAttempted > 0) {\n const ok = llmAccounting.llmAttempted - llmErrors;\n llmExtras['LLM extraction'] = `${ok}/${llmAccounting.llmAttempted} succeeded`;\n if (!llmAccounting.llmSucceeded) {\n llmExtras['LLM credit'] = '0 charged (no extraction produced)';\n }\n } else if (llmErrors > 0) {\n llmExtras['LLM extraction failures'] = llmErrors;\n }\n\n const batchHeader = formatBatchHeader({\n title: `Scraped Content (${params.urls.length} URLs)`,\n totalItems: params.urls.length,\n successful: metrics.successful,\n failed: metrics.failed,\n extras: {\n 'Credits used': metrics.totalCredits,\n ...llmExtras,\n },\n });\n\n const formattedContent = formatSuccess({\n title: 'Scraping Complete',\n summary: batchHeader,\n data: contents.join('\\n\\n---\\n\\n'),\n metadata: {\n 'Execution time': formatDuration(executionTime),\n },\n });\n\n const metadata: ScrapeLinksOutput['metadata'] = {\n total_items: params.urls.length,\n successful: metrics.successful,\n failed: metrics.failed,\n execution_time_ms: executionTime,\n total_credits: metrics.totalCredits,\n };\n return { content: formattedContent, structuredContent: { metadata } };\n}\n\n// --- Handler ---\n\nexport async function handleScrapeLinks(\n params: ScrapeLinksParams,\n reporter: ToolReporter = NOOP_REPORTER,\n): Promise<ToolExecutionResult<ScrapeLinksOutput>> {\n const startTime = Date.now();\n\n if (!params.urls || params.urls.length === 0) {\n return createScrapeErrorResponse('NO_URLS', 'No URLs provided', startTime);\n }\n\n const { webInputs, redditInputs, documentInputs, invalidEntries } = partitionUrls(params.urls);\n const validCount = webInputs.length + redditInputs.length + documentInputs.length;\n\n await reporter.log(\n 'info',\n `Partitioned ${params.urls.length} URL(s): ${webInputs.length} web, ${redditInputs.length} reddit, ${documentInputs.length} document, ${invalidEntries.length} invalid`,\n );\n\n if (validCount === 0) {\n return createScrapeErrorResponse(\n 'INVALID_URLS',\n `All ${params.urls.length} URLs are invalid`,\n startTime,\n false,\n [\n 'web-search(queries=[...], extract=\"...\") \u2014 search for valid URLs first, then scrape the results',\n ],\n );\n }\n\n const scrapingAvailable = getCapabilities().scraping;\n const hasIndependentBranchInputs = redditInputs.length > 0 || documentInputs.length > 0;\n\n if (webInputs.length > 0 && !scrapingAvailable && !hasIndependentBranchInputs) {\n return toolFailure(getMissingEnvMessage('scraping'));\n }\n\n mcpLog(\n 'info',\n `Starting scrape: ${webInputs.length} web + ${redditInputs.length} reddit + ${documentInputs.length} document URL(s)`,\n 'scrape',\n );\n await reporter.progress(15, 100, 'Preparing scraper clients');\n\n // Only initialize the Scrape.do client if we actually have HTML/web URLs.\n // The Jina client is cheap (no auth needed) and always constructed so the\n // document branch and the web\u2192Jina fallback path both work uniformly.\n let clients: ScrapeClients;\n try {\n const jinaClient = new JinaClient();\n clients = {\n jinaClient,\n llmProcessor: createLLMProcessor(),\n ...(webInputs.length > 0 && scrapingAvailable ? { scraperClient: new ScraperClient() } : {}),\n };\n } catch (error) {\n const err = classifyError(error);\n return createScrapeErrorResponse(\n 'CLIENT_INIT_FAILED',\n `Failed to initialize scraper: ${err.message}`,\n startTime,\n false,\n [\n 'web-search(queries=[\"topic key findings\", \"topic summary\"], extract=\"key findings and summary\") \u2014 search instead of scraping',\n ],\n );\n }\n\n // Only enhance + run LLM when caller supplied an extract instruction.\n // Undefined \u2192 raw mode (cleaned markdown returned without LLM pass).\n const enhancedInstruction = params.extract\n ? enhanceExtractionInstruction(params.extract)\n : undefined;\n\n await reporter.progress(35, 100, 'Fetching page content');\n\n // Phase 1 \u2014 run all three branches in parallel. Failures in one branch do\n // not block the others. The web branch may surface URLs to reroute via\n // `jinaFallbacks` (binary content-type OR non-404 Scrape.do failure),\n // which Phase 2 re-runs through Jina Reader.\n const emptyPhase: WebPhaseResult = {\n successItems: [], failedContents: [],\n metrics: { successful: 0, failed: 0, totalCredits: 0 },\n jinaFallbacks: [],\n };\n let webPhasePromise: Promise<WebPhaseResult>;\n if (webInputs.length > 0) {\n if (!scrapingAvailable) {\n webPhasePromise = Promise.resolve<WebPhaseResult>(missingScraperWebPhase(webInputs));\n } else if (!clients.scraperClient) {\n return createScrapeErrorResponse(\n 'CLIENT_INIT_FAILED',\n 'Failed to initialize scraper: Scrape.do client missing for web URLs',\n startTime,\n );\n } else {\n webPhasePromise = fetchWebBranch(webInputs, clients.scraperClient);\n }\n } else {\n webPhasePromise = Promise.resolve<WebPhaseResult>(emptyPhase);\n }\n const [webPhase, redditPhase, documentPhase] = await Promise.all([\n webPhasePromise,\n fetchRedditBranch(redditInputs),\n fetchDocumentBranch(documentInputs, clients.jinaClient),\n ]);\n\n // Phase 2 \u2014 Jina Reader as a fallback for web-branch URLs that either\n // returned binary content or failed outright on Scrape.do.\n let deferredPhase: ScrapePhaseResult = {\n successItems: [], failedContents: [],\n metrics: { successful: 0, failed: 0, totalCredits: 0 },\n };\n if (webPhase.jinaFallbacks.length > 0) {\n const binaryCount = webPhase.jinaFallbacks.filter((f) => f.reason === 'binary_content').length;\n const failedCount = webPhase.jinaFallbacks.length - binaryCount;\n await reporter.log(\n 'info',\n `Rerouting ${webPhase.jinaFallbacks.length} URL(s) to Jina Reader: ${binaryCount} binary, ${failedCount} scrape-failed`,\n );\n const fallbackInputs: BranchInput[] = webPhase.jinaFallbacks.map((f) => ({\n url: f.url,\n origIndex: f.origIndex,\n }));\n const errorContext = new Map<string, string>(\n webPhase.jinaFallbacks\n .filter((f) => f.scrapeError !== undefined)\n .map((f) => [f.url, f.scrapeError as string]),\n );\n deferredPhase = await fetchDocumentBranch(fallbackInputs, clients.jinaClient, errorContext);\n }\n\n const successItems = [\n ...webPhase.successItems,\n ...redditPhase.successItems,\n ...documentPhase.successItems,\n ...deferredPhase.successItems,\n ];\n const invalidFailed = invalidEntries.map(\n ({ url, origIndex }) => ({ index: origIndex, content: `## ${url}\\n\\n\u274C Invalid URL format` }),\n );\n const failedContents = [\n ...invalidFailed,\n ...webPhase.failedContents,\n ...redditPhase.failedContents,\n ...documentPhase.failedContents,\n ...deferredPhase.failedContents,\n ];\n const metrics: ScrapeMetrics = {\n successful:\n webPhase.metrics.successful\n + redditPhase.metrics.successful\n + documentPhase.metrics.successful\n + deferredPhase.metrics.successful,\n failed:\n invalidEntries.length\n + webPhase.metrics.failed\n + redditPhase.metrics.failed\n + documentPhase.metrics.failed\n + deferredPhase.metrics.failed,\n totalCredits: webPhase.metrics.totalCredits,\n };\n\n await reporter.log('info', `Fetched ${metrics.successful} page(s), ${metrics.failed} failed`);\n\n if (successItems.length > 0) {\n await reporter.progress(80, 100, 'Running LLM extraction over fetched pages');\n }\n\n const { items: processedItems, llmErrors, llmAttempted } = await processItemsWithLlm(\n successItems,\n enhancedInstruction,\n clients.llmProcessor,\n reporter,\n );\n\n const contents = assembleContentEntries(processedItems, failedContents);\n const executionTime = Date.now() - startTime;\n\n mcpLog(\n 'info',\n `Completed: ${metrics.successful} successful, ${metrics.failed} failed, ${metrics.totalCredits} credits used`,\n 'scrape',\n );\n\n const llmSucceeded = llmAttempted > 0 && llmErrors < llmAttempted;\n const result = buildScrapeResponse(\n params,\n contents,\n metrics,\n llmErrors,\n executionTime,\n { llmAttempted, llmSucceeded },\n );\n\n if (metrics.successful === 0 && metrics.failed > 0) {\n return toolFailure(result.content);\n }\n\n return toolSuccess(result.content, result.structuredContent);\n}\n\nexport function registerScrapeLinksTool(server: MCPServer): void {\n server.tool(\n {\n name: 'scrape-links',\n title: 'Scrape Links',\n description:\n 'Fetch many URLs in parallel. With `extract` set, run per-URL structured LLM extraction (each page returns `## Source`, `## Matches` verbatim facts, `## Not found` gaps, `## Follow-up signals` new terms + referenced URLs); omit `extract` for raw mode (cleaned markdown per URL, no LLM pass). Auto-detects reddit.com post permalinks \u2192 Reddit API (threaded post + comments); PDF/DOCX/PPTX/XLSX \u2192 Jina Reader; everything else \u2192 HTTP scraper. Safe to call in parallel \u2014 group URLs by context rather than jamming unrelated batches together. Describe the SHAPE of what you want in `extract`, facets separated by `|` (e.g. `root cause | affected versions | fix | workarounds | timeline`).',\n schema: scrapeLinksParamsSchema,\n outputSchema: scrapeLinksOutputSchema,\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n destructiveHint: false,\n openWorldHint: true,\n },\n },\n async (args, ctx) => {\n const reporter = createToolReporter(ctx, 'scrape-links');\n const result = await handleScrapeLinks(args, reporter);\n\n await reporter.progress(100, 100, result.isError ? 'Scrape failed' : 'Scrape complete');\n return toToolResponse(result);\n },\n );\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Scrape Links Tool Handler\n *\n * Scrapes many URLs in parallel. Reddit permalinks (reddit.com/r/.../comments/...)\n * are auto-detected and routed through the Reddit API; all other URLs go through\n * the scraper. Both branches feed the same per-URL LLM extraction pipeline.\n *\n * NEVER throws \u2014 every error is returned as a tool-level failure response.\n */\n\nimport type { MCPServer } from 'mcp-use/server';\n\nimport {\n SCRAPER,\n CONCURRENCY,\n getCapabilities,\n getMissingEnvMessage,\n parseEnv,\n} from '../config/index.js';\nimport {\n scrapeLinksOutputSchema,\n scrapeLinksParamsSchema,\n type ScrapeLinksParams,\n type ScrapeLinksOutput,\n} from '../schemas/scrape-links.js';\nimport { ScraperClient } from '../clients/scraper.js';\nimport { RedditClient, type PostResult } from '../clients/reddit.js';\nimport { JinaClient } from '../clients/jina.js';\nimport { MarkdownCleaner } from '../services/markdown-cleaner.js';\nimport { createLLMProcessor, processContentWithLLM } from '../services/llm-processor.js';\nimport { removeMetaTags } from '../utils/markdown-formatter.js';\nimport { extractReadableContent } from '../utils/content-extractor.js';\nimport { classifyError, ErrorCode } from '../utils/errors.js';\nimport { isDocumentUrl } from '../utils/source-type.js';\nimport { pMap, pMapSettled } from '../utils/concurrency.js';\nimport {\n mcpLog,\n formatSuccess,\n formatError,\n formatBatchHeader,\n formatDuration,\n} from './utils.js';\nimport {\n createToolReporter,\n NOOP_REPORTER,\n toolFailure,\n toolSuccess,\n toToolResponse,\n type ToolExecutionResult,\n type ToolReporter,\n} from './mcp-helpers.js';\n\nconst markdownCleaner = new MarkdownCleaner();\n\nfunction enhanceExtractionInstruction(instruction: string | undefined): string {\n const base = instruction || 'Extract the main content and key information from this page.';\n return `${SCRAPER.EXTRACTION_PREFIX}\\n\\n${base}\\n\\n${SCRAPER.EXTRACTION_SUFFIX}`;\n}\n\n// --- Types ---\n\ninterface ProcessedResult {\n url: string;\n content: string;\n index: number; // original position in params.urls[]\n /**\n * Cleaned markdown captured before LLM extraction. Preserved so the handler\n * can fall back to it when the LLM emits the terse \"Page did not load: X\"\n * escape line and would otherwise nuke the scraped body.\n */\n rawContent?: string;\n}\n\ninterface ScrapeMetrics {\n successful: number;\n failed: number;\n totalCredits: number;\n}\n\ninterface FailedContent {\n content: string;\n index: number;\n}\n\ninterface ScrapePhaseResult {\n successItems: ProcessedResult[];\n failedContents: FailedContent[];\n metrics: ScrapeMetrics;\n}\n\ninterface BranchInput {\n url: string;\n origIndex: number;\n}\n\ninterface ScrapeClients {\n scraperClient?: ScraperClient;\n jinaClient: JinaClient;\n llmProcessor: ReturnType<typeof createLLMProcessor>;\n}\n\n/**\n * Any URL the web branch decides to hand off to Jina Reader \u2014 either because\n * Scrape.do returned a binary content-type, or because Scrape.do failed\n * outright (non-404 error). `scrapeError` is preserved so that, if Jina also\n * fails, the final error message can surface both layers.\n *\n * Genuine 404s are NOT put here \u2014 the URL doesn't exist; Jina won't help.\n */\ninterface JinaFallback {\n url: string;\n origIndex: number;\n reason: 'binary_content' | 'scrape_failed';\n scrapeError?: string;\n}\n\ninterface WebPhaseResult extends ScrapePhaseResult {\n jinaFallbacks: JinaFallback[];\n}\n\n// --- Reddit URL detection ---\n\nconst REDDIT_HOST = /(?:^|\\.)reddit\\.com$/i;\nconst REDDIT_POST_PERMALINK = /\\/r\\/[^/]+\\/comments\\/[a-z0-9]+/i;\n\nfunction isRedditUrl(url: string): boolean {\n try {\n const u = new URL(url);\n return REDDIT_HOST.test(u.hostname);\n } catch {\n return false;\n }\n}\n\nfunction isRedditPostPermalink(url: string): boolean {\n try {\n const u = new URL(url);\n return REDDIT_HOST.test(u.hostname) && REDDIT_POST_PERMALINK.test(u.pathname);\n } catch {\n return false;\n }\n}\n\n// --- Error helper ---\n\nfunction createScrapeErrorResponse(\n code: string,\n message: string,\n startTime: number,\n retryable = false,\n alternatives?: string[],\n): ToolExecutionResult<ScrapeLinksOutput> {\n return toolFailure(\n `${formatError({\n code,\n message,\n retryable,\n toolName: 'scrape-links',\n howToFix: code === 'NO_URLS' ? ['Provide at least one valid URL'] : undefined,\n alternatives,\n })}\\n\\nExecution time: ${formatDuration(Date.now() - startTime)}`,\n );\n}\n\n// --- URL partitioning ---\n\ninterface PartitionedUrls {\n webInputs: BranchInput[];\n redditInputs: BranchInput[];\n documentInputs: BranchInput[];\n invalidEntries: { url: string; origIndex: number }[];\n}\n\nfunction partitionUrls(urls: string[]): PartitionedUrls {\n const webInputs: BranchInput[] = [];\n const redditInputs: BranchInput[] = [];\n const documentInputs: BranchInput[] = [];\n const invalidEntries: { url: string; origIndex: number }[] = [];\n\n for (let i = 0; i < urls.length; i++) {\n const url = urls[i]!;\n try {\n new URL(url);\n } catch {\n invalidEntries.push({ url, origIndex: i });\n continue;\n }\n // Document URLs (.pdf/.docx/.pptx/.xlsx) go straight to Jina Reader \u2014\n // bypassing Scrape.do because it cannot decode binary bodies. Ordered\n // before the Reddit check so a hypothetical PDF on a reddit-adjacent host\n // still takes the document path.\n if (isDocumentUrl(url)) {\n documentInputs.push({ url, origIndex: i });\n } else if (isRedditUrl(url)) {\n redditInputs.push({ url, origIndex: i });\n } else {\n webInputs.push({ url, origIndex: i });\n }\n }\n\n return { webInputs, redditInputs, documentInputs, invalidEntries };\n}\n\n// --- Web branch ---\n\nasync function fetchWebBranch(\n inputs: BranchInput[],\n client: ScraperClient,\n): Promise<WebPhaseResult> {\n if (inputs.length === 0) {\n return {\n successItems: [],\n failedContents: [],\n metrics: { successful: 0, failed: 0, totalCredits: 0 },\n jinaFallbacks: [],\n };\n }\n\n mcpLog('info', `[concurrency] web branch: fanning out ${inputs.length} URL(s) with limit=${CONCURRENCY.SCRAPER}`, 'scrape');\n const urls = inputs.map((i) => i.url);\n const results = await client.scrapeMultiple(urls, { timeout: 60 });\n\n const successItems: ProcessedResult[] = [];\n const failedContents: FailedContent[] = [];\n const jinaFallbacks: JinaFallback[] = [];\n let successful = 0;\n let failed = 0;\n let totalCredits = 0;\n\n for (let i = 0; i < results.length; i++) {\n const result = results[i];\n const input = inputs[i]!;\n const origIndex = input.origIndex;\n if (!result) {\n failed++;\n failedContents.push({ index: origIndex, content: `## ${input.url}\\n\\n\u274C No result returned` });\n continue;\n }\n\n // Binary document detected by content-type \u2014 defer to Jina Reader.\n if (result.error?.code === ErrorCode.UNSUPPORTED_BINARY_CONTENT) {\n jinaFallbacks.push({\n url: result.url,\n origIndex,\n reason: 'binary_content',\n });\n continue;\n }\n\n // Scrape.do failure \u2014 only 404s are treated as hard fails (Jina won't\n // help when the page genuinely doesn't exist). Every other failure mode\n // (302 redirect loops, WAF blocks, timeouts, 5xx, service unavailable)\n // gets a second chance through Jina Reader, which uses different IPs\n // and handles many anti-bot surfaces differently.\n const scrapeFailed = Boolean(result.error) || result.statusCode < 200 || result.statusCode >= 300;\n if (scrapeFailed && result.statusCode !== 404) {\n jinaFallbacks.push({\n url: result.url,\n origIndex,\n reason: 'scrape_failed',\n scrapeError: result.error?.message || result.content || `HTTP ${result.statusCode}`,\n });\n continue;\n }\n if (scrapeFailed) {\n failed++;\n failedContents.push({ index: origIndex, content: `## ${result.url}\\n\\n\u274C Failed to scrape: HTTP 404 \u2014 Page not found` });\n continue;\n }\n\n successful++;\n totalCredits += result.credits;\n\n let content: string;\n try {\n const readable = extractReadableContent(result.content, result.url);\n const sourceForCleaner = readable.extracted ? readable.content : result.content;\n content = markdownCleaner.processContent(sourceForCleaner);\n } catch {\n content = result.content;\n }\n\n successItems.push({ url: result.url, content, index: origIndex, rawContent: content });\n }\n\n return {\n successItems,\n failedContents,\n metrics: { successful, failed, totalCredits },\n jinaFallbacks,\n };\n}\n\nfunction missingScraperWebPhase(inputs: BranchInput[]): WebPhaseResult {\n return {\n successItems: [],\n failedContents: inputs.map((input) => ({\n index: input.origIndex,\n content: `## ${input.url}\\n\\n\u274C Web scraping unavailable. Set \\`SCRAPEDO_API_KEY\\` to enable non-reddit, non-document web URL scraping.`,\n })),\n metrics: { successful: 0, failed: inputs.length, totalCredits: 0 },\n jinaFallbacks: [],\n };\n}\n\n// --- Document branch (Jina Reader) ---\n\n/**\n * Format a Jina-failure line. If the URL was deferred here *after* Scrape.do\n * already failed, surface both layers' errors so the caller can see that this\n * isn't just a Jina glitch \u2014 the primary path failed too.\n *\n * Exported for unit testing.\n */\nexport function formatJinaFailure(url: string, jinaError: string, scrapeError?: string): string {\n if (scrapeError) {\n return `## ${url}\\n\\n\u274C Both scrapers failed. Scrape.do: ${scrapeError}. Jina Reader: ${jinaError}.`;\n }\n return `## ${url}\\n\\n\u274C Document conversion failed: ${jinaError}`;\n}\n\nasync function fetchDocumentBranch(\n inputs: BranchInput[],\n jinaClient: JinaClient,\n /** Optional: map url \u2192 original Scrape.do error, for fallback messaging. */\n scrapeErrorContext?: Map<string, string>,\n): Promise<ScrapePhaseResult> {\n if (inputs.length === 0) {\n return { successItems: [], failedContents: [], metrics: { successful: 0, failed: 0, totalCredits: 0 } };\n }\n\n mcpLog(\n 'info',\n `[concurrency] document branch (jina): converting ${inputs.length} URL(s) with limit=${CONCURRENCY.SCRAPER}`,\n 'scrape',\n );\n\n const results = await pMapSettled(\n inputs,\n (input) => jinaClient.convert({ url: input.url }),\n CONCURRENCY.SCRAPER,\n );\n\n const successItems: ProcessedResult[] = [];\n const failedContents: FailedContent[] = [];\n let successful = 0;\n let failed = 0;\n\n for (let i = 0; i < results.length; i++) {\n const settled = results[i];\n const input = inputs[i]!;\n const scrapeError = scrapeErrorContext?.get(input.url);\n if (!settled) {\n failed++;\n failedContents.push({ index: input.origIndex, content: formatJinaFailure(input.url, 'No result returned', scrapeError) });\n continue;\n }\n if (settled.status === 'rejected') {\n failed++;\n const reason = settled.reason instanceof Error ? settled.reason.message : String(settled.reason);\n failedContents.push({ index: input.origIndex, content: formatJinaFailure(input.url, reason, scrapeError) });\n continue;\n }\n\n const result = settled.value;\n if (result.error || result.statusCode < 200 || result.statusCode >= 300) {\n failed++;\n const errorMsg = result.error?.message || `HTTP ${result.statusCode}`;\n failedContents.push({ index: input.origIndex, content: formatJinaFailure(input.url, errorMsg, scrapeError) });\n continue;\n }\n\n successful++;\n successItems.push({ url: input.url, content: result.content, index: input.origIndex, rawContent: result.content });\n }\n\n return { successItems, failedContents, metrics: { successful, failed, totalCredits: 0 } };\n}\n\n// --- Reddit branch ---\n\nfunction formatRedditPostAsMarkdown(result: PostResult): string {\n const { post, comments } = result;\n const lines: string[] = [];\n lines.push(`# ${post.title}`);\n lines.push('');\n lines.push(`**r/${post.subreddit}** \u2022 u/${post.author} \u2022 \u2B06\uFE0F ${post.score} \u2022 \uD83D\uDCAC ${post.commentCount} comments`);\n lines.push(`\uD83D\uDD17 ${post.url}`);\n lines.push('');\n if (post.body) {\n lines.push('## Post content');\n lines.push('');\n lines.push(post.body);\n lines.push('');\n }\n if (comments.length > 0) {\n lines.push(`## Top comments (${comments.length} total)`);\n lines.push('');\n for (const c of comments) {\n const indent = ' '.repeat(c.depth);\n const op = c.isOP ? ' **[OP]**' : '';\n const score = c.score >= 0 ? `+${c.score}` : `${c.score}`;\n lines.push(`${indent}- **u/${c.author}**${op} _(${score})_`);\n for (const line of c.body.split('\\n')) {\n lines.push(`${indent} ${line}`);\n }\n lines.push('');\n }\n }\n return lines.join('\\n');\n}\n\nasync function fetchRedditBranch(inputs: BranchInput[]): Promise<ScrapePhaseResult> {\n if (inputs.length === 0) {\n return { successItems: [], failedContents: [], metrics: { successful: 0, failed: 0, totalCredits: 0 } };\n }\n\n const env = parseEnv();\n if (!env.REDDIT_CLIENT_ID || !env.REDDIT_CLIENT_SECRET) {\n const failedContents = inputs.map(\n (i) => ({\n index: i.origIndex,\n content: `## ${i.url}\\n\\n\u274C Reddit URL detected, but Reddit API is not configured. Set \\`REDDIT_CLIENT_ID\\` and \\`REDDIT_CLIENT_SECRET\\` in the server env to enable threaded Reddit scraping.`,\n }),\n );\n return {\n successItems: [],\n failedContents,\n metrics: { successful: 0, failed: inputs.length, totalCredits: 0 },\n };\n }\n\n // Warn for non-permalink Reddit URLs (subreddit homepages, /new, /top, /hot,\n // user profiles). The Reddit API path we call requires /r/.../comments/... \u2014\n // reject upfront so the caller sees a helpful message instead of a 404.\n const [postInputs, nonPermalinks] = inputs.reduce<[BranchInput[], BranchInput[]]>(\n ([posts, rest], input) => {\n if (isRedditPostPermalink(input.url)) posts.push(input);\n else rest.push(input);\n return [posts, rest];\n },\n [[], []],\n );\n\n const nonPermalinkFailed = nonPermalinks.map(\n (i) => ({\n index: i.origIndex,\n content: `## ${i.url}\\n\\n\u274C Only Reddit post permalinks (/r/<sub>/comments/<id>/...) are supported. Use web-search with scope:\"reddit\" to discover post permalinks first.`,\n }),\n );\n\n if (postInputs.length === 0) {\n return {\n successItems: [],\n failedContents: nonPermalinkFailed,\n metrics: { successful: 0, failed: nonPermalinks.length, totalCredits: 0 },\n };\n }\n\n mcpLog('info', `[concurrency] reddit branch: fetching ${postInputs.length} post(s) with limit=${CONCURRENCY.REDDIT}`, 'scrape');\n const client = new RedditClient(env.REDDIT_CLIENT_ID, env.REDDIT_CLIENT_SECRET);\n const urls = postInputs.map((i) => i.url);\n const batchResult = await client.batchGetPosts(urls, true);\n const urlToIndex = new Map(postInputs.map((i) => [i.url, i.origIndex]));\n\n const successItems: ProcessedResult[] = [];\n const failedContents: FailedContent[] = [...nonPermalinkFailed];\n let successful = 0;\n let failed = nonPermalinks.length;\n\n for (const [url, result] of batchResult.results) {\n const origIndex = urlToIndex.get(url) ?? -1;\n if (result instanceof Error) {\n failed++;\n failedContents.push({ index: origIndex, content: `## ${url}\\n\\n\u274C Reddit fetch failed: ${result.message}` });\n continue;\n }\n successful++;\n const md = formatRedditPostAsMarkdown(result);\n successItems.push({ url, content: md, index: origIndex, rawContent: md });\n }\n\n return { successItems, failedContents, metrics: { successful, failed, totalCredits: 0 } };\n}\n\n// --- Terse-LLM-escape detection + raw fallback merger ---\n\n/**\n * The LLM extraction prompt tells the model to emit a single terse line when\n * a page \"clearly failed to load\" (login walls, JS-render-empty, paywalls,\n * etc.). In practice the LLM over-triggers this on partially-rendered pages,\n * causing scrape-links to return a one-line verdict and discard the cleaned\n * markdown. This detector + merger keep the verdict but re-attach a capped\n * slice of the raw markdown so the caller always has something to work with.\n */\nconst TERSE_LLM_FAILURE_RE =\n /^\\s*##\\s*Matches\\s*\\n+\\s*_Page did not load:\\s*([a-z0-9_-]+)_\\s*\\.?\\s*$/i;\n\n/** Cap on the raw-markdown slice appended under \"## Raw content ...\" */\nexport const RAW_FALLBACK_CHAR_CAP = 4000;\n\n/**\n * If `llmOutput` is exactly the terse \"## Matches\\n_Page did not load: X_\"\n * line, return the reason token (e.g. \"login-wall\"). Otherwise null.\n */\nexport function detectTerseFailure(llmOutput: string): string | null {\n const m = llmOutput.trim().match(TERSE_LLM_FAILURE_RE);\n return m ? m[1]! : null;\n}\n\n/**\n * When the LLM emitted the terse escape line, append a capped slice of the\n * raw cleaned markdown under a `## Raw content (...)` section so the caller\n * still has the actual scraped body to inspect. No-op otherwise.\n */\nexport function mergeLlmWithRawFallback(\n llmOutput: string,\n rawContent: string | undefined,\n): string {\n const reason = detectTerseFailure(llmOutput);\n if (!reason) return llmOutput;\n const trimmed = rawContent?.trim();\n if (!trimmed) return llmOutput;\n const snippet =\n trimmed.length > RAW_FALLBACK_CHAR_CAP\n ? trimmed.slice(0, RAW_FALLBACK_CHAR_CAP) + '\\n\\n\u2026[raw truncated]'\n : trimmed;\n return `${llmOutput.trim()}\\n\\n## Raw content (LLM flagged page as ${reason})\\n\\n${snippet}`;\n}\n\n// --- LLM extraction (shared by both branches) ---\n\nasync function processItemsWithLlm(\n successItems: ProcessedResult[],\n enhancedInstruction: string | undefined,\n llmProcessor: ReturnType<typeof createLLMProcessor>,\n reporter: ToolReporter,\n): Promise<{ items: ProcessedResult[]; llmErrors: number; llmAttempted: number }> {\n let llmErrors = 0;\n\n // Raw-mode bypass: caller omitted `extract` \u2192 return cleaned markdown as-is.\n if (!enhancedInstruction) {\n if (successItems.length > 0) {\n mcpLog('info', 'Raw mode: extract omitted \u2014 returning cleaned scraped content without LLM pass', 'scrape');\n }\n return { items: successItems, llmErrors, llmAttempted: 0 };\n }\n\n if (!llmProcessor || successItems.length === 0) {\n if (!llmProcessor && successItems.length > 0) {\n mcpLog('warning', 'LLM unavailable (LLM_API_KEY not set). Returning raw scraped content.', 'scrape');\n void reporter.log('warning', 'llm_extractor_unreachable: planner not configured; raw scraped content returned');\n }\n return { items: successItems, llmErrors, llmAttempted: 0 };\n }\n\n mcpLog('info', `[concurrency] llm extraction: fanning out ${successItems.length} item(s) with limit=${CONCURRENCY.LLM_EXTRACTION}`, 'scrape');\n\n const llmResults = await pMap(\n successItems,\n async (item) => {\n mcpLog('debug', `LLM extracting ${item.url}...`, 'scrape');\n\n const llmResult = await processContentWithLLM(\n item.content,\n { enabled: true, extract: enhancedInstruction, url: item.url },\n llmProcessor,\n );\n\n if (llmResult.processed) {\n const merged = mergeLlmWithRawFallback(llmResult.content, item.rawContent);\n if (merged !== llmResult.content) {\n mcpLog('warning', `LLM emitted terse escape line for ${item.url} \u2014 preserved raw fallback`, 'scrape');\n void reporter.log('warning', `llm_terse_escape: ${item.url} \u2014 preserving raw fallback`);\n }\n return { ...item, content: merged };\n }\n\n llmErrors++;\n mcpLog('warning', `LLM extraction failed for ${item.url}: ${llmResult.error || 'unknown reason'}`, 'scrape');\n void reporter.log('warning', `llm_extractor_unreachable: ${item.url} \u2014 ${llmResult.error || 'unknown reason'}`);\n return item;\n },\n CONCURRENCY.LLM_EXTRACTION,\n );\n\n return { items: llmResults, llmErrors, llmAttempted: successItems.length };\n}\n\n// --- Output assembly ---\n\ninterface ContentEntry {\n content: string;\n index: number;\n}\n\nexport function assembleContentEntries(successItems: ProcessedResult[], failedContents: FailedContent[]): string[] {\n const successEntries: ContentEntry[] = successItems.map((item) => {\n let content = item.content;\n try {\n content = removeMetaTags(content);\n } catch {\n // Use content as-is\n }\n return { index: item.index, content: `## ${item.url}\\n\\n${content}` };\n });\n\n return [...failedContents, ...successEntries]\n .sort((a, b) => a.index - b.index)\n .map((entry) => entry.content);\n}\n\nfunction buildScrapeResponse(\n params: ScrapeLinksParams,\n contents: string[],\n metrics: ScrapeMetrics,\n llmErrors: number,\n executionTime: number,\n llmAccounting: { llmAttempted: number; llmSucceeded: boolean },\n): { content: string; structuredContent: ScrapeLinksOutput } {\n const llmExtras: Record<string, string | number> = {};\n if (llmAccounting.llmAttempted > 0) {\n const ok = llmAccounting.llmAttempted - llmErrors;\n llmExtras['LLM extraction'] = `${ok}/${llmAccounting.llmAttempted} succeeded`;\n if (!llmAccounting.llmSucceeded) {\n llmExtras['LLM credit'] = '0 charged (no extraction produced)';\n }\n } else if (llmErrors > 0) {\n llmExtras['LLM extraction failures'] = llmErrors;\n }\n\n const batchHeader = formatBatchHeader({\n title: `Scraped Content (${params.urls.length} URLs)`,\n totalItems: params.urls.length,\n successful: metrics.successful,\n failed: metrics.failed,\n extras: {\n 'Credits used': metrics.totalCredits,\n ...llmExtras,\n },\n });\n\n const formattedContent = formatSuccess({\n title: 'Scraping Complete',\n summary: batchHeader,\n data: contents.join('\\n\\n---\\n\\n'),\n metadata: {\n 'Execution time': formatDuration(executionTime),\n },\n });\n\n const metadata: ScrapeLinksOutput['metadata'] = {\n total_items: params.urls.length,\n successful: metrics.successful,\n failed: metrics.failed,\n execution_time_ms: executionTime,\n total_credits: metrics.totalCredits,\n };\n return { content: formattedContent, structuredContent: { content: formattedContent, metadata } };\n}\n\n// --- Handler ---\n\nexport async function handleScrapeLinks(\n params: ScrapeLinksParams,\n reporter: ToolReporter = NOOP_REPORTER,\n): Promise<ToolExecutionResult<ScrapeLinksOutput>> {\n const startTime = Date.now();\n\n if (!params.urls || params.urls.length === 0) {\n return createScrapeErrorResponse('NO_URLS', 'No URLs provided', startTime);\n }\n\n const { webInputs, redditInputs, documentInputs, invalidEntries } = partitionUrls(params.urls);\n const validCount = webInputs.length + redditInputs.length + documentInputs.length;\n\n await reporter.log(\n 'info',\n `Partitioned ${params.urls.length} URL(s): ${webInputs.length} web, ${redditInputs.length} reddit, ${documentInputs.length} document, ${invalidEntries.length} invalid`,\n );\n\n if (validCount === 0) {\n return createScrapeErrorResponse(\n 'INVALID_URLS',\n `All ${params.urls.length} URLs are invalid`,\n startTime,\n false,\n [\n 'web-search(queries=[...], extract=\"...\") \u2014 search for valid URLs first, then scrape the results',\n ],\n );\n }\n\n const scrapingAvailable = getCapabilities().scraping;\n const hasIndependentBranchInputs = redditInputs.length > 0 || documentInputs.length > 0;\n\n if (webInputs.length > 0 && !scrapingAvailable && !hasIndependentBranchInputs) {\n return toolFailure(getMissingEnvMessage('scraping'));\n }\n\n mcpLog(\n 'info',\n `Starting scrape: ${webInputs.length} web + ${redditInputs.length} reddit + ${documentInputs.length} document URL(s)`,\n 'scrape',\n );\n await reporter.progress(15, 100, 'Preparing scraper clients');\n\n // Only initialize the Scrape.do client if we actually have HTML/web URLs.\n // The Jina client is cheap (no auth needed) and always constructed so the\n // document branch and the web\u2192Jina fallback path both work uniformly.\n let clients: ScrapeClients;\n try {\n const jinaClient = new JinaClient();\n clients = {\n jinaClient,\n llmProcessor: createLLMProcessor(),\n ...(webInputs.length > 0 && scrapingAvailable ? { scraperClient: new ScraperClient() } : {}),\n };\n } catch (error) {\n const err = classifyError(error);\n return createScrapeErrorResponse(\n 'CLIENT_INIT_FAILED',\n `Failed to initialize scraper: ${err.message}`,\n startTime,\n false,\n [\n 'web-search(queries=[\"topic key findings\", \"topic summary\"], extract=\"key findings and summary\") \u2014 search instead of scraping',\n ],\n );\n }\n\n // Only enhance + run LLM when caller supplied an extract instruction.\n // Undefined \u2192 raw mode (cleaned markdown returned without LLM pass).\n const enhancedInstruction = params.extract\n ? enhanceExtractionInstruction(params.extract)\n : undefined;\n\n await reporter.progress(35, 100, 'Fetching page content');\n\n // Phase 1 \u2014 run all three branches in parallel. Failures in one branch do\n // not block the others. The web branch may surface URLs to reroute via\n // `jinaFallbacks` (binary content-type OR non-404 Scrape.do failure),\n // which Phase 2 re-runs through Jina Reader.\n const emptyPhase: WebPhaseResult = {\n successItems: [], failedContents: [],\n metrics: { successful: 0, failed: 0, totalCredits: 0 },\n jinaFallbacks: [],\n };\n let webPhasePromise: Promise<WebPhaseResult>;\n if (webInputs.length > 0) {\n if (!scrapingAvailable) {\n webPhasePromise = Promise.resolve<WebPhaseResult>(missingScraperWebPhase(webInputs));\n } else if (!clients.scraperClient) {\n return createScrapeErrorResponse(\n 'CLIENT_INIT_FAILED',\n 'Failed to initialize scraper: Scrape.do client missing for web URLs',\n startTime,\n );\n } else {\n webPhasePromise = fetchWebBranch(webInputs, clients.scraperClient);\n }\n } else {\n webPhasePromise = Promise.resolve<WebPhaseResult>(emptyPhase);\n }\n const [webPhase, redditPhase, documentPhase] = await Promise.all([\n webPhasePromise,\n fetchRedditBranch(redditInputs),\n fetchDocumentBranch(documentInputs, clients.jinaClient),\n ]);\n\n // Phase 2 \u2014 Jina Reader as a fallback for web-branch URLs that either\n // returned binary content or failed outright on Scrape.do.\n let deferredPhase: ScrapePhaseResult = {\n successItems: [], failedContents: [],\n metrics: { successful: 0, failed: 0, totalCredits: 0 },\n };\n if (webPhase.jinaFallbacks.length > 0) {\n const binaryCount = webPhase.jinaFallbacks.filter((f) => f.reason === 'binary_content').length;\n const failedCount = webPhase.jinaFallbacks.length - binaryCount;\n await reporter.log(\n 'info',\n `Rerouting ${webPhase.jinaFallbacks.length} URL(s) to Jina Reader: ${binaryCount} binary, ${failedCount} scrape-failed`,\n );\n const fallbackInputs: BranchInput[] = webPhase.jinaFallbacks.map((f) => ({\n url: f.url,\n origIndex: f.origIndex,\n }));\n const errorContext = new Map<string, string>(\n webPhase.jinaFallbacks\n .filter((f) => f.scrapeError !== undefined)\n .map((f) => [f.url, f.scrapeError as string]),\n );\n deferredPhase = await fetchDocumentBranch(fallbackInputs, clients.jinaClient, errorContext);\n }\n\n const successItems = [\n ...webPhase.successItems,\n ...redditPhase.successItems,\n ...documentPhase.successItems,\n ...deferredPhase.successItems,\n ];\n const invalidFailed = invalidEntries.map(\n ({ url, origIndex }) => ({ index: origIndex, content: `## ${url}\\n\\n\u274C Invalid URL format` }),\n );\n const failedContents = [\n ...invalidFailed,\n ...webPhase.failedContents,\n ...redditPhase.failedContents,\n ...documentPhase.failedContents,\n ...deferredPhase.failedContents,\n ];\n const metrics: ScrapeMetrics = {\n successful:\n webPhase.metrics.successful\n + redditPhase.metrics.successful\n + documentPhase.metrics.successful\n + deferredPhase.metrics.successful,\n failed:\n invalidEntries.length\n + webPhase.metrics.failed\n + redditPhase.metrics.failed\n + documentPhase.metrics.failed\n + deferredPhase.metrics.failed,\n totalCredits: webPhase.metrics.totalCredits,\n };\n\n await reporter.log('info', `Fetched ${metrics.successful} page(s), ${metrics.failed} failed`);\n\n if (successItems.length > 0) {\n await reporter.progress(80, 100, 'Running LLM extraction over fetched pages');\n }\n\n const { items: processedItems, llmErrors, llmAttempted } = await processItemsWithLlm(\n successItems,\n enhancedInstruction,\n clients.llmProcessor,\n reporter,\n );\n\n const contents = assembleContentEntries(processedItems, failedContents);\n const executionTime = Date.now() - startTime;\n\n mcpLog(\n 'info',\n `Completed: ${metrics.successful} successful, ${metrics.failed} failed, ${metrics.totalCredits} credits used`,\n 'scrape',\n );\n\n const llmSucceeded = llmAttempted > 0 && llmErrors < llmAttempted;\n const result = buildScrapeResponse(\n params,\n contents,\n metrics,\n llmErrors,\n executionTime,\n { llmAttempted, llmSucceeded },\n );\n\n if (metrics.successful === 0 && metrics.failed > 0) {\n return toolFailure(result.content);\n }\n\n return toolSuccess(result.content, result.structuredContent);\n}\n\nexport function registerScrapeLinksTool(server: MCPServer): void {\n server.tool(\n {\n name: 'scrape-links',\n title: 'Scrape Links',\n description:\n 'Fetch many URLs in parallel. With `extract` set, run per-URL structured LLM extraction (each page returns `## Source`, `## Matches` verbatim facts, `## Not found` gaps, `## Follow-up signals` new terms + referenced URLs); omit `extract` for raw mode (cleaned markdown per URL, no LLM pass). Auto-detects reddit.com post permalinks \u2192 Reddit API (threaded post + comments); PDF/DOCX/PPTX/XLSX \u2192 Jina Reader; everything else \u2192 HTTP scraper. Safe to call in parallel \u2014 group URLs by context rather than jamming unrelated batches together. Describe the SHAPE of what you want in `extract`, facets separated by `|` (e.g. `root cause | affected versions | fix | workarounds | timeline`).',\n schema: scrapeLinksParamsSchema,\n outputSchema: scrapeLinksOutputSchema,\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n destructiveHint: false,\n openWorldHint: true,\n },\n },\n async (args, ctx) => {\n const reporter = createToolReporter(ctx, 'scrape-links');\n const result = await handleScrapeLinks(args, reporter);\n\n await reporter.progress(100, 100, result.isError ? 'Scrape failed' : 'Scrape complete');\n return toToolResponse(result);\n },\n );\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["failedContents"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-researchpowerpack",
3
- "version": "6.0.11",
3
+ "version": "6.0.12",
4
4
  "description": "HTTP-first MCP research server: start-research (goal-tailored brief), web-search (with Reddit scope), scrape-links (auto-detects Reddit URLs) — built on mcp-use.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",