firecrawl-mcp 2.1.0 ā 3.0.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 +298 -1268
- package/dist/server-v1.js +12 -8
- package/dist/versioned-server.js +50 -11
- package/package.json +10 -24
package/dist/server-v1.js
CHANGED
|
@@ -771,10 +771,10 @@ export function createV1Server() {
|
|
|
771
771
|
const startTime = Date.now();
|
|
772
772
|
try {
|
|
773
773
|
const { name, arguments: args } = request.params;
|
|
774
|
-
const apiKey = process.env.CLOUD_SERVICE
|
|
774
|
+
const apiKey = process.env.CLOUD_SERVICE === 'true'
|
|
775
775
|
? request.params._meta?.apiKey
|
|
776
776
|
: FIRECRAWL_API_KEY;
|
|
777
|
-
if (process.env.CLOUD_SERVICE && !apiKey) {
|
|
777
|
+
if (process.env.CLOUD_SERVICE === 'true' && !apiKey) {
|
|
778
778
|
throw new Error('No API key provided');
|
|
779
779
|
}
|
|
780
780
|
const client = new FirecrawlApp({
|
|
@@ -807,22 +807,26 @@ export function createV1Server() {
|
|
|
807
807
|
}
|
|
808
808
|
// Format content based on requested formats
|
|
809
809
|
const contentParts = [];
|
|
810
|
-
|
|
810
|
+
const requestedFormats = options.formats && options.formats.length > 0
|
|
811
|
+
? options.formats
|
|
812
|
+
: ['markdown'];
|
|
813
|
+
if (requestedFormats.includes('markdown') && response.markdown) {
|
|
811
814
|
contentParts.push(response.markdown);
|
|
812
815
|
}
|
|
813
|
-
if (
|
|
816
|
+
if (requestedFormats.includes('html') && response.html) {
|
|
814
817
|
contentParts.push(response.html);
|
|
815
818
|
}
|
|
816
|
-
if (
|
|
819
|
+
if (requestedFormats.includes('rawHtml') && response.rawHtml) {
|
|
817
820
|
contentParts.push(response.rawHtml);
|
|
818
821
|
}
|
|
819
|
-
if (
|
|
822
|
+
if (requestedFormats.includes('links') && response.links) {
|
|
820
823
|
contentParts.push(response.links.join('\n'));
|
|
821
824
|
}
|
|
822
|
-
if (
|
|
825
|
+
if (requestedFormats.includes('screenshot') &&
|
|
826
|
+
response.screenshot) {
|
|
823
827
|
contentParts.push(response.screenshot);
|
|
824
828
|
}
|
|
825
|
-
if (
|
|
829
|
+
if (requestedFormats.includes('extract') && response.extract) {
|
|
826
830
|
contentParts.push(JSON.stringify(response.extract, null, 2));
|
|
827
831
|
}
|
|
828
832
|
// If options.formats is empty, default to markdown
|
package/dist/versioned-server.js
CHANGED
|
@@ -14,8 +14,14 @@ export async function runVersionedSSECloudServer() {
|
|
|
14
14
|
status: 'OK',
|
|
15
15
|
versions: ['v1', 'v2'],
|
|
16
16
|
endpoints: {
|
|
17
|
-
v1:
|
|
18
|
-
|
|
17
|
+
v1: {
|
|
18
|
+
sse: '/{apiKey}/sse',
|
|
19
|
+
messages: '/{apiKey}/messages'
|
|
20
|
+
},
|
|
21
|
+
v2: {
|
|
22
|
+
sse: '/{apiKey}/v2/sse',
|
|
23
|
+
messages: '/{apiKey}/v2/messages'
|
|
24
|
+
}
|
|
19
25
|
}
|
|
20
26
|
});
|
|
21
27
|
});
|
|
@@ -35,6 +41,10 @@ export async function runVersionedSSECloudServer() {
|
|
|
35
41
|
});
|
|
36
42
|
await v1Server.connect(transport);
|
|
37
43
|
});
|
|
44
|
+
// V1 SSE HEAD for quick availability checks
|
|
45
|
+
app.head('/:apiKey/sse', (req, res) => {
|
|
46
|
+
res.status(200).end();
|
|
47
|
+
});
|
|
38
48
|
// V2 SSE endpoint (new)
|
|
39
49
|
app.get('/:apiKey/v2/sse', async (req, res) => {
|
|
40
50
|
const apiKey = req.params.apiKey;
|
|
@@ -48,6 +58,10 @@ export async function runVersionedSSECloudServer() {
|
|
|
48
58
|
});
|
|
49
59
|
await v2Server.connect(transport);
|
|
50
60
|
});
|
|
61
|
+
// V2 SSE HEAD for quick availability checks
|
|
62
|
+
app.head('/:apiKey/v2/sse', (req, res) => {
|
|
63
|
+
res.status(200).end();
|
|
64
|
+
});
|
|
51
65
|
// V1 message endpoint (legacy)
|
|
52
66
|
app.post('/:apiKey/messages', express.json(), async (req, res) => {
|
|
53
67
|
const apiKey = req.params.apiKey;
|
|
@@ -65,9 +79,23 @@ export async function runVersionedSSECloudServer() {
|
|
|
65
79
|
enrichedBody.params._meta.apiKey = apiKey;
|
|
66
80
|
}
|
|
67
81
|
console.log(`[V1] Message received for API key: ${apiKey}`);
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
82
|
+
// Prefer explicit sessionId from query, then common header names
|
|
83
|
+
const rawSessionId = req.query.sessionId ||
|
|
84
|
+
req.headers['mcp-session-id'] ||
|
|
85
|
+
req.headers['x-mcp-session-id'] ||
|
|
86
|
+
'';
|
|
87
|
+
let compositeKey = `${apiKey}-${rawSessionId}`;
|
|
88
|
+
let versionedTransport = transports[compositeKey];
|
|
89
|
+
// Fallback: if not found, and there is exactly one active V1 transport for this apiKey, use it
|
|
90
|
+
if (!versionedTransport) {
|
|
91
|
+
const candidates = Object.entries(transports).filter(([key, vt]) => vt.version === 'v1' && key.startsWith(`${apiKey}-`));
|
|
92
|
+
if (candidates.length === 1) {
|
|
93
|
+
const [fallbackKey, vt] = candidates[0];
|
|
94
|
+
console.warn(`[V1] sessionId not provided or not found. Falling back to single active transport: ${fallbackKey}`);
|
|
95
|
+
compositeKey = fallbackKey;
|
|
96
|
+
versionedTransport = vt;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
71
99
|
if (versionedTransport && versionedTransport.version === 'v1') {
|
|
72
100
|
await versionedTransport.transport.handlePostMessage(req, res, enrichedBody);
|
|
73
101
|
}
|
|
@@ -113,7 +141,7 @@ export async function runVersionedSSECloudServer() {
|
|
|
113
141
|
}
|
|
114
142
|
});
|
|
115
143
|
// Catch-all for unsupported endpoints
|
|
116
|
-
app.use(
|
|
144
|
+
app.use((req, res) => {
|
|
117
145
|
res.status(404).json({
|
|
118
146
|
error: 'Endpoint not found',
|
|
119
147
|
supportedEndpoints: {
|
|
@@ -130,19 +158,26 @@ export async function runVersionedSSECloudServer() {
|
|
|
130
158
|
});
|
|
131
159
|
});
|
|
132
160
|
const PORT = process.env.PORT || 3000;
|
|
133
|
-
app.listen(PORT, () => {
|
|
161
|
+
const server = app.listen(PORT, () => {
|
|
134
162
|
console.log(`š Versioned MCP SSE Server listening on http://localhost:${PORT}`);
|
|
135
163
|
console.log('š Available endpoints:');
|
|
136
164
|
console.log(` Health: http://localhost:${PORT}/health`);
|
|
137
|
-
console.log(` V1 SSE: http://localhost:${PORT}
|
|
138
|
-
console.log(` V1 Messages: http://localhost:${PORT}
|
|
139
|
-
console.log(` V2 SSE: http://localhost:${PORT}
|
|
140
|
-
console.log(` V2 Messages: http://localhost:${PORT}
|
|
165
|
+
console.log(` V1 SSE: http://localhost:${PORT}/{apiKey}/sse`);
|
|
166
|
+
console.log(` V1 Messages: http://localhost:${PORT}/{apiKey}/messages`);
|
|
167
|
+
console.log(` V2 SSE: http://localhost:${PORT}/{apiKey}/v2/sse`);
|
|
168
|
+
console.log(` V2 Messages: http://localhost:${PORT}/{apiKey}/v2/messages`);
|
|
141
169
|
console.log('');
|
|
142
170
|
console.log('š§ Versions:');
|
|
143
171
|
console.log(' V1: Firecrawl JS 1.29.3 (legacy tools + deep research + llms.txt)');
|
|
144
172
|
console.log(' V2: Firecrawl JS 3.1.0 (modern API + JSON extraction)');
|
|
145
173
|
});
|
|
174
|
+
server.on('error', (error) => {
|
|
175
|
+
console.error('ā Server error:', error);
|
|
176
|
+
if (error.code === 'EADDRINUSE') {
|
|
177
|
+
console.error(`ā Port ${PORT} is already in use. Please use a different port.`);
|
|
178
|
+
}
|
|
179
|
+
process.exit(1);
|
|
180
|
+
});
|
|
146
181
|
// Graceful shutdown
|
|
147
182
|
process.on('SIGINT', () => {
|
|
148
183
|
console.log('\nš Shutting down server...');
|
|
@@ -162,3 +197,7 @@ export async function runVersionedSSECloudServer() {
|
|
|
162
197
|
process.exit(0);
|
|
163
198
|
});
|
|
164
199
|
}
|
|
200
|
+
// Start the server if this file is run directly
|
|
201
|
+
// if (import.meta.url === `file://${process.argv[1]}`) {
|
|
202
|
+
// runVersionedSSECloudServer().catch(console.error);
|
|
203
|
+
// }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firecrawl-mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "MCP server for Firecrawl web scraping integration. Supports both cloud and self-hosted instances. Features include web scraping, search, batch processing, structured data extraction, and LLM-powered content analysis.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"test:endpoints": "node test-endpoints.js",
|
|
19
19
|
"start": "node dist/index.js",
|
|
20
20
|
"start:cloud": "CLOUD_SERVICE=true node dist/index.js",
|
|
21
|
+
"start:fastmcp": "node dist/fastmcp/server.js",
|
|
21
22
|
"lint": "eslint src/**/*.ts",
|
|
22
23
|
"lint:fix": "eslint src/**/*.ts --fix",
|
|
23
24
|
"format": "prettier --write .",
|
|
@@ -27,29 +28,11 @@
|
|
|
27
28
|
},
|
|
28
29
|
"license": "MIT",
|
|
29
30
|
"dependencies": {
|
|
30
|
-
"@mendable/firecrawl-js": "^3.
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"express": "^5.1.0",
|
|
34
|
-
"firecrawl-js-current": "npm:@mendable/firecrawl-js@^3.1.0",
|
|
35
|
-
"firecrawl-js-legacy": "npm:@mendable/firecrawl-js@^1.29.3",
|
|
36
|
-
"shx": "^0.3.4",
|
|
31
|
+
"@mendable/firecrawl-js": "^4.3.4",
|
|
32
|
+
"dotenv": "^17.2.2",
|
|
33
|
+
"fastmcp": "^3.16.0",
|
|
37
34
|
"typescript": "^5.9.2",
|
|
38
|
-
"
|
|
39
|
-
},
|
|
40
|
-
"devDependencies": {
|
|
41
|
-
"@jest/globals": "^29.7.0",
|
|
42
|
-
"@types/express": "^5.0.1",
|
|
43
|
-
"@types/jest": "^29.5.14",
|
|
44
|
-
"@types/node": "^20.10.5",
|
|
45
|
-
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
46
|
-
"@typescript-eslint/parser": "^7.0.0",
|
|
47
|
-
"eslint": "^8.56.0",
|
|
48
|
-
"eslint-config-prettier": "^9.1.0",
|
|
49
|
-
"jest": "^29.7.0",
|
|
50
|
-
"jest-mock-extended": "^4.0.0-beta1",
|
|
51
|
-
"prettier": "^3.1.1",
|
|
52
|
-
"ts-jest": "^29.1.1"
|
|
35
|
+
"zod": "^4.1.5"
|
|
53
36
|
},
|
|
54
37
|
"engines": {
|
|
55
38
|
"node": ">=18.0.0"
|
|
@@ -69,5 +52,8 @@
|
|
|
69
52
|
"bugs": {
|
|
70
53
|
"url": "https://github.com/firecrawl/firecrawl-mcp-server/issues"
|
|
71
54
|
},
|
|
72
|
-
"homepage": "https://github.com/firecrawl/firecrawl-mcp-server#readme"
|
|
55
|
+
"homepage": "https://github.com/firecrawl/firecrawl-mcp-server#readme",
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/node": "^24.3.1"
|
|
58
|
+
}
|
|
73
59
|
}
|