Repository navigation
Add server info, idempotent hints and input descriptions - #540
JuampiHernandez wants to merge 1 commit into
Conversation
Send title, description, website and icons in the initialize result (the bundled fastmcp patch adds title, description, websiteUrl and icons server options), declare idempotentHint on every tool, and describe the inputs of firecrawl_scrape, firecrawl_parse and firecrawl_search. Search shares the scrape option descriptions through scrapeOptions; crawl and interact point to firecrawl_scrape instead of repeating them. Defaults in the descriptions match the Firecrawl API. No tool, input name, default, output or behaviour changes. Contract snapshots regenerated; a new test pins the server info, the hints and description coverage. Bump to 3.30.2.
There was a problem hiding this comment.
7 issues found across 25 files
Confidence score: 3/5
src/research.tsandsrc/service/search-surface.tsexpose billed searches without warning clients that retries can incur another charge. Mark both tools non-idempotent.- In
src/server.ts, hosted phase one creates a new upload reference on each call, so retrying the samefilePathcan repeat the side effect. Mark the tool non-idempotent or make reference creation idempotent. - In
src/server.ts, the feedback tool is marked non-idempotent even though repeated calls with the samesearchIdare documented as idempotent; clients may retry and create duplicate feedback records. Set the hint totrue. tests/contract-snapshots/hosted-search-primary.jsondescribes tools and API-key auth that this search-only endpoint does not support. Replace it with a surface-specific description.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/research.ts">
<violation number="1" location="src/research.ts:211">
P2: `firecrawl_research_search_papers` incurs a charge on every request, so repeating the same arguments has an additional effect. Mark this tool non-idempotent so clients do not treat retries as cost-free.
(Based on your team's feedback about per-request research-search billing.)</violation>
</file>
<file name="src/server.ts">
<violation number="1" location="src/server.ts:1727">
P2: Hosted phase one mints an upload reference on each call, so repeating the same `filePath` does more than repeat a read. Mark this tool non-idempotent, or make upload-reference creation idempotent before advertising it as repeat-safe.</violation>
<violation number="2" location="src/server.ts:2148">
P2: Repeated calls with the same `searchId` are explicitly documented as idempotent, so this false hint tells clients retries may add another feedback record. Set it to `true` to match the per-search behavior.</violation>
</file>
<file name="CHANGELOG.md">
<violation number="1" location="CHANGELOG.md:11">
P3: This says there is no output change, but the first bullet adds fields to the MCP `initialize` result. Clarify that tool result shapes remain unchanged so readers do not infer protocol responses are unchanged.</violation>
</file>
<file name="tests/contract-snapshots/hosted-search-primary.json">
<violation number="1" location="tests/contract-snapshots/hosted-search-primary.json:14">
P2: This search-only endpoint advertises `parse` and API-key-enabled crawl/map/interact/extract, but exposes none of those tools and rejects API-key auth; use a surface-specific description.</violation>
</file>
<file name="src/service/search-surface.ts">
<violation number="1" location="src/service/search-surface.ts:64">
P2: The default search includes web results, and each web search is billed per request, so repeating identical arguments incurs another charge. Mark this tool non-idempotent so clients do not treat retries as effect-free.</violation>
</file>
<file name="tests/server-info-and-schema-docs.test.mjs">
<violation number="1" location="tests/server-info-and-schema-docs.test.mjs:19">
P2: This walker never checks array `items`, so fields in `firecrawl_scrape.actions[]` can lose their descriptions without failing the test. Recurse into `prop.items` as well.</violation>
</file>
Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.
View guided diff | Turn on auto-fix | Re-trigger cubic
| readOnlyHint: true, // Semantic search over indexed paper metadata; returns ranked results only. | ||
| openWorldHint: true, // Searches the Firecrawl research paper index. | ||
| destructiveHint: false, // Query-only; no writes to external sources or the research index. | ||
| idempotentHint: true, |
There was a problem hiding this comment.
P2: firecrawl_research_search_papers incurs a charge on every request, so repeating the same arguments has an additional effect. Mark this tool non-idempotent so clients do not treat retries as cost-free.
(Based on your team's feedback about per-request research-search billing.)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/research.ts, line 211:
<comment>`firecrawl_research_search_papers` incurs a charge on every request, so repeating the same arguments has an additional effect. Mark this tool non-idempotent so clients do not treat retries as cost-free.
(Based on your team's feedback about per-request research-search billing.) </comment>
<file context>
@@ -208,6 +208,7 @@ export function registerResearchTools(
readOnlyHint: true, // Semantic search over indexed paper metadata; returns ranked results only.
openWorldHint: true, // Searches the Firecrawl research paper index.
destructiveHint: false, // Query-only; no writes to external sources or the research index.
+ idempotentHint: true,
},
description: `
</file context>
| idempotentHint: true, | |
| idempotentHint: false, |
| readOnlyHint: true, // Discovers and returns indexed URLs; does not modify the target site. | ||
| openWorldHint: true, // Operates against arbitrary user-supplied web domains. | ||
| destructiveHint: false, // Read-only discovery; no deletion or destructive updates. | ||
| idempotentHint: true, |
There was a problem hiding this comment.
P2: Hosted phase one mints an upload reference on each call, so repeating the same filePath does more than repeat a read. Mark this tool non-idempotent, or make upload-reference creation idempotent before advertising it as repeat-safe.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/server.ts, line 1727:
<comment>Hosted phase one mints an upload reference on each call, so repeating the same `filePath` does more than repeat a read. Mark this tool non-idempotent, or make upload-reference creation idempotent before advertising it as repeat-safe.</comment>
<file context>
@@ -1516,6 +1724,7 @@ Alexandria mode, on an authenticated session with Alexandria access: \`alexandri
readOnlyHint: true, // Discovers and returns indexed URLs; does not modify the target site.
openWorldHint: true, // Operates against arbitrary user-supplied web domains.
destructiveHint: false, // Read-only discovery; no deletion or destructive updates.
+ idempotentHint: true,
},
description: `
</file context>
| idempotentHint: true, | |
| idempotentHint: false, |
| readOnlyHint: false, // POSTs structured feedback to the API, creating a server-side record. | ||
| openWorldHint: true, // Feedback references open-web search results and external URLs. | ||
| destructiveHint: false, // Additive only; records feedback and may refund credits, does not delete data. | ||
| idempotentHint: false, |
There was a problem hiding this comment.
P2: Repeated calls with the same searchId are explicitly documented as idempotent, so this false hint tells clients retries may add another feedback record. Set it to true to match the per-search behavior.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/server.ts, line 2148:
<comment>Repeated calls with the same `searchId` are explicitly documented as idempotent, so this false hint tells clients retries may add another feedback record. Set it to `true` to match the per-search behavior.</comment>
<file context>
@@ -1928,6 +2145,7 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
readOnlyHint: false, // POSTs structured feedback to the API, creating a server-side record.
openWorldHint: true, // Feedback references open-web search results and external URLs.
destructiveHint: false, // Additive only; records feedback and may refund credits, does not delete data.
+ idempotentHint: false,
},
description: `
</file context>
| idempotentHint: false, | |
| idempotentHint: true, |
| "name": "firecrawl-fastmcp", | ||
| "version": "<version>" | ||
| "version": "<version>", | ||
| "description": "Search the web and scrape pages or parse documents; with an API key, also crawl, map, interact and extract structured data. Built for AI agents.", |
There was a problem hiding this comment.
P2: This search-only endpoint advertises parse and API-key-enabled crawl/map/interact/extract, but exposes none of those tools and rejects API-key auth; use a surface-specific description.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At tests/contract-snapshots/hosted-search-primary.json, line 14:
<comment>This search-only endpoint advertises `parse` and API-key-enabled crawl/map/interact/extract, but exposes none of those tools and rejects API-key auth; use a surface-specific description.</comment>
<file context>
@@ -10,19 +10,38 @@
"name": "firecrawl-fastmcp",
- "version": "<version>"
+ "version": "<version>",
+ "description": "Search the web and scrape pages or parse documents; with an API key, also crawl, map, interact and extract structured data. Built for AI agents.",
+ "icons": [
+ {
</file context>
| readOnlyHint: true, | ||
| openWorldHint: true, | ||
| destructiveHint: false, | ||
| idempotentHint: true, |
There was a problem hiding this comment.
P2: The default search includes web results, and each web search is billed per request, so repeating identical arguments incurs another charge. Mark this tool non-idempotent so clients do not treat retries as effect-free.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/service/search-surface.ts, line 64:
<comment>The default search includes web results, and each web search is billed per request, so repeating identical arguments incurs another charge. Mark this tool non-idempotent so clients do not treat retries as effect-free.</comment>
<file context>
@@ -61,6 +61,7 @@ function registerMarketplaceSearchTool(
readOnlyHint: true,
openWorldHint: true,
destructiveHint: false,
+ idempotentHint: true,
},
description: `
</file context>
| idempotentHint: true, | |
| idempotentHint: false, |
| ) { | ||
| missing.push(here); | ||
| } | ||
| missing.push(...undocumented(prop, here)); |
There was a problem hiding this comment.
P2: This walker never checks array items, so fields in firecrawl_scrape.actions[] can lose their descriptions without failing the test. Recurse into prop.items as well.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At tests/server-info-and-schema-docs.test.mjs, line 19:
<comment>This walker never checks array `items`, so fields in `firecrawl_scrape.actions[]` can lose their descriptions without failing the test. Recurse into `prop.items` as well.</comment>
<file context>
@@ -0,0 +1,92 @@
+ ) {
+ missing.push(here);
+ }
+ missing.push(...undocumented(prop, here));
+ }
+ return missing;
</file context>
| missing.push(...undocumented(prop, here)); | |
| missing.push(...undocumented(prop, here)); | |
| if (prop.items) { | |
| missing.push(...undocumented(prop.items, `${here}[]`)); | |
| } |
| - Every tool declares `idempotentHint`: `true` for read-only lookups, status checks, search, map, parse, and monitor/interact-stop operations that are safe to repeat, `false` for tools that start a crawl, agent, monitor run or interact session or record feedback. `firecrawl_scrape` follows safe mode, since browser `actions` are only offered outside it. | ||
| - Inputs of `firecrawl_scrape`, `firecrawl_parse` and `firecrawl_search` carry descriptions, including the scrape options reused as `scrapeOptions` by search (crawl and interact point to `firecrawl_scrape` instead of repeating them): formats, JSON and query options, screenshot and PDF settings, wait, cache, location, proxy and profile fields, and search `limit`, `tbs`, `location` and `enterprise`. | ||
|
|
||
| No tool, input name, default, output or behaviour changes. |
There was a problem hiding this comment.
P3: This says there is no output change, but the first bullet adds fields to the MCP initialize result. Clarify that tool result shapes remain unchanged so readers do not infer protocol responses are unchanged.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At CHANGELOG.md, line 11:
<comment>This says there is no output change, but the first bullet adds fields to the MCP `initialize` result. Clarify that tool result shapes remain unchanged so readers do not infer protocol responses are unchanged.</comment>
<file context>
@@ -1,5 +1,15 @@
+- Every tool declares `idempotentHint`: `true` for read-only lookups, status checks, search, map, parse, and monitor/interact-stop operations that are safe to repeat, `false` for tools that start a crawl, agent, monitor run or interact session or record feedback. `firecrawl_scrape` follows safe mode, since browser `actions` are only offered outside it.
+- Inputs of `firecrawl_scrape`, `firecrawl_parse` and `firecrawl_search` carry descriptions, including the scrape options reused as `scrapeOptions` by search (crawl and interact point to `firecrawl_scrape` instead of repeating them): formats, JSON and query options, screenshot and PDF settings, wait, cache, location, proxy and profile fields, and search `limit`, `tbs`, `location` and `enterprise`.
+
+No tool, input name, default, output or behaviour changes.
+
## [3.30.1] - 2026-10-14
</file context>
| No tool, input name, default, output or behaviour changes. | |
| Tool names, input names and defaults, tool result shapes, and runtime behaviour are unchanged. |
|
Split into smaller PRs, with Cubic's comments addressed:
|
Hey, I'm Juampi from the Firecrawl team. MCP directories and quality scanners (Manufact's catalog score is the one that prompted this) read the server's handshake metadata and each tool's input documentation, and ours was thin. Our hosted server scored 84/100 there, mostly because of three gaps: the server sends no title, description, website or icon; only
find_toolsdeclaresidempotentHint; and most inputs offirecrawl_scrape,firecrawl_searchandfirecrawl_parsehave no description.This PR fills those in. It changes no tool, input name, default, output or behaviour.
Changes
patches/fastmcp@4.3.2.patch: fastmcp builds the initialize result with onlynameandversion, so the existing patch now also acceptstitle,description,websiteUrlandiconsserver options.title,websiteUrlandiconsuse the same names as upstream fastmcp, so the hunks drop out on upgrade.descriptionis not upstream.pnpm-lock.yamlonly gets the new patch hash.src/server.ts: sets those four fields next toname(name staysfirecrawl-fastmcp, origin attribution keys on it).idempotentHinton every tool (src/server.ts,monitor.ts,research.ts,developer.ts,gov.ts,usage.ts,service/search-surface.ts).truefor lookups, search, map, parse, status checks and the monitor/interact operations that are safe to repeat;falsefor tools that start a crawl, agent, monitor run or interact session or record feedback.firecrawl_scrapefollows safe mode:trueon the hosted surface,falseoutside it because browseractionsexist there.firecrawl_scrape,firecrawl_parseandfirecrawl_search(src/server.ts,src/tool-helpers.ts,src/alexandria.ts). Defaults were checked against the API source and docs. Where an input does nothing on a tool (parsestoreInCache, parseproxy, searchscrapeOptions.maxAge), the description says so. Search reuses the scrape option descriptions throughscrapeOptions; crawl and interact say "Same fields as firecrawl_scrape." instead of repeating them, to keeptools/listsmall.CHANGELOG.mdentry, version bump to 3.30.2 inpackage.json,server.jsonandgemini-extension.json.tests/server-info-and-schema-docs.test.mjs: pins the four server fields,idempotentHinton every tool, and description coverage for every input of the three tools.Heads up
tools/listgrows by about 12 KB on the full surface (94.5 to 107 KB, 28 tools) and about 10 KB on the hosted surface, all of it descriptions.package.json, sopublish.ymlreleases 3.30.2 to npm.mcp.firecrawl.devruns fromfirecrawl-mcp-service, which vendors a tarball of this package. The hosted score only moves once that is re-vendored and redeployed. The service's ownsrc/service/search-surface.tsalso needsidempotentHint: trueon the hosted search tool, as a separate change there.How I tested it
pnpm install --frozen-lockfile,pnpm run build,pnpm test: 171/171 pass (168 on main plus the 3 new tests), on a fresh clone as well.tools/listandinitializebetween main and this branch on all 8 contract surfaces. With descriptions andidempotentHintremoved, the input and output schemas are identical, and tool descriptions, names, order and counts are unchanged.CLOUD_SERVICE=true: scrape (markdown, JSON, screenshot,waitFor,location,maxAge: 0), search (limit,tbs,location), parse against a mock API. Results match main. A request without a credential on the hosted setup still gets a 401.waitFor,limit0 and 1000, unknown format and proxy,urlplusalexandria) return identical errors, and the requests sent upstream are byte-identical.idempotentHintis on every tool, and the metadata fields are all present. I expect the hosted score to land in the mid-nineties after the redeploy; I have not seen the real number yet.