@spicrawl/mcp 0.0.0-stage → 0.1.1
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 +24 -0
- package/LICENSE +61 -0
- package/README.md +411 -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 +75 -4
package/dist/client.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin client over the Spicrawl REST API.
|
|
3
|
+
*
|
|
4
|
+
* Every MCP tool ultimately calls one of these methods. The client owns three
|
|
5
|
+
* things the tools should not each re-implement: authentication (the caller's
|
|
6
|
+
* API key as a bearer token), the base URL, and turning the platform's RFC 7807
|
|
7
|
+
* problem envelope into an actionable Error the agent can act on rather than a
|
|
8
|
+
* bare HTTP status.
|
|
9
|
+
*/
|
|
10
|
+
import { createRequire } from "node:module";
|
|
11
|
+
/**
|
|
12
|
+
* This package's version, read from package.json so the User-Agent, the MCP
|
|
13
|
+
* server's advertised version and the published package cannot drift. The
|
|
14
|
+
* path is relative to the compiled file (dist/client.js), and package.json
|
|
15
|
+
* ships with every npm package regardless of `files`.
|
|
16
|
+
*/
|
|
17
|
+
export const PACKAGE_VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
18
|
+
/**
|
|
19
|
+
* Sent on every API call. The API classifies the client surface from it
|
|
20
|
+
* (cli / mcp / sdk / other) and records it on each request-log row, so MCP
|
|
21
|
+
* traffic is countable. The product token must stay `spicrawl-mcp/`.
|
|
22
|
+
*/
|
|
23
|
+
export const USER_AGENT = `spicrawl-mcp/${PACKAGE_VERSION}`;
|
|
24
|
+
/** The public Spicrawl API; set SPICRAWL_BASE_URL for a self-hosted or local stack. */
|
|
25
|
+
const DEFAULT_BASE_URL = "https://api.spicrawl.com";
|
|
26
|
+
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
27
|
+
/**
|
|
28
|
+
* Where the public docs live when nothing in the environment says otherwise:
|
|
29
|
+
* the root of their own host. llms.txt and the API's error doc_url links point
|
|
30
|
+
* here, and spicrawl_docs_read only reads URLs on the docs origin.
|
|
31
|
+
*/
|
|
32
|
+
const DEFAULT_DOCS_URL = "https://docs.spicrawl.com";
|
|
33
|
+
const trimURL = (s) => (s ?? "").trim().replace(/\/+$/, "");
|
|
34
|
+
/** An origin that serves the docs under /docs (a self-hosted API, the legacy SPICRAWL_DOCS_HOST). */
|
|
35
|
+
const underDocs = (origin) => (origin.endsWith("/docs") ? origin : `${origin}/docs`);
|
|
36
|
+
/**
|
|
37
|
+
* The docs base URL from the environment, no trailing slash; page paths are
|
|
38
|
+
* appended to it directly (`<base>/errors`, `<base>/llms.txt`):
|
|
39
|
+
*
|
|
40
|
+
* 1. SPICRAWL_DOCS_URL, the full base (`https://docs.spicrawl.com`, or
|
|
41
|
+
* `http://host:8080/docs` on a self-hosted deployment);
|
|
42
|
+
* 2. SPICRAWL_DOCS_HOST + `/docs`, the legacy origin-only setting, which keeps
|
|
43
|
+
* its old meaning so an existing env file does not silently change;
|
|
44
|
+
* 3. a self-hosted API (SPICRAWL_PUBLIC_BASE_URL, else SPICRAWL_BASE_URL) +
|
|
45
|
+
* `/docs`, where every deployment serves its own docs;
|
|
46
|
+
* 4. https://docs.spicrawl.com, also when that API is the public one.
|
|
47
|
+
*
|
|
48
|
+
* Read from the raw environment, not the client's baseURL, so a stdio server
|
|
49
|
+
* with no base URL set points at the public docs rather than localhost.
|
|
50
|
+
*/
|
|
51
|
+
export function docsBaseFromEnv(env = process.env) {
|
|
52
|
+
const url = trimURL(env.SPICRAWL_DOCS_URL);
|
|
53
|
+
if (url)
|
|
54
|
+
return url;
|
|
55
|
+
const host = trimURL(env.SPICRAWL_DOCS_HOST);
|
|
56
|
+
if (host)
|
|
57
|
+
return underDocs(host);
|
|
58
|
+
const api = trimURL(env.SPICRAWL_PUBLIC_BASE_URL || env.SPICRAWL_BASE_URL);
|
|
59
|
+
if (api && api.toLowerCase() !== DEFAULT_BASE_URL)
|
|
60
|
+
return underDocs(api);
|
|
61
|
+
return DEFAULT_DOCS_URL;
|
|
62
|
+
}
|
|
63
|
+
/** A failed request, carrying the platform's own error code and guidance. */
|
|
64
|
+
export class ILayerError extends Error {
|
|
65
|
+
code;
|
|
66
|
+
status;
|
|
67
|
+
retryable;
|
|
68
|
+
docUrl;
|
|
69
|
+
constructor(status, problem, fallback) {
|
|
70
|
+
const detail = problem.detail || problem.title || fallback;
|
|
71
|
+
super(detail);
|
|
72
|
+
this.name = "ILayerError";
|
|
73
|
+
this.code = problem.code || `HTTP_${status}`;
|
|
74
|
+
this.status = problem.status ?? status;
|
|
75
|
+
this.retryable = problem.retryable ?? status >= 500;
|
|
76
|
+
this.docUrl = problem.doc_url;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export class ILayerClient {
|
|
80
|
+
baseURL;
|
|
81
|
+
/** Base URL for URLs returned to the caller; never used for upstream calls. */
|
|
82
|
+
publicBaseURL;
|
|
83
|
+
/** The caller's key. Read by tools that hand out URLs carrying it (browser). */
|
|
84
|
+
apiKey;
|
|
85
|
+
/** Public docs base, e.g. https://docs.spicrawl.com; no trailing slash. */
|
|
86
|
+
docsBaseURL;
|
|
87
|
+
timeoutMs;
|
|
88
|
+
constructor(config) {
|
|
89
|
+
if (!config.apiKey) {
|
|
90
|
+
throw new Error("SPICRAWL_API_KEY is required. Set it to an Spicrawl API key (spicrawl_live_...) in the MCP server's environment.");
|
|
91
|
+
}
|
|
92
|
+
this.baseURL = (config.baseURL || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
93
|
+
this.publicBaseURL = (config.publicBaseURL || this.baseURL).replace(/\/+$/, "");
|
|
94
|
+
this.apiKey = config.apiKey;
|
|
95
|
+
this.docsBaseURL = (config.docsBaseURL || docsBaseFromEnv()).replace(/\/+$/, "");
|
|
96
|
+
this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
97
|
+
}
|
|
98
|
+
/** POST /v1/scrape — retrieve, render, and extract one URL. */
|
|
99
|
+
async scrape(params) {
|
|
100
|
+
return this.request("POST", "/v1/scrape", params);
|
|
101
|
+
}
|
|
102
|
+
/** Any REST call. Tools for newer endpoints use this directly. */
|
|
103
|
+
async request(method, path, body) {
|
|
104
|
+
const controller = new AbortController();
|
|
105
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
106
|
+
let response;
|
|
107
|
+
try {
|
|
108
|
+
response = await fetch(`${this.baseURL}${path}`, {
|
|
109
|
+
method,
|
|
110
|
+
headers: {
|
|
111
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
112
|
+
Accept: "application/json",
|
|
113
|
+
"User-Agent": USER_AGENT,
|
|
114
|
+
...(body ? { "Content-Type": "application/json" } : {}),
|
|
115
|
+
},
|
|
116
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
117
|
+
signal: controller.signal,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
122
|
+
throw new Error(`Request to ${path} timed out after ${this.timeoutMs} ms. A render or batch may need a longer timeout, or the API at ${this.baseURL} is unreachable.`);
|
|
123
|
+
}
|
|
124
|
+
throw new Error(`Could not reach the Spicrawl API at ${this.baseURL}${path}: ${err instanceof Error ? err.message : String(err)}. Check SPICRAWL_BASE_URL and that the server is running.`);
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
clearTimeout(timer);
|
|
128
|
+
}
|
|
129
|
+
// A PDF (`response_format=pdf`) is bytes: read as text it would be corrupted.
|
|
130
|
+
if (response.ok && mediaType(response) === "application/pdf")
|
|
131
|
+
return pdfPayload(response);
|
|
132
|
+
const text = await response.text();
|
|
133
|
+
let parsed;
|
|
134
|
+
try {
|
|
135
|
+
parsed = text ? JSON.parse(text) : {};
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// Not JSON: a scrape body (markdown/text/html) — or, on an error, a
|
|
139
|
+
// gateway's page, which the check below still reports as a failure.
|
|
140
|
+
parsed = response.ok || response.headers.get("x-engine") !== null ? { content: text } : { detail: text };
|
|
141
|
+
}
|
|
142
|
+
// A non-2xx status is only an error when the body is a platform problem.
|
|
143
|
+
// With `original_status` the API relays the target's own status (a 404
|
|
144
|
+
// page the caller allowed via `allowed_status_codes`) on a SUCCESSFUL
|
|
145
|
+
// scrape, and that content must reach the agent, not be reported as a
|
|
146
|
+
// failure.
|
|
147
|
+
// A completed scrape always carries X-Engine; a gateway's error page does not.
|
|
148
|
+
const relayedTargetStatus = response.headers.get("x-engine") !== null;
|
|
149
|
+
const isProblem = typeof parsed === "object" && parsed !== null && typeof parsed.code === "string";
|
|
150
|
+
if (!response.ok && (isProblem || !relayedTargetStatus)) {
|
|
151
|
+
throw new ILayerError(response.status, parsed, `Request failed with HTTP ${response.status}`);
|
|
152
|
+
}
|
|
153
|
+
return parsed;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/** The response's media type, lowercase, without parameters. */
|
|
157
|
+
function mediaType(response) {
|
|
158
|
+
return (response.headers.get("content-type") ?? "").split(";")[0].trim().toLowerCase();
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* A `response_format=pdf` scrape answers with the file itself, and the envelope's
|
|
162
|
+
* fields as headers. The bytes come back base64 under `pdf`, with those fields
|
|
163
|
+
* beside it, for the scrape tool to hand over as a resource block (tools/pdf.ts).
|
|
164
|
+
*/
|
|
165
|
+
async function pdfPayload(response) {
|
|
166
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
167
|
+
const header = (name) => response.headers.get(name) ?? undefined;
|
|
168
|
+
const number = (name) => {
|
|
169
|
+
const v = header(name);
|
|
170
|
+
return v !== undefined && v !== "" && Number.isFinite(Number(v)) ? Number(v) : undefined;
|
|
171
|
+
};
|
|
172
|
+
const out = {
|
|
173
|
+
pdf: { content_type: "application/pdf", size_bytes: bytes.length, encoding: "base64", data: bytes.toString("base64") },
|
|
174
|
+
};
|
|
175
|
+
const fields = {
|
|
176
|
+
engine: header("x-engine"),
|
|
177
|
+
status: number("x-target-status"),
|
|
178
|
+
credits: number("x-credits-charged"),
|
|
179
|
+
final_url: header("x-final-url"),
|
|
180
|
+
request_id: header("x-request-id"),
|
|
181
|
+
};
|
|
182
|
+
for (const [k, v] of Object.entries(fields))
|
|
183
|
+
if (v !== undefined)
|
|
184
|
+
out[k] = v;
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
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;
|