@imenam/simple-scraper 1.0.21 → 1.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +398 -383
- package/dist/gui.js +3 -2
- package/dist/index.js +29 -24
- package/dist/relay.js +67 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,383 +1,398 @@
|
|
|
1
|
-
# simple-scraper-mcp
|
|
2
|
-
|
|
3
|
-
A Model Context Protocol (MCP) server for web scraping and JavaScript execution using a headless browser (Puppeteer). Includes an optional GUI for cookie management.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **`scrape_page`** — Navigate to a URL and return the full rendered HTML of the page
|
|
8
|
-
- **`execute_js`** — Navigate to a URL and execute custom JavaScript in the page context
|
|
9
|
-
- **`get_page_inputs`** — Extract all form inputs from a page as a structured JSON object
|
|
10
|
-
- **`get_show_page`** — Parse a detail/show page and extract key-value blocks and tables as structured JSON
|
|
11
|
-
- **Interactive sessions** — Keep a browser page alive across multiple tool calls to navigate, click, type, run JS, and screenshot the page in any desired state
|
|
12
|
-
- **`screenshot`** — Capture a screenshot of any page or active session, with inline or file output
|
|
13
|
-
- **Cookie support** — Load Netscape-format cookie files automatically before each request
|
|
14
|
-
- **Optional GUI** — Cookie manager interface, available when integrated with the [MCP proxy](https://www.npmjs.com/package/@imenam/mcp-http-gateway)
|
|
15
|
-
|
|
16
|
-
## Requirements
|
|
17
|
-
|
|
18
|
-
- Node.js >= 18
|
|
19
|
-
- Puppeteer will automatically download a compatible Chromium browser (~300 MB) on first install
|
|
20
|
-
|
|
21
|
-
## Installation
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
npx @imenam/simple-scraper
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Or install globally:
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
npm install -g @imenam/simple-scraper
|
|
31
|
-
simple-scraper
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## MCP Configuration
|
|
35
|
-
|
|
36
|
-
### Quick setup
|
|
37
|
-
|
|
38
|
-
From the repo you want to scrape from, generate a ready-to-fill `.mcp.json` in the current directory:
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
npx @imenam/simple-scraper --claude-setup-mcp
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
It creates the `simple-scraper-mcp` entry with the proxy variables pre-filled from the current
|
|
45
|
-
folder name (`PROXY_URL`, `APP_GROUP`, `PROXY_APP_NAME`, `PROXY_APP_PATH`) plus a 1920x1080
|
|
46
|
-
viewport, and prints the remaining optional variables. An empty variable is simply inactive
|
|
47
|
-
(no `PROXY_URL` = no GUI), so the generated config runs as-is. An existing `.mcp.json` is merged,
|
|
48
|
-
other servers are preserved, and an existing `simple-scraper-mcp` entry is only replaced when
|
|
49
|
-
`--force` is passed.
|
|
50
|
-
|
|
51
|
-
## Environment Variables
|
|
52
|
-
|
|
53
|
-
| Variable | Required | Default | Description |
|
|
54
|
-
|----------|----------|---------|-------------|
|
|
55
|
-
| `PUPPETEER_HEADLESS` | No | `true` | Run Chromium in headless mode. Set to `false` to display the browser window. |
|
|
56
|
-
| `PUPPETEER_TIMEOUT` | No | `30000` | Default timeout in milliseconds for page navigation and waits. |
|
|
57
|
-
| `PUPPETEER_VIEWPORT_WIDTH` | No | `1280` | Viewport width in CSS pixels, applied to every page. Affects responsive layouts and lazy-loading, not just screenshots. |
|
|
58
|
-
| `PUPPETEER_VIEWPORT_HEIGHT` | No | `800` | Viewport height in CSS pixels, applied to every page. |
|
|
59
|
-
| `PUPPETEER_DEVICE_SCALE_FACTOR` | No | `1` | Physical pixels per CSS pixel. Set to `2` for retina-density screenshots (same framing, twice the detail, ~4× the file size). |
|
|
60
|
-
| `COOKIES_DIR` | No | - | Absolute path to a folder containing Netscape-format `.txt` cookie files. The folder is created automatically if it does not exist. All files are loaded and merged automatically before each request. |
|
|
61
|
-
| `MCP_LOG_DIR` | No | `.mcp-gui/logs` | Absolute path to the directory where log files are written. |
|
|
62
|
-
| `PROXY_URL` | No | - | Base URL of the [MCP HTTP Gateway](https://www.npmjs.com/package/@imenam/mcp-http-gateway). Required to enable the GUI. |
|
|
63
|
-
| `PROXY_APP_PATH` | No | `/simple-scraper-mcp` | URL path under which the GUI is registered on the proxy. |
|
|
64
|
-
| `PROXY_APP_NAME` | No | `Simple Scraper MCP` | Display name shown in the proxy's app list. |
|
|
65
|
-
| `APP_GROUP` | No | - | Groups the app under a collapsible section (folded by default) in the proxy dashboard. Apps sharing the same value appear together; if unset, the app lands in the default "ungrouped" section. |
|
|
66
|
-
| `SCRAPER_MAX_SESSIONS` | No | `5` | Maximum number of concurrent interactive sessions. |
|
|
67
|
-
| `SCRAPER_SESSION_TTL_MS` | No | `600000` | Inactivity TTL for sessions in milliseconds (default: 10 minutes). Sessions unused beyond this duration are closed automatically. |
|
|
68
|
-
|
|
69
|
-
## Configuration
|
|
70
|
-
|
|
71
|
-
Copy `.env.example` to `.env` and configure the variables:
|
|
72
|
-
|
|
73
|
-
```env
|
|
74
|
-
# Puppeteer options (optional)
|
|
75
|
-
PUPPETEER_HEADLESS=true
|
|
76
|
-
PUPPETEER_TIMEOUT=30000
|
|
77
|
-
PUPPETEER_VIEWPORT_WIDTH=1280
|
|
78
|
-
PUPPETEER_VIEWPORT_HEIGHT=800
|
|
79
|
-
PUPPETEER_DEVICE_SCALE_FACTOR=1
|
|
80
|
-
|
|
81
|
-
# Optional: path to a folder containing Netscape-format cookie files (.txt)
|
|
82
|
-
# All files in this folder will be loaded automatically before each request.
|
|
83
|
-
# COOKIES_DIR=/path/to/cookies
|
|
84
|
-
|
|
85
|
-
# GUI (optional) — required to enable the cookie manager interface
|
|
86
|
-
# PROXY_URL=http://localhost:3000
|
|
87
|
-
# PROXY_APP_PATH=/simple-scraper-mcp
|
|
88
|
-
# PROXY_APP_NAME=Simple Scraper MCP
|
|
89
|
-
# Optional: groups this app under a collapsible section of the proxy dashboard
|
|
90
|
-
# APP_GROUP=Scraping
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### The process environment wins over `.env`
|
|
94
|
-
|
|
95
|
-
Any variable already set in the environment of the process that starts this MCP
|
|
96
|
-
(a shell `export`, a `claude_desktop_config.json` `env` block, or a supervisor
|
|
97
|
-
such as `@imenam/mcp-gateway-manager`) **takes precedence over the same key in
|
|
98
|
-
this repository's `.env`**. This is dotenv's default behaviour, and the usual
|
|
99
|
-
Unix convention: the caller wins.
|
|
100
|
-
|
|
101
|
-
The `.env` is still reloaded on the fly — the GUI writes to it and the change is
|
|
102
|
-
picked up without restarting the server — but that reload can no longer clobber
|
|
103
|
-
what the caller injected. Concretely, an `APP_GROUP=Scraping` line in this
|
|
104
|
-
`.env` no longer overrides an `APP_GROUP` passed in by a supervisor; it only
|
|
105
|
-
applies when nobody passed one.
|
|
106
|
-
|
|
107
|
-
To change a variable that the caller provides, change it where the caller sets
|
|
108
|
-
it; editing `.env` will have no effect on that key.
|
|
109
|
-
|
|
110
|
-
## Usage with Claude Desktop
|
|
111
|
-
|
|
112
|
-
Add the following to your `claude_desktop_config.json`. Full example with all available options:
|
|
113
|
-
|
|
114
|
-
```json
|
|
115
|
-
{
|
|
116
|
-
"mcpServers": {
|
|
117
|
-
"simple-scraper": {
|
|
118
|
-
"command": "npx",
|
|
119
|
-
"args": ["@imenam/simple-scraper"],
|
|
120
|
-
"env": {
|
|
121
|
-
"PUPPETEER_HEADLESS": "true",
|
|
122
|
-
"PUPPETEER_TIMEOUT": "30000",
|
|
123
|
-
"COOKIES_DIR": "/path/to/your/cookies",
|
|
124
|
-
"MCP_LOG_DIR": "/path/to/your/logs",
|
|
125
|
-
"PROXY_URL": "http://localhost:4500",
|
|
126
|
-
"PROXY_APP_PATH": "/simple-scraper",
|
|
127
|
-
"PROXY_APP_NAME": "Simple Scraper"
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
To load cookies automatically, add `COOKIES_DIR` pointing to a folder containing `.txt` files in [Netscape cookie format](http://www.cookiecentral.com/faq/#3.5):
|
|
135
|
-
|
|
136
|
-
```json
|
|
137
|
-
{
|
|
138
|
-
"mcpServers": {
|
|
139
|
-
"simple-scraper": {
|
|
140
|
-
"command": "npx",
|
|
141
|
-
"args": ["@imenam/simple-scraper"],
|
|
142
|
-
"env": {
|
|
143
|
-
"PUPPETEER_HEADLESS": "true",
|
|
144
|
-
"COOKIES_DIR": "/path/to/your/cookies"
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
## Usage with Cursor
|
|
152
|
-
|
|
153
|
-
In Cursor, MCP servers are configured in `.cursor/mcp.json`. You can pass environment variables directly in the config. Full example with all available options:
|
|
154
|
-
|
|
155
|
-
```json
|
|
156
|
-
{
|
|
157
|
-
"mcpServers": {
|
|
158
|
-
"simple-scraper": {
|
|
159
|
-
"command": "npx",
|
|
160
|
-
"args": ["-y", "@imenam/simple-scraper"],
|
|
161
|
-
"env": {
|
|
162
|
-
"PUPPETEER_HEADLESS": "true",
|
|
163
|
-
"PUPPETEER_TIMEOUT": "30000",
|
|
164
|
-
"COOKIES_DIR": "/path/to/your/cookies",
|
|
165
|
-
"MCP_LOG_DIR": "/path/to/your/logs",
|
|
166
|
-
"PROXY_URL": "http://localhost:4500",
|
|
167
|
-
"PROXY_APP_PATH": "/simple-scraper",
|
|
168
|
-
"PROXY_APP_NAME": "Simple Scraper"
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
> **Note:** The `-y` flag in `args` avoids the interactive confirmation prompt when using `npx`.
|
|
176
|
-
|
|
177
|
-
## MCP Tools
|
|
178
|
-
|
|
179
|
-
### `scrape_page`
|
|
180
|
-
|
|
181
|
-
Navigate to a URL and return the full rendered HTML.
|
|
182
|
-
|
|
183
|
-
| Parameter | Type | Required | Description |
|
|
184
|
-
|-----------|------|----------|-------------|
|
|
185
|
-
| `url` | string | ✅ | URL of the page to scrape |
|
|
186
|
-
| `wait_for` | string | | CSS selector to wait for before capturing HTML |
|
|
187
|
-
| `timeout` | number | | Timeout in ms (default: 30000) |
|
|
188
|
-
|
|
189
|
-
### `execute_js`
|
|
190
|
-
|
|
191
|
-
Navigate to a URL and execute custom JavaScript in the page context.
|
|
192
|
-
|
|
193
|
-
The `script` parameter is executed as the body of a JavaScript function in the browser page context, equivalent to:
|
|
194
|
-
|
|
195
|
-
```js
|
|
196
|
-
new Function(script)();
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
To return data from the tool, the script must use an explicit `return`. A bare expression such as `document.title` will evaluate but the tool will receive `undefined`.
|
|
200
|
-
|
|
201
|
-
Example:
|
|
202
|
-
|
|
203
|
-
```js
|
|
204
|
-
return {
|
|
205
|
-
title: document.title,
|
|
206
|
-
url: window.location.href,
|
|
207
|
-
text: document.body.innerText.slice(0, 500)
|
|
208
|
-
};
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
For asynchronous work, return a promise, for example with an async IIFE:
|
|
212
|
-
|
|
213
|
-
```js
|
|
214
|
-
return (async () => {
|
|
215
|
-
const response = await fetch('/api/data');
|
|
216
|
-
return await response.json();
|
|
217
|
-
})();
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
Returned objects and arrays are serialized as formatted JSON. Primitive values are returned as text.
|
|
221
|
-
|
|
222
|
-
| Parameter | Type | Required | Description |
|
|
223
|
-
|-----------|------|----------|-------------|
|
|
224
|
-
| `url` | string | ✅ | URL of the page |
|
|
225
|
-
| `script` | string | ✅ | JavaScript function body to execute in the page context. Use `return` to send a result back to the tool. |
|
|
226
|
-
| `wait_for` | string | | CSS selector to wait for before executing |
|
|
227
|
-
| `timeout` | number | | Timeout in ms (default: 30000) |
|
|
228
|
-
|
|
229
|
-
### `get_page_inputs`
|
|
230
|
-
|
|
231
|
-
Extract all form inputs from a page as a structured JSON object.
|
|
232
|
-
|
|
233
|
-
| Parameter | Type | Required | Description |
|
|
234
|
-
|-----------|------|----------|-------------|
|
|
235
|
-
| `url` | string | ✅ | URL of the page |
|
|
236
|
-
| `selector` | string | | CSS selector to scope the search (e.g. `#my-form`) |
|
|
237
|
-
| `wait_for` | string | | CSS selector to wait for before extracting |
|
|
238
|
-
| `show_hidden` | boolean | | Include `input[type=hidden]` fields (default: false) |
|
|
239
|
-
| `timeout` | number | | Timeout in ms (default: 30000) |
|
|
240
|
-
|
|
241
|
-
### `get_show_page`
|
|
242
|
-
|
|
243
|
-
Parse a detail page and extract key-value blocks and tables as structured JSON.
|
|
244
|
-
|
|
245
|
-
| Parameter | Type | Required | Description |
|
|
246
|
-
|-----------|------|----------|-------------|
|
|
247
|
-
| `url` | string | ✅ | URL of the page |
|
|
248
|
-
| `keys_map` | object | | Map of HTML label → JS key for field name translation |
|
|
249
|
-
| `box_selector` | string | | CSS selector for section containers (default: `.box.box-primary`) |
|
|
250
|
-
| `tables_max_items` | number | | Max rows per table (default: 2) |
|
|
251
|
-
| `wait_for` | string | | CSS selector to wait for before extraction |
|
|
252
|
-
| `timeout` | number | | Timeout in ms (default: 30000) |
|
|
253
|
-
|
|
254
|
-
---
|
|
255
|
-
|
|
256
|
-
## Interactive Sessions
|
|
257
|
-
|
|
258
|
-
Interactive sessions let you keep a browser page alive across multiple tool calls, so you can bring the page into the exact state you need before extracting data or taking a screenshot.
|
|
259
|
-
|
|
260
|
-
### Typical workflow
|
|
261
|
-
|
|
262
|
-
```
|
|
263
|
-
open_session → session_click / session_type / session_evaluate → screenshot / session_html → close_session
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
### `open_session`
|
|
267
|
-
|
|
268
|
-
Open a persistent browser session. Returns a `session_id` to use with all `session_*` tools and `screenshot`.
|
|
269
|
-
|
|
270
|
-
| Parameter | Type | Required | Description |
|
|
271
|
-
|-----------|------|----------|-------------|
|
|
272
|
-
| `url` | string | ✅ | URL to navigate to |
|
|
273
|
-
| `wait_for` | string | | CSS selector to wait for before the session is considered ready |
|
|
274
|
-
| `timeout` | number | | Timeout in ms (default: 30000) |
|
|
275
|
-
| `viewport` | object | | Viewport override for this session, applied before navigation: `{ width, height, device_scale_factor }`. Omitted fields fall back to the server defaults. |
|
|
276
|
-
|
|
277
|
-
### `close_session`
|
|
278
|
-
|
|
279
|
-
Close a session and free its resources. Always call this when you are done.
|
|
280
|
-
|
|
281
|
-
| Parameter | Type | Required | Description |
|
|
282
|
-
|-----------|------|----------|-------------|
|
|
283
|
-
| `session_id` | string | ✅ | Session ID returned by `open_session` |
|
|
284
|
-
|
|
285
|
-
### `list_sessions`
|
|
286
|
-
|
|
287
|
-
List all currently active sessions with their IDs and timestamps. No parameters.
|
|
288
|
-
|
|
289
|
-
### `session_goto`
|
|
290
|
-
|
|
291
|
-
Navigate the session to a new URL without closing it.
|
|
292
|
-
|
|
293
|
-
| Parameter | Type | Required | Description |
|
|
294
|
-
|-----------|------|----------|-------------|
|
|
295
|
-
| `session_id` | string | ✅ | Session ID |
|
|
296
|
-
| `url` | string | ✅ | URL to navigate to |
|
|
297
|
-
| `wait_for` | string | | CSS selector to wait for after navigation |
|
|
298
|
-
| `timeout` | number | | Timeout in ms (default: 30000) |
|
|
299
|
-
|
|
300
|
-
### `session_click`
|
|
301
|
-
|
|
302
|
-
Click an element in the session page.
|
|
303
|
-
|
|
304
|
-
| Parameter | Type | Required | Description |
|
|
305
|
-
|-----------|------|----------|-------------|
|
|
306
|
-
| `session_id` | string | ✅ | Session ID |
|
|
307
|
-
| `selector` | string | ✅ | CSS selector of the element to click |
|
|
308
|
-
| `timeout` | number | | Timeout in ms to wait for the element (default: 30000) |
|
|
309
|
-
|
|
310
|
-
### `session_type`
|
|
311
|
-
|
|
312
|
-
Type text into an input element in the session page.
|
|
313
|
-
|
|
314
|
-
| Parameter | Type | Required | Description |
|
|
315
|
-
|-----------|------|----------|-------------|
|
|
316
|
-
| `session_id` | string | ✅ | Session ID |
|
|
317
|
-
| `selector` | string | ✅ | CSS selector of the input element |
|
|
318
|
-
| `text` | string | ✅ | Text to type |
|
|
319
|
-
| `clear` | boolean | | Clear the field before typing (default: false) |
|
|
320
|
-
| `timeout` | number | | Timeout in ms to wait for the element (default: 30000) |
|
|
321
|
-
|
|
322
|
-
### `session_wait_for`
|
|
323
|
-
|
|
324
|
-
Wait for a CSS selector to appear in the session page.
|
|
325
|
-
|
|
326
|
-
| Parameter | Type | Required | Description |
|
|
327
|
-
|-----------|------|----------|-------------|
|
|
328
|
-
| `session_id` | string | ✅ | Session ID |
|
|
329
|
-
| `selector` | string | ✅ | CSS selector to wait for |
|
|
330
|
-
| `timeout` | number | | Timeout in ms (default: 30000) |
|
|
331
|
-
|
|
332
|
-
### `session_evaluate`
|
|
333
|
-
|
|
334
|
-
Execute JavaScript in the context of the session page. Same conventions as `execute_js`.
|
|
335
|
-
|
|
336
|
-
| Parameter | Type | Required | Description |
|
|
337
|
-
|-----------|------|----------|-------------|
|
|
338
|
-
| `session_id` | string | ✅ | Session ID |
|
|
339
|
-
| `script` | string | ✅ | JavaScript function body. Use `return` to get a result back. |
|
|
340
|
-
| `wait_for` | string | | CSS selector to wait for before executing |
|
|
341
|
-
| `timeout` | number | | Timeout in ms (default: 30000) |
|
|
342
|
-
|
|
343
|
-
### `session_html`
|
|
344
|
-
|
|
345
|
-
Return the current full rendered HTML of the session page.
|
|
346
|
-
|
|
347
|
-
| Parameter | Type | Required | Description |
|
|
348
|
-
|-----------|------|----------|-------------|
|
|
349
|
-
| `session_id` | string | ✅ | Session ID |
|
|
350
|
-
|
|
351
|
-
### `screenshot`
|
|
352
|
-
|
|
353
|
-
Capture a screenshot of a page. Use `session_id` to capture an active session in its current state, or `url` for a one-shot capture.
|
|
354
|
-
|
|
355
|
-
The capture area is resolved by precedence: `selector` → `clip` → `full_page` → the visible viewport.
|
|
356
|
-
|
|
357
|
-
| Parameter | Type | Required | Description |
|
|
358
|
-
|-----------|------|----------|-------------|
|
|
359
|
-
| `session_id` | string | | Session ID. If provided, `url` is ignored and the current page state is captured. |
|
|
360
|
-
| `url` | string | | URL for a one-shot screenshot. Required if `session_id` is not provided. |
|
|
361
|
-
| `wait_for` | string | | CSS selector to wait for (one-shot mode only) |
|
|
362
|
-
| `timeout` | number | | Timeout in ms (default: 30000) |
|
|
363
|
-
| `selector` | string | | CSS selector of a specific element to capture |
|
|
364
|
-
| `clip` | object | | Arbitrary pixel region `{ x, y, width, height }` in CSS pixels, measured from the top-left of the page. May extend below the visible viewport. Ignored when `selector` is set; cannot be combined with `full_page`. |
|
|
365
|
-
| `viewport` | object | | Viewport override for this capture: `{ width, height, device_scale_factor }`. For a session, the previous viewport is restored afterwards. |
|
|
366
|
-
| `full_page` | boolean | | Capture the full scrollable page height (default: false, ignored when `selector` or `clip` is provided) |
|
|
367
|
-
| `format` | `png` \| `jpeg` | | Image format (default: `png`) |
|
|
368
|
-
| `output` | `inline` \| `file` \| `both` | ✅ | `inline` embeds the image in the response, `file`
|
|
369
|
-
| `
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
1
|
+
# simple-scraper-mcp
|
|
2
|
+
|
|
3
|
+
A Model Context Protocol (MCP) server for web scraping and JavaScript execution using a headless browser (Puppeteer). Includes an optional GUI for cookie management.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **`scrape_page`** — Navigate to a URL and return the full rendered HTML of the page
|
|
8
|
+
- **`execute_js`** — Navigate to a URL and execute custom JavaScript in the page context
|
|
9
|
+
- **`get_page_inputs`** — Extract all form inputs from a page as a structured JSON object
|
|
10
|
+
- **`get_show_page`** — Parse a detail/show page and extract key-value blocks and tables as structured JSON
|
|
11
|
+
- **Interactive sessions** — Keep a browser page alive across multiple tool calls to navigate, click, type, run JS, and screenshot the page in any desired state
|
|
12
|
+
- **`screenshot`** — Capture a screenshot of any page or active session, with inline or file output
|
|
13
|
+
- **Cookie support** — Load Netscape-format cookie files automatically before each request
|
|
14
|
+
- **Optional GUI** — Cookie manager interface, available when integrated with the [MCP proxy](https://www.npmjs.com/package/@imenam/mcp-http-gateway)
|
|
15
|
+
|
|
16
|
+
## Requirements
|
|
17
|
+
|
|
18
|
+
- Node.js >= 18
|
|
19
|
+
- Puppeteer will automatically download a compatible Chromium browser (~300 MB) on first install
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx @imenam/simple-scraper
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or install globally:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install -g @imenam/simple-scraper
|
|
31
|
+
simple-scraper
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## MCP Configuration
|
|
35
|
+
|
|
36
|
+
### Quick setup
|
|
37
|
+
|
|
38
|
+
From the repo you want to scrape from, generate a ready-to-fill `.mcp.json` in the current directory:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx @imenam/simple-scraper --claude-setup-mcp
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
It creates the `simple-scraper-mcp` entry with the proxy variables pre-filled from the current
|
|
45
|
+
folder name (`PROXY_URL`, `APP_GROUP`, `PROXY_APP_NAME`, `PROXY_APP_PATH`) plus a 1920x1080
|
|
46
|
+
viewport, and prints the remaining optional variables. An empty variable is simply inactive
|
|
47
|
+
(no `PROXY_URL` = no GUI), so the generated config runs as-is. An existing `.mcp.json` is merged,
|
|
48
|
+
other servers are preserved, and an existing `simple-scraper-mcp` entry is only replaced when
|
|
49
|
+
`--force` is passed.
|
|
50
|
+
|
|
51
|
+
## Environment Variables
|
|
52
|
+
|
|
53
|
+
| Variable | Required | Default | Description |
|
|
54
|
+
|----------|----------|---------|-------------|
|
|
55
|
+
| `PUPPETEER_HEADLESS` | No | `true` | Run Chromium in headless mode. Set to `false` to display the browser window. |
|
|
56
|
+
| `PUPPETEER_TIMEOUT` | No | `30000` | Default timeout in milliseconds for page navigation and waits. |
|
|
57
|
+
| `PUPPETEER_VIEWPORT_WIDTH` | No | `1280` | Viewport width in CSS pixels, applied to every page. Affects responsive layouts and lazy-loading, not just screenshots. |
|
|
58
|
+
| `PUPPETEER_VIEWPORT_HEIGHT` | No | `800` | Viewport height in CSS pixels, applied to every page. |
|
|
59
|
+
| `PUPPETEER_DEVICE_SCALE_FACTOR` | No | `1` | Physical pixels per CSS pixel. Set to `2` for retina-density screenshots (same framing, twice the detail, ~4× the file size). |
|
|
60
|
+
| `COOKIES_DIR` | No | - | Absolute path to a folder containing Netscape-format `.txt` cookie files. The folder is created automatically if it does not exist. All files are loaded and merged automatically before each request. |
|
|
61
|
+
| `MCP_LOG_DIR` | No | `.mcp-gui/logs` | Absolute path to the directory where log files are written. |
|
|
62
|
+
| `PROXY_URL` | No | - | Base URL of the [MCP HTTP Gateway](https://www.npmjs.com/package/@imenam/mcp-http-gateway). Required to enable the GUI. |
|
|
63
|
+
| `PROXY_APP_PATH` | No | `/simple-scraper-mcp` | URL path under which the GUI is registered on the proxy. |
|
|
64
|
+
| `PROXY_APP_NAME` | No | `Simple Scraper MCP` | Display name shown in the proxy's app list. |
|
|
65
|
+
| `APP_GROUP` | No | - | Groups the app under a collapsible section (folded by default) in the proxy dashboard. Apps sharing the same value appear together; if unset, the app lands in the default "ungrouped" section. |
|
|
66
|
+
| `SCRAPER_MAX_SESSIONS` | No | `5` | Maximum number of concurrent interactive sessions. |
|
|
67
|
+
| `SCRAPER_SESSION_TTL_MS` | No | `600000` | Inactivity TTL for sessions in milliseconds (default: 10 minutes). Sessions unused beyond this duration are closed automatically. |
|
|
68
|
+
|
|
69
|
+
## Configuration
|
|
70
|
+
|
|
71
|
+
Copy `.env.example` to `.env` and configure the variables:
|
|
72
|
+
|
|
73
|
+
```env
|
|
74
|
+
# Puppeteer options (optional)
|
|
75
|
+
PUPPETEER_HEADLESS=true
|
|
76
|
+
PUPPETEER_TIMEOUT=30000
|
|
77
|
+
PUPPETEER_VIEWPORT_WIDTH=1280
|
|
78
|
+
PUPPETEER_VIEWPORT_HEIGHT=800
|
|
79
|
+
PUPPETEER_DEVICE_SCALE_FACTOR=1
|
|
80
|
+
|
|
81
|
+
# Optional: path to a folder containing Netscape-format cookie files (.txt)
|
|
82
|
+
# All files in this folder will be loaded automatically before each request.
|
|
83
|
+
# COOKIES_DIR=/path/to/cookies
|
|
84
|
+
|
|
85
|
+
# GUI (optional) — required to enable the cookie manager interface
|
|
86
|
+
# PROXY_URL=http://localhost:3000
|
|
87
|
+
# PROXY_APP_PATH=/simple-scraper-mcp
|
|
88
|
+
# PROXY_APP_NAME=Simple Scraper MCP
|
|
89
|
+
# Optional: groups this app under a collapsible section of the proxy dashboard
|
|
90
|
+
# APP_GROUP=Scraping
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### The process environment wins over `.env`
|
|
94
|
+
|
|
95
|
+
Any variable already set in the environment of the process that starts this MCP
|
|
96
|
+
(a shell `export`, a `claude_desktop_config.json` `env` block, or a supervisor
|
|
97
|
+
such as `@imenam/mcp-gateway-manager`) **takes precedence over the same key in
|
|
98
|
+
this repository's `.env`**. This is dotenv's default behaviour, and the usual
|
|
99
|
+
Unix convention: the caller wins.
|
|
100
|
+
|
|
101
|
+
The `.env` is still reloaded on the fly — the GUI writes to it and the change is
|
|
102
|
+
picked up without restarting the server — but that reload can no longer clobber
|
|
103
|
+
what the caller injected. Concretely, an `APP_GROUP=Scraping` line in this
|
|
104
|
+
`.env` no longer overrides an `APP_GROUP` passed in by a supervisor; it only
|
|
105
|
+
applies when nobody passed one.
|
|
106
|
+
|
|
107
|
+
To change a variable that the caller provides, change it where the caller sets
|
|
108
|
+
it; editing `.env` will have no effect on that key.
|
|
109
|
+
|
|
110
|
+
## Usage with Claude Desktop
|
|
111
|
+
|
|
112
|
+
Add the following to your `claude_desktop_config.json`. Full example with all available options:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"mcpServers": {
|
|
117
|
+
"simple-scraper": {
|
|
118
|
+
"command": "npx",
|
|
119
|
+
"args": ["@imenam/simple-scraper"],
|
|
120
|
+
"env": {
|
|
121
|
+
"PUPPETEER_HEADLESS": "true",
|
|
122
|
+
"PUPPETEER_TIMEOUT": "30000",
|
|
123
|
+
"COOKIES_DIR": "/path/to/your/cookies",
|
|
124
|
+
"MCP_LOG_DIR": "/path/to/your/logs",
|
|
125
|
+
"PROXY_URL": "http://localhost:4500",
|
|
126
|
+
"PROXY_APP_PATH": "/simple-scraper",
|
|
127
|
+
"PROXY_APP_NAME": "Simple Scraper"
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
To load cookies automatically, add `COOKIES_DIR` pointing to a folder containing `.txt` files in [Netscape cookie format](http://www.cookiecentral.com/faq/#3.5):
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"mcpServers": {
|
|
139
|
+
"simple-scraper": {
|
|
140
|
+
"command": "npx",
|
|
141
|
+
"args": ["@imenam/simple-scraper"],
|
|
142
|
+
"env": {
|
|
143
|
+
"PUPPETEER_HEADLESS": "true",
|
|
144
|
+
"COOKIES_DIR": "/path/to/your/cookies"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Usage with Cursor
|
|
152
|
+
|
|
153
|
+
In Cursor, MCP servers are configured in `.cursor/mcp.json`. You can pass environment variables directly in the config. Full example with all available options:
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"mcpServers": {
|
|
158
|
+
"simple-scraper": {
|
|
159
|
+
"command": "npx",
|
|
160
|
+
"args": ["-y", "@imenam/simple-scraper"],
|
|
161
|
+
"env": {
|
|
162
|
+
"PUPPETEER_HEADLESS": "true",
|
|
163
|
+
"PUPPETEER_TIMEOUT": "30000",
|
|
164
|
+
"COOKIES_DIR": "/path/to/your/cookies",
|
|
165
|
+
"MCP_LOG_DIR": "/path/to/your/logs",
|
|
166
|
+
"PROXY_URL": "http://localhost:4500",
|
|
167
|
+
"PROXY_APP_PATH": "/simple-scraper",
|
|
168
|
+
"PROXY_APP_NAME": "Simple Scraper"
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
> **Note:** The `-y` flag in `args` avoids the interactive confirmation prompt when using `npx`.
|
|
176
|
+
|
|
177
|
+
## MCP Tools
|
|
178
|
+
|
|
179
|
+
### `scrape_page`
|
|
180
|
+
|
|
181
|
+
Navigate to a URL and return the full rendered HTML.
|
|
182
|
+
|
|
183
|
+
| Parameter | Type | Required | Description |
|
|
184
|
+
|-----------|------|----------|-------------|
|
|
185
|
+
| `url` | string | ✅ | URL of the page to scrape |
|
|
186
|
+
| `wait_for` | string | | CSS selector to wait for before capturing HTML |
|
|
187
|
+
| `timeout` | number | | Timeout in ms (default: 30000) |
|
|
188
|
+
|
|
189
|
+
### `execute_js`
|
|
190
|
+
|
|
191
|
+
Navigate to a URL and execute custom JavaScript in the page context.
|
|
192
|
+
|
|
193
|
+
The `script` parameter is executed as the body of a JavaScript function in the browser page context, equivalent to:
|
|
194
|
+
|
|
195
|
+
```js
|
|
196
|
+
new Function(script)();
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
To return data from the tool, the script must use an explicit `return`. A bare expression such as `document.title` will evaluate but the tool will receive `undefined`.
|
|
200
|
+
|
|
201
|
+
Example:
|
|
202
|
+
|
|
203
|
+
```js
|
|
204
|
+
return {
|
|
205
|
+
title: document.title,
|
|
206
|
+
url: window.location.href,
|
|
207
|
+
text: document.body.innerText.slice(0, 500)
|
|
208
|
+
};
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
For asynchronous work, return a promise, for example with an async IIFE:
|
|
212
|
+
|
|
213
|
+
```js
|
|
214
|
+
return (async () => {
|
|
215
|
+
const response = await fetch('/api/data');
|
|
216
|
+
return await response.json();
|
|
217
|
+
})();
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Returned objects and arrays are serialized as formatted JSON. Primitive values are returned as text.
|
|
221
|
+
|
|
222
|
+
| Parameter | Type | Required | Description |
|
|
223
|
+
|-----------|------|----------|-------------|
|
|
224
|
+
| `url` | string | ✅ | URL of the page |
|
|
225
|
+
| `script` | string | ✅ | JavaScript function body to execute in the page context. Use `return` to send a result back to the tool. |
|
|
226
|
+
| `wait_for` | string | | CSS selector to wait for before executing |
|
|
227
|
+
| `timeout` | number | | Timeout in ms (default: 30000) |
|
|
228
|
+
|
|
229
|
+
### `get_page_inputs`
|
|
230
|
+
|
|
231
|
+
Extract all form inputs from a page as a structured JSON object.
|
|
232
|
+
|
|
233
|
+
| Parameter | Type | Required | Description |
|
|
234
|
+
|-----------|------|----------|-------------|
|
|
235
|
+
| `url` | string | ✅ | URL of the page |
|
|
236
|
+
| `selector` | string | | CSS selector to scope the search (e.g. `#my-form`) |
|
|
237
|
+
| `wait_for` | string | | CSS selector to wait for before extracting |
|
|
238
|
+
| `show_hidden` | boolean | | Include `input[type=hidden]` fields (default: false) |
|
|
239
|
+
| `timeout` | number | | Timeout in ms (default: 30000) |
|
|
240
|
+
|
|
241
|
+
### `get_show_page`
|
|
242
|
+
|
|
243
|
+
Parse a detail page and extract key-value blocks and tables as structured JSON.
|
|
244
|
+
|
|
245
|
+
| Parameter | Type | Required | Description |
|
|
246
|
+
|-----------|------|----------|-------------|
|
|
247
|
+
| `url` | string | ✅ | URL of the page |
|
|
248
|
+
| `keys_map` | object | | Map of HTML label → JS key for field name translation |
|
|
249
|
+
| `box_selector` | string | | CSS selector for section containers (default: `.box.box-primary`) |
|
|
250
|
+
| `tables_max_items` | number | | Max rows per table (default: 2) |
|
|
251
|
+
| `wait_for` | string | | CSS selector to wait for before extraction |
|
|
252
|
+
| `timeout` | number | | Timeout in ms (default: 30000) |
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Interactive Sessions
|
|
257
|
+
|
|
258
|
+
Interactive sessions let you keep a browser page alive across multiple tool calls, so you can bring the page into the exact state you need before extracting data or taking a screenshot.
|
|
259
|
+
|
|
260
|
+
### Typical workflow
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
open_session → session_click / session_type / session_evaluate → screenshot / session_html → close_session
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### `open_session`
|
|
267
|
+
|
|
268
|
+
Open a persistent browser session. Returns a `session_id` to use with all `session_*` tools and `screenshot`.
|
|
269
|
+
|
|
270
|
+
| Parameter | Type | Required | Description |
|
|
271
|
+
|-----------|------|----------|-------------|
|
|
272
|
+
| `url` | string | ✅ | URL to navigate to |
|
|
273
|
+
| `wait_for` | string | | CSS selector to wait for before the session is considered ready |
|
|
274
|
+
| `timeout` | number | | Timeout in ms (default: 30000) |
|
|
275
|
+
| `viewport` | object | | Viewport override for this session, applied before navigation: `{ width, height, device_scale_factor }`. Omitted fields fall back to the server defaults. |
|
|
276
|
+
|
|
277
|
+
### `close_session`
|
|
278
|
+
|
|
279
|
+
Close a session and free its resources. Always call this when you are done.
|
|
280
|
+
|
|
281
|
+
| Parameter | Type | Required | Description |
|
|
282
|
+
|-----------|------|----------|-------------|
|
|
283
|
+
| `session_id` | string | ✅ | Session ID returned by `open_session` |
|
|
284
|
+
|
|
285
|
+
### `list_sessions`
|
|
286
|
+
|
|
287
|
+
List all currently active sessions with their IDs and timestamps. No parameters.
|
|
288
|
+
|
|
289
|
+
### `session_goto`
|
|
290
|
+
|
|
291
|
+
Navigate the session to a new URL without closing it.
|
|
292
|
+
|
|
293
|
+
| Parameter | Type | Required | Description |
|
|
294
|
+
|-----------|------|----------|-------------|
|
|
295
|
+
| `session_id` | string | ✅ | Session ID |
|
|
296
|
+
| `url` | string | ✅ | URL to navigate to |
|
|
297
|
+
| `wait_for` | string | | CSS selector to wait for after navigation |
|
|
298
|
+
| `timeout` | number | | Timeout in ms (default: 30000) |
|
|
299
|
+
|
|
300
|
+
### `session_click`
|
|
301
|
+
|
|
302
|
+
Click an element in the session page.
|
|
303
|
+
|
|
304
|
+
| Parameter | Type | Required | Description |
|
|
305
|
+
|-----------|------|----------|-------------|
|
|
306
|
+
| `session_id` | string | ✅ | Session ID |
|
|
307
|
+
| `selector` | string | ✅ | CSS selector of the element to click |
|
|
308
|
+
| `timeout` | number | | Timeout in ms to wait for the element (default: 30000) |
|
|
309
|
+
|
|
310
|
+
### `session_type`
|
|
311
|
+
|
|
312
|
+
Type text into an input element in the session page.
|
|
313
|
+
|
|
314
|
+
| Parameter | Type | Required | Description |
|
|
315
|
+
|-----------|------|----------|-------------|
|
|
316
|
+
| `session_id` | string | ✅ | Session ID |
|
|
317
|
+
| `selector` | string | ✅ | CSS selector of the input element |
|
|
318
|
+
| `text` | string | ✅ | Text to type |
|
|
319
|
+
| `clear` | boolean | | Clear the field before typing (default: false) |
|
|
320
|
+
| `timeout` | number | | Timeout in ms to wait for the element (default: 30000) |
|
|
321
|
+
|
|
322
|
+
### `session_wait_for`
|
|
323
|
+
|
|
324
|
+
Wait for a CSS selector to appear in the session page.
|
|
325
|
+
|
|
326
|
+
| Parameter | Type | Required | Description |
|
|
327
|
+
|-----------|------|----------|-------------|
|
|
328
|
+
| `session_id` | string | ✅ | Session ID |
|
|
329
|
+
| `selector` | string | ✅ | CSS selector to wait for |
|
|
330
|
+
| `timeout` | number | | Timeout in ms (default: 30000) |
|
|
331
|
+
|
|
332
|
+
### `session_evaluate`
|
|
333
|
+
|
|
334
|
+
Execute JavaScript in the context of the session page. Same conventions as `execute_js`.
|
|
335
|
+
|
|
336
|
+
| Parameter | Type | Required | Description |
|
|
337
|
+
|-----------|------|----------|-------------|
|
|
338
|
+
| `session_id` | string | ✅ | Session ID |
|
|
339
|
+
| `script` | string | ✅ | JavaScript function body. Use `return` to get a result back. |
|
|
340
|
+
| `wait_for` | string | | CSS selector to wait for before executing |
|
|
341
|
+
| `timeout` | number | | Timeout in ms (default: 30000) |
|
|
342
|
+
|
|
343
|
+
### `session_html`
|
|
344
|
+
|
|
345
|
+
Return the current full rendered HTML of the session page.
|
|
346
|
+
|
|
347
|
+
| Parameter | Type | Required | Description |
|
|
348
|
+
|-----------|------|----------|-------------|
|
|
349
|
+
| `session_id` | string | ✅ | Session ID |
|
|
350
|
+
|
|
351
|
+
### `screenshot`
|
|
352
|
+
|
|
353
|
+
Capture a screenshot of a page. Use `session_id` to capture an active session in its current state, or `url` for a one-shot capture.
|
|
354
|
+
|
|
355
|
+
The capture area is resolved by precedence: `selector` → `clip` → `full_page` → the visible viewport.
|
|
356
|
+
|
|
357
|
+
| Parameter | Type | Required | Description |
|
|
358
|
+
|-----------|------|----------|-------------|
|
|
359
|
+
| `session_id` | string | | Session ID. If provided, `url` is ignored and the current page state is captured. |
|
|
360
|
+
| `url` | string | | URL for a one-shot screenshot. Required if `session_id` is not provided. |
|
|
361
|
+
| `wait_for` | string | | CSS selector to wait for (one-shot mode only) |
|
|
362
|
+
| `timeout` | number | | Timeout in ms (default: 30000) |
|
|
363
|
+
| `selector` | string | | CSS selector of a specific element to capture |
|
|
364
|
+
| `clip` | object | | Arbitrary pixel region `{ x, y, width, height }` in CSS pixels, measured from the top-left of the page. May extend below the visible viewport. Ignored when `selector` is set; cannot be combined with `full_page`. |
|
|
365
|
+
| `viewport` | object | | Viewport override for this capture: `{ width, height, device_scale_factor }`. For a session, the previous viewport is restored afterwards. |
|
|
366
|
+
| `full_page` | boolean | | Capture the full scrollable page height (default: false, ignored when `selector` or `clip` is provided) |
|
|
367
|
+
| `format` | `png` \| `jpeg` | | Image format (default: `png`) |
|
|
368
|
+
| `output` | `inline` \| `file` \| `both` | ✅ | `inline` embeds the image in the response, `file` writes it on the agent machine, `both` does both |
|
|
369
|
+
| `destination_path` | string | ⚠️ | **Absolute directory** on the agent machine where the image is written. Created if missing. Required when `output` is `file` or `both` |
|
|
370
|
+
| `filename` | string | ⚠️ | Name the image gets there, e.g. `home.png`. No path separators. Required when `output` is `file` or `both` |
|
|
371
|
+
| `destination_overwrite` | boolean | | Replace the destination file if it exists (default: `false`) |
|
|
372
|
+
| `destination_relay` | boolean | | Filled in by the mcp-http-gateway relay, never by the agent |
|
|
373
|
+
|
|
374
|
+
> **Where the file lands.** There is no default destination: one would resolve on *this
|
|
375
|
+
> server's* disk, which is not necessarily the agent's — a file nobody finds rather than an
|
|
376
|
+
> error. In stdio the server writes it itself; behind
|
|
377
|
+
> [`@imenam/mcp-http-gateway`](https://www.npmjs.com/package/@imenam/mcp-http-gateway) it
|
|
378
|
+
> returns the bytes and the relay, which runs on the agent machine, writes them at
|
|
379
|
+
> `destination_path`. That path requires `GATEWAY_INLINE_WRITE_ROOTS` in the relay's `env`
|
|
380
|
+
> block. The image never passes through the conversation unless `output` includes `inline`.
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## Cookie Files
|
|
385
|
+
|
|
386
|
+
Cookies are loaded from `.txt` files in [Netscape format](http://www.cookiecentral.com/faq/#3.5). Place them in the folder specified by `COOKIES_DIR`. All files in the folder are loaded and merged automatically before each request. If the folder does not exist yet, it is created automatically (including any missing parent directories), so a fresh `COOKIES_DIR` no longer raises an error.
|
|
387
|
+
|
|
388
|
+
## GUI (Optional)
|
|
389
|
+
|
|
390
|
+
When `PROXY_URL` is set, a cookie manager web interface is registered with the MCP proxy. It allows you to list, upload, and delete cookie files through a browser UI.
|
|
391
|
+
|
|
392
|
+
### Network exposure
|
|
393
|
+
|
|
394
|
+
The GUI listens on `127.0.0.1` only: other machines reach it through the proxy or the gateway manager. A request guard from [`@imenam/mcp-gui-interface`](https://www.npmjs.com/package/@imenam/mcp-gui-interface) answers `403` to any request whose `Host` header is not `localhost`, `127.0.0.1` or `::1` (DNS rebinding protection), and to modifying requests (`POST`, `DELETE`…) sent from another site (foreign `Sec-Fetch-Site` or `Origin`).
|
|
395
|
+
|
|
396
|
+
## License
|
|
397
|
+
|
|
398
|
+
ISC
|
package/dist/gui.js
CHANGED
|
@@ -3,7 +3,7 @@ import bodyParser from 'body-parser';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
-
import { setupLogging, ProxyClient, IpcHub } from '@imenam/mcp-gui-interface';
|
|
6
|
+
import { setupLogging, ProxyClient, IpcHub, GUI_LISTEN_HOST, expressGuiGuard } from '@imenam/mcp-gui-interface';
|
|
7
7
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
8
|
const ipc = new IpcHub(process);
|
|
9
9
|
async function callMaster(type, data, timeoutMs = 5000) {
|
|
@@ -70,6 +70,7 @@ const getCookiesDir = () => {
|
|
|
70
70
|
return cookiesDir;
|
|
71
71
|
};
|
|
72
72
|
const app = express();
|
|
73
|
+
app.use(expressGuiGuard());
|
|
73
74
|
app.use(bodyParser.json());
|
|
74
75
|
app.use(express.static(path.join(rootDir, 'public/dist')));
|
|
75
76
|
app.get(/^(?!\/api\/|\/proxy\/)/, (_req, res) => {
|
|
@@ -223,7 +224,7 @@ async function startServer() {
|
|
|
223
224
|
}
|
|
224
225
|
}, 500);
|
|
225
226
|
});
|
|
226
|
-
const serverListener = app.listen(port, () => {
|
|
227
|
+
const serverListener = app.listen(port, GUI_LISTEN_HOST, () => {
|
|
227
228
|
console.error(`[GUI] Server running at http://localhost:${port}`);
|
|
228
229
|
});
|
|
229
230
|
serverListener.on('error', (err) => {
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,6 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
4
4
|
// Importé AVANT tout appel à dotenv : ce module prend l'instantané de
|
|
5
5
|
// l'environnement hérité du process, qui doit primer sur le `.env` du dépôt.
|
|
6
6
|
import { loadEnvFile } from './env-precedence.js';
|
|
7
|
-
import fs from 'fs';
|
|
8
7
|
import path from 'path';
|
|
9
8
|
import { fileURLToPath } from 'url';
|
|
10
9
|
import { z } from 'zod';
|
|
@@ -14,6 +13,7 @@ import { loadAllCookies } from './cookies.js';
|
|
|
14
13
|
import { GuiLauncher, reconnectGuiToolDefinition } from '@imenam/mcp-gui-interface';
|
|
15
14
|
import { createSession, getSession, closeSession, closeAllSessions, listSessions, getSessionLogs, clearSessionLogs, resolveViewport } from './sessions.js';
|
|
16
15
|
import { registerSessionRpc } from './session-rpc.js';
|
|
16
|
+
import { deliverFile, RELAY_SHAPE_DESCRIPTIONS } from './relay.js';
|
|
17
17
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
18
|
const rootDir = path.resolve(__dirname, '..');
|
|
19
19
|
const envPath = path.join(rootDir, '.env');
|
|
@@ -602,21 +602,34 @@ server.tool('screenshot', 'Take a screenshot of a page. Provide either session_i
|
|
|
602
602
|
viewport: viewportSchema.describe('Viewport override for this capture. For a session, the previous viewport is restored afterwards.'),
|
|
603
603
|
full_page: z.boolean().optional().describe('Capture the full scrollable page height (default: false, ignored when selector or clip is provided)'),
|
|
604
604
|
format: z.enum(['png', 'jpeg']).optional().describe('Image format (default: png)'),
|
|
605
|
-
output: z.enum(['inline', 'file', 'both']).describe('Return format: "inline" embeds the image in the response, "file" saves
|
|
606
|
-
|
|
605
|
+
output: z.enum(['inline', 'file', 'both']).describe('Return format: "inline" embeds the image in the response, "file" saves the image on the agent machine, "both" does both'),
|
|
606
|
+
destination_path: z
|
|
607
|
+
.string()
|
|
608
|
+
.optional()
|
|
609
|
+
.describe(`${RELAY_SHAPE_DESCRIPTIONS.destination_path}. Required when output is "file" or "both"`),
|
|
610
|
+
filename: z
|
|
607
611
|
.string()
|
|
608
|
-
.refine(p => path.isAbsolute(p), {
|
|
609
|
-
message: 'path must be absolute — a relative path would resolve against the MCP server\'s working directory, which is set by the client and not predictable.',
|
|
610
|
-
})
|
|
611
612
|
.optional()
|
|
612
|
-
.describe(
|
|
613
|
-
|
|
613
|
+
.describe(`${RELAY_SHAPE_DESCRIPTIONS.filename}. Required when output is "file" or "both"`),
|
|
614
|
+
destination_overwrite: z.boolean().optional().describe(RELAY_SHAPE_DESCRIPTIONS.destination_overwrite),
|
|
615
|
+
destination_relay: z.boolean().optional().describe(RELAY_SHAPE_DESCRIPTIONS.destination_relay),
|
|
616
|
+
}, async ({ session_id, url, wait_for, timeout, selector, clip, viewport, full_page, format, output, destination_path, filename, destination_overwrite, destination_relay }) => {
|
|
614
617
|
const effectiveTimeout = timeout ?? getDefaultTimeout();
|
|
615
618
|
const fmt = format ?? 'png';
|
|
616
619
|
const mimeType = fmt === 'jpeg' ? 'image/jpeg' : 'image/png';
|
|
617
620
|
if (clip && full_page) {
|
|
618
621
|
return { content: [{ type: 'text', text: 'Error: clip and full_page cannot be combined — clip already defines the region to capture.' }], isError: true };
|
|
619
622
|
}
|
|
623
|
+
// Écrire un fichier suppose de savoir où, et sur quelle machine. Il n'y a pas de
|
|
624
|
+
// destination par défaut : elle atterrirait sur le disque de ce serveur, qui n'est
|
|
625
|
+
// pas forcément celui de l'agent — un fichier introuvable plutôt qu'une erreur.
|
|
626
|
+
const wantsFile = output === 'file' || output === 'both';
|
|
627
|
+
if (wantsFile && (!destination_path || !filename)) {
|
|
628
|
+
return {
|
|
629
|
+
content: [{ type: 'text', text: 'Error: destination_path and filename are required when output is "file" or "both".' }],
|
|
630
|
+
isError: true,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
620
633
|
let page = null;
|
|
621
634
|
let ownedPage = false;
|
|
622
635
|
// Only set for session pages, whose viewport must survive this call unchanged.
|
|
@@ -662,25 +675,17 @@ server.tool('screenshot', 'Take a screenshot of a page. Provide either session_i
|
|
|
662
675
|
else {
|
|
663
676
|
imageBuffer = Buffer.from(await page.screenshot({ type: fmt, fullPage: full_page ?? false }));
|
|
664
677
|
}
|
|
665
|
-
const base64 = imageBuffer.toString('base64');
|
|
666
|
-
// Resolve file path. filePath is guaranteed absolute by the schema; resolve()
|
|
667
|
-
// is kept only to normalise separators and any ".." segments.
|
|
668
|
-
const resolvedPath = (() => {
|
|
669
|
-
if (filePath)
|
|
670
|
-
return path.resolve(filePath);
|
|
671
|
-
const ts = Date.now();
|
|
672
|
-
const dir = path.join(rootDir, 'screenshots');
|
|
673
|
-
return path.join(dir, `screenshot-${ts}.${fmt}`);
|
|
674
|
-
})();
|
|
675
678
|
const resultContent = [];
|
|
676
679
|
if (output === 'inline' || output === 'both') {
|
|
677
|
-
resultContent.push({ type: 'image', data: base64, mimeType });
|
|
680
|
+
resultContent.push({ type: 'image', data: imageBuffer.toString('base64'), mimeType });
|
|
678
681
|
}
|
|
679
|
-
if (
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
682
|
+
if (wantsFile) {
|
|
683
|
+
resultContent.push(...deliverFile(imageBuffer, mimeType, {
|
|
684
|
+
destination_path: destination_path,
|
|
685
|
+
filename: filename,
|
|
686
|
+
destination_overwrite,
|
|
687
|
+
destination_relay,
|
|
688
|
+
}));
|
|
684
689
|
}
|
|
685
690
|
return { content: resultContent };
|
|
686
691
|
}
|
package/dist/relay.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
// Un fichier produit par ce MCP n'a aucun moyen d'atteindre la machine de l'agent quand
|
|
4
|
+
// le MCP tourne derrière mcp-http-gateway : le protocole n'offre au serveur aucune
|
|
5
|
+
// primitive pour y écrire. La convention du relais règle ça — dès qu'un outil déclare la
|
|
6
|
+
// paire `destination_path` / `destination_relay`, le relais retient la destination, pose
|
|
7
|
+
// `destination_relay`, et écrit lui-même les octets qu'on lui renvoie.
|
|
8
|
+
//
|
|
9
|
+
// Ici, on ne sait pas si un relais est dans la chaîne : `destination_relay` le dit. Posé,
|
|
10
|
+
// on renvoie le contenu ; absent, les deux machines n'en font qu'une et on écrit nous-même.
|
|
11
|
+
/** Plafond de ce qui peut être encodé dans une réponse d'outil. Le relais applique sa
|
|
12
|
+
* propre limite, configurable ; celle-ci n'existe que pour qu'un fichier démesuré
|
|
13
|
+
* échoue proprement ici plutôt qu'en épuisant la mémoire pendant l'encodage base64. */
|
|
14
|
+
const RELAY_MAX_BYTES = 32 * 1024 * 1024;
|
|
15
|
+
export const RELAY_SHAPE_DESCRIPTIONS = {
|
|
16
|
+
destination_path: "ABSOLUTE path of the destination DIRECTORY on the agent machine. It is created if missing, and the file keeps the name given in filename",
|
|
17
|
+
filename: "Name of the file to create, extension included (e.g. 'screenshot.png'). Must not contain path separators",
|
|
18
|
+
destination_overwrite: "Replace the destination file if it already exists. Defaults to false",
|
|
19
|
+
destination_relay: "Filled in by the mcp-http-gateway relay, never by the agent. Signals that the destination lives on another machine and that the file content must travel in the response",
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Livre un fichier produit par le MCP, du bon côté de la chaîne.
|
|
23
|
+
*
|
|
24
|
+
* Renvoie les blocs de contenu à faire figurer dans le résultat de l'outil : un bloc
|
|
25
|
+
* `resource` que le relais interceptera et retirera, ou une simple confirmation quand
|
|
26
|
+
* l'écriture a eu lieu ici. Toute impossibilité est une erreur franche : mieux vaut un
|
|
27
|
+
* fichier perdu qu'un fichier écrit sur la mauvaise machine.
|
|
28
|
+
*/
|
|
29
|
+
export function deliverFile(data, mimeType, target) {
|
|
30
|
+
const name = path.basename(target.filename);
|
|
31
|
+
if (name !== target.filename || name === "." || name === "..") {
|
|
32
|
+
throw new Error(`filename must be a bare file name, without path separators: ${target.filename}`);
|
|
33
|
+
}
|
|
34
|
+
if (data.length > RELAY_MAX_BYTES) {
|
|
35
|
+
throw new Error(`File too large to transfer: ${data.length} bytes, the limit is ${RELAY_MAX_BYTES}.`);
|
|
36
|
+
}
|
|
37
|
+
// Le relais tourne sur la machine de l'agent et a retenu la destination : il attend les
|
|
38
|
+
// octets, qu'il écrira lui-même. Écrire ici viserait le mauvais disque.
|
|
39
|
+
if (target.destination_relay) {
|
|
40
|
+
return [
|
|
41
|
+
{
|
|
42
|
+
type: "resource",
|
|
43
|
+
resource: {
|
|
44
|
+
uri: `file:///${name}`,
|
|
45
|
+
name,
|
|
46
|
+
mimeType,
|
|
47
|
+
blob: data.toString("base64"),
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
// Pas de relais : le MCP tourne à côté de l'agent, son disque est le bon.
|
|
53
|
+
if (!path.isAbsolute(target.destination_path)) {
|
|
54
|
+
throw new Error(`destination_path must be an absolute directory path: ${target.destination_path}`);
|
|
55
|
+
}
|
|
56
|
+
if (fs.existsSync(target.destination_path) &&
|
|
57
|
+
!fs.statSync(target.destination_path).isDirectory()) {
|
|
58
|
+
throw new Error(`destination_path must be a directory, but a file already exists there: ${target.destination_path}`);
|
|
59
|
+
}
|
|
60
|
+
const written = path.join(target.destination_path, name);
|
|
61
|
+
if (!target.destination_overwrite && fs.existsSync(written)) {
|
|
62
|
+
throw new Error(`Destination already exists: ${written}. Pass destination_overwrite to replace it.`);
|
|
63
|
+
}
|
|
64
|
+
fs.mkdirSync(target.destination_path, { recursive: true });
|
|
65
|
+
fs.writeFileSync(written, data);
|
|
66
|
+
return [{ type: "text", text: `File written to ${written} (${data.length} bytes).` }];
|
|
67
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imenam/simple-scraper",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.23",
|
|
4
4
|
"description": "MCP server for web scraping and JavaScript execution using Puppeteer",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"node": ">=18.0.0"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@imenam/mcp-gui-interface": "^1.0.
|
|
38
|
+
"@imenam/mcp-gui-interface": "^1.0.14",
|
|
39
39
|
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
40
40
|
"body-parser": "^2.2.2",
|
|
41
41
|
"dotenv": "^17.3.1",
|