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.
Files changed (2) hide show
  1. package/dist/index.js +328 -166
  2. 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
- if (process.env.CLOUD_SERVICE !== "true") {
3706
- let inferContentType = function(filename) {
3707
- const ext = path.extname(filename).toLowerCase();
3708
- return EXTENSION_CONTENT_TYPES[ext] ?? "application/octet-stream";
3709
- };
3710
- inferContentType2 = inferContentType;
3711
- const parseParamsSchema = z4.object({
3712
- filePath: z4.string().min(1).describe(
3713
- "Absolute or relative path to a local file to parse. Supported: .html, .htm, .pdf, .docx, .doc, .odt, .rtf, .xlsx, .xls"
3714
- ),
3715
- contentType: z4.string().optional().describe(
3716
- "Optional MIME type override. If omitted, the server infers the file kind from the extension."
3717
- ),
3718
- formats: z4.array(
3719
- z4.enum([
3720
- "markdown",
3721
- "html",
3722
- "rawHtml",
3723
- "links",
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.) when you don't want to host it on the public web first; pulling structured data out of a file with JSON format; converting binary documents into markdown for downstream reasoning.
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:** Passing a URL instead of a local file path; requesting an unsupported format (screenshot, branding, changeTracking); setting waitFor, location, mobile, or a non-basic/auto proxy \u2014 parse uploads reject all of those.
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
- **Usage Example (markdown from a local PDF):**
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
- "onlyMainContent": true
3989
+ "zeroDataRetention": true
3810
3990
  }
3811
3991
  }
3812
3992
  \`\`\`
3813
3993
 
3814
- **Usage Example (structured JSON extraction from a local HTML file):**
3994
+ **Hosted phase 2 example:**
3815
3995
  \`\`\`json
3816
3996
  {
3817
3997
  "name": "firecrawl_parse",
3818
3998
  "arguments": {
3819
- "filePath": "./invoice.html",
3820
- "formats": ["json"],
3821
- "jsonOptions": {
3822
- "prompt": "Extract the invoice number, total, and line items",
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
- **Returns:** A parsed document with markdown, html, links, summary, json, or query results depending on the requested formats.
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
- parameters: parseParamsSchema,
3847
- execute: async (args2, { session, log }) => {
3848
- const apiUrl = process.env.FIRECRAWL_API_URL;
3849
- if (!apiUrl) {
3850
- throw new Error(
3851
- "firecrawl_parse requires FIRECRAWL_API_URL to be set to a self-hosted Firecrawl API instance."
3852
- );
3853
- }
3854
- const {
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
- var inferContentType2;
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",
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",