firecrawl-mcp 3.30.3 → 3.31.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/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:
@@ -201,14 +205,6 @@ docker run --rm -p 127.0.0.1:3000:3000 \
201
205
 
202
206
  The image sets `HOST=0.0.0.0` and `PORT=3000`; override `PORT` (and the `-p` mapping) to listen elsewhere. The server uses the container's `FIRECRAWL_API_KEY` for requests that don't send their own credential, so the example publishes the port on `127.0.0.1` only; don't expose it more widely unless clients must supply their own key. Set `FIRECRAWL_API_URL` to use a self-hosted Firecrawl API. The container runs as the unprivileged `node` user.
203
207
 
204
- ### Installing via Smithery (Legacy)
205
-
206
- To install Firecrawl for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@mendableai/mcp-server-firecrawl):
207
-
208
- ```bash
209
- npx -y @smithery/cli install @mendableai/mcp-server-firecrawl --client claude
210
- ```
211
-
212
208
  ### Running on VS Code
213
209
 
214
210
  For one-click installation, click one of the install buttons below...
@@ -1246,7 +1242,7 @@ Options mirror the CLI's environment variables: `apiUrl`, `apiKey`, `transport`,
1246
1242
 
1247
1243
  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
1244
 
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.
1245
+ 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
1246
 
1251
1247
  ## Logging System
1252
1248
 
package/dist/index.js CHANGED
@@ -2583,19 +2583,79 @@ import { z as z6 } from "zod";
2583
2583
  // src/origin.ts
2584
2584
  import { createRequire } from "module";
2585
2585
  var ORIGIN_SERVER = "mcp-fastmcp";
2586
+ var DECLARED_ORIGIN_HEADER = "x-firecrawl-origin";
2586
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
+ ]);
2587
2626
  function slugOf(name2) {
2588
2627
  return (name2 ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, NAME_MAX).replace(/-+$/, "");
2589
2628
  }
2629
+ function isOwnSlug(slug) {
2630
+ return slug === "" || slug === "fastmcp" || slug === "firecrawl-fastmcp";
2631
+ }
2590
2632
  function userAgentProduct(userAgent) {
2591
2633
  const first = (userAgent ?? "").trim().split(/\s+/)[0] ?? "";
2592
2634
  return slugOf(first.split("/")[0]);
2593
2635
  }
2594
- function originForClient(client, serverVersion2, userAgent) {
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);
2595
2654
  const slug = slugOf(client?.name);
2596
- const own = slug === "" || slug === "fastmcp" || slug === "firecrawl-fastmcp";
2597
- const product = own ? userAgentProduct(userAgent) : "";
2598
- const base = !own ? `mcp-${slug}` : product ? `mcp-ua-${product}` : ORIGIN_SERVER;
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;
2599
2659
  return serverVersion2 ? `${base}@${serverVersion2}` : base;
2600
2660
  }
2601
2661
  function originHeaders(origin) {
@@ -2608,7 +2668,12 @@ function requestOrigin(client, session) {
2608
2668
  return originForClient(
2609
2669
  client?.version ?? null,
2610
2670
  serverVersion,
2611
- session?.clientUserAgent
2671
+ session?.clientUserAgent,
2672
+ {
2673
+ declaredOrigin: session?.clientOrigin,
2674
+ initializeClient: session?.initializeClient,
2675
+ referrer: session?.clientReferrer
2676
+ }
2612
2677
  );
2613
2678
  }
2614
2679
 
@@ -3961,10 +4026,18 @@ function createFirecrawlMcpServer(options2 = {}) {
3961
4026
  const cached = request?.[authResultByRequest];
3962
4027
  if (cached) return cached;
3963
4028
  const authResult = authenticateSession(request).then((session) => {
3964
- const userAgent = request?.headers?.["user-agent"];
4029
+ const headers = request?.headers;
4030
+ const userAgent = headers?.["user-agent"];
3965
4031
  if (typeof userAgent === "string" && userAgent !== "") {
3966
4032
  session.clientUserAgent = userAgent;
3967
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;
3968
4041
  return session;
3969
4042
  });
3970
4043
  if (request) request[authResultByRequest] = authResult;
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 originForClient(client, serverVersion2, userAgent) {
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 own = slug === "" || slug === "fastmcp" || slug === "firecrawl-fastmcp";
15
- const product = own ? userAgentProduct(userAgent) : "";
16
- const base = !own ? `mcp-${slug}` : product ? `mcp-ua-${product}` : ORIGIN_SERVER;
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
- * User-Agent the session was authenticated with (stateless HTTP calls carry
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;
@@ -209,10 +224,23 @@ interface SessionData {
209
224
  */
210
225
  firecrawlApiKey?: string;
211
226
  /**
212
- * The User-Agent of the HTTP request that opened the session, the only
213
- * client signal a stateless HTTP tool call carries (see src/origin.ts).
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).
214
229
  */
215
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
+ };
216
244
  authType?: 'api-key' | 'oauth' | 'env' | 'keyless' | 'none';
217
245
  /** Admitted, but the supplied credential cannot be used. */
218
246
  credentialError?: 'CREDENTIAL_INVALID';
@@ -428,4 +456,4 @@ declare const structuredCompactResult: (data: unknown) => ToolResult;
428
456
 
429
457
  declare function createFirecrawlMcpServer(options?: FirecrawlMcpServerOptions): FirecrawlMcpServer;
430
458
 
431
- 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, enterpriseAllowsAlexandria, hasAlexandria, invalidApiKeyRecoveryPayload, invalidOAuthRecoveryPayload, keylessQuotaReason, keylessSignupUrlFrom, normalizeHeader, normalizeSearchSources, originHeaders, postSearchWithFallback, recoveryPayload, relayExchangeError, relayTermsRequired, removeEmptyTopLevel, requestOrigin, searchDomainsAreExclusive, searchOutputSchema, searchQueryIsValid, searchToolBaseFields, structuredCompactResult as structuredCompact, withAlexandriaSource, 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
@@ -2578,19 +2578,83 @@ import { z as z6 } from "zod";
2578
2578
  // src/origin.ts
2579
2579
  import { createRequire } from "module";
2580
2580
  var ORIGIN_SERVER = "mcp-fastmcp";
2581
+ var DECLARED_ORIGIN_HEADER = "x-firecrawl-origin";
2581
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
+ ]);
2582
2621
  function slugOf(name2) {
2583
2622
  return (name2 ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, NAME_MAX).replace(/-+$/, "");
2584
2623
  }
2624
+ function isOwnSlug(slug) {
2625
+ return slug === "" || slug === "fastmcp" || slug === "firecrawl-fastmcp";
2626
+ }
2585
2627
  function userAgentProduct(userAgent) {
2586
2628
  const first = (userAgent ?? "").trim().split(/\s+/)[0] ?? "";
2587
2629
  return slugOf(first.split("/")[0]);
2588
2630
  }
2589
- function originForClient(client, serverVersion2, userAgent) {
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);
2590
2653
  const slug = slugOf(client?.name);
2591
- const own = slug === "" || slug === "fastmcp" || slug === "firecrawl-fastmcp";
2592
- const product = own ? userAgentProduct(userAgent) : "";
2593
- const base = !own ? `mcp-${slug}` : product ? `mcp-ua-${product}` : ORIGIN_SERVER;
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;
2594
2658
  return serverVersion2 ? `${base}@${serverVersion2}` : base;
2595
2659
  }
2596
2660
  function originHeaders(origin) {
@@ -2603,7 +2667,12 @@ function requestOrigin(client, session) {
2603
2667
  return originForClient(
2604
2668
  client?.version ?? null,
2605
2669
  serverVersion,
2606
- session?.clientUserAgent
2670
+ session?.clientUserAgent,
2671
+ {
2672
+ declaredOrigin: session?.clientOrigin,
2673
+ initializeClient: session?.initializeClient,
2674
+ referrer: session?.clientReferrer
2675
+ }
2607
2676
  );
2608
2677
  }
2609
2678
 
@@ -3956,10 +4025,18 @@ function createFirecrawlMcpServer(options = {}) {
3956
4025
  const cached = request?.[authResultByRequest];
3957
4026
  if (cached) return cached;
3958
4027
  const authResult = authenticateSession(request).then((session) => {
3959
- const userAgent = request?.headers?.["user-agent"];
4028
+ const headers = request?.headers;
4029
+ const userAgent = headers?.["user-agent"];
3960
4030
  if (typeof userAgent === "string" && userAgent !== "") {
3961
4031
  session.clientUserAgent = userAgent;
3962
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;
3963
4040
  return session;
3964
4041
  });
3965
4042
  if (request) request[authResultByRequest] = authResult;
@@ -5734,6 +5811,7 @@ Set \`redactPII\` to request redaction of personally identifiable information in
5734
5811
  export {
5735
5812
  ALEXANDRIA_CATALOGUE_VERTICALS,
5736
5813
  ALEXANDRIA_SEARCH_INSTRUCTIONS,
5814
+ DECLARED_ORIGIN_HEADER,
5737
5815
  keylessToolNames as KEYLESS_TOOL_NAMES,
5738
5816
  SEARCH_DOMAINS_CONFLICT_MESSAGE,
5739
5817
  UserError2 as UserError,
@@ -5742,8 +5820,10 @@ export {
5742
5820
  defaultDomainTools,
5743
5821
  enterpriseAllowsAlexandria,
5744
5822
  hasAlexandria,
5823
+ initializeClientSlug,
5745
5824
  invalidApiKeyRecoveryPayload,
5746
5825
  invalidOAuthRecoveryPayload,
5826
+ isRuntimeUserAgent,
5747
5827
  keylessQuotaReason,
5748
5828
  keylessSignupUrlFrom,
5749
5829
  normalizeHeader,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firecrawl-mcp",
3
- "version": "3.30.3",
3
+ "version": "3.31.1",
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",
@@ -38,7 +38,7 @@
38
38
  "@modelcontextprotocol/sdk": "^1.29.0",
39
39
  "dotenv": "^17.2.2",
40
40
  "fastmcp": "4.3.2",
41
- "firecrawl": "4.42.0",
41
+ "firecrawl": "4.46.0",
42
42
  "fuse.js": "^7.1.0",
43
43
  "hono": "^4.12.27",
44
44
  "mcp-proxy": "^6.5.1",