crw-mcp 0.6.0 → 0.16.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/bin/crw-mcp.js +114 -22
- package/bin/init.js +4 -2
- package/package.json +9 -8
- package/skills/SKILL.md +35 -10
package/bin/crw-mcp.js
CHANGED
|
@@ -7,41 +7,133 @@ if (process.argv[2] === "init") {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
const { spawnSync } = require("child_process");
|
|
10
|
+
const fs = require("fs");
|
|
10
11
|
const path = require("path");
|
|
12
|
+
const os = require("os");
|
|
13
|
+
const https = require("https");
|
|
11
14
|
|
|
15
|
+
const VERSION = require("../package.json").version;
|
|
16
|
+
const REPO = "us/crw";
|
|
17
|
+
|
|
18
|
+
// Each platform has a prebuilt npm package (fast path, installed via
|
|
19
|
+
// optionalDependencies) AND a matching GitHub release asset (fallback path,
|
|
20
|
+
// downloaded on first run). The fallback makes the launcher robust to a
|
|
21
|
+
// missing/unpublishable platform package (e.g. npm security-held names) — no
|
|
22
|
+
// single package can freeze the channel, mirroring the PyPI launcher.
|
|
12
23
|
const PLATFORMS = {
|
|
13
|
-
"darwin-x64": "crw-mcp-darwin-x64",
|
|
14
|
-
"darwin-arm64": "crw-mcp-darwin-arm64",
|
|
15
|
-
"linux-x64": "crw-mcp-linux-x64",
|
|
16
|
-
"linux-arm64": "crw-mcp-linux-arm64",
|
|
17
|
-
"win32-x64": "crw-mcp-win32-x64",
|
|
18
|
-
"win32-arm64": "crw-mcp-win32-arm64",
|
|
24
|
+
"darwin-x64": { pkg: "crw-mcp-darwin-x64", asset: "crw-mcp-darwin-x64.tar.gz" },
|
|
25
|
+
"darwin-arm64": { pkg: "crw-mcp-darwin-arm64", asset: "crw-mcp-darwin-arm64.tar.gz" },
|
|
26
|
+
"linux-x64": { pkg: "crw-mcp-linux-x64", asset: "crw-mcp-linux-x64.tar.gz" },
|
|
27
|
+
"linux-arm64": { pkg: "crw-mcp-linux-arm64", asset: "crw-mcp-linux-arm64.tar.gz" },
|
|
28
|
+
"win32-x64": { pkg: "crw-mcp-win32-x64", asset: "crw-mcp-win32-x64.zip" },
|
|
29
|
+
"win32-arm64": { pkg: "crw-mcp-win32-arm64", asset: "crw-mcp-win32-arm64.zip" },
|
|
19
30
|
};
|
|
20
31
|
|
|
21
32
|
const key = `${process.platform}-${process.arch}`;
|
|
22
|
-
const
|
|
33
|
+
const plat = PLATFORMS[key];
|
|
23
34
|
|
|
24
|
-
if (!
|
|
35
|
+
if (!plat) {
|
|
25
36
|
console.error(
|
|
26
37
|
`crw-mcp: unsupported platform ${key}. Supported: ${Object.keys(PLATFORMS).join(", ")}`
|
|
27
38
|
);
|
|
28
39
|
process.exit(1);
|
|
29
40
|
}
|
|
30
41
|
|
|
31
|
-
const
|
|
42
|
+
const binName = `crw-mcp${process.platform === "win32" ? ".exe" : ""}`;
|
|
32
43
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
// 1. Explicit override.
|
|
45
|
+
function fromEnv() {
|
|
46
|
+
const p = process.env.CRW_MCP_BINARY || process.env.CRW_BINARY;
|
|
47
|
+
return p && fs.existsSync(p) ? p : null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 2. Prebuilt platform package (fast path — no download).
|
|
51
|
+
function fromPackage() {
|
|
52
|
+
try {
|
|
53
|
+
const bin = path.join(
|
|
54
|
+
path.dirname(require.resolve(`${plat.pkg}/package.json`)),
|
|
55
|
+
binName
|
|
56
|
+
);
|
|
57
|
+
return fs.existsSync(bin) ? bin : null;
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function cacheDir() {
|
|
64
|
+
const base =
|
|
65
|
+
process.platform === "win32"
|
|
66
|
+
? process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local")
|
|
67
|
+
: process.env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache");
|
|
68
|
+
return path.join(base, "crw-mcp", `v${VERSION}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function httpsGet(url, dest, redirects = 0) {
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
https
|
|
74
|
+
.get(url, { headers: { "User-Agent": `crw-mcp/${VERSION}` } }, (res) => {
|
|
75
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
76
|
+
res.resume();
|
|
77
|
+
if (redirects > 5) return reject(new Error("too many redirects"));
|
|
78
|
+
return resolve(httpsGet(res.headers.location, dest, redirects + 1));
|
|
79
|
+
}
|
|
80
|
+
if (res.statusCode !== 200) {
|
|
81
|
+
res.resume();
|
|
82
|
+
return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
|
|
83
|
+
}
|
|
84
|
+
const file = fs.createWriteStream(dest);
|
|
85
|
+
res.pipe(file);
|
|
86
|
+
file.on("finish", () => file.close(() => resolve()));
|
|
87
|
+
file.on("error", reject);
|
|
88
|
+
})
|
|
89
|
+
.on("error", reject);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 3. Download the matching release asset and extract it (cached per version).
|
|
94
|
+
async function fromDownload() {
|
|
95
|
+
const dir = cacheDir();
|
|
96
|
+
const bin = path.join(dir, binName);
|
|
97
|
+
if (fs.existsSync(bin)) return bin;
|
|
98
|
+
|
|
99
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
100
|
+
const archive = path.join(dir, plat.asset);
|
|
101
|
+
const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${plat.asset}`;
|
|
102
|
+
|
|
103
|
+
// stderr only: stdout is the MCP (JSON-RPC) channel.
|
|
104
|
+
console.error(`crw-mcp: downloading ${plat.asset} (v${VERSION})...`);
|
|
105
|
+
await httpsGet(url, archive);
|
|
106
|
+
|
|
107
|
+
const tarArgs = plat.asset.endsWith(".zip")
|
|
108
|
+
? ["-xf", archive, "-C", dir]
|
|
109
|
+
: ["-xzf", archive, "-C", dir];
|
|
110
|
+
const x = spawnSync("tar", tarArgs, { stdio: "inherit" });
|
|
111
|
+
if (x.status !== 0) {
|
|
112
|
+
throw new Error(`failed to extract ${plat.asset} (tar exit ${x.status})`);
|
|
113
|
+
}
|
|
114
|
+
fs.rmSync(archive, { force: true });
|
|
115
|
+
|
|
116
|
+
if (!fs.existsSync(bin)) {
|
|
117
|
+
throw new Error(`binary ${binName} not found in ${plat.asset}`);
|
|
118
|
+
}
|
|
119
|
+
if (process.platform !== "win32") fs.chmodSync(bin, 0o755);
|
|
120
|
+
return bin;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function resolveBinary() {
|
|
124
|
+
return fromEnv() || fromPackage() || (await fromDownload());
|
|
44
125
|
}
|
|
45
126
|
|
|
46
|
-
|
|
47
|
-
|
|
127
|
+
resolveBinary()
|
|
128
|
+
.then((bin) => {
|
|
129
|
+
const result = spawnSync(bin, process.argv.slice(2), { stdio: "inherit" });
|
|
130
|
+
process.exit(result.status ?? 1);
|
|
131
|
+
})
|
|
132
|
+
.catch((err) => {
|
|
133
|
+
console.error(
|
|
134
|
+
`crw-mcp: could not locate or download the ${key} binary.\n ${err.message}\n` +
|
|
135
|
+
` Set CRW_MCP_BINARY=/path/to/crw-mcp to use a local build, or install\n` +
|
|
136
|
+
` from https://github.com/${REPO}/releases.`
|
|
137
|
+
);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
});
|
package/bin/init.js
CHANGED
|
@@ -123,11 +123,13 @@ Without flags, auto-detects installed agents and installs to all of them.
|
|
|
123
123
|
console.log(` export CRW_API_KEY=${apiKey}`);
|
|
124
124
|
} else {
|
|
125
125
|
console.log("\nCloud mode (fastcrw.com):");
|
|
126
|
+
console.log(" 500 free one-time credits, managed infra — https://fastcrw.com");
|
|
126
127
|
console.log(" export CRW_API_KEY=fc-your-key");
|
|
127
|
-
console.log(" export CRW_API_URL=https://fastcrw.com
|
|
128
|
+
console.log(" export CRW_API_URL=https://api.fastcrw.com");
|
|
129
|
+
console.log(" Terms of Service: https://fastcrw.com/terms");
|
|
128
130
|
}
|
|
129
131
|
|
|
130
|
-
console.log("\nLocal mode (
|
|
132
|
+
console.log("\nLocal mode (free, same binary, fully capable):");
|
|
131
133
|
console.log(" npx crw-mcp");
|
|
132
134
|
console.log("\nDocs: https://fastcrw.com/docs");
|
|
133
135
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crw-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server for CRW web scraper — scrape, crawl, and
|
|
3
|
+
"version": "0.16.0",
|
|
4
|
+
"description": "MCP server for CRW web scraper — scrape, crawl, map, search, and PDF-parse tools for AI agents",
|
|
5
5
|
"license": "AGPL-3.0",
|
|
6
6
|
"homepage": "https://github.com/us/crw",
|
|
7
7
|
"repository": {
|
|
@@ -21,17 +21,18 @@
|
|
|
21
21
|
"bin": {
|
|
22
22
|
"crw-mcp": "bin/crw-mcp.js"
|
|
23
23
|
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18"
|
|
26
|
+
},
|
|
24
27
|
"files": [
|
|
25
28
|
"bin/crw-mcp.js",
|
|
26
29
|
"bin/init.js",
|
|
27
30
|
"skills/SKILL.md"
|
|
28
31
|
],
|
|
29
32
|
"optionalDependencies": {
|
|
30
|
-
"crw-mcp-darwin-x64": "0.
|
|
31
|
-
"crw-mcp-darwin-arm64": "0.
|
|
32
|
-
"crw-mcp-linux-x64": "0.
|
|
33
|
-
"crw-mcp-linux-arm64": "0.
|
|
34
|
-
"crw-mcp-win32-x64": "0.3.5",
|
|
35
|
-
"crw-mcp-win32-arm64": "0.3.5"
|
|
33
|
+
"crw-mcp-darwin-x64": "0.16.0",
|
|
34
|
+
"crw-mcp-darwin-arm64": "0.16.0",
|
|
35
|
+
"crw-mcp-linux-x64": "0.16.0",
|
|
36
|
+
"crw-mcp-linux-arm64": "0.16.0"
|
|
36
37
|
}
|
|
37
38
|
}
|
package/skills/SKILL.md
CHANGED
|
@@ -36,19 +36,22 @@ This installs the CRW skill and MCP server to all detected AI agents (Claude Cod
|
|
|
36
36
|
|
|
37
37
|
## MCP Tools
|
|
38
38
|
|
|
39
|
+
> **Output bounds:** By default, content is truncated to ~15 000 chars (`crw_scrape`, `crw_check_crawl_status`, `crw_parse_file`) and `crw_map` returns ≤ 100 URLs. Truncated results carry a `truncated: true` marker (`crw_map` also adds `totalDiscovered`). Pass `maxLength: 0` or `limit: 0` to opt out of bounding.
|
|
40
|
+
|
|
39
41
|
### crw_scrape
|
|
40
42
|
|
|
41
43
|
Scrape a single URL and return clean content.
|
|
42
44
|
|
|
43
45
|
Parameters:
|
|
44
46
|
- `url` (required) — The URL to scrape
|
|
45
|
-
- `formats` — Output formats: `markdown` (default), `html`, `
|
|
47
|
+
- `formats` — Output formats: `markdown` (default), `html`, `links`
|
|
46
48
|
- `onlyMainContent` — Strip navs/footers/sidebars. Default: `true`
|
|
49
|
+
- `includeTags` — Only include content matching these CSS selectors (e.g. `["article", "main"]`)
|
|
50
|
+
- `excludeTags` — Exclude content matching these CSS selectors (e.g. `["nav", "footer"]`)
|
|
47
51
|
- `renderJs` — Force JavaScript rendering. Default: auto-detect (null)
|
|
48
|
-
- `
|
|
49
|
-
- `
|
|
50
|
-
- `
|
|
51
|
-
- `excludeTags` — Remove these HTML tags (e.g. `["nav", "footer"]`)
|
|
52
|
+
- `waitFor` — Milliseconds to wait after page load before capturing
|
|
53
|
+
- `renderer` — Renderer override (e.g. `"playwright"`)
|
|
54
|
+
- `maxLength` — Truncate output to this many chars. `0` = unbounded. Default: ~15 000
|
|
52
55
|
|
|
53
56
|
### crw_crawl
|
|
54
57
|
|
|
@@ -56,8 +59,12 @@ Start an async BFS crawl from a URL. Returns a job ID — poll with `crw_check_c
|
|
|
56
59
|
|
|
57
60
|
Parameters:
|
|
58
61
|
- `url` (required) — Starting URL
|
|
59
|
-
- `maxDepth` — Maximum link depth. Default: `2
|
|
60
|
-
- `
|
|
62
|
+
- `maxDepth` — Maximum link depth. Default: `2`
|
|
63
|
+
- `maxPages` — Maximum pages to crawl
|
|
64
|
+
- `jsonSchema` — JSON schema for structured extraction per page
|
|
65
|
+
- `renderJs` — Force JavaScript rendering
|
|
66
|
+
- `waitFor` — Milliseconds to wait after page load before capturing
|
|
67
|
+
- `renderer` — Renderer override
|
|
61
68
|
|
|
62
69
|
Returns: `{ "id": "job-uuid" }` — use this ID with crw_check_crawl_status.
|
|
63
70
|
|
|
@@ -67,18 +74,22 @@ Poll an async crawl job for results.
|
|
|
67
74
|
|
|
68
75
|
Parameters:
|
|
69
76
|
- `id` (required) — The crawl job ID from `crw_crawl`
|
|
77
|
+
- `maxLength` — Truncate each page's content fields to this many chars. `0` = unbounded. Default: ~15 000
|
|
70
78
|
|
|
71
79
|
Returns: `{ "status": "pending|running|completed|failed", "data": [...] }`
|
|
72
80
|
|
|
73
81
|
### crw_search
|
|
74
82
|
|
|
75
|
-
Search the web and return relevant results with titles, URLs, and descriptions.
|
|
83
|
+
Search the web and return relevant results with titles, URLs, and descriptions. Always available in proxy/cloud mode; in embedded mode only when a SearXNG backend is configured.
|
|
76
84
|
|
|
77
85
|
Parameters:
|
|
78
86
|
- `query` (required) — The search query
|
|
79
87
|
- `limit` — Maximum number of results to return. Default: `5`
|
|
80
88
|
- `lang` — Language code for results (e.g. `"en"`, `"tr"`)
|
|
81
89
|
- `country` — Country code for results (e.g. `"us"`, `"tr"`)
|
|
90
|
+
- `tbs` — Time filter: `qdr:h|qdr:d|qdr:w|qdr:m|qdr:y` (past hour/day/week/month/year)
|
|
91
|
+
- `sources` — If set, group results by source: `web`, `news`, `images`
|
|
92
|
+
- `categories` — Bias toward a category (e.g. `"pdf"`, `"github"`, `"research"`, or a native SearXNG category)
|
|
82
93
|
- `scrapeOptions` — Options for scraping each result page (e.g. `{"formats": ["markdown"]}`)
|
|
83
94
|
|
|
84
95
|
### crw_map
|
|
@@ -89,8 +100,22 @@ Parameters:
|
|
|
89
100
|
- `url` (required) — The URL to map
|
|
90
101
|
- `maxDepth` — Discovery depth. Default: `2`
|
|
91
102
|
- `useSitemap` — Check sitemap.xml. Default: `true`
|
|
103
|
+
- `crawlFallback` — Supplement sitemap discovery with a short BFS crawl. Default: `true` (`false` = sitemap-only)
|
|
104
|
+
- `limit` — Maximum URLs to return. `0` = unbounded. Default: `100`
|
|
105
|
+
|
|
106
|
+
Returns: `{ "links": ["url1", "url2", ...] }`
|
|
107
|
+
|
|
108
|
+
### crw_parse_file
|
|
92
109
|
|
|
93
|
-
|
|
110
|
+
Parse a local file (PDF) into markdown or structured output without fetching from the web.
|
|
111
|
+
|
|
112
|
+
Parameters:
|
|
113
|
+
- `contentBase64` (required) — Base64-encoded file contents
|
|
114
|
+
- `filename` — Original filename (optional, e.g. `"report.pdf"`)
|
|
115
|
+
- `formats` — Output formats: `markdown` (default), `plainText`, `links`, `json`, `summary` (json/summary need a server LLM)
|
|
116
|
+
- `jsonSchema` — JSON schema for LLM extraction (when `formats` includes `json`)
|
|
117
|
+
- `parsers` — Document parsers to apply. Default: `["pdf"]`
|
|
118
|
+
- `maxLength` — Truncate output to this many chars. `0` = unbounded. Default: ~15 000
|
|
94
119
|
|
|
95
120
|
## Common Patterns
|
|
96
121
|
|
|
@@ -103,7 +128,7 @@ crw_scrape(url="https://example.com", formats=["markdown"])
|
|
|
103
128
|
First discover URLs, then crawl:
|
|
104
129
|
```
|
|
105
130
|
crw_map(url="https://docs.example.com") → get URL list
|
|
106
|
-
crw_crawl(url="https://docs.example.com",
|
|
131
|
+
crw_crawl(url="https://docs.example.com", maxPages=50) → extract all content
|
|
107
132
|
crw_check_crawl_status(id="...") → poll until completed
|
|
108
133
|
```
|
|
109
134
|
|