Skip to content

Commit 52b173e

Browse files
committed
feat:
1 parent 01e3b8a commit 52b173e

6 files changed

Lines changed: 388 additions & 12 deletions

File tree

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "firecrawl-mcp",
3-
"version": "3.20.2",
3+
"version": "3.20.3",
44
"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.",
55
"type": "module",
66
"mcpName": "io.github.firecrawl/firecrawl-mcp-server",
@@ -28,7 +28,7 @@
2828
},
2929
"license": "MIT",
3030
"dependencies": {
31-
"@mendable/firecrawl-js": "4.24.0",
31+
"@mendable/firecrawl-js": "4.25.2",
3232
"dotenv": "^17.2.2",
3333
"firecrawl-fastmcp": "^1.0.5",
3434
"typescript": "^5.9.2",

pnpm-lock.yaml

Lines changed: 54 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
allowBuilds:
2+
tldjs: true

src/index.ts

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { readFile } from 'node:fs/promises';
77
import path from 'node:path';
88
import { z } from 'zod';
99
import { registerMonitorTools } from './monitor.js';
10+
import { registerResearchTools } from './research.js';
1011

1112
dotenv.config({ debug: false, quiet: true });
1213

@@ -16,9 +17,37 @@ interface SessionData {
1617
* `Authorization: Bearer ...` to the Firecrawl API.
1718
*/
1819
firecrawlApiKey?: string;
20+
/**
21+
* Whether the (experimental) research tools are exposed for this session.
22+
* Enabled locally via `FIRECRAWL_RESEARCH=true`, or per-request via the
23+
* `?research=true` query param on the MCP endpoint.
24+
*/
25+
research?: boolean;
1926
[key: string]: unknown;
2027
}
2128

29+
/**
30+
* Decide whether the research tools should be visible for a session.
31+
* Local/stdio/self-hosted: gated by `FIRECRAWL_RESEARCH=true`.
32+
* Remote (HTTP): additionally enabled by a `?research=true` query param on the
33+
* incoming MCP request URL.
34+
*/
35+
function isResearchEnabled(request?: { url?: string }): boolean {
36+
if (process.env.FIRECRAWL_RESEARCH === 'true') return true;
37+
const url = request?.url;
38+
if (url) {
39+
try {
40+
const research = new URL(url, 'http://localhost').searchParams.get(
41+
'research'
42+
);
43+
if (research === 'true') return true;
44+
} catch {
45+
// malformed URL — fall through to disabled
46+
}
47+
}
48+
return false;
49+
}
50+
2251
function normalizeHeader(
2352
value: string | string[] | undefined
2453
): string | undefined {
@@ -253,7 +282,9 @@ const server = new FastMCP<SessionData>({
253282
},
254283
authenticate: async (request?: {
255284
headers: IncomingHttpHeaders;
285+
url?: string;
256286
}): Promise<SessionData> => {
287+
const research = isResearchEnabled(request);
257288
// FastMCP invokes `authenticate(undefined)` for the stdio transport
258289
// because there is no HTTP request context. Without this null guard,
259290
// accessing `request.headers` throws a TypeError, FastMCP silently
@@ -271,7 +302,7 @@ const server = new FastMCP<SessionData>({
271302
'Firecrawl credentials required: OAuth access token (Authorization: Bearer fco_...) or API key (x-firecrawl-api-key)'
272303
);
273304
}
274-
return { firecrawlApiKey: headerCred };
305+
return { firecrawlApiKey: headerCred, research };
275306
}
276307
277308
const credential = headerCred ?? envCred;
@@ -296,7 +327,7 @@ const server = new FastMCP<SessionData>({
296327
process.exit(1);
297328
}
298329
299-
return { firecrawlApiKey: credential };
330+
return { firecrawlApiKey: credential, research };
300331
},
301332
// Lightweight health endpoint for LB checks
302333
health: {
@@ -1808,4 +1839,20 @@ if (
18081839

18091840
registerMonitorTools(server);
18101841

1842+
// Research tools gating. FastMCP's `canAccess` is only honored on the HTTP
1843+
// transport (the stdio path exposes every registered tool regardless), so we
1844+
// split the two cases:
1845+
// - HTTP (cloud / SSE_LOCAL / HTTP_STREAMABLE_SERVER): always register; each
1846+
// tool's `canAccess` hides it unless the session has research enabled
1847+
// (`FIRECRAWL_RESEARCH=true` env or `?research=true` on the request).
1848+
// - stdio (local): register only when `FIRECRAWL_RESEARCH=true`, since
1849+
// `canAccess` cannot hide them there.
1850+
const isHttpTransport =
1851+
process.env.CLOUD_SERVICE === 'true' ||
1852+
process.env.SSE_LOCAL === 'true' ||
1853+
process.env.HTTP_STREAMABLE_SERVER === 'true';
1854+
if (isHttpTransport || process.env.FIRECRAWL_RESEARCH === 'true') {
1855+
registerResearchTools(server, getClient);
1856+
}
1857+
18111858
await server.start(args);

0 commit comments

Comments
 (0)