@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 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` saves to disk and returns the path, `both` does both |
369
- | `path` | string | | **Absolute** path for the saved file — relative paths are rejected, since they would resolve against the server's working directory, which the client sets unpredictably. Missing parent directories are created. Defaults to `<server_root>/screenshots/screenshot-<timestamp>.<format>` |
370
-
371
- ---
372
-
373
- ## Cookie Files
374
-
375
- 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.
376
-
377
- ## GUI (Optional)
378
-
379
- 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.
380
-
381
- ## License
382
-
383
- ISC
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 to disk and returns the path, "both" does both'),
606
- path: z
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('Absolute file path for the saved image (used when output is "file" or "both"). Must be absolute; missing parent directories are created. Defaults to <server_root>/screenshots/screenshot-<timestamp>.<format>'),
613
- }, async ({ session_id, url, wait_for, timeout, selector, clip, viewport, full_page, format, output, path: filePath }) => {
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 (output === 'file' || output === 'both') {
680
- const dir = path.dirname(resolvedPath);
681
- fs.mkdirSync(dir, { recursive: true });
682
- fs.writeFileSync(resolvedPath, imageBuffer);
683
- resultContent.push({ type: 'text', text: `Screenshot saved to: ${resolvedPath}` });
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.21",
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.12",
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",