mcp-scraper 0.3.25 → 0.3.27

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 (42) hide show
  1. package/dist/bin/api-server.cjs +2439 -346
  2. package/dist/bin/api-server.cjs.map +1 -1
  3. package/dist/bin/api-server.js +2 -2
  4. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  5. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  6. package/dist/bin/mcp-scraper-cli.js +1 -1
  7. package/dist/bin/mcp-scraper-install.cjs +2 -2
  8. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  9. package/dist/bin/mcp-scraper-install.js +2 -2
  10. package/dist/bin/mcp-stdio-server.cjs +88 -33
  11. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  12. package/dist/bin/mcp-stdio-server.js +10 -1357
  13. package/dist/bin/mcp-stdio-server.js.map +1 -1
  14. package/dist/bin/paa-harvest.cjs +1 -1
  15. package/dist/bin/paa-harvest.cjs.map +1 -1
  16. package/dist/bin/paa-harvest.js +1 -1
  17. package/dist/{chunk-FE2WC4JR.js → chunk-6KHGYJIT.js} +1431 -35
  18. package/dist/chunk-6KHGYJIT.js.map +1 -0
  19. package/dist/chunk-DYV72F6A.js +7 -0
  20. package/dist/chunk-DYV72F6A.js.map +1 -0
  21. package/dist/{chunk-WYCER2HW.js → chunk-H74L743B.js} +12 -11
  22. package/dist/chunk-H74L743B.js.map +1 -0
  23. package/dist/{chunk-G7PQ64LM.js → chunk-QSSH4RCX.js} +2 -2
  24. package/dist/chunk-QSSH4RCX.js.map +1 -0
  25. package/dist/{chunk-DOBQN3EY.js → chunk-T3VZD2I4.js} +2 -2
  26. package/dist/{chunk-DOBQN3EY.js.map → chunk-T3VZD2I4.js.map} +1 -1
  27. package/dist/index.cjs +1 -1
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.js +1 -1
  30. package/dist/{server-RRCKVBHG.js → server-KUNOK47B.js} +695 -29
  31. package/dist/server-KUNOK47B.js.map +1 -0
  32. package/dist/{worker-UC6D2756.js → worker-NV4GTIRG.js} +3 -3
  33. package/docs/spec-kernel-computer-controls.md +166 -0
  34. package/docs/spec-server-instructions.md +94 -0
  35. package/package.json +1 -1
  36. package/dist/chunk-FE2WC4JR.js.map +0 -1
  37. package/dist/chunk-G7PQ64LM.js.map +0 -1
  38. package/dist/chunk-WPHAMNT7.js +0 -7
  39. package/dist/chunk-WPHAMNT7.js.map +0 -1
  40. package/dist/chunk-WYCER2HW.js.map +0 -1
  41. package/dist/server-RRCKVBHG.js.map +0 -1
  42. /package/dist/{worker-UC6D2756.js.map → worker-NV4GTIRG.js.map} +0 -0
@@ -4,10 +4,10 @@ import {
4
4
  createHarvestAttemptRecorder,
5
5
  harvestProblemResponse,
6
6
  serializeHarvestProblem
7
- } from "./chunk-WYCER2HW.js";
7
+ } from "./chunk-H74L743B.js";
8
8
  import {
9
9
  harvest
10
- } from "./chunk-G7PQ64LM.js";
10
+ } from "./chunk-QSSH4RCX.js";
11
11
  import {
12
12
  browserServiceApiKey
13
13
  } from "./chunk-3PRO376E.js";
@@ -125,4 +125,4 @@ export {
125
125
  startWorker,
126
126
  tickOnce
127
127
  };
128
- //# sourceMappingURL=worker-UC6D2756.js.map
128
+ //# sourceMappingURL=worker-NV4GTIRG.js.map
@@ -0,0 +1,166 @@
1
+ # Spec — Tighten Kernel Computer Controls usage (human-like input on the hosted browser)
2
+
3
+ ## Correction to the original premise
4
+ An earlier assessment claimed "we drive the Kernel browser over CDP with raw Playwright — we never use the Computer Controls API." **That was wrong** (a bad grep — searched raw HTTP paths + `page.mouse`, missed the SDK method names). Ground truth in `src/services/browser-agent/browser-agent-service.ts`:
5
+
6
+ | Action | Implementation today | API used |
7
+ |---|---|---|
8
+ | screenshot | `k.browsers.computer.captureScreenshot` | ✅ Computer Controls |
9
+ | click | `k.browsers.computer.clickMouse` | ✅ Computer Controls |
10
+ | type | `k.browsers.computer.typeText` | ✅ Computer Controls |
11
+ | scroll | `k.browsers.computer.scroll` | ✅ Computer Controls |
12
+ | press | `k.browsers.computer.pressKey` | ✅ Computer Controls |
13
+ | moveMouse | `k.browsers.computer.moveMouse({smooth:true})` | ✅ defined… but never called |
14
+ | goto / read / locate / captureFanout | `playwrightChromium.connectOverCDP` | Playwright/CDP (DOM access — required, kept) |
15
+
16
+ So the interactive agent's **input + screenshot already follow Kernel's API**. The DOM-read path uses Playwright-over-CDP because Computer Controls cannot read the DOM / element coordinates — that path stays (and is the same free Playwright code used off-Kernel).
17
+
18
+ ## Dual-path principle (the rule, stated)
19
+ - **On Kernel (hosted browser):** drive *input* (move/click/type/scroll/press/screenshot) through **Computer Controls** (`k.browsers.computer.*`) — native OS input, no CDP input fingerprint, bezier curves, inter-keystroke delays. Read the DOM via Playwright-over-CDP (no Computer Controls equivalent).
20
+ - **Off Kernel (free local/HTTP path, `BrowserDriver` non-hosted, future local mode):** keep Playwright. It's free and there's no Computer Controls layer to use.
21
+
22
+ This spec changes only the Kernel input path. It does not touch Playwright scraping (`BrowserDriver`, reddit/serp/maps/fb deterministic tools) — their bottleneck is IP/challenge, not input cadence, and they barely click.
23
+
24
+ ## The two real gaps (verified)
25
+ 1. **Clicks teleport.** `click()` calls `clickMouse(x,y)` with no preceding move. `moveMouse({smooth:true})` exists (bezier) but is **called from nowhere** (`grep` confirms one definition, zero call sites). So every click is an instant cursor jump to the target — a behavioral bot signal Kernel's docs flag ("identical cursor paths, rapid mouse movements"). The bezier capability is built and unused.
26
+ 2. **Typing has no human cadence by default.** `typeText(id, text, delayMs?)` only passes `delay` if the *caller* supplies `body.delay` (`browser-agent-routes.ts:486`). The model never sends it → `delay` is `undefined` → instant uniform typing ("uniform typing speeds" — another flagged signal).
27
+
28
+ ## Changes
29
+
30
+ ### 1. `src/services/browser-agent/browser-agent-service.ts`
31
+
32
+ **Add a constant + jitter helper near the top of the action section (after `client()`):**
33
+ ```ts
34
+ const HUMAN_TYPE_DELAY_MS = 55
35
+ function jitter(base: number, spread: number): number {
36
+ return Math.max(1, Math.round(base + (Math.random() * 2 - 1) * spread))
37
+ }
38
+ ```
39
+
40
+ **`click()` — move (bezier) to the target before clicking.** Replace the body:
41
+ ```ts
42
+ export async function click(
43
+ runtimeSessionId: string,
44
+ x: number,
45
+ y: number,
46
+ opts: { button?: 'left' | 'right' | 'middle'; numClicks?: number } = {},
47
+ ): Promise<void> {
48
+ const k = client()
49
+ await k.browsers.computer.moveMouse(runtimeSessionId, { x, y, smooth: true })
50
+ await k.browsers.computer.clickMouse(runtimeSessionId, {
51
+ x,
52
+ y,
53
+ button: opts.button ?? 'left',
54
+ ...(opts.numClicks ? { num_clicks: opts.numClicks } : {}),
55
+ })
56
+ }
57
+ ```
58
+ (Optional optimization — collapse the two round-trips into one with `computer.batch` if the installed SDK exposes it: `await k.browsers.computer.batch(runtimeSessionId, { actions: [{ type: 'move_mouse', x, y, smooth: true }, { type: 'click_mouse', x, y, button: opts.button ?? 'left', num_clicks: opts.numClicks ?? 1 }] })`. Verify the SDK method/shape before using; if unavailable, keep the two sequential calls above.)
59
+
60
+ **`typeText()` — default a jittered human delay when the caller gives none:**
61
+ ```ts
62
+ export async function typeText(runtimeSessionId: string, text: string, delayMs?: number): Promise<void> {
63
+ const k = client()
64
+ const delay = typeof delayMs === 'number' ? delayMs : jitter(HUMAN_TYPE_DELAY_MS, 30)
65
+ await k.browsers.computer.typeText(runtimeSessionId, { text, delay })
66
+ }
67
+ ```
68
+
69
+ No route changes required — `browser-agent-routes.ts` already forwards `x/y`, `button`, `num_clicks`, and an optional `delay`; the new behavior is purely in the service defaults. The MCP tool schemas/descriptions need no change (the human-like behavior is automatic; callers still just pass x/y/text).
70
+
71
+ ### 2. No change to
72
+ - `goto` / `readPage` / `locatePageTargets` / `captureFanout` — DOM access over CDP is required and free-path-shared. Leave as-is.
73
+ - `BrowserDriver` and all deterministic scrapers (reddit, serp, maps, fb, instagram, youtube) — Playwright stays.
74
+ - The free/local browser path — Playwright stays.
75
+
76
+ ## Verification
77
+ 1. `grep -n "moveMouse" src/services/browser-agent/browser-agent-service.ts` → now **2** hits (definition + the new call in `click`).
78
+ 2. `npm run build` clean.
79
+ 3. Deploy prod; open a session and drive it:
80
+ - `POST /agent/sessions {url:"https://example.com"}` → `POST /agent/sessions/:id/screenshot` (note an element's x/y) → `POST /agent/sessions/:id/click {x,y}` → screenshot again: the click lands (move+click path works).
81
+ - `POST /agent/sessions/:id/type {text:"hello world"}` (no `delay`) succeeds — typing now carries the default jittered cadence server-side.
82
+ - `DELETE /agent/sessions/:id`.
83
+ 4. Behavioral check on a detection-sensitive target (manual): drive a known bot-walled login form; clicks should approach via curve and typing should be paced (observable in the live-view / replay).
84
+
85
+ ## Release
86
+ - One file changed (`browser-agent-service.ts`). The behavior ships server-side → live on Vercel deploy; **no npm republish required** (the stdio package is a thin client; tool schemas/descriptions are unchanged).
87
+ - Commit on `main`: `feat(browser): human-like input on Kernel — bezier move-before-click + default typing cadence`.
88
+
89
+ ---
90
+
91
+ # Phase 2 — Move the DOM-read path to the Playwright Execution API (in-VM + Patchright)
92
+
93
+ ## Why (revised from "defer")
94
+ The read path (`goto`, `readPage`, `locatePageTargets`, `captureFanout`) uses `playwrightChromium.connectOverCDP(cdpWsUrl)` from our serverless function — **outside** the Kernel VM. Kernel's recommended-practices table explicitly says *"Prefer our Playwright Execution API or Computer Controls API over self-hosted Playwright/Puppeteer,"* and the bot-detection guide states the Execution API *"runs in the same VM as the browser, ensuring headers, user-agent strings, and environment match. Kernel automatically applies Patchright to remove automation fingerprints, including headless indicators."* So external CDP is the residual detection signal, and this is the documented fix — not optional polish.
95
+
96
+ ## The API
97
+ `POST /browsers/{id}/playwright/execute` — body `{ code: string, timeout_sec?: 1..300 }`. The `code` runs **in-VM** inside a function with `page`, `context`, `browser` in scope; `return` a value. Response `{ success, result, error, stdout, stderr }`. SDK call (verify exact name against installed `@onkernel/sdk`): `k.browsers.playwright.execute(runtimeSessionId, { code, timeout_sec })`.
98
+
99
+ ## Mechanic
100
+ Our read functions already do their work via `page.evaluate(FN)` over a `connectOverCDP` page. The migration is **mechanical**: take the existing in-page extraction JS (the function bodies passed to `page.evaluate`) and ship them as the `code` string to `execute`, returning the same shape — but now running in-VM with Patchright instead of over external CDP.
101
+
102
+ ### `src/services/browser-agent/browser-agent-service.ts`
103
+ Add a helper:
104
+ ```ts
105
+ async function runInVm<T>(runtimeSessionId: string, code: string, timeoutSec = 30): Promise<T> {
106
+ const k = client()
107
+ const res = await k.browsers.playwright.execute(runtimeSessionId, { code, timeout_sec: timeoutSec })
108
+ if (!res.success) throw new Error(res.error || 'playwright/execute failed')
109
+ return res.result as T
110
+ }
111
+ ```
112
+ Then rewrite each read function to call `runInVm` with its existing extraction logic as a code string instead of `connectOverCDP`:
113
+ - **`goto(runtimeSessionId, url)`** → `code`: `` await page.goto(${JSON.stringify(url)}, { waitUntil: 'domcontentloaded', timeout: 45000 }); return { url: page.url(), title: await page.title() }; `` (signature changes from `cdpWsUrl` to `runtimeSessionId`).
114
+ - **`readPage`** → move the current element/text-collection `page.evaluate` body into the `code` string; `return` the same `{ url, title, text, elements }` object.
115
+ - **`locatePageTargets`** → same: the selector/text-bounds logic becomes the `code` body, `return` the bounds array.
116
+ - **`captureFanout`** → port its capture logic; if it relies on response listeners over CDP, evaluate whether it can run in-VM or must stay on CDP (the fan-out hook listens to network events — this one may legitimately stay on CDP; verify before migrating).
117
+
118
+ ### Routes (`src/api/browser-agent-routes.ts`)
119
+ The read/locate/goto routes currently pass `row.cdp_ws_url` to the service. Change those call sites to pass `row.runtime_session_id`. No other route logic changes.
120
+
121
+ ### Keep CDP only where required
122
+ If `captureFanout` needs live network-event listeners (CDP-only), leave it on `connectOverCDP`. Everything else moves in-VM.
123
+
124
+ ## Dual-path still holds
125
+ - **On Kernel:** input → Computer Controls (Phase 1); DOM read → Playwright Execution API (Phase 2). Zero external CDP except any network-listener edge case.
126
+ - **Off Kernel (free/local):** unchanged local Playwright.
127
+
128
+ ## Verification (Phase 2)
129
+ 1. `grep -c connectOverCDP src/services/browser-agent/browser-agent-service.ts` → drops to 0 (or 1 if captureFanout stays).
130
+ 2. Build clean.
131
+ 3. Drive a session: `goto` → `read` → `locate` return the same shapes as before (regression check against current output).
132
+ 4. On a detection-sensitive page, confirm reads no longer attach an external CDP client (the session shows only in-VM execution + Computer Controls input).
133
+
134
+ ## Release (Phase 2)
135
+ - Same file (+ route call-site edits). Server-side → ships on deploy; no npm republish.
136
+ - Separate commit from Phase 1 so each is independently revertible: `feat(browser): read DOM via Kernel Playwright Execution API (in-VM, Patchright) instead of external CDP`.
137
+
138
+ ## Sequencing
139
+ Ship **Phase 1 first** (tiny, pure win), validate, then **Phase 2** (mechanical but touches 3–4 functions + their routes; needs a regression pass on read/locate output shape). Don't bundle — Phase 2 has real surface area.
140
+
141
+ ## Still out of scope
142
+ - Migrating the **deterministic scrapers** (`BrowserDriver` reddit/serp/maps/fb) from `connectOverCDP` to Playwright Execution API. Same benefit would apply, but their bottleneck was IP/challenge (solved) and they're a larger blast radius. Flag as Phase 3 if detection on those tools resurfaces.
143
+ - **Batch beyond move+click** — only if a concrete need appears.
144
+
145
+ ---
146
+
147
+ # Other Kernel APIs — assessment + findings (2026-06-30)
148
+
149
+ Reviewed the full Kernel API reference (browsers, playwright, computer-controls, filesystem, processes). Findings:
150
+
151
+ ## TESTED & REJECTED — browser curl for Reddit
152
+ **Hypothesis:** `client.browsers.curl(id, {url})` (`POST /browsers/{id}/curl`) sends an HTTP request through Chrome's stack (TLS fingerprint + cookies + proxy), so curling `old.reddit/...json` through a residential-proxied browser would return the comment tree cheaply, no render.
153
+ **Result: FAILED.** Both residential-proxied and no-proxy curl returned **HTTP 403 / Reddit block page** (no JSON). Reason: curl does NOT execute JavaScript, and Reddit's wall is a **JS challenge** that only a *rendered* browser solves. `reddit_thread` therefore MUST stay headful-render (the render is what beats the challenge; the ~$0.0009 cost is justified). **Do not re-attempt curl for Reddit.**
154
+ **Curl is still valid** for genuine JSON/REST APIs with no JS challenge (inherits the browser's authenticated session + residential IP). Keep as a back-pocket tier; not a current build item.
155
+
156
+ ## REJECTED — do NOT run ffmpeg/media in the Kernel VM (cost-wrong)
157
+ Initially flagged process-exec/filesystem for in-VM ffmpeg muxing. **Retracted — it's the wrong place cost-wise.** A Kernel browser VM bills $0.0001333/s (headful); muxing is pure CPU work unrelated to browsing, so running it there is the most expensive option. And we don't need it for product value:
158
+ - **Transcription already runs outside Kernel** via fal.ai wizper, which takes an `audio_url` and fetches it itself (`media-transcription.ts`) — no download, no ffmpeg, no Kernel. Cheap and correct.
159
+ - **Muxing** (`VideoMixer.ts`) is an optional convenience that gracefully degrades to returning separate track URLs when ffmpeg is absent.
160
+ - The `instagram_media` ENOENT was the **Vercel serverless read-only filesystem** (`mkdir ~/Downloads/...`), NOT a Kernel gap. Fix = write to `/tmp` or return track URLs; if muxing must be reliable, run ffmpeg on a cheap non-serverless worker — never in Kernel.
161
+ **Conclusion: keep all media transcode/transcription OUTSIDE Kernel.** Process-exec/filesystem APIs have no compelling MCP Scraper use today.
162
+
163
+ ## LOW / skip unless needed
164
+ - computer-controls extras: `drag` (slider captchas), `clipboard` read/write, `set_cursor`, `get_cursor_position` — situational agent polish.
165
+ - browsers: create/get/list/delete/update (already used), ad-hoc extension upload (niche), load-extensions.
166
+ - filesystem watch/stream, process async/PTY/signals — only for long-running in-VM jobs.
@@ -0,0 +1,94 @@
1
+ # Spec — server-instructions.ts routing-map rewrite
2
+
3
+ Implements the approved improvements from the MCP-design audit + reviewer feedback. **One file changes:** `src/mcp/server-instructions.ts` (the `SERVER_INSTRUCTIONS` string — the only content loaded into the model's context before it fetches any tool schema). No other source files. Requires a build + Vercel deploy (remote-MCP gets it immediately) + npm republish (the string ships in the stdio package).
4
+
5
+ ## Approved improvements (the full list)
6
+ 1. **Add `reddit_thread` to the map; stop routing Reddit to the browser.** Current line 15 says "Anything without a dedicated tool (e.g. Reddit…) -> browser_* agent" — **stale**, we shipped `reddit_thread` in 0.3.25. A model reading the current map will drive a browser for Reddit instead of calling the dedicated tool.
7
+ 2. **Make discovery→action ID seams explicit and schema-accurate** (reviewer #1). Encode, per chain, what the discovery tool *returns* and what the action tool *takes* — using the real params, not inferred ones.
8
+ 3. **Workflow triggers + boundary** (reviewer #2): plain-language "when to reach for a workflow vs hand-chaining," and state `query_fanout_workflow`'s browser prerequisite.
9
+ 4. **Reorder the no-dedicated-tool branch to check login FIRST** (reviewer #3), so a logged-in site isn't short-circuited into `browser_open` without `browser_profile_connect`.
10
+ 5. **Keep** the `rotateProxies` NOTE (reviewer #4 — defensible cross-cutting hint).
11
+
12
+ Not in this spec (explicitly out): annotations (audited → correct as-is, not a bug), server namespace rename (breaking, deferred decision), tool-count tiering (soft, not needed). `deep_research_workflow` removal already shipped.
13
+
14
+ ## Verified schema facts backing each seam (source of truth)
15
+ | Discovery tool | Returns (output schema) | Action tool | Takes (input schema) | Seam |
16
+ |---|---|---|---|---|
17
+ | `map_site_urls` / `search_serp` | `urls` / `organicResults[].url` | `extract_url` | `url` | url → url |
18
+ | `search_serp` | `organicResults[].url` (incl. reddit.com) | `reddit_thread` | `url` | reddit url → reddit_thread |
19
+ | `maps_search` | `results[].{name, placeUrl, cid}` | `maps_place_intel` | **`businessName` + `location`** (NOT an id) | name → name; **independent, callable directly** |
20
+ | `youtube_harvest` | `videos[].videoId` | `youtube_transcribe` | `videoId` OR `url` | videoId → videoId |
21
+ | `facebook_ad_search` | `advertisers[].{pageId, libraryId}` | `facebook_page_intel` | `pageId` / `libraryId` / `query` | id → id |
22
+ | `facebook_page_intel` | `ads[].videoUrl` | `facebook_ad_transcribe` | `videoUrl` | videoUrl → videoUrl |
23
+ | (organic FB url) | — | `facebook_video_transcribe` | `url` (FB reel/video/post) | url direct |
24
+ | `instagram_profile_content` | `post`/`reel`/`tv` url arrays | `instagram_media_download` | `url` | url → url |
25
+ | (must precede) `browser_open` on chatgpt.com/claude.ai | — | `query_fanout_workflow` | runs against the open AI session | browser-session prerequisite |
26
+
27
+ `facebook_ad_transcribe` does NOT consume anything from `facebook_ad_search` — the videoUrl comes from `facebook_page_intel`. `maps_place_intel` does NOT consume a `place_id` — it takes a business name + location.
28
+
29
+ ## Exact change
30
+ Replace the entire `SERVER_INSTRUCTIONS` template literal in `src/mcp/server-instructions.ts` (lines 1–29) with:
31
+
32
+ ```ts
33
+ export const SERVER_INSTRUCTIONS = `
34
+ This server scrapes and analyzes web, social, search, maps, and site data. Pick a tool by NAME using
35
+ this routing map, then load its schema before calling. Where a tool needs a value another tool
36
+ produces, the seam is noted so you can chain them.
37
+
38
+ ROUTING
39
+ - One web page -> extract_url (takes a url). Whole site (crawl + SEO report) -> extract_site (takes a
40
+ url). Just the URL list -> map_site_urls (takes a url). map_site_urls and search_serp return urls you
41
+ can feed straight into extract_url.
42
+ - Web search (organic SERP) -> search_serp. Full SERP + People-Also-Ask / AI-Overview detail ->
43
+ harvest_paa. search_serp returns organicResults[].url (often including reddit.com threads) to feed
44
+ extract_url or reddit_thread.
45
+ - Google Maps: find places -> maps_search (returns results[] with name, placeUrl, cid). One place
46
+ deep-dive + reviews -> maps_place_intel (takes businessName + location, NOT an id — call it directly
47
+ with a name from a maps_search result or from the user).
48
+ - YouTube: find or list videos -> youtube_harvest (returns videos[].videoId). Transcribe one ->
49
+ youtube_transcribe (takes a videoId from youtube_harvest, or a url).
50
+ - Facebook: find advertisers -> facebook_ad_search (returns advertisers[] with pageId, libraryId). Get
51
+ one advertiser's ads -> facebook_page_intel (takes pageId, libraryId, or query; returns ads[].videoUrl).
52
+ Transcribe an ad video -> facebook_ad_transcribe (takes a videoUrl from facebook_page_intel).
53
+ Transcribe an organic Facebook reel/video/post -> facebook_video_transcribe (takes the Facebook url
54
+ directly).
55
+ - Instagram: profile inventory -> instagram_profile_content (returns post/reel/tv urls). One post or
56
+ reel -> instagram_media_download (takes a url from instagram_profile_content, or a url the user gives).
57
+ - Reddit: a reddit.com thread/post URL -> reddit_thread. It returns the post plus its comment tree and
58
+ handles Reddit's bot wall itself (no login needed). Find threads first with search_serp.
59
+ - No dedicated tool (an arbitrary site, or a logged-in dashboard) -> the browser_* agent. FIRST decide
60
+ whether the site needs a login:
61
+ - Needs a login (ChatGPT, Claude, any account) -> save it first: browser_profile_connect returns an
62
+ mcpscraper.dev sign-in link for the user; one profile holds MANY logins (call it again with the same
63
+ profile + a new domain to add accounts). Poll browser_profile_list until AUTHENTICATED, then
64
+ browser_open with that profile. browser_profile_list also shows what a profile is connected to.
65
+ - No login -> browser_open, then navigate/read.
66
+
67
+ WORKFLOWS (multi-step orchestrations — prefer these over hand-chaining primitives when the whole job is one of these)
68
+ - Build a local business directory for a niche across a state/region -> directory_workflow.
69
+ - Stand up a rank-tracking blueprint (database + cron + ingestion plan) -> rank_tracker_workflow.
70
+ - Find which sources an AI answer cites for a query (AEO) -> query_fanout_workflow (open a browser
71
+ session on chatgpt.com or claude.ai FIRST, then run it against that session).
72
+
73
+ NOTES
74
+ - Bulk / full-site crawls: call extract_site with rotateProxies:true for blocked or rate-limited sites.
75
+ It discovers URLs and returns a saved folder/artifact plus a summary, not the full content inline.
76
+ - Tools that open a browser session or transcribe media cost credits and take longer. Prefer the
77
+ cheapest tool that answers the question (plain search/extract before browser agents).
78
+ - Large results are saved to disk or an artifact and returned as a summary plus a path; read the path
79
+ for full detail rather than expecting the whole payload inline.
80
+ \`.trim()
81
+ ```
82
+
83
+ ## Build / deploy / release
84
+ 1. Edit the one file above.
85
+ 2. `npm run build` (typecheck + bundle; regenerates rate catalog — no functional change here).
86
+ 3. `vercel deploy --prod --yes` → remote-MCP clients get the new routing map immediately.
87
+ 4. **Bump 0.3.25 → 0.3.26** in the 3 version files (`package.json`, `package-lock.json`, `src/version.ts`) and `npm publish` — `SERVER_INSTRUCTIONS` is compiled into the stdio package, so npm-installed clients need the new version to see the corrected map.
88
+ 5. Commit on `main` (now the source of truth): `fix(mcp): routing map — add reddit_thread, explicit chaining seams, workflow triggers, login-first fallback`.
89
+
90
+ ## Verification
91
+ - `grep -c reddit_thread src/mcp/server-instructions.ts` → ≥1 (was 0).
92
+ - `grep -c "without a dedicated tool" src/mcp/server-instructions.ts` → 0 (old Reddit-to-browser line gone).
93
+ - After deploy, the live `/mcp` server-instructions block reflects the new map (inspect via an MCP client's server info, or the SERVER_INSTRUCTIONS in the published tarball).
94
+ - Behavioral smoke (manual, in an MCP client): "get the comments on <reddit thread url>" routes to `reddit_thread`, not `browser_open`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-scraper",
3
- "version": "0.3.25",
3
+ "version": "0.3.27",
4
4
  "description": "MCP server for MCP Scraper web intelligence tools",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",