firecrawl-mcp 3.23.4 → 3.23.7
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 +36 -68
- package/dist/index.js +185 -59
- package/dist/www-authenticate.js +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,15 +35,29 @@ Connect to the remote hosted server with no setup:
|
|
|
35
35
|
https://mcp.firecrawl.dev/v2/mcp
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
On the keyless free tier, `scrape`, `search`, and `
|
|
38
|
+
On the keyless free tier, `scrape`, `search`, and `parse` work without an API key (rate-limited). Other tools such as `crawl`, `map`, and `agent` still need a key.
|
|
39
39
|
|
|
40
|
-
Prefer an API key
|
|
40
|
+
Prefer OAuth or an API key whenever the human can sign up. It unlocks the full tool set and higher limits.
|
|
41
|
+
|
|
42
|
+
For an interactive account connection, configure your MCP client to use this server URL. This is an MCP endpoint, **not a browser page**; use the client's account-connection flow and do not add a second Firecrawl server entry when reconnecting:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
https://mcp.firecrawl.dev/v2/mcp-oauth
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
For an API-key connection (for example, an unattended integration), keep the server URL as:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
https://mcp.firecrawl.dev/v2/mcp
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Then configure the client's secure header or secret setting with:
|
|
41
55
|
|
|
42
56
|
```
|
|
43
|
-
|
|
57
|
+
Authorization: Bearer <FIRECRAWL_API_KEY>
|
|
44
58
|
```
|
|
45
59
|
|
|
46
|
-
See the [MCP
|
|
60
|
+
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.
|
|
47
61
|
|
|
48
62
|
#### Search-only endpoint
|
|
49
63
|
|
|
@@ -290,7 +304,6 @@ Use this guide to select the right tool for your task:
|
|
|
290
304
|
| map | Discovering URLs on a site | URL[] |
|
|
291
305
|
| crawl | Multi-page extraction (with limits) | final crawl status/data after internal polling |
|
|
292
306
|
| parse | Files and hosted upload refs | markdown, JSON, or document output |
|
|
293
|
-
| extract | Structured extraction from URLs | JSON structured data |
|
|
294
307
|
| search | Web search for info | results[] |
|
|
295
308
|
| developer | Programming questions over developer sources | results[] with passages |
|
|
296
309
|
| agent | Complex multi-source research | JSON (structured data) |
|
|
@@ -643,79 +656,34 @@ Parse local files or hosted upload references with Firecrawl's `/v2/parse` endpo
|
|
|
643
656
|
|
|
644
657
|
**Returns:** Parsed document content or hosted upload instructions with a `nextToolCall`.
|
|
645
658
|
|
|
646
|
-
### 7.
|
|
647
|
-
|
|
648
|
-
Extract structured information from web pages using LLM capabilities. Supports both cloud AI and self-hosted LLM extraction.
|
|
649
|
-
|
|
650
|
-
**Best for:**
|
|
651
|
-
|
|
652
|
-
- Extracting specific structured data like prices, names, details.
|
|
653
|
-
|
|
654
|
-
**Not recommended for:**
|
|
655
|
-
|
|
656
|
-
- When you need the full content of a page (use scrape)
|
|
657
|
-
- When you're not looking for specific structured data
|
|
658
|
-
|
|
659
|
-
**Arguments:**
|
|
660
|
-
|
|
661
|
-
- `urls`: Array of URLs to extract information from
|
|
662
|
-
- `prompt`: Custom prompt for the LLM extraction
|
|
663
|
-
- `systemPrompt`: System prompt to guide the LLM
|
|
664
|
-
- `schema`: JSON schema for structured data extraction
|
|
665
|
-
- `allowExternalLinks`: Allow extraction from external links
|
|
666
|
-
- `enableWebSearch`: Enable web search for additional context
|
|
667
|
-
- `includeSubdomains`: Include subdomains in extraction
|
|
668
|
-
|
|
669
|
-
When using a self-hosted instance, the extraction will use your configured LLM. For cloud API, it uses Firecrawl's managed LLM service.
|
|
670
|
-
**Prompt Example:**
|
|
671
|
-
|
|
672
|
-
> "Extract the product name, price, and description from these product pages."
|
|
659
|
+
### 7. Structured data with Scrape JSON
|
|
673
660
|
|
|
674
|
-
|
|
661
|
+
For structured data from a known page, call `firecrawl_scrape` once per URL with `formats: ["json"]`. Put the extraction prompt and JSON schema in `jsonOptions`.
|
|
675
662
|
|
|
676
663
|
```json
|
|
677
664
|
{
|
|
678
|
-
"name": "
|
|
665
|
+
"name": "firecrawl_scrape",
|
|
679
666
|
"arguments": {
|
|
680
|
-
"
|
|
681
|
-
"
|
|
682
|
-
"
|
|
683
|
-
|
|
684
|
-
"
|
|
685
|
-
|
|
686
|
-
"
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
"allowExternalLinks": false,
|
|
693
|
-
"enableWebSearch": false,
|
|
694
|
-
"includeSubdomains": false
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
```
|
|
698
|
-
|
|
699
|
-
**Returns:**
|
|
700
|
-
|
|
701
|
-
- Extracted structured data as defined by your schema
|
|
702
|
-
|
|
703
|
-
```json
|
|
704
|
-
{
|
|
705
|
-
"content": [
|
|
706
|
-
{
|
|
707
|
-
"type": "text",
|
|
708
|
-
"text": {
|
|
709
|
-
"name": "Example Product",
|
|
710
|
-
"price": 99.99,
|
|
711
|
-
"description": "This is an example product description"
|
|
667
|
+
"url": "https://example.com/product",
|
|
668
|
+
"formats": ["json"],
|
|
669
|
+
"jsonOptions": {
|
|
670
|
+
"prompt": "Extract the product name, price, and description.",
|
|
671
|
+
"schema": {
|
|
672
|
+
"type": "object",
|
|
673
|
+
"properties": {
|
|
674
|
+
"name": { "type": "string" },
|
|
675
|
+
"price": { "type": "number" },
|
|
676
|
+
"description": { "type": "string" }
|
|
677
|
+
},
|
|
678
|
+
"required": ["name", "price"]
|
|
712
679
|
}
|
|
713
680
|
}
|
|
714
|
-
|
|
715
|
-
"isError": false
|
|
681
|
+
}
|
|
716
682
|
}
|
|
717
683
|
```
|
|
718
684
|
|
|
685
|
+
For unknown URLs or multi-source research, use `firecrawl_search` or `firecrawl_agent` before Scrape.
|
|
686
|
+
|
|
719
687
|
### 8. Agent Tool (`firecrawl_agent`)
|
|
720
688
|
|
|
721
689
|
Autonomous web research agent. This is a separate AI agent layer that independently browses the internet, searches for information, navigates through pages, and extracts structured data based on your query.
|
package/dist/index.js
CHANGED
|
@@ -802,6 +802,11 @@ Search indexed public GitHub issue, pull-request, and README content. Returns ra
|
|
|
802
802
|
});
|
|
803
803
|
}
|
|
804
804
|
|
|
805
|
+
// src/www-authenticate.ts
|
|
806
|
+
function escapeWWWAuthenticateValue(value) {
|
|
807
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
808
|
+
}
|
|
809
|
+
|
|
805
810
|
// src/index.ts
|
|
806
811
|
dotenv.config({ debug: false, quiet: true });
|
|
807
812
|
var require2 = createRequire(import.meta.url);
|
|
@@ -849,6 +854,9 @@ var DEFAULT_MCP_RESOURCE_URL = "https://mcp.firecrawl.dev/v2/mcp";
|
|
|
849
854
|
var DEFAULT_MCP_OAUTH_RESOURCE_URL = "https://mcp.firecrawl.dev/v2/mcp-oauth";
|
|
850
855
|
var DEFAULT_MCP_SEARCH_RESOURCE_URL = "https://mcp.firecrawl.dev/v2/mcp-search";
|
|
851
856
|
var DEFAULT_MCP_SEARCH_ENDPOINT = "/v2/mcp-search";
|
|
857
|
+
var MCP_CONNECTION_GUIDE_URL = "https://docs.firecrawl.dev/mcp-server";
|
|
858
|
+
var MCP_OAUTH_SERVER_URL = "https://mcp.firecrawl.dev/v2/mcp-oauth";
|
|
859
|
+
var API_KEY_SIGNUP_URL = "https://www.firecrawl.dev/app/api-keys";
|
|
852
860
|
function withoutTrailingSlash(value) {
|
|
853
861
|
return value.replace(/\/+$/, "");
|
|
854
862
|
}
|
|
@@ -884,23 +892,23 @@ function getOAuthProtectedResourceMetadataUrl(profile) {
|
|
|
884
892
|
const base = `${resource.origin}/.well-known/oauth-protected-resource`;
|
|
885
893
|
return profile.id === "full" ? base : `${base}${resource.pathname}`;
|
|
886
894
|
}
|
|
887
|
-
function
|
|
888
|
-
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
889
|
-
}
|
|
890
|
-
function createOAuthChallengeResponse(error, profile) {
|
|
895
|
+
function createOAuthChallengeResponse(error, profile, details = {}) {
|
|
891
896
|
if (!isMcpOAuthEnabled()) {
|
|
892
897
|
return void 0;
|
|
893
898
|
}
|
|
894
899
|
const errorMessage = error instanceof Error ? error.message : String(error || "Unauthorized");
|
|
895
900
|
const wwwAuthenticate = [
|
|
896
|
-
|
|
901
|
+
...profile.advertiseOAuth ? [
|
|
902
|
+
`resource_metadata="${escapeWWWAuthenticateValue(getOAuthProtectedResourceMetadataUrl(profile))}"`
|
|
903
|
+
] : [],
|
|
897
904
|
'error="invalid_token"',
|
|
898
905
|
`error_description="${escapeWWWAuthenticateValue(errorMessage)}"`
|
|
899
906
|
].join(", ");
|
|
900
907
|
return new Response(
|
|
901
908
|
JSON.stringify({
|
|
902
909
|
error: "invalid_token",
|
|
903
|
-
error_description: errorMessage
|
|
910
|
+
error_description: errorMessage,
|
|
911
|
+
...details
|
|
904
912
|
}),
|
|
905
913
|
{
|
|
906
914
|
headers: {
|
|
@@ -911,11 +919,26 @@ function createOAuthChallengeResponse(error, profile) {
|
|
|
911
919
|
}
|
|
912
920
|
);
|
|
913
921
|
}
|
|
914
|
-
function createInvalidCredentialResponse(
|
|
922
|
+
function createInvalidCredentialResponse(_error) {
|
|
923
|
+
const recovery = invalidApiKeyRecoveryPayload();
|
|
915
924
|
return new Response(
|
|
916
925
|
JSON.stringify({
|
|
917
926
|
error: "invalid_api_key",
|
|
918
|
-
error_description:
|
|
927
|
+
error_description: recovery.message,
|
|
928
|
+
...recovery
|
|
929
|
+
}),
|
|
930
|
+
{
|
|
931
|
+
headers: { "Content-Type": "application/json" },
|
|
932
|
+
status: 401
|
|
933
|
+
}
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
function createInvalidOAuthRecoveryResponse(recovery) {
|
|
937
|
+
return new Response(
|
|
938
|
+
JSON.stringify({
|
|
939
|
+
error: "invalid_token",
|
|
940
|
+
error_description: recovery.message,
|
|
941
|
+
...recovery
|
|
919
942
|
}),
|
|
920
943
|
{
|
|
921
944
|
headers: { "Content-Type": "application/json" },
|
|
@@ -941,6 +964,12 @@ var InvalidFirecrawlCredentialError = class extends Error {
|
|
|
941
964
|
this.name = "InvalidFirecrawlCredentialError";
|
|
942
965
|
}
|
|
943
966
|
};
|
|
967
|
+
var InvalidOAuthCredentialError = class extends Error {
|
|
968
|
+
constructor() {
|
|
969
|
+
super("Invalid OAuth access token");
|
|
970
|
+
this.name = "InvalidOAuthCredentialError";
|
|
971
|
+
}
|
|
972
|
+
};
|
|
944
973
|
var MCP_GLOBAL_SCOPE = "firecrawl:global";
|
|
945
974
|
function values(value) {
|
|
946
975
|
if (typeof value === "string") return value.split(/\s+/).filter(Boolean);
|
|
@@ -1019,7 +1048,7 @@ async function resolveCredentialFromHeaders(headers, profile) {
|
|
|
1019
1048
|
}
|
|
1020
1049
|
if (!data.active || !data.api_key) {
|
|
1021
1050
|
if (isFirecrawlOAuthAccessToken(token)) {
|
|
1022
|
-
throw new
|
|
1051
|
+
throw new InvalidOAuthCredentialError();
|
|
1023
1052
|
}
|
|
1024
1053
|
return { invalid: true };
|
|
1025
1054
|
}
|
|
@@ -1171,6 +1200,15 @@ function makeAuthenticate(profile) {
|
|
|
1171
1200
|
if (error instanceof InvalidFirecrawlCredentialError) {
|
|
1172
1201
|
throw createInvalidCredentialResponse(error);
|
|
1173
1202
|
}
|
|
1203
|
+
if (error instanceof InvalidOAuthCredentialError) {
|
|
1204
|
+
const recovery = invalidOAuthRecoveryPayload(profile);
|
|
1205
|
+
const oauthChallenge2 = createOAuthChallengeResponse(
|
|
1206
|
+
new Error(recovery.message),
|
|
1207
|
+
profile,
|
|
1208
|
+
recovery
|
|
1209
|
+
);
|
|
1210
|
+
throw oauthChallenge2 ?? createInvalidOAuthRecoveryResponse(recovery);
|
|
1211
|
+
}
|
|
1174
1212
|
if (error instanceof CredentialValidationUnavailableError) {
|
|
1175
1213
|
throw new Response(
|
|
1176
1214
|
JSON.stringify({
|
|
@@ -1273,8 +1311,8 @@ var ConsoleLogger = class {
|
|
|
1273
1311
|
var openAiAppsChallengeToken = normalizeHeader(
|
|
1274
1312
|
process.env.OPENAI_APPS_CHALLENGE_TOKEN
|
|
1275
1313
|
);
|
|
1276
|
-
var FULL_PROFILE_INSTRUCTIONS = `Firecrawl provides web search, page retrieval, site URL discovery, multi-page collection, structured
|
|
1277
|
-
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
|
|
1314
|
+
var FULL_PROFILE_INSTRUCTIONS = `Firecrawl provides web search, page retrieval, site URL discovery, multi-page collection, structured page data, monitoring, and asynchronous research. 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 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.`;
|
|
1315
|
+
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 page data, monitoring, and asynchronous research. 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 starts multi-source research whose result is read with firecrawl_agent_status. Provide only the required inputs.`;
|
|
1278
1316
|
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, use firecrawl_search with categories: ["developer"] 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.`;
|
|
1279
1317
|
var SEARCH_PROFILE_TOOLS = /* @__PURE__ */ new Set([
|
|
1280
1318
|
"firecrawl_search",
|
|
@@ -1296,6 +1334,7 @@ function makeFullProfile() {
|
|
|
1296
1334
|
allowKeyless: !account,
|
|
1297
1335
|
acceptApiKeys: true,
|
|
1298
1336
|
acceptLegacyAudience: account && process.env.MCP_OAUTH_ACCEPT_LEGACY_V2_MCP_AUD !== "false",
|
|
1337
|
+
advertiseOAuth: account,
|
|
1299
1338
|
primary: true
|
|
1300
1339
|
};
|
|
1301
1340
|
}
|
|
@@ -1323,6 +1362,7 @@ function makeSearchProfile({ primary = false } = {}) {
|
|
|
1323
1362
|
// deployment explicitly enables the same profile flag used by primary.
|
|
1324
1363
|
acceptApiKeys: !oauthOnly,
|
|
1325
1364
|
requireManagedOAuth: oauthOnly,
|
|
1365
|
+
advertiseOAuth: true,
|
|
1326
1366
|
primary
|
|
1327
1367
|
};
|
|
1328
1368
|
}
|
|
@@ -1337,7 +1377,7 @@ function createServer(profile) {
|
|
|
1337
1377
|
logger: new ConsoleLogger(),
|
|
1338
1378
|
roots: { enabled: false },
|
|
1339
1379
|
oauth: {
|
|
1340
|
-
enabled: isMcpOAuthEnabled(),
|
|
1380
|
+
enabled: isMcpOAuthEnabled() && profile.advertiseOAuth,
|
|
1341
1381
|
protectedResource: {
|
|
1342
1382
|
authorizationServers: [getOAuthIssuer()],
|
|
1343
1383
|
bearerMethodsSupported: ["header"],
|
|
@@ -1369,30 +1409,107 @@ function isHostedKeylessSession(session) {
|
|
|
1369
1409
|
function isLocalKeylessStartup() {
|
|
1370
1410
|
return process.env.CLOUD_SERVICE !== "true" && !isHttpStreamingTransport() && !resolveCredentialFromEnv() && !normalizeHeader(process.env.FIRECRAWL_API_URL);
|
|
1371
1411
|
}
|
|
1412
|
+
var NEVER_SHARE_API_KEY_SENTENCE = "Never ask for, accept, or put an API key in chat or in an MCP URL.";
|
|
1413
|
+
var RETRY_AFTER_CONFIRM_SUFFIX = "start a new client session or run and retry the original task.";
|
|
1414
|
+
var HUMAN_CONNECTION_GUIDANCE = `A human or operator must complete the connection because this session cannot reconnect Firecrawl itself. Ask the human to choose before changing anything: (1) in the MCP client's settings, update or replace the existing Firecrawl server entry so its URL is ${MCP_OAUTH_SERVER_URL}, then complete sign-in through the client. That URL is a client configuration value, not a page to open in a browser. Do not add a second Firecrawl server or change configuration without approval; or (2) have an operator create an API key at ${API_KEY_SIGNUP_URL} and configure it in the client or secret manager outside this chat. ${NEVER_SHARE_API_KEY_SENTENCE} After the human confirms setup, ${RETRY_AFTER_CONFIRM_SUFFIX} Connection guide: ${MCP_CONNECTION_GUIDE_URL}`;
|
|
1415
|
+
var ACCOUNT_ONLY_TOOL_GUIDANCE = `This tool needs a connected Firecrawl account. Search, Scrape, and Parse remain available, so continue with those if they can complete the task. Only if this task specifically requires this tool, tell the user that a human must update the existing Firecrawl connection using ${MCP_CONNECTION_GUIDE_URL}. Never ask for or accept an API key in chat. After setup, start a new client session and retry.`;
|
|
1416
|
+
var HUMAN_RECONNECT_ACCOUNT_ACTION = {
|
|
1417
|
+
kind: "human_reconnect_account",
|
|
1418
|
+
actor: "human",
|
|
1419
|
+
requires_user_consent: true,
|
|
1420
|
+
existing_server_only: true,
|
|
1421
|
+
server_url: MCP_OAUTH_SERVER_URL,
|
|
1422
|
+
open_server_url_in_browser: false,
|
|
1423
|
+
docs_url: MCP_CONNECTION_GUIDE_URL
|
|
1424
|
+
};
|
|
1425
|
+
var OPERATOR_CONFIGURE_API_KEY_ACTION = {
|
|
1426
|
+
kind: "operator_configure_api_key",
|
|
1427
|
+
actor: "human_or_operator",
|
|
1428
|
+
requires_user_consent: true,
|
|
1429
|
+
credential_delivery: "outside_agent_chat",
|
|
1430
|
+
signup_url: API_KEY_SIGNUP_URL
|
|
1431
|
+
};
|
|
1432
|
+
var HUMAN_CONNECTION_ACTIONS = [
|
|
1433
|
+
HUMAN_RECONNECT_ACCOUNT_ACTION,
|
|
1434
|
+
OPERATOR_CONFIGURE_API_KEY_ACTION
|
|
1435
|
+
];
|
|
1436
|
+
function connectionRecoveryPayload(params) {
|
|
1437
|
+
return {
|
|
1438
|
+
code: params.code,
|
|
1439
|
+
auth_mode: params.authMode,
|
|
1440
|
+
message: params.message,
|
|
1441
|
+
docs_url: MCP_CONNECTION_GUIDE_URL,
|
|
1442
|
+
next_actions: params.nextActions
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
function invalidApiKeyRecoveryPayload() {
|
|
1446
|
+
return connectionRecoveryPayload({
|
|
1447
|
+
code: "CREDENTIAL_INVALID",
|
|
1448
|
+
authMode: "api_key",
|
|
1449
|
+
message: `The Firecrawl API key configured for this server is invalid or revoked. Ask a human or operator to replace it in this existing server configuration or secret manager outside this chat. ${NEVER_SHARE_API_KEY_SENTENCE} After the human confirms the change, ${RETRY_AFTER_CONFIRM_SUFFIX}`,
|
|
1450
|
+
nextActions: [OPERATOR_CONFIGURE_API_KEY_ACTION]
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
function invalidOAuthRecoveryPayload(profile) {
|
|
1454
|
+
if (isMcpOAuthEnabled() && profile.advertiseOAuth) {
|
|
1455
|
+
return connectionRecoveryPayload({
|
|
1456
|
+
code: "OAUTH_CONNECTION_INVALID",
|
|
1457
|
+
authMode: "oauth",
|
|
1458
|
+
message: `This Firecrawl account connection is no longer valid. Ask the human to sign in again through this MCP client's account-connection flow. Do not add a second Firecrawl server or open the MCP server URL directly in a browser. After sign-in, ${RETRY_AFTER_CONFIRM_SUFFIX}`,
|
|
1459
|
+
nextActions: [HUMAN_RECONNECT_ACCOUNT_ACTION, OPERATOR_CONFIGURE_API_KEY_ACTION]
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
return connectionRecoveryPayload({
|
|
1463
|
+
code: "OAUTH_CONNECTION_INVALID",
|
|
1464
|
+
authMode: "oauth",
|
|
1465
|
+
message: `This Firecrawl account connection is no longer valid, and this server does not start account sign-in. Ask the human to choose before changing anything: (1) have an operator create a Firecrawl API key at ${API_KEY_SIGNUP_URL} and configure it on this existing server outside this chat; or (2) update this existing server's URL to ${MCP_OAUTH_SERVER_URL} and complete sign-in through the MCP client. That URL is a client configuration value, not a page to open in a browser. ${NEVER_SHARE_API_KEY_SENTENCE} After the human confirms the change, ${RETRY_AFTER_CONFIRM_SUFFIX}`,
|
|
1466
|
+
nextActions: [OPERATOR_CONFIGURE_API_KEY_ACTION, HUMAN_RECONNECT_ACCOUNT_ACTION]
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1372
1469
|
function recoveryPayload(code, requestId = randomUUID(), options = {}) {
|
|
1373
1470
|
const retryAfterSeconds = options.retryAfterSeconds;
|
|
1374
1471
|
const isQuotaExhausted = code === "KEYLESS_QUOTA_EXHAUSTED" || code === "KEYLESS_LIMIT_REACHED";
|
|
1375
1472
|
const isToolUnavailable = code === "KEYLESS_TOOL_NOT_AVAILABLE";
|
|
1376
1473
|
const isKeylessAccessUnavailable = code === "KEYLESS_ACCESS_NOT_AVAILABLE";
|
|
1377
1474
|
const isKeylessEligibilityUnavailable = code === "KEYLESS_ELIGIBILITY_UNAVAILABLE";
|
|
1378
|
-
const oauthUrl = "https://mcp.firecrawl.dev/v2/mcp-oauth";
|
|
1379
1475
|
return {
|
|
1380
1476
|
code,
|
|
1381
1477
|
request_id: requestId,
|
|
1382
1478
|
auth_mode: code === "CREDENTIAL_INVALID" ? "credential_error" : "keyless",
|
|
1383
|
-
message: code === "CREDENTIAL_INVALID" ?
|
|
1479
|
+
message: code === "CREDENTIAL_INVALID" ? `The supplied Firecrawl credential is invalid or revoked. ${HUMAN_CONNECTION_GUIDANCE}` : isQuotaExhausted ? `The free daily limit for this network has been reached${retryAfterSeconds ? `; try again in about ${retryAfterSeconds} seconds` : ""}. To continue now: ${HUMAN_CONNECTION_GUIDANCE}` : isToolUnavailable ? ACCOUNT_ONLY_TOOL_GUIDANCE : isKeylessAccessUnavailable ? `Anonymous keyless access is unavailable for this request. To continue: ${HUMAN_CONNECTION_GUIDANCE}` : isKeylessEligibilityUnavailable ? "The anonymous keyless eligibility check is temporarily unavailable. Retry shortly." : `This tool requires a Firecrawl account or API key. ${HUMAN_CONNECTION_GUIDANCE}`,
|
|
1384
1480
|
...isKeylessAccessUnavailable ? {} : { available_tools: [...KEYLESS_TOOL_NAMES] },
|
|
1385
|
-
docs_url:
|
|
1481
|
+
docs_url: MCP_CONNECTION_GUIDE_URL,
|
|
1386
1482
|
...retryAfterSeconds ? { retry_after_seconds: retryAfterSeconds } : {},
|
|
1387
|
-
next_actions: isKeylessEligibilityUnavailable ? [{ kind: "retry_later", after_seconds: 30 }] : isQuotaExhausted || isKeylessAccessUnavailable ? [
|
|
1388
|
-
{ kind: "
|
|
1389
|
-
|
|
1390
|
-
] :
|
|
1391
|
-
|
|
1392
|
-
{ kind: "configure_api_key", header: "Authorization: Bearer <FIRECRAWL_API_KEY>" }
|
|
1483
|
+
next_actions: isKeylessEligibilityUnavailable ? [{ kind: "retry_later", after_seconds: 30 }] : isQuotaExhausted || isKeylessAccessUnavailable ? HUMAN_CONNECTION_ACTIONS : isToolUnavailable ? [
|
|
1484
|
+
{ kind: "continue_keyless", tools: [...KEYLESS_TOOL_NAMES] },
|
|
1485
|
+
...HUMAN_CONNECTION_ACTIONS
|
|
1486
|
+
] : [
|
|
1487
|
+
...HUMAN_CONNECTION_ACTIONS
|
|
1393
1488
|
]
|
|
1394
1489
|
};
|
|
1395
1490
|
}
|
|
1491
|
+
function deprecatedExtractPayload() {
|
|
1492
|
+
return {
|
|
1493
|
+
code: "DEPRECATED_TOOL",
|
|
1494
|
+
message: 'firecrawl_extract is deprecated and unavailable through MCP. For structured data from a known page, call firecrawl_scrape once per URL with formats: ["json"] and jsonOptions containing the prompt and schema. For unknown URLs or multi-source research, use firecrawl_search or firecrawl_agent first.',
|
|
1495
|
+
replacement: {
|
|
1496
|
+
name: "firecrawl_scrape",
|
|
1497
|
+
instructions: 'Call once per known URL. Set formats to ["json"] and pass the extraction prompt and JSON schema in jsonOptions.',
|
|
1498
|
+
example_arguments: {
|
|
1499
|
+
url: "https://example.com/page",
|
|
1500
|
+
formats: ["json"],
|
|
1501
|
+
jsonOptions: {
|
|
1502
|
+
prompt: "Extract the requested fields from this page.",
|
|
1503
|
+
schema: {
|
|
1504
|
+
type: "object",
|
|
1505
|
+
properties: {}
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
},
|
|
1510
|
+
docs_url: "https://docs.firecrawl.dev/developer-guides/usage-guides/choosing-the-data-extractor"
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1396
1513
|
function emitActionLog(toolName, status, session, error, requestId = randomUUID(), code) {
|
|
1397
1514
|
if (process.env.CLOUD_SERVICE !== "true") return;
|
|
1398
1515
|
const payload = {
|
|
@@ -1428,22 +1545,39 @@ function emitActionLog(toolName, status, session, error, requestId = randomUUID(
|
|
|
1428
1545
|
function guardHostedTool(tool, { logActions }) {
|
|
1429
1546
|
const keylessTool = KEYLESS_TOOL_NAMES.has(tool.name);
|
|
1430
1547
|
const execute = tool.execute;
|
|
1548
|
+
const canList = tool.canList;
|
|
1549
|
+
const beforeValidate = tool.beforeValidate;
|
|
1431
1550
|
return {
|
|
1432
1551
|
...tool,
|
|
1433
|
-
canList: (session) => !session?.credentialError && (!isHostedKeylessSession(session) || keylessTool),
|
|
1434
|
-
beforeValidate: (
|
|
1552
|
+
canList: (session) => !session?.credentialError && (!isHostedKeylessSession(session) || keylessTool) && (canList?.(session) ?? true),
|
|
1553
|
+
beforeValidate: async (args2, session) => {
|
|
1435
1554
|
const code = session?.credentialError ? "CREDENTIAL_INVALID" : isHostedKeylessSession(session) && !keylessTool ? "KEYLESS_TOOL_NOT_AVAILABLE" : void 0;
|
|
1436
|
-
if (
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1555
|
+
if (code) {
|
|
1556
|
+
const requestId = randomUUID();
|
|
1557
|
+
const payload2 = recoveryPayload(code, requestId);
|
|
1558
|
+
if (logActions) {
|
|
1559
|
+
emitActionLog(tool.name, "error", session, new UserError(String(payload2.message), payload2), requestId, code);
|
|
1560
|
+
}
|
|
1561
|
+
return {
|
|
1562
|
+
content: [{ type: "text", text: String(payload2.message) }],
|
|
1563
|
+
isError: true,
|
|
1564
|
+
structuredContent: payload2
|
|
1565
|
+
};
|
|
1441
1566
|
}
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1567
|
+
const earlyResult = await beforeValidate?.(args2, session);
|
|
1568
|
+
const payload = earlyResult?.structuredContent;
|
|
1569
|
+
const recoveryCode = payload && typeof payload === "object" && "code" in payload && typeof payload.code === "string" ? payload.code : void 0;
|
|
1570
|
+
if (logActions && earlyResult?.isError && recoveryCode) {
|
|
1571
|
+
emitActionLog(
|
|
1572
|
+
tool.name,
|
|
1573
|
+
"error",
|
|
1574
|
+
session,
|
|
1575
|
+
new UserError(`Tool validation failed: ${recoveryCode}`, payload),
|
|
1576
|
+
randomUUID(),
|
|
1577
|
+
recoveryCode
|
|
1578
|
+
);
|
|
1579
|
+
}
|
|
1580
|
+
return earlyResult;
|
|
1447
1581
|
},
|
|
1448
1582
|
execute: async (args2, context) => {
|
|
1449
1583
|
const requestId = randomUUID();
|
|
@@ -1734,7 +1868,7 @@ var parseOptionParamsSchema = z4.object({
|
|
|
1734
1868
|
skipTlsVerification: z4.boolean().optional(),
|
|
1735
1869
|
storeInCache: z4.boolean().optional(),
|
|
1736
1870
|
zeroDataRetention: z4.boolean().optional(),
|
|
1737
|
-
maxAge: z4.number().optional(),
|
|
1871
|
+
maxAge: z4.number().optional().describe("Ignored: parse never reuses or stores indexed content."),
|
|
1738
1872
|
proxy: z4.enum(["basic", "auto"]).optional()
|
|
1739
1873
|
});
|
|
1740
1874
|
var localParseParamsSchema = parseOptionParamsSchema.extend({
|
|
@@ -1972,6 +2106,8 @@ Retrieve and extract content from one supplied URL through Firecrawl. Use this w
|
|
|
1972
2106
|
|
|
1973
2107
|
This tool operates on a known page. For a set of pages use \`firecrawl_crawl\`, and to discover page URLs use \`firecrawl_map\` or \`firecrawl_search\`. Options include JavaScript render delay, cache age, main-content filtering, PII redaction, and lockdown cache-only retrieval. Browser actions may change the live page when interactive actions are enabled.
|
|
1974
2108
|
|
|
2109
|
+
Firecrawl may reuse recently indexed content instead of refetching the page, and the reuse window varies by domain. Set \`maxAge: 0\` to force a live fetch, or a smaller \`maxAge\` to bound how stale reused content may be. A successful response does not by itself confirm that the state it describes is still current.
|
|
2110
|
+
|
|
1975
2111
|
Returns the selected content formats and page metadata.
|
|
1976
2112
|
`,
|
|
1977
2113
|
parameters: scrapeParamsSchema,
|
|
@@ -2058,7 +2194,7 @@ Search web, news, or image sources and return ranked results. Operators include
|
|
|
2058
2194
|
|
|
2059
2195
|
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.
|
|
2060
2196
|
|
|
2061
|
-
\`scrapeOptions\` can attach extracted page content. Returns source-type result groups and usage metadata. Authenticated responses can include an \`id\` for optional search feedback.
|
|
2197
|
+
\`scrapeOptions\` can attach extracted page content; pages fetched this way use a fixed reuse window and ignore \`maxAge\`, so use \`firecrawl_scrape\` when a live fetch is required. Returns source-type result groups and usage metadata. Authenticated responses can include an \`id\` for optional search feedback.
|
|
2062
2198
|
`,
|
|
2063
2199
|
parameters: z4.object({
|
|
2064
2200
|
...searchToolBaseFields,
|
|
@@ -2576,18 +2712,13 @@ Retrieve the current status, progress, and available results for an existing cra
|
|
|
2576
2712
|
server.addTool({
|
|
2577
2713
|
name: "firecrawl_extract",
|
|
2578
2714
|
annotations: {
|
|
2579
|
-
title: "
|
|
2715
|
+
title: "Deprecated: use Scrape JSON",
|
|
2580
2716
|
readOnlyHint: true,
|
|
2581
|
-
// Uses LLM extraction to pull structured data from URLs without modifying those sites.
|
|
2582
2717
|
openWorldHint: true,
|
|
2583
|
-
// Accepts arbitrary user-supplied URLs on the public web.
|
|
2584
2718
|
destructiveHint: false
|
|
2585
|
-
// Read-only extraction; no destructive changes to external content.
|
|
2586
2719
|
},
|
|
2587
2720
|
description: `
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
Use this for a defined structured result rather than full page content. Returns data shaped by the supplied schema or prompt.
|
|
2721
|
+
Deprecated compatibility entry point. Use firecrawl_scrape once per known URL with formats: ["json"] and jsonOptions containing the prompt and schema. Use firecrawl_search or firecrawl_agent before Scrape when URLs are not known.
|
|
2591
2722
|
`,
|
|
2592
2723
|
parameters: z4.object({
|
|
2593
2724
|
urls: z4.array(z4.string()),
|
|
@@ -2597,23 +2728,18 @@ Use this for a defined structured result rather than full page content. Returns
|
|
|
2597
2728
|
enableWebSearch: z4.boolean().optional(),
|
|
2598
2729
|
includeSubdomains: z4.boolean().optional()
|
|
2599
2730
|
}),
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
const
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
includeSubdomains: a.includeSubdomains,
|
|
2613
|
-
origin: ORIGIN
|
|
2614
|
-
});
|
|
2615
|
-
const res = await client.extract(extractBody);
|
|
2616
|
-
return asText2(res);
|
|
2731
|
+
canList: () => false,
|
|
2732
|
+
beforeValidate: () => {
|
|
2733
|
+
const payload = deprecatedExtractPayload();
|
|
2734
|
+
return {
|
|
2735
|
+
content: [{ type: "text", text: payload.message }],
|
|
2736
|
+
isError: true,
|
|
2737
|
+
structuredContent: payload
|
|
2738
|
+
};
|
|
2739
|
+
},
|
|
2740
|
+
execute: async () => {
|
|
2741
|
+
const payload = deprecatedExtractPayload();
|
|
2742
|
+
throw new UserError(payload.message, payload);
|
|
2617
2743
|
}
|
|
2618
2744
|
});
|
|
2619
2745
|
server.addTool({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firecrawl-mcp",
|
|
3
|
-
"version": "3.23.
|
|
3
|
+
"version": "3.23.7",
|
|
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",
|