@webclaw/mcp 0.6.15

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.
Files changed (3) hide show
  1. package/README.md +71 -0
  2. package/package.json +39 -0
  3. package/webclaw-mcp.mjs +266 -0
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # @webclaw/mcp
2
+
3
+ **Clean web access for AI agents, over MCP.** Turn any URL into markdown, JSON, or LLM-ready context — including pages that block bots or need JavaScript — straight from Claude, Cursor, and any MCP client.
4
+
5
+ Zero-install launcher for the [webclaw](https://webclaw.io) MCP server. `npx @webclaw/mcp` downloads a prebuilt `webclaw-mcp` binary once (verified against the release `SHA256SUMS`), caches it, and runs it as an MCP stdio server. No Rust build, no global install. It runs on your machine; the hosted webclaw cloud is used only for the tools that need it, and only when you set `WEBCLAW_API_KEY`.
6
+
7
+ ## Add to your MCP client
8
+
9
+ Point any stdio MCP client at the npx command — Claude Desktop, Cursor, Windsurf, Antigravity (`mcpServers` JSON):
10
+
11
+ ```json
12
+ {
13
+ "mcpServers": {
14
+ "webclaw": {
15
+ "command": "npx",
16
+ "args": ["-y", "@webclaw/mcp"]
17
+ }
18
+ }
19
+ }
20
+ ```
21
+
22
+ Add a key to unlock the cloud-backed tools (bot-protection bypass, JS rendering, web search, research, lead enrichment):
23
+
24
+ ```json
25
+ {
26
+ "mcpServers": {
27
+ "webclaw": {
28
+ "command": "npx",
29
+ "args": ["-y", "@webclaw/mcp"],
30
+ "env": { "WEBCLAW_API_KEY": "wc_your_key" }
31
+ }
32
+ }
33
+ }
34
+ ```
35
+
36
+ Claude Code:
37
+
38
+ ```bash
39
+ claude mcp add webclaw -- npx -y @webclaw/mcp
40
+ ```
41
+
42
+ Or run `npx create-webclaw` to auto-detect your AI tools and write their configs for you.
43
+
44
+ ## Tools (14)
45
+
46
+ scrape, search, crawl, map, batch, extract, summarize, diff, brand, research, lead, lead_batch, plus `list_extractors` / `vertical_scrape` for 30+ site-specific extractors (Amazon, GitHub, Reddit, YouTube, npm, PyPI, and more).
47
+
48
+ - **No key needed:** scrape, crawl, map, batch, diff, brand, list_extractors, vertical_scrape.
49
+ - **Needs an LLM** (local Ollama or a provider key): extract, summarize.
50
+ - **Needs `WEBCLAW_API_KEY`:** search, research, lead, lead_batch — plus automatic bot-protection bypass and JS rendering for the fetch tools.
51
+
52
+ Get a key at [webclaw.io](https://webclaw.io).
53
+
54
+ ## Environment
55
+
56
+ | Variable | Purpose |
57
+ |---|---|
58
+ | `WEBCLAW_API_KEY` | Enables cloud-backed tools (optional). |
59
+ | `WEBCLAW_MCP_BIN` | Absolute path to a `webclaw-mcp` binary; skips the download. |
60
+ | `WEBCLAW_MCP_VERSION` | Release tag to install (default: the pinned release). |
61
+ | `WEBCLAW_MCP_CACHE` | Cache directory (default: `~/.cache/webclaw`). |
62
+
63
+ ## Links
64
+
65
+ - **Docs:** [webclaw.io/docs/mcp](https://webclaw.io/docs/mcp)
66
+ - **Source** (CLI, REST API, SDKs, extraction engine): [github.com/0xMassi/webclaw](https://github.com/0xMassi/webclaw)
67
+ - **Hosted API & keys:** [webclaw.io](https://webclaw.io)
68
+
69
+ ## License
70
+
71
+ AGPL-3.0-only
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@webclaw/mcp",
3
+ "version": "0.6.15",
4
+ "description": "Zero-install launcher for the webclaw MCP server: web extraction and anti-bot access for AI agents. Runs `npx @webclaw/mcp`.",
5
+ "type": "module",
6
+ "bin": {
7
+ "webclaw-mcp": "./webclaw-mcp.mjs"
8
+ },
9
+ "mcpName": "io.github.0xMassi/webclaw",
10
+ "files": [
11
+ "webclaw-mcp.mjs",
12
+ "README.md"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18"
16
+ },
17
+ "license": "AGPL-3.0-only",
18
+ "author": "0xMassi",
19
+ "homepage": "https://webclaw.io",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/0xMassi/webclaw.git",
23
+ "directory": "packages/webclaw-mcp"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/0xMassi/webclaw/issues"
27
+ },
28
+ "keywords": [
29
+ "mcp",
30
+ "model-context-protocol",
31
+ "webclaw",
32
+ "web-scraping",
33
+ "web-extraction",
34
+ "ai-agents",
35
+ "cloudflare",
36
+ "anti-bot",
37
+ "llm"
38
+ ]
39
+ }
@@ -0,0 +1,266 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @webclaw/mcp — zero-install launcher for the webclaw MCP server.
4
+ *
5
+ * `npx -y @webclaw/mcp` resolves a prebuilt `webclaw-mcp` binary (downloaded
6
+ * once from the pinned GitHub release, then cached) and execs it as an MCP
7
+ * stdio server. This is what makes webclaw introspectable/installable in MCP
8
+ * clients and registries (Claude Desktop, Cursor, Glama, Smithery, ...) without
9
+ * a Rust build.
10
+ *
11
+ * HARD RULE: stdout carries the MCP JSON-RPC stream. Every diagnostic this
12
+ * launcher emits MUST go to stderr, or it corrupts the protocol. Never
13
+ * console.log here — only logErr().
14
+ *
15
+ * Overrides (env):
16
+ * WEBCLAW_MCP_BIN absolute path to a webclaw-mcp binary; skip download
17
+ * WEBCLAW_MCP_VERSION release tag to install (default: pinned RELEASE_TAG)
18
+ * WEBCLAW_MCP_CACHE cache root (default: ~/.cache/webclaw)
19
+ * GITHUB_TOKEN only used to fetch SHA256SUMS if rate-limited
20
+ */
21
+
22
+ import {
23
+ existsSync,
24
+ mkdirSync,
25
+ createWriteStream,
26
+ readFileSync,
27
+ renameSync,
28
+ copyFileSync,
29
+ rmSync,
30
+ chmodSync,
31
+ } from "node:fs";
32
+ import { homedir, platform, arch } from "node:os";
33
+ import { join } from "node:path";
34
+ import { spawn, execFileSync } from "node:child_process";
35
+ import { createHash } from "node:crypto";
36
+ import https from "node:https";
37
+
38
+ const REPO = "0xMassi/webclaw";
39
+ // Release the wrapper installs. Bump this (and the package version) on each
40
+ // core release, or override at runtime with WEBCLAW_MCP_VERSION.
41
+ const RELEASE_TAG = process.env.WEBCLAW_MCP_VERSION || "v0.6.15";
42
+
43
+ const IS_WINDOWS = platform() === "win32";
44
+ const BIN_NAME = IS_WINDOWS ? "webclaw-mcp.exe" : "webclaw-mcp";
45
+ const CACHE_ROOT =
46
+ process.env.WEBCLAW_MCP_CACHE || join(homedir(), ".cache", "webclaw");
47
+ const CACHE_DIR = join(CACHE_ROOT, RELEASE_TAG);
48
+ const CACHED_BIN = join(CACHE_DIR, BIN_NAME);
49
+
50
+ function logErr(msg) {
51
+ process.stderr.write(`[@webclaw/mcp] ${msg}\n`);
52
+ }
53
+
54
+ function target() {
55
+ const map = {
56
+ "darwin-arm64": "aarch64-apple-darwin",
57
+ "darwin-x64": "x86_64-apple-darwin",
58
+ "linux-x64": "x86_64-unknown-linux-gnu",
59
+ "linux-arm64": "aarch64-unknown-linux-gnu",
60
+ "win32-x64": "x86_64-pc-windows-msvc",
61
+ };
62
+ return map[`${platform()}-${arch()}`] || null;
63
+ }
64
+
65
+ // GET a URL to a Buffer, following redirects. Auth headers are dropped on
66
+ // redirect so a token never reaches the release CDN (its signed URLs reject it).
67
+ function getBuffer(url, headers = {}) {
68
+ return new Promise((resolve, reject) => {
69
+ https
70
+ .get(
71
+ url,
72
+ { headers: { "User-Agent": "@webclaw/mcp", ...headers } },
73
+ (res) => {
74
+ if (
75
+ res.statusCode >= 300 &&
76
+ res.statusCode < 400 &&
77
+ res.headers.location
78
+ ) {
79
+ res.resume();
80
+ return getBuffer(res.headers.location).then(resolve, reject);
81
+ }
82
+ if (res.statusCode !== 200) {
83
+ res.resume();
84
+ return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
85
+ }
86
+ const chunks = [];
87
+ res.on("data", (c) => chunks.push(c));
88
+ res.on("end", () => resolve(Buffer.concat(chunks)));
89
+ res.on("error", reject);
90
+ },
91
+ )
92
+ .on("error", reject);
93
+ });
94
+ }
95
+
96
+ // Stream a URL to a file, following redirects.
97
+ function getFile(url, dest) {
98
+ return new Promise((resolve, reject) => {
99
+ https
100
+ .get(url, { headers: { "User-Agent": "@webclaw/mcp" } }, (res) => {
101
+ if (
102
+ res.statusCode >= 300 &&
103
+ res.statusCode < 400 &&
104
+ res.headers.location
105
+ ) {
106
+ res.resume();
107
+ return getFile(res.headers.location, dest).then(resolve, reject);
108
+ }
109
+ if (res.statusCode !== 200) {
110
+ res.resume();
111
+ return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
112
+ }
113
+ const out = createWriteStream(dest);
114
+ res.pipe(out);
115
+ out.on("finish", () => out.close(() => resolve()));
116
+ out.on("error", reject);
117
+ })
118
+ .on("error", reject);
119
+ });
120
+ }
121
+
122
+ function sha256(path) {
123
+ return createHash("sha256").update(readFileSync(path)).digest("hex");
124
+ }
125
+
126
+ function extract(archivePath, destDir) {
127
+ if (IS_WINDOWS) {
128
+ execFileSync(
129
+ "powershell",
130
+ [
131
+ "-NoProfile",
132
+ "-Command",
133
+ `Expand-Archive -Path '${archivePath}' -DestinationPath '${destDir}' -Force`,
134
+ ],
135
+ { stdio: "ignore" },
136
+ );
137
+ } else {
138
+ execFileSync("tar", ["xzf", archivePath, "-C", destDir], {
139
+ stdio: "ignore",
140
+ });
141
+ }
142
+ }
143
+
144
+ async function ensureBinary() {
145
+ // 0. Explicit local binary (dev / offline / CI).
146
+ const override = process.env.WEBCLAW_MCP_BIN;
147
+ if (override) {
148
+ if (!existsSync(override)) {
149
+ throw new Error(`WEBCLAW_MCP_BIN points at a missing file: ${override}`);
150
+ }
151
+ return override;
152
+ }
153
+
154
+ // 1. Cache hit for this release.
155
+ if (existsSync(CACHED_BIN)) return CACHED_BIN;
156
+
157
+ // 2. Download, verify, extract, cache.
158
+ const tgt = target();
159
+ if (!tgt) {
160
+ throw new Error(
161
+ `unsupported platform ${platform()}-${arch()} — install the webclaw-mcp binary manually ` +
162
+ `(https://github.com/${REPO}/releases) and set WEBCLAW_MCP_BIN`,
163
+ );
164
+ }
165
+ const ext = IS_WINDOWS ? "zip" : "tar.gz";
166
+ const assetName = `webclaw-${RELEASE_TAG}-${tgt}.${ext}`;
167
+ const base = `https://github.com/${REPO}/releases/download/${RELEASE_TAG}`;
168
+
169
+ mkdirSync(CACHE_DIR, { recursive: true });
170
+ const archivePath = join(CACHE_DIR, assetName);
171
+ const tmpPath = `${archivePath}.download`;
172
+
173
+ logErr(`fetching ${assetName} (first run for ${RELEASE_TAG}) ...`);
174
+ await getFile(`${base}/${assetName}`, tmpPath);
175
+
176
+ // Verify against the release SHA256SUMS. If the sums file can't be fetched we
177
+ // proceed with a warning rather than hard-failing an otherwise-good install.
178
+ try {
179
+ const apiHeaders = process.env.GITHUB_TOKEN
180
+ ? { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` }
181
+ : {};
182
+ const sums = (await getBuffer(`${base}/SHA256SUMS`, apiHeaders)).toString(
183
+ "utf8",
184
+ );
185
+ const line = sums.split("\n").find((l) => l.trim().endsWith(assetName));
186
+ const expected = line ? line.trim().split(/\s+/)[0] : null;
187
+ if (expected) {
188
+ const actual = sha256(tmpPath);
189
+ if (actual !== expected) {
190
+ rmSync(tmpPath, { force: true });
191
+ throw new Error(
192
+ `checksum mismatch for ${assetName}: expected ${expected}, got ${actual}`,
193
+ );
194
+ }
195
+ } else {
196
+ logErr(
197
+ `warning: ${assetName} not found in SHA256SUMS — skipping verification`,
198
+ );
199
+ }
200
+ } catch (e) {
201
+ if (/checksum mismatch/.test(e.message)) throw e;
202
+ logErr(`warning: could not verify checksum (${e.message}) — proceeding`);
203
+ }
204
+
205
+ renameSync(tmpPath, archivePath);
206
+ extract(archivePath, CACHE_DIR);
207
+
208
+ // Archives hold a top-level `webclaw-<tag>-<target>/` dir with all binaries.
209
+ const extractedDir = join(CACHE_DIR, `webclaw-${RELEASE_TAG}-${tgt}`);
210
+ const extractedBin = join(extractedDir, BIN_NAME);
211
+ if (!existsSync(extractedBin)) {
212
+ throw new Error(`binary missing after extract: ${extractedBin}`);
213
+ }
214
+ copyFileSync(extractedBin, CACHED_BIN);
215
+ if (!IS_WINDOWS) chmodSync(CACHED_BIN, 0o755);
216
+
217
+ // Drop the archive and the extra binaries; keep only the cached webclaw-mcp.
218
+ try {
219
+ rmSync(extractedDir, { recursive: true, force: true });
220
+ rmSync(archivePath, { force: true });
221
+ } catch {
222
+ /* non-fatal */
223
+ }
224
+
225
+ logErr(`installed ${BIN_NAME} for ${RELEASE_TAG} → ${CACHED_BIN}`);
226
+ return CACHED_BIN;
227
+ }
228
+
229
+ async function main() {
230
+ let bin;
231
+ try {
232
+ bin = await ensureBinary();
233
+ } catch (e) {
234
+ logErr(`error: ${e.message}`);
235
+ process.exit(1);
236
+ }
237
+
238
+ // Hand off to the real server. stdio:inherit wires the MCP JSON-RPC stream
239
+ // straight through; env passes WEBCLAW_API_KEY and friends unchanged.
240
+ const child = spawn(bin, process.argv.slice(2), {
241
+ stdio: "inherit",
242
+ env: process.env,
243
+ });
244
+
245
+ child.on("error", (e) => {
246
+ logErr(`failed to start webclaw-mcp: ${e.message}`);
247
+ process.exit(1);
248
+ });
249
+ child.on("exit", (code, signal) => {
250
+ if (signal) {
251
+ // Re-raise the signal so the parent's exit reflects it.
252
+ process.kill(process.pid, signal);
253
+ } else {
254
+ process.exit(code ?? 0);
255
+ }
256
+ });
257
+
258
+ // Forward termination signals to the child so clients can stop the server.
259
+ for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) {
260
+ process.on(sig, () => {
261
+ if (!child.killed) child.kill(sig);
262
+ });
263
+ }
264
+ }
265
+
266
+ main();