firecrawl-mcp 2.0.1 → 2.1.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.
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env node
2
+ import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
3
+ import express from 'express';
4
+ import dotenv from 'dotenv';
5
+ import { createV1Server } from './server-v1.js';
6
+ import { createV2Server } from './server-v2.js';
7
+ dotenv.config();
8
+ export async function runVersionedSSECloudServer() {
9
+ const transports = {};
10
+ const app = express();
11
+ // Health check endpoint
12
+ app.get('/health', (req, res) => {
13
+ res.status(200).json({
14
+ status: 'OK',
15
+ versions: ['v1', 'v2'],
16
+ endpoints: {
17
+ v1: '/:apiKey/sse',
18
+ v2: '/:apiKey/v2/sse'
19
+ }
20
+ });
21
+ });
22
+ // Create server instances
23
+ const v1Server = createV1Server();
24
+ const v2Server = createV2Server();
25
+ // V1 SSE endpoint (legacy)
26
+ app.get('/:apiKey/sse', async (req, res) => {
27
+ const apiKey = req.params.apiKey;
28
+ const transport = new SSEServerTransport(`/${apiKey}/messages`, res);
29
+ console.log(`[V1] New SSE connection for API key: ${apiKey}`);
30
+ const compositeKey = `${apiKey}-${transport.sessionId}`;
31
+ transports[compositeKey] = { transport, version: 'v1' };
32
+ res.on('close', () => {
33
+ console.log(`[V1] SSE connection closed for: ${compositeKey}`);
34
+ delete transports[compositeKey];
35
+ });
36
+ await v1Server.connect(transport);
37
+ });
38
+ // V2 SSE endpoint (new)
39
+ app.get('/:apiKey/v2/sse', async (req, res) => {
40
+ const apiKey = req.params.apiKey;
41
+ const transport = new SSEServerTransport(`/${apiKey}/v2/messages`, res);
42
+ console.log(`[V2] New SSE connection for API key: ${apiKey}`);
43
+ const compositeKey = `${apiKey}-${transport.sessionId}`;
44
+ transports[compositeKey] = { transport, version: 'v2' };
45
+ res.on('close', () => {
46
+ console.log(`[V2] SSE connection closed for: ${compositeKey}`);
47
+ delete transports[compositeKey];
48
+ });
49
+ await v2Server.connect(transport);
50
+ });
51
+ // V1 message endpoint (legacy)
52
+ app.post('/:apiKey/messages', express.json(), async (req, res) => {
53
+ const apiKey = req.params.apiKey;
54
+ const body = req.body;
55
+ // Enrich the body with API key metadata
56
+ const enrichedBody = {
57
+ ...body,
58
+ };
59
+ if (enrichedBody && enrichedBody.params && !enrichedBody.params._meta) {
60
+ enrichedBody.params._meta = { apiKey };
61
+ }
62
+ else if (enrichedBody &&
63
+ enrichedBody.params &&
64
+ enrichedBody.params._meta) {
65
+ enrichedBody.params._meta.apiKey = apiKey;
66
+ }
67
+ console.log(`[V1] Message received for API key: ${apiKey}`);
68
+ const sessionId = req.query.sessionId;
69
+ const compositeKey = `${apiKey}-${sessionId}`;
70
+ const versionedTransport = transports[compositeKey];
71
+ if (versionedTransport && versionedTransport.version === 'v1') {
72
+ await versionedTransport.transport.handlePostMessage(req, res, enrichedBody);
73
+ }
74
+ else {
75
+ console.error(`[V1] No transport found for sessionId: ${compositeKey}`);
76
+ res.status(400).json({
77
+ error: 'No V1 transport found for sessionId',
78
+ sessionId: compositeKey,
79
+ availableTransports: Object.keys(transports)
80
+ });
81
+ }
82
+ });
83
+ // V2 message endpoint (new)
84
+ app.post('/:apiKey/v2/messages', express.json(), async (req, res) => {
85
+ const apiKey = req.params.apiKey;
86
+ const body = req.body;
87
+ // Enrich the body with API key metadata
88
+ const enrichedBody = {
89
+ ...body,
90
+ };
91
+ if (enrichedBody && enrichedBody.params && !enrichedBody.params._meta) {
92
+ enrichedBody.params._meta = { apiKey };
93
+ }
94
+ else if (enrichedBody &&
95
+ enrichedBody.params &&
96
+ enrichedBody.params._meta) {
97
+ enrichedBody.params._meta.apiKey = apiKey;
98
+ }
99
+ console.log(`[V2] Message received for API key: ${apiKey}`);
100
+ const sessionId = req.query.sessionId;
101
+ const compositeKey = `${apiKey}-${sessionId}`;
102
+ const versionedTransport = transports[compositeKey];
103
+ if (versionedTransport && versionedTransport.version === 'v2') {
104
+ await versionedTransport.transport.handlePostMessage(req, res, enrichedBody);
105
+ }
106
+ else {
107
+ console.error(`[V2] No transport found for sessionId: ${compositeKey}`);
108
+ res.status(400).json({
109
+ error: 'No V2 transport found for sessionId',
110
+ sessionId: compositeKey,
111
+ availableTransports: Object.keys(transports)
112
+ });
113
+ }
114
+ });
115
+ // Catch-all for unsupported endpoints
116
+ app.use('*', (req, res) => {
117
+ res.status(404).json({
118
+ error: 'Endpoint not found',
119
+ supportedEndpoints: {
120
+ health: '/health',
121
+ v1: {
122
+ sse: '/:apiKey/sse',
123
+ messages: '/:apiKey/messages'
124
+ },
125
+ v2: {
126
+ sse: '/:apiKey/v2/sse',
127
+ messages: '/:apiKey/v2/messages'
128
+ }
129
+ }
130
+ });
131
+ });
132
+ const PORT = process.env.PORT || 3000;
133
+ app.listen(PORT, () => {
134
+ console.log(`šŸš€ Versioned MCP SSE Server listening on http://localhost:${PORT}`);
135
+ console.log('šŸ“‹ Available endpoints:');
136
+ console.log(` Health: http://localhost:${PORT}/health`);
137
+ console.log(` V1 SSE: http://localhost:${PORT}/:apiKey/sse`);
138
+ console.log(` V1 Messages: http://localhost:${PORT}/:apiKey/messages`);
139
+ console.log(` V2 SSE: http://localhost:${PORT}/:apiKey/v2/sse`);
140
+ console.log(` V2 Messages: http://localhost:${PORT}/:apiKey/v2/messages`);
141
+ console.log('');
142
+ console.log('šŸ”§ Versions:');
143
+ console.log(' V1: Firecrawl JS 1.29.3 (legacy tools + deep research + llms.txt)');
144
+ console.log(' V2: Firecrawl JS 3.1.0 (modern API + JSON extraction)');
145
+ });
146
+ // Graceful shutdown
147
+ process.on('SIGINT', () => {
148
+ console.log('\nšŸ›‘ Shutting down server...');
149
+ console.log(`šŸ“Š Active connections: ${Object.keys(transports).length}`);
150
+ // Close all transports
151
+ for (const [key, versionedTransport] of Object.entries(transports)) {
152
+ try {
153
+ console.log(`šŸ”Œ Closing transport: ${key} (${versionedTransport.version})`);
154
+ // Note: SSEServerTransport doesn't have a close method, connections will close naturally
155
+ delete transports[key];
156
+ }
157
+ catch (error) {
158
+ console.error(`āŒ Error closing transport ${key}:`, error);
159
+ }
160
+ }
161
+ console.log('āœ… Server shutdown complete');
162
+ process.exit(0);
163
+ });
164
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "firecrawl-mcp",
3
- "version": "2.0.1",
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.",
3
+ "version": "2.1.0",
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": {
7
7
  "firecrawl-mcp": "dist/index.js"
@@ -15,7 +15,9 @@
15
15
  "scripts": {
16
16
  "build": "tsc && node -e \"require('fs').chmodSync('dist/index.js', '755')\"",
17
17
  "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
18
+ "test:endpoints": "node test-endpoints.js",
18
19
  "start": "node dist/index.js",
20
+ "start:cloud": "CLOUD_SERVICE=true node dist/index.js",
19
21
  "lint": "eslint src/**/*.ts",
20
22
  "lint:fix": "eslint src/**/*.ts --fix",
21
23
  "format": "prettier --write .",
@@ -29,6 +31,8 @@
29
31
  "@modelcontextprotocol/sdk": "^1.17.3",
30
32
  "dotenv": "^16.4.7",
31
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",
32
36
  "shx": "^0.3.4",
33
37
  "typescript": "^5.9.2",
34
38
  "ws": "^8.18.1"