@v1nvn/readability-mcp 0.17.0 → 0.19.0

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/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/sampling.ts","../src/tools/chunk_text.ts","../src/policy/explain.ts","../src/tools/explain.ts","../src/policy/sibling-scan.ts","../src/policy/grid-detector.ts","../src/tools/extract_grid.ts","../src/tools/extract_links.ts","../src/policy/list-detector.ts","../src/tools/extract_list.ts","../src/tools/extract_metadata.ts","../src/policy/outline.ts","../src/policy/section.ts","../src/tools/extract_section.ts","../src/tools/extract_tables.ts","../src/tools/html_to_markdown.ts","../src/tools/outline.ts","../src/server.ts","../src/index.ts"],"sourcesContent":["// Optional, capability-gated features backed by the HOST's model via MCP\n// `sampling/createMessage` (server→client request). The server never embeds a\n// model and never calls a provider directly — every LLM call is delegated to\n// the connected client, which picks the model and may prompt the user first.\n\nimport { z } from 'zod';\n\nimport type { ToolHandle } from './server.js';\n\nimport { toErrorResult } from './errors.js';\nimport { logger } from './logger.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\nconst SUMMARIZE_SYSTEM_PROMPT =\n 'Summarize the user-supplied text concisely while preserving its key points, entities, and any decisive conclusions. Output only the summary prose — no preamble, no headings unless the source had them.';\n\nconst summarizeInputShape = {\n text: z\n .string()\n .describe(\n 'The markdown or text to summarize — typically the output of `extract`, `extract_section`, `html_to_markdown`, or `chunk_text`. Passed through to the host model verbatim; the server does not parse or modify it.',\n ),\n maxTokens: z\n .number()\n .int()\n .min(1)\n .describe(\n 'Upper bound on the summary length in tokens, forwarded to the host as `sampling/createMessage` maxTokens. The host chooses the actual length.',\n )\n .default(512),\n} as const;\n\nconst summarizeInputSchema = z.object(summarizeInputShape);\n\nexport const SUMMARIZE_TOOL_DESCRIPTION = `Summarize text using the HOST's model via MCP \\`sampling/createMessage\\` — the server embeds no model and calls no provider directly. Hand it the output of \\`extract\\`, \\`extract_section\\`, \\`html_to_markdown\\`, or any markdown/text string; the host picks the model and may ask the user to approve the sampling request (human-in-the-loop per MCP). The tool is only listed when the connected client advertises the sampling capability.`;\n\nasync function summarizeWithHost(\n server: McpServer,\n args: { maxTokens: number; text: string },\n): Promise<string> {\n const result = await server.server.createMessage({\n messages: [\n {\n role: 'user',\n content: { type: 'text', text: args.text },\n },\n ],\n systemPrompt: SUMMARIZE_SYSTEM_PROMPT,\n maxTokens: args.maxTokens,\n });\n if (result.content.type !== 'text') {\n throw new Error(\n `host sampling returned non-text content (${result.content.type}); summarize expects a text response`,\n );\n }\n return result.content.text;\n}\n\nexport function registerSummarizeTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'summarize',\n {\n title: 'Summarize text using the host model',\n description: SUMMARIZE_TOOL_DESCRIPTION,\n inputSchema: summarizeInputShape,\n },\n async (rawArgs: unknown): Promise<CallToolResult> => {\n const args = summarizeInputSchema.parse(rawArgs);\n try {\n const summary = await summarizeWithHost(server, args);\n return {\n content: [{ type: 'text', text: summary }],\n };\n } catch (err) {\n logger.error(\n `summarize failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n return toErrorResult(err);\n }\n },\n );\n}\n\n// Mirrors registerTools/registerResources so the dev reload\n// loop and capability gate can treat sampling as one registration family.\nexport function registerSamplingTools(server: McpServer): ToolHandle[] {\n return [registerSummarizeTool(server)];\n}\n","import type { Chunk } from '../policy/chunk.js';\nimport type { ToolHandle } from '../server.js';\n\nimport { toErrorResult } from '../errors.js';\nimport { logger } from '../logger.js';\nimport { chunkMarkdown } from '../policy/chunk.js';\nimport { chunkTextOutputShape } from './output-schema.js';\nimport { chunkTextInputSchema, chunkTextInputShape } from './schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\n// One numbered section per chunk, heading context in brackets when present, so\n// content[0].text is always scannable without unpacking structuredContent.\nfunction renderChunkIndex(chunks: readonly Chunk[]): string {\n if (chunks.length === 0) {\n return '(no chunks emitted — input had no non-whitespace content)';\n }\n return chunks\n .map(chunk => {\n const head = chunk.headingContext ? ` [${chunk.headingContext}]` : '';\n return `## Chunk ${chunk.index}${head}\\n\\n${chunk.text}`;\n })\n .join('\\n\\n');\n}\n\nexport function chunkTextDocument(rawArgs: unknown): CallToolResult {\n const args = chunkTextInputSchema.parse(rawArgs);\n const { text, maxTokens, overlap, strategy } = args;\n const chunks = chunkMarkdown(text, { maxTokens, overlap, strategy });\n const content = renderChunkIndex(chunks);\n return {\n content: [{ text: content, type: 'text' }],\n structuredContent: {\n schemaVersion: 1,\n content,\n chunks,\n },\n };\n}\n\nexport const CHUNK_TEXT_TOOL_DESCRIPTION = `Split already-extracted text into token-bounded chunks for embedding/RAG. Each chunk carries its index, tokenCount (chars/4), and the nearest preceding markdown heading as headingContext. Operates on any text — pair with the \\`chunk\\` option on \\`extract\\` when you want chunks inline with the extraction. The server fetches nothing: \\`text\\` is the only input.`;\n\nexport function chunkTextHandler(args: unknown): CallToolResult {\n try {\n return chunkTextDocument(args);\n } catch (err) {\n logger.error(\n `chunk_text failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n return toErrorResult(err);\n }\n}\n\nexport function registerChunkTextTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'chunk_text',\n {\n title: 'Chunk text for RAG/embedding',\n description: CHUNK_TEXT_TOOL_DESCRIPTION,\n inputSchema: chunkTextInputShape,\n outputSchema: chunkTextOutputShape,\n },\n chunkTextHandler,\n );\n}\n","import { Readability } from '@mozilla/readability';\n\nimport type { GatingSignal } from './gating.js';\nimport type { PaginationSignal } from './pagination.js';\n\nimport { buildDocument } from '../pipeline/dom.js';\nimport {\n applySelectors,\n normalizeDocument,\n resolveLazyImages,\n type SelectorScope,\n} from '../pipeline/normalize.js';\nimport { isReaderable } from '../pipeline/readability.js';\nimport { assembleDiagnostics } from './diagnostics.js';\nimport { detectGating } from './gating.js';\nimport { detectPagination } from './pagination.js';\nimport { resolveReadabilityOptions } from './resolver.js';\n\n// Readability stamps `{ contentScore }` on candidate DOM nodes under a\n// `readability` expando (Readability.js:894/1272/1288). The property is\n// untyped upstream, so reach it through `unknown` rather than a guessed shape.\ninterface ReadabilityExpando {\n readonly contentScore?: number;\n}\n\nexport interface ExplainCandidate {\n readonly className: string;\n readonly id: string;\n readonly score: number;\n readonly selector: string;\n readonly tag: string;\n readonly textLength: number;\n}\n\nexport interface ExplainRemovedNodes {\n readonly boilerplate: number;\n readonly chrome: number;\n readonly total: number;\n}\n\nexport interface ExplainSnapshot {\n readonly html: string;\n readonly truncated: boolean;\n}\n\nexport interface ExplainReport {\n readonly candidates: readonly ExplainCandidate[];\n readonly chosenRoot: ExplainCandidate | null;\n readonly fallbackUsed: boolean;\n readonly gating: GatingSignal | undefined;\n readonly pagination: PaginationSignal | undefined;\n readonly parseSucceeded: boolean;\n readonly readerable: boolean;\n readonly removedNodes: ExplainRemovedNodes;\n readonly snapshot: ExplainSnapshot;\n}\n\nexport interface BuildExplainOptions {\n readonly baseUrl?: string;\n readonly html: string;\n readonly selectors?: Readonly<SelectorScope>;\n readonly snapshotMaxChars?: number;\n readonly topN?: number;\n}\n\nconst DEFAULT_SNAPSHOT_MAX = 4000;\nconst DEFAULT_TOP_N = 5;\n\nfunction describeSelector(parts: {\n className: string;\n id: string;\n tag: string;\n}): string {\n // A CSS-ish hint for the host, not a unique locator — Readability's expando is\n // a JS-only property invisible to CSS, and we deliberately avoid an nth-child\n // chain that would be brittle against the host's live DOM. Tag + id + first\n // two classes is enough for a human to pick the node out of a small candidate\n // list.\n const idPart = parts.id ? `#${parts.id}` : '';\n const cls = parts.className\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .slice(0, 2)\n .join('.');\n const classPart = cls ? `.${cls}` : '';\n return `${parts.tag.toLowerCase()}${idPart}${classPart}`;\n}\n\nfunction readScored(el: Element): ExplainCandidate | null {\n const stamp = (el as unknown as { readability?: ReadabilityExpando })\n .readability;\n const score = stamp?.contentScore;\n if (typeof score !== 'number') {\n return null;\n }\n const className = typeof el.className === 'string' ? el.className : '';\n const candidate = {\n className,\n id: el.id,\n score,\n tag: el.tagName,\n textLength: el.textContent.trim().length,\n };\n return { ...candidate, selector: describeSelector(candidate) };\n}\n\nfunction truncateSnapshot(html: string, max: number): ExplainSnapshot {\n if (html.length <= max) {\n return { html, truncated: false };\n }\n return { html: html.slice(0, max), truncated: true };\n}\n\nexport function buildExplainReport(\n options: Readonly<BuildExplainOptions>,\n): ExplainReport {\n const {\n html,\n selectors,\n snapshotMaxChars = DEFAULT_SNAPSHOT_MAX,\n topN = DEFAULT_TOP_N,\n baseUrl,\n } = options;\n\n const { document, window } = buildDocument(html, baseUrl);\n\n // Mirror the extract pipeline's ordering: gating detection must precede\n // normalization (chrome stripping would remove the overlay), and pagination\n // detection runs before applySelectors (a caller's include could hide the\n // sentinel but not the \"more content exists\" signal).\n const gating = detectGating(document);\n const documentElementCount = document.querySelectorAll('*').length;\n const normalizeCounts = normalizeDocument(document);\n resolveLazyImages(document);\n const pagination = detectPagination(document, baseUrl);\n applySelectors(document, selectors);\n\n const snapshot = truncateSnapshot(document.body.innerHTML, snapshotMaxChars);\n\n const readerable = isReaderable(document);\n const readabilityOptions = resolveReadabilityOptions({});\n\n // Clone so the normalized doc (and the snapshot above) is preserved untouched\n // — Readability restructures its input during parse.\n const clone = document.cloneNode(true) as Document;\n // Grab node references before parse: Readability detaches scored candidates\n // from the live tree while restructuring, so a post-parse querySelectorAll\n // finds almost nothing. The object refs held here keep their `readability`\n // stamps after being detached — that retention is what surfaces the real\n // per-candidate scores without forking the library or parsing its debug log.\n const heldNodes = Array.from(clone.querySelectorAll('*'));\n\n const reader = new Readability(clone, readabilityOptions);\n const article = reader.parse();\n const parseSucceeded = !!article?.content;\n\n const scored: ExplainCandidate[] = [];\n for (const el of heldNodes) {\n const entry = readScored(el);\n if (entry) {\n scored.push(entry);\n }\n }\n scored.sort((a, b) => b.score - a.score);\n\n const candidates = scored.slice(0, Math.max(0, topN));\n const chosenRoot = scored[0] ?? null;\n\n const diagnostics = assembleDiagnostics({\n articleHtml: article?.content ?? '',\n boilerplateRemoved: normalizeCounts.boilerplateRemoved,\n chromeRemoved: normalizeCounts.chromeRemoved,\n documentElementCount,\n extractedNode: 'readability',\n fallbackUsed: false,\n gated: gating,\n pagination,\n readerable,\n window,\n });\n\n return {\n candidates,\n chosenRoot,\n fallbackUsed: diagnostics.fallbackUsed,\n gating: diagnostics.gated,\n pagination: diagnostics.pagination,\n parseSucceeded,\n readerable: diagnostics.readerable ?? false,\n removedNodes: {\n boilerplate: diagnostics.boilerplateRemoved ?? 0,\n chrome: diagnostics.chromeRemoved ?? 0,\n total: diagnostics.removedNodes ?? 0,\n },\n snapshot,\n };\n}\n","import { z } from 'zod';\n\nimport type { ExplainReport } from '../policy/explain.js';\nimport type { ToolHandle } from '../server.js';\n\nimport { toErrorResult } from '../errors.js';\nimport { logger } from '../logger.js';\nimport { buildExplainReport } from '../policy/explain.js';\nimport { readHtmlFile } from './html-source.js';\nimport {\n type FromHtmlInput,\n localPathField,\n selectorsSchema,\n} from './schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\n// Schemas live with the tool rather than in schemas.ts/output-schema.ts to keep\n// the explain surface in one file (the report shape is explain-specific and not\n// shared with other tools). `selectorsSchema` is reused verbatim from extract so\n// the two tools agree on the include/exclude contract.\nconst explainInputShape = {\n localPath: localPathField,\n baseUrl: z\n .url()\n .describe(\n 'Base URL for absolutizing relative links during pagination/gating detection. NEVER fetched — origin context only.',\n )\n .optional(),\n selectors: selectorsSchema,\n topN: z\n .number()\n .int()\n .min(1)\n .max(20)\n .describe(\n 'Maximum number of scored candidate nodes to return (highest first). Default 5.',\n )\n .default(5),\n} as const;\n\nconst explainInputSchema = z.object(explainInputShape);\n\ntype ExplainInput = z.infer<typeof explainInputSchema>;\ntype ExplainFromHtmlInput = FromHtmlInput<ExplainInput>;\n\n// Schema defaults for callers that pass only a subset of the knobs (topN).\nconst DEFAULTS: Omit<ExplainInput, 'localPath'> = explainInputSchema.parse({\n localPath: '',\n});\n\nconst candidateSchema = z\n .object({\n className: z\n .string()\n .describe(\n \"The candidate's class attribute (raw, unsplit). Empty string when absent.\",\n ),\n id: z.string().describe(\"The candidate's id attribute, or empty string.\"),\n score: z\n .number()\n .describe(\n \"Readability's actual contentScore for this node (link-density-scaled). Higher is better; the top entry is Readability's raw top candidate before its parent-walking/only-child adjustments.\",\n ),\n selector: z\n .string()\n .describe(\n \"A CSS-ish hint (tag#id.class1.class2) for locating the node in the host DOM. NOT a unique locator — Readability's score lives on a JS expando invisible to CSS.\",\n ),\n tag: z\n .string()\n .describe('Uppercase DOM tag name (e.g. \"ARTICLE\", \"MAIN\", \"DIV\").'),\n textLength: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Trimmed textContent length of the candidate node, for eyeballing content density.',\n ),\n })\n .describe(\n 'One scored candidate node Readability considered, with its real contentScore.',\n );\n\nconst explainOutputShape = {\n schemaVersion: z\n .literal(1)\n .describe(\n 'Structured-content schema version. Bumps only on breaking shape changes to this object.',\n ),\n content: z\n .string()\n .describe(\n 'Readable rendering of the report (chosen root, ranked candidates, removal counts, gating/pagination, snapshot head) so content[0].text is always scannable.',\n ),\n chosenRoot: candidateSchema\n .nullable()\n .describe(\n 'The highest-scoring candidate Readability computed — its raw top pick before parent-walking/only-child post-processing. Null when Readability scored nothing (e.g. empty input).',\n ),\n candidates: z\n .array(candidateSchema)\n .describe(\n \"Scored candidate nodes (highest first), capped at topN. These are Readability's real contentScore values, not a self-computed heuristic.\",\n ),\n readerable: z\n .boolean()\n .describe(\n 'Readability isProbablyReaderable verdict on the normalized document.',\n ),\n parseSucceeded: z\n .boolean()\n .describe(\n 'True when reader.parse() returned article content. False signals that `extract` would fall back to its selector cascade.',\n ),\n fallbackUsed: z\n .boolean()\n .describe(\n 'Always false for explain — this tool runs only the Readability path it is diagnosing, never the fallback cascade.',\n ),\n gating: z\n .object({\n likely: z\n .boolean()\n .describe(\n 'True when heuristics strongly suggest the content is paywalled or truncated.',\n ),\n reason: z\n .string()\n .describe(\n 'Short label naming the detected signal (e.g. \"paywall overlay\").',\n ),\n })\n .nullable()\n .describe(\n 'Likely paywall / gating signal detected before normalization. Null when none.',\n ),\n pagination: z\n .object({\n type: z\n .enum(['infinite', 'paginated'])\n .describe('Kind of pagination signal detected.'),\n nextUrl: z\n .string()\n .optional()\n .describe(\n 'Absolute URL of the detected next page (paginated only). Never fetched.',\n ),\n selector: z\n .string()\n .optional()\n .describe(\n 'CSS selector of the load-more / infinite-scroll sentinel (infinite only).',\n ),\n })\n .nullable()\n .describe('Detected pagination / infinite-scroll signal. Null when none.'),\n removedNodes: z\n .object({\n boilerplate: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Boilerplate blocks (related-posts, newsletter signup) stripped before Readability.',\n ),\n chrome: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Browser-chrome nodes stripped (scrollbars, consent banners, overlays).',\n ),\n total: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Net element count removed across the whole pipeline (delta vs. the parsed document).',\n ),\n })\n .describe(\n 'Breakdown of nodes removed before Readability saw the document, reused from the extract diagnostics path.',\n ),\n snapshot: z\n .object({\n html: z\n .string()\n .describe(\n 'The sanitized-by-normalization (post chrome/boilerplate/script strip) HTML fed to Readability — \"what Readability saw\". Not DOMPurify-sanitized (that runs on Readability\\'s output in `extract`), so it may still carry inline event handlers (`onerror`/`onclick`/…); it is diagnostic data — do not render verbatim.',\n ),\n truncated: z\n .boolean()\n .describe(\n 'True when the snapshot was cut at snapshotMaxChars (default 4000).',\n ),\n })\n .describe('Pre-Readability HTML snapshot of the normalized document body.'),\n} as const;\n\nconst explainOutput = z.object(explainOutputShape);\n\nfunction formatGating(report: ExplainReport): string {\n const g = report.gating;\n if (!g) {\n return 'none';\n }\n return `${g.reason}${g.likely ? '' : ' (weak)'}`;\n}\n\nfunction formatPagination(report: ExplainReport): string {\n const p = report.pagination;\n if (!p) {\n return 'none';\n }\n if (p.type === 'paginated') {\n return `paginated -> ${p.nextUrl ?? '(no href)'}`;\n }\n return `infinite (${p.selector ?? 'sentinel'})`;\n}\n\nfunction renderText(report: ExplainReport): string {\n const lines: string[] = [];\n const root = report.chosenRoot;\n lines.push(\n `readerable: ${report.readerable ? 'yes' : 'no'} parse: ${report.parseSucceeded ? 'ok' : 'fail'} fallback: ${report.fallbackUsed ? 'yes' : 'no'}`,\n );\n lines.push(\n `chosen root: ${root ? `${root.selector} (score ${root.score.toFixed(2)}, ${root.textLength} chars)` : '(no candidate scored)'}`,\n );\n lines.push(`top candidates (${report.candidates.length}):`);\n if (report.candidates.length === 0) {\n lines.push(' (none)');\n }\n report.candidates.forEach((c, i) => {\n lines.push(\n ` ${i + 1}. ${c.selector} score ${c.score.toFixed(2)} (${c.textLength} chars)`,\n );\n });\n const r = report.removedNodes;\n lines.push(\n `removed: total=${r.total} (chrome=${r.chrome}, boilerplate=${r.boilerplate})`,\n );\n lines.push(`gating: ${formatGating(report)}`);\n lines.push(`pagination: ${formatPagination(report)}`);\n lines.push(\n `snapshot (${report.snapshot.html.length} chars${report.snapshot.truncated ? ', truncated' : ''}):`,\n );\n lines.push(report.snapshot.html);\n return lines.join('\\n');\n}\n\nexport function explain(rawArgs: unknown): CallToolResult {\n const { localPath, ...rest } = explainInputSchema.parse(rawArgs);\n return explainFromHtml({ html: readHtmlFile(localPath), ...rest });\n}\n\nexport function explainFromHtml(\n input: Readonly<ExplainFromHtmlInput>,\n): CallToolResult {\n const { html, baseUrl, selectors, topN } = { ...DEFAULTS, ...input };\n const report = buildExplainReport({\n html,\n selectors,\n topN,\n baseUrl,\n });\n const content = renderText(report);\n return {\n content: [{ text: content, type: 'text' }],\n structuredContent: {\n schemaVersion: 1,\n content,\n chosenRoot: report.chosenRoot,\n candidates: report.candidates,\n readerable: report.readerable,\n parseSucceeded: report.parseSucceeded,\n fallbackUsed: report.fallbackUsed,\n gating: report.gating ?? null,\n pagination: report.pagination ?? null,\n removedNodes: report.removedNodes,\n snapshot: report.snapshot,\n },\n };\n}\n\nexport const EXPLAIN_TOOL_DESCRIPTION = `Post-mortem diagnostics for extraction: shows WHY Readability picked what it picked. Returns the chosen root, the ranked candidate nodes with their REAL Readability contentScore values (read off the DOM expando Readability stamps during scoring), a categorized removed-nodes breakdown, gating/pagination signals, and a snapshot of the normalized HTML fed to Readability. Runs the same normalize + Readability pipeline as \\`extract\\` (no fallback cascade, no Turndown). The server fetches nothing: \\`localPath\\` is the only source, and \\`baseUrl\\` (optional) is origin context only.`;\n\nexport function explainHandler(args: unknown): CallToolResult {\n try {\n return explain(args);\n } catch (err) {\n logger.error(\n `explain failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n return toErrorResult(err);\n }\n}\n\nexport function registerExplainTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'explain',\n {\n title: 'Explain why Readability picked what it picked',\n description: EXPLAIN_TOOL_DESCRIPTION,\n inputSchema: explainInputShape,\n outputSchema: explainOutput,\n },\n explainHandler,\n );\n}\n","// Shared pre-pass for the sibling-cluster detectors (list-detector,\n// grid-detector). Both walk the document for a container whose direct children\n// form a same-shape sibling group; the constants and helpers here are the\n// heuristic-agnostic infrastructure that scan shares. Detector-specific scoring\n// (anchor/pathname signals vs data-cell text) stays in each detector.\n\n// Direct children are scanned only at these container levels. <tbody> covers\n// HN's <table>-based layout; the rest cover semantic (<main>/<article>) and\n// generic (<div>/<section>) wrappers. <table> is included for HTML that skips\n// <tbody>; jsdom synthesizes one anyway, so this is mostly defensive.\nexport const CONTAINER_TAGS = new Set([\n 'ARTICLE',\n 'DIV',\n 'MAIN',\n 'OL',\n 'SECTION',\n 'TABLE',\n 'TBODY',\n 'UL',\n]);\n\n// Subtrees that never carry a page's primary repeated structure. Stripping them\n// is the false-positive guard: without it, an article's <nav> menu or footer\n// link list would look identical to a feed/grid and the detectors would mis-fire\n// on every page with chrome. Distinct from normalize.ts's stripChrome (which\n// removes consent/overlay banners) — this is landmark chrome only.\nconst LANDMARK_CHROME_SELECTOR =\n 'nav, header, footer, aside, [role=\"navigation\"], [role=\"banner\"], ' +\n '[role=\"contentinfo\"], [role=\"complementary\"], [role=\"search\"], ' +\n '[role=\"menu\"], [role=\"menubar\"]';\n\nexport function stripLandmarkChrome(document: Document): void {\n for (const el of document.querySelectorAll(LANDMARK_CHROME_SELECTOR)) {\n el.remove();\n }\n for (const el of document.querySelectorAll('script, style, template')) {\n el.remove();\n }\n}\n\n// Composite of tag + class signature so mixed siblings — e.g. HN's\n// <tr class=\"athing\"> title row vs classless <tr> subtext row, or a grid's\n// header row vs data rows — split into separate candidate groups instead of\n// blending into one ragged group.\nexport function shapeKey(el: Element): string {\n const raw = el.getAttribute('class');\n if (!raw) {\n return el.tagName;\n }\n const normalized = raw.trim().split(/\\s+/).sort().join(' ');\n return normalized ? `${el.tagName}|${normalized}` : el.tagName;\n}\n\n// tag#id.class hint for the winning container. Best-effort, not a unique\n// locator — used only for diagnostics so a human can see which subtree won.\nexport function describeSelector(el: Element): string {\n const parts = [el.tagName.toLowerCase()];\n const id = el.getAttribute('id');\n if (id) {\n parts.push(`#${id}`);\n }\n const cls = el.getAttribute('class');\n if (cls) {\n for (const token of cls.trim().split(/\\s+/)) {\n if (token) {\n parts.push(`.${token}`);\n }\n }\n }\n return parts.join('');\n}\n","import { isElement } from '../pipeline/dom.js';\nimport { resolveCellText } from './cell-text.js';\nimport {\n CONTAINER_TAGS,\n describeSelector,\n shapeKey,\n stripLandmarkChrome,\n} from './sibling-scan.js';\n\nexport interface GridRow {\n readonly cells: readonly string[];\n}\n\nexport type GridConfidence = 'high' | 'low' | 'medium';\n\nexport interface GridDetectionResult {\n readonly colCount: number;\n readonly confidence: GridConfidence;\n readonly containerSelector: string;\n readonly detected: boolean;\n readonly note: string;\n readonly rowCount: number;\n readonly rows: readonly GridRow[];\n readonly rowTag: string;\n}\n\nexport interface DetectGridOptions {\n readonly cellSelector?: string;\n readonly minRows?: number;\n readonly rowSelector?: string;\n}\n\nconst MIN_ROWS = 3;\nconst HIGH_CONF_ROWS = 6;\nconst MIN_CELLS_PER_ROW = 2;\n\nfunction rowCells(row: Element): string[] {\n return Array.from(row.children).map(resolveCellText);\n}\n\nfunction notDetected(note: string): GridDetectionResult {\n return {\n confidence: 'low',\n containerSelector: '',\n detected: false,\n rowCount: 0,\n colCount: 0,\n rows: [],\n rowTag: '',\n note,\n };\n}\n\n// Confidence tracks the detected cluster (the data-row count), not any header\n// row inferred on top of it — a recovered header is inference, not evidence, so\n// it must not push a grid past HIGH_CONF_ROWS. Both detection modes gate on\n// minRows before reaching buildResult, so this is only ever called with a\n// data-row count >= minRows; `low` comes solely from notDetected().\nfunction confidenceFor(dataRowCount: number): GridConfidence {\n return dataRowCount >= HIGH_CONF_ROWS ? 'high' : 'medium';\n}\n\ninterface GridCandidate {\n readonly container: Element;\n readonly containerSelector: string;\n readonly memberCount: number;\n readonly members: readonly Element[];\n readonly rows: readonly string[][];\n readonly rowTag: string;\n readonly totalCellText: number;\n}\n\n// Pad ragged rows to the max cell width so the matrix is rectangular — the\n// GFM/CSV/JSON renderers in policy/tables.ts assume a dense grid (the\n// delimiter row is derived from row-0 width, so a short row would mis-align\n// every column). Mirrors parseTableMatrix's dense-pad step.\n// `evidence` separates the detected cluster from the emitted matrix: rowCount\n// counts every emitted row (data plus any recovered header), but confidence and\n// the \"detected N\" note track the data-row cluster alone, since the header is\n// inferred rather than detected. Omitted in selector mode and when auto-detect\n// recovers no header, where the two counts coincide.\nfunction buildResult(\n raggedRows: readonly string[][],\n rowTag: string,\n containerSelector: string,\n selectorHint?: string,\n evidence?: { readonly dataRowCount: number; readonly headerCount: number },\n): GridDetectionResult {\n let maxCols = 0;\n for (const row of raggedRows) {\n if (row.length > maxCols) {\n maxCols = row.length;\n }\n }\n if (maxCols === 0) {\n return notDetected(\n selectorHint\n ? `not a grid: rows matched ${selectorHint} but no cells were found`\n : 'not a grid: rows matched but no cells were found',\n );\n }\n const rowCount = raggedRows.length;\n const dataRowCount = evidence?.dataRowCount ?? rowCount;\n const headerCount = evidence?.headerCount ?? 0;\n const rows: GridRow[] = raggedRows.map(row => ({\n cells: Array.from({ length: maxCols }, (_, i) => row[i] ?? ''),\n }));\n const where = containerSelector || selectorHint || 'document';\n const descriptor =\n headerCount > 0\n ? `detected ${dataRowCount} ${rowTag} data rows plus ${headerCount} header row${headerCount > 1 ? 's' : ''} (${maxCols} cols) in ${where}`\n : `detected ${dataRowCount} ${rowTag} rows (${maxCols} cols) in ${where}`;\n return {\n confidence: confidenceFor(dataRowCount),\n containerSelector,\n detected: true,\n rowCount,\n colCount: maxCols,\n rows,\n rowTag,\n note: descriptor,\n };\n}\n\nfunction detectSelectorMode(\n document: Document,\n rowSelector: string,\n cellSelector: string,\n minRows: number,\n): GridDetectionResult {\n const rowEls = Array.from(document.querySelectorAll(rowSelector));\n if (rowEls.length < minRows) {\n return notDetected(\n `not a grid: rowSelector \"${rowSelector}\" matched ${rowEls.length} row(s) (min ${minRows})`,\n );\n }\n const rows = rowEls.map(row =>\n Array.from(row.querySelectorAll(cellSelector)).map(resolveCellText),\n );\n return buildResult(rows, rowEls[0].tagName, '', rowSelector);\n}\n\nfunction modalWidth(members: readonly Element[]): number {\n const counts = new Map<number, number>();\n for (const member of members) {\n const width = member.children.length;\n counts.set(width, (counts.get(width) ?? 0) + 1);\n }\n let best = members[0].children.length;\n let bestCount = 0;\n for (const [width, count] of counts) {\n if (count > bestCount) {\n best = width;\n bestCount = count;\n }\n }\n return best;\n}\n\nfunction classTokens(el: Element): Set<string> {\n const cls = el.getAttribute('class');\n return cls ? new Set(cls.trim().split(/\\s+/)) : new Set();\n}\n\n// A div-grid header has no <th>, so recovery leans on two signals, strongest\n// first. (1) An ARIA grid header — role=\"row\" with a columnheader child — is a\n// semantic role and reliable on its own. (2) Class kinship with the data\n// cluster: a real header is the data rows' own class plus a discriminator\n// (\"est-header\"), so it carries every data class token. Chrome such as\n// \"card-header\" / \"page-header\" shares no class with the data rows and must not\n// be promoted — which is why we match token ownership, never the \"header\"\n// substring. Substring matching is what split the header into its own shape\n// group to begin with, and it over-fires on any chrome whose class merely\n// contains the word.\nfunction looksLikeHeader(\n child: Element,\n rowTag: string,\n dataClass: Set<string>,\n dataWidth: number,\n): boolean {\n if (child.tagName !== rowTag || child.children.length !== dataWidth) {\n return false;\n }\n if (\n child.getAttribute('role') === 'row' &&\n Array.from(child.children).some(\n c => c.getAttribute('role') === 'columnheader',\n )\n ) {\n return true;\n }\n const childClass = classTokens(child);\n return (\n dataClass.size > 0 && [...dataClass].every(token => childClass.has(token))\n );\n}\n\n// A header row often carries an extra class token (e.g. \"est-header\") that\n// splits it into its own 1-member shape group, below the minRows cutoff, so the\n// data-row cluster wins on its own and the header is dropped — leaving the\n// first data row mis-read as the header and the real header lost. Recover it:\n// scan the winning container's children that precede the first data row and\n// prepend any sibling that looks like a header for this cluster. Children after\n// the first data row (totals/footer) are left alone.\nfunction collectHeaderRows(\n container: Element,\n members: readonly Element[],\n): string[][] {\n const memberSet = new Set<Element>(members);\n const rowTag = members[0].tagName;\n const dataWidth = modalWidth(members);\n // shapeKey freezes tag + class signature, so every member shares one class\n // set and members[0] represents it; child count is not part of shapeKey, so\n // widths can vary within the group (hence modalWidth above).\n const dataClass = classTokens(members[0]);\n const headers: string[][] = [];\n for (const child of Array.from(container.children)) {\n if (memberSet.has(child)) {\n break;\n }\n if (looksLikeHeader(child, rowTag, dataClass, dataWidth)) {\n headers.push(rowCells(child));\n }\n }\n return headers;\n}\n\nfunction detectAuto(document: Document, minRows: number): GridDetectionResult {\n stripLandmarkChrome(document);\n\n const candidates: GridCandidate[] = [];\n for (const container of document.querySelectorAll('*')) {\n if (!CONTAINER_TAGS.has(container.tagName)) {\n continue;\n }\n // Group direct element-children by shape so a homogeneous row cluster\n // surfaces as one candidate and mixed-shape siblings (header row vs data\n // rows) split apart rather than blending into a ragged group.\n const groups = new Map<string, Element[]>();\n for (const child of Array.from(container.childNodes)) {\n if (!isElement(child)) {\n continue;\n }\n const key = shapeKey(child);\n const bucket = groups.get(key);\n if (bucket) {\n bucket.push(child);\n } else {\n groups.set(key, [child]);\n }\n }\n for (const members of groups.values()) {\n if (members.length < minRows) {\n continue;\n }\n // A row needs at least two cells to be a row; a group of single-child\n // wrappers is a list of links/labels, not a data grid.\n if (!members.every(m => m.children.length >= MIN_CELLS_PER_ROW)) {\n continue;\n }\n const rows = members.map(rowCells);\n const totalCellText = rows.reduce(\n (sum, row) => sum + row.reduce((s, c) => s + c.length, 0),\n 0,\n );\n candidates.push({\n container,\n containerSelector: describeSelector(container),\n members,\n rows,\n rowTag: members[0].tagName,\n memberCount: members.length,\n totalCellText,\n });\n }\n }\n\n if (candidates.length === 0) {\n return notDetected(\n 'not a grid: no repeating row structure (≥3 same-shape siblings each with ≥2 direct element-children, outside nav/header/footer/aside)',\n );\n }\n\n // Most rows wins; ties break on total cell text so a sparse 6-row grid beats\n // a dense 6-row one only when it carries more substance, and a longer\n // analyst-estimates table beats a stub sidebar mini-grid on both axes.\n let winner = candidates[0];\n for (let i = 1; i < candidates.length; i++) {\n const candidate = candidates[i];\n if (\n candidate.memberCount > winner.memberCount ||\n (candidate.memberCount === winner.memberCount &&\n candidate.totalCellText > winner.totalCellText)\n ) {\n winner = candidate;\n }\n }\n const headerRows = collectHeaderRows(winner.container, winner.members);\n return buildResult(\n [...headerRows, ...winner.rows],\n winner.rowTag,\n winner.containerSelector,\n undefined,\n { dataRowCount: winner.memberCount, headerCount: headerRows.length },\n );\n}\n\n// Detect a CSS-grid/div \"table\" — the div equivalent of extract_tables. In\n// selector mode (both rowSelector and cellSelector given) the rows and cells\n// are whatever the caller names. In auto mode, chrome is stripped first and\n// the container whose direct children form the largest same-shape sibling\n// group (each member a row of ≥2 direct element-children) wins. The resulting\n// rows are padded to a dense rectangular matrix so the shared table renderer\n// can serialize them as gfm/csv/json.\nexport function detectGrid(\n document: Document,\n opts?: DetectGridOptions,\n): GridDetectionResult {\n const minRows = opts?.minRows ?? MIN_ROWS;\n if (opts?.rowSelector && opts.cellSelector) {\n return detectSelectorMode(\n document,\n opts.rowSelector,\n opts.cellSelector,\n minRows,\n );\n }\n return detectAuto(document, minRows);\n}\n","import type { ToolHandle } from '../server.js';\n\nimport { toErrorResult } from '../errors.js';\nimport { logger } from '../logger.js';\nimport { buildDocument } from '../pipeline/dom.js';\nimport { applySelectors } from '../pipeline/normalize.js';\nimport { detectGrid } from '../policy/grid-detector.js';\nimport { renderTable } from '../policy/tables.js';\nimport { readHtmlFile } from './html-source.js';\nimport { extractGridOutputShape } from './output-schema.js';\nimport {\n type ExtractGridFromHtmlInput,\n type ExtractGridInput,\n extractGridInputSchema,\n extractGridInputShape,\n} from './schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\ninterface GridEntry {\n readonly cols: number;\n readonly markdown: string;\n readonly rows: number;\n}\n\nconst NO_GRID = '(no repeating grid found)';\n\nexport function extractGrid(rawArgs: unknown): CallToolResult {\n const { localPath, ...rest } = extractGridInputSchema.parse(rawArgs);\n return extractGridFromHtml({ html: readHtmlFile(localPath), ...rest });\n}\n\n// Schema defaults for callers that pass only a subset of the knobs (format).\nconst DEFAULTS: Omit<ExtractGridInput, 'localPath'> =\n extractGridInputSchema.parse({ localPath: '' });\n\nexport function extractGridFromHtml(\n input: Readonly<ExtractGridFromHtmlInput>,\n): CallToolResult {\n const { html, baseUrl, format, selectors, rowSelector, cellSelector } = {\n ...DEFAULTS,\n ...input,\n };\n\n // Same raw-DOM rationale as extract_tables/extract_list: the repeating grid\n // lives in statically-rendered divs that Readability/normalize would discard\n // (nav/aside/boilerplate), and grid shape is unaffected by unsanitized\n // scripts/styles. Chrome stripping lives inside detectGrid (auto mode only)\n // so selector mode sees the page as captured.\n const { document } = buildDocument(html, baseUrl);\n applySelectors(document, selectors);\n const result = detectGrid(document, { rowSelector, cellSelector });\n\n let content = NO_GRID;\n let grid: GridEntry = { rows: 0, cols: 0, markdown: '' };\n if (result.detected && result.rows.length > 0) {\n const matrix = result.rows.map(row => [...row.cells]);\n const markdown = renderTable(matrix, format);\n content = markdown;\n grid = { rows: result.rowCount, cols: result.colCount, markdown };\n }\n return {\n content: [{ text: content, type: 'text' }],\n structuredContent: {\n schemaVersion: 1,\n content,\n grid,\n diagnostics: {\n confidence: result.confidence,\n containerSelector: result.containerSelector,\n detected: result.detected,\n rowCount: result.rowCount,\n colCount: result.colCount,\n rowTag: result.rowTag,\n note: result.note,\n },\n metadata: { baseUrl, format, detected: result.detected },\n },\n };\n}\n\nexport const EXTRACT_GRID_TOOL_DESCRIPTION = `Detect and extract a CSS-grid / div \"table\" from already-rendered (post-JavaScript) HTML — the div equivalent of \\`extract_tables\\` for SPAs that render data into repeating \\`<div>\\` rows instead of \\`<table>\\`. Supports auto-detect (find the container whose direct children form the largest same-shape sibling group of ≥3 rows, each row a set of ≥2 direct element-children) and explicit \\`rowSelector\\` + \\`cellSelector\\` selector mode (cells scoped to each row subtree). Renders the matrix through the SAME gfm/csv/json renderer as \\`extract_tables\\`. Runs no Readability, no Turndown, no sanitization — the server fetches nothing: \\`localPath\\` is the only source, and \\`baseUrl\\` (optional) is origin context only.`;\n\nexport function extractGridHandler(args: unknown): CallToolResult {\n try {\n return extractGrid(args);\n } catch (err) {\n logger.error(\n `extract_grid failed: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n return toErrorResult(err);\n }\n}\n\nexport function registerExtractGridTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'extract_grid',\n {\n title:\n 'Extract a CSS-grid / div table (the div equivalent of extract_tables)',\n description: EXTRACT_GRID_TOOL_DESCRIPTION,\n inputSchema: extractGridInputShape,\n outputSchema: extractGridOutputShape,\n },\n extractGridHandler,\n );\n}\n","import type { ToolHandle } from '../server.js';\n\nimport { toErrorResult } from '../errors.js';\nimport { logger } from '../logger.js';\nimport { buildDocument } from '../pipeline/dom.js';\nimport { applySelectors } from '../pipeline/normalize.js';\nimport { absolutize } from '../pipeline/urls.js';\nimport { readHtmlFile } from './html-source.js';\nimport { extractLinksOutputShape } from './output-schema.js';\nimport {\n type ExtractLinksFromHtmlInput,\n type ExtractLinksInput,\n extractLinksInputSchema,\n extractLinksInputShape,\n} from './schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\nexport interface ExtractedLink {\n readonly href: string;\n readonly isExternal: boolean;\n readonly rel: string;\n readonly text: string;\n}\n\nconst MAX_TEXT_LENGTH = 300;\n\nfunction clipText(raw: string): string {\n const collapsed = raw.replace(/\\s+/g, ' ').trim();\n return collapsed.length > MAX_TEXT_LENGTH\n ? `${collapsed.slice(0, MAX_TEXT_LENGTH)}…`\n : collapsed;\n}\n\n// Non-http(s) schemes (mailto/tel/javascript/data) have an opaque origin and\n// would otherwise compare as \"different\" — treat them as non-external so the\n// isExternal flag tracks real cross-origin navigation only.\nfunction isWebOrigin(parsed: URL): boolean {\n return parsed.protocol === 'http:' || parsed.protocol === 'https:';\n}\n\nfunction resolveExternal(absolutizedHref: string, baseUrl: string): boolean {\n let parsedHref: URL;\n try {\n parsedHref = new URL(absolutizedHref);\n } catch {\n return false;\n }\n if (!isWebOrigin(parsedHref)) {\n return false;\n }\n try {\n return new URL(baseUrl).origin !== parsedHref.origin;\n } catch {\n return false;\n }\n}\n\n// Drop <script>/<template> for safety (templates never render, scripts carry no\n// crawl value), but keep nav/footer/main — crawl-relevant links live there.\nfunction pruneUnsafeRoots(document: Document): void {\n for (const el of document.querySelectorAll('script, template')) {\n el.remove();\n }\n}\n\nexport function extractLinks(rawArgs: unknown): CallToolResult {\n const { localPath, ...rest } = extractLinksInputSchema.parse(rawArgs);\n return extractLinksFromHtml({ html: readHtmlFile(localPath), ...rest });\n}\n\n// Schema defaults for callers that pass only a subset of the knobs (sameOriginOnly).\nconst DEFAULTS: Omit<ExtractLinksInput, 'localPath'> =\n extractLinksInputSchema.parse({ localPath: '' });\n\nexport function extractLinksFromHtml(\n input: Readonly<ExtractLinksFromHtmlInput>,\n): CallToolResult {\n const { html, baseUrl, sameOriginOnly, selectors } = {\n ...DEFAULTS,\n ...input,\n };\n\n const { document } = buildDocument(html, baseUrl);\n applySelectors(document, selectors);\n pruneUnsafeRoots(document);\n\n const links: ExtractedLink[] = [];\n for (const anchor of document.querySelectorAll('a')) {\n const rawHref = anchor.getAttribute('href');\n if (!rawHref) {\n continue;\n }\n const href = absolutize(rawHref, baseUrl);\n const isExternal = baseUrl ? resolveExternal(href, baseUrl) : false;\n if (sameOriginOnly && isExternal) {\n continue;\n }\n links.push({\n text: clipText(anchor.textContent),\n href,\n rel: anchor.getAttribute('rel') ?? '',\n isExternal,\n });\n }\n\n const content = renderLinksIndex(links);\n return {\n content: [{ text: content, type: 'text' }],\n structuredContent: {\n schemaVersion: 1,\n content,\n links,\n metadata: { baseUrl },\n },\n };\n}\n\n// One `- [text](href)` line per link; never blank so content[0].text is scannable.\nfunction renderLinksIndex(links: readonly ExtractedLink[]): string {\n if (links.length === 0) {\n return '(no links found)';\n }\n return links\n .map(link => `- [${link.text || '(no text)'}](${link.href})`)\n .join('\\n');\n}\n\nexport const EXTRACT_LINKS_TOOL_DESCRIPTION = `Return a structured list of anchor links from already-rendered (post-JavaScript) HTML — \\`[{text, href, rel, isExternal}]\\` in document order, hrefs absolutized against \\`baseUrl\\`. No Readability scoring, Turndown, or sanitization — links are gathered from the raw parsed DOM so nav/footer/main links survive. Pairs with chrome-devtools for crawl/navigation decisions. The server fetches nothing: \\`localPath\\` is the only source, and \\`baseUrl\\` is origin context only (never fetched).`;\n\nexport function extractLinksHandler(args: unknown): CallToolResult {\n try {\n return extractLinks(args);\n } catch (err) {\n logger.error(\n `extract_links failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n return toErrorResult(err);\n }\n}\n\nexport function registerExtractLinksTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'extract_links',\n {\n title: 'Extract anchor links',\n description: EXTRACT_LINKS_TOOL_DESCRIPTION,\n inputSchema: extractLinksInputShape,\n outputSchema: extractLinksOutputShape,\n },\n extractLinksHandler,\n );\n}\n","import { isElement } from '../pipeline/dom.js';\nimport { absolutize } from '../pipeline/urls.js';\nimport {\n CONTAINER_TAGS,\n describeSelector,\n shapeKey,\n stripLandmarkChrome,\n} from './sibling-scan.js';\n\nexport interface ListItem {\n readonly score: number;\n readonly snippet: string;\n readonly title: string;\n readonly url: string;\n}\n\nexport type ListConfidence = 'high' | 'low' | 'medium';\n\nexport interface ListDetectionResult {\n readonly confidence: ListConfidence;\n readonly containerSelector: string;\n readonly detected: boolean;\n readonly itemCount: number;\n readonly items: readonly ListItem[];\n readonly itemTag: string;\n readonly note: string;\n}\n\n// Three siblings of the same shape (tag + class signature), each carrying a\n// real navigation anchor, is the smallest cluster that is not accidentally a\n// nav menu or a article's <ul> of inline links. Below this the detector\n// refuses to claim the page is a list.\nconst MIN_ITEMS = 3;\n\n// Six-or-more items with non-trivial body text per item is the empirical\n// signature of a true index/search/blog-roll page (HN shows 30, Google ~10,\n// WP indexes ~10). Used to separate `high` from `medium` confidence so a\n// 3-item related-links block doesn't masquerade as a feed.\nconst HIGH_CONF_ITEMS = 6;\nconst HIGH_CONF_AVG_SCORE = 30;\n\nconst MAX_SNIPPET_CHARS = 200;\n\n// href schemes that don't point at a list target. Excluding them keeps mailto:\n// /tel:/javascript:/ anchors from satisfying the \"every item has a link\" bar.\nfunction isNavigationHref(href: string): boolean {\n if (!href || href === '#') {\n return false;\n }\n const lower = href.toLowerCase();\n return (\n !lower.startsWith('javascript:') &&\n !lower.startsWith('mailto:') &&\n !lower.startsWith('tel:')\n );\n}\n\nfunction anchorText(anchor: HTMLAnchorElement): string {\n return anchor.textContent.replace(/\\s+/g, ' ').trim();\n}\n\n// Anchors with a navigation-worthy href AND visible text. Lives inside the\n// candidate child so a single <a href=\"#\"> footer link doesn't satisfy the\n// \"every item has a link\" requirement.\nfunction navigationAnchors(child: Element): HTMLAnchorElement[] {\n const anchors: HTMLAnchorElement[] = [];\n for (const el of child.querySelectorAll('a[href]')) {\n const anchor = el as HTMLAnchorElement;\n const href = anchor.getAttribute('href') ?? '';\n if (!isNavigationHref(href)) {\n continue;\n }\n if (anchorText(anchor).length === 0) {\n continue;\n }\n anchors.push(anchor);\n }\n return anchors;\n}\n\nfunction pickPrimaryAnchor(\n anchors: readonly HTMLAnchorElement[],\n): HTMLAnchorElement {\n let primary = anchors[0];\n let best = anchorText(primary).length;\n for (let i = 1; i < anchors.length; i++) {\n const len = anchorText(anchors[i]).length;\n if (len > best) {\n primary = anchors[i];\n best = len;\n }\n }\n return primary;\n}\n\nfunction clipSnippet(raw: string): string {\n const collapsed = raw.replace(/\\s+/g, ' ').trim();\n if (collapsed.length <= MAX_SNIPPET_CHARS) {\n return collapsed;\n }\n return `${collapsed.slice(0, MAX_SNIPPET_CHARS)}…`;\n}\n\n// Score = primary-anchor text length + non-link body text length. A pure\n// link-density penalty (textLength * (1 - linkDensity)) ranks HN's subtext\n// rows ABOVE the title rows because the title row is anchor-dominated (its\n// title IS the link), so non-link body alone mis-ranks. Adding the primary\n// anchor's text length rewards long titles (real feed items) over short nav\n// labels (\"Home\", \"Sign in\"), which is the signal we actually want for both\n// the candidate tiebreak and the high-confidence threshold.\nfunction scoreItem(\n textLength: number,\n linkTextLength: number,\n primaryAnchorTextLength: number,\n): number {\n if (textLength === 0) {\n return 0;\n }\n const nonLinkBody = Math.max(0, textLength - linkTextLength);\n return primaryAnchorTextLength + nonLinkBody;\n}\n\nfunction extractItem(\n child: Element,\n baseUrl: string | undefined,\n): ListItem | null {\n const anchors = navigationAnchors(child);\n if (anchors.length === 0) {\n return null;\n }\n const primary = pickPrimaryAnchor(anchors);\n const title = anchorText(primary);\n if (!title) {\n return null;\n }\n const href = primary.getAttribute('href') ?? '';\n const url = absolutize(href, baseUrl);\n if (!url) {\n return null;\n }\n\n const fullText = child.textContent.replace(/\\s+/g, ' ').trim();\n // Snippet = body text with the title peeled off the front when present, so\n // the title link doesn't echo into the excerpt.\n const snippet =\n fullText === title\n ? ''\n : fullText.startsWith(title)\n ? clipSnippet(fullText.slice(title.length + 1))\n : clipSnippet(fullText);\n\n const linkTextLength = anchors.reduce(\n (sum, anchor) => sum + anchorText(anchor).length,\n 0,\n );\n return {\n score: scoreItem(fullText.length, linkTextLength, title.length),\n snippet,\n title,\n url,\n };\n}\n\ninterface Candidate {\n readonly containerSelector: string;\n readonly distinctPathnames: number;\n readonly items: readonly ListItem[];\n readonly itemTag: string;\n readonly totalScore: number;\n}\n\n// Count of distinct URL pathnames a candidate's items point at. This is the\n// signal that separates a real feed from a metadata cluster: a feed's items\n// each navigate to a distinct destination (HN title rows link to ~30 different\n// external stories; a blog index links to /post-1../post-N; a search results\n// page links to a heterogeneous set of sites), so distinctPathnames ≈ item\n// count. A metadata cluster's items all navigate to the same internal route —\n// HN's subtext rows are 31 anchors all pointing at news.ycombinator.com/item?id=…,\n// collapsing to pathname=/item (distinctPathnames = 1). Counting items alone\n// lets subtext win on HN (31 vs 30 title rows) because of the trailing \"More\"\n// row; counting distinct destinations restores the title group as the winner.\nfunction distinctPathnames(items: readonly ListItem[]): number {\n const paths = new Set<string>();\n for (const item of items) {\n try {\n paths.add(new URL(item.url).pathname);\n } catch {\n paths.add(item.url);\n }\n }\n return paths.size;\n}\n\nfunction collectCandidates(document: Document, baseUrl: string | undefined) {\n const candidates: Candidate[] = [];\n for (const container of document.querySelectorAll('*')) {\n if (!CONTAINER_TAGS.has(container.tagName)) {\n continue;\n }\n // Group direct element-children by shape so homogeneous sibling lists\n // surface as one cluster and mixed-shape siblings (e.g. HN's athing +\n // subtext rows) split apart.\n const groups = new Map<string, Element[]>();\n for (const child of Array.from(container.childNodes)) {\n if (!isElement(child)) {\n continue;\n }\n const key = shapeKey(child);\n const bucket = groups.get(key);\n if (bucket) {\n bucket.push(child);\n } else {\n groups.set(key, [child]);\n }\n }\n for (const children of groups.values()) {\n if (children.length < MIN_ITEMS) {\n continue;\n }\n // EVERY member must carry a real anchor — one linkless <li> breaks\n // homogeneity and rejects the group, which is what keeps a <ul> of\n // plain-text list items (e.g. recipe ingredients) from triggering.\n if (!children.every(child => navigationAnchors(child).length > 0)) {\n continue;\n }\n const items: ListItem[] = [];\n for (const child of children) {\n const item = extractItem(child, baseUrl);\n if (item) {\n items.push(item);\n }\n }\n if (items.length < MIN_ITEMS) {\n continue;\n }\n candidates.push({\n containerSelector: describeSelector(container),\n distinctPathnames: distinctPathnames(items),\n itemTag: children[0].tagName,\n items,\n totalScore: items.reduce((sum, item) => sum + item.score, 0),\n });\n }\n }\n return candidates;\n}\n\nfunction confidenceFor(items: readonly ListItem[]): ListConfidence {\n if (items.length < MIN_ITEMS) {\n return 'low';\n }\n if (items.length >= HIGH_CONF_ITEMS) {\n const avg = items.reduce((sum, item) => sum + item.score, 0) / items.length;\n return avg >= HIGH_CONF_AVG_SCORE ? 'high' : 'medium';\n }\n return 'medium';\n}\n\nfunction notDetected(note: string): ListDetectionResult {\n return {\n confidence: 'low',\n containerSelector: '',\n detected: false,\n itemCount: 0,\n itemTag: '',\n items: [],\n note,\n };\n}\n\n// Detect a list/feed/index structure on a non-article page. Strips chrome\n// (nav/header/footer/aside + ARIA roles) first so an article's menu doesn't\n// look like a 4-item feed, then walks for containers whose direct children\n// form a homogeneous sibling group of ≥3 elements each carrying a real anchor.\n// Winner selection prefers the candidate whose items navigate to the most\n// distinct destinations — the signature of a real feed — over a same-path\n// metadata cluster (HN's subtext rows all link to /item?id=…). Item count is\n// the second key (a 30-item feed beats a 4-item related-links block) and total\n// per-item score breaks remaining ties.\nexport function detectList(\n document: Document,\n baseUrl?: string,\n): ListDetectionResult {\n stripLandmarkChrome(document);\n\n const candidates = collectCandidates(document, baseUrl);\n if (candidates.length === 0) {\n return notDetected(\n 'not a list: no repeated item structure with links (≥3 same-shape siblings each carrying an anchor, outside nav/header/footer/aside)',\n );\n }\n\n let winner = candidates[0];\n for (let i = 1; i < candidates.length; i++) {\n const candidate = candidates[i];\n if (\n candidate.distinctPathnames > winner.distinctPathnames ||\n (candidate.distinctPathnames === winner.distinctPathnames &&\n candidate.items.length > winner.items.length) ||\n (candidate.distinctPathnames === winner.distinctPathnames &&\n candidate.items.length === winner.items.length &&\n candidate.totalScore > winner.totalScore)\n ) {\n winner = candidate;\n }\n }\n\n if (winner.items.length < MIN_ITEMS) {\n return notDetected(\n 'not a list: best candidate had fewer than 3 extracted items',\n );\n }\n\n const items = winner.items;\n return {\n confidence: confidenceFor(items),\n containerSelector: winner.containerSelector,\n detected: true,\n itemCount: items.length,\n itemTag: winner.itemTag,\n items,\n note: `detected ${items.length} ${winner.itemTag} items in ${winner.containerSelector}`,\n };\n}\n","import type { ToolHandle } from '../server.js';\n\nimport { toErrorResult } from '../errors.js';\nimport { logger } from '../logger.js';\nimport { buildDocument } from '../pipeline/dom.js';\nimport { applySelectors } from '../pipeline/normalize.js';\nimport {\n detectList,\n type ListDetectionResult,\n type ListItem,\n} from '../policy/list-detector.js';\nimport { readHtmlFile } from './html-source.js';\nimport { extractListOutputShape } from './output-schema.js';\nimport {\n type ExtractListFromHtmlInput,\n extractListInputSchema,\n extractListInputShape,\n} from './schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\nconst NOT_A_LIST = 'not a list: no repeated item structure with links';\n\nfunction renderItems(result: ListDetectionResult): string {\n if (!result.detected || result.items.length === 0) {\n return result.note || NOT_A_LIST;\n }\n return result.items\n .map((item, index) => {\n const head = `${index + 1}. ${item.title} — ${item.url}`;\n return item.snippet ? `${head}\\n ${item.snippet}` : head;\n })\n .join('\\n');\n}\n\nfunction toStructuredItem(item: ListItem) {\n return {\n score: item.score,\n snippet: item.snippet,\n title: item.title,\n url: item.url,\n };\n}\n\nexport function extractList(rawArgs: unknown): CallToolResult {\n const { localPath, ...rest } = extractListInputSchema.parse(rawArgs);\n return extractListFromHtml({ html: readHtmlFile(localPath), ...rest });\n}\n\nexport function extractListFromHtml(\n input: Readonly<ExtractListFromHtmlInput>,\n): CallToolResult {\n const { html, baseUrl, selectors } = input;\n\n // No Readability/Turndown/normalize: list pages survive on raw DOM shape\n // (sibling TR/LI/ARTICLE clusters), and the article normalizer would\n // discard the very chrome-bearing structure the detector scores against.\n // Chrome stripping (nav/header/footer/aside) lives inside detectList so\n // this tool sees the page as captured.\n const { document } = buildDocument(html, baseUrl);\n applySelectors(document, selectors);\n const result = detectList(document, baseUrl);\n const content = renderItems(result);\n return {\n content: [{ text: content, type: 'text' }],\n structuredContent: {\n schemaVersion: 1,\n content,\n items: result.items.map(toStructuredItem),\n diagnostics: {\n confidence: result.confidence,\n containerSelector: result.containerSelector,\n detected: result.detected,\n itemCount: result.itemCount,\n itemTag: result.itemTag,\n note: result.note,\n },\n metadata: { baseUrl },\n },\n };\n}\n\nexport const EXTRACT_LIST_TOOL_DESCRIPTION = `Detect and extract a list/feed/index structure from already-rendered (post-JavaScript) HTML — for HN-style, search-result, and blog-index pages that Readability cannot turn into one article. Returns \\`{items: [{title, url, snippet, score}], diagnostics}\\` instead of one article. Strips nav/header/footer/aside + ARIA chrome roles first (the false-positive guard so an article's nav menu doesn't look like a 4-item feed), then finds the container whose direct children form a same-shape sibling cluster of ≥3 elements each carrying a navigation anchor, and the cluster with the most items wins. No Readability, no Turndown, no sanitization. The server fetches nothing: \\`localPath\\` is the only source, and \\`baseUrl\\` (optional) is origin context for absolutizing item hrefs.`;\n\nexport function extractListHandler(args: unknown): CallToolResult {\n try {\n return extractList(args);\n } catch (err) {\n logger.error(\n `extract_list failed: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n return toErrorResult(err);\n }\n}\n\nexport function registerExtractListTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'extract_list',\n {\n title: 'Extract a list/feed (HN/search/blog-index pages)',\n description: EXTRACT_LIST_TOOL_DESCRIPTION,\n inputSchema: extractListInputShape,\n outputSchema: extractListOutputShape,\n },\n extractListHandler,\n );\n}\n","import type { Metadata } from '../pipeline/context.js';\nimport type { ToolHandle } from '../server.js';\n\nimport { toErrorResult } from '../errors.js';\nimport { logger } from '../logger.js';\nimport { buildDocument } from '../pipeline/dom.js';\nimport { normalizeDocument } from '../pipeline/normalize.js';\nimport { resolveMetadata } from '../policy/metadata.js';\nimport { readHtmlFile } from './html-source.js';\nimport { extractMetadataOutputShape } from './output-schema.js';\nimport {\n type ExtractMetadataFromHtmlInput,\n extractMetadataInputSchema,\n extractMetadataInputShape,\n} from './schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\n// Bibliographic fields only — wordCount/readingTimeMin/tokenEstimate are\n// meaningless without the extracted body, so a metadata-only caller never\n// sees wordCount: 0.\nconst BIBLIOGRAPHIC_KEYS = [\n 'title',\n 'byline',\n 'siteName',\n 'lang',\n 'publishedTime',\n 'excerpt',\n 'canonical',\n 'baseUrl',\n] as const satisfies readonly (keyof Metadata)[];\n\nfunction pickBibliographic(\n metadata: Readonly<Metadata>,\n): Record<string, string> {\n const out: Record<string, string> = {};\n for (const key of BIBLIOGRAPHIC_KEYS) {\n const value = metadata[key];\n if (value !== undefined) {\n out[key] = value;\n }\n }\n return out;\n}\n\n// One line per present field; never blank so content[0].text is always scannable.\nfunction renderMetadataLines(\n metadata: Readonly<Record<string, string>>,\n): string {\n const lines = Object.entries(metadata).map(\n ([key, value]) => `${key}: ${value}`,\n );\n return lines.length > 0 ? lines.join('\\n') : '(no metadata found)';\n}\n\nexport function extractMetadataDocument(rawArgs: unknown): CallToolResult {\n const { localPath, ...rest } = extractMetadataInputSchema.parse(rawArgs);\n return extractMetadataDocumentFromHtml({\n html: readHtmlFile(localPath),\n ...rest,\n });\n}\n\nexport function extractMetadataDocumentFromHtml(\n input: Readonly<ExtractMetadataFromHtmlInput>,\n): CallToolResult {\n const { html, baseUrl } = input;\n\n const { document } = buildDocument(html, baseUrl);\n normalizeDocument(document);\n const resolved = resolveMetadata({\n document,\n textContent: '',\n wordCount: 0,\n readingTimeMin: 0,\n baseUrl,\n });\n\n const metadata = pickBibliographic(resolved);\n const content = renderMetadataLines(metadata);\n return {\n content: [{ text: content, type: 'text' }],\n structuredContent: {\n schemaVersion: 1,\n content,\n metadata,\n },\n };\n}\n\nexport const EXTRACT_METADATA_TOOL_DESCRIPTION = `Return only the bibliographic metadata (title, byline, siteName, lang, publishedTime, excerpt, canonical, baseUrl) of already-rendered (post-JavaScript) HTML without running Readability/Turndown — a fast pre-check for crawlers and citation. Resolves the same metadata cascade as \\`extract\\` (JSON-LD → OpenGraph → Twitter → <meta> → <time> → <title>), plus <link rel=\"canonical\"> → og:url. The server fetches nothing: \\`localPath\\` is the only source, and \\`baseUrl\\` is origin context only (never fetched).`;\n\nexport function extractMetadataHandler(args: unknown): CallToolResult {\n try {\n return extractMetadataDocument(args);\n } catch (err) {\n logger.error(\n `extract_metadata failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n return toErrorResult(err);\n }\n}\n\nexport function registerExtractMetadataTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'extract_metadata',\n {\n title: 'Extract metadata only (no Readability)',\n description: EXTRACT_METADATA_TOOL_DESCRIPTION,\n inputSchema: extractMetadataInputShape,\n outputSchema: extractMetadataOutputShape,\n },\n extractMetadataHandler,\n );\n}\n","export interface OutlineEntry {\n readonly anchor: string;\n readonly level: number;\n readonly text: string;\n}\n\n// Collapse whitespace, trim, then strip leading/trailing `#` (GitHub permalinks\n// append `<a>#</a>`, so heading textContent ends with `#`).\nexport function normalizeHeadingText(raw: string): string {\n const collapsed = raw.replace(/\\s+/g, ' ').trim();\n return collapsed.replace(/^#+|#+$/g, '').trim();\n}\n\n// GFM anchor algorithm (comrak `anchorize`): keep Unicode letters, marks,\n// numbers, and connector punctuation; map spaces to hyphens. No hyphen collapse\n// or trim, so anchors match GitHub and non-Latin scripts survive. An all-symbol\n// heading yields an empty slug; fall back to `section` so anchors stay non-empty.\nfunction slugify(text: string): string {\n const cleaned = text\n .toLowerCase()\n .replace(/[^\\p{L}\\p{M}\\p{N}\\p{Pc} -]/gu, '');\n return cleaned.replace(/ /g, '-') || 'section';\n}\n\n// `href=\"#\"` (empty fragment) does not win — fall through to slugify.\nfunction linkAnchor(heading: Element): string | undefined {\n for (const link of heading.querySelectorAll('a[href^=\"#\"]')) {\n const href = link.getAttribute('href');\n if (!href) {\n continue;\n }\n const fragment = href.slice(1);\n if (fragment) {\n return fragment;\n }\n }\n return undefined;\n}\n\nfunction resolveCandidate(\n heading: Element,\n text: string,\n): { readonly anchor: string; readonly explicit: boolean } {\n const id = heading.getAttribute('id');\n if (id) {\n return { anchor: id, explicit: true };\n }\n const link = linkAnchor(heading);\n if (link) {\n return { anchor: link, explicit: true };\n }\n return { anchor: slugify(text), explicit: false };\n}\n\n// Explicit anchors are kept verbatim; generated slugs are collision-suffixed\n// (`-1`, `-2`, …), first occurrence bare.\nfunction dedupe(\n candidate: string,\n explicit: boolean,\n used: Set<string>,\n): string {\n if (explicit || !used.has(candidate)) {\n return candidate;\n }\n let suffix = 1;\n while (used.has(`${candidate}-${suffix}`)) {\n suffix++;\n }\n return `${candidate}-${suffix}`;\n}\n\nexport function resolveOutline(document: Document): OutlineEntry[] {\n const entries: OutlineEntry[] = [];\n const used = new Set<string>();\n const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6');\n headings.forEach(heading => {\n const rawText = heading.textContent;\n if (!rawText.trim()) {\n return;\n }\n const text = normalizeHeadingText(rawText);\n const level = Number.parseInt(heading.tagName.slice(1), 10);\n const { anchor: candidate, explicit } = resolveCandidate(heading, text);\n const anchor = dedupe(candidate, explicit, used);\n used.add(anchor);\n entries.push({ anchor, level, text });\n });\n return entries;\n}\n","import { isElement } from '../pipeline/dom.js';\nimport { normalizeHeadingText } from './outline.js';\n\ninterface HeadingMatch {\n readonly heading: Element;\n readonly level: number;\n}\n\n// Match the first heading by normalized text. Exact equality wins; otherwise\n// fall back to the first substring match so a short query (\"auth\") still\n// lands on a longer heading (\"Authentication\") without forcing an exact match.\nfunction findHeading(\n document: Document,\n query: string,\n): HeadingMatch | undefined {\n const needle = normalizeHeadingText(query).toLowerCase();\n if (!needle) {\n return undefined;\n }\n const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6');\n let substring: HeadingMatch | undefined;\n for (const heading of headings) {\n const text = normalizeHeadingText(heading.textContent).toLowerCase();\n if (!text) {\n continue;\n }\n const level = Number.parseInt(heading.tagName.slice(1), 10);\n if (text === needle) {\n return { heading, level };\n }\n if (!substring && text.includes(needle)) {\n substring = { heading, level };\n }\n }\n return substring;\n}\n\n// Elements whose direct children form the section flow. The matched heading's\n// nearest such ancestor is the level at which the section's content lives as\n// siblings: on docs sites (GitHub markdown, etc.) the heading is wrapped in\n// <div class=\"markdown-heading\">, so the body <p> is a sibling of the wrapper\n// div, not of the <h2>. Walking the <h2>'s own siblings would capture only the\n// permalink anchor and miss the body.\nconst FLOW_CONTAINERS = new Set([\n 'ARTICLE',\n 'ASIDE',\n 'BLOCKQUOTE',\n 'BODY',\n 'DD',\n 'DETAILS',\n 'DT',\n 'FOOTER',\n 'HEADER',\n 'LI',\n 'MAIN',\n 'NAV',\n 'SECTION',\n 'TD',\n]);\n\nfunction findFlowContainer(heading: Element): Element {\n let ancestor: Element | null = heading.parentElement;\n while (ancestor) {\n if (FLOW_CONTAINERS.has(ancestor.tagName)) {\n return ancestor;\n }\n ancestor = ancestor.parentElement;\n }\n // BODY is in FLOW_CONTAINERS, so the loop reaches it for any attached\n // heading; this fallback only covers a detached heading.\n return heading.ownerDocument.body;\n}\n\n// Level of the first heading at or inside `el`, or undefined when `el` is\n// neither a heading nor a wrapper around one. Used to test whether a following\n// sibling terminates the section.\nfunction firstHeadingLevel(el: Element): number | undefined {\n const direct = /^H([1-6])$/.exec(el.tagName);\n if (direct) {\n return Number.parseInt(direct[1], 10);\n }\n const inner = el.querySelector('h1, h2, h3, h4, h5, h6');\n if (inner) {\n return Number.parseInt(inner.tagName.slice(1), 10);\n }\n return undefined;\n}\n\n// Wraps the matched heading and its subtree in a `<section data-rdrm-section-scope>`\n// so the existing selectors.include path can isolate it. Returns false (and\n// leaves the document untouched) when no heading matches.\nexport function scopeToHeading(\n document: Document,\n headingText: string,\n): boolean {\n const match = findHeading(document, headingText);\n if (!match) {\n return false;\n }\n const { heading, level } = match;\n const container = findFlowContainer(heading);\n let startChild: Element = heading;\n while (startChild.parentElement && startChild.parentElement !== container) {\n startChild = startChild.parentElement;\n }\n const wrap = document.createElement('section');\n wrap.setAttribute('data-rdrm-section-scope', '');\n container.insertBefore(wrap, startChild);\n let node: Node | null = startChild;\n while (node) {\n const next: Node | null = node.nextSibling;\n wrap.appendChild(node);\n // A section ends at the next same-or-higher-level heading (level <= L):\n // deeper headings (h3 under an h2) belong to this section, peers and\n // shallower headings start the next one. The terminating sibling may be a\n // wrapper around the heading (e.g. <div class=\"markdown-heading\">), so the\n // check looks for a heading at OR inside the sibling.\n if (next !== null && isElement(next)) {\n const nextLevel = firstHeadingLevel(next);\n if (nextLevel !== undefined && nextLevel <= level) {\n break;\n }\n }\n node = next;\n }\n return true;\n}\n","import type { ToolHandle } from '../server.js';\n\nimport { ExtractionError, toErrorResult } from '../errors.js';\nimport { logger } from '../logger.js';\nimport { buildDocument } from '../pipeline/dom.js';\nimport { normalizeDocument } from '../pipeline/normalize.js';\nimport { scopeToHeading } from '../policy/section.js';\nimport { extractArticleFromHtml } from './extract.js';\nimport { readHtmlFile } from './html-source.js';\nimport { outputSchemaShape } from './output-schema.js';\nimport {\n type ExtractSectionFromHtmlInput,\n extractSectionInputSchema,\n extractSectionInputShape,\n} from './schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\nconst SECTION_SCOPE_SELECTOR = '[data-rdrm-section-scope]';\n\nexport function extractSection(rawArgs: unknown): CallToolResult {\n const { localPath, ...rest } = extractSectionInputSchema.parse(rawArgs);\n return extractSectionFromHtml({ html: readHtmlFile(localPath), ...rest });\n}\n\nexport function extractSectionFromHtml(\n input: Readonly<ExtractSectionFromHtmlInput>,\n): CallToolResult {\n const { html, baseUrl, selector, heading } = input;\n\n if (selector !== undefined) {\n return extractArticleFromHtml({\n html,\n baseUrl,\n selectors: { include: selector },\n });\n }\n // The superRefine on the schema enforces selector/heading XOR, so reaching\n // here means heading is set — but its declared type is still optional, so\n // narrow explicitly rather than asserting.\n if (heading === undefined) {\n throw new ExtractionError(\n 'Provide exactly one of `selector` or `heading`.',\n );\n }\n\n // Heading mode: wrap the matched subtree, re-serialize, and route through\n // extractArticleFromHtml's selectors.include so this tool stays a thin\n // resolver over the existing extraction pipeline — no forked extraction logic.\n // The DOM is parsed+normalized twice: here to scope against a normalized\n // DOM, and again inside the extract worker — which owns detectGating-before-\n // normalize, resolveLazyImages, and applySelectors. Folding scoping into\n // that pipeline would breach its invariants for a non-hot path; accepted.\n const { document } = buildDocument(html, baseUrl);\n normalizeDocument(document);\n if (!scopeToHeading(document, heading)) {\n throw new ExtractionError(`no heading matched: ${heading}`);\n }\n const scoped = `<!DOCTYPE html><html><head></head><body>${document.body.innerHTML}</body></html>`;\n return extractArticleFromHtml({\n html: scoped,\n baseUrl,\n selectors: { include: SECTION_SCOPE_SELECTOR },\n });\n}\n\nexport const EXTRACT_SECTION_TOOL_DESCRIPTION = `Extract one section of an already-rendered (post-JavaScript) HTML document and return its Markdown + metadata + diagnostics — a thin resolver over extract’s \\`selectors.include\\` path, not a new extractor. Pick the section by CSS \\`selector\\` (passed straight through) OR by \\`heading\\` text (case-insensitive, first match wins; the section spans from the matched heading to the next same-or-higher-level heading). Exactly one of \\`selector\\`/\\`heading\\` is required. The server fetches nothing: \\`localPath\\` is the only source, and \\`baseUrl\\` (optional) is origin context only (never fetched).`;\n\nexport function extractSectionHandler(args: unknown): CallToolResult {\n try {\n return extractSection(args);\n } catch (err) {\n logger.error(\n `extract_section failed: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n return toErrorResult(err);\n }\n}\n\nexport function registerExtractSectionTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'extract_section',\n {\n title: 'Extract one section by selector or heading',\n description: EXTRACT_SECTION_TOOL_DESCRIPTION,\n inputSchema: extractSectionInputShape,\n outputSchema: outputSchemaShape,\n },\n extractSectionHandler,\n );\n}\n","import type { ToolHandle } from '../server.js';\n\nimport { toErrorResult } from '../errors.js';\nimport { logger } from '../logger.js';\nimport { buildDocument } from '../pipeline/dom.js';\nimport { applySelectors } from '../pipeline/normalize.js';\nimport {\n parseTableMatrix,\n renderTable,\n resolveHeaderKeys,\n} from '../policy/tables.js';\nimport { readHtmlFile } from './html-source.js';\nimport { extractTablesOutputShape } from './output-schema.js';\nimport {\n type ExtractTablesFromHtmlInput,\n type ExtractTablesInput,\n extractTablesInputSchema,\n extractTablesInputShape,\n} from './schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\ninterface ExtractedTable {\n readonly cols: number;\n readonly index: number;\n readonly markdown: string;\n readonly rows: number;\n}\n\nconst NO_TABLES = '(no tables found)';\n\nexport function extractTables(rawArgs: unknown): CallToolResult {\n const { localPath, ...rest } = extractTablesInputSchema.parse(rawArgs);\n return extractTablesFromHtml({ html: readHtmlFile(localPath), ...rest });\n}\n\n// Schema defaults for callers that pass only a subset of the knobs (format).\nconst DEFAULTS: Omit<ExtractTablesInput, 'localPath'> =\n extractTablesInputSchema.parse({ localPath: '' });\n\nexport function extractTablesFromHtml(\n input: Readonly<ExtractTablesFromHtmlInput>,\n): CallToolResult {\n const { html, baseUrl, format, selectors } = { ...DEFAULTS, ...input };\n\n // Skip normalizeDocument/Readability on purpose: this tool exists to reach\n // tables that live outside the scored article (nav, aside, boilerplate), which\n // those stages would discard. Table structure is static HTML, so the matrix\n // walk is unaffected by unsanitized scripts/styles.\n const { document } = buildDocument(html, baseUrl);\n applySelectors(document, selectors);\n\n const tables: ExtractedTable[] = [];\n let index = 0;\n for (const table of document.querySelectorAll('table')) {\n const matrix = parseTableMatrix(table);\n if (matrix.length === 0) {\n continue;\n }\n const keys =\n format === 'json' ? resolveHeaderKeys(table, matrix) : undefined;\n const markdown = renderTable(matrix, format, keys);\n tables.push({\n index,\n rows: matrix.length,\n cols: matrix[0]?.length ?? 0,\n markdown,\n });\n index++;\n }\n\n const content =\n tables.length > 0\n ? tables.map(entry => entry.markdown).join('\\n\\n')\n : NO_TABLES;\n return {\n content: [{ text: content, type: 'text' }],\n structuredContent: {\n schemaVersion: 1,\n content,\n tables,\n metadata: { baseUrl, format, tableCount: tables.length },\n },\n };\n}\n\nexport const EXTRACT_TABLES_TOOL_DESCRIPTION = `Extract every <table> on the page from already-rendered (post-JavaScript) HTML and return each as GFM / CSV / JSON (caller picks). Runs no Readability, Turndown, or sanitization — a page-wide \\`querySelectorAll('table')\\` walk in front of the same rowspan/colspan-aware matrix serializer used by the \\`tables\\` option on \\`extract\\`. Captures tables outside the article body (nav, aside, boilerplate) that the \\`tables\\` option never sees. The server fetches nothing: \\`localPath\\` is the only source, and \\`baseUrl\\` (optional) is origin context only (never fetched).`;\n\nexport function extractTablesHandler(args: unknown): CallToolResult {\n try {\n return extractTables(args);\n } catch (err) {\n logger.error(\n `extract_tables failed: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n return toErrorResult(err);\n }\n}\n\nexport function registerExtractTablesTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'extract_tables',\n {\n title: 'Extract every table on the page',\n description: EXTRACT_TABLES_TOOL_DESCRIPTION,\n inputSchema: extractTablesInputShape,\n outputSchema: extractTablesOutputShape,\n },\n extractTablesHandler,\n );\n}\n","import type { SanitizationDiagnostics } from '../pipeline/context.js';\nimport type { ToolHandle } from '../server.js';\n\nimport { toErrorResult } from '../errors.js';\nimport { logger } from '../logger.js';\nimport { formatPayload } from '../output/format.js';\nimport { buildDocument } from '../pipeline/dom.js';\nimport {\n applySelectors,\n normalizeDocument,\n resolveLazyImages,\n} from '../pipeline/normalize.js';\nimport { sanitizeHtml } from '../pipeline/sanitize.js';\nimport { toMarkdown } from '../pipeline/turndown.js';\nimport { assembleDiagnostics, TraceCollector } from '../policy/diagnostics.js';\nimport { computeTextMetrics, nonEmpty } from '../policy/text.js';\nimport { truncateMarkdown } from '../policy/truncate.js';\nimport { readHtmlFile } from './html-source.js';\nimport { outputSchemaShape } from './output-schema.js';\nimport {\n type HtmlToMarkdownFromHtmlInput,\n type HtmlToMarkdownInput,\n htmlToMarkdownInputSchema,\n htmlToMarkdownInputShape,\n} from './schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\nconst EXTRACTED_NODE = 'fragment';\n\nexport function htmlToMarkdown(rawArgs: unknown): CallToolResult {\n const { localPath, ...rest } = htmlToMarkdownInputSchema.parse(rawArgs);\n return htmlToMarkdownFromHtml({ html: readHtmlFile(localPath), ...rest });\n}\n\n// Schema defaults for callers that pass only a subset of the knobs.\nconst DEFAULTS: Omit<HtmlToMarkdownInput, 'localPath'> =\n htmlToMarkdownInputSchema.parse({ localPath: '' });\n\nexport function htmlToMarkdownFromHtml(\n input: Readonly<HtmlToMarkdownFromHtmlInput>,\n): CallToolResult {\n const {\n html,\n baseUrl,\n selectors,\n format,\n metadataMode,\n gfm,\n headingStyle,\n codeBlockStyle,\n images,\n sanitize: shouldSanitize,\n maxChars,\n wordsPerMinute,\n cleanChrome,\n tables,\n debug,\n } = { ...DEFAULTS, ...input };\n\n const trace = new TraceCollector(debug);\n\n const { document, window } = buildDocument(html, baseUrl);\n\n const {\n documentElementCount,\n normalizeCounts,\n imagesResolved,\n textContent,\n rawHtml,\n } = trace.run('normalize', () => {\n const documentElementCount = document.querySelectorAll('*').length;\n const normalizeCounts = normalizeDocument(document, { cleanChrome });\n const imagesResolved = resolveLazyImages(document);\n applySelectors(document, selectors);\n const body = document.body;\n return {\n documentElementCount,\n imagesResolved,\n normalizeCounts,\n rawHtml: body.innerHTML,\n textContent: body.textContent,\n };\n });\n\n const { html: sanitizedHtml, counts: sanitizeCounts } = trace.run(\n 'sanitize',\n (): { counts: SanitizationDiagnostics; html: string } => {\n if (!shouldSanitize) {\n return { counts: { iframes: 0, scripts: 0 }, html: rawHtml };\n }\n const res = sanitizeHtml(rawHtml, window);\n return {\n counts: { iframes: res.iframesRemoved, scripts: res.scriptsRemoved },\n html: res.html,\n };\n },\n );\n const markdown = trace.run('turndown', () =>\n toMarkdown(sanitizedHtml, {\n codeBlockStyle,\n gfm,\n headingStyle,\n images,\n tables,\n baseUrl,\n }),\n );\n\n const { metadata } = trace.run('metadata', () => {\n const firstHeading = nonEmpty(\n document.body.querySelector('h1, h2, h3, h4, h5, h6')?.textContent,\n );\n const metadata = {\n title: firstHeading,\n baseUrl,\n ...computeTextMetrics(textContent, wordsPerMinute),\n };\n return { metadata };\n });\n\n const sanitization: SanitizationDiagnostics = {\n iframes: normalizeCounts.iframes + sanitizeCounts.iframes,\n scripts: normalizeCounts.scripts + sanitizeCounts.scripts,\n };\n const baseDiagnostics = assembleDiagnostics({\n articleHtml: sanitizedHtml,\n boilerplateRemoved: normalizeCounts.boilerplateRemoved,\n chromeRemoved: normalizeCounts.chromeRemoved,\n documentElementCount,\n extractedNode: EXTRACTED_NODE,\n fallbackUsed: true,\n imagesResolved,\n sanitization,\n trace: trace.collect(),\n truncated: false,\n window,\n });\n\n let payload = formatPayload({\n diagnostics: baseDiagnostics,\n format,\n markdown,\n metadata,\n metadataMode,\n sanitizedHtml,\n textContent,\n });\n\n let truncated = false;\n if (maxChars !== undefined && (format === 'markdown' || format === 'text')) {\n const res = truncateMarkdown(payload, maxChars);\n payload = res.text;\n truncated = res.truncated;\n }\n const diagnostics = truncated\n ? { ...baseDiagnostics, truncated }\n : baseDiagnostics;\n\n return {\n content: [{ text: payload, type: 'text' }],\n structuredContent: {\n schemaVersion: 1,\n content: payload,\n metadata,\n diagnostics,\n },\n };\n}\n\nexport const HTML_TO_MARKDOWN_TOOL_DESCRIPTION = `Convert an arbitrary HTML fragment to Markdown WITHOUT Readability article extraction (e.g. a snippet already isolated via chrome-devtools). Same Turndown + DOMPurify path as \\`extract\\`. The server fetches nothing: \\`localPath\\` is the only source, and \\`baseUrl\\` (optional) absolutizes relative links.`;\n\nexport function htmlToMarkdownHandler(args: unknown): CallToolResult {\n try {\n return htmlToMarkdown(args);\n } catch (err) {\n logger.error(\n `html_to_markdown failed: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n return toErrorResult(err);\n }\n}\n\nexport function registerHtmlToMarkdownTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'html_to_markdown',\n {\n title: 'Convert HTML fragment to Markdown',\n description: HTML_TO_MARKDOWN_TOOL_DESCRIPTION,\n inputSchema: htmlToMarkdownInputShape,\n outputSchema: outputSchemaShape,\n },\n htmlToMarkdownHandler,\n );\n}\n","import type { ToolHandle } from '../server.js';\n\nimport { toErrorResult } from '../errors.js';\nimport { logger } from '../logger.js';\nimport { buildDocument } from '../pipeline/dom.js';\nimport { applySelectors, normalizeDocument } from '../pipeline/normalize.js';\nimport { resolveOutline } from '../policy/outline.js';\nimport { readHtmlFile } from './html-source.js';\nimport { outlineOutputShape } from './output-schema.js';\nimport {\n type OutlineFromHtmlInput,\n outlineInputSchema,\n outlineInputShape,\n} from './schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\n// One line per heading, nested by depth; never blank so content[0].text is always scannable.\nfunction renderOutlineToc(\n outline: readonly { level: number; text: string }[],\n): string {\n if (outline.length === 0) {\n return '(no headings found)';\n }\n return outline\n .map(entry => `${' '.repeat(entry.level - 1)}- ${entry.text}`)\n .join('\\n');\n}\n\nexport function outlineDocument(rawArgs: unknown): CallToolResult {\n const { localPath, ...rest } = outlineInputSchema.parse(rawArgs);\n return outlineDocumentFromHtml({ html: readHtmlFile(localPath), ...rest });\n}\n\nexport function outlineDocumentFromHtml(\n input: Readonly<OutlineFromHtmlInput>,\n): CallToolResult {\n const { html, baseUrl, selectors } = input;\n\n const { document } = buildDocument(html, baseUrl);\n normalizeDocument(document);\n applySelectors(document, selectors);\n const outline = resolveOutline(document);\n\n const title =\n document.title.trim() ||\n document.querySelector('h1')?.textContent.replace(/\\s+/g, ' ').trim() ||\n undefined;\n const metadata = { title, baseUrl };\n\n const content = renderOutlineToc(outline);\n return {\n content: [{ text: content, type: 'text' }],\n structuredContent: {\n schemaVersion: 1,\n content,\n outline,\n metadata,\n },\n };\n}\n\nexport const OUTLINE_TOOL_DESCRIPTION = `Return the document outline (h1-h6 headings with stable anchor ids) of already-rendered (post-JavaScript) HTML as a cheap pre-check before full extraction. No Readability scoring, no Turndown, no sanitization — a pure heading walk. The server fetches nothing: \\`localPath\\` is the only source, and \\`baseUrl\\` is origin context only (never fetched).`;\n\nexport function outlineHandler(args: unknown): CallToolResult {\n try {\n return outlineDocument(args);\n } catch (err) {\n logger.error(\n `outline failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n return toErrorResult(err);\n }\n}\n\nexport function registerOutlineTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'outline',\n {\n title: 'Get document outline (heading TOC)',\n description: OUTLINE_TOOL_DESCRIPTION,\n inputSchema: outlineInputShape,\n outputSchema: outlineOutputShape,\n },\n outlineHandler,\n );\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nimport { loadConfig } from './config.js';\nimport { registerResources } from './resources.js';\nimport { registerSamplingTools } from './sampling.js';\nimport { registerChunkTextTool } from './tools/chunk_text.js';\nimport { registerExplainTool } from './tools/explain.js';\nimport { registerExtractGridTool } from './tools/extract_grid.js';\nimport { registerExtractLinksTool } from './tools/extract_links.js';\nimport { registerExtractListTool } from './tools/extract_list.js';\nimport { registerExtractMetadataTool } from './tools/extract_metadata.js';\nimport { registerExtractSectionTool } from './tools/extract_section.js';\nimport { registerExtractTablesTool } from './tools/extract_tables.js';\nimport { registerExtractTool } from './tools/extract.js';\nimport { registerHtmlToMarkdownTool } from './tools/html_to_markdown.js';\nimport { registerOutlineTool } from './tools/outline.js';\n\n// Re-exported so the dev hot-reload loop can import server.ts as a single\n// RuntimeModule and pick up every registration family (tools/resources).\nexport { registerResources };\n\n// `remove()` unregisters the tool and notifies the client; dev reload holds the\n// previous batch to remove before re-registering.\nexport interface ToolHandle {\n remove(): void;\n}\n\nexport function createMcpServer(): McpServer {\n const { name, version, title, description, instructions } = loadConfig();\n return new McpServer({ name, version, title, description }, { instructions });\n}\n\n// A tool earns its place only by skipping a pipeline stage (outline,\n// extract_metadata skip Readability) or returning a fundamentally different\n// shape (extract_list is a second engine). Alternate views of the one pipeline\n// — tables, images, structured data, code — are options or fields on `extract`,\n// not separate tools; that keeps the MCP surface small and the pipeline deep.\nexport function registerTools(server: McpServer): ToolHandle[] {\n return [\n registerChunkTextTool(server),\n registerExplainTool(server),\n registerExtractGridTool(server),\n registerExtractLinksTool(server),\n registerExtractListTool(server),\n registerExtractTool(server),\n registerExtractMetadataTool(server),\n registerExtractSectionTool(server),\n registerExtractTablesTool(server),\n registerHtmlToMarkdownTool(server),\n registerOutlineTool(server),\n ];\n}\n\n// Capability-gated tools are registered AFTER the initialize handshake, not\n// eagerly with the families above. The MCP `tools` capability locks in on the\n// first pre-connect registration (registerCapabilities throws post-connect),\n// so the tool-list handlers are already live; adding to `_registeredTools`\n// later simply appears in the next `tools/list` and fires `listChanged`. The\n// low-level Server populates `getClientCapabilities()` from the client's\n// `initialize` request, exposed via `McpServer.server`.\nexport function registerCapabilityGatedTools(server: McpServer): ToolHandle[] {\n const caps = server.server.getClientCapabilities();\n if (!caps?.sampling) {\n return [];\n }\n return registerSamplingTools(server);\n}\n\nexport function createServer(): McpServer {\n const server = createMcpServer();\n registerTools(server);\n registerResources(server);\n // Capability-gated tools (sampling) need the client's advertised\n // capabilities, which the low-level Server only populates after the\n // initialize handshake. Hook the `initialized` notification — it fires once\n // the client has connected and before any `tools/list`. `dev.ts` re-runs the\n // gate directly on reload (the client is already past `initialized`).\n server.server.oninitialized = () => {\n registerCapabilityGatedTools(server);\n };\n return server;\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\n\nimport { createServer } from './server.js';\n\nif (process.argv[2] === 'extract') {\n void import('./cli.js')\n .then(m => m.runCli(process.argv.slice(2)))\n .then(code => {\n // eslint-disable-next-line n/no-process-exit\n process.exit(code);\n })\n .catch((err: unknown) => {\n process.stderr.write(\n `${err instanceof Error ? err.message : String(err)}\\n`,\n );\n // eslint-disable-next-line n/no-process-exit\n process.exit(1);\n });\n} else {\n const server: McpServer = createServer();\n const transport = new StdioServerTransport();\n\n await server.connect(transport);\n\n function shutdown(): void {\n void server\n .close()\n .catch(() => {\n // best-effort; exiting regardless\n })\n .finally(() => {\n // Force-exit on signal; the n/no-process-exit rule targets libraries.\n // eslint-disable-next-line n/no-process-exit\n process.exit(0);\n });\n }\n\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/preset-cache.ts","../src/host-sampling.ts","../src/policy/lost-signal.ts","../src/policy/identifiers.ts","../src/policy/outline-chains.ts","../src/policy/selector-lint.ts","../src/tools/suggest-preset.ts","../src/sampling.ts","../src/tools/chunk_text.ts","../src/policy/explain.ts","../src/tools/explain.ts","../src/policy/sibling-scan.ts","../src/policy/grid-detector.ts","../src/tools/extract_grid.ts","../src/tools/extract_links.ts","../src/policy/list-detector.ts","../src/tools/extract_list.ts","../src/tools/extract_metadata.ts","../src/policy/outline.ts","../src/policy/section.ts","../src/tools/extract_section.ts","../src/tools/extract_tables.ts","../src/tools/html_to_markdown.ts","../src/tools/outline.ts","../src/server.ts","../src/index.ts"],"sourcesContent":["// Disk side of the preset store: one <site>.json file per preset, read into\n// the in-memory store at boot. Local to the user's machine, ordinary files;\n// the server never fetches and nothing expires by clock — staleness is\n// detector-checked per page (policy/presets.ts), so eviction here is disk\n// hygiene only.\n\nimport {\n mkdirSync,\n readdirSync,\n readFileSync,\n statSync,\n unlinkSync,\n writeFileSync,\n} from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { z } from 'zod';\n\nimport type { SitePreset } from './policy/presets.js';\n\nimport { logger } from './logger.js';\nimport { addPreset, normalizeSiteKey } from './policy/presets.js';\n\nexport const PRESETS_DIR_ENV = 'READABILITY_MCP_PRESETS_DIR';\n\nexport const MAX_PRESET_FILES = 64;\n\n// An empty scope applies nothing while reporting applied:true, and detectors\n// only mean something as a non-empty set — reject both at the file boundary.\nconst presetFileSchema = z.object({\n site: z.string().min(1),\n detectors: z.array(z.string().min(1)).min(1),\n scope: z\n .object({\n include: z.string().min(1).optional(),\n exclude: z.array(z.string().min(1)).min(1).optional(),\n })\n .refine(\n scope => scope.include !== undefined || scope.exclude !== undefined,\n {\n message: 'scope must carry include or exclude',\n },\n ),\n});\n\nexport interface PresetCacheReport {\n readonly loaded: number;\n readonly pruned: number;\n readonly skipped: number;\n}\n\n// Empty string disables preset loading; an absolute path wins over the\n// platform default.\nexport function resolvePresetsDir(\n env: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n const override = env[PRESETS_DIR_ENV];\n if (override !== undefined) {\n return override.trim() === '' ? undefined : override;\n }\n const root =\n env.XDG_CACHE_HOME ||\n join(\n homedir(),\n process.platform === 'darwin' ? 'Library/Caches' : '.cache',\n );\n return join(root, 'readability-mcp', 'presets');\n}\n\n// A preset that cannot load is a warning, never a boot failure — the pipeline\n// is designed to run without one.\nexport function loadPresetDir(dir: string): PresetCacheReport {\n let names: string[];\n try {\n names = readdirSync(dir)\n .filter(name => name.endsWith('.json'))\n .sort();\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n logger.debug(`no preset directory at ${dir}`);\n } else {\n logger.warn(`preset directory unreadable: ${dir}`);\n }\n return { loaded: 0, pruned: 0, skipped: 0 };\n }\n\n const ranked = rankPresetFiles(dir, names);\n\n const pruned = pruneRanked(dir, ranked.slice(MAX_PRESET_FILES));\n\n let loaded = 0;\n let skipped = 0;\n for (const { name } of ranked.slice(0, MAX_PRESET_FILES)) {\n const path = join(dir, name);\n if (loadPresetFile(path)) {\n loaded++;\n } else {\n skipped++;\n }\n }\n if (loaded > 0) {\n logger.info(`loaded ${loaded} site preset(s) from ${dir}`);\n }\n if (pruned > 0) {\n logger.info(\n `pruned ${pruned} preset file(s) beyond the ${MAX_PRESET_FILES}-file bound`,\n );\n }\n return { loaded, pruned, skipped };\n}\n\n// Env-resolving entry point for the boot paths; undefined means disabled.\nexport function loadPresets(\n env: NodeJS.ProcessEnv = process.env,\n): PresetCacheReport | undefined {\n const dir = resolvePresetsDir(env);\n return dir ? loadPresetDir(dir) : undefined;\n}\n\nfunction rankPresetFiles(\n dir: string,\n names: string[],\n): { mtimeMs: number; name: string }[] {\n const ranked: { mtimeMs: number; name: string }[] = [];\n for (const name of names) {\n try {\n ranked.push({ name, mtimeMs: statSync(join(dir, name)).mtimeMs });\n } catch {\n // Vanished between readdir and stat — nothing to load or prune.\n }\n }\n ranked.sort((a, b) => b.mtimeMs - a.mtimeMs);\n return ranked;\n}\n\nfunction pruneRanked(\n dir: string,\n ranked: { mtimeMs: number; name: string }[],\n): number {\n let pruned = 0;\n for (const { name } of ranked) {\n try {\n unlinkSync(join(dir, name));\n pruned++;\n } catch (err) {\n logger.warn(\n `could not prune preset file ${name}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n }\n return pruned;\n}\n\nexport interface PersistReport {\n readonly path?: string;\n readonly persisted: boolean;\n readonly reason?: string;\n}\n\n// The writer the loader was waiting for: the suggest loop saves an accepted\n// preset so the next server start loads it like any hand-placed file. A\n// failure leaves the in-memory preset working — persistence is best-effort,\n// exactly like loading.\nexport function savePreset(\n preset: SitePreset,\n env: NodeJS.ProcessEnv = process.env,\n): PersistReport {\n const dir = resolvePresetsDir(env);\n if (!dir) {\n return { persisted: false, reason: 'preset-directory-disabled' };\n }\n const shape = presetFileSchema.safeParse(preset);\n if (!shape.success) {\n return {\n persisted: false,\n reason: shape.error.issues[0]?.message ?? 'invalid preset shape',\n };\n }\n const key = normalizeSiteKey(preset.site);\n if (!key) {\n return { persisted: false, reason: 'site-is-not-a-hostname' };\n }\n const path = join(dir, `${key}.json`);\n try {\n mkdirSync(dir, { recursive: true });\n writeFileSync(path, `${JSON.stringify(shape.data, null, 2)}\\n`);\n } catch (err) {\n return {\n persisted: false,\n reason: err instanceof Error ? err.message : String(err),\n };\n }\n const over = rankPresetFiles(\n dir,\n readdirSync(dir).filter(name => name.endsWith('.json')),\n );\n pruneRanked(dir, over.slice(MAX_PRESET_FILES));\n return { path, persisted: true };\n}\n\nfunction loadPresetFile(path: string): boolean {\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(path, 'utf8')) as unknown;\n } catch (err) {\n logger.warn(\n `${path}: not readable JSON (${\n err instanceof Error ? err.message : String(err)\n }), preset skipped`,\n );\n return false;\n }\n const result = presetFileSchema.safeParse(parsed);\n if (!result.success) {\n logger.warn(`${path}: ${result.error.issues[0]?.message}, preset skipped`);\n return false;\n }\n if (!normalizeSiteKey(result.data.site)) {\n logger.warn(`${path}: site is not a hostname, preset skipped`);\n return false;\n }\n addPreset(result.data);\n return true;\n}\n","// The one seam to the HOST's model via MCP `sampling/createMessage`\n// (server→client request). The server never embeds a model and never calls a\n// provider directly — every LLM call is delegated to the connected client,\n// which picks the model and may prompt the user first. Gating on the\n// capability happens at registration (server.ts), not here.\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\n// Host models — and the human-in-the-loop approval MCP allows before each\n// call — routinely take longer than the SDK's 60s request default.\nconst SAMPLING_TIMEOUT_MS = 300_000;\n\nexport interface HostSampleInput {\n readonly maxTokens: number;\n readonly systemPrompt: string;\n readonly userText: string;\n}\n\nexport async function sampleText(\n server: McpServer,\n args: HostSampleInput,\n): Promise<string> {\n const result = await server.server.createMessage(\n {\n messages: [\n {\n role: 'user',\n content: { type: 'text', text: args.userText },\n },\n ],\n systemPrompt: args.systemPrompt,\n maxTokens: args.maxTokens,\n },\n { timeout: SAMPLING_TIMEOUT_MS },\n );\n if (result.content.type !== 'text') {\n throw new Error(\n `host sampling returned non-text content (${result.content.type})`,\n );\n }\n return result.content.text;\n}\n\n// createMessage has no structured-output field, so JSON proposals are\n// negotiated in prose: the prompt demands strict JSON, and the reply is\n// tolerated past code fences before the caller's schema takes over.\nexport class SuggestParseError extends Error {\n constructor(\n message: string,\n readonly rawText: string,\n ) {\n super(message);\n }\n}\n\nexport function extractJsonReply(text: string): unknown {\n const withoutFences = text.replace(\n /^[\\s\\S]*?```(?:json)?\\s*\\n?([\\s\\S]*?)\\n?```[\\s\\S]*$/,\n '$1',\n );\n const trimmed = withoutFences.trim();\n try {\n return JSON.parse(trimmed);\n } catch {\n const start = trimmed.indexOf('{');\n const end = trimmed.lastIndexOf('}');\n if (start !== -1 && end > start) {\n try {\n return JSON.parse(trimmed.slice(start, end + 1));\n } catch {\n // Fall through to the error below.\n }\n }\n throw new SuggestParseError(\n `host sampling returned text that does not parse as JSON (${trimmed.slice(0, 80)}…)`,\n trimmed,\n );\n }\n}\n\nexport async function sampleJson(\n server: McpServer,\n args: HostSampleInput,\n): Promise<unknown> {\n return extractJsonReply(await sampleText(server, args));\n}\n","import type { GatingSignal } from './gating.js';\n\nimport { countWords } from './text.js';\n\n// Below this the extraction cannot be the article a reader came for. The\n// measured corpus floor: the smallest healthy extract is gatwick at 435 words;\n// the smallest known-lost one (WIRED's metered barrier) still carries 180.\nexport const NEAR_EMPTY_WORDS = 120;\n\n// Curated junk signatures over the EXTRACTED text, not the page — gating's\n// METERED_TEXT_RE reads the whole document, which is a different question.\n// Each entry is measured on a real capture: the first two are the Daily Mail\n// player controls that survive inside itemprop=\"articleBody\" (dailymail-a66),\n// the third is the metered barrier WIRED prints when the article itself was\n// truncated server-side.\nconst DEBRIS_PROBES: readonly {\n readonly label: string;\n readonly pattern: RegExp;\n}[] = [\n { label: 'player-controls', pattern: /Loaded:\\s*\\d+%/ },\n { label: 'player-controls', pattern: /Duration Time \\d+:\\d\\d/ },\n {\n label: 'metered-barrier',\n pattern: /read your last free article|subscribe to continue reading/i,\n },\n];\n\nexport interface LostEvidence {\n readonly debrisProbes: readonly string[];\n readonly fallbackUsed: boolean;\n readonly gatedReason?: string;\n readonly nearEmpty: boolean;\n readonly reasons: readonly string[];\n readonly wordCount: number;\n}\n\n// The verdict the suggest loop fires on. Gating alone never fires it — vendor\n// SDK classes (paywall-ineligible feed badges, piano offer headers) report on\n// free articles, and the pairing is enforced by construction: `gatedReason` is\n// carried as evidence for the prompt and the report, and only\n// fallback/near-empty/debris count as reasons.\nexport function assessLostSignal(input: {\n contentText: string;\n fallbackUsed: boolean;\n gated?: GatingSignal;\n}): LostEvidence {\n const wordCount = countWords(input.contentText);\n const nearEmpty = wordCount < NEAR_EMPTY_WORDS;\n const debrisProbes = [\n ...new Set(\n DEBRIS_PROBES.filter(probe => probe.pattern.test(input.contentText)).map(\n probe => probe.label,\n ),\n ),\n ];\n const reasons = [\n ...(input.fallbackUsed ? ['fallback-used'] : []),\n ...(nearEmpty ? ['near-empty'] : []),\n ...debrisProbes.map(label => `debris:${label}`),\n ];\n return {\n debrisProbes,\n fallbackUsed: input.fallbackUsed,\n gatedReason: input.gated?.likely ? input.gated.reason : undefined,\n nearEmpty,\n reasons,\n wordCount,\n };\n}\n","// Char-class transition scoring for CSS identifiers, after readweb's measured\n// price table: a class like `css-1a2b3c` or `content_1tsiE` is priced per\n// adjacent char-class transition and normalized by length, and a compound token\n// also fails if any delimiter-separated segment does — a stable stem must not\n// carry a volatile build hash (`BoxStyles_commercial__Wo6Z4`).\nexport type CharClass = 'Digit' | 'Lower' | 'Other' | 'Symbol' | 'Upper';\n\nexport const GIBBERISH_THRESHOLD = 0.3;\n\nconst PRICE_TABLE: Partial<Record<`${CharClass}:${CharClass}`, number>> = {\n 'Upper:Lower': 0.2,\n 'Lower:Upper': 0.5,\n 'Symbol:Upper': 0.4,\n 'Symbol:Lower': 0.3,\n 'Symbol:Digit': 0.9,\n 'Upper:Digit': 1.4,\n 'Lower:Digit': 1.3,\n 'Digit:Upper': 1.4,\n 'Digit:Lower': 1.5,\n 'Digit:Symbol': 1.2,\n 'Upper:Symbol': 0.2,\n 'Lower:Symbol': 0.2,\n 'Digit:Digit': 1.2,\n 'Symbol:Symbol': 0.3,\n 'Upper:Upper': 0.1,\n 'Lower:Lower': 0.1,\n 'Other:Other': 0.0,\n};\n\nfunction classifyChar(ch: string): CharClass {\n if (/[A-Z]/.test(ch)) {\n return 'Upper';\n }\n if (/[a-z]/.test(ch)) {\n return 'Lower';\n }\n if (/[0-9]/.test(ch)) {\n return 'Digit';\n }\n if (/[-_]/.test(ch)) {\n return 'Symbol';\n }\n return 'Other';\n}\n\nfunction transitionPrice(from: CharClass, to: CharClass): number {\n return PRICE_TABLE[`${from}:${to}`] ?? 1.0;\n}\n\nexport function gibberishScore(value: string): number {\n if (value.length < 2) {\n return 0;\n }\n let absolute = 0;\n let previous: CharClass | null = null;\n for (const ch of value) {\n const current = classifyChar(ch);\n if (previous !== null) {\n absolute += transitionPrice(previous, current);\n }\n previous = current;\n }\n return absolute / Math.max(1, value.length - 1);\n}\n\nfunction exceedsThreshold(value: string): boolean {\n return value.length >= 4 && gibberishScore(value) >= GIBBERISH_THRESHOLD;\n}\n\nexport function isGeneratedIdentifier(value: string): boolean {\n if (exceedsThreshold(value)) {\n return true;\n }\n return value.split(/[-_]/).some(segment => exceedsThreshold(segment));\n}\n","import type { SelectorScope } from '../pipeline/normalize.js';\n\nimport { buildDocument } from '../pipeline/dom.js';\nimport {\n applySelectors,\n normalizeDocument,\n resolveLazyImages,\n} from '../pipeline/normalize.js';\nimport { isGeneratedIdentifier } from './identifiers.js';\n\n// Step-0 measured thresholds: 200 own chars is the chain view that produced a\n// working proposal at ~630 tokens, and the debris that view misses (video\n// controls, captions) holds under 200 own chars — the round-two sweep runs at\n// 40 to surface it.\nexport const OUTLINE_OWN_TEXT_MIN = 200;\nexport const BLOCK_OWN_TEXT_MIN = 40;\nexport const OUTLINE_MAX_CHARS = 6000;\nexport const OUTLINE_MAX_CHAINS = 60;\nconst SAMPLE_MAX_CHARS = 80;\n\nconst NON_CONTENT_SELECTOR = 'script, style, noscript, template';\n\nconst HOP_ATTRIBUTE_WHITELIST = ['itemprop', 'role', 'data-testid'] as const;\n\nexport interface OutlineResult {\n readonly chainCount: number;\n readonly text: string;\n readonly truncated: boolean;\n}\n\nexport interface OutlineInput {\n readonly baseUrl?: string;\n readonly cleanChrome: boolean;\n readonly html: string;\n // `page` renders the round-one view of the whole normalized document; a\n // scope renders the round-two view of the included subtree after the scope\n // has been applied for real.\n readonly mode: 'page' | { readonly scope: SelectorScope };\n}\n\ninterface Candidate {\n readonly element: Element;\n readonly own: number;\n}\n\n// The document the outline renders is normalized exactly as extraction\n// normalizes it, so a selector proposed from this material validates and\n// applies against the same tree the pipeline will run it on.\nexport function buildChainOutline(input: OutlineInput): OutlineResult {\n const { document } = buildDocument(input.html, input.baseUrl);\n normalizeDocument(document, { cleanChrome: input.cleanChrome });\n resolveLazyImages(document);\n const scope = input.mode === 'page' ? undefined : input.mode.scope;\n if (scope) {\n applySelectors(document, scope);\n }\n const scopeRoot = scope?.include\n ? (document.body.querySelector(scope.include) ?? undefined)\n : undefined;\n\n const min = scope ? BLOCK_OWN_TEXT_MIN : OUTLINE_OWN_TEXT_MIN;\n const candidates: Candidate[] = [];\n for (const element of (scopeRoot ?? document.body).querySelectorAll('*')) {\n if (element.closest(NON_CONTENT_SELECTOR)) {\n continue;\n }\n const own = ownTextLength(element);\n if (own >= min) {\n candidates.push({ element, own });\n }\n }\n\n return scope\n ? groupedOutline(candidates, scopeRoot)\n : flatOutline(candidates);\n}\n\n// Round one: one full ancestor chain per qualifying node, step-0 format.\nfunction flatOutline(candidates: Candidate[]): OutlineResult {\n const lines: string[] = [];\n let truncated = false;\n for (const candidate of [...candidates].sort((a, b) => b.own - a.own)) {\n if (lines.length >= OUTLINE_MAX_CHAINS) {\n truncated = true;\n break;\n }\n const hops = ancestorsUpTo(candidate.element, undefined).map(element =>\n hopWithSizes(element),\n );\n const line = hops.join(' > ');\n if (lines.join('\\n---\\n').length + line.length > OUTLINE_MAX_CHARS) {\n truncated = true;\n break;\n }\n lines.push(line);\n }\n return { chainCount: lines.length, text: lines.join('\\n---\\n'), truncated };\n}\n\n// Round two: the included subtree's remaining text blocks, grouped by\n// structural chain so dozens of prose paragraphs collapse into one line and\n// the odd control-text/caption container stands alone. Smallest own text\n// first — that is where the debris lives.\nfunction groupedOutline(\n candidates: Candidate[],\n scopeRoot: Element | undefined,\n): OutlineResult {\n const groups = new Map<\n string,\n { chain: string; count: number; max: number; min: number; sample: string }\n >();\n for (const candidate of candidates) {\n const chain = ancestorsUpTo(candidate.element, scopeRoot).map(element =>\n renderHop(element),\n );\n if (scopeRoot) {\n chain.push(renderHop(scopeRoot));\n }\n const key = chain.join(' > ');\n const existing = groups.get(key);\n if (existing) {\n existing.count += 1;\n existing.min = Math.min(existing.min, candidate.own);\n existing.max = Math.max(existing.max, candidate.own);\n } else {\n groups.set(key, {\n chain: key,\n count: 1,\n min: candidate.own,\n max: candidate.own,\n sample: sampleText(candidate.element),\n });\n }\n }\n\n const lines: string[] = [];\n let truncated = false;\n const sorted = [...groups.values()].sort((a, b) => a.min - b.min);\n for (const group of sorted) {\n if (lines.length >= OUTLINE_MAX_CHAINS) {\n truncated = true;\n break;\n }\n const line = `${group.chain} (own:${group.min}..${group.max}, ×${group.count}) sample: \"${group.sample}\"`;\n if (lines.join('\\n').length + line.length > OUTLINE_MAX_CHARS) {\n truncated = true;\n break;\n }\n lines.push(line);\n }\n return { chainCount: lines.length, text: lines.join('\\n'), truncated };\n}\n\n// Nearest-to-leaf first; the walk stops below <body> (the root applySelectors\n// never matches) and below the include root when one is given — the caller\n// prepends that root's own hop where it is wanted.\nfunction ancestorsUpTo(element: Element, stop: Element | undefined): Element[] {\n const hops: Element[] = [];\n let node: Element | null = element;\n const body = element.ownerDocument.body;\n while (node && node !== stop && node !== body) {\n hops.push(node);\n node = node.parentElement;\n }\n return hops;\n}\n\nfunction hopWithSizes(element: Element): string {\n const own = ownTextLength(element);\n const all = element.textContent.length;\n return `${renderHop(element)} (own:${own}, all:${all})`;\n}\n\nfunction ownTextLength(element: Element): number {\n let own = 0;\n for (const node of element.childNodes) {\n if (node.nodeType === node.TEXT_NODE) {\n own += (node.textContent ?? '').length;\n }\n }\n return own;\n}\n\n// Copy-safety: what renders here is what the suggester may type back. Every\n// token is real CSS — real attribute names and values, and classes/ids that\n// survive the generated-identifier lint, so a hash class can never be copied\n// into a proposal because it was never shown.\nfunction renderHop(element: Element): string {\n let hop = element.tagName.toLowerCase();\n const id = element.id;\n if (id && !isGeneratedIdentifier(id)) {\n hop += `#${id}`;\n }\n const classes = (element.getAttribute('class') ?? '')\n .split(/\\s+/)\n .filter(Boolean)\n .filter(className => !isGeneratedIdentifier(className));\n if (classes.length > 0) {\n hop += `.${classes.join('.')}`;\n }\n for (const attribute of HOP_ATTRIBUTE_WHITELIST) {\n const value = element.getAttribute(attribute);\n if (value) {\n hop += `[${attribute}=\"${value}\"]`;\n }\n }\n return hop;\n}\n\nfunction sampleText(element: Element): string {\n const text = element.textContent.trim().replace(/\\s+/g, ' ');\n return text.length > SAMPLE_MAX_CHARS\n ? `${text.slice(0, SAMPLE_MAX_CHARS)}…`\n : text;\n}\n","import { isGeneratedIdentifier } from './identifiers.js';\nimport { selectorMisses } from './presets.js';\n\nexport type SelectorViolationKind =\n | 'contains-pseudo'\n | 'exclude-shadows-include'\n | 'generated-identifier'\n | 'no-match'\n | 'positional-pseudo'\n | 'unparseable';\n\nexport interface SelectorViolation {\n readonly detail: string;\n readonly kind: SelectorViolationKind;\n readonly selector: string;\n}\n\nexport interface ProposalInput {\n readonly detectors: readonly string[];\n readonly document: Document;\n readonly scope: {\n readonly exclude?: readonly string[];\n readonly include?: string;\n };\n}\n\n// nwsapi/jsdom accept `:contains` and silently apply it, so an engine error can\n// reject nothing — the rejection has to happen here, before any selector\n// reaches querySelector. Positional pseudos are rejected as a family: they\n// describe this render's child order, not the site's structure.\nconst POSITIONAL_PSEUDO_RE =\n /:(?:nth(?:-last)?(?:-child|-of-type)|first(?:-child|-of-type)|last(?:-child|-of-type)|only(?:-child|-of-type))\\b/i;\nconst CONTAINS_PSEUDO_RE = /:contains\\s*\\(/i;\n\nfunction quotedSegmentsOut(selector: string): string {\n return selector.replace(/([\"'])(?:\\\\.|(?!\\1)[\\s\\S])*\\1/g, '\"\"');\n}\n\n// `.class` and `#id` tokens are identifiers; `[attr=\"value\"]` values are\n// substring matches against whatever the site emitted and are exempt.\nfunction identifierTokens(selector: string): string[] {\n return [...quotedSegmentsOut(selector).matchAll(/[.#]([A-Za-z0-9_-]+)/g)].map(\n match => match[1],\n );\n}\n\nexport function lintSelectorText(\n selector: string,\n): SelectorViolation | undefined {\n if (CONTAINS_PSEUDO_RE.test(selector)) {\n return {\n kind: 'contains-pseudo',\n selector,\n detail: ':contains is not standard CSS and changes the result silently',\n };\n }\n const positional = POSITIONAL_PSEUDO_RE.exec(selector);\n if (positional) {\n return {\n kind: 'positional-pseudo',\n selector,\n detail: `${positional[0]} depends on this page's child order, not the site's layout`,\n };\n }\n for (const token of identifierTokens(selector)) {\n if (isGeneratedIdentifier(token)) {\n return {\n kind: 'generated-identifier',\n selector,\n detail: `\"${token}\" reads as a generated hash that changes on deploy`,\n };\n }\n }\n return undefined;\n}\n\n// Propose-time validation for a preset a suggester wants stored. Stricter than\n// presetMatches: an exclude that matches nothing is tolerated at runtime (the\n// debris is absent from some pages) but at propose time it is a mistake — the\n// proposal names debris that is not there.\nexport function lintProposal({\n document,\n detectors,\n scope,\n}: ProposalInput): readonly SelectorViolation[] {\n const violations: SelectorViolation[] = [];\n const selectors = [\n ...detectors,\n ...(scope.include ? [scope.include] : []),\n ...(scope.exclude ?? []),\n ];\n for (const selector of selectors) {\n const violation = lintSelectorText(selector);\n if (violation) {\n violations.push(violation);\n }\n }\n\n let includeRoot: Element | undefined;\n if (\n scope.include &&\n !violations.some(violation => violation.selector === scope.include)\n ) {\n try {\n includeRoot = document.body.querySelector(scope.include) ?? undefined;\n } catch {\n violations.push({\n kind: 'unparseable',\n selector: scope.include,\n detail: 'the selector engine rejects this selector',\n });\n }\n }\n\n for (const detector of detectors) {\n if (violations.some(violation => violation.selector === detector)) {\n continue;\n }\n if (selectorMisses(document, detector)) {\n violations.push({\n kind: 'no-match',\n selector: detector,\n detail: 'detector matches nothing on this page',\n });\n }\n }\n if (\n scope.include &&\n !includeRoot &&\n !violations.some(violation => violation.selector === scope.include)\n ) {\n violations.push({\n kind: 'no-match',\n selector: scope.include,\n detail:\n 'include matches nothing inside <body>, where applySelectors searches',\n });\n }\n for (const selector of scope.exclude ?? []) {\n if (violations.some(violation => violation.selector === selector)) {\n continue;\n }\n try {\n if (document.querySelectorAll(selector).length === 0) {\n violations.push({\n kind: 'no-match',\n selector,\n detail: 'exclude matches nothing on this page',\n });\n }\n } catch {\n violations.push({\n kind: 'unparseable',\n selector,\n detail: 'the selector engine rejects this selector',\n });\n }\n }\n\n // Excludes are removed document-wide before the include is applied, so an\n // exclude matching an ancestor of the include root (or the root itself)\n // deletes it, and the include then quietly no-ops over a damaged body.\n if (includeRoot) {\n for (const selector of scope.exclude ?? []) {\n if (violations.some(violation => violation.selector === selector)) {\n continue;\n }\n try {\n for (const match of document.querySelectorAll(selector)) {\n if (match.contains(includeRoot)) {\n violations.push({\n kind: 'exclude-shadows-include',\n selector,\n detail: 'this exclude removes the include root itself',\n });\n break;\n }\n }\n } catch {\n // Already reported as unparseable.\n }\n }\n }\n\n return violations;\n}\n","import { z } from 'zod';\n\nimport type { GatingSignal } from '../policy/gating.js';\nimport type { PresetScope } from '../policy/presets.js';\nimport type { SelectorViolation } from '../policy/selector-lint.js';\nimport type { ToolHandle } from '../server.js';\nimport type { StructuredContent } from './output-schema.js';\n\nimport { toErrorResult } from '../errors.js';\nimport { sampleJson, SuggestParseError } from '../host-sampling.js';\nimport { logger } from '../logger.js';\nimport { buildDocument } from '../pipeline/dom.js';\nimport { normalizeDocument, resolveLazyImages } from '../pipeline/normalize.js';\nimport { assessLostSignal } from '../policy/lost-signal.js';\nimport { buildChainOutline } from '../policy/outline-chains.js';\nimport {\n addPreset,\n normalizeSiteKey,\n presetForSite,\n removePreset,\n} from '../policy/presets.js';\nimport { lintProposal } from '../policy/selector-lint.js';\nimport { savePreset } from '../preset-cache.js';\nimport { extractArticleFromHtml } from './extract.js';\nimport { readHtmlFile } from './html-source.js';\nimport { localPathField } from './schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\nconst SUGGEST_SYSTEM_PROMPT = `You propose site-level CSS presets for a web-article extractor. The extractor removes every \"exclude\" match document-wide, then keeps the FIRST match of \"include\" in document order as the entire page body (the search runs inside <body>). An exclude matching an ancestor of the include root destroys it.\n\nRules:\n- \"include\": exactly ONE selector that matches an element inside <body> on THIS page. A non-matching selector silently does nothing.\n- \"exclude\": selectors naming debris containers (video players, carousels, caption figures, comment blocks, promo modules).\n- \"detectors\": 1-3 selectors naming stable site-template containers that also match on other pages of the same site. Their loss is what retires the preset, so they must be structural, not per-page.\n- Forbidden — statically rejected and fed back to you: :nth-child and every positional pseudo (:first-child, :last-of-type, :only-child, ...), :contains, and generated hash class names (css-1a2b3c, content_1tsiE, BoxStyles_x__Wo6Z4).\n- Prefer itemprop, role, data-testid, ids, and stable class-name stems; quote attribute values exactly as rendered in the material.\n- The material shows every text-bearing node as a CSS chain with text sizes. It deliberately carries no article prose.\nReturn strict JSON only — no prose, no code fences.`;\n\nconst suggestPresetInputShape = {\n localPath: localPathField,\n baseUrl: z\n .url()\n .describe(\n 'A URL of the site the page belongs to — its host names the preset. Required: without a host there is no site key to store a preset under, and presets only resolve for pages whose site matches.',\n ),\n secondPath: localPathField\n .optional()\n .describe(\n 'A second capture of the SAME site. The converged preset must verify on it too — detectors match, extraction clean — before anything is persisted; without it the preset is verified on the proposal page alone.',\n ),\n maxSamplingCalls: z\n .number()\n .int()\n .min(1)\n .max(8)\n .describe(\n 'Upper bound on host-model sampling calls for this run. Round one (include + detectors) and round two (excludes) each consume a call, rejected proposals consume retries. A budget of 1 skips round two.',\n )\n .default(4),\n} as const;\n\nconst suggestPresetInputSchema = z.object(suggestPresetInputShape);\n\nconst proposalSchema = z.object({\n detectors: z.array(z.string().min(1)).min(1).max(6),\n scope: z.object({\n include: z.string().min(1),\n exclude: z.array(z.string().min(1)).max(12).optional(),\n }),\n});\n\nconst excludeProposalSchema = z.object({\n exclude: z.array(z.string().min(1)).max(12),\n});\n\nconst MAX_SAMPLING_TOKENS = 1024;\n\nexport const SUGGEST_PRESET_TOOL_DESCRIPTION = `Ask the HOST's model to propose a site preset (detectors + selectors) for a page whose extraction was lost, then verify it: proposals are validated deterministically, applied through the real pipeline, and a converged preset is stored in memory and persisted to the local preset cache so later extractions of the same site apply it automatically. Runs a bounded two-round loop over MCP \\`sampling/createMessage\\` — the server embeds no model. The tool is only listed when the connected client advertises the sampling capability, and it refuses to run when the baseline extraction looks healthy: call it after \\`extract\\` reports gated content, a fallback extraction, a near-empty result, or visible debris such as video-player controls.`;\n\nconst triggerShape = {\n fired: z.boolean().describe('Whether the suggest loop ran at all.'),\n reasons: z\n .array(z.string())\n .describe(\n 'Why the loop ran (the lost signals observed on the baseline extraction), or why it refused.',\n ),\n wordCount: z\n .number()\n .describe(\n 'Word count of the baseline extraction; 0 when the loop never ran.',\n ),\n debrisProbes: z\n .array(z.string())\n .describe('Debris signatures matched in the baseline extraction text.'),\n gatedReason: z\n .string()\n .optional()\n .describe('The gating signal reported by the baseline extraction, if any.'),\n} as const;\n\nconst suggestPresetOutputShape = {\n schemaVersion: z.literal(1).describe('Structured-content schema version.'),\n content: z.string().describe('Human-readable report of the run.'),\n trigger: z.object(triggerShape).describe('Baseline lost-signal verdict.'),\n preset: z\n .object({\n site: z.string().describe('The host the preset is keyed by.'),\n detectors: z\n .array(z.string())\n .describe('Template fingerprints that must keep matching.'),\n scope: z\n .object({\n include: z.string().optional(),\n exclude: z.array(z.string()).optional(),\n })\n .describe('The accepted selector scope.'),\n })\n .optional()\n .describe(\n 'The preset that converged, present only when one was accepted and verified.',\n ),\n verification: z\n .object({\n applied: z\n .boolean()\n .describe(\n 'Whether the stored preset resolved and applied during the verification extraction.',\n ),\n converged: z\n .boolean()\n .describe('Whether the verification extraction came back clean.'),\n verifiedPages: z\n .number()\n .int()\n .min(1)\n .max(2)\n .describe(\n 'Pages the preset verified on: 2 only when secondPath was given and the preset verified on that capture too.',\n ),\n wordCountBefore: z.number().describe('Baseline extraction word count.'),\n wordCountAfter: z\n .number()\n .describe('Verification extraction word count.'),\n })\n .optional()\n .describe('Result of extracting with the preset actually stored.'),\n secondVerification: z\n .object({\n applied: z\n .boolean()\n .describe(\n 'Whether the preset resolved and applied on the second capture.',\n ),\n converged: z\n .boolean()\n .describe('Whether the second-capture extraction came back clean.'),\n wordCountAfter: z\n .number()\n .describe('Second-capture extraction word count.'),\n })\n .optional()\n .describe(\n 'Verification on the secondPath capture; absent when secondPath was not given or the capture belongs to another site.',\n ),\n persistence: z\n .object({\n persisted: z\n .boolean()\n .describe(\n 'Whether the preset file was written to the local preset cache directory.',\n ),\n path: z.string().optional().describe('The file written, when persisted.'),\n reason: z\n .string()\n .optional()\n .describe('Why nothing was persisted, when it was not.'),\n })\n .optional()\n .describe(\n 'Outcome of the disk write; absent when the run never accepted a preset.',\n ),\n sampling: z\n .object({\n calls: z.number().int().describe('Sampling calls actually made.'),\n budget: z.number().int().describe('The budget this run was given.'),\n budgetExhausted: z\n .boolean()\n .describe('Whether the budget ran out before the loop could converge.'),\n })\n .describe('Sampling accounting for this run.'),\n} as const;\n\nexport function registerSuggestPresetTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'suggest_preset',\n {\n title: 'Suggest a site preset using the host model',\n description: SUGGEST_PRESET_TOOL_DESCRIPTION,\n inputSchema: suggestPresetInputShape,\n outputSchema: suggestPresetOutputShape,\n },\n async (rawArgs: unknown): Promise<CallToolResult> => {\n const args = suggestPresetInputSchema.parse(rawArgs);\n try {\n return await runSuggestLoop(args, server);\n } catch (err) {\n logger.error(\n `suggest_preset failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n return toErrorResult(err);\n }\n },\n );\n}\n\ninterface Trigger {\n debrisProbes: readonly string[];\n fired: boolean;\n gatedReason?: string;\n reasons: readonly string[];\n wordCount: number;\n}\n\ninterface AcceptedPreset {\n detectors: readonly string[];\n scope: PresetScope;\n site: string;\n}\n\ninterface Verification {\n applied: boolean;\n converged: boolean;\n verifiedPages: 1 | 2;\n wordCountAfter: number;\n wordCountBefore: number;\n}\n\ninterface SecondVerification {\n applied: boolean;\n converged: boolean;\n wordCountAfter: number;\n}\n\ninterface LoopState {\n budget: number;\n calls: number;\n // A sampling attempt was blocked by the budget — distinct from a run that\n // simply never needed the remaining calls.\n exhausted: boolean;\n persistence?: { path?: string; persisted: boolean; reason?: string };\n}\n\nexport async function runSuggestLoop(\n args: {\n baseUrl: string;\n localPath: string;\n maxSamplingCalls: number;\n secondPath?: string;\n },\n server: McpServer,\n): Promise<CallToolResult> {\n const site = normalizeSiteKey(args.baseUrl);\n if (!site) {\n throw new Error(`baseUrl does not name a site: ${args.baseUrl}`);\n }\n const html = readHtmlFile(args.localPath);\n const secondHtml = args.secondPath\n ? readHtmlFile(args.secondPath)\n : undefined;\n const state: LoopState = {\n budget: args.maxSamplingCalls,\n calls: 0,\n exhausted: false,\n };\n\n if (presetForSite(site)) {\n return report(state, {\n trigger: {\n debrisProbes: [],\n fired: false,\n reasons: ['preset-exists'],\n wordCount: 0,\n },\n });\n }\n\n const baseline = extractText(html, args.baseUrl);\n const canonicalSite = normalizeSiteKey(baseline.metadata.canonical);\n if (canonicalSite && canonicalSite !== site) {\n throw new Error(\n `baseUrl names ${site} but the page's canonical URL names ${canonicalSite} — the capture does not belong to the site the preset would be keyed by.`,\n );\n }\n const baselineEvidence = assessLostSignal({\n contentText: baseline.content,\n fallbackUsed: baseline.diagnostics.fallbackUsed ?? false,\n gated: baseline.diagnostics.gated,\n });\n const trigger: Trigger = {\n debrisProbes: baselineEvidence.debrisProbes,\n fired: baselineEvidence.reasons.length > 0,\n gatedReason: baselineEvidence.gatedReason,\n reasons: baselineEvidence.reasons,\n wordCount: baselineEvidence.wordCount,\n };\n if (!trigger.fired) {\n return report(state, { trigger });\n }\n\n const core = await proposeCore(\n html,\n args.baseUrl,\n site,\n baseline,\n trigger,\n state,\n server,\n );\n if (!core) {\n return report(state, { trigger });\n }\n\n const excludes = await proposeExcludes(\n html,\n args.baseUrl,\n core,\n state,\n server,\n );\n const preset: AcceptedPreset = {\n detectors: core.detectors,\n site,\n scope: {\n exclude: excludes.length > 0 ? excludes : undefined,\n include: core.scope.include,\n },\n };\n\n addPreset({\n detectors: preset.detectors,\n scope: preset.scope,\n site: preset.site,\n });\n const verification = extractText(html, args.baseUrl);\n const verificationEvidence = assessLostSignal({\n contentText: verification.content,\n fallbackUsed: verification.diagnostics.fallbackUsed ?? false,\n gated: verification.diagnostics.gated,\n });\n const applied = verification.presetSignal?.applied === true;\n const converged = applied && verificationEvidence.reasons.length === 0;\n const verdict: Verification = {\n applied,\n converged,\n verifiedPages: 1,\n wordCountAfter: verificationEvidence.wordCount,\n wordCountBefore: trigger.wordCount,\n };\n\n if (!converged) {\n removePreset(preset.site);\n state.persistence = { persisted: false, reason: 'not-converged' };\n return report(state, { preset, trigger, verification: verdict });\n }\n\n let secondVerification: SecondVerification | undefined;\n if (secondHtml) {\n const second = extractText(secondHtml, args.baseUrl);\n const secondCanonicalSite = normalizeSiteKey(second.metadata.canonical);\n if (secondCanonicalSite && secondCanonicalSite !== site) {\n removePreset(preset.site);\n state.persistence = {\n persisted: false,\n reason: 'second-page-canonical-mismatch',\n };\n return report(state, { preset, trigger, verification: verdict });\n }\n const secondEvidence = assessLostSignal({\n contentText: second.content,\n fallbackUsed: second.diagnostics.fallbackUsed ?? false,\n gated: second.diagnostics.gated,\n });\n const secondApplied = second.presetSignal?.applied === true;\n const secondConverged =\n secondApplied && secondEvidence.reasons.length === 0;\n secondVerification = {\n applied: secondApplied,\n converged: secondConverged,\n wordCountAfter: secondEvidence.wordCount,\n };\n if (!secondConverged) {\n removePreset(preset.site);\n state.persistence = {\n persisted: false,\n reason: 'second-page-not-converged',\n };\n return report(state, {\n preset,\n secondVerification,\n trigger,\n verification: verdict,\n });\n }\n verdict.verifiedPages = 2;\n }\n\n state.persistence = savePreset(preset);\n return report(state, {\n preset,\n secondVerification,\n trigger,\n verification: verdict,\n });\n}\n\nfunction report(\n state: LoopState,\n parts: {\n preset?: AcceptedPreset;\n secondVerification?: SecondVerification;\n trigger: Trigger;\n verification?: Verification;\n },\n): CallToolResult {\n const lines: string[] = [];\n if (!parts.trigger.fired) {\n lines.push(\n parts.trigger.reasons.length > 0\n ? `No suggest loop run: ${parts.trigger.reasons.join(', ')}.`\n : `No suggest loop run: the baseline extraction looks healthy (${parts.trigger.wordCount} words, no lost signal).`,\n );\n } else {\n lines.push(\n `Lost signals on the baseline extraction (${parts.trigger.wordCount} words): ${parts.trigger.reasons.join(', ')}.`,\n );\n }\n if (state.calls > 0) {\n lines.push(\n `Sampling: ${state.calls}/${state.budget} call(s)${state.exhausted ? ' — budget exhausted' : ''}.`,\n );\n }\n if (parts.verification) {\n lines.push(\n `Verification with the preset stored: applied=${parts.verification.applied}, converged=${parts.verification.converged}, ${parts.verification.wordCountAfter} words, pages verified: ${parts.verification.verifiedPages}.`,\n );\n }\n if (parts.secondVerification) {\n lines.push(\n `Second-page verification: applied=${parts.secondVerification.applied}, converged=${parts.secondVerification.converged}, ${parts.secondVerification.wordCountAfter} words.`,\n );\n }\n if (parts.preset) {\n lines.push(\n `Accepted preset for ${parts.preset.site}: include=${parts.preset.scope.include ?? '(none)'}, exclude=${(parts.preset.scope.exclude ?? []).join(', ') || '(none)'}, detectors=${parts.preset.detectors.join(', ')}.`,\n );\n }\n if (state.persistence) {\n lines.push(\n state.persistence.persisted\n ? `Persisted to ${state.persistence.path}.`\n : `Not persisted: ${state.persistence.reason}.`,\n );\n }\n\n return {\n content: [{ text: lines.join('\\n'), type: 'text' }],\n structuredContent: {\n content: lines.join('\\n'),\n persistence: state.persistence,\n preset: parts.preset,\n sampling: {\n budget: state.budget,\n budgetExhausted: state.exhausted,\n calls: state.calls,\n },\n schemaVersion: 1 as const,\n secondVerification: parts.secondVerification,\n trigger: parts.trigger,\n verification: parts.verification,\n },\n };\n}\n\ninterface CoreProposal {\n detectors: readonly string[];\n scope: PresetScope & { include: string };\n}\n\nfunction coreProposalUserText(\n site: string,\n title: string,\n evidence: {\n debrisProbes: readonly string[];\n gatedReason?: string;\n wordCount: number;\n },\n outline: { text: string; truncated: boolean },\n rejection: string | undefined,\n): string {\n return [\n `SITE: ${site}`,\n `PAGE: ${title || '(untitled)'}`,\n `BASELINE: ${evidence.wordCount} words; debris probes: ${evidence.debrisProbes.join(', ') || 'none'}; gating: ${evidence.gatedReason ?? 'none'}. The extraction lost to page junk — propose a preset.`,\n '',\n 'MATERIAL — ancestor chains of every node holding at least 200 chars of its own text, hop format tag#id.classes[attr=\"value\"] (own:X, all:Y):',\n outline.text,\n ...(outline.truncated\n ? [\n '(material truncated at the budget — the largest own-text chains come first)',\n ]\n : []),\n ...(rejection\n ? [\n '',\n 'YOUR PREVIOUS PROPOSAL WAS REJECTED:',\n rejection,\n 'Return corrected strict JSON.',\n ]\n : ['']),\n 'Return JSON: {\"detectors\": [\"...\"], \"scope\": {\"include\": \"...\", \"exclude\": [\"...\"]}}',\n ].join('\\n');\n}\n\nasync function proposeCore(\n html: string,\n baseUrl: string,\n site: string,\n baseline: { metadata: { title?: string } },\n evidence: Trigger,\n state: LoopState,\n server: McpServer,\n): Promise<CoreProposal | undefined> {\n const outline = buildChainOutline({\n baseUrl,\n cleanChrome: true,\n html,\n mode: 'page',\n });\n let rejection: string | undefined;\n while (state.calls < state.budget) {\n state.calls += 1;\n let reply: unknown;\n try {\n reply = await sampleJson(server, {\n maxTokens: MAX_SAMPLING_TOKENS,\n systemPrompt: SUGGEST_SYSTEM_PROMPT,\n userText: coreProposalUserText(\n site,\n baseline.metadata.title ?? '',\n evidence,\n outline,\n rejection,\n ),\n });\n } catch (err) {\n if (err instanceof SuggestParseError) {\n rejection = err.message;\n continue;\n }\n throw err;\n }\n const parsed = proposalSchema.safeParse(reply);\n if (!parsed.success) {\n rejection = `the reply must parse as {\"detectors\": [...], \"scope\": {\"include\": \"...\", \"exclude\": [...]}} — ${parsed.error.issues[0]?.message}`;\n continue;\n }\n const violations = lintProposal({\n detectors: parsed.data.detectors,\n document: normalizedDocument(html, baseUrl),\n scope: parsed.data.scope,\n });\n if (violations.length > 0) {\n rejection = renderViolations(violations);\n continue;\n }\n return {\n detectors: parsed.data.detectors,\n scope: {\n exclude: parsed.data.scope.exclude,\n include: parsed.data.scope.include,\n },\n };\n }\n state.exhausted = true;\n return undefined;\n}\n\nasync function proposeExcludes(\n html: string,\n baseUrl: string,\n core: CoreProposal,\n state: LoopState,\n server: McpServer,\n): Promise<readonly string[]> {\n if (state.calls >= state.budget) {\n // Documented skip: the round-one scope stands, nothing was blocked.\n return core.scope.exclude ?? [];\n }\n const outline = buildChainOutline({\n baseUrl,\n cleanChrome: true,\n html,\n mode: { scope: { include: core.scope.include } },\n });\n let rejection: string | undefined;\n while (state.calls < state.budget) {\n state.calls += 1;\n let reply: unknown;\n try {\n reply = await sampleJson(server, {\n maxTokens: MAX_SAMPLING_TOKENS,\n systemPrompt: SUGGEST_SYSTEM_PROMPT,\n userText: [\n `ACCEPTED SO FAR: {\"detectors\": ${JSON.stringify(core.detectors)}, \"scope\": {\"include\": ${JSON.stringify(core.scope.include)}}}`,\n '',\n 'MATERIAL — text blocks remaining inside the included subtree after the include was applied, grouped by chain, smallest own text first, each with a text sample:',\n outline.text,\n ...(rejection\n ? [\n '',\n 'YOUR PREVIOUS PROPOSAL WAS REJECTED:',\n rejection,\n 'Return corrected strict JSON.',\n ]\n : ['']),\n 'Propose excludes for the debris groups (video players, carousels, caption figures, comment blocks, promos) — never for prose paragraphs. Return [] if none. Return JSON: {\"exclude\": [\"...\"]}',\n ].join('\\n'),\n });\n } catch (err) {\n if (err instanceof SuggestParseError) {\n rejection = err.message;\n continue;\n }\n throw err;\n }\n const parsed = excludeProposalSchema.safeParse(reply);\n if (!parsed.success) {\n rejection = `the reply must parse as {\"exclude\": [\"...\"]} — ${parsed.error.issues[0]?.message}`;\n continue;\n }\n const violations = lintProposal({\n detectors: core.detectors,\n document: normalizedDocument(html, baseUrl),\n scope: { exclude: parsed.data.exclude, include: core.scope.include },\n });\n if (violations.length > 0) {\n rejection = renderViolations(violations);\n continue;\n }\n const merged = [...(core.scope.exclude ?? [])];\n for (const selector of parsed.data.exclude) {\n if (!merged.includes(selector)) {\n merged.push(selector);\n }\n }\n return merged;\n }\n state.exhausted = true;\n return core.scope.exclude ?? [];\n}\n\nfunction renderViolations(violations: readonly SelectorViolation[]): string {\n return violations\n .map(\n violation =>\n `- ${violation.kind}: ${violation.selector} — ${violation.detail}`,\n )\n .join('\\n');\n}\n\n// The tree proposals are validated against is normalized exactly as the\n// extract pipeline normalizes it — preset validation at extraction time runs\n// on the same tree, so a lint pass here is a faithful rehearsal.\nfunction normalizedDocument(html: string, baseUrl: string): Document {\n const { document } = buildDocument(html, baseUrl);\n normalizeDocument(document, { cleanChrome: true });\n resolveLazyImages(document);\n return document;\n}\n\nfunction extractText(\n html: string,\n baseUrl: string,\n): {\n content: string;\n diagnostics: {\n fallbackUsed?: boolean;\n gated?: GatingSignal;\n };\n metadata: { canonical?: string; title?: string };\n presetSignal?: { applied: boolean };\n} {\n const result = extractArticleFromHtml({\n baseUrl,\n cache: false,\n format: 'text',\n html,\n });\n if (result.isError || !result.structuredContent) {\n throw new Error('extraction failed inside the suggest loop');\n }\n const structured = result.structuredContent as StructuredContent;\n return {\n content: structured.content,\n diagnostics: {\n fallbackUsed: structured.diagnostics.fallbackUsed,\n gated: structured.diagnostics.gated,\n },\n metadata: structured.metadata,\n presetSignal: structured.diagnostics.preset,\n };\n}\n","// Optional, capability-gated tools backed by the HOST's model. The sampling\n// seam itself lives in host-sampling.ts; this file owns the family's tool\n// registrations.\n\nimport { z } from 'zod';\n\nimport type { ToolHandle } from './server.js';\n\nimport { toErrorResult } from './errors.js';\nimport { sampleText } from './host-sampling.js';\nimport { logger } from './logger.js';\nimport { registerSuggestPresetTool } from './tools/suggest-preset.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\nconst SUMMARIZE_SYSTEM_PROMPT =\n 'Summarize the user-supplied text concisely while preserving its key points, entities, and any decisive conclusions. Output only the summary prose — no preamble, no headings unless the source had them.';\n\nconst summarizeInputShape = {\n text: z\n .string()\n .describe(\n 'The markdown or text to summarize — typically the output of `extract`, `extract_section`, `html_to_markdown`, or `chunk_text`. Passed through to the host model verbatim; the server does not parse or modify it.',\n ),\n maxTokens: z\n .number()\n .int()\n .min(1)\n .describe(\n 'Upper bound on the summary length in tokens, forwarded to the host as `sampling/createMessage` maxTokens. The host chooses the actual length.',\n )\n .default(512),\n} as const;\n\nconst summarizeInputSchema = z.object(summarizeInputShape);\n\nexport const SUMMARIZE_TOOL_DESCRIPTION = `Summarize text using the HOST's model via MCP \\`sampling/createMessage\\` — the server embeds no model and calls no provider directly. Hand it the output of \\`extract\\`, \\`extract_section\\`, \\`html_to_markdown\\`, or any markdown/text string; the host picks the model and may ask the user to approve the sampling request (human-in-the-loop per MCP). The tool is only listed when the connected client advertises the sampling capability.`;\n\nasync function summarizeWithHost(\n server: McpServer,\n args: { maxTokens: number; text: string },\n): Promise<string> {\n return sampleText(server, {\n maxTokens: args.maxTokens,\n systemPrompt: SUMMARIZE_SYSTEM_PROMPT,\n userText: args.text,\n });\n}\n\nexport function registerSummarizeTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'summarize',\n {\n title: 'Summarize text using the host model',\n description: SUMMARIZE_TOOL_DESCRIPTION,\n inputSchema: summarizeInputShape,\n },\n async (rawArgs: unknown): Promise<CallToolResult> => {\n const args = summarizeInputSchema.parse(rawArgs);\n try {\n const summary = await summarizeWithHost(server, args);\n return {\n content: [{ type: 'text', text: summary }],\n };\n } catch (err) {\n logger.error(\n `summarize failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n return toErrorResult(err);\n }\n },\n );\n}\n\n// Mirrors registerTools/registerResources so the dev reload\n// loop and capability gate can treat sampling as one registration family.\nexport function registerSamplingTools(server: McpServer): ToolHandle[] {\n return [registerSummarizeTool(server), registerSuggestPresetTool(server)];\n}\n","import type { Chunk } from '../policy/chunk.js';\nimport type { ToolHandle } from '../server.js';\n\nimport { toErrorResult } from '../errors.js';\nimport { logger } from '../logger.js';\nimport { chunkMarkdown } from '../policy/chunk.js';\nimport { chunkTextOutputShape } from './output-schema.js';\nimport { chunkTextInputSchema, chunkTextInputShape } from './schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\n// One numbered section per chunk, heading context in brackets when present, so\n// content[0].text is always scannable without unpacking structuredContent.\nfunction renderChunkIndex(chunks: readonly Chunk[]): string {\n if (chunks.length === 0) {\n return '(no chunks emitted — input had no non-whitespace content)';\n }\n return chunks\n .map(chunk => {\n const head = chunk.headingContext ? ` [${chunk.headingContext}]` : '';\n return `## Chunk ${chunk.index}${head}\\n\\n${chunk.text}`;\n })\n .join('\\n\\n');\n}\n\nexport function chunkTextDocument(rawArgs: unknown): CallToolResult {\n const args = chunkTextInputSchema.parse(rawArgs);\n const { text, maxTokens, overlap, strategy } = args;\n const chunks = chunkMarkdown(text, { maxTokens, overlap, strategy });\n const content = renderChunkIndex(chunks);\n return {\n content: [{ text: content, type: 'text' }],\n structuredContent: {\n schemaVersion: 1,\n content,\n chunks,\n },\n };\n}\n\nexport const CHUNK_TEXT_TOOL_DESCRIPTION = `Split already-extracted text into token-bounded chunks for embedding/RAG. Each chunk carries its index, tokenCount (chars/4), and the nearest preceding markdown heading as headingContext. Operates on any text — pair with the \\`chunk\\` option on \\`extract\\` when you want chunks inline with the extraction. The server fetches nothing: \\`text\\` is the only input.`;\n\nexport function chunkTextHandler(args: unknown): CallToolResult {\n try {\n return chunkTextDocument(args);\n } catch (err) {\n logger.error(\n `chunk_text failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n return toErrorResult(err);\n }\n}\n\nexport function registerChunkTextTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'chunk_text',\n {\n title: 'Chunk text for RAG/embedding',\n description: CHUNK_TEXT_TOOL_DESCRIPTION,\n inputSchema: chunkTextInputShape,\n outputSchema: chunkTextOutputShape,\n },\n chunkTextHandler,\n );\n}\n","import { Readability } from '@mozilla/readability';\n\nimport type { GatingSignal } from './gating.js';\nimport type { PaginationSignal } from './pagination.js';\n\nimport { buildDocument } from '../pipeline/dom.js';\nimport {\n applySelectors,\n normalizeDocument,\n resolveLazyImages,\n type SelectorScope,\n} from '../pipeline/normalize.js';\nimport { isReaderable } from '../pipeline/readability.js';\nimport { assembleDiagnostics } from './diagnostics.js';\nimport { detectGating } from './gating.js';\nimport { detectPagination } from './pagination.js';\nimport { resolveReadabilityOptions } from './resolver.js';\n\n// Readability stamps `{ contentScore }` on candidate DOM nodes under a\n// `readability` expando (Readability.js:894/1272/1288). The property is\n// untyped upstream, so reach it through `unknown` rather than a guessed shape.\ninterface ReadabilityExpando {\n readonly contentScore?: number;\n}\n\nexport interface ExplainCandidate {\n readonly className: string;\n readonly id: string;\n readonly score: number;\n readonly selector: string;\n readonly tag: string;\n readonly textLength: number;\n}\n\nexport interface ExplainRemovedNodes {\n readonly boilerplate: number;\n readonly chrome: number;\n readonly total: number;\n}\n\nexport interface ExplainSnapshot {\n readonly html: string;\n readonly truncated: boolean;\n}\n\nexport interface ExplainReport {\n readonly candidates: readonly ExplainCandidate[];\n readonly chosenRoot: ExplainCandidate | null;\n readonly fallbackUsed: boolean;\n readonly gating: GatingSignal | undefined;\n readonly pagination: PaginationSignal | undefined;\n readonly parseSucceeded: boolean;\n readonly readerable: boolean;\n readonly removedNodes: ExplainRemovedNodes;\n readonly snapshot: ExplainSnapshot;\n}\n\nexport interface BuildExplainOptions {\n readonly baseUrl?: string;\n readonly html: string;\n readonly selectors?: Readonly<SelectorScope>;\n readonly snapshotMaxChars?: number;\n readonly topN?: number;\n}\n\nconst DEFAULT_SNAPSHOT_MAX = 4000;\nconst DEFAULT_TOP_N = 5;\n\nfunction describeSelector(parts: {\n className: string;\n id: string;\n tag: string;\n}): string {\n // A CSS-ish hint for the host, not a unique locator — Readability's expando is\n // a JS-only property invisible to CSS, and we deliberately avoid an nth-child\n // chain that would be brittle against the host's live DOM. Tag + id + first\n // two classes is enough for a human to pick the node out of a small candidate\n // list.\n const idPart = parts.id ? `#${parts.id}` : '';\n const cls = parts.className\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .slice(0, 2)\n .join('.');\n const classPart = cls ? `.${cls}` : '';\n return `${parts.tag.toLowerCase()}${idPart}${classPart}`;\n}\n\nfunction readScored(el: Element): ExplainCandidate | null {\n const stamp = (el as unknown as { readability?: ReadabilityExpando })\n .readability;\n const score = stamp?.contentScore;\n if (typeof score !== 'number') {\n return null;\n }\n const className = typeof el.className === 'string' ? el.className : '';\n const candidate = {\n className,\n id: el.id,\n score,\n tag: el.tagName,\n textLength: el.textContent.trim().length,\n };\n return { ...candidate, selector: describeSelector(candidate) };\n}\n\nfunction truncateSnapshot(html: string, max: number): ExplainSnapshot {\n if (html.length <= max) {\n return { html, truncated: false };\n }\n return { html: html.slice(0, max), truncated: true };\n}\n\nexport function buildExplainReport(\n options: Readonly<BuildExplainOptions>,\n): ExplainReport {\n const {\n html,\n selectors,\n snapshotMaxChars = DEFAULT_SNAPSHOT_MAX,\n topN = DEFAULT_TOP_N,\n baseUrl,\n } = options;\n\n const { document, window } = buildDocument(html, baseUrl);\n\n // Mirror the extract pipeline's ordering: gating detection must precede\n // normalization (chrome stripping would remove the overlay), and pagination\n // detection runs before applySelectors (a caller's include could hide the\n // sentinel but not the \"more content exists\" signal).\n const gating = detectGating(document);\n const documentElementCount = document.querySelectorAll('*').length;\n const normalizeCounts = normalizeDocument(document);\n resolveLazyImages(document);\n const pagination = detectPagination(document, baseUrl);\n applySelectors(document, selectors);\n\n const snapshot = truncateSnapshot(document.body.innerHTML, snapshotMaxChars);\n\n const readerable = isReaderable(document);\n const readabilityOptions = resolveReadabilityOptions({});\n\n // Clone so the normalized doc (and the snapshot above) is preserved untouched\n // — Readability restructures its input during parse.\n const clone = document.cloneNode(true) as Document;\n // Grab node references before parse: Readability detaches scored candidates\n // from the live tree while restructuring, so a post-parse querySelectorAll\n // finds almost nothing. The object refs held here keep their `readability`\n // stamps after being detached — that retention is what surfaces the real\n // per-candidate scores without forking the library or parsing its debug log.\n const heldNodes = Array.from(clone.querySelectorAll('*'));\n\n const reader = new Readability(clone, readabilityOptions);\n const article = reader.parse();\n const parseSucceeded = !!article?.content;\n\n const scored: ExplainCandidate[] = [];\n for (const el of heldNodes) {\n const entry = readScored(el);\n if (entry) {\n scored.push(entry);\n }\n }\n scored.sort((a, b) => b.score - a.score);\n\n const candidates = scored.slice(0, Math.max(0, topN));\n const chosenRoot = scored[0] ?? null;\n\n const diagnostics = assembleDiagnostics({\n articleHtml: article?.content ?? '',\n boilerplateRemoved: normalizeCounts.boilerplateRemoved,\n chromeRemoved: normalizeCounts.chromeRemoved,\n documentElementCount,\n extractedNode: 'readability',\n fallbackUsed: false,\n gated: gating,\n pagination,\n readerable,\n window,\n });\n\n return {\n candidates,\n chosenRoot,\n fallbackUsed: diagnostics.fallbackUsed,\n gating: diagnostics.gated,\n pagination: diagnostics.pagination,\n parseSucceeded,\n readerable: diagnostics.readerable ?? false,\n removedNodes: {\n boilerplate: diagnostics.boilerplateRemoved ?? 0,\n chrome: diagnostics.chromeRemoved ?? 0,\n total: diagnostics.removedNodes ?? 0,\n },\n snapshot,\n };\n}\n","import { z } from 'zod';\n\nimport type { ExplainReport } from '../policy/explain.js';\nimport type { ToolHandle } from '../server.js';\n\nimport { toErrorResult } from '../errors.js';\nimport { logger } from '../logger.js';\nimport { buildExplainReport } from '../policy/explain.js';\nimport { readHtmlFile } from './html-source.js';\nimport {\n type FromHtmlInput,\n localPathField,\n selectorsSchema,\n} from './schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\n// Schemas live with the tool rather than in schemas.ts/output-schema.ts to keep\n// the explain surface in one file (the report shape is explain-specific and not\n// shared with other tools). `selectorsSchema` is reused verbatim from extract so\n// the two tools agree on the include/exclude contract.\nconst explainInputShape = {\n localPath: localPathField,\n baseUrl: z\n .url()\n .describe(\n 'Base URL for absolutizing relative links during pagination/gating detection. NEVER fetched — origin context only.',\n )\n .optional(),\n selectors: selectorsSchema,\n topN: z\n .number()\n .int()\n .min(1)\n .max(20)\n .describe(\n 'Maximum number of scored candidate nodes to return (highest first). Default 5.',\n )\n .default(5),\n} as const;\n\nconst explainInputSchema = z.object(explainInputShape);\n\ntype ExplainInput = z.infer<typeof explainInputSchema>;\ntype ExplainFromHtmlInput = FromHtmlInput<ExplainInput>;\n\n// Schema defaults for callers that pass only a subset of the knobs (topN).\nconst DEFAULTS: Omit<ExplainInput, 'localPath'> = explainInputSchema.parse({\n localPath: '',\n});\n\nconst candidateSchema = z\n .object({\n className: z\n .string()\n .describe(\n \"The candidate's class attribute (raw, unsplit). Empty string when absent.\",\n ),\n id: z.string().describe(\"The candidate's id attribute, or empty string.\"),\n score: z\n .number()\n .describe(\n \"Readability's actual contentScore for this node (link-density-scaled). Higher is better; the top entry is Readability's raw top candidate before its parent-walking/only-child adjustments.\",\n ),\n selector: z\n .string()\n .describe(\n \"A CSS-ish hint (tag#id.class1.class2) for locating the node in the host DOM. NOT a unique locator — Readability's score lives on a JS expando invisible to CSS.\",\n ),\n tag: z\n .string()\n .describe('Uppercase DOM tag name (e.g. \"ARTICLE\", \"MAIN\", \"DIV\").'),\n textLength: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Trimmed textContent length of the candidate node, for eyeballing content density.',\n ),\n })\n .describe(\n 'One scored candidate node Readability considered, with its real contentScore.',\n );\n\nconst explainOutputShape = {\n schemaVersion: z\n .literal(1)\n .describe(\n 'Structured-content schema version. Bumps only on breaking shape changes to this object.',\n ),\n content: z\n .string()\n .describe(\n 'Readable rendering of the report (chosen root, ranked candidates, removal counts, gating/pagination, snapshot head) so content[0].text is always scannable.',\n ),\n chosenRoot: candidateSchema\n .nullable()\n .describe(\n 'The highest-scoring candidate Readability computed — its raw top pick before parent-walking/only-child post-processing. Null when Readability scored nothing (e.g. empty input).',\n ),\n candidates: z\n .array(candidateSchema)\n .describe(\n \"Scored candidate nodes (highest first), capped at topN. These are Readability's real contentScore values, not a self-computed heuristic.\",\n ),\n readerable: z\n .boolean()\n .describe(\n 'Readability isProbablyReaderable verdict on the normalized document.',\n ),\n parseSucceeded: z\n .boolean()\n .describe(\n 'True when reader.parse() returned article content. False signals that `extract` would fall back to its selector cascade.',\n ),\n fallbackUsed: z\n .boolean()\n .describe(\n 'Always false for explain — this tool runs only the Readability path it is diagnosing, never the fallback cascade.',\n ),\n gating: z\n .object({\n likely: z\n .boolean()\n .describe(\n 'True when heuristics strongly suggest the content is paywalled or truncated.',\n ),\n reason: z\n .string()\n .describe(\n 'Short label naming the detected signal (e.g. \"paywall overlay\").',\n ),\n })\n .nullable()\n .describe(\n 'Likely paywall / gating signal detected before normalization. Null when none.',\n ),\n pagination: z\n .object({\n type: z\n .enum(['infinite', 'paginated'])\n .describe('Kind of pagination signal detected.'),\n nextUrl: z\n .string()\n .optional()\n .describe(\n 'Absolute URL of the detected next page (paginated only). Never fetched.',\n ),\n selector: z\n .string()\n .optional()\n .describe(\n 'CSS selector of the load-more / infinite-scroll sentinel (infinite only).',\n ),\n })\n .nullable()\n .describe('Detected pagination / infinite-scroll signal. Null when none.'),\n removedNodes: z\n .object({\n boilerplate: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Boilerplate blocks (related-posts, newsletter signup) stripped before Readability.',\n ),\n chrome: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Browser-chrome nodes stripped (scrollbars, consent banners, overlays).',\n ),\n total: z\n .number()\n .int()\n .min(0)\n .describe(\n 'Net element count removed across the whole pipeline (delta vs. the parsed document).',\n ),\n })\n .describe(\n 'Breakdown of nodes removed before Readability saw the document, reused from the extract diagnostics path.',\n ),\n snapshot: z\n .object({\n html: z\n .string()\n .describe(\n 'The sanitized-by-normalization (post chrome/boilerplate/script strip) HTML fed to Readability — \"what Readability saw\". Not DOMPurify-sanitized (that runs on Readability\\'s output in `extract`), so it may still carry inline event handlers (`onerror`/`onclick`/…); it is diagnostic data — do not render verbatim.',\n ),\n truncated: z\n .boolean()\n .describe(\n 'True when the snapshot was cut at snapshotMaxChars (default 4000).',\n ),\n })\n .describe('Pre-Readability HTML snapshot of the normalized document body.'),\n} as const;\n\nconst explainOutput = z.object(explainOutputShape);\n\nfunction formatGating(report: ExplainReport): string {\n const g = report.gating;\n if (!g) {\n return 'none';\n }\n return `${g.reason}${g.likely ? '' : ' (weak)'}`;\n}\n\nfunction formatPagination(report: ExplainReport): string {\n const p = report.pagination;\n if (!p) {\n return 'none';\n }\n if (p.type === 'paginated') {\n return `paginated -> ${p.nextUrl ?? '(no href)'}`;\n }\n return `infinite (${p.selector ?? 'sentinel'})`;\n}\n\nfunction renderText(report: ExplainReport): string {\n const lines: string[] = [];\n const root = report.chosenRoot;\n lines.push(\n `readerable: ${report.readerable ? 'yes' : 'no'} parse: ${report.parseSucceeded ? 'ok' : 'fail'} fallback: ${report.fallbackUsed ? 'yes' : 'no'}`,\n );\n lines.push(\n `chosen root: ${root ? `${root.selector} (score ${root.score.toFixed(2)}, ${root.textLength} chars)` : '(no candidate scored)'}`,\n );\n lines.push(`top candidates (${report.candidates.length}):`);\n if (report.candidates.length === 0) {\n lines.push(' (none)');\n }\n report.candidates.forEach((c, i) => {\n lines.push(\n ` ${i + 1}. ${c.selector} score ${c.score.toFixed(2)} (${c.textLength} chars)`,\n );\n });\n const r = report.removedNodes;\n lines.push(\n `removed: total=${r.total} (chrome=${r.chrome}, boilerplate=${r.boilerplate})`,\n );\n lines.push(`gating: ${formatGating(report)}`);\n lines.push(`pagination: ${formatPagination(report)}`);\n lines.push(\n `snapshot (${report.snapshot.html.length} chars${report.snapshot.truncated ? ', truncated' : ''}):`,\n );\n lines.push(report.snapshot.html);\n return lines.join('\\n');\n}\n\nexport function explain(rawArgs: unknown): CallToolResult {\n const { localPath, ...rest } = explainInputSchema.parse(rawArgs);\n return explainFromHtml({ html: readHtmlFile(localPath), ...rest });\n}\n\nexport function explainFromHtml(\n input: Readonly<ExplainFromHtmlInput>,\n): CallToolResult {\n const { html, baseUrl, selectors, topN } = { ...DEFAULTS, ...input };\n const report = buildExplainReport({\n html,\n selectors,\n topN,\n baseUrl,\n });\n const content = renderText(report);\n return {\n content: [{ text: content, type: 'text' }],\n structuredContent: {\n schemaVersion: 1,\n content,\n chosenRoot: report.chosenRoot,\n candidates: report.candidates,\n readerable: report.readerable,\n parseSucceeded: report.parseSucceeded,\n fallbackUsed: report.fallbackUsed,\n gating: report.gating ?? null,\n pagination: report.pagination ?? null,\n removedNodes: report.removedNodes,\n snapshot: report.snapshot,\n },\n };\n}\n\nexport const EXPLAIN_TOOL_DESCRIPTION = `Post-mortem diagnostics for extraction: shows WHY Readability picked what it picked. Returns the chosen root, the ranked candidate nodes with their REAL Readability contentScore values (read off the DOM expando Readability stamps during scoring), a categorized removed-nodes breakdown, gating/pagination signals, and a snapshot of the normalized HTML fed to Readability. Runs the same normalize + Readability pipeline as \\`extract\\` (no fallback cascade, no Turndown). The server fetches nothing: \\`localPath\\` is the only source, and \\`baseUrl\\` (optional) is origin context only.`;\n\nexport function explainHandler(args: unknown): CallToolResult {\n try {\n return explain(args);\n } catch (err) {\n logger.error(\n `explain failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n return toErrorResult(err);\n }\n}\n\nexport function registerExplainTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'explain',\n {\n title: 'Explain why Readability picked what it picked',\n description: EXPLAIN_TOOL_DESCRIPTION,\n inputSchema: explainInputShape,\n outputSchema: explainOutput,\n },\n explainHandler,\n );\n}\n","// Shared pre-pass for the sibling-cluster detectors (list-detector,\n// grid-detector). Both walk the document for a container whose direct children\n// form a same-shape sibling group; the constants and helpers here are the\n// heuristic-agnostic infrastructure that scan shares. Detector-specific scoring\n// (anchor/pathname signals vs data-cell text) stays in each detector.\n\n// Direct children are scanned only at these container levels. <tbody> covers\n// HN's <table>-based layout; the rest cover semantic (<main>/<article>) and\n// generic (<div>/<section>) wrappers. <table> is included for HTML that skips\n// <tbody>; jsdom synthesizes one anyway, so this is mostly defensive.\nexport const CONTAINER_TAGS = new Set([\n 'ARTICLE',\n 'DIV',\n 'MAIN',\n 'OL',\n 'SECTION',\n 'TABLE',\n 'TBODY',\n 'UL',\n]);\n\n// Subtrees that never carry a page's primary repeated structure. Stripping them\n// is the false-positive guard: without it, an article's <nav> menu or footer\n// link list would look identical to a feed/grid and the detectors would mis-fire\n// on every page with chrome. Distinct from normalize.ts's stripChrome (which\n// removes consent/overlay banners) — this is landmark chrome only.\nconst LANDMARK_CHROME_SELECTOR =\n 'nav, header, footer, aside, [role=\"navigation\"], [role=\"banner\"], ' +\n '[role=\"contentinfo\"], [role=\"complementary\"], [role=\"search\"], ' +\n '[role=\"menu\"], [role=\"menubar\"]';\n\nexport function stripLandmarkChrome(document: Document): void {\n for (const el of document.querySelectorAll(LANDMARK_CHROME_SELECTOR)) {\n el.remove();\n }\n for (const el of document.querySelectorAll('script, style, template')) {\n el.remove();\n }\n}\n\n// Composite of tag + class signature so mixed siblings — e.g. HN's\n// <tr class=\"athing\"> title row vs classless <tr> subtext row, or a grid's\n// header row vs data rows — split into separate candidate groups instead of\n// blending into one ragged group.\nexport function shapeKey(el: Element): string {\n const raw = el.getAttribute('class');\n if (!raw) {\n return el.tagName;\n }\n const normalized = raw.trim().split(/\\s+/).sort().join(' ');\n return normalized ? `${el.tagName}|${normalized}` : el.tagName;\n}\n\n// tag#id.class hint for the winning container. Best-effort, not a unique\n// locator — used only for diagnostics so a human can see which subtree won.\nexport function describeSelector(el: Element): string {\n const parts = [el.tagName.toLowerCase()];\n const id = el.getAttribute('id');\n if (id) {\n parts.push(`#${id}`);\n }\n const cls = el.getAttribute('class');\n if (cls) {\n for (const token of cls.trim().split(/\\s+/)) {\n if (token) {\n parts.push(`.${token}`);\n }\n }\n }\n return parts.join('');\n}\n","import { isElement } from '../pipeline/dom.js';\nimport { resolveCellText } from './cell-text.js';\nimport {\n CONTAINER_TAGS,\n describeSelector,\n shapeKey,\n stripLandmarkChrome,\n} from './sibling-scan.js';\n\nexport interface GridRow {\n readonly cells: readonly string[];\n}\n\nexport type GridConfidence = 'high' | 'low' | 'medium';\n\nexport interface GridDetectionResult {\n readonly colCount: number;\n readonly confidence: GridConfidence;\n readonly containerSelector: string;\n readonly detected: boolean;\n readonly note: string;\n readonly rowCount: number;\n readonly rows: readonly GridRow[];\n readonly rowTag: string;\n}\n\nexport interface DetectGridOptions {\n readonly cellSelector?: string;\n readonly minRows?: number;\n readonly rowSelector?: string;\n}\n\nconst MIN_ROWS = 3;\nconst HIGH_CONF_ROWS = 6;\nconst MIN_CELLS_PER_ROW = 2;\n\nfunction rowCells(row: Element): string[] {\n return Array.from(row.children).map(resolveCellText);\n}\n\nfunction notDetected(note: string): GridDetectionResult {\n return {\n confidence: 'low',\n containerSelector: '',\n detected: false,\n rowCount: 0,\n colCount: 0,\n rows: [],\n rowTag: '',\n note,\n };\n}\n\n// Confidence tracks the detected cluster (the data-row count), not any header\n// row inferred on top of it — a recovered header is inference, not evidence, so\n// it must not push a grid past HIGH_CONF_ROWS. Both detection modes gate on\n// minRows before reaching buildResult, so this is only ever called with a\n// data-row count >= minRows; `low` comes solely from notDetected().\nfunction confidenceFor(dataRowCount: number): GridConfidence {\n return dataRowCount >= HIGH_CONF_ROWS ? 'high' : 'medium';\n}\n\ninterface GridCandidate {\n readonly container: Element;\n readonly containerSelector: string;\n readonly memberCount: number;\n readonly members: readonly Element[];\n readonly rows: readonly string[][];\n readonly rowTag: string;\n readonly totalCellText: number;\n}\n\n// Pad ragged rows to the max cell width so the matrix is rectangular — the\n// GFM/CSV/JSON renderers in policy/tables.ts assume a dense grid (the\n// delimiter row is derived from row-0 width, so a short row would mis-align\n// every column). Mirrors parseTableMatrix's dense-pad step.\n// `evidence` separates the detected cluster from the emitted matrix: rowCount\n// counts every emitted row (data plus any recovered header), but confidence and\n// the \"detected N\" note track the data-row cluster alone, since the header is\n// inferred rather than detected. Omitted in selector mode and when auto-detect\n// recovers no header, where the two counts coincide.\nfunction buildResult(\n raggedRows: readonly string[][],\n rowTag: string,\n containerSelector: string,\n selectorHint?: string,\n evidence?: { readonly dataRowCount: number; readonly headerCount: number },\n): GridDetectionResult {\n let maxCols = 0;\n for (const row of raggedRows) {\n if (row.length > maxCols) {\n maxCols = row.length;\n }\n }\n if (maxCols === 0) {\n return notDetected(\n selectorHint\n ? `not a grid: rows matched ${selectorHint} but no cells were found`\n : 'not a grid: rows matched but no cells were found',\n );\n }\n const rowCount = raggedRows.length;\n const dataRowCount = evidence?.dataRowCount ?? rowCount;\n const headerCount = evidence?.headerCount ?? 0;\n const rows: GridRow[] = raggedRows.map(row => ({\n cells: Array.from({ length: maxCols }, (_, i) => row[i] ?? ''),\n }));\n const where = containerSelector || selectorHint || 'document';\n const descriptor =\n headerCount > 0\n ? `detected ${dataRowCount} ${rowTag} data rows plus ${headerCount} header row${headerCount > 1 ? 's' : ''} (${maxCols} cols) in ${where}`\n : `detected ${dataRowCount} ${rowTag} rows (${maxCols} cols) in ${where}`;\n return {\n confidence: confidenceFor(dataRowCount),\n containerSelector,\n detected: true,\n rowCount,\n colCount: maxCols,\n rows,\n rowTag,\n note: descriptor,\n };\n}\n\nfunction detectSelectorMode(\n document: Document,\n rowSelector: string,\n cellSelector: string,\n minRows: number,\n): GridDetectionResult {\n const rowEls = Array.from(document.querySelectorAll(rowSelector));\n if (rowEls.length < minRows) {\n return notDetected(\n `not a grid: rowSelector \"${rowSelector}\" matched ${rowEls.length} row(s) (min ${minRows})`,\n );\n }\n const rows = rowEls.map(row =>\n Array.from(row.querySelectorAll(cellSelector)).map(resolveCellText),\n );\n return buildResult(rows, rowEls[0].tagName, '', rowSelector);\n}\n\nfunction modalWidth(members: readonly Element[]): number {\n const counts = new Map<number, number>();\n for (const member of members) {\n const width = member.children.length;\n counts.set(width, (counts.get(width) ?? 0) + 1);\n }\n let best = members[0].children.length;\n let bestCount = 0;\n for (const [width, count] of counts) {\n if (count > bestCount) {\n best = width;\n bestCount = count;\n }\n }\n return best;\n}\n\nfunction classTokens(el: Element): Set<string> {\n const cls = el.getAttribute('class');\n return cls ? new Set(cls.trim().split(/\\s+/)) : new Set();\n}\n\n// A div-grid header has no <th>, so recovery leans on two signals, strongest\n// first. (1) An ARIA grid header — role=\"row\" with a columnheader child — is a\n// semantic role and reliable on its own. (2) Class kinship with the data\n// cluster: a real header is the data rows' own class plus a discriminator\n// (\"est-header\"), so it carries every data class token. Chrome such as\n// \"card-header\" / \"page-header\" shares no class with the data rows and must not\n// be promoted — which is why we match token ownership, never the \"header\"\n// substring. Substring matching is what split the header into its own shape\n// group to begin with, and it over-fires on any chrome whose class merely\n// contains the word.\nfunction looksLikeHeader(\n child: Element,\n rowTag: string,\n dataClass: Set<string>,\n dataWidth: number,\n): boolean {\n if (child.tagName !== rowTag || child.children.length !== dataWidth) {\n return false;\n }\n if (\n child.getAttribute('role') === 'row' &&\n Array.from(child.children).some(\n c => c.getAttribute('role') === 'columnheader',\n )\n ) {\n return true;\n }\n const childClass = classTokens(child);\n return (\n dataClass.size > 0 && [...dataClass].every(token => childClass.has(token))\n );\n}\n\n// A header row often carries an extra class token (e.g. \"est-header\") that\n// splits it into its own 1-member shape group, below the minRows cutoff, so the\n// data-row cluster wins on its own and the header is dropped — leaving the\n// first data row mis-read as the header and the real header lost. Recover it:\n// scan the winning container's children that precede the first data row and\n// prepend any sibling that looks like a header for this cluster. Children after\n// the first data row (totals/footer) are left alone.\nfunction collectHeaderRows(\n container: Element,\n members: readonly Element[],\n): string[][] {\n const memberSet = new Set<Element>(members);\n const rowTag = members[0].tagName;\n const dataWidth = modalWidth(members);\n // shapeKey freezes tag + class signature, so every member shares one class\n // set and members[0] represents it; child count is not part of shapeKey, so\n // widths can vary within the group (hence modalWidth above).\n const dataClass = classTokens(members[0]);\n const headers: string[][] = [];\n for (const child of Array.from(container.children)) {\n if (memberSet.has(child)) {\n break;\n }\n if (looksLikeHeader(child, rowTag, dataClass, dataWidth)) {\n headers.push(rowCells(child));\n }\n }\n return headers;\n}\n\nfunction detectAuto(document: Document, minRows: number): GridDetectionResult {\n stripLandmarkChrome(document);\n\n const candidates: GridCandidate[] = [];\n for (const container of document.querySelectorAll('*')) {\n if (!CONTAINER_TAGS.has(container.tagName)) {\n continue;\n }\n // Group direct element-children by shape so a homogeneous row cluster\n // surfaces as one candidate and mixed-shape siblings (header row vs data\n // rows) split apart rather than blending into a ragged group.\n const groups = new Map<string, Element[]>();\n for (const child of Array.from(container.childNodes)) {\n if (!isElement(child)) {\n continue;\n }\n const key = shapeKey(child);\n const bucket = groups.get(key);\n if (bucket) {\n bucket.push(child);\n } else {\n groups.set(key, [child]);\n }\n }\n for (const members of groups.values()) {\n if (members.length < minRows) {\n continue;\n }\n // A row needs at least two cells to be a row; a group of single-child\n // wrappers is a list of links/labels, not a data grid.\n if (!members.every(m => m.children.length >= MIN_CELLS_PER_ROW)) {\n continue;\n }\n const rows = members.map(rowCells);\n const totalCellText = rows.reduce(\n (sum, row) => sum + row.reduce((s, c) => s + c.length, 0),\n 0,\n );\n candidates.push({\n container,\n containerSelector: describeSelector(container),\n members,\n rows,\n rowTag: members[0].tagName,\n memberCount: members.length,\n totalCellText,\n });\n }\n }\n\n if (candidates.length === 0) {\n return notDetected(\n 'not a grid: no repeating row structure (≥3 same-shape siblings each with ≥2 direct element-children, outside nav/header/footer/aside)',\n );\n }\n\n // Most rows wins; ties break on total cell text so a sparse 6-row grid beats\n // a dense 6-row one only when it carries more substance, and a longer\n // analyst-estimates table beats a stub sidebar mini-grid on both axes.\n let winner = candidates[0];\n for (let i = 1; i < candidates.length; i++) {\n const candidate = candidates[i];\n if (\n candidate.memberCount > winner.memberCount ||\n (candidate.memberCount === winner.memberCount &&\n candidate.totalCellText > winner.totalCellText)\n ) {\n winner = candidate;\n }\n }\n const headerRows = collectHeaderRows(winner.container, winner.members);\n return buildResult(\n [...headerRows, ...winner.rows],\n winner.rowTag,\n winner.containerSelector,\n undefined,\n { dataRowCount: winner.memberCount, headerCount: headerRows.length },\n );\n}\n\n// Detect a CSS-grid/div \"table\" — the div equivalent of extract_tables. In\n// selector mode (both rowSelector and cellSelector given) the rows and cells\n// are whatever the caller names. In auto mode, chrome is stripped first and\n// the container whose direct children form the largest same-shape sibling\n// group (each member a row of ≥2 direct element-children) wins. The resulting\n// rows are padded to a dense rectangular matrix so the shared table renderer\n// can serialize them as gfm/csv/json.\nexport function detectGrid(\n document: Document,\n opts?: DetectGridOptions,\n): GridDetectionResult {\n const minRows = opts?.minRows ?? MIN_ROWS;\n if (opts?.rowSelector && opts.cellSelector) {\n return detectSelectorMode(\n document,\n opts.rowSelector,\n opts.cellSelector,\n minRows,\n );\n }\n return detectAuto(document, minRows);\n}\n","import type { ToolHandle } from '../server.js';\n\nimport { toErrorResult } from '../errors.js';\nimport { logger } from '../logger.js';\nimport { buildDocument } from '../pipeline/dom.js';\nimport { applySelectors } from '../pipeline/normalize.js';\nimport { detectGrid } from '../policy/grid-detector.js';\nimport { renderTable } from '../policy/tables.js';\nimport { readHtmlFile } from './html-source.js';\nimport { extractGridOutputShape } from './output-schema.js';\nimport {\n type ExtractGridFromHtmlInput,\n type ExtractGridInput,\n extractGridInputSchema,\n extractGridInputShape,\n} from './schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\ninterface GridEntry {\n readonly cols: number;\n readonly markdown: string;\n readonly rows: number;\n}\n\nconst NO_GRID = '(no repeating grid found)';\n\nexport function extractGrid(rawArgs: unknown): CallToolResult {\n const { localPath, ...rest } = extractGridInputSchema.parse(rawArgs);\n return extractGridFromHtml({ html: readHtmlFile(localPath), ...rest });\n}\n\n// Schema defaults for callers that pass only a subset of the knobs (format).\nconst DEFAULTS: Omit<ExtractGridInput, 'localPath'> =\n extractGridInputSchema.parse({ localPath: '' });\n\nexport function extractGridFromHtml(\n input: Readonly<ExtractGridFromHtmlInput>,\n): CallToolResult {\n const { html, baseUrl, format, selectors, rowSelector, cellSelector } = {\n ...DEFAULTS,\n ...input,\n };\n\n // Same raw-DOM rationale as extract_tables/extract_list: the repeating grid\n // lives in statically-rendered divs that Readability/normalize would discard\n // (nav/aside/boilerplate), and grid shape is unaffected by unsanitized\n // scripts/styles. Chrome stripping lives inside detectGrid (auto mode only)\n // so selector mode sees the page as captured.\n const { document } = buildDocument(html, baseUrl);\n applySelectors(document, selectors);\n const result = detectGrid(document, { rowSelector, cellSelector });\n\n let content = NO_GRID;\n let grid: GridEntry = { rows: 0, cols: 0, markdown: '' };\n if (result.detected && result.rows.length > 0) {\n const matrix = result.rows.map(row => [...row.cells]);\n const markdown = renderTable(matrix, format);\n content = markdown;\n grid = { rows: result.rowCount, cols: result.colCount, markdown };\n }\n return {\n content: [{ text: content, type: 'text' }],\n structuredContent: {\n schemaVersion: 1,\n content,\n grid,\n diagnostics: {\n confidence: result.confidence,\n containerSelector: result.containerSelector,\n detected: result.detected,\n rowCount: result.rowCount,\n colCount: result.colCount,\n rowTag: result.rowTag,\n note: result.note,\n },\n metadata: { baseUrl, format, detected: result.detected },\n },\n };\n}\n\nexport const EXTRACT_GRID_TOOL_DESCRIPTION = `Detect and extract a CSS-grid / div \"table\" from already-rendered (post-JavaScript) HTML — the div equivalent of \\`extract_tables\\` for SPAs that render data into repeating \\`<div>\\` rows instead of \\`<table>\\`. Supports auto-detect (find the container whose direct children form the largest same-shape sibling group of ≥3 rows, each row a set of ≥2 direct element-children) and explicit \\`rowSelector\\` + \\`cellSelector\\` selector mode (cells scoped to each row subtree). Renders the matrix through the SAME gfm/csv/json renderer as \\`extract_tables\\`. Runs no Readability, no Turndown, no sanitization — the server fetches nothing: \\`localPath\\` is the only source, and \\`baseUrl\\` (optional) is origin context only.`;\n\nexport function extractGridHandler(args: unknown): CallToolResult {\n try {\n return extractGrid(args);\n } catch (err) {\n logger.error(\n `extract_grid failed: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n return toErrorResult(err);\n }\n}\n\nexport function registerExtractGridTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'extract_grid',\n {\n title:\n 'Extract a CSS-grid / div table (the div equivalent of extract_tables)',\n description: EXTRACT_GRID_TOOL_DESCRIPTION,\n inputSchema: extractGridInputShape,\n outputSchema: extractGridOutputShape,\n },\n extractGridHandler,\n );\n}\n","import type { ToolHandle } from '../server.js';\n\nimport { toErrorResult } from '../errors.js';\nimport { logger } from '../logger.js';\nimport { buildDocument } from '../pipeline/dom.js';\nimport { applySelectors } from '../pipeline/normalize.js';\nimport { absolutize } from '../pipeline/urls.js';\nimport { readHtmlFile } from './html-source.js';\nimport { extractLinksOutputShape } from './output-schema.js';\nimport {\n type ExtractLinksFromHtmlInput,\n type ExtractLinksInput,\n extractLinksInputSchema,\n extractLinksInputShape,\n} from './schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\nexport interface ExtractedLink {\n readonly href: string;\n readonly isExternal: boolean;\n readonly rel: string;\n readonly text: string;\n}\n\nconst MAX_TEXT_LENGTH = 300;\n\nfunction clipText(raw: string): string {\n const collapsed = raw.replace(/\\s+/g, ' ').trim();\n return collapsed.length > MAX_TEXT_LENGTH\n ? `${collapsed.slice(0, MAX_TEXT_LENGTH)}…`\n : collapsed;\n}\n\n// Non-http(s) schemes (mailto/tel/javascript/data) have an opaque origin and\n// would otherwise compare as \"different\" — treat them as non-external so the\n// isExternal flag tracks real cross-origin navigation only.\nfunction isWebOrigin(parsed: URL): boolean {\n return parsed.protocol === 'http:' || parsed.protocol === 'https:';\n}\n\nfunction resolveExternal(absolutizedHref: string, baseUrl: string): boolean {\n let parsedHref: URL;\n try {\n parsedHref = new URL(absolutizedHref);\n } catch {\n return false;\n }\n if (!isWebOrigin(parsedHref)) {\n return false;\n }\n try {\n return new URL(baseUrl).origin !== parsedHref.origin;\n } catch {\n return false;\n }\n}\n\n// Drop <script>/<template> for safety (templates never render, scripts carry no\n// crawl value), but keep nav/footer/main — crawl-relevant links live there.\nfunction pruneUnsafeRoots(document: Document): void {\n for (const el of document.querySelectorAll('script, template')) {\n el.remove();\n }\n}\n\nexport function extractLinks(rawArgs: unknown): CallToolResult {\n const { localPath, ...rest } = extractLinksInputSchema.parse(rawArgs);\n return extractLinksFromHtml({ html: readHtmlFile(localPath), ...rest });\n}\n\n// Schema defaults for callers that pass only a subset of the knobs (sameOriginOnly).\nconst DEFAULTS: Omit<ExtractLinksInput, 'localPath'> =\n extractLinksInputSchema.parse({ localPath: '' });\n\nexport function extractLinksFromHtml(\n input: Readonly<ExtractLinksFromHtmlInput>,\n): CallToolResult {\n const { html, baseUrl, sameOriginOnly, selectors } = {\n ...DEFAULTS,\n ...input,\n };\n\n const { document } = buildDocument(html, baseUrl);\n applySelectors(document, selectors);\n pruneUnsafeRoots(document);\n\n const links: ExtractedLink[] = [];\n for (const anchor of document.querySelectorAll('a')) {\n const rawHref = anchor.getAttribute('href');\n if (!rawHref) {\n continue;\n }\n const href = absolutize(rawHref, baseUrl);\n const isExternal = baseUrl ? resolveExternal(href, baseUrl) : false;\n if (sameOriginOnly && isExternal) {\n continue;\n }\n links.push({\n text: clipText(anchor.textContent),\n href,\n rel: anchor.getAttribute('rel') ?? '',\n isExternal,\n });\n }\n\n const content = renderLinksIndex(links);\n return {\n content: [{ text: content, type: 'text' }],\n structuredContent: {\n schemaVersion: 1,\n content,\n links,\n metadata: { baseUrl },\n },\n };\n}\n\n// One `- [text](href)` line per link; never blank so content[0].text is scannable.\nfunction renderLinksIndex(links: readonly ExtractedLink[]): string {\n if (links.length === 0) {\n return '(no links found)';\n }\n return links\n .map(link => `- [${link.text || '(no text)'}](${link.href})`)\n .join('\\n');\n}\n\nexport const EXTRACT_LINKS_TOOL_DESCRIPTION = `Return a structured list of anchor links from already-rendered (post-JavaScript) HTML — \\`[{text, href, rel, isExternal}]\\` in document order, hrefs absolutized against \\`baseUrl\\`. No Readability scoring, Turndown, or sanitization — links are gathered from the raw parsed DOM so nav/footer/main links survive. Pairs with chrome-devtools for crawl/navigation decisions. The server fetches nothing: \\`localPath\\` is the only source, and \\`baseUrl\\` is origin context only (never fetched).`;\n\nexport function extractLinksHandler(args: unknown): CallToolResult {\n try {\n return extractLinks(args);\n } catch (err) {\n logger.error(\n `extract_links failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n return toErrorResult(err);\n }\n}\n\nexport function registerExtractLinksTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'extract_links',\n {\n title: 'Extract anchor links',\n description: EXTRACT_LINKS_TOOL_DESCRIPTION,\n inputSchema: extractLinksInputShape,\n outputSchema: extractLinksOutputShape,\n },\n extractLinksHandler,\n );\n}\n","import { isElement } from '../pipeline/dom.js';\nimport { absolutize } from '../pipeline/urls.js';\nimport {\n CONTAINER_TAGS,\n describeSelector,\n shapeKey,\n stripLandmarkChrome,\n} from './sibling-scan.js';\n\nexport interface ListItem {\n readonly score: number;\n readonly snippet: string;\n readonly title: string;\n readonly url: string;\n}\n\nexport type ListConfidence = 'high' | 'low' | 'medium';\n\nexport interface ListDetectionResult {\n readonly confidence: ListConfidence;\n readonly containerSelector: string;\n readonly detected: boolean;\n readonly itemCount: number;\n readonly items: readonly ListItem[];\n readonly itemTag: string;\n readonly note: string;\n}\n\n// Three siblings of the same shape (tag + class signature), each carrying a\n// real navigation anchor, is the smallest cluster that is not accidentally a\n// nav menu or a article's <ul> of inline links. Below this the detector\n// refuses to claim the page is a list.\nconst MIN_ITEMS = 3;\n\n// Six-or-more items with non-trivial body text per item is the empirical\n// signature of a true index/search/blog-roll page (HN shows 30, Google ~10,\n// WP indexes ~10). Used to separate `high` from `medium` confidence so a\n// 3-item related-links block doesn't masquerade as a feed.\nconst HIGH_CONF_ITEMS = 6;\nconst HIGH_CONF_AVG_SCORE = 30;\n\nconst MAX_SNIPPET_CHARS = 200;\n\n// href schemes that don't point at a list target. Excluding them keeps mailto:\n// /tel:/javascript:/ anchors from satisfying the \"every item has a link\" bar.\nfunction isNavigationHref(href: string): boolean {\n if (!href || href === '#') {\n return false;\n }\n const lower = href.toLowerCase();\n return (\n !lower.startsWith('javascript:') &&\n !lower.startsWith('mailto:') &&\n !lower.startsWith('tel:')\n );\n}\n\nfunction anchorText(anchor: HTMLAnchorElement): string {\n return anchor.textContent.replace(/\\s+/g, ' ').trim();\n}\n\n// Anchors with a navigation-worthy href AND visible text. Lives inside the\n// candidate child so a single <a href=\"#\"> footer link doesn't satisfy the\n// \"every item has a link\" requirement.\nfunction navigationAnchors(child: Element): HTMLAnchorElement[] {\n const anchors: HTMLAnchorElement[] = [];\n for (const el of child.querySelectorAll('a[href]')) {\n const anchor = el as HTMLAnchorElement;\n const href = anchor.getAttribute('href') ?? '';\n if (!isNavigationHref(href)) {\n continue;\n }\n if (anchorText(anchor).length === 0) {\n continue;\n }\n anchors.push(anchor);\n }\n return anchors;\n}\n\nfunction pickPrimaryAnchor(\n anchors: readonly HTMLAnchorElement[],\n): HTMLAnchorElement {\n let primary = anchors[0];\n let best = anchorText(primary).length;\n for (let i = 1; i < anchors.length; i++) {\n const len = anchorText(anchors[i]).length;\n if (len > best) {\n primary = anchors[i];\n best = len;\n }\n }\n return primary;\n}\n\nfunction clipSnippet(raw: string): string {\n const collapsed = raw.replace(/\\s+/g, ' ').trim();\n if (collapsed.length <= MAX_SNIPPET_CHARS) {\n return collapsed;\n }\n return `${collapsed.slice(0, MAX_SNIPPET_CHARS)}…`;\n}\n\n// Score = primary-anchor text length + non-link body text length. A pure\n// link-density penalty (textLength * (1 - linkDensity)) ranks HN's subtext\n// rows ABOVE the title rows because the title row is anchor-dominated (its\n// title IS the link), so non-link body alone mis-ranks. Adding the primary\n// anchor's text length rewards long titles (real feed items) over short nav\n// labels (\"Home\", \"Sign in\"), which is the signal we actually want for both\n// the candidate tiebreak and the high-confidence threshold.\nfunction scoreItem(\n textLength: number,\n linkTextLength: number,\n primaryAnchorTextLength: number,\n): number {\n if (textLength === 0) {\n return 0;\n }\n const nonLinkBody = Math.max(0, textLength - linkTextLength);\n return primaryAnchorTextLength + nonLinkBody;\n}\n\nfunction extractItem(\n child: Element,\n baseUrl: string | undefined,\n): ListItem | null {\n const anchors = navigationAnchors(child);\n if (anchors.length === 0) {\n return null;\n }\n const primary = pickPrimaryAnchor(anchors);\n const title = anchorText(primary);\n if (!title) {\n return null;\n }\n const href = primary.getAttribute('href') ?? '';\n const url = absolutize(href, baseUrl);\n if (!url) {\n return null;\n }\n\n const fullText = child.textContent.replace(/\\s+/g, ' ').trim();\n // Snippet = body text with the title peeled off the front when present, so\n // the title link doesn't echo into the excerpt.\n const snippet =\n fullText === title\n ? ''\n : fullText.startsWith(title)\n ? clipSnippet(fullText.slice(title.length + 1))\n : clipSnippet(fullText);\n\n const linkTextLength = anchors.reduce(\n (sum, anchor) => sum + anchorText(anchor).length,\n 0,\n );\n return {\n score: scoreItem(fullText.length, linkTextLength, title.length),\n snippet,\n title,\n url,\n };\n}\n\ninterface Candidate {\n readonly containerSelector: string;\n readonly distinctPathnames: number;\n readonly items: readonly ListItem[];\n readonly itemTag: string;\n readonly totalScore: number;\n}\n\n// Count of distinct URL pathnames a candidate's items point at. This is the\n// signal that separates a real feed from a metadata cluster: a feed's items\n// each navigate to a distinct destination (HN title rows link to ~30 different\n// external stories; a blog index links to /post-1../post-N; a search results\n// page links to a heterogeneous set of sites), so distinctPathnames ≈ item\n// count. A metadata cluster's items all navigate to the same internal route —\n// HN's subtext rows are 31 anchors all pointing at news.ycombinator.com/item?id=…,\n// collapsing to pathname=/item (distinctPathnames = 1). Counting items alone\n// lets subtext win on HN (31 vs 30 title rows) because of the trailing \"More\"\n// row; counting distinct destinations restores the title group as the winner.\nfunction distinctPathnames(items: readonly ListItem[]): number {\n const paths = new Set<string>();\n for (const item of items) {\n try {\n paths.add(new URL(item.url).pathname);\n } catch {\n paths.add(item.url);\n }\n }\n return paths.size;\n}\n\nfunction collectCandidates(document: Document, baseUrl: string | undefined) {\n const candidates: Candidate[] = [];\n for (const container of document.querySelectorAll('*')) {\n if (!CONTAINER_TAGS.has(container.tagName)) {\n continue;\n }\n // Group direct element-children by shape so homogeneous sibling lists\n // surface as one cluster and mixed-shape siblings (e.g. HN's athing +\n // subtext rows) split apart.\n const groups = new Map<string, Element[]>();\n for (const child of Array.from(container.childNodes)) {\n if (!isElement(child)) {\n continue;\n }\n const key = shapeKey(child);\n const bucket = groups.get(key);\n if (bucket) {\n bucket.push(child);\n } else {\n groups.set(key, [child]);\n }\n }\n for (const children of groups.values()) {\n if (children.length < MIN_ITEMS) {\n continue;\n }\n // EVERY member must carry a real anchor — one linkless <li> breaks\n // homogeneity and rejects the group, which is what keeps a <ul> of\n // plain-text list items (e.g. recipe ingredients) from triggering.\n if (!children.every(child => navigationAnchors(child).length > 0)) {\n continue;\n }\n const items: ListItem[] = [];\n for (const child of children) {\n const item = extractItem(child, baseUrl);\n if (item) {\n items.push(item);\n }\n }\n if (items.length < MIN_ITEMS) {\n continue;\n }\n candidates.push({\n containerSelector: describeSelector(container),\n distinctPathnames: distinctPathnames(items),\n itemTag: children[0].tagName,\n items,\n totalScore: items.reduce((sum, item) => sum + item.score, 0),\n });\n }\n }\n return candidates;\n}\n\nfunction confidenceFor(items: readonly ListItem[]): ListConfidence {\n if (items.length < MIN_ITEMS) {\n return 'low';\n }\n if (items.length >= HIGH_CONF_ITEMS) {\n const avg = items.reduce((sum, item) => sum + item.score, 0) / items.length;\n return avg >= HIGH_CONF_AVG_SCORE ? 'high' : 'medium';\n }\n return 'medium';\n}\n\nfunction notDetected(note: string): ListDetectionResult {\n return {\n confidence: 'low',\n containerSelector: '',\n detected: false,\n itemCount: 0,\n itemTag: '',\n items: [],\n note,\n };\n}\n\n// Detect a list/feed/index structure on a non-article page. Strips chrome\n// (nav/header/footer/aside + ARIA roles) first so an article's menu doesn't\n// look like a 4-item feed, then walks for containers whose direct children\n// form a homogeneous sibling group of ≥3 elements each carrying a real anchor.\n// Winner selection prefers the candidate whose items navigate to the most\n// distinct destinations — the signature of a real feed — over a same-path\n// metadata cluster (HN's subtext rows all link to /item?id=…). Item count is\n// the second key (a 30-item feed beats a 4-item related-links block) and total\n// per-item score breaks remaining ties.\nexport function detectList(\n document: Document,\n baseUrl?: string,\n): ListDetectionResult {\n stripLandmarkChrome(document);\n\n const candidates = collectCandidates(document, baseUrl);\n if (candidates.length === 0) {\n return notDetected(\n 'not a list: no repeated item structure with links (≥3 same-shape siblings each carrying an anchor, outside nav/header/footer/aside)',\n );\n }\n\n let winner = candidates[0];\n for (let i = 1; i < candidates.length; i++) {\n const candidate = candidates[i];\n if (\n candidate.distinctPathnames > winner.distinctPathnames ||\n (candidate.distinctPathnames === winner.distinctPathnames &&\n candidate.items.length > winner.items.length) ||\n (candidate.distinctPathnames === winner.distinctPathnames &&\n candidate.items.length === winner.items.length &&\n candidate.totalScore > winner.totalScore)\n ) {\n winner = candidate;\n }\n }\n\n if (winner.items.length < MIN_ITEMS) {\n return notDetected(\n 'not a list: best candidate had fewer than 3 extracted items',\n );\n }\n\n const items = winner.items;\n return {\n confidence: confidenceFor(items),\n containerSelector: winner.containerSelector,\n detected: true,\n itemCount: items.length,\n itemTag: winner.itemTag,\n items,\n note: `detected ${items.length} ${winner.itemTag} items in ${winner.containerSelector}`,\n };\n}\n","import type { ToolHandle } from '../server.js';\n\nimport { toErrorResult } from '../errors.js';\nimport { logger } from '../logger.js';\nimport { buildDocument } from '../pipeline/dom.js';\nimport { applySelectors } from '../pipeline/normalize.js';\nimport {\n detectList,\n type ListDetectionResult,\n type ListItem,\n} from '../policy/list-detector.js';\nimport { readHtmlFile } from './html-source.js';\nimport { extractListOutputShape } from './output-schema.js';\nimport {\n type ExtractListFromHtmlInput,\n extractListInputSchema,\n extractListInputShape,\n} from './schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\nconst NOT_A_LIST = 'not a list: no repeated item structure with links';\n\nfunction renderItems(result: ListDetectionResult): string {\n if (!result.detected || result.items.length === 0) {\n return result.note || NOT_A_LIST;\n }\n return result.items\n .map((item, index) => {\n const head = `${index + 1}. ${item.title} — ${item.url}`;\n return item.snippet ? `${head}\\n ${item.snippet}` : head;\n })\n .join('\\n');\n}\n\nfunction toStructuredItem(item: ListItem) {\n return {\n score: item.score,\n snippet: item.snippet,\n title: item.title,\n url: item.url,\n };\n}\n\nexport function extractList(rawArgs: unknown): CallToolResult {\n const { localPath, ...rest } = extractListInputSchema.parse(rawArgs);\n return extractListFromHtml({ html: readHtmlFile(localPath), ...rest });\n}\n\nexport function extractListFromHtml(\n input: Readonly<ExtractListFromHtmlInput>,\n): CallToolResult {\n const { html, baseUrl, selectors } = input;\n\n // No Readability/Turndown/normalize: list pages survive on raw DOM shape\n // (sibling TR/LI/ARTICLE clusters), and the article normalizer would\n // discard the very chrome-bearing structure the detector scores against.\n // Chrome stripping (nav/header/footer/aside) lives inside detectList so\n // this tool sees the page as captured.\n const { document } = buildDocument(html, baseUrl);\n applySelectors(document, selectors);\n const result = detectList(document, baseUrl);\n const content = renderItems(result);\n return {\n content: [{ text: content, type: 'text' }],\n structuredContent: {\n schemaVersion: 1,\n content,\n items: result.items.map(toStructuredItem),\n diagnostics: {\n confidence: result.confidence,\n containerSelector: result.containerSelector,\n detected: result.detected,\n itemCount: result.itemCount,\n itemTag: result.itemTag,\n note: result.note,\n },\n metadata: { baseUrl },\n },\n };\n}\n\nexport const EXTRACT_LIST_TOOL_DESCRIPTION = `Detect and extract a list/feed/index structure from already-rendered (post-JavaScript) HTML — for HN-style, search-result, and blog-index pages that Readability cannot turn into one article. Returns \\`{items: [{title, url, snippet, score}], diagnostics}\\` instead of one article. Strips nav/header/footer/aside + ARIA chrome roles first (the false-positive guard so an article's nav menu doesn't look like a 4-item feed), then finds the container whose direct children form a same-shape sibling cluster of ≥3 elements each carrying a navigation anchor, and the cluster with the most items wins. No Readability, no Turndown, no sanitization. The server fetches nothing: \\`localPath\\` is the only source, and \\`baseUrl\\` (optional) is origin context for absolutizing item hrefs.`;\n\nexport function extractListHandler(args: unknown): CallToolResult {\n try {\n return extractList(args);\n } catch (err) {\n logger.error(\n `extract_list failed: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n return toErrorResult(err);\n }\n}\n\nexport function registerExtractListTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'extract_list',\n {\n title: 'Extract a list/feed (HN/search/blog-index pages)',\n description: EXTRACT_LIST_TOOL_DESCRIPTION,\n inputSchema: extractListInputShape,\n outputSchema: extractListOutputShape,\n },\n extractListHandler,\n );\n}\n","import type { Metadata } from '../pipeline/context.js';\nimport type { ToolHandle } from '../server.js';\n\nimport { toErrorResult } from '../errors.js';\nimport { logger } from '../logger.js';\nimport { buildDocument } from '../pipeline/dom.js';\nimport { normalizeDocument } from '../pipeline/normalize.js';\nimport { resolveMetadata } from '../policy/metadata.js';\nimport { readHtmlFile } from './html-source.js';\nimport { extractMetadataOutputShape } from './output-schema.js';\nimport {\n type ExtractMetadataFromHtmlInput,\n extractMetadataInputSchema,\n extractMetadataInputShape,\n} from './schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\n// Bibliographic fields only — wordCount/readingTimeMin/tokenEstimate are\n// meaningless without the extracted body, so a metadata-only caller never\n// sees wordCount: 0.\nconst BIBLIOGRAPHIC_KEYS = [\n 'title',\n 'byline',\n 'siteName',\n 'lang',\n 'publishedTime',\n 'excerpt',\n 'canonical',\n 'baseUrl',\n] as const satisfies readonly (keyof Metadata)[];\n\nfunction pickBibliographic(\n metadata: Readonly<Metadata>,\n): Record<string, string> {\n const out: Record<string, string> = {};\n for (const key of BIBLIOGRAPHIC_KEYS) {\n const value = metadata[key];\n if (value !== undefined) {\n out[key] = value;\n }\n }\n return out;\n}\n\n// One line per present field; never blank so content[0].text is always scannable.\nfunction renderMetadataLines(\n metadata: Readonly<Record<string, string>>,\n): string {\n const lines = Object.entries(metadata).map(\n ([key, value]) => `${key}: ${value}`,\n );\n return lines.length > 0 ? lines.join('\\n') : '(no metadata found)';\n}\n\nexport function extractMetadataDocument(rawArgs: unknown): CallToolResult {\n const { localPath, ...rest } = extractMetadataInputSchema.parse(rawArgs);\n return extractMetadataDocumentFromHtml({\n html: readHtmlFile(localPath),\n ...rest,\n });\n}\n\nexport function extractMetadataDocumentFromHtml(\n input: Readonly<ExtractMetadataFromHtmlInput>,\n): CallToolResult {\n const { html, baseUrl } = input;\n\n const { document } = buildDocument(html, baseUrl);\n normalizeDocument(document);\n const resolved = resolveMetadata({\n document,\n textContent: '',\n wordCount: 0,\n readingTimeMin: 0,\n baseUrl,\n });\n\n const metadata = pickBibliographic(resolved);\n const content = renderMetadataLines(metadata);\n return {\n content: [{ text: content, type: 'text' }],\n structuredContent: {\n schemaVersion: 1,\n content,\n metadata,\n },\n };\n}\n\nexport const EXTRACT_METADATA_TOOL_DESCRIPTION = `Return only the bibliographic metadata (title, byline, siteName, lang, publishedTime, excerpt, canonical, baseUrl) of already-rendered (post-JavaScript) HTML without running Readability/Turndown — a fast pre-check for crawlers and citation. Resolves the same metadata cascade as \\`extract\\` (JSON-LD → OpenGraph → Twitter → <meta> → <time> → <title>), plus <link rel=\"canonical\"> → og:url. The server fetches nothing: \\`localPath\\` is the only source, and \\`baseUrl\\` is origin context only (never fetched).`;\n\nexport function extractMetadataHandler(args: unknown): CallToolResult {\n try {\n return extractMetadataDocument(args);\n } catch (err) {\n logger.error(\n `extract_metadata failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n return toErrorResult(err);\n }\n}\n\nexport function registerExtractMetadataTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'extract_metadata',\n {\n title: 'Extract metadata only (no Readability)',\n description: EXTRACT_METADATA_TOOL_DESCRIPTION,\n inputSchema: extractMetadataInputShape,\n outputSchema: extractMetadataOutputShape,\n },\n extractMetadataHandler,\n );\n}\n","export interface OutlineEntry {\n readonly anchor: string;\n readonly level: number;\n readonly text: string;\n}\n\n// Collapse whitespace, trim, then strip leading/trailing `#` (GitHub permalinks\n// append `<a>#</a>`, so heading textContent ends with `#`).\nexport function normalizeHeadingText(raw: string): string {\n const collapsed = raw.replace(/\\s+/g, ' ').trim();\n return collapsed.replace(/^#+|#+$/g, '').trim();\n}\n\n// GFM anchor algorithm (comrak `anchorize`): keep Unicode letters, marks,\n// numbers, and connector punctuation; map spaces to hyphens. No hyphen collapse\n// or trim, so anchors match GitHub and non-Latin scripts survive. An all-symbol\n// heading yields an empty slug; fall back to `section` so anchors stay non-empty.\nfunction slugify(text: string): string {\n const cleaned = text\n .toLowerCase()\n .replace(/[^\\p{L}\\p{M}\\p{N}\\p{Pc} -]/gu, '');\n return cleaned.replace(/ /g, '-') || 'section';\n}\n\n// `href=\"#\"` (empty fragment) does not win — fall through to slugify.\nfunction linkAnchor(heading: Element): string | undefined {\n for (const link of heading.querySelectorAll('a[href^=\"#\"]')) {\n const href = link.getAttribute('href');\n if (!href) {\n continue;\n }\n const fragment = href.slice(1);\n if (fragment) {\n return fragment;\n }\n }\n return undefined;\n}\n\nfunction resolveCandidate(\n heading: Element,\n text: string,\n): { readonly anchor: string; readonly explicit: boolean } {\n const id = heading.getAttribute('id');\n if (id) {\n return { anchor: id, explicit: true };\n }\n const link = linkAnchor(heading);\n if (link) {\n return { anchor: link, explicit: true };\n }\n return { anchor: slugify(text), explicit: false };\n}\n\n// Explicit anchors are kept verbatim; generated slugs are collision-suffixed\n// (`-1`, `-2`, …), first occurrence bare.\nfunction dedupe(\n candidate: string,\n explicit: boolean,\n used: Set<string>,\n): string {\n if (explicit || !used.has(candidate)) {\n return candidate;\n }\n let suffix = 1;\n while (used.has(`${candidate}-${suffix}`)) {\n suffix++;\n }\n return `${candidate}-${suffix}`;\n}\n\nexport function resolveOutline(document: Document): OutlineEntry[] {\n const entries: OutlineEntry[] = [];\n const used = new Set<string>();\n const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6');\n headings.forEach(heading => {\n const rawText = heading.textContent;\n if (!rawText.trim()) {\n return;\n }\n const text = normalizeHeadingText(rawText);\n const level = Number.parseInt(heading.tagName.slice(1), 10);\n const { anchor: candidate, explicit } = resolveCandidate(heading, text);\n const anchor = dedupe(candidate, explicit, used);\n used.add(anchor);\n entries.push({ anchor, level, text });\n });\n return entries;\n}\n","import { isElement } from '../pipeline/dom.js';\nimport { normalizeHeadingText } from './outline.js';\n\ninterface HeadingMatch {\n readonly heading: Element;\n readonly level: number;\n}\n\n// Match the first heading by normalized text. Exact equality wins; otherwise\n// fall back to the first substring match so a short query (\"auth\") still\n// lands on a longer heading (\"Authentication\") without forcing an exact match.\nfunction findHeading(\n document: Document,\n query: string,\n): HeadingMatch | undefined {\n const needle = normalizeHeadingText(query).toLowerCase();\n if (!needle) {\n return undefined;\n }\n const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6');\n let substring: HeadingMatch | undefined;\n for (const heading of headings) {\n const text = normalizeHeadingText(heading.textContent).toLowerCase();\n if (!text) {\n continue;\n }\n const level = Number.parseInt(heading.tagName.slice(1), 10);\n if (text === needle) {\n return { heading, level };\n }\n if (!substring && text.includes(needle)) {\n substring = { heading, level };\n }\n }\n return substring;\n}\n\n// Elements whose direct children form the section flow. The matched heading's\n// nearest such ancestor is the level at which the section's content lives as\n// siblings: on docs sites (GitHub markdown, etc.) the heading is wrapped in\n// <div class=\"markdown-heading\">, so the body <p> is a sibling of the wrapper\n// div, not of the <h2>. Walking the <h2>'s own siblings would capture only the\n// permalink anchor and miss the body.\nconst FLOW_CONTAINERS = new Set([\n 'ARTICLE',\n 'ASIDE',\n 'BLOCKQUOTE',\n 'BODY',\n 'DD',\n 'DETAILS',\n 'DT',\n 'FOOTER',\n 'HEADER',\n 'LI',\n 'MAIN',\n 'NAV',\n 'SECTION',\n 'TD',\n]);\n\nfunction findFlowContainer(heading: Element): Element {\n let ancestor: Element | null = heading.parentElement;\n while (ancestor) {\n if (FLOW_CONTAINERS.has(ancestor.tagName)) {\n return ancestor;\n }\n ancestor = ancestor.parentElement;\n }\n // BODY is in FLOW_CONTAINERS, so the loop reaches it for any attached\n // heading; this fallback only covers a detached heading.\n return heading.ownerDocument.body;\n}\n\n// Level of the first heading at or inside `el`, or undefined when `el` is\n// neither a heading nor a wrapper around one. Used to test whether a following\n// sibling terminates the section.\nfunction firstHeadingLevel(el: Element): number | undefined {\n const direct = /^H([1-6])$/.exec(el.tagName);\n if (direct) {\n return Number.parseInt(direct[1], 10);\n }\n const inner = el.querySelector('h1, h2, h3, h4, h5, h6');\n if (inner) {\n return Number.parseInt(inner.tagName.slice(1), 10);\n }\n return undefined;\n}\n\n// Wraps the matched heading and its subtree in a `<section data-rdrm-section-scope>`\n// so the existing selectors.include path can isolate it. Returns false (and\n// leaves the document untouched) when no heading matches.\nexport function scopeToHeading(\n document: Document,\n headingText: string,\n): boolean {\n const match = findHeading(document, headingText);\n if (!match) {\n return false;\n }\n const { heading, level } = match;\n const container = findFlowContainer(heading);\n let startChild: Element = heading;\n while (startChild.parentElement && startChild.parentElement !== container) {\n startChild = startChild.parentElement;\n }\n const wrap = document.createElement('section');\n wrap.setAttribute('data-rdrm-section-scope', '');\n container.insertBefore(wrap, startChild);\n let node: Node | null = startChild;\n while (node) {\n const next: Node | null = node.nextSibling;\n wrap.appendChild(node);\n // A section ends at the next same-or-higher-level heading (level <= L):\n // deeper headings (h3 under an h2) belong to this section, peers and\n // shallower headings start the next one. The terminating sibling may be a\n // wrapper around the heading (e.g. <div class=\"markdown-heading\">), so the\n // check looks for a heading at OR inside the sibling.\n if (next !== null && isElement(next)) {\n const nextLevel = firstHeadingLevel(next);\n if (nextLevel !== undefined && nextLevel <= level) {\n break;\n }\n }\n node = next;\n }\n return true;\n}\n","import type { ToolHandle } from '../server.js';\n\nimport { ExtractionError, toErrorResult } from '../errors.js';\nimport { logger } from '../logger.js';\nimport { buildDocument } from '../pipeline/dom.js';\nimport { normalizeDocument } from '../pipeline/normalize.js';\nimport { scopeToHeading } from '../policy/section.js';\nimport { extractArticleFromHtml } from './extract.js';\nimport { readHtmlFile } from './html-source.js';\nimport { outputSchemaShape } from './output-schema.js';\nimport {\n type ExtractSectionFromHtmlInput,\n extractSectionInputSchema,\n extractSectionInputShape,\n} from './schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\nconst SECTION_SCOPE_SELECTOR = '[data-rdrm-section-scope]';\n\nexport function extractSection(rawArgs: unknown): CallToolResult {\n const { localPath, ...rest } = extractSectionInputSchema.parse(rawArgs);\n return extractSectionFromHtml({ html: readHtmlFile(localPath), ...rest });\n}\n\nexport function extractSectionFromHtml(\n input: Readonly<ExtractSectionFromHtmlInput>,\n): CallToolResult {\n const { html, baseUrl, selector, heading } = input;\n\n if (selector !== undefined) {\n return extractArticleFromHtml({\n html,\n baseUrl,\n selectors: { include: selector },\n resolvePreset: false,\n });\n }\n // The superRefine on the schema enforces selector/heading XOR, so reaching\n // here means heading is set — but its declared type is still optional, so\n // narrow explicitly rather than asserting.\n if (heading === undefined) {\n throw new ExtractionError(\n 'Provide exactly one of `selector` or `heading`.',\n );\n }\n\n // Heading mode: wrap the matched subtree, re-serialize, and route through\n // extractArticleFromHtml's selectors.include so this tool stays a thin\n // resolver over the existing extraction pipeline — no forked extraction logic.\n // The DOM is parsed+normalized twice: here to scope against a normalized\n // DOM, and again inside the extract worker — which owns detectGating-before-\n // normalize, resolveLazyImages, and applySelectors. Folding scoping into\n // that pipeline would breach its invariants for a non-hot path; accepted.\n const { document } = buildDocument(html, baseUrl);\n normalizeDocument(document);\n if (!scopeToHeading(document, heading)) {\n throw new ExtractionError(`no heading matched: ${heading}`);\n }\n const scoped = `<!DOCTYPE html><html><head></head><body>${document.body.innerHTML}</body></html>`;\n return extractArticleFromHtml({\n html: scoped,\n baseUrl,\n selectors: { include: SECTION_SCOPE_SELECTOR },\n resolvePreset: false,\n });\n}\n\nexport const EXTRACT_SECTION_TOOL_DESCRIPTION = `Extract one section of an already-rendered (post-JavaScript) HTML document and return its Markdown + metadata + diagnostics — a thin resolver over extract’s \\`selectors.include\\` path, not a new extractor. Pick the section by CSS \\`selector\\` (passed straight through) OR by \\`heading\\` text (case-insensitive, first match wins; the section spans from the matched heading to the next same-or-higher-level heading). Exactly one of \\`selector\\`/\\`heading\\` is required. The server fetches nothing: \\`localPath\\` is the only source, and \\`baseUrl\\` (optional) is origin context only (never fetched).`;\n\nexport function extractSectionHandler(args: unknown): CallToolResult {\n try {\n return extractSection(args);\n } catch (err) {\n logger.error(\n `extract_section failed: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n return toErrorResult(err);\n }\n}\n\nexport function registerExtractSectionTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'extract_section',\n {\n title: 'Extract one section by selector or heading',\n description: EXTRACT_SECTION_TOOL_DESCRIPTION,\n inputSchema: extractSectionInputShape,\n outputSchema: outputSchemaShape,\n },\n extractSectionHandler,\n );\n}\n","import type { ToolHandle } from '../server.js';\n\nimport { toErrorResult } from '../errors.js';\nimport { logger } from '../logger.js';\nimport { buildDocument } from '../pipeline/dom.js';\nimport { applySelectors } from '../pipeline/normalize.js';\nimport {\n parseTableMatrix,\n renderTable,\n resolveHeaderKeys,\n} from '../policy/tables.js';\nimport { readHtmlFile } from './html-source.js';\nimport { extractTablesOutputShape } from './output-schema.js';\nimport {\n type ExtractTablesFromHtmlInput,\n type ExtractTablesInput,\n extractTablesInputSchema,\n extractTablesInputShape,\n} from './schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\ninterface ExtractedTable {\n readonly cols: number;\n readonly index: number;\n readonly markdown: string;\n readonly rows: number;\n}\n\nconst NO_TABLES = '(no tables found)';\n\nexport function extractTables(rawArgs: unknown): CallToolResult {\n const { localPath, ...rest } = extractTablesInputSchema.parse(rawArgs);\n return extractTablesFromHtml({ html: readHtmlFile(localPath), ...rest });\n}\n\n// Schema defaults for callers that pass only a subset of the knobs (format).\nconst DEFAULTS: Omit<ExtractTablesInput, 'localPath'> =\n extractTablesInputSchema.parse({ localPath: '' });\n\nexport function extractTablesFromHtml(\n input: Readonly<ExtractTablesFromHtmlInput>,\n): CallToolResult {\n const { html, baseUrl, format, selectors } = { ...DEFAULTS, ...input };\n\n // Skip normalizeDocument/Readability on purpose: this tool exists to reach\n // tables that live outside the scored article (nav, aside, boilerplate), which\n // those stages would discard. Table structure is static HTML, so the matrix\n // walk is unaffected by unsanitized scripts/styles.\n const { document } = buildDocument(html, baseUrl);\n applySelectors(document, selectors);\n\n const tables: ExtractedTable[] = [];\n let index = 0;\n for (const table of document.querySelectorAll('table')) {\n const matrix = parseTableMatrix(table);\n if (matrix.length === 0) {\n continue;\n }\n const keys =\n format === 'json' ? resolveHeaderKeys(table, matrix) : undefined;\n const markdown = renderTable(matrix, format, keys);\n tables.push({\n index,\n rows: matrix.length,\n cols: matrix[0]?.length ?? 0,\n markdown,\n });\n index++;\n }\n\n const content =\n tables.length > 0\n ? tables.map(entry => entry.markdown).join('\\n\\n')\n : NO_TABLES;\n return {\n content: [{ text: content, type: 'text' }],\n structuredContent: {\n schemaVersion: 1,\n content,\n tables,\n metadata: { baseUrl, format, tableCount: tables.length },\n },\n };\n}\n\nexport const EXTRACT_TABLES_TOOL_DESCRIPTION = `Extract every <table> on the page from already-rendered (post-JavaScript) HTML and return each as GFM / CSV / JSON (caller picks). Runs no Readability, Turndown, or sanitization — a page-wide \\`querySelectorAll('table')\\` walk in front of the same rowspan/colspan-aware matrix serializer used by the \\`tables\\` option on \\`extract\\`. Captures tables outside the article body (nav, aside, boilerplate) that the \\`tables\\` option never sees. The server fetches nothing: \\`localPath\\` is the only source, and \\`baseUrl\\` (optional) is origin context only (never fetched).`;\n\nexport function extractTablesHandler(args: unknown): CallToolResult {\n try {\n return extractTables(args);\n } catch (err) {\n logger.error(\n `extract_tables failed: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n return toErrorResult(err);\n }\n}\n\nexport function registerExtractTablesTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'extract_tables',\n {\n title: 'Extract every table on the page',\n description: EXTRACT_TABLES_TOOL_DESCRIPTION,\n inputSchema: extractTablesInputShape,\n outputSchema: extractTablesOutputShape,\n },\n extractTablesHandler,\n );\n}\n","import type { SanitizationDiagnostics } from '../pipeline/context.js';\nimport type { ToolHandle } from '../server.js';\n\nimport { toErrorResult } from '../errors.js';\nimport { logger } from '../logger.js';\nimport { formatPayload } from '../output/format.js';\nimport { buildDocument } from '../pipeline/dom.js';\nimport {\n applySelectors,\n normalizeDocument,\n resolveLazyImages,\n} from '../pipeline/normalize.js';\nimport { sanitizeHtml } from '../pipeline/sanitize.js';\nimport { toMarkdown } from '../pipeline/turndown.js';\nimport { assembleDiagnostics, TraceCollector } from '../policy/diagnostics.js';\nimport { computeTextMetrics, nonEmpty } from '../policy/text.js';\nimport { truncateMarkdown } from '../policy/truncate.js';\nimport { readHtmlFile } from './html-source.js';\nimport { outputSchemaShape } from './output-schema.js';\nimport {\n type HtmlToMarkdownFromHtmlInput,\n type HtmlToMarkdownInput,\n htmlToMarkdownInputSchema,\n htmlToMarkdownInputShape,\n} from './schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\nconst EXTRACTED_NODE = 'fragment';\n\nexport function htmlToMarkdown(rawArgs: unknown): CallToolResult {\n const { localPath, ...rest } = htmlToMarkdownInputSchema.parse(rawArgs);\n return htmlToMarkdownFromHtml({ html: readHtmlFile(localPath), ...rest });\n}\n\n// Schema defaults for callers that pass only a subset of the knobs.\nconst DEFAULTS: Omit<HtmlToMarkdownInput, 'localPath'> =\n htmlToMarkdownInputSchema.parse({ localPath: '' });\n\nexport function htmlToMarkdownFromHtml(\n input: Readonly<HtmlToMarkdownFromHtmlInput>,\n): CallToolResult {\n const {\n html,\n baseUrl,\n selectors,\n format,\n metadataMode,\n gfm,\n headingStyle,\n codeBlockStyle,\n images,\n sanitize: shouldSanitize,\n maxChars,\n wordsPerMinute,\n cleanChrome,\n tables,\n debug,\n } = { ...DEFAULTS, ...input };\n\n const trace = new TraceCollector(debug);\n\n const { document, window } = buildDocument(html, baseUrl);\n\n const {\n documentElementCount,\n normalizeCounts,\n imagesResolved,\n textContent,\n rawHtml,\n } = trace.run('normalize', () => {\n const documentElementCount = document.querySelectorAll('*').length;\n const normalizeCounts = normalizeDocument(document, { cleanChrome });\n const imagesResolved = resolveLazyImages(document);\n applySelectors(document, selectors);\n const body = document.body;\n return {\n documentElementCount,\n imagesResolved,\n normalizeCounts,\n rawHtml: body.innerHTML,\n textContent: body.textContent,\n };\n });\n\n const { html: sanitizedHtml, counts: sanitizeCounts } = trace.run(\n 'sanitize',\n (): { counts: SanitizationDiagnostics; html: string } => {\n if (!shouldSanitize) {\n return { counts: { iframes: 0, scripts: 0 }, html: rawHtml };\n }\n const res = sanitizeHtml(rawHtml, window);\n return {\n counts: { iframes: res.iframesRemoved, scripts: res.scriptsRemoved },\n html: res.html,\n };\n },\n );\n const markdown = trace.run('turndown', () =>\n toMarkdown(sanitizedHtml, {\n codeBlockStyle,\n gfm,\n headingStyle,\n images,\n tables,\n baseUrl,\n }),\n );\n\n const { metadata } = trace.run('metadata', () => {\n const firstHeading = nonEmpty(\n document.body.querySelector('h1, h2, h3, h4, h5, h6')?.textContent,\n );\n const metadata = {\n title: firstHeading,\n baseUrl,\n ...computeTextMetrics(textContent, wordsPerMinute),\n };\n return { metadata };\n });\n\n const sanitization: SanitizationDiagnostics = {\n iframes: normalizeCounts.iframes + sanitizeCounts.iframes,\n scripts: normalizeCounts.scripts + sanitizeCounts.scripts,\n };\n const baseDiagnostics = assembleDiagnostics({\n articleHtml: sanitizedHtml,\n boilerplateRemoved: normalizeCounts.boilerplateRemoved,\n chromeRemoved: normalizeCounts.chromeRemoved,\n documentElementCount,\n extractedNode: EXTRACTED_NODE,\n fallbackUsed: true,\n imagesResolved,\n sanitization,\n trace: trace.collect(),\n truncated: false,\n window,\n });\n\n let payload = formatPayload({\n diagnostics: baseDiagnostics,\n format,\n markdown,\n metadata,\n metadataMode,\n sanitizedHtml,\n textContent,\n });\n\n let truncated = false;\n if (maxChars !== undefined && (format === 'markdown' || format === 'text')) {\n const res = truncateMarkdown(payload, maxChars);\n payload = res.text;\n truncated = res.truncated;\n }\n const diagnostics = truncated\n ? { ...baseDiagnostics, truncated }\n : baseDiagnostics;\n\n return {\n content: [{ text: payload, type: 'text' }],\n structuredContent: {\n schemaVersion: 1,\n content: payload,\n metadata,\n diagnostics,\n },\n };\n}\n\nexport const HTML_TO_MARKDOWN_TOOL_DESCRIPTION = `Convert an arbitrary HTML fragment to Markdown WITHOUT Readability article extraction (e.g. a snippet already isolated via chrome-devtools). Same Turndown + DOMPurify path as \\`extract\\`. The server fetches nothing: \\`localPath\\` is the only source, and \\`baseUrl\\` (optional) absolutizes relative links.`;\n\nexport function htmlToMarkdownHandler(args: unknown): CallToolResult {\n try {\n return htmlToMarkdown(args);\n } catch (err) {\n logger.error(\n `html_to_markdown failed: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n return toErrorResult(err);\n }\n}\n\nexport function registerHtmlToMarkdownTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'html_to_markdown',\n {\n title: 'Convert HTML fragment to Markdown',\n description: HTML_TO_MARKDOWN_TOOL_DESCRIPTION,\n inputSchema: htmlToMarkdownInputShape,\n outputSchema: outputSchemaShape,\n },\n htmlToMarkdownHandler,\n );\n}\n","import type { ToolHandle } from '../server.js';\n\nimport { toErrorResult } from '../errors.js';\nimport { logger } from '../logger.js';\nimport { buildDocument } from '../pipeline/dom.js';\nimport { applySelectors, normalizeDocument } from '../pipeline/normalize.js';\nimport { resolveOutline } from '../policy/outline.js';\nimport { readHtmlFile } from './html-source.js';\nimport { outlineOutputShape } from './output-schema.js';\nimport {\n type OutlineFromHtmlInput,\n outlineInputSchema,\n outlineInputShape,\n} from './schemas.js';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\n// One line per heading, nested by depth; never blank so content[0].text is always scannable.\nfunction renderOutlineToc(\n outline: readonly { level: number; text: string }[],\n): string {\n if (outline.length === 0) {\n return '(no headings found)';\n }\n return outline\n .map(entry => `${' '.repeat(entry.level - 1)}- ${entry.text}`)\n .join('\\n');\n}\n\nexport function outlineDocument(rawArgs: unknown): CallToolResult {\n const { localPath, ...rest } = outlineInputSchema.parse(rawArgs);\n return outlineDocumentFromHtml({ html: readHtmlFile(localPath), ...rest });\n}\n\nexport function outlineDocumentFromHtml(\n input: Readonly<OutlineFromHtmlInput>,\n): CallToolResult {\n const { html, baseUrl, selectors } = input;\n\n const { document } = buildDocument(html, baseUrl);\n normalizeDocument(document);\n applySelectors(document, selectors);\n const outline = resolveOutline(document);\n\n const title =\n document.title.trim() ||\n document.querySelector('h1')?.textContent.replace(/\\s+/g, ' ').trim() ||\n undefined;\n const metadata = { title, baseUrl };\n\n const content = renderOutlineToc(outline);\n return {\n content: [{ text: content, type: 'text' }],\n structuredContent: {\n schemaVersion: 1,\n content,\n outline,\n metadata,\n },\n };\n}\n\nexport const OUTLINE_TOOL_DESCRIPTION = `Return the document outline (h1-h6 headings with stable anchor ids) of already-rendered (post-JavaScript) HTML as a cheap pre-check before full extraction. No Readability scoring, no Turndown, no sanitization — a pure heading walk. The server fetches nothing: \\`localPath\\` is the only source, and \\`baseUrl\\` is origin context only (never fetched).`;\n\nexport function outlineHandler(args: unknown): CallToolResult {\n try {\n return outlineDocument(args);\n } catch (err) {\n logger.error(\n `outline failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n return toErrorResult(err);\n }\n}\n\nexport function registerOutlineTool(server: McpServer): ToolHandle {\n return server.registerTool(\n 'outline',\n {\n title: 'Get document outline (heading TOC)',\n description: OUTLINE_TOOL_DESCRIPTION,\n inputSchema: outlineInputShape,\n outputSchema: outlineOutputShape,\n },\n outlineHandler,\n );\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nimport { loadConfig } from './config.js';\nimport { loadPresets } from './preset-cache.js';\nimport { registerResources } from './resources.js';\nimport { registerSamplingTools } from './sampling.js';\nimport { registerChunkTextTool } from './tools/chunk_text.js';\nimport { registerExplainTool } from './tools/explain.js';\nimport { registerExtractGridTool } from './tools/extract_grid.js';\nimport { registerExtractLinksTool } from './tools/extract_links.js';\nimport { registerExtractListTool } from './tools/extract_list.js';\nimport { registerExtractMetadataTool } from './tools/extract_metadata.js';\nimport { registerExtractSectionTool } from './tools/extract_section.js';\nimport { registerExtractTablesTool } from './tools/extract_tables.js';\nimport { registerExtractTool } from './tools/extract.js';\nimport { registerHtmlToMarkdownTool } from './tools/html_to_markdown.js';\nimport { registerOutlineTool } from './tools/outline.js';\n\n// Re-exported so the dev hot-reload loop can import server.ts as a single\n// RuntimeModule and pick up every registration family (tools/resources) plus\n// the boot-time preset load.\nexport { loadPresets, registerResources };\n\n// `remove()` unregisters the tool and notifies the client; dev reload holds the\n// previous batch to remove before re-registering.\nexport interface ToolHandle {\n remove(): void;\n}\n\nexport function createMcpServer(): McpServer {\n const { name, version, title, description, instructions } = loadConfig();\n return new McpServer({ name, version, title, description }, { instructions });\n}\n\n// A tool earns its place only by skipping a pipeline stage (outline,\n// extract_metadata skip Readability) or returning a fundamentally different\n// shape (extract_list is a second engine). Alternate views of the one pipeline\n// — tables, images, structured data, code — are options or fields on `extract`,\n// not separate tools; that keeps the MCP surface small and the pipeline deep.\nexport function registerTools(server: McpServer): ToolHandle[] {\n return [\n registerChunkTextTool(server),\n registerExplainTool(server),\n registerExtractGridTool(server),\n registerExtractLinksTool(server),\n registerExtractListTool(server),\n registerExtractTool(server),\n registerExtractMetadataTool(server),\n registerExtractSectionTool(server),\n registerExtractTablesTool(server),\n registerHtmlToMarkdownTool(server),\n registerOutlineTool(server),\n ];\n}\n\n// Capability-gated tools are registered AFTER the initialize handshake, not\n// eagerly with the families above. The MCP `tools` capability locks in on the\n// first pre-connect registration (registerCapabilities throws post-connect),\n// so the tool-list handlers are already live; adding to `_registeredTools`\n// later simply appears in the next `tools/list` and fires `listChanged`. The\n// low-level Server populates `getClientCapabilities()` from the client's\n// `initialize` request, exposed via `McpServer.server`.\nexport function registerCapabilityGatedTools(server: McpServer): ToolHandle[] {\n const caps = server.server.getClientCapabilities();\n if (!caps?.sampling) {\n return [];\n }\n return registerSamplingTools(server);\n}\n\nexport function createServer(): McpServer {\n const server = createMcpServer();\n loadPresets();\n registerTools(server);\n registerResources(server);\n // Capability-gated tools (sampling) need the client's advertised\n // capabilities, which the low-level Server only populates after the\n // initialize handshake. Hook the `initialized` notification — it fires once\n // the client has connected and before any `tools/list`. `dev.ts` re-runs the\n // gate directly on reload (the client is already past `initialized`).\n server.server.oninitialized = () => {\n registerCapabilityGatedTools(server);\n };\n return server;\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\n\nimport { createServer } from './server.js';\n\nif (process.argv[2] === 'extract') {\n void import('./cli.js')\n .then(m => m.runCli(process.argv.slice(2)))\n .then(code => {\n // eslint-disable-next-line n/no-process-exit\n process.exit(code);\n })\n .catch((err: unknown) => {\n process.stderr.write(\n `${err instanceof Error ? err.message : String(err)}\\n`,\n );\n // eslint-disable-next-line n/no-process-exit\n process.exit(1);\n });\n} else {\n const server: McpServer = createServer();\n const transport = new StdioServerTransport();\n\n await server.connect(transport);\n\n function shutdown(): void {\n void server\n .close()\n .catch(() => {\n // best-effort; exiting regardless\n })\n .finally(() => {\n // Force-exit on signal; the n/no-process-exit rule targets libraries.\n // eslint-disable-next-line n/no-process-exit\n process.exit(0);\n });\n }\n\n process.on('SIGINT', shutdown);\n process.on('SIGTERM', shutdown);\n}\n"],"mappings":"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"}