crw-mcp 0.37.1 → 0.37.2
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/bin/crw-mcp.js +79 -10
- package/package.json +5 -5
package/bin/crw-mcp.js
CHANGED
|
@@ -5,7 +5,9 @@ const crypto = require("crypto");
|
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
const path = require("path");
|
|
7
7
|
const os = require("os");
|
|
8
|
+
const http = require("http");
|
|
8
9
|
const https = require("https");
|
|
10
|
+
const tls = require("tls");
|
|
9
11
|
|
|
10
12
|
const VERSION = require("../package.json").version;
|
|
11
13
|
const REPO = "fastcrw/crw";
|
|
@@ -56,16 +58,55 @@ function cacheDir() {
|
|
|
56
58
|
return path.join(base, "crw-mcp", `v${VERSION}`);
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
// HTTPS_PROXY / HTTP_PROXY support: Node's https ignores them, and restricted
|
|
62
|
+
// networks often only allow egress through one. Plain-http proxies only (the
|
|
63
|
+
// usual corporate setup), tunnelled with CONNECT. ponytail: NO_PROXY is not
|
|
64
|
+
// honoured, add when someone needs a bypass list.
|
|
65
|
+
function proxyAgent() {
|
|
66
|
+
const raw =
|
|
67
|
+
process.env.HTTPS_PROXY || process.env.https_proxy ||
|
|
68
|
+
process.env.HTTP_PROXY || process.env.http_proxy;
|
|
69
|
+
if (!raw) return undefined;
|
|
70
|
+
const proxy = new URL(raw);
|
|
71
|
+
const headers = {};
|
|
72
|
+
if (proxy.username) {
|
|
73
|
+
const cred = `${decodeURIComponent(proxy.username)}:${decodeURIComponent(proxy.password)}`;
|
|
74
|
+
headers["Proxy-Authorization"] = `Basic ${Buffer.from(cred).toString("base64")}`;
|
|
75
|
+
}
|
|
76
|
+
const agent = new https.Agent();
|
|
77
|
+
agent.createConnection = (opts, cb) => {
|
|
78
|
+
const req = http.request({
|
|
79
|
+
host: proxy.hostname,
|
|
80
|
+
port: proxy.port || 80,
|
|
81
|
+
method: "CONNECT",
|
|
82
|
+
path: `${opts.host}:${opts.port || 443}`,
|
|
83
|
+
headers,
|
|
84
|
+
});
|
|
85
|
+
req.once("connect", (res, socket) => {
|
|
86
|
+
if (res.statusCode !== 200) {
|
|
87
|
+
socket.destroy();
|
|
88
|
+
return cb(new Error(`proxy CONNECT failed: HTTP ${res.statusCode}`));
|
|
89
|
+
}
|
|
90
|
+
cb(null, tls.connect({ socket, servername: opts.servername || opts.host }));
|
|
91
|
+
});
|
|
92
|
+
req.once("error", cb);
|
|
93
|
+
req.end();
|
|
94
|
+
};
|
|
95
|
+
return agent;
|
|
96
|
+
}
|
|
97
|
+
|
|
59
98
|
// Always resolves a Buffer: the archive is verified in memory, so an unverified
|
|
60
99
|
// byte never reaches disk at all.
|
|
61
|
-
function httpsGet(url, redirects = 0) {
|
|
100
|
+
function httpsGet(url, extraHeaders = {}, redirects = 0) {
|
|
62
101
|
return new Promise((resolve, reject) => {
|
|
102
|
+
const headers = { "User-Agent": `crw-mcp/${VERSION}`, ...extraHeaders };
|
|
63
103
|
https
|
|
64
|
-
.get(url, { headers
|
|
104
|
+
.get(url, { headers, agent: proxyAgent() }, (res) => {
|
|
65
105
|
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
66
106
|
res.resume();
|
|
67
107
|
if (redirects > 5) return reject(new Error("too many redirects"));
|
|
68
|
-
|
|
108
|
+
// Extra headers are not forwarded: the redirect target is another host.
|
|
109
|
+
return resolve(httpsGet(res.headers.location, {}, redirects + 1));
|
|
69
110
|
}
|
|
70
111
|
if (res.statusCode !== 200) {
|
|
71
112
|
res.resume();
|
|
@@ -80,6 +121,34 @@ function httpsGet(url, redirects = 0) {
|
|
|
80
121
|
});
|
|
81
122
|
}
|
|
82
123
|
|
|
124
|
+
// Same release file through api.github.com, for networks where the
|
|
125
|
+
// github.com download host is blocked but the API host is reachable.
|
|
126
|
+
async function viaApi(name) {
|
|
127
|
+
const tag = `https://api.github.com/repos/${REPO}/releases/tags/v${VERSION}`;
|
|
128
|
+
const release = JSON.parse((await httpsGet(tag)).toString("utf8"));
|
|
129
|
+
const asset = (release.assets || []).find((a) => a.name === name);
|
|
130
|
+
if (!asset) throw new Error(`${name} is not an asset of v${VERSION}`);
|
|
131
|
+
return httpsGet(
|
|
132
|
+
`https://api.github.com/repos/${REPO}/releases/assets/${asset.id}`,
|
|
133
|
+
{ Accept: "application/octet-stream" }
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// A 404 means the release lacks the file, so the API would say the same. Any
|
|
138
|
+
// other failure is a network problem worth one retry through the API host.
|
|
139
|
+
async function fetchAsset(name) {
|
|
140
|
+
try {
|
|
141
|
+
return await httpsGet(`https://github.com/${REPO}/releases/download/v${VERSION}/${name}`);
|
|
142
|
+
} catch (e) {
|
|
143
|
+
if (/^HTTP 404 /.test(e.message)) throw e;
|
|
144
|
+
try {
|
|
145
|
+
return await viaApi(name);
|
|
146
|
+
} catch (e2) {
|
|
147
|
+
throw new Error(`${e.message}; api.github.com fallback: ${e2.message}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
83
152
|
// Parse one entry out of a coreutils-style SHA256SUMS. Written on Linux, read
|
|
84
153
|
// here on every platform, so tolerate CRLF and the "*" binary-mode marker.
|
|
85
154
|
function digestFor(sumsText, asset) {
|
|
@@ -98,14 +167,12 @@ async function fromDownload() {
|
|
|
98
167
|
if (fs.existsSync(bin)) return bin;
|
|
99
168
|
|
|
100
169
|
fs.mkdirSync(dir, { recursive: true });
|
|
101
|
-
const base = `https://github.com/${REPO}/releases/download/v${VERSION}`;
|
|
102
|
-
const url = `${base}/${plat.asset}`;
|
|
103
170
|
|
|
104
171
|
// Fail closed: fetch the expected digest first, so a release without one is
|
|
105
172
|
// refused before anything is downloaded rather than after.
|
|
106
173
|
let sums;
|
|
107
174
|
try {
|
|
108
|
-
sums = (await
|
|
175
|
+
sums = (await fetchAsset("SHA256SUMS")).toString("utf8");
|
|
109
176
|
} catch (e) {
|
|
110
177
|
// Only a 404 means the release genuinely lacks checksums. Reporting a
|
|
111
178
|
// proxy or DNS failure as "our release is broken" sends users to file
|
|
@@ -123,7 +190,7 @@ async function fromDownload() {
|
|
|
123
190
|
|
|
124
191
|
// stderr only: stdout is the MCP (JSON-RPC) channel.
|
|
125
192
|
console.error(`crw-mcp: downloading ${plat.asset} (v${VERSION})...`);
|
|
126
|
-
const data = await
|
|
193
|
+
const data = await fetchAsset(plat.asset);
|
|
127
194
|
|
|
128
195
|
const actual = crypto.createHash("sha256").update(data).digest("hex");
|
|
129
196
|
if (actual !== expected) {
|
|
@@ -168,7 +235,7 @@ async function resolveBinary() {
|
|
|
168
235
|
return fromEnv() || fromPackage() || (await fromDownload());
|
|
169
236
|
}
|
|
170
237
|
|
|
171
|
-
module.exports = { digestFor, fromDownload, cacheDir, plat, binName };
|
|
238
|
+
module.exports = { digestFor, fromDownload, cacheDir, plat, binName, proxyAgent };
|
|
172
239
|
|
|
173
240
|
// Only run when invoked as the CLI, so tests can require this file.
|
|
174
241
|
if (require.main === module) {
|
|
@@ -198,8 +265,10 @@ if (require.main === module) {
|
|
|
198
265
|
.catch((err) => {
|
|
199
266
|
console.error(
|
|
200
267
|
`crw-mcp: could not locate or download the ${key} binary.\n ${err.message}\n` +
|
|
201
|
-
`
|
|
202
|
-
`
|
|
268
|
+
` Manual install: download ${plat.asset} from\n` +
|
|
269
|
+
` https://github.com/${REPO}/releases/tag/v${VERSION}, check it against SHA256SUMS,\n` +
|
|
270
|
+
` and put ${binName} in ${cacheDir()}\n` +
|
|
271
|
+
` (or set CRW_MCP_BINARY=/path/to/${binName}). HTTPS_PROXY is honoured.`
|
|
203
272
|
);
|
|
204
273
|
process.exit(1);
|
|
205
274
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crw-mcp",
|
|
3
|
-
"version": "0.37.
|
|
3
|
+
"version": "0.37.2",
|
|
4
4
|
"description": "MCP server for CRW web scraper — scrape, crawl, map, extract, search, and PDF-parse tools for AI agents",
|
|
5
5
|
"license": "AGPL-3.0",
|
|
6
6
|
"author": "fastCRW <hello@fastcrw.com>",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"skills/SKILL.md"
|
|
39
39
|
],
|
|
40
40
|
"optionalDependencies": {
|
|
41
|
-
"crw-mcp-darwin-x64": "0.37.
|
|
42
|
-
"crw-mcp-darwin-arm64": "0.37.
|
|
43
|
-
"crw-mcp-linux-x64": "0.37.
|
|
44
|
-
"crw-mcp-linux-arm64": "0.37.
|
|
41
|
+
"crw-mcp-darwin-x64": "0.37.2",
|
|
42
|
+
"crw-mcp-darwin-arm64": "0.37.2",
|
|
43
|
+
"crw-mcp-linux-x64": "0.37.2",
|
|
44
|
+
"crw-mcp-linux-arm64": "0.37.2"
|
|
45
45
|
}
|
|
46
46
|
}
|