firecrawl-mcp 3.30.2 → 3.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/index.js +112 -22
- package/dist/origin.js +77 -5
- package/dist/server.d.ts +42 -9
- package/dist/server.js +111 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -73,6 +73,10 @@ Authorization: Bearer <FIRECRAWL_API_KEY>
|
|
|
73
73
|
|
|
74
74
|
Never put an API key in the server URL. Never put an API key in an agent chat. Configure it directly in the client or secret manager. See the [hosted MCP setup guide](https://docs.firecrawl.dev/mcp-server) and the [agent onboarding guide](https://www.firecrawl.dev/agent-onboarding/SKILL.md) for client-specific instructions.
|
|
75
75
|
|
|
76
|
+
#### Naming your integration
|
|
77
|
+
|
|
78
|
+
An integration that connects over HTTP can name itself with an `X-Firecrawl-Origin` header, for example `X-Firecrawl-Origin: my-agent`. The server reports it to Firecrawl as the request origin `mcp-my-agent@<server version>`, in place of the client name or User-Agent it would otherwise report. Use letters, digits and dashes: each run of other characters becomes one dash, leading and trailing dashes are dropped, and the name is cut at 48 characters. Tool results are unaffected; only the origin sent to the Firecrawl API (and passed to an embedder's `beforeKeylessRequest`) changes.
|
|
79
|
+
|
|
76
80
|
#### Search-only endpoint
|
|
77
81
|
|
|
78
82
|
A fixed-scope search surface is also hosted at:
|
|
@@ -1246,7 +1250,7 @@ Options mirror the CLI's environment variables: `apiUrl`, `apiKey`, `transport`,
|
|
|
1246
1250
|
|
|
1247
1251
|
FastMCP is bundled inside the package, so use the types exported from `firecrawl-mcp/server` (`Session`, `ToolDefinition`, `ToolRegistrar`, `FirecrawlMcpServerHooks` and others) rather than importing `fastmcp` yourself.
|
|
1248
1252
|
|
|
1249
|
-
The entry point also exports the helpers the built-in tools use, such as `recoveryPayload`, `normalizeHeader`, `requestOrigin`, `searchToolBaseFields`, `searchOutputSchema` and `structuredCompact`, so extra tools and hooks can match the built-in behaviour. They are unstable as well.
|
|
1253
|
+
The entry point also exports the helpers the built-in tools use, such as `recoveryPayload`, `normalizeHeader`, `requestOrigin`, `isRuntimeUserAgent`, `initializeClientSlug`, `searchToolBaseFields`, `searchOutputSchema` and `structuredCompact`, so extra tools and hooks can match the built-in behaviour. They are unstable as well.
|
|
1250
1254
|
|
|
1251
1255
|
## Logging System
|
|
1252
1256
|
|
package/dist/index.js
CHANGED
|
@@ -2401,6 +2401,14 @@ function defaultDomainTools(sources) {
|
|
|
2401
2401
|
(source) => ["web", "news", "images"].includes(typeof source === "string" ? source : source?.type)
|
|
2402
2402
|
);
|
|
2403
2403
|
}
|
|
2404
|
+
function withAlexandriaSource(sources) {
|
|
2405
|
+
if (!Array.isArray(sources) || sources.length === 0) return ["web", "alexandria"];
|
|
2406
|
+
if (hasAlexandria(sources)) return sources;
|
|
2407
|
+
return [...sources, typeof sources[0] === "string" ? "alexandria" : { type: "alexandria" }];
|
|
2408
|
+
}
|
|
2409
|
+
function enterpriseAllowsAlexandria(enterprise) {
|
|
2410
|
+
return !(Array.isArray(enterprise) && enterprise.some((mode) => mode === "zdr" || mode === "anon"));
|
|
2411
|
+
}
|
|
2404
2412
|
function normalizeSearchSources(sources) {
|
|
2405
2413
|
if (!Array.isArray(sources)) return sources;
|
|
2406
2414
|
return sources.map(
|
|
@@ -2427,8 +2435,8 @@ var findToolsSchema = z5.object({
|
|
|
2427
2435
|
}).strict();
|
|
2428
2436
|
var ALEXANDRIA_CATALOGUE_VERTICALS = "companies, people, jobs, finance and filings, public records and government spending, real estate, places and restaurants, retail and prices, package registries and developer data, news, research, and more";
|
|
2429
2437
|
var ALEXANDRIA_CATALOGUE_SENTENCE = "Alexandria is Firecrawl's catalogue of data providers and workflows across " + ALEXANDRIA_CATALOGUE_VERTICALS + "; providers return typed, sourced records through published contracts.";
|
|
2430
|
-
var
|
|
2431
|
-
var ALEXANDRIA_SEARCH_LEAD = "Authenticated search also returns matching Alexandria data providers in data.tools (" + ALEXANDRIA_CATALOGUE_VERTICALS + "). Prefer a provider over scraping pages when the task needs the same fields across several entities, exact figures or timestamps, provenance, or many records; use web results when they already answer the question. " +
|
|
2438
|
+
var ALEXANDRIA_SOURCES_ALWAYS_ON = 'Authenticated searches always add alexandria to the sources, so sources: ["web"] also returns providers; domainTools: false turns off website-matched tools only.';
|
|
2439
|
+
var ALEXANDRIA_SEARCH_LEAD = "Authenticated search also returns matching Alexandria data providers in data.tools (" + ALEXANDRIA_CATALOGUE_VERTICALS + "). Prefer a provider over scraping pages when the task needs the same fields across several entities, exact figures or timestamps, provenance, or many records; use web results when they already answer the question. " + ALEXANDRIA_SOURCES_ALWAYS_ON;
|
|
2432
2440
|
var ALEXANDRIA_CONTRACT_GUIDANCE = "The selected contract marks required inputs and any requiresOneOf groups (at least one member per group); it may include example.request/example.response and response.key (which may differ from records). Where pagination is declared, its fields govern paging with the same filters; catalogue next is separate from provider pagination.";
|
|
2433
2441
|
function findToolsOptions(args2) {
|
|
2434
2442
|
const level = args2.level ?? (args2.query || args2.capabilities?.length || args2.providers?.length || args2.groups?.length || args2.urls?.length ? "tools" : args2.categories?.length ? "providers" : "categories");
|
|
@@ -2460,7 +2468,7 @@ function withFindToolsNavigation(envelope, carry = {}) {
|
|
|
2460
2468
|
if (link(page.next)) page.nextTool = link(page.next);
|
|
2461
2469
|
return envelope;
|
|
2462
2470
|
}
|
|
2463
|
-
var ALEXANDRIA_SEARCH_INSTRUCTIONS = 'Authenticated search combines web results, semantic tool summaries and domain matches. Use sources: ["alexandria"] for semantic tools only. ' +
|
|
2471
|
+
var ALEXANDRIA_SEARCH_INSTRUCTIONS = 'Authenticated search combines web results, semantic tool summaries and domain matches. Use sources: ["alexandria"] for semantic tools only. ' + ALEXANDRIA_SOURCES_ALWAYS_ON + ' Tool matches describe available structured-data capabilities, not executed data. toolDetail: "compact" (default) returns only provider, capability and description; "summary" adds metadata; "full" includes their input and output contracts. firecrawl_scrape with an alexandria body executes a selected capability; firecrawl_find_tools provides catalogue browsing and full contracts.';
|
|
2464
2472
|
var TOOL_PRICE_KEYS = ["creditsCost", "perRecord", "billing", "recordsPerUnit", "zeroDataRetentionCreditsCost"];
|
|
2465
2473
|
function withoutToolPrice(tool) {
|
|
2466
2474
|
if (!tool || typeof tool !== "object" || Array.isArray(tool)) return tool;
|
|
@@ -2575,19 +2583,79 @@ import { z as z6 } from "zod";
|
|
|
2575
2583
|
// src/origin.ts
|
|
2576
2584
|
import { createRequire } from "module";
|
|
2577
2585
|
var ORIGIN_SERVER = "mcp-fastmcp";
|
|
2586
|
+
var DECLARED_ORIGIN_HEADER = "x-firecrawl-origin";
|
|
2578
2587
|
var NAME_MAX = 48;
|
|
2588
|
+
var RUNTIME_PRODUCTS = /* @__PURE__ */ new Set([
|
|
2589
|
+
"apache-httpasyncclient",
|
|
2590
|
+
"apache-httpclient",
|
|
2591
|
+
"aiohttp",
|
|
2592
|
+
"axios",
|
|
2593
|
+
"bun",
|
|
2594
|
+
"curl",
|
|
2595
|
+
"dart",
|
|
2596
|
+
"deno",
|
|
2597
|
+
"faraday",
|
|
2598
|
+
"go-http-client",
|
|
2599
|
+
"got",
|
|
2600
|
+
"guzzlehttp",
|
|
2601
|
+
"java-http-client",
|
|
2602
|
+
"ktor-client",
|
|
2603
|
+
"libcurl-agent",
|
|
2604
|
+
"mozilla",
|
|
2605
|
+
"node",
|
|
2606
|
+
"node-fetch",
|
|
2607
|
+
"okhttp",
|
|
2608
|
+
"python",
|
|
2609
|
+
"python-httpx",
|
|
2610
|
+
"python-httpx2",
|
|
2611
|
+
"python-requests",
|
|
2612
|
+
"python-urllib",
|
|
2613
|
+
"reqwest",
|
|
2614
|
+
"ruby",
|
|
2615
|
+
"undici",
|
|
2616
|
+
"wget"
|
|
2617
|
+
]);
|
|
2618
|
+
var GENERIC_CLIENT_NAMES = /* @__PURE__ */ new Set([
|
|
2619
|
+
"client",
|
|
2620
|
+
"example-client",
|
|
2621
|
+
"mcp",
|
|
2622
|
+
"mcp-client",
|
|
2623
|
+
"test",
|
|
2624
|
+
"test-client"
|
|
2625
|
+
]);
|
|
2579
2626
|
function slugOf(name2) {
|
|
2580
2627
|
return (name2 ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, NAME_MAX).replace(/-+$/, "");
|
|
2581
2628
|
}
|
|
2629
|
+
function isOwnSlug(slug) {
|
|
2630
|
+
return slug === "" || slug === "fastmcp" || slug === "firecrawl-fastmcp";
|
|
2631
|
+
}
|
|
2582
2632
|
function userAgentProduct(userAgent) {
|
|
2583
2633
|
const first = (userAgent ?? "").trim().split(/\s+/)[0] ?? "";
|
|
2584
2634
|
return slugOf(first.split("/")[0]);
|
|
2585
2635
|
}
|
|
2586
|
-
function
|
|
2636
|
+
function initializeClientSlug(name2) {
|
|
2637
|
+
const slug = slugOf(name2);
|
|
2638
|
+
return isOwnSlug(slug) || GENERIC_CLIENT_NAMES.has(slug) ? "" : slug;
|
|
2639
|
+
}
|
|
2640
|
+
function referrerSlug(referrer) {
|
|
2641
|
+
let host;
|
|
2642
|
+
try {
|
|
2643
|
+
host = new URL(referrer ?? "").hostname.toLowerCase().replace(/\.$/, "");
|
|
2644
|
+
} catch {
|
|
2645
|
+
return "";
|
|
2646
|
+
}
|
|
2647
|
+
if (!host.includes(".") || host.endsWith(".localhost") || host.endsWith(".local") || /^[\d.]+$/.test(host) || host.startsWith("[")) {
|
|
2648
|
+
return "";
|
|
2649
|
+
}
|
|
2650
|
+
return slugOf(host.replace(/^www\./, ""));
|
|
2651
|
+
}
|
|
2652
|
+
function originForClient(client, serverVersion2, userAgent, signals = {}) {
|
|
2653
|
+
const declared = slugOf(signals.declaredOrigin);
|
|
2587
2654
|
const slug = slugOf(client?.name);
|
|
2588
|
-
const
|
|
2589
|
-
const
|
|
2590
|
-
const
|
|
2655
|
+
const product = userAgentProduct(userAgent);
|
|
2656
|
+
const remembered = initializeClientSlug(signals.initializeClient?.name);
|
|
2657
|
+
const referrer = referrerSlug(signals.referrer);
|
|
2658
|
+
const base = !isOwnSlug(declared) ? `mcp-${declared}` : !isOwnSlug(slug) ? `mcp-${slug}` : product && !RUNTIME_PRODUCTS.has(product) ? `mcp-ua-${product}` : remembered ? `mcp-${remembered}` : referrer ? `mcp-ref-${referrer}` : product ? `mcp-ua-${product}` : ORIGIN_SERVER;
|
|
2591
2659
|
return serverVersion2 ? `${base}@${serverVersion2}` : base;
|
|
2592
2660
|
}
|
|
2593
2661
|
function originHeaders(origin) {
|
|
@@ -2600,7 +2668,12 @@ function requestOrigin(client, session) {
|
|
|
2600
2668
|
return originForClient(
|
|
2601
2669
|
client?.version ?? null,
|
|
2602
2670
|
serverVersion,
|
|
2603
|
-
session?.clientUserAgent
|
|
2671
|
+
session?.clientUserAgent,
|
|
2672
|
+
{
|
|
2673
|
+
declaredOrigin: session?.clientOrigin,
|
|
2674
|
+
initializeClient: session?.initializeClient,
|
|
2675
|
+
referrer: session?.clientReferrer
|
|
2676
|
+
}
|
|
2604
2677
|
);
|
|
2605
2678
|
}
|
|
2606
2679
|
|
|
@@ -3677,7 +3750,7 @@ var searchToolBaseFields = {
|
|
|
3677
3750
|
location: z11.string().optional(),
|
|
3678
3751
|
includeDomains: z11.array(searchDomainSchema).optional().describe("Hostnames to restrict results to. Mutually exclusive with excludeDomains."),
|
|
3679
3752
|
excludeDomains: z11.array(searchDomainSchema).optional().describe("Hostnames to leave out of results. Mutually exclusive with includeDomains."),
|
|
3680
|
-
sources: z11.array(searchSourceSchema).optional().describe("Search sources; authenticated sessions default to web + alexandria, keyless sessions to web only. " +
|
|
3753
|
+
sources: z11.array(searchSourceSchema).optional().describe("Search sources; authenticated sessions default to web + alexandria, keyless sessions to web only. " + ALEXANDRIA_SOURCES_ALWAYS_ON + ' enterprise zdr or anon searches do not add it. Use ["alexandria"] alone for provider discovery without web results.'),
|
|
3681
3754
|
categories: z11.array(z11.enum(["research", "pdf", "developer", "gov"])).optional().describe(
|
|
3682
3755
|
"Limit results to specific source types. `research` restricts ordinary web results to research-affiliated websites and returns page snippets, which is separate from the `firecrawl_research_*` tools that search paper abstracts and full text across biomedical (PubMed, bioRxiv, medRxiv) and arXiv literature; `pdf` searches PDF results; `developer` searches an index built for coding agents over public repositories, GitHub issues, merged pull requests, repository READMEs, and code documentation; `gov` searches the Government Index of US federal, state, and local legal and regulatory sources, the index behind firecrawl_gov_search, and cannot be combined with other categories; gov hits can include historical versions, bill text, agency guidance, and third-party reproductions, so verify the issuer, jurisdiction, enacted/effective status, and version before citing current governing law, clarify a missing jurisdiction or ambiguous as-of date, and use general search for non-US questions or missing coverage (no hits does not establish that no applicable law exists). `developer` and `gov` return hits in `data.web` with `category` set to their name; the other categories also filter `data.web`."
|
|
3683
3756
|
),
|
|
@@ -3819,7 +3892,7 @@ async function relayExchangeError(run, context) {
|
|
|
3819
3892
|
});
|
|
3820
3893
|
}
|
|
3821
3894
|
}
|
|
3822
|
-
async function postSearchWithFallback(client, body, implicitTools) {
|
|
3895
|
+
async function postSearchWithFallback(client, body, implicitTools, fallbackSources = ["web"]) {
|
|
3823
3896
|
try {
|
|
3824
3897
|
return await client.http.post("/v2/search", body);
|
|
3825
3898
|
} catch (error2) {
|
|
@@ -3832,7 +3905,7 @@ async function postSearchWithFallback(client, body, implicitTools) {
|
|
|
3832
3905
|
if (!implicitTools || response?.status !== 403 || !unavailable) throw error2;
|
|
3833
3906
|
return client.http.post("/v2/search", {
|
|
3834
3907
|
...body,
|
|
3835
|
-
sources:
|
|
3908
|
+
sources: fallbackSources,
|
|
3836
3909
|
domainTools: false
|
|
3837
3910
|
});
|
|
3838
3911
|
}
|
|
@@ -3892,7 +3965,7 @@ var ConsoleLogger = class {
|
|
|
3892
3965
|
}
|
|
3893
3966
|
}
|
|
3894
3967
|
};
|
|
3895
|
-
var FULL_PROFILE_INSTRUCTIONS = `Firecrawl provides web search, page retrieval, site URL discovery, multi-page collection, structured page data, monitoring, and multi-source research that returns structured data. Match the requested operation to the tool boundary: firecrawl_scrape retrieves one supplied page and can return JSON matching a supplied schema, firecrawl_map enumerates URLs under a site without retrieving their content, and firecrawl_agent runs multi-source research and returns structured data when the URLs are not known or the answer spans several sites (an entity plus its fields, a list, a dataset); its result is read with firecrawl_agent_status. Authenticated firecrawl_search returns web results together with matching Alexandria providers in data.tools. ${ALEXANDRIA_CATALOGUE_SENTENCE} A matching provider can return the same fields across several entities, provenance, exact figures or timestamps, or a large set of records through firecrawl_scrape with its published contract. If web results already answer the question, use them. For the same fields across multiple pages, firecrawl_find_tools offers free provider discovery. Use firecrawl_find_tools to read a contract that was not returned in full or to browse the catalogue by category. ${
|
|
3968
|
+
var FULL_PROFILE_INSTRUCTIONS = `Firecrawl provides web search, page retrieval, site URL discovery, multi-page collection, structured page data, monitoring, and multi-source research that returns structured data. Match the requested operation to the tool boundary: firecrawl_scrape retrieves one supplied page and can return JSON matching a supplied schema, firecrawl_map enumerates URLs under a site without retrieving their content, and firecrawl_agent runs multi-source research and returns structured data when the URLs are not known or the answer spans several sites (an entity plus its fields, a list, a dataset); its result is read with firecrawl_agent_status. Authenticated firecrawl_search returns web results together with matching Alexandria providers in data.tools. ${ALEXANDRIA_CATALOGUE_SENTENCE} A matching provider can return the same fields across several entities, provenance, exact figures or timestamps, or a large set of records through firecrawl_scrape with its published contract. If web results already answer the question, use them. For the same fields across multiple pages, firecrawl_find_tools offers free provider discovery. Use firecrawl_find_tools to read a contract that was not returned in full or to browse the catalogue by category. ${ALEXANDRIA_SOURCES_ALWAYS_ON} If no provider fits, continue with web search or firecrawl_agent. For biomedical, life-science, clinical, or arXiv literature, the firecrawl_research_* tools search a paper index of abstracts and full text; firecrawl_search with categories: ["research"] is a website filter over ordinary web results and reaches different sources. For a programming question (code behaviour, a library or framework, an API contract, an error message, or a known bug), firecrawl_developer_search (or firecrawl_search with categories: ["developer"]) searches an index of public repositories, GitHub issues, merged pull requests, READMEs, and code documentation. For a legal or regulatory question, firecrawl_gov_search searches primary law and regulatory material from US federal, state, and local government sources: statutes, regulations, codes, court opinions, and other government publications; firecrawl_search with categories: ["gov"] reaches the same sources and cannot be combined with other categories. firecrawl_search with sources: [{type: "alexandria"}] returns compact tool summaries in data.tools; toolDetail: "full" includes contracts, firecrawl_find_tools starts with categories, lists providers, then compact tools, and expands the selected full contract, and firecrawl_scrape with alexandria: [{provider, capability, options}] executes up to ten capabilities and returns their results. Alexandria access needs an API key on a team with it enabled. Provide only the required inputs and account for stated network or external side effects.`;
|
|
3896
3969
|
var KEYLESS_PROFILE_INSTRUCTIONS = `Hosted keyless sessions expose firecrawl_search, firecrawl_scrape, and firecrawl_parse with usage limits. firecrawl_search searches the web. For programming questions, firecrawl_search with categories: ["developer"] searches indexed public repositories, GitHub issues, merged pull requests, repository READMEs, and code documentation. For legal or regulatory questions, firecrawl_search with categories: ["gov"] searches US government legal and regulatory sources and cannot be combined with other categories. For biomedical, life-science, clinical, or arXiv literature, firecrawl_search with categories: ["research"] filters ordinary web results to research-affiliated websites. firecrawl_scrape retrieves one supplied page and can return JSON matching a supplied schema. firecrawl_parse processes supported local files through its two-phase upload flow. An Authorization bearer API key can provide higher usage limits and expose additional tools, subject to plan, deployment, and team policy, including firecrawl_map for site URL discovery, firecrawl_agent and firecrawl_agent_status for multi-source research that returns structured data when the URLs are not known, firecrawl_research_* for paper-index and repository research, and firecrawl_find_tools as the progressive Alexandria catalogue lookup alongside the Alexandria options of firecrawl_search and firecrawl_scrape for catalogued data providers.`;
|
|
3897
3970
|
function deprecatedExtractPayload() {
|
|
3898
3971
|
return {
|
|
@@ -3953,10 +4026,18 @@ function createFirecrawlMcpServer(options2 = {}) {
|
|
|
3953
4026
|
const cached = request?.[authResultByRequest];
|
|
3954
4027
|
if (cached) return cached;
|
|
3955
4028
|
const authResult = authenticateSession(request).then((session) => {
|
|
3956
|
-
const
|
|
4029
|
+
const headers = request?.headers;
|
|
4030
|
+
const userAgent = headers?.["user-agent"];
|
|
3957
4031
|
if (typeof userAgent === "string" && userAgent !== "") {
|
|
3958
4032
|
session.clientUserAgent = userAgent;
|
|
3959
4033
|
}
|
|
4034
|
+
const declaredOrigin = normalizeHeader(headers?.[DECLARED_ORIGIN_HEADER]);
|
|
4035
|
+
if (declaredOrigin) session.clientOrigin = declaredOrigin;
|
|
4036
|
+
const referrer = [
|
|
4037
|
+
normalizeHeader(headers?.["origin"]),
|
|
4038
|
+
normalizeHeader(headers?.["referer"])
|
|
4039
|
+
].find((value) => value && value !== "null");
|
|
4040
|
+
if (referrer) session.clientReferrer = referrer;
|
|
3960
4041
|
return session;
|
|
3961
4042
|
});
|
|
3962
4043
|
if (request) request[authResultByRequest] = authResult;
|
|
@@ -4561,7 +4642,7 @@ Scrape one URL and return its content: markdown by default, or HTML, links, scre
|
|
|
4561
4642
|
|
|
4562
4643
|
Firecrawl may serve recently indexed content; set \`maxAge: 0\` for a live fetch or a smaller \`maxAge\` to bound staleness. A successful response does not by itself confirm the page is still current. ${SAFE_MODE ? "A named browser profile loads saved session data without saving changes to it." : "Browser actions can change the live page when interactive actions are enabled."} Authenticated responses can include a \`metadata.scrapeId\` for optional scrape feedback.
|
|
4563
4644
|
|
|
4564
|
-
On an authenticated session with Alexandria access, \`firecrawl_search\`
|
|
4645
|
+
On an authenticated session with Alexandria access, \`firecrawl_search\` and \`firecrawl_find_tools\` can discover providers for the same fields across several pages; a matching provider returns typed records in one call. Keyless sessions have no provider matches.
|
|
4565
4646
|
|
|
4566
4647
|
Alexandria mode, on an authenticated session with Alexandria access: \`alexandria\` selects catalogued capability execution and is mutually exclusive with \`url\`.
|
|
4567
4648
|
`,
|
|
@@ -4691,11 +4772,13 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
|
|
|
4691
4772
|
),
|
|
4692
4773
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
4693
4774
|
const { query, ...opts } = args2;
|
|
4775
|
+
const requestedSources = normalizeSearchSources(
|
|
4776
|
+
Array.isArray(opts.sources) && opts.sources.length ? opts.sources : ["web"]
|
|
4777
|
+
);
|
|
4778
|
+
const addsAlexandria = hasCredential(session) && enterpriseAllowsAlexandria(opts.enterprise) && !hasAlexandria(requestedSources);
|
|
4694
4779
|
const searchOpts = {
|
|
4695
4780
|
...opts,
|
|
4696
|
-
sources:
|
|
4697
|
-
opts.sources ?? (hasCredential(session) ? ["web", "alexandria"] : ["web"])
|
|
4698
|
-
)
|
|
4781
|
+
sources: addsAlexandria ? withAlexandriaSource(requestedSources) : requestedSources
|
|
4699
4782
|
};
|
|
4700
4783
|
searchOpts.domainTools ??= defaultDomainTools(searchOpts.sources);
|
|
4701
4784
|
searchOpts.toolDetail ??= "compact";
|
|
@@ -4725,7 +4808,8 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
|
|
|
4725
4808
|
const postSearch = () => postSearchWithFallback(
|
|
4726
4809
|
client,
|
|
4727
4810
|
searchBody,
|
|
4728
|
-
|
|
4811
|
+
addsAlexandria && opts.domainTools !== true,
|
|
4812
|
+
requestedSources
|
|
4729
4813
|
);
|
|
4730
4814
|
const httpRes = await relayExchangeError(postSearch, { tool: "firecrawl_search" });
|
|
4731
4815
|
return structuredCompact(withoutDiscoveredToolPrices(httpRes?.data ?? {}));
|
|
@@ -6384,7 +6468,7 @@ function searchSurfaceScrapeDescription(safeMode) {
|
|
|
6384
6468
|
return `
|
|
6385
6469
|
Scrape one URL and return its content, or execute catalogued Alexandria capabilities. URL mode returns markdown by default, or HTML, links, screenshots, branding data, a targeted answer, or JSON matching a supplied schema, plus page metadata. Firecrawl may serve recently indexed content; set \`maxAge: 0\` for a live fetch. A successful response does not by itself confirm the page is still current. ${safeMode ? "A named browser profile loads saved session data without saving changes to it." : "Browser actions can change the live page, and a named browser profile can load saved session data and overwrite its stored state."}
|
|
6386
6470
|
|
|
6387
|
-
\`firecrawl_search\`
|
|
6471
|
+
\`firecrawl_search\` and \`firecrawl_find_tools\` can discover providers for the same fields across several pages; a matching Alexandria provider returns typed records in one call.
|
|
6388
6472
|
|
|
6389
6473
|
Alexandria mode: \`alexandria\` selects catalogued capability execution and is mutually exclusive with \`url\`. Alexandria execution is billed per capability (pass includeCosts to firecrawl_find_tools to see prices if you need) and needs a team with Alexandria enabled.
|
|
6390
6474
|
`;
|
|
@@ -6440,6 +6524,11 @@ Returns result groups in \`data\` and an operation \`id\`.
|
|
|
6440
6524
|
toolDetail
|
|
6441
6525
|
} = args2;
|
|
6442
6526
|
const searchQuery = query ?? "";
|
|
6527
|
+
const requestedSources = normalizeSearchSources(
|
|
6528
|
+
sources?.length ? sources : ["web"]
|
|
6529
|
+
);
|
|
6530
|
+
const addsAlexandria = hasCredential(session) && enterpriseAllowsAlexandria(enterprise) && !hasAlexandria(requestedSources);
|
|
6531
|
+
const searchSources = addsAlexandria ? withAlexandriaSource(requestedSources) : requestedSources;
|
|
6443
6532
|
const searchBody = {
|
|
6444
6533
|
query: searchQuery,
|
|
6445
6534
|
...removeEmptyTopLevel({
|
|
@@ -6449,12 +6538,12 @@ Returns result groups in \`data\` and an operation \`id\`.
|
|
|
6449
6538
|
tbs,
|
|
6450
6539
|
filter,
|
|
6451
6540
|
location,
|
|
6452
|
-
sources:
|
|
6541
|
+
sources: searchSources,
|
|
6453
6542
|
categories,
|
|
6454
6543
|
highlights,
|
|
6455
6544
|
enterprise,
|
|
6456
6545
|
toolDetail: toolDetail ?? "compact",
|
|
6457
|
-
domainTools: domainTools ?? defaultDomainTools(
|
|
6546
|
+
domainTools: domainTools ?? defaultDomainTools(searchSources)
|
|
6458
6547
|
}),
|
|
6459
6548
|
origin: requestOrigin(mcpClient, session)
|
|
6460
6549
|
};
|
|
@@ -6466,7 +6555,8 @@ Returns result groups in \`data\` and an operation \`id\`.
|
|
|
6466
6555
|
const postSearch = () => postSearchWithFallback(
|
|
6467
6556
|
client,
|
|
6468
6557
|
searchBody,
|
|
6469
|
-
|
|
6558
|
+
addsAlexandria && domainTools !== true,
|
|
6559
|
+
requestedSources
|
|
6470
6560
|
);
|
|
6471
6561
|
const httpRes = await relayExchangeError(postSearch, { tool: "firecrawl_search" });
|
|
6472
6562
|
return structuredCompactResult(httpRes?.data ?? {});
|
package/dist/origin.js
CHANGED
|
@@ -1,19 +1,83 @@
|
|
|
1
1
|
// src/origin.ts
|
|
2
2
|
import { createRequire } from "module";
|
|
3
3
|
var ORIGIN_SERVER = "mcp-fastmcp";
|
|
4
|
+
var DECLARED_ORIGIN_HEADER = "x-firecrawl-origin";
|
|
4
5
|
var NAME_MAX = 48;
|
|
6
|
+
var RUNTIME_PRODUCTS = /* @__PURE__ */ new Set([
|
|
7
|
+
"apache-httpasyncclient",
|
|
8
|
+
"apache-httpclient",
|
|
9
|
+
"aiohttp",
|
|
10
|
+
"axios",
|
|
11
|
+
"bun",
|
|
12
|
+
"curl",
|
|
13
|
+
"dart",
|
|
14
|
+
"deno",
|
|
15
|
+
"faraday",
|
|
16
|
+
"go-http-client",
|
|
17
|
+
"got",
|
|
18
|
+
"guzzlehttp",
|
|
19
|
+
"java-http-client",
|
|
20
|
+
"ktor-client",
|
|
21
|
+
"libcurl-agent",
|
|
22
|
+
"mozilla",
|
|
23
|
+
"node",
|
|
24
|
+
"node-fetch",
|
|
25
|
+
"okhttp",
|
|
26
|
+
"python",
|
|
27
|
+
"python-httpx",
|
|
28
|
+
"python-httpx2",
|
|
29
|
+
"python-requests",
|
|
30
|
+
"python-urllib",
|
|
31
|
+
"reqwest",
|
|
32
|
+
"ruby",
|
|
33
|
+
"undici",
|
|
34
|
+
"wget"
|
|
35
|
+
]);
|
|
36
|
+
var GENERIC_CLIENT_NAMES = /* @__PURE__ */ new Set([
|
|
37
|
+
"client",
|
|
38
|
+
"example-client",
|
|
39
|
+
"mcp",
|
|
40
|
+
"mcp-client",
|
|
41
|
+
"test",
|
|
42
|
+
"test-client"
|
|
43
|
+
]);
|
|
5
44
|
function slugOf(name) {
|
|
6
45
|
return (name ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, NAME_MAX).replace(/-+$/, "");
|
|
7
46
|
}
|
|
47
|
+
function isOwnSlug(slug) {
|
|
48
|
+
return slug === "" || slug === "fastmcp" || slug === "firecrawl-fastmcp";
|
|
49
|
+
}
|
|
8
50
|
function userAgentProduct(userAgent) {
|
|
9
51
|
const first = (userAgent ?? "").trim().split(/\s+/)[0] ?? "";
|
|
10
52
|
return slugOf(first.split("/")[0]);
|
|
11
53
|
}
|
|
12
|
-
function
|
|
54
|
+
function isRuntimeUserAgent(userAgent) {
|
|
55
|
+
const product = userAgentProduct(userAgent);
|
|
56
|
+
return product === "" || RUNTIME_PRODUCTS.has(product);
|
|
57
|
+
}
|
|
58
|
+
function initializeClientSlug(name) {
|
|
59
|
+
const slug = slugOf(name);
|
|
60
|
+
return isOwnSlug(slug) || GENERIC_CLIENT_NAMES.has(slug) ? "" : slug;
|
|
61
|
+
}
|
|
62
|
+
function referrerSlug(referrer) {
|
|
63
|
+
let host;
|
|
64
|
+
try {
|
|
65
|
+
host = new URL(referrer ?? "").hostname.toLowerCase().replace(/\.$/, "");
|
|
66
|
+
} catch {
|
|
67
|
+
return "";
|
|
68
|
+
}
|
|
69
|
+
if (!host.includes(".") || host.endsWith(".localhost") || host.endsWith(".local") || /^[\d.]+$/.test(host) || host.startsWith("[")) {
|
|
70
|
+
return "";
|
|
71
|
+
}
|
|
72
|
+
return slugOf(host.replace(/^www\./, ""));
|
|
73
|
+
}
|
|
74
|
+
function originForClient(client, serverVersion2, userAgent, signals = {}) {
|
|
75
|
+
const declared = slugOf(signals.declaredOrigin);
|
|
13
76
|
const slug = slugOf(client?.name);
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
77
|
+
const product = userAgentProduct(userAgent);
|
|
78
|
+
const remembered = initializeClientSlug(signals.initializeClient?.name);
|
|
79
|
+
const referrer = referrerSlug(signals.referrer);
|
|
80
|
+
const base = !isOwnSlug(declared) ? `mcp-${declared}` : !isOwnSlug(slug) ? `mcp-${slug}` : product && !RUNTIME_PRODUCTS.has(product) ? `mcp-ua-${product}` : remembered ? `mcp-${remembered}` : referrer ? `mcp-ref-${referrer}` : product ? `mcp-ua-${product}` : ORIGIN_SERVER;
|
|
17
81
|
return serverVersion2 ? `${base}@${serverVersion2}` : base;
|
|
18
82
|
}
|
|
19
83
|
function originHeaders(origin) {
|
|
@@ -26,11 +90,19 @@ function requestOrigin(client, session) {
|
|
|
26
90
|
return originForClient(
|
|
27
91
|
client?.version ?? null,
|
|
28
92
|
serverVersion,
|
|
29
|
-
session?.clientUserAgent
|
|
93
|
+
session?.clientUserAgent,
|
|
94
|
+
{
|
|
95
|
+
declaredOrigin: session?.clientOrigin,
|
|
96
|
+
initializeClient: session?.initializeClient,
|
|
97
|
+
referrer: session?.clientReferrer
|
|
98
|
+
}
|
|
30
99
|
);
|
|
31
100
|
}
|
|
32
101
|
export {
|
|
102
|
+
DECLARED_ORIGIN_HEADER,
|
|
33
103
|
ORIGIN_SERVER,
|
|
104
|
+
initializeClientSlug,
|
|
105
|
+
isRuntimeUserAgent,
|
|
34
106
|
originForClient,
|
|
35
107
|
originHeaders,
|
|
36
108
|
requestOrigin,
|
package/dist/server.d.ts
CHANGED
|
@@ -6,10 +6,20 @@ import { z } from 'zod';
|
|
|
6
6
|
declare function normalizeHeader(value: string | string[] | undefined): string | undefined;
|
|
7
7
|
declare function withoutTrailingSlash(value: string): string;
|
|
8
8
|
|
|
9
|
+
/** The header a connection sets to name the integration it comes from. */
|
|
10
|
+
declare const DECLARED_ORIGIN_HEADER = "x-firecrawl-origin";
|
|
9
11
|
type ClientImplementation = {
|
|
10
12
|
name?: string;
|
|
11
13
|
version?: string;
|
|
12
14
|
};
|
|
15
|
+
/** Whether a User-Agent is absent or names an HTTP runtime rather than an MCP client. */
|
|
16
|
+
declare function isRuntimeUserAgent(userAgent: string | null | undefined): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* The token an initialize client name stamps when it reaches a call only
|
|
19
|
+
* through `ClientSignals.initializeClient`; empty for the server's own names
|
|
20
|
+
* and the defaults SDKs send when the integration sets none.
|
|
21
|
+
*/
|
|
22
|
+
declare function initializeClientSlug(name: string | null | undefined): string;
|
|
13
23
|
/** The header form of an origin, for the requests that reach the API without a body. */
|
|
14
24
|
declare function originHeaders(origin: string): Record<string, string>;
|
|
15
25
|
/**
|
|
@@ -19,14 +29,19 @@ declare function originHeaders(origin: string): Record<string, string>;
|
|
|
19
29
|
type McpClient = {
|
|
20
30
|
version?: ClientImplementation | undefined;
|
|
21
31
|
} | undefined;
|
|
32
|
+
/** The client signals the authenticated session carries (see SessionData). */
|
|
33
|
+
type OriginSession = {
|
|
34
|
+
clientUserAgent?: string;
|
|
35
|
+
clientOrigin?: string;
|
|
36
|
+
clientReferrer?: string;
|
|
37
|
+
initializeClient?: ClientImplementation;
|
|
38
|
+
};
|
|
22
39
|
/**
|
|
23
40
|
* The origin of every request a tool call makes: the call's client, the
|
|
24
|
-
*
|
|
25
|
-
* no client name), and this server's version.
|
|
41
|
+
* client signals of the request the session was authenticated with
|
|
42
|
+
* (stateless HTTP calls carry no client name), and this server's version.
|
|
26
43
|
*/
|
|
27
|
-
declare function requestOrigin(client: McpClient, session?:
|
|
28
|
-
clientUserAgent?: string;
|
|
29
|
-
} | undefined): string;
|
|
44
|
+
declare function requestOrigin(client: McpClient, session?: OriginSession | undefined): string;
|
|
30
45
|
|
|
31
46
|
/** An API-issued keyless signup link, or undefined for anything else. */
|
|
32
47
|
declare function keylessSignupUrlFrom(value: unknown): string | undefined;
|
|
@@ -44,6 +59,10 @@ declare function keylessQuotaReason(reason: unknown): reason is 'requests' | 'cr
|
|
|
44
59
|
|
|
45
60
|
declare function hasAlexandria(sources: unknown): boolean;
|
|
46
61
|
declare function defaultDomainTools(sources: unknown): boolean;
|
|
62
|
+
/** Adds alexandria to the requested sources, keeping the caller's entries and their string or object form. */
|
|
63
|
+
declare function withAlexandriaSource(sources: unknown): unknown[];
|
|
64
|
+
/** Zero data retention and anonymous searches cannot run provider discovery. */
|
|
65
|
+
declare function enterpriseAllowsAlexandria(enterprise: unknown): boolean;
|
|
47
66
|
declare function normalizeSearchSources(sources: unknown): unknown;
|
|
48
67
|
declare function searchQueryIsValid(args: {
|
|
49
68
|
query?: string;
|
|
@@ -146,7 +165,8 @@ type ExchangeErrorContext = {
|
|
|
146
165
|
};
|
|
147
166
|
declare function relayTermsRequired<T>(run: () => Promise<T>, context: ExchangeErrorContext): Promise<T>;
|
|
148
167
|
declare function relayExchangeError(run: () => Promise<any>, context: ExchangeErrorContext): Promise<any>;
|
|
149
|
-
|
|
168
|
+
/** Retries without Alexandria, on fallbackSources, when tools the caller did not ask for are refused. */
|
|
169
|
+
declare function postSearchWithFallback(client: any, body: Record<string, unknown>, implicitTools: boolean, fallbackSources?: unknown[]): Promise<any>;
|
|
150
170
|
|
|
151
171
|
/**
|
|
152
172
|
* Library entry point: `import { createFirecrawlMcpServer } from 'firecrawl-mcp/server'`.
|
|
@@ -204,10 +224,23 @@ interface SessionData {
|
|
|
204
224
|
*/
|
|
205
225
|
firecrawlApiKey?: string;
|
|
206
226
|
/**
|
|
207
|
-
* The User-Agent of the HTTP request that opened the session, the
|
|
208
|
-
* client
|
|
227
|
+
* The User-Agent of the HTTP request that opened the session, one of the
|
|
228
|
+
* client signals a stateless HTTP tool call carries (see src/origin.ts).
|
|
209
229
|
*/
|
|
210
230
|
clientUserAgent?: string;
|
|
231
|
+
/** The `X-Firecrawl-Origin` header: the integration the connection names. */
|
|
232
|
+
clientOrigin?: string;
|
|
233
|
+
/** The `Origin` or `Referer` header of that request. */
|
|
234
|
+
clientReferrer?: string;
|
|
235
|
+
/**
|
|
236
|
+
* The client an earlier initialize named, for stateless HTTP calls that do
|
|
237
|
+
* not carry it. Set by an embedder that can recover it; read only when the
|
|
238
|
+
* User-Agent names a runtime rather than a client.
|
|
239
|
+
*/
|
|
240
|
+
initializeClient?: {
|
|
241
|
+
name?: string;
|
|
242
|
+
version?: string;
|
|
243
|
+
};
|
|
211
244
|
authType?: 'api-key' | 'oauth' | 'env' | 'keyless' | 'none';
|
|
212
245
|
/** Admitted, but the supplied credential cannot be used. */
|
|
213
246
|
credentialError?: 'CREDENTIAL_INVALID';
|
|
@@ -423,4 +456,4 @@ declare const structuredCompactResult: (data: unknown) => ToolResult;
|
|
|
423
456
|
|
|
424
457
|
declare function createFirecrawlMcpServer(options?: FirecrawlMcpServerOptions): FirecrawlMcpServer;
|
|
425
458
|
|
|
426
|
-
export { ALEXANDRIA_CATALOGUE_VERTICALS, ALEXANDRIA_SEARCH_INSTRUCTIONS, type AuthenticationRequest, type ExtraToolsContext, type FirecrawlMcpServer, type FirecrawlMcpServerHooks, type FirecrawlMcpServerOptions, type FirecrawlMcpServerStartArgs, type HttpApp, type InstructionDefaults, keylessToolNames as KEYLESS_TOOL_NAMES, type OutboundRequest, type ProtectedResourceMetadata, type ResourceContent, type ResourceDefinition, type ResourceRegistrar, SEARCH_DOMAINS_CONFLICT_MESSAGE, type Session, type SessionData, type ToolCallEvent, type ToolContext, type ToolDefinition, type ToolIcon, type ToolLogger, type ToolRegistrar, type ToolResult, UserError, assertExchangeCredential, createFirecrawlMcpServer, defaultDomainTools, hasAlexandria, invalidApiKeyRecoveryPayload, invalidOAuthRecoveryPayload, keylessQuotaReason, keylessSignupUrlFrom, normalizeHeader, normalizeSearchSources, originHeaders, postSearchWithFallback, recoveryPayload, relayExchangeError, relayTermsRequired, removeEmptyTopLevel, requestOrigin, searchDomainsAreExclusive, searchOutputSchema, searchQueryIsValid, searchToolBaseFields, structuredCompactResult as structuredCompact, withoutTrailingSlash };
|
|
459
|
+
export { ALEXANDRIA_CATALOGUE_VERTICALS, ALEXANDRIA_SEARCH_INSTRUCTIONS, type AuthenticationRequest, DECLARED_ORIGIN_HEADER, type ExtraToolsContext, type FirecrawlMcpServer, type FirecrawlMcpServerHooks, type FirecrawlMcpServerOptions, type FirecrawlMcpServerStartArgs, type HttpApp, type InstructionDefaults, keylessToolNames as KEYLESS_TOOL_NAMES, type OutboundRequest, type ProtectedResourceMetadata, type ResourceContent, type ResourceDefinition, type ResourceRegistrar, SEARCH_DOMAINS_CONFLICT_MESSAGE, type Session, type SessionData, type ToolCallEvent, type ToolContext, type ToolDefinition, type ToolIcon, type ToolLogger, type ToolRegistrar, type ToolResult, UserError, assertExchangeCredential, createFirecrawlMcpServer, defaultDomainTools, enterpriseAllowsAlexandria, hasAlexandria, initializeClientSlug, invalidApiKeyRecoveryPayload, invalidOAuthRecoveryPayload, isRuntimeUserAgent, keylessQuotaReason, keylessSignupUrlFrom, normalizeHeader, normalizeSearchSources, originHeaders, postSearchWithFallback, recoveryPayload, relayExchangeError, relayTermsRequired, removeEmptyTopLevel, requestOrigin, searchDomainsAreExclusive, searchOutputSchema, searchQueryIsValid, searchToolBaseFields, structuredCompactResult as structuredCompact, withAlexandriaSource, withoutTrailingSlash };
|
package/dist/server.js
CHANGED
|
@@ -2396,6 +2396,14 @@ function defaultDomainTools(sources) {
|
|
|
2396
2396
|
(source) => ["web", "news", "images"].includes(typeof source === "string" ? source : source?.type)
|
|
2397
2397
|
);
|
|
2398
2398
|
}
|
|
2399
|
+
function withAlexandriaSource(sources) {
|
|
2400
|
+
if (!Array.isArray(sources) || sources.length === 0) return ["web", "alexandria"];
|
|
2401
|
+
if (hasAlexandria(sources)) return sources;
|
|
2402
|
+
return [...sources, typeof sources[0] === "string" ? "alexandria" : { type: "alexandria" }];
|
|
2403
|
+
}
|
|
2404
|
+
function enterpriseAllowsAlexandria(enterprise) {
|
|
2405
|
+
return !(Array.isArray(enterprise) && enterprise.some((mode) => mode === "zdr" || mode === "anon"));
|
|
2406
|
+
}
|
|
2399
2407
|
function normalizeSearchSources(sources) {
|
|
2400
2408
|
if (!Array.isArray(sources)) return sources;
|
|
2401
2409
|
return sources.map(
|
|
@@ -2422,8 +2430,8 @@ var findToolsSchema = z5.object({
|
|
|
2422
2430
|
}).strict();
|
|
2423
2431
|
var ALEXANDRIA_CATALOGUE_VERTICALS = "companies, people, jobs, finance and filings, public records and government spending, real estate, places and restaurants, retail and prices, package registries and developer data, news, research, and more";
|
|
2424
2432
|
var ALEXANDRIA_CATALOGUE_SENTENCE = "Alexandria is Firecrawl's catalogue of data providers and workflows across " + ALEXANDRIA_CATALOGUE_VERTICALS + "; providers return typed, sourced records through published contracts.";
|
|
2425
|
-
var
|
|
2426
|
-
var ALEXANDRIA_SEARCH_LEAD = "Authenticated search also returns matching Alexandria data providers in data.tools (" + ALEXANDRIA_CATALOGUE_VERTICALS + "). Prefer a provider over scraping pages when the task needs the same fields across several entities, exact figures or timestamps, provenance, or many records; use web results when they already answer the question. " +
|
|
2433
|
+
var ALEXANDRIA_SOURCES_ALWAYS_ON = 'Authenticated searches always add alexandria to the sources, so sources: ["web"] also returns providers; domainTools: false turns off website-matched tools only.';
|
|
2434
|
+
var ALEXANDRIA_SEARCH_LEAD = "Authenticated search also returns matching Alexandria data providers in data.tools (" + ALEXANDRIA_CATALOGUE_VERTICALS + "). Prefer a provider over scraping pages when the task needs the same fields across several entities, exact figures or timestamps, provenance, or many records; use web results when they already answer the question. " + ALEXANDRIA_SOURCES_ALWAYS_ON;
|
|
2427
2435
|
var ALEXANDRIA_CONTRACT_GUIDANCE = "The selected contract marks required inputs and any requiresOneOf groups (at least one member per group); it may include example.request/example.response and response.key (which may differ from records). Where pagination is declared, its fields govern paging with the same filters; catalogue next is separate from provider pagination.";
|
|
2428
2436
|
function findToolsOptions(args) {
|
|
2429
2437
|
const level = args.level ?? (args.query || args.capabilities?.length || args.providers?.length || args.groups?.length || args.urls?.length ? "tools" : args.categories?.length ? "providers" : "categories");
|
|
@@ -2455,7 +2463,7 @@ function withFindToolsNavigation(envelope, carry = {}) {
|
|
|
2455
2463
|
if (link(page.next)) page.nextTool = link(page.next);
|
|
2456
2464
|
return envelope;
|
|
2457
2465
|
}
|
|
2458
|
-
var ALEXANDRIA_SEARCH_INSTRUCTIONS = 'Authenticated search combines web results, semantic tool summaries and domain matches. Use sources: ["alexandria"] for semantic tools only. ' +
|
|
2466
|
+
var ALEXANDRIA_SEARCH_INSTRUCTIONS = 'Authenticated search combines web results, semantic tool summaries and domain matches. Use sources: ["alexandria"] for semantic tools only. ' + ALEXANDRIA_SOURCES_ALWAYS_ON + ' Tool matches describe available structured-data capabilities, not executed data. toolDetail: "compact" (default) returns only provider, capability and description; "summary" adds metadata; "full" includes their input and output contracts. firecrawl_scrape with an alexandria body executes a selected capability; firecrawl_find_tools provides catalogue browsing and full contracts.';
|
|
2459
2467
|
var TOOL_PRICE_KEYS = ["creditsCost", "perRecord", "billing", "recordsPerUnit", "zeroDataRetentionCreditsCost"];
|
|
2460
2468
|
function withoutToolPrice(tool) {
|
|
2461
2469
|
if (!tool || typeof tool !== "object" || Array.isArray(tool)) return tool;
|
|
@@ -2570,19 +2578,83 @@ import { z as z6 } from "zod";
|
|
|
2570
2578
|
// src/origin.ts
|
|
2571
2579
|
import { createRequire } from "module";
|
|
2572
2580
|
var ORIGIN_SERVER = "mcp-fastmcp";
|
|
2581
|
+
var DECLARED_ORIGIN_HEADER = "x-firecrawl-origin";
|
|
2573
2582
|
var NAME_MAX = 48;
|
|
2583
|
+
var RUNTIME_PRODUCTS = /* @__PURE__ */ new Set([
|
|
2584
|
+
"apache-httpasyncclient",
|
|
2585
|
+
"apache-httpclient",
|
|
2586
|
+
"aiohttp",
|
|
2587
|
+
"axios",
|
|
2588
|
+
"bun",
|
|
2589
|
+
"curl",
|
|
2590
|
+
"dart",
|
|
2591
|
+
"deno",
|
|
2592
|
+
"faraday",
|
|
2593
|
+
"go-http-client",
|
|
2594
|
+
"got",
|
|
2595
|
+
"guzzlehttp",
|
|
2596
|
+
"java-http-client",
|
|
2597
|
+
"ktor-client",
|
|
2598
|
+
"libcurl-agent",
|
|
2599
|
+
"mozilla",
|
|
2600
|
+
"node",
|
|
2601
|
+
"node-fetch",
|
|
2602
|
+
"okhttp",
|
|
2603
|
+
"python",
|
|
2604
|
+
"python-httpx",
|
|
2605
|
+
"python-httpx2",
|
|
2606
|
+
"python-requests",
|
|
2607
|
+
"python-urllib",
|
|
2608
|
+
"reqwest",
|
|
2609
|
+
"ruby",
|
|
2610
|
+
"undici",
|
|
2611
|
+
"wget"
|
|
2612
|
+
]);
|
|
2613
|
+
var GENERIC_CLIENT_NAMES = /* @__PURE__ */ new Set([
|
|
2614
|
+
"client",
|
|
2615
|
+
"example-client",
|
|
2616
|
+
"mcp",
|
|
2617
|
+
"mcp-client",
|
|
2618
|
+
"test",
|
|
2619
|
+
"test-client"
|
|
2620
|
+
]);
|
|
2574
2621
|
function slugOf(name2) {
|
|
2575
2622
|
return (name2 ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, NAME_MAX).replace(/-+$/, "");
|
|
2576
2623
|
}
|
|
2624
|
+
function isOwnSlug(slug) {
|
|
2625
|
+
return slug === "" || slug === "fastmcp" || slug === "firecrawl-fastmcp";
|
|
2626
|
+
}
|
|
2577
2627
|
function userAgentProduct(userAgent) {
|
|
2578
2628
|
const first = (userAgent ?? "").trim().split(/\s+/)[0] ?? "";
|
|
2579
2629
|
return slugOf(first.split("/")[0]);
|
|
2580
2630
|
}
|
|
2581
|
-
function
|
|
2631
|
+
function isRuntimeUserAgent(userAgent) {
|
|
2632
|
+
const product = userAgentProduct(userAgent);
|
|
2633
|
+
return product === "" || RUNTIME_PRODUCTS.has(product);
|
|
2634
|
+
}
|
|
2635
|
+
function initializeClientSlug(name2) {
|
|
2636
|
+
const slug = slugOf(name2);
|
|
2637
|
+
return isOwnSlug(slug) || GENERIC_CLIENT_NAMES.has(slug) ? "" : slug;
|
|
2638
|
+
}
|
|
2639
|
+
function referrerSlug(referrer) {
|
|
2640
|
+
let host;
|
|
2641
|
+
try {
|
|
2642
|
+
host = new URL(referrer ?? "").hostname.toLowerCase().replace(/\.$/, "");
|
|
2643
|
+
} catch {
|
|
2644
|
+
return "";
|
|
2645
|
+
}
|
|
2646
|
+
if (!host.includes(".") || host.endsWith(".localhost") || host.endsWith(".local") || /^[\d.]+$/.test(host) || host.startsWith("[")) {
|
|
2647
|
+
return "";
|
|
2648
|
+
}
|
|
2649
|
+
return slugOf(host.replace(/^www\./, ""));
|
|
2650
|
+
}
|
|
2651
|
+
function originForClient(client, serverVersion2, userAgent, signals = {}) {
|
|
2652
|
+
const declared = slugOf(signals.declaredOrigin);
|
|
2582
2653
|
const slug = slugOf(client?.name);
|
|
2583
|
-
const
|
|
2584
|
-
const
|
|
2585
|
-
const
|
|
2654
|
+
const product = userAgentProduct(userAgent);
|
|
2655
|
+
const remembered = initializeClientSlug(signals.initializeClient?.name);
|
|
2656
|
+
const referrer = referrerSlug(signals.referrer);
|
|
2657
|
+
const base = !isOwnSlug(declared) ? `mcp-${declared}` : !isOwnSlug(slug) ? `mcp-${slug}` : product && !RUNTIME_PRODUCTS.has(product) ? `mcp-ua-${product}` : remembered ? `mcp-${remembered}` : referrer ? `mcp-ref-${referrer}` : product ? `mcp-ua-${product}` : ORIGIN_SERVER;
|
|
2586
2658
|
return serverVersion2 ? `${base}@${serverVersion2}` : base;
|
|
2587
2659
|
}
|
|
2588
2660
|
function originHeaders(origin) {
|
|
@@ -2595,7 +2667,12 @@ function requestOrigin(client, session) {
|
|
|
2595
2667
|
return originForClient(
|
|
2596
2668
|
client?.version ?? null,
|
|
2597
2669
|
serverVersion,
|
|
2598
|
-
session?.clientUserAgent
|
|
2670
|
+
session?.clientUserAgent,
|
|
2671
|
+
{
|
|
2672
|
+
declaredOrigin: session?.clientOrigin,
|
|
2673
|
+
initializeClient: session?.initializeClient,
|
|
2674
|
+
referrer: session?.clientReferrer
|
|
2675
|
+
}
|
|
2599
2676
|
);
|
|
2600
2677
|
}
|
|
2601
2678
|
|
|
@@ -3672,7 +3749,7 @@ var searchToolBaseFields = {
|
|
|
3672
3749
|
location: z11.string().optional(),
|
|
3673
3750
|
includeDomains: z11.array(searchDomainSchema).optional().describe("Hostnames to restrict results to. Mutually exclusive with excludeDomains."),
|
|
3674
3751
|
excludeDomains: z11.array(searchDomainSchema).optional().describe("Hostnames to leave out of results. Mutually exclusive with includeDomains."),
|
|
3675
|
-
sources: z11.array(searchSourceSchema).optional().describe("Search sources; authenticated sessions default to web + alexandria, keyless sessions to web only. " +
|
|
3752
|
+
sources: z11.array(searchSourceSchema).optional().describe("Search sources; authenticated sessions default to web + alexandria, keyless sessions to web only. " + ALEXANDRIA_SOURCES_ALWAYS_ON + ' enterprise zdr or anon searches do not add it. Use ["alexandria"] alone for provider discovery without web results.'),
|
|
3676
3753
|
categories: z11.array(z11.enum(["research", "pdf", "developer", "gov"])).optional().describe(
|
|
3677
3754
|
"Limit results to specific source types. `research` restricts ordinary web results to research-affiliated websites and returns page snippets, which is separate from the `firecrawl_research_*` tools that search paper abstracts and full text across biomedical (PubMed, bioRxiv, medRxiv) and arXiv literature; `pdf` searches PDF results; `developer` searches an index built for coding agents over public repositories, GitHub issues, merged pull requests, repository READMEs, and code documentation; `gov` searches the Government Index of US federal, state, and local legal and regulatory sources, the index behind firecrawl_gov_search, and cannot be combined with other categories; gov hits can include historical versions, bill text, agency guidance, and third-party reproductions, so verify the issuer, jurisdiction, enacted/effective status, and version before citing current governing law, clarify a missing jurisdiction or ambiguous as-of date, and use general search for non-US questions or missing coverage (no hits does not establish that no applicable law exists). `developer` and `gov` return hits in `data.web` with `category` set to their name; the other categories also filter `data.web`."
|
|
3678
3755
|
),
|
|
@@ -3814,7 +3891,7 @@ async function relayExchangeError(run, context) {
|
|
|
3814
3891
|
});
|
|
3815
3892
|
}
|
|
3816
3893
|
}
|
|
3817
|
-
async function postSearchWithFallback(client, body, implicitTools) {
|
|
3894
|
+
async function postSearchWithFallback(client, body, implicitTools, fallbackSources = ["web"]) {
|
|
3818
3895
|
try {
|
|
3819
3896
|
return await client.http.post("/v2/search", body);
|
|
3820
3897
|
} catch (error2) {
|
|
@@ -3827,7 +3904,7 @@ async function postSearchWithFallback(client, body, implicitTools) {
|
|
|
3827
3904
|
if (!implicitTools || response?.status !== 403 || !unavailable) throw error2;
|
|
3828
3905
|
return client.http.post("/v2/search", {
|
|
3829
3906
|
...body,
|
|
3830
|
-
sources:
|
|
3907
|
+
sources: fallbackSources,
|
|
3831
3908
|
domainTools: false
|
|
3832
3909
|
});
|
|
3833
3910
|
}
|
|
@@ -3887,7 +3964,7 @@ var ConsoleLogger = class {
|
|
|
3887
3964
|
}
|
|
3888
3965
|
}
|
|
3889
3966
|
};
|
|
3890
|
-
var FULL_PROFILE_INSTRUCTIONS = `Firecrawl provides web search, page retrieval, site URL discovery, multi-page collection, structured page data, monitoring, and multi-source research that returns structured data. Match the requested operation to the tool boundary: firecrawl_scrape retrieves one supplied page and can return JSON matching a supplied schema, firecrawl_map enumerates URLs under a site without retrieving their content, and firecrawl_agent runs multi-source research and returns structured data when the URLs are not known or the answer spans several sites (an entity plus its fields, a list, a dataset); its result is read with firecrawl_agent_status. Authenticated firecrawl_search returns web results together with matching Alexandria providers in data.tools. ${ALEXANDRIA_CATALOGUE_SENTENCE} A matching provider can return the same fields across several entities, provenance, exact figures or timestamps, or a large set of records through firecrawl_scrape with its published contract. If web results already answer the question, use them. For the same fields across multiple pages, firecrawl_find_tools offers free provider discovery. Use firecrawl_find_tools to read a contract that was not returned in full or to browse the catalogue by category. ${
|
|
3967
|
+
var FULL_PROFILE_INSTRUCTIONS = `Firecrawl provides web search, page retrieval, site URL discovery, multi-page collection, structured page data, monitoring, and multi-source research that returns structured data. Match the requested operation to the tool boundary: firecrawl_scrape retrieves one supplied page and can return JSON matching a supplied schema, firecrawl_map enumerates URLs under a site without retrieving their content, and firecrawl_agent runs multi-source research and returns structured data when the URLs are not known or the answer spans several sites (an entity plus its fields, a list, a dataset); its result is read with firecrawl_agent_status. Authenticated firecrawl_search returns web results together with matching Alexandria providers in data.tools. ${ALEXANDRIA_CATALOGUE_SENTENCE} A matching provider can return the same fields across several entities, provenance, exact figures or timestamps, or a large set of records through firecrawl_scrape with its published contract. If web results already answer the question, use them. For the same fields across multiple pages, firecrawl_find_tools offers free provider discovery. Use firecrawl_find_tools to read a contract that was not returned in full or to browse the catalogue by category. ${ALEXANDRIA_SOURCES_ALWAYS_ON} If no provider fits, continue with web search or firecrawl_agent. For biomedical, life-science, clinical, or arXiv literature, the firecrawl_research_* tools search a paper index of abstracts and full text; firecrawl_search with categories: ["research"] is a website filter over ordinary web results and reaches different sources. For a programming question (code behaviour, a library or framework, an API contract, an error message, or a known bug), firecrawl_developer_search (or firecrawl_search with categories: ["developer"]) searches an index of public repositories, GitHub issues, merged pull requests, READMEs, and code documentation. For a legal or regulatory question, firecrawl_gov_search searches primary law and regulatory material from US federal, state, and local government sources: statutes, regulations, codes, court opinions, and other government publications; firecrawl_search with categories: ["gov"] reaches the same sources and cannot be combined with other categories. firecrawl_search with sources: [{type: "alexandria"}] returns compact tool summaries in data.tools; toolDetail: "full" includes contracts, firecrawl_find_tools starts with categories, lists providers, then compact tools, and expands the selected full contract, and firecrawl_scrape with alexandria: [{provider, capability, options}] executes up to ten capabilities and returns their results. Alexandria access needs an API key on a team with it enabled. Provide only the required inputs and account for stated network or external side effects.`;
|
|
3891
3968
|
var KEYLESS_PROFILE_INSTRUCTIONS = `Hosted keyless sessions expose firecrawl_search, firecrawl_scrape, and firecrawl_parse with usage limits. firecrawl_search searches the web. For programming questions, firecrawl_search with categories: ["developer"] searches indexed public repositories, GitHub issues, merged pull requests, repository READMEs, and code documentation. For legal or regulatory questions, firecrawl_search with categories: ["gov"] searches US government legal and regulatory sources and cannot be combined with other categories. For biomedical, life-science, clinical, or arXiv literature, firecrawl_search with categories: ["research"] filters ordinary web results to research-affiliated websites. firecrawl_scrape retrieves one supplied page and can return JSON matching a supplied schema. firecrawl_parse processes supported local files through its two-phase upload flow. An Authorization bearer API key can provide higher usage limits and expose additional tools, subject to plan, deployment, and team policy, including firecrawl_map for site URL discovery, firecrawl_agent and firecrawl_agent_status for multi-source research that returns structured data when the URLs are not known, firecrawl_research_* for paper-index and repository research, and firecrawl_find_tools as the progressive Alexandria catalogue lookup alongside the Alexandria options of firecrawl_search and firecrawl_scrape for catalogued data providers.`;
|
|
3892
3969
|
function deprecatedExtractPayload() {
|
|
3893
3970
|
return {
|
|
@@ -3948,10 +4025,18 @@ function createFirecrawlMcpServer(options = {}) {
|
|
|
3948
4025
|
const cached = request?.[authResultByRequest];
|
|
3949
4026
|
if (cached) return cached;
|
|
3950
4027
|
const authResult = authenticateSession(request).then((session) => {
|
|
3951
|
-
const
|
|
4028
|
+
const headers = request?.headers;
|
|
4029
|
+
const userAgent = headers?.["user-agent"];
|
|
3952
4030
|
if (typeof userAgent === "string" && userAgent !== "") {
|
|
3953
4031
|
session.clientUserAgent = userAgent;
|
|
3954
4032
|
}
|
|
4033
|
+
const declaredOrigin = normalizeHeader(headers?.[DECLARED_ORIGIN_HEADER]);
|
|
4034
|
+
if (declaredOrigin) session.clientOrigin = declaredOrigin;
|
|
4035
|
+
const referrer = [
|
|
4036
|
+
normalizeHeader(headers?.["origin"]),
|
|
4037
|
+
normalizeHeader(headers?.["referer"])
|
|
4038
|
+
].find((value) => value && value !== "null");
|
|
4039
|
+
if (referrer) session.clientReferrer = referrer;
|
|
3955
4040
|
return session;
|
|
3956
4041
|
});
|
|
3957
4042
|
if (request) request[authResultByRequest] = authResult;
|
|
@@ -4556,7 +4641,7 @@ Scrape one URL and return its content: markdown by default, or HTML, links, scre
|
|
|
4556
4641
|
|
|
4557
4642
|
Firecrawl may serve recently indexed content; set \`maxAge: 0\` for a live fetch or a smaller \`maxAge\` to bound staleness. A successful response does not by itself confirm the page is still current. ${SAFE_MODE ? "A named browser profile loads saved session data without saving changes to it." : "Browser actions can change the live page when interactive actions are enabled."} Authenticated responses can include a \`metadata.scrapeId\` for optional scrape feedback.
|
|
4558
4643
|
|
|
4559
|
-
On an authenticated session with Alexandria access, \`firecrawl_search\`
|
|
4644
|
+
On an authenticated session with Alexandria access, \`firecrawl_search\` and \`firecrawl_find_tools\` can discover providers for the same fields across several pages; a matching provider returns typed records in one call. Keyless sessions have no provider matches.
|
|
4560
4645
|
|
|
4561
4646
|
Alexandria mode, on an authenticated session with Alexandria access: \`alexandria\` selects catalogued capability execution and is mutually exclusive with \`url\`.
|
|
4562
4647
|
`,
|
|
@@ -4686,11 +4771,13 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
|
|
|
4686
4771
|
),
|
|
4687
4772
|
execute: async (args, { session, log, client: mcpClient }) => {
|
|
4688
4773
|
const { query, ...opts } = args;
|
|
4774
|
+
const requestedSources = normalizeSearchSources(
|
|
4775
|
+
Array.isArray(opts.sources) && opts.sources.length ? opts.sources : ["web"]
|
|
4776
|
+
);
|
|
4777
|
+
const addsAlexandria = hasCredential(session) && enterpriseAllowsAlexandria(opts.enterprise) && !hasAlexandria(requestedSources);
|
|
4689
4778
|
const searchOpts = {
|
|
4690
4779
|
...opts,
|
|
4691
|
-
sources:
|
|
4692
|
-
opts.sources ?? (hasCredential(session) ? ["web", "alexandria"] : ["web"])
|
|
4693
|
-
)
|
|
4780
|
+
sources: addsAlexandria ? withAlexandriaSource(requestedSources) : requestedSources
|
|
4694
4781
|
};
|
|
4695
4782
|
searchOpts.domainTools ??= defaultDomainTools(searchOpts.sources);
|
|
4696
4783
|
searchOpts.toolDetail ??= "compact";
|
|
@@ -4720,7 +4807,8 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
|
|
|
4720
4807
|
const postSearch = () => postSearchWithFallback(
|
|
4721
4808
|
client,
|
|
4722
4809
|
searchBody,
|
|
4723
|
-
|
|
4810
|
+
addsAlexandria && opts.domainTools !== true,
|
|
4811
|
+
requestedSources
|
|
4724
4812
|
);
|
|
4725
4813
|
const httpRes = await relayExchangeError(postSearch, { tool: "firecrawl_search" });
|
|
4726
4814
|
return structuredCompact(withoutDiscoveredToolPrices(httpRes?.data ?? {}));
|
|
@@ -5723,15 +5811,19 @@ Set \`redactPII\` to request redaction of personally identifiable information in
|
|
|
5723
5811
|
export {
|
|
5724
5812
|
ALEXANDRIA_CATALOGUE_VERTICALS,
|
|
5725
5813
|
ALEXANDRIA_SEARCH_INSTRUCTIONS,
|
|
5814
|
+
DECLARED_ORIGIN_HEADER,
|
|
5726
5815
|
keylessToolNames as KEYLESS_TOOL_NAMES,
|
|
5727
5816
|
SEARCH_DOMAINS_CONFLICT_MESSAGE,
|
|
5728
5817
|
UserError2 as UserError,
|
|
5729
5818
|
assertExchangeCredential,
|
|
5730
5819
|
createFirecrawlMcpServer,
|
|
5731
5820
|
defaultDomainTools,
|
|
5821
|
+
enterpriseAllowsAlexandria,
|
|
5732
5822
|
hasAlexandria,
|
|
5823
|
+
initializeClientSlug,
|
|
5733
5824
|
invalidApiKeyRecoveryPayload,
|
|
5734
5825
|
invalidOAuthRecoveryPayload,
|
|
5826
|
+
isRuntimeUserAgent,
|
|
5735
5827
|
keylessQuotaReason,
|
|
5736
5828
|
keylessSignupUrlFrom,
|
|
5737
5829
|
normalizeHeader,
|
|
@@ -5748,5 +5840,6 @@ export {
|
|
|
5748
5840
|
searchQueryIsValid,
|
|
5749
5841
|
searchToolBaseFields,
|
|
5750
5842
|
structuredCompactResult as structuredCompact,
|
|
5843
|
+
withAlexandriaSource,
|
|
5751
5844
|
withoutTrailingSlash
|
|
5752
5845
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firecrawl-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.31.0",
|
|
4
4
|
"description": "MCP server for Firecrawl — search, scrape, and interact with the web, and search scientific papers. Supports both cloud and self-hosted instances. Features include web search, scraping, page interaction, batch processing, LLM-powered content analysis, and research paper search over biomedical and arXiv literature (PubMed, bioRxiv, medRxiv, arXiv) with citation-graph expansion and full-text reading.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"mcpName": "io.github.firecrawl/firecrawl-mcp-server",
|