scrapeunblocker-mcp 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +8 -6
  2. package/dist/index.js +47 -17
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -12,7 +12,8 @@ You bring your **own** API key. Nothing is shared or proxied through us.
12
12
 
13
13
  | Tool | What it does |
14
14
  |------|--------------|
15
- | `fetch_html` | Fetch the fully rendered HTML of a URL (optionally after running interactive browser `steps`). |
15
+ | `fetch_html` | Fetch the fully rendered HTML of a URL. |
16
+ | `browse_page` | Run interactive browser `steps` on a page (click, type, select, scroll, wait), then return the resulting HTML. Not read-only: it acts on the website. |
16
17
  | `list_elements` | List a page's notable elements with a ready-to-use selector for each (selector discovery). |
17
18
  | `fetch_parsed` | Fetch a page and return AI-parsed structured JSON. |
18
19
  | `google_search` | Run a Google search and return organic results as JSON. |
@@ -43,7 +44,7 @@ Each run is billed like an API request.
43
44
 
44
45
  Some pages only reveal what you need after you interact with them - accept a
45
46
  cookie banner, click a tab, type into a search box and submit, or scroll to
46
- trigger lazy loading. Pass an optional `steps` array to `fetch_html` and those
47
+ trigger lazy loading. Pass a `steps` array to `browse_page` and those
47
48
  actions run in a **real browser**, in order, after the page loads; the resulting
48
49
  HTML is then returned.
49
50
 
@@ -60,8 +61,9 @@ Available actions:
60
61
  | `press_key` | `value` (`Enter`, `Tab`, `Escape`, `Backspace`, `Delete`, `Space`, `Arrow*`, `Home`, `End`, `PageUp`, `PageDown`) |
61
62
  | `scroll` | `value` (`"bottom"` or a pixel offset) |
62
63
 
63
- Steps are **not idempotent** - they run once per call. If a step fails,
64
- `fetch_html` returns which step failed, why, and the page HTML at that moment so
64
+ Steps are **not idempotent** - they run once per call - and they act on the
65
+ website, so `browse_page` is the one tool not marked read-only. If a step fails,
66
+ `browse_page` returns which step failed, why, and the page HTML at that moment so
65
67
  you can fix the selector and retry.
66
68
 
67
69
  ### List elements (discover selectors first)
@@ -76,7 +78,7 @@ you can fix the selector and retry.
76
78
  ```
77
79
 
78
80
  The natural workflow is **discover, then act**: call `list_elements` to find the
79
- selectors you need, then pass matching `steps` to `fetch_html` to click/type/
81
+ selectors you need, then pass matching `steps` to `browse_page` to click/type/
80
82
  select and capture the resulting HTML.
81
83
 
82
84
  ## Get an API key
@@ -140,7 +142,7 @@ SCRAPEUNBLOCKER_KEY=your_api_key_here npx -y scrapeunblocker-mcp
140
142
 
141
143
  - "Fetch the HTML of https://www.example-shop.com/product/123 and list the price."
142
144
  - "This page keeps blocking me: <url>. Use fetch_html to get it."
143
- - "List the elements on <url>, then use fetch_html steps to type 'laptop' into the search box, press Enter, wait for the results, and give me the HTML."
145
+ - "List the elements on <url>, then use browse_page to type 'laptop' into the search box, press Enter, wait for the results, and give me the HTML."
144
146
  - "Search Google for 'best running shoes 2026' and give me the top 5 links."
145
147
  - "Read https://en.wikipedia.org/wiki/Web_scraping as Markdown and summarise it in five bullets."
146
148
  - "Search the web for the current population of Vilnius and cite the pages you read."
package/dist/index.js CHANGED
@@ -5979,7 +5979,7 @@ ${body}`);
5979
5979
  }
5980
5980
 
5981
5981
  // src/index.ts
5982
- var VERSION = "0.5.0";
5982
+ var VERSION = "0.6.0";
5983
5983
  var DEFAULT_BASE_URL = "https://api.scrapeunblocker.com";
5984
5984
  var API_KEY_HEADER = "x-scrapeunblocker-key";
5985
5985
  function resolveApiKey() {
@@ -6137,7 +6137,7 @@ server.registerTool(
6137
6137
  {
6138
6138
  title: "Fetch page HTML",
6139
6139
  annotations: { title: "Fetch page HTML", readOnlyHint: true, openWorldHint: true, destructiveHint: false },
6140
- description: "Fetch the fully rendered HTML of any web page through ScrapeUnblocker, bypassing anti-bot protection (Cloudflare, DataDome, PerimeterX, Akamai, Shape). Use this when a normal fetch is blocked (403/429, captcha, 'access denied') or when the page needs a real browser to render. Returns the raw HTML as text.\n\nFor pages that need interaction (accept a cookie banner, click a tab, type into a search box, scroll to trigger lazy loading) pass `steps`: an ordered list of browser actions run in a real browser AFTER the page loads, then the resulting HTML is returned. Workflow: first call the `list_elements` tool to discover the real selectors on the page, then build `steps` against them. Steps are NOT idempotent - they run once per call. If a step fails, this tool returns which step failed, why, and the page HTML at that moment so you can fix the selector and retry.",
6140
+ description: "Fetch the fully rendered HTML of any web page through ScrapeUnblocker, bypassing anti-bot protection (Cloudflare, DataDome, PerimeterX, Akamai, Shape). Use this when a normal fetch is blocked (403/429, captcha, 'access denied') or when the page needs a real browser to render. Returns the raw HTML as text. To read or summarise a page, fetch_markdown is much smaller. To click, type or scroll before capturing, use browse_page.",
6141
6141
  inputSchema: {
6142
6142
  url: z3.string().url().describe("The absolute URL to fetch (http/https)."),
6143
6143
  proxy_country: z3.string().length(2).optional().describe(
@@ -6150,24 +6150,54 @@ server.registerTool(
6150
6150
  `The CSS selector or JS expression paired with wait_method (e.g. '#price' or 'document.readyState==="complete"').`
6151
6151
  ),
6152
6152
  method_timeout_seconds: z3.number().int().positive().optional().describe("Cap in seconds for the render-wait method."),
6153
- sleep_seconds: z3.number().positive().optional().describe("Extra seconds to wait after load before capturing the HTML."),
6154
- steps: z3.array(stepSchema).optional().describe(
6155
- "Optional ordered browser actions to run in a real browser after the page loads, before the HTML is captured. Use `list_elements` first to find selectors. Runs once (not idempotent)."
6156
- )
6153
+ sleep_seconds: z3.number().positive().optional().describe("Extra seconds to wait after load before capturing the HTML.")
6154
+ }
6155
+ },
6156
+ async (args) => {
6157
+ try {
6158
+ const html = await client().getPageSource(args.url, {
6159
+ proxyCountry: args.proxy_country,
6160
+ method: args.wait_method,
6161
+ value: args.wait_value,
6162
+ methodTimeout: args.method_timeout_seconds,
6163
+ timeSleep: args.sleep_seconds
6164
+ });
6165
+ return { content: [{ type: "text", text: html }] };
6166
+ } catch (err) {
6167
+ return errorResult(args.url, err);
6168
+ }
6169
+ }
6170
+ );
6171
+ server.registerTool(
6172
+ "browse_page",
6173
+ {
6174
+ title: "Interact with a page, then capture it",
6175
+ annotations: {
6176
+ title: "Interact with a page, then capture it",
6177
+ readOnlyHint: false,
6178
+ destructiveHint: true,
6179
+ idempotentHint: false,
6180
+ openWorldHint: true
6181
+ },
6182
+ description: "Load a web page in a real browser through ScrapeUnblocker, run an ordered list of browser actions on it (accept a cookie banner, click a tab, type into a search box, choose an option, press a key, scroll to trigger lazy loading, wait), then return the resulting HTML. Workflow: first call the `list_elements` tool to discover the real selectors on the page, then build `steps` against them. Steps act on the website and are NOT idempotent - they run once per call; never use them to buy, post, delete or change anything unless the user asked for exactly that. If a step fails, this tool returns which step failed, why, and the page HTML at that moment so you can fix the selector and retry. Billed as one request.",
6183
+ inputSchema: {
6184
+ url: z3.string().url().describe("The absolute URL to open (http/https)."),
6185
+ steps: z3.array(stepSchema).min(1).describe(
6186
+ "Ordered browser actions to run in a real browser after the page loads, before the HTML is captured. Use `list_elements` first to find selectors. Runs once (not idempotent)."
6187
+ ),
6188
+ proxy_country: z3.string().length(2).optional().describe(
6189
+ "Optional ISO 3166-1 alpha-2 country code to route through, e.g. 'US', 'GB', 'DE'."
6190
+ ),
6191
+ wait_method: z3.enum(["css", "js"]).optional().describe(
6192
+ "Optional render-wait before the steps run: 'css' waits for a selector, 'js' waits for a JS expression to be truthy."
6193
+ ),
6194
+ wait_value: z3.string().optional().describe("The CSS selector or JS expression paired with wait_method."),
6195
+ method_timeout_seconds: z3.number().int().positive().optional().describe("Cap in seconds for the render-wait method."),
6196
+ sleep_seconds: z3.number().positive().optional().describe("Extra seconds to wait after load.")
6157
6197
  }
6158
6198
  },
6159
6199
  async (args) => {
6160
6200
  try {
6161
- if (!args.steps || args.steps.length === 0) {
6162
- const html = await client().getPageSource(args.url, {
6163
- proxyCountry: args.proxy_country,
6164
- method: args.wait_method,
6165
- value: args.wait_value,
6166
- methodTimeout: args.method_timeout_seconds,
6167
- timeSleep: args.sleep_seconds
6168
- });
6169
- return { content: [{ type: "text", text: html }] };
6170
- }
6171
6201
  const res = await getPageSourceRaw({
6172
6202
  url: args.url,
6173
6203
  proxy_country: args.proxy_country,
@@ -6223,7 +6253,7 @@ server.registerTool(
6223
6253
  {
6224
6254
  title: "List page elements (selector discovery)",
6225
6255
  annotations: { title: "List page elements (selector discovery)", readOnlyHint: true, openWorldHint: true, destructiveHint: false },
6226
- description: "Load a page through ScrapeUnblocker and return a JSON list of its notable elements (links, inputs, buttons, selects, etc.) with a ready-to-use `selector` for each, plus tag, text and useful attributes (name, id, type, placeholder, aria_label, href, ...). Read-only and does not interact with the page. This is the discovery half of interactive scraping: call `list_elements` to find the selectors you need, then pass matching `steps` to `fetch_html` to click/type/select and capture the resulting HTML.",
6256
+ description: "Load a page through ScrapeUnblocker and return a JSON list of its notable elements (links, inputs, buttons, selects, etc.) with a ready-to-use `selector` for each, plus tag, text and useful attributes (name, id, type, placeholder, aria_label, href, ...). Read-only and does not interact with the page. This is the discovery half of interactive scraping: call `list_elements` to find the selectors you need, then pass matching `steps` to `browse_page` to click/type/select and capture the resulting HTML.",
6227
6257
  inputSchema: {
6228
6258
  url: z3.string().url().describe("The absolute URL whose elements you want to list."),
6229
6259
  proxy_country: z3.string().length(2).optional().describe("Optional ISO country code to route through, e.g. 'US'.")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scrapeunblocker-mcp",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "mcpName": "com.scrapeunblocker/scrapeunblocker-mcp",
5
5
  "description": "Model Context Protocol (MCP) server for ScrapeUnblocker - lets Claude fetch any web page's HTML (or AI-parsed JSON) and run 100+ site plugins that return structured JSON, through the ScrapeUnblocker anti-bot API, using your own API key.",
6
6
  "keywords": [