firecrawl-mcp 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +151 -125
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -513,11 +513,33 @@ const DEEP_RESEARCH_TOOL = {
|
|
|
513
513
|
maxUrls: {
|
|
514
514
|
type: 'number',
|
|
515
515
|
description: 'Maximum number of URLs to analyze (1-1000)',
|
|
516
|
-
}
|
|
516
|
+
},
|
|
517
517
|
},
|
|
518
518
|
required: ['query'],
|
|
519
519
|
},
|
|
520
520
|
};
|
|
521
|
+
const GENERATE_LLMSTXT_TOOL = {
|
|
522
|
+
name: 'firecrawl_generate_llmstxt',
|
|
523
|
+
description: 'Generate standardized LLMs.txt file for a given URL, which provides context about how LLMs should interact with the website.',
|
|
524
|
+
inputSchema: {
|
|
525
|
+
type: 'object',
|
|
526
|
+
properties: {
|
|
527
|
+
url: {
|
|
528
|
+
type: 'string',
|
|
529
|
+
description: 'The URL to generate LLMs.txt from',
|
|
530
|
+
},
|
|
531
|
+
maxUrls: {
|
|
532
|
+
type: 'number',
|
|
533
|
+
description: 'Maximum number of URLs to process (1-100, default: 10)',
|
|
534
|
+
},
|
|
535
|
+
showFullText: {
|
|
536
|
+
type: 'boolean',
|
|
537
|
+
description: 'Whether to show the full LLMs-full.txt in the response',
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
required: ['url'],
|
|
541
|
+
},
|
|
542
|
+
};
|
|
521
543
|
// Type guards
|
|
522
544
|
function isScrapeOptions(args) {
|
|
523
545
|
return (typeof args === 'object' &&
|
|
@@ -563,10 +585,16 @@ function isExtractOptions(args) {
|
|
|
563
585
|
return (Array.isArray(urls) &&
|
|
564
586
|
urls.every((url) => typeof url === 'string'));
|
|
565
587
|
}
|
|
588
|
+
function isGenerateLLMsTextOptions(args) {
|
|
589
|
+
return (typeof args === 'object' &&
|
|
590
|
+
args !== null &&
|
|
591
|
+
'url' in args &&
|
|
592
|
+
typeof args.url === 'string');
|
|
593
|
+
}
|
|
566
594
|
// Server implementation
|
|
567
595
|
const server = new Server({
|
|
568
596
|
name: 'firecrawl-mcp',
|
|
569
|
-
version: '1.
|
|
597
|
+
version: '1.7.0',
|
|
570
598
|
}, {
|
|
571
599
|
capabilities: {
|
|
572
600
|
tools: {},
|
|
@@ -607,6 +635,17 @@ const creditUsage = {
|
|
|
607
635
|
function delay(ms) {
|
|
608
636
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
609
637
|
}
|
|
638
|
+
let isStdioTransport = false;
|
|
639
|
+
function safeLog(level, data) {
|
|
640
|
+
if (isStdioTransport) {
|
|
641
|
+
// For stdio transport, log to stderr to avoid protocol interference
|
|
642
|
+
console.error(`[${level}] ${typeof data === 'object' ? JSON.stringify(data) : data}`);
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
// For other transport types, use the normal logging mechanism
|
|
646
|
+
server.sendLoggingMessage({ level, data });
|
|
647
|
+
}
|
|
648
|
+
}
|
|
610
649
|
// Add retry logic with exponential backoff
|
|
611
650
|
async function withRetry(operation, context, attempt = 1) {
|
|
612
651
|
try {
|
|
@@ -618,10 +657,7 @@ async function withRetry(operation, context, attempt = 1) {
|
|
|
618
657
|
if (isRateLimit && attempt < CONFIG.retry.maxAttempts) {
|
|
619
658
|
const delayMs = Math.min(CONFIG.retry.initialDelay *
|
|
620
659
|
Math.pow(CONFIG.retry.backoffFactor, attempt - 1), CONFIG.retry.maxDelay);
|
|
621
|
-
|
|
622
|
-
level: 'warning',
|
|
623
|
-
data: `Rate limit hit for ${context}. Attempt ${attempt}/${CONFIG.retry.maxAttempts}. Retrying in ${delayMs}ms`,
|
|
624
|
-
});
|
|
660
|
+
safeLog('warning', `Rate limit hit for ${context}. Attempt ${attempt}/${CONFIG.retry.maxAttempts}. Retrying in ${delayMs}ms`);
|
|
625
661
|
await delay(delayMs);
|
|
626
662
|
return withRetry(operation, context, attempt + 1);
|
|
627
663
|
}
|
|
@@ -632,22 +668,13 @@ async function withRetry(operation, context, attempt = 1) {
|
|
|
632
668
|
async function updateCreditUsage(creditsUsed) {
|
|
633
669
|
creditUsage.total += creditsUsed;
|
|
634
670
|
// Log credit usage
|
|
635
|
-
|
|
636
|
-
level: 'info',
|
|
637
|
-
data: `Credit usage: ${creditUsage.total} credits used total`,
|
|
638
|
-
});
|
|
671
|
+
safeLog('info', `Credit usage: ${creditUsage.total} credits used total`);
|
|
639
672
|
// Check thresholds
|
|
640
673
|
if (creditUsage.total >= CONFIG.credit.criticalThreshold) {
|
|
641
|
-
|
|
642
|
-
level: 'error',
|
|
643
|
-
data: `CRITICAL: Credit usage has reached ${creditUsage.total}`,
|
|
644
|
-
});
|
|
674
|
+
safeLog('error', `CRITICAL: Credit usage has reached ${creditUsage.total}`);
|
|
645
675
|
}
|
|
646
676
|
else if (creditUsage.total >= CONFIG.credit.warningThreshold) {
|
|
647
|
-
|
|
648
|
-
level: 'warning',
|
|
649
|
-
data: `WARNING: Credit usage has reached ${creditUsage.total}`,
|
|
650
|
-
});
|
|
677
|
+
safeLog('warning', `WARNING: Credit usage has reached ${creditUsage.total}`);
|
|
651
678
|
}
|
|
652
679
|
}
|
|
653
680
|
// Initialize queue system
|
|
@@ -672,19 +699,13 @@ async function processBatchOperation(operation) {
|
|
|
672
699
|
operation.result = response;
|
|
673
700
|
// Log final credit usage for the batch
|
|
674
701
|
if (!FIRECRAWL_API_URL) {
|
|
675
|
-
|
|
676
|
-
level: 'info',
|
|
677
|
-
data: `Batch ${operation.id} completed. Total credits used: ${totalCreditsUsed}`,
|
|
678
|
-
});
|
|
702
|
+
safeLog('info', `Batch ${operation.id} completed. Total credits used: ${totalCreditsUsed}`);
|
|
679
703
|
}
|
|
680
704
|
}
|
|
681
705
|
catch (error) {
|
|
682
706
|
operation.status = 'failed';
|
|
683
707
|
operation.error = error instanceof Error ? error.message : String(error);
|
|
684
|
-
|
|
685
|
-
level: 'error',
|
|
686
|
-
data: `Batch ${operation.id} failed: ${operation.error}`,
|
|
687
|
-
});
|
|
708
|
+
safeLog('error', `Batch ${operation.id} failed: ${operation.error}`);
|
|
688
709
|
}
|
|
689
710
|
}
|
|
690
711
|
// Tool handlers
|
|
@@ -699,6 +720,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
699
720
|
SEARCH_TOOL,
|
|
700
721
|
EXTRACT_TOOL,
|
|
701
722
|
DEEP_RESEARCH_TOOL,
|
|
723
|
+
GENERATE_LLMSTXT_TOOL,
|
|
702
724
|
],
|
|
703
725
|
}));
|
|
704
726
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -706,10 +728,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
706
728
|
try {
|
|
707
729
|
const { name, arguments: args } = request.params;
|
|
708
730
|
// Log incoming request with timestamp
|
|
709
|
-
|
|
710
|
-
level: 'info',
|
|
711
|
-
data: `[${new Date().toISOString()}] Received request for tool: ${name}`,
|
|
712
|
-
});
|
|
731
|
+
safeLog('info', `[${new Date().toISOString()}] Received request for tool: ${name}`);
|
|
713
732
|
if (!args) {
|
|
714
733
|
throw new Error('No arguments provided');
|
|
715
734
|
}
|
|
@@ -721,16 +740,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
721
740
|
const { url, ...options } = args;
|
|
722
741
|
try {
|
|
723
742
|
const scrapeStartTime = Date.now();
|
|
724
|
-
|
|
725
|
-
level: 'info',
|
|
726
|
-
data: `Starting scrape for URL: ${url} with options: ${JSON.stringify(options)}`,
|
|
727
|
-
});
|
|
743
|
+
safeLog('info', `Starting scrape for URL: ${url} with options: ${JSON.stringify(options)}`);
|
|
728
744
|
const response = await client.scrapeUrl(url, options);
|
|
729
745
|
// Log performance metrics
|
|
730
|
-
|
|
731
|
-
level: 'info',
|
|
732
|
-
data: `Scrape completed in ${Date.now() - scrapeStartTime}ms`,
|
|
733
|
-
});
|
|
746
|
+
safeLog('info', `Scrape completed in ${Date.now() - scrapeStartTime}ms`);
|
|
734
747
|
if ('success' in response && !response.success) {
|
|
735
748
|
throw new Error(response.error || 'Scraping failed');
|
|
736
749
|
}
|
|
@@ -756,14 +769,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
756
769
|
}
|
|
757
770
|
// Add warning to response if present
|
|
758
771
|
if (response.warning) {
|
|
759
|
-
|
|
760
|
-
level: 'warning',
|
|
761
|
-
data: response.warning,
|
|
762
|
-
});
|
|
772
|
+
safeLog('warning', response.warning);
|
|
763
773
|
}
|
|
764
774
|
return {
|
|
765
775
|
content: [
|
|
766
|
-
{
|
|
776
|
+
{
|
|
777
|
+
type: 'text',
|
|
778
|
+
text: trimResponseText(contentParts.join('\n\n') || 'No content available'),
|
|
779
|
+
},
|
|
767
780
|
],
|
|
768
781
|
isError: false,
|
|
769
782
|
};
|
|
@@ -771,7 +784,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
771
784
|
catch (error) {
|
|
772
785
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
773
786
|
return {
|
|
774
|
-
content: [{ type: 'text', text: errorMessage }],
|
|
787
|
+
content: [{ type: 'text', text: trimResponseText(errorMessage) }],
|
|
775
788
|
isError: true,
|
|
776
789
|
};
|
|
777
790
|
}
|
|
@@ -789,7 +802,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
789
802
|
throw new Error('No links received from FireCrawl API');
|
|
790
803
|
}
|
|
791
804
|
return {
|
|
792
|
-
content: [
|
|
805
|
+
content: [
|
|
806
|
+
{ type: 'text', text: trimResponseText(response.links.join('\n')) },
|
|
807
|
+
],
|
|
793
808
|
isError: false,
|
|
794
809
|
};
|
|
795
810
|
}
|
|
@@ -812,15 +827,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
812
827
|
batchOperations.set(operationId, operation);
|
|
813
828
|
// Queue the operation
|
|
814
829
|
batchQueue.add(() => processBatchOperation(operation));
|
|
815
|
-
|
|
816
|
-
level: 'info',
|
|
817
|
-
data: `Queued batch operation ${operationId} with ${args.urls.length} URLs`,
|
|
818
|
-
});
|
|
830
|
+
safeLog('info', `Queued batch operation ${operationId} with ${args.urls.length} URLs`);
|
|
819
831
|
return {
|
|
820
832
|
content: [
|
|
821
833
|
{
|
|
822
834
|
type: 'text',
|
|
823
|
-
text: `Batch operation queued with ID: ${operationId}. Use firecrawl_check_batch_status to check progress
|
|
835
|
+
text: trimResponseText(`Batch operation queued with ID: ${operationId}. Use firecrawl_check_batch_status to check progress.`),
|
|
824
836
|
},
|
|
825
837
|
],
|
|
826
838
|
isError: false,
|
|
@@ -831,7 +843,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
831
843
|
? error.message
|
|
832
844
|
: `Batch operation failed: ${JSON.stringify(error)}`;
|
|
833
845
|
return {
|
|
834
|
-
content: [{ type: 'text', text: errorMessage }],
|
|
846
|
+
content: [{ type: 'text', text: trimResponseText(errorMessage) }],
|
|
835
847
|
isError: true,
|
|
836
848
|
};
|
|
837
849
|
}
|
|
@@ -846,7 +858,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
846
858
|
content: [
|
|
847
859
|
{
|
|
848
860
|
type: 'text',
|
|
849
|
-
text: `No batch operation found with ID: ${args.id}
|
|
861
|
+
text: trimResponseText(`No batch operation found with ID: ${args.id}`),
|
|
850
862
|
},
|
|
851
863
|
],
|
|
852
864
|
isError: true,
|
|
@@ -860,7 +872,7 @@ ${operation.result
|
|
|
860
872
|
? `Results: ${JSON.stringify(operation.result, null, 2)}`
|
|
861
873
|
: ''}`;
|
|
862
874
|
return {
|
|
863
|
-
content: [{ type: 'text', text: status }],
|
|
875
|
+
content: [{ type: 'text', text: trimResponseText(status) }],
|
|
864
876
|
isError: false,
|
|
865
877
|
};
|
|
866
878
|
}
|
|
@@ -881,7 +893,7 @@ ${operation.result
|
|
|
881
893
|
content: [
|
|
882
894
|
{
|
|
883
895
|
type: 'text',
|
|
884
|
-
text: `Started crawl for ${url} with job ID: ${response.id}
|
|
896
|
+
text: trimResponseText(`Started crawl for ${url} with job ID: ${response.id}`),
|
|
885
897
|
},
|
|
886
898
|
],
|
|
887
899
|
isError: false,
|
|
@@ -902,7 +914,7 @@ Credits Used: ${response.creditsUsed}
|
|
|
902
914
|
Expires At: ${response.expiresAt}
|
|
903
915
|
${response.data.length > 0 ? '\nResults:\n' + formatResults(response.data) : ''}`;
|
|
904
916
|
return {
|
|
905
|
-
content: [{ type: 'text', text: status }],
|
|
917
|
+
content: [{ type: 'text', text: trimResponseText(status) }],
|
|
906
918
|
isError: false,
|
|
907
919
|
};
|
|
908
920
|
}
|
|
@@ -927,7 +939,7 @@ Description: ${result.description || 'No description'}
|
|
|
927
939
|
${result.markdown ? `\nContent:\n${result.markdown}` : ''}`)
|
|
928
940
|
.join('\n\n');
|
|
929
941
|
return {
|
|
930
|
-
content: [{ type: 'text', text: results }],
|
|
942
|
+
content: [{ type: 'text', text: trimResponseText(results) }],
|
|
931
943
|
isError: false,
|
|
932
944
|
};
|
|
933
945
|
}
|
|
@@ -936,7 +948,7 @@ ${result.markdown ? `\nContent:\n${result.markdown}` : ''}`)
|
|
|
936
948
|
? error.message
|
|
937
949
|
: `Search failed: ${JSON.stringify(error)}`;
|
|
938
950
|
return {
|
|
939
|
-
content: [{ type: 'text', text: errorMessage }],
|
|
951
|
+
content: [{ type: 'text', text: trimResponseText(errorMessage) }],
|
|
940
952
|
isError: true,
|
|
941
953
|
};
|
|
942
954
|
}
|
|
@@ -947,16 +959,10 @@ ${result.markdown ? `\nContent:\n${result.markdown}` : ''}`)
|
|
|
947
959
|
}
|
|
948
960
|
try {
|
|
949
961
|
const extractStartTime = Date.now();
|
|
950
|
-
|
|
951
|
-
level: 'info',
|
|
952
|
-
data: `Starting extraction for URLs: ${args.urls.join(', ')}`,
|
|
953
|
-
});
|
|
962
|
+
safeLog('info', `Starting extraction for URLs: ${args.urls.join(', ')}`);
|
|
954
963
|
// Log if using self-hosted instance
|
|
955
964
|
if (FIRECRAWL_API_URL) {
|
|
956
|
-
|
|
957
|
-
level: 'info',
|
|
958
|
-
data: 'Using self-hosted instance for extraction',
|
|
959
|
-
});
|
|
965
|
+
safeLog('info', 'Using self-hosted instance for extraction');
|
|
960
966
|
}
|
|
961
967
|
const extractResponse = await withRetry(async () => client.extract(args.urls, {
|
|
962
968
|
prompt: args.prompt,
|
|
@@ -977,25 +983,19 @@ ${result.markdown ? `\nContent:\n${result.markdown}` : ''}`)
|
|
|
977
983
|
await updateCreditUsage(response.creditsUsed || 0);
|
|
978
984
|
}
|
|
979
985
|
// Log performance metrics
|
|
980
|
-
|
|
981
|
-
level: 'info',
|
|
982
|
-
data: `Extraction completed in ${Date.now() - extractStartTime}ms`,
|
|
983
|
-
});
|
|
986
|
+
safeLog('info', `Extraction completed in ${Date.now() - extractStartTime}ms`);
|
|
984
987
|
// Add warning to response if present
|
|
985
988
|
const result = {
|
|
986
989
|
content: [
|
|
987
990
|
{
|
|
988
991
|
type: 'text',
|
|
989
|
-
text: JSON.stringify(response.data, null, 2),
|
|
992
|
+
text: trimResponseText(JSON.stringify(response.data, null, 2)),
|
|
990
993
|
},
|
|
991
994
|
],
|
|
992
995
|
isError: false,
|
|
993
996
|
};
|
|
994
997
|
if (response.warning) {
|
|
995
|
-
|
|
996
|
-
level: 'warning',
|
|
997
|
-
data: response.warning,
|
|
998
|
-
});
|
|
998
|
+
safeLog('warning', response.warning);
|
|
999
999
|
}
|
|
1000
1000
|
return result;
|
|
1001
1001
|
}
|
|
@@ -1004,22 +1004,19 @@ ${result.markdown ? `\nContent:\n${result.markdown}` : ''}`)
|
|
|
1004
1004
|
// Special handling for self-hosted instance errors
|
|
1005
1005
|
if (FIRECRAWL_API_URL &&
|
|
1006
1006
|
errorMessage.toLowerCase().includes('not supported')) {
|
|
1007
|
-
|
|
1008
|
-
level: 'error',
|
|
1009
|
-
data: 'Extraction is not supported by this self-hosted instance',
|
|
1010
|
-
});
|
|
1007
|
+
safeLog('error', 'Extraction is not supported by this self-hosted instance');
|
|
1011
1008
|
return {
|
|
1012
1009
|
content: [
|
|
1013
1010
|
{
|
|
1014
1011
|
type: 'text',
|
|
1015
|
-
text: 'Extraction is not supported by this self-hosted instance. Please ensure LLM support is configured.',
|
|
1012
|
+
text: trimResponseText('Extraction is not supported by this self-hosted instance. Please ensure LLM support is configured.'),
|
|
1016
1013
|
},
|
|
1017
1014
|
],
|
|
1018
1015
|
isError: true,
|
|
1019
1016
|
};
|
|
1020
1017
|
}
|
|
1021
1018
|
return {
|
|
1022
|
-
content: [{ type: 'text', text: errorMessage }],
|
|
1019
|
+
content: [{ type: 'text', text: trimResponseText(errorMessage) }],
|
|
1023
1020
|
isError: true,
|
|
1024
1021
|
};
|
|
1025
1022
|
}
|
|
@@ -1030,10 +1027,7 @@ ${result.markdown ? `\nContent:\n${result.markdown}` : ''}`)
|
|
|
1030
1027
|
}
|
|
1031
1028
|
try {
|
|
1032
1029
|
const researchStartTime = Date.now();
|
|
1033
|
-
|
|
1034
|
-
level: 'info',
|
|
1035
|
-
data: `Starting deep research for query: ${args.query}`,
|
|
1036
|
-
});
|
|
1030
|
+
safeLog('info', `Starting deep research for query: ${args.query}`);
|
|
1037
1031
|
const response = await client.deepResearch(args.query, {
|
|
1038
1032
|
maxDepth: args.maxDepth,
|
|
1039
1033
|
timeLimit: args.timeLimit,
|
|
@@ -1041,23 +1035,14 @@ ${result.markdown ? `\nContent:\n${result.markdown}` : ''}`)
|
|
|
1041
1035
|
},
|
|
1042
1036
|
// Activity callback
|
|
1043
1037
|
(activity) => {
|
|
1044
|
-
|
|
1045
|
-
level: 'info',
|
|
1046
|
-
data: `Research activity: ${activity.message} (Depth: ${activity.depth})`,
|
|
1047
|
-
});
|
|
1038
|
+
safeLog('info', `Research activity: ${activity.message} (Depth: ${activity.depth})`);
|
|
1048
1039
|
},
|
|
1049
1040
|
// Source callback
|
|
1050
1041
|
(source) => {
|
|
1051
|
-
|
|
1052
|
-
level: 'info',
|
|
1053
|
-
data: `Research source found: ${source.url}${source.title ? ` - ${source.title}` : ''}`,
|
|
1054
|
-
});
|
|
1042
|
+
safeLog('info', `Research source found: ${source.url}${source.title ? ` - ${source.title}` : ''}`);
|
|
1055
1043
|
});
|
|
1056
1044
|
// Log performance metrics
|
|
1057
|
-
|
|
1058
|
-
level: 'info',
|
|
1059
|
-
data: `Deep research completed in ${Date.now() - researchStartTime}ms`,
|
|
1060
|
-
});
|
|
1045
|
+
safeLog('info', `Deep research completed in ${Date.now() - researchStartTime}ms`);
|
|
1061
1046
|
if (!response.success) {
|
|
1062
1047
|
throw new Error(response.error || 'Deep research failed');
|
|
1063
1048
|
}
|
|
@@ -1068,42 +1053,82 @@ ${result.markdown ? `\nContent:\n${result.markdown}` : ''}`)
|
|
|
1068
1053
|
sources: response.data.sources,
|
|
1069
1054
|
};
|
|
1070
1055
|
return {
|
|
1071
|
-
content: [
|
|
1056
|
+
content: [
|
|
1057
|
+
{
|
|
1058
|
+
type: 'text',
|
|
1059
|
+
text: trimResponseText(formattedResponse.finalAnalysis),
|
|
1060
|
+
},
|
|
1061
|
+
],
|
|
1072
1062
|
isError: false,
|
|
1073
1063
|
};
|
|
1074
1064
|
}
|
|
1075
1065
|
catch (error) {
|
|
1076
1066
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1077
1067
|
return {
|
|
1078
|
-
content: [{ type: 'text', text: errorMessage }],
|
|
1068
|
+
content: [{ type: 'text', text: trimResponseText(errorMessage) }],
|
|
1069
|
+
isError: true,
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
case 'firecrawl_generate_llmstxt': {
|
|
1074
|
+
if (!isGenerateLLMsTextOptions(args)) {
|
|
1075
|
+
throw new Error('Invalid arguments for firecrawl_generate_llmstxt');
|
|
1076
|
+
}
|
|
1077
|
+
try {
|
|
1078
|
+
const { url, ...params } = args;
|
|
1079
|
+
const generateStartTime = Date.now();
|
|
1080
|
+
safeLog('info', `Starting LLMs.txt generation for URL: ${url}`);
|
|
1081
|
+
// Start the generation process
|
|
1082
|
+
const response = await withRetry(async () => client.generateLLMsText(url, params), 'LLMs.txt generation');
|
|
1083
|
+
if (!response.success) {
|
|
1084
|
+
throw new Error(response.error || 'LLMs.txt generation failed');
|
|
1085
|
+
}
|
|
1086
|
+
// Log performance metrics
|
|
1087
|
+
safeLog('info', `LLMs.txt generation completed in ${Date.now() - generateStartTime}ms`);
|
|
1088
|
+
// Format the response
|
|
1089
|
+
let resultText = '';
|
|
1090
|
+
if ('data' in response) {
|
|
1091
|
+
resultText = `LLMs.txt content:\n\n${response.data.llmstxt}`;
|
|
1092
|
+
if (args.showFullText && response.data.llmsfulltxt) {
|
|
1093
|
+
resultText += `\n\nLLMs-full.txt content:\n\n${response.data.llmsfulltxt}`;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
return {
|
|
1097
|
+
content: [{ type: 'text', text: trimResponseText(resultText) }],
|
|
1098
|
+
isError: false,
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
catch (error) {
|
|
1102
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1103
|
+
return {
|
|
1104
|
+
content: [{ type: 'text', text: trimResponseText(errorMessage) }],
|
|
1079
1105
|
isError: true,
|
|
1080
1106
|
};
|
|
1081
1107
|
}
|
|
1082
1108
|
}
|
|
1083
1109
|
default:
|
|
1084
1110
|
return {
|
|
1085
|
-
content: [
|
|
1111
|
+
content: [
|
|
1112
|
+
{ type: 'text', text: trimResponseText(`Unknown tool: ${name}`) },
|
|
1113
|
+
],
|
|
1086
1114
|
isError: true,
|
|
1087
1115
|
};
|
|
1088
1116
|
}
|
|
1089
1117
|
}
|
|
1090
1118
|
catch (error) {
|
|
1091
1119
|
// Log detailed error information
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
timestamp: new Date().toISOString(),
|
|
1099
|
-
duration: Date.now() - startTime,
|
|
1100
|
-
},
|
|
1120
|
+
safeLog('error', {
|
|
1121
|
+
message: `Request failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1122
|
+
tool: request.params.name,
|
|
1123
|
+
arguments: request.params.arguments,
|
|
1124
|
+
timestamp: new Date().toISOString(),
|
|
1125
|
+
duration: Date.now() - startTime,
|
|
1101
1126
|
});
|
|
1102
1127
|
return {
|
|
1103
1128
|
content: [
|
|
1104
1129
|
{
|
|
1105
1130
|
type: 'text',
|
|
1106
|
-
text: `Error: ${error instanceof Error ? error.message : String(error)}
|
|
1131
|
+
text: trimResponseText(`Error: ${error instanceof Error ? error.message : String(error)}`),
|
|
1107
1132
|
},
|
|
1108
1133
|
],
|
|
1109
1134
|
isError: true,
|
|
@@ -1111,10 +1136,7 @@ ${result.markdown ? `\nContent:\n${result.markdown}` : ''}`)
|
|
|
1111
1136
|
}
|
|
1112
1137
|
finally {
|
|
1113
1138
|
// Log request completion with performance metrics
|
|
1114
|
-
|
|
1115
|
-
level: 'info',
|
|
1116
|
-
data: `Request completed in ${Date.now() - startTime}ms`,
|
|
1117
|
-
});
|
|
1139
|
+
safeLog('info', `Request completed in ${Date.now() - startTime}ms`);
|
|
1118
1140
|
}
|
|
1119
1141
|
});
|
|
1120
1142
|
// Helper function to format results
|
|
@@ -1128,21 +1150,29 @@ ${doc.metadata?.title ? `Title: ${doc.metadata.title}` : ''}`;
|
|
|
1128
1150
|
})
|
|
1129
1151
|
.join('\n\n');
|
|
1130
1152
|
}
|
|
1153
|
+
// Add type guard for credit usage
|
|
1154
|
+
function hasCredits(response) {
|
|
1155
|
+
return 'creditsUsed' in response && typeof response.creditsUsed === 'number';
|
|
1156
|
+
}
|
|
1157
|
+
// Utility function to trim trailing whitespace from text responses
|
|
1158
|
+
// This prevents Claude API errors with "final assistant content cannot end with trailing whitespace"
|
|
1159
|
+
function trimResponseText(text) {
|
|
1160
|
+
return text.trim();
|
|
1161
|
+
}
|
|
1131
1162
|
// Server startup
|
|
1132
1163
|
async function runServer() {
|
|
1133
1164
|
try {
|
|
1134
1165
|
console.error('Initializing FireCrawl MCP Server...');
|
|
1135
1166
|
const transport = new StdioServerTransport();
|
|
1167
|
+
// Detect if we're using stdio transport
|
|
1168
|
+
isStdioTransport = transport instanceof StdioServerTransport;
|
|
1169
|
+
if (isStdioTransport) {
|
|
1170
|
+
console.error('Running in stdio mode, logging will be directed to stderr');
|
|
1171
|
+
}
|
|
1136
1172
|
await server.connect(transport);
|
|
1137
1173
|
// Now that we're connected, we can send logging messages
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
data: 'FireCrawl MCP Server initialized successfully',
|
|
1141
|
-
});
|
|
1142
|
-
server.sendLoggingMessage({
|
|
1143
|
-
level: 'info',
|
|
1144
|
-
data: `Configuration: API URL: ${FIRECRAWL_API_URL || 'default'}`,
|
|
1145
|
-
});
|
|
1174
|
+
safeLog('info', 'FireCrawl MCP Server initialized successfully');
|
|
1175
|
+
safeLog('info', `Configuration: API URL: ${FIRECRAWL_API_URL || 'default'}`);
|
|
1146
1176
|
console.error('FireCrawl MCP Server running on stdio');
|
|
1147
1177
|
}
|
|
1148
1178
|
catch (error) {
|
|
@@ -1154,7 +1184,3 @@ runServer().catch((error) => {
|
|
|
1154
1184
|
console.error('Fatal error running server:', error);
|
|
1155
1185
|
process.exit(1);
|
|
1156
1186
|
});
|
|
1157
|
-
// Add type guard for credit usage
|
|
1158
|
-
function hasCredits(response) {
|
|
1159
|
-
return 'creditsUsed' in response && typeof response.creditsUsed === 'number';
|
|
1160
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firecrawl-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "MCP server for FireCrawl web scraping integration. Supports both cloud and self-hosted instances. Features include web scraping, batch processing, structured data extraction, and LLM-powered content analysis.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|