@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
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { COMING_SOON, run } from "./common.js";
|
|
3
|
+
import { attachScreenshots } from "./screenshots.js";
|
|
4
|
+
import { attachPdf } from "./pdf.js";
|
|
5
|
+
import { ActionsSchema, toApiActions } from "./actions.js";
|
|
6
|
+
export const registerScrapeTools = (server, client) => {
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// spicrawl_scrape — retrieve, render, and extract one URL
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
const ScrapeInput = {
|
|
11
|
+
url: z.string().url().describe("The absolute URL to retrieve."),
|
|
12
|
+
render: z
|
|
13
|
+
.boolean()
|
|
14
|
+
.default(false)
|
|
15
|
+
.describe("Render with a real browser (executes JavaScript). Use for single-page apps or pages whose content is painted client-side. Costs more than the default fetch tier."),
|
|
16
|
+
format: z
|
|
17
|
+
.enum(["markdown", "text", "html", "json", "pdf"])
|
|
18
|
+
.default("markdown")
|
|
19
|
+
.describe("Output format. 'markdown' (default) is best for feeding to an LLM; 'html' returns the raw document; 'json' returns a structured envelope; " +
|
|
20
|
+
"'pdf' prints the page in a browser and returns the file as a resource content block (needs `render` or `engine: \"chromium\"`)."),
|
|
21
|
+
main_content_only: z
|
|
22
|
+
.boolean()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Strip the page to its main article, dropping nav/footer/aside (like Firecrawl's onlyMainContent). Defaults on for markdown."),
|
|
25
|
+
include_tags: z
|
|
26
|
+
.array(z.string())
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("CSS selectors to KEEP (everything else is dropped)."),
|
|
29
|
+
exclude_tags: z
|
|
30
|
+
.array(z.string())
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("CSS selectors to remove (e.g. ['nav','.ad','#cookie'])."),
|
|
33
|
+
links: z
|
|
34
|
+
.boolean()
|
|
35
|
+
.default(false)
|
|
36
|
+
.describe("Also return the page's discovered links as an absolute, de-duplicated list."),
|
|
37
|
+
extract: z
|
|
38
|
+
.record(z.any())
|
|
39
|
+
.optional()
|
|
40
|
+
.describe('Selector-based extraction: a map of field -> CSS/XPath selector, e.g. {"title":"h1","price":".amount"}. Returns structured `data`. Precise and cheap when you know the page structure.'),
|
|
41
|
+
ai_extract: z
|
|
42
|
+
.object({
|
|
43
|
+
prompt: z.string().optional().describe("Describe the data to pull, in plain language."),
|
|
44
|
+
schema: z.record(z.any()).optional().describe("Optional JSON schema for typed output."),
|
|
45
|
+
})
|
|
46
|
+
.strict() // closed set: an unknown key here is refused, like a top-level one
|
|
47
|
+
.optional()
|
|
48
|
+
.describe(COMING_SOON +
|
|
49
|
+
"Model-driven extraction: describe what you want and let a model find it, no selectors. Refused with ERR::INTERNAL::UNAVAILABLE until it launches; use `extract` or `autoparse`."),
|
|
50
|
+
screenshot: z.boolean().default(false).describe("Capture a screenshot (requires render)."),
|
|
51
|
+
impersonate: z
|
|
52
|
+
.boolean()
|
|
53
|
+
.optional()
|
|
54
|
+
.describe("Fetch tier only: present a real browser's TLS/JA3 + HTTP/2 fingerprint to clear passive bot gates. On by default; set false to send a plain client handshake."),
|
|
55
|
+
premium_proxy: z
|
|
56
|
+
.boolean()
|
|
57
|
+
.default(false)
|
|
58
|
+
.describe(COMING_SOON + "Use a residential exit from Spicrawl's managed pool instead of datacenter. Until then, pass your own `proxy`."),
|
|
59
|
+
proxy_country: z
|
|
60
|
+
.string()
|
|
61
|
+
.length(2)
|
|
62
|
+
.optional()
|
|
63
|
+
.describe(COMING_SOON + "ISO-3166 alpha-2 country for a managed-pool exit, lowercase (e.g. 'us', 'de')."),
|
|
64
|
+
proxy: z
|
|
65
|
+
.string()
|
|
66
|
+
.optional()
|
|
67
|
+
.describe("Your own proxy URL to egress through. Takes precedence over the pool; no surcharge."),
|
|
68
|
+
cache: z
|
|
69
|
+
.boolean()
|
|
70
|
+
.optional()
|
|
71
|
+
.describe("Serve a recent cached result if fresh (on by default). A hit is billed at the same price as the fetch that stored it: the cache saves time, not credits. Set false only when you need live data."),
|
|
72
|
+
cache_ttl: z
|
|
73
|
+
.number()
|
|
74
|
+
.int()
|
|
75
|
+
.min(0)
|
|
76
|
+
.optional()
|
|
77
|
+
.describe("Max age in seconds of a cached copy to accept (0 forces a fresh fetch; capped at 48h). Freshness only: a hit is billed like a fetch."),
|
|
78
|
+
wait: z
|
|
79
|
+
.number()
|
|
80
|
+
.int()
|
|
81
|
+
.min(0)
|
|
82
|
+
.optional()
|
|
83
|
+
.describe("Milliseconds to wait after load before capturing (render only)."),
|
|
84
|
+
// --- The rest of POST /v1/scrape, under the API's own names (passed through).
|
|
85
|
+
engine: z
|
|
86
|
+
.enum(["fetch", "obscura", "chromium", "camoufox"])
|
|
87
|
+
.optional()
|
|
88
|
+
.describe("Pin the execution engine instead of letting the router choose. A pinned engine the deployment does not run is refused (ERR::ENGINE::UNAVAILABLE), never substituted. `camoufox` is coming soon: do not pin it yet."),
|
|
89
|
+
mode: z.enum(["auto"]).optional().describe("Routing mode. `auto` lets the platform escalate fetch -> browser as needed, billing only the rung that worked."),
|
|
90
|
+
stealth: z.boolean().optional().describe(COMING_SOON + "Stealth mode: render in the hardened Camoufox browser. Default false."),
|
|
91
|
+
headless: z.boolean().optional().describe("false runs Chromium on a real display (1920x1080 screen). chromium engine only."),
|
|
92
|
+
method: z.string().optional().describe("HTTP method for the target request (default GET)."),
|
|
93
|
+
custom_headers: z.record(z.string()).optional().describe("Extra request headers sent to the target, e.g. {\"Accept-Language\":\"de\"}."),
|
|
94
|
+
session_id: z
|
|
95
|
+
.string()
|
|
96
|
+
.optional()
|
|
97
|
+
.describe("Run inside a session from spicrawl_session_create: same exit IP and browser state (cookies, localStorage) across calls."),
|
|
98
|
+
sticky_key: z.string().optional().describe(COMING_SOON + "Reuse the same managed-pool exit for every request carrying this key, without a session."),
|
|
99
|
+
proxy_verify: z.boolean().optional().describe("Verify `proxy` is reachable before using it."),
|
|
100
|
+
wait_for: z.string().optional().describe("CSS selector to wait for before capturing (render only)."),
|
|
101
|
+
wait_for_timeout: z.number().int().min(0).optional().describe("Max milliseconds to wait for `wait_for`."),
|
|
102
|
+
block_resources: z
|
|
103
|
+
.array(z.string())
|
|
104
|
+
.optional()
|
|
105
|
+
.describe("Resource types the browser should not load, e.g. [\"fonts\",\"media\",\"images\"] (render only). Faster and cheaper."),
|
|
106
|
+
actions: ActionsSchema.optional(),
|
|
107
|
+
screenshot_fullpage: z.boolean().optional().describe("Capture the whole page, not just the viewport."),
|
|
108
|
+
screenshot_selector: z.string().optional().describe("Capture only the element matching this CSS selector."),
|
|
109
|
+
screenshot_format: z.string().optional().describe("Screenshot image format."),
|
|
110
|
+
screenshot_quality: z.number().int().min(1).max(100).optional().describe("Screenshot quality 1-100 (lossy formats)."),
|
|
111
|
+
autoparse: z
|
|
112
|
+
.boolean()
|
|
113
|
+
.optional()
|
|
114
|
+
.describe("Return the structured data the page publishes about itself (JSON-LD, OpenGraph, Twitter Card, microdata). No selectors; survives redesigns."),
|
|
115
|
+
extract_preset: z.string().optional().describe(COMING_SOON + "A named extraction preset instead of an inline `extract` map; any value fails today. Put the rules in `extract`."),
|
|
116
|
+
network_capture: z
|
|
117
|
+
.object({
|
|
118
|
+
urls: z.array(z.string()).optional().describe("URL patterns of the page's own requests to record."),
|
|
119
|
+
resource_types: z.array(z.string()).optional().describe("Resource types to record, e.g. [\"xhr\",\"fetch\"]."),
|
|
120
|
+
include_bodies: z.boolean().optional().describe("Include response bodies."),
|
|
121
|
+
max_body_bytes: z.number().int().optional().describe("Per-response body cap."),
|
|
122
|
+
max_responses: z.number().int().optional().describe("Cap on recorded responses."),
|
|
123
|
+
})
|
|
124
|
+
.strict() // closed set: an unknown key here is refused, like a top-level one
|
|
125
|
+
.optional()
|
|
126
|
+
.describe("Record the API responses the PAGE fetched while rendering — often cleaner JSON than the DOM. Render only."),
|
|
127
|
+
parse_pdf: z.boolean().optional().describe("Turn a PDF target into text/markdown (default true). false refuses PDFs."),
|
|
128
|
+
max_cost: z.number().int().min(0).optional().describe("Refuse (rather than run) a request that would cost more than this many credits."),
|
|
129
|
+
original_status: z.boolean().optional().describe("Return the target's own HTTP status instead of 200 for a successful scrape."),
|
|
130
|
+
allowed_status_codes: z
|
|
131
|
+
.array(z.number().int())
|
|
132
|
+
.optional()
|
|
133
|
+
.describe("Target statuses to treat as success instead of an error (e.g. [404] to capture a not-found page)."),
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* API parameter names this tool exposes under a different argument name. An
|
|
137
|
+
* API message saying "set `js_render=true`" is advice the agent cannot follow
|
|
138
|
+
* with this tool's schema, so errors and warnings are rewritten.
|
|
139
|
+
*/
|
|
140
|
+
const SCRAPE_ARG_NAMES = { js_render: "render", response_format: "format" };
|
|
141
|
+
/** Arguments passed to the API unchanged: the tool uses the REST name. */
|
|
142
|
+
const PASSTHROUGH = [
|
|
143
|
+
"engine", "mode", "stealth", "headless", "method", "custom_headers", "session_id", "sticky_key",
|
|
144
|
+
"proxy_verify", "wait_for", "wait_for_timeout", "block_resources", "screenshot_fullpage",
|
|
145
|
+
"screenshot_selector", "screenshot_format", "screenshot_quality", "autoparse", "extract_preset",
|
|
146
|
+
"network_capture", "parse_pdf", "max_cost", "original_status", "allowed_status_codes",
|
|
147
|
+
];
|
|
148
|
+
server.registerTool("spicrawl_scrape", {
|
|
149
|
+
title: "Scrape a URL",
|
|
150
|
+
description: "Retrieve a web page and return its content as markdown (default), text, HTML, a JSON envelope, or a printed PDF. " +
|
|
151
|
+
"Handles the fetch vs. browser decision, charset decoding, and PDF-to-text for you. " +
|
|
152
|
+
"Optionally extract structured data with CSS selectors (`extract`) or `autoparse` (a natural-language `ai_extract` is coming soon), " +
|
|
153
|
+
"return discovered `links`, take a `screenshot`, or scope the DOM with `include_tags`/`exclude_tags`. " +
|
|
154
|
+
"Results are cached by default, and a cache hit is billed like the fetch that stored it (the cache saves time, not credits); set `cache=false` for time-sensitive pages. " +
|
|
155
|
+
"Returns the content plus a `meta` object (engine, status, credits, cache state). " +
|
|
156
|
+
"Screenshots come back as image content blocks; each `screenshots` entry in the JSON keeps its metadata and says which block holds it. " +
|
|
157
|
+
"A PDF comes back as a resource content block, with its size and the engine and credits in the JSON.",
|
|
158
|
+
inputSchema: ScrapeInput,
|
|
159
|
+
annotations: {
|
|
160
|
+
readOnlyHint: true,
|
|
161
|
+
destructiveHint: false,
|
|
162
|
+
idempotentHint: false,
|
|
163
|
+
openWorldHint: true,
|
|
164
|
+
},
|
|
165
|
+
}, async (input) => {
|
|
166
|
+
const params = { url: input.url, response_format: input.format };
|
|
167
|
+
if (input.render)
|
|
168
|
+
params.js_render = true;
|
|
169
|
+
if (input.main_content_only !== undefined)
|
|
170
|
+
params.main_content_only = input.main_content_only;
|
|
171
|
+
if (input.include_tags?.length)
|
|
172
|
+
params.include_tags = input.include_tags;
|
|
173
|
+
if (input.exclude_tags?.length)
|
|
174
|
+
params.exclude_tags = input.exclude_tags;
|
|
175
|
+
if (input.links)
|
|
176
|
+
params.links = true;
|
|
177
|
+
if (input.extract)
|
|
178
|
+
params.extract = input.extract;
|
|
179
|
+
if (input.ai_extract)
|
|
180
|
+
params.ai_extract = input.ai_extract;
|
|
181
|
+
if (input.screenshot)
|
|
182
|
+
params.screenshot = true;
|
|
183
|
+
if (input.impersonate !== undefined)
|
|
184
|
+
params.impersonate = input.impersonate;
|
|
185
|
+
if (input.premium_proxy)
|
|
186
|
+
params.premium_proxy = true;
|
|
187
|
+
if (input.proxy_country)
|
|
188
|
+
params.proxy_country = input.proxy_country;
|
|
189
|
+
if (input.proxy)
|
|
190
|
+
params.proxy = input.proxy;
|
|
191
|
+
if (input.cache !== undefined)
|
|
192
|
+
params.cache = input.cache;
|
|
193
|
+
if (input.cache_ttl !== undefined)
|
|
194
|
+
params.cache_ttl = input.cache_ttl;
|
|
195
|
+
if (input.wait !== undefined)
|
|
196
|
+
params.wait = input.wait;
|
|
197
|
+
if (input.actions?.length)
|
|
198
|
+
params.actions = toApiActions(input.actions);
|
|
199
|
+
for (const k of PASSTHROUGH) {
|
|
200
|
+
const v = input[k];
|
|
201
|
+
if (v !== undefined)
|
|
202
|
+
params[k] = v;
|
|
203
|
+
}
|
|
204
|
+
// Screenshots become image blocks and a PDF a resource block, in that order.
|
|
205
|
+
const attach = (data) => {
|
|
206
|
+
const shots = attachScreenshots(data);
|
|
207
|
+
const pdf = attachPdf(shots.data, input.url, shots.blocks.length);
|
|
208
|
+
return { data: pdf.data, blocks: [...shots.blocks, ...pdf.blocks] };
|
|
209
|
+
};
|
|
210
|
+
return run(() => client.scrape(params), attach, SCRAPE_ARG_NAMES);
|
|
211
|
+
});
|
|
212
|
+
};
|
|
213
|
+
//# sourceMappingURL=scrape.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrape.js","sourceRoot":"","sources":["../../src/tools/scrape.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Attached } from "./common.js";
|
|
2
|
+
export declare const MAX_IMAGE_BASE64: number;
|
|
3
|
+
/**
|
|
4
|
+
* Moves the inline base64 screenshots of a scrape envelope into MCP image
|
|
5
|
+
* blocks, leaving a small placeholder in their place.
|
|
6
|
+
*
|
|
7
|
+
* A model reads an image block as a picture; the same bytes as JSON text are
|
|
8
|
+
* unreadable noise that can run to megabytes of its context. Anything that is
|
|
9
|
+
* not an inline base64 entry (a URL or object ref, an unknown shape) is left
|
|
10
|
+
* untouched, and so is any payload without a `screenshots` array.
|
|
11
|
+
*
|
|
12
|
+
* Batch payloads are deliberately not passed through here: the batch worker
|
|
13
|
+
* writes its own body, not the /v1/scrape envelope, and emits no screenshots.
|
|
14
|
+
*/
|
|
15
|
+
export declare function attachScreenshots(data: unknown): Attached;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Largest base64 string attached as an image. The worker only inlines images up
|
|
2
|
+
// to 2 MiB raw (~2.8 MB base64) and the API drops anything larger, so today this
|
|
3
|
+
// never trips; it exists so a change upstream cannot put an image into the
|
|
4
|
+
// result that the model host then rejects wholesale. 5 MB is the per-image
|
|
5
|
+
// ceiling of the Claude API, the strictest common MCP host.
|
|
6
|
+
export const MAX_IMAGE_BASE64 = 5 * 1024 * 1024;
|
|
7
|
+
const MIME = { png: "image/png", jpeg: "image/jpeg", jpg: "image/jpeg", webp: "image/webp" };
|
|
8
|
+
// The API leaves `format` out when the worker named none; the base64 of the
|
|
9
|
+
// container's magic bytes still identifies the common ones.
|
|
10
|
+
function sniff(b64) {
|
|
11
|
+
if (b64.startsWith("iVBORw0KGgo"))
|
|
12
|
+
return "image/png";
|
|
13
|
+
if (b64.startsWith("/9j/"))
|
|
14
|
+
return "image/jpeg";
|
|
15
|
+
if (b64.startsWith("UklGR") && b64.slice(8, 16) === "V0VCUF")
|
|
16
|
+
return "image/webp";
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Moves the inline base64 screenshots of a scrape envelope into MCP image
|
|
21
|
+
* blocks, leaving a small placeholder in their place.
|
|
22
|
+
*
|
|
23
|
+
* A model reads an image block as a picture; the same bytes as JSON text are
|
|
24
|
+
* unreadable noise that can run to megabytes of its context. Anything that is
|
|
25
|
+
* not an inline base64 entry (a URL or object ref, an unknown shape) is left
|
|
26
|
+
* untouched, and so is any payload without a `screenshots` array.
|
|
27
|
+
*
|
|
28
|
+
* Batch payloads are deliberately not passed through here: the batch worker
|
|
29
|
+
* writes its own body, not the /v1/scrape envelope, and emits no screenshots.
|
|
30
|
+
*/
|
|
31
|
+
export function attachScreenshots(data) {
|
|
32
|
+
if (typeof data !== "object" || data === null || Array.isArray(data))
|
|
33
|
+
return { data, blocks: [] };
|
|
34
|
+
const shots = data.screenshots;
|
|
35
|
+
if (!Array.isArray(shots))
|
|
36
|
+
return { data, blocks: [] };
|
|
37
|
+
const images = [];
|
|
38
|
+
const replaced = shots.map((shot) => {
|
|
39
|
+
if (typeof shot !== "object" || shot === null)
|
|
40
|
+
return shot;
|
|
41
|
+
const s = shot;
|
|
42
|
+
if (typeof s.data !== "string" || (s.encoding !== undefined && s.encoding !== "base64"))
|
|
43
|
+
return shot;
|
|
44
|
+
const { data: b64, encoding: _encoding, ...rest } = s;
|
|
45
|
+
const format = typeof s.format === "string" ? s.format.toLowerCase() : undefined;
|
|
46
|
+
const mimeType = (format && MIME[format]) || sniff(b64);
|
|
47
|
+
const placeholder = { ...rest };
|
|
48
|
+
if (!mimeType) {
|
|
49
|
+
placeholder.not_attached =
|
|
50
|
+
"The image format is unknown, so it cannot be attached as an image. Re-request with screenshot_format=png.";
|
|
51
|
+
}
|
|
52
|
+
else if (b64.length > MAX_IMAGE_BASE64) {
|
|
53
|
+
placeholder.not_attached =
|
|
54
|
+
`The image is ${(b64.length / 1e6).toFixed(1)} MB as base64, over the ${MAX_IMAGE_BASE64 / (1024 * 1024)} MB ` +
|
|
55
|
+
"per-image limit model hosts accept. Re-request a smaller one (screenshot_format=jpeg with a lower " +
|
|
56
|
+
"screenshot_quality, no screenshot_fullpage, or a screenshot_selector), or call POST /v1/scrape directly for the bytes.";
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
images.push({ type: "image", data: b64, mimeType });
|
|
60
|
+
// content[0] is the JSON text, so image n is content[n].
|
|
61
|
+
placeholder.attached_as = `image content block ${images.length} (content[${images.length}], ${mimeType})`;
|
|
62
|
+
}
|
|
63
|
+
return placeholder;
|
|
64
|
+
});
|
|
65
|
+
if (replaced.every((r, i) => r === shots[i]))
|
|
66
|
+
return { data, blocks: [] };
|
|
67
|
+
return { data: { ...data, screenshots: replaced }, blocks: images };
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=screenshots.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenshots.js","sourceRoot":"","sources":["../../src/tools/screenshots.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { COMING_SOON, run } from "./common.js";
|
|
3
|
+
const sessionPath = (id) => `/v1/sessions/${encodeURIComponent(id)}`;
|
|
4
|
+
function withQuery(path, q) {
|
|
5
|
+
const params = new URLSearchParams();
|
|
6
|
+
for (const [k, v] of Object.entries(q))
|
|
7
|
+
if (v !== undefined)
|
|
8
|
+
params.set(k, String(v));
|
|
9
|
+
const s = params.toString();
|
|
10
|
+
return s ? `${path}?${s}` : path;
|
|
11
|
+
}
|
|
12
|
+
const sessionId = z.string().min(1).describe("The session id returned by spicrawl_session_create or spicrawl_session_list.");
|
|
13
|
+
const force = z
|
|
14
|
+
.boolean()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("Break an active lease (a scrape currently using the session). Only set when you know it is safe.");
|
|
17
|
+
const engines = ["fetch", "obscura", "chromium", "camoufox"];
|
|
18
|
+
// Sessions: /v1/sessions.
|
|
19
|
+
export const registerSessionsTools = (server, client) => {
|
|
20
|
+
// -------------------------------------------------------------------------
|
|
21
|
+
// spicrawl_session_create — POST /v1/sessions
|
|
22
|
+
// -------------------------------------------------------------------------
|
|
23
|
+
server.registerTool("spicrawl_session_create", {
|
|
24
|
+
title: "Create a browsing session",
|
|
25
|
+
description: "Create a session: a persistent browser identity (cookie jar, localStorage, pinned engine and, by default, a " +
|
|
26
|
+
"sticky exit IP) that survives across scrapes. Use it for multi-step flows — log in, then fetch pages behind " +
|
|
27
|
+
"the login — by passing the returned `id` as `session_id` to every `spicrawl_scrape` call that should share the " +
|
|
28
|
+
"state. The engine is pinned for the session's lifetime. Without `engine`, the server picks one this deployment " +
|
|
29
|
+
"runs: `obscura`, else `chromium`, else `fetch`. Release the session with `spicrawl_session_release` when done. " +
|
|
30
|
+
"Returns the session object (id, engine, status, sticky_key, proxy, expires_at).",
|
|
31
|
+
inputSchema: {
|
|
32
|
+
engine: z
|
|
33
|
+
.enum(engines)
|
|
34
|
+
.optional()
|
|
35
|
+
.describe("Engine pinned for the session's lifetime. Default: `obscura` if deployed, else `chromium`, else `fetch`. `camoufox` is coming soon: do not pin it yet."),
|
|
36
|
+
ttl_seconds: z
|
|
37
|
+
.number()
|
|
38
|
+
.int()
|
|
39
|
+
.min(30)
|
|
40
|
+
.optional()
|
|
41
|
+
.describe("Idle lifetime in seconds (min 30, default 1800; the organization's maximum is enforced, not clamped)."),
|
|
42
|
+
sticky_key: z
|
|
43
|
+
.string()
|
|
44
|
+
.optional()
|
|
45
|
+
.describe(COMING_SOON + "Pin the managed-pool exit IP to this key; sessions sharing a key share an exit. Cannot combine with rotate_ip."),
|
|
46
|
+
rotate_ip: z
|
|
47
|
+
.boolean()
|
|
48
|
+
.optional()
|
|
49
|
+
.describe(COMING_SOON + "Keep the cookie jar stable but rotate the managed-pool exit IP per request. Cannot combine with sticky_key."),
|
|
50
|
+
region_pool: z.string().optional().describe(COMING_SOON + "Managed-pool region to draw the exit from."),
|
|
51
|
+
premium_proxy: z.boolean().optional().describe(COMING_SOON + "Use a residential managed-pool exit, exactly as on spicrawl_scrape."),
|
|
52
|
+
proxy_country: z
|
|
53
|
+
.string()
|
|
54
|
+
.length(2)
|
|
55
|
+
.optional()
|
|
56
|
+
.describe(COMING_SOON + "ISO-3166 alpha-2 exit country; requires premium_proxy."),
|
|
57
|
+
fingerprint: z
|
|
58
|
+
.record(z.unknown())
|
|
59
|
+
.optional()
|
|
60
|
+
.describe(COMING_SOON + "Camoufox fingerprint pin, stored verbatim. Only valid with engine `camoufox`."),
|
|
61
|
+
domain_scores: z
|
|
62
|
+
.record(z.object({
|
|
63
|
+
score: z.number(),
|
|
64
|
+
good: z.number().int(),
|
|
65
|
+
bad: z.number().int(),
|
|
66
|
+
last_updated: z.number().int(),
|
|
67
|
+
}).passthrough())
|
|
68
|
+
.optional()
|
|
69
|
+
.describe("Seed per-domain error scores (usually copied from a spicrawl_session_context dump)."),
|
|
70
|
+
session_context: z
|
|
71
|
+
.record(z.unknown())
|
|
72
|
+
.optional()
|
|
73
|
+
.describe("Seed the session's state (cookies, storage) — e.g. the `session_context` object from spicrawl_session_context, to clone a session."),
|
|
74
|
+
},
|
|
75
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
76
|
+
}, async (input) => {
|
|
77
|
+
const body = {};
|
|
78
|
+
for (const [k, v] of Object.entries(input))
|
|
79
|
+
if (v !== undefined)
|
|
80
|
+
body[k] = v;
|
|
81
|
+
return run(() => client.request("POST", "/v1/sessions", body));
|
|
82
|
+
});
|
|
83
|
+
// -------------------------------------------------------------------------
|
|
84
|
+
// spicrawl_session_list — GET /v1/sessions
|
|
85
|
+
// -------------------------------------------------------------------------
|
|
86
|
+
server.registerTool("spicrawl_session_list", {
|
|
87
|
+
title: "List sessions",
|
|
88
|
+
description: "List this API key's project's sessions, newest first. Returns `{ sessions: [...], next_cursor? }`; pass " +
|
|
89
|
+
"`next_cursor` back as `cursor` for the next page. Never includes the stored credentials.",
|
|
90
|
+
inputSchema: {
|
|
91
|
+
status: z.enum(["active", "released", "expired"]).optional().describe("Only sessions in this state."),
|
|
92
|
+
engine: z.enum(engines).optional().describe("Only sessions pinned to this engine."),
|
|
93
|
+
limit: z.number().int().min(1).max(200).optional().describe("Page size (default 50, max 200)."),
|
|
94
|
+
cursor: z.string().optional().describe("`next_cursor` from a previous page, verbatim."),
|
|
95
|
+
},
|
|
96
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
97
|
+
}, async ({ status, engine, limit, cursor }) => run(() => client.request("GET", withQuery("/v1/sessions", { status, engine, limit, cursor }))));
|
|
98
|
+
// -------------------------------------------------------------------------
|
|
99
|
+
// spicrawl_session_get — GET /v1/sessions/{id}
|
|
100
|
+
// -------------------------------------------------------------------------
|
|
101
|
+
server.registerTool("spicrawl_session_get", {
|
|
102
|
+
title: "Get a session",
|
|
103
|
+
description: "Return one session's metadata: status, engine, proxy/exit, usage count, expiry, lease holder, and a summary " +
|
|
104
|
+
"of its stored context. Never returns the credentials themselves (use spicrawl_session_context for that).",
|
|
105
|
+
inputSchema: { session_id: sessionId },
|
|
106
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
107
|
+
}, async ({ session_id }) => run(() => client.request("GET", sessionPath(session_id))));
|
|
108
|
+
// -------------------------------------------------------------------------
|
|
109
|
+
// spicrawl_session_context — GET /v1/sessions/{id}/context
|
|
110
|
+
// -------------------------------------------------------------------------
|
|
111
|
+
server.registerTool("spicrawl_session_context", {
|
|
112
|
+
title: "Dump a session's stored context",
|
|
113
|
+
description: "Return the full stored state of a LIVE session — cookies, localStorage and other credentials — plus its " +
|
|
114
|
+
"fingerprint and domain scores. The `session_context` object can be passed to spicrawl_session_create to clone " +
|
|
115
|
+
"the session. The result contains secrets (login cookies): do not echo it to the user or logs unless asked.",
|
|
116
|
+
inputSchema: { session_id: sessionId },
|
|
117
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
118
|
+
}, async ({ session_id }) => run(() => client.request("GET", `${sessionPath(session_id)}/context`)));
|
|
119
|
+
// -------------------------------------------------------------------------
|
|
120
|
+
// spicrawl_session_release — POST /v1/sessions/{id}/release
|
|
121
|
+
// -------------------------------------------------------------------------
|
|
122
|
+
server.registerTool("spicrawl_session_release", {
|
|
123
|
+
title: "Release a session",
|
|
124
|
+
description: "End a session when you are done with it: frees its exit IP and browser state; it can no longer be used with " +
|
|
125
|
+
"spicrawl_scrape. The record stays visible (status `released`) so its history can still be inspected. " +
|
|
126
|
+
"Refused while a scrape holds the session's lease unless `force` is true. Returns the session object.",
|
|
127
|
+
inputSchema: { session_id: sessionId, force },
|
|
128
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
129
|
+
}, async ({ session_id, force }) => run(() => client.request("POST", withQuery(`${sessionPath(session_id)}/release`, { force }))));
|
|
130
|
+
// -------------------------------------------------------------------------
|
|
131
|
+
// spicrawl_session_delete — DELETE /v1/sessions/{id}
|
|
132
|
+
// -------------------------------------------------------------------------
|
|
133
|
+
server.registerTool("spicrawl_session_delete", {
|
|
134
|
+
title: "Delete a session",
|
|
135
|
+
description: "Permanently delete a session and its stored state, including the record itself (prefer spicrawl_session_release " +
|
|
136
|
+
"if you only want to stop using it). Idempotent: deleting an already-deleted session succeeds. Refused while a " +
|
|
137
|
+
"scrape holds the session's lease unless `force` is true.",
|
|
138
|
+
inputSchema: { session_id: sessionId, force },
|
|
139
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
|
|
140
|
+
}, async ({ session_id, force }) => run(async () => {
|
|
141
|
+
await client.request("DELETE", withQuery(sessionPath(session_id), { force }));
|
|
142
|
+
return { deleted: true, session_id };
|
|
143
|
+
}));
|
|
144
|
+
};
|
|
145
|
+
//# sourceMappingURL=sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../src/tools/sessions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { run } from "./common.js";
|
|
3
|
+
// Usage analytics: /v1/usage, /v1/usage/summary, /v1/usage/reconciliation.
|
|
4
|
+
// All three require the API key to carry the `read` scope (not in the default
|
|
5
|
+
// key scopes {scrape, batch, sessions}); without it the API answers 403.
|
|
6
|
+
const METRICS = [
|
|
7
|
+
"requests",
|
|
8
|
+
"credits",
|
|
9
|
+
"engine_ms",
|
|
10
|
+
"bytes_egress",
|
|
11
|
+
"bytes_ingress",
|
|
12
|
+
"proxy_bytes_dc",
|
|
13
|
+
"proxy_bytes_resi",
|
|
14
|
+
"extractions",
|
|
15
|
+
"ai_extractions",
|
|
16
|
+
"batch_items",
|
|
17
|
+
"requests_failed",
|
|
18
|
+
"requests_timeout",
|
|
19
|
+
"feature_actions",
|
|
20
|
+
"feature_ai_extract",
|
|
21
|
+
"feature_autoparse",
|
|
22
|
+
"feature_extract",
|
|
23
|
+
"feature_extract_preset",
|
|
24
|
+
"feature_js_render",
|
|
25
|
+
"feature_links",
|
|
26
|
+
"feature_network_capture",
|
|
27
|
+
"feature_pdf",
|
|
28
|
+
"feature_proxy_country",
|
|
29
|
+
"feature_proxy_custom",
|
|
30
|
+
"feature_screenshot",
|
|
31
|
+
"feature_session",
|
|
32
|
+
"client_cli",
|
|
33
|
+
"client_mcp",
|
|
34
|
+
"client_sdk",
|
|
35
|
+
"client_other",
|
|
36
|
+
];
|
|
37
|
+
const DATE = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "YYYY-MM-DD");
|
|
38
|
+
export const registerUsageTools = (server, client) => {
|
|
39
|
+
server.registerTool("spicrawl_usage", {
|
|
40
|
+
title: "Usage breakdown",
|
|
41
|
+
description: "Billing-grade usage for your org over a date window, from the daily rollup (usage_daily), grouped by day, project, engine, feature or key. group_by=key returns per API key (key_id, name, prefix, project_id) counters per metric per day, " +
|
|
42
|
+
"from usage_key_daily; keyless (dashboard) usage is not in it, and a project-scoped key sees only its own project's keys. " +
|
|
43
|
+
"Use it to answer 'how many requests/credits did I use, and where'. The window defaults to the last 30 days including today; `to` is EXCLUSIVE " +
|
|
44
|
+
"(a single day 2026-08-01 is from=2026-08-01, to=2026-08-02) and the window may not exceed 400 days. The response carries stale_seconds " +
|
|
45
|
+
"(age of the newest rollup row; null when the window is empty). Requires the key to have the `read` scope.",
|
|
46
|
+
inputSchema: {
|
|
47
|
+
from: DATE.optional().describe("Inclusive start date, YYYY-MM-DD (UTC). Default: 29 days before today."),
|
|
48
|
+
to: DATE.optional().describe("EXCLUSIVE end date, YYYY-MM-DD (UTC). Default: tomorrow, so today is included."),
|
|
49
|
+
group_by: z
|
|
50
|
+
.enum(["day", "project", "engine", "feature", "key"])
|
|
51
|
+
.optional()
|
|
52
|
+
.describe("How to group the rows. Default 'day'."),
|
|
53
|
+
project_id: z.string().optional().describe("Restrict to one project id (must be a valid Spicrawl identifier)."),
|
|
54
|
+
metrics: z
|
|
55
|
+
.array(z.enum(METRICS))
|
|
56
|
+
.optional()
|
|
57
|
+
.describe("Metrics to return. Omit for all of them."),
|
|
58
|
+
},
|
|
59
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
60
|
+
}, async (args) => run(() => {
|
|
61
|
+
const qs = new URLSearchParams();
|
|
62
|
+
if (args.from)
|
|
63
|
+
qs.set("from", args.from);
|
|
64
|
+
if (args.to)
|
|
65
|
+
qs.set("to", args.to);
|
|
66
|
+
if (args.group_by)
|
|
67
|
+
qs.set("group_by", args.group_by);
|
|
68
|
+
if (args.project_id)
|
|
69
|
+
qs.set("project_id", args.project_id);
|
|
70
|
+
if (args.metrics?.length)
|
|
71
|
+
qs.set("metrics", args.metrics.join(","));
|
|
72
|
+
const s = qs.toString();
|
|
73
|
+
return client.request("GET", `/v1/usage${s ? `?${s}` : ""}`);
|
|
74
|
+
}));
|
|
75
|
+
server.registerTool("spicrawl_usage_summary", {
|
|
76
|
+
title: "Usage summary",
|
|
77
|
+
description: "A compact current-period summary of your org's usage (totals as of now, from the daily rollup). No parameters. " +
|
|
78
|
+
"Use for a quick 'how much have I used so far'; use spicrawl_usage for a windowed breakdown. Requires the `read` scope.",
|
|
79
|
+
inputSchema: {},
|
|
80
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
81
|
+
}, async () => run(() => client.request("GET", "/v1/usage/summary")));
|
|
82
|
+
server.registerTool("spicrawl_usage_reconciliation", {
|
|
83
|
+
title: "Usage reconciliation",
|
|
84
|
+
description: "Drift report for ONE day: re-derives usage from that day's request log and compares it with the billed daily rollup, so you can see whether " +
|
|
85
|
+
"the rollup missed or double-counted anything. Defaults to yesterday (UTC); today is still accumulating and would always show drift. " +
|
|
86
|
+
"Requires the `read` scope.",
|
|
87
|
+
inputSchema: {
|
|
88
|
+
day: DATE.optional().describe("The UTC day to reconcile, YYYY-MM-DD. Default: yesterday."),
|
|
89
|
+
},
|
|
90
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
91
|
+
}, async ({ day }) => run(() => client.request("GET", `/v1/usage/reconciliation${day ? `?day=${encodeURIComponent(day)}` : ""}`)));
|
|
92
|
+
};
|
|
93
|
+
//# sourceMappingURL=usage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage.js","sourceRoot":"","sources":["../../src/tools/usage.ts"],"names":[],"mappings":"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"}
|