The server runs two FastMCP instances in one process:
- The full surface at
/v2/mcp, with the complete tool set unchanged. - The search surface at
/v2/mcp-search, with a fixed, read-only subset.
Only the request path selects the surface. There is no inspection of client
identity, User-Agent, or clientInfo.
The search surface exposes exactly these six read-only tools and nothing else:
| Tool | Purpose |
|---|---|
firecrawl_search |
Ranked web / index search results |
firecrawl_research_search_papers |
Semantic search over indexed research papers |
firecrawl_research_inspect_paper |
Canonical metadata for one paper |
firecrawl_research_related_papers |
Citation-graph expansion from anchor papers |
firecrawl_research_read_paper |
Full-text passages from one paper |
firecrawl_research_search_github |
Search public repository history and readmes |
Registration on this instance is filtered against that allowlist, so any tool
outside the set, including scrape, map, crawl, extract, agent, interact, parse,
monitor, and the feedback tools, is never registered. Both tools/list and
tools/call reflect only the six tools; calling anything else returns an
unknown-tool error.
The search surface's firecrawl_search takes no scrapeOptions. Its input
schema is strict (unknown fields are rejected), and its executor builds the
outbound /v2/search body from an explicit list of allowed fields
(query, limit, tbs, filter, location, sources, categories,
highlights, enterprise) plus origin. It never spreads raw arguments, so
no request from this surface can ask the API to fetch third-party page content.
The schema and body construction enforce this directly, and contract tests guard
the behavior. No runtime filter is involved.
- Resource identity. The surface advertises its own protected resource,
https://mcp.firecrawl.dev/v2/mcp-search, distinct from the full surface'shttps://mcp.firecrawl.dev/v2/mcp. - Path-scoped metadata (RFC 9728). The protected-resource metadata document
is served at
/.well-known/oauth-protected-resource/v2/mcp-search. The 401 challenge'sWWW-Authenticate: ... resource_metadata=...points at that document. - Authenticated discovery. Every request, including
tools/list, requires a credential; the keyless free-tier fallback does not apply here. - Audience enforcement. OAuth access tokens are introspected and must be bound to this exact resource: a token whose audience names a different resource, or that carries no audience binding at all, is rejected with a 401 (fail closed). Plain API keys carry no audience and are a direct credential, so they are unaffected.
The full surface's OAuth behavior is unchanged (origin-level metadata, no audience enforcement, keyless fallback intact).
Both instances start from the same image and process. The in-pod reverse proxy
routes /v2/mcp-search (and its key-in-path form /{apiKey}/v2/mcp-search)
plus the metadata path to the search instance's local port; everything else
routes to the full instance as before. If the search instance fails to bind its
port, it is logged and skipped; the full instance is unaffected. No new domain
and no infrastructure changes are required.
The bundled hosted deployment fixes the public path at /v2/mcp-search, the
internal search port at 3001, and the OAuth resource at
https://mcp.firecrawl.dev/v2/mcp-search. FIRECRAWL_MCP_SEARCH_ENABLED is the
supported operational toggle. Node-level overrides for the port, endpoint, and
resource exist for isolated tests only; they do not update docker/nginx.conf
or the authorization server allowlist.
tests/mcp-search-profile.test.mjs asserts the six-tool contract, unknown-tool
rejection, scrapeOptions rejection, the clean outbound body, authenticated
tools/list, the path-scoped metadata document, audience acceptance/rejection,
and that the full surface is unaffected. It runs in CI via pnpm test.