mcp-scraper 0.3.14 → 0.3.16

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.
Files changed (50) hide show
  1. package/dist/bin/api-server.cjs +2261 -511
  2. package/dist/bin/api-server.cjs.map +1 -1
  3. package/dist/bin/api-server.js +3 -3
  4. package/dist/bin/browser-agent-stdio-server.cjs +6 -6
  5. package/dist/bin/browser-agent-stdio-server.cjs.map +1 -1
  6. package/dist/bin/browser-agent-stdio-server.js +2 -2
  7. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  8. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  9. package/dist/bin/mcp-scraper-cli.js +1 -1
  10. package/dist/bin/mcp-scraper-combined-stdio-server.cjs +663 -48
  11. package/dist/bin/mcp-scraper-combined-stdio-server.cjs.map +1 -1
  12. package/dist/bin/mcp-scraper-combined-stdio-server.js +6 -5
  13. package/dist/bin/mcp-scraper-combined-stdio-server.js.map +1 -1
  14. package/dist/bin/mcp-scraper-install.cjs +4 -4
  15. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  16. package/dist/bin/mcp-scraper-install.js +2 -2
  17. package/dist/bin/mcp-stdio-server.cjs +655 -40
  18. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  19. package/dist/bin/mcp-stdio-server.js +2 -2
  20. package/dist/{chunk-DBQDG7EH.js → chunk-5H22TOXQ.js} +28 -1
  21. package/dist/chunk-5H22TOXQ.js.map +1 -0
  22. package/dist/{chunk-HM6FHV5U.js → chunk-7XARJHS6.js} +662 -41
  23. package/dist/chunk-7XARJHS6.js.map +1 -0
  24. package/dist/{chunk-KPXMPAJ3.js → chunk-ACIUCZ27.js} +4 -4
  25. package/dist/chunk-ACIUCZ27.js.map +1 -0
  26. package/dist/{chunk-AX7UBYLG.js → chunk-ISZWGIWL.js} +7 -7
  27. package/dist/chunk-ISZWGIWL.js.map +1 -0
  28. package/dist/chunk-SOMBZK3V.js +7 -0
  29. package/dist/chunk-SOMBZK3V.js.map +1 -0
  30. package/dist/{chunk-AZ5PKU4F.js → chunk-ZAUMSBV3.js} +2 -2
  31. package/dist/{db-BE4JVB3V.js → db-P76GVIFN.js} +2 -2
  32. package/dist/{server-ILIVSPNY.js → server-LPVBSE2E.js} +1133 -93
  33. package/dist/server-LPVBSE2E.js.map +1 -0
  34. package/dist/{worker-FG7ZWEGA.js → worker-2WVKKCC7.js} +3 -3
  35. package/docs/final-tooling-spec.md +206 -0
  36. package/docs/mcp-tool-design-guide.md +225 -0
  37. package/docs/mcp-tool-manifest.generated.json +57 -22
  38. package/docs/seo-crawl-report-spec.md +287 -0
  39. package/docs/tool-catalog-spec.md +388 -0
  40. package/package.json +2 -1
  41. package/dist/chunk-3QHZPR4U.js +0 -7
  42. package/dist/chunk-3QHZPR4U.js.map +0 -1
  43. package/dist/chunk-AX7UBYLG.js.map +0 -1
  44. package/dist/chunk-DBQDG7EH.js.map +0 -1
  45. package/dist/chunk-HM6FHV5U.js.map +0 -1
  46. package/dist/chunk-KPXMPAJ3.js.map +0 -1
  47. package/dist/server-ILIVSPNY.js.map +0 -1
  48. /package/dist/{chunk-AZ5PKU4F.js.map → chunk-ZAUMSBV3.js.map} +0 -0
  49. /package/dist/{db-BE4JVB3V.js.map → db-P76GVIFN.js.map} +0 -0
  50. /package/dist/{worker-FG7ZWEGA.js.map → worker-2WVKKCC7.js.map} +0 -0
@@ -0,0 +1,287 @@
1
+ # SEO Crawl Report — Screaming Frog parity spec (scrape-report scope)
2
+
3
+ Goal: from our `extract_site` crawl HTML, reproduce the most valuable ~50% of Screaming Frog's
4
+ per-URL crawl reports — **titles, metas, headings, indexability, canonicals, content, structured
5
+ data, and internal-link analysis** — and emit them as structured files in the bulk-scrape folder
6
+ plus an audit report.
7
+
8
+ Scope boundary (user directive): **scrape reports only** — per-URL crawl data and the link graph we
9
+ can derive from it. Out of scope here: JS-rendering diffs, log-file analysis, GA/GSC API joins,
10
+ spell-check, PageSpeed/Lighthouse, AMP validation, crawl scheduling.
11
+
12
+ ---
13
+
14
+ ## 0. Current state (verified)
15
+
16
+ - `PageData` (`src/api/site-extractor.ts:11`): `url, status, via, title, metaDescription, h1,
17
+ headings[{level,text}], wordCount, schemaTypes[], canonicalUrl, internalLinks (count),
18
+ externalLinks (count), bodyMarkdown, schema[]`.
19
+ - `parsePageData(url, html, status, via)` (`src/api/site-extractor.ts:45`) — HTML only, **no response headers**.
20
+ - Fetch layers that DO have headers/timing:
21
+ - plain: `fetchAndParse` → `res: Response` (`res.headers`).
22
+ - rotating: `rotating-proxy-crawl.ts` `fetchBatch` → playwright `resp` (`resp.status()`, `resp.headers()`).
23
+ - `RotatingFetchResult` (`rotating-proxy-crawl.ts:5`) = `{ url, html, status, via }` — must be extended.
24
+ - Bulk output: `saveBulkSite(siteUrl, BulkPage[])` (`mcp-response-formatter.ts:68`); `BulkPage` only
25
+ carries `url,title,bodyMarkdown,metaDescription,schemaTypes`. Writes `index.md` + `pages/*.md`.
26
+ - `formatExtractSite` (`mcp-response-formatter.ts`) maps `pages` → `BulkPage` for the folder.
27
+
28
+ ---
29
+
30
+ ## 1. Screaming Frog feature inventory → coverage decision
31
+
32
+ Legend: ✅ already captured · ◑ partial · ➕ ADD (in this spec) · 🔗 link-graph (Section 3) · ⛔ out of scope
33
+
34
+ ### 1.1 Response / crawl
35
+ | SF data | Decision | Source |
36
+ |---|---|---|
37
+ | Address (URL) | ✅ | `PageData.url` |
38
+ | Status code + status text | ✅ | `PageData.status` |
39
+ | Content-Type | ➕ | response header |
40
+ | Response time (ms) | ➕ | fetch timing |
41
+ | Size (bytes, HTML transfer) | ➕ | `Content-Length` / `html.length` |
42
+ | Redirect URL + redirect type | ➕ | `Location` header / 3xx |
43
+ | Last-Modified | ➕ | response header |
44
+ | Crawl depth (clicks from start) | 🔗 | computed from link graph |
45
+ | Indexability + reason | ➕ | meta-robots + X-Robots + canonical + status |
46
+
47
+ ### 1.2 On-page elements
48
+ | SF data | Decision | Source |
49
+ |---|---|---|
50
+ | Title 1 + length | ✅/➕ | have title; ADD length + pixel-width estimate |
51
+ | Title — pixel width | ➕ | char-width table estimate (flag approximate) |
52
+ | Meta description + length + pixel width | ✅/➕ | have text; ADD lengths |
53
+ | Meta keywords | ➕ | regex (low value, cheap) |
54
+ | H1 (1st + 2nd) + length | ✅/➕ | have headings[]; derive H1-1/H1-2 + length |
55
+ | H2 (1st/2nd) + count | ✅ | from headings[] |
56
+ | Word count | ✅ | `PageData.wordCount` |
57
+ | Text ratio (text/HTML) | ➕ | `bodyText.length / html.length` |
58
+ | Meta robots | ➕ | regex |
59
+ | X-Robots-Tag | ➕ | response header |
60
+ | Canonical link element | ✅ | `PageData.canonicalUrl` |
61
+ | rel=next / rel=prev | ➕ | regex |
62
+ | hreflang entries | ➕ | regex (list of {lang,href}) |
63
+ | Open Graph tags | ➕ | regex (og:title/description/image/type) |
64
+ | Twitter card tags | ➕ | regex |
65
+ | Mobile/AMP alternate | ➕ | regex `<link rel="amphtml">`, `alternate` |
66
+
67
+ ### 1.3 Images
68
+ | SF data | Decision | Source |
69
+ |---|---|---|
70
+ | Image count | ➕ | count `<img>` |
71
+ | Images missing alt | ➕ | `<img>` without non-empty `alt` |
72
+ | Alt text over N chars | ➕ | alt length check |
73
+ | Image >100KB | ⛔ (needs per-asset fetch) | skip in v1 |
74
+
75
+ ### 1.4 Structured data
76
+ | SF data | Decision | Source |
77
+ |---|---|---|
78
+ | Schema types present | ✅ | `PageData.schemaTypes` |
79
+ | Raw JSON-LD | ✅ | `PageData.schema` |
80
+ | Microdata / RDFa | ◑ | JSON-LD only in v1 |
81
+ | Validation errors | ◑ | shape checks only (not full Google validator) |
82
+
83
+ ### 1.5 Content
84
+ | SF data | Decision | Source |
85
+ |---|---|---|
86
+ | Low content / thin pages | ➕ | wordCount threshold |
87
+ | Exact duplicates | ➕ | hash of normalized body |
88
+ | Near-duplicates | ◑ | simhash (v2) — exact-hash in v1 |
89
+
90
+ ### 1.6 Links — **the high-value gap (user-flagged)**
91
+ | SF data | Decision | Source |
92
+ |---|---|---|
93
+ | Outlinks (count) | ✅ | `internalLinks`/`externalLinks` counts |
94
+ | Outlinks edge list (target, anchor, rel, position) | 🔗➕ | replace counts with edge capture |
95
+ | Inlinks + unique inlinks per URL | 🔗 | computed (invert outlinks) |
96
+ | Anchor text per inlink | 🔗 | from edges |
97
+ | Internal nofollow | 🔗➕ | `rel="nofollow"` on edge |
98
+ | Crawl depth | 🔗 | BFS from start over internal edges |
99
+ | Orphan URLs | 🔗 | in sitemap/crawl set but zero internal inlinks |
100
+ | Broken internal/external links | 🔗 | edge target status ∈ 4xx/5xx |
101
+ | Redirecting links | 🔗 | edge target status ∈ 3xx |
102
+
103
+ ### 1.7 Issue filters (the actionable "reports" — computed cross-page)
104
+ Titles: missing, duplicate, >60 char/>561px, <30 char, multiple, same-as-H1.
105
+ Meta desc: missing, duplicate, >155/>985px, <70, multiple.
106
+ H1: missing, duplicate, multiple, >70 char.
107
+ H2: missing, multiple.
108
+ Canonical: missing, canonicalised (non-self), multiple, non-indexable canonical target.
109
+ Directives: noindex, nofollow.
110
+ Response: 3xx (incl. chains/loops via edges), 4xx broken, 5xx, blocked-by-robots.
111
+ URL: >115 char, uppercase, underscores, params, non-ASCII, duplicate.
112
+ Content: thin (<X words), exact duplicate (hash collision).
113
+ Images: missing alt.
114
+ Structured data: present-but-invalid (shape), missing on key templates.
115
+ Links: broken internal/external, redirected internal, orphan pages.
116
+
117
+ ---
118
+
119
+ ## 2. Suggested data model (THE deliverable)
120
+
121
+ ### 2.1 Extend `PageData` (`src/api/site-extractor.ts:11`)
122
+ Add these fields (keep existing):
123
+
124
+ ```ts
125
+ // response/header-derived (require fetch-layer plumbing, Section 3.2)
126
+ contentType: string | null
127
+ responseTimeMs: number | null
128
+ sizeBytes: number | null
129
+ redirectUrl: string | null // Location on 3xx
130
+ lastModified: string | null
131
+ xRobotsTag: string | null
132
+
133
+ // on-page additions
134
+ titleLength: number | null
135
+ titlePixels: number | null // estimate, approximate
136
+ metaDescLength: number | null
137
+ metaKeywords: string | null
138
+ h1_2: string | null // second H1 if present
139
+ h2Count: number
140
+ metaRobots: string | null
141
+ relNext: string | null
142
+ relPrev: string | null
143
+ hreflang: Array<{ lang: string; href: string }>
144
+ og: { title?: string; description?: string; image?: string; type?: string } | null
145
+ twitter: { card?: string; title?: string; description?: string } | null
146
+ ampHref: string | null
147
+
148
+ // images
149
+ imageCount: number
150
+ imagesMissingAlt: number
151
+
152
+ // content
153
+ textRatio: number // bodyText.length / html.length
154
+ contentHash: string // sha1 of normalized visible text
155
+
156
+ // indexability (derived, see 3.3)
157
+ indexable: boolean
158
+ indexabilityReason: string | null // 'noindex' | 'canonicalised' | 'non-200' | 'x-robots-noindex' | null
159
+
160
+ // LINKS — replace the two counts with the edge list
161
+ outlinks: Array<{ href: string; anchor: string; rel: string | null; internal: boolean }>
162
+ // keep internalLinks/externalLinks as derived counts for back-compat
163
+ ```
164
+
165
+ ### 2.2 Link edge + graph model (post-crawl, computed)
166
+ ```ts
167
+ interface LinkEdge { from: string; to: string; anchor: string; rel: string | null; internal: boolean }
168
+
169
+ interface PageLinkMetrics {
170
+ url: string
171
+ inlinks: number
172
+ uniqueInlinks: number
173
+ outlinksInternal: number
174
+ outlinksExternal: number
175
+ crawlDepth: number | null // BFS hops from startUrl over internal 200 edges; null = orphan/unreachable
176
+ orphan: boolean // crawled/in-sitemap but 0 internal inlinks
177
+ topAnchors: string[] // most common inbound anchor texts
178
+ }
179
+ ```
180
+
181
+ ### 2.3 Structured outputs written into the bulk folder
182
+ Alongside `index.md` + `pages/*.md`, add:
183
+ - `pages.jsonl` — one JSON line per page = full extended `PageData` minus `bodyMarkdown`/`schema`
184
+ (those stay in `pages/*.md`). This is the "Internal" tab.
185
+ - `links.jsonl` — one `LinkEdge` per line (internal + external). The "All Outlinks" export.
186
+ - `link-metrics.jsonl` — one `PageLinkMetrics` per URL. The inlinks/depth/orphan view.
187
+ - `issues.json` — the computed issue filters (Section 1.7) as `{ issueKey: { count, urls[] } }`.
188
+ - `report.md` — human summary (counts per issue, top offenders) = the audit deliverable.
189
+
190
+ ---
191
+
192
+ ## 3. Implementation blueprint (atomic)
193
+
194
+ ### 3.1 Capture: extend `parsePageData` — `src/api/site-extractor.ts:45`
195
+ Signature change:
196
+ ```ts
197
+ function parsePageData(
198
+ url: string, html: string, status: number, via: 'fetch'|'browser',
199
+ resp?: { headers?: Record<string,string>; responseTimeMs?: number; redirectUrl?: string|null }
200
+ ): PageData
201
+ ```
202
+ Add, inside the function, regex/derivations:
203
+ - `titleLength = title?.length`; `titlePixels = estimatePixels(title)` (new helper, char-width table).
204
+ - `metaDescLength`, `metaKeywords` via `<meta name="keywords">`.
205
+ - `h1_2`/`h2Count` from existing `headings` array.
206
+ - `metaRobots` via `<meta name="robots" content="...">`.
207
+ - `relNext`/`relPrev` via `<link rel="next|prev">`.
208
+ - `hreflang[]` via `<link rel="alternate" hreflang="..">`.
209
+ - `og`/`twitter` via `<meta property="og:.."|name="twitter:..">`.
210
+ - `ampHref` via `<link rel="amphtml">`.
211
+ - `imageCount` = matches of `<img`; `imagesMissingAlt` = `<img>` lacking non-empty `alt`.
212
+ - `textRatio = bodyText.length / Math.max(1, html.length)`.
213
+ - `contentHash = sha1(bodyText.replace(/\s+/g,' ').trim())` (`node:crypto`).
214
+ - header fields from `resp`: `contentType, xRobotsTag, lastModified, sizeBytes, responseTimeMs, redirectUrl`.
215
+ - `outlinks[]`: replace the count-only loop (`site-extractor.ts:102-110`) — for each `<a href>` also
216
+ capture anchor text and `rel`; classify `internal` by origin; keep `internalLinks`/`externalLinks`
217
+ as `.filter().length` for back-compat.
218
+ - `indexable`/`indexabilityReason`: `false` if status!=200, or metaRobots/xRobots contains `noindex`,
219
+ or canonical present and != self → reason set accordingly.
220
+
221
+ New helper `estimatePixels(s)` — sum per-char widths from a static map (approx Arial 'M'≈14, 'i'≈4…);
222
+ mark all pixel fields "approximate" in docs. ~25 lines.
223
+
224
+ ### 3.2 Plumb headers/timing into both fetch paths
225
+ - `RotatingFetchResult` (`rotating-proxy-crawl.ts:5`): add
226
+ `headers?: Record<string,string>; responseTimeMs?: number; redirectUrl?: string|null`.
227
+ In `fetchBatch` (`:60-98`): time the `goto`, `resp.headers()`, capture `Location` when 3xx.
228
+ - `fetchAndParse` (`site-extractor.ts:115`): build the same `resp` object from `res.headers` + timing,
229
+ pass as 5th arg to `parsePageData`.
230
+ - In `extractSite` rotating branch (`site-extractor.ts:~185`): pass `r.headers/responseTimeMs/redirectUrl`
231
+ into `parsePageData(r.url, r.html, r.status, 'browser', {...})`.
232
+
233
+ ### 3.3 Post-crawl link graph — new file `src/api/seo-link-graph.ts`
234
+ ```ts
235
+ export function buildLinkGraph(pages: PageData[], startUrl: string):
236
+ { edges: LinkEdge[]; metrics: Map<string, PageLinkMetrics> }
237
+ ```
238
+ Logic:
239
+ - Flatten `pages[].outlinks` → `edges` (from = page.url).
240
+ - Build `inlinks` map by inverting internal edges; `uniqueInlinks` = distinct `from`.
241
+ - `crawlDepth`: BFS from `startUrl` over internal edges whose target status==200; unreached → null.
242
+ - `orphan`: page in set with 0 internal inlinks and url != startUrl.
243
+ - `topAnchors`: top-3 inbound anchors by frequency.
244
+ - Status join: map target→status from `pages` to flag broken (4xx/5xx) / redirect (3xx) edges.
245
+
246
+ ### 3.4 Issue computation — new file `src/api/seo-issues.ts`
247
+ ```ts
248
+ export function computeIssues(pages: PageData[], metrics: Map<string,PageLinkMetrics>):
249
+ Record<string, { count: number; urls: string[] }>
250
+ ```
251
+ Thresholds (SF defaults): title >60/<30 char & >561px; meta >155/<70 & >985px; H1 >70; URL >115;
252
+ thin <200 words (configurable). Duplicates: group by exact `title`/`metaDescription`/`contentHash`,
253
+ flag groups size>1. Broken/redirect/orphan from `metrics`/edges. Indexability from `PageData`.
254
+
255
+ ### 3.5 Write structured outputs — extend `saveBulkSite` (`mcp-response-formatter.ts:68`)
256
+ - Widen `BulkPage` → accept full extended `PageData` (or pass `PageData[]` directly).
257
+ - After writing `pages/*.md`, also `writeFileSync`:
258
+ - `pages.jsonl` (PageData minus bodyMarkdown/schema),
259
+ - `links.jsonl`, `link-metrics.jsonl`, `issues.json`, `report.md`.
260
+ - `formatExtractSite` already has full `pages`; pass them through (today it maps to the slim BulkPage —
261
+ change to pass the full objects, call `buildLinkGraph` + `computeIssues` before `saveBulkSite`).
262
+ - Return extra paths in the bulk summary + `structuredContent` (e.g. `pagesJsonl`, `issuesFile`).
263
+
264
+ ### 3.6 The skill — `seo-crawl-audit`
265
+ Orchestrator (no new server code):
266
+ 1. Call `extract_site(url, rotateProxies:true)` → folder path from response.
267
+ 2. Read `pages.jsonl` + `issues.json` + `link-metrics.jsonl` from the folder.
268
+ 3. Emit a prioritized SEO report: response-code breakdown, title/meta/H1 issues with offender lists,
269
+ indexability summary, thin/duplicate content, structured-data coverage, and the internal-link
270
+ section (orphans, deepest pages, most-linked, broken internal links).
271
+ 4. Hand the full link graph to the existing `site-architecture-auditor` skill for equity/architecture
272
+ scoring (it already builds this graph from SF exports — we now feed it ours).
273
+
274
+ ---
275
+
276
+ ## 4. Build order (phased, each independently shippable)
277
+ 1. **P1 — page fields (no headers):** 3.1 minus header fields + 3.5 `pages.jsonl`. Unlocks titles/metas/
278
+ H1/canonical/indexability(meta)/thin/dup/schema reports immediately.
279
+ 2. **P2 — link graph:** 3.1 outlinks edge capture + 3.3 + `links.jsonl`/`link-metrics.jsonl`. Unlocks
280
+ inlinks/depth/orphans/broken-links — the user's headline ask.
281
+ 3. **P3 — headers/timing:** 3.2 (both fetch paths). Adds content-type/size/response-time/X-Robots/redirects.
282
+ 4. **P4 — issues + report:** 3.4 + `issues.json`/`report.md`.
283
+ 5. **P5 — skill:** 3.6 + handoff to `site-architecture-auditor`.
284
+
285
+ ## 5. Explicit non-goals (v1)
286
+ Per-asset image weight, microdata/RDFa, full structured-data validation, near-duplicate simhash,
287
+ JS-render diffing, pixel-width exactness (estimate only), redirect-chain hop-by-hop beyond one hop.