Skip to content

Commit d083a62

Browse files
Hide Alexandria capability prices by default; add includeCosts to find_tools (#538)
* Hide Alexandria capability prices by default; add includeCosts to find_tools Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore: release 3.30.1 Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: scope creditsCost changelog note Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Update search-surface.ts --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
1 parent 83f7e6a commit d083a62

22 files changed

Lines changed: 169 additions & 41 deletions

‎CHANGELOG.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# Changelog
22

3+
## [3.30.1] - 2026-10-14
4+
5+
### Added
6+
7+
- `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.
8+
9+
### Changed
10+
11+
- 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.
12+
- Hosted instructions say Alexandria execution is billed per capability, replacing "billed at the capability's listed price".
13+
314
## [3.30.0] - 2026-10-14
415

516
### Added

‎README.md‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,14 +1112,16 @@ Start with no arguments for categories, then progressively narrow the catalogue:
11121112
| --- | --- |
11131113
| `{}` | Categories and short descriptions |
11141114
| `{"categories":["podcasts"]}` | Providers in that category |
1115-
| `{"categories":["podcasts"],"providers":["particle"]}` | Compact tool names, descriptions, and prices |
1115+
| `{"categories":["podcasts"],"providers":["particle"]}` | Compact tool names and descriptions |
11161116
| `{"providers":["particle"],"capabilities":["podcasts/episodes/search"]}` | Complete selected inputs, constraints, response, and examples |
11171117

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

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

1149-
**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`.
1151+
**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`.
11501152

11511153
Execution generates one `x-request-id` and returns it on success or failure. Retry
11521154
the identical payload with that `requestId`; do not create a new ID after an

‎gemini-extension.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "firecrawl",
3-
"version": "3.30.0",
3+
"version": "3.30.1",
44
"description": "Official Firecrawl MCP for web search, scraping, crawling, and structured data extraction.",
55
"mcpServers": {
66
"firecrawl": {

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "firecrawl-mcp",
3-
"version": "3.30.0",
3+
"version": "3.30.1",
44
"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.",
55
"type": "module",
66
"mcpName": "io.github.firecrawl/firecrawl-mcp-server",

‎server.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"name": "io.github.firecrawl/firecrawl-mcp-server",
44
"title": "Firecrawl MCP Server",
55
"description": "MCP server for Firecrawl — web search, scraping, and biomedical/arXiv paper search.",
6-
"version": "3.30.0",
6+
"version": "3.30.1",
77
"repository": {
88
"url": "https://github.com/firecrawl/firecrawl-mcp-server.git",
99
"source": "github"
@@ -12,7 +12,7 @@
1212
{
1313
"registryType": "npm",
1414
"identifier": "firecrawl-mcp",
15-
"version": "3.30.0",
15+
"version": "3.30.1",
1616
"transport": {
1717
"type": "stdio"
1818
},

‎src/alexandria.ts‎

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export const findToolsSchema = z
5858
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.'),
5959
limit: z.number().int().min(1).max(100).optional(),
6060
offset: z.number().int().nonnegative().optional(),
61+
includeCosts: z.boolean().optional().describe('Include per-capability credit prices (creditsCost, perRecord) in tool results.'),
6162
})
6263
.strict();
6364

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

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

108110
export const ALEXANDRIA_SEARCH_INSTRUCTIONS =
109111
'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.';
112+
113+
const TOOL_PRICE_KEYS = ['creditsCost', 'perRecord', 'billing', 'recordsPerUnit', 'zeroDataRetentionCreditsCost'];
114+
115+
/** Drops per-capability price metadata from a discovered tool or an execution result entry. */
116+
export function withoutToolPrice(tool: any) {
117+
if (!tool || typeof tool !== 'object' || Array.isArray(tool)) return tool;
118+
const copy = { ...tool };
119+
for (const key of TOOL_PRICE_KEYS) delete copy[key];
120+
return copy;
121+
}
122+
123+
/** Strips per-capability prices from `data.tools` in a search or URL scrape response. */
124+
export function withoutDiscoveredToolPrices(body: any) {
125+
const tools = body?.data?.tools ?? body?.tools;
126+
if (!Array.isArray(tools)) return body;
127+
return body?.data?.tools
128+
? { ...body, data: { ...body.data, tools: tools.map(withoutToolPrice) } }
129+
: { ...body, tools: tools.map(withoutToolPrice) };
130+
}
131+
132+
function withoutPagePrices(page: any) {
133+
if (!page || typeof page !== 'object') return page;
134+
return {
135+
...page,
136+
...(Array.isArray(page.items) ? { items: page.items.map(withoutToolPrice) } : {}),
137+
...(Array.isArray(page.relatedTools) ? {
138+
relatedTools: page.relatedTools.map((group: any) => Array.isArray(group?.tools)
139+
? { ...group, tools: group.tools.map((entry: any) => entry?.tool ? { ...entry, tool: withoutToolPrice(entry.tool) } : withoutToolPrice(entry)) }
140+
: group),
141+
} : {}),
142+
};
143+
}
144+
145+
/** Strips the per-capability cost from each Alexandria result entry, and tool prices from Find Tools pages. */
146+
export function withoutResultPrices(body: any) {
147+
const results = body?.data?.alexandria;
148+
if (!Array.isArray(results)) return body;
149+
return {
150+
...body,
151+
data: {
152+
...body.data,
153+
alexandria: results.map((entry: any) => {
154+
const stripped = withoutToolPrice(entry);
155+
return entry?.provider === 'firecrawl' && entry?.capability === 'find-tools' && stripped && typeof stripped === 'object'
156+
? { ...stripped, data: withoutPagePrices(entry.data) }
157+
: stripped;
158+
}),
159+
},
160+
};
161+
}
162+
163+
/** Find Tools is free; its envelope drops the zero total along with tool prices. */
164+
export function withoutFindToolsPrices(envelope: any) {
165+
const stripped = withoutResultPrices(envelope);
166+
if (!stripped?.data || typeof stripped.data !== 'object') return stripped;
167+
const data = { ...stripped.data };
168+
delete data.creditsCost;
169+
return { ...stripped, data };
170+
}

‎src/server.ts‎

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ import {
4141
findToolsSchema,
4242
findToolsOptions,
4343
withFindToolsNavigation,
44+
withoutDiscoveredToolPrices,
45+
withoutFindToolsPrices,
46+
withoutResultPrices,
4447
hasAlexandria,
4548
defaultDomainTools,
4649
normalizeSearchSources,
@@ -1487,7 +1490,7 @@ Alexandria mode, on an authenticated session with Alexandria access: \`alexandri
14871490
},
14881491
session
14891492
);
1490-
return structuredText(json?.data ?? json);
1493+
return structuredText(withoutDiscoveredToolPrices(json?.data ?? json));
14911494
}
14921495
const client = getClient(session);
14931496
const res = await relayTermsRequired(
@@ -1498,7 +1501,7 @@ Alexandria mode, on an authenticated session with Alexandria access: \`alexandri
14981501
} as any),
14991502
{ tool: 'firecrawl_scrape' }
15001503
);
1501-
return structuredText(res);
1504+
return structuredText(withoutDiscoveredToolPrices(res));
15021505
},
15031506
};
15041507
server.addTool({
@@ -1616,7 +1619,7 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
16161619
// identifier to keyless clients, where it would invite an unusable call.
16171620
const keylessResponse = { ...(json ?? {}) };
16181621
delete keylessResponse.id;
1619-
return structuredCompact(keylessResponse);
1622+
return structuredCompact(withoutDiscoveredToolPrices(keylessResponse));
16201623
}
16211624
// Call /v2/search through the SDK's HTTP layer (auth + retries) instead
16221625
// of `client.search()` so we preserve the full response envelope. The
@@ -1633,7 +1636,7 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
16331636
const httpRes = exchangeSource
16341637
? await relayExchangeError(postSearch, context)
16351638
: await relayTermsRequired(postSearch, context);
1636-
return structuredCompact(httpRes?.data ?? {});
1639+
return structuredCompact(withoutDiscoveredToolPrices(httpRes?.data ?? {}));
16371640
},
16381641
});
16391642

@@ -1644,7 +1647,8 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
16441647
| z.infer<typeof exchangeCallsSchema>,
16451648
suppliedRequestId?: string,
16461649
timeout?: number,
1647-
origin = requestOrigin(undefined, session)
1650+
origin = requestOrigin(undefined, session),
1651+
includeCosts = false
16481652
): Promise<string> {
16491653
assertExchangeCredential(session);
16501654
const client = getClient(session);
@@ -1667,7 +1671,7 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
16671671
);
16681672
const calls = Array.isArray(alexandria) ? alexandria : [alexandria];
16691673
return alexandriaOutput(
1670-
{ ...(response?.data ?? {}), requestId },
1674+
{ ...(includeCosts ? (response?.data ?? {}) : withoutResultPrices(response?.data ?? {})), requestId },
16711675
calls,
16721676
async (body, id) => {
16731677
const result = await (client as any).http.post(
@@ -1709,6 +1713,7 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
17091713
let options;
17101714
try { options = findToolsOptions(args as Parameters<typeof findToolsOptions>[0]); }
17111715
catch (error) { throw new UserError(error instanceof Error ? error.message : String(error)); }
1716+
const carry: { includeCosts?: true } = (args as { includeCosts?: boolean }).includeCosts ? { includeCosts: true } : {};
17121717
if (options.level === 'categories') {
17131718
assertExchangeCredential(session);
17141719
const response = await relayExchangeError(
@@ -1725,11 +1730,12 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
17251730
next: { provider: 'firecrawl', capability: 'find-tools', options: { categories: [row.cohort], level: 'providers', limit: options.limit } },
17261731
}));
17271732
const page: any = { level: 'categories', items, total: rows.length };
1728-
if (offset + options.limit < rows.length) page.nextTool = { name: 'firecrawl_find_tools', arguments: { level: 'categories', limit: options.limit, offset: offset + options.limit } };
1729-
return structuredCompact(withAlexandriaFeedbackHint(withFindToolsNavigation({ success: true, data: { creditsCost: 0, alexandria: [{ provider: 'firecrawl', capability: 'find-tools', creditsCost: 0, data: page }] } }), alexandriaFeedbackAvailable()));
1733+
if (offset + options.limit < rows.length) page.nextTool = { name: 'firecrawl_find_tools', arguments: { level: 'categories', limit: options.limit, offset: offset + options.limit, ...carry } };
1734+
return structuredCompact(withAlexandriaFeedbackHint(withFindToolsNavigation({ success: true, data: { alexandria: [{ provider: 'firecrawl', capability: 'find-tools', data: page }] } }, carry), alexandriaFeedbackAvailable()));
17301735
}
1731-
const result = await executeExchangeCalls(session, { provider: 'firecrawl', capability: 'find-tools', options }, undefined, undefined, origin);
1732-
return structuredCompact(withFindToolsNavigation(JSON.parse(result)));
1736+
const result = await executeExchangeCalls(session, { provider: 'firecrawl', capability: 'find-tools', options }, undefined, undefined, origin, carry.includeCosts);
1737+
const envelope = JSON.parse(result);
1738+
return structuredCompact(withFindToolsNavigation(carry.includeCosts ? envelope : withoutFindToolsPrices(envelope), carry));
17331739
},
17341740
};
17351741
server.addTool(findToolsTool);

‎src/service/profiles.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export function getOAuthIssuer(): string {
5858
// uses them.
5959
export const SEARCH_PROFILE_INSTRUCTIONS =
6060
ALEXANDRIA_SEARCH_INSTRUCTIONS +
61-
` 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.`;
61+
` 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).`;
6262

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

0 commit comments

Comments
 (0)