firecrawl-mcp 3.22.1 → 3.22.3

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.
Files changed (2) hide show
  1. package/dist/index.js +342 -1586
  2. package/package.json +11 -19
package/dist/index.js CHANGED
@@ -3,1327 +3,14 @@
3
3
  // src/index.ts
4
4
  import FirecrawlApp from "@mendable/firecrawl-js";
5
5
  import dotenv from "dotenv";
6
+ import { FastMCP } from "fastmcp";
6
7
  import { readFile } from "fs/promises";
7
8
  import { createRequire } from "module";
8
9
  import path from "path";
9
- import { z as z4 } from "zod";
10
-
11
- // src/fastmcp/FastMCP.ts
12
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
13
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
14
- import {
15
- CallToolRequestSchema,
16
- CompleteRequestSchema,
17
- ErrorCode,
18
- GetPromptRequestSchema,
19
- ListPromptsRequestSchema,
20
- ListResourcesRequestSchema,
21
- ListResourceTemplatesRequestSchema,
22
- ListToolsRequestSchema,
23
- McpError,
24
- ReadResourceRequestSchema,
25
- RootsListChangedNotificationSchema,
26
- SetLevelRequestSchema
27
- } from "@modelcontextprotocol/sdk/types.js";
28
- import { EventEmitter } from "events";
29
- import Fuse from "fuse.js";
30
- import { startHTTPServer } from "mcp-proxy";
31
- import { setTimeout as delay } from "timers/promises";
32
- import parseURITemplate from "uri-templates";
33
- import { toJsonSchema } from "xsschema";
34
- import { z } from "zod";
35
- var FastMCPError = class extends Error {
36
- constructor(message) {
37
- super(message);
38
- this.name = new.target.name;
39
- }
40
- };
41
- var UnexpectedStateError = class extends FastMCPError {
42
- extras;
43
- constructor(message, extras) {
44
- super(message);
45
- this.name = new.target.name;
46
- this.extras = extras;
47
- }
48
- };
49
- var UserError = class extends UnexpectedStateError {
50
- };
51
- var TextContentZodSchema = z.object({
52
- /**
53
- * The text content of the message.
54
- */
55
- text: z.string(),
56
- type: z.literal("text")
57
- }).strict();
58
- var ImageContentZodSchema = z.object({
59
- /**
60
- * The base64-encoded image data.
61
- */
62
- data: z.string().base64(),
63
- /**
64
- * The MIME type of the image. Different providers may support different image types.
65
- */
66
- mimeType: z.string(),
67
- type: z.literal("image")
68
- }).strict();
69
- var AudioContentZodSchema = z.object({
70
- /**
71
- * The base64-encoded audio data.
72
- */
73
- data: z.string().base64(),
74
- mimeType: z.string(),
75
- type: z.literal("audio")
76
- }).strict();
77
- var ResourceContentZodSchema = z.object({
78
- resource: z.object({
79
- blob: z.string().optional(),
80
- mimeType: z.string().optional(),
81
- text: z.string().optional(),
82
- uri: z.string()
83
- }),
84
- type: z.literal("resource")
85
- }).strict();
86
- var ResourceLinkZodSchema = z.object({
87
- description: z.string().optional(),
88
- mimeType: z.string().optional(),
89
- name: z.string(),
90
- title: z.string().optional(),
91
- type: z.literal("resource_link"),
92
- uri: z.string()
93
- });
94
- var ContentZodSchema = z.discriminatedUnion("type", [
95
- TextContentZodSchema,
96
- ImageContentZodSchema,
97
- AudioContentZodSchema,
98
- ResourceContentZodSchema,
99
- ResourceLinkZodSchema
100
- ]);
101
- var ContentResultZodSchema = z.object({
102
- content: ContentZodSchema.array(),
103
- isError: z.boolean().optional()
104
- }).strict();
105
- var CompletionZodSchema = z.object({
106
- /**
107
- * Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown.
108
- */
109
- hasMore: z.optional(z.boolean()),
110
- /**
111
- * The total number of completion options available. This can exceed the number of values actually sent in the response.
112
- */
113
- total: z.optional(z.number().int()),
114
- /**
115
- * An array of completion values. Must not exceed 100 items.
116
- */
117
- values: z.array(z.string()).max(100)
118
- });
119
- var FastMCPSessionEventEmitterBase = EventEmitter;
120
- var escapeWWWAuthenticateValue = (value) => {
121
- return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
122
- };
123
- var FastMCPSessionEventEmitter = class extends FastMCPSessionEventEmitterBase {
124
- };
125
- var FastMCPSession = class extends FastMCPSessionEventEmitter {
126
- get clientCapabilities() {
127
- return this.#clientCapabilities ?? null;
128
- }
129
- get isReady() {
130
- return this.#connectionState === "ready";
131
- }
132
- get loggingLevel() {
133
- return this.#loggingLevel;
134
- }
135
- get roots() {
136
- return this.#roots;
137
- }
138
- get server() {
139
- return this.#server;
140
- }
141
- #auth;
142
- #capabilities = {};
143
- #clientCapabilities;
144
- #connectionState = "connecting";
145
- #logger;
146
- #loggingLevel = "info";
147
- #needsEventLoopFlush = false;
148
- #pingConfig;
149
- #pingInterval = null;
150
- #prompts = [];
151
- #resources = [];
152
- #resourceTemplates = [];
153
- #roots = [];
154
- #rootsConfig;
155
- #server;
156
- #utils;
157
- constructor({
158
- auth,
159
- instructions,
160
- logger,
161
- name,
162
- ping,
163
- prompts,
164
- resources,
165
- resourcesTemplates,
166
- roots,
167
- tools,
168
- transportType,
169
- utils,
170
- version
171
- }) {
172
- super();
173
- this.#auth = auth;
174
- this.#logger = logger;
175
- this.#pingConfig = ping;
176
- this.#rootsConfig = roots;
177
- this.#needsEventLoopFlush = transportType === "httpStream";
178
- if (tools.length) {
179
- this.#capabilities.tools = {};
180
- }
181
- if (resources.length || resourcesTemplates.length) {
182
- this.#capabilities.resources = {};
183
- }
184
- if (prompts.length) {
185
- for (const prompt of prompts) {
186
- this.addPrompt(prompt);
187
- }
188
- this.#capabilities.prompts = {};
189
- }
190
- this.#capabilities.logging = {};
191
- this.#server = new Server(
192
- { name, version },
193
- { capabilities: this.#capabilities, instructions }
194
- );
195
- this.#utils = utils;
196
- this.setupErrorHandling();
197
- this.setupLoggingHandlers();
198
- this.setupRootsHandlers();
199
- this.setupCompleteHandlers();
200
- if (tools.length) {
201
- this.setupToolHandlers(tools);
202
- }
203
- if (resources.length || resourcesTemplates.length) {
204
- for (const resource of resources) {
205
- this.addResource(resource);
206
- }
207
- this.setupResourceHandlers(resources);
208
- if (resourcesTemplates.length) {
209
- for (const resourceTemplate of resourcesTemplates) {
210
- this.addResourceTemplate(resourceTemplate);
211
- }
212
- this.setupResourceTemplateHandlers(resourcesTemplates);
213
- }
214
- }
215
- if (prompts.length) {
216
- this.setupPromptHandlers(prompts);
217
- }
218
- }
219
- async close() {
220
- this.#connectionState = "closed";
221
- if (this.#pingInterval) {
222
- clearInterval(this.#pingInterval);
223
- }
224
- try {
225
- await this.#server.close();
226
- } catch (error) {
227
- this.#logger.error("[FastMCP error]", "could not close server", error);
228
- }
229
- }
230
- async connect(transport) {
231
- if (this.#server.transport) {
232
- throw new UnexpectedStateError("Server is already connected");
233
- }
234
- this.#connectionState = "connecting";
235
- try {
236
- await this.#server.connect(transport);
237
- let attempt = 0;
238
- const maxAttempts = 10;
239
- const retryDelay = 100;
240
- while (attempt++ < maxAttempts) {
241
- const capabilities = this.#server.getClientCapabilities();
242
- if (capabilities) {
243
- this.#clientCapabilities = capabilities;
244
- break;
245
- }
246
- await delay(retryDelay);
247
- }
248
- if (!this.#clientCapabilities) {
249
- this.#logger.warn(
250
- `[FastMCP warning] could not infer client capabilities after ${maxAttempts} attempts. Connection may be unstable.`
251
- );
252
- }
253
- if (this.#clientCapabilities?.roots?.listChanged && typeof this.#server.listRoots === "function") {
254
- try {
255
- const roots = await this.#server.listRoots();
256
- this.#roots = roots?.roots || [];
257
- } catch (e) {
258
- if (e instanceof McpError && e.code === ErrorCode.MethodNotFound) {
259
- this.#logger.debug(
260
- "[FastMCP debug] listRoots method not supported by client"
261
- );
262
- } else {
263
- this.#logger.error(
264
- `[FastMCP error] received error listing roots.
265
-
266
- ${e instanceof Error ? e.stack : JSON.stringify(e)}`
267
- );
268
- }
269
- }
270
- }
271
- if (this.#clientCapabilities) {
272
- const pingConfig = this.#getPingConfig(transport);
273
- if (pingConfig.enabled) {
274
- this.#pingInterval = setInterval(async () => {
275
- try {
276
- await this.#server.ping();
277
- } catch {
278
- const logLevel = pingConfig.logLevel;
279
- if (logLevel === "debug") {
280
- this.#logger.debug("[FastMCP debug] server ping failed");
281
- } else if (logLevel === "warning") {
282
- this.#logger.warn(
283
- "[FastMCP warning] server is not responding to ping"
284
- );
285
- } else if (logLevel === "error") {
286
- this.#logger.error(
287
- "[FastMCP error] server is not responding to ping"
288
- );
289
- } else {
290
- this.#logger.info("[FastMCP info] server ping failed");
291
- }
292
- }
293
- }, pingConfig.intervalMs);
294
- }
295
- }
296
- this.#connectionState = "ready";
297
- this.emit("ready");
298
- } catch (error) {
299
- this.#connectionState = "error";
300
- const errorEvent = {
301
- error: error instanceof Error ? error : new Error(String(error))
302
- };
303
- this.emit("error", errorEvent);
304
- throw error;
305
- }
306
- }
307
- async requestSampling(message, options) {
308
- return this.#server.createMessage(message, options);
309
- }
310
- waitForReady() {
311
- if (this.isReady) {
312
- return Promise.resolve();
313
- }
314
- if (this.#connectionState === "error" || this.#connectionState === "closed") {
315
- return Promise.reject(
316
- new Error(`Connection is in ${this.#connectionState} state`)
317
- );
318
- }
319
- return new Promise((resolve, reject) => {
320
- const timeout = setTimeout(() => {
321
- reject(
322
- new Error(
323
- "Connection timeout: Session failed to become ready within 5 seconds"
324
- )
325
- );
326
- }, 5e3);
327
- this.once("ready", () => {
328
- clearTimeout(timeout);
329
- resolve();
330
- });
331
- this.once("error", (event) => {
332
- clearTimeout(timeout);
333
- reject(event.error);
334
- });
335
- });
336
- }
337
- #getPingConfig(transport) {
338
- const pingConfig = this.#pingConfig || {};
339
- let defaultEnabled = false;
340
- if ("type" in transport) {
341
- if (transport.type === "httpStream") {
342
- defaultEnabled = true;
343
- }
344
- }
345
- return {
346
- enabled: pingConfig.enabled !== void 0 ? pingConfig.enabled : defaultEnabled,
347
- intervalMs: pingConfig.intervalMs || 5e3,
348
- logLevel: pingConfig.logLevel || "debug"
349
- };
350
- }
351
- addPrompt(inputPrompt) {
352
- const completers = {};
353
- const enums = {};
354
- const fuseInstances = {};
355
- for (const argument of inputPrompt.arguments ?? []) {
356
- if (argument.complete) {
357
- completers[argument.name] = argument.complete;
358
- }
359
- if (argument.enum) {
360
- enums[argument.name] = argument.enum;
361
- fuseInstances[argument.name] = new Fuse(argument.enum, {
362
- includeScore: true,
363
- threshold: 0.3
364
- // More flexible matching!
365
- });
366
- }
367
- }
368
- const prompt = {
369
- ...inputPrompt,
370
- complete: async (name, value, auth) => {
371
- if (completers[name]) {
372
- return await completers[name](value, auth);
373
- }
374
- if (fuseInstances[name]) {
375
- const result = fuseInstances[name].search(value);
376
- return {
377
- total: result.length,
378
- values: result.map((item) => item.item)
379
- };
380
- }
381
- return {
382
- values: []
383
- };
384
- }
385
- };
386
- this.#prompts.push(prompt);
387
- }
388
- addResource(inputResource) {
389
- this.#resources.push(inputResource);
390
- }
391
- addResourceTemplate(inputResourceTemplate) {
392
- const completers = {};
393
- for (const argument of inputResourceTemplate.arguments ?? []) {
394
- if (argument.complete) {
395
- completers[argument.name] = argument.complete;
396
- }
397
- }
398
- const resourceTemplate = {
399
- ...inputResourceTemplate,
400
- complete: async (name, value, auth) => {
401
- if (completers[name]) {
402
- return await completers[name](value, auth);
403
- }
404
- return {
405
- values: []
406
- };
407
- }
408
- };
409
- this.#resourceTemplates.push(resourceTemplate);
410
- }
411
- setupCompleteHandlers() {
412
- this.#server.setRequestHandler(CompleteRequestSchema, async (request) => {
413
- if (request.params.ref.type === "ref/prompt") {
414
- const prompt = this.#prompts.find(
415
- (prompt2) => prompt2.name === request.params.ref.name
416
- );
417
- if (!prompt) {
418
- throw new UnexpectedStateError("Unknown prompt", {
419
- request
420
- });
421
- }
422
- if (!prompt.complete) {
423
- throw new UnexpectedStateError("Prompt does not support completion", {
424
- request
425
- });
426
- }
427
- const completion = CompletionZodSchema.parse(
428
- await prompt.complete(
429
- request.params.argument.name,
430
- request.params.argument.value,
431
- this.#auth
432
- )
433
- );
434
- return {
435
- completion
436
- };
437
- }
438
- if (request.params.ref.type === "ref/resource") {
439
- const resource = this.#resourceTemplates.find(
440
- (resource2) => resource2.uriTemplate === request.params.ref.uri
441
- );
442
- if (!resource) {
443
- throw new UnexpectedStateError("Unknown resource", {
444
- request
445
- });
446
- }
447
- if (!("uriTemplate" in resource)) {
448
- throw new UnexpectedStateError("Unexpected resource");
449
- }
450
- if (!resource.complete) {
451
- throw new UnexpectedStateError(
452
- "Resource does not support completion",
453
- {
454
- request
455
- }
456
- );
457
- }
458
- const completion = CompletionZodSchema.parse(
459
- await resource.complete(
460
- request.params.argument.name,
461
- request.params.argument.value,
462
- this.#auth
463
- )
464
- );
465
- return {
466
- completion
467
- };
468
- }
469
- throw new UnexpectedStateError("Unexpected completion request", {
470
- request
471
- });
472
- });
473
- }
474
- setupErrorHandling() {
475
- this.#server.onerror = (error) => {
476
- this.#logger.error("[FastMCP error]", error);
477
- };
478
- }
479
- setupLoggingHandlers() {
480
- this.#server.setRequestHandler(SetLevelRequestSchema, (request) => {
481
- this.#loggingLevel = request.params.level;
482
- return {};
483
- });
484
- }
485
- setupPromptHandlers(prompts) {
486
- this.#server.setRequestHandler(ListPromptsRequestSchema, async () => {
487
- return {
488
- prompts: prompts.map((prompt) => {
489
- return {
490
- arguments: prompt.arguments,
491
- complete: prompt.complete,
492
- description: prompt.description,
493
- name: prompt.name
494
- };
495
- })
496
- };
497
- });
498
- this.#server.setRequestHandler(GetPromptRequestSchema, async (request) => {
499
- const prompt = prompts.find(
500
- (prompt2) => prompt2.name === request.params.name
501
- );
502
- if (!prompt) {
503
- throw new McpError(
504
- ErrorCode.MethodNotFound,
505
- `Unknown prompt: ${request.params.name}`
506
- );
507
- }
508
- const args2 = request.params.arguments;
509
- for (const arg of prompt.arguments ?? []) {
510
- if (arg.required && !(args2 && arg.name in args2)) {
511
- throw new McpError(
512
- ErrorCode.InvalidRequest,
513
- `Prompt '${request.params.name}' requires argument '${arg.name}': ${arg.description || "No description provided"}`
514
- );
515
- }
516
- }
517
- let result;
518
- try {
519
- result = await prompt.load(
520
- args2,
521
- this.#auth
522
- );
523
- } catch (error) {
524
- const errorMessage = error instanceof Error ? error.message : String(error);
525
- throw new McpError(
526
- ErrorCode.InternalError,
527
- `Failed to load prompt '${request.params.name}': ${errorMessage}`
528
- );
529
- }
530
- if (typeof result === "string") {
531
- return {
532
- description: prompt.description,
533
- messages: [
534
- {
535
- content: { text: result, type: "text" },
536
- role: "user"
537
- }
538
- ]
539
- };
540
- } else {
541
- return {
542
- description: prompt.description,
543
- messages: result.messages
544
- };
545
- }
546
- });
547
- }
548
- setupResourceHandlers(resources) {
549
- this.#server.setRequestHandler(ListResourcesRequestSchema, async () => {
550
- return {
551
- resources: resources.map((resource) => ({
552
- description: resource.description,
553
- mimeType: resource.mimeType,
554
- name: resource.name,
555
- uri: resource.uri
556
- }))
557
- };
558
- });
559
- this.#server.setRequestHandler(
560
- ReadResourceRequestSchema,
561
- async (request) => {
562
- if ("uri" in request.params) {
563
- const resource = resources.find(
564
- (resource2) => "uri" in resource2 && resource2.uri === request.params.uri
565
- );
566
- if (!resource) {
567
- for (const resourceTemplate of this.#resourceTemplates) {
568
- const uriTemplate = parseURITemplate(
569
- resourceTemplate.uriTemplate
570
- );
571
- const match = uriTemplate.fromUri(request.params.uri);
572
- if (!match) {
573
- continue;
574
- }
575
- const uri = uriTemplate.fill(match);
576
- const result = await resourceTemplate.load(match, this.#auth);
577
- const resources2 = Array.isArray(result) ? result : [result];
578
- return {
579
- contents: resources2.map((resource2) => ({
580
- ...resource2,
581
- description: resourceTemplate.description,
582
- mimeType: resource2.mimeType ?? resourceTemplate.mimeType,
583
- name: resourceTemplate.name,
584
- uri: resource2.uri ?? uri
585
- }))
586
- };
587
- }
588
- throw new McpError(
589
- ErrorCode.MethodNotFound,
590
- `Resource not found: '${request.params.uri}'. Available resources: ${resources.map((r) => r.uri).join(", ") || "none"}`
591
- );
592
- }
593
- if (!("uri" in resource)) {
594
- throw new UnexpectedStateError("Resource does not support reading");
595
- }
596
- let maybeArrayResult;
597
- try {
598
- maybeArrayResult = await resource.load(this.#auth);
599
- } catch (error) {
600
- const errorMessage = error instanceof Error ? error.message : String(error);
601
- throw new McpError(
602
- ErrorCode.InternalError,
603
- `Failed to load resource '${resource.name}' (${resource.uri}): ${errorMessage}`,
604
- {
605
- uri: resource.uri
606
- }
607
- );
608
- }
609
- const resourceResults = Array.isArray(maybeArrayResult) ? maybeArrayResult : [maybeArrayResult];
610
- return {
611
- contents: resourceResults.map((result) => ({
612
- ...result,
613
- mimeType: result.mimeType ?? resource.mimeType,
614
- name: resource.name,
615
- uri: result.uri ?? resource.uri
616
- }))
617
- };
618
- }
619
- throw new UnexpectedStateError("Unknown resource request", {
620
- request
621
- });
622
- }
623
- );
624
- }
625
- setupResourceTemplateHandlers(resourceTemplates) {
626
- this.#server.setRequestHandler(
627
- ListResourceTemplatesRequestSchema,
628
- async () => {
629
- return {
630
- resourceTemplates: resourceTemplates.map((resourceTemplate) => ({
631
- description: resourceTemplate.description,
632
- mimeType: resourceTemplate.mimeType,
633
- name: resourceTemplate.name,
634
- uriTemplate: resourceTemplate.uriTemplate
635
- }))
636
- };
637
- }
638
- );
639
- }
640
- setupRootsHandlers() {
641
- if (this.#rootsConfig?.enabled === false) {
642
- this.#logger.debug(
643
- "[FastMCP debug] roots capability explicitly disabled via config"
644
- );
645
- return;
646
- }
647
- if (typeof this.#server.listRoots === "function") {
648
- this.#server.setNotificationHandler(
649
- RootsListChangedNotificationSchema,
650
- () => {
651
- this.#server.listRoots().then((roots) => {
652
- this.#roots = roots.roots;
653
- this.emit("rootsChanged", {
654
- roots: roots.roots
655
- });
656
- }).catch((error) => {
657
- if (error instanceof McpError && error.code === ErrorCode.MethodNotFound) {
658
- this.#logger.debug(
659
- "[FastMCP debug] listRoots method not supported by client"
660
- );
661
- } else {
662
- this.#logger.error(
663
- `[FastMCP error] received error listing roots.
664
-
665
- ${error instanceof Error ? error.stack : JSON.stringify(error)}`
666
- );
667
- }
668
- });
669
- }
670
- );
671
- } else {
672
- this.#logger.debug(
673
- "[FastMCP debug] roots capability not available, not setting up notification handler"
674
- );
675
- }
676
- }
677
- setupToolHandlers(tools) {
678
- this.#server.setRequestHandler(ListToolsRequestSchema, async () => {
679
- return {
680
- tools: await Promise.all(
681
- tools.map(async (tool) => {
682
- return {
683
- annotations: tool.annotations,
684
- description: tool.description,
685
- inputSchema: tool.parameters ? await toJsonSchema(tool.parameters) : {
686
- additionalProperties: false,
687
- properties: {},
688
- type: "object"
689
- },
690
- // More complete schema for Cursor compatibility
691
- name: tool.name
692
- };
693
- })
694
- )
695
- };
696
- });
697
- this.#server.setRequestHandler(CallToolRequestSchema, async (request) => {
698
- const tool = tools.find((tool2) => tool2.name === request.params.name);
699
- if (!tool) {
700
- throw new McpError(
701
- ErrorCode.MethodNotFound,
702
- `Unknown tool: ${request.params.name}`
703
- );
704
- }
705
- let args2 = void 0;
706
- if (tool.parameters) {
707
- const parsed = await tool.parameters["~standard"].validate(
708
- request.params.arguments
709
- );
710
- if (parsed.issues) {
711
- const friendlyErrors = this.#utils?.formatInvalidParamsErrorMessage ? this.#utils.formatInvalidParamsErrorMessage(parsed.issues) : parsed.issues.map((issue) => {
712
- const path2 = issue.path?.join(".") || "root";
713
- return `${path2}: ${issue.message}`;
714
- }).join(", ");
715
- throw new McpError(
716
- ErrorCode.InvalidParams,
717
- `Tool '${request.params.name}' parameter validation failed: ${friendlyErrors}. Please check the parameter types and values according to the tool's schema.`
718
- );
719
- }
720
- args2 = parsed.value;
721
- }
722
- const progressToken = request.params?._meta?.progressToken;
723
- let result;
724
- try {
725
- const reportProgress = async (progress) => {
726
- try {
727
- await this.#server.notification({
728
- method: "notifications/progress",
729
- params: {
730
- ...progress,
731
- progressToken
732
- }
733
- });
734
- if (this.#needsEventLoopFlush) {
735
- await new Promise((resolve) => setImmediate(resolve));
736
- }
737
- } catch (progressError) {
738
- this.#logger.warn(
739
- `[FastMCP warning] Failed to report progress for tool '${request.params.name}':`,
740
- progressError instanceof Error ? progressError.message : String(progressError)
741
- );
742
- }
743
- };
744
- const log = {
745
- debug: (message, context) => {
746
- this.#server.sendLoggingMessage({
747
- data: {
748
- context,
749
- message
750
- },
751
- level: "debug"
752
- });
753
- },
754
- error: (message, context) => {
755
- this.#server.sendLoggingMessage({
756
- data: {
757
- context,
758
- message
759
- },
760
- level: "error"
761
- });
762
- },
763
- info: (message, context) => {
764
- this.#server.sendLoggingMessage({
765
- data: {
766
- context,
767
- message
768
- },
769
- level: "info"
770
- });
771
- },
772
- warn: (message, context) => {
773
- this.#server.sendLoggingMessage({
774
- data: {
775
- context,
776
- message
777
- },
778
- level: "warning"
779
- });
780
- }
781
- };
782
- const streamContent = async (content) => {
783
- const contentArray = Array.isArray(content) ? content : [content];
784
- try {
785
- await this.#server.notification({
786
- method: "notifications/tool/streamContent",
787
- params: {
788
- content: contentArray,
789
- toolName: request.params.name
790
- }
791
- });
792
- if (this.#needsEventLoopFlush) {
793
- await new Promise((resolve) => setImmediate(resolve));
794
- }
795
- } catch (streamError) {
796
- this.#logger.warn(
797
- `[FastMCP warning] Failed to stream content for tool '${request.params.name}':`,
798
- streamError instanceof Error ? streamError.message : String(streamError)
799
- );
800
- }
801
- };
802
- const executeToolPromise = tool.execute(args2, {
803
- client: {
804
- version: this.#server.getClientVersion()
805
- },
806
- log,
807
- reportProgress,
808
- session: this.#auth,
809
- streamContent
810
- });
811
- const maybeStringResult = await (tool.timeoutMs ? Promise.race([
812
- executeToolPromise,
813
- new Promise((_, reject) => {
814
- const timeoutId = setTimeout(() => {
815
- reject(
816
- new UserError(
817
- `Tool '${request.params.name}' timed out after ${tool.timeoutMs}ms. Consider increasing timeoutMs or optimizing the tool implementation.`
818
- )
819
- );
820
- }, tool.timeoutMs);
821
- executeToolPromise.finally(() => clearTimeout(timeoutId));
822
- })
823
- ]) : executeToolPromise);
824
- await delay(1);
825
- if (maybeStringResult === void 0 || maybeStringResult === null) {
826
- result = ContentResultZodSchema.parse({
827
- content: []
828
- });
829
- } else if (typeof maybeStringResult === "string") {
830
- result = ContentResultZodSchema.parse({
831
- content: [{ text: maybeStringResult, type: "text" }]
832
- });
833
- } else if ("type" in maybeStringResult) {
834
- result = ContentResultZodSchema.parse({
835
- content: [maybeStringResult]
836
- });
837
- } else {
838
- result = ContentResultZodSchema.parse(maybeStringResult);
839
- }
840
- } catch (error) {
841
- if (error instanceof UserError) {
842
- return {
843
- content: [{ text: error.message, type: "text" }],
844
- isError: true
845
- };
846
- }
847
- const errorMessage = error instanceof Error ? error.message : String(error);
848
- return {
849
- content: [
850
- {
851
- text: `Tool '${request.params.name}' execution failed: ${errorMessage}`,
852
- type: "text"
853
- }
854
- ],
855
- isError: true
856
- };
857
- }
858
- return result;
859
- });
860
- }
861
- };
862
- function camelToSnakeCase(str) {
863
- return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
864
- }
865
- function convertObjectToSnakeCase(obj) {
866
- const result = {};
867
- for (const [key, value] of Object.entries(obj)) {
868
- const snakeKey = camelToSnakeCase(key);
869
- result[snakeKey] = value;
870
- }
871
- return result;
872
- }
873
- var FastMCPEventEmitterBase = EventEmitter;
874
- var FastMCPEventEmitter = class extends FastMCPEventEmitterBase {
875
- };
876
- var FastMCP = class extends FastMCPEventEmitter {
877
- constructor(options) {
878
- super();
879
- this.options = options;
880
- this.#options = options;
881
- this.#authenticate = options.authenticate;
882
- this.#logger = options.logger || console;
883
- }
884
- options;
885
- get sessions() {
886
- return this.#sessions;
887
- }
888
- #authenticate;
889
- #httpStreamServer = null;
890
- #logger;
891
- #options;
892
- #prompts = [];
893
- #resources = [];
894
- #resourcesTemplates = [];
895
- #sessions = [];
896
- #tools = [];
897
- /**
898
- * Adds a prompt to the server.
899
- */
900
- addPrompt(prompt) {
901
- this.#prompts.push(prompt);
902
- }
903
- /**
904
- * Adds a resource to the server.
905
- */
906
- addResource(resource) {
907
- this.#resources.push(resource);
908
- }
909
- /**
910
- * Adds a resource template to the server.
911
- */
912
- addResourceTemplate(resource) {
913
- this.#resourcesTemplates.push(resource);
914
- }
915
- /**
916
- * Adds a tool to the server.
917
- */
918
- addTool(tool) {
919
- this.#tools.push(tool);
920
- }
921
- /**
922
- * Embeds a resource by URI, making it easy to include resources in tool responses.
923
- *
924
- * @param uri - The URI of the resource to embed
925
- * @returns Promise<ResourceContent> - The embedded resource content
926
- */
927
- async embedded(uri) {
928
- const directResource = this.#resources.find(
929
- (resource) => resource.uri === uri
930
- );
931
- if (directResource) {
932
- const result = await directResource.load();
933
- const results = Array.isArray(result) ? result : [result];
934
- const firstResult = results[0];
935
- const resourceData = {
936
- mimeType: directResource.mimeType,
937
- uri
938
- };
939
- if ("text" in firstResult) {
940
- resourceData.text = firstResult.text;
941
- }
942
- if ("blob" in firstResult) {
943
- resourceData.blob = firstResult.blob;
944
- }
945
- return resourceData;
946
- }
947
- for (const template of this.#resourcesTemplates) {
948
- const templateBase = template.uriTemplate.split("{")[0];
949
- if (uri.startsWith(templateBase)) {
950
- const params = {};
951
- const templateParts = template.uriTemplate.split("/");
952
- const uriParts = uri.split("/");
953
- for (let i = 0; i < templateParts.length; i++) {
954
- const templatePart = templateParts[i];
955
- if (templatePart?.startsWith("{") && templatePart.endsWith("}")) {
956
- const paramName = templatePart.slice(1, -1);
957
- const paramValue = uriParts[i];
958
- if (paramValue) {
959
- params[paramName] = paramValue;
960
- }
961
- }
962
- }
963
- const result = await template.load(
964
- params
965
- );
966
- const resourceData = {
967
- mimeType: template.mimeType,
968
- uri
969
- };
970
- if ("text" in result) {
971
- resourceData.text = result.text;
972
- }
973
- if ("blob" in result) {
974
- resourceData.blob = result.blob;
975
- }
976
- return resourceData;
977
- }
978
- }
979
- throw new UnexpectedStateError(`Resource not found: ${uri}`, { uri });
980
- }
981
- /**
982
- * Starts the server.
983
- */
984
- async start(options) {
985
- const config = this.#parseRuntimeConfig(options);
986
- if (config.transportType === "stdio") {
987
- const transport = new StdioServerTransport();
988
- let auth;
989
- if (this.#authenticate) {
990
- try {
991
- auth = await this.#authenticate(
992
- void 0
993
- );
994
- } catch (error) {
995
- this.#logger.error(
996
- "[FastMCP error] Authentication failed for stdio transport:",
997
- error instanceof Error ? error.message : String(error)
998
- );
999
- }
1000
- }
1001
- const session = new FastMCPSession({
1002
- auth,
1003
- instructions: this.#options.instructions,
1004
- logger: this.#logger,
1005
- name: this.#options.name,
1006
- ping: this.#options.ping,
1007
- prompts: this.#prompts,
1008
- resources: this.#resources,
1009
- resourcesTemplates: this.#resourcesTemplates,
1010
- roots: this.#options.roots,
1011
- tools: this.#tools,
1012
- transportType: "stdio",
1013
- utils: this.#options.utils,
1014
- version: this.#options.version
1015
- });
1016
- await session.connect(transport);
1017
- this.#sessions.push(session);
1018
- session.once("error", () => {
1019
- this.#removeSession(session);
1020
- });
1021
- if (transport.onclose) {
1022
- const originalOnClose = transport.onclose;
1023
- transport.onclose = () => {
1024
- this.#removeSession(session);
1025
- if (originalOnClose) {
1026
- originalOnClose();
1027
- }
1028
- };
1029
- } else {
1030
- transport.onclose = () => {
1031
- this.#removeSession(session);
1032
- };
1033
- }
1034
- this.emit("connect", {
1035
- session
1036
- });
1037
- } else if (config.transportType === "httpStream") {
1038
- const httpConfig = config.httpStream;
1039
- if (httpConfig.stateless) {
1040
- this.#logger.info(
1041
- `[FastMCP info] Starting server in stateless mode on HTTP Stream at http://${httpConfig.host}:${httpConfig.port}${httpConfig.endpoint}`
1042
- );
1043
- this.#httpStreamServer = await startHTTPServer({
1044
- createServer: async (request) => {
1045
- const auth = await this.#authenticateRequest(request);
1046
- return this.#createSession(auth);
1047
- },
1048
- enableJsonResponse: httpConfig.enableJsonResponse,
1049
- eventStore: httpConfig.eventStore,
1050
- host: httpConfig.host,
1051
- oauth: this.#options.oauth,
1052
- // In stateless mode, we don't track sessions
1053
- onClose: async () => {
1054
- },
1055
- onConnect: async () => {
1056
- this.#logger.debug(
1057
- `[FastMCP debug] Stateless HTTP Stream request handled`
1058
- );
1059
- },
1060
- onUnhandledRequest: async (req, res) => {
1061
- await this.#handleUnhandledRequest(req, res, true, httpConfig.host);
1062
- },
1063
- port: httpConfig.port,
1064
- sseEndpoint: "/sse",
1065
- // Server-Sent Events endpoint for streaming events
1066
- stateless: true,
1067
- streamEndpoint: httpConfig.endpoint
1068
- });
1069
- } else {
1070
- this.#httpStreamServer = await startHTTPServer({
1071
- createServer: async (request) => {
1072
- const auth = await this.#authenticateRequest(request);
1073
- return this.#createSession(auth);
1074
- },
1075
- enableJsonResponse: httpConfig.enableJsonResponse,
1076
- eventStore: httpConfig.eventStore,
1077
- host: httpConfig.host,
1078
- oauth: this.#options.oauth,
1079
- onClose: async (session) => {
1080
- const sessionIndex = this.#sessions.indexOf(session);
1081
- if (sessionIndex !== -1) this.#sessions.splice(sessionIndex, 1);
1082
- this.emit("disconnect", {
1083
- session
1084
- });
1085
- },
1086
- onConnect: async (session) => {
1087
- this.#sessions.push(session);
1088
- this.#logger.info(`[FastMCP info] HTTP Stream session established`);
1089
- this.emit("connect", {
1090
- session
1091
- });
1092
- },
1093
- onUnhandledRequest: async (req, res) => {
1094
- await this.#handleUnhandledRequest(
1095
- req,
1096
- res,
1097
- false,
1098
- httpConfig.host
1099
- );
1100
- },
1101
- port: httpConfig.port,
1102
- sseEndpoint: "/sse",
1103
- streamEndpoint: httpConfig.endpoint
1104
- });
1105
- this.#logger.info(
1106
- `[FastMCP info] server is running on HTTP Stream at http://${httpConfig.host}:${httpConfig.port}${httpConfig.endpoint}`
1107
- );
1108
- this.#logger.info(
1109
- `[FastMCP info] Transport type: httpStream (Streamable HTTP, not SSE)`
1110
- );
1111
- }
1112
- } else {
1113
- throw new Error("Invalid transport type");
1114
- }
1115
- }
1116
- /**
1117
- * Stops the server.
1118
- */
1119
- async stop() {
1120
- if (this.#httpStreamServer) {
1121
- await this.#httpStreamServer.close();
1122
- }
1123
- }
1124
- async #authenticateRequest(request) {
1125
- if (!this.#authenticate) {
1126
- return void 0;
1127
- }
1128
- try {
1129
- return await this.#authenticate(request);
1130
- } catch (error) {
1131
- const response = this.#createOAuthChallengeResponse(error);
1132
- if (response) {
1133
- throw response;
1134
- }
1135
- throw error;
1136
- }
1137
- }
1138
- #createOAuthChallengeResponse(error) {
1139
- if (error instanceof Response) {
1140
- return void 0;
1141
- }
1142
- const resourceMetadataUrl = this.#options.oauth?.protectedResourceMetadataUrl;
1143
- if (!this.#options.oauth?.enabled || !resourceMetadataUrl) {
1144
- return void 0;
1145
- }
1146
- const errorMessage = error instanceof Error ? error.message : String(error || "Unauthorized");
1147
- const wwwAuthenticate = [
1148
- `resource_metadata="${escapeWWWAuthenticateValue(resourceMetadataUrl)}"`,
1149
- `error="invalid_token"`,
1150
- `error_description="${escapeWWWAuthenticateValue(errorMessage)}"`
1151
- ].join(", ");
1152
- return new Response(
1153
- JSON.stringify({
1154
- error: "invalid_token",
1155
- error_description: errorMessage
1156
- }),
1157
- {
1158
- headers: {
1159
- "Content-Type": "application/json",
1160
- "WWW-Authenticate": `Bearer ${wwwAuthenticate}`
1161
- },
1162
- status: 401
1163
- }
1164
- );
1165
- }
1166
- /**
1167
- * Creates a new FastMCPSession instance with the current configuration.
1168
- * Used both for regular sessions and stateless requests.
1169
- */
1170
- #createSession(auth) {
1171
- const allowedTools = auth ? this.#tools.filter(
1172
- (tool) => tool.canAccess ? tool.canAccess(auth) : true
1173
- ) : this.#tools;
1174
- return new FastMCPSession({
1175
- auth,
1176
- instructions: this.#options.instructions,
1177
- logger: this.#logger,
1178
- name: this.#options.name,
1179
- ping: this.#options.ping,
1180
- prompts: this.#prompts,
1181
- resources: this.#resources,
1182
- resourcesTemplates: this.#resourcesTemplates,
1183
- roots: this.#options.roots,
1184
- tools: allowedTools,
1185
- transportType: "httpStream",
1186
- utils: this.#options.utils,
1187
- version: this.#options.version
1188
- });
1189
- }
1190
- /**
1191
- * Handles unhandled HTTP requests with health, readiness, and OAuth endpoints
1192
- */
1193
- #handleUnhandledRequest = async (req, res, isStateless = false, host) => {
1194
- const url = new URL(req.url || "", `http://${host}`);
1195
- if (url.pathname === "/messages") {
1196
- return;
1197
- }
1198
- const healthConfig = this.#options.health ?? {};
1199
- const enabled = healthConfig.enabled === void 0 ? true : healthConfig.enabled;
1200
- if (enabled) {
1201
- const path2 = healthConfig.path ?? "/health";
1202
- try {
1203
- if (req.method === "GET" && url.pathname === path2) {
1204
- res.writeHead(healthConfig.status ?? 200, {
1205
- "Content-Type": "text/plain"
1206
- }).end(healthConfig.message ?? "\u2713 Ok");
1207
- return;
1208
- }
1209
- if (req.method === "GET" && url.pathname === "/ready") {
1210
- if (isStateless) {
1211
- const response = {
1212
- mode: "stateless",
1213
- ready: 1,
1214
- status: "ready",
1215
- total: 1
1216
- };
1217
- res.writeHead(200, {
1218
- "Content-Type": "application/json"
1219
- }).end(JSON.stringify(response));
1220
- } else {
1221
- const readySessions = this.#sessions.filter(
1222
- (s) => s.isReady
1223
- ).length;
1224
- const totalSessions = this.#sessions.length;
1225
- const allReady = readySessions === totalSessions && totalSessions > 0;
1226
- const response = {
1227
- ready: readySessions,
1228
- status: allReady ? "ready" : totalSessions === 0 ? "no_sessions" : "initializing",
1229
- total: totalSessions
1230
- };
1231
- res.writeHead(allReady ? 200 : 503, {
1232
- "Content-Type": "application/json"
1233
- }).end(JSON.stringify(response));
1234
- }
1235
- return;
1236
- }
1237
- } catch (error) {
1238
- this.#logger.error("[FastMCP error] health endpoint error", error);
1239
- }
1240
- }
1241
- const openaiAppsChallengeConfig = this.#options.openaiAppsChallenge;
1242
- const openaiAppsChallengeToken = openaiAppsChallengeConfig?.token?.trim();
1243
- const openaiAppsChallengeEnabled = openaiAppsChallengeConfig?.enabled !== false && !!openaiAppsChallengeToken;
1244
- if (openaiAppsChallengeEnabled && req.method === "GET") {
1245
- const path2 = openaiAppsChallengeConfig?.path ?? "/.well-known/openai-apps-challenge";
1246
- if (url.pathname === path2) {
1247
- res.writeHead(200, {
1248
- "Content-Type": "text/plain"
1249
- }).end(openaiAppsChallengeToken);
1250
- return;
1251
- }
1252
- }
1253
- const oauthConfig = this.#options.oauth;
1254
- if (oauthConfig?.enabled && req.method === "GET") {
1255
- if (url.pathname === "/.well-known/oauth-authorization-server" && oauthConfig.authorizationServer) {
1256
- const metadata = convertObjectToSnakeCase(
1257
- oauthConfig.authorizationServer
1258
- );
1259
- res.writeHead(200, {
1260
- "Content-Type": "application/json"
1261
- }).end(JSON.stringify(metadata));
1262
- return;
1263
- }
1264
- if (url.pathname === "/.well-known/oauth-protected-resource" && oauthConfig.protectedResource) {
1265
- const metadata = convertObjectToSnakeCase(
1266
- oauthConfig.protectedResource
1267
- );
1268
- res.writeHead(200, {
1269
- "Content-Type": "application/json"
1270
- }).end(JSON.stringify(metadata));
1271
- return;
1272
- }
1273
- }
1274
- res.writeHead(404).end();
1275
- };
1276
- #parseRuntimeConfig(overrides) {
1277
- const args2 = process.argv.slice(2);
1278
- const getArg = (name) => {
1279
- const index = args2.findIndex((arg) => arg === `--${name}`);
1280
- return index !== -1 && index + 1 < args2.length ? args2[index + 1] : void 0;
1281
- };
1282
- const transportArg = getArg("transport");
1283
- const portArg = getArg("port");
1284
- const endpointArg = getArg("endpoint");
1285
- const statelessArg = getArg("stateless");
1286
- const hostArg = getArg("host");
1287
- const envTransport = process.env.FASTMCP_TRANSPORT;
1288
- const envPort = process.env.FASTMCP_PORT;
1289
- const envEndpoint = process.env.FASTMCP_ENDPOINT;
1290
- const envStateless = process.env.FASTMCP_STATELESS;
1291
- const envHost = process.env.FASTMCP_HOST;
1292
- const transportType = overrides?.transportType || (transportArg === "http-stream" ? "httpStream" : transportArg) || envTransport || "stdio";
1293
- if (transportType === "httpStream") {
1294
- const port = parseInt(
1295
- overrides?.httpStream?.port?.toString() || portArg || envPort || "8080"
1296
- );
1297
- const host = overrides?.httpStream?.host || hostArg || envHost || "localhost";
1298
- const endpoint = overrides?.httpStream?.endpoint || endpointArg || envEndpoint || "/mcp";
1299
- const enableJsonResponse = overrides?.httpStream?.enableJsonResponse || false;
1300
- const stateless = overrides?.httpStream?.stateless || statelessArg === "true" || envStateless === "true" || false;
1301
- return {
1302
- httpStream: {
1303
- enableJsonResponse,
1304
- endpoint,
1305
- host,
1306
- port,
1307
- stateless
1308
- },
1309
- transportType: "httpStream"
1310
- };
1311
- }
1312
- return { transportType: "stdio" };
1313
- }
1314
- #removeSession(session) {
1315
- const sessionIndex = this.#sessions.indexOf(session);
1316
- if (sessionIndex !== -1) {
1317
- this.#sessions.splice(sessionIndex, 1);
1318
- this.emit("disconnect", {
1319
- session
1320
- });
1321
- }
1322
- }
1323
- };
10
+ import { z as z3 } from "zod";
1324
11
 
1325
12
  // src/monitor.ts
1326
- import { z as z2 } from "zod";
13
+ import { z } from "zod";
1327
14
  var DEFAULT_API_URL = "https://api.firecrawl.dev";
1328
15
  function resolveAuth(session) {
1329
16
  const apiKey = session?.firecrawlApiKey ?? process.env.FIRECRAWL_API_KEY;
@@ -1365,8 +52,8 @@ async function monitorRequest(session, path2, init = {}) {
1365
52
  function asText(data) {
1366
53
  return JSON.stringify(data, null, 2);
1367
54
  }
1368
- var pageStatusSchema = z2.enum(["same", "new", "changed", "removed", "error"]);
1369
- var checkStatusSchema = z2.enum([
55
+ var pageStatusSchema = z.enum(["same", "new", "changed", "removed", "error"]);
56
+ var checkStatusSchema = z.enum([
1370
57
  "queued",
1371
58
  "running",
1372
59
  "completed",
@@ -1495,6 +182,13 @@ Goal guidance:
1495
182
  - If the user says they do not care about something, include that explicitly. It is okay to ask whether they want to ignore specific noise when it is likely to matter.
1496
183
  - Do not invent page-specific sections, thresholds, entities, or business rules unless the user mentioned them.
1497
184
 
185
+ Query guidance (web monitors): \`queries\` control recall (what search retrieves) and \`goal\` controls precision (which results alert) \u2014 tune both.
186
+ - Write keywords, not sentences: \`OpenAI new model release\`, not \`tell me when OpenAI releases a new model\`.
187
+ - Quote multi-word entities (\`"Llama 4"\`); group synonyms with \`OR\` (\`launch OR release OR announcement\`).
188
+ - Keep each query tight (~2-6 terms). One broad query usually beats several narrow ones \u2014 extra queries split the \`maxResults\` budget. Use one query per distinct entity; do not emit one per facet of a single subject.
189
+ - Keep \`site:\` operators out of queries \u2014 use \`includeDomains\` / \`excludeDomains\`.
190
+ - A healthy web monitor mostly returns \`new: 0\` and alerts only on genuinely new, on-goal results. Many \`ignored\` results \u21D2 queries too broad (tighten them); nothing for long stretches \u21D2 queries too narrow or window too tight (broaden); dismissed alerts \u21D2 goal too broad (add an intent-specific Ignore). Aim for high precision with enough recall.
191
+
1498
192
  Full \`body\` requests require: \`name\`, \`schedule\` (with \`cron\` or \`text\`), and \`targets\` (one or more \`{ type: 'scrape', urls: [...] }\`, \`{ type: 'crawl', url: '...' }\`, or \`{ type: 'search', queries: [...], searchWindow?, maxResults?, includeDomains?, excludeDomains? }\`). Optional: \`goal\` (required when any search target is present), \`judgeEnabled\`, \`webhook\`, \`notification\`, \`retentionDays\`.
1499
193
 
1500
194
  **Markdown-mode (default):** Each check produces a unified text diff of the page's markdown. No extra configuration needed.
@@ -1567,22 +261,22 @@ Full \`body\` requests require: \`name\`, \`schedule\` (with \`cron\` or \`text\
1567
261
 
1568
262
  **Mixed mode (JSON + git-diff):** Use \`modes: ["json", "git-diff"]\` to get both per-field diffs and a markdown sidecar. The page is marked \`changed\` whenever either surface changed.
1569
263
  `,
1570
- parameters: z2.object({
1571
- body: z2.record(z2.string(), z2.any()).optional(),
1572
- page: z2.string().optional(),
1573
- pages: z2.array(z2.string()).optional(),
1574
- queries: z2.array(z2.string()).optional(),
1575
- searchWindow: z2.enum(["5m", "15m", "1h", "6h", "24h", "7d"]).optional(),
1576
- maxResults: z2.number().int().min(1).max(50).optional(),
1577
- includeDomains: z2.array(z2.string()).optional(),
1578
- excludeDomains: z2.array(z2.string()).optional(),
1579
- goal: z2.string().optional(),
1580
- name: z2.string().optional(),
1581
- scheduleText: z2.string().optional(),
1582
- timezone: z2.string().optional(),
1583
- email: z2.string().optional(),
1584
- includeDiffs: z2.boolean().optional(),
1585
- webhookUrl: z2.string().optional()
264
+ parameters: z.object({
265
+ body: z.record(z.string(), z.any()).optional(),
266
+ page: z.string().optional(),
267
+ pages: z.array(z.string()).optional(),
268
+ queries: z.array(z.string()).optional(),
269
+ searchWindow: z.enum(["5m", "15m", "1h", "6h", "24h", "7d"]).optional(),
270
+ maxResults: z.number().int().min(1).max(50).optional(),
271
+ includeDomains: z.array(z.string()).optional(),
272
+ excludeDomains: z.array(z.string()).optional(),
273
+ goal: z.string().optional(),
274
+ name: z.string().optional(),
275
+ scheduleText: z.string().optional(),
276
+ timezone: z.string().optional(),
277
+ email: z.string().optional(),
278
+ includeDiffs: z.boolean().optional(),
279
+ webhookUrl: z.string().optional()
1586
280
  }),
1587
281
  execute: async (args2, { session, log }) => {
1588
282
  const body = buildMonitorCreateBody(args2);
@@ -1613,11 +307,11 @@ List all Firecrawl monitors for the authenticated account.
1613
307
  { "name": "firecrawl_monitor_list", "arguments": { "limit": 20 } }
1614
308
  \`\`\`
1615
309
  `,
1616
- parameters: z2.object({
1617
- limit: z2.number().int().positive().optional(),
1618
- offset: z2.number().int().nonnegative().optional()
310
+ parameters: z.object({
311
+ limit: z.number().int().positive().optional(),
312
+ offset: z.number().int().nonnegative().optional()
1619
313
  }),
1620
- execute: async (args2, { session, log }) => {
314
+ execute: async (args2, { session }) => {
1621
315
  const { limit, offset } = args2;
1622
316
  const res = await monitorRequest(session, "/monitor", {
1623
317
  query: { limit, offset }
@@ -1644,8 +338,8 @@ Get a single monitor by ID.
1644
338
  { "name": "firecrawl_monitor_get", "arguments": { "id": "mon_abc123" } }
1645
339
  \`\`\`
1646
340
  `,
1647
- parameters: z2.object({ id: z2.string() }),
1648
- execute: async (args2, { session, log }) => {
341
+ parameters: z.object({ id: z.string() }),
342
+ execute: async (args2, { session }) => {
1649
343
  const { id } = args2;
1650
344
  const res = await monitorRequest(
1651
345
  session,
@@ -1679,11 +373,11 @@ Update a monitor. Pass any subset of fields to patch: \`name\`, \`status\` ("act
1679
373
  }
1680
374
  \`\`\`
1681
375
  `,
1682
- parameters: z2.object({
1683
- id: z2.string(),
1684
- body: z2.record(z2.string(), z2.any())
376
+ parameters: z.object({
377
+ id: z.string(),
378
+ body: z.record(z.string(), z.any())
1685
379
  }),
1686
- execute: async (args2, { session, log }) => {
380
+ execute: async (args2, { session }) => {
1687
381
  const { id, body } = args2;
1688
382
  const res = await monitorRequest(
1689
383
  session,
@@ -1712,7 +406,7 @@ Permanently delete a monitor and stop its schedule. This cannot be undone.
1712
406
  { "name": "firecrawl_monitor_delete", "arguments": { "id": "mon_abc123" } }
1713
407
  \`\`\`
1714
408
  `,
1715
- parameters: z2.object({ id: z2.string() }),
409
+ parameters: z.object({ id: z.string() }),
1716
410
  execute: async (args2, { session, log }) => {
1717
411
  const { id } = args2;
1718
412
  log.info("Deleting monitor", { id });
@@ -1743,8 +437,8 @@ Trigger a monitor check immediately, outside its normal schedule. Returns the qu
1743
437
  { "name": "firecrawl_monitor_run", "arguments": { "id": "mon_abc123" } }
1744
438
  \`\`\`
1745
439
  `,
1746
- parameters: z2.object({ id: z2.string() }),
1747
- execute: async (args2, { session, log }) => {
440
+ parameters: z.object({ id: z.string() }),
441
+ execute: async (args2, { session }) => {
1748
442
  const { id } = args2;
1749
443
  const res = await monitorRequest(
1750
444
  session,
@@ -1773,13 +467,13 @@ List historical checks for a monitor.
1773
467
  { "name": "firecrawl_monitor_checks", "arguments": { "id": "mon_abc123", "limit": 10, "status": "completed" } }
1774
468
  \`\`\`
1775
469
  `,
1776
- parameters: z2.object({
1777
- id: z2.string(),
1778
- limit: z2.number().int().positive().optional(),
1779
- offset: z2.number().int().nonnegative().optional(),
470
+ parameters: z.object({
471
+ id: z.string(),
472
+ limit: z.number().int().positive().optional(),
473
+ offset: z.number().int().nonnegative().optional(),
1780
474
  status: checkStatusSchema.optional()
1781
475
  }),
1782
- execute: async (args2, { session, log }) => {
476
+ execute: async (args2, { session }) => {
1783
477
  const { id, limit, offset, status } = args2;
1784
478
  const res = await monitorRequest(
1785
479
  session,
@@ -1856,14 +550,14 @@ The endpoint paginates via a top-level \`next\` URL; this tool returns one page
1856
550
  }
1857
551
  \`\`\`
1858
552
  `,
1859
- parameters: z2.object({
1860
- id: z2.string(),
1861
- checkId: z2.string(),
1862
- limit: z2.number().int().positive().optional(),
1863
- skip: z2.number().int().nonnegative().optional(),
553
+ parameters: z.object({
554
+ id: z.string(),
555
+ checkId: z.string(),
556
+ limit: z.number().int().positive().optional(),
557
+ skip: z.number().int().nonnegative().optional(),
1864
558
  pageStatus: pageStatusSchema.optional()
1865
559
  }),
1866
- execute: async (args2, { session, log }) => {
560
+ execute: async (args2, { session }) => {
1867
561
  const { id, checkId, limit, skip, pageStatus } = args2;
1868
562
  const res = await monitorRequest(
1869
563
  session,
@@ -1876,7 +570,7 @@ The endpoint paginates via a top-level \`next\` URL; this tool returns one page
1876
570
  }
1877
571
 
1878
572
  // src/research.ts
1879
- import { z as z3 } from "zod";
573
+ import { z as z2 } from "zod";
1880
574
  var BASE = "/v2/search/research";
1881
575
  var ORIGIN_HEADERS = { "X-Origin": "mcp-fastmcp" };
1882
576
  function appendParam(params, key, value) {
@@ -1987,26 +681,30 @@ function registerResearchTools(server2, getClient2) {
1987
681
  server2.addTool({
1988
682
  name: "firecrawl_research_search_papers",
1989
683
  annotations: {
1990
- title: "Search arXiv papers",
684
+ title: "Search research papers",
1991
685
  readOnlyHint: true,
1992
- // Semantic search over indexed arXiv metadata; returns ranked results only.
686
+ // Semantic search over indexed paper metadata; returns ranked results only.
1993
687
  openWorldHint: true,
1994
- // Searches the public arXiv research corpus.
688
+ // Searches the Firecrawl research paper index.
1995
689
  destructiveHint: false
1996
- // Query-only; no writes to arXiv or the research index.
690
+ // Query-only; no writes to external sources or the research index.
1997
691
  },
1998
- description: "Primary entry point for finding arXiv papers by topic. Semantic (HyDE) search over arXiv abstracts; returns ranked papers with arXiv id, title, and abstract. The query should be a natural-language description of what you want. Run SEVERAL distinct framings of the question (sibling domains, rival methods, dataset/benchmark names) rather than one query \u2014 recall improves markedly with diverse framings. Returns up to `k` results (default 40).",
1999
- parameters: z3.object({
2000
- query: z3.string().min(1),
2001
- k: z3.number().int().min(1).max(500).optional(),
2002
- authors: z3.array(z3.string()).optional().describe(
692
+ description: "Primary entry point for finding research papers by topic across AI/ML, computer science, math, physics, biomedical, life sciences, and clinical literature. Semantic (HyDE) search over indexed paper metadata and abstracts; returns ranked papers with paper id, title, authors, and abstract. The query should be a natural-language research topic or question. Run SEVERAL distinct framings of the question (sibling domains, rival methods, dataset or benchmark names, conditions, populations, interventions, or outcomes) rather than one query \u2014 recall improves markedly with diverse framings.",
693
+ parameters: z2.object({
694
+ query: z2.string().min(1).describe(
695
+ "Natural-language research topic or question, including methods, systems, conditions, populations, interventions, or outcomes when relevant."
696
+ ),
697
+ k: z2.number().int().min(1).max(500).optional().describe("Number of ranked papers to return (default 40)."),
698
+ authors: z2.array(z2.string()).optional().describe(
2003
699
  "Author substring filter(s); ALL must match (case-insensitive)."
2004
700
  ),
2005
- categories: z3.array(z3.string()).optional().describe("arXiv category filter(s) (e.g. `cs.LG`); ALL must match."),
2006
- from: z3.string().optional().describe(
701
+ categories: z2.array(z2.string()).optional().describe(
702
+ "Paper category filter(s) (e.g. `cs.LG`); ALL provided values must match."
703
+ ),
704
+ from: z2.string().optional().describe(
2007
705
  "Inclusive lower bound on created/updated date (`YYYY-MM-DD`)."
2008
706
  ),
2009
- to: z3.string().optional().describe(
707
+ to: z2.string().optional().describe(
2010
708
  "Inclusive upper bound on created/updated date (`YYYY-MM-DD`)."
2011
709
  )
2012
710
  }),
@@ -2039,8 +737,8 @@ function registerResearchTools(server2, getClient2) {
2039
737
  // Read-only metadata lookup.
2040
738
  },
2041
739
  description: "Fetch canonical metadata for one paper by primaryId or canonical paperId. Use this after search/related results when you need the full title, abstract, authors, categories, source ids, and dates rendered as markdown.",
2042
- parameters: z3.object({
2043
- paperId: z3.string().min(1).describe(
740
+ parameters: z2.object({
741
+ paperId: z2.string().min(1).describe(
2044
742
  "Canonical paperId or primaryId such as `arxiv:1706.03762`, `pmcid:PMC12530322`, `pmid:40953549`, or `doi:10.1016/j.neunet.2025.108095`."
2045
743
  )
2046
744
  }),
@@ -2066,12 +764,12 @@ function registerResearchTools(server2, getClient2) {
2066
764
  // Read-only graph query; no modifications.
2067
765
  },
2068
766
  description: "Expand from anchor papers you have already found, via the citation graph, ranked and filtered to a natural-language `intent`. Pass arXiv ids of your strongest hits as `seed_ids`. Modes: `similar` (cocitation/coupling \u2014 papers in the same niche; the default), `citers` (papers that cite the anchors), `references` (papers the anchors cite). This reaches relevant papers that plain search misses, so use it on your best hits before finishing. A `similar` call already runs a DEEP multi-round expansion internally (re-seeding from each round\u2019s best finds), so one call reaches the wider neighborhood \u2014 no need to chain many. Returns the candidates plus the pool size.",
2069
- parameters: z3.object({
2070
- seed_ids: z3.array(z3.string()).min(1).max(10),
2071
- intent: z3.string().min(1),
2072
- mode: z3.enum(["similar", "citers", "references"]).optional(),
2073
- k: z3.number().int().min(1).max(500).optional(),
2074
- rerank: z3.boolean().optional().describe("Apply an additional rerank over the fused candidates.")
767
+ parameters: z2.object({
768
+ seed_ids: z2.array(z2.string()).min(1).max(10),
769
+ intent: z2.string().min(1),
770
+ mode: z2.enum(["similar", "citers", "references"]).optional(),
771
+ k: z2.number().int().min(1).max(500).optional(),
772
+ rerank: z2.boolean().optional().describe("Apply an additional rerank over the fused candidates.")
2075
773
  }),
2076
774
  execute: async (args2, { session }) => {
2077
775
  const { seed_ids, intent, mode, k, rerank } = args2;
@@ -2108,12 +806,12 @@ note: ${res.data.note}` : "";
2108
806
  // Read-only passage retrieval.
2109
807
  },
2110
808
  description: "Read the most relevant in-body (full-text) passages of ONE specific paper for a question. Use this to VERIFY whether a candidate actually satisfies a constraint before you include or reject it (e.g. 'does this paper actually use technique X / report a score on benchmark Y'). Returns the best-matching passages, or a notice if the paper's full text is unavailable.",
2111
- parameters: z3.object({
2112
- paperId: z3.string().min(1).describe(
809
+ parameters: z2.object({
810
+ paperId: z2.string().min(1).describe(
2113
811
  "Canonical paperId or primaryId such as `arxiv:1706.03762`, `pmcid:PMC12530322`, `pmid:40953549`, or `doi:10.1016/j.neunet.2025.108095`."
2114
812
  ),
2115
- question: z3.string().min(1),
2116
- k: z3.number().int().min(1).max(50).optional().describe("Number of passages to return (default 4).")
813
+ question: z2.string().min(1),
814
+ k: z2.number().int().min(1).max(50).optional().describe("Number of passages to return (default 4).")
2117
815
  }),
2118
816
  execute: async (args2, { session }) => {
2119
817
  const { paperId, question, k } = args2;
@@ -2141,9 +839,9 @@ note: ${res.data.note}` : "";
2141
839
  // Query-only; does not create issues, PRs, or modify repositories.
2142
840
  },
2143
841
  description: "Search GitHub issue/PR history and repository readmes. Returns ranked matches with repo, url, a short snippet, and (when available) the full matched content in markdown.",
2144
- parameters: z3.object({
2145
- query: z3.string().min(1),
2146
- k: z3.number().int().min(1).max(100).optional()
842
+ parameters: z2.object({
843
+ query: z2.string().min(1),
844
+ k: z2.number().int().min(1).max(100).optional()
2147
845
  }),
2148
846
  execute: async (args2, { session }) => {
2149
847
  const { query, k } = args2;
@@ -2164,6 +862,7 @@ note: ${res.data.note}` : "";
2164
862
  dotenv.config({ debug: false, quiet: true });
2165
863
  var require2 = createRequire(import.meta.url);
2166
864
  var { version: packageVersion } = require2("../package.json");
865
+ var authResultByRequest = /* @__PURE__ */ Symbol("firecrawlMcpAuthResult");
2167
866
  function normalizeHeader(value) {
2168
867
  if (value == null) return void 0;
2169
868
  const v = Array.isArray(value) ? value[0] : value;
@@ -2201,6 +900,33 @@ function getMcpResourceUrl() {
2201
900
  function getOAuthProtectedResourceMetadataUrl() {
2202
901
  return `${new URL(getMcpResourceUrl()).origin}/.well-known/oauth-protected-resource`;
2203
902
  }
903
+ function escapeWWWAuthenticateValue(value) {
904
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
905
+ }
906
+ function createOAuthChallengeResponse(error) {
907
+ if (!isMcpOAuthEnabled()) {
908
+ return void 0;
909
+ }
910
+ const errorMessage = error instanceof Error ? error.message : String(error || "Unauthorized");
911
+ const wwwAuthenticate = [
912
+ `resource_metadata="${escapeWWWAuthenticateValue(getOAuthProtectedResourceMetadataUrl())}"`,
913
+ 'error="invalid_token"',
914
+ `error_description="${escapeWWWAuthenticateValue(errorMessage)}"`
915
+ ].join(", ");
916
+ return new Response(
917
+ JSON.stringify({
918
+ error: "invalid_token",
919
+ error_description: errorMessage
920
+ }),
921
+ {
922
+ headers: {
923
+ "Content-Type": "application/json",
924
+ "WWW-Authenticate": `Bearer ${wwwAuthenticate}`
925
+ },
926
+ status: 401
927
+ }
928
+ );
929
+ }
2204
930
  function getOAuthIntrospectionEndpoint() {
2205
931
  return `${getOAuthIssuer()}/api/oauth/introspect`;
2206
932
  }
@@ -2251,6 +977,52 @@ async function resolveCredentialFromHeaders(headers) {
2251
977
  }
2252
978
  return void 0;
2253
979
  }
980
+ async function authenticateRequest(request) {
981
+ const headerCred = request?.headers ? await resolveCredentialFromHeaders(request.headers) : void 0;
982
+ const envCred = resolveCredentialFromEnv();
983
+ if (process.env.CLOUD_SERVICE === "true") {
984
+ if (!headerCred) {
985
+ const clientIp = extractClientIp(request);
986
+ if (process.env.KEYLESS_PROXY_SECRET && clientIp && await keylessEligible(clientIp)) {
987
+ return { firecrawlApiKey: void 0, keylessClientIp: clientIp };
988
+ }
989
+ throw new Error(
990
+ "Firecrawl credentials required: OAuth access token (Authorization: Bearer fco_...) or API key (x-firecrawl-api-key)"
991
+ );
992
+ }
993
+ return { firecrawlApiKey: headerCred };
994
+ }
995
+ const credential = headerCred ?? envCred;
996
+ const httpStreaming = isHttpStreamingTransport();
997
+ if (!httpStreaming && !process.env.FIRECRAWL_API_KEY && !process.env.FIRECRAWL_API_URL) {
998
+ console.error(
999
+ "No FIRECRAWL_API_KEY or FIRECRAWL_API_URL set \u2014 running in keyless mode. firecrawl_scrape and firecrawl_search are free (rate-limited per IP) against the Firecrawl cloud; other tools require an API key (get one free at https://firecrawl.dev)."
1000
+ );
1001
+ }
1002
+ if (httpStreaming && !credential && !process.env.FIRECRAWL_API_URL) {
1003
+ console.error(
1004
+ "HTTP MCP transport requires FIRECRAWL_API_URL and/or credentials (OAuth: Authorization Bearer fco_..., or FIRECRAWL_API_KEY / FIRECRAWL_OAUTH_TOKEN)"
1005
+ );
1006
+ process.exit(1);
1007
+ }
1008
+ return { firecrawlApiKey: credential };
1009
+ }
1010
+ async function authenticateWithOAuthChallenge(request) {
1011
+ if (request?.[authResultByRequest]) {
1012
+ return request[authResultByRequest];
1013
+ }
1014
+ const authResult = authenticateRequest(request).catch((error) => {
1015
+ const oauthChallenge = createOAuthChallengeResponse(error);
1016
+ if (oauthChallenge) {
1017
+ throw oauthChallenge;
1018
+ }
1019
+ throw error;
1020
+ });
1021
+ if (request) {
1022
+ request[authResultByRequest] = authResult;
1023
+ }
1024
+ return authResult;
1025
+ }
2254
1026
  function removeEmptyTopLevel(obj) {
2255
1027
  const out = {};
2256
1028
  for (const [k, v] of Object.entries(obj)) {
@@ -2263,7 +1035,7 @@ function removeEmptyTopLevel(obj) {
2263
1035
  }
2264
1036
  return out;
2265
1037
  }
2266
- var searchDomainSchema = z4.string().trim().toLowerCase().min(1).max(253).regex(
1038
+ var searchDomainSchema = z3.string().trim().toLowerCase().min(1).max(253).regex(
2267
1039
  /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/,
2268
1040
  "Domain must be a valid hostname without protocol or path"
2269
1041
  );
@@ -2323,48 +1095,23 @@ var server = new FastMCP({
2323
1095
  resource: getMcpResourceUrl(),
2324
1096
  resourceName: "Firecrawl MCP",
2325
1097
  scopesSupported: ["firecrawl:global"]
2326
- },
2327
- protectedResourceMetadataUrl: getOAuthProtectedResourceMetadataUrl()
2328
- },
2329
- authenticate: async (request) => {
2330
- const headerCred = request?.headers ? await resolveCredentialFromHeaders(request.headers) : void 0;
2331
- const envCred = resolveCredentialFromEnv();
2332
- if (process.env.CLOUD_SERVICE === "true") {
2333
- if (!headerCred) {
2334
- const clientIp = extractClientIp(request);
2335
- if (process.env.KEYLESS_PROXY_SECRET && clientIp && await keylessEligible(clientIp)) {
2336
- return { firecrawlApiKey: void 0, keylessClientIp: clientIp };
2337
- }
2338
- throw new Error(
2339
- "Firecrawl credentials required: OAuth access token (Authorization: Bearer fco_...) or API key (x-firecrawl-api-key)"
2340
- );
2341
- }
2342
- return { firecrawlApiKey: headerCred };
2343
1098
  }
2344
- const credential = headerCred ?? envCred;
2345
- const httpStreaming = isHttpStreamingTransport();
2346
- if (!httpStreaming && !process.env.FIRECRAWL_API_KEY && !process.env.FIRECRAWL_API_URL) {
2347
- console.error(
2348
- "No FIRECRAWL_API_KEY or FIRECRAWL_API_URL set \u2014 running in keyless mode. firecrawl_scrape and firecrawl_search are free (rate-limited per IP) against the Firecrawl cloud; other tools require an API key (get one free at https://firecrawl.dev)."
2349
- );
2350
- }
2351
- if (httpStreaming && !credential && !process.env.FIRECRAWL_API_URL) {
2352
- console.error(
2353
- "HTTP MCP transport requires FIRECRAWL_API_URL and/or credentials (OAuth: Authorization Bearer fco_..., or FIRECRAWL_API_KEY / FIRECRAWL_OAUTH_TOKEN)"
2354
- );
2355
- process.exit(1);
2356
- }
2357
- return { firecrawlApiKey: credential };
2358
1099
  },
1100
+ authenticate: authenticateWithOAuthChallenge,
2359
1101
  // Lightweight health endpoint for LB checks
2360
1102
  health: {
2361
1103
  enabled: true,
2362
1104
  message: "ok",
2363
1105
  path: "/health",
2364
1106
  status: 200
2365
- },
2366
- ...openAiAppsChallengeToken ? { openaiAppsChallenge: { token: openAiAppsChallengeToken } } : {}
1107
+ }
2367
1108
  });
1109
+ if (openAiAppsChallengeToken) {
1110
+ server.getApp().get(
1111
+ "/.well-known/openai-apps-challenge",
1112
+ (context) => context.text(openAiAppsChallengeToken)
1113
+ );
1114
+ }
2368
1115
  function createClient(apiKey) {
2369
1116
  const config = {
2370
1117
  ...process.env.FIRECRAWL_API_URL && {
@@ -2461,10 +1208,10 @@ function transformScrapeParams(args2) {
2461
1208
  delete out.pdfOptions;
2462
1209
  return out;
2463
1210
  }
2464
- var scrapeParamsSchema = z4.object({
2465
- url: z4.string().url(),
2466
- formats: z4.array(
2467
- z4.enum([
1211
+ var scrapeParamsSchema = z3.object({
1212
+ url: z3.string().url(),
1213
+ formats: z3.array(
1214
+ z3.enum([
2468
1215
  "markdown",
2469
1216
  "html",
2470
1217
  "rawHtml",
@@ -2478,62 +1225,62 @@ var scrapeParamsSchema = z4.object({
2478
1225
  "audio"
2479
1226
  ])
2480
1227
  ).optional(),
2481
- jsonOptions: z4.object({
2482
- prompt: z4.string().optional(),
2483
- schema: z4.record(z4.string(), z4.any()).optional()
1228
+ jsonOptions: z3.object({
1229
+ prompt: z3.string().optional(),
1230
+ schema: z3.record(z3.string(), z3.any()).optional()
2484
1231
  }).optional(),
2485
- queryOptions: z4.object({
2486
- prompt: z4.string().max(1e4),
2487
- mode: z4.enum(["directQuote", "freeform"]).default("freeform")
1232
+ queryOptions: z3.object({
1233
+ prompt: z3.string().max(1e4),
1234
+ mode: z3.enum(["directQuote", "freeform"]).default("freeform")
2488
1235
  }).optional(),
2489
- screenshotOptions: z4.object({
2490
- fullPage: z4.boolean().optional(),
2491
- quality: z4.number().optional(),
2492
- viewport: z4.object({ width: z4.number(), height: z4.number() }).optional()
1236
+ screenshotOptions: z3.object({
1237
+ fullPage: z3.boolean().optional(),
1238
+ quality: z3.number().optional(),
1239
+ viewport: z3.object({ width: z3.number(), height: z3.number() }).optional()
2493
1240
  }).optional(),
2494
- parsers: z4.array(z4.enum(["pdf"])).optional(),
2495
- pdfOptions: z4.object({
2496
- maxPages: z4.number().int().min(1).max(1e4).optional()
1241
+ parsers: z3.array(z3.enum(["pdf"])).optional(),
1242
+ pdfOptions: z3.object({
1243
+ maxPages: z3.number().int().min(1).max(1e4).optional()
2497
1244
  }).optional(),
2498
- onlyMainContent: z4.boolean().optional(),
2499
- redactPII: z4.boolean().optional(),
2500
- includeTags: z4.array(z4.string()).optional(),
2501
- excludeTags: z4.array(z4.string()).optional(),
2502
- waitFor: z4.number().optional(),
1245
+ onlyMainContent: z3.boolean().optional(),
1246
+ redactPII: z3.boolean().optional(),
1247
+ includeTags: z3.array(z3.string()).optional(),
1248
+ excludeTags: z3.array(z3.string()).optional(),
1249
+ waitFor: z3.number().optional(),
2503
1250
  ...SAFE_MODE ? {} : {
2504
- actions: z4.array(
2505
- z4.object({
2506
- type: z4.enum(allowedActionTypes),
2507
- selector: z4.string().optional(),
2508
- milliseconds: z4.number().optional(),
2509
- text: z4.string().optional(),
2510
- key: z4.string().optional(),
2511
- direction: z4.enum(["up", "down"]).optional(),
2512
- script: z4.string().optional(),
2513
- fullPage: z4.boolean().optional()
1251
+ actions: z3.array(
1252
+ z3.object({
1253
+ type: z3.enum(allowedActionTypes),
1254
+ selector: z3.string().optional(),
1255
+ milliseconds: z3.number().optional(),
1256
+ text: z3.string().optional(),
1257
+ key: z3.string().optional(),
1258
+ direction: z3.enum(["up", "down"]).optional(),
1259
+ script: z3.string().optional(),
1260
+ fullPage: z3.boolean().optional()
2514
1261
  })
2515
1262
  ).optional()
2516
1263
  },
2517
- mobile: z4.boolean().optional(),
2518
- skipTlsVerification: z4.boolean().optional(),
2519
- removeBase64Images: z4.boolean().optional(),
2520
- location: z4.object({
2521
- country: z4.string().optional(),
2522
- languages: z4.array(z4.string()).optional()
1264
+ mobile: z3.boolean().optional(),
1265
+ skipTlsVerification: z3.boolean().optional(),
1266
+ removeBase64Images: z3.boolean().optional(),
1267
+ location: z3.object({
1268
+ country: z3.string().optional(),
1269
+ languages: z3.array(z3.string()).optional()
2523
1270
  }).optional(),
2524
- storeInCache: z4.boolean().optional(),
2525
- zeroDataRetention: z4.boolean().optional(),
2526
- maxAge: z4.number().optional(),
2527
- lockdown: z4.boolean().optional(),
2528
- proxy: z4.enum(["basic", "stealth", "enhanced", "auto"]).optional(),
2529
- profile: z4.object({
2530
- name: z4.string(),
2531
- saveChanges: z4.boolean().optional()
1271
+ storeInCache: z3.boolean().optional(),
1272
+ zeroDataRetention: z3.boolean().optional(),
1273
+ maxAge: z3.number().optional(),
1274
+ lockdown: z3.boolean().optional(),
1275
+ proxy: z3.enum(["basic", "stealth", "enhanced", "auto"]).optional(),
1276
+ profile: z3.object({
1277
+ name: z3.string(),
1278
+ saveChanges: z3.boolean().optional()
2532
1279
  }).optional()
2533
1280
  });
2534
- var parseOptionParamsSchema = z4.object({
2535
- formats: z4.array(
2536
- z4.enum([
1281
+ var parseOptionParamsSchema = z3.object({
1282
+ formats: z3.array(
1283
+ z3.enum([
2537
1284
  "markdown",
2538
1285
  "html",
2539
1286
  "rawHtml",
@@ -2543,48 +1290,48 @@ var parseOptionParamsSchema = z4.object({
2543
1290
  "query"
2544
1291
  ])
2545
1292
  ).optional(),
2546
- jsonOptions: z4.object({
2547
- prompt: z4.string().optional(),
2548
- schema: z4.record(z4.string(), z4.any()).optional()
1293
+ jsonOptions: z3.object({
1294
+ prompt: z3.string().optional(),
1295
+ schema: z3.record(z3.string(), z3.any()).optional()
2549
1296
  }).optional(),
2550
- queryOptions: z4.object({
2551
- prompt: z4.string().max(1e4),
2552
- mode: z4.enum(["directQuote", "freeform"]).default("freeform")
1297
+ queryOptions: z3.object({
1298
+ prompt: z3.string().max(1e4),
1299
+ mode: z3.enum(["directQuote", "freeform"]).default("freeform")
2553
1300
  }).optional(),
2554
- parsers: z4.array(z4.enum(["pdf"])).optional(),
2555
- pdfOptions: z4.object({
2556
- maxPages: z4.number().int().min(1).max(1e4).optional()
1301
+ parsers: z3.array(z3.enum(["pdf"])).optional(),
1302
+ pdfOptions: z3.object({
1303
+ maxPages: z3.number().int().min(1).max(1e4).optional()
2557
1304
  }).optional(),
2558
- onlyMainContent: z4.boolean().optional(),
2559
- redactPII: z4.boolean().optional(),
2560
- includeTags: z4.array(z4.string()).optional(),
2561
- excludeTags: z4.array(z4.string()).optional(),
2562
- removeBase64Images: z4.boolean().optional(),
2563
- skipTlsVerification: z4.boolean().optional(),
2564
- storeInCache: z4.boolean().optional(),
2565
- zeroDataRetention: z4.boolean().optional(),
2566
- maxAge: z4.number().optional(),
2567
- proxy: z4.enum(["basic", "auto"]).optional()
1305
+ onlyMainContent: z3.boolean().optional(),
1306
+ redactPII: z3.boolean().optional(),
1307
+ includeTags: z3.array(z3.string()).optional(),
1308
+ excludeTags: z3.array(z3.string()).optional(),
1309
+ removeBase64Images: z3.boolean().optional(),
1310
+ skipTlsVerification: z3.boolean().optional(),
1311
+ storeInCache: z3.boolean().optional(),
1312
+ zeroDataRetention: z3.boolean().optional(),
1313
+ maxAge: z3.number().optional(),
1314
+ proxy: z3.enum(["basic", "auto"]).optional()
2568
1315
  });
2569
1316
  var localParseParamsSchema = parseOptionParamsSchema.extend({
2570
- filePath: z4.string().min(1).describe(
1317
+ filePath: z3.string().min(1).describe(
2571
1318
  "Absolute or relative path to a local file to parse. Supported: .html, .htm, .pdf, .docx, .doc, .odt, .rtf, .xlsx, .xls"
2572
1319
  ),
2573
- contentType: z4.string().optional().describe(
1320
+ contentType: z3.string().optional().describe(
2574
1321
  "Optional MIME type override. If omitted, the server infers the file kind from the extension."
2575
1322
  )
2576
1323
  });
2577
1324
  var hostedParseParamsSchema = parseOptionParamsSchema.extend({
2578
- filePath: z4.string().min(1).optional().describe(
1325
+ filePath: z3.string().min(1).optional().describe(
2579
1326
  "Phase 1 only: path to the local file on the caller/harness machine. Hosted MCP will not read or stat this path; it is used only to produce upload instructions."
2580
1327
  ),
2581
- uploadRef: z4.string().min(1).optional().describe(
1328
+ uploadRef: z3.string().min(1).optional().describe(
2582
1329
  "Phase 2 only: short-lived upload reference returned by phase 1 after the local PUT upload completes."
2583
1330
  ),
2584
- contentType: z4.string().optional().describe(
1331
+ contentType: z3.string().optional().describe(
2585
1332
  "Phase 1 MIME type override. If omitted, the server infers it from the file extension without reading the file."
2586
1333
  ),
2587
- declaredSizeBytes: z4.number().int().positive().optional().describe(
1334
+ declaredSizeBytes: z3.number().int().positive().optional().describe(
2588
1335
  "Optional phase 1 size declaration. Hosted MCP does not stat the file; provide this only if the caller already knows it."
2589
1336
  )
2590
1337
  }).superRefine((value, ctx) => {
@@ -2592,7 +1339,7 @@ var hostedParseParamsSchema = parseOptionParamsSchema.extend({
2592
1339
  const hasUploadRef = typeof value.uploadRef === "string" && value.uploadRef.length > 0;
2593
1340
  if (hasFilePath === hasUploadRef) {
2594
1341
  ctx.addIssue({
2595
- code: z4.ZodIssueCode.custom,
1342
+ code: z3.ZodIssueCode.custom,
2596
1343
  message: "Hosted firecrawl_parse requires exactly one of filePath (phase 1) or uploadRef (phase 2).",
2597
1344
  path: hasFilePath && hasUploadRef ? ["uploadRef"] : ["filePath"]
2598
1345
  });
@@ -2616,7 +1363,11 @@ function inferContentType(filename) {
2616
1363
  return EXTENSION_CONTENT_TYPES[ext] ?? "application/octet-stream";
2617
1364
  }
2618
1365
  function extractParseOptions(args2) {
2619
- const { filePath, uploadRef, contentType, declaredSizeBytes, ...options } = args2;
1366
+ const options = { ...args2 };
1367
+ delete options.filePath;
1368
+ delete options.uploadRef;
1369
+ delete options.contentType;
1370
+ delete options.declaredSizeBytes;
2620
1371
  return options;
2621
1372
  }
2622
1373
  function buildParseOptionsPayload(options) {
@@ -2959,13 +1710,13 @@ Map a website to discover all indexed URLs on the site.
2959
1710
  \`\`\`
2960
1711
  **Returns:** Array of URLs found on the site, filtered by search query if provided.
2961
1712
  `,
2962
- parameters: z4.object({
2963
- url: z4.string().url(),
2964
- search: z4.string().optional(),
2965
- sitemap: z4.enum(["include", "skip", "only"]).optional(),
2966
- includeSubdomains: z4.boolean().optional(),
2967
- limit: z4.number().optional(),
2968
- ignoreQueryParameters: z4.boolean().optional()
1713
+ parameters: z3.object({
1714
+ url: z3.string().url(),
1715
+ search: z3.string().optional(),
1716
+ sitemap: z3.enum(["include", "skip", "only"]).optional(),
1717
+ includeSubdomains: z3.boolean().optional(),
1718
+ limit: z3.number().optional(),
1719
+ ignoreQueryParameters: z3.boolean().optional()
2969
1720
  }),
2970
1721
  execute: async (args2, { session, log }) => {
2971
1722
  const { url, ...options } = args2;
@@ -2996,7 +1747,7 @@ Search the web and optionally extract content from search results. This is the m
2996
1747
  The query also supports search operators, that you can use if needed to refine the search:
2997
1748
  | Operator | Functionality | Examples |
2998
1749
  ---|-|-|
2999
- | \`""\` | Non-fuzzy matches a string of text | \`"Firecrawl"\`
1750
+ | \`"\` | Non-fuzzy matches a string of text | \`"Firecrawl"\`
3000
1751
  | \`-\` | Excludes certain keywords or negates other operators | \`-bad\`, \`-site:firecrawl.dev\`
3001
1752
  | \`site:\` | Only returns results from a specified website | \`site:firecrawl.dev\`
3002
1753
  | \`inurl:\` | Only returns results that include a word in the URL | \`inurl:firecrawl\`
@@ -3012,6 +1763,7 @@ The query also supports search operators, that you can use if needed to refine t
3012
1763
  **Common mistakes:** Using crawl or map for open-ended questions (use search instead).
3013
1764
  **Prompt Example:** "Find the latest research papers on AI published in 2023."
3014
1765
  **Sources:** web, images, news, default to web unless needed images or news.
1766
+ **Categories:** Optional filter to limit result types: \`github\` (GitHub repositories, code, issues, and docs), \`research\` (academic and research sources), \`pdf\` (PDF results). Example: \`categories: ["github", "research"]\`.
3015
1767
  **Domain filters:** Use includeDomains to restrict results to specific domains, or excludeDomains to remove domains. Do not use both in the same request. Domains must be hostnames only, without protocol or path.
3016
1768
  **Scrape Options:** Only use scrapeOptions when you think it is absolutely necessary. When you do so default to a lower limit to avoid timeouts, 5 or lower.
3017
1769
  **Optimal Workflow:** Search first using firecrawl_search without formats, then after fetching the results, use the scrape tool to get the content of the relevantpage(s) that you want to scrape
@@ -3038,6 +1790,7 @@ The query also supports search operators, that you can use if needed to refine t
3038
1790
  "arguments": {
3039
1791
  "query": "latest AI research papers 2023",
3040
1792
  "limit": 5,
1793
+ "categories": ["github", "research"],
3041
1794
  "lang": "en",
3042
1795
  "country": "us",
3043
1796
  "sources": [
@@ -3054,17 +1807,20 @@ The query also supports search operators, that you can use if needed to refine t
3054
1807
  \`\`\`
3055
1808
  **Returns:** A JSON envelope of the form \`{ success, data: { web?, images?, news? }, id, creditsUsed }\`. Each result array contains the search results (with optional scraped content). Pass the top-level \`id\` to \`firecrawl_search_feedback\` after you've used the results.
3056
1809
  `,
3057
- parameters: z4.object({
3058
- query: z4.string().min(1),
3059
- limit: z4.number().optional(),
3060
- tbs: z4.string().optional(),
3061
- filter: z4.string().optional(),
3062
- location: z4.string().optional(),
3063
- includeDomains: z4.array(searchDomainSchema).optional(),
3064
- excludeDomains: z4.array(searchDomainSchema).optional(),
3065
- sources: z4.array(z4.object({ type: z4.enum(["web", "images", "news"]) })).optional(),
1810
+ parameters: z3.object({
1811
+ query: z3.string().min(1),
1812
+ limit: z3.number().optional(),
1813
+ tbs: z3.string().optional(),
1814
+ filter: z3.string().optional(),
1815
+ location: z3.string().optional(),
1816
+ includeDomains: z3.array(searchDomainSchema).optional(),
1817
+ excludeDomains: z3.array(searchDomainSchema).optional(),
1818
+ sources: z3.array(z3.object({ type: z3.enum(["web", "images", "news"]) })).optional(),
1819
+ categories: z3.array(z3.enum(["github", "research", "pdf"])).optional().describe(
1820
+ "Limit results to specific source types. `github` searches GitHub repositories, code, issues, and docs; `research` searches academic and research sources; `pdf` searches PDF results."
1821
+ ),
3066
1822
  scrapeOptions: scrapeParamsSchema.omit({ url: true }).partial().optional(),
3067
- enterprise: z4.array(z4.enum(["default", "anon", "zdr"])).optional()
1823
+ enterprise: z3.array(z3.enum(["default", "anon", "zdr"])).optional()
3068
1824
  }).refine(
3069
1825
  (args2) => !(args2.includeDomains?.length && args2.excludeDomains?.length),
3070
1826
  "includeDomains and excludeDomains cannot both be specified"
@@ -3207,7 +1963,7 @@ async function getCrawlStatusWithOrigin(client, jobId) {
3207
1963
  }
3208
1964
  async function waitForCrawlCompletionWithOrigin(client, jobId, pollInterval = 2, timeout) {
3209
1965
  const startedAt = Date.now();
3210
- while (true) {
1966
+ for (; ; ) {
3211
1967
  const status = await getCrawlStatusWithOrigin(client, jobId);
3212
1968
  if (["completed", "failed", "cancelled"].includes(String(status.status ?? ""))) {
3213
1969
  return status;
@@ -3220,17 +1976,17 @@ async function waitForCrawlCompletionWithOrigin(client, jobId, pollInterval = 2,
3220
1976
  );
3221
1977
  }
3222
1978
  }
3223
- var feedbackIssueSchema = z4.string().trim().min(1).max(80).regex(
1979
+ var feedbackIssueSchema = z3.string().trim().min(1).max(80).regex(
3224
1980
  /^[a-z0-9][a-z0-9_-]*$/,
3225
1981
  "Issue codes must use lowercase letters, numbers, underscores, or hyphens"
3226
1982
  );
3227
- var valuableSourceSchema = z4.object({
3228
- url: z4.string().url(),
3229
- reason: z4.string().max(1e3).optional()
1983
+ var valuableSourceSchema = z3.object({
1984
+ url: z3.string().url(),
1985
+ reason: z3.string().max(1e3).optional()
3230
1986
  });
3231
- var missingContentSchema = z4.object({
3232
- topic: z4.string().min(1, "topic must not be empty").max(200, "topic must be 200 characters or fewer"),
3233
- description: z4.string().max(2e3).optional()
1987
+ var missingContentSchema = z3.object({
1988
+ topic: z3.string().min(1, "topic must not be empty").max(200, "topic must be 200 characters or fewer"),
1989
+ description: z3.string().max(2e3).optional()
3234
1990
  });
3235
1991
  var FEEDBACK_DISABLED_VALUES = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
3236
1992
  function feedbackEnvEnabled(...keys) {
@@ -3324,24 +2080,24 @@ Pass the \`searchId\` returned by \`firecrawl_search\` (the \`id\` field on the
3324
2080
 
3325
2081
  **Returns:** \`{ success, feedbackId, creditsRefunded, creditsRefundedToday, dailyRefundCap, dailyCapReached?, alreadySubmitted?, warning? }\` JSON.
3326
2082
  `,
3327
- parameters: z4.object({
3328
- searchId: z4.string().uuid("searchId must be the UUID returned by firecrawl_search"),
3329
- rating: z4.enum(["good", "bad", "partial"]),
3330
- valuableSources: z4.array(
3331
- z4.object({
3332
- url: z4.string().url(),
3333
- reason: z4.string().max(1e3).optional()
2083
+ parameters: z3.object({
2084
+ searchId: z3.string().uuid("searchId must be the UUID returned by firecrawl_search"),
2085
+ rating: z3.enum(["good", "bad", "partial"]),
2086
+ valuableSources: z3.array(
2087
+ z3.object({
2088
+ url: z3.string().url(),
2089
+ reason: z3.string().max(1e3).optional()
3334
2090
  })
3335
2091
  ).max(50).optional(),
3336
- missingContent: z4.array(
3337
- z4.object({
3338
- topic: z4.string().min(1, "topic must not be empty").max(200, "topic must be 200 characters or fewer"),
3339
- description: z4.string().max(2e3).optional()
2092
+ missingContent: z3.array(
2093
+ z3.object({
2094
+ topic: z3.string().min(1, "topic must not be empty").max(200, "topic must be 200 characters or fewer"),
2095
+ description: z3.string().max(2e3).optional()
3340
2096
  })
3341
2097
  ).max(20).optional().describe(
3342
2098
  "Array of specific pieces of content the agent expected to find but did not. One entry per distinct topic. Each entry has a short `topic` and optional longer `description`."
3343
2099
  ),
3344
- querySuggestions: z4.string().max(2e3).optional()
2100
+ querySuggestions: z3.string().max(2e3).optional()
3345
2101
  }),
3346
2102
  execute: async (args2, { session, log }) => {
3347
2103
  const {
@@ -3440,19 +2196,19 @@ Do not store multi-MB outputs in feedback. Use concise notes, issue codes, URLs,
3440
2196
 
3441
2197
  **Returns:** \`{ success, feedbackId, creditsRefunded, creditsRefundedToday?, dailyRefundCap?, dailyCapReached?, alreadySubmitted?, warning? }\` JSON.
3442
2198
  `,
3443
- parameters: z4.object({
3444
- endpoint: z4.enum(["search", "scrape", "parse", "map"]),
3445
- jobId: z4.string().uuid("jobId must be the UUID returned by Firecrawl"),
3446
- rating: z4.enum(["good", "bad", "partial"]),
3447
- issues: z4.array(feedbackIssueSchema).max(20).optional(),
3448
- tags: z4.array(feedbackIssueSchema).max(20).optional(),
3449
- note: z4.string().max(4e3).optional(),
3450
- valuableSources: z4.array(valuableSourceSchema).max(50).optional(),
3451
- missingContent: z4.array(missingContentSchema).max(50).optional(),
3452
- querySuggestions: z4.string().max(2e3).optional(),
3453
- url: z4.string().url().optional(),
3454
- pageNumbers: z4.array(z4.number().int().positive()).max(100).optional(),
3455
- metadata: z4.record(z4.string(), z4.unknown()).optional()
2199
+ parameters: z3.object({
2200
+ endpoint: z3.enum(["search", "scrape", "parse", "map"]),
2201
+ jobId: z3.string().uuid("jobId must be the UUID returned by Firecrawl"),
2202
+ rating: z3.enum(["good", "bad", "partial"]),
2203
+ issues: z3.array(feedbackIssueSchema).max(20).optional(),
2204
+ tags: z3.array(feedbackIssueSchema).max(20).optional(),
2205
+ note: z3.string().max(4e3).optional(),
2206
+ valuableSources: z3.array(valuableSourceSchema).max(50).optional(),
2207
+ missingContent: z3.array(missingContentSchema).max(50).optional(),
2208
+ querySuggestions: z3.string().max(2e3).optional(),
2209
+ url: z3.string().url().optional(),
2210
+ pageNumbers: z3.array(z3.number().int().positive()).max(100).optional(),
2211
+ metadata: z3.record(z3.string(), z3.unknown()).optional()
3456
2212
  }),
3457
2213
  execute: async (args2, { session, log }) => {
3458
2214
  const {
@@ -3561,25 +2317,25 @@ server.addTool({
3561
2317
  **Returns:** Final crawl status and data after internal polling, including the crawl id. Use firecrawl_check_crawl_status only when you need to re-check an existing crawl ID later.
3562
2318
  ${SAFE_MODE ? "**Safe Mode:** Read-only crawling. Webhooks and interactive actions are disabled for security." : ""}
3563
2319
  `,
3564
- parameters: z4.object({
3565
- url: z4.string(),
3566
- prompt: z4.string().optional(),
3567
- excludePaths: z4.array(z4.string()).optional(),
3568
- includePaths: z4.array(z4.string()).optional(),
3569
- maxDiscoveryDepth: z4.number().optional(),
3570
- sitemap: z4.enum(["skip", "include", "only"]).optional(),
3571
- limit: z4.number().optional(),
3572
- allowExternalLinks: z4.boolean().optional(),
3573
- allowSubdomains: z4.boolean().optional(),
3574
- crawlEntireDomain: z4.boolean().optional(),
3575
- delay: z4.number().optional(),
3576
- maxConcurrency: z4.number().optional(),
2320
+ parameters: z3.object({
2321
+ url: z3.string(),
2322
+ prompt: z3.string().optional(),
2323
+ excludePaths: z3.array(z3.string()).optional(),
2324
+ includePaths: z3.array(z3.string()).optional(),
2325
+ maxDiscoveryDepth: z3.number().optional(),
2326
+ sitemap: z3.enum(["skip", "include", "only"]).optional(),
2327
+ limit: z3.number().optional(),
2328
+ allowExternalLinks: z3.boolean().optional(),
2329
+ allowSubdomains: z3.boolean().optional(),
2330
+ crawlEntireDomain: z3.boolean().optional(),
2331
+ delay: z3.number().optional(),
2332
+ maxConcurrency: z3.number().optional(),
3577
2333
  ...SAFE_MODE ? {} : {
3578
- webhook: z4.string().optional(),
3579
- webhookHeaders: z4.record(z4.string(), z4.string()).optional()
2334
+ webhook: z3.string().optional(),
2335
+ webhookHeaders: z3.record(z3.string(), z3.string()).optional()
3580
2336
  },
3581
- deduplicateSimilarURLs: z4.boolean().optional(),
3582
- ignoreQueryParameters: z4.boolean().optional(),
2337
+ deduplicateSimilarURLs: z3.boolean().optional(),
2338
+ ignoreQueryParameters: z3.boolean().optional(),
3583
2339
  scrapeOptions: scrapeParamsSchema.omit({ url: true }).partial().optional()
3584
2340
  }),
3585
2341
  execute: async (args2, { session, log }) => {
@@ -3643,7 +2399,7 @@ Check the status of a crawl job.
3643
2399
  \`\`\`
3644
2400
  **Returns:** Status and progress of the crawl job, including results if available.
3645
2401
  `,
3646
- parameters: z4.object({ id: z4.string() }),
2402
+ parameters: z3.object({ id: z3.string() }),
3647
2403
  execute: async (args2, { session }) => {
3648
2404
  const client = getClient(session);
3649
2405
  const id = args2.id;
@@ -3699,13 +2455,13 @@ Extract structured information from web pages using LLM capabilities. Supports b
3699
2455
  \`\`\`
3700
2456
  **Returns:** Extracted structured data as defined by your schema.
3701
2457
  `,
3702
- parameters: z4.object({
3703
- urls: z4.array(z4.string()),
3704
- prompt: z4.string().optional(),
3705
- schema: z4.record(z4.string(), z4.any()).optional(),
3706
- allowExternalLinks: z4.boolean().optional(),
3707
- enableWebSearch: z4.boolean().optional(),
3708
- includeSubdomains: z4.boolean().optional()
2458
+ parameters: z3.object({
2459
+ urls: z3.array(z3.string()),
2460
+ prompt: z3.string().optional(),
2461
+ schema: z3.record(z3.string(), z3.any()).optional(),
2462
+ allowExternalLinks: z3.boolean().optional(),
2463
+ enableWebSearch: z3.boolean().optional(),
2464
+ includeSubdomains: z3.boolean().optional()
3709
2465
  }),
3710
2466
  execute: async (args2, { session, log }) => {
3711
2467
  const client = getClient(session);
@@ -3806,10 +2562,10 @@ Then poll with \`firecrawl_agent_status\` every 15-30 seconds for at least 2-3 m
3806
2562
  \`\`\`
3807
2563
  **Returns:** Job ID for status checking. Use \`firecrawl_agent_status\` to poll for results.
3808
2564
  `,
3809
- parameters: z4.object({
3810
- prompt: z4.string().min(1).max(1e4),
3811
- urls: z4.array(z4.string().url()).optional(),
3812
- schema: z4.record(z4.string(), z4.any()).optional()
2565
+ parameters: z3.object({
2566
+ prompt: z3.string().min(1).max(1e4),
2567
+ urls: z3.array(z3.string().url()).optional(),
2568
+ schema: z3.record(z3.string(), z3.any()).optional()
3813
2569
  }),
3814
2570
  execute: async (args2, { session, log }) => {
3815
2571
  const client = getClient(session);
@@ -3866,7 +2622,7 @@ Check the status of an agent job and retrieve results when complete. Use this to
3866
2622
 
3867
2623
  **Returns:** Status, progress, and results (if completed) of the agent job.
3868
2624
  `,
3869
- parameters: z4.object({ id: z4.string() }),
2625
+ parameters: z3.object({ id: z3.string() }),
3870
2626
  execute: async (args2, { session, log }) => {
3871
2627
  const client = getClient(session);
3872
2628
  const { id } = args2;
@@ -3930,13 +2686,13 @@ Interact with a page in a live browser session: click buttons, fill forms, extra
3930
2686
  \`\`\`
3931
2687
  **Returns:** Execution result including output, stdout, stderr, exit code, and live view URLs.
3932
2688
  `,
3933
- parameters: z4.object({
3934
- scrapeId: z4.string().trim().min(1).optional(),
3935
- url: z4.string().trim().url().optional(),
3936
- prompt: z4.string().trim().min(1).optional(),
3937
- code: z4.string().trim().min(1).optional(),
3938
- language: z4.enum(["bash", "python", "node"]).optional(),
3939
- timeout: z4.number().min(1).max(300).optional(),
2689
+ parameters: z3.object({
2690
+ scrapeId: z3.string().trim().min(1).optional(),
2691
+ url: z3.string().trim().url().optional(),
2692
+ prompt: z3.string().trim().min(1).optional(),
2693
+ code: z3.string().trim().min(1).optional(),
2694
+ language: z3.enum(["bash", "python", "node"]).optional(),
2695
+ timeout: z3.number().min(1).max(300).optional(),
3940
2696
  scrapeOptions: scrapeParamsSchema.omit({ url: true }).partial().optional()
3941
2697
  }).refine((data) => Boolean(data.scrapeId) !== Boolean(data.url), {
3942
2698
  message: "Provide either 'url' (interact directly) or 'scrapeId' (reuse a previous scrape), not both."
@@ -4024,8 +2780,8 @@ Stop an interact session for a scraped page. Call this when you are done interac
4024
2780
  \`\`\`
4025
2781
  **Returns:** Success confirmation.
4026
2782
  `,
4027
- parameters: z4.object({
4028
- scrapeId: z4.string()
2783
+ parameters: z3.object({
2784
+ scrapeId: z3.string()
4029
2785
  }),
4030
2786
  execute: async (args2, { session, log }) => {
4031
2787
  const client = getClient(session);