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/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
- if (options.formats?.includes('markdown') && response.markdown) {
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 (options.formats?.includes('html') && response.html) {
816
+ if (requestedFormats.includes('html') && response.html) {
814
817
  contentParts.push(response.html);
815
818
  }
816
- if (options.formats?.includes('rawHtml') && response.rawHtml) {
819
+ if (requestedFormats.includes('rawHtml') && response.rawHtml) {
817
820
  contentParts.push(response.rawHtml);
818
821
  }
819
- if (options.formats?.includes('links') && response.links) {
822
+ if (requestedFormats.includes('links') && response.links) {
820
823
  contentParts.push(response.links.join('\n'));
821
824
  }
822
- if (options.formats?.includes('screenshot') && response.screenshot) {
825
+ if (requestedFormats.includes('screenshot') &&
826
+ response.screenshot) {
823
827
  contentParts.push(response.screenshot);
824
828
  }
825
- if (options.formats?.includes('extract') && response.extract) {
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
@@ -14,8 +14,14 @@ export async function runVersionedSSECloudServer() {
14
14
  status: 'OK',
15
15
  versions: ['v1', 'v2'],
16
16
  endpoints: {
17
- v1: '/:apiKey/sse',
18
- v2: '/:apiKey/v2/sse'
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
- const sessionId = req.query.sessionId;
69
- const compositeKey = `${apiKey}-${sessionId}`;
70
- const versionedTransport = transports[compositeKey];
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('*', (req, res) => {
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}/: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`);
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": "2.1.0",
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.0.3",
31
- "@modelcontextprotocol/sdk": "^1.17.3",
32
- "dotenv": "^16.4.7",
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
- "ws": "^8.18.1"
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
  }