firecrawl-mcp 3.20.6 → 3.21.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/dist/index.js +3217 -1175
- package/package.json +19 -7
- package/dist/monitor.js +0 -471
- package/dist/research.js +0 -291
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firecrawl-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.21.1",
|
|
4
4
|
"description": "MCP server for Firecrawl — search, scrape, and interact with the web. Supports both cloud and self-hosted instances. Features include web search, scraping, page interaction, batch processing, and LLM-powered content analysis.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"mcpName": "io.github.firecrawl/firecrawl-mcp-server",
|
|
@@ -16,10 +16,20 @@
|
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@mendable/firecrawl-js": "4.25.2",
|
|
19
|
+
"@modelcontextprotocol/sdk": "1.18.0",
|
|
20
|
+
"@standard-schema/spec": "^1.0.0",
|
|
19
21
|
"dotenv": "^17.2.2",
|
|
20
|
-
"
|
|
22
|
+
"execa": "^9.6.0",
|
|
23
|
+
"file-type": "^21.0.0",
|
|
24
|
+
"fuse.js": "^7.1.0",
|
|
25
|
+
"mcp-proxy": "^6.5.1",
|
|
26
|
+
"strict-event-emitter-types": "^2.0.0",
|
|
21
27
|
"typescript": "^5.9.2",
|
|
22
|
-
"
|
|
28
|
+
"uri-templates": "^0.2.0",
|
|
29
|
+
"xsschema": "0.3.5",
|
|
30
|
+
"yargs": "^18.0.0",
|
|
31
|
+
"zod": "^4.1.5",
|
|
32
|
+
"zod-to-json-schema": "^3.24.6"
|
|
23
33
|
},
|
|
24
34
|
"engines": {
|
|
25
35
|
"node": ">=18.0.0"
|
|
@@ -41,12 +51,14 @@
|
|
|
41
51
|
},
|
|
42
52
|
"homepage": "https://github.com/firecrawl/firecrawl-mcp-server#readme",
|
|
43
53
|
"devDependencies": {
|
|
44
|
-
"@types/node": "^24.3.1"
|
|
54
|
+
"@types/node": "^24.3.1",
|
|
55
|
+
"@types/uri-templates": "^0.1.34",
|
|
56
|
+
"@types/yargs": "^17.0.33",
|
|
57
|
+
"tsup": "^8.5.0"
|
|
45
58
|
},
|
|
46
59
|
"scripts": {
|
|
47
|
-
"build": "
|
|
48
|
-
"test": "
|
|
49
|
-
"test:endpoints": "node test-endpoints.js",
|
|
60
|
+
"build": "rm -rf dist && tsup && node -e \"require('fs').chmodSync('dist/index.js', '755')\"",
|
|
61
|
+
"test": "echo 'No tests'",
|
|
50
62
|
"start": "node dist/index.js",
|
|
51
63
|
"start:cloud": "CLOUD_SERVICE=true node dist/index.js",
|
|
52
64
|
"lint": "eslint src/**/*.ts",
|
package/dist/monitor.js
DELETED
|
@@ -1,471 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Firecrawl Monitor tools.
|
|
3
|
-
*
|
|
4
|
-
* Monitors run recurring scrapes/crawls and diff each result against the last
|
|
5
|
-
* retained snapshot. The SDK exposes monitor methods, but its HttpClient
|
|
6
|
-
* injects a top-level `origin` field into every POST/PATCH body and
|
|
7
|
-
* /v2/monitor rejects that with "Unrecognized key in body". Until the SDK
|
|
8
|
-
* strips `origin` for monitor requests, we hit /v2/monitor directly via fetch
|
|
9
|
-
* — same pattern the CLI uses.
|
|
10
|
-
*/
|
|
11
|
-
import { z } from 'zod';
|
|
12
|
-
const DEFAULT_API_URL = 'https://api.firecrawl.dev';
|
|
13
|
-
function resolveAuth(session) {
|
|
14
|
-
const apiKey = session?.firecrawlApiKey ?? process.env.FIRECRAWL_API_KEY;
|
|
15
|
-
const baseUrl = (process.env.FIRECRAWL_API_URL ?? DEFAULT_API_URL).replace(/\/$/, '');
|
|
16
|
-
return { apiKey, baseUrl };
|
|
17
|
-
}
|
|
18
|
-
async function monitorRequest(session, path, init = {}) {
|
|
19
|
-
const { apiKey, baseUrl } = resolveAuth(session);
|
|
20
|
-
if (!apiKey && !process.env.FIRECRAWL_API_URL) {
|
|
21
|
-
throw new Error('Unauthorized: API key is required for monitor requests');
|
|
22
|
-
}
|
|
23
|
-
let url = `${baseUrl}/v2${path}`;
|
|
24
|
-
if (init.query) {
|
|
25
|
-
const qs = new URLSearchParams();
|
|
26
|
-
for (const [k, v] of Object.entries(init.query)) {
|
|
27
|
-
if (v !== undefined && v !== null && v !== '')
|
|
28
|
-
qs.set(k, String(v));
|
|
29
|
-
}
|
|
30
|
-
const s = qs.toString();
|
|
31
|
-
if (s)
|
|
32
|
-
url += `?${s}`;
|
|
33
|
-
}
|
|
34
|
-
const headers = { 'X-Origin': 'mcp' };
|
|
35
|
-
if (apiKey)
|
|
36
|
-
headers.Authorization = `Bearer ${apiKey}`;
|
|
37
|
-
if (init.body !== undefined)
|
|
38
|
-
headers['Content-Type'] = 'application/json';
|
|
39
|
-
const response = await fetch(url, {
|
|
40
|
-
method: init.method ?? 'GET',
|
|
41
|
-
headers,
|
|
42
|
-
body: init.body !== undefined ? JSON.stringify(init.body) : undefined,
|
|
43
|
-
});
|
|
44
|
-
const payload = (await response.json().catch(() => ({})));
|
|
45
|
-
if (!response.ok || payload?.success === false) {
|
|
46
|
-
const message = payload?.error ||
|
|
47
|
-
`HTTP ${response.status}: ${response.statusText || 'Request failed'}`;
|
|
48
|
-
throw new Error(message);
|
|
49
|
-
}
|
|
50
|
-
return payload;
|
|
51
|
-
}
|
|
52
|
-
function asText(data) {
|
|
53
|
-
return JSON.stringify(data, null, 2);
|
|
54
|
-
}
|
|
55
|
-
const pageStatusSchema = z.enum(['same', 'new', 'changed', 'removed', 'error']);
|
|
56
|
-
const checkStatusSchema = z.enum([
|
|
57
|
-
'queued',
|
|
58
|
-
'running',
|
|
59
|
-
'completed',
|
|
60
|
-
'failed',
|
|
61
|
-
'partial',
|
|
62
|
-
'skipped_overlap',
|
|
63
|
-
]);
|
|
64
|
-
function splitPages(page, pages) {
|
|
65
|
-
return [page, ...(pages ?? [])]
|
|
66
|
-
.filter((url) => typeof url === 'string')
|
|
67
|
-
.map(url => url.trim())
|
|
68
|
-
.filter(Boolean);
|
|
69
|
-
}
|
|
70
|
-
function buildMonitorCreateBody(args) {
|
|
71
|
-
if (args.body && typeof args.body === 'object' && !Array.isArray(args.body)) {
|
|
72
|
-
return args.body;
|
|
73
|
-
}
|
|
74
|
-
const urls = splitPages(args.page, args.pages);
|
|
75
|
-
if (urls.length === 0) {
|
|
76
|
-
throw new Error('firecrawl_monitor_create requires either `body`, `page`, or `pages`.');
|
|
77
|
-
}
|
|
78
|
-
const goal = typeof args.goal === 'string' ? args.goal.trim() : '';
|
|
79
|
-
if (!goal) {
|
|
80
|
-
throw new Error('firecrawl_monitor_create shorthand requires `goal`. Use `body` for advanced requests without a goal.');
|
|
81
|
-
}
|
|
82
|
-
const webhookUrl = typeof args.webhookUrl === 'string' ? args.webhookUrl.trim() : '';
|
|
83
|
-
const email = typeof args.email === 'string' && args.email.trim()
|
|
84
|
-
? {
|
|
85
|
-
email: {
|
|
86
|
-
enabled: true,
|
|
87
|
-
recipients: [args.email.trim()],
|
|
88
|
-
includeDiffs: Boolean(args.includeDiffs),
|
|
89
|
-
},
|
|
90
|
-
}
|
|
91
|
-
: undefined;
|
|
92
|
-
return {
|
|
93
|
-
name: typeof args.name === 'string' && args.name.trim()
|
|
94
|
-
? args.name.trim()
|
|
95
|
-
: `Monitor ${urls[0]}`,
|
|
96
|
-
schedule: {
|
|
97
|
-
text: typeof args.scheduleText === 'string' && args.scheduleText.trim()
|
|
98
|
-
? args.scheduleText.trim()
|
|
99
|
-
: 'every 30 minutes',
|
|
100
|
-
timezone: typeof args.timezone === 'string' && args.timezone.trim()
|
|
101
|
-
? args.timezone.trim()
|
|
102
|
-
: 'UTC',
|
|
103
|
-
},
|
|
104
|
-
goal,
|
|
105
|
-
targets: [{ type: 'scrape', urls }],
|
|
106
|
-
...(email ? { notification: email } : {}),
|
|
107
|
-
...(webhookUrl
|
|
108
|
-
? {
|
|
109
|
-
webhook: {
|
|
110
|
-
url: webhookUrl,
|
|
111
|
-
events: ['monitor.page', 'monitor.check.completed'],
|
|
112
|
-
},
|
|
113
|
-
}
|
|
114
|
-
: {}),
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
export function registerMonitorTools(server) {
|
|
118
|
-
server.addTool({
|
|
119
|
-
name: 'firecrawl_monitor_create',
|
|
120
|
-
annotations: {
|
|
121
|
-
title: 'Create monitor',
|
|
122
|
-
readOnlyHint: false,
|
|
123
|
-
openWorldHint: true,
|
|
124
|
-
},
|
|
125
|
-
description: `
|
|
126
|
-
Create a Firecrawl monitor — a recurring scrape or crawl that diffs each result against the last retained snapshot.
|
|
127
|
-
|
|
128
|
-
Prefer the simple path: pass \`page\` or \`pages\` plus \`goal\`. The tool will create a scrape monitor with a 30-minute schedule and meaningful-change judging enabled by the API. Use \`body\` only for advanced requests such as crawl targets, JSON change tracking, custom retention, or manual \`judgeEnabled\` control.
|
|
129
|
-
|
|
130
|
-
Meaningful-change judge: set \`goal\` to a plain-language description of what the user actually cares about. \`judgeEnabled\` defaults to true when \`goal\` is set, so providing \`goal\` is enough. Page webhooks expose \`isMeaningful\` and \`judgment\` on \`monitor.page\` events.
|
|
131
|
-
|
|
132
|
-
Simple fields:
|
|
133
|
-
- \`page\`: one page URL to monitor.
|
|
134
|
-
- \`pages\`: multiple page URLs to monitor.
|
|
135
|
-
- \`goal\`: plain-English instruction for what changes matter. Required for the simple path.
|
|
136
|
-
- \`scheduleText\`: optional natural-language schedule, default \`every 30 minutes\`.
|
|
137
|
-
- \`email\`: optional email recipient for summaries.
|
|
138
|
-
- \`webhookUrl\`: optional webhook URL. Configures \`monitor.page\` and \`monitor.check.completed\`.
|
|
139
|
-
|
|
140
|
-
Goal guidance:
|
|
141
|
-
- Expand the user's one-line monitoring intent into a concise 2-3 sentence monitor goal.
|
|
142
|
-
- State what should trigger an alert, restate any scope the user gave, and include intent-specific exclusions only when obvious from the user's request.
|
|
143
|
-
- Generic noise such as whitespace, formatting-only changes, request IDs, tracking params, generic metadata, and unrelated page chrome is already handled by the judge; do not repeat it in every goal.
|
|
144
|
-
- If the user is vague, keep the goal broad rather than guessing exclusions. If the user asks for broad monitoring or "any change", preserve that and do not add exclusions that hide changes.
|
|
145
|
-
- If the user says they do not care about something, include that explicitly. It is okay to ask whether they want to ignore specific noise when it is likely to matter.
|
|
146
|
-
- Do not invent page-specific sections, thresholds, entities, or business rules unless the user mentioned them.
|
|
147
|
-
|
|
148
|
-
Full \`body\` requests require: \`name\`, \`schedule\` (with \`cron\` or \`text\`), and \`targets\` (one or more \`{ type: 'scrape', urls: [...] }\` or \`{ type: 'crawl', url: '...' }\`). Optional: \`goal\`, \`judgeEnabled\`, \`webhook\`, \`notification\`, \`retentionDays\`.
|
|
149
|
-
|
|
150
|
-
**Markdown-mode (default):** Each check produces a unified text diff of the page's markdown. No extra configuration needed.
|
|
151
|
-
|
|
152
|
-
\`\`\`json
|
|
153
|
-
{
|
|
154
|
-
"name": "firecrawl_monitor_create",
|
|
155
|
-
"arguments": {
|
|
156
|
-
"page": "https://example.com/blog",
|
|
157
|
-
"goal": "Alert when a new blog post is published or an existing headline changes.",
|
|
158
|
-
"email": "alerts@example.com"
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
\`\`\`
|
|
162
|
-
|
|
163
|
-
**Multiple pages:**
|
|
164
|
-
|
|
165
|
-
\`\`\`json
|
|
166
|
-
{
|
|
167
|
-
"name": "firecrawl_monitor_create",
|
|
168
|
-
"arguments": {
|
|
169
|
-
"pages": ["https://example.com/pricing", "https://example.com/changelog"],
|
|
170
|
-
"goal": "Alert when pricing, packaging, or launch messaging changes.",
|
|
171
|
-
"webhookUrl": "https://example.com/webhooks/firecrawl"
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
\`\`\`
|
|
175
|
-
|
|
176
|
-
**JSON-mode change tracking:** To detect changes in **specific structured fields** (price, headline, in-stock flag, list items) instead of the whole page, add a \`changeTracking\` format with \`modes: ["json"]\` and a JSON schema to the target's \`scrapeOptions.formats\`. The check response will then carry a per-field diff (keyed by JSON path, e.g. \`plans[0].price\`) and a \`snapshot.json\` with the full current extraction. See \`firecrawl_monitor_check\` for the response shape.
|
|
177
|
-
|
|
178
|
-
\`\`\`json
|
|
179
|
-
{
|
|
180
|
-
"name": "firecrawl_monitor_create",
|
|
181
|
-
"arguments": {
|
|
182
|
-
"body": {
|
|
183
|
-
"name": "Pricing watch",
|
|
184
|
-
"schedule": { "text": "hourly", "timezone": "UTC" },
|
|
185
|
-
"goal": "Alert when a pricing tier, price, billing period, limit, or headline feature changes. Ignore unrelated marketing copy unless it changes the pricing offer.",
|
|
186
|
-
"targets": [{
|
|
187
|
-
"type": "scrape",
|
|
188
|
-
"urls": ["https://example.com/pricing"],
|
|
189
|
-
"scrapeOptions": {
|
|
190
|
-
"formats": [{
|
|
191
|
-
"type": "changeTracking",
|
|
192
|
-
"modes": ["json"],
|
|
193
|
-
"prompt": "Extract pricing tiers and headline features for each plan.",
|
|
194
|
-
"schema": {
|
|
195
|
-
"type": "object",
|
|
196
|
-
"properties": {
|
|
197
|
-
"plans": {
|
|
198
|
-
"type": "array",
|
|
199
|
-
"items": {
|
|
200
|
-
"type": "object",
|
|
201
|
-
"properties": {
|
|
202
|
-
"name": { "type": "string" },
|
|
203
|
-
"price": { "type": "string" },
|
|
204
|
-
"features": { "type": "array", "items": { "type": "string" } }
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}]
|
|
211
|
-
}
|
|
212
|
-
}]
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
\`\`\`
|
|
217
|
-
|
|
218
|
-
**Mixed mode (JSON + git-diff):** Use \`modes: ["json", "git-diff"]\` to get both per-field diffs and a markdown sidecar. The page is marked \`changed\` whenever either surface changed.
|
|
219
|
-
`,
|
|
220
|
-
parameters: z.object({
|
|
221
|
-
body: z.record(z.string(), z.any()).optional(),
|
|
222
|
-
page: z.string().optional(),
|
|
223
|
-
pages: z.array(z.string()).optional(),
|
|
224
|
-
goal: z.string().optional(),
|
|
225
|
-
name: z.string().optional(),
|
|
226
|
-
scheduleText: z.string().optional(),
|
|
227
|
-
timezone: z.string().optional(),
|
|
228
|
-
email: z.string().optional(),
|
|
229
|
-
includeDiffs: z.boolean().optional(),
|
|
230
|
-
webhookUrl: z.string().optional(),
|
|
231
|
-
}),
|
|
232
|
-
execute: async (args, { session, log }) => {
|
|
233
|
-
const body = buildMonitorCreateBody(args);
|
|
234
|
-
log.info('Creating monitor', { name: body.name });
|
|
235
|
-
const res = await monitorRequest(session, '/monitor', {
|
|
236
|
-
method: 'POST',
|
|
237
|
-
body,
|
|
238
|
-
});
|
|
239
|
-
return asText(res);
|
|
240
|
-
},
|
|
241
|
-
});
|
|
242
|
-
server.addTool({
|
|
243
|
-
name: 'firecrawl_monitor_list',
|
|
244
|
-
annotations: {
|
|
245
|
-
title: 'List monitors',
|
|
246
|
-
readOnlyHint: true,
|
|
247
|
-
openWorldHint: false,
|
|
248
|
-
},
|
|
249
|
-
description: `
|
|
250
|
-
List all Firecrawl monitors for the authenticated account.
|
|
251
|
-
|
|
252
|
-
**Usage Example:**
|
|
253
|
-
\`\`\`json
|
|
254
|
-
{ "name": "firecrawl_monitor_list", "arguments": { "limit": 20 } }
|
|
255
|
-
\`\`\`
|
|
256
|
-
`,
|
|
257
|
-
parameters: z.object({
|
|
258
|
-
limit: z.number().int().positive().optional(),
|
|
259
|
-
offset: z.number().int().nonnegative().optional(),
|
|
260
|
-
}),
|
|
261
|
-
execute: async (args, { session }) => {
|
|
262
|
-
const { limit, offset } = args;
|
|
263
|
-
const res = await monitorRequest(session, '/monitor', {
|
|
264
|
-
query: { limit, offset },
|
|
265
|
-
});
|
|
266
|
-
return asText(res);
|
|
267
|
-
},
|
|
268
|
-
});
|
|
269
|
-
server.addTool({
|
|
270
|
-
name: 'firecrawl_monitor_get',
|
|
271
|
-
annotations: {
|
|
272
|
-
title: 'Get monitor',
|
|
273
|
-
readOnlyHint: true,
|
|
274
|
-
openWorldHint: false,
|
|
275
|
-
},
|
|
276
|
-
description: `
|
|
277
|
-
Get a single monitor by ID.
|
|
278
|
-
|
|
279
|
-
**Usage Example:**
|
|
280
|
-
\`\`\`json
|
|
281
|
-
{ "name": "firecrawl_monitor_get", "arguments": { "id": "mon_abc123" } }
|
|
282
|
-
\`\`\`
|
|
283
|
-
`,
|
|
284
|
-
parameters: z.object({ id: z.string() }),
|
|
285
|
-
execute: async (args, { session }) => {
|
|
286
|
-
const { id } = args;
|
|
287
|
-
const res = await monitorRequest(session, `/monitor/${encodeURIComponent(id)}`);
|
|
288
|
-
return asText(res);
|
|
289
|
-
},
|
|
290
|
-
});
|
|
291
|
-
server.addTool({
|
|
292
|
-
name: 'firecrawl_monitor_update',
|
|
293
|
-
annotations: {
|
|
294
|
-
title: 'Update monitor',
|
|
295
|
-
readOnlyHint: false,
|
|
296
|
-
openWorldHint: true,
|
|
297
|
-
},
|
|
298
|
-
description: `
|
|
299
|
-
Update a monitor. Pass any subset of fields to patch: \`name\`, \`status\` ("active" | "paused"), \`schedule\`, \`targets\`, \`goal\`, \`judgeEnabled\`, \`webhook\`, \`notification\`, \`retentionDays\`.
|
|
300
|
-
|
|
301
|
-
**Usage Example:**
|
|
302
|
-
\`\`\`json
|
|
303
|
-
{
|
|
304
|
-
"name": "firecrawl_monitor_update",
|
|
305
|
-
"arguments": {
|
|
306
|
-
"id": "mon_abc123",
|
|
307
|
-
"body": { "status": "paused" }
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
\`\`\`
|
|
311
|
-
`,
|
|
312
|
-
parameters: z.object({
|
|
313
|
-
id: z.string(),
|
|
314
|
-
body: z.record(z.string(), z.any()),
|
|
315
|
-
}),
|
|
316
|
-
execute: async (args, { session }) => {
|
|
317
|
-
const { id, body } = args;
|
|
318
|
-
const res = await monitorRequest(session, `/monitor/${encodeURIComponent(id)}`, { method: 'PATCH', body });
|
|
319
|
-
return asText(res);
|
|
320
|
-
},
|
|
321
|
-
});
|
|
322
|
-
server.addTool({
|
|
323
|
-
name: 'firecrawl_monitor_delete',
|
|
324
|
-
annotations: {
|
|
325
|
-
title: 'Delete monitor',
|
|
326
|
-
readOnlyHint: false,
|
|
327
|
-
destructiveHint: true,
|
|
328
|
-
openWorldHint: true,
|
|
329
|
-
},
|
|
330
|
-
description: `
|
|
331
|
-
Permanently delete a monitor and stop its schedule. This cannot be undone.
|
|
332
|
-
|
|
333
|
-
**Usage Example:**
|
|
334
|
-
\`\`\`json
|
|
335
|
-
{ "name": "firecrawl_monitor_delete", "arguments": { "id": "mon_abc123" } }
|
|
336
|
-
\`\`\`
|
|
337
|
-
`,
|
|
338
|
-
parameters: z.object({ id: z.string() }),
|
|
339
|
-
execute: async (args, { session, log }) => {
|
|
340
|
-
const { id } = args;
|
|
341
|
-
log.info('Deleting monitor', { id });
|
|
342
|
-
const res = await monitorRequest(session, `/monitor/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
|
343
|
-
return asText(res);
|
|
344
|
-
},
|
|
345
|
-
});
|
|
346
|
-
server.addTool({
|
|
347
|
-
name: 'firecrawl_monitor_run',
|
|
348
|
-
annotations: {
|
|
349
|
-
title: 'Run monitor now',
|
|
350
|
-
readOnlyHint: false,
|
|
351
|
-
openWorldHint: true,
|
|
352
|
-
},
|
|
353
|
-
description: `
|
|
354
|
-
Trigger a monitor check immediately, outside its normal schedule. Returns the queued check.
|
|
355
|
-
|
|
356
|
-
**Usage Example:**
|
|
357
|
-
\`\`\`json
|
|
358
|
-
{ "name": "firecrawl_monitor_run", "arguments": { "id": "mon_abc123" } }
|
|
359
|
-
\`\`\`
|
|
360
|
-
`,
|
|
361
|
-
parameters: z.object({ id: z.string() }),
|
|
362
|
-
execute: async (args, { session }) => {
|
|
363
|
-
const { id } = args;
|
|
364
|
-
const res = await monitorRequest(session, `/monitor/${encodeURIComponent(id)}/run`, { method: 'POST' });
|
|
365
|
-
return asText(res);
|
|
366
|
-
},
|
|
367
|
-
});
|
|
368
|
-
server.addTool({
|
|
369
|
-
name: 'firecrawl_monitor_checks',
|
|
370
|
-
annotations: {
|
|
371
|
-
title: 'List monitor checks',
|
|
372
|
-
readOnlyHint: true,
|
|
373
|
-
openWorldHint: false,
|
|
374
|
-
},
|
|
375
|
-
description: `
|
|
376
|
-
List historical checks for a monitor.
|
|
377
|
-
|
|
378
|
-
**Usage Example:**
|
|
379
|
-
\`\`\`json
|
|
380
|
-
{ "name": "firecrawl_monitor_checks", "arguments": { "id": "mon_abc123", "limit": 10, "status": "completed" } }
|
|
381
|
-
\`\`\`
|
|
382
|
-
`,
|
|
383
|
-
parameters: z.object({
|
|
384
|
-
id: z.string(),
|
|
385
|
-
limit: z.number().int().positive().optional(),
|
|
386
|
-
offset: z.number().int().nonnegative().optional(),
|
|
387
|
-
status: checkStatusSchema.optional(),
|
|
388
|
-
}),
|
|
389
|
-
execute: async (args, { session }) => {
|
|
390
|
-
const { id, limit, offset, status } = args;
|
|
391
|
-
const res = await monitorRequest(session, `/monitor/${encodeURIComponent(id)}/checks`, { query: { limit, offset, status } });
|
|
392
|
-
return asText(res);
|
|
393
|
-
},
|
|
394
|
-
});
|
|
395
|
-
server.addTool({
|
|
396
|
-
name: 'firecrawl_monitor_check',
|
|
397
|
-
annotations: {
|
|
398
|
-
title: 'Get monitor check',
|
|
399
|
-
readOnlyHint: true,
|
|
400
|
-
openWorldHint: false,
|
|
401
|
-
},
|
|
402
|
-
description: `
|
|
403
|
-
Get a single check with page-level diff results. Filter \`pageStatus\` to surface only the pages that changed (or were new, removed, etc.).
|
|
404
|
-
|
|
405
|
-
Each entry in \`data.pages[]\` has \`url\`, \`status\` (\`same\` | \`new\` | \`changed\` | \`removed\` | \`error\`), optional \`judgment\` when goal-based judging ran, and — when changed — a \`diff\` and possibly a \`snapshot\`. The shape of \`diff\` depends on the monitor's \`formats\` configuration:
|
|
406
|
-
|
|
407
|
-
- **Markdown mode (default).** \`diff.text\` is the unified markdown diff; \`diff.json\` is a parse-diff AST (\`{ files: [...] }\`). No \`snapshot\`.
|
|
408
|
-
- **JSON mode** (\`changeTracking\` with \`modes: ["json"]\`). \`diff.json\` is a per-field map keyed by JSON path into the extraction, e.g. \`plans[0].price\`, with each value being \`{ previous, current }\`. \`snapshot.json\` is the full current extraction. No \`diff.text\`.
|
|
409
|
-
- **Mixed mode** (\`modes: ["json", "git-diff"]\`). Both \`diff.text\` (markdown sidecar) AND \`diff.json\` (per-field map) are present, plus \`snapshot.json\`.
|
|
410
|
-
|
|
411
|
-
**Example JSON-mode response \`pages[]\` entry:**
|
|
412
|
-
|
|
413
|
-
\`\`\`json
|
|
414
|
-
{
|
|
415
|
-
"url": "https://example.com/pricing",
|
|
416
|
-
"status": "changed",
|
|
417
|
-
"diff": {
|
|
418
|
-
"json": {
|
|
419
|
-
"plans[0].price": { "previous": "$19/mo", "current": "$24/mo" },
|
|
420
|
-
"plans[1].features[2]": { "previous": "10 GB storage", "current": "25 GB storage" }
|
|
421
|
-
}
|
|
422
|
-
},
|
|
423
|
-
"snapshot": { "json": { "plans": [/* current full extraction matching the monitor's schema */] } },
|
|
424
|
-
"judgment": {
|
|
425
|
-
"meaningful": true,
|
|
426
|
-
"confidence": "high",
|
|
427
|
-
"reason": "The pricing changed, which matches the monitor goal.",
|
|
428
|
-
"meaningfulChanges": [
|
|
429
|
-
{
|
|
430
|
-
"type": "changed",
|
|
431
|
-
"before": "$19/mo",
|
|
432
|
-
"after": "$24/mo",
|
|
433
|
-
"reason": "The tracked plan price changed."
|
|
434
|
-
}
|
|
435
|
-
]
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
\`\`\`
|
|
439
|
-
|
|
440
|
-
When summarizing a check for the user, prefer \`diff.json\` paths (e.g. "plans[0].price changed from $19/mo to $24/mo") over re-printing the markdown diff — it's more concise and grounded in the schema fields they asked for.
|
|
441
|
-
|
|
442
|
-
When \`judgment\` is present, use it to decide what to surface. \`judgment.meaningful: false\` means the change was classified as noise for the monitor's goal. When \`judgment.meaningfulChanges\` is present, prefer those goal-relevant changes over raw diff hunks; each item includes \`type\`, \`before\`, \`after\`, and \`reason\`.
|
|
443
|
-
|
|
444
|
-
The endpoint paginates via a top-level \`next\` URL; this tool returns one page at a time. Increase \`limit\` (max 100) to fetch fewer pages.
|
|
445
|
-
|
|
446
|
-
**Usage Example:**
|
|
447
|
-
\`\`\`json
|
|
448
|
-
{
|
|
449
|
-
"name": "firecrawl_monitor_check",
|
|
450
|
-
"arguments": {
|
|
451
|
-
"id": "mon_abc123",
|
|
452
|
-
"checkId": "chk_xyz",
|
|
453
|
-
"pageStatus": "changed"
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
\`\`\`
|
|
457
|
-
`,
|
|
458
|
-
parameters: z.object({
|
|
459
|
-
id: z.string(),
|
|
460
|
-
checkId: z.string(),
|
|
461
|
-
limit: z.number().int().positive().optional(),
|
|
462
|
-
skip: z.number().int().nonnegative().optional(),
|
|
463
|
-
pageStatus: pageStatusSchema.optional(),
|
|
464
|
-
}),
|
|
465
|
-
execute: async (args, { session }) => {
|
|
466
|
-
const { id, checkId, limit, skip, pageStatus } = args;
|
|
467
|
-
const res = await monitorRequest(session, `/monitor/${encodeURIComponent(id)}/checks/${encodeURIComponent(checkId)}`, { query: { limit, skip, status: pageStatus } });
|
|
468
|
-
return asText(res);
|
|
469
|
-
},
|
|
470
|
-
});
|
|
471
|
-
}
|