@spicrawl/mcp 0.0.0-stage → 0.1.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/CHANGELOG.md +17 -0
- package/LICENSE +61 -0
- package/README.md +251 -2
- package/dist/client.d.ts +89 -0
- package/dist/client.js +187 -0
- package/dist/client.js.map +1 -0
- package/dist/http.d.ts +27 -0
- package/dist/http.js +220 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +79 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +16 -0
- package/dist/server.js +141 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/actions.d.ts +534 -0
- package/dist/tools/actions.js +103 -0
- package/dist/tools/actions.js.map +1 -0
- package/dist/tools/batch.d.ts +2 -0
- package/dist/tools/batch.js +307 -0
- package/dist/tools/batch.js.map +1 -0
- package/dist/tools/browser.d.ts +2 -0
- package/dist/tools/browser.js +101 -0
- package/dist/tools/browser.js.map +1 -0
- package/dist/tools/common.d.ts +64 -0
- package/dist/tools/common.js +64 -0
- package/dist/tools/common.js.map +1 -0
- package/dist/tools/docs.d.ts +12 -0
- package/dist/tools/docs.js +277 -0
- package/dist/tools/docs.js.map +1 -0
- package/dist/tools/pdf.d.ts +11 -0
- package/dist/tools/pdf.js +36 -0
- package/dist/tools/pdf.js.map +1 -0
- package/dist/tools/requests.d.ts +2 -0
- package/dist/tools/requests.js +71 -0
- package/dist/tools/requests.js.map +1 -0
- package/dist/tools/scrape.d.ts +2 -0
- package/dist/tools/scrape.js +213 -0
- package/dist/tools/scrape.js.map +1 -0
- package/dist/tools/screenshots.d.ts +15 -0
- package/dist/tools/screenshots.js +69 -0
- package/dist/tools/screenshots.js.map +1 -0
- package/dist/tools/sessions.d.ts +2 -0
- package/dist/tools/sessions.js +145 -0
- package/dist/tools/sessions.js.map +1 -0
- package/dist/tools/usage.d.ts +2 -0
- package/dist/tools/usage.js +93 -0
- package/dist/tools/usage.js.map +1 -0
- package/package.json +67 -4
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* spicrawl-mcp-http — the Spicrawl MCP server over Streamable HTTP, so a user can
|
|
4
|
+
* connect an MCP client to a hosted URL instead of installing anything.
|
|
5
|
+
*
|
|
6
|
+
* POST/GET/DELETE /mcp MCP Streamable HTTP endpoint (Bearer auth)
|
|
7
|
+
* GET /healthz liveness, no auth
|
|
8
|
+
*
|
|
9
|
+
* Auth is the caller's own Spicrawl API key (`Authorization: Bearer spicrawl_live_...`);
|
|
10
|
+
* every tool call runs upstream under that key. One MCP server per session, and
|
|
11
|
+
* a session is pinned to the key that created it: a request presenting another
|
|
12
|
+
* key with that session id is refused (403), so no caller can run under someone
|
|
13
|
+
* else's key. An initialize is refused (401) unless the API accepts the key,
|
|
14
|
+
* and answered 503 when the API cannot be asked.
|
|
15
|
+
*
|
|
16
|
+
* SPICRAWL_MCP_ADDR listen address, default 127.0.0.1:8090
|
|
17
|
+
* SPICRAWL_BASE_URL upstream Spicrawl API, default http://127.0.0.1:8080
|
|
18
|
+
* SPICRAWL_PUBLIC_BASE_URL the API as remote callers reach it, used for URLs
|
|
19
|
+
* handed back (browser connect URL); default SPICRAWL_BASE_URL
|
|
20
|
+
* SPICRAWL_DOCS_URL the public docs base URL, read by the docs tools (never
|
|
21
|
+
* sent a key), e.g. http://HOST:8080/docs self-hosted; default
|
|
22
|
+
* SPICRAWL_DOCS_HOST + /docs (legacy), then a self-hosted
|
|
23
|
+
* SPICRAWL_PUBLIC_BASE_URL / SPICRAWL_BASE_URL + /docs, then
|
|
24
|
+
* https://docs.spicrawl.com (see docsBaseFromEnv)
|
|
25
|
+
* SPICRAWL_MCP_SESSION_IDLE_MS idle session eviction, default 1800000 (30 min)
|
|
26
|
+
*/
|
|
27
|
+
export {};
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* spicrawl-mcp-http — the Spicrawl MCP server over Streamable HTTP, so a user can
|
|
4
|
+
* connect an MCP client to a hosted URL instead of installing anything.
|
|
5
|
+
*
|
|
6
|
+
* POST/GET/DELETE /mcp MCP Streamable HTTP endpoint (Bearer auth)
|
|
7
|
+
* GET /healthz liveness, no auth
|
|
8
|
+
*
|
|
9
|
+
* Auth is the caller's own Spicrawl API key (`Authorization: Bearer spicrawl_live_...`);
|
|
10
|
+
* every tool call runs upstream under that key. One MCP server per session, and
|
|
11
|
+
* a session is pinned to the key that created it: a request presenting another
|
|
12
|
+
* key with that session id is refused (403), so no caller can run under someone
|
|
13
|
+
* else's key. An initialize is refused (401) unless the API accepts the key,
|
|
14
|
+
* and answered 503 when the API cannot be asked.
|
|
15
|
+
*
|
|
16
|
+
* SPICRAWL_MCP_ADDR listen address, default 127.0.0.1:8090
|
|
17
|
+
* SPICRAWL_BASE_URL upstream Spicrawl API, default http://127.0.0.1:8080
|
|
18
|
+
* SPICRAWL_PUBLIC_BASE_URL the API as remote callers reach it, used for URLs
|
|
19
|
+
* handed back (browser connect URL); default SPICRAWL_BASE_URL
|
|
20
|
+
* SPICRAWL_DOCS_URL the public docs base URL, read by the docs tools (never
|
|
21
|
+
* sent a key), e.g. http://HOST:8080/docs self-hosted; default
|
|
22
|
+
* SPICRAWL_DOCS_HOST + /docs (legacy), then a self-hosted
|
|
23
|
+
* SPICRAWL_PUBLIC_BASE_URL / SPICRAWL_BASE_URL + /docs, then
|
|
24
|
+
* https://docs.spicrawl.com (see docsBaseFromEnv)
|
|
25
|
+
* SPICRAWL_MCP_SESSION_IDLE_MS idle session eviction, default 1800000 (30 min)
|
|
26
|
+
*/
|
|
27
|
+
import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
|
|
28
|
+
import { createServer } from "node:http";
|
|
29
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
30
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
31
|
+
import { ILayerClient, USER_AGENT } from "./client.js";
|
|
32
|
+
import { buildServer } from "./server.js";
|
|
33
|
+
const ADDR = process.env.SPICRAWL_MCP_ADDR || "127.0.0.1:8090";
|
|
34
|
+
const BASE_URL = process.env.SPICRAWL_BASE_URL || "http://127.0.0.1:8080";
|
|
35
|
+
const PUBLIC_BASE_URL = process.env.SPICRAWL_PUBLIC_BASE_URL || BASE_URL;
|
|
36
|
+
const IDLE_MS = Number(process.env.SPICRAWL_MCP_SESSION_IDLE_MS) || 30 * 60 * 1000;
|
|
37
|
+
const MAX_BODY = 4 * 1024 * 1024;
|
|
38
|
+
const KEY_CHECK_TIMEOUT_MS = 5_000;
|
|
39
|
+
const KEY_RE = /^spicrawl_(live|test)_[A-Za-z0-9_-]{8,}$/;
|
|
40
|
+
const sessions = new Map();
|
|
41
|
+
const log = (...a) => console.error(new Date().toISOString(), ...a);
|
|
42
|
+
const hashKey = (k) => createHash("sha256").update(k).digest();
|
|
43
|
+
function rpcError(res, status, code, message, extra = {}) {
|
|
44
|
+
if (res.headersSent)
|
|
45
|
+
return;
|
|
46
|
+
res.writeHead(status, { "Content-Type": "application/json", ...extra });
|
|
47
|
+
res.end(JSON.stringify({ jsonrpc: "2.0", error: { code, message }, id: null }));
|
|
48
|
+
}
|
|
49
|
+
function bearer(req) {
|
|
50
|
+
const h = req.headers.authorization;
|
|
51
|
+
if (!h)
|
|
52
|
+
return null;
|
|
53
|
+
const m = /^Bearer\s+(\S+)\s*$/i.exec(h);
|
|
54
|
+
return m && KEY_RE.test(m[1]) ? m[1] : null;
|
|
55
|
+
}
|
|
56
|
+
async function readJSON(req) {
|
|
57
|
+
const chunks = [];
|
|
58
|
+
let size = 0;
|
|
59
|
+
for await (const c of req) {
|
|
60
|
+
size += c.length;
|
|
61
|
+
if (size > MAX_BODY)
|
|
62
|
+
throw new Error("body too large");
|
|
63
|
+
chunks.push(c);
|
|
64
|
+
}
|
|
65
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
66
|
+
return raw ? JSON.parse(raw) : undefined;
|
|
67
|
+
}
|
|
68
|
+
// The key's shape is checked locally, but only the API knows whether it exists.
|
|
69
|
+
// Without asking, a revoked key opens a session and fails only on its first tool
|
|
70
|
+
// call, which clients surface as a broken tool rather than a bad credential.
|
|
71
|
+
// GET /v1/requests needs a valid key and no scope, so any non-401 answer below
|
|
72
|
+
// 500 (a 403 on scope included) proves the key authenticated.
|
|
73
|
+
async function checkKey(key) {
|
|
74
|
+
let res;
|
|
75
|
+
try {
|
|
76
|
+
res = await fetch(`${BASE_URL}/v1/requests?limit=1`, {
|
|
77
|
+
headers: { Authorization: `Bearer ${key}`, Accept: "application/json", "User-Agent": USER_AGENT },
|
|
78
|
+
signal: AbortSignal.timeout(KEY_CHECK_TIMEOUT_MS),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
log("key check: API unreachable:", err instanceof Error ? err.message : String(err));
|
|
83
|
+
return { ok: false, status: 503, message: "Service Unavailable: could not reach the Spicrawl API to verify the key; retry shortly" };
|
|
84
|
+
}
|
|
85
|
+
const text = await res.text().catch(() => "");
|
|
86
|
+
if (res.status === 401) {
|
|
87
|
+
let code = "ERR::AUTH::INVALID_KEY";
|
|
88
|
+
let detail = "the API key is invalid, revoked or expired";
|
|
89
|
+
try {
|
|
90
|
+
const p = JSON.parse(text);
|
|
91
|
+
if (typeof p.code === "string" && p.code)
|
|
92
|
+
code = p.code;
|
|
93
|
+
if (typeof p.detail === "string" && p.detail)
|
|
94
|
+
detail = p.detail;
|
|
95
|
+
else if (typeof p.title === "string" && p.title)
|
|
96
|
+
detail = p.title;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Not a platform problem body; keep the generic wording.
|
|
100
|
+
}
|
|
101
|
+
return { ok: false, status: 401, message: `Unauthorized: ${code}: ${detail}` };
|
|
102
|
+
}
|
|
103
|
+
// A 5xx says nothing about the key, so refusing it as bad would send the
|
|
104
|
+
// caller off to rotate a working credential.
|
|
105
|
+
if (res.status >= 500) {
|
|
106
|
+
log(`key check: API answered HTTP ${res.status}`);
|
|
107
|
+
return { ok: false, status: 503, message: `Service Unavailable: the Spicrawl API answered HTTP ${res.status} while verifying the key; retry shortly` };
|
|
108
|
+
}
|
|
109
|
+
return { ok: true };
|
|
110
|
+
}
|
|
111
|
+
async function closeSession(id, why) {
|
|
112
|
+
const s = sessions.get(id);
|
|
113
|
+
if (!s)
|
|
114
|
+
return;
|
|
115
|
+
sessions.delete(id);
|
|
116
|
+
log(`session ${id.slice(0, 8)} closed (${why}); ${sessions.size} open`);
|
|
117
|
+
await s.transport.close().catch(() => { });
|
|
118
|
+
await s.server.close().catch(() => { });
|
|
119
|
+
}
|
|
120
|
+
async function handleMcp(req, res) {
|
|
121
|
+
const key = bearer(req);
|
|
122
|
+
if (!key) {
|
|
123
|
+
return rpcError(res, 401, -32001, "Unauthorized: send your Spicrawl API key as 'Authorization: Bearer spicrawl_live_...'", { "WWW-Authenticate": 'Bearer realm="spicrawl-mcp"' });
|
|
124
|
+
}
|
|
125
|
+
const keyHash = hashKey(key);
|
|
126
|
+
let body;
|
|
127
|
+
if (req.method === "POST") {
|
|
128
|
+
try {
|
|
129
|
+
body = await readJSON(req);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return rpcError(res, 400, -32700, "Parse error: body must be JSON (max 4 MiB)");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const sid = req.headers["mcp-session-id"];
|
|
136
|
+
if (typeof sid === "string" && sid) {
|
|
137
|
+
const s = sessions.get(sid);
|
|
138
|
+
if (!s)
|
|
139
|
+
return rpcError(res, 404, -32001, "Session not found or expired: re-initialize");
|
|
140
|
+
if (!timingSafeEqual(s.keyHash, keyHash)) {
|
|
141
|
+
log(`session ${sid.slice(0, 8)}: request with a different API key refused`);
|
|
142
|
+
return rpcError(res, 403, -32003, "Forbidden: this session belongs to a different API key");
|
|
143
|
+
}
|
|
144
|
+
s.lastSeen = Date.now();
|
|
145
|
+
await s.transport.handleRequest(req, res, body);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (req.method !== "POST" || !isInitializeRequest(body)) {
|
|
149
|
+
return rpcError(res, 400, -32000, "Bad Request: no Mcp-Session-Id; the first request must be initialize");
|
|
150
|
+
}
|
|
151
|
+
// Only a new session is checked: an existing one is already bound to this
|
|
152
|
+
// key's hash, and re-asking the API on every call would double its load.
|
|
153
|
+
const check = await checkKey(key);
|
|
154
|
+
if (!check.ok) {
|
|
155
|
+
return check.status === 401
|
|
156
|
+
? rpcError(res, 401, -32001, check.message, { "WWW-Authenticate": 'Bearer realm="spicrawl-mcp"' })
|
|
157
|
+
: rpcError(res, 503, -32000, check.message, { "Retry-After": "5" });
|
|
158
|
+
}
|
|
159
|
+
const server = buildServer(new ILayerClient({ apiKey: key, baseURL: BASE_URL, publicBaseURL: PUBLIC_BASE_URL }));
|
|
160
|
+
const transport = new StreamableHTTPServerTransport({
|
|
161
|
+
sessionIdGenerator: () => randomUUID(),
|
|
162
|
+
onsessioninitialized: (id) => {
|
|
163
|
+
sessions.set(id, { transport, server, keyHash, lastSeen: Date.now() });
|
|
164
|
+
log(`session ${id.slice(0, 8)} opened; ${sessions.size} open`);
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
transport.onclose = () => {
|
|
168
|
+
if (transport.sessionId)
|
|
169
|
+
void closeSession(transport.sessionId, "closed");
|
|
170
|
+
};
|
|
171
|
+
await server.connect(transport);
|
|
172
|
+
await transport.handleRequest(req, res, body);
|
|
173
|
+
}
|
|
174
|
+
const http = createServer((req, res) => {
|
|
175
|
+
const path = (req.url || "/").split("?")[0];
|
|
176
|
+
if (path === "/healthz" && (req.method === "GET" || req.method === "HEAD")) {
|
|
177
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
178
|
+
res.end(req.method === "HEAD" ? undefined : JSON.stringify({ status: "ok", sessions: sessions.size }));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (path !== "/mcp")
|
|
182
|
+
return rpcError(res, 404, -32601, "Not found: the MCP endpoint is /mcp");
|
|
183
|
+
if (!["GET", "POST", "DELETE"].includes(req.method || "")) {
|
|
184
|
+
return rpcError(res, 405, -32000, "Method not allowed", { Allow: "GET, POST, DELETE" });
|
|
185
|
+
}
|
|
186
|
+
handleMcp(req, res).catch((err) => {
|
|
187
|
+
log("request failed:", err instanceof Error ? err.message : String(err));
|
|
188
|
+
rpcError(res, 500, -32603, "Internal error");
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
// Idle eviction keeps memory bounded: a client that vanishes without DELETE
|
|
192
|
+
// leaves its session behind otherwise.
|
|
193
|
+
setInterval(() => {
|
|
194
|
+
const cutoff = Date.now() - IDLE_MS;
|
|
195
|
+
for (const [id, s] of sessions)
|
|
196
|
+
if (s.lastSeen < cutoff)
|
|
197
|
+
void closeSession(id, "idle");
|
|
198
|
+
}, 60_000).unref();
|
|
199
|
+
const i = ADDR.lastIndexOf(":");
|
|
200
|
+
const host = i > 0 ? ADDR.slice(0, i).replace(/^\[|\]$/g, "") : "127.0.0.1";
|
|
201
|
+
const port = Number(i >= 0 ? ADDR.slice(i + 1) : ADDR);
|
|
202
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
203
|
+
log(`invalid SPICRAWL_MCP_ADDR '${ADDR}' (want host:port)`);
|
|
204
|
+
process.exit(78);
|
|
205
|
+
}
|
|
206
|
+
http.listen(port, host, () => log(`spicrawl-mcp-http listening on http://${host}:${port}/mcp -> ${BASE_URL}`));
|
|
207
|
+
http.on("error", (err) => {
|
|
208
|
+
log("spicrawl-mcp-http failed:", err.message);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
});
|
|
211
|
+
const shutdown = () => {
|
|
212
|
+
log("shutting down");
|
|
213
|
+
http.close();
|
|
214
|
+
for (const id of [...sessions.keys()])
|
|
215
|
+
void closeSession(id, "shutdown");
|
|
216
|
+
setTimeout(() => process.exit(0), 2000).unref();
|
|
217
|
+
};
|
|
218
|
+
process.on("SIGTERM", shutdown);
|
|
219
|
+
process.on("SIGINT", shutdown);
|
|
220
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* spicrawl-mcp — the Spicrawl web-data platform as native agent tools.
|
|
4
|
+
*
|
|
5
|
+
* A thin MCP wrapper over the Spicrawl REST API. Each tool maps to one endpoint;
|
|
6
|
+
* the caller's API key and the API base URL come from the environment, so the
|
|
7
|
+
* same server works against a local dev stack or a production deployment.
|
|
8
|
+
*
|
|
9
|
+
* spicrawl-mcp stdio server (the default; what `npx -y @spicrawl/mcp` runs)
|
|
10
|
+
* spicrawl-mcp --http Streamable HTTP server (dist/http.js; see there for its env)
|
|
11
|
+
*
|
|
12
|
+
* Every bin in package.json points at this file. `npx -y @spicrawl/mcp` can only
|
|
13
|
+
* pick a bin on its own when the package has a bin named `mcp` (too generic to
|
|
14
|
+
* install globally) or when every bin is the same file, so the HTTP server is a
|
|
15
|
+
* flag here rather than a second bin. `node dist/http.js` still runs it directly.
|
|
16
|
+
*
|
|
17
|
+
* SPICRAWL_API_KEY required — a Spicrawl key (spicrawl_live_...)
|
|
18
|
+
* SPICRAWL_BASE_URL optional — default https://api.spicrawl.com
|
|
19
|
+
* SPICRAWL_PUBLIC_BASE_URL optional — base for URLs handed back to the caller
|
|
20
|
+
* (browser connect URL); default SPICRAWL_BASE_URL
|
|
21
|
+
* SPICRAWL_DOCS_URL optional — the public docs base URL (docs tools), e.g.
|
|
22
|
+
* http://HOST:8080/docs self-hosted; default SPICRAWL_DOCS_HOST
|
|
23
|
+
* + /docs (legacy), then a self-hosted SPICRAWL_PUBLIC_BASE_URL /
|
|
24
|
+
* SPICRAWL_BASE_URL + /docs, then https://docs.spicrawl.com
|
|
25
|
+
*
|
|
26
|
+
* Transport is stdio: this runs as a subprocess of an MCP client (Claude Code,
|
|
27
|
+
* Claude Desktop, Cursor, …), which is the local-integration case the MCP spec
|
|
28
|
+
* recommends stdio for.
|
|
29
|
+
*/
|
|
30
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* spicrawl-mcp — the Spicrawl web-data platform as native agent tools.
|
|
4
|
+
*
|
|
5
|
+
* A thin MCP wrapper over the Spicrawl REST API. Each tool maps to one endpoint;
|
|
6
|
+
* the caller's API key and the API base URL come from the environment, so the
|
|
7
|
+
* same server works against a local dev stack or a production deployment.
|
|
8
|
+
*
|
|
9
|
+
* spicrawl-mcp stdio server (the default; what `npx -y @spicrawl/mcp` runs)
|
|
10
|
+
* spicrawl-mcp --http Streamable HTTP server (dist/http.js; see there for its env)
|
|
11
|
+
*
|
|
12
|
+
* Every bin in package.json points at this file. `npx -y @spicrawl/mcp` can only
|
|
13
|
+
* pick a bin on its own when the package has a bin named `mcp` (too generic to
|
|
14
|
+
* install globally) or when every bin is the same file, so the HTTP server is a
|
|
15
|
+
* flag here rather than a second bin. `node dist/http.js` still runs it directly.
|
|
16
|
+
*
|
|
17
|
+
* SPICRAWL_API_KEY required — a Spicrawl key (spicrawl_live_...)
|
|
18
|
+
* SPICRAWL_BASE_URL optional — default https://api.spicrawl.com
|
|
19
|
+
* SPICRAWL_PUBLIC_BASE_URL optional — base for URLs handed back to the caller
|
|
20
|
+
* (browser connect URL); default SPICRAWL_BASE_URL
|
|
21
|
+
* SPICRAWL_DOCS_URL optional — the public docs base URL (docs tools), e.g.
|
|
22
|
+
* http://HOST:8080/docs self-hosted; default SPICRAWL_DOCS_HOST
|
|
23
|
+
* + /docs (legacy), then a self-hosted SPICRAWL_PUBLIC_BASE_URL /
|
|
24
|
+
* SPICRAWL_BASE_URL + /docs, then https://docs.spicrawl.com
|
|
25
|
+
*
|
|
26
|
+
* Transport is stdio: this runs as a subprocess of an MCP client (Claude Code,
|
|
27
|
+
* Claude Desktop, Cursor, …), which is the local-integration case the MCP spec
|
|
28
|
+
* recommends stdio for.
|
|
29
|
+
*/
|
|
30
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
31
|
+
import { ILayerClient, PACKAGE_VERSION } from "./client.js";
|
|
32
|
+
import { buildServer } from "./server.js";
|
|
33
|
+
const USAGE = `spicrawl-mcp ${PACKAGE_VERSION} — the Spicrawl MCP server
|
|
34
|
+
|
|
35
|
+
Usage:
|
|
36
|
+
spicrawl-mcp run over stdio (for an MCP client; needs SPICRAWL_API_KEY)
|
|
37
|
+
spicrawl-mcp --http run the Streamable HTTP server (SPICRAWL_MCP_ADDR, default 127.0.0.1:8090)
|
|
38
|
+
spicrawl-mcp --version print the version
|
|
39
|
+
spicrawl-mcp --help print this help
|
|
40
|
+
|
|
41
|
+
Docs: https://docs.spicrawl.com/agents/mcp
|
|
42
|
+
`;
|
|
43
|
+
async function stdio() {
|
|
44
|
+
const server = buildServer(new ILayerClient({
|
|
45
|
+
apiKey: process.env.SPICRAWL_API_KEY ?? "",
|
|
46
|
+
baseURL: process.env.SPICRAWL_BASE_URL,
|
|
47
|
+
publicBaseURL: process.env.SPICRAWL_PUBLIC_BASE_URL,
|
|
48
|
+
}));
|
|
49
|
+
const transport = new StdioServerTransport();
|
|
50
|
+
await server.connect(transport);
|
|
51
|
+
// stdout is the transport; logs go to stderr.
|
|
52
|
+
console.error(`spicrawl-mcp ${PACKAGE_VERSION} ready (stdio)`);
|
|
53
|
+
}
|
|
54
|
+
async function main(args) {
|
|
55
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
56
|
+
process.stdout.write(USAGE);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
60
|
+
process.stdout.write(`${PACKAGE_VERSION}\n`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const unknown = args.filter((a) => a !== "--http");
|
|
64
|
+
if (unknown.length > 0) {
|
|
65
|
+
console.error(`spicrawl-mcp: unknown argument ${unknown[0]}\n\n${USAGE}`);
|
|
66
|
+
process.exit(2);
|
|
67
|
+
}
|
|
68
|
+
if (args.includes("--http")) {
|
|
69
|
+
// dist/http.js starts listening when it is loaded.
|
|
70
|
+
await import("./http.js");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
await stdio();
|
|
74
|
+
}
|
|
75
|
+
main(process.argv.slice(2)).catch((err) => {
|
|
76
|
+
console.error("spicrawl-mcp failed to start:", err);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
});
|
|
79
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,KAAK,GAAG,gBAAgB,eAAe;;;;;;;;;CAS5C,CAAC;AAEF,KAAK,UAAU,KAAK;IAClB,MAAM,MAAM,GAAG,WAAW,CACxB,IAAI,YAAY,CAAC;QACf,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,EAAE;QAC1C,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB;QACtC,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,wBAAwB;KACpD,CAAC,CACH,CAAC;IACF,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,8CAA8C;IAC9C,OAAO,CAAC,KAAK,CAAC,gBAAgB,eAAe,gBAAgB,CAAC,CAAC;AACjE,CAAC;AAED,KAAK,UAAU,IAAI,CAAC,IAAc;IAChC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5B,OAAO;IACT,CAAC;IACD,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACtD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,eAAe,IAAI,CAAC,CAAC;QAC7C,OAAO;IACT,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC;IACnD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,KAAK,CAAC,kCAAkC,OAAO,CAAC,CAAC,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC;QAC1E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,mDAAmD;QACnD,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;QAC1B,OAAO;IACT,CAAC;IACD,MAAM,KAAK,EAAE,CAAC;AAChB,CAAC;AAED,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACxC,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,GAAG,CAAC,CAAC;IACpD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { ILayerClient } from "./client.js";
|
|
4
|
+
export declare const SERVER_NAME = "spicrawl-mcp-server";
|
|
5
|
+
export declare const SERVER_VERSION: string;
|
|
6
|
+
/**
|
|
7
|
+
* Wraps a raw zod shape into a strict object: unknown keys are a validation
|
|
8
|
+
* error naming them (with a did-you-mean), and the JSON Schema carries
|
|
9
|
+
* `additionalProperties: false`. Descriptions and defaults are untouched.
|
|
10
|
+
*/
|
|
11
|
+
export declare function strictArgs<T extends z.ZodRawShape>(shape: T): z.ZodObject<T, "strict", z.ZodTypeAny, z.objectUtil.addQuestionMarks<z.baseObjectOutputType<T>, any> extends infer T_1 ? { [k in keyof T_1]: T_1[k]; } : never, z.baseObjectInputType<T> extends infer T_2 ? { [k_1 in keyof T_2]: T_2[k_1]; } : never>;
|
|
12
|
+
/**
|
|
13
|
+
* One MCP server bound to one caller's API key. The stdio entry builds one for
|
|
14
|
+
* the process; the HTTP entry builds one per session, from that session's key.
|
|
15
|
+
*/
|
|
16
|
+
export declare function buildServer(client: ILayerClient): McpServer;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { PACKAGE_VERSION } from "./client.js";
|
|
4
|
+
import { registerScrapeTools } from "./tools/scrape.js";
|
|
5
|
+
import { registerBatchTools } from "./tools/batch.js";
|
|
6
|
+
import { registerSessionsTools } from "./tools/sessions.js";
|
|
7
|
+
import { registerUsageTools } from "./tools/usage.js";
|
|
8
|
+
import { registerRequestsTools } from "./tools/requests.js";
|
|
9
|
+
import { registerBrowserTools } from "./tools/browser.js";
|
|
10
|
+
import { registerDocsTools } from "./tools/docs.js";
|
|
11
|
+
export const SERVER_NAME = "spicrawl-mcp-server";
|
|
12
|
+
export const SERVER_VERSION = PACKAGE_VERSION;
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Unknown arguments are refused, never dropped.
|
|
15
|
+
//
|
|
16
|
+
// zod's default object strips keys it does not know, so `js_render: true` (the
|
|
17
|
+
// REST name; the tool calls it `render`) used to become a silent plain fetch —
|
|
18
|
+
// the customer believes rendering is on and is billed for something else. The
|
|
19
|
+
// Spicrawl API refuses unknown fields for exactly this reason; the MCP layer must
|
|
20
|
+
// not bypass that. Every tool's top-level arguments are therefore a strict
|
|
21
|
+
// object: an unknown key fails validation with a message naming it (and the
|
|
22
|
+
// closest real argument), and tools/list advertises `additionalProperties:
|
|
23
|
+
// false`. Free-form maps inside a tool (headers, JSON Schemas, records) stay open.
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
/** Names an agent is likely to guess (REST / other-scraper spellings) -> the tool's name. */
|
|
26
|
+
const ALIASES = {
|
|
27
|
+
js_render: "render",
|
|
28
|
+
javascript: "render",
|
|
29
|
+
response_format: "format",
|
|
30
|
+
formats: "format",
|
|
31
|
+
only_main_content: "main_content_only",
|
|
32
|
+
onlyMainContent: "main_content_only",
|
|
33
|
+
includeTags: "include_tags",
|
|
34
|
+
excludeTags: "exclude_tags",
|
|
35
|
+
waitFor: "wait_for",
|
|
36
|
+
headers: "custom_headers",
|
|
37
|
+
country: "proxy_country",
|
|
38
|
+
};
|
|
39
|
+
const normalise = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
40
|
+
function levenshtein(a, b) {
|
|
41
|
+
let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
42
|
+
for (let i = 1; i <= a.length; i++) {
|
|
43
|
+
const cur = [i];
|
|
44
|
+
for (let j = 1; j <= b.length; j++) {
|
|
45
|
+
cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
46
|
+
}
|
|
47
|
+
prev = cur;
|
|
48
|
+
}
|
|
49
|
+
return prev[b.length];
|
|
50
|
+
}
|
|
51
|
+
/** The valid argument closest to `key`, if one is close enough to be a plausible typo. */
|
|
52
|
+
function closestArgument(key, valid) {
|
|
53
|
+
const alias = ALIASES[key];
|
|
54
|
+
if (alias && valid.includes(alias))
|
|
55
|
+
return alias;
|
|
56
|
+
const nk = normalise(key);
|
|
57
|
+
// Candidates: every valid name, plus every alias whose target is valid (so a
|
|
58
|
+
// typo of a REST name, e.g. `js_rendr`, still lands on `render`).
|
|
59
|
+
const candidates = valid.map((v) => [v, v]);
|
|
60
|
+
for (const [a, target] of Object.entries(ALIASES))
|
|
61
|
+
if (valid.includes(target))
|
|
62
|
+
candidates.push([a, target]);
|
|
63
|
+
let best;
|
|
64
|
+
let bestDistance = Infinity;
|
|
65
|
+
for (const [spelling, target] of candidates) {
|
|
66
|
+
const d = levenshtein(nk, normalise(spelling));
|
|
67
|
+
if (d < bestDistance)
|
|
68
|
+
[best, bestDistance] = [target, d];
|
|
69
|
+
}
|
|
70
|
+
return bestDistance <= Math.max(1, Math.floor(nk.length / 3)) ? best : undefined;
|
|
71
|
+
}
|
|
72
|
+
function unknownArgumentsMessage(keys, valid) {
|
|
73
|
+
let unresolved = false;
|
|
74
|
+
const parts = keys.map((k) => {
|
|
75
|
+
const s = closestArgument(k, valid);
|
|
76
|
+
if (!s)
|
|
77
|
+
unresolved = true;
|
|
78
|
+
return s ? `unknown argument \`${k}\`; did you mean \`${s}\`?` : `unknown argument \`${k}\``;
|
|
79
|
+
});
|
|
80
|
+
let msg = parts.join("; ");
|
|
81
|
+
if (unresolved) {
|
|
82
|
+
msg += valid.length
|
|
83
|
+
? ` (valid arguments: ${valid.map((v) => `\`${v}\``).join(", ")})`
|
|
84
|
+
: " (this tool takes no arguments)";
|
|
85
|
+
}
|
|
86
|
+
return msg;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Wraps a raw zod shape into a strict object: unknown keys are a validation
|
|
90
|
+
* error naming them (with a did-you-mean), and the JSON Schema carries
|
|
91
|
+
* `additionalProperties: false`. Descriptions and defaults are untouched.
|
|
92
|
+
*/
|
|
93
|
+
export function strictArgs(shape) {
|
|
94
|
+
const valid = Object.keys(shape);
|
|
95
|
+
const errorMap = (issue, ctx) => issue.code === z.ZodIssueCode.unrecognized_keys
|
|
96
|
+
? { message: unknownArgumentsMessage(issue.keys, valid) }
|
|
97
|
+
: { message: ctx.defaultError };
|
|
98
|
+
return z.object(shape, { errorMap }).strict();
|
|
99
|
+
}
|
|
100
|
+
/** Makes a registerTool inputSchema strict: raw shapes and non-strict zod objects. */
|
|
101
|
+
function strictInputSchema(schema) {
|
|
102
|
+
if (schema === undefined)
|
|
103
|
+
return undefined; // no schema -> SDK passes no args at all
|
|
104
|
+
if (schema instanceof z.ZodObject) {
|
|
105
|
+
return schema._def.unknownKeys === "strip" ? strictArgs(schema.shape) : schema;
|
|
106
|
+
}
|
|
107
|
+
const isSchemaInstance = "_def" in schema || "_zod" in schema;
|
|
108
|
+
return isSchemaInstance ? schema : strictArgs(schema);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* McpServer whose registerTool makes every tool's input schema strict. Tools
|
|
112
|
+
* keep declaring plain raw shapes; this one place guarantees none of them (or
|
|
113
|
+
* any tool added later) can silently drop an argument.
|
|
114
|
+
*/
|
|
115
|
+
class StrictArgsMcpServer extends McpServer {
|
|
116
|
+
registerTool(name, config, cb) {
|
|
117
|
+
// The strict object parses to exactly what the raw shape did, so the
|
|
118
|
+
// callback's argument type is unchanged; only the declared type widens.
|
|
119
|
+
return super.registerTool(name, { ...config, inputSchema: strictInputSchema(config.inputSchema) }, cb);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* One MCP server bound to one caller's API key. The stdio entry builds one for
|
|
124
|
+
* the process; the HTTP entry builds one per session, from that session's key.
|
|
125
|
+
*/
|
|
126
|
+
export function buildServer(client) {
|
|
127
|
+
const server = new StrictArgsMcpServer({ name: SERVER_NAME, version: SERVER_VERSION });
|
|
128
|
+
for (const register of [
|
|
129
|
+
registerScrapeTools,
|
|
130
|
+
registerBatchTools,
|
|
131
|
+
registerSessionsTools,
|
|
132
|
+
registerUsageTools,
|
|
133
|
+
registerRequestsTools,
|
|
134
|
+
registerBrowserTools,
|
|
135
|
+
registerDocsTools,
|
|
136
|
+
]) {
|
|
137
|
+
register(server, client);
|
|
138
|
+
}
|
|
139
|
+
return server;
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|