scorchcrawl-mcp 2.1.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "scorchcrawl-mcp",
3
- "version": "2.1.1",
4
- "description": "MCP client for ScorchCrawl connect Copilot to a self-hosted stealth web scraping server",
3
+ "version": "2.3.0",
4
+ "description": "Open-source MCP server for web scraping with stealth bot-detection bypass.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "scorchcrawl-mcp": "dist/cli.js"
7
+ "scorchcrawl-mcp": "dist/index.js"
8
8
  },
9
9
  "files": [
10
10
  "dist"
@@ -13,18 +13,29 @@
13
13
  "access": "public"
14
14
  },
15
15
  "scripts": {
16
- "build": "tsc",
17
- "start": "node dist/cli.js",
18
- "test": "npm run test:unit && npm run test:integration && npm run test:e2e",
19
- "test:unit": "npm run build && node --test test/unit/*.test.mjs",
20
- "test:integration": "npm run build && node --test test/integration/*.test.mjs",
21
- "test:e2e": "npm run build && node --test test/e2e/*.test.mjs",
22
- "smoke": "node test/smoke/smoke.js",
16
+ "build": "tsc && node -e \"require('fs').chmodSync('dist/index.js', '755')\"",
17
+ "start": "node dist/index.js",
18
+ "start:server": "HTTP_STREAMABLE_SERVER=true node dist/index.js",
19
+ "dev": "tsc --watch",
20
+ "test": "vitest run",
21
+ "test:watch": "vitest",
22
+ "test:integration": "vitest run --config vitest.integration.config.ts",
23
+ "test:coverage": "vitest run --coverage",
24
+ "lint": "eslint src/**/*.ts",
25
+ "lint:fix": "eslint src/**/*.ts --fix",
26
+ "format": "prettier --write .",
23
27
  "prepare": "npm run build"
24
28
  },
25
29
  "license": "AGPL-3.0",
26
30
  "dependencies": {
27
- "dotenv": "^17.2.2"
31
+ "@mendable/firecrawl-js": "^4.9.3",
32
+ "cheerio": "^1.2.0",
33
+ "dotenv": "^17.2.2",
34
+ "firecrawl-fastmcp": "^1.0.4",
35
+ "turndown": "^7.2.2",
36
+ "typescript": "^5.9.2",
37
+ "uuid": "^11.1.0",
38
+ "zod": "^4.1.5"
28
39
  },
29
40
  "engines": {
30
41
  "node": ">=18.0.0"
@@ -32,12 +43,22 @@
32
43
  "keywords": [
33
44
  "mcp",
34
45
  "scorchcrawl",
35
- "copilot",
36
46
  "web-scraping",
37
- "stealth"
47
+ "stealth",
48
+ "bot-detection-bypass",
49
+ "crawler",
50
+ "content-extraction"
38
51
  ],
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "https://github.com/user/scorchcrawl.git"
55
+ },
56
+ "author": "ScorchCrawl Contributors",
57
+ "homepage": "https://github.com/user/scorchcrawl#readme",
39
58
  "devDependencies": {
40
- "@types/node": "^25.3.5",
41
- "typescript": "^5.9.3"
59
+ "@types/node": "^22.0.0",
60
+ "@types/turndown": "^5.0.5",
61
+ "@types/uuid": "^10.0.0",
62
+ "vitest": "^3.2.1"
42
63
  }
43
64
  }
package/dist/cli.js DELETED
@@ -1,207 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * scorchcrawl-mcp CLI
4
- *
5
- * A thin wrapper that connects to a ScorchCrawl server and exposes its MCP
6
- * tools over stdio transport. This lets MCP clients (VS Code, Copilot CLI,
7
- * etc.) use a remote ScorchCrawl server as if it were a local MCP server.
8
- *
9
- * Usage:
10
- * SCORCHCRAWL_URL=http://localhost:24787 scorchcrawl-mcp
11
- *
12
- * Or with a remote server + API key:
13
- * SCORCHCRAWL_URL=https://your-server.com/mcp-api/scorchcrawl/YOUR_KEY scorchcrawl-mcp
14
- *
15
- * Environment variables:
16
- * SCORCHCRAWL_URL - Base URL of the ScorchCrawl MCP server (required)
17
- * GITHUB_TOKEN - GitHub PAT for Copilot SDK agent (optional, sent as x-copilot-token)
18
- * SCORCHCRAWL_LOCAL_PROXY - Set to "true" to route scraping through your local IP
19
- */
20
- import { pathToFileURL } from 'url';
21
- async function loadDotenv() {
22
- try {
23
- const dotenv = await import('dotenv');
24
- dotenv.config({ quiet: true });
25
- }
26
- catch {
27
- // Allow direct source execution in constrained environments.
28
- }
29
- }
30
- const DEFAULT_MCP_URL = 'http://localhost:24787';
31
- function trimTrailingSlash(value) {
32
- return value.replace(/\/+$/, '');
33
- }
34
- function normalizeServerBaseUrl(value) {
35
- const trimmed = trimTrailingSlash(value.trim());
36
- if (!trimmed) {
37
- return DEFAULT_MCP_URL;
38
- }
39
- if (trimmed.endsWith('/mcp')) {
40
- return trimmed.slice(0, -4);
41
- }
42
- return trimmed;
43
- }
44
- function deriveLocalMcpUrl(apiUrl) {
45
- const localHosts = new Set(['localhost', '127.0.0.1', '::1']);
46
- if (!localHosts.has(apiUrl.hostname)) {
47
- return null;
48
- }
49
- if (apiUrl.port === '24786') {
50
- const derived = new URL(apiUrl.toString());
51
- derived.port = '24787';
52
- derived.pathname = '';
53
- derived.search = '';
54
- derived.hash = '';
55
- return normalizeServerBaseUrl(derived.toString());
56
- }
57
- return null;
58
- }
59
- export function resolveServerConfig(env = process.env) {
60
- const warnings = [];
61
- const directUrl = env.SCORCHCRAWL_URL?.trim();
62
- if (directUrl) {
63
- return {
64
- serverBaseUrl: normalizeServerBaseUrl(directUrl),
65
- source: 'SCORCHCRAWL_URL',
66
- warnings,
67
- };
68
- }
69
- const apiUrlValue = env.SCORCHCRAWL_API_URL?.trim();
70
- if (!apiUrlValue) {
71
- return {
72
- serverBaseUrl: DEFAULT_MCP_URL,
73
- source: 'default',
74
- warnings,
75
- };
76
- }
77
- let parsed;
78
- try {
79
- parsed = new URL(apiUrlValue);
80
- }
81
- catch {
82
- throw new Error(`Invalid SCORCHCRAWL_API_URL: ${apiUrlValue}. Set SCORCHCRAWL_URL to your MCP server URL instead.`);
83
- }
84
- if (parsed.port === '24787' || parsed.pathname.endsWith('/mcp')) {
85
- warnings.push('SCORCHCRAWL_API_URL is being used as an MCP endpoint alias. Prefer SCORCHCRAWL_URL for the npm client.');
86
- return {
87
- serverBaseUrl: normalizeServerBaseUrl(apiUrlValue),
88
- source: 'SCORCHCRAWL_API_URL',
89
- warnings,
90
- };
91
- }
92
- const derivedLocalUrl = deriveLocalMcpUrl(parsed);
93
- if (derivedLocalUrl) {
94
- warnings.push(`SCORCHCRAWL_API_URL points to the scraping engine (${apiUrlValue}). The npm client needs the MCP server, so it will use ${derivedLocalUrl} instead.`);
95
- return {
96
- serverBaseUrl: derivedLocalUrl,
97
- source: 'SCORCHCRAWL_API_URL',
98
- warnings,
99
- };
100
- }
101
- throw new Error(`SCORCHCRAWL_API_URL points to the scraping engine (${apiUrlValue}), not the MCP server. Set SCORCHCRAWL_URL to the MCP endpoint, for example http://localhost:24787.`);
102
- }
103
- export function isLocalProxyEnabled(env = process.env) {
104
- return env.SCORCHCRAWL_LOCAL_PROXY === 'true';
105
- }
106
- /**
107
- * Forward a JSON-RPC request to the remote ScorchCrawl server.
108
- */
109
- export async function forwardToServer(request, serverBaseUrl, githubToken) {
110
- const url = `${serverBaseUrl}/mcp`;
111
- const headers = {
112
- 'Content-Type': 'application/json',
113
- 'Accept': 'application/json, text/event-stream',
114
- };
115
- if (githubToken) {
116
- headers['x-copilot-token'] = githubToken;
117
- }
118
- try {
119
- const response = await fetch(url, {
120
- method: 'POST',
121
- headers,
122
- body: JSON.stringify(request),
123
- });
124
- const text = await response.text();
125
- // Handle SSE responses (event: message\ndata: {...})
126
- if (text.startsWith('event:') || text.startsWith('data:')) {
127
- const dataLine = text.split('\n').find(l => l.startsWith('data:'));
128
- if (dataLine) {
129
- return JSON.parse(dataLine.slice(5).trim());
130
- }
131
- }
132
- // Handle direct JSON responses
133
- if (text.trim().startsWith('{')) {
134
- return JSON.parse(text);
135
- }
136
- return {
137
- jsonrpc: '2.0',
138
- error: { code: -32603, message: `Unexpected response: ${text.substring(0, 200)}` },
139
- id: request.id,
140
- };
141
- }
142
- catch (err) {
143
- return {
144
- jsonrpc: '2.0',
145
- error: { code: -32603, message: `Connection failed: ${err.message}` },
146
- id: request.id,
147
- };
148
- }
149
- }
150
- /**
151
- * Read JSON-RPC messages from stdin and forward to the server.
152
- */
153
- async function main() {
154
- await loadDotenv();
155
- const { serverBaseUrl, warnings } = resolveServerConfig(process.env);
156
- const githubToken = process.env.GITHUB_TOKEN;
157
- const localProxy = isLocalProxyEnabled(process.env);
158
- if (!serverBaseUrl || serverBaseUrl === DEFAULT_MCP_URL) {
159
- process.stderr.write(`[scorchcrawl-mcp] Connecting to ${serverBaseUrl}\n` +
160
- `[scorchcrawl-mcp] Set SCORCHCRAWL_URL to change the server address\n`);
161
- }
162
- else {
163
- process.stderr.write(`[scorchcrawl-mcp] Connecting to ${serverBaseUrl}\n`);
164
- }
165
- for (const warning of warnings) {
166
- process.stderr.write(`[scorchcrawl-mcp] ${warning}\n`);
167
- }
168
- if (localProxy) {
169
- process.stderr.write('[scorchcrawl-mcp] Local proxy mode: ON (scraping through your IP)\n');
170
- process.stderr.write('[scorchcrawl-mcp] Note: this only works when the MCP server itself is running on this machine.\n');
171
- }
172
- // Read from stdin line by line
173
- let buffer = '';
174
- process.stdin.setEncoding('utf8');
175
- process.stdin.on('data', async (chunk) => {
176
- buffer += chunk;
177
- // Process complete lines
178
- const lines = buffer.split('\n');
179
- buffer = lines.pop() || '';
180
- for (const line of lines) {
181
- const trimmed = line.trim();
182
- if (!trimmed)
183
- continue;
184
- try {
185
- const request = JSON.parse(trimmed);
186
- const response = await forwardToServer(request, serverBaseUrl, githubToken);
187
- process.stdout.write(JSON.stringify(response) + '\n');
188
- }
189
- catch (err) {
190
- const errorResponse = {
191
- jsonrpc: '2.0',
192
- error: { code: -32700, message: `Parse error: ${err.message}` },
193
- };
194
- process.stdout.write(JSON.stringify(errorResponse) + '\n');
195
- }
196
- }
197
- });
198
- process.stdin.on('end', () => {
199
- process.exit(0);
200
- });
201
- }
202
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
203
- main().catch((err) => {
204
- process.stderr.write(`[scorchcrawl-mcp] Fatal error: ${err.message}\n`);
205
- process.exit(1);
206
- });
207
- }