Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## [3.30.1] - 2026-10-14

### Added

- `firecrawl_find_tools` accepts `includeCosts`. When it is `true`, tool results carry per-capability credit prices as before, and `nextTool` arguments keep the flag. The server handles it locally and does not send it upstream.

### Changed

- Alexandria output omits per-capability credit prices by default. `creditsCost`, `perRecord`, `billing`, `recordsPerUnit` and `zeroDataRetentionCreditsCost` are dropped from `firecrawl_find_tools` items and related tools, from `data.tools` in `firecrawl_search` and URL-mode `firecrawl_scrape`, and from each Alexandria result entry. Execution request totals such as `data.creditsCost` are unchanged; the free `firecrawl_find_tools` response omits its zero total.
- Hosted instructions say Alexandria execution is billed per capability, replacing "billed at the capability's listed price".

## [3.30.0] - 2026-10-14

### Added
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1112,14 +1112,16 @@ Start with no arguments for categories, then progressively narrow the catalogue:
| --- | --- |
| `{}` | Categories and short descriptions |
| `{"categories":["podcasts"]}` | Providers in that category |
| `{"categories":["podcasts"],"providers":["particle"]}` | Compact tool names, descriptions, and prices |
| `{"categories":["podcasts"],"providers":["particle"]}` | Compact tool names and descriptions |
| `{"providers":["particle"],"capabilities":["podcasts/episodes/search"]}` | Complete selected inputs, constraints, response, and examples |

No group hop is required. Explicit `level` supports `categories`, `providers`,
`groups`, or `tools`; explicit `expand` selects contract sections. `expand: []`
keeps results compact even when selecting a capability. For broad full contracts,
explicitly request `level: "tools"` and `expand: ["options", "response", "examples"]`.
URLs provide contextual discovery without fetching the page.
URLs provide contextual discovery without fetching the page. Tool results omit
per-capability credit prices unless `includeCosts: true` is set; follow-up
`nextTool` arguments keep it.

Results are in `data.alexandria[0].data`. Follow an item's `nextTool` by calling
its `name` with its `arguments`; the page's `nextTool` advances pagination.
Expand All @@ -1146,7 +1148,7 @@ These controls require the matching Alexandria API deployment.
}
```

**Returns:** `{ success, scrape_id, requestId, data: { alexandria: [...], creditsCost } }`. Each item is either a result (`provider`, `capability`, `creditsCost`, `data`, `records`, `upstreamStatus`) or an `error` with a `code`; the batch never fails as a whole for a provider error and `data.creditsCost` sums the successful items. Alexandria error bodies (403 without Alexandria access, and 402/409 billing statuses) are relayed in-band with their `code`.
**Returns:** `{ success, scrape_id, requestId, data: { alexandria: [...], creditsCost } }`. Each item is either a result (`provider`, `capability`, `data`, `records`, `upstreamStatus`) or an `error` with a `code`; the batch never fails as a whole for a provider error and `data.creditsCost` is the total charged for the request. Alexandria error bodies (403 without Alexandria access, and 402/409 billing statuses) are relayed in-band with their `code`.

Execution generates one `x-request-id` and returns it on success or failure. Retry
the identical payload with that `requestId`; do not create a new ID after an
Expand Down
2 changes: 1 addition & 1 deletion gemini-extension.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "firecrawl",
"version": "3.30.0",
"version": "3.30.1",
"description": "Official Firecrawl MCP for web search, scraping, crawling, and structured data extraction.",
"mcpServers": {
"firecrawl": {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "firecrawl-mcp",
"version": "3.30.0",
"version": "3.30.1",
"description": "MCP server for Firecrawl — search, scrape, and interact with the web, and search scientific papers. Supports both cloud and self-hosted instances. Features include web search, scraping, page interaction, batch processing, LLM-powered content analysis, and research paper search over biomedical and arXiv literature (PubMed, bioRxiv, medRxiv, arXiv) with citation-graph expansion and full-text reading.",
"type": "module",
"mcpName": "io.github.firecrawl/firecrawl-mcp-server",
Expand Down
4 changes: 2 additions & 2 deletions server.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "io.github.firecrawl/firecrawl-mcp-server",
"title": "Firecrawl MCP Server",
"description": "MCP server for Firecrawl — web search, scraping, and biomedical/arXiv paper search.",
"version": "3.30.0",
"version": "3.30.1",
"repository": {
"url": "https://github.com/firecrawl/firecrawl-mcp-server.git",
"source": "github"
Expand All @@ -12,7 +12,7 @@
{
"registryType": "npm",
"identifier": "firecrawl-mcp",
"version": "3.30.0",
"version": "3.30.1",
"transport": {
"type": "stdio"
},
Expand Down
67 changes: 64 additions & 3 deletions src/alexandria.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const findToolsSchema = z
expand: z.array(z.enum(['options', 'response', 'examples'])).optional().describe('Use ["options", "response"] for inputs and output shape without example payloads; [] keeps results compact. Omit for full contracts on selected capabilities.'),
limit: z.number().int().min(1).max(100).optional(),
offset: z.number().int().nonnegative().optional(),
includeCosts: z.boolean().optional().describe('Include per-capability credit prices (creditsCost, perRecord) in tool results.'),
})
.strict();

Expand All @@ -81,17 +82,18 @@ export function findToolsOptions(args: z.infer<typeof findToolsSchema>) {
if (level === 'categories' && (args.query || args.providers?.length || args.categories?.length || args.groups?.length || args.capabilities?.length || args.urls?.length || args.expand !== undefined)) {
throw new Error('Category index accepts only level, limit and offset. Select a category to browse providers.');
}
const { expand, ...selectors } = args;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { expand, includeCosts, ...selectors } = args;
return { ...selectors, ...(expand !== undefined ? { expand } : {}), level, limit: args.limit ?? 20,
...(args.expand === undefined && level === 'tools' && args.capabilities?.length ? { expand: ['options', 'response', 'examples'] as const } : {}),
};
}

export function withFindToolsNavigation(envelope: any) {
export function withFindToolsNavigation(envelope: any, carry: Record<string, unknown> = {}) {
const page = envelope?.data?.alexandria?.[0]?.data;
if (!page || !Array.isArray(page.items)) return envelope;
const link = (next: any) => next?.provider === 'firecrawl' && next?.capability === 'find-tools' && next.options
? { name: 'firecrawl_find_tools', arguments: next.options } : undefined;
? { name: 'firecrawl_find_tools', arguments: { ...next.options, ...carry } } : undefined;
for (const item of page.items) {
if (page.level === 'providers' && link(item.next)) {
const selectors = { ...item.next.options };
Expand All @@ -107,3 +109,62 @@ export function withFindToolsNavigation(envelope: any) {

export const ALEXANDRIA_SEARCH_INSTRUCTIONS =
'Authenticated search combines web results, semantic tool summaries and domain matches. Use sources: ["alexandria"] for semantic tools only. ' + ALEXANDRIA_SOURCES_OPT_OUT + ' Tool matches describe available structured-data capabilities, not executed data. toolDetail: "compact" (default) returns only provider, capability and description; "summary" adds metadata; "full" includes their input and output contracts. firecrawl_scrape with an alexandria body executes a selected capability; firecrawl_find_tools provides catalogue browsing and full contracts.';

const TOOL_PRICE_KEYS = ['creditsCost', 'perRecord', 'billing', 'recordsPerUnit', 'zeroDataRetentionCreditsCost'];

/** Drops per-capability price metadata from a discovered tool or an execution result entry. */
export function withoutToolPrice(tool: any) {
if (!tool || typeof tool !== 'object' || Array.isArray(tool)) return tool;
const copy = { ...tool };
for (const key of TOOL_PRICE_KEYS) delete copy[key];
return copy;
}

/** Strips per-capability prices from `data.tools` in a search or URL scrape response. */
export function withoutDiscoveredToolPrices(body: any) {
const tools = body?.data?.tools ?? body?.tools;
if (!Array.isArray(tools)) return body;
return body?.data?.tools
? { ...body, data: { ...body.data, tools: tools.map(withoutToolPrice) } }
: { ...body, tools: tools.map(withoutToolPrice) };
}

function withoutPagePrices(page: any) {
if (!page || typeof page !== 'object') return page;
return {
...page,
...(Array.isArray(page.items) ? { items: page.items.map(withoutToolPrice) } : {}),
...(Array.isArray(page.relatedTools) ? {
relatedTools: page.relatedTools.map((group: any) => Array.isArray(group?.tools)
? { ...group, tools: group.tools.map((entry: any) => entry?.tool ? { ...entry, tool: withoutToolPrice(entry.tool) } : withoutToolPrice(entry)) }
: group),
} : {}),
};
}

/** Strips the per-capability cost from each Alexandria result entry, and tool prices from Find Tools pages. */
export function withoutResultPrices(body: any) {
const results = body?.data?.alexandria;
if (!Array.isArray(results)) return body;
return {
...body,
data: {
...body.data,
alexandria: results.map((entry: any) => {
const stripped = withoutToolPrice(entry);
return entry?.provider === 'firecrawl' && entry?.capability === 'find-tools' && stripped && typeof stripped === 'object'
? { ...stripped, data: withoutPagePrices(entry.data) }
: stripped;
}),
},
};
}

/** Find Tools is free; its envelope drops the zero total along with tool prices. */
export function withoutFindToolsPrices(envelope: any) {
const stripped = withoutResultPrices(envelope);
if (!stripped?.data || typeof stripped.data !== 'object') return stripped;
const data = { ...stripped.data };
delete data.creditsCost;
return { ...stripped, data };
}
26 changes: 16 additions & 10 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ import {
findToolsSchema,
findToolsOptions,
withFindToolsNavigation,
withoutDiscoveredToolPrices,
withoutFindToolsPrices,
withoutResultPrices,
hasAlexandria,
defaultDomainTools,
normalizeSearchSources,
Expand Down Expand Up @@ -1487,7 +1490,7 @@ Alexandria mode, on an authenticated session with Alexandria access: \`alexandri
},
session
);
return structuredText(json?.data ?? json);
return structuredText(withoutDiscoveredToolPrices(json?.data ?? json));
}
const client = getClient(session);
const res = await relayTermsRequired(
Expand All @@ -1498,7 +1501,7 @@ Alexandria mode, on an authenticated session with Alexandria access: \`alexandri
} as any),
{ tool: 'firecrawl_scrape' }
);
return structuredText(res);
return structuredText(withoutDiscoveredToolPrices(res));
},
};
server.addTool({
Expand Down Expand Up @@ -1616,7 +1619,7 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
// identifier to keyless clients, where it would invite an unusable call.
const keylessResponse = { ...(json ?? {}) };
delete keylessResponse.id;
return structuredCompact(keylessResponse);
return structuredCompact(withoutDiscoveredToolPrices(keylessResponse));
}
// Call /v2/search through the SDK's HTTP layer (auth + retries) instead
// of `client.search()` so we preserve the full response envelope. The
Expand All @@ -1633,7 +1636,7 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
const httpRes = exchangeSource
? await relayExchangeError(postSearch, context)
: await relayTermsRequired(postSearch, context);
return structuredCompact(httpRes?.data ?? {});
return structuredCompact(withoutDiscoveredToolPrices(httpRes?.data ?? {}));
},
});

Expand All @@ -1644,7 +1647,8 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
| z.infer<typeof exchangeCallsSchema>,
suppliedRequestId?: string,
timeout?: number,
origin = requestOrigin(undefined, session)
origin = requestOrigin(undefined, session),
includeCosts = false
): Promise<string> {
assertExchangeCredential(session);
const client = getClient(session);
Expand All @@ -1667,7 +1671,7 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
);
const calls = Array.isArray(alexandria) ? alexandria : [alexandria];
return alexandriaOutput(
{ ...(response?.data ?? {}), requestId },
{ ...(includeCosts ? (response?.data ?? {}) : withoutResultPrices(response?.data ?? {})), requestId },
calls,
async (body, id) => {
const result = await (client as any).http.post(
Expand Down Expand Up @@ -1709,6 +1713,7 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
let options;
try { options = findToolsOptions(args as Parameters<typeof findToolsOptions>[0]); }
catch (error) { throw new UserError(error instanceof Error ? error.message : String(error)); }
const carry: { includeCosts?: true } = (args as { includeCosts?: boolean }).includeCosts ? { includeCosts: true } : {};
if (options.level === 'categories') {
assertExchangeCredential(session);
const response = await relayExchangeError(
Expand All @@ -1725,11 +1730,12 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
next: { provider: 'firecrawl', capability: 'find-tools', options: { categories: [row.cohort], level: 'providers', limit: options.limit } },
}));
const page: any = { level: 'categories', items, total: rows.length };
if (offset + options.limit < rows.length) page.nextTool = { name: 'firecrawl_find_tools', arguments: { level: 'categories', limit: options.limit, offset: offset + options.limit } };
return structuredCompact(withAlexandriaFeedbackHint(withFindToolsNavigation({ success: true, data: { creditsCost: 0, alexandria: [{ provider: 'firecrawl', capability: 'find-tools', creditsCost: 0, data: page }] } }), alexandriaFeedbackAvailable()));
if (offset + options.limit < rows.length) page.nextTool = { name: 'firecrawl_find_tools', arguments: { level: 'categories', limit: options.limit, offset: offset + options.limit, ...carry } };
return structuredCompact(withAlexandriaFeedbackHint(withFindToolsNavigation({ success: true, data: { alexandria: [{ provider: 'firecrawl', capability: 'find-tools', data: page }] } }, carry), alexandriaFeedbackAvailable()));
}
const result = await executeExchangeCalls(session, { provider: 'firecrawl', capability: 'find-tools', options }, undefined, undefined, origin);
return structuredCompact(withFindToolsNavigation(JSON.parse(result)));
const result = await executeExchangeCalls(session, { provider: 'firecrawl', capability: 'find-tools', options }, undefined, undefined, origin, carry.includeCosts);
const envelope = JSON.parse(result);
return structuredCompact(withFindToolsNavigation(carry.includeCosts ? envelope : withoutFindToolsPrices(envelope), carry));
},
};
server.addTool(findToolsTool);
Expand Down
2 changes: 1 addition & 1 deletion src/service/profiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export function getOAuthIssuer(): string {
// uses them.
export const SEARCH_PROFILE_INSTRUCTIONS =
ALEXANDRIA_SEARCH_INSTRUCTIONS +
` Firecrawl provides web, developer, government, and research search, and executes catalogued Alexandria data providers. Use firecrawl_search to find relevant results across the web and specialized indexes; authenticated searches also return matching Alexandria providers in data.tools. firecrawl_find_tools provides catalogue browsing and provider contracts; firecrawl_scrape with an alexandria body executes a selected capability; firecrawl_scrape with a url retrieves one supplied page. For a programming question, firecrawl_developer_search searches indexed public repositories, GitHub issues, merged pull requests, READMEs, and code documentation and returns the matched passages, and skills: "only" narrows it to agent-skill files; firecrawl_search with categories: ["developer"] reaches the same index beside ordinary web results, returning the hits in the web group rather than as passages and offering no skills filter. For a legal or regulatory question, firecrawl_gov_search searches primary law and regulatory material from US federal, state, and local government sources: statutes, regulations, codes, court opinions, and other government publications; firecrawl_search with categories: ["gov"] reaches the same sources in the web group and cannot be combined with other categories. For a biomedical, life-science, clinical, or arXiv literature question, the firecrawl_research_* tools search the paper index, while categories: ["research"] on firecrawl_search filters ordinary web results to research-affiliated websites. Use the firecrawl_research_* tools to search academic and research literature, expand from anchor papers via the citation graph, and read full-text passages from a specific paper. Search and discovery tools are read-only and return ranked results. Billing: web, developer, and research search are billed per request; government search is free; Alexandria discovery (firecrawl_search with sources ["alexandria"] alone, and firecrawl_find_tools) is free; firecrawl_scrape is billed, as a page retrieval in url mode or at each executed capability's listed price in alexandria mode.`;
` Firecrawl provides web, developer, government, and research search, and executes catalogued Alexandria data providers. Use firecrawl_search to find relevant results across the web and specialized indexes; authenticated searches also return matching Alexandria providers in data.tools. firecrawl_find_tools provides catalogue browsing and provider contracts; firecrawl_scrape with an alexandria body executes a selected capability; firecrawl_scrape with a url retrieves one supplied page. For a programming question, firecrawl_developer_search searches indexed public repositories, GitHub issues, merged pull requests, READMEs, and code documentation and returns the matched passages, and skills: "only" narrows it to agent-skill files; firecrawl_search with categories: ["developer"] reaches the same index beside ordinary web results, returning the hits in the web group rather than as passages and offering no skills filter. For a legal or regulatory question, firecrawl_gov_search searches primary law and regulatory material from US federal, state, and local government sources: statutes, regulations, codes, court opinions, and other government publications; firecrawl_search with categories: ["gov"] reaches the same sources in the web group and cannot be combined with other categories. For a biomedical, life-science, clinical, or arXiv literature question, the firecrawl_research_* tools search the paper index, while categories: ["research"] on firecrawl_search filters ordinary web results to research-affiliated websites. Use the firecrawl_research_* tools to search academic and research literature, expand from anchor papers via the citation graph, and read full-text passages from a specific paper. Search and discovery tools are read-only and return ranked results. Billing: web, developer, and research search are billed per request; government search is free; Alexandria discovery (firecrawl_search with sources ["alexandria"] alone, and firecrawl_find_tools) is free; firecrawl_scrape is billed, as a page retrieval in url mode or per executed capability in alexandria mode (pass includeCosts to firecrawl_find_tools to see prices).`;

// The exact set of tools the search surface exposes. Registration is filtered
// against this set, so anything not listed here can never appear on that
Expand Down
Loading
Loading