mcp-scraper 0.39.0 → 0.40.2

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 (80) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/api-server.cjs +382 -44
  3. package/dist/bin/api-server.cjs.map +1 -1
  4. package/dist/bin/api-server.js +3 -3
  5. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  6. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  7. package/dist/bin/mcp-scraper-cli.js +1 -1
  8. package/dist/bin/mcp-scraper-install.cjs +1 -1
  9. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  10. package/dist/bin/mcp-scraper-install.js +1 -1
  11. package/dist/bin/mcp-stdio-server.cjs +6 -6
  12. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  13. package/dist/bin/mcp-stdio-server.js +4 -4
  14. package/dist/bin/paa-harvest.cjs.map +1 -1
  15. package/dist/bin/paa-harvest.js +3 -3
  16. package/dist/{chunk-DO6WGZRS.js → chunk-3PIWJS6Y.js} +2 -2
  17. package/dist/{chunk-YUE46JGV.js → chunk-7XBBFBYY.js} +2 -2
  18. package/dist/{chunk-AFY6DNL7.js → chunk-GUVKHCKE.js} +13 -3
  19. package/dist/chunk-GUVKHCKE.js.map +1 -0
  20. package/dist/{chunk-TJRCAP7C.js → chunk-LP6E462I.js} +8 -8
  21. package/dist/chunk-LP6E462I.js.map +1 -0
  22. package/dist/{chunk-4WAE4AV5.js → chunk-NVXNEOUQ.js} +2 -2
  23. package/dist/{chunk-3HOANNNR.js → chunk-X2LKCX6H.js} +2 -2
  24. package/dist/{chunk-ATWQRW3K.js → chunk-XORPNO3Z.js} +2 -2
  25. package/dist/chunk-ZUJLSICT.js +7 -0
  26. package/dist/chunk-ZUJLSICT.js.map +1 -0
  27. package/dist/{db-6AJW47NF.js → db-W3CP562I.js} +2 -2
  28. package/dist/{extract-bundle-CWG35SKV.js → extract-bundle-K4PG3RZJ.js} +3 -3
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.js +3 -3
  31. package/dist/{location-data-repository-BGKZF7DV.js → location-data-repository-RLQX6SNM.js} +3 -3
  32. package/dist/{server-BXNVNAMT.js → server-KM3CFWCF.js} +373 -52
  33. package/dist/server-KM3CFWCF.js.map +1 -0
  34. package/dist/{site-extract-repository-QZKZSQLQ.js → site-extract-repository-2SMMFKKL.js} +3 -3
  35. package/dist/{worker-YTIMPE57.js → worker-FXAGFYOE.js} +5 -5
  36. package/package.json +7 -7
  37. package/dist/chunk-6NWL3MVU.js +0 -7
  38. package/dist/chunk-6NWL3MVU.js.map +0 -1
  39. package/dist/chunk-AFY6DNL7.js.map +0 -1
  40. package/dist/chunk-TJRCAP7C.js.map +0 -1
  41. package/dist/server-BXNVNAMT.js.map +0 -1
  42. package/docs/adr/0001-in-page-graphql-interception-for-anti-bot-scraping.md +0 -58
  43. package/docs/adr/0002-hybrid-smart-rag-vault-retrieval.md +0 -62
  44. package/docs/adr/0003-waive-unrecoverable-scheduled-model-cost.md +0 -22
  45. package/docs/adr/2026-07-28-owned-aug-webinar-registration.md +0 -61
  46. package/docs/adr/README.md +0 -13
  47. package/docs/final-tooling-spec.md +0 -206
  48. package/docs/hosted-location-data.md +0 -108
  49. package/docs/kernel-proxy-future-enhancements.md +0 -80
  50. package/docs/mcp-tool-craft-lint.generated.md +0 -186
  51. package/docs/mcp-tool-design-guide.md +0 -225
  52. package/docs/mcp-tool-manifest.generated.json +0 -23305
  53. package/docs/mcp-tool-quality-spec.md +0 -240
  54. package/docs/oauth-legal-review.md +0 -38
  55. package/docs/seo-crawl-report-spec.md +0 -287
  56. package/docs/specs/api-forge-spec.md +0 -234
  57. package/docs/specs/connected-services-control-plane-decoupling-spec.md +0 -1044
  58. package/docs/specs/deferred-work-spec.md +0 -86
  59. package/docs/specs/google-drive-bulk-access-and-mcp-schema-passthrough-spec.md +0 -1689
  60. package/docs/specs/kernel-stealth-captcha-test-matrix.md +0 -278
  61. package/docs/specs/main-mcp-integration-ownership-spec.md +0 -1164
  62. package/docs/specs/mcp-tool-definition-quality-audit-spec.md +0 -1602
  63. package/docs/specs/meta-ad-creative-media-resolution-spec.md +0 -31
  64. package/docs/specs/multimodal-image-memory-architecture-spec.md +0 -1022
  65. package/docs/specs/oauth-mcp-spec.md +0 -213
  66. package/docs/specs/plan-visibility-and-management-roadmap.md +0 -293
  67. package/docs/specs/query-fanout-transport-contract-fix.md +0 -45
  68. package/docs/specs/relationship-workspace-ai-behavior-plan.md +0 -26
  69. package/docs/specs/unified-credit-and-scheduled-execution-billing-spec.md +0 -995
  70. package/docs/tool-catalog-spec.md +0 -388
  71. /package/dist/{chunk-DO6WGZRS.js.map → chunk-3PIWJS6Y.js.map} +0 -0
  72. /package/dist/{chunk-YUE46JGV.js.map → chunk-7XBBFBYY.js.map} +0 -0
  73. /package/dist/{chunk-4WAE4AV5.js.map → chunk-NVXNEOUQ.js.map} +0 -0
  74. /package/dist/{chunk-3HOANNNR.js.map → chunk-X2LKCX6H.js.map} +0 -0
  75. /package/dist/{chunk-ATWQRW3K.js.map → chunk-XORPNO3Z.js.map} +0 -0
  76. /package/dist/{db-6AJW47NF.js.map → db-W3CP562I.js.map} +0 -0
  77. /package/dist/{extract-bundle-CWG35SKV.js.map → extract-bundle-K4PG3RZJ.js.map} +0 -0
  78. /package/dist/{location-data-repository-BGKZF7DV.js.map → location-data-repository-RLQX6SNM.js.map} +0 -0
  79. /package/dist/{site-extract-repository-QZKZSQLQ.js.map → site-extract-repository-2SMMFKKL.js.map} +0 -0
  80. /package/dist/{worker-YTIMPE57.js.map → worker-FXAGFYOE.js.map} +0 -0
@@ -1,58 +0,0 @@
1
- # ADR 0001: In-page API interception for anti-bot scraping (Facebook Ad Library)
2
-
3
- - **Status:** Accepted
4
- - **Date:** 2026-06-03
5
- - **Deciders:** Andrew (operator), engineering
6
- - **Applies to:** `facebook_ad_search`, `facebook_page_intel`, and future scrapers of JS-heavy, anti-bot-protected sites
7
-
8
- ## Context
9
-
10
- `facebook_ad_search` was unreliable — it usually returned `soft-block: no results (refunded)`, and when it did return, advertiser names came back `undefined` with `—` for library IDs. Two independent root causes:
11
-
12
- 1. **DOM scraping a hostile SPA.** The Facebook Ad Library is a React app. The ads are *not* in the initial HTML — the page fires its own background GraphQL request and renders the JSON into DOM cards asynchronously. Our scraper waited for those cards to render, then re-parsed the rendered text with regexes (`See ad details … Sponsored`). That is fragile (breaks on any UI change) and slow, and it conflated "page still loading" with "no results."
13
-
14
- 2. **A single static proxy.** Every Facebook scrape launched with the static `KERNEL_PROXY_ID`. Facebook flags/rate-limits a repeatedly-used IP and serves a near-empty / login-gated page to it. A real residential browser loaded the same search fine — confirming the block was IP-reputation, not the URL or a login requirement. `facebook_page_intel` worked intermittently for the same reason (same proxy, sometimes flagged).
15
-
16
- The "undefined names / — library IDs" was a third, smaller bug: a field-name mismatch between the API response (`pageName`, `sampleLibraryId`) and the MCP formatter (`name`, `libraryId`).
17
-
18
- ## Decision
19
-
20
- **Stop scraping the rendered page. Intercept the JSON the page already fetches, on a browser-service proxy, with the DOM scrape kept only as a fallback.**
21
-
22
- Concretely, for the Ad Library:
23
-
24
- 1. **Intercept the in-page GraphQL response.** The SPA issues `AdLibrarySearchPaginationQuery` (a `doc_id`-based POST to `/api/graphql/`) and Facebook returns the ads as JSON. We attach a Playwright `page.on('response')` listener before navigation and parse the JSON directly (`data.ad_library_main.search_results_connection.edges[].node.collated_results[]`). No request is forged — the page makes the request itself, so it carries Facebook's own session/cookies and looks like a legitimate first-party call. Implemented in `src/extractor/FacebookAdGraphql.ts`.
25
- 2. **Configured proxy by default.** Both FB routes now launch via `kernelLaunchOptsResidential()`, which uses the configured browser-service proxy without city/ZIP targeting by default. Location-targeted residential proxying remains an explicit escalation mode rather than the default.
26
- 3. **DOM scrape as fallback.** If no GraphQL response is captured (e.g., the query shape drifts), the existing regex DOM parse still runs on the already-loaded page.
27
- 4. **Field aliasing for client compatibility.** The API now returns both `pageName`/`name` and `sampleLibraryId`/`libraryId`, so even already-installed MCP clients render correctly without a package update; the formatter was also fixed to read the canonical fields.
28
-
29
- This is the **same pattern already proven by the YouTube transcription InnerTube tier** (`CaptionFetcher.ts` does `page.evaluate(fetch('/youtubei/v1/player'…))` from inside the loaded page). This ADR names it as the preferred default for this class of target.
30
-
31
- ### The reusable pattern
32
-
33
- > For a JS-heavy, anti-bot-protected site whose data arrives via an internal API call: load the page on a clean (residential) IP, capture or replay that internal request **from within the page's own session**, parse the JSON, and keep DOM scraping only as a fallback.
34
-
35
- Prefer **intercepting the response** (`page.on('response')`) over **replaying the request** when the page fires the call itself — it needs zero token/`doc_id`/`fb_dtsg` reconstruction and is the most robust form.
36
-
37
- ## Consequences
38
-
39
- **Positive**
40
- - Parses structured JSON, not DOM → immune to CSS/markup changes (the usual scraper breakage).
41
- - Faster — no waiting for the grid to render/scroll before reading.
42
- - Unambiguous — an explicit ad list or an error, killing the "0 results = blocked?" guesswork.
43
- - Lower block rate — an in-session first-party request plus a clean residential IP looks legitimate.
44
- - Verified live: `Nike` returned 10 distinct, correctly-named advertisers with library IDs (Nike, Jordan, Nordstrom Rack, eBay, Whatnot…), end-to-end through the MCP, no soft-block.
45
-
46
- **Negative / risks**
47
- - The GraphQL `doc_id` and response shape are Facebook-internal and **drift every few months**. Mitigation: the DOM fallback, plus the capture probe technique below to re-derive the shape in minutes.
48
- - Still not 100% — the Ad Library is adversarial; residential IPs can occasionally be challenged.
49
- - Captured `doc_id` (`24922295957467452` as of 2026-06-03) is intentionally *not* hardcoded — we match on the `fb_api_req_friendly_name` (`AdLibrarySearchPaginationQuery`) so a `doc_id` rotation alone doesn't break us.
50
-
51
- **Maintenance — re-capturing the request shape when it drifts**
52
- Run a Kernel session on a residential proxy, attach `page.on('request'|'response')`, navigate to the Ad Library search URL, and log GraphQL `fb_api_req_friendly_name` / `doc_id` / `variables` / response keys. (A throwaway `fb-capture.ts` probe was used during this work; recreate it from this description.)
53
-
54
- ## Alternatives considered
55
-
56
- - **Official Graph Ad Library API (`/ads_archive`).** Rejected: for the US it only returns political/issue ads; commercial ads (the use case) aren't exposed outside the EU.
57
- - **Replay the GraphQL request manually** (rebuild `lsd`/`jazoest`/`__dyn`/`doc_id`/variables). Rejected as the default: more brittle than intercepting the page's own response, though viable for pagination beyond what scrolling triggers.
58
- - **Just rotate proxies, keep DOM scraping.** Rejected: fixes the block but leaves the fragile, slow DOM parse and the "0 results" ambiguity.
@@ -1,62 +0,0 @@
1
- # ADR 0002: Hybrid Smart RAG retrieval for memory vaults
2
-
3
- - **Status:** Accepted
4
- - **Date:** 2026-07-16
5
- - **Deciders:** Andrew (operator), engineering
6
- - **Applies to:** MCP Memory retrieval, memory writes, vault search, and the default Skills vault
7
-
8
- ## Context
9
-
10
- Semantic similarity alone can miss exact tags, dates, metadata, and the note graph that gives an Obsidian vault its useful local context. Expanding every link from every semantic result has the opposite problem: it creates a large, noisy candidate set and can make structural proximity look like evidence of relevance. The connected AI also needs a strong habit of reusing the live tag vocabulary without turning one advisory tool-call order into a persisted write precondition.
11
-
12
- Skills add a second vault-specific constraint. A stored skill is not just one note: its internal links may point to reference material, templates, and executable support files, so the vault needs a stable folder contract.
13
-
14
- ## Decision
15
-
16
- **Use bounded hybrid Smart RAG for memory retrieval, and treat tag and graph context as evidence-bearing candidates rather than automatic conclusions.**
17
-
18
- The default retrieval flow is:
19
-
20
- 1. Form **2–4 focused query variants**, with **3** as the default.
21
- 2. Combine exact **tag, metadata, vault, and date** retrieval with semantic retrieval.
22
- 3. Fuse and deduplicate those channels into a total candidate pool of **50**.
23
- 4. Select the top **8** preliminary seeds.
24
- 5. Expand each seed to **depth 1**, considering at most **5** outgoing links or backlinks per seed.
25
- 6. Apply **Jina reranking** to the combined candidates and retain the top **30** for reasoning.
26
-
27
- Graph-expanded items remain candidates. They are not automatically relevant and must never become automatic note links; the AI adds an internal link only when the note contents support it.
28
-
29
- Before memory search, creation, or update, the MCP instructions strongly direct the AI to inspect the complete accessible tag inventory, reuse existing tags, and add a tag only when the inventory does not contain an adequate equivalent. This is an automatic behavioral directive, **not** a persisted call-order precondition that makes writes fail solely because a separate tag-list call was not recorded.
30
-
31
- The default **Skills** vault stores reusable skill packages. Every skill requires a `scripts/` folder and at least one of `references/` or `templates/`; a skill may have both. Skill notes use normal internal links to those supporting files.
32
-
33
- ## Consequences
34
-
35
- **Positive**
36
- - Exact filters preserve high-precision matches while multi-query semantic retrieval recovers differently worded concepts.
37
- - Bounded one-hop expansion recovers directly connected context without recursively walking the vault.
38
- - Jina reranking makes semantic, exact, and graph-derived candidates comparable before the AI reasons over them.
39
- - The 50-candidate and 30-result bounds provide predictable retrieval and context costs.
40
- - Tag reuse becomes the default AI behavior without coupling persisted data validity to orchestration traces.
41
- - Skills have a portable, inspectable support-file layout.
42
-
43
- **Negative / risks**
44
- - Multi-query retrieval, graph expansion, and reranking add latency and Jina dependency cost.
45
- - A fixed pool of 50 may under-recall unusually broad vaults; 30 reranked notes may still exceed the useful reading budget for narrow questions.
46
- - Link quality depends on note contents and graph maintenance; a bad link can introduce a weak candidate even though reranking limits its impact.
47
- - The tag-inventory-first behavior remains advisory at the AI layer and can be skipped by a non-compliant client.
48
- - Skills that do not need executable logic must still carry a `scripts/` folder to satisfy the vault contract.
49
-
50
- ## Alternatives considered
51
-
52
- - **One semantic query only.** Rejected because it misses exact structured constraints and alternate phrasings.
53
- - **Expand every link from every retrieved note.** Rejected because candidate growth and graph noise become unbounded; expansion is limited to the strongest eight seeds.
54
- - **Treat graph neighbors as automatically relevant or automatically link them.** Rejected because structural proximity is not evidence that a note answers the current request.
55
- - **Persist tag-list call order as a write precondition.** Rejected because it couples durable writes to client orchestration history; forceful instructions provide the intended behavior without rejecting otherwise valid writes.
56
-
57
- ## Revisit when
58
-
59
- - Retrieval evaluation shows that the 50/8/5/30 bounds materially harm recall or precision.
60
- - Jina reranking becomes unavailable, too costly, or is outperformed by a tested replacement.
61
- - Vault graphs become dense enough that one-hop expansion regularly adds mostly irrelevant candidates.
62
- - The Skills packaging contract adopts an external standard that makes `scripts/`, `references/`, or `templates/` incompatible.
@@ -1,22 +0,0 @@
1
- # ADR-0003: Waive unrecoverable scheduled model cost during repair
2
-
3
- **Status:** Accepted
4
-
5
- **Date:** 2026-07-22
6
-
7
- ## Context
8
-
9
- An agent-mode scheduled run completed provider/model work on 2026-07-14, but OpenRouter did not return a model-cost value. The 75-Credit base charge was already captured. Its settlement remained `cost_pending`, which correctly prevented an unverified model delta from being guessed but also blocked every later scheduled run for the account. The original authorization token and exact vendor cost are not recoverable.
10
-
11
- ## Decision
12
-
13
- For this historical repair, preserve the captured base charge, waive the unknown model-cost delta, and close the settlement and authorization as settled with an explicit `model_cost_unavailable_waived` audit reason. Do not invent or estimate a vendor cost.
14
-
15
- Future runs continue to require exact reported OpenRouter cost under the existing billing contract. A new missing-cost occurrence must retain enough durable provider evidence for reconciliation or be explicitly waived by an audited operator repair; it must never be silently estimated.
16
-
17
- ## Consequences
18
-
19
- - The account is no longer permanently blocked by an unrecoverable historical delta.
20
- - MCP Scraper absorbs the unknown model expense for this run.
21
- - The repair remains visible in billing-event metadata and preserves the original base-charge receipt.
22
- - A productized recovery path is still needed if missing provider cost recurs.
@@ -1,61 +0,0 @@
1
- # Own the August workshop registration path
2
-
3
- - Status: Accepted
4
- - Date: 2026-07-28
5
-
6
- ## Context
7
-
8
- The August MCP Scraper workshop needs a branded registration experience at
9
- `/aug-webinar`. Zoom can host the live room, but its registration page should
10
- not own the subscriber experience. Each attendee still needs an individual
11
- Zoom join link, while reminders need a reliable event-only audience.
12
-
13
- ## Decision
14
-
15
- The canonical MCP Scraper API will own registration state. The public form
16
- creates a bounded, idempotent registration record, adds the attendee to a
17
- dedicated Resend segment and optional event topic, and sends the confirmation
18
- through Resend with a standards-based calendar invitation attached.
19
-
20
- The event exists in the owner's Google Calendar and runs in Zoom. Its meeting
21
- link remains server-side and is delivered through the attached invitation and
22
- registered email address. Event registration does not opt a person into general
23
- marketing.
24
- Capacity is enforced by the registration ledger; overflow registrations are
25
- stored and confirmed as waitlisted.
26
-
27
- For the August 6, 2026 workshop, a successful registration immediately sends
28
- the attendee's confirmation and access details. The same transactional path
29
- schedules two event-only Resend reminders: 24 hours and one hour before the
30
- 9:00 AM Mountain Time start. Each scheduled email ID is stored independently
31
- so a partial retry does not knowingly schedule the same reminder twice.
32
-
33
- ## Alternatives considered
34
-
35
- - Use Zoom's hosted registration page or per-attendee registrant endpoint.
36
- Rejected because the hosted page breaks the branded experience and the live
37
- registrant action proved rate-limited during production setup.
38
- - Mutate the same Google Calendar event for every registration. Rejected after
39
- the provider's attendee mutation proved unreliable even while event creation
40
- and connection health succeeded.
41
- - Treat Resend contacts as the registration database. Rejected because contact
42
- membership is not an atomic seat ledger and should not contain the private
43
- join-link state.
44
-
45
- ## Consequences
46
-
47
- - Opening registration requires explicit Calendar event identity, Zoom meeting
48
- and server-only join URL, capacity, Resend segment, and date-label
49
- configuration.
50
- - Resend owns attendee confirmation, the attached calendar invitation, and
51
- reminders; Zoom remains the live room.
52
- - Confirmation and reminder systems can query one authoritative event ledger.
53
- - Resend owns reminder timing instead of requiring another cron or scheduler.
54
- - Failed downstream fulfillment is recorded for safe retry without exposing
55
- whether an email was already registered.
56
-
57
- ## Revisit when
58
-
59
- - The live host moves away from Zoom.
60
- - Workshops become recurring multi-host events that need a general event model.
61
- - Capacity or ticketing needs transactional payment and inventory semantics.
@@ -1,13 +0,0 @@
1
- # Architecture Decision Records
2
-
3
- Short, durable records of *why* a non-obvious technical decision was made — the context, the choice, and the consequences — so future readers (and future us) don't re-litigate or accidentally undo it.
4
-
5
- Write one when a decision is hard to reverse, surprising, or encodes a constraint that isn't visible in the code (an anti-bot workaround, a vendor limitation, a deliberate fallback). Don't write one for routine changes.
6
-
7
- ## Format
8
- `NNNN-kebab-title.md`, Nygard-style: **Status · Context · Decision · Consequences**. Status is `Proposed` → `Accepted` → (later) `Superseded by ADR-XXXX`. Number sequentially; never renumber.
9
-
10
- ## Index
11
- - [0001 — In-page API interception for anti-bot scraping (Facebook Ad Library)](./0001-in-page-graphql-interception-for-anti-bot-scraping.md)
12
- - [0002 — Hybrid Smart RAG retrieval for memory vaults](./0002-hybrid-smart-rag-vault-retrieval.md)
13
- - [0003 — Waive unrecoverable scheduled model cost during repair](./0003-waive-unrecoverable-scheduled-model-cost.md)
@@ -1,206 +0,0 @@
1
- # Final Tooling Spec — rename, server instructions, description optimization, catalog
2
-
3
- Execution blueprint consolidating the decisions reached: (A) server namespace rename
4
- `mcp-scraper → scraper`, (B) `*_workflow` rename of workflow-class tools, (C) a front-loaded
5
- **server-instructions block** carrying the cross-tool routing map, (D) a per-tool description
6
- optimization pass that moves routing *out* of descriptions, and (E) catalog integration.
7
-
8
- Grounded in: the SDK's `instructions?: string` option
9
- (`node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.d.ts:15`), verified tool registrations
10
- in `src/mcp/paa-mcp-server.ts` and `src/mcp/browser-agent-mcp-server.ts`, and the executor map in
11
- `src/mcp/http-mcp-tool-executor.ts`.
12
-
13
- Governing principle (from `docs/mcp-tool-design-guide.md`): **the model routes on names + the
14
- server-instructions block before it fetches any schema.** Therefore the cross-tool routing map must
15
- live in the instructions block, never solely in per-tool descriptions that may never be fetched.
16
-
17
- ---
18
-
19
- ## Part A — Server namespace rename: `mcp-scraper` → `scraper`
20
-
21
- The client prepends `mcp__`, so today every tool is `mcp__mcp-scraper__…` (stutter). Renaming the
22
- **server name** to `scraper` yields `mcp__scraper__…`.
23
-
24
- ### A.1 Exact edits — CORRECTED after verification
25
-
26
- > **The namespace `mcp__<key>__` comes from the CLIENT REGISTRATION KEY, not the McpServer `name`.**
27
- > Verified: `src/cli/agent-config.ts` registers the server with the key `mcp-scraper` via
28
- > `claude mcp add mcp-scraper …` and the Desktop config block `'mcp-scraper': {…}`. THAT key is what
29
- > Claude turns into `mcp__mcp-scraper__*`. Changing only `new McpServer({name})` does NOT change the
30
- > prefix. The full set of edits to actually rename the namespace:
31
-
32
- - `src/cli/agent-config.ts:54` — `['mcp', 'remove', 'mcp-scraper', …]` → `'scraper'`.
33
- - `src/cli/agent-config.ts:58` — `['mcp', 'add', 'mcp-scraper', …]` → `'scraper'`.
34
- - `src/cli/agent-config.ts:75` — Desktop config key `'mcp-scraper': {…}` → `'scraper': {…}`.
35
- - `scripts/build-mcpb.mjs:61` — `.mcpb` manifest server `name: 'mcp-scraper'` → `'scraper'` (Desktop
36
- extension registration key). Line 50 (`mcp-scraper-desktop-extension`) is the extension *package* id; leave it.
37
- - `src/mcp/paa-mcp-server.ts:152` and `bin/mcp-scraper-combined-stdio-server.ts:61` — the McpServer
38
- `name` is cosmetic self-description; update to `'scraper'` for consistency but it is NOT what drives the prefix.
39
- - **Tests:** `tests/unit/cli-agent-config.test.ts:32` asserts `'mcp-scraper'` — update.
40
- - **Migration is harder than a code change:** existing users already have the server registered under
41
- `mcp-scraper` in *their* client config. Renaming the key here only affects NEW installs; existing
42
- installs keep `mcp__mcp-scraper__*` until they `claude mcp remove mcp-scraper && claude mcp add scraper`
43
- (or reinstall the `.mcpb`). Document this explicitly; consider shipping a migration note/command.
44
-
45
- ### A.2 Leave alone (URL / artifact stability)
46
- - Bundle filenames/paths in `build-mcpb.mjs` (`mcp-scraper.mcpb`, `mcp-scraper-${version}.mcpb`,
47
- `build/mcpb/mcp-scraper`, `dist/bin/mcp-scraper-combined-stdio-server.js`) — **keep**. These are
48
- download URLs and build paths; changing them breaks existing download links for no namespace benefit.
49
- - Repo name, GitHub URLs — keep.
50
-
51
- ### A.3 Breaking-change handling (REQUIRED — do not skip)
52
- Renaming the server changes **every tool identifier** clients see → invalidates:
53
- - User permission allowlists referencing `mcp__mcp-scraper__*`.
54
- - Any saved agent configs / scripted tool calls by full name.
55
-
56
- Mitigations:
57
- - **Do it in a single release, called out in the changelog** as a breaking namespace change with a
58
- one-line migration ("re-allow tools; the namespace is now `mcp__scraper__…`").
59
- - Bump a **minor/major** version, not a patch.
60
- - Ship in the same release as Parts B–D so users re-onboard once, not repeatedly.
61
-
62
- ---
63
-
64
- ## Part B — Workflow-class tool renames (`*_workflow` suffix)
65
-
66
- Rule: a tool whose mental model is "run a multi-step workflow" carries `_workflow` so the keyword is in
67
- the name (matches a user saying "run the … workflow / research / directory"). Engine plumbing
68
- (`workflow_run/step/status/list/suggest/artifact_read`) is **not** renamed — it's infrastructure, not a
69
- user-named capability.
70
-
71
- ### B.1 Rename map
72
- | Current name | New name | File(s) |
73
- |---|---|---|
74
- | `rank_tracker_blueprint` | `rank_tracker_workflow` | `paa-mcp-server.ts:346`, schema, manifest, catalog |
75
- | `browser_capture_fanout` | `query_fanout_workflow` | `browser-agent-mcp-server.ts:791`, `browser-agent-tool-schemas.ts:189`, manifest, catalog |
76
- | `directory_workflow` | *(unchanged — already correct)* | — |
77
- | *(new, optional)* | `deep_research_workflow` | thin wrapper → `workflow_run` for the `deep-research` workflow; register in `paa-mcp-server.ts` |
78
-
79
- ### B.2 Per-tool change checklist (apply to each renamed tool)
80
- 1. **Registration** — `server.registerTool('<new_name>', { … })` (the string literal).
81
- 2. **Input schema** — rename the exported `…InputSchema` only if its name encodes the old tool name;
82
- the schema *shape* is unchanged.
83
- 3. **Manifest** — `src/mcp/mcp-tool-manifest.ts` `entry({ name: '<new_name>', … })`.
84
- 4. **Executor** — `src/mcp/http-mcp-tool-executor.ts` method name only if it mirrors the tool name; the
85
- **REST route string is unchanged** (rename is MCP-surface only, not the backend).
86
- 5. **Formatter** — `format<Tool>` function name optional; wiring in `registerTool` handler updated.
87
- 6. **Catalog** — update `mcp_name` in `docs/tool-catalog-spec.md` §3.2/§9 and the seed.
88
- 7. **Lint/manifest generators** — re-run `npm run mcp:manifest` and `npm run mcp:lint-tools` to
89
- regenerate `docs/mcp-tool-manifest.generated.*`.
90
-
91
- ### B.3 Optional backward-compat (recommended for 1–2 releases)
92
- Register the **old name as a hidden alias** pointing at the same handler, so in-flight callers don't
93
- hard-break: `server.registerTool('<old_name>', { ...sameConfig, annotations: { deprecated: true } }, handler)`.
94
- Remove after one release. If you'd rather hard-cut, fold it into the Part A breaking release.
95
-
96
- ---
97
-
98
- ## Part C — Server-instructions block (the pre-fetch routing map)
99
-
100
- This is the highest-leverage change: a short string loaded **at session start** (alongside tool names),
101
- so the model routes correctly *before* fetching any schema.
102
-
103
- ### C.1 New file — `src/mcp/server-instructions.ts`
104
- ```ts
105
- export const SERVER_INSTRUCTIONS = `
106
- This server scrapes and analyzes web, social, search, maps, and site data. Pick a tool by NAME using
107
- this routing map; load its schema before calling.
108
-
109
- ROUTING
110
- - One web page → extract_url. Whole site (crawl + SEO) → extract_site. Just the URL list → map_site_urls.
111
- - Web search (SERP) → search_serp. Full SERP + People-Also-Ask detail → harvest_paa.
112
- - Google Maps: find places → maps_search; one place deep-dive + reviews → maps_place_intel.
113
- - YouTube: find/list videos → youtube_harvest; transcribe one → youtube_transcribe.
114
- - Facebook: find ads → facebook_ad_search; transcribe an ad → facebook_ad_transcribe; transcribe a
115
- video → facebook_video_transcribe; page profile → facebook_page_intel.
116
- - Instagram: profile inventory → instagram_profile_content; one post/reel → instagram_media_download.
117
- - Workflows (multi-step): local directory → directory_workflow; rank blueprint → rank_tracker_workflow;
118
- AI-answer citation fan-out (AEO) → query_fanout_workflow; deep research → deep_research_workflow.
119
- - Anything without a dedicated tool (e.g. Reddit, arbitrary logged-in sites) → the browser_* agent
120
- (browser_open then navigate/read), and capture profiles via browser_profile_*.
121
-
122
- NOTES
123
- - Bulk/full-site crawls: call extract_site with rotateProxies:true for blocked/rate-limited sites; it
124
- discovers URLs and returns a saved folder/artifact, not the full content inline.
125
- - Tools that open a browser session or transcribe media cost credits and take longer — prefer the
126
- cheapest tool that answers the question (plain search/extract before browser agents).
127
- - Results may save large output to disk/artifact and return a summary + path; read the path for detail.
128
- `.trim()
129
- ```
130
- > Keep it **cross-tool only** — no per-tool parameter detail (that's the fetched schema's job). Use
131
- > neutral capability language; never name the underlying browser/proxy vendor (concealment policy).
132
-
133
- ### C.2 Wiring (both server construction sites)
134
- - `src/mcp/paa-mcp-server.ts:152`:
135
- `new McpServer({ name: 'scraper', version: PACKAGE_VERSION }, { instructions: SERVER_INSTRUCTIONS })`
136
- - `bin/mcp-scraper-combined-stdio-server.ts:61`: same second argument.
137
- - Import `SERVER_INSTRUCTIONS` from `../src/mcp/server-instructions.js` (and `./server-instructions.js`).
138
-
139
- ### C.3 The insight, enforced
140
- Per-tool descriptions must **NOT** be the only home for "which tool for which job." That guidance moves
141
- to `SERVER_INSTRUCTIONS` (always loaded). Per-tool descriptions keep only a **single** negative-space
142
- line for post-fetch tie-breaking (see Part D). This prevents the failure where the routing hint lives in
143
- a description the model never fetched.
144
-
145
- ---
146
-
147
- ## Part D — Tool-description optimization pass
148
-
149
- For every tool in `mcp-tool-schemas.ts` / `paa-mcp-server.ts` `registerTool` descriptions, rewrite to
150
- this shape (apply the `docs/mcp-tool-design-guide.md` checklist):
151
-
152
- 1. **Line 1 = when-to-use**, in the user's words. ("Transcribe one YouTube video to text…")
153
- 2. **Line 2 = side-effects/cost** if any. ("Opens a browser session; costs credits per minute.")
154
- 3. **Params**: each param's `.describe()` states intent + bounds + default, mirroring the Zod schema.
155
- 4. **One** negative-space line max. ("For a whole site use extract_site.") — the *broad* map lives in
156
- §C, so do not enumerate the full tool family here.
157
- 5. **Delete** redundant cross-tool prose and marketing. Shorter descriptions load faster and rank cleaner.
158
-
159
- Concrete trims to make now:
160
- - `extract_url` / `extract_site` / `map_site_urls`: each currently restates the others; cut to one
161
- negative-space line each (the trio map is in §C).
162
- - `search_serp` vs `harvest_paa`: both hit `/harvest/sync`; clarify in one line each that
163
- search_serp = organic results only, harvest_paa = + PAA/AI-Overview/SERP features.
164
- - Renamed workflow tools: rewrite line 1 to lead with the keyword now in the name.
165
-
166
- Re-run `npm run mcp:lint-tools` (the craft linter) after edits; fix any flagged tools.
167
-
168
- ---
169
-
170
- ## Part E — Catalog integration
171
-
172
- Reconcile `docs/tool-catalog-spec.md` with the renames, and ship the **static v1** (no DB) first:
173
-
174
- - Update catalog `mcp_name` values: `rank_tracker_blueprint→rank_tracker_workflow`,
175
- `browser_capture_fanout→query_fanout_workflow`; add `deep_research_workflow`.
176
- - Categories stay **platform-first** (YouTube / Facebook / Instagram / Google Search / Google Maps /
177
- Websites / Browser / Workflows), with **Reddit** served by the Browser category (no dedicated tool) —
178
- or add a `reddit_*` named wrapper later if AI routing for Reddit is wanted (per the design guide).
179
- - **v1 implementation = static**: `src/api/catalog-seed.ts` exports `CATALOG`; `GET /catalog` in
180
- `server.ts` returns it. Defer the 6-table DB schema (§2 of the catalog spec) to v2 (runtime editing).
181
- - Catalog params must mirror the live Zod schemas (field names/bounds) so the frontend posts valid bodies.
182
-
183
- ---
184
-
185
- ## Part F — Build order
186
- 1. **Part C** (server instructions) — highest leverage, lowest risk, non-breaking. Ship first.
187
- 2. **Part D** (description optimization) — non-breaking; pairs with C (move routing out → into C).
188
- 3. **Part B** (workflow renames) — with hidden aliases (B.3) so it's non-breaking.
189
- 4. **Part A** (server rename) — the one breaking change; batch into a single versioned release with a
190
- changelog migration note. Run after B/C/D so users re-onboard once.
191
- 5. **Part E** static catalog — independent; any time.
192
- 6. Regenerate `docs/mcp-tool-manifest.generated.*`; run `npm run mcp:lint-tools`; full test + live tests;
193
- then the release flow (3 version files, Vercel prod first, npm publish, rebuild `.mcpb`).
194
-
195
- ## Part G — Verification checklist
196
- - [ ] `grep -rn "mcp-scraper'" src bin scripts` → only intentional (bundle filenames), server `name` = `scraper`.
197
- - [ ] No tool identifier appears in two namespaces; `npm run mcp:manifest` regenerated.
198
- - [ ] `SERVER_INSTRUCTIONS` loads on `initialize` (verify via an MCP inspector / the live protocol test).
199
- - [ ] Every renamed tool: registration + manifest + catalog `mcp_name` updated; old name aliased or cut.
200
- - [ ] No per-tool description contains the full cross-tool map (only one negative-space line).
201
- - [ ] No vendor name (browser/proxy provider) in instructions or any description.
202
- - [ ] `npm run mcp:lint-tools` clean; `vitest run tests/unit tests/contract` green.
203
-
204
- ## Non-goals (this spec)
205
- DB-backed catalog (v2), the `reddit_*` / `deep_research_workflow` *implementations* beyond thin
206
- registration, per-plan tool gating, and the frontend rendering itself.
@@ -1,108 +0,0 @@
1
- # Hosted location data
2
-
3
- Production directory workflows and `location_markets` read versioned location snapshots from Turso. They do not read a server-local CSV and do not call Census during a customer request.
4
-
5
- Two datasets are active independently:
6
-
7
- - `us_census_places:<STATE>` stores every Census place and its 2020–2025 population estimates for one state.
8
- - `us_zip_groups` stores normalized city ZIP and county groups for all imported states.
9
-
10
- Each import receives an immutable dataset ID. An active pointer is switched only after every normalized row is durable, so readers see either the old complete version or the new complete version. Responses include both active dataset IDs, source URLs, and refresh timestamps.
11
-
12
- ## Required environment
13
-
14
- ```dotenv
15
- TURSO_DATABASE_URL=...
16
- TURSO_AUTH_TOKEN=...
17
- MCP_SCRAPER_LOCATION_ADMIN_KEY=...
18
- ```
19
-
20
- `MCP_SCRAPER_USZIPS_CSV_URL` may hold the default public HTTPS ZIP source. The import endpoint also accepts a source URL explicitly. `MCP_SCRAPER_USZIPS_CSV_PATH` remains available only for local tests and local CLI runs.
21
-
22
- ZIP imports are fail-closed: a request URL must exactly match `MCP_SCRAPER_USZIPS_CSV_URL` or one entry in `MCP_SCRAPER_LOCATION_IMPORT_SOURCE_ALLOWLIST`. Redirects are rejected, private-network targets are rejected, and response bytes are streamed through a 64 MiB in-flight limit. Census sync does not accept a URL; it constructs the trusted `www2.census.gov` state URL server-side.
23
-
24
- ## Publish the nationwide GeoNames source
25
-
26
- The production ZIP/county snapshot is derived from the official GeoNames US postal-code archive at `https://download.geonames.org/export/zip/US.zip`. GeoNames postal-code data is licensed under [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/); retain attribution to [GeoNames](https://www.geonames.org/) anywhere this derived dataset is redistributed.
27
-
28
- Publish a normalized, immutable public CSV to Vercel Blob:
29
-
30
- ```bash
31
- export LOCATION_DATA_READ_WRITE_TOKEN='...'
32
- npm run locations:publish-geonames
33
- ```
34
-
35
- `BLOB_READ_WRITE_TOKEN` is also accepted for projects that use their default
36
- public Blob store. Production uses a dedicated public store connected with the
37
- `LOCATION_DATA_` environment-variable prefix so this dataset cannot collide
38
- with private connected-data artifacts.
39
-
40
- The publisher downloads the direct HTTPS ZIP without following redirects, applies status, timeout, compressed-size, and extracted-size limits, extracts only `US.txt`, and converts the documented tab-separated fields to `state_abbr,zipcode,county,city`. It then runs the same production CSV parser and nationwide gate used by the import route: all 50 states plus DC and at least 25,000 distinct five-digit ZIPs must be present.
41
-
42
- The Blob pathname includes the normalized CSV's SHA-256 digest. Random suffixes and overwrites are disabled, so a published URL is an immutable snapshot. The command prints only non-secret counts, hashes, paths, and public URLs; copy its `url` value into `MCP_SCRAPER_USZIPS_CSV_URL`. A repeat publication of byte-identical data intentionally fails instead of replacing the existing content-addressed object.
43
-
44
- ## Seed and refresh
45
-
46
- Bootstrap the nationwide ZIP snapshot plus all 50 states and DC in one command:
47
-
48
- ```bash
49
- export MCP_SCRAPER_LOCATION_ADMIN_KEY='...'
50
- export MCP_SCRAPER_USZIPS_CSV_URL='https://...blob.vercel-storage.com/location-data/geonames/us/geonames-us-<sha256>.csv'
51
- npm run locations:sync
52
- ```
53
-
54
- The bootstrap uses three concurrent state requests by default, retries retryable HTTP/network failures up to three times, prints a per-state result and final failed-state list, and exits nonzero if any dataset failed. It never prints the admin key or ZIP source URL. Useful bounded refreshes include:
55
-
56
- ```bash
57
- # Refresh only selected Census snapshots and leave the active ZIP version unchanged.
58
- npm run locations:sync -- --skip-zips --states=TN,CO,CA
59
-
60
- # Point the operator at a preview API and lower concurrency.
61
- npm run locations:sync -- --base-url=https://preview.example.com --concurrency=2
62
- ```
63
-
64
- The individual admin calls below remain available for targeted recovery.
65
-
66
- Sync one state's trusted Census snapshot:
67
-
68
- ```bash
69
- curl -sS https://mcpscraper.dev/locations/admin/sync-census \
70
- -H "x-location-admin-key: $MCP_SCRAPER_LOCATION_ADMIN_KEY" \
71
- -H 'content-type: application/json' \
72
- --data '{"state":"TN"}'
73
- ```
74
-
75
- Import the nationwide ZIP/county CSV from a public HTTPS object:
76
-
77
- ```bash
78
- curl -sS https://mcpscraper.dev/locations/admin/import \
79
- -H "x-location-admin-key: $MCP_SCRAPER_LOCATION_ADMIN_KEY" \
80
- -H 'content-type: application/json' \
81
- --data '{"sourceUrl":"https://data.example.com/uszips.csv"}'
82
- ```
83
-
84
- The ZIP import accepts the common header aliases `state_abbr|state|state_id`, `zipcode|zip|zip_code`, `city|primary_city`, and `county|county_name`. A replacement must cover all 50 states plus DC and contain at least 25,000 distinct five-digit ZIPs. Unsupported state codes and malformed rows are counted as rejected rows in the dataset record. A partial or undersized snapshot is rejected without changing the active pointer. Re-importing byte-identical active data is idempotent.
85
-
86
- Census imports verify that every place row's `STATE` FIPS matches the requested state before activation. `/locations/status` reports missing population states, required state count, ZIP coverage, rejected rows, and readiness; `available` becomes true only when all 51 population snapshots and a nationwide ZIP snapshot are active.
87
-
88
- ## Read and verify
89
-
90
- Dataset readiness requires an API key:
91
-
92
- ```bash
93
- curl -sS https://mcpscraper.dev/locations/status \
94
- -H "x-api-key: $MCP_SCRAPER_API_KEY"
95
- ```
96
-
97
- Query hosted markets directly:
98
-
99
- ```bash
100
- curl -sS 'https://mcpscraper.dev/locations/markets?state=TN&populationYear=2025&minPopulation=100000&maxResults=25&includeZipGroups=true' \
101
- -H "x-api-key: $MCP_SCRAPER_API_KEY"
102
- ```
103
-
104
- The same read is exposed to agents as `location_markets`. Filters are applied against the hosted population and ZIP rows before the population-sorted result limit. If a required state snapshot or ZIP snapshot has not been activated, the API returns a typed `503` instead of silently falling back to a local path or a live Census fetch.
105
-
106
- ## Operational follow-up
107
-
108
- The active-pointer design prevents partial data from becoming visible and preserves old dataset rows for rollback. A future maintenance pass should add an explicit cross-process import lease, an admin rollback/history endpoint, and retention cleanup for superseded snapshot rows. These do not weaken current read isolation or coverage gates, but they will make concurrent operator refreshes and long-term storage management more ergonomic.
@@ -1,80 +0,0 @@
1
- # Kernel / proxy retry — future enhancement notes
2
-
3
- **Status:** ideas only — NOT implemented, NO code from here is in the tree.
4
- **Origin:** salvaged from an abandoned automated branch `codex/kernel-proxy-retry-cleanup`
5
- (a stale `~/Projects/mcp-scraper` clone, since deleted). That branch was evaluated against the
6
- current Desktop code and found **superseded** — Desktop independently built the same retry +
7
- error-classification machinery and went further (fresh disposable proxy per attempt, state
8
- escalation, rollout to maps/Facebook/site-crawl). **Do not `git merge` that branch** — it
9
- diverged onto an older base and a merge would regress the current proxy layer.
10
-
11
- The two items below are the *only* things Desktop genuinely lacked. They are documented here as
12
- "if we want to build X, here's how" — to be rebuilt from scratch onto current code, never pasted.
13
-
14
- ---
15
-
16
- ## A. SERP-timeout salvage (best candidate)
17
-
18
- **Problem it solves:** when a Google page load hits a navigation timeout (e.g.
19
- `domcontentloaded` slow) but the page is *actually* a usable `/search` results page with no
20
- CAPTCHA, the current code throws and triggers a wasted retry. We can instead detect "good page,
21
- just slow" and continue.
22
-
23
- **Where:** `src/driver/BrowserDriver.ts`, in the `goto` flow.
24
-
25
- **How to build:**
26
- 1. Add a predicate `currentGoogleSerpIsUsable(page)` — returns true when the current URL is a
27
- Google `/search` page, results containers are present, and no CAPTCHA/consent interstitial is
28
- detected.
29
- 2. In `goto`, catch the navigation-timeout case (an `isNavigationTimeout(err)` guard). Before
30
- rethrowing, if `currentGoogleSerpIsUsable(page)` is true, log a "salvaged slow SERP" telemetry
31
- line and proceed instead of throwing.
32
- 3. Keep the throw for every other timeout (non-SERP pages, CAPTCHA present, empty page).
33
-
34
- **Tools it improves:** `search_serp`, `harvest_paa`, `capture_serp_*` — fewer discarded captures
35
- and fewer retry credits burned on pages that were fine.
36
-
37
- **Watch out for:** only salvage when results are genuinely present — a timed-out blank/consent
38
- page must still fail so the normal retry/rotation kicks in.
39
-
40
- ---
41
-
42
- ## B. Opt-in proxy connectivity-check bypass (situational)
43
-
44
- **Problem it solves:** the browser-service residential proxies run an internal connectivity ping
45
- (an S3-style reachability check) before use. When that ping falsely fails, proxy creation is
46
- rejected even though the proxy would work — surfacing as `proxy_unavailable`.
47
-
48
- **Where:** `src/kernel-proxy-resolver.ts`, in the residential-proxy creation path.
49
-
50
- **How to build:**
51
- 1. Gate behind an env flag `KERNEL_PROXY_BYPASS_CONNECTIVITY_CHECK` (default off).
52
- 2. When set, pass a `bypass_hosts` / skip-connectivity-check option into the proxy-create call so
53
- the internal ping is skipped.
54
- 3. Add a `shouldRetryWithoutBypass` fallback: if a bypassed proxy then fails for real, retry once
55
- with the check re-enabled so we don't mask a genuinely dead proxy.
56
-
57
- **Build ONLY if** we actually observe proxy-create failures from the connectivity ping in
58
- production. Otherwise leave it — it's complexity with no payoff until that failure mode is real.
59
-
60
- ---
61
-
62
- ## C. Minor niceties (low priority, build only if convenient)
63
-
64
- - **Per-attempt hard timeout:** a fresh `AbortController` per retry attempt with its own
65
- `perAttemptTimeoutMs` (~120s), chained to the parent signal. Desktop bounds attempt *count* but
66
- has no per-attempt hard cap.
67
- - **Typed `KernelProxyUnavailableError`:** Desktop detects proxy-unavailable via regex on the
68
- message; a dedicated error class would be cleaner to branch on.
69
- - **Request-level retry knobs:** expose `maxAttempts`, `retryOnCaptcha`, `retryOnProxyFailure`,
70
- `allowLocationFallback` as request schema fields instead of hardcoded constants, for per-call
71
- tuning / debugging.
72
-
73
- ---
74
-
75
- ## What NOT to take
76
-
77
- Everything else from the codex branch — the retry loop, the `proxy_tunnel_failed` /
78
- `proxy_unavailable` / `location_mismatch` error classification, `LocationMismatchError`, the
79
- 503/retryable mappings — is **already in Desktop** (`src/harvest.ts`, `src/errors.ts`,
80
- `src/api/harvest-problems.ts`) and in a more advanced form. Do not reintroduce it.