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