@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.
Files changed (49) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/LICENSE +61 -0
  3. package/README.md +251 -2
  4. package/dist/client.d.ts +89 -0
  5. package/dist/client.js +187 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/http.d.ts +27 -0
  8. package/dist/http.js +220 -0
  9. package/dist/http.js.map +1 -0
  10. package/dist/index.d.ts +30 -0
  11. package/dist/index.js +79 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/server.d.ts +16 -0
  14. package/dist/server.js +141 -0
  15. package/dist/server.js.map +1 -0
  16. package/dist/tools/actions.d.ts +534 -0
  17. package/dist/tools/actions.js +103 -0
  18. package/dist/tools/actions.js.map +1 -0
  19. package/dist/tools/batch.d.ts +2 -0
  20. package/dist/tools/batch.js +307 -0
  21. package/dist/tools/batch.js.map +1 -0
  22. package/dist/tools/browser.d.ts +2 -0
  23. package/dist/tools/browser.js +101 -0
  24. package/dist/tools/browser.js.map +1 -0
  25. package/dist/tools/common.d.ts +64 -0
  26. package/dist/tools/common.js +64 -0
  27. package/dist/tools/common.js.map +1 -0
  28. package/dist/tools/docs.d.ts +12 -0
  29. package/dist/tools/docs.js +277 -0
  30. package/dist/tools/docs.js.map +1 -0
  31. package/dist/tools/pdf.d.ts +11 -0
  32. package/dist/tools/pdf.js +36 -0
  33. package/dist/tools/pdf.js.map +1 -0
  34. package/dist/tools/requests.d.ts +2 -0
  35. package/dist/tools/requests.js +71 -0
  36. package/dist/tools/requests.js.map +1 -0
  37. package/dist/tools/scrape.d.ts +2 -0
  38. package/dist/tools/scrape.js +213 -0
  39. package/dist/tools/scrape.js.map +1 -0
  40. package/dist/tools/screenshots.d.ts +15 -0
  41. package/dist/tools/screenshots.js +69 -0
  42. package/dist/tools/screenshots.js.map +1 -0
  43. package/dist/tools/sessions.d.ts +2 -0
  44. package/dist/tools/sessions.js +145 -0
  45. package/dist/tools/sessions.js.map +1 -0
  46. package/dist/tools/usage.d.ts +2 -0
  47. package/dist/tools/usage.js +93 -0
  48. package/dist/tools/usage.js.map +1 -0
  49. package/package.json +67 -4
package/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@spicrawl/mcp` are documented here. This project follows
4
+ [Semantic Versioning](https://semver.org/).
5
+
6
+ ## 0.1.0 — 2026-10-01
7
+
8
+ First public release of the Spicrawl MCP server, published to npm as `@spicrawl/mcp` from https://github.com/Spicrawl/mcp with an npm provenance attestation.
9
+
10
+ - `npx -y @spicrawl/mcp` runs the stdio server for any MCP client (Claude Code, Claude Desktop, Cursor, VS Code, Codex, …), keyed by `SPICRAWL_API_KEY`. Installed globally, the command is `spicrawl-mcp` (`spicrawl-mcp-server` is an alias).
11
+ - `spicrawl-mcp --http` (or `node dist/http.js`) runs the Streamable HTTP server behind the hosted endpoint at https://mcp.spicrawl.com/mcp: per-caller bearer-token auth validated against the API, sessions pinned to the key that opened them, idle-session eviction and a `/healthz` liveness route.
12
+ - 25 tools: `spicrawl_scrape`; batch jobs (`spicrawl_batch_submit`, `_list`, `_status`, `_results`, `_task_content`, `_cancel`, `_retry`, `_add_items`, `_close`); browser sessions (`spicrawl_session_create`, `_list`, `_get`, `_context`, `_release`, `_delete`); usage and request history (`spicrawl_usage`, `_usage_summary`, `_usage_reconciliation`, `spicrawl_requests_list`, `spicrawl_request_get`); `spicrawl_browser_connect_url` (coming soon); and the docs (`spicrawl_docs_search`, `_read`, `_index`).
13
+ - Tools refuse unknown arguments instead of dropping them, and surface the platform's error codes and guidance.
14
+ - Screenshots come back as MCP image blocks and printed pages (`format: "pdf"`) as PDF resource blocks.
15
+ - `--version` and `--help`. The version reported to MCP clients and in the `User-Agent` (`spicrawl-mcp/<version>`) is read from package.json.
16
+
17
+ Previously developed in Spicrawl's private repository as the unpublished `spicrawl-mcp-server` 0.2.0; this is the same server under its public name.
package/LICENSE ADDED
@@ -0,0 +1,61 @@
1
+ Apache License
2
+
3
+ Version 2.0, January 2004
4
+
5
+ http://www.apache.org/licenses/
6
+
7
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
8
+
9
+ 1. Definitions.
10
+
11
+ "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
16
+
17
+ "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
18
+
19
+ "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
20
+
21
+ "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
22
+
23
+ "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
24
+
25
+ "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
26
+
27
+ "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
28
+
29
+ "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
30
+
31
+ 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
32
+
33
+ 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
34
+
35
+ 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
36
+
37
+ You must give any other recipients of the Work or Derivative Works a copy of this License; and
38
+
39
+ You must cause any modified files to carry prominent notices stating that You changed the files; and
40
+
41
+ You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
42
+
43
+ If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
44
+
45
+ 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
46
+
47
+ 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
48
+
49
+ 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
50
+
51
+ 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
52
+
53
+ 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
54
+
55
+ END OF TERMS AND CONDITIONS
56
+
57
+ Copyright 2026 Spicrawl
58
+
59
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
60
+ this file except in compliance with the License. You may obtain a copy of the
61
+ License at http://www.apache.org/licenses/LICENSE-2.0
package/README.md CHANGED
@@ -1,3 +1,252 @@
1
- # Temporary Holding Version
1
+ # Spicrawl MCP Server: Web Scraping Tools for AI Agents
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The official [Model Context Protocol](https://modelcontextprotocol.io) server for [Spicrawl](https://spicrawl.com), the web scraping API for AI agents and LLMs. Connect it to Claude Code, Claude Desktop, Cursor, VS Code, Codex or any MCP client, and your agent can scrape pages to Markdown, extract structured data, run batch jobs over thousands of URLs, keep browser sessions and read the Spicrawl docs, all as native tools.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@spicrawl/mcp.svg)](https://www.npmjs.com/package/@spicrawl/mcp)
6
+ [![license](https://img.shields.io/npm/l/@spicrawl/mcp.svg)](https://www.npmjs.com/package/@spicrawl/mcp)
7
+ [![CI](https://github.com/Spicrawl/mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/Spicrawl/mcp/actions/workflows/ci.yml)
8
+ [![docs](https://img.shields.io/badge/docs-docs.spicrawl.com-blue.svg)](https://docs.spicrawl.com/agents/mcp)
9
+
10
+ **[Docs](https://docs.spicrawl.com/agents/mcp)** · **[Get an API key](https://app.spicrawl.com/signup)** · **[GitHub](https://github.com/Spicrawl/mcp)** · **[SDK](https://github.com/Spicrawl/sdk)** · **[CLI](https://github.com/Spicrawl/cli)** · **[Changelog](https://github.com/Spicrawl/mcp/blob/main/CHANGELOG.md)**
11
+
12
+ ## Two ways to connect
13
+
14
+ | | Hosted endpoint | Local (stdio) |
15
+ |---|---|---|
16
+ | What | `https://mcp.spicrawl.com/mcp`, MCP Streamable HTTP | `npx -y @spicrawl/mcp`, a subprocess of your MCP client |
17
+ | Install | nothing | Node.js 20 or later |
18
+ | Auth | `Authorization: Bearer <API key>` header | `SPICRAWL_API_KEY` environment variable |
19
+ | Use when | your client supports remote MCP servers (most do) | your client only launches local servers, or you want to pin a version |
20
+
21
+ Both serve the same 25 `spicrawl_*` tools. [Create an API key](https://app.spicrawl.com/signup) in the dashboard first; keys start with `spicrawl_live_` or `spicrawl_test_`.
22
+
23
+ ## Hosted endpoint
24
+
25
+ Each tool call runs under your key, exactly as if you had called the API yourself, and a session is bound to the key that opened it. The key is checked when a session opens: an unknown or revoked key gets `401`, and if the API cannot be reached to check it the endpoint answers `503` with `Retry-After` (retry rather than rotating the key).
26
+
27
+ **Claude Code:**
28
+
29
+ ```bash
30
+ claude mcp add --transport http spicrawl https://mcp.spicrawl.com/mcp \
31
+ --header "Authorization: Bearer spicrawl_live_..."
32
+ ```
33
+
34
+ or in a project's `.mcp.json`:
35
+
36
+ ```json
37
+ {
38
+ "mcpServers": {
39
+ "spicrawl": {
40
+ "type": "http",
41
+ "url": "https://mcp.spicrawl.com/mcp",
42
+ "headers": { "Authorization": "Bearer spicrawl_live_..." }
43
+ }
44
+ }
45
+ }
46
+ ```
47
+
48
+ **Cursor** (`.cursor/mcp.json` or `~/.cursor/mcp.json`):
49
+
50
+ ```json
51
+ {
52
+ "mcpServers": {
53
+ "spicrawl": {
54
+ "url": "https://mcp.spicrawl.com/mcp",
55
+ "headers": { "Authorization": "Bearer spicrawl_live_..." }
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ **VS Code** (`.vscode/mcp.json`; VS Code uses the `servers` key). The `inputs` entry makes VS Code ask for the key once and store it, so the file is safe to commit:
62
+
63
+ ```json
64
+ {
65
+ "inputs": [
66
+ { "type": "promptString", "id": "spicrawl-api-key", "description": "Spicrawl API key", "password": true }
67
+ ],
68
+ "servers": {
69
+ "spicrawl": {
70
+ "type": "http",
71
+ "url": "https://mcp.spicrawl.com/mcp",
72
+ "headers": { "Authorization": "Bearer ${input:spicrawl-api-key}" }
73
+ }
74
+ }
75
+ }
76
+ ```
77
+
78
+ **Codex** reads the key from the environment each time it connects, so the key never lands in a file:
79
+
80
+ ```bash
81
+ export SPICRAWL_API_KEY=spicrawl_live_...
82
+ codex mcp add spicrawl --url https://mcp.spicrawl.com/mcp --bearer-token-env-var SPICRAWL_API_KEY
83
+ ```
84
+
85
+ The equivalent `~/.codex/config.toml`:
86
+
87
+ ```toml
88
+ [mcp_servers.spicrawl]
89
+ url = "https://mcp.spicrawl.com/mcp"
90
+ bearer_token_env_var = "SPICRAWL_API_KEY"
91
+ ```
92
+
93
+ **Claude Desktop** only launches local servers from `claude_desktop_config.json`, and custom connectors cannot send an `Authorization` header, so use the [local server](#local-server-stdio) below.
94
+
95
+ ## Local server (stdio)
96
+
97
+ `npx -y @spicrawl/mcp` downloads the package and runs the stdio server. The client starts it and passes your key in its environment.
98
+
99
+ **Claude Code:**
100
+
101
+ ```bash
102
+ claude mcp add spicrawl --env SPICRAWL_API_KEY=spicrawl_live_... -- npx -y @spicrawl/mcp
103
+ ```
104
+
105
+ **Claude Desktop** (`claude_desktop_config.json`) and **Cursor** (`.cursor/mcp.json`):
106
+
107
+ ```json
108
+ {
109
+ "mcpServers": {
110
+ "spicrawl": {
111
+ "command": "npx",
112
+ "args": ["-y", "@spicrawl/mcp"],
113
+ "env": { "SPICRAWL_API_KEY": "spicrawl_live_..." }
114
+ }
115
+ }
116
+ }
117
+ ```
118
+
119
+ **VS Code** (`.vscode/mcp.json`):
120
+
121
+ ```json
122
+ {
123
+ "inputs": [
124
+ { "type": "promptString", "id": "spicrawl-api-key", "description": "Spicrawl API key", "password": true }
125
+ ],
126
+ "servers": {
127
+ "spicrawl": {
128
+ "type": "stdio",
129
+ "command": "npx",
130
+ "args": ["-y", "@spicrawl/mcp"],
131
+ "env": { "SPICRAWL_API_KEY": "${input:spicrawl-api-key}" }
132
+ }
133
+ }
134
+ }
135
+ ```
136
+
137
+ **Codex** (`~/.codex/config.toml`):
138
+
139
+ ```toml
140
+ [mcp_servers.spicrawl]
141
+ command = "npx"
142
+ args = ["-y", "@spicrawl/mcp"]
143
+ env = { SPICRAWL_API_KEY = "spicrawl_live_..." }
144
+ ```
145
+
146
+ Restart the client; the `spicrawl_*` tools appear. To install it once instead of fetching it on each start, run `npm install -g @spicrawl/mcp` and use the command `spicrawl-mcp` (`spicrawl-mcp-server` is an alias).
147
+
148
+ ### Environment variables
149
+
150
+ | Variable | Required | Default | Notes |
151
+ |---|---|---|---|
152
+ | `SPICRAWL_API_KEY` | yes (stdio) | — | Your Spicrawl API key. The HTTP server ignores it and uses each caller's bearer token instead. |
153
+ | `SPICRAWL_BASE_URL` | no | `https://api.spicrawl.com` (stdio), `http://127.0.0.1:8080` (HTTP server) | The Spicrawl API base URL. Set it for a self-hosted or local stack, and always when you run the HTTP server against the public API. |
154
+ | `SPICRAWL_PUBLIC_BASE_URL` | no | `SPICRAWL_BASE_URL` | The API as the end user reaches it, used only for URLs handed back (`spicrawl_browser_connect_url`). Set it when the server talks to the API over a private address. |
155
+ | `SPICRAWL_DOCS_URL` | no | see below | The docs base URL, no trailing slash. The docs tools read `<base>/api/search`, `<base>/<page>.md` and `<base>/llms.txt`. The docs are public: your API key is never sent there. |
156
+ | `SPICRAWL_DOCS_HOST` | no | — | Legacy: an origin whose docs are at `/docs`. Read only when `SPICRAWL_DOCS_URL` is unset. |
157
+ | `SPICRAWL_MCP_ADDR` | no | `127.0.0.1:8090` | HTTP server only: the listen address, `host:port`. |
158
+ | `SPICRAWL_MCP_SESSION_IDLE_MS` | no | `1800000` (30 min) | HTTP server only: idle sessions are dropped after this long. |
159
+
160
+ Without `SPICRAWL_DOCS_URL` or `SPICRAWL_DOCS_HOST`, the docs tools read a self-hosted API's own docs (`SPICRAWL_PUBLIC_BASE_URL`, else `SPICRAWL_BASE_URL`, plus `/docs`), and otherwise `https://docs.spicrawl.com`.
161
+
162
+ ## Tools
163
+
164
+ | Tool | What it does |
165
+ |---|---|
166
+ | **Scrape** | |
167
+ | `spicrawl_scrape` | Retrieve a URL as markdown/text/html/json, or print it to a PDF. Optional browser render, selector or AI extraction, links, screenshot, DOM scoping, proxy control, caching, browser actions, network capture. |
168
+ | **Batch** | |
169
+ | `spicrawl_batch_submit` | Scrape many URLs as one async job with shared settings. Output is markdown unless `format` says otherwise, the same default as `spicrawl_scrape`. Returns the job object. |
170
+ | `spicrawl_batch_list` | List the project's batch jobs, newest first, with status and progress. |
171
+ | `spicrawl_batch_status` | Poll one batch job's status and progress. |
172
+ | `spicrawl_batch_results` | Read a page of a job's finished items (content, or a `result_url` API content path per item). |
173
+ | `spicrawl_batch_task_content` | Read the full payload of one item by its `seq`. |
174
+ | `spicrawl_batch_cancel` | Cancel a job: unstarted items never run, finished ones keep their results. |
175
+ | `spicrawl_batch_retry` | Re-queue every failed item of a job. |
176
+ | `spicrawl_batch_add_items` | Append URLs to a job submitted with `open: true`. |
177
+ | `spicrawl_batch_close` | Stop an open job accepting items so it can complete. |
178
+ | **Sessions** | |
179
+ | `spicrawl_session_create` | Create a persistent browser identity (cookies, storage, pinned engine, sticky exit IP) to reuse across scrapes. |
180
+ | `spicrawl_session_list` | List the project's sessions, newest first. |
181
+ | `spicrawl_session_get` | One session's metadata (status, engine, exit, usage, expiry). |
182
+ | `spicrawl_session_context` | Dump a live session's stored state (cookies, storage). It contains secrets, and can clone a session. |
183
+ | `spicrawl_session_release` | End a session; its record stays visible as `released`. |
184
+ | `spicrawl_session_delete` | Permanently delete a session and its stored state. |
185
+ | **Usage and request history** | |
186
+ | `spicrawl_usage` | Billing-grade usage over a date window, grouped by day, project, engine or feature (needs the `read` scope). |
187
+ | `spicrawl_usage_summary` | Current-period usage totals (needs the `read` scope). |
188
+ | `spicrawl_usage_reconciliation` | One day's drift between the request log and the billed rollup (needs the `read` scope). |
189
+ | `spicrawl_requests_list` | Recent API calls in the key's project with status, errors, engine, proxy, latency and credits; `all_projects: true` lists every project in the organization (needs the `read` scope). |
190
+ | `spicrawl_request_get` | The full log record of one call by its request id, for debugging a failed or slow scrape. |
191
+ | **Browser** | |
192
+ | `spicrawl_browser_connect_url` | Coming soon: remote browsers are not available yet. Mints a single-use CDP WebSocket URL for driving Spicrawl's remote browser from your own Puppeteer or Playwright. |
193
+ | **Docs** | |
194
+ | `spicrawl_docs_search` | Search the Spicrawl docs (`query`, optional `limit` 1-20, default 8). Results are grouped by page, with matching sections, snippets and the `.md` URL to read next. An error code (`ERR::FAMILY::NAME`) also gets a link to its entry on the errors page. |
195
+ | `spicrawl_docs_read` | Read one docs page as Markdown by path (`guides/anti-bot`) or docs URL, optionally with `#anchor`. Pages over 60k characters are truncated and say so; other hosts and `..` are refused. |
196
+ | `spicrawl_docs_index` | The docs' `llms.txt`: every page with its title, description and `.md` link. |
197
+
198
+ See the [MCP server docs](https://docs.spicrawl.com/agents/mcp) for each tool's arguments.
199
+
200
+ ## Example prompts
201
+
202
+ Once connected, an agent can act on requests like:
203
+
204
+ - *"Scrape https://example.com/pricing as markdown and summarise the plans."*
205
+ - *"Get the product name, price and stock status from this URL."* The agent calls `spicrawl_scrape` with `autoparse` or an `extract` map.
206
+ - *"Scrape these 50 URLs and give me the titles."* The agent calls `spicrawl_batch_submit`, then `spicrawl_batch_status` and `spicrawl_batch_results`.
207
+
208
+ ## Behaviour worth knowing
209
+
210
+ - Tools reject unknown arguments rather than ignoring them. A misspelt or unsupported field (e.g. `js_render` on `spicrawl_scrape`, whose argument is `render`) fails the call with an error naming it, so a setting the agent believes it applied is never dropped.
211
+ - Errors carry the platform's own code and guidance (e.g. `ERR::UPSTREAM::CHALLENGE` when a site served a bot wall), so the agent can decide whether to retry or change parameters.
212
+ - `spicrawl_scrape` screenshots come back as MCP image content blocks the model can see. In the JSON, each `screenshots` entry keeps its metadata but its base64 is replaced by a pointer to its block. An image over 5 MB of base64 is not attached, and its entry says how to get a smaller one.
213
+ - `spicrawl_scrape` with `format: "pdf"` (plus `render` or `engine: "chromium"`) returns the printed page as an MCP resource block (`application/pdf`). A PDF over 10 MB of base64 is not attached; `pdf.not_attached` says so.
214
+ - Coming soon, and marked so in the tool descriptions: `ai_extract`, stealth mode (`stealth`, engine `camoufox`), `extract_preset`, the managed proxy pool (`premium_proxy`, `proxy_country`, `sticky_key`, session `rotate_ip`/`region_pool`) and `spicrawl_browser_connect_url`. The schemas already accept them so nothing changes the day they ship; until then agents should not send them.
215
+
216
+ ## Self-hosting the HTTP server
217
+
218
+ The hosted endpoint is this package's Streamable HTTP server. To run your own, for example beside a self-hosted Spicrawl deployment:
219
+
220
+ ```bash
221
+ SPICRAWL_MCP_ADDR=127.0.0.1:8090 \
222
+ SPICRAWL_BASE_URL=https://api.spicrawl.com \
223
+ npx -y @spicrawl/mcp --http
224
+ ```
225
+
226
+ or, from an installed copy, `node node_modules/@spicrawl/mcp/dist/http.js` with the same environment.
227
+
228
+ - It serves MCP at `POST/GET/DELETE /mcp` and liveness at `GET /healthz` (no auth): `curl http://127.0.0.1:8090/healthz`.
229
+ - **`SPICRAWL_BASE_URL` defaults to `http://127.0.0.1:8080`** for the HTTP server (a Spicrawl API on the same host), not to the public API. Set it to `https://api.spicrawl.com` to front the public API.
230
+ - Each caller authenticates with their own key as `Authorization: Bearer spicrawl_live_...`. The key is validated against the API before a session opens, and a session is pinned to the key that created it.
231
+ - The server speaks plain HTTP. Put it behind TLS (a reverse proxy) before sending keys across a network.
232
+
233
+ ## Development
234
+
235
+ ```bash
236
+ git clone https://github.com/Spicrawl/mcp.git && cd mcp
237
+ npm ci
238
+ npm run typecheck
239
+ npm test
240
+ ```
241
+
242
+ `npm test` builds the package, then runs the `node:test` suite in `test/` against the real `dist/http.js`, pointed at a fake Spicrawl API on loopback. It needs no deployment, no network and no real API key, and finishes in a few seconds.
243
+
244
+ `npm run build` writes `dist/index.js` (the `spicrawl-mcp` command: stdio, or HTTP with `--http`) and `dist/http.js` (the HTTP server). Run the stdio server from a checkout with `SPICRAWL_API_KEY=... node dist/index.js`.
245
+
246
+ ## Security
247
+
248
+ See [SECURITY.md](SECURITY.md). Never commit an API key; if one leaks, revoke it at https://app.spicrawl.com.
249
+
250
+ ## License
251
+
252
+ [Apache-2.0](LICENSE)
@@ -0,0 +1,89 @@
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
+ /**
11
+ * This package's version, read from package.json so the User-Agent, the MCP
12
+ * server's advertised version and the published package cannot drift. The
13
+ * path is relative to the compiled file (dist/client.js), and package.json
14
+ * ships with every npm package regardless of `files`.
15
+ */
16
+ export declare const PACKAGE_VERSION: string;
17
+ /**
18
+ * Sent on every API call. The API classifies the client surface from it
19
+ * (cli / mcp / sdk / other) and records it on each request-log row, so MCP
20
+ * traffic is countable. The product token must stay `spicrawl-mcp/`.
21
+ */
22
+ export declare const USER_AGENT: string;
23
+ /**
24
+ * The docs base URL from the environment, no trailing slash; page paths are
25
+ * appended to it directly (`<base>/errors`, `<base>/llms.txt`):
26
+ *
27
+ * 1. SPICRAWL_DOCS_URL, the full base (`https://docs.spicrawl.com`, or
28
+ * `http://host:8080/docs` on a self-hosted deployment);
29
+ * 2. SPICRAWL_DOCS_HOST + `/docs`, the legacy origin-only setting, which keeps
30
+ * its old meaning so an existing env file does not silently change;
31
+ * 3. a self-hosted API (SPICRAWL_PUBLIC_BASE_URL, else SPICRAWL_BASE_URL) +
32
+ * `/docs`, where every deployment serves its own docs;
33
+ * 4. https://docs.spicrawl.com, also when that API is the public one.
34
+ *
35
+ * Read from the raw environment, not the client's baseURL, so a stdio server
36
+ * with no base URL set points at the public docs rather than localhost.
37
+ */
38
+ export declare function docsBaseFromEnv(env?: NodeJS.ProcessEnv): string;
39
+ /** The RFC 7807 problem shape every Spicrawl error uses. */
40
+ interface Problem {
41
+ code?: string;
42
+ title?: string;
43
+ detail?: string;
44
+ status?: number;
45
+ retryable?: boolean;
46
+ target_status?: number | null;
47
+ doc_url?: string;
48
+ }
49
+ /** A failed request, carrying the platform's own error code and guidance. */
50
+ export declare class ILayerError extends Error {
51
+ readonly code: string;
52
+ readonly status: number;
53
+ readonly retryable: boolean;
54
+ readonly docUrl?: string;
55
+ constructor(status: number, problem: Problem, fallback: string);
56
+ }
57
+ export interface ClientConfig {
58
+ baseURL?: string;
59
+ /**
60
+ * The API base URL as the MCP's USERS reach it, for URLs handed back to them
61
+ * (the browser tool's CDP URL). Differs from baseURL when the MCP talks to
62
+ * the API over loopback. Defaults to baseURL.
63
+ */
64
+ publicBaseURL?: string;
65
+ apiKey: string;
66
+ timeoutMs?: number;
67
+ /**
68
+ * The public docs base (`https://docs.spicrawl.com`, `http://host:8080/docs`)
69
+ * the docs tools read from. Defaults to docsBaseFromEnv(). The API key is
70
+ * never sent there.
71
+ */
72
+ docsBaseURL?: string;
73
+ }
74
+ export declare class ILayerClient {
75
+ readonly baseURL: string;
76
+ /** Base URL for URLs returned to the caller; never used for upstream calls. */
77
+ readonly publicBaseURL: string;
78
+ /** The caller's key. Read by tools that hand out URLs carrying it (browser). */
79
+ readonly apiKey: string;
80
+ /** Public docs base, e.g. https://docs.spicrawl.com; no trailing slash. */
81
+ readonly docsBaseURL: string;
82
+ private readonly timeoutMs;
83
+ constructor(config: ClientConfig);
84
+ /** POST /v1/scrape — retrieve, render, and extract one URL. */
85
+ scrape(params: Record<string, unknown>): Promise<unknown>;
86
+ /** Any REST call. Tools for newer endpoints use this directly. */
87
+ request(method: string, path: string, body?: Record<string, unknown>): Promise<unknown>;
88
+ }
89
+ export {};
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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"}