@blazecrawl/mcp 0.1.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.
Files changed (3) hide show
  1. package/README.md +30 -0
  2. package/package.json +23 -0
  3. package/src/index.js +144 -0
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # @blazecrawl/mcp
2
+
3
+ MCP server exposing a self-hosted BlazeCrawl Core instance's `scrape`, `map`,
4
+ and `crawl` tools over stdio. Requires Node.js 18+.
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ npm install @blazecrawl/mcp@0.1.2
10
+ ```
11
+
12
+ ## Generic MCP client configuration
13
+
14
+ ```json
15
+ {
16
+ "mcpServers": {
17
+ "blazecrawl": {
18
+ "command": "npx",
19
+ "args": ["-y", "@blazecrawl/mcp@0.1.2"],
20
+ "env": {
21
+ "BLAZECRAWL_API_URL": "http://127.0.0.1:8000",
22
+ "BLAZECRAWL_API_KEY": "blz_local_..."
23
+ }
24
+ }
25
+ }
26
+ }
27
+ ```
28
+
29
+ The server forwards tool calls to BlazeCrawl. Crawl operations return a job
30
+ reference; use the crawl-status tool to poll it.
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@blazecrawl/mcp",
3
+ "version": "0.1.2",
4
+ "description": "MCP server for BlazeCrawl Core (scrape/map/crawl tools).",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/danishxsethi/blazecrawl.git"
9
+ },
10
+ "homepage": "https://github.com/danishxsethi/blazecrawl#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/danishxsethi/blazecrawl/issues"
13
+ },
14
+ "publishConfig": {
15
+ "access": "public",
16
+ "provenance": true
17
+ },
18
+ "type": "module",
19
+ "main": "./src/index.js",
20
+ "bin": { "blazecrawl-mcp": "./src/index.js" },
21
+ "engines": { "node": ">=18" },
22
+ "files": ["src"]
23
+ }
package/src/index.js ADDED
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * BlazeCrawl MCP server (Node.js) — scrape / map / crawl tools over stdio.
4
+ *
5
+ * Implements the MCP stdio JSON-RPC handshake directly (no SDK dependency).
6
+ *
7
+ * BLAZECRAWL_API_URL (default http://127.0.0.1:8000)
8
+ * BLAZECRAWL_API_KEY
9
+ */
10
+
11
+ const API_KEY = process.env.BLAZECRAWL_API_KEY || null;
12
+ const BASE = (process.env.BLAZECRAWL_API_URL || "http://127.0.0.1:8000").replace(/\/+$/, "");
13
+
14
+ const TOOLS = [
15
+ {
16
+ name: "scrape",
17
+ description: "Scrape a URL and return clean Markdown (LLM-ready).",
18
+ inputSchema: {
19
+ type: "object",
20
+ properties: {
21
+ url: { type: "string" },
22
+ render: { type: "string", enum: ["auto", "static", "browser"], default: "auto" },
23
+ },
24
+ required: ["url"],
25
+ },
26
+ },
27
+ {
28
+ name: "map",
29
+ description: "Discover the URL set of a site (sitemap + link graph).",
30
+ inputSchema: { type: "object", properties: { url: { type: "string" } }, required: ["url"] },
31
+ },
32
+ {
33
+ name: "crawl",
34
+ description: "Crawl a site (BFS, same-origin) and return page Markdown.",
35
+ inputSchema: {
36
+ type: "object",
37
+ properties: {
38
+ url: { type: "string" },
39
+ max_pages: { type: "integer" },
40
+ max_depth: { type: "integer" },
41
+ },
42
+ required: ["url"],
43
+ },
44
+ },
45
+ ];
46
+
47
+ function headers() {
48
+ const h = { "Content-Type": "application/json" };
49
+ if (API_KEY) h["Authorization"] = `Bearer ${API_KEY}`;
50
+ return h;
51
+ }
52
+
53
+ async function post(path, body) {
54
+ const r = await fetch(`${BASE}${path}`, { method: "POST", headers: headers(), body: JSON.stringify(body) });
55
+ return r.json();
56
+ }
57
+ async function get(path) {
58
+ const r = await fetch(`${BASE}${path}`, { headers: headers() });
59
+ return r.json();
60
+ }
61
+
62
+ async function callTool(name, args) {
63
+ if (name === "scrape") {
64
+ const body = { url: args.url };
65
+ if (args.render) body.render = args.render;
66
+ const d = await post("/v1/scrape", body);
67
+ return (d.data && d.data.markdown) || JSON.stringify(d);
68
+ }
69
+ if (name === "map") {
70
+ const d = await post("/v1/map", { url: args.url });
71
+ return (d.urls || []).join("\n") || JSON.stringify(d);
72
+ }
73
+ if (name === "crawl") {
74
+ const body = { url: args.url };
75
+ if (args.max_pages) body.max_pages = args.max_pages;
76
+ if (args.max_depth !== undefined) body.max_depth = args.max_depth;
77
+ const job = await post("/v1/crawl", body);
78
+ const id = job.job_id;
79
+ for (let i = 0; i < 600; i++) {
80
+ const st = await get(`/v1/crawl/${id}`);
81
+ if (["completed", "failed", "cancelled"].includes(st.status)) {
82
+ return (st.pages || []).map((p) => `## ${p.url}\n\n${p.markdown || ""}`).join("\n\n") || JSON.stringify(st);
83
+ }
84
+ await new Promise((r) => setTimeout(r, 1000));
85
+ }
86
+ return "crawl timed out";
87
+ }
88
+ return `unknown tool: ${name}`;
89
+ }
90
+
91
+ function send(msg) {
92
+ process.stdout.write(JSON.stringify(msg) + "\n");
93
+ }
94
+
95
+ let buffer = "";
96
+ process.stdin.on("data", (chunk) => {
97
+ buffer += chunk;
98
+ let idx;
99
+ while ((idx = buffer.indexOf("\n")) >= 0) {
100
+ const line = buffer.slice(0, idx).trim();
101
+ buffer = buffer.slice(idx + 1);
102
+ if (!line) continue;
103
+ let msg;
104
+ try {
105
+ msg = JSON.parse(line);
106
+ } catch {
107
+ continue;
108
+ }
109
+ handle(msg).catch(() => {});
110
+ }
111
+ });
112
+
113
+ async function handle(msg) {
114
+ const { id, method, params } = msg;
115
+ if (method === "initialize") {
116
+ send({
117
+ jsonrpc: "2.0",
118
+ id,
119
+ result: {
120
+ protocolVersion: "2024-11-05",
121
+ capabilities: { tools: {} },
122
+ serverInfo: { name: "blazecrawl", version: "0.1.0" },
123
+ },
124
+ });
125
+ return;
126
+ }
127
+ if (method === "notifications/initialized") return;
128
+ if (method === "tools/list") {
129
+ send({ jsonrpc: "2.0", id, result: { tools: TOOLS } });
130
+ return;
131
+ }
132
+ if (method === "tools/call") {
133
+ try {
134
+ const text = await callTool(params.name, params.arguments || {});
135
+ send({ jsonrpc: "2.0", id, result: { content: [{ type: "text", text }] } });
136
+ } catch (e) {
137
+ send({ jsonrpc: "2.0", id, result: { content: [{ type: "text", text: String(e) }], isError: true } });
138
+ }
139
+ return;
140
+ }
141
+ if (id !== undefined) {
142
+ send({ jsonrpc: "2.0", id, error: { code: -32601, message: `method not found: ${method}` } });
143
+ }
144
+ }