@imenam/simple-scraper 1.0.15 → 1.0.17
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 +18 -1
- package/dist/cli/setup-mcp.js +103 -0
- package/dist/index.js +15 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,6 +31,23 @@ npm install -g @imenam/simple-scraper
|
|
|
31
31
|
simple-scraper
|
|
32
32
|
```
|
|
33
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
|
+
|
|
34
51
|
## Environment Variables
|
|
35
52
|
|
|
36
53
|
| Variable | Required | Default | Description |
|
|
@@ -332,7 +349,7 @@ The capture area is resolved by precedence: `selector` → `clip` → `full_page
|
|
|
332
349
|
| `full_page` | boolean | | Capture the full scrollable page height (default: false, ignored when `selector` or `clip` is provided) |
|
|
333
350
|
| `format` | `png` \| `jpeg` | | Image format (default: `png`) |
|
|
334
351
|
| `output` | `inline` \| `file` \| `both` | ✅ | `inline` embeds the image in the response, `file` saves to disk and returns the path, `both` does both |
|
|
335
|
-
| `path` | string | | Absolute
|
|
352
|
+
| `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>` |
|
|
336
353
|
|
|
337
354
|
---
|
|
338
355
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
const SERVER_NAME = 'simple-scraper-mcp';
|
|
4
|
+
const PACKAGE_NAME = '@imenam/simple-scraper';
|
|
5
|
+
/** "simple-scraper" -> "Simple-scraper" */
|
|
6
|
+
function capitalize(value) {
|
|
7
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
8
|
+
}
|
|
9
|
+
/** "my_project-web" -> "My Project Web" */
|
|
10
|
+
function toDisplayName(folder) {
|
|
11
|
+
return folder
|
|
12
|
+
.split(/[-_]+/)
|
|
13
|
+
.filter(Boolean)
|
|
14
|
+
.map(capitalize)
|
|
15
|
+
.join(' ');
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Valeurs volontairement vides : chaque variable est inactive tant qu'elle n'est
|
|
19
|
+
* pas remplie (PROXY_URL vide = GUI désactivée), donc le fichier généré est
|
|
20
|
+
* utilisable tel quel, jamais à moitié cassé. Le viewport fait exception : un
|
|
21
|
+
* défaut 1920x1080 évite les layouts mobiles/tablette sur la plupart des sites.
|
|
22
|
+
*/
|
|
23
|
+
function buildServerEntry(codeRoot) {
|
|
24
|
+
// Le nom du dossier courant sert de base aux identifiants d'affichage côté proxy.
|
|
25
|
+
const folder = path.basename(codeRoot) || 'scraper';
|
|
26
|
+
return {
|
|
27
|
+
command: 'npx',
|
|
28
|
+
args: ['-y', PACKAGE_NAME],
|
|
29
|
+
env: {
|
|
30
|
+
PROXY_URL: 'http://localhost:',
|
|
31
|
+
APP_GROUP: capitalize(folder),
|
|
32
|
+
PROXY_APP_NAME: `Simple Scraper ${toDisplayName(folder)}`,
|
|
33
|
+
PROXY_APP_PATH: `/simple-scraper-${folder}`,
|
|
34
|
+
PUPPETEER_VIEWPORT_WIDTH: '1920',
|
|
35
|
+
PUPPETEER_VIEWPORT_HEIGHT: '1080',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const GENERATED_VARS = [
|
|
40
|
+
['PROXY_URL', 'URL du proxy pour exposer la GUI (compléter le port)'],
|
|
41
|
+
['APP_GROUP', 'groupe d\'affichage dans le dashboard du proxy'],
|
|
42
|
+
['PROXY_APP_NAME', 'nom affiché de la GUI dans le dashboard du proxy'],
|
|
43
|
+
['PROXY_APP_PATH', 'chemin de montage de la GUI derrière le proxy'],
|
|
44
|
+
['PUPPETEER_VIEWPORT_WIDTH', 'largeur du viewport en pixels CSS'],
|
|
45
|
+
['PUPPETEER_VIEWPORT_HEIGHT', 'hauteur du viewport en pixels CSS'],
|
|
46
|
+
];
|
|
47
|
+
const OPTIONAL_VARS = [
|
|
48
|
+
['PUPPETEER_DEVICE_SCALE_FACTOR', 'pixels physiques par pixel CSS (2 = screenshots retina)'],
|
|
49
|
+
['PUPPETEER_HEADLESS', 'false pour afficher la fenêtre Chromium'],
|
|
50
|
+
['PUPPETEER_TIMEOUT', 'timeout de navigation en ms (défaut : 30000)'],
|
|
51
|
+
['COOKIES_DIR', 'dossier de fichiers cookies au format Netscape (.txt)'],
|
|
52
|
+
['MCP_LOG_DIR', 'répertoire des logs'],
|
|
53
|
+
['SCRAPER_MAX_SESSIONS', 'nombre max de sessions interactives (défaut : 5)'],
|
|
54
|
+
['SCRAPER_SESSION_TTL_MS', 'TTL d\'inactivité des sessions en ms (défaut : 600000)'],
|
|
55
|
+
];
|
|
56
|
+
export function runSetupMcp({ cwd, force }) {
|
|
57
|
+
const target = path.join(cwd, '.mcp.json');
|
|
58
|
+
let config = {};
|
|
59
|
+
const exists = fs.existsSync(target);
|
|
60
|
+
if (exists) {
|
|
61
|
+
const raw = fs.readFileSync(target, 'utf-8').trim();
|
|
62
|
+
if (raw !== '') {
|
|
63
|
+
try {
|
|
64
|
+
config = JSON.parse(raw);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
console.error(`Erreur : ${target} contient du JSON invalide (${err.message}).`);
|
|
68
|
+
console.error('Corrigez le fichier ou supprimez-le, puis relancez la commande.');
|
|
69
|
+
return 1;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (typeof config !== 'object' || config === null || Array.isArray(config)) {
|
|
73
|
+
console.error(`Erreur : ${target} ne contient pas un objet JSON.`);
|
|
74
|
+
return 1;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (typeof config.mcpServers !== 'object' || config.mcpServers === null || Array.isArray(config.mcpServers)) {
|
|
78
|
+
config.mcpServers = {};
|
|
79
|
+
}
|
|
80
|
+
const alreadyPresent = Object.prototype.hasOwnProperty.call(config.mcpServers, SERVER_NAME);
|
|
81
|
+
if (alreadyPresent && !force) {
|
|
82
|
+
console.error(`Le serveur "${SERVER_NAME}" est déjà configuré dans ${target}.`);
|
|
83
|
+
console.error('Relancez avec --force pour écraser cette entrée (les autres serveurs sont préservés).');
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
86
|
+
config.mcpServers[SERVER_NAME] = buildServerEntry(cwd);
|
|
87
|
+
fs.writeFileSync(target, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
88
|
+
const action = alreadyPresent ? 'remplacée' : exists ? 'ajoutée' : 'créée';
|
|
89
|
+
console.log(`Configuration ${SERVER_NAME} ${action} dans ${target}`);
|
|
90
|
+
console.log('');
|
|
91
|
+
console.log('À compléter dans le champ "env" :');
|
|
92
|
+
for (const [name, description] of GENERATED_VARS) {
|
|
93
|
+
console.log(` ${name.padEnd(32)} ${description}`);
|
|
94
|
+
}
|
|
95
|
+
console.log('');
|
|
96
|
+
console.log('Variables optionnelles à ajouter au besoin :');
|
|
97
|
+
for (const [name, description] of OPTIONAL_VARS) {
|
|
98
|
+
console.log(` ${name.padEnd(32)} ${description}`);
|
|
99
|
+
}
|
|
100
|
+
console.log('');
|
|
101
|
+
console.log('Une variable laissée vide est simplement inactive.');
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,12 @@ import { registerSessionRpc } from './session-rpc.js';
|
|
|
15
15
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
16
|
const rootDir = path.resolve(__dirname, '..');
|
|
17
17
|
const envPath = path.join(rootDir, '.env');
|
|
18
|
+
// Les flags CLI sont traités avant toute initialisation (dotenv, logs, GUI) :
|
|
19
|
+
// ils s'exécutent dans le répertoire de l'utilisateur, pas dans un serveur MCP.
|
|
20
|
+
if (process.argv.slice(2).includes('--claude-setup-mcp')) {
|
|
21
|
+
const { runSetupMcp } = await import('./cli/setup-mcp.js');
|
|
22
|
+
process.exit(runSetupMcp({ cwd: process.cwd(), force: process.argv.slice(2).includes('--force') }));
|
|
23
|
+
}
|
|
18
24
|
dotenv.config({ path: envPath, override: true });
|
|
19
25
|
setupLogging('MASTER');
|
|
20
26
|
const launcher = new GuiLauncher({
|
|
@@ -592,7 +598,13 @@ server.tool('screenshot', 'Take a screenshot of a page. Provide either session_i
|
|
|
592
598
|
full_page: z.boolean().optional().describe('Capture the full scrollable page height (default: false, ignored when selector or clip is provided)'),
|
|
593
599
|
format: z.enum(['png', 'jpeg']).optional().describe('Image format (default: png)'),
|
|
594
600
|
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'),
|
|
595
|
-
path: z
|
|
601
|
+
path: z
|
|
602
|
+
.string()
|
|
603
|
+
.refine(p => path.isAbsolute(p), {
|
|
604
|
+
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.',
|
|
605
|
+
})
|
|
606
|
+
.optional()
|
|
607
|
+
.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>'),
|
|
596
608
|
}, async ({ session_id, url, wait_for, timeout, selector, clip, viewport, full_page, format, output, path: filePath }) => {
|
|
597
609
|
const effectiveTimeout = timeout ?? getDefaultTimeout();
|
|
598
610
|
const fmt = format ?? 'png';
|
|
@@ -646,7 +658,8 @@ server.tool('screenshot', 'Take a screenshot of a page. Provide either session_i
|
|
|
646
658
|
imageBuffer = Buffer.from(await page.screenshot({ type: fmt, fullPage: full_page ?? false }));
|
|
647
659
|
}
|
|
648
660
|
const base64 = imageBuffer.toString('base64');
|
|
649
|
-
// Resolve file path
|
|
661
|
+
// Resolve file path. filePath is guaranteed absolute by the schema; resolve()
|
|
662
|
+
// is kept only to normalise separators and any ".." segments.
|
|
650
663
|
const resolvedPath = (() => {
|
|
651
664
|
if (filePath)
|
|
652
665
|
return path.resolve(filePath);
|