@webcrawl/mcp 1.3.7 → 1.3.8

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/dist/format.d.ts CHANGED
@@ -10,4 +10,5 @@ export declare function formatScrape(res: Rec): string;
10
10
  export declare function formatParse(res: Rec): string;
11
11
  export declare function formatMap(res: Rec): string;
12
12
  export declare function formatCrawl(res: Rec): string;
13
+ export declare function formatMonitor(res: Rec): string;
13
14
  export {};
package/dist/format.js CHANGED
@@ -124,3 +124,66 @@ export function formatCrawl(res) {
124
124
  })
125
125
  .join("\n\n---\n\n");
126
126
  }
127
+ function formatMonitorSummaryLine(m, i) {
128
+ const prefix = i !== undefined ? `${i + 1}. ` : "";
129
+ const id = str(m.id);
130
+ const name = str(m.name) || str(m.url) || "(unnamed watch)";
131
+ const status = str(m.status) || "active";
132
+ const mode = str(m.mode) || "page";
133
+ const urls = Array.isArray(m.urls) ? m.urls.map(str).filter(Boolean) : [];
134
+ const scopeLabel = mode === "site"
135
+ ? `site (up to ${str(m.maxPages) || "5"} pages)`
136
+ : mode === "urls"
137
+ ? `${urls.length || 1} URLs`
138
+ : "single page";
139
+ const interval = typeof m.intervalMinutes === "number" ? `every ${m.intervalMinutes}m` : "";
140
+ const condExpr = str(m.conditionJev) || (typeof m.condition === "string" ? m.condition : "");
141
+ const goal = condExpr ? `rule: ${condExpr}` : str(m.watchFor) ? `goal: ${str(m.watchFor)}` : "any change";
142
+ const condMet = typeof m.lastConditionMet === "boolean"
143
+ ? m.lastConditionMet
144
+ ? "triggered"
145
+ : "waiting"
146
+ : "";
147
+ const meta = [status, scopeLabel, interval, condMet].filter(Boolean).join(" · ");
148
+ const diff = str(m.lastDiffSummary);
149
+ return [
150
+ `${prefix}${name} [${id}] (${meta})`,
151
+ ` URL: ${str(m.url)}${urls.length > 1 ? ` (+${urls.length - 1} more)` : ""}`,
152
+ ` Watch: ${goal}`,
153
+ diff ? ` Latest: ${diff}` : "",
154
+ ]
155
+ .filter(Boolean)
156
+ .join("\n");
157
+ }
158
+ export function formatMonitor(res) {
159
+ if (res.deleted === true) {
160
+ return "Monitor deleted.";
161
+ }
162
+ if (Array.isArray(res.monitors)) {
163
+ const list = res.monitors.filter(isRec);
164
+ if (!list.length) {
165
+ return "No active watches.";
166
+ }
167
+ return `monitors (${list.length}):\n${list.map((m, i) => formatMonitorSummaryLine(m, i)).join("\n\n")}`;
168
+ }
169
+ const m = isRec(res.monitor) ? res.monitor : isRec(res.data) ? res.data : null;
170
+ if (!m) {
171
+ return JSON.stringify(res, null, 2);
172
+ }
173
+ const check = isRec(res.check) ? res.check : isRec(res.initialCheck) ? res.initialCheck : null;
174
+ const parts = [formatMonitorSummaryLine(m)];
175
+ if (check) {
176
+ const condRes = isRec(check.conditionResult) ? check.conditionResult : null;
177
+ if (condRes && str(condRes.summary)) {
178
+ parts.push(`Condition: ${str(condRes.summary)} (met: ${Boolean(condRes.met)})`);
179
+ }
180
+ if (str(check.diffSummary)) {
181
+ parts.push(`Summary: ${str(check.diffSummary)}`);
182
+ }
183
+ }
184
+ const snap = (check && str(check.snapshot)) || str(m.lastSnapshot);
185
+ if (snap) {
186
+ parts.push(`Snapshot:\n${truncate(snap, 6_000)}`);
187
+ }
188
+ return parts.join("\n\n");
189
+ }
package/dist/server.d.ts CHANGED
@@ -7,6 +7,12 @@ export interface McpServerOptions {
7
7
  }
8
8
  export type WebcrawlLikeClient = Pick<WebcrawlClient, "baseUrl" | "search" | "scrape" | "parse" | "run" | "map" | "crawl" | "extract" | "rerank" | "categories"> & {
9
9
  targets?: () => Promise<Record<string, unknown>>;
10
+ monitors?: WebcrawlClient["monitors"];
11
+ getMonitor?: WebcrawlClient["getMonitor"];
12
+ createMonitor?: WebcrawlClient["createMonitor"];
13
+ checkMonitor?: WebcrawlClient["checkMonitor"];
14
+ updateMonitor?: WebcrawlClient["updateMonitor"];
15
+ deleteMonitor?: WebcrawlClient["deleteMonitor"];
10
16
  };
11
17
  export declare function resolveSchemaProfile(env?: NodeJS.ProcessEnv): SchemaProfile;
12
18
  /**
package/dist/server.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { describeError } from "@webcrawl/client";
2
+ import { WebcrawlError, describeError } from "@webcrawl/client";
3
3
  import { z } from "zod";
4
- import { formatCredits, formatCrawl, formatMap, formatParse, formatScrape, formatSearch, truncate } from "./format.js";
4
+ import { formatCredits, formatCrawl, formatMap, formatMonitor, formatParse, formatScrape, formatSearch, truncate, } from "./format.js";
5
5
  const INSTRUCTIONS = `Webcrawl Anything gives you live web & target inspection access (zero-key local engine or webcrawl.app cloud).
6
6
  - web_search: find pages or structured results. Set category for flights, hotels, real-estate, shopping, news, local business, security (CVEs), academic or tech. Results are localised to the user.
7
7
  - web_scrape: read one URL as clean markdown with target evidence (websites, SPAs, GitHub, ArXiv, llms.txt).
@@ -10,6 +10,7 @@ const INSTRUCTIONS = `Webcrawl Anything gives you live web & target inspection a
10
10
  - web_crawl: read several pages of one site (max 25).
11
11
  - web_extract: pull structured JSON from one URL with a prompt or schema.
12
12
  - web_rerank: order your own documents by relevance to a query.
13
+ - web_monitor: watch a page, list of URLs, or whole website on a schedule with trigger conditions and email/webhook alerts (create, list, get, check, pause, resume, delete).
13
14
  - search_categories: list valid categories, subcategories, filters, and supported crawl targets.`;
14
15
  const ok = (text) => ({ content: [{ type: "text", text: truncate(text) }] });
15
16
  export function resolveSchemaProfile(env = process.env) {
@@ -183,6 +184,102 @@ export function createWebcrawlMcpServer(client, versionOrOpts = "0.1.0") {
183
184
  });
184
185
  return (lines.length ? lines.join("\n") : JSON.stringify(res, null, 2)) + formatCredits(res);
185
186
  }));
187
+ server.registerTool("web_monitor", {
188
+ title: "Monitor web pages or sites",
189
+ description: compact
190
+ ? "Create, list, inspect, check, pause, resume, or delete scheduled web monitors with trigger conditions."
191
+ : "Watch a single page, a list of URLs, or a whole website section on a schedule. Supports natural-language `watch_for` focus, explicit `condition` rules (e.g. `price < 50`, `content contains \"In Stock\"`, `added_urls_count > 0`), and email or webhook notifications.",
192
+ inputSchema: {
193
+ action: desc(z.enum(["create", "list", "get", "check", "pause", "resume", "delete"]).optional(), "Action to perform (defaults to 'create' when url/urls given, else 'list')"),
194
+ id: desc(z.string().optional(), "Monitor ID (required for get, check, pause, resume, delete)"),
195
+ url: desc(z.string().url().optional(), "Page or site URL to watch"),
196
+ urls: desc(z.array(z.string().url()).optional(), "Multiple URLs to watch when mode is 'urls'"),
197
+ mode: desc(z.enum(["page", "urls", "site"]).optional(), "Watch scope: page (default), urls, or site"),
198
+ max_pages: desc(z.number().int().min(1).max(25).optional(), "Max pages to sample when mode is 'site' (default 5)"),
199
+ name: desc(z.string().optional(), "Optional friendly watch name"),
200
+ interval_minutes: desc(z.number().int().min(5).max(10080).optional(), "Check interval in minutes (default 60)"),
201
+ watch_for: desc(z.string().optional(), "What to watch for, e.g. 'Pricing tiers and discounts'"),
202
+ condition: desc(z.string().optional(), "Optional trigger rule expression, e.g. 'price < 50', 'content contains \"In Stock\"', 'added_urls_count > 0'"),
203
+ notify_email: desc(z.string().optional(), "Email address to notify when triggered, or 'true' to use the account email"),
204
+ webhook_url: desc(z.string().url().optional(), "HTTPS webhook URL to POST when triggered"),
205
+ },
206
+ annotations: { readOnlyHint: false, openWorldHint: true },
207
+ }, ({ action, id, url, urls, mode, max_pages, name, interval_minutes, watch_for, condition, notify_email, webhook_url, }) => run(async () => {
208
+ const effectiveAction = action ?? (url || (urls && urls.length) ? "create" : "list");
209
+ const requireId = (op) => {
210
+ if (!id) {
211
+ throw new WebcrawlError("bad_request", `web_monitor action '${op}' requires 'id'`);
212
+ }
213
+ return id;
214
+ };
215
+ switch (effectiveAction) {
216
+ case "list": {
217
+ if (!client.monitors) {
218
+ throw new WebcrawlError("bad_request", "Web monitors require a cloud WebcrawlClient (set WEBCRAWL_API_KEY).");
219
+ }
220
+ const res = await client.monitors();
221
+ return formatMonitor(res) + formatCredits(res);
222
+ }
223
+ case "get": {
224
+ if (!client.getMonitor) {
225
+ throw new WebcrawlError("bad_request", "Web monitors require a cloud WebcrawlClient (set WEBCRAWL_API_KEY).");
226
+ }
227
+ const res = await client.getMonitor(requireId("get"));
228
+ return formatMonitor(res) + formatCredits(res);
229
+ }
230
+ case "check": {
231
+ if (!client.checkMonitor) {
232
+ throw new WebcrawlError("bad_request", "Web monitors require a cloud WebcrawlClient (set WEBCRAWL_API_KEY).");
233
+ }
234
+ const res = await client.checkMonitor(requireId("check"));
235
+ return formatMonitor(res) + formatCredits(res);
236
+ }
237
+ case "pause":
238
+ case "resume": {
239
+ if (!client.updateMonitor) {
240
+ throw new WebcrawlError("bad_request", "Web monitors require a cloud WebcrawlClient (set WEBCRAWL_API_KEY).");
241
+ }
242
+ const res = await client.updateMonitor({
243
+ id: requireId(effectiveAction),
244
+ status: effectiveAction === "pause" ? "paused" : "active",
245
+ ...(name !== undefined ? { name } : {}),
246
+ ...(interval_minutes !== undefined ? { intervalMinutes: interval_minutes } : {}),
247
+ ...(watch_for !== undefined ? { watchFor: watch_for } : {}),
248
+ ...(condition !== undefined ? { condition } : {}),
249
+ ...(notify_email !== undefined
250
+ ? { notifyEmail: notify_email === "true" ? true : notify_email }
251
+ : {}),
252
+ ...(webhook_url !== undefined ? { webhookUrl: webhook_url } : {}),
253
+ });
254
+ return formatMonitor(res) + formatCredits(res);
255
+ }
256
+ case "delete": {
257
+ if (!client.deleteMonitor) {
258
+ throw new WebcrawlError("bad_request", "Web monitors require a cloud WebcrawlClient (set WEBCRAWL_API_KEY).");
259
+ }
260
+ const res = await client.deleteMonitor(requireId("delete"));
261
+ return formatMonitor(res) + formatCredits(res);
262
+ }
263
+ case "create": {
264
+ if (!client.createMonitor) {
265
+ throw new WebcrawlError("bad_request", "Web monitors require a cloud WebcrawlClient (set WEBCRAWL_API_KEY).");
266
+ }
267
+ const res = await client.createMonitor({
268
+ url: url ?? urls?.[0],
269
+ urls,
270
+ mode: mode ?? (urls && urls.length > 1 ? "urls" : "page"),
271
+ maxPages: max_pages,
272
+ name,
273
+ intervalMinutes: interval_minutes,
274
+ watchFor: watch_for,
275
+ condition,
276
+ notifyEmail: notify_email === "true" ? true : notify_email,
277
+ webhookUrl: webhook_url,
278
+ });
279
+ return formatMonitor(res) + formatCredits(res);
280
+ }
281
+ }
282
+ }));
186
283
  server.registerTool("search_categories", {
187
284
  title: "Search categories",
188
285
  description: "List the search categories, subcategories and filters web_search supports.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webcrawl/mcp",
3
- "version": "1.3.7",
3
+ "version": "1.3.8",
4
4
  "description": "MCP server for Webcrawl Anything — zero-key local engine or webcrawl.app cloud (search, scrape, parse PDF/Office/YouTube, map, crawl, extract, rerank)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -52,7 +52,7 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "@modelcontextprotocol/sdk": "^1.32.2",
55
- "@webcrawl/client": "^1.3.7",
55
+ "@webcrawl/client": "^1.3.8",
56
56
  "zod": "^4.0.0"
57
57
  },
58
58
  "engines": {