firecrawl-mcp 3.25.4 → 3.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -3
- package/dist/agent-hints.js +46 -0
- package/dist/index.js +1138 -430
- package/dist/introspection-cache.js +67 -0
- package/package.json +15 -14
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,367 @@ 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/agent-hints.ts
|
|
91
|
+
var AGENT_HINTS_HEADERS = { "X-Firecrawl-Agent-Hints": "true" };
|
|
92
|
+
function readAgentHints(value) {
|
|
93
|
+
if (!value || typeof value !== "object") return void 0;
|
|
94
|
+
const hints = value.agent_hints;
|
|
95
|
+
return Array.isArray(hints) && hints.every((hint) => typeof hint === "string") ? hints : void 0;
|
|
96
|
+
}
|
|
97
|
+
function preserveAgentHints(data, envelope) {
|
|
98
|
+
const hints = readAgentHints(envelope);
|
|
99
|
+
if (!hints) return data;
|
|
100
|
+
return data && typeof data === "object" && !Array.isArray(data) ? { ...data, agent_hints: hints } : { data, agent_hints: hints };
|
|
101
|
+
}
|
|
102
|
+
function agentHintsText(hints) {
|
|
103
|
+
return `Firecrawl API agent_hints (response guidance, separate from page content):
|
|
104
|
+
${JSON.stringify(hints, null, 2)}`;
|
|
105
|
+
}
|
|
106
|
+
function withAgentHints(result, response, separateText = false) {
|
|
107
|
+
const hints = readAgentHints(response);
|
|
108
|
+
if (!hints) return result;
|
|
109
|
+
return {
|
|
110
|
+
...result,
|
|
111
|
+
content: separateText && hints.length ? [...result.content, { type: "text", text: agentHintsText(hints) }] : result.content,
|
|
112
|
+
structuredContent: { ...result.structuredContent, agent_hints: hints }
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function readErrorAgentHints(error2) {
|
|
116
|
+
const hints = readAgentHints(error2);
|
|
117
|
+
if (hints) return hints;
|
|
118
|
+
if (!error2 || typeof error2 !== "object") return void 0;
|
|
119
|
+
const extras = error2.extras;
|
|
120
|
+
const extrasHints = readAgentHints(extras);
|
|
121
|
+
if (extrasHints) return extrasHints;
|
|
122
|
+
const details = error2.details;
|
|
123
|
+
const detailsHints = readAgentHints(details);
|
|
124
|
+
if (detailsHints) return detailsHints;
|
|
125
|
+
const response = error2.response;
|
|
126
|
+
return readAgentHints(response?.data);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/tool-output.ts
|
|
92
130
|
import { z as z2 } from "zod";
|
|
131
|
+
function isRecord(value) {
|
|
132
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
133
|
+
}
|
|
134
|
+
function withStructured(text, data) {
|
|
135
|
+
return isRecord(data) ? { content: [{ type: "text", text }], structuredContent: data } : { content: [{ type: "text", text }] };
|
|
136
|
+
}
|
|
137
|
+
function structuredText(data) {
|
|
138
|
+
return withStructured(JSON.stringify(data, null, 2), data);
|
|
139
|
+
}
|
|
140
|
+
function structuredCompact(data) {
|
|
141
|
+
return withStructured(JSON.stringify(data), data);
|
|
142
|
+
}
|
|
143
|
+
function structuredJsonText(text) {
|
|
144
|
+
try {
|
|
145
|
+
return withStructured(text, JSON.parse(text));
|
|
146
|
+
} catch {
|
|
147
|
+
return { content: [{ type: "text", text }] };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
var str = (description) => z2.string().nullable().optional().describe(description);
|
|
151
|
+
var num = (description) => z2.number().nullable().optional().describe(description);
|
|
152
|
+
var bool = (description) => z2.boolean().nullable().optional().describe(description);
|
|
153
|
+
var unknown = (description) => z2.unknown().optional().describe(description);
|
|
154
|
+
var success = bool("Whether the API call succeeded.");
|
|
155
|
+
var error = unknown("Error message or error object when the call did not succeed.");
|
|
156
|
+
var warning = str("Non-fatal warning about the result.");
|
|
157
|
+
var agentHints = {
|
|
158
|
+
agent_hints: z2.array(z2.string()).optional().describe("Optional response guidance from the Firecrawl API.")
|
|
159
|
+
};
|
|
160
|
+
var alexandriaEnvelope = {
|
|
161
|
+
requestId: str("Identifier of this Alexandria execution."),
|
|
162
|
+
scrape_id: str("Identifier of the underlying scrape."),
|
|
163
|
+
receipt: unknown("Billing receipt for the execution."),
|
|
164
|
+
creditsCost: num("Credits this call consumed."),
|
|
165
|
+
delivery: str("`retained` when the full result stayed server-side instead of being inlined."),
|
|
166
|
+
responseBytes: num("Size of the full result in bytes."),
|
|
167
|
+
estimatedTokens: num("Estimated token cost of the full result."),
|
|
168
|
+
tokenEstimateMethod: str("How `estimatedTokens` was derived."),
|
|
169
|
+
inlineTokenBudget: num("Token budget above which a result is retained rather than inlined."),
|
|
170
|
+
workspaceId: str("Workspace holding a retained result, for inspection through virtual Bash."),
|
|
171
|
+
idleTtlSeconds: num("Seconds a retained workspace stays available while idle."),
|
|
172
|
+
message: str("Guidance that accompanies the result."),
|
|
173
|
+
nextTool: unknown("A follow-up tool call (`{name, arguments}`) that continues or inspects this result."),
|
|
174
|
+
feedbackTool: unknown("Pointer to the feedback tool for reporting how this result served the task.")
|
|
175
|
+
};
|
|
176
|
+
var scrapeOutputSchema = z2.object({
|
|
177
|
+
...agentHints,
|
|
178
|
+
// URL mode: the scraped document, with the formats that were requested.
|
|
179
|
+
markdown: str("Page content as markdown."),
|
|
180
|
+
html: str("Processed HTML of the page."),
|
|
181
|
+
rawHtml: str("Unprocessed HTML of the page."),
|
|
182
|
+
summary: str("Summary of the page content."),
|
|
183
|
+
links: unknown("Links found on the page."),
|
|
184
|
+
screenshot: unknown("Screenshot of the page."),
|
|
185
|
+
images: unknown("Images found on the page."),
|
|
186
|
+
audio: unknown("Audio extracted from the page."),
|
|
187
|
+
video: unknown("Video extracted from the page."),
|
|
188
|
+
answer: str("Targeted answer to the question that was asked of the page."),
|
|
189
|
+
highlights: unknown("Highlighted passages from the page."),
|
|
190
|
+
pages: unknown("Physical PDF pages, when `parsers[].pages` is set."),
|
|
191
|
+
blocks: unknown("Typed PDF layout blocks, when `parsers[].blocks` is set."),
|
|
192
|
+
product: unknown("Product data extracted from the page."),
|
|
193
|
+
menu: unknown("Menu data extracted from the page."),
|
|
194
|
+
json: unknown("Structured data matching the requested JSON schema or prompt."),
|
|
195
|
+
attributes: unknown("Values collected by the requested attribute selectors."),
|
|
196
|
+
actions: unknown("Results of the browser actions that ran during the scrape."),
|
|
197
|
+
changeTracking: unknown("Change-tracking comparison against the previous scrape."),
|
|
198
|
+
branding: unknown("Branding data extracted from the page."),
|
|
199
|
+
metadata: unknown("Page metadata; authenticated responses can include `metadata.scrapeId` for scrape feedback."),
|
|
200
|
+
tools: unknown("Domain-matched Alexandria tools for the page, when `domainTools` is set."),
|
|
201
|
+
warning,
|
|
202
|
+
// Alexandria mode and error envelopes.
|
|
203
|
+
success,
|
|
204
|
+
error,
|
|
205
|
+
data: unknown("Alexandria mode: per-capability results in `data.alexandria`, each with `data`, `records`, or an `error`."),
|
|
206
|
+
...alexandriaEnvelope
|
|
207
|
+
}).describe("A scraped document, or the Alexandria execution envelope when `alexandria` was passed.");
|
|
208
|
+
var mapOutputSchema = z2.object({
|
|
209
|
+
...agentHints,
|
|
210
|
+
links: unknown("URLs discovered under the website."),
|
|
211
|
+
success,
|
|
212
|
+
error,
|
|
213
|
+
warning,
|
|
214
|
+
id: str("Identifier of the map run, for optional map feedback.")
|
|
215
|
+
}).describe("URLs indexed under the requested website.");
|
|
216
|
+
var searchOutputSchema = z2.object({
|
|
217
|
+
...agentHints,
|
|
218
|
+
success,
|
|
219
|
+
data: unknown("Ranked results grouped by source, such as `web`, `news`, `images`, and `alexandria`."),
|
|
220
|
+
error,
|
|
221
|
+
warning,
|
|
222
|
+
id: str("Search identifier, for optional `firecrawl_search_feedback`."),
|
|
223
|
+
creditsUsed: num("Credits this search consumed."),
|
|
224
|
+
tools: unknown("Domain-matched Alexandria tools for the results."),
|
|
225
|
+
nextTool: unknown("A follow-up tool call that continues this search."),
|
|
226
|
+
feedbackTool: unknown("Pointer to the feedback tool for this search.")
|
|
227
|
+
}).describe("Ranked search results grouped by source.");
|
|
228
|
+
var findToolsOutputSchema = z2.object({
|
|
229
|
+
...agentHints,
|
|
230
|
+
success,
|
|
231
|
+
data: unknown("Discovery page in `data.alexandria[0].data`, with `level`, `items`, `total`, and an optional `nextTool`."),
|
|
232
|
+
error,
|
|
233
|
+
...alexandriaEnvelope
|
|
234
|
+
}).describe("One page of the Alexandria catalogue: categories, providers, tools, or one contract.");
|
|
235
|
+
var feedbackOutputSchema = z2.object({
|
|
236
|
+
...agentHints,
|
|
237
|
+
success,
|
|
238
|
+
error,
|
|
239
|
+
status: num("HTTP status when the submission was rejected."),
|
|
240
|
+
feedbackErrorCode: str("Machine-readable reason the submission was rejected."),
|
|
241
|
+
retryable: bool("Whether retrying the submission can succeed."),
|
|
242
|
+
message: str("Human-readable result of the submission."),
|
|
243
|
+
feedbackId: str("Identifier of the recorded feedback."),
|
|
244
|
+
creditsRefunded: num("Credits refunded for this submission, when eligible."),
|
|
245
|
+
creditsRefundedToday: num("Credits refunded to the team so far this UTC day."),
|
|
246
|
+
dailyRefundCap: num("The team's daily refund cap in credits."),
|
|
247
|
+
dailyCapReached: bool("Whether the daily refund cap is reached; further feedback today still records but refunds nothing."),
|
|
248
|
+
alreadySubmitted: bool("Whether feedback for this job was already recorded."),
|
|
249
|
+
warning,
|
|
250
|
+
data: unknown("Payload returned with the accepted feedback."),
|
|
251
|
+
raw: str("Response body when it was not JSON.")
|
|
252
|
+
}).describe("Result of submitting feedback.");
|
|
253
|
+
var crawlOutputSchema = z2.object({
|
|
254
|
+
...agentHints,
|
|
255
|
+
success,
|
|
256
|
+
error,
|
|
257
|
+
warning,
|
|
258
|
+
id: str("Crawl identifier, for `firecrawl_check_crawl_status`."),
|
|
259
|
+
status: str("Crawl state, such as `scraping`, `completed`, or `failed`."),
|
|
260
|
+
completed: num("Pages crawled so far."),
|
|
261
|
+
total: num("Pages the crawl expects to visit."),
|
|
262
|
+
creditsUsed: num("Credits the crawl consumed."),
|
|
263
|
+
expiresAt: str("When the crawl results expire."),
|
|
264
|
+
next: str("URL of the next page of results, when the data is paginated."),
|
|
265
|
+
url: str("The URL the crawl started from."),
|
|
266
|
+
data: unknown("Scraped documents collected by the crawl.")
|
|
267
|
+
}).describe("Crawl job state and the documents collected so far.");
|
|
268
|
+
var agentOutputSchema = z2.object({
|
|
269
|
+
...agentHints,
|
|
270
|
+
success,
|
|
271
|
+
error,
|
|
272
|
+
id: str("Agent job identifier, for `firecrawl_agent_status`."),
|
|
273
|
+
status: str("Job state at submission time."),
|
|
274
|
+
threadId: str("Research thread this job belongs to."),
|
|
275
|
+
threadTurn: num("Turn number of this job within its thread.")
|
|
276
|
+
}).describe("The queued research job. The result is read with `firecrawl_agent_status`.");
|
|
277
|
+
var agentStatusOutputSchema = z2.object({
|
|
278
|
+
...agentHints,
|
|
279
|
+
success,
|
|
280
|
+
error,
|
|
281
|
+
id: str("Agent job identifier."),
|
|
282
|
+
status: str("Job state: `processing` is non-terminal; `completed` and `failed` are terminal."),
|
|
283
|
+
progress: unknown("Progress information for a running job."),
|
|
284
|
+
data: unknown("Research result, present once the job has completed."),
|
|
285
|
+
creditsUsed: num("Credits the job consumed."),
|
|
286
|
+
sources: unknown("Sources the agent read."),
|
|
287
|
+
expiresAt: str("When the job result expires and can no longer be read."),
|
|
288
|
+
model: str("Agent model that ran the job."),
|
|
289
|
+
mode: str("Agent mode the job ran in."),
|
|
290
|
+
threadId: str("Research thread this job belongs to."),
|
|
291
|
+
threadTurn: num("Turn number of this job within its thread."),
|
|
292
|
+
message: unknown("The agent's reply; in chat mode, the short answer to a follow-up."),
|
|
293
|
+
suggestions: unknown("Follow-ups the agent offers; send one as the prompt of the next turn with this threadId."),
|
|
294
|
+
exchange: unknown(
|
|
295
|
+
"What the job did with Alexandria providers: onTermsRequired, paidCalls, creditsUsed, skippedProviders (gated providers that would have helped), and requiresAction (approvalId plus the terms/show and terms/accept calls, each provider digest string | null and always present; call accept only with the user's explicit consent, then continue the thread with exchange.approve: {approvalId})."
|
|
296
|
+
),
|
|
297
|
+
pendingApproval: unknown(
|
|
298
|
+
'Set when the job ended waiting on the caller. Answer it by calling firecrawl_agent with this threadId and exchange.approve or exchange.decline carrying its id. kind "terms" lists providers whose data terms need accepting; otherwise calls lists paid calls waiting for approval.'
|
|
299
|
+
)
|
|
300
|
+
}).describe("Progress or final result of a research agent job.");
|
|
301
|
+
var interactOutputSchema = z2.object({
|
|
302
|
+
...agentHints,
|
|
303
|
+
success,
|
|
304
|
+
error: unknown("Why the interaction or the session could not run."),
|
|
305
|
+
url: str("The URL the session was opened from, when opening failed."),
|
|
306
|
+
scrapeId: str("Session identifier; pass it to reuse the session or to `firecrawl_interact_stop`."),
|
|
307
|
+
result: unknown("Interaction result, when it is not an object of its own."),
|
|
308
|
+
output: unknown("Execution output."),
|
|
309
|
+
stdout: str("Standard output of the executed code."),
|
|
310
|
+
stderr: str("Standard error of the executed code."),
|
|
311
|
+
exitCode: num("Exit status of the executed code."),
|
|
312
|
+
data: unknown("Payload returned by the interaction."),
|
|
313
|
+
sessionUrl: str("URL for viewing the live session."),
|
|
314
|
+
liveUrl: str("URL for viewing the live session.")
|
|
315
|
+
}).describe("Result of a browser interaction, with the session identifier to continue or stop it.");
|
|
316
|
+
var interactStopOutputSchema = z2.object({
|
|
317
|
+
...agentHints,
|
|
318
|
+
success,
|
|
319
|
+
error,
|
|
320
|
+
message: str("Confirmation that the session was stopped."),
|
|
321
|
+
scrapeId: str("Session that was stopped.")
|
|
322
|
+
}).describe("Confirmation that the interact session was stopped.");
|
|
323
|
+
var parseOutputSchema = z2.object({
|
|
324
|
+
...agentHints,
|
|
325
|
+
success,
|
|
326
|
+
error,
|
|
327
|
+
warning,
|
|
328
|
+
mode: str("Which phase of the hosted flow produced this response."),
|
|
329
|
+
message: str("Guidance for the next call."),
|
|
330
|
+
data: unknown("Parsed document content; can include `data.metadata.scrapeId` for parse feedback."),
|
|
331
|
+
upload: z2.object({
|
|
332
|
+
command: str("Local command that performs the upload. It carries no Firecrawl API key."),
|
|
333
|
+
method: str("HTTP method for the upload."),
|
|
334
|
+
headers: unknown("Headers the upload request must send."),
|
|
335
|
+
fields: unknown("Form fields the upload request must send, for a POST upload."),
|
|
336
|
+
uploadUrl: str("URL to upload the local file to."),
|
|
337
|
+
uploadRef: str("Reference to pass back on the second call."),
|
|
338
|
+
expiresAt: str("When the upload URL expires."),
|
|
339
|
+
maxSizeBytes: num("Largest file the upload URL accepts.")
|
|
340
|
+
}).optional().describe("Hosted phase one: how to upload the local file."),
|
|
341
|
+
nextToolCall: unknown("Hosted phase one: the second `firecrawl_parse` call to make once the upload succeeds, as `{name, arguments}`."),
|
|
342
|
+
notes: z2.array(z2.string()).nullable().optional().describe("Hosted phase one: constraints on completing the upload flow."),
|
|
343
|
+
raw: str("Response body when it was not JSON.")
|
|
344
|
+
}).describe("Parsed document content, or the upload instructions for the hosted two-call flow.");
|
|
345
|
+
var deprecatedToolOutputSchema = z2.object({
|
|
346
|
+
code: str("Always `DEPRECATED_TOOL`."),
|
|
347
|
+
message: str("Why the tool is unavailable and what to call instead."),
|
|
348
|
+
replacement: unknown("The replacement tool, with instructions and example arguments."),
|
|
349
|
+
docs_url: str("Documentation for the replacement.")
|
|
350
|
+
}).describe("A pointer to the replacement tool. This entry point is deprecated and always errors.");
|
|
351
|
+
var monitorOutputSchema = z2.object({
|
|
352
|
+
...agentHints,
|
|
353
|
+
success,
|
|
354
|
+
error,
|
|
355
|
+
data: unknown("The monitor record: configuration, schedule, targets, and current state.")
|
|
356
|
+
}).describe("One monitor record.");
|
|
357
|
+
var monitorListOutputSchema = z2.object({
|
|
358
|
+
...agentHints,
|
|
359
|
+
success,
|
|
360
|
+
error,
|
|
361
|
+
data: unknown("One page of monitor records."),
|
|
362
|
+
pagination: unknown("Pagination metadata for the listing.")
|
|
363
|
+
}).describe("One page of monitors for the authenticated account.");
|
|
364
|
+
var monitorDeleteOutputSchema = z2.object({
|
|
365
|
+
...agentHints,
|
|
366
|
+
success,
|
|
367
|
+
error,
|
|
368
|
+
data: unknown("Deletion details, when the API returns any.")
|
|
369
|
+
}).describe("Deletion status for the monitor.");
|
|
370
|
+
var monitorRunOutputSchema = z2.object({
|
|
371
|
+
...agentHints,
|
|
372
|
+
success,
|
|
373
|
+
error,
|
|
374
|
+
data: unknown("The queued check.")
|
|
375
|
+
}).describe("The check queued outside the monitor schedule.");
|
|
376
|
+
var monitorChecksOutputSchema = z2.object({
|
|
377
|
+
...agentHints,
|
|
378
|
+
success,
|
|
379
|
+
error,
|
|
380
|
+
data: unknown("One page of check summaries."),
|
|
381
|
+
pagination: unknown("Pagination metadata for the listing.")
|
|
382
|
+
}).describe("One page of historical checks for the monitor.");
|
|
383
|
+
var monitorCheckOutputSchema = z2.object({
|
|
384
|
+
...agentHints,
|
|
385
|
+
success,
|
|
386
|
+
error,
|
|
387
|
+
data: unknown("The check and its page-level results: each page reports `same`, `new`, `changed`, `removed`, or `error`, with diffs and any goal judgment."),
|
|
388
|
+
pagination: unknown("Pagination metadata for the page results."),
|
|
389
|
+
next: str("URL of the next page of results, when more exist.")
|
|
390
|
+
}).describe("One monitor check with its page-level diff results.");
|
|
391
|
+
var paperSchema = z2.looseObject({
|
|
392
|
+
paperId: str("Canonical paper identifier."),
|
|
393
|
+
primaryId: str("Display identifier, ordered for citation and fetch use."),
|
|
394
|
+
ids: unknown("Source identifiers by namespace, such as `arxiv`, `doi`, or `pmid`."),
|
|
395
|
+
title: str("Paper title."),
|
|
396
|
+
abstract: str("Paper abstract."),
|
|
397
|
+
authors: unknown("Authors, as a comma-joined string or as `{name, affiliation}` entries."),
|
|
398
|
+
categories: z2.array(z2.string()).nullable().optional().describe("Paper categories, such as `cs.LG`."),
|
|
399
|
+
createdDate: str("Date the paper was first indexed or published."),
|
|
400
|
+
updateDate: str("Date the paper was last updated.")
|
|
401
|
+
});
|
|
402
|
+
var researchSearchOutputSchema = z2.object({
|
|
403
|
+
...agentHints,
|
|
404
|
+
results: z2.array(paperSchema).describe("Ranked papers, in the order the text block lists them.")
|
|
405
|
+
}).describe("Ranked papers with canonical IDs, titles, authors, and abstracts.");
|
|
406
|
+
var researchPaperOutputSchema = z2.object({
|
|
407
|
+
...agentHints,
|
|
408
|
+
paper: paperSchema.optional().describe("Canonical metadata for the requested paper, absent when it was not found.")
|
|
409
|
+
}).describe("Canonical metadata for one paper.");
|
|
410
|
+
var researchRelatedOutputSchema = z2.object({
|
|
411
|
+
...agentHints,
|
|
412
|
+
results: z2.array(paperSchema).describe("Ranked citation-graph candidates."),
|
|
413
|
+
poolSize: z2.number().describe("Number of candidates evaluated before ranking."),
|
|
414
|
+
note: str("Note about how the candidates were produced.")
|
|
415
|
+
}).describe("Ranked citation-graph candidates and the size of the evaluated pool.");
|
|
416
|
+
var researchReadOutputSchema = z2.object({
|
|
417
|
+
...agentHints,
|
|
418
|
+
passages: z2.array(z2.looseObject({ text: str("Passage text.") })).describe("In-body passages relevant to the question; empty when no full text is indexed.")
|
|
419
|
+
}).describe("Full-text passages from one paper.");
|
|
420
|
+
var developerSearchOutputSchema = z2.object({
|
|
421
|
+
...agentHints,
|
|
422
|
+
results: z2.array(
|
|
423
|
+
z2.looseObject({
|
|
424
|
+
id: str("Stable result id, such as `issue:owner/repo#123` or `doc:<hash>`; its prefix gives the source type."),
|
|
425
|
+
url: str("Source URL."),
|
|
426
|
+
title: str("Result title."),
|
|
427
|
+
passages: z2.array(z2.looseObject({ text: str("Matched passage, in markdown.") })).optional().describe("Matched passages.")
|
|
428
|
+
})
|
|
429
|
+
).describe("Ranked results, in the order the text block lists them.")
|
|
430
|
+
}).describe("Ranked developer-index results with their matched passages.");
|
|
431
|
+
var creditUsageOutputSchema = z2.object({
|
|
432
|
+
// Current view.
|
|
433
|
+
remainingCredits: num("Credits left in the current billing period; can exceed `planCredits` when extra credits were purchased or granted."),
|
|
434
|
+
planCredits: num("Credits included in the plan for the period."),
|
|
435
|
+
billingPeriodStart: str("Start of the current billing period; null when the billing provider reports no period."),
|
|
436
|
+
billingPeriodEnd: str("End of the current billing period; null when the billing provider reports no period."),
|
|
437
|
+
// Historical view.
|
|
438
|
+
success,
|
|
439
|
+
error,
|
|
440
|
+
periods: unknown("Historical periods sorted by start date, each with `startDate`, `endDate`, `creditsUsed`, and `apiKey` when broken down by key.")
|
|
441
|
+
}).describe("The team credit balance, or historical credit consumption by period.");
|
|
442
|
+
|
|
443
|
+
// src/alexandria.ts
|
|
444
|
+
import { z as z3 } from "zod";
|
|
93
445
|
var catalogueTypes = ["alexandria", "exchange"];
|
|
94
|
-
var searchSourceSchema =
|
|
95
|
-
|
|
96
|
-
|
|
446
|
+
var searchSourceSchema = z3.union([
|
|
447
|
+
z3.enum(["web", "images", "news", ...catalogueTypes]),
|
|
448
|
+
z3.object({ type: z3.enum(["web", "images", "news", ...catalogueTypes]) }).strict()
|
|
97
449
|
]);
|
|
98
450
|
function hasAlexandria(sources) {
|
|
99
451
|
return Array.isArray(sources) && sources.some(
|
|
@@ -116,25 +468,25 @@ function normalizeSearchSources(sources) {
|
|
|
116
468
|
function searchQueryIsValid(args2) {
|
|
117
469
|
return !!args2.query?.trim();
|
|
118
470
|
}
|
|
119
|
-
var findToolsSchema =
|
|
120
|
-
query:
|
|
121
|
-
urls:
|
|
122
|
-
|
|
471
|
+
var findToolsSchema = z3.object({
|
|
472
|
+
query: z3.string().trim().min(1).max(2e3).optional().describe("Semantic lookup of tools for the data you need. Selectors constrain the search."),
|
|
473
|
+
urls: z3.array(
|
|
474
|
+
z3.string().url().regex(/^https?:\/\//)
|
|
123
475
|
).min(1).max(100).optional(),
|
|
124
|
-
providers:
|
|
125
|
-
categories:
|
|
126
|
-
groups:
|
|
127
|
-
capabilities:
|
|
128
|
-
level:
|
|
129
|
-
expand:
|
|
130
|
-
limit:
|
|
131
|
-
offset:
|
|
476
|
+
providers: z3.array(z3.string().min(1)).min(1).max(50).optional().describe("Provider IDs returned by category browsing. Lists compact tools by default."),
|
|
477
|
+
categories: z3.array(z3.string().min(1)).min(1).max(50).optional().describe("Category IDs returned by an empty call. Lists providers by default."),
|
|
478
|
+
groups: z3.array(z3.string().min(1)).min(1).max(50).optional().describe("Optional group IDs for explicit group browsing."),
|
|
479
|
+
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."),
|
|
480
|
+
level: z3.enum(["categories", "providers", "groups", "tools"]).optional(),
|
|
481
|
+
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.'),
|
|
482
|
+
limit: z3.number().int().min(1).max(100).optional(),
|
|
483
|
+
offset: z3.number().int().nonnegative().optional()
|
|
132
484
|
}).strict();
|
|
133
485
|
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
486
|
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 = '
|
|
487
|
+
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.';
|
|
136
488
|
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;
|
|
137
|
-
var ALEXANDRIA_CONTRACT_GUIDANCE = "
|
|
489
|
+
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.";
|
|
138
490
|
function findToolsOptions(args2) {
|
|
139
491
|
const level = args2.level ?? (args2.query || args2.capabilities?.length || args2.providers?.length || args2.groups?.length || args2.urls?.length ? "tools" : args2.categories?.length ? "providers" : "categories");
|
|
140
492
|
if (level === "categories" && (args2.query || args2.providers?.length || args2.categories?.length || args2.groups?.length || args2.capabilities?.length || args2.urls?.length || args2.expand !== void 0)) {
|
|
@@ -155,7 +507,8 @@ function withFindToolsNavigation(envelope) {
|
|
|
155
507
|
const link = (next) => next?.provider === "firecrawl" && next?.capability === "find-tools" && next.options ? { name: "firecrawl_find_tools", arguments: next.options } : void 0;
|
|
156
508
|
for (const item of page.items) {
|
|
157
509
|
if (page.level === "providers" && link(item.next)) {
|
|
158
|
-
const
|
|
510
|
+
const selectors = { ...item.next.options };
|
|
511
|
+
delete selectors.expand;
|
|
159
512
|
item.next = { ...item.next, options: { ...selectors, level: "tools" } };
|
|
160
513
|
}
|
|
161
514
|
const nextTool = link(item.next);
|
|
@@ -164,7 +517,7 @@ function withFindToolsNavigation(envelope) {
|
|
|
164
517
|
if (link(page.next)) page.nextTool = link(page.next);
|
|
165
518
|
return envelope;
|
|
166
519
|
}
|
|
167
|
-
var ALEXANDRIA_SEARCH_INSTRUCTIONS = 'Authenticated search combines web results, semantic tool summaries and domain matches. Use sources: ["alexandria"] for semantic tools only
|
|
520
|
+
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.';
|
|
168
521
|
|
|
169
522
|
// src/alexandria-output.ts
|
|
170
523
|
var INLINE_TOKEN_BUDGET = 2e4;
|
|
@@ -231,7 +584,7 @@ async function alexandriaOutput(payload, calls, post, recoveryRequestId, extras
|
|
|
231
584
|
}
|
|
232
585
|
|
|
233
586
|
// src/developer.ts
|
|
234
|
-
import { z as
|
|
587
|
+
import { z as z4 } from "zod";
|
|
235
588
|
|
|
236
589
|
// src/origin.ts
|
|
237
590
|
import { createRequire } from "module";
|
|
@@ -297,12 +650,13 @@ Search an index of public repositories, GitHub issues, merged pull requests, rep
|
|
|
297
650
|
|
|
298
651
|
Returns ranked results with an ID, source type, URL, title, and the matched passages in markdown.
|
|
299
652
|
`,
|
|
300
|
-
|
|
301
|
-
|
|
653
|
+
outputSchema: developerSearchOutputSchema,
|
|
654
|
+
parameters: z4.object({
|
|
655
|
+
query: z4.string().min(1).describe(
|
|
302
656
|
"Natural-language developer question or search phrase, including the library, error message, or API involved when relevant."
|
|
303
657
|
),
|
|
304
|
-
k:
|
|
305
|
-
skills:
|
|
658
|
+
k: z4.number().int().min(1).max(100).optional().describe("Number of ranked results to return (default 10)."),
|
|
659
|
+
skills: z4.enum(["only"]).optional().describe('Set to "only" to search only agent-skill files.')
|
|
306
660
|
}),
|
|
307
661
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
308
662
|
const { query, k, skills } = args2;
|
|
@@ -312,7 +666,8 @@ Returns ranked results with an ID, source type, URL, title, and the matched pass
|
|
|
312
666
|
if (skills != null) params.append("skills", skills);
|
|
313
667
|
const client = getClient2(session);
|
|
314
668
|
const res = await client.http.get(`${BASE}?${params.toString()}`, originHeaders(requestOrigin(mcpClient, session)));
|
|
315
|
-
|
|
669
|
+
const results = res.data?.results ?? [];
|
|
670
|
+
return withAgentHints(withStructured(fmtDeveloper(results), { results }), res.data, true);
|
|
316
671
|
}
|
|
317
672
|
});
|
|
318
673
|
}
|
|
@@ -332,17 +687,19 @@ function extractSingleTrustedClientIp(rawForwardedFor) {
|
|
|
332
687
|
}
|
|
333
688
|
|
|
334
689
|
// src/monitor.ts
|
|
335
|
-
import { z as
|
|
690
|
+
import { z as z5 } from "zod";
|
|
336
691
|
|
|
337
692
|
// src/session-credential.ts
|
|
338
693
|
import { createHmac } from "crypto";
|
|
339
694
|
var managedOAuthApiKey = /* @__PURE__ */ Symbol("firecrawlManagedOAuthApiKey");
|
|
340
695
|
var CoreHttpError = class extends Error {
|
|
341
696
|
status;
|
|
342
|
-
|
|
697
|
+
agent_hints;
|
|
698
|
+
constructor(message, status, agentHints2) {
|
|
343
699
|
super(message);
|
|
344
700
|
this.name = "CoreHttpError";
|
|
345
701
|
this.status = status;
|
|
702
|
+
if (agentHints2 !== void 0) this.agent_hints = agentHints2;
|
|
346
703
|
}
|
|
347
704
|
};
|
|
348
705
|
var CredentialValidationUnavailableError = class extends Error {
|
|
@@ -354,11 +711,12 @@ var CredentialValidationUnavailableError = class extends Error {
|
|
|
354
711
|
}
|
|
355
712
|
};
|
|
356
713
|
function credentialValidationUnavailable(diagnostics) {
|
|
357
|
-
const { aborted, elapsedMs, reason, resource, status } = diagnostics;
|
|
714
|
+
const { aborted, edgeMitigation: edgeMitigation2, elapsedMs, reason, resource, status } = diagnostics;
|
|
358
715
|
console.error(
|
|
359
716
|
"[MCP_CREDENTIAL_VALIDATION]",
|
|
360
717
|
JSON.stringify({
|
|
361
718
|
aborted: aborted ?? null,
|
|
719
|
+
edge_mitigation: edgeMitigation2 ?? null,
|
|
362
720
|
elapsed_ms: elapsedMs ?? null,
|
|
363
721
|
introspect_status: status ?? null,
|
|
364
722
|
reason,
|
|
@@ -442,7 +800,10 @@ async function monitorRequest(session, origin, path2, init = {}) {
|
|
|
442
800
|
const s = qs.toString();
|
|
443
801
|
if (s) url += `?${s}`;
|
|
444
802
|
}
|
|
445
|
-
const headers =
|
|
803
|
+
const headers = {
|
|
804
|
+
...originHeaders(origin),
|
|
805
|
+
...AGENT_HINTS_HEADERS
|
|
806
|
+
};
|
|
446
807
|
if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
|
|
447
808
|
if (init.body !== void 0) headers["Content-Type"] = "application/json";
|
|
448
809
|
const response = await fetch(url, {
|
|
@@ -453,15 +814,15 @@ async function monitorRequest(session, origin, path2, init = {}) {
|
|
|
453
814
|
const payload = await response.json().catch(() => ({}));
|
|
454
815
|
if (!response.ok || payload?.success === false) {
|
|
455
816
|
const message = payload?.error || `HTTP ${response.status}: ${response.statusText || "Request failed"}`;
|
|
456
|
-
throw new CoreHttpError(message, response.status);
|
|
817
|
+
throw new CoreHttpError(message, response.status, readAgentHints(payload));
|
|
457
818
|
}
|
|
458
819
|
return payload;
|
|
459
820
|
}
|
|
460
|
-
function
|
|
461
|
-
return
|
|
821
|
+
function monitorResult(data) {
|
|
822
|
+
return withAgentHints(structuredText(data), data);
|
|
462
823
|
}
|
|
463
|
-
var pageStatusSchema =
|
|
464
|
-
var checkStatusSchema =
|
|
824
|
+
var pageStatusSchema = z5.enum(["same", "new", "changed", "removed", "error"]);
|
|
825
|
+
var checkStatusSchema = z5.enum([
|
|
465
826
|
"queued",
|
|
466
827
|
"running",
|
|
467
828
|
"completed",
|
|
@@ -554,22 +915,23 @@ Create a recurring scrape, crawl, or search monitor that compares each check wit
|
|
|
554
915
|
|
|
555
916
|
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.
|
|
556
917
|
`,
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
918
|
+
outputSchema: monitorOutputSchema,
|
|
919
|
+
parameters: z5.object({
|
|
920
|
+
body: z5.record(z5.string(), z5.any()).optional(),
|
|
921
|
+
page: z5.string().optional(),
|
|
922
|
+
pages: z5.array(z5.string()).optional(),
|
|
923
|
+
queries: z5.array(z5.string()).optional(),
|
|
924
|
+
searchWindow: z5.enum(["5m", "15m", "1h", "6h", "24h", "7d"]).optional(),
|
|
925
|
+
maxResults: z5.number().int().min(1).max(50).optional(),
|
|
926
|
+
includeDomains: z5.array(z5.string()).optional(),
|
|
927
|
+
excludeDomains: z5.array(z5.string()).optional(),
|
|
928
|
+
goal: z5.string().optional(),
|
|
929
|
+
name: z5.string().optional(),
|
|
930
|
+
scheduleText: z5.string().optional(),
|
|
931
|
+
timezone: z5.string().optional(),
|
|
932
|
+
email: z5.string().optional(),
|
|
933
|
+
includeDiffs: z5.boolean().optional(),
|
|
934
|
+
webhookUrl: z5.string().optional()
|
|
573
935
|
}),
|
|
574
936
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
575
937
|
const body = buildMonitorCreateBody(args2);
|
|
@@ -583,7 +945,7 @@ In the simple form, a \`goal\` is required. If \`queries\` contains one or more
|
|
|
583
945
|
body
|
|
584
946
|
}
|
|
585
947
|
);
|
|
586
|
-
return
|
|
948
|
+
return monitorResult(res);
|
|
587
949
|
}
|
|
588
950
|
});
|
|
589
951
|
server2.addTool({
|
|
@@ -600,9 +962,10 @@ In the simple form, a \`goal\` is required. If \`queries\` contains one or more
|
|
|
600
962
|
description: `
|
|
601
963
|
List monitors for the authenticated account with optional pagination controls. Returns one page of monitor records and pagination metadata.
|
|
602
964
|
`,
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
965
|
+
outputSchema: monitorListOutputSchema,
|
|
966
|
+
parameters: z5.object({
|
|
967
|
+
limit: z5.number().int().positive().optional(),
|
|
968
|
+
offset: z5.number().int().nonnegative().optional()
|
|
606
969
|
}),
|
|
607
970
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
608
971
|
const { limit, offset } = args2;
|
|
@@ -614,7 +977,7 @@ List monitors for the authenticated account with optional pagination controls. R
|
|
|
614
977
|
query: { limit, offset }
|
|
615
978
|
}
|
|
616
979
|
);
|
|
617
|
-
return
|
|
980
|
+
return monitorResult(res);
|
|
618
981
|
}
|
|
619
982
|
});
|
|
620
983
|
server2.addTool({
|
|
@@ -631,7 +994,8 @@ List monitors for the authenticated account with optional pagination controls. R
|
|
|
631
994
|
description: `
|
|
632
995
|
Retrieve one monitor by ID, including its configuration and current state. This does not run or modify the monitor.
|
|
633
996
|
`,
|
|
634
|
-
|
|
997
|
+
outputSchema: monitorOutputSchema,
|
|
998
|
+
parameters: z5.object({ id: z5.string() }),
|
|
635
999
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
636
1000
|
const { id } = args2;
|
|
637
1001
|
const res = await monitorRequest(
|
|
@@ -639,7 +1003,7 @@ Retrieve one monitor by ID, including its configuration and current state. This
|
|
|
639
1003
|
requestOrigin(mcpClient, session),
|
|
640
1004
|
`/monitor/${encodeURIComponent(id)}`
|
|
641
1005
|
);
|
|
642
|
-
return
|
|
1006
|
+
return monitorResult(res);
|
|
643
1007
|
}
|
|
644
1008
|
});
|
|
645
1009
|
server2.addTool({
|
|
@@ -658,9 +1022,10 @@ Patch an existing monitor by ID. The body can change its name, active/paused sta
|
|
|
658
1022
|
|
|
659
1023
|
Returns the updated monitor.
|
|
660
1024
|
`,
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
1025
|
+
outputSchema: monitorOutputSchema,
|
|
1026
|
+
parameters: z5.object({
|
|
1027
|
+
id: z5.string(),
|
|
1028
|
+
body: z5.record(z5.string(), z5.any())
|
|
664
1029
|
}),
|
|
665
1030
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
666
1031
|
const { id, body } = args2;
|
|
@@ -670,7 +1035,7 @@ Returns the updated monitor.
|
|
|
670
1035
|
`/monitor/${encodeURIComponent(id)}`,
|
|
671
1036
|
{ method: "PATCH", body }
|
|
672
1037
|
);
|
|
673
|
-
return
|
|
1038
|
+
return monitorResult(res);
|
|
674
1039
|
}
|
|
675
1040
|
});
|
|
676
1041
|
server2.addTool({
|
|
@@ -687,7 +1052,8 @@ Returns the updated monitor.
|
|
|
687
1052
|
description: `
|
|
688
1053
|
Permanently delete a monitor by ID and stop its future schedule. This operation cannot be undone and returns deletion status.
|
|
689
1054
|
`,
|
|
690
|
-
|
|
1055
|
+
outputSchema: monitorDeleteOutputSchema,
|
|
1056
|
+
parameters: z5.object({ id: z5.string() }),
|
|
691
1057
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
692
1058
|
const { id } = args2;
|
|
693
1059
|
log.info("Deleting monitor", { id });
|
|
@@ -697,7 +1063,7 @@ Permanently delete a monitor by ID and stop its future schedule. This operation
|
|
|
697
1063
|
`/monitor/${encodeURIComponent(id)}`,
|
|
698
1064
|
{ method: "DELETE" }
|
|
699
1065
|
);
|
|
700
|
-
return
|
|
1066
|
+
return monitorResult(res);
|
|
701
1067
|
}
|
|
702
1068
|
});
|
|
703
1069
|
server2.addTool({
|
|
@@ -714,7 +1080,8 @@ Permanently delete a monitor by ID and stop its future schedule. This operation
|
|
|
714
1080
|
description: `
|
|
715
1081
|
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.
|
|
716
1082
|
`,
|
|
717
|
-
|
|
1083
|
+
outputSchema: monitorRunOutputSchema,
|
|
1084
|
+
parameters: z5.object({ id: z5.string() }),
|
|
718
1085
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
719
1086
|
const { id } = args2;
|
|
720
1087
|
const res = await monitorRequest(
|
|
@@ -723,7 +1090,7 @@ Queue an immediate check for a monitor outside its normal schedule. This starts
|
|
|
723
1090
|
`/monitor/${encodeURIComponent(id)}/run`,
|
|
724
1091
|
{ method: "POST" }
|
|
725
1092
|
);
|
|
726
|
-
return
|
|
1093
|
+
return monitorResult(res);
|
|
727
1094
|
}
|
|
728
1095
|
});
|
|
729
1096
|
server2.addTool({
|
|
@@ -740,10 +1107,11 @@ Queue an immediate check for a monitor outside its normal schedule. This starts
|
|
|
740
1107
|
description: `
|
|
741
1108
|
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.
|
|
742
1109
|
`,
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
1110
|
+
outputSchema: monitorChecksOutputSchema,
|
|
1111
|
+
parameters: z5.object({
|
|
1112
|
+
id: z5.string(),
|
|
1113
|
+
limit: z5.number().int().positive().optional(),
|
|
1114
|
+
offset: z5.number().int().nonnegative().optional(),
|
|
747
1115
|
status: checkStatusSchema.optional()
|
|
748
1116
|
}),
|
|
749
1117
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
@@ -754,7 +1122,7 @@ List historical checks for a monitor, optionally filtered by status and bounded
|
|
|
754
1122
|
`/monitor/${encodeURIComponent(id)}/checks`,
|
|
755
1123
|
{ query: { limit, offset, status } }
|
|
756
1124
|
);
|
|
757
|
-
return
|
|
1125
|
+
return monitorResult(res);
|
|
758
1126
|
}
|
|
759
1127
|
});
|
|
760
1128
|
server2.addTool({
|
|
@@ -773,11 +1141,12 @@ Retrieve one monitor check and its page-level results, optionally filtered by pa
|
|
|
773
1141
|
|
|
774
1142
|
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.
|
|
775
1143
|
`,
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
1144
|
+
outputSchema: monitorCheckOutputSchema,
|
|
1145
|
+
parameters: z5.object({
|
|
1146
|
+
id: z5.string(),
|
|
1147
|
+
checkId: z5.string(),
|
|
1148
|
+
limit: z5.number().int().positive().optional(),
|
|
1149
|
+
skip: z5.number().int().nonnegative().optional(),
|
|
781
1150
|
pageStatus: pageStatusSchema.optional()
|
|
782
1151
|
}),
|
|
783
1152
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
@@ -788,15 +1157,18 @@ Markdown tracking returns a unified text diff, JSON tracking returns field paths
|
|
|
788
1157
|
`/monitor/${encodeURIComponent(id)}/checks/${encodeURIComponent(checkId)}`,
|
|
789
1158
|
{ query: { limit, skip, status: pageStatus } }
|
|
790
1159
|
);
|
|
791
|
-
return
|
|
1160
|
+
return monitorResult(res);
|
|
792
1161
|
}
|
|
793
1162
|
});
|
|
794
1163
|
}
|
|
795
1164
|
|
|
796
1165
|
// src/research.ts
|
|
797
|
-
import { z as
|
|
1166
|
+
import { z as z6 } from "zod";
|
|
798
1167
|
import { UserError } from "fastmcp";
|
|
799
1168
|
var BASE2 = "/v2/search/research";
|
|
1169
|
+
function researchHeaders(origin) {
|
|
1170
|
+
return { ...originHeaders(origin), ...AGENT_HINTS_HEADERS };
|
|
1171
|
+
}
|
|
800
1172
|
function appendParam(params, key, value) {
|
|
801
1173
|
if (value == null) return;
|
|
802
1174
|
if (Array.isArray(value)) {
|
|
@@ -910,21 +1282,22 @@ Several distinct framings of the same question surface different papers than a s
|
|
|
910
1282
|
|
|
911
1283
|
Returns ranked papers with canonical IDs, titles, authors, and abstracts.
|
|
912
1284
|
`,
|
|
913
|
-
|
|
914
|
-
|
|
1285
|
+
outputSchema: researchSearchOutputSchema,
|
|
1286
|
+
parameters: z6.object({
|
|
1287
|
+
query: z6.string().min(1).describe(
|
|
915
1288
|
"Natural-language research topic or question, including methods, systems, conditions, populations, interventions, or outcomes when relevant."
|
|
916
1289
|
),
|
|
917
|
-
k:
|
|
918
|
-
authors:
|
|
1290
|
+
k: z6.number().int().min(1).max(500).optional().describe("Number of ranked papers to return (default 40)."),
|
|
1291
|
+
authors: z6.array(z6.string()).optional().describe(
|
|
919
1292
|
"Author substring filter(s); ALL must match (case-insensitive)."
|
|
920
1293
|
),
|
|
921
|
-
categories:
|
|
1294
|
+
categories: z6.array(z6.string()).optional().describe(
|
|
922
1295
|
"Paper category filter(s) (e.g. `cs.LG`); ALL provided values must match."
|
|
923
1296
|
),
|
|
924
|
-
from:
|
|
1297
|
+
from: z6.string().optional().describe(
|
|
925
1298
|
"Inclusive lower bound on created/updated date (`YYYY-MM-DD`)."
|
|
926
1299
|
),
|
|
927
|
-
to:
|
|
1300
|
+
to: z6.string().optional().describe(
|
|
928
1301
|
"Inclusive upper bound on created/updated date (`YYYY-MM-DD`)."
|
|
929
1302
|
)
|
|
930
1303
|
}),
|
|
@@ -940,9 +1313,10 @@ Returns ranked papers with canonical IDs, titles, authors, and abstracts.
|
|
|
940
1313
|
const client = getClient2(session);
|
|
941
1314
|
const res = await client.http.get(
|
|
942
1315
|
withQuery(`${BASE2}/papers`, params),
|
|
943
|
-
|
|
1316
|
+
researchHeaders(requestOrigin(mcpClient, session))
|
|
944
1317
|
);
|
|
945
|
-
|
|
1318
|
+
const results = res.data?.results ?? [];
|
|
1319
|
+
return withAgentHints(withStructured(fmtHits(results), { results }), res.data, true);
|
|
946
1320
|
}
|
|
947
1321
|
});
|
|
948
1322
|
server2.addTool({
|
|
@@ -959,8 +1333,9 @@ Returns ranked papers with canonical IDs, titles, authors, and abstracts.
|
|
|
959
1333
|
description: `
|
|
960
1334
|
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.
|
|
961
1335
|
`,
|
|
962
|
-
|
|
963
|
-
|
|
1336
|
+
outputSchema: researchPaperOutputSchema,
|
|
1337
|
+
parameters: z6.object({
|
|
1338
|
+
paperId: z6.string().min(1).describe(
|
|
964
1339
|
"Canonical paperId or primaryId such as `arxiv:1706.03762`, `pmcid:PMC12530322`, `pmid:40953549`, or `doi:10.1016/j.neunet.2025.108095`."
|
|
965
1340
|
)
|
|
966
1341
|
}),
|
|
@@ -969,9 +1344,13 @@ Retrieve canonical metadata for one paper ID, such as an arXiv, PMC, PMID, or DO
|
|
|
969
1344
|
const client = getClient2(session);
|
|
970
1345
|
const res = await client.http.get(
|
|
971
1346
|
`${BASE2}/papers/${encodeURIComponent(paperId)}`,
|
|
972
|
-
|
|
1347
|
+
researchHeaders(requestOrigin(mcpClient, session))
|
|
973
1348
|
);
|
|
974
|
-
|
|
1349
|
+
const paper = res.data?.paper;
|
|
1350
|
+
return withAgentHints(withStructured(
|
|
1351
|
+
fmtPaperMetadata(paper),
|
|
1352
|
+
paper ? { paper } : {}
|
|
1353
|
+
), res.data, true);
|
|
975
1354
|
}
|
|
976
1355
|
});
|
|
977
1356
|
server2.addTool({
|
|
@@ -990,12 +1369,13 @@ Find citation-graph candidates from one to ten \`seed_ids\`; the first ID is the
|
|
|
990
1369
|
|
|
991
1370
|
Returns ranked candidates and the evaluated pool size.
|
|
992
1371
|
`,
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1372
|
+
outputSchema: researchRelatedOutputSchema,
|
|
1373
|
+
parameters: z6.object({
|
|
1374
|
+
seed_ids: z6.array(z6.string()).min(1).max(10),
|
|
1375
|
+
intent: z6.string().min(1),
|
|
1376
|
+
mode: z6.enum(["similar", "citers", "references"]).optional(),
|
|
1377
|
+
k: z6.number().int().min(1).max(500).optional(),
|
|
1378
|
+
rerank: z6.boolean().optional().describe("Apply an additional rerank over the fused candidates.")
|
|
999
1379
|
}),
|
|
1000
1380
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
1001
1381
|
const { seed_ids, intent, mode, k, rerank } = args2;
|
|
@@ -1012,12 +1392,17 @@ Returns ranked candidates and the evaluated pool size.
|
|
|
1012
1392
|
`${BASE2}/papers/${encodeURIComponent(primary)}/similar`,
|
|
1013
1393
|
params
|
|
1014
1394
|
),
|
|
1015
|
-
|
|
1395
|
+
researchHeaders(requestOrigin(mcpClient, session))
|
|
1016
1396
|
);
|
|
1397
|
+
const results = res.data?.results ?? [];
|
|
1398
|
+
const poolSize = res.data?.poolSize ?? 0;
|
|
1017
1399
|
const note = res.data?.note ? `
|
|
1018
1400
|
note: ${res.data.note}` : "";
|
|
1019
|
-
return
|
|
1020
|
-
(
|
|
1401
|
+
return withAgentHints(withStructured(
|
|
1402
|
+
`${fmtHits(results)}
|
|
1403
|
+
(poolSize=${poolSize})${note}`,
|
|
1404
|
+
{ results, poolSize, ...res.data?.note ? { note: res.data.note } : {} }
|
|
1405
|
+
), res.data, true);
|
|
1021
1406
|
}
|
|
1022
1407
|
});
|
|
1023
1408
|
server2.addTool({
|
|
@@ -1036,12 +1421,13 @@ Retrieve in-body passages from one paper that are relevant to a specific questio
|
|
|
1036
1421
|
|
|
1037
1422
|
Returns matching passages or a notice when full text is unavailable.
|
|
1038
1423
|
`,
|
|
1039
|
-
|
|
1040
|
-
|
|
1424
|
+
outputSchema: researchReadOutputSchema,
|
|
1425
|
+
parameters: z6.object({
|
|
1426
|
+
paperId: z6.string().min(1).describe(
|
|
1041
1427
|
"Canonical paperId or primaryId such as `arxiv:1706.03762`, `pmcid:PMC12530322`, `pmid:40953549`, or `doi:10.1016/j.neunet.2025.108095`."
|
|
1042
1428
|
),
|
|
1043
|
-
question:
|
|
1044
|
-
k:
|
|
1429
|
+
question: z6.string().min(1),
|
|
1430
|
+
k: z6.number().int().min(1).max(50).optional().describe("Number of passages to return (default 4).")
|
|
1045
1431
|
}),
|
|
1046
1432
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
1047
1433
|
const { paperId, question, k } = args2;
|
|
@@ -1051,10 +1437,13 @@ Returns matching passages or a notice when full text is unavailable.
|
|
|
1051
1437
|
const client = getClient2(session);
|
|
1052
1438
|
const res = await client.http.get(
|
|
1053
1439
|
withQuery(`${BASE2}/papers/${encodeURIComponent(paperId)}`, params),
|
|
1054
|
-
|
|
1440
|
+
researchHeaders(requestOrigin(mcpClient, session))
|
|
1055
1441
|
);
|
|
1056
1442
|
const passages = res.data?.passages ?? [];
|
|
1057
|
-
return
|
|
1443
|
+
return withAgentHints(withStructured(
|
|
1444
|
+
passages.length ? passages.map((p) => p.text).join("\n---\n") : "(no full-text passages available for this paper)",
|
|
1445
|
+
{ passages }
|
|
1446
|
+
), res.data, true);
|
|
1058
1447
|
}
|
|
1059
1448
|
});
|
|
1060
1449
|
server2.addTool({
|
|
@@ -1068,9 +1457,10 @@ Returns matching passages or a notice when full text is unavailable.
|
|
|
1068
1457
|
description: `
|
|
1069
1458
|
Deprecated compatibility entry point. Use firecrawl_developer_search for GitHub issues, pull requests, and READMEs, plus code documentation, returned as matched passages.
|
|
1070
1459
|
`,
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1460
|
+
outputSchema: deprecatedToolOutputSchema,
|
|
1461
|
+
parameters: z6.object({
|
|
1462
|
+
query: z6.string().min(1),
|
|
1463
|
+
k: z6.number().int().min(1).max(100).optional()
|
|
1074
1464
|
}),
|
|
1075
1465
|
canList: () => false,
|
|
1076
1466
|
beforeValidate: () => {
|
|
@@ -1089,10 +1479,7 @@ Deprecated compatibility entry point. Use firecrawl_developer_search for GitHub
|
|
|
1089
1479
|
}
|
|
1090
1480
|
|
|
1091
1481
|
// src/usage.ts
|
|
1092
|
-
import { z as
|
|
1093
|
-
function asText2(data) {
|
|
1094
|
-
return JSON.stringify(data, null, 2);
|
|
1095
|
-
}
|
|
1482
|
+
import { z as z7 } from "zod";
|
|
1096
1483
|
function assertSuccessful(status, response, operation) {
|
|
1097
1484
|
if (status === 200 && response.success) return;
|
|
1098
1485
|
throw new Error(response.error || `Failed to ${operation}`);
|
|
@@ -1113,11 +1500,12 @@ The current view returns \`remainingCredits\`, \`planCredits\`, \`billingPeriodS
|
|
|
1113
1500
|
|
|
1114
1501
|
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.
|
|
1115
1502
|
`,
|
|
1116
|
-
|
|
1117
|
-
|
|
1503
|
+
outputSchema: creditUsageOutputSchema,
|
|
1504
|
+
parameters: z7.object({
|
|
1505
|
+
view: z7.enum(["current", "historical"]).optional().describe(
|
|
1118
1506
|
"Select current balance or historical monthly usage. Defaults to current."
|
|
1119
1507
|
),
|
|
1120
|
-
byApiKey:
|
|
1508
|
+
byApiKey: z7.boolean().optional().describe(
|
|
1121
1509
|
'Break historical usage down by API key. When view is omitted, true selects the historical view; it cannot be combined with view "current".'
|
|
1122
1510
|
)
|
|
1123
1511
|
}),
|
|
@@ -1142,7 +1530,7 @@ The historical view returns periods sorted by start date with \`startDate\`, \`e
|
|
|
1142
1530
|
response2.data,
|
|
1143
1531
|
"get historical credit usage"
|
|
1144
1532
|
);
|
|
1145
|
-
return
|
|
1533
|
+
return structuredText(response2.data);
|
|
1146
1534
|
}
|
|
1147
1535
|
const response = await client.http.get(
|
|
1148
1536
|
"/v2/team/credit-usage",
|
|
@@ -1150,7 +1538,7 @@ The historical view returns periods sorted by start date with \`startDate\`, \`e
|
|
|
1150
1538
|
);
|
|
1151
1539
|
assertSuccessful(response.status, response.data, "get credit usage");
|
|
1152
1540
|
const data = response.data.data ?? response.data;
|
|
1153
|
-
return
|
|
1541
|
+
return structuredText({
|
|
1154
1542
|
remainingCredits: data.remainingCredits ?? data.remaining_credits ?? 0,
|
|
1155
1543
|
planCredits: data.planCredits ?? data.plan_credits,
|
|
1156
1544
|
billingPeriodStart: data.billingPeriodStart ?? data.billing_period_start ?? null,
|
|
@@ -1165,6 +1553,68 @@ function escapeWWWAuthenticateValue(value) {
|
|
|
1165
1553
|
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
1166
1554
|
}
|
|
1167
1555
|
|
|
1556
|
+
// src/introspection-cache.ts
|
|
1557
|
+
import { createHash } from "crypto";
|
|
1558
|
+
function createIntrospectionCache(options) {
|
|
1559
|
+
const now = options.now ?? Date.now;
|
|
1560
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1561
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
1562
|
+
function store(key, value) {
|
|
1563
|
+
const at = now();
|
|
1564
|
+
const ttl = options.ttlMs(value, at);
|
|
1565
|
+
if (!(ttl > 0)) return;
|
|
1566
|
+
entries.delete(key);
|
|
1567
|
+
entries.set(key, { expiresAt: at + ttl, value });
|
|
1568
|
+
while (entries.size > options.maxEntries) {
|
|
1569
|
+
const oldest = entries.keys().next().value;
|
|
1570
|
+
if (oldest === void 0) break;
|
|
1571
|
+
entries.delete(oldest);
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
return {
|
|
1575
|
+
async get(parts, load) {
|
|
1576
|
+
const key = createHash("sha256").update(parts.join("\0")).digest("hex");
|
|
1577
|
+
const hit = entries.get(key);
|
|
1578
|
+
if (hit) {
|
|
1579
|
+
entries.delete(key);
|
|
1580
|
+
if (hit.expiresAt > now()) {
|
|
1581
|
+
entries.set(key, hit);
|
|
1582
|
+
return hit.value;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
const pending = inflight.get(key);
|
|
1586
|
+
if (pending) return pending;
|
|
1587
|
+
const request = load().then(
|
|
1588
|
+
(value) => {
|
|
1589
|
+
inflight.delete(key);
|
|
1590
|
+
store(key, value);
|
|
1591
|
+
return value;
|
|
1592
|
+
},
|
|
1593
|
+
(error2) => {
|
|
1594
|
+
inflight.delete(key);
|
|
1595
|
+
throw error2;
|
|
1596
|
+
}
|
|
1597
|
+
);
|
|
1598
|
+
inflight.set(key, request);
|
|
1599
|
+
return request;
|
|
1600
|
+
},
|
|
1601
|
+
clear() {
|
|
1602
|
+
entries.clear();
|
|
1603
|
+
},
|
|
1604
|
+
get size() {
|
|
1605
|
+
return entries.size;
|
|
1606
|
+
}
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
var INTROSPECTION_ACTIVE_TTL_MS = 6e4;
|
|
1610
|
+
var INTROSPECTION_INACTIVE_TTL_MS = 1e4;
|
|
1611
|
+
function introspectionTtlMs(value, now, activeTtlMs = INTROSPECTION_ACTIVE_TTL_MS, inactiveTtlMs = INTROSPECTION_INACTIVE_TTL_MS) {
|
|
1612
|
+
if (!value.active) return inactiveTtlMs;
|
|
1613
|
+
if (value.exp === void 0) return activeTtlMs;
|
|
1614
|
+
if (typeof value.exp !== "number" || !Number.isFinite(value.exp)) return 0;
|
|
1615
|
+
return Math.min(activeTtlMs, value.exp * 1e3 - now);
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1168
1618
|
// src/index.ts
|
|
1169
1619
|
dotenv.config({ debug: false, quiet: true });
|
|
1170
1620
|
var require2 = createRequire2(import.meta.url);
|
|
@@ -1248,11 +1698,11 @@ function getOAuthProtectedResourceMetadataUrl(profile) {
|
|
|
1248
1698
|
const base = `${resource.origin}/.well-known/oauth-protected-resource`;
|
|
1249
1699
|
return profile.id === "full" ? base : `${base}${resource.pathname}`;
|
|
1250
1700
|
}
|
|
1251
|
-
function createOAuthChallengeResponse(
|
|
1701
|
+
function createOAuthChallengeResponse(error2, profile, details = {}) {
|
|
1252
1702
|
if (!isMcpOAuthEnabled()) {
|
|
1253
1703
|
return void 0;
|
|
1254
1704
|
}
|
|
1255
|
-
const errorMessage =
|
|
1705
|
+
const errorMessage = error2 instanceof Error ? error2.message : String(error2 || "Unauthorized");
|
|
1256
1706
|
const wwwAuthenticateDescription = errorMessage.replace(/[\r\n]+/g, " ").trim();
|
|
1257
1707
|
const wwwAuthenticate = [
|
|
1258
1708
|
...profile.advertiseOAuth ? [
|
|
@@ -1304,8 +1754,8 @@ function createInvalidOAuthRecoveryResponse(recovery) {
|
|
|
1304
1754
|
);
|
|
1305
1755
|
}
|
|
1306
1756
|
var CREDENTIAL_VALIDATION_RETRY_AFTER_SECONDS = 5;
|
|
1307
|
-
function createCredentialValidationUnavailableResponse(
|
|
1308
|
-
return new Response(
|
|
1757
|
+
function createCredentialValidationUnavailableResponse(error2) {
|
|
1758
|
+
return new Response(error2.message, {
|
|
1309
1759
|
headers: {
|
|
1310
1760
|
"Content-Type": "text/plain; charset=utf-8",
|
|
1311
1761
|
"Retry-After": String(CREDENTIAL_VALIDATION_RETRY_AFTER_SECONDS)
|
|
@@ -1355,7 +1805,40 @@ function credentialMetadata(data) {
|
|
|
1355
1805
|
resource: typeof data.aud === "string" ? data.aud : void 0
|
|
1356
1806
|
};
|
|
1357
1807
|
}
|
|
1358
|
-
|
|
1808
|
+
function introspectionActiveTtlMs() {
|
|
1809
|
+
const raw = normalizeHeader(
|
|
1810
|
+
process.env.FIRECRAWL_OAUTH_INTROSPECT_CACHE_TTL_MS
|
|
1811
|
+
);
|
|
1812
|
+
if (raw === void 0) return INTROSPECTION_ACTIVE_TTL_MS;
|
|
1813
|
+
const parsed = Number(raw);
|
|
1814
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : INTROSPECTION_ACTIVE_TTL_MS;
|
|
1815
|
+
}
|
|
1816
|
+
var introspectionCache = createIntrospectionCache({
|
|
1817
|
+
maxEntries: 5e4,
|
|
1818
|
+
ttlMs: (value, now) => {
|
|
1819
|
+
const activeTtlMs = introspectionActiveTtlMs();
|
|
1820
|
+
if (activeTtlMs === 0) return 0;
|
|
1821
|
+
return introspectionTtlMs(
|
|
1822
|
+
value,
|
|
1823
|
+
now,
|
|
1824
|
+
activeTtlMs,
|
|
1825
|
+
Math.min(activeTtlMs, INTROSPECTION_INACTIVE_TTL_MS)
|
|
1826
|
+
);
|
|
1827
|
+
}
|
|
1828
|
+
});
|
|
1829
|
+
function introspectToken(token, expectedResource) {
|
|
1830
|
+
return introspectionCache.get(
|
|
1831
|
+
[token, expectedResource],
|
|
1832
|
+
() => fetchIntrospection(token, expectedResource)
|
|
1833
|
+
);
|
|
1834
|
+
}
|
|
1835
|
+
var EDGE_MITIGATIONS = /* @__PURE__ */ new Set(["deny", "challenge", "rate_limit"]);
|
|
1836
|
+
function edgeMitigation(response) {
|
|
1837
|
+
const value = response.headers.get("x-vercel-mitigated")?.trim().toLowerCase();
|
|
1838
|
+
if (!value) return void 0;
|
|
1839
|
+
return EDGE_MITIGATIONS.has(value) ? value : "other";
|
|
1840
|
+
}
|
|
1841
|
+
async function fetchIntrospection(token, expectedResource) {
|
|
1359
1842
|
const introspectionSecret = getOAuthIntrospectionSecret();
|
|
1360
1843
|
if (!introspectionSecret) {
|
|
1361
1844
|
throw credentialValidationUnavailable({
|
|
@@ -1394,6 +1877,7 @@ async function introspectToken(token, expectedResource) {
|
|
|
1394
1877
|
}
|
|
1395
1878
|
if (!response.ok) {
|
|
1396
1879
|
throw credentialValidationUnavailable({
|
|
1880
|
+
edgeMitigation: edgeMitigation(response),
|
|
1397
1881
|
elapsedMs: elapsedMs(),
|
|
1398
1882
|
reason: "introspect_http_status",
|
|
1399
1883
|
resource: expectedResource,
|
|
@@ -1601,13 +2085,13 @@ function makeAuthenticate(profile) {
|
|
|
1601
2085
|
session
|
|
1602
2086
|
);
|
|
1603
2087
|
return session;
|
|
1604
|
-
}).catch((
|
|
2088
|
+
}).catch((error2) => {
|
|
1605
2089
|
emitSearchCompanionAuthTelemetry(profile, request, "rejected");
|
|
1606
2090
|
emitLegacyKeyPathTelemetry(profile, request, "rejected");
|
|
1607
|
-
if (
|
|
1608
|
-
throw createInvalidCredentialResponse(
|
|
2091
|
+
if (error2 instanceof InvalidFirecrawlCredentialError) {
|
|
2092
|
+
throw createInvalidCredentialResponse(error2);
|
|
1609
2093
|
}
|
|
1610
|
-
if (
|
|
2094
|
+
if (error2 instanceof InvalidOAuthCredentialError) {
|
|
1611
2095
|
const recovery = invalidOAuthRecoveryPayload();
|
|
1612
2096
|
const oauthChallenge2 = createOAuthChallengeResponse(
|
|
1613
2097
|
new Error(recovery.message),
|
|
@@ -1616,15 +2100,15 @@ function makeAuthenticate(profile) {
|
|
|
1616
2100
|
);
|
|
1617
2101
|
throw oauthChallenge2 ?? createInvalidOAuthRecoveryResponse(recovery);
|
|
1618
2102
|
}
|
|
1619
|
-
if (
|
|
1620
|
-
throw createCredentialValidationUnavailableResponse(
|
|
2103
|
+
if (error2 instanceof CredentialValidationUnavailableError) {
|
|
2104
|
+
throw createCredentialValidationUnavailableResponse(error2);
|
|
1621
2105
|
}
|
|
1622
2106
|
const shouldChallenge = requestShouldReceiveOAuthChallenge(request, profile);
|
|
1623
|
-
const oauthChallenge = shouldChallenge ? createOAuthChallengeResponse(
|
|
2107
|
+
const oauthChallenge = shouldChallenge ? createOAuthChallengeResponse(error2, profile) : void 0;
|
|
1624
2108
|
if (oauthChallenge) {
|
|
1625
2109
|
throw oauthChallenge;
|
|
1626
2110
|
}
|
|
1627
|
-
throw
|
|
2111
|
+
throw error2;
|
|
1628
2112
|
});
|
|
1629
2113
|
if (request) {
|
|
1630
2114
|
request[authResultByRequest] = authResult;
|
|
@@ -1644,30 +2128,30 @@ function removeEmptyTopLevel(obj) {
|
|
|
1644
2128
|
}
|
|
1645
2129
|
return out;
|
|
1646
2130
|
}
|
|
1647
|
-
var searchDomainSchema =
|
|
2131
|
+
var searchDomainSchema = z8.string().trim().toLowerCase().min(1).max(253).regex(
|
|
1648
2132
|
/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/,
|
|
1649
2133
|
"Domain must be a valid hostname without protocol or path"
|
|
1650
2134
|
);
|
|
1651
2135
|
var searchToolBaseFields = {
|
|
1652
|
-
toolDetail:
|
|
1653
|
-
query:
|
|
1654
|
-
domainTools:
|
|
2136
|
+
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."),
|
|
2137
|
+
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."),
|
|
2138
|
+
domainTools: z8.boolean().optional().describe(
|
|
1655
2139
|
"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."
|
|
1656
2140
|
),
|
|
1657
|
-
highlights:
|
|
2141
|
+
highlights: z8.boolean().optional().describe(
|
|
1658
2142
|
"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."
|
|
1659
2143
|
),
|
|
1660
|
-
limit:
|
|
1661
|
-
tbs:
|
|
1662
|
-
filter:
|
|
1663
|
-
location:
|
|
1664
|
-
includeDomains:
|
|
1665
|
-
excludeDomains:
|
|
1666
|
-
sources:
|
|
1667
|
-
categories:
|
|
2144
|
+
limit: z8.number().int().min(1).max(100).optional(),
|
|
2145
|
+
tbs: z8.string().optional(),
|
|
2146
|
+
filter: z8.string().optional(),
|
|
2147
|
+
location: z8.string().optional(),
|
|
2148
|
+
includeDomains: z8.array(searchDomainSchema).optional().describe("Hostnames to restrict results to. Mutually exclusive with excludeDomains."),
|
|
2149
|
+
excludeDomains: z8.array(searchDomainSchema).optional().describe("Hostnames to leave out of results. Mutually exclusive with includeDomains."),
|
|
2150
|
+
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.'),
|
|
2151
|
+
categories: z8.array(z8.enum(["research", "pdf", "developer"])).optional().describe(
|
|
1668
2152
|
'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`.'
|
|
1669
2153
|
),
|
|
1670
|
-
enterprise:
|
|
2154
|
+
enterprise: z8.array(z8.enum(["default", "anon", "zdr"])).optional()
|
|
1671
2155
|
};
|
|
1672
2156
|
function searchDomainsAreExclusive(args2) {
|
|
1673
2157
|
return !(args2.includeDomains?.length && args2.excludeDomains?.length);
|
|
@@ -1675,15 +2159,15 @@ function searchDomainsAreExclusive(args2) {
|
|
|
1675
2159
|
var SEARCH_DOMAINS_CONFLICT_MESSAGE = "includeDomains and excludeDomains cannot both be specified";
|
|
1676
2160
|
var EXCHANGE_KEY_REQUIRED_MESSAGE = "Alexandria requires an API key on a team with Alexandria access";
|
|
1677
2161
|
var EXCHANGE_MAX_CALLS = 10;
|
|
1678
|
-
var exchangeCallSchema =
|
|
1679
|
-
provider:
|
|
1680
|
-
capability:
|
|
2162
|
+
var exchangeCallSchema = z8.object({
|
|
2163
|
+
provider: z8.string().min(1).describe('Provider slug, e.g. "fred".'),
|
|
2164
|
+
capability: z8.string().min(1).describe(
|
|
1681
2165
|
'Capability address as returned by search or discover, e.g. "series/observations".'
|
|
1682
2166
|
),
|
|
1683
|
-
version:
|
|
1684
|
-
options:
|
|
2167
|
+
version: z8.string().trim().min(1).max(128).optional().describe("Optional published workflow version. Omit to use the latest version."),
|
|
2168
|
+
options: z8.record(z8.string(), z8.any()).optional().describe("Capability options as declared by its contract.")
|
|
1685
2169
|
});
|
|
1686
|
-
var exchangeCallsSchema =
|
|
2170
|
+
var exchangeCallsSchema = z8.array(exchangeCallSchema).min(1).max(EXCHANGE_MAX_CALLS);
|
|
1687
2171
|
function assertExchangeCredential(session) {
|
|
1688
2172
|
if (hasCredential(session)) return;
|
|
1689
2173
|
const payload = {
|
|
@@ -1707,7 +2191,7 @@ function termsRequiredAction(body) {
|
|
|
1707
2191
|
url: action.url
|
|
1708
2192
|
};
|
|
1709
2193
|
}
|
|
1710
|
-
function termsRequiredError(action, context) {
|
|
2194
|
+
function termsRequiredError(action, context, hints) {
|
|
1711
2195
|
const requestId = context.requestId;
|
|
1712
2196
|
const retryIdentity = requestId ? ` and requestId ${requestId}` : "";
|
|
1713
2197
|
const message = `Alexandria provider terms required. An organization admin must accept the ${action.terms} provider's terms (version ${action.version}) before this request can run.`;
|
|
@@ -1722,6 +2206,7 @@ Do not retry until acceptance is confirmed.`,
|
|
|
1722
2206
|
code: TERMS_REQUIRED_CODE,
|
|
1723
2207
|
status: 403,
|
|
1724
2208
|
message,
|
|
2209
|
+
...hints ? { agent_hints: hints } : {},
|
|
1725
2210
|
...requestId ? { requestId } : {},
|
|
1726
2211
|
requiresAction: action,
|
|
1727
2212
|
nextTool: {
|
|
@@ -1749,27 +2234,29 @@ Do not retry until acceptance is confirmed.`,
|
|
|
1749
2234
|
}
|
|
1750
2235
|
);
|
|
1751
2236
|
}
|
|
1752
|
-
function throwIfTermsRequired(
|
|
1753
|
-
const source =
|
|
2237
|
+
function throwIfTermsRequired(error2, context) {
|
|
2238
|
+
const source = error2;
|
|
1754
2239
|
const action = termsRequiredAction(source?.response?.data ?? source?.details);
|
|
1755
|
-
if (action)
|
|
2240
|
+
if (action)
|
|
2241
|
+
throw termsRequiredError(action, context, readErrorAgentHints(error2));
|
|
1756
2242
|
}
|
|
1757
2243
|
async function relayTermsRequired(run, context) {
|
|
1758
2244
|
try {
|
|
1759
2245
|
return await run();
|
|
1760
|
-
} catch (
|
|
1761
|
-
throwIfTermsRequired(
|
|
1762
|
-
throw
|
|
2246
|
+
} catch (error2) {
|
|
2247
|
+
throwIfTermsRequired(error2, context);
|
|
2248
|
+
throw error2;
|
|
1763
2249
|
}
|
|
1764
2250
|
}
|
|
1765
2251
|
async function relayExchangeError(run, context) {
|
|
1766
2252
|
try {
|
|
1767
2253
|
return await run();
|
|
1768
|
-
} catch (
|
|
1769
|
-
throwIfTermsRequired(
|
|
1770
|
-
const response =
|
|
1771
|
-
if (!response || response.status === 401) throw
|
|
2254
|
+
} catch (error2) {
|
|
2255
|
+
throwIfTermsRequired(error2, context);
|
|
2256
|
+
const response = error2?.response;
|
|
2257
|
+
if (!response || response.status === 401) throw error2;
|
|
1772
2258
|
const data = response.data;
|
|
2259
|
+
const hints = readErrorAgentHints(error2);
|
|
1773
2260
|
const message = typeof data?.error === "string" ? data.error : `Alexandria request failed (HTTP ${response.status})`;
|
|
1774
2261
|
const disabledProvider = response.status === 403 ? context.providers?.find((provider) => message === `Access to ${provider} is disabled for this organization.`) : void 0;
|
|
1775
2262
|
if (disabledProvider) {
|
|
@@ -1779,6 +2266,7 @@ async function relayExchangeError(run, context) {
|
|
|
1779
2266
|
code: typeof data?.code === "string" ? data.code : "exchange_error",
|
|
1780
2267
|
status: 403,
|
|
1781
2268
|
message,
|
|
2269
|
+
...hints ? { agent_hints: hints } : {},
|
|
1782
2270
|
nextTool: {
|
|
1783
2271
|
name: "firecrawl_scrape",
|
|
1784
2272
|
arguments: {
|
|
@@ -1792,6 +2280,7 @@ async function relayExchangeError(run, context) {
|
|
|
1792
2280
|
code: typeof data?.code === "string" ? data.code : "exchange_error",
|
|
1793
2281
|
status: response.status,
|
|
1794
2282
|
message,
|
|
2283
|
+
...hints ? { agent_hints: hints } : {},
|
|
1795
2284
|
...typeof data?.chargeId === "string" ? { chargeId: data.chargeId } : {}
|
|
1796
2285
|
});
|
|
1797
2286
|
}
|
|
@@ -1799,14 +2288,14 @@ async function relayExchangeError(run, context) {
|
|
|
1799
2288
|
async function postSearchWithFallback(client, body, implicitTools) {
|
|
1800
2289
|
try {
|
|
1801
2290
|
return await client.http.post("/v2/search", body);
|
|
1802
|
-
} catch (
|
|
1803
|
-
const response =
|
|
2291
|
+
} catch (error2) {
|
|
2292
|
+
const response = error2?.response;
|
|
1804
2293
|
const unavailable = [
|
|
1805
2294
|
"Provider discovery requires access and does not support zero data retention.",
|
|
1806
2295
|
"This endpoint is not enabled for this team.",
|
|
1807
2296
|
"Exchange is not enabled for this team."
|
|
1808
2297
|
].includes(response?.data?.error);
|
|
1809
|
-
if (!implicitTools || response?.status !== 403 || !unavailable) throw
|
|
2298
|
+
if (!implicitTools || response?.status !== 403 || !unavailable) throw error2;
|
|
1810
2299
|
return client.http.post("/v2/search", {
|
|
1811
2300
|
...body,
|
|
1812
2301
|
sources: ["web"],
|
|
@@ -1845,10 +2334,9 @@ var ConsoleLogger = class {
|
|
|
1845
2334
|
var openAiAppsChallengeToken = normalizeHeader(
|
|
1846
2335
|
process.env.OPENAI_APPS_CHALLENGE_TOKEN
|
|
1847
2336
|
);
|
|
1848
|
-
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}
|
|
1849
|
-
${ALEXANDRIA_FEEDBACK_GUIDANCE}`;
|
|
2337
|
+
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. 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. Provide only the required inputs and account for stated network or external side effects.`;
|
|
1850
2338
|
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.`;
|
|
1851
|
-
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.
|
|
2339
|
+
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.`;
|
|
1852
2340
|
var SEARCH_PROFILE_TOOLS = /* @__PURE__ */ new Set([
|
|
1853
2341
|
"firecrawl_search",
|
|
1854
2342
|
"firecrawl_developer_search",
|
|
@@ -1992,17 +2480,31 @@ function invalidOAuthRecoveryPayload() {
|
|
|
1992
2480
|
message: INVALID_OAUTH_MESSAGE
|
|
1993
2481
|
});
|
|
1994
2482
|
}
|
|
1995
|
-
function isCoreCredentialRejection(
|
|
1996
|
-
if (!(
|
|
1997
|
-
const candidate =
|
|
2483
|
+
function isCoreCredentialRejection(error2) {
|
|
2484
|
+
if (!(error2 instanceof Error)) return false;
|
|
2485
|
+
const candidate = error2;
|
|
1998
2486
|
return candidate.status === 401 || candidate.response?.status === 401;
|
|
1999
2487
|
}
|
|
2000
2488
|
async function runWithCredentialRecovery(run, requestId, session) {
|
|
2001
2489
|
try {
|
|
2002
2490
|
return await run();
|
|
2003
|
-
} catch (
|
|
2004
|
-
if (session?.authType !== "api-key" || !isCoreCredentialRejection(
|
|
2005
|
-
|
|
2491
|
+
} catch (error2) {
|
|
2492
|
+
if (session?.authType !== "api-key" || !isCoreCredentialRejection(error2)) {
|
|
2493
|
+
const hints = readErrorAgentHints(error2);
|
|
2494
|
+
if (hints) {
|
|
2495
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
2496
|
+
throw new UserError2(
|
|
2497
|
+
hints.length ? `${message}
|
|
2498
|
+
|
|
2499
|
+
${agentHintsText(hints)}` : message,
|
|
2500
|
+
{
|
|
2501
|
+
...error2 instanceof UserError2 ? error2.extras : {},
|
|
2502
|
+
error: message,
|
|
2503
|
+
agent_hints: hints
|
|
2504
|
+
}
|
|
2505
|
+
);
|
|
2506
|
+
}
|
|
2507
|
+
throw error2;
|
|
2006
2508
|
}
|
|
2007
2509
|
const payload = recoveryPayload("CREDENTIAL_INVALID", requestId);
|
|
2008
2510
|
throw new UserError2(String(payload.message), payload);
|
|
@@ -2055,7 +2557,7 @@ function deprecatedExtractPayload() {
|
|
|
2055
2557
|
docs_url: "https://docs.firecrawl.dev/developer-guides/usage-guides/choosing-the-data-extractor"
|
|
2056
2558
|
};
|
|
2057
2559
|
}
|
|
2058
|
-
function emitActionLog(toolName, status, session,
|
|
2560
|
+
function emitActionLog(toolName, status, session, error2, requestId = randomUUID(), code) {
|
|
2059
2561
|
if (process.env.CLOUD_SERVICE !== "true") return;
|
|
2060
2562
|
const payload = {
|
|
2061
2563
|
team_id: session?.teamId,
|
|
@@ -2067,7 +2569,7 @@ function emitActionLog(toolName, status, session, error, requestId = randomUUID(
|
|
|
2067
2569
|
status,
|
|
2068
2570
|
request_id: requestId,
|
|
2069
2571
|
resource: primaryProfile.resourceUrl,
|
|
2070
|
-
...
|
|
2572
|
+
...error2 ? { error_class: error2 instanceof Error ? error2.name : typeof error2 } : {},
|
|
2071
2573
|
...code ? { code } : {}
|
|
2072
2574
|
};
|
|
2073
2575
|
console.error("[MCP_ACTION]", JSON.stringify(payload));
|
|
@@ -2087,6 +2589,26 @@ function emitActionLog(toolName, status, session, error, requestId = randomUUID(
|
|
|
2087
2589
|
signal: AbortSignal.timeout(1500)
|
|
2088
2590
|
}).catch(() => void 0);
|
|
2089
2591
|
}
|
|
2592
|
+
var AGENT_HINT_LOG_MAX_CHARS = 1e3;
|
|
2593
|
+
function emitAgentHintsLog(toolName, status, hints, session, requestId) {
|
|
2594
|
+
if (process.env.CLOUD_SERVICE !== "true" || !hints) return;
|
|
2595
|
+
console.error(
|
|
2596
|
+
"[MCP_AGENT_HINTS]",
|
|
2597
|
+
JSON.stringify({
|
|
2598
|
+
tool_name: toolName,
|
|
2599
|
+
status,
|
|
2600
|
+
request_id: requestId,
|
|
2601
|
+
auth_type: session.authType ?? "none",
|
|
2602
|
+
// The companion search server shares this process and wrapper.
|
|
2603
|
+
profile: session.profile ?? primaryProfile.id,
|
|
2604
|
+
hint_count: hints.length,
|
|
2605
|
+
hints: hints.map((hint) => hint.slice(0, AGENT_HINT_LOG_MAX_CHARS))
|
|
2606
|
+
})
|
|
2607
|
+
);
|
|
2608
|
+
}
|
|
2609
|
+
function resultAgentHints(result) {
|
|
2610
|
+
return result && typeof result === "object" ? readAgentHints(result.structuredContent) : void 0;
|
|
2611
|
+
}
|
|
2090
2612
|
function guardHostedTool(tool, { logActions }) {
|
|
2091
2613
|
const keylessTool = KEYLESS_TOOL_NAMES.has(tool.name);
|
|
2092
2614
|
const execute = tool.execute;
|
|
@@ -2156,20 +2678,35 @@ function guardHostedTool(tool, { logActions }) {
|
|
|
2156
2678
|
if (logActions) emitActionLog(tool.name, "error", invocationSession, new UserError2(String(payload.message), payload), requestId, code);
|
|
2157
2679
|
throw new UserError2(String(payload.message), payload);
|
|
2158
2680
|
}
|
|
2159
|
-
const runTool = () =>
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2681
|
+
const runTool = async () => {
|
|
2682
|
+
try {
|
|
2683
|
+
const result = await runWithCredentialRecovery(
|
|
2684
|
+
() => execute(args2, invocationContext),
|
|
2685
|
+
requestId,
|
|
2686
|
+
invocationSession
|
|
2687
|
+
);
|
|
2688
|
+
emitAgentHintsLog(
|
|
2689
|
+
tool.name,
|
|
2690
|
+
result?.isError ? "error" : "success",
|
|
2691
|
+
resultAgentHints(result),
|
|
2692
|
+
invocationSession,
|
|
2693
|
+
requestId
|
|
2694
|
+
);
|
|
2695
|
+
return result;
|
|
2696
|
+
} catch (error2) {
|
|
2697
|
+
emitAgentHintsLog(tool.name, "error", readErrorAgentHints(error2), invocationSession, requestId);
|
|
2698
|
+
throw error2;
|
|
2699
|
+
}
|
|
2700
|
+
};
|
|
2164
2701
|
if (!logActions) return runTool();
|
|
2165
2702
|
emitActionLog(tool.name, "started", invocationSession, void 0, requestId);
|
|
2166
2703
|
try {
|
|
2167
2704
|
const result = await runTool();
|
|
2168
2705
|
emitActionLog(tool.name, "success", invocationSession, void 0, requestId);
|
|
2169
2706
|
return result;
|
|
2170
|
-
} catch (
|
|
2171
|
-
emitActionLog(tool.name, "error", invocationSession,
|
|
2172
|
-
throw
|
|
2707
|
+
} catch (error2) {
|
|
2708
|
+
emitActionLog(tool.name, "error", invocationSession, error2, requestId);
|
|
2709
|
+
throw error2;
|
|
2173
2710
|
}
|
|
2174
2711
|
}
|
|
2175
2712
|
};
|
|
@@ -2215,6 +2752,7 @@ server.getApp().get("/ready", (context) => {
|
|
|
2215
2752
|
}
|
|
2216
2753
|
return missing.length ? context.json({ ok: false, missing }, 503) : context.json({ ok: true }, 200);
|
|
2217
2754
|
});
|
|
2755
|
+
var warnedMissingAgentHintsInterceptor = false;
|
|
2218
2756
|
function createClient(apiKey) {
|
|
2219
2757
|
const config = {
|
|
2220
2758
|
...process.env.FIRECRAWL_API_URL && {
|
|
@@ -2224,7 +2762,44 @@ function createClient(apiKey) {
|
|
|
2224
2762
|
if (apiKey) {
|
|
2225
2763
|
config.apiKey = apiKey;
|
|
2226
2764
|
}
|
|
2227
|
-
|
|
2765
|
+
const client = new FirecrawlApp(config);
|
|
2766
|
+
const axiosInstance = client.http?.instance;
|
|
2767
|
+
if (axiosInstance?.interceptors?.request?.use) {
|
|
2768
|
+
axiosInstance.interceptors.request.use((request) => {
|
|
2769
|
+
if (typeof request.headers?.set === "function") {
|
|
2770
|
+
request.headers.set("X-Firecrawl-Agent-Hints", "true");
|
|
2771
|
+
} else {
|
|
2772
|
+
request.headers = { ...request.headers ?? {}, ...AGENT_HINTS_HEADERS };
|
|
2773
|
+
}
|
|
2774
|
+
return request;
|
|
2775
|
+
});
|
|
2776
|
+
} else if (!warnedMissingAgentHintsInterceptor) {
|
|
2777
|
+
warnedMissingAgentHintsInterceptor = true;
|
|
2778
|
+
console.warn(
|
|
2779
|
+
"[firecrawl-mcp] SDK request interceptor unavailable; API agent hints may be absent."
|
|
2780
|
+
);
|
|
2781
|
+
}
|
|
2782
|
+
return client;
|
|
2783
|
+
}
|
|
2784
|
+
async function sdkResultWithAgentHints(client, execute) {
|
|
2785
|
+
const responseInterceptors = client.http?.instance?.interceptors?.response;
|
|
2786
|
+
if (!responseInterceptors?.use) return execute();
|
|
2787
|
+
let hints;
|
|
2788
|
+
const interceptor = responseInterceptors.use((response) => {
|
|
2789
|
+
hints = readAgentHints(response?.data) ?? hints;
|
|
2790
|
+
return response;
|
|
2791
|
+
});
|
|
2792
|
+
try {
|
|
2793
|
+
const result = await execute();
|
|
2794
|
+
return preserveAgentHints(result, { agent_hints: hints });
|
|
2795
|
+
} catch (error2) {
|
|
2796
|
+
if (hints && error2 && typeof error2 === "object" && !readErrorAgentHints(error2)) {
|
|
2797
|
+
error2.agent_hints = hints;
|
|
2798
|
+
}
|
|
2799
|
+
throw error2;
|
|
2800
|
+
} finally {
|
|
2801
|
+
responseInterceptors.eject?.(interceptor);
|
|
2802
|
+
}
|
|
2228
2803
|
}
|
|
2229
2804
|
var SAFE_MODE = process.env.CLOUD_SERVICE === "true";
|
|
2230
2805
|
function getClient(session) {
|
|
@@ -2253,20 +2828,21 @@ function getClient(session) {
|
|
|
2253
2828
|
reason: "delegated_credential_unavailable"
|
|
2254
2829
|
});
|
|
2255
2830
|
}
|
|
2256
|
-
config.headers
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2831
|
+
if (typeof config.headers?.set === "function") {
|
|
2832
|
+
config.headers.set("Authorization", `Bearer ${credential}`);
|
|
2833
|
+
} else {
|
|
2834
|
+
config.headers = {
|
|
2835
|
+
...config.headers ?? {},
|
|
2836
|
+
Authorization: `Bearer ${credential}`
|
|
2837
|
+
};
|
|
2838
|
+
}
|
|
2260
2839
|
return config;
|
|
2261
2840
|
});
|
|
2262
2841
|
return client;
|
|
2263
2842
|
}
|
|
2264
|
-
function
|
|
2843
|
+
function asText(data) {
|
|
2265
2844
|
return JSON.stringify(data, null, 2);
|
|
2266
2845
|
}
|
|
2267
|
-
function compactText(data) {
|
|
2268
|
-
return JSON.stringify(data);
|
|
2269
|
-
}
|
|
2270
2846
|
var safeActionTypes = ["wait", "screenshot", "scroll", "scrape"];
|
|
2271
2847
|
var otherActions = [
|
|
2272
2848
|
"click",
|
|
@@ -2332,10 +2908,10 @@ function transformScrapeParams(args2) {
|
|
|
2332
2908
|
delete out.pdfOptions;
|
|
2333
2909
|
return out;
|
|
2334
2910
|
}
|
|
2335
|
-
var scrapeParamsSchema =
|
|
2336
|
-
url:
|
|
2337
|
-
formats:
|
|
2338
|
-
|
|
2911
|
+
var scrapeParamsSchema = z8.object({
|
|
2912
|
+
url: z8.string().url(),
|
|
2913
|
+
formats: z8.array(
|
|
2914
|
+
z8.enum([
|
|
2339
2915
|
"markdown",
|
|
2340
2916
|
"html",
|
|
2341
2917
|
"rawHtml",
|
|
@@ -2349,87 +2925,93 @@ var scrapeParamsSchema = z7.object({
|
|
|
2349
2925
|
"audio"
|
|
2350
2926
|
])
|
|
2351
2927
|
).optional(),
|
|
2352
|
-
jsonOptions:
|
|
2353
|
-
prompt:
|
|
2354
|
-
schema:
|
|
2928
|
+
jsonOptions: z8.object({
|
|
2929
|
+
prompt: z8.string().optional(),
|
|
2930
|
+
schema: z8.record(z8.string(), z8.any()).optional()
|
|
2355
2931
|
}).optional(),
|
|
2356
|
-
queryOptions:
|
|
2357
|
-
prompt:
|
|
2358
|
-
mode:
|
|
2932
|
+
queryOptions: z8.object({
|
|
2933
|
+
prompt: z8.string().max(1e4),
|
|
2934
|
+
mode: z8.enum(["directQuote", "freeform"]).default("freeform")
|
|
2359
2935
|
}).optional(),
|
|
2360
|
-
screenshotOptions:
|
|
2361
|
-
fullPage:
|
|
2362
|
-
quality:
|
|
2363
|
-
viewport:
|
|
2936
|
+
screenshotOptions: z8.object({
|
|
2937
|
+
fullPage: z8.boolean().optional(),
|
|
2938
|
+
quality: z8.number().optional(),
|
|
2939
|
+
viewport: z8.object({ width: z8.number(), height: z8.number() }).optional()
|
|
2364
2940
|
}).optional(),
|
|
2365
|
-
parsers:
|
|
2366
|
-
pdfOptions:
|
|
2367
|
-
maxPages:
|
|
2941
|
+
parsers: z8.array(z8.enum(["pdf"])).optional(),
|
|
2942
|
+
pdfOptions: z8.object({
|
|
2943
|
+
maxPages: z8.number().int().min(1).max(1e4).optional()
|
|
2368
2944
|
}).optional(),
|
|
2369
|
-
onlyMainContent:
|
|
2370
|
-
redactPII:
|
|
2371
|
-
includeTags:
|
|
2372
|
-
excludeTags:
|
|
2373
|
-
waitFor:
|
|
2945
|
+
onlyMainContent: z8.boolean().optional(),
|
|
2946
|
+
redactPII: z8.boolean().optional(),
|
|
2947
|
+
includeTags: z8.array(z8.string()).optional(),
|
|
2948
|
+
excludeTags: z8.array(z8.string()).optional(),
|
|
2949
|
+
waitFor: z8.number().optional(),
|
|
2374
2950
|
...SAFE_MODE ? {} : {
|
|
2375
|
-
actions:
|
|
2376
|
-
|
|
2377
|
-
type:
|
|
2378
|
-
selector:
|
|
2379
|
-
milliseconds:
|
|
2380
|
-
text:
|
|
2381
|
-
key:
|
|
2382
|
-
direction:
|
|
2383
|
-
script:
|
|
2384
|
-
fullPage:
|
|
2951
|
+
actions: z8.array(
|
|
2952
|
+
z8.object({
|
|
2953
|
+
type: z8.enum(allowedActionTypes),
|
|
2954
|
+
selector: z8.string().optional(),
|
|
2955
|
+
milliseconds: z8.number().optional(),
|
|
2956
|
+
text: z8.string().optional(),
|
|
2957
|
+
key: z8.string().optional(),
|
|
2958
|
+
direction: z8.enum(["up", "down"]).optional(),
|
|
2959
|
+
script: z8.string().optional(),
|
|
2960
|
+
fullPage: z8.boolean().optional()
|
|
2385
2961
|
})
|
|
2386
2962
|
).optional()
|
|
2387
2963
|
},
|
|
2388
|
-
mobile:
|
|
2389
|
-
skipTlsVerification:
|
|
2390
|
-
removeBase64Images:
|
|
2391
|
-
location:
|
|
2392
|
-
country:
|
|
2393
|
-
languages:
|
|
2964
|
+
mobile: z8.boolean().optional(),
|
|
2965
|
+
skipTlsVerification: z8.boolean().optional(),
|
|
2966
|
+
removeBase64Images: z8.boolean().optional(),
|
|
2967
|
+
location: z8.object({
|
|
2968
|
+
country: z8.string().optional(),
|
|
2969
|
+
languages: z8.array(z8.string()).optional()
|
|
2394
2970
|
}).optional(),
|
|
2395
|
-
storeInCache:
|
|
2396
|
-
zeroDataRetention:
|
|
2397
|
-
maxAge:
|
|
2398
|
-
lockdown:
|
|
2399
|
-
proxy:
|
|
2400
|
-
profile:
|
|
2401
|
-
name:
|
|
2402
|
-
saveChanges:
|
|
2971
|
+
storeInCache: z8.boolean().optional(),
|
|
2972
|
+
zeroDataRetention: z8.boolean().optional(),
|
|
2973
|
+
maxAge: z8.number().optional(),
|
|
2974
|
+
lockdown: z8.boolean().optional(),
|
|
2975
|
+
proxy: z8.enum(["basic", "stealth", "enhanced", "auto"]).optional(),
|
|
2976
|
+
profile: z8.object({
|
|
2977
|
+
name: z8.string(),
|
|
2978
|
+
saveChanges: z8.boolean().optional()
|
|
2403
2979
|
}).optional()
|
|
2404
2980
|
});
|
|
2981
|
+
var ALEXANDRIA_IGNORED_SCRAPE_OPTIONS = /* @__PURE__ */ new Set(["toolDetail", "domainTools"]);
|
|
2405
2982
|
var scrapeToolParamsSchema = scrapeParamsSchema.extend({
|
|
2406
|
-
url:
|
|
2407
|
-
timeout:
|
|
2408
|
-
requestId:
|
|
2409
|
-
"
|
|
2983
|
+
url: z8.string().url().optional(),
|
|
2984
|
+
timeout: z8.number().int().positive().optional().describe("Execution timeout in milliseconds."),
|
|
2985
|
+
requestId: z8.string().regex(/^[A-Za-z0-9._:-]{1,128}$/).optional().describe(
|
|
2986
|
+
"Idempotency key bound to one Alexandria execution payload. Generated when omitted and returned with the result."
|
|
2410
2987
|
),
|
|
2411
|
-
alexandria:
|
|
2412
|
-
"
|
|
2988
|
+
alexandria: z8.union([exchangeCallSchema, exchangeCallsSchema]).optional().describe(
|
|
2989
|
+
"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. Requires an API key on a team with Alexandria enabled. Some providers require accepted terms; blocked requests return the applicable requirements."
|
|
2413
2990
|
),
|
|
2414
|
-
toolDetail:
|
|
2415
|
-
domainTools:
|
|
2416
|
-
"URL mode only: include domain-matched Alexandria tools for the page in tools on the returned document."
|
|
2991
|
+
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."),
|
|
2992
|
+
domainTools: z8.boolean().optional().describe(
|
|
2993
|
+
"URL mode only: include domain-matched Alexandria tools for the page in tools on the returned document. Ignored with alexandria."
|
|
2417
2994
|
)
|
|
2418
2995
|
}).refine(
|
|
2419
2996
|
(args2) => Boolean(args2.url) !== Boolean(args2.alexandria),
|
|
2420
2997
|
"Provide exactly one of url or alexandria"
|
|
2421
2998
|
).refine(
|
|
2422
2999
|
(args2) => !args2.alexandria || Object.entries(args2).every(
|
|
2423
|
-
([key, value]) => key === "alexandria" || key === "requestId" || key === "timeout" ||
|
|
3000
|
+
([key, value]) => key === "alexandria" || key === "requestId" || key === "timeout" || // URL-mode discovery options that mean nothing when executing a
|
|
3001
|
+
// provider. Agents carry toolDetail over from firecrawl_search (where
|
|
3002
|
+
// it selects contract detail), so accept and ignore them rather than
|
|
3003
|
+
// failing the call: Codex sent toolDetail on 18 of 315 Alexandria
|
|
3004
|
+
// executions across the AX EXP-058 runs, each one a wasted round trip.
|
|
3005
|
+
ALEXANDRIA_IGNORED_SCRAPE_OPTIONS.has(key) || value === void 0
|
|
2424
3006
|
),
|
|
2425
3007
|
"alexandria cannot be combined with url or other scrape options"
|
|
2426
3008
|
).refine(
|
|
2427
3009
|
(args2) => !args2.requestId || !!args2.alexandria,
|
|
2428
3010
|
"requestId applies to Alexandria execution only"
|
|
2429
3011
|
);
|
|
2430
|
-
var parseOptionParamsSchema =
|
|
2431
|
-
formats:
|
|
2432
|
-
|
|
3012
|
+
var parseOptionParamsSchema = z8.object({
|
|
3013
|
+
formats: z8.array(
|
|
3014
|
+
z8.enum([
|
|
2433
3015
|
"markdown",
|
|
2434
3016
|
"html",
|
|
2435
3017
|
"rawHtml",
|
|
@@ -2439,48 +3021,48 @@ var parseOptionParamsSchema = z7.object({
|
|
|
2439
3021
|
"query"
|
|
2440
3022
|
])
|
|
2441
3023
|
).optional(),
|
|
2442
|
-
jsonOptions:
|
|
2443
|
-
prompt:
|
|
2444
|
-
schema:
|
|
3024
|
+
jsonOptions: z8.object({
|
|
3025
|
+
prompt: z8.string().optional(),
|
|
3026
|
+
schema: z8.record(z8.string(), z8.any()).optional()
|
|
2445
3027
|
}).optional(),
|
|
2446
|
-
queryOptions:
|
|
2447
|
-
prompt:
|
|
2448
|
-
mode:
|
|
3028
|
+
queryOptions: z8.object({
|
|
3029
|
+
prompt: z8.string().max(1e4),
|
|
3030
|
+
mode: z8.enum(["directQuote", "freeform"]).default("freeform")
|
|
2449
3031
|
}).optional(),
|
|
2450
|
-
parsers:
|
|
2451
|
-
pdfOptions:
|
|
2452
|
-
maxPages:
|
|
3032
|
+
parsers: z8.array(z8.enum(["pdf"])).optional(),
|
|
3033
|
+
pdfOptions: z8.object({
|
|
3034
|
+
maxPages: z8.number().int().min(1).max(1e4).optional()
|
|
2453
3035
|
}).optional(),
|
|
2454
|
-
onlyMainContent:
|
|
2455
|
-
redactPII:
|
|
2456
|
-
includeTags:
|
|
2457
|
-
excludeTags:
|
|
2458
|
-
removeBase64Images:
|
|
2459
|
-
skipTlsVerification:
|
|
2460
|
-
storeInCache:
|
|
2461
|
-
zeroDataRetention:
|
|
2462
|
-
maxAge:
|
|
2463
|
-
proxy:
|
|
3036
|
+
onlyMainContent: z8.boolean().optional(),
|
|
3037
|
+
redactPII: z8.boolean().optional(),
|
|
3038
|
+
includeTags: z8.array(z8.string()).optional(),
|
|
3039
|
+
excludeTags: z8.array(z8.string()).optional(),
|
|
3040
|
+
removeBase64Images: z8.boolean().optional(),
|
|
3041
|
+
skipTlsVerification: z8.boolean().optional(),
|
|
3042
|
+
storeInCache: z8.boolean().optional(),
|
|
3043
|
+
zeroDataRetention: z8.boolean().optional(),
|
|
3044
|
+
maxAge: z8.number().optional().describe("Ignored: parse never reuses or stores indexed content."),
|
|
3045
|
+
proxy: z8.enum(["basic", "auto"]).optional()
|
|
2464
3046
|
});
|
|
2465
3047
|
var localParseParamsSchema = parseOptionParamsSchema.extend({
|
|
2466
|
-
filePath:
|
|
3048
|
+
filePath: z8.string().min(1).describe(
|
|
2467
3049
|
"Absolute or relative path to a local file to parse. Supported: .html, .htm, .pdf, .docx, .doc, .odt, .rtf, .xlsx, .xls"
|
|
2468
3050
|
),
|
|
2469
|
-
contentType:
|
|
3051
|
+
contentType: z8.string().optional().describe(
|
|
2470
3052
|
"Optional MIME type override. If omitted, the server infers the file kind from the extension."
|
|
2471
3053
|
)
|
|
2472
3054
|
});
|
|
2473
3055
|
var hostedParseParamsSchema = parseOptionParamsSchema.extend({
|
|
2474
|
-
filePath:
|
|
3056
|
+
filePath: z8.string().min(1).optional().describe(
|
|
2475
3057
|
"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."
|
|
2476
3058
|
),
|
|
2477
|
-
uploadRef:
|
|
3059
|
+
uploadRef: z8.string().min(1).optional().describe(
|
|
2478
3060
|
"Phase 2 only: short-lived upload reference returned by phase 1 after the local PUT upload completes."
|
|
2479
3061
|
),
|
|
2480
|
-
contentType:
|
|
3062
|
+
contentType: z8.string().optional().describe(
|
|
2481
3063
|
"Phase 1 MIME type override. If omitted, the server infers it from the file extension without reading the file."
|
|
2482
3064
|
),
|
|
2483
|
-
declaredSizeBytes:
|
|
3065
|
+
declaredSizeBytes: z8.number().int().positive().optional().describe(
|
|
2484
3066
|
"Optional phase 1 size declaration. Hosted MCP does not stat the file; provide this only if the caller already knows it."
|
|
2485
3067
|
)
|
|
2486
3068
|
}).superRefine((value, ctx) => {
|
|
@@ -2488,7 +3070,7 @@ var hostedParseParamsSchema = parseOptionParamsSchema.extend({
|
|
|
2488
3070
|
const hasUploadRef = typeof value.uploadRef === "string" && value.uploadRef.length > 0;
|
|
2489
3071
|
if (hasFilePath === hasUploadRef) {
|
|
2490
3072
|
ctx.addIssue({
|
|
2491
|
-
code:
|
|
3073
|
+
code: z8.ZodIssueCode.custom,
|
|
2492
3074
|
message: "Hosted firecrawl_parse requires exactly one of filePath (phase 1) or uploadRef (phase 2).",
|
|
2493
3075
|
path: hasFilePath && hasUploadRef ? ["uploadRef"] : ["filePath"]
|
|
2494
3076
|
});
|
|
@@ -2541,6 +3123,7 @@ async function apiPostJson(pathName, body, apiKey, origin) {
|
|
|
2541
3123
|
const response = await fetch(`${resolveApiBaseUrl()}${pathName}`, {
|
|
2542
3124
|
method: "POST",
|
|
2543
3125
|
headers: {
|
|
3126
|
+
...AGENT_HINTS_HEADERS,
|
|
2544
3127
|
"Content-Type": "application/json",
|
|
2545
3128
|
Authorization: `Bearer ${apiKey}`,
|
|
2546
3129
|
...origin ? originHeaders(origin) : {}
|
|
@@ -2557,7 +3140,8 @@ async function apiPostJson(pathName, body, apiKey, origin) {
|
|
|
2557
3140
|
if (!response.ok) {
|
|
2558
3141
|
throw new CoreHttpError(
|
|
2559
3142
|
parsed?.error || parsed?.message || `Firecrawl request failed (HTTP ${response.status})`,
|
|
2560
|
-
response.status
|
|
3143
|
+
response.status,
|
|
3144
|
+
readAgentHints(parsed)
|
|
2561
3145
|
);
|
|
2562
3146
|
}
|
|
2563
3147
|
return parsed;
|
|
@@ -2609,7 +3193,7 @@ async function executeHostedParse(args2, session, log, origin) {
|
|
|
2609
3193
|
);
|
|
2610
3194
|
}
|
|
2611
3195
|
if (!hasCredential(session) && !isKeylessMode(session)) {
|
|
2612
|
-
return
|
|
3196
|
+
return asText({
|
|
2613
3197
|
success: false,
|
|
2614
3198
|
mode: "hosted-upload-ref-auth-required",
|
|
2615
3199
|
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."
|
|
@@ -2639,6 +3223,7 @@ async function executeHostedParse(args2, session, log, origin) {
|
|
|
2639
3223
|
session,
|
|
2640
3224
|
origin
|
|
2641
3225
|
);
|
|
3226
|
+
const uploadHints = readAgentHints(uploadJson);
|
|
2642
3227
|
const upload = parseApiData(uploadJson);
|
|
2643
3228
|
if (!upload?.uploadUrl || !upload?.uploadRef) {
|
|
2644
3229
|
throw new Error(
|
|
@@ -2647,8 +3232,9 @@ async function executeHostedParse(args2, session, log, origin) {
|
|
|
2647
3232
|
}
|
|
2648
3233
|
const uploadHeaders = upload.headers && Object.keys(upload.headers).length > 0 ? upload.headers : (upload.method ?? "PUT").toUpperCase() === "POST" ? {} : { "Content-Type": contentType };
|
|
2649
3234
|
const uploadForCommand = { ...upload, headers: uploadHeaders };
|
|
2650
|
-
return
|
|
3235
|
+
return asText({
|
|
2651
3236
|
success: true,
|
|
3237
|
+
...uploadHints ? { agent_hints: uploadHints } : {},
|
|
2652
3238
|
mode: "hosted-upload-ref-awaiting-upload",
|
|
2653
3239
|
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.",
|
|
2654
3240
|
upload: {
|
|
@@ -2683,7 +3269,7 @@ async function executeHostedParse(args2, session, log, origin) {
|
|
|
2683
3269
|
session,
|
|
2684
3270
|
origin
|
|
2685
3271
|
);
|
|
2686
|
-
return
|
|
3272
|
+
return asText(parseJson);
|
|
2687
3273
|
}
|
|
2688
3274
|
var scrapeTool = {
|
|
2689
3275
|
name: "firecrawl_scrape",
|
|
@@ -2697,14 +3283,15 @@ var scrapeTool = {
|
|
|
2697
3283
|
// Does not modify, delete, or write to external websites.
|
|
2698
3284
|
},
|
|
2699
3285
|
description: `
|
|
2700
|
-
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, \`firecrawl_map\`
|
|
3286
|
+
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.
|
|
2701
3287
|
|
|
2702
3288
|
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.
|
|
2703
3289
|
|
|
2704
|
-
On an authenticated session with Alexandria access,
|
|
3290
|
+
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.
|
|
2705
3291
|
|
|
2706
|
-
Alexandria mode
|
|
3292
|
+
Alexandria mode, on an authenticated session with Alexandria access: \`alexandria\` selects catalogued capability execution and is mutually exclusive with \`url\`.
|
|
2707
3293
|
`,
|
|
3294
|
+
outputSchema: scrapeOutputSchema,
|
|
2708
3295
|
parameters: scrapeToolParamsSchema,
|
|
2709
3296
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
2710
3297
|
const origin = requestOrigin(mcpClient, session);
|
|
@@ -2719,7 +3306,9 @@ Alexandria mode: pass \`alexandria\` instead of \`url\` to execute catalogued ca
|
|
|
2719
3306
|
log.info("Executing Alexandria capabilities", {
|
|
2720
3307
|
count: Array.isArray(alexandria) ? alexandria.length : 1
|
|
2721
3308
|
});
|
|
2722
|
-
return
|
|
3309
|
+
return structuredJsonText(
|
|
3310
|
+
await executeExchangeCalls(session, alexandria, suppliedRequestId, options.timeout, origin)
|
|
3311
|
+
);
|
|
2723
3312
|
}
|
|
2724
3313
|
const transformed = transformScrapeParams(
|
|
2725
3314
|
options
|
|
@@ -2740,20 +3329,26 @@ Alexandria mode: pass \`alexandria\` instead of \`url\` to execute catalogued ca
|
|
|
2740
3329
|
},
|
|
2741
3330
|
session
|
|
2742
3331
|
);
|
|
2743
|
-
return
|
|
3332
|
+
return structuredText(preserveAgentHints(json?.data ?? json, json));
|
|
2744
3333
|
}
|
|
2745
3334
|
const client = getClient(session);
|
|
2746
3335
|
const res = await relayTermsRequired(
|
|
2747
|
-
() =>
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
3336
|
+
() => sdkResultWithAgentHints(
|
|
3337
|
+
client,
|
|
3338
|
+
() => client.scrape(String(url), {
|
|
3339
|
+
...cleaned,
|
|
3340
|
+
origin
|
|
3341
|
+
})
|
|
3342
|
+
),
|
|
2751
3343
|
{ tool: "firecrawl_scrape" }
|
|
2752
3344
|
);
|
|
2753
|
-
return
|
|
3345
|
+
return structuredText(res);
|
|
2754
3346
|
}
|
|
2755
3347
|
};
|
|
2756
|
-
server.addTool(
|
|
3348
|
+
server.addTool({
|
|
3349
|
+
...scrapeTool,
|
|
3350
|
+
_meta: { "anthropic/alwaysLoad": true }
|
|
3351
|
+
});
|
|
2757
3352
|
server.addTool({
|
|
2758
3353
|
name: "firecrawl_map",
|
|
2759
3354
|
annotations: {
|
|
@@ -2770,28 +3365,33 @@ Enumerate URLs indexed under one website through Firecrawl without fetching each
|
|
|
2770
3365
|
|
|
2771
3366
|
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.
|
|
2772
3367
|
`,
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
3368
|
+
outputSchema: mapOutputSchema,
|
|
3369
|
+
parameters: z8.object({
|
|
3370
|
+
url: z8.string().url(),
|
|
3371
|
+
search: z8.string().optional(),
|
|
3372
|
+
sitemap: z8.enum(["include", "skip", "only"]).optional(),
|
|
3373
|
+
includeSubdomains: z8.boolean().optional(),
|
|
3374
|
+
limit: z8.number().optional(),
|
|
3375
|
+
ignoreQueryParameters: z8.boolean().optional()
|
|
2780
3376
|
}),
|
|
2781
3377
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
2782
3378
|
const { url, ...options } = args2;
|
|
2783
3379
|
const client = getClient(session);
|
|
2784
3380
|
const cleaned = removeEmptyTopLevel(options);
|
|
2785
3381
|
log.info("Mapping URL", { url: String(url) });
|
|
2786
|
-
const res = await
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
3382
|
+
const res = await sdkResultWithAgentHints(
|
|
3383
|
+
client,
|
|
3384
|
+
() => client.map(String(url), {
|
|
3385
|
+
...cleaned,
|
|
3386
|
+
origin: requestOrigin(mcpClient, session)
|
|
3387
|
+
})
|
|
3388
|
+
);
|
|
3389
|
+
return structuredText(res);
|
|
2791
3390
|
}
|
|
2792
3391
|
});
|
|
2793
3392
|
server.addTool({
|
|
2794
3393
|
name: "firecrawl_search",
|
|
3394
|
+
_meta: { "anthropic/alwaysLoad": true },
|
|
2795
3395
|
annotations: {
|
|
2796
3396
|
title: "Firecrawl web search",
|
|
2797
3397
|
readOnlyHint: true,
|
|
@@ -2806,11 +3406,12 @@ Search web, news, or image sources and return ranked results with query-relevant
|
|
|
2806
3406
|
|
|
2807
3407
|
${ALEXANDRIA_SEARCH_LEAD}
|
|
2808
3408
|
|
|
2809
|
-
|
|
3409
|
+
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.
|
|
2810
3410
|
|
|
2811
3411
|
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.
|
|
2812
3412
|
`,
|
|
2813
|
-
|
|
3413
|
+
outputSchema: searchOutputSchema,
|
|
3414
|
+
parameters: z8.object({
|
|
2814
3415
|
...searchToolBaseFields,
|
|
2815
3416
|
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.")
|
|
2816
3417
|
}).refine(searchDomainsAreExclusive, SEARCH_DOMAINS_CONFLICT_MESSAGE).refine(
|
|
@@ -2847,7 +3448,7 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
|
|
|
2847
3448
|
const json = await keylessPost("/v2/search", searchBody, session);
|
|
2848
3449
|
const keylessResponse = { ...json ?? {} };
|
|
2849
3450
|
delete keylessResponse.id;
|
|
2850
|
-
return
|
|
3451
|
+
return structuredCompact(keylessResponse);
|
|
2851
3452
|
}
|
|
2852
3453
|
const client = getClient(session);
|
|
2853
3454
|
const postSearch = () => postSearchWithFallback(
|
|
@@ -2857,7 +3458,7 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
|
|
|
2857
3458
|
);
|
|
2858
3459
|
const context = { tool: "firecrawl_search" };
|
|
2859
3460
|
const httpRes = exchangeSource ? await relayExchangeError(postSearch, context) : await relayTermsRequired(postSearch, context);
|
|
2860
|
-
return
|
|
3461
|
+
return structuredCompact(httpRes?.data ?? {});
|
|
2861
3462
|
}
|
|
2862
3463
|
});
|
|
2863
3464
|
async function executeExchangeCalls(session, alexandria, suppliedRequestId, timeout, origin = requestOrigin(void 0, session)) {
|
|
@@ -2895,11 +3496,11 @@ async function executeExchangeCalls(session, alexandria, suppliedRequestId, time
|
|
|
2895
3496
|
randomUUID(),
|
|
2896
3497
|
alexandriaFeedbackAvailable(session) && alexandriaCallsWarrantFeedback(calls) ? { feedbackTool: ALEXANDRIA_FEEDBACK_HINT } : {}
|
|
2897
3498
|
);
|
|
2898
|
-
} catch (
|
|
2899
|
-
if (
|
|
3499
|
+
} catch (error2) {
|
|
3500
|
+
if (error2?.response?.status === 401) throw error2;
|
|
2900
3501
|
throw new UserError2(
|
|
2901
|
-
`${
|
|
2902
|
-
{ ...
|
|
3502
|
+
`${error2 instanceof Error ? error2.message : String(error2)} Request ID: ${requestId}. Reuse this ID only with the identical payload.`,
|
|
3503
|
+
{ ...error2 instanceof UserError2 ? error2.extras : {}, requestId }
|
|
2903
3504
|
);
|
|
2904
3505
|
}
|
|
2905
3506
|
}
|
|
@@ -2912,15 +3513,16 @@ var findToolsTool = {
|
|
|
2912
3513
|
destructiveHint: false,
|
|
2913
3514
|
idempotentHint: true
|
|
2914
3515
|
},
|
|
2915
|
-
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.
|
|
3516
|
+
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.",
|
|
3517
|
+
outputSchema: findToolsOutputSchema,
|
|
2916
3518
|
parameters: findToolsSchema,
|
|
2917
3519
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
2918
3520
|
const origin = requestOrigin(mcpClient, session);
|
|
2919
3521
|
let options;
|
|
2920
3522
|
try {
|
|
2921
3523
|
options = findToolsOptions(args2);
|
|
2922
|
-
} catch (
|
|
2923
|
-
throw new UserError2(
|
|
3524
|
+
} catch (error2) {
|
|
3525
|
+
throw new UserError2(error2 instanceof Error ? error2.message : String(error2));
|
|
2924
3526
|
}
|
|
2925
3527
|
if (options.level === "categories") {
|
|
2926
3528
|
assertExchangeCredential(session);
|
|
@@ -2940,25 +3542,26 @@ var findToolsTool = {
|
|
|
2940
3542
|
}));
|
|
2941
3543
|
const page = { level: "categories", items, total: rows.length };
|
|
2942
3544
|
if (offset + options.limit < rows.length) page.nextTool = { name: "firecrawl_find_tools", arguments: { level: "categories", limit: options.limit, offset: offset + options.limit } };
|
|
2943
|
-
return
|
|
3545
|
+
return structuredCompact(withAlexandriaFeedbackHint(withFindToolsNavigation({ success: true, data: { creditsCost: 0, alexandria: [{ provider: "firecrawl", capability: "find-tools", creditsCost: 0, data: page }] } }), alexandriaFeedbackAvailable(session)));
|
|
2944
3546
|
}
|
|
2945
3547
|
const result = await executeExchangeCalls(session, { provider: "firecrawl", capability: "find-tools", options }, void 0, void 0, origin);
|
|
2946
|
-
return
|
|
3548
|
+
return structuredCompact(withFindToolsNavigation(JSON.parse(result)));
|
|
2947
3549
|
}
|
|
2948
3550
|
};
|
|
2949
3551
|
server.addTool(findToolsTool);
|
|
2950
3552
|
var SEARCH_SURFACE_SCRAPE_DESCRIPTION = `
|
|
2951
3553
|
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.
|
|
2952
3554
|
|
|
2953
|
-
|
|
3555
|
+
\`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.
|
|
2954
3556
|
|
|
2955
|
-
Alexandria mode:
|
|
3557
|
+
Alexandria mode: \`alexandria\` selects catalogued capability execution and is mutually exclusive with \`url\`. Alexandria execution is billed at the capability's listed price and needs a team with Alexandria enabled.
|
|
2956
3558
|
`;
|
|
2957
3559
|
var searchSurfaceScrapeTool = {
|
|
2958
3560
|
...scrapeTool,
|
|
3561
|
+
_meta: { "anthropic/alwaysLoad": true },
|
|
2959
3562
|
description: SEARCH_SURFACE_SCRAPE_DESCRIPTION
|
|
2960
3563
|
};
|
|
2961
|
-
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.
|
|
3564
|
+
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.";
|
|
2962
3565
|
var searchSurfaceFindToolsTool = {
|
|
2963
3566
|
...findToolsTool,
|
|
2964
3567
|
description: SEARCH_SURFACE_FIND_TOOLS_DESCRIPTION
|
|
@@ -3024,6 +3627,7 @@ async function keylessPost(path2, body, session, originOverride) {
|
|
|
3024
3627
|
}
|
|
3025
3628
|
}
|
|
3026
3629
|
const headers = {
|
|
3630
|
+
...AGENT_HINTS_HEADERS,
|
|
3027
3631
|
...originHeaders(origin),
|
|
3028
3632
|
"Content-Type": "application/json"
|
|
3029
3633
|
};
|
|
@@ -3043,10 +3647,14 @@ async function keylessPost(path2, body, session, originOverride) {
|
|
|
3043
3647
|
const payload = recoveryPayload(code, session?.requestId, {
|
|
3044
3648
|
retryAfterSeconds: Number.isFinite(json?.retry_after_seconds) && json.retry_after_seconds > 0 ? json.retry_after_seconds : void 0
|
|
3045
3649
|
});
|
|
3650
|
+
const hints = readAgentHints(json);
|
|
3651
|
+
if (hints) payload.agent_hints = hints;
|
|
3046
3652
|
throw new UserError2(String(payload.message), payload);
|
|
3047
3653
|
}
|
|
3048
|
-
throw new
|
|
3049
|
-
json?.error || `Firecrawl request failed (HTTP ${response.status})
|
|
3654
|
+
throw new CoreHttpError(
|
|
3655
|
+
json?.error || `Firecrawl request failed (HTTP ${response.status})`,
|
|
3656
|
+
response.status,
|
|
3657
|
+
readAgentHints(json)
|
|
3050
3658
|
);
|
|
3051
3659
|
}
|
|
3052
3660
|
return json;
|
|
@@ -3067,7 +3675,8 @@ async function getCrawlStatusWithOrigin(client, jobId, origin) {
|
|
|
3067
3675
|
creditsUsed: body.creditsUsed,
|
|
3068
3676
|
expiresAt: body.expiresAt,
|
|
3069
3677
|
next: body.next ?? null,
|
|
3070
|
-
data: initialDocs
|
|
3678
|
+
data: initialDocs,
|
|
3679
|
+
...readAgentHints(body) ? { agent_hints: readAgentHints(body) } : {}
|
|
3071
3680
|
};
|
|
3072
3681
|
}
|
|
3073
3682
|
const docs = initialDocs.slice();
|
|
@@ -3091,7 +3700,8 @@ async function getCrawlStatusWithOrigin(client, jobId, origin) {
|
|
|
3091
3700
|
creditsUsed: body.creditsUsed,
|
|
3092
3701
|
expiresAt: body.expiresAt,
|
|
3093
3702
|
next: null,
|
|
3094
|
-
data: docs
|
|
3703
|
+
data: docs,
|
|
3704
|
+
...readAgentHints(body) ? { agent_hints: readAgentHints(body) } : {}
|
|
3095
3705
|
};
|
|
3096
3706
|
}
|
|
3097
3707
|
async function waitForCrawlCompletionWithOrigin(client, jobId, origin, pollInterval = 2, timeout) {
|
|
@@ -3109,17 +3719,17 @@ async function waitForCrawlCompletionWithOrigin(client, jobId, origin, pollInter
|
|
|
3109
3719
|
);
|
|
3110
3720
|
}
|
|
3111
3721
|
}
|
|
3112
|
-
var feedbackIssueSchema =
|
|
3722
|
+
var feedbackIssueSchema = z8.string().trim().min(1).max(80).regex(
|
|
3113
3723
|
/^[a-z0-9][a-z0-9_-]*$/,
|
|
3114
3724
|
"Issue codes must use lowercase letters, numbers, underscores, or hyphens"
|
|
3115
3725
|
);
|
|
3116
|
-
var valuableSourceSchema =
|
|
3117
|
-
url:
|
|
3118
|
-
reason:
|
|
3726
|
+
var valuableSourceSchema = z8.object({
|
|
3727
|
+
url: z8.string().url(),
|
|
3728
|
+
reason: z8.string().max(1e3).optional()
|
|
3119
3729
|
});
|
|
3120
|
-
var missingContentSchema =
|
|
3121
|
-
topic:
|
|
3122
|
-
description:
|
|
3730
|
+
var missingContentSchema = z8.object({
|
|
3731
|
+
topic: z8.string().min(1, "topic must not be empty").max(200, "topic must be 200 characters or fewer"),
|
|
3732
|
+
description: z8.string().max(2e3).optional()
|
|
3123
3733
|
});
|
|
3124
3734
|
var FEEDBACK_DISABLED_VALUES = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
3125
3735
|
function feedbackEnvEnabled(...keys) {
|
|
@@ -3157,24 +3767,25 @@ Records schema-validated quality feedback for a prior \`firecrawl_search\` UUID
|
|
|
3157
3767
|
|
|
3158
3768
|
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.
|
|
3159
3769
|
`,
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3770
|
+
outputSchema: feedbackOutputSchema,
|
|
3771
|
+
parameters: z8.object({
|
|
3772
|
+
searchId: z8.string().uuid("searchId must be the UUID returned by firecrawl_search"),
|
|
3773
|
+
rating: z8.enum(["good", "bad", "partial"]),
|
|
3774
|
+
valuableSources: z8.array(
|
|
3775
|
+
z8.object({
|
|
3776
|
+
url: z8.string().url(),
|
|
3777
|
+
reason: z8.string().max(1e3).optional()
|
|
3167
3778
|
})
|
|
3168
3779
|
).max(50).optional(),
|
|
3169
|
-
missingContent:
|
|
3170
|
-
|
|
3171
|
-
topic:
|
|
3172
|
-
description:
|
|
3780
|
+
missingContent: z8.array(
|
|
3781
|
+
z8.object({
|
|
3782
|
+
topic: z8.string().min(1, "topic must not be empty").max(200, "topic must be 200 characters or fewer"),
|
|
3783
|
+
description: z8.string().max(2e3).optional()
|
|
3173
3784
|
})
|
|
3174
3785
|
).max(20).optional().describe(
|
|
3175
3786
|
"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`."
|
|
3176
3787
|
),
|
|
3177
|
-
querySuggestions:
|
|
3788
|
+
querySuggestions: z8.string().max(2e3).optional()
|
|
3178
3789
|
}),
|
|
3179
3790
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
3180
3791
|
const origin = requestOrigin(mcpClient, session);
|
|
@@ -3201,6 +3812,7 @@ Eligibility is limited to successful searches within the feedback age window. Th
|
|
|
3201
3812
|
}
|
|
3202
3813
|
if (querySuggestions) body.querySuggestions = querySuggestions;
|
|
3203
3814
|
const headers = {
|
|
3815
|
+
...AGENT_HINTS_HEADERS,
|
|
3204
3816
|
...originHeaders(origin),
|
|
3205
3817
|
"Content-Type": "application/json"
|
|
3206
3818
|
};
|
|
@@ -3234,15 +3846,16 @@ Eligibility is limited to successful searches within the feedback age window. Th
|
|
|
3234
3846
|
status: response.status,
|
|
3235
3847
|
feedbackErrorCode: parsed?.feedbackErrorCode
|
|
3236
3848
|
});
|
|
3237
|
-
return
|
|
3849
|
+
return structuredText({
|
|
3238
3850
|
success: false,
|
|
3239
3851
|
status: response.status,
|
|
3240
3852
|
feedbackErrorCode: parsed?.feedbackErrorCode,
|
|
3241
3853
|
error: parsed?.error ?? `HTTP ${response.status}`,
|
|
3242
|
-
retryable: response.status >= 500
|
|
3854
|
+
retryable: response.status >= 500,
|
|
3855
|
+
...readAgentHints(parsed) ? { agent_hints: readAgentHints(parsed) } : {}
|
|
3243
3856
|
});
|
|
3244
3857
|
}
|
|
3245
|
-
return
|
|
3858
|
+
return structuredText(parsed);
|
|
3246
3859
|
}
|
|
3247
3860
|
});
|
|
3248
3861
|
}
|
|
@@ -3273,20 +3886,21 @@ For an Alexandria session, set endpoint to \`alexandria\`, omit jobId, and provi
|
|
|
3273
3886
|
|
|
3274
3887
|
Returns submission status, feedback ID, and accounting fields.
|
|
3275
3888
|
`,
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3889
|
+
outputSchema: feedbackOutputSchema,
|
|
3890
|
+
parameters: z8.object({
|
|
3891
|
+
endpoint: z8.enum(["search", "scrape", "parse", "map", "alexandria"]),
|
|
3892
|
+
jobId: z8.string().uuid("jobId must be the UUID returned by Firecrawl").optional(),
|
|
3279
3893
|
...alexandriaFeedbackFields,
|
|
3280
|
-
rating:
|
|
3281
|
-
issues:
|
|
3282
|
-
tags:
|
|
3283
|
-
note:
|
|
3284
|
-
valuableSources:
|
|
3285
|
-
missingContent:
|
|
3286
|
-
querySuggestions:
|
|
3287
|
-
url:
|
|
3288
|
-
pageNumbers:
|
|
3289
|
-
metadata:
|
|
3894
|
+
rating: z8.enum(["good", "bad", "partial"]),
|
|
3895
|
+
issues: z8.array(feedbackIssueSchema).max(20).optional(),
|
|
3896
|
+
tags: z8.array(feedbackIssueSchema).max(20).optional(),
|
|
3897
|
+
note: z8.string().max(4e3).optional(),
|
|
3898
|
+
valuableSources: z8.array(valuableSourceSchema).max(50).optional(),
|
|
3899
|
+
missingContent: z8.array(missingContentSchema).max(50).optional(),
|
|
3900
|
+
querySuggestions: z8.string().max(2e3).optional(),
|
|
3901
|
+
url: z8.string().url().optional(),
|
|
3902
|
+
pageNumbers: z8.array(z8.number().int().positive()).max(100).optional(),
|
|
3903
|
+
metadata: z8.record(z8.string(), z8.unknown()).optional()
|
|
3290
3904
|
}).superRefine((value, ctx) => {
|
|
3291
3905
|
if (value.endpoint === "alexandria") {
|
|
3292
3906
|
const parsed = alexandriaSessionFeedbackSchema.safeParse(value);
|
|
@@ -3320,6 +3934,7 @@ Returns submission status, feedback ID, and accounting fields.
|
|
|
3320
3934
|
} = args2;
|
|
3321
3935
|
const apiBase = resolveApiBaseUrl();
|
|
3322
3936
|
const headers = {
|
|
3937
|
+
...AGENT_HINTS_HEADERS,
|
|
3323
3938
|
...originHeaders(origin),
|
|
3324
3939
|
"Content-Type": "application/json"
|
|
3325
3940
|
};
|
|
@@ -3368,15 +3983,16 @@ Returns submission status, feedback ID, and accounting fields.
|
|
|
3368
3983
|
status: response.status,
|
|
3369
3984
|
feedbackErrorCode: parsed?.feedbackErrorCode
|
|
3370
3985
|
});
|
|
3371
|
-
return
|
|
3986
|
+
return structuredText({
|
|
3372
3987
|
success: false,
|
|
3373
3988
|
status: response.status,
|
|
3374
3989
|
feedbackErrorCode: parsed?.feedbackErrorCode,
|
|
3375
3990
|
error: parsed?.error ?? `HTTP ${response.status}`,
|
|
3376
|
-
retryable: response.status >= 500
|
|
3991
|
+
retryable: response.status >= 500,
|
|
3992
|
+
...readAgentHints(parsed) ? { agent_hints: readAgentHints(parsed) } : {}
|
|
3377
3993
|
});
|
|
3378
3994
|
}
|
|
3379
|
-
return
|
|
3995
|
+
return structuredText(parsed);
|
|
3380
3996
|
}
|
|
3381
3997
|
});
|
|
3382
3998
|
}
|
|
@@ -3396,27 +4012,28 @@ Start a multi-page crawl at a website URL, poll it to a terminal state, and retu
|
|
|
3396
4012
|
|
|
3397
4013
|
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.
|
|
3398
4014
|
`,
|
|
3399
|
-
parameters:
|
|
3400
|
-
url:
|
|
3401
|
-
prompt:
|
|
3402
|
-
excludePaths:
|
|
3403
|
-
includePaths:
|
|
3404
|
-
maxDiscoveryDepth:
|
|
3405
|
-
sitemap:
|
|
3406
|
-
limit:
|
|
3407
|
-
allowExternalLinks:
|
|
3408
|
-
allowSubdomains:
|
|
3409
|
-
crawlEntireDomain:
|
|
3410
|
-
delay:
|
|
3411
|
-
maxConcurrency:
|
|
4015
|
+
parameters: z8.object({
|
|
4016
|
+
url: z8.string(),
|
|
4017
|
+
prompt: z8.string().optional(),
|
|
4018
|
+
excludePaths: z8.array(z8.string()).optional(),
|
|
4019
|
+
includePaths: z8.array(z8.string()).optional(),
|
|
4020
|
+
maxDiscoveryDepth: z8.number().optional(),
|
|
4021
|
+
sitemap: z8.enum(["skip", "include", "only"]).optional(),
|
|
4022
|
+
limit: z8.number().optional(),
|
|
4023
|
+
allowExternalLinks: z8.boolean().optional(),
|
|
4024
|
+
allowSubdomains: z8.boolean().optional(),
|
|
4025
|
+
crawlEntireDomain: z8.boolean().optional(),
|
|
4026
|
+
delay: z8.number().optional(),
|
|
4027
|
+
maxConcurrency: z8.number().optional(),
|
|
3412
4028
|
...SAFE_MODE ? {} : {
|
|
3413
|
-
webhook:
|
|
3414
|
-
webhookHeaders:
|
|
4029
|
+
webhook: z8.string().optional(),
|
|
4030
|
+
webhookHeaders: z8.record(z8.string(), z8.string()).optional()
|
|
3415
4031
|
},
|
|
3416
|
-
deduplicateSimilarURLs:
|
|
3417
|
-
ignoreQueryParameters:
|
|
4032
|
+
deduplicateSimilarURLs: z8.boolean().optional(),
|
|
4033
|
+
ignoreQueryParameters: z8.boolean().optional(),
|
|
3418
4034
|
scrapeOptions: scrapeParamsSchema.omit({ url: true }).partial().optional()
|
|
3419
4035
|
}),
|
|
4036
|
+
outputSchema: crawlOutputSchema,
|
|
3420
4037
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
3421
4038
|
const origin = requestOrigin(mcpClient, session);
|
|
3422
4039
|
const { url, ...options } = args2;
|
|
@@ -3443,7 +4060,7 @@ Crawl results can be large; use conservative limits when full-site coverage is u
|
|
|
3443
4060
|
});
|
|
3444
4061
|
const crawlId = started?.data?.id;
|
|
3445
4062
|
if (!crawlId) {
|
|
3446
|
-
return
|
|
4063
|
+
return structuredText(started?.data ?? {});
|
|
3447
4064
|
}
|
|
3448
4065
|
const res = await waitForCrawlCompletionWithOrigin(
|
|
3449
4066
|
client,
|
|
@@ -3452,7 +4069,7 @@ Crawl results can be large; use conservative limits when full-site coverage is u
|
|
|
3452
4069
|
pollInterval,
|
|
3453
4070
|
timeout
|
|
3454
4071
|
);
|
|
3455
|
-
return
|
|
4072
|
+
return structuredText(res);
|
|
3456
4073
|
}
|
|
3457
4074
|
});
|
|
3458
4075
|
server.addTool({
|
|
@@ -3469,7 +4086,8 @@ server.addTool({
|
|
|
3469
4086
|
description: `
|
|
3470
4087
|
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.
|
|
3471
4088
|
`,
|
|
3472
|
-
|
|
4089
|
+
outputSchema: crawlOutputSchema,
|
|
4090
|
+
parameters: z8.object({ id: z8.string() }),
|
|
3473
4091
|
execute: async (args2, {
|
|
3474
4092
|
session,
|
|
3475
4093
|
client: mcpClient
|
|
@@ -3481,7 +4099,7 @@ Retrieve the current status, progress, and available results for an existing cra
|
|
|
3481
4099
|
id,
|
|
3482
4100
|
requestOrigin(mcpClient, session)
|
|
3483
4101
|
);
|
|
3484
|
-
return
|
|
4102
|
+
return structuredText(res);
|
|
3485
4103
|
}
|
|
3486
4104
|
});
|
|
3487
4105
|
server.addTool({
|
|
@@ -3495,13 +4113,14 @@ server.addTool({
|
|
|
3495
4113
|
description: `
|
|
3496
4114
|
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.
|
|
3497
4115
|
`,
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
4116
|
+
outputSchema: deprecatedToolOutputSchema,
|
|
4117
|
+
parameters: z8.object({
|
|
4118
|
+
urls: z8.array(z8.string()),
|
|
4119
|
+
prompt: z8.string().optional(),
|
|
4120
|
+
schema: z8.record(z8.string(), z8.any()).optional(),
|
|
4121
|
+
allowExternalLinks: z8.boolean().optional(),
|
|
4122
|
+
enableWebSearch: z8.boolean().optional(),
|
|
4123
|
+
includeSubdomains: z8.boolean().optional()
|
|
3505
4124
|
}),
|
|
3506
4125
|
canList: () => false,
|
|
3507
4126
|
beforeValidate: () => {
|
|
@@ -3517,6 +4136,29 @@ Deprecated compatibility entry point. Use firecrawl_scrape once per known URL wi
|
|
|
3517
4136
|
throw new UserError2(payload.message, payload);
|
|
3518
4137
|
}
|
|
3519
4138
|
});
|
|
4139
|
+
var agentExchangeSchema = z8.strictObject({
|
|
4140
|
+
enabled: z8.boolean().optional().describe("Let the agent call Alexandria providers. On by default."),
|
|
4141
|
+
toolkits: z8.array(z8.string()).max(5).optional().describe("Pin up to 5 providers the agent may use, by slug. Omitted means the whole catalog."),
|
|
4142
|
+
maxCalls: z8.number().int().min(1).max(30).optional().describe("Most provider calls the agent may make in this turn."),
|
|
4143
|
+
requireApproval: z8.boolean().optional().describe(
|
|
4144
|
+
'End the turn with a paid-call pendingApproval before any paid provider call. Requires mode "chat" on the same request, even on a follow-up.'
|
|
4145
|
+
),
|
|
4146
|
+
approve: z8.strictObject({
|
|
4147
|
+
approvalId: z8.string().uuid(),
|
|
4148
|
+
callIds: z8.array(z8.string()).optional(),
|
|
4149
|
+
always: z8.boolean().optional()
|
|
4150
|
+
}).optional().describe(
|
|
4151
|
+
"Answer yes to the pendingApproval the previous turn of this thread ended on (its id, also exchange.requiresAction.approvalId). Needs threadId. For a terms offer, send it only after the user explicitly agreed and terms/accept succeeded; callIds and always are ignored on terms offers. For paid calls, callIds picks a subset (default all) and always stops asking for the rest of the thread."
|
|
4152
|
+
),
|
|
4153
|
+
decline: z8.strictObject({ approvalId: z8.string().uuid() }).optional().describe(
|
|
4154
|
+
"Answer no to that pendingApproval. Needs threadId. A declined terms offer keeps those providers out of the rest of the thread."
|
|
4155
|
+
),
|
|
4156
|
+
onTermsRequired: z8.enum(["skip", "ask"]).optional().describe(
|
|
4157
|
+
`What to do when a provider the agent would use needs data terms the team has not accepted. Gated providers are never called. "skip" (default): answer with accepted providers and list the rest in exchange.skippedProviders. "ask": the same, plus a terms pendingApproval and exchange.requiresAction with the terms/show and terms/accept calls. Each provider digest is string | null and always present; when null, terms/show returns it. There is no auto-accept. Omitted on a follow-up keeps the previous turn's value.`
|
|
4158
|
+
)
|
|
4159
|
+
}).describe(
|
|
4160
|
+
"Alexandria provider settings for this turn, forwarded as the request's exchange object."
|
|
4161
|
+
);
|
|
3520
4162
|
server.addTool({
|
|
3521
4163
|
name: "firecrawl_agent",
|
|
3522
4164
|
annotations: {
|
|
@@ -3529,32 +4171,80 @@ server.addTool({
|
|
|
3529
4171
|
// Gathers information only; does not delete external data or user resources.
|
|
3530
4172
|
},
|
|
3531
4173
|
description: `
|
|
3532
|
-
Run web research that returns structured data when the URLs are not known or the answer spans several sites. Describe the fields you need in \`prompt\`, optionally pass a JSON \`schema\` and seed \`urls\`, and the research agent searches, navigates, reads pages, and returns JSON assembled across sources. Use it to research an entity plus its fields (founders, pricing, contact details), to build lists and datasets (companies, people, products, jobs, papers), and for pages that need navigation or interaction to reach the data.
|
|
4174
|
+
Run web research that returns structured data when the URLs are not known or the answer spans several sites. Describe the fields you need in \`prompt\`, optionally pass a JSON \`schema\` and seed \`urls\`, and the research agent searches, navigates, reads pages, and returns JSON assembled across sources. Use it to research an entity plus its fields (founders, pricing, contact details), to build lists and datasets (companies, people, products, jobs, papers), and for pages that need navigation or interaction to reach the data. Optional \`effort\` sets the reasoning budget, \`maxCredits\` caps spend, and \`strictConstrainToURLs\` keeps the agent to the supplied \`urls\`.
|
|
3533
4175
|
|
|
3534
4176
|
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\`.
|
|
4177
|
+
|
|
4178
|
+
The job also returns a \`threadId\`. To continue that thread, pass it with a follow-up \`prompt\`; omitted \`mode\`, \`urls\`, \`schema\` and exchange settings carry over from the previous turn.
|
|
4179
|
+
|
|
4180
|
+
The agent only calls Alexandria providers whose data terms the team has accepted. The status result's \`exchange.skippedProviders\` lists gated providers that would have helped. With \`exchange.onTermsRequired\` "ask", a terms offer ends the turn: \`pendingApproval\` (kind "terms") and \`exchange.requiresAction\` carry the \`approvalId\` and the exact terms/show and terms/accept calls. Show the user the terms, get their EXPLICIT consent, run terms/accept through \`firecrawl_scrape\`, then call \`firecrawl_agent\` with the same \`threadId\` and \`exchange.approve: {approvalId}\`. If they decline, send \`exchange.decline: {approvalId}\` instead. Never call terms/accept without that consent; a data request is not consent.
|
|
3535
4181
|
`,
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
4182
|
+
outputSchema: agentOutputSchema,
|
|
4183
|
+
parameters: z8.object({
|
|
4184
|
+
prompt: z8.string().min(1).max(1e4),
|
|
4185
|
+
urls: z8.array(z8.string().url()).optional(),
|
|
4186
|
+
schema: z8.record(z8.string(), z8.any()).optional(),
|
|
4187
|
+
effort: z8.enum(["low", "medium", "high"]).optional().describe("Reasoning budget for the agent task."),
|
|
4188
|
+
maxCredits: z8.number().int().positive().optional().describe(
|
|
4189
|
+
"Spending limit in credits for this run. Defaults to 2500 on the API."
|
|
4190
|
+
),
|
|
4191
|
+
strictConstrainToURLs: z8.boolean().optional().describe(
|
|
4192
|
+
"If true, agent will only visit URLs provided in the urls array."
|
|
4193
|
+
),
|
|
4194
|
+
threadId: z8.string().uuid().optional().describe(
|
|
4195
|
+
"Continue this thread: the threadId from an earlier firecrawl_agent or firecrawl_agent_status result. Omit to start a new thread."
|
|
4196
|
+
),
|
|
4197
|
+
mode: z8.enum(["extract", "chat"]).optional().describe(
|
|
4198
|
+
`"extract" (default) returns the complete structured result every turn. "chat" lets a follow-up that asks no new data get a short reply in message instead of a re-run; required for exchange.requireApproval. Omitted on a follow-up keeps the previous turn's mode.`
|
|
4199
|
+
),
|
|
4200
|
+
exchange: agentExchangeSchema.optional()
|
|
4201
|
+
}).refine(
|
|
4202
|
+
(data) => !(data.exchange?.approve && data.exchange?.decline),
|
|
4203
|
+
{
|
|
4204
|
+
message: "Send exchange.approve or exchange.decline, not both: each answers the pending approval one way.",
|
|
4205
|
+
path: ["exchange"]
|
|
4206
|
+
}
|
|
4207
|
+
).refine(
|
|
4208
|
+
(data) => !(data.exchange?.approve || data.exchange?.decline) || Boolean(data.threadId),
|
|
4209
|
+
{
|
|
4210
|
+
message: "exchange.approve and exchange.decline answer a pending approval on an existing thread: pass that thread's threadId.",
|
|
4211
|
+
path: ["threadId"]
|
|
4212
|
+
}
|
|
4213
|
+
).refine(
|
|
4214
|
+
(data) => !data.exchange?.requireApproval || data.mode === "chat",
|
|
4215
|
+
{
|
|
4216
|
+
// The agent service checks the mode sent on this request, not the
|
|
4217
|
+
// thread's inherited one, and the gateway turns its 400 into a 500.
|
|
4218
|
+
message: 'exchange.requireApproval needs mode: "chat" on the same request, including on a follow-up.',
|
|
4219
|
+
path: ["mode"]
|
|
4220
|
+
}
|
|
4221
|
+
),
|
|
3541
4222
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
3542
4223
|
const client = getClient(session);
|
|
3543
4224
|
const a = args2;
|
|
3544
4225
|
log.info("Starting agent", {
|
|
3545
4226
|
prompt: a.prompt.substring(0, 100),
|
|
3546
|
-
urlCount: Array.isArray(a.urls) ? a.urls.length : 0
|
|
4227
|
+
urlCount: Array.isArray(a.urls) ? a.urls.length : 0,
|
|
4228
|
+
threadId: a.threadId ?? null
|
|
3547
4229
|
});
|
|
3548
4230
|
const agentBody = removeEmptyTopLevel({
|
|
3549
4231
|
prompt: a.prompt,
|
|
3550
4232
|
urls: a.urls,
|
|
3551
|
-
schema: a.schema || void 0
|
|
4233
|
+
schema: a.schema || void 0,
|
|
4234
|
+
effort: a.effort,
|
|
4235
|
+
maxCredits: a.maxCredits,
|
|
4236
|
+
strictConstrainToURLs: a.strictConstrainToURLs,
|
|
4237
|
+
threadId: a.threadId,
|
|
4238
|
+
mode: a.mode,
|
|
4239
|
+
// Forwarded verbatim: the agent service owns the defaults and the
|
|
4240
|
+
// per-thread inheritance.
|
|
4241
|
+
exchange: a.exchange
|
|
3552
4242
|
});
|
|
3553
4243
|
const res = await client.startAgent({
|
|
3554
4244
|
...agentBody,
|
|
3555
4245
|
origin: requestOrigin(mcpClient, session)
|
|
3556
4246
|
});
|
|
3557
|
-
return
|
|
4247
|
+
return structuredText(res);
|
|
3558
4248
|
}
|
|
3559
4249
|
});
|
|
3560
4250
|
server.addTool({
|
|
@@ -3573,7 +4263,8 @@ Retrieve progress or final results for a \`firecrawl_agent\` job ID. A \`process
|
|
|
3573
4263
|
|
|
3574
4264
|
Returns job status, progress information, and result data when completed.
|
|
3575
4265
|
`,
|
|
3576
|
-
|
|
4266
|
+
outputSchema: agentStatusOutputSchema,
|
|
4267
|
+
parameters: z8.object({ id: z8.string() }),
|
|
3577
4268
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
3578
4269
|
const client = getClient(session);
|
|
3579
4270
|
const { id } = args2;
|
|
@@ -3582,7 +4273,7 @@ Returns job status, progress information, and result data when completed.
|
|
|
3582
4273
|
`/v2/agent/${encodeURIComponent(id)}`,
|
|
3583
4274
|
originHeaders(requestOrigin(mcpClient, session))
|
|
3584
4275
|
);
|
|
3585
|
-
return
|
|
4276
|
+
return structuredText(res?.data ?? {});
|
|
3586
4277
|
}
|
|
3587
4278
|
});
|
|
3588
4279
|
server.addTool({
|
|
@@ -3601,13 +4292,14 @@ Open or reuse a live browser session to navigate a page, click controls, fill fi
|
|
|
3601
4292
|
|
|
3602
4293
|
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.
|
|
3603
4294
|
`,
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
4295
|
+
outputSchema: interactOutputSchema,
|
|
4296
|
+
parameters: z8.object({
|
|
4297
|
+
scrapeId: z8.string().trim().min(1).optional(),
|
|
4298
|
+
url: z8.string().trim().url().optional(),
|
|
4299
|
+
prompt: z8.string().trim().min(1).optional(),
|
|
4300
|
+
code: z8.string().trim().min(1).optional(),
|
|
4301
|
+
language: z8.enum(["bash", "python", "node"]).optional(),
|
|
4302
|
+
timeout: z8.number().min(1).max(300).optional(),
|
|
3611
4303
|
scrapeOptions: scrapeParamsSchema.omit({ url: true }).partial().optional()
|
|
3612
4304
|
}).refine((data) => Boolean(data.scrapeId) !== Boolean(data.url), {
|
|
3613
4305
|
message: "Provide either 'url' (interact directly) or 'scrapeId' (reuse a previous scrape), not both."
|
|
@@ -3639,14 +4331,14 @@ This acts on the live site, so actions such as form submission can create persis
|
|
|
3639
4331
|
});
|
|
3640
4332
|
scrapeId = scraped?.metadata?.scrapeId;
|
|
3641
4333
|
if (!scrapeId) {
|
|
3642
|
-
return
|
|
4334
|
+
return structuredText({
|
|
3643
4335
|
error: "Could not open an interact session: the scrape did not return a scrapeId. Try firecrawl_scrape first, then pass its scrapeId.",
|
|
3644
4336
|
url
|
|
3645
4337
|
});
|
|
3646
4338
|
}
|
|
3647
4339
|
}
|
|
3648
4340
|
if (!scrapeId) {
|
|
3649
|
-
return
|
|
4341
|
+
return structuredText({
|
|
3650
4342
|
error: "Could not open an interact session: missing scrapeId.",
|
|
3651
4343
|
url
|
|
3652
4344
|
});
|
|
@@ -3660,15 +4352,15 @@ This acts on the live site, so actions such as form submission can create persis
|
|
|
3660
4352
|
if (timeout != null) interactArgs.timeout = timeout;
|
|
3661
4353
|
const res = await client.interact(activeScrapeId, interactArgs);
|
|
3662
4354
|
if (openedFromUrl && res && typeof res === "object" && !Array.isArray(res)) {
|
|
3663
|
-
return
|
|
4355
|
+
return structuredText({
|
|
3664
4356
|
...res,
|
|
3665
4357
|
scrapeId: activeScrapeId
|
|
3666
4358
|
});
|
|
3667
4359
|
}
|
|
3668
4360
|
if (openedFromUrl) {
|
|
3669
|
-
return
|
|
4361
|
+
return structuredText({ scrapeId: activeScrapeId, result: res });
|
|
3670
4362
|
}
|
|
3671
|
-
return
|
|
4363
|
+
return structuredText(res);
|
|
3672
4364
|
}
|
|
3673
4365
|
});
|
|
3674
4366
|
server.addTool({
|
|
@@ -3685,8 +4377,9 @@ server.addTool({
|
|
|
3685
4377
|
description: `
|
|
3686
4378
|
Stop the live interact session associated with a \`scrapeId\` and release its resources. Returns a success confirmation.
|
|
3687
4379
|
`,
|
|
3688
|
-
|
|
3689
|
-
|
|
4380
|
+
outputSchema: interactStopOutputSchema,
|
|
4381
|
+
parameters: z8.object({
|
|
4382
|
+
scrapeId: z8.string()
|
|
3690
4383
|
}),
|
|
3691
4384
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
3692
4385
|
const client = getClient(session);
|
|
@@ -3696,7 +4389,7 @@ Stop the live interact session associated with a \`scrapeId\` and release its re
|
|
|
3696
4389
|
`/v2/scrape/${encodeURIComponent(scrapeId)}/interact`,
|
|
3697
4390
|
originHeaders(requestOrigin(mcpClient, session))
|
|
3698
4391
|
);
|
|
3699
|
-
return
|
|
4392
|
+
return structuredText(res?.data ?? {});
|
|
3700
4393
|
}
|
|
3701
4394
|
});
|
|
3702
4395
|
server.addTool({
|
|
@@ -3717,11 +4410,14 @@ Local MCP reads \`filePath\` from the server filesystem. Hosted MCP uses two cal
|
|
|
3717
4410
|
|
|
3718
4411
|
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.
|
|
3719
4412
|
`,
|
|
4413
|
+
outputSchema: parseOutputSchema,
|
|
3720
4414
|
parameters: parseParamsSchema,
|
|
3721
4415
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
3722
4416
|
const origin = requestOrigin(mcpClient, session);
|
|
3723
4417
|
if (process.env.CLOUD_SERVICE === "true") {
|
|
3724
|
-
return
|
|
4418
|
+
return structuredJsonText(
|
|
4419
|
+
await executeHostedParse(args2, session, log, origin)
|
|
4420
|
+
);
|
|
3725
4421
|
}
|
|
3726
4422
|
const apiUrl = process.env.FIRECRAWL_API_URL;
|
|
3727
4423
|
if (!apiUrl) {
|
|
@@ -3748,7 +4444,10 @@ Set \`redactPII\` to request redaction of personally identifiable information in
|
|
|
3748
4444
|
});
|
|
3749
4445
|
form.append("file", blob, filename);
|
|
3750
4446
|
form.append("options", JSON.stringify(optionsPayload));
|
|
3751
|
-
const headers = {
|
|
4447
|
+
const headers = {
|
|
4448
|
+
...AGENT_HINTS_HEADERS,
|
|
4449
|
+
...originHeaders(origin)
|
|
4450
|
+
};
|
|
3752
4451
|
const credential = credentialForOutboundRequest(session);
|
|
3753
4452
|
if (credential) {
|
|
3754
4453
|
headers["Authorization"] = `Bearer ${credential}`;
|
|
@@ -3766,20 +4465,28 @@ Set \`redactPII\` to request redaction of personally identifiable information in
|
|
|
3766
4465
|
});
|
|
3767
4466
|
const responseText = await response.text();
|
|
3768
4467
|
if (!response.ok) {
|
|
3769
|
-
|
|
3770
|
-
|
|
4468
|
+
let parsed;
|
|
4469
|
+
try {
|
|
4470
|
+
parsed = JSON.parse(responseText);
|
|
4471
|
+
} catch {
|
|
4472
|
+
}
|
|
4473
|
+
throw new CoreHttpError(
|
|
4474
|
+
`Parse request failed with status ${response.status}: ${responseText}`,
|
|
4475
|
+
response.status,
|
|
4476
|
+
readAgentHints(parsed)
|
|
3771
4477
|
);
|
|
3772
4478
|
}
|
|
3773
4479
|
try {
|
|
3774
|
-
return
|
|
4480
|
+
return structuredText(JSON.parse(responseText));
|
|
3775
4481
|
} catch {
|
|
3776
|
-
return responseText;
|
|
4482
|
+
return withStructured(responseText, { raw: responseText });
|
|
3777
4483
|
}
|
|
3778
4484
|
}
|
|
3779
4485
|
});
|
|
3780
4486
|
function registerMarketplaceSearchTool(registrar, getClientFn) {
|
|
3781
4487
|
registrar.addTool({
|
|
3782
4488
|
name: "firecrawl_search",
|
|
4489
|
+
_meta: { "anthropic/alwaysLoad": true },
|
|
3783
4490
|
annotations: {
|
|
3784
4491
|
title: "Firecrawl web search",
|
|
3785
4492
|
readOnlyHint: true,
|
|
@@ -3795,7 +4502,8 @@ ${ALEXANDRIA_SEARCH_INSTRUCTIONS}
|
|
|
3795
4502
|
|
|
3796
4503
|
Returns result groups in \`data\` and an operation \`id\`.
|
|
3797
4504
|
`,
|
|
3798
|
-
|
|
4505
|
+
outputSchema: searchOutputSchema,
|
|
4506
|
+
parameters: z8.object({ ...searchToolBaseFields }).strict().refine(searchDomainsAreExclusive, SEARCH_DOMAINS_CONFLICT_MESSAGE).refine(
|
|
3799
4507
|
searchQueryIsValid,
|
|
3800
4508
|
"A query is required. Use firecrawl_find_tools to browse the catalogue."
|
|
3801
4509
|
),
|
|
@@ -3846,7 +4554,7 @@ Returns result groups in \`data\` and an operation \`id\`.
|
|
|
3846
4554
|
);
|
|
3847
4555
|
const context = { tool: "firecrawl_search" };
|
|
3848
4556
|
const httpRes = exchangeSource ? await relayExchangeError(postSearch, context) : await relayTermsRequired(postSearch, context);
|
|
3849
|
-
return
|
|
4557
|
+
return structuredCompact(httpRes?.data ?? {});
|
|
3850
4558
|
}
|
|
3851
4559
|
});
|
|
3852
4560
|
}
|
|
@@ -3918,10 +4626,10 @@ if (searchProfileEnabled) {
|
|
|
3918
4626
|
stateless: true
|
|
3919
4627
|
}
|
|
3920
4628
|
});
|
|
3921
|
-
} catch (
|
|
4629
|
+
} catch (error2) {
|
|
3922
4630
|
console.error(
|
|
3923
4631
|
`[search-profile] failed to start on port ${searchProfile.port}; the full surface is unaffected`,
|
|
3924
|
-
|
|
4632
|
+
error2
|
|
3925
4633
|
);
|
|
3926
4634
|
}
|
|
3927
4635
|
}
|