firecrawl-mcp 3.23.0 → 3.23.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +395 -291
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8,7 +8,63 @@ import { readFile } from "fs/promises";
|
|
|
8
8
|
import { createRequire } from "module";
|
|
9
9
|
import { randomUUID } from "crypto";
|
|
10
10
|
import path from "path";
|
|
11
|
-
import { z as
|
|
11
|
+
import { z as z4 } from "zod";
|
|
12
|
+
|
|
13
|
+
// src/developer.ts
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
var BASE = "/v2/developer/search";
|
|
16
|
+
var ORIGIN_HEADERS = { "X-Origin": "mcp-fastmcp" };
|
|
17
|
+
var MAX_PASSAGE_CHARS = 1200;
|
|
18
|
+
function fmtDeveloper(results) {
|
|
19
|
+
if (!results || results.length === 0) return "(no results)";
|
|
20
|
+
return results.map((r) => {
|
|
21
|
+
const kind = r.type ? ` (${r.type})` : "";
|
|
22
|
+
const lines = [`## [${r.id ?? "?"}]${kind} ${r.title ?? "(untitled)"}`];
|
|
23
|
+
if (r.url) lines.push(r.url);
|
|
24
|
+
const body = (r.passages ?? []).map((p) => p.text ?? "").join("\n---\n").trim();
|
|
25
|
+
lines.push(body ? body.slice(0, MAX_PASSAGE_CHARS) : "(no content)");
|
|
26
|
+
return lines.join("\n");
|
|
27
|
+
}).join("\n\n");
|
|
28
|
+
}
|
|
29
|
+
function registerDeveloperTools(server2, getClient2) {
|
|
30
|
+
server2.addTool({
|
|
31
|
+
name: "firecrawl_developer_search",
|
|
32
|
+
annotations: {
|
|
33
|
+
title: "Search developer sources",
|
|
34
|
+
readOnlyHint: true,
|
|
35
|
+
// Semantic search over an indexed developer corpus; returns ranked results only.
|
|
36
|
+
openWorldHint: true,
|
|
37
|
+
// Searches the Firecrawl developer index of public GitHub and documentation content.
|
|
38
|
+
destructiveHint: false
|
|
39
|
+
// Query-only; no writes to external sources or the developer index.
|
|
40
|
+
},
|
|
41
|
+
description: `
|
|
42
|
+
For a developer question \u2014 code behaviour, a library or framework, an API contract, an error message, or a known bug \u2014 search an index built for coding agents. The index covers GitHub issues, merged pull requests, repository READMEs, and curated documentation sites. Set skills to "only" to limit the search to agent-skill files.
|
|
43
|
+
|
|
44
|
+
Returns ranked results with an ID, source type, URL, title, and the matched passages in markdown.
|
|
45
|
+
`,
|
|
46
|
+
parameters: z.object({
|
|
47
|
+
query: z.string().min(1).describe(
|
|
48
|
+
"Natural-language developer question or search phrase, including the library, error message, or API involved when relevant."
|
|
49
|
+
),
|
|
50
|
+
k: z.number().int().min(1).max(100).optional().describe("Number of ranked results to return (default 20)."),
|
|
51
|
+
skills: z.enum(["only"]).optional().describe('Set to "only" to search only agent-skill files.')
|
|
52
|
+
}),
|
|
53
|
+
execute: async (args2, { session }) => {
|
|
54
|
+
const { query, k, skills } = args2;
|
|
55
|
+
const params = new URLSearchParams();
|
|
56
|
+
params.append("query", query);
|
|
57
|
+
if (k != null) params.append("k", String(k));
|
|
58
|
+
if (skills != null) params.append("skills", skills);
|
|
59
|
+
const client = getClient2(session);
|
|
60
|
+
const res = await client.http.get(
|
|
61
|
+
`${BASE}?${params.toString()}`,
|
|
62
|
+
ORIGIN_HEADERS
|
|
63
|
+
);
|
|
64
|
+
return fmtDeveloper(res.data?.results);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
12
68
|
|
|
13
69
|
// src/keyless-client-ip.ts
|
|
14
70
|
import net from "net";
|
|
@@ -25,7 +81,7 @@ function extractSingleTrustedClientIp(rawForwardedFor) {
|
|
|
25
81
|
}
|
|
26
82
|
|
|
27
83
|
// src/monitor.ts
|
|
28
|
-
import { z } from "zod";
|
|
84
|
+
import { z as z2 } from "zod";
|
|
29
85
|
|
|
30
86
|
// src/session-credential.ts
|
|
31
87
|
import { createHmac } from "crypto";
|
|
@@ -124,8 +180,8 @@ async function monitorRequest(session, path2, init = {}) {
|
|
|
124
180
|
function asText(data) {
|
|
125
181
|
return JSON.stringify(data, null, 2);
|
|
126
182
|
}
|
|
127
|
-
var pageStatusSchema =
|
|
128
|
-
var checkStatusSchema =
|
|
183
|
+
var pageStatusSchema = z2.enum(["same", "new", "changed", "removed", "error"]);
|
|
184
|
+
var checkStatusSchema = z2.enum([
|
|
129
185
|
"queued",
|
|
130
186
|
"running",
|
|
131
187
|
"completed",
|
|
@@ -218,22 +274,22 @@ Create a recurring scrape, crawl, or search monitor that compares each check wit
|
|
|
218
274
|
|
|
219
275
|
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.
|
|
220
276
|
`,
|
|
221
|
-
parameters:
|
|
222
|
-
body:
|
|
223
|
-
page:
|
|
224
|
-
pages:
|
|
225
|
-
queries:
|
|
226
|
-
searchWindow:
|
|
227
|
-
maxResults:
|
|
228
|
-
includeDomains:
|
|
229
|
-
excludeDomains:
|
|
230
|
-
goal:
|
|
231
|
-
name:
|
|
232
|
-
scheduleText:
|
|
233
|
-
timezone:
|
|
234
|
-
email:
|
|
235
|
-
includeDiffs:
|
|
236
|
-
webhookUrl:
|
|
277
|
+
parameters: z2.object({
|
|
278
|
+
body: z2.record(z2.string(), z2.any()).optional(),
|
|
279
|
+
page: z2.string().optional(),
|
|
280
|
+
pages: z2.array(z2.string()).optional(),
|
|
281
|
+
queries: z2.array(z2.string()).optional(),
|
|
282
|
+
searchWindow: z2.enum(["5m", "15m", "1h", "6h", "24h", "7d"]).optional(),
|
|
283
|
+
maxResults: z2.number().int().min(1).max(50).optional(),
|
|
284
|
+
includeDomains: z2.array(z2.string()).optional(),
|
|
285
|
+
excludeDomains: z2.array(z2.string()).optional(),
|
|
286
|
+
goal: z2.string().optional(),
|
|
287
|
+
name: z2.string().optional(),
|
|
288
|
+
scheduleText: z2.string().optional(),
|
|
289
|
+
timezone: z2.string().optional(),
|
|
290
|
+
email: z2.string().optional(),
|
|
291
|
+
includeDiffs: z2.boolean().optional(),
|
|
292
|
+
webhookUrl: z2.string().optional()
|
|
237
293
|
}),
|
|
238
294
|
execute: async (args2, { session, log }) => {
|
|
239
295
|
const body = buildMonitorCreateBody(args2);
|
|
@@ -259,9 +315,9 @@ In the simple form, a \`goal\` is required. If \`queries\` contains one or more
|
|
|
259
315
|
description: `
|
|
260
316
|
List monitors for the authenticated account with optional pagination controls. Returns one page of monitor records and pagination metadata.
|
|
261
317
|
`,
|
|
262
|
-
parameters:
|
|
263
|
-
limit:
|
|
264
|
-
offset:
|
|
318
|
+
parameters: z2.object({
|
|
319
|
+
limit: z2.number().int().positive().optional(),
|
|
320
|
+
offset: z2.number().int().nonnegative().optional()
|
|
265
321
|
}),
|
|
266
322
|
execute: async (args2, { session }) => {
|
|
267
323
|
const { limit, offset } = args2;
|
|
@@ -285,7 +341,7 @@ List monitors for the authenticated account with optional pagination controls. R
|
|
|
285
341
|
description: `
|
|
286
342
|
Retrieve one monitor by ID, including its configuration and current state. This does not run or modify the monitor.
|
|
287
343
|
`,
|
|
288
|
-
parameters:
|
|
344
|
+
parameters: z2.object({ id: z2.string() }),
|
|
289
345
|
execute: async (args2, { session }) => {
|
|
290
346
|
const { id } = args2;
|
|
291
347
|
const res = await monitorRequest(
|
|
@@ -311,9 +367,9 @@ Patch an existing monitor by ID. The body can change its name, active/paused sta
|
|
|
311
367
|
|
|
312
368
|
Returns the updated monitor.
|
|
313
369
|
`,
|
|
314
|
-
parameters:
|
|
315
|
-
id:
|
|
316
|
-
body:
|
|
370
|
+
parameters: z2.object({
|
|
371
|
+
id: z2.string(),
|
|
372
|
+
body: z2.record(z2.string(), z2.any())
|
|
317
373
|
}),
|
|
318
374
|
execute: async (args2, { session }) => {
|
|
319
375
|
const { id, body } = args2;
|
|
@@ -339,7 +395,7 @@ Returns the updated monitor.
|
|
|
339
395
|
description: `
|
|
340
396
|
Permanently delete a monitor by ID and stop its future schedule. This operation cannot be undone and returns deletion status.
|
|
341
397
|
`,
|
|
342
|
-
parameters:
|
|
398
|
+
parameters: z2.object({ id: z2.string() }),
|
|
343
399
|
execute: async (args2, { session, log }) => {
|
|
344
400
|
const { id } = args2;
|
|
345
401
|
log.info("Deleting monitor", { id });
|
|
@@ -365,7 +421,7 @@ Permanently delete a monitor by ID and stop its future schedule. This operation
|
|
|
365
421
|
description: `
|
|
366
422
|
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.
|
|
367
423
|
`,
|
|
368
|
-
parameters:
|
|
424
|
+
parameters: z2.object({ id: z2.string() }),
|
|
369
425
|
execute: async (args2, { session }) => {
|
|
370
426
|
const { id } = args2;
|
|
371
427
|
const res = await monitorRequest(
|
|
@@ -390,10 +446,10 @@ Queue an immediate check for a monitor outside its normal schedule. This starts
|
|
|
390
446
|
description: `
|
|
391
447
|
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.
|
|
392
448
|
`,
|
|
393
|
-
parameters:
|
|
394
|
-
id:
|
|
395
|
-
limit:
|
|
396
|
-
offset:
|
|
449
|
+
parameters: z2.object({
|
|
450
|
+
id: z2.string(),
|
|
451
|
+
limit: z2.number().int().positive().optional(),
|
|
452
|
+
offset: z2.number().int().nonnegative().optional(),
|
|
397
453
|
status: checkStatusSchema.optional()
|
|
398
454
|
}),
|
|
399
455
|
execute: async (args2, { session }) => {
|
|
@@ -422,11 +478,11 @@ Retrieve one monitor check and its page-level results, optionally filtered by pa
|
|
|
422
478
|
|
|
423
479
|
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.
|
|
424
480
|
`,
|
|
425
|
-
parameters:
|
|
426
|
-
id:
|
|
427
|
-
checkId:
|
|
428
|
-
limit:
|
|
429
|
-
skip:
|
|
481
|
+
parameters: z2.object({
|
|
482
|
+
id: z2.string(),
|
|
483
|
+
checkId: z2.string(),
|
|
484
|
+
limit: z2.number().int().positive().optional(),
|
|
485
|
+
skip: z2.number().int().nonnegative().optional(),
|
|
430
486
|
pageStatus: pageStatusSchema.optional()
|
|
431
487
|
}),
|
|
432
488
|
execute: async (args2, { session }) => {
|
|
@@ -442,9 +498,9 @@ Markdown tracking returns a unified text diff, JSON tracking returns field paths
|
|
|
442
498
|
}
|
|
443
499
|
|
|
444
500
|
// src/research.ts
|
|
445
|
-
import { z as
|
|
446
|
-
var
|
|
447
|
-
var
|
|
501
|
+
import { z as z3 } from "zod";
|
|
502
|
+
var BASE2 = "/v2/search/research";
|
|
503
|
+
var ORIGIN_HEADERS2 = { "X-Origin": "mcp-fastmcp" };
|
|
448
504
|
function appendParam(params, key, value) {
|
|
449
505
|
if (value == null) return;
|
|
450
506
|
if (Array.isArray(value)) {
|
|
@@ -566,21 +622,21 @@ For topics represented in the indexed corpus, search paper metadata and abstract
|
|
|
566
622
|
|
|
567
623
|
Returns ranked papers with canonical IDs, titles, authors, and abstracts.
|
|
568
624
|
`,
|
|
569
|
-
parameters:
|
|
570
|
-
query:
|
|
625
|
+
parameters: z3.object({
|
|
626
|
+
query: z3.string().min(1).describe(
|
|
571
627
|
"Natural-language research topic or question, including methods, systems, conditions, populations, interventions, or outcomes when relevant."
|
|
572
628
|
),
|
|
573
|
-
k:
|
|
574
|
-
authors:
|
|
629
|
+
k: z3.number().int().min(1).max(500).optional().describe("Number of ranked papers to return (default 40)."),
|
|
630
|
+
authors: z3.array(z3.string()).optional().describe(
|
|
575
631
|
"Author substring filter(s); ALL must match (case-insensitive)."
|
|
576
632
|
),
|
|
577
|
-
categories:
|
|
633
|
+
categories: z3.array(z3.string()).optional().describe(
|
|
578
634
|
"Paper category filter(s) (e.g. `cs.LG`); ALL provided values must match."
|
|
579
635
|
),
|
|
580
|
-
from:
|
|
636
|
+
from: z3.string().optional().describe(
|
|
581
637
|
"Inclusive lower bound on created/updated date (`YYYY-MM-DD`)."
|
|
582
638
|
),
|
|
583
|
-
to:
|
|
639
|
+
to: z3.string().optional().describe(
|
|
584
640
|
"Inclusive upper bound on created/updated date (`YYYY-MM-DD`)."
|
|
585
641
|
)
|
|
586
642
|
}),
|
|
@@ -595,8 +651,8 @@ Returns ranked papers with canonical IDs, titles, authors, and abstracts.
|
|
|
595
651
|
appendParam(params, "to", to);
|
|
596
652
|
const client = getClient2(session);
|
|
597
653
|
const res = await client.http.get(
|
|
598
|
-
withQuery(`${
|
|
599
|
-
|
|
654
|
+
withQuery(`${BASE2}/papers`, params),
|
|
655
|
+
ORIGIN_HEADERS2
|
|
600
656
|
);
|
|
601
657
|
return fmtHits(res.data?.results);
|
|
602
658
|
}
|
|
@@ -615,8 +671,8 @@ Returns ranked papers with canonical IDs, titles, authors, and abstracts.
|
|
|
615
671
|
description: `
|
|
616
672
|
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.
|
|
617
673
|
`,
|
|
618
|
-
parameters:
|
|
619
|
-
paperId:
|
|
674
|
+
parameters: z3.object({
|
|
675
|
+
paperId: z3.string().min(1).describe(
|
|
620
676
|
"Canonical paperId or primaryId such as `arxiv:1706.03762`, `pmcid:PMC12530322`, `pmid:40953549`, or `doi:10.1016/j.neunet.2025.108095`."
|
|
621
677
|
)
|
|
622
678
|
}),
|
|
@@ -624,8 +680,8 @@ Retrieve canonical metadata for one paper ID, such as an arXiv, PMC, PMID, or DO
|
|
|
624
680
|
const { paperId } = args2;
|
|
625
681
|
const client = getClient2(session);
|
|
626
682
|
const res = await client.http.get(
|
|
627
|
-
`${
|
|
628
|
-
|
|
683
|
+
`${BASE2}/papers/${encodeURIComponent(paperId)}`,
|
|
684
|
+
ORIGIN_HEADERS2
|
|
629
685
|
);
|
|
630
686
|
return fmtPaperMetadata(res.data?.paper);
|
|
631
687
|
}
|
|
@@ -646,12 +702,12 @@ Find citation-graph candidates from one to ten \`seed_ids\`; the first ID is the
|
|
|
646
702
|
|
|
647
703
|
Returns ranked candidates and the evaluated pool size.
|
|
648
704
|
`,
|
|
649
|
-
parameters:
|
|
650
|
-
seed_ids:
|
|
651
|
-
intent:
|
|
652
|
-
mode:
|
|
653
|
-
k:
|
|
654
|
-
rerank:
|
|
705
|
+
parameters: z3.object({
|
|
706
|
+
seed_ids: z3.array(z3.string()).min(1).max(10),
|
|
707
|
+
intent: z3.string().min(1),
|
|
708
|
+
mode: z3.enum(["similar", "citers", "references"]).optional(),
|
|
709
|
+
k: z3.number().int().min(1).max(500).optional(),
|
|
710
|
+
rerank: z3.boolean().optional().describe("Apply an additional rerank over the fused candidates.")
|
|
655
711
|
}),
|
|
656
712
|
execute: async (args2, { session }) => {
|
|
657
713
|
const { seed_ids, intent, mode, k, rerank } = args2;
|
|
@@ -665,10 +721,10 @@ Returns ranked candidates and the evaluated pool size.
|
|
|
665
721
|
const client = getClient2(session);
|
|
666
722
|
const res = await client.http.get(
|
|
667
723
|
withQuery(
|
|
668
|
-
`${
|
|
724
|
+
`${BASE2}/papers/${encodeURIComponent(primary)}/similar`,
|
|
669
725
|
params
|
|
670
726
|
),
|
|
671
|
-
|
|
727
|
+
ORIGIN_HEADERS2
|
|
672
728
|
);
|
|
673
729
|
const note = res.data?.note ? `
|
|
674
730
|
note: ${res.data.note}` : "";
|
|
@@ -692,12 +748,12 @@ Retrieve in-body passages from one paper that are relevant to a specific questio
|
|
|
692
748
|
|
|
693
749
|
Returns matching passages or a notice when full text is unavailable.
|
|
694
750
|
`,
|
|
695
|
-
parameters:
|
|
696
|
-
paperId:
|
|
751
|
+
parameters: z3.object({
|
|
752
|
+
paperId: z3.string().min(1).describe(
|
|
697
753
|
"Canonical paperId or primaryId such as `arxiv:1706.03762`, `pmcid:PMC12530322`, `pmid:40953549`, or `doi:10.1016/j.neunet.2025.108095`."
|
|
698
754
|
),
|
|
699
|
-
question:
|
|
700
|
-
k:
|
|
755
|
+
question: z3.string().min(1),
|
|
756
|
+
k: z3.number().int().min(1).max(50).optional().describe("Number of passages to return (default 4).")
|
|
701
757
|
}),
|
|
702
758
|
execute: async (args2, { session }) => {
|
|
703
759
|
const { paperId, question, k } = args2;
|
|
@@ -706,8 +762,8 @@ Returns matching passages or a notice when full text is unavailable.
|
|
|
706
762
|
appendParam(params, "k", k);
|
|
707
763
|
const client = getClient2(session);
|
|
708
764
|
const res = await client.http.get(
|
|
709
|
-
withQuery(`${
|
|
710
|
-
|
|
765
|
+
withQuery(`${BASE2}/papers/${encodeURIComponent(paperId)}`, params),
|
|
766
|
+
ORIGIN_HEADERS2
|
|
711
767
|
);
|
|
712
768
|
const passages = res.data?.passages ?? [];
|
|
713
769
|
return passages.length ? passages.map((p) => p.text).join("\n---\n") : "(no full-text passages available for this paper)";
|
|
@@ -727,9 +783,9 @@ Returns matching passages or a notice when full text is unavailable.
|
|
|
727
783
|
description: `
|
|
728
784
|
Search indexed public GitHub issue, pull-request, and README content. Returns ranked matches with repository, URL, snippet, and full matched markdown when available.
|
|
729
785
|
`,
|
|
730
|
-
parameters:
|
|
731
|
-
query:
|
|
732
|
-
k:
|
|
786
|
+
parameters: z3.object({
|
|
787
|
+
query: z3.string().min(1),
|
|
788
|
+
k: z3.number().int().min(1).max(100).optional()
|
|
733
789
|
}),
|
|
734
790
|
execute: async (args2, { session }) => {
|
|
735
791
|
const { query, k } = args2;
|
|
@@ -738,8 +794,8 @@ Search indexed public GitHub issue, pull-request, and README content. Returns ra
|
|
|
738
794
|
appendParam(params, "k", k);
|
|
739
795
|
const client = getClient2(session);
|
|
740
796
|
const res = await client.http.get(
|
|
741
|
-
withQuery(`${
|
|
742
|
-
|
|
797
|
+
withQuery(`${BASE2}/github`, params),
|
|
798
|
+
ORIGIN_HEADERS2
|
|
743
799
|
);
|
|
744
800
|
return fmtGithub(res.data?.results);
|
|
745
801
|
}
|
|
@@ -1160,7 +1216,7 @@ function removeEmptyTopLevel(obj) {
|
|
|
1160
1216
|
}
|
|
1161
1217
|
return out;
|
|
1162
1218
|
}
|
|
1163
|
-
var searchDomainSchema =
|
|
1219
|
+
var searchDomainSchema = z4.string().trim().toLowerCase().min(1).max(253).regex(
|
|
1164
1220
|
/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/,
|
|
1165
1221
|
"Domain must be a valid hostname without protocol or path"
|
|
1166
1222
|
);
|
|
@@ -1174,21 +1230,21 @@ function buildSearchQueryWithDomains(query, includeDomains, excludeDomains) {
|
|
|
1174
1230
|
return query;
|
|
1175
1231
|
}
|
|
1176
1232
|
var searchToolBaseFields = {
|
|
1177
|
-
query:
|
|
1178
|
-
highlights:
|
|
1233
|
+
query: z4.string().min(1),
|
|
1234
|
+
highlights: z4.boolean().optional().describe(
|
|
1179
1235
|
"Return query-relevant highlights for each search result. Set to false to keep the original search snippets."
|
|
1180
1236
|
),
|
|
1181
|
-
limit:
|
|
1182
|
-
tbs:
|
|
1183
|
-
filter:
|
|
1184
|
-
location:
|
|
1185
|
-
includeDomains:
|
|
1186
|
-
excludeDomains:
|
|
1187
|
-
sources:
|
|
1188
|
-
categories:
|
|
1189
|
-
"Limit results to specific source types. `github` searches GitHub repositories, code, issues, and docs; `research` searches academic and research sources; `pdf` searches PDF results."
|
|
1237
|
+
limit: z4.number().optional(),
|
|
1238
|
+
tbs: z4.string().optional(),
|
|
1239
|
+
filter: z4.string().optional(),
|
|
1240
|
+
location: z4.string().optional(),
|
|
1241
|
+
includeDomains: z4.array(searchDomainSchema).optional(),
|
|
1242
|
+
excludeDomains: z4.array(searchDomainSchema).optional(),
|
|
1243
|
+
sources: z4.array(z4.object({ type: z4.enum(["web", "images", "news"]) })).optional(),
|
|
1244
|
+
categories: z4.array(z4.enum(["github", "research", "pdf", "developer"])).optional().describe(
|
|
1245
|
+
"Limit results to specific source types. `github` searches GitHub repositories, code, issues, and docs; `research` searches academic and research sources; `pdf` searches PDF results; `developer` searches an index built for coding agents over GitHub issues, merged pull requests, repository READMEs, and curated documentation sites. `developer` adds a `data.developer` group of `{ url, title, description }` results, where `description` holds the matched passage; the other categories filter `data.web`."
|
|
1190
1246
|
),
|
|
1191
|
-
enterprise:
|
|
1247
|
+
enterprise: z4.array(z4.enum(["default", "anon", "zdr"])).optional()
|
|
1192
1248
|
};
|
|
1193
1249
|
function searchDomainsAreExclusive(args2) {
|
|
1194
1250
|
return !(args2.includeDomains?.length && args2.excludeDomains?.length);
|
|
@@ -1227,9 +1283,10 @@ var openAiAppsChallengeToken = normalizeHeader(
|
|
|
1227
1283
|
);
|
|
1228
1284
|
var FULL_PROFILE_INSTRUCTIONS = `Firecrawl provides web search, page retrieval, site URL discovery, multi-page collection, structured extraction, monitoring, and asynchronous research. Match the requested operation to the tool boundary: firecrawl_scrape retrieves one supplied page, firecrawl_map enumerates URLs under a site without retrieving their content, and firecrawl_agent starts multi-source research whose result is read with firecrawl_agent_status. Provide only the required inputs and account for stated network or external side effects.`;
|
|
1229
1285
|
var KEYLESS_PROFILE_INSTRUCTIONS = `Without authentication, this endpoint exposes Search, Scrape, and Parse with usage limits. An OAuth connection or Authorization bearer API key exposes account tools; unavailable tools return connection guidance. Firecrawl provides web search, page retrieval, site URL discovery, multi-page collection, structured extraction, monitoring, and asynchronous research. Match the requested operation to the tool boundary: firecrawl_scrape retrieves one supplied page, firecrawl_map enumerates URLs under a site without retrieving their content, and firecrawl_agent starts multi-source research whose result is read with firecrawl_agent_status. Provide only the required inputs.`;
|
|
1230
|
-
var SEARCH_PROFILE_INSTRUCTIONS = `Firecrawl provides web and research search. Use firecrawl_search to find relevant results across the web and specialized indexes. Use the firecrawl_research_* tools to search academic and research literature, expand from anchor papers via the citation graph, read full-text passages from a specific paper, and search public code repositories. All tools are read-only and return ranked results.`;
|
|
1286
|
+
var SEARCH_PROFILE_INSTRUCTIONS = `Firecrawl provides web, developer, and research search. Use firecrawl_search to find relevant results across the web and specialized indexes. For a programming question, call firecrawl_developer_search \u2014 or firecrawl_search with categories: ["developer"] \u2014 to search indexed GitHub issues, merged pull requests, READMEs, and documentation. Use the firecrawl_research_* tools to search academic and research literature, expand from anchor papers via the citation graph, read full-text passages from a specific paper, and search public code repositories. All tools are read-only and return ranked results.`;
|
|
1231
1287
|
var SEARCH_PROFILE_TOOLS = /* @__PURE__ */ new Set([
|
|
1232
1288
|
"firecrawl_search",
|
|
1289
|
+
"firecrawl_developer_search",
|
|
1233
1290
|
"firecrawl_research_search_papers",
|
|
1234
1291
|
"firecrawl_research_inspect_paper",
|
|
1235
1292
|
"firecrawl_research_related_papers",
|
|
@@ -1318,24 +1375,34 @@ var KEYLESS_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
|
1318
1375
|
function isHostedKeylessSession(session) {
|
|
1319
1376
|
return process.env.CLOUD_SERVICE === "true" && session?.authType === "keyless" && !session.firecrawlApiKey;
|
|
1320
1377
|
}
|
|
1321
|
-
function
|
|
1378
|
+
function isLocalKeylessStartup() {
|
|
1379
|
+
return process.env.CLOUD_SERVICE !== "true" && !isHttpStreamingTransport() && !resolveCredentialFromEnv() && !normalizeHeader(process.env.FIRECRAWL_API_URL);
|
|
1380
|
+
}
|
|
1381
|
+
function recoveryPayload(code, requestId = randomUUID(), options = {}) {
|
|
1382
|
+
const retryAfterSeconds = options.retryAfterSeconds;
|
|
1383
|
+
const isQuotaExhausted = code === "KEYLESS_QUOTA_EXHAUSTED" || code === "KEYLESS_LIMIT_REACHED";
|
|
1384
|
+
const isToolUnavailable = code === "KEYLESS_TOOL_NOT_AVAILABLE";
|
|
1385
|
+
const isKeylessAccessUnavailable = code === "KEYLESS_ACCESS_NOT_AVAILABLE";
|
|
1386
|
+
const isKeylessEligibilityUnavailable = code === "KEYLESS_ELIGIBILITY_UNAVAILABLE";
|
|
1387
|
+
const oauthUrl = "https://mcp.firecrawl.dev/v2/mcp-oauth";
|
|
1322
1388
|
return {
|
|
1323
1389
|
code,
|
|
1324
1390
|
request_id: requestId,
|
|
1325
1391
|
auth_mode: code === "CREDENTIAL_INVALID" ? "credential_error" : "keyless",
|
|
1326
|
-
message: code === "CREDENTIAL_INVALID" ? "The supplied Firecrawl credential is invalid or revoked. Replace it or reconnect the account, then retry." : "This tool requires a Firecrawl account or API key. Connect an account or configure Authorization: Bearer <FIRECRAWL_API_KEY>, then retry.",
|
|
1327
|
-
available_tools: [...KEYLESS_TOOL_NAMES],
|
|
1392
|
+
message: code === "CREDENTIAL_INVALID" ? "The supplied Firecrawl credential is invalid or revoked. Replace it or reconnect the account, then retry." : isQuotaExhausted ? `The free daily limit for this network has been reached${retryAfterSeconds ? `; try again in about ${retryAfterSeconds} seconds` : ""}. To continue now, connect a Firecrawl account via OAuth or configure an API key.` : isToolUnavailable ? "The free tier includes Search, Scrape, and Parse. This tool needs a connected account; Search, Scrape, and Parse still work, so no action is required." : isKeylessAccessUnavailable ? "Anonymous keyless access is unavailable for this request. To continue, connect a Firecrawl account via OAuth or configure an API key." : isKeylessEligibilityUnavailable ? "The anonymous keyless eligibility check is temporarily unavailable. Retry shortly." : "This tool requires a Firecrawl account or API key. Connect an account or configure Authorization: Bearer <FIRECRAWL_API_KEY>, then retry.",
|
|
1393
|
+
...isKeylessAccessUnavailable ? {} : { available_tools: [...KEYLESS_TOOL_NAMES] },
|
|
1328
1394
|
docs_url: "https://docs.firecrawl.dev/mcp-server",
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
{
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
}
|
|
1395
|
+
...retryAfterSeconds ? { retry_after_seconds: retryAfterSeconds } : {},
|
|
1396
|
+
next_actions: isKeylessEligibilityUnavailable ? [{ kind: "retry_later", after_seconds: 30 }] : isQuotaExhausted || isKeylessAccessUnavailable ? [
|
|
1397
|
+
{ kind: "connect_oauth", url: oauthUrl, client_commands: [{ client: "claude-code", command: "claude mcp add --transport http firecrawl https://mcp.firecrawl.dev/v2/mcp-oauth" }] },
|
|
1398
|
+
{ kind: "configure_api_key", header: "Authorization: Bearer <FIRECRAWL_API_KEY>" }
|
|
1399
|
+
] : isToolUnavailable ? [{ kind: "continue_keyless", tools: [...KEYLESS_TOOL_NAMES] }] : [
|
|
1400
|
+
{ kind: "connect_oauth", url: oauthUrl },
|
|
1401
|
+
{ kind: "configure_api_key", header: "Authorization: Bearer <FIRECRAWL_API_KEY>" }
|
|
1335
1402
|
]
|
|
1336
1403
|
};
|
|
1337
1404
|
}
|
|
1338
|
-
function emitActionLog(toolName, status, session, error, requestId = randomUUID()) {
|
|
1405
|
+
function emitActionLog(toolName, status, session, error, requestId = randomUUID(), code) {
|
|
1339
1406
|
if (process.env.CLOUD_SERVICE !== "true") return;
|
|
1340
1407
|
const payload = {
|
|
1341
1408
|
team_id: session?.teamId,
|
|
@@ -1347,20 +1414,23 @@ function emitActionLog(toolName, status, session, error, requestId = randomUUID(
|
|
|
1347
1414
|
status,
|
|
1348
1415
|
request_id: requestId,
|
|
1349
1416
|
resource: primaryProfile.resourceUrl,
|
|
1350
|
-
...error ? { error_class: error instanceof Error ? error.name : typeof error } : {}
|
|
1417
|
+
...error ? { error_class: error instanceof Error ? error.name : typeof error } : {},
|
|
1418
|
+
...code ? { code } : {}
|
|
1351
1419
|
};
|
|
1352
1420
|
console.error("[MCP_ACTION]", JSON.stringify(payload));
|
|
1353
1421
|
const secret = normalizeHeader(process.env.FIRECRAWL_MCP_ACTION_LOG_SECRET);
|
|
1354
1422
|
const apiUrl = normalizeHeader(process.env.FIRECRAWL_API_URL);
|
|
1355
1423
|
const endpoint = normalizeHeader(process.env.FIRECRAWL_MCP_ACTION_LOG_URL) ?? (apiUrl ? `${withoutTrailingSlash(apiUrl)}/v2/mcp/action-logs` : void 0);
|
|
1356
1424
|
if (!secret || !endpoint || !payload.team_id || status === "started") return;
|
|
1425
|
+
const actionLogPayload = { ...payload };
|
|
1426
|
+
delete actionLogPayload.code;
|
|
1357
1427
|
void fetch(endpoint, {
|
|
1358
1428
|
method: "POST",
|
|
1359
1429
|
headers: {
|
|
1360
1430
|
Authorization: `Bearer ${secret}`,
|
|
1361
1431
|
"Content-Type": "application/json"
|
|
1362
1432
|
},
|
|
1363
|
-
body: JSON.stringify(
|
|
1433
|
+
body: JSON.stringify(actionLogPayload),
|
|
1364
1434
|
signal: AbortSignal.timeout(1500)
|
|
1365
1435
|
}).catch(() => void 0);
|
|
1366
1436
|
}
|
|
@@ -1373,7 +1443,11 @@ function guardHostedTool(tool, { logActions }) {
|
|
|
1373
1443
|
beforeValidate: (_args, session) => {
|
|
1374
1444
|
const code = session?.credentialError ? "CREDENTIAL_INVALID" : isHostedKeylessSession(session) && !keylessTool ? "KEYLESS_TOOL_NOT_AVAILABLE" : void 0;
|
|
1375
1445
|
if (!code) return void 0;
|
|
1376
|
-
const
|
|
1446
|
+
const requestId = randomUUID();
|
|
1447
|
+
const payload = recoveryPayload(code, requestId);
|
|
1448
|
+
if (logActions) {
|
|
1449
|
+
emitActionLog(tool.name, "error", session, new UserError(String(payload.message), payload), requestId, code);
|
|
1450
|
+
}
|
|
1377
1451
|
return {
|
|
1378
1452
|
content: [{ type: "text", text: String(payload.message) }],
|
|
1379
1453
|
isError: true,
|
|
@@ -1392,11 +1466,15 @@ function guardHostedTool(tool, { logActions }) {
|
|
|
1392
1466
|
session: invocationSession
|
|
1393
1467
|
};
|
|
1394
1468
|
if (invocationSession.credentialError) {
|
|
1395
|
-
const
|
|
1469
|
+
const code = "CREDENTIAL_INVALID";
|
|
1470
|
+
const payload = recoveryPayload(code, requestId);
|
|
1471
|
+
if (logActions) emitActionLog(tool.name, "error", invocationSession, new UserError(String(payload.message), payload), requestId, code);
|
|
1396
1472
|
throw new UserError(String(payload.message), payload);
|
|
1397
1473
|
}
|
|
1398
1474
|
if (isHostedKeylessSession(invocationSession) && !keylessTool) {
|
|
1399
|
-
const
|
|
1475
|
+
const code = "KEYLESS_TOOL_NOT_AVAILABLE";
|
|
1476
|
+
const payload = recoveryPayload(code, requestId);
|
|
1477
|
+
if (logActions) emitActionLog(tool.name, "error", invocationSession, new UserError(String(payload.message), payload), requestId, code);
|
|
1400
1478
|
throw new UserError(String(payload.message), payload);
|
|
1401
1479
|
}
|
|
1402
1480
|
if (!logActions) return execute(args2, invocationContext);
|
|
@@ -1465,7 +1543,7 @@ function createClient(apiKey) {
|
|
|
1465
1543
|
return new FirecrawlApp(config);
|
|
1466
1544
|
}
|
|
1467
1545
|
var ORIGIN = "mcp-fastmcp";
|
|
1468
|
-
var
|
|
1546
|
+
var ORIGIN_HEADERS3 = { "X-Origin": ORIGIN };
|
|
1469
1547
|
var SAFE_MODE = process.env.CLOUD_SERVICE === "true";
|
|
1470
1548
|
function getClient(session) {
|
|
1471
1549
|
if (process.env.CLOUD_SERVICE === "true" && !hasCredential(session)) {
|
|
@@ -1563,10 +1641,10 @@ function transformScrapeParams(args2) {
|
|
|
1563
1641
|
delete out.pdfOptions;
|
|
1564
1642
|
return out;
|
|
1565
1643
|
}
|
|
1566
|
-
var scrapeParamsSchema =
|
|
1567
|
-
url:
|
|
1568
|
-
formats:
|
|
1569
|
-
|
|
1644
|
+
var scrapeParamsSchema = z4.object({
|
|
1645
|
+
url: z4.string().url(),
|
|
1646
|
+
formats: z4.array(
|
|
1647
|
+
z4.enum([
|
|
1570
1648
|
"markdown",
|
|
1571
1649
|
"html",
|
|
1572
1650
|
"rawHtml",
|
|
@@ -1580,62 +1658,62 @@ var scrapeParamsSchema = z3.object({
|
|
|
1580
1658
|
"audio"
|
|
1581
1659
|
])
|
|
1582
1660
|
).optional(),
|
|
1583
|
-
jsonOptions:
|
|
1584
|
-
prompt:
|
|
1585
|
-
schema:
|
|
1661
|
+
jsonOptions: z4.object({
|
|
1662
|
+
prompt: z4.string().optional(),
|
|
1663
|
+
schema: z4.record(z4.string(), z4.any()).optional()
|
|
1586
1664
|
}).optional(),
|
|
1587
|
-
queryOptions:
|
|
1588
|
-
prompt:
|
|
1589
|
-
mode:
|
|
1665
|
+
queryOptions: z4.object({
|
|
1666
|
+
prompt: z4.string().max(1e4),
|
|
1667
|
+
mode: z4.enum(["directQuote", "freeform"]).default("freeform")
|
|
1590
1668
|
}).optional(),
|
|
1591
|
-
screenshotOptions:
|
|
1592
|
-
fullPage:
|
|
1593
|
-
quality:
|
|
1594
|
-
viewport:
|
|
1669
|
+
screenshotOptions: z4.object({
|
|
1670
|
+
fullPage: z4.boolean().optional(),
|
|
1671
|
+
quality: z4.number().optional(),
|
|
1672
|
+
viewport: z4.object({ width: z4.number(), height: z4.number() }).optional()
|
|
1595
1673
|
}).optional(),
|
|
1596
|
-
parsers:
|
|
1597
|
-
pdfOptions:
|
|
1598
|
-
maxPages:
|
|
1674
|
+
parsers: z4.array(z4.enum(["pdf"])).optional(),
|
|
1675
|
+
pdfOptions: z4.object({
|
|
1676
|
+
maxPages: z4.number().int().min(1).max(1e4).optional()
|
|
1599
1677
|
}).optional(),
|
|
1600
|
-
onlyMainContent:
|
|
1601
|
-
redactPII:
|
|
1602
|
-
includeTags:
|
|
1603
|
-
excludeTags:
|
|
1604
|
-
waitFor:
|
|
1678
|
+
onlyMainContent: z4.boolean().optional(),
|
|
1679
|
+
redactPII: z4.boolean().optional(),
|
|
1680
|
+
includeTags: z4.array(z4.string()).optional(),
|
|
1681
|
+
excludeTags: z4.array(z4.string()).optional(),
|
|
1682
|
+
waitFor: z4.number().optional(),
|
|
1605
1683
|
...SAFE_MODE ? {} : {
|
|
1606
|
-
actions:
|
|
1607
|
-
|
|
1608
|
-
type:
|
|
1609
|
-
selector:
|
|
1610
|
-
milliseconds:
|
|
1611
|
-
text:
|
|
1612
|
-
key:
|
|
1613
|
-
direction:
|
|
1614
|
-
script:
|
|
1615
|
-
fullPage:
|
|
1684
|
+
actions: z4.array(
|
|
1685
|
+
z4.object({
|
|
1686
|
+
type: z4.enum(allowedActionTypes),
|
|
1687
|
+
selector: z4.string().optional(),
|
|
1688
|
+
milliseconds: z4.number().optional(),
|
|
1689
|
+
text: z4.string().optional(),
|
|
1690
|
+
key: z4.string().optional(),
|
|
1691
|
+
direction: z4.enum(["up", "down"]).optional(),
|
|
1692
|
+
script: z4.string().optional(),
|
|
1693
|
+
fullPage: z4.boolean().optional()
|
|
1616
1694
|
})
|
|
1617
1695
|
).optional()
|
|
1618
1696
|
},
|
|
1619
|
-
mobile:
|
|
1620
|
-
skipTlsVerification:
|
|
1621
|
-
removeBase64Images:
|
|
1622
|
-
location:
|
|
1623
|
-
country:
|
|
1624
|
-
languages:
|
|
1697
|
+
mobile: z4.boolean().optional(),
|
|
1698
|
+
skipTlsVerification: z4.boolean().optional(),
|
|
1699
|
+
removeBase64Images: z4.boolean().optional(),
|
|
1700
|
+
location: z4.object({
|
|
1701
|
+
country: z4.string().optional(),
|
|
1702
|
+
languages: z4.array(z4.string()).optional()
|
|
1625
1703
|
}).optional(),
|
|
1626
|
-
storeInCache:
|
|
1627
|
-
zeroDataRetention:
|
|
1628
|
-
maxAge:
|
|
1629
|
-
lockdown:
|
|
1630
|
-
proxy:
|
|
1631
|
-
profile:
|
|
1632
|
-
name:
|
|
1633
|
-
saveChanges:
|
|
1704
|
+
storeInCache: z4.boolean().optional(),
|
|
1705
|
+
zeroDataRetention: z4.boolean().optional(),
|
|
1706
|
+
maxAge: z4.number().optional(),
|
|
1707
|
+
lockdown: z4.boolean().optional(),
|
|
1708
|
+
proxy: z4.enum(["basic", "stealth", "enhanced", "auto"]).optional(),
|
|
1709
|
+
profile: z4.object({
|
|
1710
|
+
name: z4.string(),
|
|
1711
|
+
saveChanges: z4.boolean().optional()
|
|
1634
1712
|
}).optional()
|
|
1635
1713
|
});
|
|
1636
|
-
var parseOptionParamsSchema =
|
|
1637
|
-
formats:
|
|
1638
|
-
|
|
1714
|
+
var parseOptionParamsSchema = z4.object({
|
|
1715
|
+
formats: z4.array(
|
|
1716
|
+
z4.enum([
|
|
1639
1717
|
"markdown",
|
|
1640
1718
|
"html",
|
|
1641
1719
|
"rawHtml",
|
|
@@ -1645,48 +1723,48 @@ var parseOptionParamsSchema = z3.object({
|
|
|
1645
1723
|
"query"
|
|
1646
1724
|
])
|
|
1647
1725
|
).optional(),
|
|
1648
|
-
jsonOptions:
|
|
1649
|
-
prompt:
|
|
1650
|
-
schema:
|
|
1726
|
+
jsonOptions: z4.object({
|
|
1727
|
+
prompt: z4.string().optional(),
|
|
1728
|
+
schema: z4.record(z4.string(), z4.any()).optional()
|
|
1651
1729
|
}).optional(),
|
|
1652
|
-
queryOptions:
|
|
1653
|
-
prompt:
|
|
1654
|
-
mode:
|
|
1730
|
+
queryOptions: z4.object({
|
|
1731
|
+
prompt: z4.string().max(1e4),
|
|
1732
|
+
mode: z4.enum(["directQuote", "freeform"]).default("freeform")
|
|
1655
1733
|
}).optional(),
|
|
1656
|
-
parsers:
|
|
1657
|
-
pdfOptions:
|
|
1658
|
-
maxPages:
|
|
1734
|
+
parsers: z4.array(z4.enum(["pdf"])).optional(),
|
|
1735
|
+
pdfOptions: z4.object({
|
|
1736
|
+
maxPages: z4.number().int().min(1).max(1e4).optional()
|
|
1659
1737
|
}).optional(),
|
|
1660
|
-
onlyMainContent:
|
|
1661
|
-
redactPII:
|
|
1662
|
-
includeTags:
|
|
1663
|
-
excludeTags:
|
|
1664
|
-
removeBase64Images:
|
|
1665
|
-
skipTlsVerification:
|
|
1666
|
-
storeInCache:
|
|
1667
|
-
zeroDataRetention:
|
|
1668
|
-
maxAge:
|
|
1669
|
-
proxy:
|
|
1738
|
+
onlyMainContent: z4.boolean().optional(),
|
|
1739
|
+
redactPII: z4.boolean().optional(),
|
|
1740
|
+
includeTags: z4.array(z4.string()).optional(),
|
|
1741
|
+
excludeTags: z4.array(z4.string()).optional(),
|
|
1742
|
+
removeBase64Images: z4.boolean().optional(),
|
|
1743
|
+
skipTlsVerification: z4.boolean().optional(),
|
|
1744
|
+
storeInCache: z4.boolean().optional(),
|
|
1745
|
+
zeroDataRetention: z4.boolean().optional(),
|
|
1746
|
+
maxAge: z4.number().optional(),
|
|
1747
|
+
proxy: z4.enum(["basic", "auto"]).optional()
|
|
1670
1748
|
});
|
|
1671
1749
|
var localParseParamsSchema = parseOptionParamsSchema.extend({
|
|
1672
|
-
filePath:
|
|
1750
|
+
filePath: z4.string().min(1).describe(
|
|
1673
1751
|
"Absolute or relative path to a local file to parse. Supported: .html, .htm, .pdf, .docx, .doc, .odt, .rtf, .xlsx, .xls"
|
|
1674
1752
|
),
|
|
1675
|
-
contentType:
|
|
1753
|
+
contentType: z4.string().optional().describe(
|
|
1676
1754
|
"Optional MIME type override. If omitted, the server infers the file kind from the extension."
|
|
1677
1755
|
)
|
|
1678
1756
|
});
|
|
1679
1757
|
var hostedParseParamsSchema = parseOptionParamsSchema.extend({
|
|
1680
|
-
filePath:
|
|
1758
|
+
filePath: z4.string().min(1).optional().describe(
|
|
1681
1759
|
"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."
|
|
1682
1760
|
),
|
|
1683
|
-
uploadRef:
|
|
1761
|
+
uploadRef: z4.string().min(1).optional().describe(
|
|
1684
1762
|
"Phase 2 only: short-lived upload reference returned by phase 1 after the local PUT upload completes."
|
|
1685
1763
|
),
|
|
1686
|
-
contentType:
|
|
1764
|
+
contentType: z4.string().optional().describe(
|
|
1687
1765
|
"Phase 1 MIME type override. If omitted, the server infers it from the file extension without reading the file."
|
|
1688
1766
|
),
|
|
1689
|
-
declaredSizeBytes:
|
|
1767
|
+
declaredSizeBytes: z4.number().int().positive().optional().describe(
|
|
1690
1768
|
"Optional phase 1 size declaration. Hosted MCP does not stat the file; provide this only if the caller already knows it."
|
|
1691
1769
|
)
|
|
1692
1770
|
}).superRefine((value, ctx) => {
|
|
@@ -1694,7 +1772,7 @@ var hostedParseParamsSchema = parseOptionParamsSchema.extend({
|
|
|
1694
1772
|
const hasUploadRef = typeof value.uploadRef === "string" && value.uploadRef.length > 0;
|
|
1695
1773
|
if (hasFilePath === hasUploadRef) {
|
|
1696
1774
|
ctx.addIssue({
|
|
1697
|
-
code:
|
|
1775
|
+
code: z4.ZodIssueCode.custom,
|
|
1698
1776
|
message: "Hosted firecrawl_parse requires exactly one of filePath (phase 1) or uploadRef (phase 2).",
|
|
1699
1777
|
path: hasFilePath && hasUploadRef ? ["uploadRef"] : ["filePath"]
|
|
1700
1778
|
});
|
|
@@ -1953,13 +2031,13 @@ Enumerate URLs indexed under one website through Firecrawl without fetching each
|
|
|
1953
2031
|
|
|
1954
2032
|
Returns matching URLs rather than page bodies. Retrieve one page with \`firecrawl_scrape\`; collect content across multiple pages with \`firecrawl_crawl\`.
|
|
1955
2033
|
`,
|
|
1956
|
-
parameters:
|
|
1957
|
-
url:
|
|
1958
|
-
search:
|
|
1959
|
-
sitemap:
|
|
1960
|
-
includeSubdomains:
|
|
1961
|
-
limit:
|
|
1962
|
-
ignoreQueryParameters:
|
|
2034
|
+
parameters: z4.object({
|
|
2035
|
+
url: z4.string().url(),
|
|
2036
|
+
search: z4.string().optional(),
|
|
2037
|
+
sitemap: z4.enum(["include", "skip", "only"]).optional(),
|
|
2038
|
+
includeSubdomains: z4.boolean().optional(),
|
|
2039
|
+
limit: z4.number().optional(),
|
|
2040
|
+
ignoreQueryParameters: z4.boolean().optional()
|
|
1963
2041
|
}),
|
|
1964
2042
|
execute: async (args2, { session, log }) => {
|
|
1965
2043
|
const { url, ...options } = args2;
|
|
@@ -1985,11 +2063,13 @@ server.addTool({
|
|
|
1985
2063
|
// Query-only; no destructive side effects on external entities.
|
|
1986
2064
|
},
|
|
1987
2065
|
description: `
|
|
1988
|
-
Search web, news, or image sources and return ranked results. Operators include quoted phrases, \`-term\`, \`site:host\`, \`inurl:term\`, \`intitle:term\`, and \`related:host\`; the set is non-exhaustive. \`includeDomains\` and \`excludeDomains\` are mutually exclusive hostname filters; categories limit results to GitHub, research, or
|
|
2066
|
+
Search web, news, or image sources and return ranked results. Operators include quoted phrases, \`-term\`, \`site:host\`, \`inurl:term\`, \`intitle:term\`, and \`related:host\`; the set is non-exhaustive. \`includeDomains\` and \`excludeDomains\` are mutually exclusive hostname filters; categories limit results to GitHub, research, PDF, or developer sources.
|
|
1989
2067
|
|
|
1990
|
-
\`
|
|
2068
|
+
For a programming question, add \`categories: ["developer"]\`. It searches an index of GitHub issues, merged pull requests, repository READMEs, and curated documentation sites, and returns the hits in \`data.developer\` beside the web results.
|
|
2069
|
+
|
|
2070
|
+
\`scrapeOptions\` can attach extracted page content. Returns source-type result groups and usage metadata. Authenticated responses can include an \`id\` for optional search feedback.
|
|
1991
2071
|
`,
|
|
1992
|
-
parameters:
|
|
2072
|
+
parameters: z4.object({
|
|
1993
2073
|
...searchToolBaseFields,
|
|
1994
2074
|
scrapeOptions: scrapeParamsSchema.omit({ url: true }).partial().optional()
|
|
1995
2075
|
}).refine(searchDomainsAreExclusive, SEARCH_DOMAINS_CONFLICT_MESSAGE),
|
|
@@ -2019,7 +2099,9 @@ Search web, news, or image sources and return ranked results. Operators include
|
|
|
2019
2099
|
};
|
|
2020
2100
|
if (isKeylessMode(session)) {
|
|
2021
2101
|
const json = await keylessPost("/v2/search", searchBody, session);
|
|
2022
|
-
|
|
2102
|
+
const keylessResponse = { ...json ?? {} };
|
|
2103
|
+
delete keylessResponse.id;
|
|
2104
|
+
return asText2(keylessResponse);
|
|
2023
2105
|
}
|
|
2024
2106
|
const client = getClient(session);
|
|
2025
2107
|
const httpRes = await client.http.post("/v2/search", searchBody);
|
|
@@ -2036,25 +2118,35 @@ function resolveApiBaseUrl() {
|
|
|
2036
2118
|
function extractClientIp(request) {
|
|
2037
2119
|
return extractSingleTrustedClientIp(request?.headers?.["x-forwarded-for"]);
|
|
2038
2120
|
}
|
|
2121
|
+
function keylessQuotaReason(reason) {
|
|
2122
|
+
return reason === "requests" || reason === "credits";
|
|
2123
|
+
}
|
|
2039
2124
|
async function keylessEligible(clientIp) {
|
|
2040
2125
|
const secret = process.env.KEYLESS_PROXY_SECRET;
|
|
2041
|
-
if (!secret) return false;
|
|
2126
|
+
if (!secret) return { eligible: false, unavailable: true };
|
|
2042
2127
|
try {
|
|
2043
2128
|
const response = await fetch(
|
|
2044
2129
|
`${resolveApiBaseUrl()}/v2/keyless/eligibility`,
|
|
2045
2130
|
{
|
|
2046
2131
|
headers: {
|
|
2047
|
-
...
|
|
2132
|
+
...ORIGIN_HEADERS3,
|
|
2048
2133
|
"x-firecrawl-keyless-ip": clientIp,
|
|
2049
2134
|
"x-firecrawl-keyless-secret": secret
|
|
2050
2135
|
}
|
|
2051
2136
|
}
|
|
2052
2137
|
);
|
|
2053
|
-
if (!response.ok) return false;
|
|
2054
|
-
const json = await response.json().catch(() =>
|
|
2055
|
-
|
|
2138
|
+
if (!response.ok) return { eligible: false, unavailable: true };
|
|
2139
|
+
const json = await response.json().catch(() => null);
|
|
2140
|
+
if (typeof json?.eligible !== "boolean") {
|
|
2141
|
+
return { eligible: false, unavailable: true };
|
|
2142
|
+
}
|
|
2143
|
+
return {
|
|
2144
|
+
eligible: json?.eligible === true,
|
|
2145
|
+
...typeof json?.reason === "string" ? { reason: json.reason } : {},
|
|
2146
|
+
...Number.isFinite(json?.retryAfterSeconds) && json.retryAfterSeconds > 0 ? { retryAfterSeconds: json.retryAfterSeconds } : {}
|
|
2147
|
+
};
|
|
2056
2148
|
} catch {
|
|
2057
|
-
return false;
|
|
2149
|
+
return { eligible: false, unavailable: true };
|
|
2058
2150
|
}
|
|
2059
2151
|
}
|
|
2060
2152
|
function isKeylessMode(session) {
|
|
@@ -2065,15 +2157,18 @@ function isKeylessMode(session) {
|
|
|
2065
2157
|
return !process.env.FIRECRAWL_API_URL;
|
|
2066
2158
|
}
|
|
2067
2159
|
async function keylessPost(path2, body, session) {
|
|
2068
|
-
if (isHostedKeylessSession(session)
|
|
2069
|
-
const
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2160
|
+
if (isHostedKeylessSession(session)) {
|
|
2161
|
+
const eligibility = session?.keylessClientIp ? await keylessEligible(session.keylessClientIp) : { eligible: false };
|
|
2162
|
+
if (!eligibility.eligible) {
|
|
2163
|
+
const code = eligibility.unavailable ? "KEYLESS_ELIGIBILITY_UNAVAILABLE" : keylessQuotaReason(eligibility.reason) ? "KEYLESS_QUOTA_EXHAUSTED" : "KEYLESS_ACCESS_NOT_AVAILABLE";
|
|
2164
|
+
const payload = recoveryPayload(code, session?.requestId, {
|
|
2165
|
+
retryAfterSeconds: eligibility.retryAfterSeconds
|
|
2166
|
+
});
|
|
2167
|
+
throw new UserError(String(payload.message), payload);
|
|
2168
|
+
}
|
|
2074
2169
|
}
|
|
2075
2170
|
const headers = {
|
|
2076
|
-
...
|
|
2171
|
+
...ORIGIN_HEADERS3,
|
|
2077
2172
|
"Content-Type": "application/json"
|
|
2078
2173
|
};
|
|
2079
2174
|
if (session?.keylessClientIp && process.env.KEYLESS_PROXY_SECRET) {
|
|
@@ -2087,11 +2182,11 @@ async function keylessPost(path2, body, session) {
|
|
|
2087
2182
|
});
|
|
2088
2183
|
const json = await response.json().catch(() => ({}));
|
|
2089
2184
|
if (!response.ok) {
|
|
2090
|
-
if (
|
|
2091
|
-
const
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
);
|
|
2185
|
+
if (isKeylessMode(session) && response.status === 429) {
|
|
2186
|
+
const code = keylessQuotaReason(json?.reason) ? "KEYLESS_QUOTA_EXHAUSTED" : "KEYLESS_LIMIT_REACHED";
|
|
2187
|
+
const payload = recoveryPayload(code, session?.requestId, {
|
|
2188
|
+
retryAfterSeconds: Number.isFinite(json?.retry_after_seconds) && json.retry_after_seconds > 0 ? json.retry_after_seconds : void 0
|
|
2189
|
+
});
|
|
2095
2190
|
throw new UserError(String(payload.message), payload);
|
|
2096
2191
|
}
|
|
2097
2192
|
throw new Error(
|
|
@@ -2103,7 +2198,7 @@ async function keylessPost(path2, body, session) {
|
|
|
2103
2198
|
async function getCrawlStatusWithOrigin(client, jobId) {
|
|
2104
2199
|
const res = await client.http.get(
|
|
2105
2200
|
`/v2/crawl/${encodeURIComponent(jobId)}`,
|
|
2106
|
-
|
|
2201
|
+
ORIGIN_HEADERS3
|
|
2107
2202
|
);
|
|
2108
2203
|
const body = res?.data ?? {};
|
|
2109
2204
|
const initialDocs = Array.isArray(body.data) ? body.data : [];
|
|
@@ -2122,7 +2217,7 @@ async function getCrawlStatusWithOrigin(client, jobId) {
|
|
|
2122
2217
|
const docs = initialDocs.slice();
|
|
2123
2218
|
let current = body.next;
|
|
2124
2219
|
while (current) {
|
|
2125
|
-
const pageRes = await client.http.get(current,
|
|
2220
|
+
const pageRes = await client.http.get(current, ORIGIN_HEADERS3);
|
|
2126
2221
|
const payload = pageRes?.data ?? {};
|
|
2127
2222
|
if (!payload.success) break;
|
|
2128
2223
|
const pageData = Array.isArray(payload.data) ? payload.data : payload.data?.pages || [];
|
|
@@ -2155,17 +2250,17 @@ async function waitForCrawlCompletionWithOrigin(client, jobId, pollInterval = 2,
|
|
|
2155
2250
|
);
|
|
2156
2251
|
}
|
|
2157
2252
|
}
|
|
2158
|
-
var feedbackIssueSchema =
|
|
2253
|
+
var feedbackIssueSchema = z4.string().trim().min(1).max(80).regex(
|
|
2159
2254
|
/^[a-z0-9][a-z0-9_-]*$/,
|
|
2160
2255
|
"Issue codes must use lowercase letters, numbers, underscores, or hyphens"
|
|
2161
2256
|
);
|
|
2162
|
-
var valuableSourceSchema =
|
|
2163
|
-
url:
|
|
2164
|
-
reason:
|
|
2257
|
+
var valuableSourceSchema = z4.object({
|
|
2258
|
+
url: z4.string().url(),
|
|
2259
|
+
reason: z4.string().max(1e3).optional()
|
|
2165
2260
|
});
|
|
2166
|
-
var missingContentSchema =
|
|
2167
|
-
topic:
|
|
2168
|
-
description:
|
|
2261
|
+
var missingContentSchema = z4.object({
|
|
2262
|
+
topic: z4.string().min(1, "topic must not be empty").max(200, "topic must be 200 characters or fewer"),
|
|
2263
|
+
description: z4.string().max(2e3).optional()
|
|
2169
2264
|
});
|
|
2170
2265
|
var FEEDBACK_DISABLED_VALUES = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
2171
2266
|
function feedbackEnvEnabled(...keys) {
|
|
@@ -2186,7 +2281,7 @@ if (SEARCH_FEEDBACK_DISABLED) {
|
|
|
2186
2281
|
"[firecrawl-mcp] Search feedback tool disabled by FIRECRAWL_NO_SEARCH_FEEDBACK; firecrawl_search_feedback will not be registered."
|
|
2187
2282
|
);
|
|
2188
2283
|
}
|
|
2189
|
-
if (!SEARCH_FEEDBACK_DISABLED) {
|
|
2284
|
+
if (!SEARCH_FEEDBACK_DISABLED && !isLocalKeylessStartup()) {
|
|
2190
2285
|
server.addTool({
|
|
2191
2286
|
name: "firecrawl_search_feedback",
|
|
2192
2287
|
annotations: {
|
|
@@ -2199,28 +2294,28 @@ if (!SEARCH_FEEDBACK_DISABLED) {
|
|
|
2199
2294
|
// Additive only; records feedback and may refund credits, does not delete data.
|
|
2200
2295
|
},
|
|
2201
2296
|
description: `
|
|
2202
|
-
Records schema-validated quality feedback for a prior \`firecrawl_search\` UUID \`searchId\`. A \`good\` rating requires a valuable source, \`partial\` a valuable source or
|
|
2297
|
+
Records schema-validated quality feedback for a prior \`firecrawl_search\` UUID \`searchId\`. A \`good\` rating requires a valuable source, \`partial\` a valuable source or at least one \`missingContent\` entry, and \`bad\` at least one \`missingContent\` entry or a query suggestion; caps are 50 \`valuableSources\` and 20 \`missingContent\` entries.
|
|
2203
2298
|
|
|
2204
|
-
Eligibility is limited to successful searches within the feedback age window. The record is idempotent per search ID.
|
|
2299
|
+
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.
|
|
2205
2300
|
`,
|
|
2206
|
-
parameters:
|
|
2207
|
-
searchId:
|
|
2208
|
-
rating:
|
|
2209
|
-
valuableSources:
|
|
2210
|
-
|
|
2211
|
-
url:
|
|
2212
|
-
reason:
|
|
2301
|
+
parameters: z4.object({
|
|
2302
|
+
searchId: z4.string().uuid("searchId must be the UUID returned by firecrawl_search"),
|
|
2303
|
+
rating: z4.enum(["good", "bad", "partial"]),
|
|
2304
|
+
valuableSources: z4.array(
|
|
2305
|
+
z4.object({
|
|
2306
|
+
url: z4.string().url(),
|
|
2307
|
+
reason: z4.string().max(1e3).optional()
|
|
2213
2308
|
})
|
|
2214
2309
|
).max(50).optional(),
|
|
2215
|
-
missingContent:
|
|
2216
|
-
|
|
2217
|
-
topic:
|
|
2218
|
-
description:
|
|
2310
|
+
missingContent: z4.array(
|
|
2311
|
+
z4.object({
|
|
2312
|
+
topic: z4.string().min(1, "topic must not be empty").max(200, "topic must be 200 characters or fewer"),
|
|
2313
|
+
description: z4.string().max(2e3).optional()
|
|
2219
2314
|
})
|
|
2220
2315
|
).max(20).optional().describe(
|
|
2221
2316
|
"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`."
|
|
2222
2317
|
),
|
|
2223
|
-
querySuggestions:
|
|
2318
|
+
querySuggestions: z4.string().max(2e3).optional()
|
|
2224
2319
|
}),
|
|
2225
2320
|
execute: async (args2, { session, log }) => {
|
|
2226
2321
|
const {
|
|
@@ -2246,7 +2341,7 @@ Eligibility is limited to successful searches within the feedback age window. Th
|
|
|
2246
2341
|
}
|
|
2247
2342
|
if (querySuggestions) body.querySuggestions = querySuggestions;
|
|
2248
2343
|
const headers = {
|
|
2249
|
-
...
|
|
2344
|
+
...ORIGIN_HEADERS3,
|
|
2250
2345
|
"Content-Type": "application/json"
|
|
2251
2346
|
};
|
|
2252
2347
|
const credential = credentialForOutboundRequest(session);
|
|
@@ -2290,7 +2385,7 @@ if (ENDPOINT_FEEDBACK_DISABLED) {
|
|
|
2290
2385
|
"[firecrawl-mcp] Endpoint feedback tool disabled by FIRECRAWL_NO_ENDPOINT_FEEDBACK; firecrawl_feedback will not be registered."
|
|
2291
2386
|
);
|
|
2292
2387
|
}
|
|
2293
|
-
if (!ENDPOINT_FEEDBACK_DISABLED) {
|
|
2388
|
+
if (!ENDPOINT_FEEDBACK_DISABLED && !isLocalKeylessStartup()) {
|
|
2294
2389
|
server.addTool({
|
|
2295
2390
|
name: "firecrawl_feedback",
|
|
2296
2391
|
annotations: {
|
|
@@ -2307,19 +2402,19 @@ Submit concise quality feedback for a completed search, scrape, parse, or map jo
|
|
|
2307
2402
|
|
|
2308
2403
|
Returns submission status, feedback ID, and accounting fields.
|
|
2309
2404
|
`,
|
|
2310
|
-
parameters:
|
|
2311
|
-
endpoint:
|
|
2312
|
-
jobId:
|
|
2313
|
-
rating:
|
|
2314
|
-
issues:
|
|
2315
|
-
tags:
|
|
2316
|
-
note:
|
|
2317
|
-
valuableSources:
|
|
2318
|
-
missingContent:
|
|
2319
|
-
querySuggestions:
|
|
2320
|
-
url:
|
|
2321
|
-
pageNumbers:
|
|
2322
|
-
metadata:
|
|
2405
|
+
parameters: z4.object({
|
|
2406
|
+
endpoint: z4.enum(["search", "scrape", "parse", "map"]),
|
|
2407
|
+
jobId: z4.string().uuid("jobId must be the UUID returned by Firecrawl"),
|
|
2408
|
+
rating: z4.enum(["good", "bad", "partial"]),
|
|
2409
|
+
issues: z4.array(feedbackIssueSchema).max(20).optional(),
|
|
2410
|
+
tags: z4.array(feedbackIssueSchema).max(20).optional(),
|
|
2411
|
+
note: z4.string().max(4e3).optional(),
|
|
2412
|
+
valuableSources: z4.array(valuableSourceSchema).max(50).optional(),
|
|
2413
|
+
missingContent: z4.array(missingContentSchema).max(50).optional(),
|
|
2414
|
+
querySuggestions: z4.string().max(2e3).optional(),
|
|
2415
|
+
url: z4.string().url().optional(),
|
|
2416
|
+
pageNumbers: z4.array(z4.number().int().positive()).max(100).optional(),
|
|
2417
|
+
metadata: z4.record(z4.string(), z4.unknown()).optional()
|
|
2323
2418
|
}),
|
|
2324
2419
|
execute: async (args2, { session, log }) => {
|
|
2325
2420
|
const {
|
|
@@ -2338,7 +2433,7 @@ Returns submission status, feedback ID, and accounting fields.
|
|
|
2338
2433
|
} = args2;
|
|
2339
2434
|
const apiBase = resolveApiBaseUrl();
|
|
2340
2435
|
const headers = {
|
|
2341
|
-
...
|
|
2436
|
+
...ORIGIN_HEADERS3,
|
|
2342
2437
|
"Content-Type": "application/json"
|
|
2343
2438
|
};
|
|
2344
2439
|
const credential = credentialForOutboundRequest(session);
|
|
@@ -2408,25 +2503,25 @@ Start a multi-page crawl at a website URL, poll it to a terminal state, and retu
|
|
|
2408
2503
|
|
|
2409
2504
|
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.
|
|
2410
2505
|
`,
|
|
2411
|
-
parameters:
|
|
2412
|
-
url:
|
|
2413
|
-
prompt:
|
|
2414
|
-
excludePaths:
|
|
2415
|
-
includePaths:
|
|
2416
|
-
maxDiscoveryDepth:
|
|
2417
|
-
sitemap:
|
|
2418
|
-
limit:
|
|
2419
|
-
allowExternalLinks:
|
|
2420
|
-
allowSubdomains:
|
|
2421
|
-
crawlEntireDomain:
|
|
2422
|
-
delay:
|
|
2423
|
-
maxConcurrency:
|
|
2506
|
+
parameters: z4.object({
|
|
2507
|
+
url: z4.string(),
|
|
2508
|
+
prompt: z4.string().optional(),
|
|
2509
|
+
excludePaths: z4.array(z4.string()).optional(),
|
|
2510
|
+
includePaths: z4.array(z4.string()).optional(),
|
|
2511
|
+
maxDiscoveryDepth: z4.number().optional(),
|
|
2512
|
+
sitemap: z4.enum(["skip", "include", "only"]).optional(),
|
|
2513
|
+
limit: z4.number().optional(),
|
|
2514
|
+
allowExternalLinks: z4.boolean().optional(),
|
|
2515
|
+
allowSubdomains: z4.boolean().optional(),
|
|
2516
|
+
crawlEntireDomain: z4.boolean().optional(),
|
|
2517
|
+
delay: z4.number().optional(),
|
|
2518
|
+
maxConcurrency: z4.number().optional(),
|
|
2424
2519
|
...SAFE_MODE ? {} : {
|
|
2425
|
-
webhook:
|
|
2426
|
-
webhookHeaders:
|
|
2520
|
+
webhook: z4.string().optional(),
|
|
2521
|
+
webhookHeaders: z4.record(z4.string(), z4.string()).optional()
|
|
2427
2522
|
},
|
|
2428
|
-
deduplicateSimilarURLs:
|
|
2429
|
-
ignoreQueryParameters:
|
|
2523
|
+
deduplicateSimilarURLs: z4.boolean().optional(),
|
|
2524
|
+
ignoreQueryParameters: z4.boolean().optional(),
|
|
2430
2525
|
scrapeOptions: scrapeParamsSchema.omit({ url: true }).partial().optional()
|
|
2431
2526
|
}),
|
|
2432
2527
|
execute: async (args2, { session, log }) => {
|
|
@@ -2479,7 +2574,7 @@ server.addTool({
|
|
|
2479
2574
|
description: `
|
|
2480
2575
|
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.
|
|
2481
2576
|
`,
|
|
2482
|
-
parameters:
|
|
2577
|
+
parameters: z4.object({ id: z4.string() }),
|
|
2483
2578
|
execute: async (args2, { session }) => {
|
|
2484
2579
|
const client = getClient(session);
|
|
2485
2580
|
const id = args2.id;
|
|
@@ -2503,13 +2598,13 @@ Extract structured information from one or more URLs with an optional natural-la
|
|
|
2503
2598
|
|
|
2504
2599
|
Use this for a defined structured result rather than full page content. Returns data shaped by the supplied schema or prompt.
|
|
2505
2600
|
`,
|
|
2506
|
-
parameters:
|
|
2507
|
-
urls:
|
|
2508
|
-
prompt:
|
|
2509
|
-
schema:
|
|
2510
|
-
allowExternalLinks:
|
|
2511
|
-
enableWebSearch:
|
|
2512
|
-
includeSubdomains:
|
|
2601
|
+
parameters: z4.object({
|
|
2602
|
+
urls: z4.array(z4.string()),
|
|
2603
|
+
prompt: z4.string().optional(),
|
|
2604
|
+
schema: z4.record(z4.string(), z4.any()).optional(),
|
|
2605
|
+
allowExternalLinks: z4.boolean().optional(),
|
|
2606
|
+
enableWebSearch: z4.boolean().optional(),
|
|
2607
|
+
includeSubdomains: z4.boolean().optional()
|
|
2513
2608
|
}),
|
|
2514
2609
|
execute: async (args2, { session, log }) => {
|
|
2515
2610
|
const client = getClient(session);
|
|
@@ -2546,10 +2641,10 @@ Start an asynchronous web research job from a prompt, optional seed URLs, and an
|
|
|
2546
2641
|
|
|
2547
2642
|
This call returns only a job ID, not the research result. Read the job with \`firecrawl_agent_status\` until it reaches \`completed\` or \`failed\`; research commonly takes several minutes. If the job cannot finish within the task's available time, \`firecrawl_search\` and \`firecrawl_scrape\` can gather evidence synchronously.
|
|
2548
2643
|
`,
|
|
2549
|
-
parameters:
|
|
2550
|
-
prompt:
|
|
2551
|
-
urls:
|
|
2552
|
-
schema:
|
|
2644
|
+
parameters: z4.object({
|
|
2645
|
+
prompt: z4.string().min(1).max(1e4),
|
|
2646
|
+
urls: z4.array(z4.string().url()).optional(),
|
|
2647
|
+
schema: z4.record(z4.string(), z4.any()).optional()
|
|
2553
2648
|
}),
|
|
2554
2649
|
execute: async (args2, { session, log }) => {
|
|
2555
2650
|
const client = getClient(session);
|
|
@@ -2586,14 +2681,14 @@ Retrieve progress or final results for a \`firecrawl_agent\` job ID. A \`process
|
|
|
2586
2681
|
|
|
2587
2682
|
Returns job status, progress information, and result data when completed.
|
|
2588
2683
|
`,
|
|
2589
|
-
parameters:
|
|
2684
|
+
parameters: z4.object({ id: z4.string() }),
|
|
2590
2685
|
execute: async (args2, { session, log }) => {
|
|
2591
2686
|
const client = getClient(session);
|
|
2592
2687
|
const { id } = args2;
|
|
2593
2688
|
log.info("Checking agent status", { id });
|
|
2594
2689
|
const res = await client.http.get(
|
|
2595
2690
|
`/v2/agent/${encodeURIComponent(id)}`,
|
|
2596
|
-
|
|
2691
|
+
ORIGIN_HEADERS3
|
|
2597
2692
|
);
|
|
2598
2693
|
return asText2(res?.data ?? {});
|
|
2599
2694
|
}
|
|
@@ -2614,13 +2709,13 @@ Open or reuse a live browser session to navigate a page, click controls, fill fi
|
|
|
2614
2709
|
|
|
2615
2710
|
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.
|
|
2616
2711
|
`,
|
|
2617
|
-
parameters:
|
|
2618
|
-
scrapeId:
|
|
2619
|
-
url:
|
|
2620
|
-
prompt:
|
|
2621
|
-
code:
|
|
2622
|
-
language:
|
|
2623
|
-
timeout:
|
|
2712
|
+
parameters: z4.object({
|
|
2713
|
+
scrapeId: z4.string().trim().min(1).optional(),
|
|
2714
|
+
url: z4.string().trim().url().optional(),
|
|
2715
|
+
prompt: z4.string().trim().min(1).optional(),
|
|
2716
|
+
code: z4.string().trim().min(1).optional(),
|
|
2717
|
+
language: z4.enum(["bash", "python", "node"]).optional(),
|
|
2718
|
+
timeout: z4.number().min(1).max(300).optional(),
|
|
2624
2719
|
scrapeOptions: scrapeParamsSchema.omit({ url: true }).partial().optional()
|
|
2625
2720
|
}).refine((data) => Boolean(data.scrapeId) !== Boolean(data.url), {
|
|
2626
2721
|
message: "Provide either 'url' (interact directly) or 'scrapeId' (reuse a previous scrape), not both."
|
|
@@ -2697,8 +2792,8 @@ server.addTool({
|
|
|
2697
2792
|
description: `
|
|
2698
2793
|
Stop the live interact session associated with a \`scrapeId\` and release its resources. Returns a success confirmation.
|
|
2699
2794
|
`,
|
|
2700
|
-
parameters:
|
|
2701
|
-
scrapeId:
|
|
2795
|
+
parameters: z4.object({
|
|
2796
|
+
scrapeId: z4.string()
|
|
2702
2797
|
}),
|
|
2703
2798
|
execute: async (args2, { session, log }) => {
|
|
2704
2799
|
const client = getClient(session);
|
|
@@ -2706,7 +2801,7 @@ Stop the live interact session associated with a \`scrapeId\` and release its re
|
|
|
2706
2801
|
log.info("Stopping interact session", { scrapeId });
|
|
2707
2802
|
const res = await client.http.delete(
|
|
2708
2803
|
`/v2/scrape/${encodeURIComponent(scrapeId)}/interact`,
|
|
2709
|
-
|
|
2804
|
+
ORIGIN_HEADERS3
|
|
2710
2805
|
);
|
|
2711
2806
|
return asText2(res?.data ?? {});
|
|
2712
2807
|
}
|
|
@@ -2758,7 +2853,7 @@ Set \`redactPII\` to request redaction of personally identifiable information in
|
|
|
2758
2853
|
});
|
|
2759
2854
|
form.append("file", blob, filename);
|
|
2760
2855
|
form.append("options", JSON.stringify(optionsPayload));
|
|
2761
|
-
const headers = { ...
|
|
2856
|
+
const headers = { ...ORIGIN_HEADERS3 };
|
|
2762
2857
|
const credential = credentialForOutboundRequest(session);
|
|
2763
2858
|
if (credential) {
|
|
2764
2859
|
headers["Authorization"] = `Bearer ${credential}`;
|
|
@@ -2797,11 +2892,13 @@ function registerMarketplaceSearchTool(registrar, getClientFn) {
|
|
|
2797
2892
|
destructiveHint: false
|
|
2798
2893
|
},
|
|
2799
2894
|
description: `
|
|
2800
|
-
Search web and specialized indexes, returning ranked results. Operators include quoted phrases, \`-term\`, \`site:host\`, \`inurl:term\`, \`intitle:term\`, and \`related:host\`; the set is non-exhaustive. \`includeDomains\` and \`excludeDomains\` are mutually exclusive hostname filters; categories limit result types to \`github\`, \`research\`, or \`
|
|
2895
|
+
Search web and specialized indexes, returning ranked results. Operators include quoted phrases, \`-term\`, \`site:host\`, \`inurl:term\`, \`intitle:term\`, and \`related:host\`; the set is non-exhaustive. \`includeDomains\` and \`excludeDomains\` are mutually exclusive hostname filters; categories limit result types to \`github\`, \`research\`, \`pdf\`, or \`developer\`.
|
|
2896
|
+
|
|
2897
|
+
For a programming question, add \`categories: ["developer"]\`. It searches an index of GitHub issues, merged pull requests, repository READMEs, and curated documentation sites, and returns the hits in \`data.developer\` beside the web results.
|
|
2801
2898
|
|
|
2802
2899
|
Returns \`{ success, data, id, creditsUsed }\`, with source arrays in \`data\`.
|
|
2803
2900
|
`,
|
|
2804
|
-
parameters:
|
|
2901
|
+
parameters: z4.object({ ...searchToolBaseFields }).strict().refine(searchDomainsAreExclusive, SEARCH_DOMAINS_CONFLICT_MESSAGE),
|
|
2805
2902
|
execute: async (args2, { session, log }) => {
|
|
2806
2903
|
const {
|
|
2807
2904
|
query,
|
|
@@ -2862,6 +2959,12 @@ if (process.env.CLOUD_SERVICE === "true" || process.env.SSE_LOCAL === "true" ||
|
|
|
2862
2959
|
}
|
|
2863
2960
|
registerMonitorTools(server);
|
|
2864
2961
|
registerResearchTools(server, getClient);
|
|
2962
|
+
registerDeveloperTools(server, getClient);
|
|
2963
|
+
if (process.env.CLOUD_SERVICE === "true" && primaryProfile.allowKeyless && !normalizeHeader(process.env.KEYLESS_PROXY_SECRET)) {
|
|
2964
|
+
console.warn(
|
|
2965
|
+
"[firecrawl-mcp] KEYLESS_PROXY_SECRET is missing; keyless requests will be unavailable and /ready will fail."
|
|
2966
|
+
);
|
|
2967
|
+
}
|
|
2865
2968
|
if (primaryProfile.id === "search") {
|
|
2866
2969
|
const primarySearchRegistrar = {
|
|
2867
2970
|
addTool: ((tool) => {
|
|
@@ -2887,6 +2990,7 @@ if (searchProfileEnabled) {
|
|
|
2887
2990
|
})
|
|
2888
2991
|
};
|
|
2889
2992
|
registerResearchTools(searchRegistrar, getClient);
|
|
2993
|
+
registerDeveloperTools(searchRegistrar, getClient);
|
|
2890
2994
|
registerMarketplaceSearchTool(searchRegistrar, getClient);
|
|
2891
2995
|
try {
|
|
2892
2996
|
await searchServer.start({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firecrawl-mcp",
|
|
3
|
-
"version": "3.23.
|
|
3
|
+
"version": "3.23.2",
|
|
4
4
|
"description": "MCP server for Firecrawl — search, scrape, and interact with the web. Supports both cloud and self-hosted instances. Features include web search, scraping, page interaction, batch processing, and LLM-powered content analysis.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"mcpName": "io.github.firecrawl/firecrawl-mcp-server",
|