firecrawl-mcp 3.28.1 → 3.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- // src/introspection-cache.ts
1
+ // src/service/introspection-cache.ts
2
2
  import { createHash } from "crypto";
3
3
  function createIntrospectionCache(options) {
4
4
  const now = options.now ?? Date.now;
@@ -0,0 +1,404 @@
1
+ import FirecrawlApp from 'firecrawl';
2
+ import { Hono } from 'hono';
3
+ import { IncomingHttpHeaders } from 'http';
4
+ import { z } from 'zod';
5
+
6
+ declare function normalizeHeader(value: string | string[] | undefined): string | undefined;
7
+ declare function withoutTrailingSlash(value: string): string;
8
+
9
+ type ClientImplementation = {
10
+ name?: string;
11
+ version?: string;
12
+ };
13
+ /** The header form of an origin, for the requests that reach the API without a body. */
14
+ declare function originHeaders(origin: string): Record<string, string>;
15
+ /**
16
+ * The client as fastmcp hands it to a tool call (`context.client`); its
17
+ * `version` is the `clientInfo` the client sent at initialize.
18
+ */
19
+ type McpClient = {
20
+ version?: ClientImplementation | undefined;
21
+ } | undefined;
22
+ /**
23
+ * The origin of every request a tool call makes: the call's client, the
24
+ * User-Agent the session was authenticated with (stateless HTTP calls carry
25
+ * no client name), and this server's version.
26
+ */
27
+ declare function requestOrigin(client: McpClient, session?: {
28
+ clientUserAgent?: string;
29
+ } | undefined): string;
30
+
31
+ /** An API-issued keyless signup link, or undefined for anything else. */
32
+ declare function keylessSignupUrlFrom(value: unknown): string | undefined;
33
+ declare function invalidApiKeyRecoveryPayload(): Record<string, unknown> & {
34
+ message: string;
35
+ };
36
+ declare function invalidOAuthRecoveryPayload(): Record<string, unknown> & {
37
+ message: string;
38
+ };
39
+ declare function recoveryPayload(code: string, requestId?: string, options?: {
40
+ retryAfterSeconds?: number;
41
+ signupUrl?: string;
42
+ }): Record<string, unknown>;
43
+ declare function keylessQuotaReason(reason: unknown): reason is 'requests' | 'credits';
44
+
45
+ declare function hasAlexandria(sources: unknown): boolean;
46
+ declare function defaultDomainTools(sources: unknown): boolean;
47
+ declare function normalizeSearchSources(sources: unknown): unknown;
48
+ declare function searchQueryIsValid(args: {
49
+ query?: string;
50
+ }): boolean;
51
+ declare const ALEXANDRIA_CATALOGUE_VERTICALS = "companies, people, jobs, finance and filings, public records and government spending, real estate, places and restaurants, retail and prices, package registries and developer data, news, research, and more";
52
+ declare const ALEXANDRIA_SEARCH_INSTRUCTIONS: string;
53
+
54
+ /**
55
+ * Declared tool output: schemas plus the helpers that attach structured
56
+ * content to a result.
57
+ *
58
+ * Every registered tool declares an `outputSchema` so a client — and the model
59
+ * behind it — knows the shape of a result before it calls the tool. MCP asks a
60
+ * tool that declares one to return `structuredContent` next to the text block,
61
+ * so the helpers here attach the structured payload without touching the text
62
+ * a tool already returned: existing clients keep reading the exact same string.
63
+ *
64
+ * Two fastmcp behaviours shape the schemas below.
65
+ *
66
+ * 1. fastmcp advertises an output schema through `strictJsonSchema`, which
67
+ * sets `additionalProperties: false` on every object it emits. A schema
68
+ * therefore has to name each top-level key the tool can return.
69
+ * 2. fastmcp validates `structuredContent` against the schema and keeps the
70
+ * parsed value, so a key the schema does not name is dropped from the
71
+ * structured payload. The text block still carries the whole response.
72
+ *
73
+ * So these schemas name the documented keys and leave the value of anything
74
+ * that passes through from the Firecrawl API unconstrained (`z.unknown()`,
75
+ * which advertises as `{}` and accepts any shape). Only fields whose scalar
76
+ * type is part of the API contract are typed, and those are optional and
77
+ * nullable through the `str`/`num`/`bool` helpers below: a value the schema
78
+ * rejects would fail validation and turn a working call into an error.
79
+ */
80
+
81
+ declare const searchOutputSchema: z.ZodObject<{
82
+ success: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
83
+ data: z.ZodOptional<z.ZodUnknown>;
84
+ error: z.ZodOptional<z.ZodUnknown>;
85
+ warning: z.ZodOptional<z.ZodNullable<z.ZodString>>;
86
+ id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
87
+ creditsUsed: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
88
+ tools: z.ZodOptional<z.ZodUnknown>;
89
+ nextTool: z.ZodOptional<z.ZodUnknown>;
90
+ feedbackTool: z.ZodOptional<z.ZodUnknown>;
91
+ agent_hints: z.ZodOptional<z.ZodArray<z.ZodString>>;
92
+ }, z.core.$strip>;
93
+
94
+ declare function removeEmptyTopLevel<T extends Record<string, any>>(obj: T): Partial<T>;
95
+ declare const searchToolBaseFields: {
96
+ toolDetail: z.ZodOptional<z.ZodEnum<{
97
+ summary: "summary";
98
+ compact: "compact";
99
+ full: "full";
100
+ }>>;
101
+ query: z.ZodString;
102
+ objective: z.ZodOptional<z.ZodString>;
103
+ clientModel: z.ZodOptional<z.ZodString>;
104
+ domainTools: z.ZodOptional<z.ZodBoolean>;
105
+ highlights: z.ZodOptional<z.ZodBoolean>;
106
+ limit: z.ZodOptional<z.ZodNumber>;
107
+ tbs: z.ZodOptional<z.ZodString>;
108
+ filter: z.ZodOptional<z.ZodString>;
109
+ location: z.ZodOptional<z.ZodString>;
110
+ includeDomains: z.ZodOptional<z.ZodArray<z.ZodString>>;
111
+ excludeDomains: z.ZodOptional<z.ZodArray<z.ZodString>>;
112
+ sources: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodEnum<{
113
+ alexandria: "alexandria";
114
+ images: "images";
115
+ web: "web";
116
+ news: "news";
117
+ exchange: "exchange";
118
+ }>, z.ZodObject<{
119
+ type: z.ZodEnum<{
120
+ alexandria: "alexandria";
121
+ images: "images";
122
+ web: "web";
123
+ news: "news";
124
+ exchange: "exchange";
125
+ }>;
126
+ }, z.core.$strict>]>>>;
127
+ categories: z.ZodOptional<z.ZodArray<z.ZodEnum<{
128
+ pdf: "pdf";
129
+ research: "research";
130
+ developer: "developer";
131
+ gov: "gov";
132
+ }>>>;
133
+ enterprise: z.ZodOptional<z.ZodArray<z.ZodEnum<{
134
+ default: "default";
135
+ anon: "anon";
136
+ zdr: "zdr";
137
+ }>>>;
138
+ };
139
+ declare function searchDomainsAreExclusive(args: {
140
+ includeDomains?: string[];
141
+ excludeDomains?: string[];
142
+ }): boolean;
143
+ declare const SEARCH_DOMAINS_CONFLICT_MESSAGE = "includeDomains and excludeDomains cannot both be specified";
144
+ declare function assertExchangeCredential(credentialPresent: boolean): void;
145
+ type ExchangeErrorContext = {
146
+ tool: string;
147
+ requestId?: string;
148
+ providers?: string[];
149
+ };
150
+ declare function relayTermsRequired<T>(run: () => Promise<T>, context: ExchangeErrorContext): Promise<T>;
151
+ declare function relayExchangeError(run: () => Promise<any>, context: ExchangeErrorContext): Promise<any>;
152
+ declare function postSearchWithFallback(client: any, body: Record<string, unknown>, implicitTools: boolean): Promise<any>;
153
+
154
+ /**
155
+ * Library entry point: `import { createFirecrawlMcpServer } from 'firecrawl-mcp/server'`.
156
+ *
157
+ * Unstable: this API, its options and the types below may change in any
158
+ * release, including minor and patch releases. FastMCP is bundled (with local
159
+ * patches), so embedders use the types exported here rather than importing
160
+ * `fastmcp` themselves.
161
+ * @packageDocumentation
162
+ */
163
+ /**
164
+ * An error whose message is shown to the agent as the tool result. `extras`
165
+ * becomes the result's structured content.
166
+ */
167
+ declare const UserError: new (message: string, extras?: Record<string, unknown>) => Error & {
168
+ extras?: Record<string, unknown>;
169
+ };
170
+ /** What a tool's `execute` or `beforeValidate` may return. */
171
+ type ToolResult = {
172
+ content: Array<{
173
+ type: 'text';
174
+ text: string;
175
+ } | {
176
+ type: 'image' | 'audio';
177
+ data: string;
178
+ mimeType: string;
179
+ } | {
180
+ type: 'resource';
181
+ resource: {
182
+ uri: string;
183
+ mimeType?: string;
184
+ text?: string;
185
+ blob?: string;
186
+ };
187
+ } | {
188
+ type: 'resource_link';
189
+ uri: string;
190
+ name: string;
191
+ title?: string;
192
+ description?: string;
193
+ mimeType?: string;
194
+ size?: number;
195
+ }>;
196
+ structuredContent?: Record<string, unknown>;
197
+ isError?: boolean;
198
+ };
199
+ /**
200
+ * Per-connection state produced by `authenticate` and handed to every tool
201
+ * call. Embedders may add their own fields.
202
+ */
203
+ interface SessionData {
204
+ /**
205
+ * FC API key (`fc-...`) or OAuth access token (`fco_...`) sent as
206
+ * `Authorization: Bearer ...` to the Firecrawl API.
207
+ */
208
+ firecrawlApiKey?: string;
209
+ /**
210
+ * The User-Agent of the HTTP request that opened the session, the only
211
+ * client signal a stateless HTTP tool call carries (see src/origin.ts).
212
+ */
213
+ clientUserAgent?: string;
214
+ authType?: 'api-key' | 'oauth' | 'env' | 'keyless' | 'none';
215
+ /** Admitted, but the supplied credential cannot be used. */
216
+ credentialError?: 'CREDENTIAL_INVALID';
217
+ /** Set per tool call. */
218
+ requestId?: string;
219
+ [key: string]: unknown;
220
+ }
221
+ /** Per-connection session state; alias of {@link SessionData}. */
222
+ type Session = SessionData;
223
+ /** Logs to the connected client. */
224
+ type ToolLogger = {
225
+ debug(message: string, data?: unknown): void;
226
+ error(message: string, data?: unknown): void;
227
+ info(message: string, data?: unknown): void;
228
+ warn(message: string, data?: unknown): void;
229
+ };
230
+ /** What a tool's `execute` receives besides its arguments. */
231
+ type ToolContext = {
232
+ session?: Session;
233
+ log: ToolLogger;
234
+ /** The connected client; `version` is the clientInfo sent at initialize. */
235
+ client?: {
236
+ version?: {
237
+ name?: string;
238
+ version?: string;
239
+ };
240
+ };
241
+ [key: string]: unknown;
242
+ };
243
+ /** A tool as registered on the server. */
244
+ interface ToolDefinition {
245
+ name: string;
246
+ description?: string;
247
+ /** A Standard Schema (for example a zod object) for the arguments. */
248
+ parameters?: unknown;
249
+ /** JSON Schema of the structured result. */
250
+ outputSchema?: unknown;
251
+ annotations?: {
252
+ title?: string;
253
+ readOnlyHint?: boolean;
254
+ destructiveHint?: boolean;
255
+ idempotentHint?: boolean;
256
+ openWorldHint?: boolean;
257
+ [key: string]: unknown;
258
+ };
259
+ _meta?: Record<string, unknown>;
260
+ /** Hide the tool from a session's tools/list. */
261
+ canList?(session: Session): boolean;
262
+ /** Answer the call before argument validation by returning a result. */
263
+ beforeValidate?(args: unknown, session: Session): ToolResult | undefined | Promise<ToolResult | undefined>;
264
+ execute(args: any, context: ToolContext): Promise<ToolResult | string | void>;
265
+ }
266
+ /** Registers a tool onto an instance. */
267
+ type ToolRegistrar = {
268
+ addTool(tool: ToolDefinition): void;
269
+ };
270
+ /** The HTTP request `authenticate` receives; absent for stdio. */
271
+ type AuthenticationRequest = {
272
+ headers: IncomingHttpHeaders;
273
+ url?: string;
274
+ };
275
+ /** The HTTP application routes can be added to (Hono). */
276
+ type HttpApp = Hono;
277
+ /** Reported around each tool call; `started` precedes the outcome. */
278
+ type ToolCallEvent = {
279
+ tool: string;
280
+ session: SessionData;
281
+ requestId: string;
282
+ } & ({
283
+ status: 'started';
284
+ } | {
285
+ status: 'success';
286
+ result: unknown;
287
+ agentHints?: string[];
288
+ } | {
289
+ status: 'error';
290
+ error: unknown;
291
+ agentHints?: string[];
292
+ });
293
+ /** Per-request credential and extra headers for calls to the Firecrawl API. */
294
+ type OutboundRequest = {
295
+ /** Replaces the session's API key as the bearer credential. */
296
+ credential?: string;
297
+ headers?: Record<string, string>;
298
+ };
299
+ /** OAuth 2.0 protected-resource metadata (RFC 9728) served by the instance. */
300
+ type ProtectedResourceMetadata = {
301
+ authorizationServers: string[];
302
+ bearerMethodsSupported?: string[];
303
+ resource: string;
304
+ resourceName?: string;
305
+ scopesSupported?: string[];
306
+ };
307
+ type ExtraToolsContext = {
308
+ /** Firecrawl client for a session, honouring `outboundRequest`. */
309
+ getClient(session?: SessionData): FirecrawlApp;
310
+ /** Whether a session will send a credential to the Firecrawl API. */
311
+ hasCredential(session?: SessionData): boolean;
312
+ /** A built-in tool definition, including one `toolFilter` excluded. */
313
+ builtInTool(name: string): ToolDefinition | undefined;
314
+ };
315
+ type InstructionDefaults = {
316
+ /** Instructions for sessions with a credential. */
317
+ authenticated: string;
318
+ /** Instructions for the keyless tool surface. */
319
+ keyless: string;
320
+ };
321
+ /**
322
+ * Extension points for embedding the server. Unstable: names and signatures
323
+ * may change in any release.
324
+ */
325
+ interface FirecrawlMcpServerHooks {
326
+ /** Replaces credential resolution. Throw a `Response` to reject the request. */
327
+ authenticate?: (request: AuthenticationRequest | undefined) => Promise<SessionData>;
328
+ /** Decorates every registered tool, including tools from `registerExtraTools`. */
329
+ wrapTool?: (tool: ToolDefinition) => ToolDefinition;
330
+ /** Observes each tool call. Must not throw. */
331
+ onToolResult?: (event: ToolCallEvent) => void;
332
+ /** Server instructions, or a function choosing them from the defaults. */
333
+ instructions?: string | ((defaults: InstructionDefaults) => string);
334
+ /** Adds tools after the built-in ones. They bypass `toolFilter`. */
335
+ registerExtraTools?: (registrar: ToolRegistrar, context: ExtraToolsContext) => void;
336
+ /** Credential and headers for each Firecrawl API request of a session. */
337
+ outboundRequest?: (session: SessionData | undefined) => OutboundRequest | undefined;
338
+ /** Runs before a keyless request; throw to refuse it. */
339
+ beforeKeylessRequest?: (session: SessionData | undefined, origin: string) => Promise<void>;
340
+ /** Built-in tools whose name fails the filter are not registered. */
341
+ toolFilter?: (name: string) => boolean;
342
+ /** Adds HTTP routes. Runs before the built-in routes, so its routes win. */
343
+ configureHttp?: (app: HttpApp) => void;
344
+ /** Serve OAuth protected-resource metadata for this instance. */
345
+ oauth?: {
346
+ protectedResource: ProtectedResourceMetadata;
347
+ };
348
+ }
349
+ interface FirecrawlMcpServerOptions {
350
+ /** Firecrawl API base URL. Unset targets the Firecrawl cloud. */
351
+ apiUrl?: string;
352
+ /** Credential used when a request does not carry one. */
353
+ apiKey?: string;
354
+ /** Transport the instance is started with. Defaults to `stdio`. */
355
+ transport?: 'stdio' | 'httpStream';
356
+ /** Write server logs to the console. Defaults to true for HTTP. */
357
+ logging?: boolean;
358
+ /** Restrict browser actions and webhooks, and mark scrape read-only. */
359
+ safeMode?: boolean;
360
+ /** `local` (default) reads files from this machine; `upload` uses a two-phase upload flow. */
361
+ fileAccess?: 'local' | 'upload';
362
+ /** Refuse Firecrawl API calls from sessions without a credential. */
363
+ requireCredential?: boolean;
364
+ /** Register firecrawl_search_feedback. Defaults to true. */
365
+ searchFeedback?: boolean;
366
+ /** Register firecrawl_feedback. Defaults to true. */
367
+ endpointFeedback?: boolean;
368
+ /** Extension hooks. Unstable: may change in any release. */
369
+ unstable_hooks?: FirecrawlMcpServerHooks;
370
+ }
371
+ type FirecrawlMcpServerStartArgs = {
372
+ transportType: 'stdio';
373
+ } | {
374
+ transportType: 'httpStream';
375
+ httpStream: {
376
+ port: number;
377
+ /**
378
+ * Address to listen on. Defaults to `FASTMCP_HOST`, then `localhost`,
379
+ * which Node binds to a single address (often `::1` only), so pass
380
+ * `127.0.0.1`, `::` or `0.0.0.0` to choose explicitly.
381
+ */
382
+ host?: string;
383
+ /** Defaults to FastMCP's endpoint (`FASTMCP_ENDPOINT` or `/mcp`). */
384
+ endpoint?: `/${string}`;
385
+ stateless?: boolean;
386
+ };
387
+ };
388
+ interface FirecrawlMcpServer {
389
+ start(args: FirecrawlMcpServerStartArgs): Promise<void>;
390
+ stop(): Promise<void>;
391
+ }
392
+
393
+ /**
394
+ * Tools a keyless session can call. A copy, so changing it cannot change
395
+ * which tools the server itself treats as keyless.
396
+ */
397
+ declare const keylessToolNames: ReadonlySet<string>;
398
+
399
+ /** Compact JSON text of `data`, with `data` as structured content when it is an object. */
400
+ declare const structuredCompactResult: (data: unknown) => ToolResult;
401
+
402
+ declare function createFirecrawlMcpServer(options?: FirecrawlMcpServerOptions): FirecrawlMcpServer;
403
+
404
+ export { ALEXANDRIA_CATALOGUE_VERTICALS, ALEXANDRIA_SEARCH_INSTRUCTIONS, type AuthenticationRequest, type ExtraToolsContext, type FirecrawlMcpServer, type FirecrawlMcpServerHooks, type FirecrawlMcpServerOptions, type FirecrawlMcpServerStartArgs, type HttpApp, type InstructionDefaults, keylessToolNames as KEYLESS_TOOL_NAMES, type OutboundRequest, type ProtectedResourceMetadata, SEARCH_DOMAINS_CONFLICT_MESSAGE, type Session, type SessionData, type ToolCallEvent, type ToolContext, type ToolDefinition, type ToolLogger, type ToolRegistrar, type ToolResult, UserError, assertExchangeCredential, createFirecrawlMcpServer, defaultDomainTools, hasAlexandria, invalidApiKeyRecoveryPayload, invalidOAuthRecoveryPayload, keylessQuotaReason, keylessSignupUrlFrom, normalizeHeader, normalizeSearchSources, originHeaders, postSearchWithFallback, recoveryPayload, relayExchangeError, relayTermsRequired, removeEmptyTopLevel, requestOrigin, searchDomainsAreExclusive, searchOutputSchema, searchQueryIsValid, searchToolBaseFields, structuredCompactResult as structuredCompact, withoutTrailingSlash };