Skip to content

Commit 6476334

Browse files
Merge pull request #290 from firecrawl/search-monitor
feat(monitor): add search-target support to firecrawl_monitor_create
2 parents 968fb2c + 2c3ce7e commit 6476334

3 files changed

Lines changed: 72 additions & 10 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "firecrawl-mcp",
3-
"version": "3.22.0",
3+
"version": "3.22.1",
44
"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.",
55
"type": "module",
66
"mcpName": "io.github.firecrawl/firecrawl-mcp-server",

server.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
{
1313
"registryType": "npm",
1414
"identifier": "firecrawl-mcp",
15-
"version": "3.20.2",
15+
"version": "3.22.1",
1616
"transport": {
1717
"type": "stdio"
1818
},

src/monitor.ts

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,17 @@ function buildMonitorCreateBody(
111111
args.page as string | undefined,
112112
args.pages as string[] | undefined
113113
);
114-
if (urls.length === 0) {
114+
const queries = Array.isArray(args.queries)
115+
? (args.queries as unknown[])
116+
.filter((q): q is string => typeof q === 'string')
117+
.map((q) => q.trim())
118+
.filter(Boolean)
119+
: [];
120+
const isSearch = queries.length > 0;
121+
122+
if (urls.length === 0 && !isSearch) {
115123
throw new Error(
116-
'firecrawl_monitor_create requires either `body`, `page`, or `pages`.'
124+
'firecrawl_monitor_create requires either `body`, `page`/`pages`, or `queries`.'
117125
);
118126
}
119127

@@ -124,6 +132,35 @@ function buildMonitorCreateBody(
124132
);
125133
}
126134

135+
// Build the target: search when `queries` are given, otherwise a scrape.
136+
let target: Record<string, unknown>;
137+
if (isSearch) {
138+
const includeDomains = Array.isArray(args.includeDomains)
139+
? (args.includeDomains as unknown[]).filter(
140+
(d): d is string => typeof d === 'string'
141+
)
142+
: undefined;
143+
const excludeDomains = Array.isArray(args.excludeDomains)
144+
? (args.excludeDomains as unknown[]).filter(
145+
(d): d is string => typeof d === 'string'
146+
)
147+
: undefined;
148+
target = {
149+
type: 'search',
150+
queries,
151+
...(typeof args.searchWindow === 'string' && args.searchWindow.trim()
152+
? { searchWindow: args.searchWindow.trim() }
153+
: {}),
154+
...(typeof args.maxResults === 'number'
155+
? { maxResults: args.maxResults }
156+
: {}),
157+
...(includeDomains && includeDomains.length > 0 ? { includeDomains } : {}),
158+
...(excludeDomains && excludeDomains.length > 0 ? { excludeDomains } : {}),
159+
};
160+
} else {
161+
target = { type: 'scrape', urls };
162+
}
163+
127164
const webhookUrl =
128165
typeof args.webhookUrl === 'string' ? args.webhookUrl.trim() : '';
129166
const email =
@@ -141,7 +178,9 @@ function buildMonitorCreateBody(
141178
name:
142179
typeof args.name === 'string' && args.name.trim()
143180
? args.name.trim()
144-
: `Monitor ${urls[0]}`,
181+
: isSearch
182+
? `Monitor ${queries[0]}`
183+
: `Monitor ${urls[0]}`,
145184
schedule: {
146185
text:
147186
typeof args.scheduleText === 'string' && args.scheduleText.trim()
@@ -153,7 +192,7 @@ function buildMonitorCreateBody(
153192
: 'UTC',
154193
},
155194
goal,
156-
targets: [{ type: 'scrape', urls }],
195+
targets: [target],
157196
...(email ? { notification: email } : {}),
158197
...(webhookUrl
159198
? {
@@ -176,20 +215,38 @@ export function registerMonitorTools(server: FastMCP<SessionData>): void {
176215
destructiveHint: false, // Additive; creates a new monitor without deleting existing monitors or external content.
177216
},
178217
description: `
179-
Create a Firecrawl monitor — a recurring scrape or crawl that diffs each result against the last retained snapshot.
218+
Create a Firecrawl monitor — a recurring scrape, crawl, or search that diffs each result against the last retained snapshot.
180219
181-
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.
220+
Prefer the simple path: pass \`page\` or \`pages\` plus \`goal\` to monitor specific URLs, OR pass \`queries\` plus \`goal\` to monitor web search results for new/changed hits. The tool will create the 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.
182221
183222
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.
184223
185224
Simple fields:
186225
- \`page\`: one page URL to monitor.
187226
- \`pages\`: multiple page URLs to monitor.
188-
- \`goal\`: plain-English instruction for what changes matter. Required for the simple path.
227+
- \`queries\`: one or more search queries (1-12) to monitor instead of fixed URLs. Each check runs the searches and diffs the result set, so you get alerted when new or changed results appear. Mutually exclusive with \`page\`/\`pages\` in the simple path.
228+
- \`searchWindow\`: optional recency window for search targets — one of \`5m\`, \`15m\`, \`1h\`, \`6h\`, \`24h\`, \`7d\` (default \`24h\`).
229+
- \`maxResults\`: optional max results per search, 1-50 (default 10).
230+
- \`includeDomains\` / \`excludeDomains\`: optional domain allow/deny lists for search targets.
231+
- \`goal\`: plain-English instruction for what changes matter. Required for the simple path (and always required when \`queries\` are set — web monitors must have a goal).
189232
- \`scheduleText\`: optional natural-language schedule, default \`every 30 minutes\`.
190233
- \`email\`: optional email recipient for summaries.
191234
- \`webhookUrl\`: optional webhook URL. Configures \`monitor.page\` and \`monitor.check.completed\`.
192235
236+
**Search-mode example:**
237+
238+
\`\`\`json
239+
{
240+
"name": "firecrawl_monitor_create",
241+
"arguments": {
242+
"queries": ["new LLM release", "frontier model launch"],
243+
"goal": "Notify me about major new LLM model releases.",
244+
"searchWindow": "24h",
245+
"maxResults": 10
246+
}
247+
}
248+
\`\`\`
249+
193250
Goal guidance:
194251
- Expand the user's one-line monitoring intent into a concise 2-3 sentence monitor goal.
195252
- 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.
@@ -198,7 +255,7 @@ Goal guidance:
198255
- 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.
199256
- Do not invent page-specific sections, thresholds, entities, or business rules unless the user mentioned them.
200257
201-
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\`.
258+
Full \`body\` requests require: \`name\`, \`schedule\` (with \`cron\` or \`text\`), and \`targets\` (one or more \`{ type: 'scrape', urls: [...] }\`, \`{ type: 'crawl', url: '...' }\`, or \`{ type: 'search', queries: [...], searchWindow?, maxResults?, includeDomains?, excludeDomains? }\`). Optional: \`goal\` (required when any search target is present), \`judgeEnabled\`, \`webhook\`, \`notification\`, \`retentionDays\`.
202259
203260
**Markdown-mode (default):** Each check produces a unified text diff of the page's markdown. No extra configuration needed.
204261
@@ -274,6 +331,11 @@ Full \`body\` requests require: \`name\`, \`schedule\` (with \`cron\` or \`text\
274331
body: z.record(z.string(), z.any()).optional(),
275332
page: z.string().optional(),
276333
pages: z.array(z.string()).optional(),
334+
queries: z.array(z.string()).optional(),
335+
searchWindow: z.enum(['5m', '15m', '1h', '6h', '24h', '7d']).optional(),
336+
maxResults: z.number().int().min(1).max(50).optional(),
337+
includeDomains: z.array(z.string()).optional(),
338+
excludeDomains: z.array(z.string()).optional(),
277339
goal: z.string().optional(),
278340
name: z.string().optional(),
279341
scheduleText: z.string().optional(),

0 commit comments

Comments
 (0)