firecrawl-mcp 3.21.3 → 3.21.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +328 -166
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2487,6 +2487,247 @@ var scrapeParamsSchema = z4.object({
|
|
|
2487
2487
|
saveChanges: z4.boolean().optional()
|
|
2488
2488
|
}).optional()
|
|
2489
2489
|
});
|
|
2490
|
+
var parseOptionParamsSchema = z4.object({
|
|
2491
|
+
formats: z4.array(
|
|
2492
|
+
z4.enum([
|
|
2493
|
+
"markdown",
|
|
2494
|
+
"html",
|
|
2495
|
+
"rawHtml",
|
|
2496
|
+
"links",
|
|
2497
|
+
"summary",
|
|
2498
|
+
"json",
|
|
2499
|
+
"query"
|
|
2500
|
+
])
|
|
2501
|
+
).optional(),
|
|
2502
|
+
jsonOptions: z4.object({
|
|
2503
|
+
prompt: z4.string().optional(),
|
|
2504
|
+
schema: z4.record(z4.string(), z4.any()).optional()
|
|
2505
|
+
}).optional(),
|
|
2506
|
+
queryOptions: z4.object({
|
|
2507
|
+
prompt: z4.string().max(1e4),
|
|
2508
|
+
mode: z4.enum(["directQuote", "freeform"]).default("freeform")
|
|
2509
|
+
}).optional(),
|
|
2510
|
+
parsers: z4.array(z4.enum(["pdf"])).optional(),
|
|
2511
|
+
pdfOptions: z4.object({
|
|
2512
|
+
maxPages: z4.number().int().min(1).max(1e4).optional()
|
|
2513
|
+
}).optional(),
|
|
2514
|
+
onlyMainContent: z4.boolean().optional(),
|
|
2515
|
+
redactPII: z4.boolean().optional(),
|
|
2516
|
+
includeTags: z4.array(z4.string()).optional(),
|
|
2517
|
+
excludeTags: z4.array(z4.string()).optional(),
|
|
2518
|
+
removeBase64Images: z4.boolean().optional(),
|
|
2519
|
+
skipTlsVerification: z4.boolean().optional(),
|
|
2520
|
+
storeInCache: z4.boolean().optional(),
|
|
2521
|
+
zeroDataRetention: z4.boolean().optional(),
|
|
2522
|
+
maxAge: z4.number().optional(),
|
|
2523
|
+
proxy: z4.enum(["basic", "auto"]).optional()
|
|
2524
|
+
});
|
|
2525
|
+
var localParseParamsSchema = parseOptionParamsSchema.extend({
|
|
2526
|
+
filePath: z4.string().min(1).describe(
|
|
2527
|
+
"Absolute or relative path to a local file to parse. Supported: .html, .htm, .pdf, .docx, .doc, .odt, .rtf, .xlsx, .xls"
|
|
2528
|
+
),
|
|
2529
|
+
contentType: z4.string().optional().describe(
|
|
2530
|
+
"Optional MIME type override. If omitted, the server infers the file kind from the extension."
|
|
2531
|
+
)
|
|
2532
|
+
});
|
|
2533
|
+
var hostedParseParamsSchema = parseOptionParamsSchema.extend({
|
|
2534
|
+
filePath: z4.string().min(1).optional().describe(
|
|
2535
|
+
"Phase 1 only: path to the local file on the caller/harness machine. Hosted MCP will not read or stat this path; it is used only to produce upload instructions."
|
|
2536
|
+
),
|
|
2537
|
+
uploadRef: z4.string().min(1).optional().describe(
|
|
2538
|
+
"Phase 2 only: short-lived upload reference returned by phase 1 after the local PUT upload completes."
|
|
2539
|
+
),
|
|
2540
|
+
contentType: z4.string().optional().describe(
|
|
2541
|
+
"Phase 1 MIME type override. If omitted, the server infers it from the file extension without reading the file."
|
|
2542
|
+
),
|
|
2543
|
+
declaredSizeBytes: z4.number().int().positive().optional().describe(
|
|
2544
|
+
"Optional phase 1 size declaration. Hosted MCP does not stat the file; provide this only if the caller already knows it."
|
|
2545
|
+
)
|
|
2546
|
+
}).superRefine((value, ctx) => {
|
|
2547
|
+
const hasFilePath = typeof value.filePath === "string" && value.filePath.length > 0;
|
|
2548
|
+
const hasUploadRef = typeof value.uploadRef === "string" && value.uploadRef.length > 0;
|
|
2549
|
+
if (hasFilePath === hasUploadRef) {
|
|
2550
|
+
ctx.addIssue({
|
|
2551
|
+
code: z4.ZodIssueCode.custom,
|
|
2552
|
+
message: "Hosted firecrawl_parse requires exactly one of filePath (phase 1) or uploadRef (phase 2).",
|
|
2553
|
+
path: hasFilePath && hasUploadRef ? ["uploadRef"] : ["filePath"]
|
|
2554
|
+
});
|
|
2555
|
+
}
|
|
2556
|
+
});
|
|
2557
|
+
var parseParamsSchema = process.env.CLOUD_SERVICE === "true" ? hostedParseParamsSchema : localParseParamsSchema;
|
|
2558
|
+
var EXTENSION_CONTENT_TYPES = {
|
|
2559
|
+
".html": "text/html",
|
|
2560
|
+
".htm": "text/html",
|
|
2561
|
+
".xhtml": "application/xhtml+xml",
|
|
2562
|
+
".pdf": "application/pdf",
|
|
2563
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
2564
|
+
".doc": "application/msword",
|
|
2565
|
+
".odt": "application/vnd.oasis.opendocument.text",
|
|
2566
|
+
".rtf": "application/rtf",
|
|
2567
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
2568
|
+
".xls": "application/vnd.ms-excel"
|
|
2569
|
+
};
|
|
2570
|
+
function inferContentType(filename) {
|
|
2571
|
+
const ext = path.extname(filename).toLowerCase();
|
|
2572
|
+
return EXTENSION_CONTENT_TYPES[ext] ?? "application/octet-stream";
|
|
2573
|
+
}
|
|
2574
|
+
function extractParseOptions(args2) {
|
|
2575
|
+
const { filePath, uploadRef, contentType, declaredSizeBytes, ...options } = args2;
|
|
2576
|
+
return options;
|
|
2577
|
+
}
|
|
2578
|
+
function buildParseOptionsPayload(options) {
|
|
2579
|
+
const transformed = transformScrapeParams(options);
|
|
2580
|
+
const cleaned = removeEmptyTopLevel(transformed);
|
|
2581
|
+
return { origin: ORIGIN, ...cleaned };
|
|
2582
|
+
}
|
|
2583
|
+
function buildContinuationArguments(uploadRef, options) {
|
|
2584
|
+
return {
|
|
2585
|
+
uploadRef,
|
|
2586
|
+
...removeEmptyTopLevel(options)
|
|
2587
|
+
};
|
|
2588
|
+
}
|
|
2589
|
+
function shellQuote(value) {
|
|
2590
|
+
if (value.length === 0) return "''";
|
|
2591
|
+
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
2592
|
+
}
|
|
2593
|
+
function parseApiData(json) {
|
|
2594
|
+
return json && typeof json === "object" && "data" in json ? json.data : json;
|
|
2595
|
+
}
|
|
2596
|
+
async function apiPostJson(pathName, body, apiKey) {
|
|
2597
|
+
const response = await fetch(`${resolveApiBaseUrl()}${pathName}`, {
|
|
2598
|
+
method: "POST",
|
|
2599
|
+
headers: {
|
|
2600
|
+
"Content-Type": "application/json",
|
|
2601
|
+
Authorization: `Bearer ${apiKey}`
|
|
2602
|
+
},
|
|
2603
|
+
body: JSON.stringify(body)
|
|
2604
|
+
});
|
|
2605
|
+
const responseText = await response.text();
|
|
2606
|
+
let parsed;
|
|
2607
|
+
try {
|
|
2608
|
+
parsed = responseText ? JSON.parse(responseText) : {};
|
|
2609
|
+
} catch {
|
|
2610
|
+
parsed = { raw: responseText };
|
|
2611
|
+
}
|
|
2612
|
+
if (!response.ok) {
|
|
2613
|
+
throw new Error(
|
|
2614
|
+
parsed?.error || parsed?.message || `Firecrawl request failed (HTTP ${response.status})`
|
|
2615
|
+
);
|
|
2616
|
+
}
|
|
2617
|
+
return parsed;
|
|
2618
|
+
}
|
|
2619
|
+
async function apiPostJsonForSession(pathName, body, session) {
|
|
2620
|
+
if (session?.firecrawlApiKey) {
|
|
2621
|
+
return apiPostJson(pathName, body, session.firecrawlApiKey);
|
|
2622
|
+
}
|
|
2623
|
+
if (isKeylessMode(session)) {
|
|
2624
|
+
return keylessPost(pathName, body, session);
|
|
2625
|
+
}
|
|
2626
|
+
throw new Error(
|
|
2627
|
+
"Firecrawl credentials or keyless eligibility required for hosted parse."
|
|
2628
|
+
);
|
|
2629
|
+
}
|
|
2630
|
+
function buildCurlUploadCommand(filePath, upload) {
|
|
2631
|
+
const method = upload.method ?? "PUT";
|
|
2632
|
+
const headerArgs = Object.entries(upload.headers ?? {}).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `-H ${shellQuote(`${key}: ${value}`)}`);
|
|
2633
|
+
if (method.toUpperCase() === "POST" && upload.fields) {
|
|
2634
|
+
const fieldArgs = Object.entries(upload.fields).sort(([a], [b]) => a.localeCompare(b)).flatMap(([key, value]) => ["-F", shellQuote(`${key}=${value}`)]);
|
|
2635
|
+
return [
|
|
2636
|
+
"curl",
|
|
2637
|
+
"-X",
|
|
2638
|
+
shellQuote("POST"),
|
|
2639
|
+
...headerArgs,
|
|
2640
|
+
...fieldArgs,
|
|
2641
|
+
"-F",
|
|
2642
|
+
shellQuote(`file=@${filePath}`),
|
|
2643
|
+
shellQuote(upload.uploadUrl)
|
|
2644
|
+
].join(" ");
|
|
2645
|
+
}
|
|
2646
|
+
return [
|
|
2647
|
+
"curl",
|
|
2648
|
+
"-X",
|
|
2649
|
+
shellQuote(method),
|
|
2650
|
+
...headerArgs,
|
|
2651
|
+
"--upload-file",
|
|
2652
|
+
shellQuote(filePath),
|
|
2653
|
+
shellQuote(upload.uploadUrl)
|
|
2654
|
+
].join(" ");
|
|
2655
|
+
}
|
|
2656
|
+
async function executeHostedParse(args2, session, log) {
|
|
2657
|
+
const hasFilePath = typeof args2.filePath === "string" && args2.filePath.length > 0;
|
|
2658
|
+
const hasUploadRef = typeof args2.uploadRef === "string" && args2.uploadRef.length > 0;
|
|
2659
|
+
if (hasFilePath === hasUploadRef) {
|
|
2660
|
+
throw new Error(
|
|
2661
|
+
"Hosted firecrawl_parse requires exactly one of filePath or uploadRef."
|
|
2662
|
+
);
|
|
2663
|
+
}
|
|
2664
|
+
if (!session?.firecrawlApiKey && !isKeylessMode(session)) {
|
|
2665
|
+
return asText2({
|
|
2666
|
+
success: false,
|
|
2667
|
+
mode: "hosted-upload-ref-auth-required",
|
|
2668
|
+
message: "Hosted firecrawl_parse requires an authenticated Firecrawl session or keyless eligibility before a local file upload URL can be minted. Connect a Firecrawl account, provide an API key, or use keyless hosted MCP while eligible, then call firecrawl_parse again."
|
|
2669
|
+
});
|
|
2670
|
+
}
|
|
2671
|
+
const options = extractParseOptions(args2);
|
|
2672
|
+
if (hasFilePath && args2.filePath) {
|
|
2673
|
+
const filename = path.basename(args2.filePath);
|
|
2674
|
+
const contentType = typeof args2.contentType === "string" && args2.contentType.length > 0 ? args2.contentType : inferContentType(filename);
|
|
2675
|
+
const uploadRequest = removeEmptyTopLevel({
|
|
2676
|
+
filename,
|
|
2677
|
+
contentType,
|
|
2678
|
+
declaredSizeBytes: args2.declaredSizeBytes
|
|
2679
|
+
});
|
|
2680
|
+
log.info("Creating hosted parse upload URL", { filename, contentType });
|
|
2681
|
+
const uploadJson = await apiPostJsonForSession(
|
|
2682
|
+
"/v2/parse/upload-url",
|
|
2683
|
+
uploadRequest,
|
|
2684
|
+
session
|
|
2685
|
+
);
|
|
2686
|
+
const upload = parseApiData(uploadJson);
|
|
2687
|
+
if (!upload?.uploadUrl || !upload?.uploadRef) {
|
|
2688
|
+
throw new Error(
|
|
2689
|
+
"Firecrawl upload-url response did not include uploadUrl and uploadRef"
|
|
2690
|
+
);
|
|
2691
|
+
}
|
|
2692
|
+
const uploadHeaders = upload.headers && Object.keys(upload.headers).length > 0 ? upload.headers : (upload.method ?? "PUT").toUpperCase() === "POST" ? {} : { "Content-Type": contentType };
|
|
2693
|
+
const uploadForCommand = { ...upload, headers: uploadHeaders };
|
|
2694
|
+
return asText2({
|
|
2695
|
+
success: true,
|
|
2696
|
+
mode: "hosted-upload-ref-awaiting-upload",
|
|
2697
|
+
message: "Hosted MCP cannot read local files. Run the local upload command, then call firecrawl_parse again with uploadRef. No Firecrawl API key is included in this command.",
|
|
2698
|
+
upload: {
|
|
2699
|
+
command: buildCurlUploadCommand(args2.filePath, uploadForCommand),
|
|
2700
|
+
method: upload.method ?? "PUT",
|
|
2701
|
+
headers: uploadHeaders,
|
|
2702
|
+
fields: upload.fields,
|
|
2703
|
+
uploadUrl: upload.uploadUrl,
|
|
2704
|
+
uploadRef: upload.uploadRef,
|
|
2705
|
+
expiresAt: upload.expiresAt,
|
|
2706
|
+
maxSizeBytes: upload.maxSizeBytes
|
|
2707
|
+
},
|
|
2708
|
+
nextToolCall: {
|
|
2709
|
+
name: "firecrawl_parse",
|
|
2710
|
+
arguments: buildContinuationArguments(upload.uploadRef, options)
|
|
2711
|
+
},
|
|
2712
|
+
notes: [
|
|
2713
|
+
"Run the curl command on the machine that can read filePath.",
|
|
2714
|
+
"After the PUT succeeds, use nextToolCall as the second MCP tool call.",
|
|
2715
|
+
"Clients without a local upload mechanism cannot complete hosted parse for local files."
|
|
2716
|
+
]
|
|
2717
|
+
});
|
|
2718
|
+
}
|
|
2719
|
+
const parsePayload = {
|
|
2720
|
+
uploadRef: args2.uploadRef,
|
|
2721
|
+
...buildParseOptionsPayload(options)
|
|
2722
|
+
};
|
|
2723
|
+
log.info("Parsing hosted upload reference");
|
|
2724
|
+
const parseJson = await apiPostJsonForSession(
|
|
2725
|
+
"/v2/parse",
|
|
2726
|
+
parsePayload,
|
|
2727
|
+
session
|
|
2728
|
+
);
|
|
2729
|
+
return asText2(parseJson);
|
|
2730
|
+
}
|
|
2490
2731
|
server.addTool({
|
|
2491
2732
|
name: "firecrawl_scrape",
|
|
2492
2733
|
annotations: {
|
|
@@ -3702,82 +3943,29 @@ Stop an interact session for a scraped page. Call this when you are done interac
|
|
|
3702
3943
|
return asText2(res?.data ?? {});
|
|
3703
3944
|
}
|
|
3704
3945
|
});
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
"summary",
|
|
3725
|
-
"json",
|
|
3726
|
-
"query"
|
|
3727
|
-
])
|
|
3728
|
-
).optional(),
|
|
3729
|
-
jsonOptions: z4.object({
|
|
3730
|
-
prompt: z4.string().optional(),
|
|
3731
|
-
schema: z4.record(z4.string(), z4.any()).optional()
|
|
3732
|
-
}).optional(),
|
|
3733
|
-
queryOptions: z4.object({
|
|
3734
|
-
prompt: z4.string().max(1e4),
|
|
3735
|
-
mode: z4.enum(["directQuote", "freeform"]).default("freeform")
|
|
3736
|
-
}).optional(),
|
|
3737
|
-
parsers: z4.array(z4.enum(["pdf"])).optional(),
|
|
3738
|
-
pdfOptions: z4.object({
|
|
3739
|
-
maxPages: z4.number().int().min(1).max(1e4).optional()
|
|
3740
|
-
}).optional(),
|
|
3741
|
-
onlyMainContent: z4.boolean().optional(),
|
|
3742
|
-
redactPII: z4.boolean().optional(),
|
|
3743
|
-
includeTags: z4.array(z4.string()).optional(),
|
|
3744
|
-
excludeTags: z4.array(z4.string()).optional(),
|
|
3745
|
-
removeBase64Images: z4.boolean().optional(),
|
|
3746
|
-
skipTlsVerification: z4.boolean().optional(),
|
|
3747
|
-
storeInCache: z4.boolean().optional(),
|
|
3748
|
-
zeroDataRetention: z4.boolean().optional(),
|
|
3749
|
-
maxAge: z4.number().optional(),
|
|
3750
|
-
proxy: z4.enum(["basic", "auto"]).optional()
|
|
3751
|
-
});
|
|
3752
|
-
const EXTENSION_CONTENT_TYPES = {
|
|
3753
|
-
".html": "text/html",
|
|
3754
|
-
".htm": "text/html",
|
|
3755
|
-
".pdf": "application/pdf",
|
|
3756
|
-
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
3757
|
-
".doc": "application/msword",
|
|
3758
|
-
".odt": "application/vnd.oasis.opendocument.text",
|
|
3759
|
-
".rtf": "application/rtf",
|
|
3760
|
-
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
3761
|
-
".xls": "application/vnd.ms-excel"
|
|
3762
|
-
};
|
|
3763
|
-
server.addTool({
|
|
3764
|
-
name: "firecrawl_parse",
|
|
3765
|
-
annotations: {
|
|
3766
|
-
title: "Parse a local file",
|
|
3767
|
-
readOnlyHint: true,
|
|
3768
|
-
// Reads and parses a local file; does not modify the file on disk.
|
|
3769
|
-
openWorldHint: false,
|
|
3770
|
-
// Operates on a local filesystem path, not the open web.
|
|
3771
|
-
destructiveHint: false
|
|
3772
|
-
// Read-only parsing; no deletion or writes to the source file.
|
|
3773
|
-
},
|
|
3774
|
-
description: `
|
|
3775
|
-
Parse a file from the local filesystem using a self-hosted Firecrawl API's /v2/parse endpoint.
|
|
3776
|
-
This is the fastest and most reliable way to extract content from a document on disk \u2014 if the file lives locally and the MCP is pointed at a self-hosted Firecrawl instance, you should always prefer this tool over uploading the file elsewhere and then scraping it.
|
|
3946
|
+
server.addTool({
|
|
3947
|
+
name: "firecrawl_parse",
|
|
3948
|
+
annotations: {
|
|
3949
|
+
title: "Parse a local file",
|
|
3950
|
+
readOnlyHint: true,
|
|
3951
|
+
// Local mode reads a file; hosted mode only returns upload instructions or parses an uploadRef.
|
|
3952
|
+
openWorldHint: false,
|
|
3953
|
+
// Operates on a local filesystem path/upload reference, not an arbitrary web URL.
|
|
3954
|
+
destructiveHint: false
|
|
3955
|
+
// Read-only parsing; no deletion or writes to the source file.
|
|
3956
|
+
},
|
|
3957
|
+
description: `
|
|
3958
|
+
Parse a file using Firecrawl's /v2/parse endpoint.
|
|
3959
|
+
|
|
3960
|
+
In local/non-cloud MCP mode, this tool reads filePath from the MCP server filesystem and posts multipart data to the configured self-hosted FIRECRAWL_API_URL, preserving the existing direct-read behavior.
|
|
3961
|
+
|
|
3962
|
+
In hosted CLOUD_SERVICE mode, this tool is a two-call flow because hosted MCP cannot read your local filesystem:
|
|
3963
|
+
1. Call with filePath, contentType, parse options, and optional declaredSizeBytes. The hosted server mints a short-lived upload URL and returns a safe local curl PUT command plus nextToolCall.
|
|
3964
|
+
2. Run the returned curl command locally, then call firecrawl_parse again with uploadRef and the desired parse options. The hosted server calls /v2/parse server-side with your session credential.
|
|
3777
3965
|
|
|
3778
|
-
**Best for:** Extracting content from a local document (PDF, Word, Excel, HTML, etc.)
|
|
3966
|
+
**Best for:** Extracting content from a local document (PDF, Word, Excel, HTML, etc.); pulling structured data out of a file with JSON format; converting binary documents into markdown for downstream reasoning.
|
|
3779
3967
|
**Not recommended for:** Remote URLs (use firecrawl_scrape); multiple files at once (call parse multiple times); documents that require interactive actions, screenshots, or change tracking \u2014 those aren't supported by the parse endpoint.
|
|
3780
|
-
**Common mistakes:**
|
|
3968
|
+
**Common mistakes:** In hosted mode, do not pass both filePath and uploadRef. Phase 1 uses filePath only to generate upload instructions; phase 2 uses uploadRef only to parse server-side.
|
|
3781
3969
|
|
|
3782
3970
|
**Supported file types:** .html, .htm, .xhtml, .pdf, .docx, .doc, .odt, .rtf, .xlsx, .xls
|
|
3783
3971
|
**Unsupported options:** actions, screenshot/branding/changeTracking formats, waitFor > 0, location, mobile, proxy values other than "auto" or "basic".
|
|
@@ -3786,122 +3974,96 @@ This is the fastest and most reliable way to extract content from a document on
|
|
|
3786
3974
|
**CRITICAL - Format Selection (same rules as firecrawl_scrape):**
|
|
3787
3975
|
When the user asks for SPECIFIC data points from a document, you MUST use JSON format with a schema. Only use markdown when the user needs the ENTIRE document content.
|
|
3788
3976
|
|
|
3789
|
-
**Use JSON format when the user asks for:**
|
|
3790
|
-
- Specific fields, parameters, or values from a form / PDF / spreadsheet
|
|
3791
|
-
- Prices, numbers, or other structured data
|
|
3792
|
-
- Lists of items or properties
|
|
3793
|
-
|
|
3794
|
-
**Use markdown format when:**
|
|
3795
|
-
- User wants to read, summarize, or analyze the full document
|
|
3796
|
-
- User explicitly asks for the complete content
|
|
3797
|
-
|
|
3798
3977
|
**Handling PDFs:**
|
|
3799
3978
|
Add \`"parsers": ["pdf"]\` (optionally with \`pdfOptions.maxPages\`) when parsing a PDF so the PDF engine is invoked explicitly. For very long documents, cap \`maxPages\` to keep the response within token limits.
|
|
3800
3979
|
|
|
3801
|
-
**
|
|
3980
|
+
**Hosted phase 1 example:**
|
|
3802
3981
|
\`\`\`json
|
|
3803
3982
|
{
|
|
3804
3983
|
"name": "firecrawl_parse",
|
|
3805
3984
|
"arguments": {
|
|
3806
3985
|
"filePath": "/absolute/path/to/document.pdf",
|
|
3986
|
+
"contentType": "application/pdf",
|
|
3807
3987
|
"formats": ["markdown"],
|
|
3808
3988
|
"parsers": ["pdf"],
|
|
3809
|
-
"
|
|
3989
|
+
"zeroDataRetention": true
|
|
3810
3990
|
}
|
|
3811
3991
|
}
|
|
3812
3992
|
\`\`\`
|
|
3813
3993
|
|
|
3814
|
-
**
|
|
3994
|
+
**Hosted phase 2 example:**
|
|
3815
3995
|
\`\`\`json
|
|
3816
3996
|
{
|
|
3817
3997
|
"name": "firecrawl_parse",
|
|
3818
3998
|
"arguments": {
|
|
3819
|
-
"
|
|
3820
|
-
"formats": ["
|
|
3821
|
-
"
|
|
3822
|
-
|
|
3823
|
-
"schema": {
|
|
3824
|
-
"type": "object",
|
|
3825
|
-
"properties": {
|
|
3826
|
-
"invoiceNumber": { "type": "string" },
|
|
3827
|
-
"total": { "type": "number" },
|
|
3828
|
-
"lineItems": {
|
|
3829
|
-
"type": "array",
|
|
3830
|
-
"items": {
|
|
3831
|
-
"type": "object",
|
|
3832
|
-
"properties": {
|
|
3833
|
-
"description": { "type": "string" },
|
|
3834
|
-
"amount": { "type": "number" }
|
|
3835
|
-
}
|
|
3836
|
-
}
|
|
3837
|
-
}
|
|
3838
|
-
}
|
|
3839
|
-
}
|
|
3840
|
-
}
|
|
3999
|
+
"uploadRef": "upload-ref-from-phase-1",
|
|
4000
|
+
"formats": ["markdown"],
|
|
4001
|
+
"parsers": ["pdf"],
|
|
4002
|
+
"zeroDataRetention": true
|
|
3841
4003
|
}
|
|
3842
4004
|
}
|
|
3843
4005
|
\`\`\`
|
|
3844
|
-
|
|
4006
|
+
|
|
4007
|
+
**Returns:** Phase 1 hosted upload instructions or a parsed document with markdown, html, links, summary, json, or query results depending on the requested formats.
|
|
3845
4008
|
`,
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
filePath,
|
|
3856
|
-
contentType: overrideContentType,
|
|
3857
|
-
...options
|
|
3858
|
-
} = args2;
|
|
3859
|
-
const absPath = path.resolve(filePath);
|
|
3860
|
-
const buffer = await readFile(absPath);
|
|
3861
|
-
const filename = path.basename(absPath);
|
|
3862
|
-
const fileContentType = overrideContentType && overrideContentType.length > 0 ? overrideContentType : inferContentType(filename);
|
|
3863
|
-
const transformed = transformScrapeParams(
|
|
3864
|
-
options
|
|
4009
|
+
parameters: parseParamsSchema,
|
|
4010
|
+
execute: async (args2, { session, log }) => {
|
|
4011
|
+
if (process.env.CLOUD_SERVICE === "true") {
|
|
4012
|
+
return executeHostedParse(args2, session, log);
|
|
4013
|
+
}
|
|
4014
|
+
const apiUrl = process.env.FIRECRAWL_API_URL;
|
|
4015
|
+
if (!apiUrl) {
|
|
4016
|
+
throw new Error(
|
|
4017
|
+
"firecrawl_parse requires FIRECRAWL_API_URL to be set to a self-hosted Firecrawl API instance."
|
|
3865
4018
|
);
|
|
3866
|
-
const cleaned = removeEmptyTopLevel(transformed);
|
|
3867
|
-
const optionsPayload = { origin: ORIGIN, ...cleaned };
|
|
3868
|
-
const form = new FormData();
|
|
3869
|
-
const blob = new Blob([new Uint8Array(buffer)], {
|
|
3870
|
-
type: fileContentType
|
|
3871
|
-
});
|
|
3872
|
-
form.append("file", blob, filename);
|
|
3873
|
-
form.append("options", JSON.stringify(optionsPayload));
|
|
3874
|
-
const headers = { ...ORIGIN_HEADERS2 };
|
|
3875
|
-
const apiKey = session?.firecrawlApiKey;
|
|
3876
|
-
if (apiKey) {
|
|
3877
|
-
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
3878
|
-
}
|
|
3879
|
-
const endpoint = `${apiUrl.replace(/\/$/, "")}/v2/parse`;
|
|
3880
|
-
log.info("Parsing local file", {
|
|
3881
|
-
endpoint,
|
|
3882
|
-
filename,
|
|
3883
|
-
size: buffer.length
|
|
3884
|
-
});
|
|
3885
|
-
const response = await fetch(endpoint, {
|
|
3886
|
-
method: "POST",
|
|
3887
|
-
headers,
|
|
3888
|
-
body: form
|
|
3889
|
-
});
|
|
3890
|
-
const responseText = await response.text();
|
|
3891
|
-
if (!response.ok) {
|
|
3892
|
-
throw new Error(
|
|
3893
|
-
`Parse request failed with status ${response.status}: ${responseText}`
|
|
3894
|
-
);
|
|
3895
|
-
}
|
|
3896
|
-
try {
|
|
3897
|
-
return asText2(JSON.parse(responseText));
|
|
3898
|
-
} catch {
|
|
3899
|
-
return responseText;
|
|
3900
|
-
}
|
|
3901
4019
|
}
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
4020
|
+
const {
|
|
4021
|
+
filePath,
|
|
4022
|
+
contentType: overrideContentType,
|
|
4023
|
+
...options
|
|
4024
|
+
} = args2;
|
|
4025
|
+
const absPath = path.resolve(filePath);
|
|
4026
|
+
const buffer = await readFile(absPath);
|
|
4027
|
+
const filename = path.basename(absPath);
|
|
4028
|
+
const fileContentType = overrideContentType && overrideContentType.length > 0 ? overrideContentType : inferContentType(filename);
|
|
4029
|
+
const optionsPayload = buildParseOptionsPayload(
|
|
4030
|
+
options
|
|
4031
|
+
);
|
|
4032
|
+
const form = new FormData();
|
|
4033
|
+
const blob = new Blob([new Uint8Array(buffer)], {
|
|
4034
|
+
type: fileContentType
|
|
4035
|
+
});
|
|
4036
|
+
form.append("file", blob, filename);
|
|
4037
|
+
form.append("options", JSON.stringify(optionsPayload));
|
|
4038
|
+
const headers = { ...ORIGIN_HEADERS2 };
|
|
4039
|
+
const apiKey = session?.firecrawlApiKey;
|
|
4040
|
+
if (apiKey) {
|
|
4041
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
4042
|
+
}
|
|
4043
|
+
const endpoint = `${apiUrl.replace(/\/$/, "")}/v2/parse`;
|
|
4044
|
+
log.info("Parsing local file", {
|
|
4045
|
+
endpoint,
|
|
4046
|
+
filename,
|
|
4047
|
+
size: buffer.length
|
|
4048
|
+
});
|
|
4049
|
+
const response = await fetch(endpoint, {
|
|
4050
|
+
method: "POST",
|
|
4051
|
+
headers,
|
|
4052
|
+
body: form
|
|
4053
|
+
});
|
|
4054
|
+
const responseText = await response.text();
|
|
4055
|
+
if (!response.ok) {
|
|
4056
|
+
throw new Error(
|
|
4057
|
+
`Parse request failed with status ${response.status}: ${responseText}`
|
|
4058
|
+
);
|
|
4059
|
+
}
|
|
4060
|
+
try {
|
|
4061
|
+
return asText2(JSON.parse(responseText));
|
|
4062
|
+
} catch {
|
|
4063
|
+
return responseText;
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
});
|
|
3905
4067
|
var PORT = Number(process.env.PORT || 3e3);
|
|
3906
4068
|
var HOST = process.env.CLOUD_SERVICE === "true" ? "0.0.0.0" : process.env.HOST || "localhost";
|
|
3907
4069
|
var args;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firecrawl-mcp",
|
|
3
|
-
"version": "3.21.
|
|
3
|
+
"version": "3.21.4",
|
|
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",
|