@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.
- package/README.md +71 -0
- package/package.json +39 -0
- 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
|
+
}
|
package/webclaw-mcp.mjs
ADDED
|
@@ -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();
|