firecrawl-mcp 3.25.3 → 3.25.5
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/README.md +2 -2
- package/dist/index.js +724 -412
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -45,10 +45,9 @@ var alexandriaFeedbackFields = {
|
|
|
45
45
|
)
|
|
46
46
|
).max(20).optional()
|
|
47
47
|
};
|
|
48
|
-
var ALEXANDRIA_FEEDBACK_GUIDANCE = 'After an Alexandria task, whether a capability ran or discovery found nothing for the website, call firecrawl_feedback once per website with endpoint "alexandria": requestedWebsite (the url the user needed data from and the requestedFunctionality they needed), a rating, a rationale from observed results, and any providerFeedback or capabilityFeedback gaps. It is free, needs no job ID, has no deadline, and follows the answer rather than replacing it.';
|
|
49
48
|
var ALEXANDRIA_FEEDBACK_HINT = {
|
|
50
49
|
name: "firecrawl_feedback",
|
|
51
|
-
when: "
|
|
50
|
+
when: "Optional after task completion; at most once per website, including uncovered sites. Free; no job ID or deadline.",
|
|
52
51
|
arguments: {
|
|
53
52
|
endpoint: "alexandria",
|
|
54
53
|
rating: "<good | partial | bad>",
|
|
@@ -86,14 +85,295 @@ import { readFile } from "fs/promises";
|
|
|
86
85
|
import { createRequire as createRequire2 } from "module";
|
|
87
86
|
import { randomUUID } from "crypto";
|
|
88
87
|
import path from "path";
|
|
89
|
-
import { z as
|
|
88
|
+
import { z as z8 } from "zod";
|
|
90
89
|
|
|
91
|
-
// src/
|
|
90
|
+
// src/tool-output.ts
|
|
92
91
|
import { z as z2 } from "zod";
|
|
92
|
+
function isRecord(value) {
|
|
93
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
94
|
+
}
|
|
95
|
+
function withStructured(text, data) {
|
|
96
|
+
return isRecord(data) ? { content: [{ type: "text", text }], structuredContent: data } : { content: [{ type: "text", text }] };
|
|
97
|
+
}
|
|
98
|
+
function structuredText(data) {
|
|
99
|
+
return withStructured(JSON.stringify(data, null, 2), data);
|
|
100
|
+
}
|
|
101
|
+
function structuredCompact(data) {
|
|
102
|
+
return withStructured(JSON.stringify(data), data);
|
|
103
|
+
}
|
|
104
|
+
function structuredJsonText(text) {
|
|
105
|
+
try {
|
|
106
|
+
return withStructured(text, JSON.parse(text));
|
|
107
|
+
} catch {
|
|
108
|
+
return { content: [{ type: "text", text }] };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
var str = (description) => z2.string().nullable().optional().describe(description);
|
|
112
|
+
var num = (description) => z2.number().nullable().optional().describe(description);
|
|
113
|
+
var bool = (description) => z2.boolean().nullable().optional().describe(description);
|
|
114
|
+
var unknown = (description) => z2.unknown().optional().describe(description);
|
|
115
|
+
var success = bool("Whether the API call succeeded.");
|
|
116
|
+
var error = unknown("Error message or error object when the call did not succeed.");
|
|
117
|
+
var warning = str("Non-fatal warning about the result.");
|
|
118
|
+
var alexandriaEnvelope = {
|
|
119
|
+
requestId: str("Identifier of this logical execution; reuse it only for a retry of the identical payload."),
|
|
120
|
+
scrape_id: str("Identifier of the underlying scrape."),
|
|
121
|
+
receipt: unknown("Billing receipt for the execution."),
|
|
122
|
+
creditsCost: num("Credits this call consumed."),
|
|
123
|
+
delivery: str("`retained` when the full result stayed server-side instead of being inlined."),
|
|
124
|
+
responseBytes: num("Size of the full result in bytes."),
|
|
125
|
+
estimatedTokens: num("Estimated token cost of the full result."),
|
|
126
|
+
tokenEstimateMethod: str("How `estimatedTokens` was derived."),
|
|
127
|
+
inlineTokenBudget: num("Token budget above which a result is retained rather than inlined."),
|
|
128
|
+
workspaceId: str("Workspace holding a retained result, for inspection through virtual Bash."),
|
|
129
|
+
idleTtlSeconds: num("Seconds a retained workspace stays available while idle."),
|
|
130
|
+
message: str("Guidance that accompanies the result."),
|
|
131
|
+
nextTool: unknown("A follow-up tool call (`{name, arguments}`) that continues or inspects this result."),
|
|
132
|
+
feedbackTool: unknown("Pointer to the feedback tool for reporting how this result served the task.")
|
|
133
|
+
};
|
|
134
|
+
var scrapeOutputSchema = z2.object({
|
|
135
|
+
// URL mode: the scraped document, with the formats that were requested.
|
|
136
|
+
markdown: str("Page content as markdown."),
|
|
137
|
+
html: str("Processed HTML of the page."),
|
|
138
|
+
rawHtml: str("Unprocessed HTML of the page."),
|
|
139
|
+
summary: str("Summary of the page content."),
|
|
140
|
+
links: unknown("Links found on the page."),
|
|
141
|
+
screenshot: unknown("Screenshot of the page."),
|
|
142
|
+
images: unknown("Images found on the page."),
|
|
143
|
+
audio: unknown("Audio extracted from the page."),
|
|
144
|
+
video: unknown("Video extracted from the page."),
|
|
145
|
+
answer: str("Targeted answer to the question that was asked of the page."),
|
|
146
|
+
highlights: unknown("Highlighted passages from the page."),
|
|
147
|
+
pages: unknown("Physical PDF pages, when `parsers[].pages` is set."),
|
|
148
|
+
blocks: unknown("Typed PDF layout blocks, when `parsers[].blocks` is set."),
|
|
149
|
+
product: unknown("Product data extracted from the page."),
|
|
150
|
+
menu: unknown("Menu data extracted from the page."),
|
|
151
|
+
json: unknown("Structured data matching the requested JSON schema or prompt."),
|
|
152
|
+
attributes: unknown("Values collected by the requested attribute selectors."),
|
|
153
|
+
actions: unknown("Results of the browser actions that ran during the scrape."),
|
|
154
|
+
changeTracking: unknown("Change-tracking comparison against the previous scrape."),
|
|
155
|
+
branding: unknown("Branding data extracted from the page."),
|
|
156
|
+
metadata: unknown("Page metadata; authenticated responses can include `metadata.scrapeId` for scrape feedback."),
|
|
157
|
+
tools: unknown("Domain-matched Alexandria tools for the page, when `domainTools` is set."),
|
|
158
|
+
warning,
|
|
159
|
+
// Alexandria mode and error envelopes.
|
|
160
|
+
success,
|
|
161
|
+
error,
|
|
162
|
+
data: unknown("Alexandria mode: per-capability results in `data.alexandria`, each with `data`, `records`, or an `error`."),
|
|
163
|
+
...alexandriaEnvelope
|
|
164
|
+
}).describe("A scraped document, or the Alexandria execution envelope when `alexandria` was passed.");
|
|
165
|
+
var mapOutputSchema = z2.object({
|
|
166
|
+
links: unknown("URLs discovered under the website."),
|
|
167
|
+
success,
|
|
168
|
+
error,
|
|
169
|
+
warning,
|
|
170
|
+
id: str("Identifier of the map run, for optional map feedback.")
|
|
171
|
+
}).describe("URLs indexed under the requested website.");
|
|
172
|
+
var searchOutputSchema = z2.object({
|
|
173
|
+
success,
|
|
174
|
+
data: unknown("Ranked results grouped by source, such as `web`, `news`, `images`, and `alexandria`."),
|
|
175
|
+
error,
|
|
176
|
+
warning,
|
|
177
|
+
id: str("Search identifier, for optional `firecrawl_search_feedback`."),
|
|
178
|
+
creditsUsed: num("Credits this search consumed."),
|
|
179
|
+
tools: unknown("Domain-matched Alexandria tools for the results."),
|
|
180
|
+
nextTool: unknown("A follow-up tool call that continues this search."),
|
|
181
|
+
feedbackTool: unknown("Pointer to the feedback tool for this search.")
|
|
182
|
+
}).describe("Ranked search results grouped by source.");
|
|
183
|
+
var findToolsOutputSchema = z2.object({
|
|
184
|
+
success,
|
|
185
|
+
data: unknown("Discovery page in `data.alexandria[0].data`, with `level`, `items`, `total`, and an optional `nextTool`."),
|
|
186
|
+
error,
|
|
187
|
+
...alexandriaEnvelope
|
|
188
|
+
}).describe("One page of the Alexandria catalogue: categories, providers, tools, or one contract.");
|
|
189
|
+
var feedbackOutputSchema = z2.object({
|
|
190
|
+
success,
|
|
191
|
+
error,
|
|
192
|
+
status: num("HTTP status when the submission was rejected."),
|
|
193
|
+
feedbackErrorCode: str("Machine-readable reason the submission was rejected."),
|
|
194
|
+
retryable: bool("Whether retrying the submission can succeed."),
|
|
195
|
+
message: str("Human-readable result of the submission."),
|
|
196
|
+
feedbackId: str("Identifier of the recorded feedback."),
|
|
197
|
+
creditsRefunded: num("Credits refunded for this submission, when eligible."),
|
|
198
|
+
creditsRefundedToday: num("Credits refunded to the team so far this UTC day."),
|
|
199
|
+
dailyRefundCap: num("The team's daily refund cap in credits."),
|
|
200
|
+
dailyCapReached: bool("Whether the daily refund cap is reached; further feedback today still records but refunds nothing."),
|
|
201
|
+
alreadySubmitted: bool("Whether feedback for this job was already recorded."),
|
|
202
|
+
warning,
|
|
203
|
+
data: unknown("Payload returned with the accepted feedback."),
|
|
204
|
+
raw: str("Response body when it was not JSON.")
|
|
205
|
+
}).describe("Result of submitting feedback.");
|
|
206
|
+
var crawlOutputSchema = z2.object({
|
|
207
|
+
success,
|
|
208
|
+
error,
|
|
209
|
+
warning,
|
|
210
|
+
id: str("Crawl identifier, for `firecrawl_check_crawl_status`."),
|
|
211
|
+
status: str("Crawl state, such as `scraping`, `completed`, or `failed`."),
|
|
212
|
+
completed: num("Pages crawled so far."),
|
|
213
|
+
total: num("Pages the crawl expects to visit."),
|
|
214
|
+
creditsUsed: num("Credits the crawl consumed."),
|
|
215
|
+
expiresAt: str("When the crawl results expire."),
|
|
216
|
+
next: str("URL of the next page of results, when the data is paginated."),
|
|
217
|
+
url: str("The URL the crawl started from."),
|
|
218
|
+
data: unknown("Scraped documents collected by the crawl.")
|
|
219
|
+
}).describe("Crawl job state and the documents collected so far.");
|
|
220
|
+
var agentOutputSchema = z2.object({
|
|
221
|
+
success,
|
|
222
|
+
error,
|
|
223
|
+
id: str("Agent job identifier, for `firecrawl_agent_status`."),
|
|
224
|
+
status: str("Job state at submission time."),
|
|
225
|
+
threadId: str("Research thread this job belongs to."),
|
|
226
|
+
threadTurn: num("Turn number of this job within its thread.")
|
|
227
|
+
}).describe("The queued research job. The result is read with `firecrawl_agent_status`.");
|
|
228
|
+
var agentStatusOutputSchema = z2.object({
|
|
229
|
+
success,
|
|
230
|
+
error,
|
|
231
|
+
id: str("Agent job identifier."),
|
|
232
|
+
status: str("Job state: `processing` is non-terminal; `completed` and `failed` are terminal."),
|
|
233
|
+
progress: unknown("Progress information for a running job."),
|
|
234
|
+
data: unknown("Research result, present once the job has completed."),
|
|
235
|
+
creditsUsed: num("Credits the job consumed."),
|
|
236
|
+
sources: unknown("Sources the agent read."),
|
|
237
|
+
expiresAt: str("When the job result expires and can no longer be read."),
|
|
238
|
+
model: str("Agent model that ran the job."),
|
|
239
|
+
mode: str("Agent mode the job ran in."),
|
|
240
|
+
threadId: str("Research thread this job belongs to."),
|
|
241
|
+
threadTurn: num("Turn number of this job within its thread.")
|
|
242
|
+
}).describe("Progress or final result of a research agent job.");
|
|
243
|
+
var interactOutputSchema = z2.object({
|
|
244
|
+
success,
|
|
245
|
+
error: unknown("Why the interaction or the session could not run."),
|
|
246
|
+
url: str("The URL the session was opened from, when opening failed."),
|
|
247
|
+
scrapeId: str("Session identifier; pass it to reuse the session or to `firecrawl_interact_stop`."),
|
|
248
|
+
result: unknown("Interaction result, when it is not an object of its own."),
|
|
249
|
+
output: unknown("Execution output."),
|
|
250
|
+
stdout: str("Standard output of the executed code."),
|
|
251
|
+
stderr: str("Standard error of the executed code."),
|
|
252
|
+
exitCode: num("Exit status of the executed code."),
|
|
253
|
+
data: unknown("Payload returned by the interaction."),
|
|
254
|
+
sessionUrl: str("URL for viewing the live session."),
|
|
255
|
+
liveUrl: str("URL for viewing the live session.")
|
|
256
|
+
}).describe("Result of a browser interaction, with the session identifier to continue or stop it.");
|
|
257
|
+
var interactStopOutputSchema = z2.object({
|
|
258
|
+
success,
|
|
259
|
+
error,
|
|
260
|
+
message: str("Confirmation that the session was stopped."),
|
|
261
|
+
scrapeId: str("Session that was stopped.")
|
|
262
|
+
}).describe("Confirmation that the interact session was stopped.");
|
|
263
|
+
var parseOutputSchema = z2.object({
|
|
264
|
+
success,
|
|
265
|
+
error,
|
|
266
|
+
warning,
|
|
267
|
+
mode: str("Which phase of the hosted flow produced this response."),
|
|
268
|
+
message: str("Guidance for the next call."),
|
|
269
|
+
data: unknown("Parsed document content; can include `data.metadata.scrapeId` for parse feedback."),
|
|
270
|
+
upload: z2.object({
|
|
271
|
+
command: str("Local command that performs the upload. It carries no Firecrawl API key."),
|
|
272
|
+
method: str("HTTP method for the upload."),
|
|
273
|
+
headers: unknown("Headers the upload request must send."),
|
|
274
|
+
fields: unknown("Form fields the upload request must send, for a POST upload."),
|
|
275
|
+
uploadUrl: str("URL to upload the local file to."),
|
|
276
|
+
uploadRef: str("Reference to pass back on the second call."),
|
|
277
|
+
expiresAt: str("When the upload URL expires."),
|
|
278
|
+
maxSizeBytes: num("Largest file the upload URL accepts.")
|
|
279
|
+
}).optional().describe("Hosted phase one: how to upload the local file."),
|
|
280
|
+
nextToolCall: unknown("Hosted phase one: the second `firecrawl_parse` call to make once the upload succeeds, as `{name, arguments}`."),
|
|
281
|
+
notes: z2.array(z2.string()).nullable().optional().describe("Hosted phase one: constraints on completing the upload flow."),
|
|
282
|
+
raw: str("Response body when it was not JSON.")
|
|
283
|
+
}).describe("Parsed document content, or the upload instructions for the hosted two-call flow.");
|
|
284
|
+
var deprecatedToolOutputSchema = z2.object({
|
|
285
|
+
code: str("Always `DEPRECATED_TOOL`."),
|
|
286
|
+
message: str("Why the tool is unavailable and what to call instead."),
|
|
287
|
+
replacement: unknown("The replacement tool, with instructions and example arguments."),
|
|
288
|
+
docs_url: str("Documentation for the replacement.")
|
|
289
|
+
}).describe("A pointer to the replacement tool. This entry point is deprecated and always errors.");
|
|
290
|
+
var monitorOutputSchema = z2.object({
|
|
291
|
+
success,
|
|
292
|
+
error,
|
|
293
|
+
data: unknown("The monitor record: configuration, schedule, targets, and current state.")
|
|
294
|
+
}).describe("One monitor record.");
|
|
295
|
+
var monitorListOutputSchema = z2.object({
|
|
296
|
+
success,
|
|
297
|
+
error,
|
|
298
|
+
data: unknown("One page of monitor records."),
|
|
299
|
+
pagination: unknown("Pagination metadata for the listing.")
|
|
300
|
+
}).describe("One page of monitors for the authenticated account.");
|
|
301
|
+
var monitorDeleteOutputSchema = z2.object({
|
|
302
|
+
success,
|
|
303
|
+
error,
|
|
304
|
+
data: unknown("Deletion details, when the API returns any.")
|
|
305
|
+
}).describe("Deletion status for the monitor.");
|
|
306
|
+
var monitorRunOutputSchema = z2.object({
|
|
307
|
+
success,
|
|
308
|
+
error,
|
|
309
|
+
data: unknown("The queued check.")
|
|
310
|
+
}).describe("The check queued outside the monitor schedule.");
|
|
311
|
+
var monitorChecksOutputSchema = z2.object({
|
|
312
|
+
success,
|
|
313
|
+
error,
|
|
314
|
+
data: unknown("One page of check summaries."),
|
|
315
|
+
pagination: unknown("Pagination metadata for the listing.")
|
|
316
|
+
}).describe("One page of historical checks for the monitor.");
|
|
317
|
+
var monitorCheckOutputSchema = z2.object({
|
|
318
|
+
success,
|
|
319
|
+
error,
|
|
320
|
+
data: unknown("The check and its page-level results: each page reports `same`, `new`, `changed`, `removed`, or `error`, with diffs and any goal judgment."),
|
|
321
|
+
pagination: unknown("Pagination metadata for the page results."),
|
|
322
|
+
next: str("URL of the next page of results, when more exist.")
|
|
323
|
+
}).describe("One monitor check with its page-level diff results.");
|
|
324
|
+
var paperSchema = z2.looseObject({
|
|
325
|
+
paperId: str("Canonical paper identifier."),
|
|
326
|
+
primaryId: str("Display identifier, ordered for citation and fetch use."),
|
|
327
|
+
ids: unknown("Source identifiers by namespace, such as `arxiv`, `doi`, or `pmid`."),
|
|
328
|
+
title: str("Paper title."),
|
|
329
|
+
abstract: str("Paper abstract."),
|
|
330
|
+
authors: unknown("Authors, as a comma-joined string or as `{name, affiliation}` entries."),
|
|
331
|
+
categories: z2.array(z2.string()).nullable().optional().describe("Paper categories, such as `cs.LG`."),
|
|
332
|
+
createdDate: str("Date the paper was first indexed or published."),
|
|
333
|
+
updateDate: str("Date the paper was last updated.")
|
|
334
|
+
});
|
|
335
|
+
var researchSearchOutputSchema = z2.object({
|
|
336
|
+
results: z2.array(paperSchema).describe("Ranked papers, in the order the text block lists them.")
|
|
337
|
+
}).describe("Ranked papers with canonical IDs, titles, authors, and abstracts.");
|
|
338
|
+
var researchPaperOutputSchema = z2.object({
|
|
339
|
+
paper: paperSchema.optional().describe("Canonical metadata for the requested paper, absent when it was not found.")
|
|
340
|
+
}).describe("Canonical metadata for one paper.");
|
|
341
|
+
var researchRelatedOutputSchema = z2.object({
|
|
342
|
+
results: z2.array(paperSchema).describe("Ranked citation-graph candidates."),
|
|
343
|
+
poolSize: z2.number().describe("Number of candidates evaluated before ranking."),
|
|
344
|
+
note: str("Note about how the candidates were produced.")
|
|
345
|
+
}).describe("Ranked citation-graph candidates and the size of the evaluated pool.");
|
|
346
|
+
var researchReadOutputSchema = z2.object({
|
|
347
|
+
passages: z2.array(z2.looseObject({ text: str("Passage text.") })).describe("In-body passages relevant to the question; empty when no full text is indexed.")
|
|
348
|
+
}).describe("Full-text passages from one paper.");
|
|
349
|
+
var developerSearchOutputSchema = z2.object({
|
|
350
|
+
results: z2.array(
|
|
351
|
+
z2.looseObject({
|
|
352
|
+
id: str("Stable result id, such as `issue:owner/repo#123` or `doc:<hash>`; its prefix gives the source type."),
|
|
353
|
+
url: str("Source URL."),
|
|
354
|
+
title: str("Result title."),
|
|
355
|
+
passages: z2.array(z2.looseObject({ text: str("Matched passage, in markdown.") })).optional().describe("Matched passages.")
|
|
356
|
+
})
|
|
357
|
+
).describe("Ranked results, in the order the text block lists them.")
|
|
358
|
+
}).describe("Ranked developer-index results with their matched passages.");
|
|
359
|
+
var creditUsageOutputSchema = z2.object({
|
|
360
|
+
// Current view.
|
|
361
|
+
remainingCredits: num("Credits left in the current billing period; can exceed `planCredits` when extra credits were purchased or granted."),
|
|
362
|
+
planCredits: num("Credits included in the plan for the period."),
|
|
363
|
+
billingPeriodStart: str("Start of the current billing period; null when the billing provider reports no period."),
|
|
364
|
+
billingPeriodEnd: str("End of the current billing period; null when the billing provider reports no period."),
|
|
365
|
+
// Historical view.
|
|
366
|
+
success,
|
|
367
|
+
error,
|
|
368
|
+
periods: unknown("Historical periods sorted by start date, each with `startDate`, `endDate`, `creditsUsed`, and `apiKey` when broken down by key.")
|
|
369
|
+
}).describe("The team credit balance, or historical credit consumption by period.");
|
|
370
|
+
|
|
371
|
+
// src/alexandria.ts
|
|
372
|
+
import { z as z3 } from "zod";
|
|
93
373
|
var catalogueTypes = ["alexandria", "exchange"];
|
|
94
|
-
var searchSourceSchema =
|
|
95
|
-
|
|
96
|
-
|
|
374
|
+
var searchSourceSchema = z3.union([
|
|
375
|
+
z3.enum(["web", "images", "news", ...catalogueTypes]),
|
|
376
|
+
z3.object({ type: z3.enum(["web", "images", "news", ...catalogueTypes]) }).strict()
|
|
97
377
|
]);
|
|
98
378
|
function hasAlexandria(sources) {
|
|
99
379
|
return Array.isArray(sources) && sources.some(
|
|
@@ -116,24 +396,25 @@ function normalizeSearchSources(sources) {
|
|
|
116
396
|
function searchQueryIsValid(args2) {
|
|
117
397
|
return !!args2.query?.trim();
|
|
118
398
|
}
|
|
119
|
-
var findToolsSchema =
|
|
120
|
-
query:
|
|
121
|
-
urls:
|
|
122
|
-
|
|
399
|
+
var findToolsSchema = z3.object({
|
|
400
|
+
query: z3.string().trim().min(1).max(2e3).optional().describe("Semantic lookup of tools for the data you need. Selectors constrain the search."),
|
|
401
|
+
urls: z3.array(
|
|
402
|
+
z3.string().url().regex(/^https?:\/\//)
|
|
123
403
|
).min(1).max(100).optional(),
|
|
124
|
-
providers:
|
|
125
|
-
categories:
|
|
126
|
-
groups:
|
|
127
|
-
capabilities:
|
|
128
|
-
level:
|
|
129
|
-
expand:
|
|
130
|
-
limit:
|
|
131
|
-
offset:
|
|
404
|
+
providers: z3.array(z3.string().min(1)).min(1).max(50).optional().describe("Provider IDs returned by category browsing. Lists compact tools by default."),
|
|
405
|
+
categories: z3.array(z3.string().min(1)).min(1).max(50).optional().describe("Category IDs returned by an empty call. Lists providers by default."),
|
|
406
|
+
groups: z3.array(z3.string().min(1)).min(1).max(50).optional().describe("Optional group IDs for explicit group browsing."),
|
|
407
|
+
capabilities: z3.array(z3.string().min(1)).min(1).max(50).optional().describe("Exact capability IDs. A selected capability expands its complete contract by default."),
|
|
408
|
+
level: z3.enum(["categories", "providers", "groups", "tools"]).optional(),
|
|
409
|
+
expand: z3.array(z3.enum(["options", "response", "examples"])).optional().describe('Use ["options", "response"] for inputs and output shape without example payloads; [] keeps results compact. Omit for full contracts on selected capabilities.'),
|
|
410
|
+
limit: z3.number().int().min(1).max(100).optional(),
|
|
411
|
+
offset: z3.number().int().nonnegative().optional()
|
|
132
412
|
}).strict();
|
|
133
413
|
var ALEXANDRIA_CATALOGUE_VERTICALS = "companies, people, jobs, finance and filings, public records and government spending, real estate, places and restaurants, retail and prices, package registries and developer data, news, research, and more";
|
|
134
414
|
var ALEXANDRIA_CATALOGUE_SENTENCE = "Alexandria is Firecrawl's catalogue of data providers and workflows across " + ALEXANDRIA_CATALOGUE_VERTICALS + "; providers return typed, sourced records through published contracts.";
|
|
135
|
-
var ALEXANDRIA_SOURCES_OPT_OUT = '
|
|
136
|
-
var
|
|
415
|
+
var ALEXANDRIA_SOURCES_OPT_OUT = 'A search with sources: ["web"] omits semantic provider discovery; domainTools: true can still return website-matched tools. Web-only results use domainTools: false.';
|
|
416
|
+
var ALEXANDRIA_SEARCH_LEAD = "Authenticated search also returns matching Alexandria data providers in data.tools (" + ALEXANDRIA_CATALOGUE_VERTICALS + "). Prefer a provider over scraping pages when the task needs the same fields across several entities, exact figures or timestamps, provenance, or many records; use web results when they already answer the question. " + ALEXANDRIA_SOURCES_OPT_OUT;
|
|
417
|
+
var ALEXANDRIA_CONTRACT_GUIDANCE = "The selected contract marks required inputs and any requiresOneOf groups (at least one member per group); it may include example.request/example.response and response.key (which may differ from records). Where pagination is declared, its fields govern paging with the same filters; catalogue next is separate from provider pagination.";
|
|
137
418
|
function findToolsOptions(args2) {
|
|
138
419
|
const level = args2.level ?? (args2.query || args2.capabilities?.length || args2.providers?.length || args2.groups?.length || args2.urls?.length ? "tools" : args2.categories?.length ? "providers" : "categories");
|
|
139
420
|
if (level === "categories" && (args2.query || args2.providers?.length || args2.categories?.length || args2.groups?.length || args2.capabilities?.length || args2.urls?.length || args2.expand !== void 0)) {
|
|
@@ -163,7 +444,7 @@ function withFindToolsNavigation(envelope) {
|
|
|
163
444
|
if (link(page.next)) page.nextTool = link(page.next);
|
|
164
445
|
return envelope;
|
|
165
446
|
}
|
|
166
|
-
var ALEXANDRIA_SEARCH_INSTRUCTIONS = 'Authenticated search combines web results, semantic tool summaries and domain matches. Use sources: ["alexandria"] for semantic tools only
|
|
447
|
+
var ALEXANDRIA_SEARCH_INSTRUCTIONS = 'Authenticated search combines web results, semantic tool summaries and domain matches. Use sources: ["alexandria"] for semantic tools only. ' + ALEXANDRIA_SOURCES_OPT_OUT + ' Tool matches describe available structured-data capabilities, not executed data. toolDetail: "compact" (default) returns only provider, capability and description; "summary" adds metadata; "full" includes their input and output contracts. firecrawl_scrape with an alexandria body executes a selected capability; firecrawl_find_tools provides catalogue browsing and full contracts.';
|
|
167
448
|
|
|
168
449
|
// src/alexandria-output.ts
|
|
169
450
|
var INLINE_TOKEN_BUDGET = 2e4;
|
|
@@ -230,7 +511,7 @@ async function alexandriaOutput(payload, calls, post, recoveryRequestId, extras
|
|
|
230
511
|
}
|
|
231
512
|
|
|
232
513
|
// src/developer.ts
|
|
233
|
-
import { z as
|
|
514
|
+
import { z as z4 } from "zod";
|
|
234
515
|
|
|
235
516
|
// src/origin.ts
|
|
236
517
|
import { createRequire } from "module";
|
|
@@ -296,12 +577,13 @@ Search an index of public repositories, GitHub issues, merged pull requests, rep
|
|
|
296
577
|
|
|
297
578
|
Returns ranked results with an ID, source type, URL, title, and the matched passages in markdown.
|
|
298
579
|
`,
|
|
299
|
-
|
|
300
|
-
|
|
580
|
+
outputSchema: developerSearchOutputSchema,
|
|
581
|
+
parameters: z4.object({
|
|
582
|
+
query: z4.string().min(1).describe(
|
|
301
583
|
"Natural-language developer question or search phrase, including the library, error message, or API involved when relevant."
|
|
302
584
|
),
|
|
303
|
-
k:
|
|
304
|
-
skills:
|
|
585
|
+
k: z4.number().int().min(1).max(100).optional().describe("Number of ranked results to return (default 10)."),
|
|
586
|
+
skills: z4.enum(["only"]).optional().describe('Set to "only" to search only agent-skill files.')
|
|
305
587
|
}),
|
|
306
588
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
307
589
|
const { query, k, skills } = args2;
|
|
@@ -311,7 +593,8 @@ Returns ranked results with an ID, source type, URL, title, and the matched pass
|
|
|
311
593
|
if (skills != null) params.append("skills", skills);
|
|
312
594
|
const client = getClient2(session);
|
|
313
595
|
const res = await client.http.get(`${BASE}?${params.toString()}`, originHeaders(requestOrigin(mcpClient, session)));
|
|
314
|
-
|
|
596
|
+
const results = res.data?.results ?? [];
|
|
597
|
+
return withStructured(fmtDeveloper(results), { results });
|
|
315
598
|
}
|
|
316
599
|
});
|
|
317
600
|
}
|
|
@@ -331,7 +614,7 @@ function extractSingleTrustedClientIp(rawForwardedFor) {
|
|
|
331
614
|
}
|
|
332
615
|
|
|
333
616
|
// src/monitor.ts
|
|
334
|
-
import { z as
|
|
617
|
+
import { z as z5 } from "zod";
|
|
335
618
|
|
|
336
619
|
// src/session-credential.ts
|
|
337
620
|
import { createHmac } from "crypto";
|
|
@@ -456,11 +739,8 @@ async function monitorRequest(session, origin, path2, init = {}) {
|
|
|
456
739
|
}
|
|
457
740
|
return payload;
|
|
458
741
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
}
|
|
462
|
-
var pageStatusSchema = z4.enum(["same", "new", "changed", "removed", "error"]);
|
|
463
|
-
var checkStatusSchema = z4.enum([
|
|
742
|
+
var pageStatusSchema = z5.enum(["same", "new", "changed", "removed", "error"]);
|
|
743
|
+
var checkStatusSchema = z5.enum([
|
|
464
744
|
"queued",
|
|
465
745
|
"running",
|
|
466
746
|
"completed",
|
|
@@ -553,22 +833,23 @@ Create a recurring scrape, crawl, or search monitor that compares each check wit
|
|
|
553
833
|
|
|
554
834
|
In the simple form, a \`goal\` is required. If \`queries\` contains one or more non-empty values and is supplied with \`page\`/\`pages\`, \`queries\` create the search target and page targets are ignored. A monitor schedules future network checks and can send configured email or webhook notifications. Returns the created monitor.
|
|
555
835
|
`,
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
836
|
+
outputSchema: monitorOutputSchema,
|
|
837
|
+
parameters: z5.object({
|
|
838
|
+
body: z5.record(z5.string(), z5.any()).optional(),
|
|
839
|
+
page: z5.string().optional(),
|
|
840
|
+
pages: z5.array(z5.string()).optional(),
|
|
841
|
+
queries: z5.array(z5.string()).optional(),
|
|
842
|
+
searchWindow: z5.enum(["5m", "15m", "1h", "6h", "24h", "7d"]).optional(),
|
|
843
|
+
maxResults: z5.number().int().min(1).max(50).optional(),
|
|
844
|
+
includeDomains: z5.array(z5.string()).optional(),
|
|
845
|
+
excludeDomains: z5.array(z5.string()).optional(),
|
|
846
|
+
goal: z5.string().optional(),
|
|
847
|
+
name: z5.string().optional(),
|
|
848
|
+
scheduleText: z5.string().optional(),
|
|
849
|
+
timezone: z5.string().optional(),
|
|
850
|
+
email: z5.string().optional(),
|
|
851
|
+
includeDiffs: z5.boolean().optional(),
|
|
852
|
+
webhookUrl: z5.string().optional()
|
|
572
853
|
}),
|
|
573
854
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
574
855
|
const body = buildMonitorCreateBody(args2);
|
|
@@ -582,7 +863,7 @@ In the simple form, a \`goal\` is required. If \`queries\` contains one or more
|
|
|
582
863
|
body
|
|
583
864
|
}
|
|
584
865
|
);
|
|
585
|
-
return
|
|
866
|
+
return structuredText(res);
|
|
586
867
|
}
|
|
587
868
|
});
|
|
588
869
|
server2.addTool({
|
|
@@ -599,9 +880,10 @@ In the simple form, a \`goal\` is required. If \`queries\` contains one or more
|
|
|
599
880
|
description: `
|
|
600
881
|
List monitors for the authenticated account with optional pagination controls. Returns one page of monitor records and pagination metadata.
|
|
601
882
|
`,
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
883
|
+
outputSchema: monitorListOutputSchema,
|
|
884
|
+
parameters: z5.object({
|
|
885
|
+
limit: z5.number().int().positive().optional(),
|
|
886
|
+
offset: z5.number().int().nonnegative().optional()
|
|
605
887
|
}),
|
|
606
888
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
607
889
|
const { limit, offset } = args2;
|
|
@@ -613,7 +895,7 @@ List monitors for the authenticated account with optional pagination controls. R
|
|
|
613
895
|
query: { limit, offset }
|
|
614
896
|
}
|
|
615
897
|
);
|
|
616
|
-
return
|
|
898
|
+
return structuredText(res);
|
|
617
899
|
}
|
|
618
900
|
});
|
|
619
901
|
server2.addTool({
|
|
@@ -630,7 +912,8 @@ List monitors for the authenticated account with optional pagination controls. R
|
|
|
630
912
|
description: `
|
|
631
913
|
Retrieve one monitor by ID, including its configuration and current state. This does not run or modify the monitor.
|
|
632
914
|
`,
|
|
633
|
-
|
|
915
|
+
outputSchema: monitorOutputSchema,
|
|
916
|
+
parameters: z5.object({ id: z5.string() }),
|
|
634
917
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
635
918
|
const { id } = args2;
|
|
636
919
|
const res = await monitorRequest(
|
|
@@ -638,7 +921,7 @@ Retrieve one monitor by ID, including its configuration and current state. This
|
|
|
638
921
|
requestOrigin(mcpClient, session),
|
|
639
922
|
`/monitor/${encodeURIComponent(id)}`
|
|
640
923
|
);
|
|
641
|
-
return
|
|
924
|
+
return structuredText(res);
|
|
642
925
|
}
|
|
643
926
|
});
|
|
644
927
|
server2.addTool({
|
|
@@ -657,9 +940,10 @@ Patch an existing monitor by ID. The body can change its name, active/paused sta
|
|
|
657
940
|
|
|
658
941
|
Returns the updated monitor.
|
|
659
942
|
`,
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
943
|
+
outputSchema: monitorOutputSchema,
|
|
944
|
+
parameters: z5.object({
|
|
945
|
+
id: z5.string(),
|
|
946
|
+
body: z5.record(z5.string(), z5.any())
|
|
663
947
|
}),
|
|
664
948
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
665
949
|
const { id, body } = args2;
|
|
@@ -669,7 +953,7 @@ Returns the updated monitor.
|
|
|
669
953
|
`/monitor/${encodeURIComponent(id)}`,
|
|
670
954
|
{ method: "PATCH", body }
|
|
671
955
|
);
|
|
672
|
-
return
|
|
956
|
+
return structuredText(res);
|
|
673
957
|
}
|
|
674
958
|
});
|
|
675
959
|
server2.addTool({
|
|
@@ -686,7 +970,8 @@ Returns the updated monitor.
|
|
|
686
970
|
description: `
|
|
687
971
|
Permanently delete a monitor by ID and stop its future schedule. This operation cannot be undone and returns deletion status.
|
|
688
972
|
`,
|
|
689
|
-
|
|
973
|
+
outputSchema: monitorDeleteOutputSchema,
|
|
974
|
+
parameters: z5.object({ id: z5.string() }),
|
|
690
975
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
691
976
|
const { id } = args2;
|
|
692
977
|
log.info("Deleting monitor", { id });
|
|
@@ -696,7 +981,7 @@ Permanently delete a monitor by ID and stop its future schedule. This operation
|
|
|
696
981
|
`/monitor/${encodeURIComponent(id)}`,
|
|
697
982
|
{ method: "DELETE" }
|
|
698
983
|
);
|
|
699
|
-
return
|
|
984
|
+
return structuredText(res);
|
|
700
985
|
}
|
|
701
986
|
});
|
|
702
987
|
server2.addTool({
|
|
@@ -713,7 +998,8 @@ Permanently delete a monitor by ID and stop its future schedule. This operation
|
|
|
713
998
|
description: `
|
|
714
999
|
Queue an immediate check for a monitor outside its normal schedule. This starts network work for the monitor's configured targets and returns the queued check.
|
|
715
1000
|
`,
|
|
716
|
-
|
|
1001
|
+
outputSchema: monitorRunOutputSchema,
|
|
1002
|
+
parameters: z5.object({ id: z5.string() }),
|
|
717
1003
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
718
1004
|
const { id } = args2;
|
|
719
1005
|
const res = await monitorRequest(
|
|
@@ -722,7 +1008,7 @@ Queue an immediate check for a monitor outside its normal schedule. This starts
|
|
|
722
1008
|
`/monitor/${encodeURIComponent(id)}/run`,
|
|
723
1009
|
{ method: "POST" }
|
|
724
1010
|
);
|
|
725
|
-
return
|
|
1011
|
+
return structuredText(res);
|
|
726
1012
|
}
|
|
727
1013
|
});
|
|
728
1014
|
server2.addTool({
|
|
@@ -739,10 +1025,11 @@ Queue an immediate check for a monitor outside its normal schedule. This starts
|
|
|
739
1025
|
description: `
|
|
740
1026
|
List historical checks for a monitor, optionally filtered by status and bounded by a result limit. Returns one page of check summaries and pagination metadata.
|
|
741
1027
|
`,
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
1028
|
+
outputSchema: monitorChecksOutputSchema,
|
|
1029
|
+
parameters: z5.object({
|
|
1030
|
+
id: z5.string(),
|
|
1031
|
+
limit: z5.number().int().positive().optional(),
|
|
1032
|
+
offset: z5.number().int().nonnegative().optional(),
|
|
746
1033
|
status: checkStatusSchema.optional()
|
|
747
1034
|
}),
|
|
748
1035
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
@@ -753,7 +1040,7 @@ List historical checks for a monitor, optionally filtered by status and bounded
|
|
|
753
1040
|
`/monitor/${encodeURIComponent(id)}/checks`,
|
|
754
1041
|
{ query: { limit, offset, status } }
|
|
755
1042
|
);
|
|
756
|
-
return
|
|
1043
|
+
return structuredText(res);
|
|
757
1044
|
}
|
|
758
1045
|
});
|
|
759
1046
|
server2.addTool({
|
|
@@ -772,11 +1059,12 @@ Retrieve one monitor check and its page-level results, optionally filtered by pa
|
|
|
772
1059
|
|
|
773
1060
|
Markdown tracking returns a unified text diff, JSON tracking returns field paths with previous/current values and a current snapshot, and mixed tracking returns both. Returns one page of results plus a \`next\` URL when more pages exist.
|
|
774
1061
|
`,
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
1062
|
+
outputSchema: monitorCheckOutputSchema,
|
|
1063
|
+
parameters: z5.object({
|
|
1064
|
+
id: z5.string(),
|
|
1065
|
+
checkId: z5.string(),
|
|
1066
|
+
limit: z5.number().int().positive().optional(),
|
|
1067
|
+
skip: z5.number().int().nonnegative().optional(),
|
|
780
1068
|
pageStatus: pageStatusSchema.optional()
|
|
781
1069
|
}),
|
|
782
1070
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
@@ -787,13 +1075,13 @@ Markdown tracking returns a unified text diff, JSON tracking returns field paths
|
|
|
787
1075
|
`/monitor/${encodeURIComponent(id)}/checks/${encodeURIComponent(checkId)}`,
|
|
788
1076
|
{ query: { limit, skip, status: pageStatus } }
|
|
789
1077
|
);
|
|
790
|
-
return
|
|
1078
|
+
return structuredText(res);
|
|
791
1079
|
}
|
|
792
1080
|
});
|
|
793
1081
|
}
|
|
794
1082
|
|
|
795
1083
|
// src/research.ts
|
|
796
|
-
import { z as
|
|
1084
|
+
import { z as z6 } from "zod";
|
|
797
1085
|
import { UserError } from "fastmcp";
|
|
798
1086
|
var BASE2 = "/v2/search/research";
|
|
799
1087
|
function appendParam(params, key, value) {
|
|
@@ -909,21 +1197,22 @@ Several distinct framings of the same question surface different papers than a s
|
|
|
909
1197
|
|
|
910
1198
|
Returns ranked papers with canonical IDs, titles, authors, and abstracts.
|
|
911
1199
|
`,
|
|
912
|
-
|
|
913
|
-
|
|
1200
|
+
outputSchema: researchSearchOutputSchema,
|
|
1201
|
+
parameters: z6.object({
|
|
1202
|
+
query: z6.string().min(1).describe(
|
|
914
1203
|
"Natural-language research topic or question, including methods, systems, conditions, populations, interventions, or outcomes when relevant."
|
|
915
1204
|
),
|
|
916
|
-
k:
|
|
917
|
-
authors:
|
|
1205
|
+
k: z6.number().int().min(1).max(500).optional().describe("Number of ranked papers to return (default 40)."),
|
|
1206
|
+
authors: z6.array(z6.string()).optional().describe(
|
|
918
1207
|
"Author substring filter(s); ALL must match (case-insensitive)."
|
|
919
1208
|
),
|
|
920
|
-
categories:
|
|
1209
|
+
categories: z6.array(z6.string()).optional().describe(
|
|
921
1210
|
"Paper category filter(s) (e.g. `cs.LG`); ALL provided values must match."
|
|
922
1211
|
),
|
|
923
|
-
from:
|
|
1212
|
+
from: z6.string().optional().describe(
|
|
924
1213
|
"Inclusive lower bound on created/updated date (`YYYY-MM-DD`)."
|
|
925
1214
|
),
|
|
926
|
-
to:
|
|
1215
|
+
to: z6.string().optional().describe(
|
|
927
1216
|
"Inclusive upper bound on created/updated date (`YYYY-MM-DD`)."
|
|
928
1217
|
)
|
|
929
1218
|
}),
|
|
@@ -941,7 +1230,8 @@ Returns ranked papers with canonical IDs, titles, authors, and abstracts.
|
|
|
941
1230
|
withQuery(`${BASE2}/papers`, params),
|
|
942
1231
|
originHeaders(requestOrigin(mcpClient, session))
|
|
943
1232
|
);
|
|
944
|
-
|
|
1233
|
+
const results = res.data?.results ?? [];
|
|
1234
|
+
return withStructured(fmtHits(results), { results });
|
|
945
1235
|
}
|
|
946
1236
|
});
|
|
947
1237
|
server2.addTool({
|
|
@@ -958,8 +1248,9 @@ Returns ranked papers with canonical IDs, titles, authors, and abstracts.
|
|
|
958
1248
|
description: `
|
|
959
1249
|
Retrieve canonical metadata for one paper ID, such as an arXiv, PMC, PMID, or DOI identifier. Returns the title, abstract, authors, categories, source IDs, and dates as markdown.
|
|
960
1250
|
`,
|
|
961
|
-
|
|
962
|
-
|
|
1251
|
+
outputSchema: researchPaperOutputSchema,
|
|
1252
|
+
parameters: z6.object({
|
|
1253
|
+
paperId: z6.string().min(1).describe(
|
|
963
1254
|
"Canonical paperId or primaryId such as `arxiv:1706.03762`, `pmcid:PMC12530322`, `pmid:40953549`, or `doi:10.1016/j.neunet.2025.108095`."
|
|
964
1255
|
)
|
|
965
1256
|
}),
|
|
@@ -970,7 +1261,11 @@ Retrieve canonical metadata for one paper ID, such as an arXiv, PMC, PMID, or DO
|
|
|
970
1261
|
`${BASE2}/papers/${encodeURIComponent(paperId)}`,
|
|
971
1262
|
originHeaders(requestOrigin(mcpClient, session))
|
|
972
1263
|
);
|
|
973
|
-
|
|
1264
|
+
const paper = res.data?.paper;
|
|
1265
|
+
return withStructured(
|
|
1266
|
+
fmtPaperMetadata(paper),
|
|
1267
|
+
paper ? { paper } : {}
|
|
1268
|
+
);
|
|
974
1269
|
}
|
|
975
1270
|
});
|
|
976
1271
|
server2.addTool({
|
|
@@ -989,12 +1284,13 @@ Find citation-graph candidates from one to ten \`seed_ids\`; the first ID is the
|
|
|
989
1284
|
|
|
990
1285
|
Returns ranked candidates and the evaluated pool size.
|
|
991
1286
|
`,
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1287
|
+
outputSchema: researchRelatedOutputSchema,
|
|
1288
|
+
parameters: z6.object({
|
|
1289
|
+
seed_ids: z6.array(z6.string()).min(1).max(10),
|
|
1290
|
+
intent: z6.string().min(1),
|
|
1291
|
+
mode: z6.enum(["similar", "citers", "references"]).optional(),
|
|
1292
|
+
k: z6.number().int().min(1).max(500).optional(),
|
|
1293
|
+
rerank: z6.boolean().optional().describe("Apply an additional rerank over the fused candidates.")
|
|
998
1294
|
}),
|
|
999
1295
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
1000
1296
|
const { seed_ids, intent, mode, k, rerank } = args2;
|
|
@@ -1013,10 +1309,15 @@ Returns ranked candidates and the evaluated pool size.
|
|
|
1013
1309
|
),
|
|
1014
1310
|
originHeaders(requestOrigin(mcpClient, session))
|
|
1015
1311
|
);
|
|
1312
|
+
const results = res.data?.results ?? [];
|
|
1313
|
+
const poolSize = res.data?.poolSize ?? 0;
|
|
1016
1314
|
const note = res.data?.note ? `
|
|
1017
1315
|
note: ${res.data.note}` : "";
|
|
1018
|
-
return
|
|
1019
|
-
(
|
|
1316
|
+
return withStructured(
|
|
1317
|
+
`${fmtHits(results)}
|
|
1318
|
+
(poolSize=${poolSize})${note}`,
|
|
1319
|
+
{ results, poolSize, ...res.data?.note ? { note: res.data.note } : {} }
|
|
1320
|
+
);
|
|
1020
1321
|
}
|
|
1021
1322
|
});
|
|
1022
1323
|
server2.addTool({
|
|
@@ -1035,12 +1336,13 @@ Retrieve in-body passages from one paper that are relevant to a specific questio
|
|
|
1035
1336
|
|
|
1036
1337
|
Returns matching passages or a notice when full text is unavailable.
|
|
1037
1338
|
`,
|
|
1038
|
-
|
|
1039
|
-
|
|
1339
|
+
outputSchema: researchReadOutputSchema,
|
|
1340
|
+
parameters: z6.object({
|
|
1341
|
+
paperId: z6.string().min(1).describe(
|
|
1040
1342
|
"Canonical paperId or primaryId such as `arxiv:1706.03762`, `pmcid:PMC12530322`, `pmid:40953549`, or `doi:10.1016/j.neunet.2025.108095`."
|
|
1041
1343
|
),
|
|
1042
|
-
question:
|
|
1043
|
-
k:
|
|
1344
|
+
question: z6.string().min(1),
|
|
1345
|
+
k: z6.number().int().min(1).max(50).optional().describe("Number of passages to return (default 4).")
|
|
1044
1346
|
}),
|
|
1045
1347
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
1046
1348
|
const { paperId, question, k } = args2;
|
|
@@ -1053,7 +1355,10 @@ Returns matching passages or a notice when full text is unavailable.
|
|
|
1053
1355
|
originHeaders(requestOrigin(mcpClient, session))
|
|
1054
1356
|
);
|
|
1055
1357
|
const passages = res.data?.passages ?? [];
|
|
1056
|
-
return
|
|
1358
|
+
return withStructured(
|
|
1359
|
+
passages.length ? passages.map((p) => p.text).join("\n---\n") : "(no full-text passages available for this paper)",
|
|
1360
|
+
{ passages }
|
|
1361
|
+
);
|
|
1057
1362
|
}
|
|
1058
1363
|
});
|
|
1059
1364
|
server2.addTool({
|
|
@@ -1067,9 +1372,10 @@ Returns matching passages or a notice when full text is unavailable.
|
|
|
1067
1372
|
description: `
|
|
1068
1373
|
Deprecated compatibility entry point. Use firecrawl_developer_search for GitHub issues, pull requests, and READMEs, plus code documentation, returned as matched passages.
|
|
1069
1374
|
`,
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1375
|
+
outputSchema: deprecatedToolOutputSchema,
|
|
1376
|
+
parameters: z6.object({
|
|
1377
|
+
query: z6.string().min(1),
|
|
1378
|
+
k: z6.number().int().min(1).max(100).optional()
|
|
1073
1379
|
}),
|
|
1074
1380
|
canList: () => false,
|
|
1075
1381
|
beforeValidate: () => {
|
|
@@ -1088,10 +1394,7 @@ Deprecated compatibility entry point. Use firecrawl_developer_search for GitHub
|
|
|
1088
1394
|
}
|
|
1089
1395
|
|
|
1090
1396
|
// src/usage.ts
|
|
1091
|
-
import { z as
|
|
1092
|
-
function asText2(data) {
|
|
1093
|
-
return JSON.stringify(data, null, 2);
|
|
1094
|
-
}
|
|
1397
|
+
import { z as z7 } from "zod";
|
|
1095
1398
|
function assertSuccessful(status, response, operation) {
|
|
1096
1399
|
if (status === 200 && response.success) return;
|
|
1097
1400
|
throw new Error(response.error || `Failed to ${operation}`);
|
|
@@ -1112,11 +1415,12 @@ The current view returns \`remainingCredits\`, \`planCredits\`, \`billingPeriodS
|
|
|
1112
1415
|
|
|
1113
1416
|
The historical view returns periods sorted by start date with \`startDate\`, \`endDate\`, and \`creditsUsed\`. Set \`byApiKey\` to include separate periods per API key, identified by the optional \`apiKey\` field. The newest period's \`endDate\` can be null.
|
|
1114
1417
|
`,
|
|
1115
|
-
|
|
1116
|
-
|
|
1418
|
+
outputSchema: creditUsageOutputSchema,
|
|
1419
|
+
parameters: z7.object({
|
|
1420
|
+
view: z7.enum(["current", "historical"]).optional().describe(
|
|
1117
1421
|
"Select current balance or historical monthly usage. Defaults to current."
|
|
1118
1422
|
),
|
|
1119
|
-
byApiKey:
|
|
1423
|
+
byApiKey: z7.boolean().optional().describe(
|
|
1120
1424
|
'Break historical usage down by API key. When view is omitted, true selects the historical view; it cannot be combined with view "current".'
|
|
1121
1425
|
)
|
|
1122
1426
|
}),
|
|
@@ -1141,7 +1445,7 @@ The historical view returns periods sorted by start date with \`startDate\`, \`e
|
|
|
1141
1445
|
response2.data,
|
|
1142
1446
|
"get historical credit usage"
|
|
1143
1447
|
);
|
|
1144
|
-
return
|
|
1448
|
+
return structuredText(response2.data);
|
|
1145
1449
|
}
|
|
1146
1450
|
const response = await client.http.get(
|
|
1147
1451
|
"/v2/team/credit-usage",
|
|
@@ -1149,7 +1453,7 @@ The historical view returns periods sorted by start date with \`startDate\`, \`e
|
|
|
1149
1453
|
);
|
|
1150
1454
|
assertSuccessful(response.status, response.data, "get credit usage");
|
|
1151
1455
|
const data = response.data.data ?? response.data;
|
|
1152
|
-
return
|
|
1456
|
+
return structuredText({
|
|
1153
1457
|
remainingCredits: data.remainingCredits ?? data.remaining_credits ?? 0,
|
|
1154
1458
|
planCredits: data.planCredits ?? data.plan_credits,
|
|
1155
1459
|
billingPeriodStart: data.billingPeriodStart ?? data.billing_period_start ?? null,
|
|
@@ -1247,11 +1551,11 @@ function getOAuthProtectedResourceMetadataUrl(profile) {
|
|
|
1247
1551
|
const base = `${resource.origin}/.well-known/oauth-protected-resource`;
|
|
1248
1552
|
return profile.id === "full" ? base : `${base}${resource.pathname}`;
|
|
1249
1553
|
}
|
|
1250
|
-
function createOAuthChallengeResponse(
|
|
1554
|
+
function createOAuthChallengeResponse(error2, profile, details = {}) {
|
|
1251
1555
|
if (!isMcpOAuthEnabled()) {
|
|
1252
1556
|
return void 0;
|
|
1253
1557
|
}
|
|
1254
|
-
const errorMessage =
|
|
1558
|
+
const errorMessage = error2 instanceof Error ? error2.message : String(error2 || "Unauthorized");
|
|
1255
1559
|
const wwwAuthenticateDescription = errorMessage.replace(/[\r\n]+/g, " ").trim();
|
|
1256
1560
|
const wwwAuthenticate = [
|
|
1257
1561
|
...profile.advertiseOAuth ? [
|
|
@@ -1303,8 +1607,8 @@ function createInvalidOAuthRecoveryResponse(recovery) {
|
|
|
1303
1607
|
);
|
|
1304
1608
|
}
|
|
1305
1609
|
var CREDENTIAL_VALIDATION_RETRY_AFTER_SECONDS = 5;
|
|
1306
|
-
function createCredentialValidationUnavailableResponse(
|
|
1307
|
-
return new Response(
|
|
1610
|
+
function createCredentialValidationUnavailableResponse(error2) {
|
|
1611
|
+
return new Response(error2.message, {
|
|
1308
1612
|
headers: {
|
|
1309
1613
|
"Content-Type": "text/plain; charset=utf-8",
|
|
1310
1614
|
"Retry-After": String(CREDENTIAL_VALIDATION_RETRY_AFTER_SECONDS)
|
|
@@ -1600,13 +1904,13 @@ function makeAuthenticate(profile) {
|
|
|
1600
1904
|
session
|
|
1601
1905
|
);
|
|
1602
1906
|
return session;
|
|
1603
|
-
}).catch((
|
|
1907
|
+
}).catch((error2) => {
|
|
1604
1908
|
emitSearchCompanionAuthTelemetry(profile, request, "rejected");
|
|
1605
1909
|
emitLegacyKeyPathTelemetry(profile, request, "rejected");
|
|
1606
|
-
if (
|
|
1607
|
-
throw createInvalidCredentialResponse(
|
|
1910
|
+
if (error2 instanceof InvalidFirecrawlCredentialError) {
|
|
1911
|
+
throw createInvalidCredentialResponse(error2);
|
|
1608
1912
|
}
|
|
1609
|
-
if (
|
|
1913
|
+
if (error2 instanceof InvalidOAuthCredentialError) {
|
|
1610
1914
|
const recovery = invalidOAuthRecoveryPayload();
|
|
1611
1915
|
const oauthChallenge2 = createOAuthChallengeResponse(
|
|
1612
1916
|
new Error(recovery.message),
|
|
@@ -1615,15 +1919,15 @@ function makeAuthenticate(profile) {
|
|
|
1615
1919
|
);
|
|
1616
1920
|
throw oauthChallenge2 ?? createInvalidOAuthRecoveryResponse(recovery);
|
|
1617
1921
|
}
|
|
1618
|
-
if (
|
|
1619
|
-
throw createCredentialValidationUnavailableResponse(
|
|
1922
|
+
if (error2 instanceof CredentialValidationUnavailableError) {
|
|
1923
|
+
throw createCredentialValidationUnavailableResponse(error2);
|
|
1620
1924
|
}
|
|
1621
1925
|
const shouldChallenge = requestShouldReceiveOAuthChallenge(request, profile);
|
|
1622
|
-
const oauthChallenge = shouldChallenge ? createOAuthChallengeResponse(
|
|
1926
|
+
const oauthChallenge = shouldChallenge ? createOAuthChallengeResponse(error2, profile) : void 0;
|
|
1623
1927
|
if (oauthChallenge) {
|
|
1624
1928
|
throw oauthChallenge;
|
|
1625
1929
|
}
|
|
1626
|
-
throw
|
|
1930
|
+
throw error2;
|
|
1627
1931
|
});
|
|
1628
1932
|
if (request) {
|
|
1629
1933
|
request[authResultByRequest] = authResult;
|
|
@@ -1643,30 +1947,30 @@ function removeEmptyTopLevel(obj) {
|
|
|
1643
1947
|
}
|
|
1644
1948
|
return out;
|
|
1645
1949
|
}
|
|
1646
|
-
var searchDomainSchema =
|
|
1950
|
+
var searchDomainSchema = z8.string().trim().toLowerCase().min(1).max(253).regex(
|
|
1647
1951
|
/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/,
|
|
1648
1952
|
"Domain must be a valid hostname without protocol or path"
|
|
1649
1953
|
);
|
|
1650
1954
|
var searchToolBaseFields = {
|
|
1651
|
-
toolDetail:
|
|
1652
|
-
query:
|
|
1653
|
-
domainTools:
|
|
1955
|
+
toolDetail: z8.enum(["compact", "summary", "full"]).optional().describe("Compact by default. Compact returns only provider, capability and description; full includes contracts. Inspect selected compact tools with firecrawl_find_tools providers and capabilities."),
|
|
1956
|
+
query: z8.string().min(1).describe("Query for web and semantic tool discovery. Operators include quoted phrases, `-term`, `site:host`, `inurl:term`, `intitle:term`, and `related:host`; the set is non-exhaustive. Catalogue browsing is available through firecrawl_find_tools."),
|
|
1957
|
+
domainTools: z8.boolean().optional().describe(
|
|
1654
1958
|
"Include domain-matched tools for result URLs. Defaults to true when Alexandria is combined with web, news or images; semantic-only search leaves domain matching off."
|
|
1655
1959
|
),
|
|
1656
|
-
highlights:
|
|
1960
|
+
highlights: z8.boolean().optional().describe(
|
|
1657
1961
|
"Return query-relevant page excerpts for web and news results when available (default). Highlights appear in web `description` and news `snippet`; otherwise, original snippets are returned. Set to false to keep the original search snippets."
|
|
1658
1962
|
),
|
|
1659
|
-
limit:
|
|
1660
|
-
tbs:
|
|
1661
|
-
filter:
|
|
1662
|
-
location:
|
|
1663
|
-
includeDomains:
|
|
1664
|
-
excludeDomains:
|
|
1665
|
-
sources:
|
|
1666
|
-
categories:
|
|
1963
|
+
limit: z8.number().int().min(1).max(100).optional(),
|
|
1964
|
+
tbs: z8.string().optional(),
|
|
1965
|
+
filter: z8.string().optional(),
|
|
1966
|
+
location: z8.string().optional(),
|
|
1967
|
+
includeDomains: z8.array(searchDomainSchema).optional().describe("Hostnames to restrict results to. Mutually exclusive with excludeDomains."),
|
|
1968
|
+
excludeDomains: z8.array(searchDomainSchema).optional().describe("Hostnames to leave out of results. Mutually exclusive with includeDomains."),
|
|
1969
|
+
sources: z8.array(searchSourceSchema).optional().describe("Search sources; authenticated sessions default to web + alexandria, keyless sessions to web only. " + ALEXANDRIA_SOURCES_OPT_OUT + ' Use ["alexandria"] alone for provider discovery without web results.'),
|
|
1970
|
+
categories: z8.array(z8.enum(["research", "pdf", "developer"])).optional().describe(
|
|
1667
1971
|
'Limit results to specific source types. `research` restricts ordinary web results to research-affiliated websites and returns page snippets, which is separate from the `firecrawl_research_*` tools that search paper abstracts and full text across biomedical (PubMed, bioRxiv, medRxiv) and arXiv literature; `pdf` searches PDF results; `developer` searches an index built for coding agents over public repositories, GitHub issues, merged pull requests, repository READMEs, and code documentation. `developer` returns hits in `data.web` with `category: "developer"`; the other categories also filter `data.web`.'
|
|
1668
1972
|
),
|
|
1669
|
-
enterprise:
|
|
1973
|
+
enterprise: z8.array(z8.enum(["default", "anon", "zdr"])).optional()
|
|
1670
1974
|
};
|
|
1671
1975
|
function searchDomainsAreExclusive(args2) {
|
|
1672
1976
|
return !(args2.includeDomains?.length && args2.excludeDomains?.length);
|
|
@@ -1674,15 +1978,15 @@ function searchDomainsAreExclusive(args2) {
|
|
|
1674
1978
|
var SEARCH_DOMAINS_CONFLICT_MESSAGE = "includeDomains and excludeDomains cannot both be specified";
|
|
1675
1979
|
var EXCHANGE_KEY_REQUIRED_MESSAGE = "Alexandria requires an API key on a team with Alexandria access";
|
|
1676
1980
|
var EXCHANGE_MAX_CALLS = 10;
|
|
1677
|
-
var exchangeCallSchema =
|
|
1678
|
-
provider:
|
|
1679
|
-
capability:
|
|
1981
|
+
var exchangeCallSchema = z8.object({
|
|
1982
|
+
provider: z8.string().min(1).describe('Provider slug, e.g. "fred".'),
|
|
1983
|
+
capability: z8.string().min(1).describe(
|
|
1680
1984
|
'Capability address as returned by search or discover, e.g. "series/observations".'
|
|
1681
1985
|
),
|
|
1682
|
-
version:
|
|
1683
|
-
options:
|
|
1986
|
+
version: z8.string().trim().min(1).max(128).optional().describe("Optional published workflow version. Omit to use the latest version."),
|
|
1987
|
+
options: z8.record(z8.string(), z8.any()).optional().describe("Capability options as declared by its contract.")
|
|
1684
1988
|
});
|
|
1685
|
-
var exchangeCallsSchema =
|
|
1989
|
+
var exchangeCallsSchema = z8.array(exchangeCallSchema).min(1).max(EXCHANGE_MAX_CALLS);
|
|
1686
1990
|
function assertExchangeCredential(session) {
|
|
1687
1991
|
if (hasCredential(session)) return;
|
|
1688
1992
|
const payload = {
|
|
@@ -1748,26 +2052,26 @@ Do not retry until acceptance is confirmed.`,
|
|
|
1748
2052
|
}
|
|
1749
2053
|
);
|
|
1750
2054
|
}
|
|
1751
|
-
function throwIfTermsRequired(
|
|
1752
|
-
const source =
|
|
2055
|
+
function throwIfTermsRequired(error2, context) {
|
|
2056
|
+
const source = error2;
|
|
1753
2057
|
const action = termsRequiredAction(source?.response?.data ?? source?.details);
|
|
1754
2058
|
if (action) throw termsRequiredError(action, context);
|
|
1755
2059
|
}
|
|
1756
2060
|
async function relayTermsRequired(run, context) {
|
|
1757
2061
|
try {
|
|
1758
2062
|
return await run();
|
|
1759
|
-
} catch (
|
|
1760
|
-
throwIfTermsRequired(
|
|
1761
|
-
throw
|
|
2063
|
+
} catch (error2) {
|
|
2064
|
+
throwIfTermsRequired(error2, context);
|
|
2065
|
+
throw error2;
|
|
1762
2066
|
}
|
|
1763
2067
|
}
|
|
1764
2068
|
async function relayExchangeError(run, context) {
|
|
1765
2069
|
try {
|
|
1766
2070
|
return await run();
|
|
1767
|
-
} catch (
|
|
1768
|
-
throwIfTermsRequired(
|
|
1769
|
-
const response =
|
|
1770
|
-
if (!response || response.status === 401) throw
|
|
2071
|
+
} catch (error2) {
|
|
2072
|
+
throwIfTermsRequired(error2, context);
|
|
2073
|
+
const response = error2?.response;
|
|
2074
|
+
if (!response || response.status === 401) throw error2;
|
|
1771
2075
|
const data = response.data;
|
|
1772
2076
|
const message = typeof data?.error === "string" ? data.error : `Alexandria request failed (HTTP ${response.status})`;
|
|
1773
2077
|
const disabledProvider = response.status === 403 ? context.providers?.find((provider) => message === `Access to ${provider} is disabled for this organization.`) : void 0;
|
|
@@ -1798,14 +2102,14 @@ async function relayExchangeError(run, context) {
|
|
|
1798
2102
|
async function postSearchWithFallback(client, body, implicitTools) {
|
|
1799
2103
|
try {
|
|
1800
2104
|
return await client.http.post("/v2/search", body);
|
|
1801
|
-
} catch (
|
|
1802
|
-
const response =
|
|
2105
|
+
} catch (error2) {
|
|
2106
|
+
const response = error2?.response;
|
|
1803
2107
|
const unavailable = [
|
|
1804
2108
|
"Provider discovery requires access and does not support zero data retention.",
|
|
1805
2109
|
"This endpoint is not enabled for this team.",
|
|
1806
2110
|
"Exchange is not enabled for this team."
|
|
1807
2111
|
].includes(response?.data?.error);
|
|
1808
|
-
if (!implicitTools || response?.status !== 403 || !unavailable) throw
|
|
2112
|
+
if (!implicitTools || response?.status !== 403 || !unavailable) throw error2;
|
|
1809
2113
|
return client.http.post("/v2/search", {
|
|
1810
2114
|
...body,
|
|
1811
2115
|
sources: ["web"],
|
|
@@ -1844,10 +2148,9 @@ var ConsoleLogger = class {
|
|
|
1844
2148
|
var openAiAppsChallengeToken = normalizeHeader(
|
|
1845
2149
|
process.env.OPENAI_APPS_CHALLENGE_TOKEN
|
|
1846
2150
|
);
|
|
1847
|
-
var FULL_PROFILE_INSTRUCTIONS = `
|
|
1848
|
-
${ALEXANDRIA_FEEDBACK_GUIDANCE}`;
|
|
2151
|
+
var FULL_PROFILE_INSTRUCTIONS = `Firecrawl provides web search, page retrieval, site URL discovery, multi-page collection, structured page data, monitoring, and multi-source research that returns structured data. Match the requested operation to the tool boundary: firecrawl_scrape retrieves one supplied page and can return JSON matching a supplied schema, firecrawl_map enumerates URLs under a site without retrieving their content, and firecrawl_agent runs multi-source research and returns structured data when the URLs are not known or the answer spans several sites (an entity plus its fields, a list, a dataset); its result is read with firecrawl_agent_status. Authenticated firecrawl_search returns web results together with matching Alexandria providers in data.tools. ${ALEXANDRIA_CATALOGUE_SENTENCE} A matching provider can return the same fields across several entities, provenance, exact figures or timestamps, or a large set of records through firecrawl_scrape with its published contract. If web results already answer the question, use them. For the same fields across multiple pages, firecrawl_find_tools offers free provider discovery. Use firecrawl_find_tools to read a contract that was not returned in full or to browse the catalogue by category. ${ALEXANDRIA_SOURCES_OPT_OUT} If no provider fits, continue with web search or firecrawl_agent. For biomedical, life-science, clinical, or arXiv literature, the firecrawl_research_* tools search a paper index of abstracts and full text; firecrawl_search with categories: ["research"] is a website filter over ordinary web results and reaches different sources. For a programming question (code behaviour, a library or framework, an API contract, an error message, or a known bug), firecrawl_developer_search (or firecrawl_search with categories: ["developer"]) searches an index of public repositories, GitHub issues, merged pull requests, READMEs, and code documentation. Alexandria requestId identifies one logical execution; repeated attempts of the identical payload require the same ID, including pending or uncertain executions. firecrawl_search with sources: [{type: "alexandria"}] returns compact tool summaries in data.tools; toolDetail: "full" includes contracts, firecrawl_find_tools starts with categories, lists providers, then compact tools, and expands the selected full contract, and firecrawl_scrape with alexandria: [{provider, capability, options}] executes up to ten capabilities and returns their results. Alexandria access needs an API key on a team with it enabled. A terms-gated Alexandria provider fails with code THIRD_PARTY_DATA_TERMS_REQUIRED and a requiresAction.url. Through firecrawl_scrape, terms/show displays the agreement and terms/accept records acceptance; acceptance requires explicit user authorization for the reviewed version and digest and confirmed:true. A data request does not authorize acceptance. The dashboard URL provides an alternative for an organization admin. A retained result provides nextTool access without repeating a successful provider call. Provide only the required inputs and account for stated network or external side effects.`;
|
|
1849
2152
|
var KEYLESS_PROFILE_INSTRUCTIONS = `Hosted keyless sessions expose firecrawl_search, firecrawl_scrape, and firecrawl_parse with usage limits. firecrawl_search searches the web. For programming questions, firecrawl_search with categories: ["developer"] searches indexed public repositories, GitHub issues, merged pull requests, repository READMEs, and code documentation. For biomedical, life-science, clinical, or arXiv literature, firecrawl_search with categories: ["research"] filters ordinary web results to research-affiliated websites. firecrawl_scrape retrieves one supplied page and can return JSON matching a supplied schema. firecrawl_parse processes supported local files through its two-phase upload flow. An Authorization bearer API key can provide higher usage limits and expose additional tools, subject to plan, deployment, and team policy, including firecrawl_map for site URL discovery, firecrawl_agent and firecrawl_agent_status for multi-source research that returns structured data when the URLs are not known, firecrawl_research_* for paper-index and repository research, and firecrawl_find_tools as the progressive Alexandria catalogue lookup alongside the Alexandria options of firecrawl_search and firecrawl_scrape for catalogued data providers.`;
|
|
1850
|
-
var SEARCH_PROFILE_INSTRUCTIONS = ALEXANDRIA_SEARCH_INSTRUCTIONS + ` Firecrawl provides web, developer, and research search, and executes catalogued Alexandria data providers. Use firecrawl_search to find relevant results across the web and specialized indexes; authenticated searches also return matching Alexandria providers in data.tools.
|
|
2153
|
+
var SEARCH_PROFILE_INSTRUCTIONS = ALEXANDRIA_SEARCH_INSTRUCTIONS + ` Firecrawl provides web, developer, and research search, and executes catalogued Alexandria data providers. Use firecrawl_search to find relevant results across the web and specialized indexes; authenticated searches also return matching Alexandria providers in data.tools. firecrawl_find_tools provides catalogue browsing and provider contracts; firecrawl_scrape with an alexandria body executes a selected capability; firecrawl_scrape with a url retrieves one supplied page. For a programming question, firecrawl_developer_search searches indexed public repositories, GitHub issues, merged pull requests, READMEs, and code documentation and returns the matched passages, and skills: "only" narrows it to agent-skill files; firecrawl_search with categories: ["developer"] reaches the same index beside ordinary web results, returning the hits in the web group rather than as passages and offering no skills filter. For a biomedical, life-science, clinical, or arXiv literature question, the firecrawl_research_* tools search the paper index, while categories: ["research"] on firecrawl_search filters ordinary web results to research-affiliated websites. Use the firecrawl_research_* tools to search academic and research literature, expand from anchor papers via the citation graph, and read full-text passages from a specific paper. Search and discovery tools are read-only and return ranked results. Billing: web, developer and research search are billed per request; Alexandria discovery (firecrawl_search with sources ["alexandria"] alone, and firecrawl_find_tools) is free; firecrawl_scrape is billed, as a page retrieval in url mode or at each executed capability's listed price in alexandria mode.`;
|
|
1851
2154
|
var SEARCH_PROFILE_TOOLS = /* @__PURE__ */ new Set([
|
|
1852
2155
|
"firecrawl_search",
|
|
1853
2156
|
"firecrawl_developer_search",
|
|
@@ -1991,17 +2294,17 @@ function invalidOAuthRecoveryPayload() {
|
|
|
1991
2294
|
message: INVALID_OAUTH_MESSAGE
|
|
1992
2295
|
});
|
|
1993
2296
|
}
|
|
1994
|
-
function isCoreCredentialRejection(
|
|
1995
|
-
if (!(
|
|
1996
|
-
const candidate =
|
|
2297
|
+
function isCoreCredentialRejection(error2) {
|
|
2298
|
+
if (!(error2 instanceof Error)) return false;
|
|
2299
|
+
const candidate = error2;
|
|
1997
2300
|
return candidate.status === 401 || candidate.response?.status === 401;
|
|
1998
2301
|
}
|
|
1999
2302
|
async function runWithCredentialRecovery(run, requestId, session) {
|
|
2000
2303
|
try {
|
|
2001
2304
|
return await run();
|
|
2002
|
-
} catch (
|
|
2003
|
-
if (session?.authType !== "api-key" || !isCoreCredentialRejection(
|
|
2004
|
-
throw
|
|
2305
|
+
} catch (error2) {
|
|
2306
|
+
if (session?.authType !== "api-key" || !isCoreCredentialRejection(error2)) {
|
|
2307
|
+
throw error2;
|
|
2005
2308
|
}
|
|
2006
2309
|
const payload = recoveryPayload("CREDENTIAL_INVALID", requestId);
|
|
2007
2310
|
throw new UserError2(String(payload.message), payload);
|
|
@@ -2054,7 +2357,7 @@ function deprecatedExtractPayload() {
|
|
|
2054
2357
|
docs_url: "https://docs.firecrawl.dev/developer-guides/usage-guides/choosing-the-data-extractor"
|
|
2055
2358
|
};
|
|
2056
2359
|
}
|
|
2057
|
-
function emitActionLog(toolName, status, session,
|
|
2360
|
+
function emitActionLog(toolName, status, session, error2, requestId = randomUUID(), code) {
|
|
2058
2361
|
if (process.env.CLOUD_SERVICE !== "true") return;
|
|
2059
2362
|
const payload = {
|
|
2060
2363
|
team_id: session?.teamId,
|
|
@@ -2066,7 +2369,7 @@ function emitActionLog(toolName, status, session, error, requestId = randomUUID(
|
|
|
2066
2369
|
status,
|
|
2067
2370
|
request_id: requestId,
|
|
2068
2371
|
resource: primaryProfile.resourceUrl,
|
|
2069
|
-
...
|
|
2372
|
+
...error2 ? { error_class: error2 instanceof Error ? error2.name : typeof error2 } : {},
|
|
2070
2373
|
...code ? { code } : {}
|
|
2071
2374
|
};
|
|
2072
2375
|
console.error("[MCP_ACTION]", JSON.stringify(payload));
|
|
@@ -2166,9 +2469,9 @@ function guardHostedTool(tool, { logActions }) {
|
|
|
2166
2469
|
const result = await runTool();
|
|
2167
2470
|
emitActionLog(tool.name, "success", invocationSession, void 0, requestId);
|
|
2168
2471
|
return result;
|
|
2169
|
-
} catch (
|
|
2170
|
-
emitActionLog(tool.name, "error", invocationSession,
|
|
2171
|
-
throw
|
|
2472
|
+
} catch (error2) {
|
|
2473
|
+
emitActionLog(tool.name, "error", invocationSession, error2, requestId);
|
|
2474
|
+
throw error2;
|
|
2172
2475
|
}
|
|
2173
2476
|
}
|
|
2174
2477
|
};
|
|
@@ -2260,12 +2563,9 @@ function getClient(session) {
|
|
|
2260
2563
|
});
|
|
2261
2564
|
return client;
|
|
2262
2565
|
}
|
|
2263
|
-
function
|
|
2566
|
+
function asText(data) {
|
|
2264
2567
|
return JSON.stringify(data, null, 2);
|
|
2265
2568
|
}
|
|
2266
|
-
function compactText(data) {
|
|
2267
|
-
return JSON.stringify(data);
|
|
2268
|
-
}
|
|
2269
2569
|
var safeActionTypes = ["wait", "screenshot", "scroll", "scrape"];
|
|
2270
2570
|
var otherActions = [
|
|
2271
2571
|
"click",
|
|
@@ -2331,10 +2631,10 @@ function transformScrapeParams(args2) {
|
|
|
2331
2631
|
delete out.pdfOptions;
|
|
2332
2632
|
return out;
|
|
2333
2633
|
}
|
|
2334
|
-
var scrapeParamsSchema =
|
|
2335
|
-
url:
|
|
2336
|
-
formats:
|
|
2337
|
-
|
|
2634
|
+
var scrapeParamsSchema = z8.object({
|
|
2635
|
+
url: z8.string().url(),
|
|
2636
|
+
formats: z8.array(
|
|
2637
|
+
z8.enum([
|
|
2338
2638
|
"markdown",
|
|
2339
2639
|
"html",
|
|
2340
2640
|
"rawHtml",
|
|
@@ -2348,87 +2648,93 @@ var scrapeParamsSchema = z7.object({
|
|
|
2348
2648
|
"audio"
|
|
2349
2649
|
])
|
|
2350
2650
|
).optional(),
|
|
2351
|
-
jsonOptions:
|
|
2352
|
-
prompt:
|
|
2353
|
-
schema:
|
|
2651
|
+
jsonOptions: z8.object({
|
|
2652
|
+
prompt: z8.string().optional(),
|
|
2653
|
+
schema: z8.record(z8.string(), z8.any()).optional()
|
|
2354
2654
|
}).optional(),
|
|
2355
|
-
queryOptions:
|
|
2356
|
-
prompt:
|
|
2357
|
-
mode:
|
|
2655
|
+
queryOptions: z8.object({
|
|
2656
|
+
prompt: z8.string().max(1e4),
|
|
2657
|
+
mode: z8.enum(["directQuote", "freeform"]).default("freeform")
|
|
2358
2658
|
}).optional(),
|
|
2359
|
-
screenshotOptions:
|
|
2360
|
-
fullPage:
|
|
2361
|
-
quality:
|
|
2362
|
-
viewport:
|
|
2659
|
+
screenshotOptions: z8.object({
|
|
2660
|
+
fullPage: z8.boolean().optional(),
|
|
2661
|
+
quality: z8.number().optional(),
|
|
2662
|
+
viewport: z8.object({ width: z8.number(), height: z8.number() }).optional()
|
|
2363
2663
|
}).optional(),
|
|
2364
|
-
parsers:
|
|
2365
|
-
pdfOptions:
|
|
2366
|
-
maxPages:
|
|
2664
|
+
parsers: z8.array(z8.enum(["pdf"])).optional(),
|
|
2665
|
+
pdfOptions: z8.object({
|
|
2666
|
+
maxPages: z8.number().int().min(1).max(1e4).optional()
|
|
2367
2667
|
}).optional(),
|
|
2368
|
-
onlyMainContent:
|
|
2369
|
-
redactPII:
|
|
2370
|
-
includeTags:
|
|
2371
|
-
excludeTags:
|
|
2372
|
-
waitFor:
|
|
2668
|
+
onlyMainContent: z8.boolean().optional(),
|
|
2669
|
+
redactPII: z8.boolean().optional(),
|
|
2670
|
+
includeTags: z8.array(z8.string()).optional(),
|
|
2671
|
+
excludeTags: z8.array(z8.string()).optional(),
|
|
2672
|
+
waitFor: z8.number().optional(),
|
|
2373
2673
|
...SAFE_MODE ? {} : {
|
|
2374
|
-
actions:
|
|
2375
|
-
|
|
2376
|
-
type:
|
|
2377
|
-
selector:
|
|
2378
|
-
milliseconds:
|
|
2379
|
-
text:
|
|
2380
|
-
key:
|
|
2381
|
-
direction:
|
|
2382
|
-
script:
|
|
2383
|
-
fullPage:
|
|
2674
|
+
actions: z8.array(
|
|
2675
|
+
z8.object({
|
|
2676
|
+
type: z8.enum(allowedActionTypes),
|
|
2677
|
+
selector: z8.string().optional(),
|
|
2678
|
+
milliseconds: z8.number().optional(),
|
|
2679
|
+
text: z8.string().optional(),
|
|
2680
|
+
key: z8.string().optional(),
|
|
2681
|
+
direction: z8.enum(["up", "down"]).optional(),
|
|
2682
|
+
script: z8.string().optional(),
|
|
2683
|
+
fullPage: z8.boolean().optional()
|
|
2384
2684
|
})
|
|
2385
2685
|
).optional()
|
|
2386
2686
|
},
|
|
2387
|
-
mobile:
|
|
2388
|
-
skipTlsVerification:
|
|
2389
|
-
removeBase64Images:
|
|
2390
|
-
location:
|
|
2391
|
-
country:
|
|
2392
|
-
languages:
|
|
2687
|
+
mobile: z8.boolean().optional(),
|
|
2688
|
+
skipTlsVerification: z8.boolean().optional(),
|
|
2689
|
+
removeBase64Images: z8.boolean().optional(),
|
|
2690
|
+
location: z8.object({
|
|
2691
|
+
country: z8.string().optional(),
|
|
2692
|
+
languages: z8.array(z8.string()).optional()
|
|
2393
2693
|
}).optional(),
|
|
2394
|
-
storeInCache:
|
|
2395
|
-
zeroDataRetention:
|
|
2396
|
-
maxAge:
|
|
2397
|
-
lockdown:
|
|
2398
|
-
proxy:
|
|
2399
|
-
profile:
|
|
2400
|
-
name:
|
|
2401
|
-
saveChanges:
|
|
2694
|
+
storeInCache: z8.boolean().optional(),
|
|
2695
|
+
zeroDataRetention: z8.boolean().optional(),
|
|
2696
|
+
maxAge: z8.number().optional(),
|
|
2697
|
+
lockdown: z8.boolean().optional(),
|
|
2698
|
+
proxy: z8.enum(["basic", "stealth", "enhanced", "auto"]).optional(),
|
|
2699
|
+
profile: z8.object({
|
|
2700
|
+
name: z8.string(),
|
|
2701
|
+
saveChanges: z8.boolean().optional()
|
|
2402
2702
|
}).optional()
|
|
2403
2703
|
});
|
|
2704
|
+
var ALEXANDRIA_IGNORED_SCRAPE_OPTIONS = /* @__PURE__ */ new Set(["toolDetail", "domainTools"]);
|
|
2404
2705
|
var scrapeToolParamsSchema = scrapeParamsSchema.extend({
|
|
2405
|
-
url:
|
|
2406
|
-
timeout:
|
|
2407
|
-
requestId:
|
|
2408
|
-
"Alexandria execution
|
|
2706
|
+
url: z8.string().url().optional(),
|
|
2707
|
+
timeout: z8.number().int().positive().optional().describe("Execution timeout in milliseconds."),
|
|
2708
|
+
requestId: z8.string().regex(/^[A-Za-z0-9._:-]{1,128}$/).optional().describe(
|
|
2709
|
+
"Identifies one logical Alexandria execution; generated when omitted and returned with the result. Repeated attempts of the identical payload require the same ID. A new ID cannot reconcile a pending or uncertain execution. A caller-supplied ID supports recovery if no response is received. Errors relay a code and may include chargeId. request_in_flight (409) means the execution is pending; request_unresolved (503) requires reconciliation under the same ID; duplicate_request (409) means the ID belongs to a different payload; unknown_provider (404), insufficient_credits (402) and billing_unavailable (503) mean nothing executed."
|
|
2409
2710
|
),
|
|
2410
|
-
alexandria:
|
|
2411
|
-
"
|
|
2711
|
+
alexandria: z8.union([exchangeCallSchema, exchangeCallsSchema]).optional().describe(
|
|
2712
|
+
"Catalogued Alexandria capability invocation, mutually exclusive with url. One {provider, capability, options} object or an array of 1-10, with contracts available through firecrawl_search or firecrawl_find_tools. Each call may include version to pin a published workflow; omitting it uses latest. Only requestId and timeout are supported alongside alexandria. " + ALEXANDRIA_CONTRACT_GUIDANCE + " Returns per-capability results in data.alexandria with data, records, or an error with a code; individual capabilities can fail even when the outer response succeeds. nextTool identifies access to retained results without repeating a successful provider call. Requires an API key on a team with Alexandria enabled. THIRD_PARTY_DATA_TERMS_REQUIRED (403) means execution is blocked by provider terms, with requiresAction.url for review by an organization admin. Through this tool, terms/show displays the agreement and terms/accept records acceptance; acceptance requires explicit user authorization for the reviewed version and digest and confirmed:true. A data request does not authorize acceptance. Provider execution remains blocked until acceptance is confirmed."
|
|
2412
2713
|
),
|
|
2413
|
-
toolDetail:
|
|
2414
|
-
domainTools:
|
|
2415
|
-
"URL mode only: include domain-matched Alexandria tools for the page in tools on the returned document."
|
|
2714
|
+
toolDetail: z8.enum(["compact", "summary", "full"]).optional().describe("URL mode only: domain discovery detail, summary by default; compact returns provider/capability/description, full includes contracts. Ignored with alexandria."),
|
|
2715
|
+
domainTools: z8.boolean().optional().describe(
|
|
2716
|
+
"URL mode only: include domain-matched Alexandria tools for the page in tools on the returned document. Ignored with alexandria."
|
|
2416
2717
|
)
|
|
2417
2718
|
}).refine(
|
|
2418
2719
|
(args2) => Boolean(args2.url) !== Boolean(args2.alexandria),
|
|
2419
2720
|
"Provide exactly one of url or alexandria"
|
|
2420
2721
|
).refine(
|
|
2421
2722
|
(args2) => !args2.alexandria || Object.entries(args2).every(
|
|
2422
|
-
([key, value]) => key === "alexandria" || key === "requestId" || key === "timeout" ||
|
|
2723
|
+
([key, value]) => key === "alexandria" || key === "requestId" || key === "timeout" || // URL-mode discovery options that mean nothing when executing a
|
|
2724
|
+
// provider. Agents carry toolDetail over from firecrawl_search (where
|
|
2725
|
+
// it selects contract detail), so accept and ignore them rather than
|
|
2726
|
+
// failing the call: Codex sent toolDetail on 18 of 315 Alexandria
|
|
2727
|
+
// executions across the AX EXP-058 runs, each one a wasted round trip.
|
|
2728
|
+
ALEXANDRIA_IGNORED_SCRAPE_OPTIONS.has(key) || value === void 0
|
|
2423
2729
|
),
|
|
2424
2730
|
"alexandria cannot be combined with url or other scrape options"
|
|
2425
2731
|
).refine(
|
|
2426
2732
|
(args2) => !args2.requestId || !!args2.alexandria,
|
|
2427
2733
|
"requestId applies to Alexandria execution only"
|
|
2428
2734
|
);
|
|
2429
|
-
var parseOptionParamsSchema =
|
|
2430
|
-
formats:
|
|
2431
|
-
|
|
2735
|
+
var parseOptionParamsSchema = z8.object({
|
|
2736
|
+
formats: z8.array(
|
|
2737
|
+
z8.enum([
|
|
2432
2738
|
"markdown",
|
|
2433
2739
|
"html",
|
|
2434
2740
|
"rawHtml",
|
|
@@ -2438,48 +2744,48 @@ var parseOptionParamsSchema = z7.object({
|
|
|
2438
2744
|
"query"
|
|
2439
2745
|
])
|
|
2440
2746
|
).optional(),
|
|
2441
|
-
jsonOptions:
|
|
2442
|
-
prompt:
|
|
2443
|
-
schema:
|
|
2747
|
+
jsonOptions: z8.object({
|
|
2748
|
+
prompt: z8.string().optional(),
|
|
2749
|
+
schema: z8.record(z8.string(), z8.any()).optional()
|
|
2444
2750
|
}).optional(),
|
|
2445
|
-
queryOptions:
|
|
2446
|
-
prompt:
|
|
2447
|
-
mode:
|
|
2751
|
+
queryOptions: z8.object({
|
|
2752
|
+
prompt: z8.string().max(1e4),
|
|
2753
|
+
mode: z8.enum(["directQuote", "freeform"]).default("freeform")
|
|
2448
2754
|
}).optional(),
|
|
2449
|
-
parsers:
|
|
2450
|
-
pdfOptions:
|
|
2451
|
-
maxPages:
|
|
2755
|
+
parsers: z8.array(z8.enum(["pdf"])).optional(),
|
|
2756
|
+
pdfOptions: z8.object({
|
|
2757
|
+
maxPages: z8.number().int().min(1).max(1e4).optional()
|
|
2452
2758
|
}).optional(),
|
|
2453
|
-
onlyMainContent:
|
|
2454
|
-
redactPII:
|
|
2455
|
-
includeTags:
|
|
2456
|
-
excludeTags:
|
|
2457
|
-
removeBase64Images:
|
|
2458
|
-
skipTlsVerification:
|
|
2459
|
-
storeInCache:
|
|
2460
|
-
zeroDataRetention:
|
|
2461
|
-
maxAge:
|
|
2462
|
-
proxy:
|
|
2759
|
+
onlyMainContent: z8.boolean().optional(),
|
|
2760
|
+
redactPII: z8.boolean().optional(),
|
|
2761
|
+
includeTags: z8.array(z8.string()).optional(),
|
|
2762
|
+
excludeTags: z8.array(z8.string()).optional(),
|
|
2763
|
+
removeBase64Images: z8.boolean().optional(),
|
|
2764
|
+
skipTlsVerification: z8.boolean().optional(),
|
|
2765
|
+
storeInCache: z8.boolean().optional(),
|
|
2766
|
+
zeroDataRetention: z8.boolean().optional(),
|
|
2767
|
+
maxAge: z8.number().optional().describe("Ignored: parse never reuses or stores indexed content."),
|
|
2768
|
+
proxy: z8.enum(["basic", "auto"]).optional()
|
|
2463
2769
|
});
|
|
2464
2770
|
var localParseParamsSchema = parseOptionParamsSchema.extend({
|
|
2465
|
-
filePath:
|
|
2771
|
+
filePath: z8.string().min(1).describe(
|
|
2466
2772
|
"Absolute or relative path to a local file to parse. Supported: .html, .htm, .pdf, .docx, .doc, .odt, .rtf, .xlsx, .xls"
|
|
2467
2773
|
),
|
|
2468
|
-
contentType:
|
|
2774
|
+
contentType: z8.string().optional().describe(
|
|
2469
2775
|
"Optional MIME type override. If omitted, the server infers the file kind from the extension."
|
|
2470
2776
|
)
|
|
2471
2777
|
});
|
|
2472
2778
|
var hostedParseParamsSchema = parseOptionParamsSchema.extend({
|
|
2473
|
-
filePath:
|
|
2779
|
+
filePath: z8.string().min(1).optional().describe(
|
|
2474
2780
|
"Phase 1 only: path to the local file on the caller/harness machine. Hosted MCP will not read or stat this path; it is used only to produce upload instructions."
|
|
2475
2781
|
),
|
|
2476
|
-
uploadRef:
|
|
2782
|
+
uploadRef: z8.string().min(1).optional().describe(
|
|
2477
2783
|
"Phase 2 only: short-lived upload reference returned by phase 1 after the local PUT upload completes."
|
|
2478
2784
|
),
|
|
2479
|
-
contentType:
|
|
2785
|
+
contentType: z8.string().optional().describe(
|
|
2480
2786
|
"Phase 1 MIME type override. If omitted, the server infers it from the file extension without reading the file."
|
|
2481
2787
|
),
|
|
2482
|
-
declaredSizeBytes:
|
|
2788
|
+
declaredSizeBytes: z8.number().int().positive().optional().describe(
|
|
2483
2789
|
"Optional phase 1 size declaration. Hosted MCP does not stat the file; provide this only if the caller already knows it."
|
|
2484
2790
|
)
|
|
2485
2791
|
}).superRefine((value, ctx) => {
|
|
@@ -2487,7 +2793,7 @@ var hostedParseParamsSchema = parseOptionParamsSchema.extend({
|
|
|
2487
2793
|
const hasUploadRef = typeof value.uploadRef === "string" && value.uploadRef.length > 0;
|
|
2488
2794
|
if (hasFilePath === hasUploadRef) {
|
|
2489
2795
|
ctx.addIssue({
|
|
2490
|
-
code:
|
|
2796
|
+
code: z8.ZodIssueCode.custom,
|
|
2491
2797
|
message: "Hosted firecrawl_parse requires exactly one of filePath (phase 1) or uploadRef (phase 2).",
|
|
2492
2798
|
path: hasFilePath && hasUploadRef ? ["uploadRef"] : ["filePath"]
|
|
2493
2799
|
});
|
|
@@ -2608,7 +2914,7 @@ async function executeHostedParse(args2, session, log, origin) {
|
|
|
2608
2914
|
);
|
|
2609
2915
|
}
|
|
2610
2916
|
if (!hasCredential(session) && !isKeylessMode(session)) {
|
|
2611
|
-
return
|
|
2917
|
+
return asText({
|
|
2612
2918
|
success: false,
|
|
2613
2919
|
mode: "hosted-upload-ref-auth-required",
|
|
2614
2920
|
message: "Hosted firecrawl_parse requires an authenticated Firecrawl session or keyless eligibility before a local file upload URL can be minted. Connect a Firecrawl account, provide an API key, or use keyless hosted MCP while eligible, then call firecrawl_parse again."
|
|
@@ -2646,7 +2952,7 @@ async function executeHostedParse(args2, session, log, origin) {
|
|
|
2646
2952
|
}
|
|
2647
2953
|
const uploadHeaders = upload.headers && Object.keys(upload.headers).length > 0 ? upload.headers : (upload.method ?? "PUT").toUpperCase() === "POST" ? {} : { "Content-Type": contentType };
|
|
2648
2954
|
const uploadForCommand = { ...upload, headers: uploadHeaders };
|
|
2649
|
-
return
|
|
2955
|
+
return asText({
|
|
2650
2956
|
success: true,
|
|
2651
2957
|
mode: "hosted-upload-ref-awaiting-upload",
|
|
2652
2958
|
message: "Hosted MCP cannot read local files. Run the local upload command, then call firecrawl_parse again with uploadRef. No Firecrawl API key is included in this command.",
|
|
@@ -2682,7 +2988,7 @@ async function executeHostedParse(args2, session, log, origin) {
|
|
|
2682
2988
|
session,
|
|
2683
2989
|
origin
|
|
2684
2990
|
);
|
|
2685
|
-
return
|
|
2991
|
+
return asText(parseJson);
|
|
2686
2992
|
}
|
|
2687
2993
|
var scrapeTool = {
|
|
2688
2994
|
name: "firecrawl_scrape",
|
|
@@ -2696,24 +3002,15 @@ var scrapeTool = {
|
|
|
2696
3002
|
// Does not modify, delete, or write to external websites.
|
|
2697
3003
|
},
|
|
2698
3004
|
description: `
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
This tool operates on a known page. Use \`firecrawl_search\` when additional web sources are needed. For a set of pages use \`firecrawl_crawl\`, and to discover page URLs use \`firecrawl_map\`. Options include JavaScript render delay, cache age, main-content filtering, PII redaction, and lockdown cache-only retrieval. Browser actions may change the live page when interactive actions are enabled.
|
|
2702
|
-
|
|
2703
|
-
Firecrawl may reuse recently indexed content instead of refetching the page, and the reuse window varies by domain. Set \`maxAge: 0\` to force a live fetch, or a smaller \`maxAge\` to bound how stale reused content may be. A successful response does not by itself confirm that the state it describes is still current.
|
|
2704
|
-
|
|
2705
|
-
Returns the selected content formats and page metadata. Authenticated responses can include a \`metadata.scrapeId\` for optional scrape feedback.
|
|
2706
|
-
|
|
2707
|
-
On an authenticated session with Alexandria access, if you are about to scrape the same fields from several pages, first run \`firecrawl_search\` with \`sources\` unset (or \`firecrawl_find_tools\`): a matching Alexandria provider returns those fields as typed records in one call. Keyless sessions have no provider matches; scrape directly.
|
|
2708
|
-
Alexandria mode: pass \`alexandria\` (one \`{provider, capability, options}\` object or an array of 1-10) instead of \`url\` to execute catalogued Alexandria capabilities found through \`firecrawl_search\` sources \`alexandria\` or \`firecrawl_find_tools\`. The optional requestId identifies one logical execution: reuse the returned ID for retries of the identical payload, never a new ID to bypass pending or uncertain execution. Each call may include version to pin a published workflow; omitting it uses latest. Only timeout also applies at the top level in this mode. Returns per-capability results in \`data.alexandria\`, including \`data\`, \`records\`, or an \`error\` with a code. Check each item for errors even when the outer response is successful. Alexandria needs an API key on a team with Alexandria enabled.
|
|
2709
|
-
Alexandria mode: pass \`alexandria\` (one \`{provider, capability, options}\` object or an array of 1-10) instead of \`url\` to execute catalogued Alexandria capabilities found through \`firecrawl_search\` sources \`alexandria\` or \`firecrawl_find_tools\`. The optional requestId identifies one logical execution: reuse the returned ID for retries of the identical payload, never a new ID to bypass pending or uncertain execution. Each call may include version to pin a published workflow; omitting it uses latest. Only timeout also applies at the top level in this mode. Returns per-capability results in \`data.alexandria\`, including \`data\`, \`records\`, or an \`error\` with a code. Check each item for errors even when the outer response is successful. Alexandria needs an API key on a team with Alexandria enabled. Alexandria results include a \`feedbackTool\` pointer: after the task, report how the catalogue served the website through \`firecrawl_feedback\` with endpoint \`alexandria\` (free, no job ID).
|
|
3005
|
+
Scrape one URL and return its content: markdown by default, or HTML, links, screenshots, branding data, a targeted answer, or JSON matching a supplied schema. Use it when the request identifies a page and needs its content or defined fields. Use \`firecrawl_search\` when additional web sources are needed; on an authenticated session, \`firecrawl_map\` lists a site's URLs and \`firecrawl_crawl\` collects a set of pages.
|
|
2710
3006
|
|
|
2711
|
-
|
|
3007
|
+
Firecrawl may serve recently indexed content; set \`maxAge: 0\` for a live fetch or a smaller \`maxAge\` to bound staleness. A successful response does not by itself confirm the page is still current. Browser actions can change the live page when interactive actions are enabled. Authenticated responses can include a \`metadata.scrapeId\` for optional scrape feedback.
|
|
2712
3008
|
|
|
2713
|
-
|
|
3009
|
+
On an authenticated session with Alexandria access, \`firecrawl_search\` with \`sources\` unset and \`firecrawl_find_tools\` can discover providers for the same fields across several pages; a matching provider returns typed records in one call. Keyless sessions have no provider matches.
|
|
2714
3010
|
|
|
2715
|
-
Alexandria
|
|
3011
|
+
Alexandria mode, on an authenticated session with Alexandria access: \`alexandria\` selects catalogued capability execution and is mutually exclusive with \`url\`; the \`alexandria\` and \`requestId\` parameters describe batching, retries, errors and provider terms.
|
|
2716
3012
|
`,
|
|
3013
|
+
outputSchema: scrapeOutputSchema,
|
|
2717
3014
|
parameters: scrapeToolParamsSchema,
|
|
2718
3015
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
2719
3016
|
const origin = requestOrigin(mcpClient, session);
|
|
@@ -2728,7 +3025,9 @@ Alexandria execution errors relay a \`code\` and \`chargeId\`: \`request_in_flig
|
|
|
2728
3025
|
log.info("Executing Alexandria capabilities", {
|
|
2729
3026
|
count: Array.isArray(alexandria) ? alexandria.length : 1
|
|
2730
3027
|
});
|
|
2731
|
-
return
|
|
3028
|
+
return structuredJsonText(
|
|
3029
|
+
await executeExchangeCalls(session, alexandria, suppliedRequestId, options.timeout, origin)
|
|
3030
|
+
);
|
|
2732
3031
|
}
|
|
2733
3032
|
const transformed = transformScrapeParams(
|
|
2734
3033
|
options
|
|
@@ -2749,7 +3048,7 @@ Alexandria execution errors relay a \`code\` and \`chargeId\`: \`request_in_flig
|
|
|
2749
3048
|
},
|
|
2750
3049
|
session
|
|
2751
3050
|
);
|
|
2752
|
-
return
|
|
3051
|
+
return structuredText(json?.data ?? json);
|
|
2753
3052
|
}
|
|
2754
3053
|
const client = getClient(session);
|
|
2755
3054
|
const res = await relayTermsRequired(
|
|
@@ -2759,10 +3058,13 @@ Alexandria execution errors relay a \`code\` and \`chargeId\`: \`request_in_flig
|
|
|
2759
3058
|
}),
|
|
2760
3059
|
{ tool: "firecrawl_scrape" }
|
|
2761
3060
|
);
|
|
2762
|
-
return
|
|
3061
|
+
return structuredText(res);
|
|
2763
3062
|
}
|
|
2764
3063
|
};
|
|
2765
|
-
server.addTool(
|
|
3064
|
+
server.addTool({
|
|
3065
|
+
...scrapeTool,
|
|
3066
|
+
_meta: { "anthropic/alwaysLoad": true }
|
|
3067
|
+
});
|
|
2766
3068
|
server.addTool({
|
|
2767
3069
|
name: "firecrawl_map",
|
|
2768
3070
|
annotations: {
|
|
@@ -2779,13 +3081,14 @@ Enumerate URLs indexed under one website through Firecrawl without fetching each
|
|
|
2779
3081
|
|
|
2780
3082
|
Returns matching URLs rather than page bodies. Retrieve one page with \`firecrawl_scrape\`; collect content across multiple pages with \`firecrawl_crawl\`. Authenticated responses can include an \`id\` for optional map feedback.
|
|
2781
3083
|
`,
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
3084
|
+
outputSchema: mapOutputSchema,
|
|
3085
|
+
parameters: z8.object({
|
|
3086
|
+
url: z8.string().url(),
|
|
3087
|
+
search: z8.string().optional(),
|
|
3088
|
+
sitemap: z8.enum(["include", "skip", "only"]).optional(),
|
|
3089
|
+
includeSubdomains: z8.boolean().optional(),
|
|
3090
|
+
limit: z8.number().optional(),
|
|
3091
|
+
ignoreQueryParameters: z8.boolean().optional()
|
|
2789
3092
|
}),
|
|
2790
3093
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
2791
3094
|
const { url, ...options } = args2;
|
|
@@ -2796,11 +3099,12 @@ Returns matching URLs rather than page bodies. Retrieve one page with \`firecraw
|
|
|
2796
3099
|
...cleaned,
|
|
2797
3100
|
origin: requestOrigin(mcpClient, session)
|
|
2798
3101
|
});
|
|
2799
|
-
return
|
|
3102
|
+
return structuredText(res);
|
|
2800
3103
|
}
|
|
2801
3104
|
});
|
|
2802
3105
|
server.addTool({
|
|
2803
3106
|
name: "firecrawl_search",
|
|
3107
|
+
_meta: { "anthropic/alwaysLoad": true },
|
|
2804
3108
|
annotations: {
|
|
2805
3109
|
title: "Firecrawl web search",
|
|
2806
3110
|
readOnlyHint: true,
|
|
@@ -2811,19 +3115,18 @@ server.addTool({
|
|
|
2811
3115
|
// Query-only; no destructive side effects on external entities.
|
|
2812
3116
|
},
|
|
2813
3117
|
description: `
|
|
2814
|
-
Search web, news, or image sources and return ranked results with query-relevant highlights.
|
|
3118
|
+
Search web, news, or image sources and return ranked results with query-relevant highlights. Each web result is a title, URL, and description; use \`firecrawl_scrape\` on a result URL when the excerpt is not enough.
|
|
2815
3119
|
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
\`categories: ["research"]\` restricts these web results to research-affiliated websites and returns page snippets. The \`firecrawl_research_*\` tools are a separate surface that searches paper abstracts and full text across biomedical (PubMed, bioRxiv, medRxiv) and arXiv literature.
|
|
3120
|
+
${ALEXANDRIA_SEARCH_LEAD}
|
|
2819
3121
|
|
|
2820
|
-
|
|
3122
|
+
On an authenticated session, tool matches describe available capabilities; \`firecrawl_find_tools\` returns their contracts and \`firecrawl_scrape\` with an \`alexandria\` body executes a selected capability. Keyless sessions get no Alexandria matches in data.tools.
|
|
2821
3123
|
|
|
2822
|
-
|
|
3124
|
+
For a programming question, add \`categories: ["developer"]\`; its hits return in \`data.web\` with \`category: "developer"\`. \`categories: ["research"]\` restricts web results to research-affiliated websites; the \`firecrawl_research_*\` tools are a separate surface over paper abstracts and full text (PubMed, bioRxiv, medRxiv, arXiv). Query operators, domain filters, \`categories\`, \`toolDetail\` and \`scrapeOptions\` are described on their parameters. Returns source-type result groups and usage metadata. Authenticated responses can include an \`id\` for optional search feedback.
|
|
2823
3125
|
`,
|
|
2824
|
-
|
|
3126
|
+
outputSchema: searchOutputSchema,
|
|
3127
|
+
parameters: z8.object({
|
|
2825
3128
|
...searchToolBaseFields,
|
|
2826
|
-
scrapeOptions: scrapeParamsSchema.omit({ url: true }).partial().optional()
|
|
3129
|
+
scrapeOptions: scrapeParamsSchema.omit({ url: true }).partial().optional().describe("Attach page content for web results in the same call. These fetches ignore maxAge, so use firecrawl_scrape when you need a live fetch. scrapeOptions fetches web pages, never Alexandria provider tools.")
|
|
2827
3130
|
}).refine(searchDomainsAreExclusive, SEARCH_DOMAINS_CONFLICT_MESSAGE).refine(
|
|
2828
3131
|
searchQueryIsValid,
|
|
2829
3132
|
"A query is required. Use Find Tools for catalogue lookup."
|
|
@@ -2858,7 +3161,7 @@ ${ALEXANDRIA_INSTRUCTIONS}
|
|
|
2858
3161
|
const json = await keylessPost("/v2/search", searchBody, session);
|
|
2859
3162
|
const keylessResponse = { ...json ?? {} };
|
|
2860
3163
|
delete keylessResponse.id;
|
|
2861
|
-
return
|
|
3164
|
+
return structuredCompact(keylessResponse);
|
|
2862
3165
|
}
|
|
2863
3166
|
const client = getClient(session);
|
|
2864
3167
|
const postSearch = () => postSearchWithFallback(
|
|
@@ -2868,7 +3171,7 @@ ${ALEXANDRIA_INSTRUCTIONS}
|
|
|
2868
3171
|
);
|
|
2869
3172
|
const context = { tool: "firecrawl_search" };
|
|
2870
3173
|
const httpRes = exchangeSource ? await relayExchangeError(postSearch, context) : await relayTermsRequired(postSearch, context);
|
|
2871
|
-
return
|
|
3174
|
+
return structuredCompact(httpRes?.data ?? {});
|
|
2872
3175
|
}
|
|
2873
3176
|
});
|
|
2874
3177
|
async function executeExchangeCalls(session, alexandria, suppliedRequestId, timeout, origin = requestOrigin(void 0, session)) {
|
|
@@ -2906,11 +3209,11 @@ async function executeExchangeCalls(session, alexandria, suppliedRequestId, time
|
|
|
2906
3209
|
randomUUID(),
|
|
2907
3210
|
alexandriaFeedbackAvailable(session) && alexandriaCallsWarrantFeedback(calls) ? { feedbackTool: ALEXANDRIA_FEEDBACK_HINT } : {}
|
|
2908
3211
|
);
|
|
2909
|
-
} catch (
|
|
2910
|
-
if (
|
|
3212
|
+
} catch (error2) {
|
|
3213
|
+
if (error2?.response?.status === 401) throw error2;
|
|
2911
3214
|
throw new UserError2(
|
|
2912
|
-
`${
|
|
2913
|
-
{ ...
|
|
3215
|
+
`${error2 instanceof Error ? error2.message : String(error2)} Request ID: ${requestId}. Reuse this ID only with the identical payload.`,
|
|
3216
|
+
{ ...error2 instanceof UserError2 ? error2.extras : {}, requestId }
|
|
2914
3217
|
);
|
|
2915
3218
|
}
|
|
2916
3219
|
}
|
|
@@ -2923,15 +3226,16 @@ var findToolsTool = {
|
|
|
2923
3226
|
destructiveHint: false,
|
|
2924
3227
|
idempotentHint: true
|
|
2925
3228
|
},
|
|
2926
|
-
description: "Browse Alexandria data providers and workflows or read a selected contract. Alexandria covers " + ALEXANDRIA_CATALOGUE_VERTICALS + ": typed, sourced records through published contracts. Prefer normal firecrawl_search for a data task; it already returns matching providers. Use this tool when the contract you need was not returned in full, to browse a category when search found nothing, or before scraping the same fields from several pages. Discovery is free. Use query for semantic discovery or urls to find providers for a website. With no arguments, browse categories, then providers and tools.
|
|
3229
|
+
description: "Browse Alexandria data providers and workflows or read a selected contract. Alexandria covers " + ALEXANDRIA_CATALOGUE_VERTICALS + ": typed, sourced records through published contracts. Prefer normal firecrawl_search for a data task; it already returns matching providers. Use this tool when the contract you need was not returned in full, to browse a category when search found nothing, or before scraping the same fields from several pages. Discovery is free. Use query for semantic discovery or urls to find providers for a website. With no arguments, browse categories, then providers and tools. Contracts describe the inputs and outputs for execution through firecrawl_scrape. nextTool identifies further discovery or pagination. Discovery does not execute providers. Use firecrawl_search when you also need web results.",
|
|
3230
|
+
outputSchema: findToolsOutputSchema,
|
|
2927
3231
|
parameters: findToolsSchema,
|
|
2928
3232
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
2929
3233
|
const origin = requestOrigin(mcpClient, session);
|
|
2930
3234
|
let options;
|
|
2931
3235
|
try {
|
|
2932
3236
|
options = findToolsOptions(args2);
|
|
2933
|
-
} catch (
|
|
2934
|
-
throw new UserError2(
|
|
3237
|
+
} catch (error2) {
|
|
3238
|
+
throw new UserError2(error2 instanceof Error ? error2.message : String(error2));
|
|
2935
3239
|
}
|
|
2936
3240
|
if (options.level === "categories") {
|
|
2937
3241
|
assertExchangeCredential(session);
|
|
@@ -2951,32 +3255,26 @@ var findToolsTool = {
|
|
|
2951
3255
|
}));
|
|
2952
3256
|
const page = { level: "categories", items, total: rows.length };
|
|
2953
3257
|
if (offset + options.limit < rows.length) page.nextTool = { name: "firecrawl_find_tools", arguments: { level: "categories", limit: options.limit, offset: offset + options.limit } };
|
|
2954
|
-
return
|
|
3258
|
+
return structuredCompact(withAlexandriaFeedbackHint(withFindToolsNavigation({ success: true, data: { creditsCost: 0, alexandria: [{ provider: "firecrawl", capability: "find-tools", creditsCost: 0, data: page }] } }), alexandriaFeedbackAvailable(session)));
|
|
2955
3259
|
}
|
|
2956
3260
|
const result = await executeExchangeCalls(session, { provider: "firecrawl", capability: "find-tools", options }, void 0, void 0, origin);
|
|
2957
|
-
return
|
|
3261
|
+
return structuredCompact(withFindToolsNavigation(JSON.parse(result)));
|
|
2958
3262
|
}
|
|
2959
3263
|
};
|
|
2960
3264
|
server.addTool(findToolsTool);
|
|
2961
3265
|
var SEARCH_SURFACE_SCRAPE_DESCRIPTION = `
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
Firecrawl may reuse recently indexed content instead of refetching the page, and the reuse window varies by domain. Set \`maxAge: 0\` to force a live fetch, or a smaller \`maxAge\` to bound how stale reused content may be. A successful response does not by itself confirm that the state it describes is still current. URL mode returns the selected content formats and page metadata.
|
|
2965
|
-
|
|
2966
|
-
Before scraping the same fields from several pages, first run \`firecrawl_search\` with \`sources\` unset (or \`firecrawl_find_tools\`): a matching Alexandria provider returns those fields as typed records in one call.
|
|
2967
|
-
Alexandria mode: pass \`alexandria\` (one \`{provider, capability, options}\` object or an array of 1-10) instead of \`url\` to execute catalogued Alexandria capabilities found through \`firecrawl_search\` sources \`alexandria\` or \`firecrawl_find_tools\`. The optional requestId identifies one logical execution: reuse the returned ID for retries of the identical payload, never a new ID to bypass pending or uncertain execution. Each call may include version to pin a published workflow; omitting it uses latest. Only timeout also applies at the top level in this mode. Returns per-capability results in \`data.alexandria\`, including \`data\`, \`records\`, or an \`error\` with a code. Check each item for errors even when the outer response is successful. Alexandria execution is billed at the capability's listed price and needs a team with Alexandria enabled.
|
|
3266
|
+
Scrape one URL and return its content, or execute catalogued Alexandria capabilities. URL mode returns markdown by default, or HTML, links, screenshots, branding data, a targeted answer, or JSON matching a supplied schema, plus page metadata. Firecrawl may serve recently indexed content; set \`maxAge: 0\` for a live fetch. A successful response does not by itself confirm the page is still current. Browser actions can change the live page when interactive actions are enabled, and a named browser profile can load saved session data and overwrite its stored state.
|
|
2968
3267
|
|
|
2969
|
-
|
|
3268
|
+
\`firecrawl_search\` with \`sources\` unset and \`firecrawl_find_tools\` can discover providers for the same fields across several pages; a matching Alexandria provider returns typed records in one call.
|
|
2970
3269
|
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
Alexandria execution errors relay a \`code\` and \`chargeId\`: \`request_in_flight\` (409) retry the same requestId later; \`request_unresolved\` (503) keep the requestId for reconciliation, never mint a new one; \`duplicate_request\` (409) the requestId belongs to a different payload; \`unknown_provider\` (404), \`insufficient_credits\` (402), and \`billing_unavailable\` (503) mean nothing executed. A terms-gated Alexandria provider returns \`THIRD_PARTY_DATA_TERMS_REQUIRED\` (403) with \`requiresAction.url\`: follow the returned terms/show and terms/accept calls through this tool, only accepting after explicit user authorization for the reviewed version and digest. An organization admin can alternatively accept at the dashboard URL. Retry only after confirmed acceptance.
|
|
3270
|
+
Alexandria mode: \`alexandria\` selects catalogued capability execution and is mutually exclusive with \`url\`; the \`alexandria\` and \`requestId\` parameters describe batching, retries, errors and provider terms. Alexandria execution is billed at the capability's listed price and needs a team with Alexandria enabled.
|
|
2974
3271
|
`;
|
|
2975
3272
|
var searchSurfaceScrapeTool = {
|
|
2976
3273
|
...scrapeTool,
|
|
3274
|
+
_meta: { "anthropic/alwaysLoad": true },
|
|
2977
3275
|
description: SEARCH_SURFACE_SCRAPE_DESCRIPTION
|
|
2978
3276
|
};
|
|
2979
|
-
var SEARCH_SURFACE_FIND_TOOLS_DESCRIPTION = "Browse Alexandria data providers and workflows or read a selected contract. Alexandria covers " + ALEXANDRIA_CATALOGUE_VERTICALS + ": typed, sourced records through published contracts. Prefer normal firecrawl_search for a data task; it already returns matching providers. Use this tool when the contract you need was not returned in full, to browse a category when search found nothing, or before scraping the same fields from several pages. Discovery is free. Use query for semantic discovery or urls to find providers for a website. With no arguments, browse categories, then providers and tools.
|
|
3277
|
+
var SEARCH_SURFACE_FIND_TOOLS_DESCRIPTION = "Browse Alexandria data providers and workflows or read a selected contract. Alexandria covers " + ALEXANDRIA_CATALOGUE_VERTICALS + ": typed, sourced records through published contracts. Prefer normal firecrawl_search for a data task; it already returns matching providers. Use this tool when the contract you need was not returned in full, to browse a category when search found nothing, or before scraping the same fields from several pages. Discovery is free. Use query for semantic discovery or urls to find providers for a website. With no arguments, browse categories, then providers and tools. Contracts describe the inputs and outputs for execution through firecrawl_scrape. nextTool identifies further discovery or pagination. Discovery does not execute providers. Use firecrawl_search when you also need web results.";
|
|
2980
3278
|
var searchSurfaceFindToolsTool = {
|
|
2981
3279
|
...findToolsTool,
|
|
2982
3280
|
description: SEARCH_SURFACE_FIND_TOOLS_DESCRIPTION
|
|
@@ -3127,17 +3425,17 @@ async function waitForCrawlCompletionWithOrigin(client, jobId, origin, pollInter
|
|
|
3127
3425
|
);
|
|
3128
3426
|
}
|
|
3129
3427
|
}
|
|
3130
|
-
var feedbackIssueSchema =
|
|
3428
|
+
var feedbackIssueSchema = z8.string().trim().min(1).max(80).regex(
|
|
3131
3429
|
/^[a-z0-9][a-z0-9_-]*$/,
|
|
3132
3430
|
"Issue codes must use lowercase letters, numbers, underscores, or hyphens"
|
|
3133
3431
|
);
|
|
3134
|
-
var valuableSourceSchema =
|
|
3135
|
-
url:
|
|
3136
|
-
reason:
|
|
3432
|
+
var valuableSourceSchema = z8.object({
|
|
3433
|
+
url: z8.string().url(),
|
|
3434
|
+
reason: z8.string().max(1e3).optional()
|
|
3137
3435
|
});
|
|
3138
|
-
var missingContentSchema =
|
|
3139
|
-
topic:
|
|
3140
|
-
description:
|
|
3436
|
+
var missingContentSchema = z8.object({
|
|
3437
|
+
topic: z8.string().min(1, "topic must not be empty").max(200, "topic must be 200 characters or fewer"),
|
|
3438
|
+
description: z8.string().max(2e3).optional()
|
|
3141
3439
|
});
|
|
3142
3440
|
var FEEDBACK_DISABLED_VALUES = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
3143
3441
|
function feedbackEnvEnabled(...keys) {
|
|
@@ -3175,24 +3473,25 @@ Records schema-validated quality feedback for a prior \`firecrawl_search\` UUID
|
|
|
3175
3473
|
|
|
3176
3474
|
Eligibility is limited to successful searches within the feedback age window. The record is idempotent per search ID. Eligible first feedback for a search can refund 1 credit; refunds are subject to the team's daily cap. The response reports whether a refund was applied, along with submission and daily-cap status.
|
|
3177
3475
|
`,
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3476
|
+
outputSchema: feedbackOutputSchema,
|
|
3477
|
+
parameters: z8.object({
|
|
3478
|
+
searchId: z8.string().uuid("searchId must be the UUID returned by firecrawl_search"),
|
|
3479
|
+
rating: z8.enum(["good", "bad", "partial"]),
|
|
3480
|
+
valuableSources: z8.array(
|
|
3481
|
+
z8.object({
|
|
3482
|
+
url: z8.string().url(),
|
|
3483
|
+
reason: z8.string().max(1e3).optional()
|
|
3185
3484
|
})
|
|
3186
3485
|
).max(50).optional(),
|
|
3187
|
-
missingContent:
|
|
3188
|
-
|
|
3189
|
-
topic:
|
|
3190
|
-
description:
|
|
3486
|
+
missingContent: z8.array(
|
|
3487
|
+
z8.object({
|
|
3488
|
+
topic: z8.string().min(1, "topic must not be empty").max(200, "topic must be 200 characters or fewer"),
|
|
3489
|
+
description: z8.string().max(2e3).optional()
|
|
3191
3490
|
})
|
|
3192
3491
|
).max(20).optional().describe(
|
|
3193
3492
|
"Array of specific pieces of content the agent expected to find but did not. One entry per distinct topic. Each entry has a short `topic` and optional longer `description`."
|
|
3194
3493
|
),
|
|
3195
|
-
querySuggestions:
|
|
3494
|
+
querySuggestions: z8.string().max(2e3).optional()
|
|
3196
3495
|
}),
|
|
3197
3496
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
3198
3497
|
const origin = requestOrigin(mcpClient, session);
|
|
@@ -3252,7 +3551,7 @@ Eligibility is limited to successful searches within the feedback age window. Th
|
|
|
3252
3551
|
status: response.status,
|
|
3253
3552
|
feedbackErrorCode: parsed?.feedbackErrorCode
|
|
3254
3553
|
});
|
|
3255
|
-
return
|
|
3554
|
+
return structuredText({
|
|
3256
3555
|
success: false,
|
|
3257
3556
|
status: response.status,
|
|
3258
3557
|
feedbackErrorCode: parsed?.feedbackErrorCode,
|
|
@@ -3260,7 +3559,7 @@ Eligibility is limited to successful searches within the feedback age window. Th
|
|
|
3260
3559
|
retryable: response.status >= 500
|
|
3261
3560
|
});
|
|
3262
3561
|
}
|
|
3263
|
-
return
|
|
3562
|
+
return structuredText(parsed);
|
|
3264
3563
|
}
|
|
3265
3564
|
});
|
|
3266
3565
|
}
|
|
@@ -3291,20 +3590,21 @@ For an Alexandria session, set endpoint to \`alexandria\`, omit jobId, and provi
|
|
|
3291
3590
|
|
|
3292
3591
|
Returns submission status, feedback ID, and accounting fields.
|
|
3293
3592
|
`,
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3593
|
+
outputSchema: feedbackOutputSchema,
|
|
3594
|
+
parameters: z8.object({
|
|
3595
|
+
endpoint: z8.enum(["search", "scrape", "parse", "map", "alexandria"]),
|
|
3596
|
+
jobId: z8.string().uuid("jobId must be the UUID returned by Firecrawl").optional(),
|
|
3297
3597
|
...alexandriaFeedbackFields,
|
|
3298
|
-
rating:
|
|
3299
|
-
issues:
|
|
3300
|
-
tags:
|
|
3301
|
-
note:
|
|
3302
|
-
valuableSources:
|
|
3303
|
-
missingContent:
|
|
3304
|
-
querySuggestions:
|
|
3305
|
-
url:
|
|
3306
|
-
pageNumbers:
|
|
3307
|
-
metadata:
|
|
3598
|
+
rating: z8.enum(["good", "bad", "partial"]),
|
|
3599
|
+
issues: z8.array(feedbackIssueSchema).max(20).optional(),
|
|
3600
|
+
tags: z8.array(feedbackIssueSchema).max(20).optional(),
|
|
3601
|
+
note: z8.string().max(4e3).optional(),
|
|
3602
|
+
valuableSources: z8.array(valuableSourceSchema).max(50).optional(),
|
|
3603
|
+
missingContent: z8.array(missingContentSchema).max(50).optional(),
|
|
3604
|
+
querySuggestions: z8.string().max(2e3).optional(),
|
|
3605
|
+
url: z8.string().url().optional(),
|
|
3606
|
+
pageNumbers: z8.array(z8.number().int().positive()).max(100).optional(),
|
|
3607
|
+
metadata: z8.record(z8.string(), z8.unknown()).optional()
|
|
3308
3608
|
}).superRefine((value, ctx) => {
|
|
3309
3609
|
if (value.endpoint === "alexandria") {
|
|
3310
3610
|
const parsed = alexandriaSessionFeedbackSchema.safeParse(value);
|
|
@@ -3386,7 +3686,7 @@ Returns submission status, feedback ID, and accounting fields.
|
|
|
3386
3686
|
status: response.status,
|
|
3387
3687
|
feedbackErrorCode: parsed?.feedbackErrorCode
|
|
3388
3688
|
});
|
|
3389
|
-
return
|
|
3689
|
+
return structuredText({
|
|
3390
3690
|
success: false,
|
|
3391
3691
|
status: response.status,
|
|
3392
3692
|
feedbackErrorCode: parsed?.feedbackErrorCode,
|
|
@@ -3394,7 +3694,7 @@ Returns submission status, feedback ID, and accounting fields.
|
|
|
3394
3694
|
retryable: response.status >= 500
|
|
3395
3695
|
});
|
|
3396
3696
|
}
|
|
3397
|
-
return
|
|
3697
|
+
return structuredText(parsed);
|
|
3398
3698
|
}
|
|
3399
3699
|
});
|
|
3400
3700
|
}
|
|
@@ -3414,27 +3714,28 @@ Start a multi-page crawl at a website URL, poll it to a terminal state, and retu
|
|
|
3414
3714
|
|
|
3415
3715
|
Crawl results can be large; use conservative limits when full-site coverage is unnecessary. Webhooks and interactive scrape actions are unavailable in safe mode. Returns the crawl ID, status, and page data.
|
|
3416
3716
|
`,
|
|
3417
|
-
parameters:
|
|
3418
|
-
url:
|
|
3419
|
-
prompt:
|
|
3420
|
-
excludePaths:
|
|
3421
|
-
includePaths:
|
|
3422
|
-
maxDiscoveryDepth:
|
|
3423
|
-
sitemap:
|
|
3424
|
-
limit:
|
|
3425
|
-
allowExternalLinks:
|
|
3426
|
-
allowSubdomains:
|
|
3427
|
-
crawlEntireDomain:
|
|
3428
|
-
delay:
|
|
3429
|
-
maxConcurrency:
|
|
3717
|
+
parameters: z8.object({
|
|
3718
|
+
url: z8.string(),
|
|
3719
|
+
prompt: z8.string().optional(),
|
|
3720
|
+
excludePaths: z8.array(z8.string()).optional(),
|
|
3721
|
+
includePaths: z8.array(z8.string()).optional(),
|
|
3722
|
+
maxDiscoveryDepth: z8.number().optional(),
|
|
3723
|
+
sitemap: z8.enum(["skip", "include", "only"]).optional(),
|
|
3724
|
+
limit: z8.number().optional(),
|
|
3725
|
+
allowExternalLinks: z8.boolean().optional(),
|
|
3726
|
+
allowSubdomains: z8.boolean().optional(),
|
|
3727
|
+
crawlEntireDomain: z8.boolean().optional(),
|
|
3728
|
+
delay: z8.number().optional(),
|
|
3729
|
+
maxConcurrency: z8.number().optional(),
|
|
3430
3730
|
...SAFE_MODE ? {} : {
|
|
3431
|
-
webhook:
|
|
3432
|
-
webhookHeaders:
|
|
3731
|
+
webhook: z8.string().optional(),
|
|
3732
|
+
webhookHeaders: z8.record(z8.string(), z8.string()).optional()
|
|
3433
3733
|
},
|
|
3434
|
-
deduplicateSimilarURLs:
|
|
3435
|
-
ignoreQueryParameters:
|
|
3734
|
+
deduplicateSimilarURLs: z8.boolean().optional(),
|
|
3735
|
+
ignoreQueryParameters: z8.boolean().optional(),
|
|
3436
3736
|
scrapeOptions: scrapeParamsSchema.omit({ url: true }).partial().optional()
|
|
3437
3737
|
}),
|
|
3738
|
+
outputSchema: crawlOutputSchema,
|
|
3438
3739
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
3439
3740
|
const origin = requestOrigin(mcpClient, session);
|
|
3440
3741
|
const { url, ...options } = args2;
|
|
@@ -3461,7 +3762,7 @@ Crawl results can be large; use conservative limits when full-site coverage is u
|
|
|
3461
3762
|
});
|
|
3462
3763
|
const crawlId = started?.data?.id;
|
|
3463
3764
|
if (!crawlId) {
|
|
3464
|
-
return
|
|
3765
|
+
return structuredText(started?.data ?? {});
|
|
3465
3766
|
}
|
|
3466
3767
|
const res = await waitForCrawlCompletionWithOrigin(
|
|
3467
3768
|
client,
|
|
@@ -3470,7 +3771,7 @@ Crawl results can be large; use conservative limits when full-site coverage is u
|
|
|
3470
3771
|
pollInterval,
|
|
3471
3772
|
timeout
|
|
3472
3773
|
);
|
|
3473
|
-
return
|
|
3774
|
+
return structuredText(res);
|
|
3474
3775
|
}
|
|
3475
3776
|
});
|
|
3476
3777
|
server.addTool({
|
|
@@ -3487,7 +3788,8 @@ server.addTool({
|
|
|
3487
3788
|
description: `
|
|
3488
3789
|
Retrieve the current status, progress, and available results for an existing crawl ID. This only reads Firecrawl job state and does not start or modify the crawl.
|
|
3489
3790
|
`,
|
|
3490
|
-
|
|
3791
|
+
outputSchema: crawlOutputSchema,
|
|
3792
|
+
parameters: z8.object({ id: z8.string() }),
|
|
3491
3793
|
execute: async (args2, {
|
|
3492
3794
|
session,
|
|
3493
3795
|
client: mcpClient
|
|
@@ -3499,7 +3801,7 @@ Retrieve the current status, progress, and available results for an existing cra
|
|
|
3499
3801
|
id,
|
|
3500
3802
|
requestOrigin(mcpClient, session)
|
|
3501
3803
|
);
|
|
3502
|
-
return
|
|
3804
|
+
return structuredText(res);
|
|
3503
3805
|
}
|
|
3504
3806
|
});
|
|
3505
3807
|
server.addTool({
|
|
@@ -3513,13 +3815,14 @@ server.addTool({
|
|
|
3513
3815
|
description: `
|
|
3514
3816
|
Deprecated compatibility entry point. Use firecrawl_scrape once per known URL with formats: ["json"] and jsonOptions containing the prompt and schema. Use firecrawl_agent for multi-source research when the URLs are not known or the data spans several sites.
|
|
3515
3817
|
`,
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3818
|
+
outputSchema: deprecatedToolOutputSchema,
|
|
3819
|
+
parameters: z8.object({
|
|
3820
|
+
urls: z8.array(z8.string()),
|
|
3821
|
+
prompt: z8.string().optional(),
|
|
3822
|
+
schema: z8.record(z8.string(), z8.any()).optional(),
|
|
3823
|
+
allowExternalLinks: z8.boolean().optional(),
|
|
3824
|
+
enableWebSearch: z8.boolean().optional(),
|
|
3825
|
+
includeSubdomains: z8.boolean().optional()
|
|
3523
3826
|
}),
|
|
3524
3827
|
canList: () => false,
|
|
3525
3828
|
beforeValidate: () => {
|
|
@@ -3551,10 +3854,11 @@ Run web research that returns structured data when the URLs are not known or the
|
|
|
3551
3854
|
|
|
3552
3855
|
This call returns only a job ID, not the research result. Read the job with \`firecrawl_agent_status\` until it reaches \`completed\` or \`failed\`; a typical research run takes one to three minutes. For one known URL use \`firecrawl_scrape\` (with formats: ["json"] for structured output); for a plain lookup that a results page answers, use \`firecrawl_search\`.
|
|
3553
3856
|
`,
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3857
|
+
outputSchema: agentOutputSchema,
|
|
3858
|
+
parameters: z8.object({
|
|
3859
|
+
prompt: z8.string().min(1).max(1e4),
|
|
3860
|
+
urls: z8.array(z8.string().url()).optional(),
|
|
3861
|
+
schema: z8.record(z8.string(), z8.any()).optional()
|
|
3558
3862
|
}),
|
|
3559
3863
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
3560
3864
|
const client = getClient(session);
|
|
@@ -3572,7 +3876,7 @@ This call returns only a job ID, not the research result. Read the job with \`fi
|
|
|
3572
3876
|
...agentBody,
|
|
3573
3877
|
origin: requestOrigin(mcpClient, session)
|
|
3574
3878
|
});
|
|
3575
|
-
return
|
|
3879
|
+
return structuredText(res);
|
|
3576
3880
|
}
|
|
3577
3881
|
});
|
|
3578
3882
|
server.addTool({
|
|
@@ -3591,7 +3895,8 @@ Retrieve progress or final results for a \`firecrawl_agent\` job ID. A \`process
|
|
|
3591
3895
|
|
|
3592
3896
|
Returns job status, progress information, and result data when completed.
|
|
3593
3897
|
`,
|
|
3594
|
-
|
|
3898
|
+
outputSchema: agentStatusOutputSchema,
|
|
3899
|
+
parameters: z8.object({ id: z8.string() }),
|
|
3595
3900
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
3596
3901
|
const client = getClient(session);
|
|
3597
3902
|
const { id } = args2;
|
|
@@ -3600,7 +3905,7 @@ Returns job status, progress information, and result data when completed.
|
|
|
3600
3905
|
`/v2/agent/${encodeURIComponent(id)}`,
|
|
3601
3906
|
originHeaders(requestOrigin(mcpClient, session))
|
|
3602
3907
|
);
|
|
3603
|
-
return
|
|
3908
|
+
return structuredText(res?.data ?? {});
|
|
3604
3909
|
}
|
|
3605
3910
|
});
|
|
3606
3911
|
server.addTool({
|
|
@@ -3619,13 +3924,14 @@ Open or reuse a live browser session to navigate a page, click controls, fill fi
|
|
|
3619
3924
|
|
|
3620
3925
|
This acts on the live site, so actions such as form submission can create persistent external side effects. Returns execution output, stdout/stderr, exit status, and session viewing URLs.
|
|
3621
3926
|
`,
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3927
|
+
outputSchema: interactOutputSchema,
|
|
3928
|
+
parameters: z8.object({
|
|
3929
|
+
scrapeId: z8.string().trim().min(1).optional(),
|
|
3930
|
+
url: z8.string().trim().url().optional(),
|
|
3931
|
+
prompt: z8.string().trim().min(1).optional(),
|
|
3932
|
+
code: z8.string().trim().min(1).optional(),
|
|
3933
|
+
language: z8.enum(["bash", "python", "node"]).optional(),
|
|
3934
|
+
timeout: z8.number().min(1).max(300).optional(),
|
|
3629
3935
|
scrapeOptions: scrapeParamsSchema.omit({ url: true }).partial().optional()
|
|
3630
3936
|
}).refine((data) => Boolean(data.scrapeId) !== Boolean(data.url), {
|
|
3631
3937
|
message: "Provide either 'url' (interact directly) or 'scrapeId' (reuse a previous scrape), not both."
|
|
@@ -3657,14 +3963,14 @@ This acts on the live site, so actions such as form submission can create persis
|
|
|
3657
3963
|
});
|
|
3658
3964
|
scrapeId = scraped?.metadata?.scrapeId;
|
|
3659
3965
|
if (!scrapeId) {
|
|
3660
|
-
return
|
|
3966
|
+
return structuredText({
|
|
3661
3967
|
error: "Could not open an interact session: the scrape did not return a scrapeId. Try firecrawl_scrape first, then pass its scrapeId.",
|
|
3662
3968
|
url
|
|
3663
3969
|
});
|
|
3664
3970
|
}
|
|
3665
3971
|
}
|
|
3666
3972
|
if (!scrapeId) {
|
|
3667
|
-
return
|
|
3973
|
+
return structuredText({
|
|
3668
3974
|
error: "Could not open an interact session: missing scrapeId.",
|
|
3669
3975
|
url
|
|
3670
3976
|
});
|
|
@@ -3678,15 +3984,15 @@ This acts on the live site, so actions such as form submission can create persis
|
|
|
3678
3984
|
if (timeout != null) interactArgs.timeout = timeout;
|
|
3679
3985
|
const res = await client.interact(activeScrapeId, interactArgs);
|
|
3680
3986
|
if (openedFromUrl && res && typeof res === "object" && !Array.isArray(res)) {
|
|
3681
|
-
return
|
|
3987
|
+
return structuredText({
|
|
3682
3988
|
...res,
|
|
3683
3989
|
scrapeId: activeScrapeId
|
|
3684
3990
|
});
|
|
3685
3991
|
}
|
|
3686
3992
|
if (openedFromUrl) {
|
|
3687
|
-
return
|
|
3993
|
+
return structuredText({ scrapeId: activeScrapeId, result: res });
|
|
3688
3994
|
}
|
|
3689
|
-
return
|
|
3995
|
+
return structuredText(res);
|
|
3690
3996
|
}
|
|
3691
3997
|
});
|
|
3692
3998
|
server.addTool({
|
|
@@ -3703,8 +4009,9 @@ server.addTool({
|
|
|
3703
4009
|
description: `
|
|
3704
4010
|
Stop the live interact session associated with a \`scrapeId\` and release its resources. Returns a success confirmation.
|
|
3705
4011
|
`,
|
|
3706
|
-
|
|
3707
|
-
|
|
4012
|
+
outputSchema: interactStopOutputSchema,
|
|
4013
|
+
parameters: z8.object({
|
|
4014
|
+
scrapeId: z8.string()
|
|
3708
4015
|
}),
|
|
3709
4016
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
3710
4017
|
const client = getClient(session);
|
|
@@ -3714,7 +4021,7 @@ Stop the live interact session associated with a \`scrapeId\` and release its re
|
|
|
3714
4021
|
`/v2/scrape/${encodeURIComponent(scrapeId)}/interact`,
|
|
3715
4022
|
originHeaders(requestOrigin(mcpClient, session))
|
|
3716
4023
|
);
|
|
3717
|
-
return
|
|
4024
|
+
return structuredText(res?.data ?? {});
|
|
3718
4025
|
}
|
|
3719
4026
|
});
|
|
3720
4027
|
server.addTool({
|
|
@@ -3735,11 +4042,14 @@ Local MCP reads \`filePath\` from the server filesystem. Hosted MCP uses two cal
|
|
|
3735
4042
|
|
|
3736
4043
|
Set \`redactPII\` to request redaction of personally identifiable information in the returned content. \`zeroDataRetention\` requires an eligible authenticated account; omit it for anonymous keyless use. Returns upload instructions for hosted phase one or parsed document content for the final call. Authenticated final responses can include a \`data.metadata.scrapeId\` for optional parse feedback.
|
|
3737
4044
|
`,
|
|
4045
|
+
outputSchema: parseOutputSchema,
|
|
3738
4046
|
parameters: parseParamsSchema,
|
|
3739
4047
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
3740
4048
|
const origin = requestOrigin(mcpClient, session);
|
|
3741
4049
|
if (process.env.CLOUD_SERVICE === "true") {
|
|
3742
|
-
return
|
|
4050
|
+
return structuredJsonText(
|
|
4051
|
+
await executeHostedParse(args2, session, log, origin)
|
|
4052
|
+
);
|
|
3743
4053
|
}
|
|
3744
4054
|
const apiUrl = process.env.FIRECRAWL_API_URL;
|
|
3745
4055
|
if (!apiUrl) {
|
|
@@ -3789,15 +4099,16 @@ Set \`redactPII\` to request redaction of personally identifiable information in
|
|
|
3789
4099
|
);
|
|
3790
4100
|
}
|
|
3791
4101
|
try {
|
|
3792
|
-
return
|
|
4102
|
+
return structuredText(JSON.parse(responseText));
|
|
3793
4103
|
} catch {
|
|
3794
|
-
return responseText;
|
|
4104
|
+
return withStructured(responseText, { raw: responseText });
|
|
3795
4105
|
}
|
|
3796
4106
|
}
|
|
3797
4107
|
});
|
|
3798
4108
|
function registerMarketplaceSearchTool(registrar, getClientFn) {
|
|
3799
4109
|
registrar.addTool({
|
|
3800
4110
|
name: "firecrawl_search",
|
|
4111
|
+
_meta: { "anthropic/alwaysLoad": true },
|
|
3801
4112
|
annotations: {
|
|
3802
4113
|
title: "Firecrawl web search",
|
|
3803
4114
|
readOnlyHint: true,
|
|
@@ -3813,7 +4124,8 @@ ${ALEXANDRIA_SEARCH_INSTRUCTIONS}
|
|
|
3813
4124
|
|
|
3814
4125
|
Returns result groups in \`data\` and an operation \`id\`.
|
|
3815
4126
|
`,
|
|
3816
|
-
|
|
4127
|
+
outputSchema: searchOutputSchema,
|
|
4128
|
+
parameters: z8.object({ ...searchToolBaseFields }).strict().refine(searchDomainsAreExclusive, SEARCH_DOMAINS_CONFLICT_MESSAGE).refine(
|
|
3817
4129
|
searchQueryIsValid,
|
|
3818
4130
|
"A query is required. Use firecrawl_find_tools to browse the catalogue."
|
|
3819
4131
|
),
|
|
@@ -3864,7 +4176,7 @@ Returns result groups in \`data\` and an operation \`id\`.
|
|
|
3864
4176
|
);
|
|
3865
4177
|
const context = { tool: "firecrawl_search" };
|
|
3866
4178
|
const httpRes = exchangeSource ? await relayExchangeError(postSearch, context) : await relayTermsRequired(postSearch, context);
|
|
3867
|
-
return
|
|
4179
|
+
return structuredCompact(httpRes?.data ?? {});
|
|
3868
4180
|
}
|
|
3869
4181
|
});
|
|
3870
4182
|
}
|
|
@@ -3936,10 +4248,10 @@ if (searchProfileEnabled) {
|
|
|
3936
4248
|
stateless: true
|
|
3937
4249
|
}
|
|
3938
4250
|
});
|
|
3939
|
-
} catch (
|
|
4251
|
+
} catch (error2) {
|
|
3940
4252
|
console.error(
|
|
3941
4253
|
`[search-profile] failed to start on port ${searchProfile.port}; the full surface is unaffected`,
|
|
3942
|
-
|
|
4254
|
+
error2
|
|
3943
4255
|
);
|
|
3944
4256
|
}
|
|
3945
4257
|
}
|