firecrawl-mcp 3.27.1 → 3.27.2

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 +2332 -347
  2. package/package.json +8 -1
package/dist/index.js CHANGED
@@ -83,12 +83,1998 @@ var alexandriaSessionFeedbackSchema = z.strictObject({
83
83
  // src/index.ts
84
84
  import FirecrawlApp from "firecrawl";
85
85
  import dotenv from "dotenv";
86
- import { FastMCP, UserError as UserError2 } from "fastmcp";
86
+
87
+ // node_modules/.pnpm/fastmcp@4.3.2_patch_hash=08f77b633292d9ae4cb022c0fa5bd722dbd5c1aaaad600204f8ed38339bfd37b/node_modules/fastmcp/dist/chunk-LWU5CQGW.js
88
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
89
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
90
+ import {
91
+ CallToolRequestSchema,
92
+ CompleteRequestSchema,
93
+ ErrorCode,
94
+ GetPromptRequestSchema,
95
+ ListPromptsRequestSchema,
96
+ ListResourcesRequestSchema,
97
+ ListResourceTemplatesRequestSchema,
98
+ ListToolsRequestSchema,
99
+ McpError,
100
+ ReadResourceRequestSchema,
101
+ RootsListChangedNotificationSchema,
102
+ SetLevelRequestSchema
103
+ } from "@modelcontextprotocol/sdk/types.js";
104
+ import { EventEmitter } from "events";
105
+ import Fuse from "fuse.js";
106
+ import { Hono } from "hono";
107
+ import { startHTTPServer } from "mcp-proxy";
108
+ import { setTimeout as delay } from "timers/promises";
109
+ import { fetch as fetch2 } from "undici";
110
+ import parseURITemplate from "uri-templates";
111
+ import { strictJsonSchema, toJsonSchema } from "xsschema";
112
+ import { z as z2 } from "zod";
113
+ var FastMCPError = class extends Error {
114
+ constructor(message) {
115
+ super(message);
116
+ this.name = new.target.name;
117
+ }
118
+ };
119
+ var UnexpectedStateError = class extends FastMCPError {
120
+ extras;
121
+ constructor(message, extras) {
122
+ super(message);
123
+ this.name = new.target.name;
124
+ this.extras = extras;
125
+ }
126
+ };
127
+ var UserError = class extends UnexpectedStateError {
128
+ };
129
+ var TextContentZodSchema = z2.object({
130
+ /**
131
+ * The text content of the message.
132
+ */
133
+ text: z2.string(),
134
+ type: z2.literal("text")
135
+ }).strict();
136
+ var ImageContentZodSchema = z2.object({
137
+ /**
138
+ * The base64-encoded image data.
139
+ */
140
+ data: z2.string().base64(),
141
+ /**
142
+ * The MIME type of the image. Different providers may support different image types.
143
+ */
144
+ mimeType: z2.string(),
145
+ type: z2.literal("image")
146
+ }).strict();
147
+ var AudioContentZodSchema = z2.object({
148
+ /**
149
+ * The base64-encoded audio data.
150
+ */
151
+ data: z2.string().base64(),
152
+ mimeType: z2.string(),
153
+ type: z2.literal("audio")
154
+ }).strict();
155
+ var ResourceContentZodSchema = z2.object({
156
+ resource: z2.object({
157
+ blob: z2.string().optional(),
158
+ mimeType: z2.string().optional(),
159
+ text: z2.string().optional(),
160
+ uri: z2.string()
161
+ }),
162
+ type: z2.literal("resource")
163
+ }).strict();
164
+ var ResourceLinkZodSchema = z2.object({
165
+ description: z2.string().optional(),
166
+ mimeType: z2.string().optional(),
167
+ name: z2.string(),
168
+ title: z2.string().optional(),
169
+ type: z2.literal("resource_link"),
170
+ uri: z2.string()
171
+ });
172
+ var ContentZodSchema = z2.discriminatedUnion("type", [
173
+ TextContentZodSchema,
174
+ ImageContentZodSchema,
175
+ AudioContentZodSchema,
176
+ ResourceContentZodSchema,
177
+ ResourceLinkZodSchema
178
+ ]);
179
+ var ContentResultZodSchema = z2.object({
180
+ content: ContentZodSchema.array(),
181
+ isError: z2.boolean().optional(),
182
+ structuredContent: z2.record(z2.string(), z2.unknown()).optional()
183
+ }).strict();
184
+ var CompletionZodSchema = z2.object({
185
+ /**
186
+ * Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown.
187
+ */
188
+ hasMore: z2.optional(z2.boolean()),
189
+ /**
190
+ * The total number of completion options available. This can exceed the number of values actually sent in the response.
191
+ */
192
+ total: z2.optional(z2.number().int()),
193
+ /**
194
+ * An array of completion values. Must not exceed 100 items.
195
+ */
196
+ values: z2.array(z2.string()).max(100)
197
+ });
198
+ var FastMCPSessionEventEmitterBase = EventEmitter;
199
+ var FastMCPSessionEventEmitter = class extends FastMCPSessionEventEmitterBase {
200
+ };
201
+ var FastMCPSession = class extends FastMCPSessionEventEmitter {
202
+ get clientCapabilities() {
203
+ return this.#clientCapabilities ?? null;
204
+ }
205
+ get isReady() {
206
+ return this.#connectionState === "ready";
207
+ }
208
+ get loggingLevel() {
209
+ return this.#loggingLevel;
210
+ }
211
+ get roots() {
212
+ return this.#roots;
213
+ }
214
+ get server() {
215
+ return this.#server;
216
+ }
217
+ get sessionId() {
218
+ return this.#sessionId;
219
+ }
220
+ set sessionId(value) {
221
+ this.#sessionId = value;
222
+ }
223
+ #auth;
224
+ #capabilities = {};
225
+ #clientCapabilities;
226
+ #connectionState = "connecting";
227
+ #logger;
228
+ #loggingLevel = "info";
229
+ #needsEventLoopFlush = false;
230
+ #onToolCall;
231
+ #pingConfig;
232
+ #pingInterval = null;
233
+ #prompts = /* @__PURE__ */ new Map();
234
+ #resources = /* @__PURE__ */ new Map();
235
+ #resourceTemplates = /* @__PURE__ */ new Map();
236
+ #roots = [];
237
+ #rootsConfig;
238
+ #server;
239
+ /**
240
+ * Session ID from the Mcp-Session-Id header (HTTP transports only).
241
+ * Used to track per-session state across multiple requests.
242
+ */
243
+ #sessionId;
244
+ #utils;
245
+ constructor({
246
+ auth,
247
+ instructions,
248
+ logger,
249
+ name: name2,
250
+ onToolCall,
251
+ ping,
252
+ prompts,
253
+ resources,
254
+ resourcesTemplates,
255
+ roots,
256
+ sessionId,
257
+ tools,
258
+ transportType,
259
+ utils,
260
+ version
261
+ }) {
262
+ super();
263
+ this.#auth = auth;
264
+ this.#logger = logger;
265
+ this.#onToolCall = onToolCall;
266
+ this.#pingConfig = ping;
267
+ this.#rootsConfig = roots;
268
+ this.#sessionId = sessionId;
269
+ this.#needsEventLoopFlush = transportType === "httpStream";
270
+ if (tools.length) {
271
+ this.#capabilities.tools = {};
272
+ }
273
+ if (resources.length || resourcesTemplates.length) {
274
+ this.#capabilities.resources = {};
275
+ }
276
+ if (prompts.length) {
277
+ for (const prompt of prompts) {
278
+ this.addPrompt(prompt);
279
+ }
280
+ this.#capabilities.prompts = {};
281
+ }
282
+ this.#capabilities.logging = {};
283
+ this.#capabilities.completions = {};
284
+ this.#server = new Server(
285
+ { name: name2, version },
286
+ { capabilities: this.#capabilities, instructions }
287
+ );
288
+ this.#utils = utils;
289
+ this.setupErrorHandling();
290
+ this.setupLoggingHandlers();
291
+ this.setupRootsHandlers();
292
+ this.setupCompleteHandlers();
293
+ if (tools.length) {
294
+ this.setupToolHandlers(tools);
295
+ }
296
+ if (resources.length || resourcesTemplates.length) {
297
+ for (const resource of resources) {
298
+ this.addResource(resource);
299
+ }
300
+ this.setupResourceHandlers();
301
+ if (resourcesTemplates.length) {
302
+ for (const resourceTemplate of resourcesTemplates) {
303
+ this.addResourceTemplate(resourceTemplate);
304
+ }
305
+ this.setupResourceTemplateHandlers();
306
+ }
307
+ }
308
+ if (prompts.length) {
309
+ this.setupPromptHandlers();
310
+ }
311
+ }
312
+ async close() {
313
+ this.#connectionState = "closed";
314
+ if (this.#pingInterval) {
315
+ clearInterval(this.#pingInterval);
316
+ }
317
+ try {
318
+ await this.#server.close();
319
+ } catch (error2) {
320
+ this.#logger.error("[FastMCP error]", "could not close server", error2);
321
+ }
322
+ }
323
+ async connect(transport) {
324
+ if (this.#server.transport) {
325
+ throw new UnexpectedStateError("Server is already connected");
326
+ }
327
+ this.#connectionState = "connecting";
328
+ try {
329
+ await this.#server.connect(transport);
330
+ if ("sessionId" in transport) {
331
+ const transportWithSessionId = transport;
332
+ if (typeof transportWithSessionId.sessionId === "string") {
333
+ this.#sessionId = transportWithSessionId.sessionId;
334
+ }
335
+ }
336
+ let attempt = 0;
337
+ const maxAttempts = 10;
338
+ const retryDelay = 100;
339
+ while (attempt++ < maxAttempts) {
340
+ const capabilities = this.#server.getClientCapabilities();
341
+ if (capabilities) {
342
+ this.#clientCapabilities = capabilities;
343
+ break;
344
+ }
345
+ await delay(retryDelay);
346
+ }
347
+ if (!this.#clientCapabilities) {
348
+ this.#logger.warn(
349
+ `[FastMCP warning] could not infer client capabilities after ${maxAttempts} attempts. Connection may be unstable.`
350
+ );
351
+ }
352
+ if (this.#rootsConfig?.enabled !== false && this.#clientCapabilities?.roots?.listChanged && typeof this.#server.listRoots === "function") {
353
+ try {
354
+ const roots = await this.#server.listRoots();
355
+ this.#roots = roots?.roots || [];
356
+ } catch (e) {
357
+ if (e instanceof McpError && e.code === ErrorCode.MethodNotFound) {
358
+ this.#logger.debug(
359
+ "[FastMCP debug] listRoots method not supported by client"
360
+ );
361
+ } else {
362
+ this.#logger.error(
363
+ `[FastMCP error] received error listing roots.
364
+
365
+ ${e instanceof Error ? e.stack : JSON.stringify(e)}`
366
+ );
367
+ }
368
+ }
369
+ }
370
+ if (this.#clientCapabilities) {
371
+ const pingConfig = this.#getPingConfig(transport);
372
+ if (pingConfig.enabled) {
373
+ this.#pingInterval = setInterval(async () => {
374
+ try {
375
+ await this.#server.ping();
376
+ } catch {
377
+ const logLevel = pingConfig.logLevel;
378
+ if (logLevel === "debug") {
379
+ this.#logger.debug("[FastMCP debug] server ping failed");
380
+ } else if (logLevel === "warning") {
381
+ this.#logger.warn(
382
+ "[FastMCP warning] server is not responding to ping"
383
+ );
384
+ } else if (logLevel === "error") {
385
+ this.#logger.error(
386
+ "[FastMCP error] server is not responding to ping"
387
+ );
388
+ } else {
389
+ this.#logger.info("[FastMCP info] server ping failed");
390
+ }
391
+ }
392
+ }, pingConfig.intervalMs);
393
+ }
394
+ }
395
+ this.#connectionState = "ready";
396
+ this.emit("ready");
397
+ } catch (error2) {
398
+ this.#connectionState = "error";
399
+ const errorEvent = {
400
+ error: error2 instanceof Error ? error2 : new Error(String(error2))
401
+ };
402
+ this.emit("error", errorEvent);
403
+ throw error2;
404
+ }
405
+ }
406
+ promptsListChanged(prompts) {
407
+ this.#prompts.clear();
408
+ for (const prompt of prompts) {
409
+ this.addPrompt(prompt);
410
+ }
411
+ this.setupPromptHandlers();
412
+ this.triggerListChangedNotification("notifications/prompts/list_changed");
413
+ }
414
+ async requestSampling(message, options) {
415
+ return this.#server.createMessage(message, options);
416
+ }
417
+ resourcesListChanged(resources) {
418
+ this.#resources.clear();
419
+ for (const resource of resources) {
420
+ this.addResource(resource);
421
+ }
422
+ this.setupResourceHandlers();
423
+ this.triggerListChangedNotification("notifications/resources/list_changed");
424
+ }
425
+ resourceTemplatesListChanged(resourceTemplates) {
426
+ this.#resourceTemplates.clear();
427
+ for (const resourceTemplate of resourceTemplates) {
428
+ this.addResourceTemplate(resourceTemplate);
429
+ }
430
+ this.setupResourceTemplateHandlers();
431
+ this.triggerListChangedNotification("notifications/resources/list_changed");
432
+ }
433
+ toolsListChanged(tools) {
434
+ const allowedTools = tools.filter(
435
+ (tool) => tool.canAccess ? tool.canAccess(this.#auth) : true
436
+ );
437
+ this.setupToolHandlers(allowedTools);
438
+ this.triggerListChangedNotification("notifications/tools/list_changed");
439
+ }
440
+ async triggerListChangedNotification(method) {
441
+ try {
442
+ await this.#server.notification({
443
+ method
444
+ });
445
+ } catch (error2) {
446
+ this.#logger.error(
447
+ `[FastMCP error] failed to send ${method} notification.
448
+
449
+ ${error2 instanceof Error ? error2.stack : JSON.stringify(error2)}`
450
+ );
451
+ }
452
+ }
453
+ /**
454
+ * Update the session's authentication context.
455
+ * Called by mcp-proxy when a new token is validated on subsequent requests.
456
+ */
457
+ updateAuth(auth) {
458
+ this.#auth = auth;
459
+ }
460
+ waitForReady() {
461
+ if (this.isReady) {
462
+ return Promise.resolve();
463
+ }
464
+ if (this.#connectionState === "error" || this.#connectionState === "closed") {
465
+ return Promise.reject(
466
+ new Error(`Connection is in ${this.#connectionState} state`)
467
+ );
468
+ }
469
+ return new Promise((resolve, reject) => {
470
+ const timeout = setTimeout(() => {
471
+ reject(
472
+ new Error(
473
+ "Connection timeout: Session failed to become ready within 5 seconds"
474
+ )
475
+ );
476
+ }, 5e3);
477
+ this.once("ready", () => {
478
+ clearTimeout(timeout);
479
+ resolve();
480
+ });
481
+ this.once("error", (event) => {
482
+ clearTimeout(timeout);
483
+ reject(event.error);
484
+ });
485
+ });
486
+ }
487
+ #formatSchemaIssues(issues) {
488
+ return this.#utils?.formatInvalidParamsErrorMessage ? this.#utils.formatInvalidParamsErrorMessage(issues) : issues.map((issue) => {
489
+ const path2 = issue.path?.join(".") || "root";
490
+ return `${path2}: ${issue.message}`;
491
+ }).join(", ");
492
+ }
493
+ #getPingConfig(transport) {
494
+ const pingConfig = this.#pingConfig || {};
495
+ let defaultEnabled = false;
496
+ if ("type" in transport) {
497
+ if (transport.type === "httpStream") {
498
+ defaultEnabled = true;
499
+ }
500
+ }
501
+ return {
502
+ enabled: pingConfig.enabled !== void 0 ? pingConfig.enabled : defaultEnabled,
503
+ intervalMs: pingConfig.intervalMs || 5e3,
504
+ logLevel: pingConfig.logLevel || "debug"
505
+ };
506
+ }
507
+ async #validateStructuredContent(tool, value, toolName) {
508
+ if (!tool.outputSchema) {
509
+ return value;
510
+ }
511
+ const parsed = await tool.outputSchema["~standard"].validate(value);
512
+ if (parsed.issues) {
513
+ throw new UserError(
514
+ `Tool '${toolName}' structured output validation failed: ${this.#formatSchemaIssues(parsed.issues)}. Please check the result matches the tool's outputSchema.`
515
+ );
516
+ }
517
+ return parsed.value;
518
+ }
519
+ addPrompt(inputPrompt) {
520
+ const completers = {};
521
+ const enums = {};
522
+ const fuseInstances = {};
523
+ for (const argument of inputPrompt.arguments ?? []) {
524
+ if (argument.complete) {
525
+ completers[argument.name] = argument.complete;
526
+ }
527
+ if (argument.enum) {
528
+ enums[argument.name] = argument.enum;
529
+ fuseInstances[argument.name] = new Fuse(argument.enum, {
530
+ includeScore: true,
531
+ threshold: 0.3
532
+ // More flexible matching!
533
+ });
534
+ }
535
+ }
536
+ const prompt = {
537
+ ...inputPrompt,
538
+ complete: async (name2, value, auth) => {
539
+ if (completers[name2]) {
540
+ return await completers[name2](value, auth);
541
+ }
542
+ if (inputPrompt.complete) {
543
+ return await inputPrompt.complete(name2, value, auth);
544
+ }
545
+ if (fuseInstances[name2]) {
546
+ const result = fuseInstances[name2].search(value);
547
+ return {
548
+ total: result.length,
549
+ values: result.map((item) => item.item)
550
+ };
551
+ }
552
+ return {
553
+ values: []
554
+ };
555
+ }
556
+ };
557
+ this.#prompts.set(prompt.name, prompt);
558
+ }
559
+ addResource(inputResource) {
560
+ this.#resources.set(inputResource.uri, inputResource);
561
+ }
562
+ addResourceTemplate(inputResourceTemplate) {
563
+ const completers = {};
564
+ for (const argument of inputResourceTemplate.arguments ?? []) {
565
+ if (argument.complete) {
566
+ completers[argument.name] = argument.complete;
567
+ }
568
+ }
569
+ const resourceTemplate = {
570
+ ...inputResourceTemplate,
571
+ complete: async (name2, value, auth) => {
572
+ if (completers[name2]) {
573
+ return await completers[name2](value, auth);
574
+ }
575
+ if (inputResourceTemplate.complete) {
576
+ return await inputResourceTemplate.complete(name2, value, auth);
577
+ }
578
+ return {
579
+ values: []
580
+ };
581
+ }
582
+ };
583
+ this.#resourceTemplates.set(resourceTemplate.name, resourceTemplate);
584
+ }
585
+ setupCompleteHandlers() {
586
+ this.#server.setRequestHandler(CompleteRequestSchema, async (request) => {
587
+ if (request.params.ref.type === "ref/prompt") {
588
+ const ref = request.params.ref;
589
+ const prompt = "name" in ref && this.#prompts.get(ref.name);
590
+ if (!prompt) {
591
+ throw new UnexpectedStateError("Unknown prompt", {
592
+ request
593
+ });
594
+ }
595
+ if (!prompt.complete) {
596
+ throw new UnexpectedStateError("Prompt does not support completion", {
597
+ request
598
+ });
599
+ }
600
+ const completion = CompletionZodSchema.parse(
601
+ await prompt.complete(
602
+ request.params.argument.name,
603
+ request.params.argument.value,
604
+ this.#auth
605
+ )
606
+ );
607
+ return {
608
+ completion
609
+ };
610
+ }
611
+ if (request.params.ref.type === "ref/resource") {
612
+ const ref = request.params.ref;
613
+ const resource = "uri" in ref && Array.from(this.#resourceTemplates.values()).find(
614
+ (resource2) => resource2.uriTemplate === ref.uri
615
+ );
616
+ if (!resource) {
617
+ throw new UnexpectedStateError("Unknown resource", {
618
+ request
619
+ });
620
+ }
621
+ if (!("uriTemplate" in resource)) {
622
+ throw new UnexpectedStateError("Unexpected resource");
623
+ }
624
+ if (!resource.complete) {
625
+ throw new UnexpectedStateError(
626
+ "Resource does not support completion",
627
+ {
628
+ request
629
+ }
630
+ );
631
+ }
632
+ const completion = CompletionZodSchema.parse(
633
+ await resource.complete(
634
+ request.params.argument.name,
635
+ request.params.argument.value,
636
+ this.#auth
637
+ )
638
+ );
639
+ return {
640
+ completion
641
+ };
642
+ }
643
+ throw new UnexpectedStateError("Unexpected completion request", {
644
+ request
645
+ });
646
+ });
647
+ }
648
+ setupErrorHandling() {
649
+ this.#server.onerror = (error2) => {
650
+ this.#logger.error("[FastMCP error]", error2);
651
+ };
652
+ }
653
+ setupLoggingHandlers() {
654
+ this.#server.setRequestHandler(SetLevelRequestSchema, (request) => {
655
+ this.#loggingLevel = request.params.level;
656
+ return {};
657
+ });
658
+ }
659
+ setupPromptHandlers() {
660
+ let cachedPromptsList = null;
661
+ this.#server.setRequestHandler(ListPromptsRequestSchema, async () => {
662
+ if (cachedPromptsList) {
663
+ return {
664
+ prompts: cachedPromptsList
665
+ };
666
+ }
667
+ cachedPromptsList = Array.from(this.#prompts.values()).map((prompt) => {
668
+ return {
669
+ arguments: prompt.arguments,
670
+ complete: prompt.complete,
671
+ description: prompt.description,
672
+ name: prompt.name
673
+ };
674
+ });
675
+ return {
676
+ prompts: cachedPromptsList
677
+ };
678
+ });
679
+ this.#server.setRequestHandler(GetPromptRequestSchema, async (request) => {
680
+ const prompt = this.#prompts.get(request.params.name);
681
+ if (!prompt) {
682
+ throw new McpError(
683
+ ErrorCode.MethodNotFound,
684
+ `Unknown prompt: ${request.params.name}`
685
+ );
686
+ }
687
+ const args2 = request.params.arguments;
688
+ for (const arg of prompt.arguments ?? []) {
689
+ if (arg.required && !(args2 && arg.name in args2)) {
690
+ throw new McpError(
691
+ ErrorCode.InvalidRequest,
692
+ `Prompt '${request.params.name}' requires argument '${arg.name}': ${arg.description || "No description provided"}`
693
+ );
694
+ }
695
+ }
696
+ let result;
697
+ try {
698
+ result = await prompt.load(
699
+ args2,
700
+ this.#auth
701
+ );
702
+ } catch (error2) {
703
+ const errorMessage = error2 instanceof Error ? error2.message : String(error2);
704
+ throw new McpError(
705
+ ErrorCode.InternalError,
706
+ `Failed to load prompt '${request.params.name}': ${errorMessage}`
707
+ );
708
+ }
709
+ if (typeof result === "string") {
710
+ return {
711
+ description: prompt.description,
712
+ messages: [
713
+ {
714
+ content: { text: result, type: "text" },
715
+ role: "user"
716
+ }
717
+ ]
718
+ };
719
+ } else {
720
+ return {
721
+ description: prompt.description,
722
+ messages: result.messages
723
+ };
724
+ }
725
+ });
726
+ }
727
+ setupResourceHandlers() {
728
+ let cachedResourcesList = null;
729
+ this.#server.setRequestHandler(ListResourcesRequestSchema, async () => {
730
+ if (cachedResourcesList) {
731
+ return {
732
+ resources: cachedResourcesList
733
+ };
734
+ }
735
+ cachedResourcesList = Array.from(this.#resources.values()).map(
736
+ (resource) => ({
737
+ description: resource.description,
738
+ mimeType: resource.mimeType,
739
+ name: resource.name,
740
+ uri: resource.uri
741
+ })
742
+ );
743
+ return {
744
+ resources: cachedResourcesList
745
+ };
746
+ });
747
+ this.#server.setRequestHandler(
748
+ ReadResourceRequestSchema,
749
+ async (request) => {
750
+ if ("uri" in request.params) {
751
+ const resource = this.#resources.get(request.params.uri);
752
+ if (!resource) {
753
+ for (const resourceTemplate of this.#resourceTemplates.values()) {
754
+ const uriTemplate = parseURITemplate(
755
+ resourceTemplate.uriTemplate
756
+ );
757
+ const match = uriTemplate.fromUri(request.params.uri);
758
+ if (!match) {
759
+ continue;
760
+ }
761
+ const uri = uriTemplate.fill(match);
762
+ const result = await resourceTemplate.load(match, this.#auth);
763
+ const resources = Array.isArray(result) ? result : [result];
764
+ return {
765
+ contents: resources.map((resource2) => ({
766
+ ...resource2,
767
+ description: resourceTemplate.description,
768
+ mimeType: resource2.mimeType ?? resourceTemplate.mimeType,
769
+ name: resourceTemplate.name,
770
+ uri: resource2.uri ?? uri
771
+ }))
772
+ };
773
+ }
774
+ throw new McpError(
775
+ ErrorCode.MethodNotFound,
776
+ `Resource not found: '${request.params.uri}'. Available resources: ${Array.from(this.#resources.values()).map((r) => r.uri).join(", ") || "none"}`
777
+ );
778
+ }
779
+ if (!("uri" in resource)) {
780
+ throw new UnexpectedStateError("Resource does not support reading");
781
+ }
782
+ let maybeArrayResult;
783
+ try {
784
+ maybeArrayResult = await resource.load(this.#auth);
785
+ } catch (error2) {
786
+ const errorMessage = error2 instanceof Error ? error2.message : String(error2);
787
+ throw new McpError(
788
+ ErrorCode.InternalError,
789
+ `Failed to load resource '${resource.name}' (${resource.uri}): ${errorMessage}`,
790
+ {
791
+ uri: resource.uri
792
+ }
793
+ );
794
+ }
795
+ const resourceResults = Array.isArray(maybeArrayResult) ? maybeArrayResult : [maybeArrayResult];
796
+ return {
797
+ contents: resourceResults.map((result) => ({
798
+ ...result,
799
+ mimeType: result.mimeType ?? resource.mimeType,
800
+ name: resource.name,
801
+ uri: result.uri ?? resource.uri
802
+ }))
803
+ };
804
+ }
805
+ throw new UnexpectedStateError("Unknown resource request", {
806
+ request
807
+ });
808
+ }
809
+ );
810
+ }
811
+ setupResourceTemplateHandlers() {
812
+ let cachedResourceTemplatesList = null;
813
+ this.#server.setRequestHandler(
814
+ ListResourceTemplatesRequestSchema,
815
+ async () => {
816
+ if (cachedResourceTemplatesList) {
817
+ return {
818
+ resourceTemplates: cachedResourceTemplatesList
819
+ };
820
+ }
821
+ cachedResourceTemplatesList = Array.from(
822
+ this.#resourceTemplates.values()
823
+ ).map((resourceTemplate) => ({
824
+ description: resourceTemplate.description,
825
+ mimeType: resourceTemplate.mimeType,
826
+ name: resourceTemplate.name,
827
+ uriTemplate: resourceTemplate.uriTemplate
828
+ }));
829
+ return {
830
+ resourceTemplates: cachedResourceTemplatesList
831
+ };
832
+ }
833
+ );
834
+ }
835
+ setupRootsHandlers() {
836
+ if (this.#rootsConfig?.enabled === false) {
837
+ this.#logger.debug(
838
+ "[FastMCP debug] roots capability explicitly disabled via config"
839
+ );
840
+ return;
841
+ }
842
+ if (typeof this.#server.listRoots === "function") {
843
+ this.#server.setNotificationHandler(
844
+ RootsListChangedNotificationSchema,
845
+ () => {
846
+ this.#server.listRoots().then((roots) => {
847
+ this.#roots = roots.roots;
848
+ this.emit("rootsChanged", {
849
+ roots: roots.roots
850
+ });
851
+ }).catch((error2) => {
852
+ if (error2 instanceof McpError && error2.code === ErrorCode.MethodNotFound) {
853
+ this.#logger.debug(
854
+ "[FastMCP debug] listRoots method not supported by client"
855
+ );
856
+ } else {
857
+ this.#logger.error(
858
+ `[FastMCP error] received error listing roots.
859
+
860
+ ${error2 instanceof Error ? error2.stack : JSON.stringify(error2)}`
861
+ );
862
+ }
863
+ });
864
+ }
865
+ );
866
+ } else {
867
+ this.#logger.debug(
868
+ "[FastMCP debug] roots capability not available, not setting up notification handler"
869
+ );
870
+ }
871
+ }
872
+ setupToolHandlers(tools) {
873
+ const toolsMap = new Map(tools.map((tool) => [tool.name, tool]));
874
+ const listedTools = tools.filter(
875
+ (tool) => tool.canList ? tool.canList(this.#auth) : true
876
+ );
877
+ let cachedToolsList = null;
878
+ this.#server.setRequestHandler(ListToolsRequestSchema, async () => {
879
+ if (cachedToolsList) {
880
+ return {
881
+ tools: cachedToolsList
882
+ };
883
+ }
884
+ cachedToolsList = await Promise.all(
885
+ listedTools.map(async (tool) => {
886
+ return {
887
+ annotations: tool.annotations,
888
+ // Top-level Tool.title (MCP 2025-06-18). Clients such as Codex index this
889
+ // for tool search and do not read annotations.title.
890
+ ...tool.annotations?.title && { title: tool.annotations.title },
891
+ description: tool.description,
892
+ inputSchema: tool.parameters ? strictJsonSchema(await toJsonSchema(tool.parameters)) : {
893
+ additionalProperties: false,
894
+ properties: {},
895
+ type: "object"
896
+ },
897
+ name: tool.name,
898
+ ...tool.outputSchema && {
899
+ outputSchema: strictJsonSchema(
900
+ await toJsonSchema(tool.outputSchema)
901
+ )
902
+ },
903
+ // Pass through _meta for MCP ext-apps UI support (issue #229)
904
+ ...tool._meta && { _meta: tool._meta }
905
+ };
906
+ })
907
+ );
908
+ return {
909
+ tools: cachedToolsList
910
+ };
911
+ });
912
+ this.#server.setRequestHandler(CallToolRequestSchema, async (request) => {
913
+ const tool = toolsMap.get(request.params.name);
914
+ if (!tool) {
915
+ throw new McpError(
916
+ ErrorCode.MethodNotFound,
917
+ `Unknown tool: ${request.params.name}`
918
+ );
919
+ }
920
+ if (tool.beforeValidate) {
921
+ const earlyResult = await tool.beforeValidate(
922
+ request.params.arguments,
923
+ this.#auth
924
+ );
925
+ if (earlyResult !== void 0) {
926
+ return ContentResultZodSchema.parse(earlyResult);
927
+ }
928
+ }
929
+ let args2 = void 0;
930
+ if (tool.parameters) {
931
+ const parsed = await tool.parameters["~standard"].validate(
932
+ request.params.arguments
933
+ );
934
+ if (parsed.issues) {
935
+ const friendlyErrors = this.#formatSchemaIssues(parsed.issues);
936
+ throw new McpError(
937
+ ErrorCode.InvalidParams,
938
+ `Tool '${request.params.name}' parameter validation failed: ${friendlyErrors}. Please check the parameter types and values according to the tool's schema.`
939
+ );
940
+ }
941
+ args2 = parsed.value;
942
+ }
943
+ const progressToken = request.params?._meta?.progressToken;
944
+ let result;
945
+ try {
946
+ const reportProgress = async (progress) => {
947
+ if (progressToken === void 0) {
948
+ return;
949
+ }
950
+ try {
951
+ await this.#server.notification({
952
+ method: "notifications/progress",
953
+ params: {
954
+ ...progress,
955
+ progressToken
956
+ }
957
+ });
958
+ if (this.#needsEventLoopFlush) {
959
+ await new Promise((resolve) => setImmediate(resolve));
960
+ }
961
+ } catch (progressError) {
962
+ this.#logger.warn(
963
+ `[FastMCP warning] Failed to report progress for tool '${request.params.name}':`,
964
+ progressError instanceof Error ? progressError.message : String(progressError)
965
+ );
966
+ }
967
+ };
968
+ const log = {
969
+ debug: (message, context) => {
970
+ this.#server.sendLoggingMessage({
971
+ data: {
972
+ context,
973
+ message
974
+ },
975
+ level: "debug"
976
+ });
977
+ },
978
+ error: (message, context) => {
979
+ this.#server.sendLoggingMessage({
980
+ data: {
981
+ context,
982
+ message
983
+ },
984
+ level: "error"
985
+ });
986
+ },
987
+ info: (message, context) => {
988
+ this.#server.sendLoggingMessage({
989
+ data: {
990
+ context,
991
+ message
992
+ },
993
+ level: "info"
994
+ });
995
+ },
996
+ warn: (message, context) => {
997
+ this.#server.sendLoggingMessage({
998
+ data: {
999
+ context,
1000
+ message
1001
+ },
1002
+ level: "warning"
1003
+ });
1004
+ }
1005
+ };
1006
+ const streamContent = async (content) => {
1007
+ const contentArray = Array.isArray(content) ? content : [content];
1008
+ try {
1009
+ await this.#server.notification({
1010
+ method: "notifications/tool/streamContent",
1011
+ params: {
1012
+ content: contentArray,
1013
+ toolName: request.params.name
1014
+ }
1015
+ });
1016
+ if (this.#needsEventLoopFlush) {
1017
+ await new Promise((resolve) => setImmediate(resolve));
1018
+ }
1019
+ } catch (streamError) {
1020
+ this.#logger.warn(
1021
+ `[FastMCP warning] Failed to stream content for tool '${request.params.name}':`,
1022
+ streamError instanceof Error ? streamError.message : String(streamError)
1023
+ );
1024
+ }
1025
+ };
1026
+ if (this.#onToolCall) {
1027
+ await this.#onToolCall({
1028
+ arguments: args2 ?? {},
1029
+ toolName: request.params.name
1030
+ });
1031
+ }
1032
+ const executeToolPromise = tool.execute(args2, {
1033
+ client: {
1034
+ version: this.#server.getClientVersion()
1035
+ },
1036
+ log,
1037
+ reportProgress,
1038
+ requestId: typeof request.params?._meta?.requestId === "string" ? request.params._meta.requestId : void 0,
1039
+ session: this.#auth,
1040
+ sessionId: this.#sessionId,
1041
+ streamContent
1042
+ });
1043
+ const maybeStringResult = await (tool.timeoutMs ? Promise.race([
1044
+ executeToolPromise,
1045
+ new Promise((_, reject) => {
1046
+ const timeoutId = setTimeout(() => {
1047
+ reject(
1048
+ new UserError(
1049
+ `Tool '${request.params.name}' timed out after ${tool.timeoutMs}ms. Consider increasing timeoutMs or optimizing the tool implementation.`
1050
+ )
1051
+ );
1052
+ }, tool.timeoutMs);
1053
+ executeToolPromise.then(
1054
+ () => clearTimeout(timeoutId),
1055
+ () => clearTimeout(timeoutId)
1056
+ );
1057
+ })
1058
+ ]) : executeToolPromise);
1059
+ await delay(1);
1060
+ if (maybeStringResult === void 0 || maybeStringResult === null) {
1061
+ result = ContentResultZodSchema.parse({
1062
+ content: []
1063
+ });
1064
+ } else if (typeof maybeStringResult === "string") {
1065
+ result = ContentResultZodSchema.parse({
1066
+ content: [{ text: maybeStringResult, type: "text" }]
1067
+ });
1068
+ } else if ("type" in maybeStringResult) {
1069
+ result = ContentResultZodSchema.parse({
1070
+ content: [maybeStringResult]
1071
+ });
1072
+ } else if ("content" in maybeStringResult) {
1073
+ result = ContentResultZodSchema.parse(maybeStringResult);
1074
+ if (result.structuredContent !== void 0 && tool.outputSchema) {
1075
+ result.structuredContent = await this.#validateStructuredContent(
1076
+ tool,
1077
+ result.structuredContent,
1078
+ request.params.name
1079
+ );
1080
+ }
1081
+ } else if (tool.outputSchema) {
1082
+ const structuredContent = await this.#validateStructuredContent(
1083
+ tool,
1084
+ maybeStringResult,
1085
+ request.params.name
1086
+ );
1087
+ result = ContentResultZodSchema.parse({
1088
+ content: [
1089
+ {
1090
+ text: JSON.stringify(structuredContent),
1091
+ type: "text"
1092
+ }
1093
+ ],
1094
+ structuredContent
1095
+ });
1096
+ } else {
1097
+ result = ContentResultZodSchema.parse(maybeStringResult);
1098
+ }
1099
+ } catch (error2) {
1100
+ if (error2 instanceof UserError) {
1101
+ return {
1102
+ content: [{ text: error2.message, type: "text" }],
1103
+ isError: true,
1104
+ ...error2.extras ? { structuredContent: error2.extras } : {}
1105
+ };
1106
+ }
1107
+ const errorMessage = error2 instanceof Error ? error2.message : String(error2);
1108
+ return {
1109
+ content: [
1110
+ {
1111
+ text: `Tool '${request.params.name}' execution failed: ${errorMessage}`,
1112
+ type: "text"
1113
+ }
1114
+ ],
1115
+ isError: true
1116
+ };
1117
+ }
1118
+ return result;
1119
+ });
1120
+ }
1121
+ };
1122
+ function camelToSnakeCase(str2) {
1123
+ return str2.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
1124
+ }
1125
+ function convertObjectToSnakeCase(obj) {
1126
+ const result = {};
1127
+ for (const [key, value] of Object.entries(obj)) {
1128
+ const snakeKey = camelToSnakeCase(key);
1129
+ result[snakeKey] = value;
1130
+ }
1131
+ return result;
1132
+ }
1133
+ function parseBasicAuthHeader(authHeader) {
1134
+ const basicMatch = authHeader?.match(/^Basic\s+(.+)$/);
1135
+ if (!basicMatch) return null;
1136
+ try {
1137
+ const credentials = Buffer.from(basicMatch[1], "base64").toString("utf-8");
1138
+ const credMatch = credentials.match(/^([^:]+):(.*)$/);
1139
+ if (!credMatch) return null;
1140
+ return { clientId: credMatch[1], clientSecret: credMatch[2] };
1141
+ } catch {
1142
+ return null;
1143
+ }
1144
+ }
1145
+ var FastMCPEventEmitterBase = EventEmitter;
1146
+ var FastMCPEventEmitter = class extends FastMCPEventEmitterBase {
1147
+ };
1148
+ var FastMCP = class extends FastMCPEventEmitter {
1149
+ constructor(options) {
1150
+ super();
1151
+ this.options = options;
1152
+ this.#options = options;
1153
+ this.#logger = options.logger || console;
1154
+ if (options.auth) {
1155
+ if (!options.authenticate) {
1156
+ this.#authenticate = ((request) => options.auth.authenticate(request));
1157
+ } else {
1158
+ this.#authenticate = options.authenticate;
1159
+ }
1160
+ if (!options.oauth) {
1161
+ this.#options = {
1162
+ ...options,
1163
+ oauth: options.auth.getOAuthConfig()
1164
+ };
1165
+ }
1166
+ } else {
1167
+ this.#authenticate = options.authenticate;
1168
+ }
1169
+ }
1170
+ get serverState() {
1171
+ return this.#serverState;
1172
+ }
1173
+ get sessions() {
1174
+ return this.#sessions;
1175
+ }
1176
+ #authenticate;
1177
+ #honoApp = new Hono();
1178
+ #httpStreamServer = null;
1179
+ #logger;
1180
+ #options;
1181
+ #prompts = [];
1182
+ #resources = [];
1183
+ #resourcesTemplates = [];
1184
+ #serverState = "stopped";
1185
+ #sessions = [];
1186
+ #tools = [];
1187
+ /**
1188
+ * Adds a prompt to the server.
1189
+ */
1190
+ addPrompt(prompt) {
1191
+ this.#prompts = this.#prompts.filter((p) => p.name !== prompt.name);
1192
+ this.#prompts.push(prompt);
1193
+ if (this.#serverState === "running") {
1194
+ this.#promptsListChanged(this.#prompts);
1195
+ }
1196
+ }
1197
+ /**
1198
+ * Adds prompts to the server.
1199
+ */
1200
+ addPrompts(prompts) {
1201
+ const newPromptNames = new Set(prompts.map((prompt) => prompt.name));
1202
+ this.#prompts = this.#prompts.filter((p) => !newPromptNames.has(p.name));
1203
+ this.#prompts.push(...prompts);
1204
+ if (this.#serverState === "running") {
1205
+ this.#promptsListChanged(this.#prompts);
1206
+ }
1207
+ }
1208
+ /**
1209
+ * Adds a resource to the server.
1210
+ */
1211
+ addResource(resource) {
1212
+ this.#resources = this.#resources.filter((r) => r.name !== resource.name);
1213
+ this.#resources.push(resource);
1214
+ if (this.#serverState === "running") {
1215
+ this.#resourcesListChanged(this.#resources);
1216
+ }
1217
+ }
1218
+ /**
1219
+ * Adds resources to the server.
1220
+ */
1221
+ addResources(resources) {
1222
+ const newResourceNames = new Set(
1223
+ resources.map((resource) => resource.name)
1224
+ );
1225
+ this.#resources = this.#resources.filter(
1226
+ (r) => !newResourceNames.has(r.name)
1227
+ );
1228
+ this.#resources.push(...resources);
1229
+ if (this.#serverState === "running") {
1230
+ this.#resourcesListChanged(this.#resources);
1231
+ }
1232
+ }
1233
+ /**
1234
+ * Adds a resource template to the server.
1235
+ */
1236
+ addResourceTemplate(resource) {
1237
+ this.#resourcesTemplates = this.#resourcesTemplates.filter(
1238
+ (t) => t.name !== resource.name
1239
+ );
1240
+ this.#resourcesTemplates.push(resource);
1241
+ if (this.#serverState === "running") {
1242
+ this.#resourceTemplatesListChanged(this.#resourcesTemplates);
1243
+ }
1244
+ }
1245
+ /**
1246
+ * Adds resource templates to the server.
1247
+ */
1248
+ addResourceTemplates(resources) {
1249
+ const newResourceTemplateNames = new Set(
1250
+ resources.map((resource) => resource.name)
1251
+ );
1252
+ this.#resourcesTemplates = this.#resourcesTemplates.filter(
1253
+ (t) => !newResourceTemplateNames.has(t.name)
1254
+ );
1255
+ this.#resourcesTemplates.push(...resources);
1256
+ if (this.#serverState === "running") {
1257
+ this.#resourceTemplatesListChanged(this.#resourcesTemplates);
1258
+ }
1259
+ }
1260
+ /**
1261
+ * Adds a tool to the server.
1262
+ */
1263
+ addTool(tool) {
1264
+ this.#tools = this.#tools.filter((t) => t.name !== tool.name);
1265
+ this.#tools.push(tool);
1266
+ if (this.#serverState === "running") {
1267
+ this.#toolsListChanged(this.#tools);
1268
+ }
1269
+ }
1270
+ /**
1271
+ * Adds tools to the server.
1272
+ */
1273
+ addTools(tools) {
1274
+ const newToolNames = new Set(tools.map((tool) => tool.name));
1275
+ this.#tools = this.#tools.filter((t) => !newToolNames.has(t.name));
1276
+ this.#tools.push(...tools);
1277
+ if (this.#serverState === "running") {
1278
+ this.#toolsListChanged(this.#tools);
1279
+ }
1280
+ }
1281
+ /**
1282
+ * Embeds a resource by URI, making it easy to include resources in tool responses.
1283
+ *
1284
+ * @param uri - The URI of the resource to embed
1285
+ * @returns Promise<ResourceContent> - The embedded resource content
1286
+ */
1287
+ async embedded(uri) {
1288
+ const directResource = this.#resources.find(
1289
+ (resource) => resource.uri === uri
1290
+ );
1291
+ if (directResource) {
1292
+ const result = await directResource.load();
1293
+ const results = Array.isArray(result) ? result : [result];
1294
+ const firstResult = results[0];
1295
+ const resourceData = {
1296
+ mimeType: directResource.mimeType,
1297
+ uri
1298
+ };
1299
+ if ("text" in firstResult) {
1300
+ resourceData.text = firstResult.text;
1301
+ }
1302
+ if ("blob" in firstResult) {
1303
+ resourceData.blob = firstResult.blob;
1304
+ }
1305
+ return resourceData;
1306
+ }
1307
+ for (const template of this.#resourcesTemplates) {
1308
+ const parsedTemplate = parseURITemplate(template.uriTemplate);
1309
+ const params = parsedTemplate.fromUri(uri);
1310
+ if (!params) {
1311
+ continue;
1312
+ }
1313
+ const result = await template.load(
1314
+ params
1315
+ );
1316
+ const resourceData = {
1317
+ mimeType: template.mimeType,
1318
+ uri
1319
+ };
1320
+ if ("text" in result) {
1321
+ resourceData.text = result.text;
1322
+ }
1323
+ if ("blob" in result) {
1324
+ resourceData.blob = result.blob;
1325
+ }
1326
+ return resourceData;
1327
+ }
1328
+ throw new UnexpectedStateError(`Resource not found: ${uri}`, { uri });
1329
+ }
1330
+ /**
1331
+ * Returns the underlying Hono app instance for direct access to Hono's native API.
1332
+ * This allows you to add custom routes, middleware, and handlers using Hono's standard methods.
1333
+ *
1334
+ * @returns The Hono app instance
1335
+ *
1336
+ * @example
1337
+ * ```typescript
1338
+ * const app = server.getApp();
1339
+ *
1340
+ * // Add routes using native Hono API
1341
+ * app.get('/api/users', async (c) => {
1342
+ * return c.json({ users: [] });
1343
+ * });
1344
+ *
1345
+ * app.post('/api/users/:id', async (c) => {
1346
+ * const id = c.req.param('id');
1347
+ * return c.json({ id });
1348
+ * });
1349
+ * ```
1350
+ */
1351
+ getApp() {
1352
+ return this.#honoApp;
1353
+ }
1354
+ /**
1355
+ * Removes a prompt from the server.
1356
+ */
1357
+ removePrompt(name2) {
1358
+ this.#prompts = this.#prompts.filter((p) => p.name !== name2);
1359
+ if (this.#serverState === "running") {
1360
+ this.#promptsListChanged(this.#prompts);
1361
+ }
1362
+ }
1363
+ /**
1364
+ * Removes prompts from the server.
1365
+ */
1366
+ removePrompts(names) {
1367
+ for (const name2 of names) {
1368
+ this.#prompts = this.#prompts.filter((p) => p.name !== name2);
1369
+ }
1370
+ if (this.#serverState === "running") {
1371
+ this.#promptsListChanged(this.#prompts);
1372
+ }
1373
+ }
1374
+ /**
1375
+ * Removes a resource from the server.
1376
+ */
1377
+ removeResource(name2) {
1378
+ this.#resources = this.#resources.filter((r) => r.name !== name2);
1379
+ if (this.#serverState === "running") {
1380
+ this.#resourcesListChanged(this.#resources);
1381
+ }
1382
+ }
1383
+ /**
1384
+ * Removes resources from the server.
1385
+ */
1386
+ removeResources(names) {
1387
+ for (const name2 of names) {
1388
+ this.#resources = this.#resources.filter((r) => r.name !== name2);
1389
+ }
1390
+ if (this.#serverState === "running") {
1391
+ this.#resourcesListChanged(this.#resources);
1392
+ }
1393
+ }
1394
+ /**
1395
+ * Removes a resource template from the server.
1396
+ */
1397
+ removeResourceTemplate(name2) {
1398
+ this.#resourcesTemplates = this.#resourcesTemplates.filter(
1399
+ (t) => t.name !== name2
1400
+ );
1401
+ if (this.#serverState === "running") {
1402
+ this.#resourceTemplatesListChanged(this.#resourcesTemplates);
1403
+ }
1404
+ }
1405
+ /**
1406
+ * Removes resource templates from the server.
1407
+ */
1408
+ removeResourceTemplates(names) {
1409
+ for (const name2 of names) {
1410
+ this.#resourcesTemplates = this.#resourcesTemplates.filter(
1411
+ (t) => t.name !== name2
1412
+ );
1413
+ }
1414
+ if (this.#serverState === "running") {
1415
+ this.#resourceTemplatesListChanged(this.#resourcesTemplates);
1416
+ }
1417
+ }
1418
+ /**
1419
+ * Removes a tool from the server.
1420
+ */
1421
+ removeTool(name2) {
1422
+ this.#tools = this.#tools.filter((t) => t.name !== name2);
1423
+ if (this.#serverState === "running") {
1424
+ this.#toolsListChanged(this.#tools);
1425
+ }
1426
+ }
1427
+ /**
1428
+ * Removes tools from the server.
1429
+ */
1430
+ removeTools(names) {
1431
+ for (const name2 of names) {
1432
+ this.#tools = this.#tools.filter((t) => t.name !== name2);
1433
+ }
1434
+ if (this.#serverState === "running") {
1435
+ this.#toolsListChanged(this.#tools);
1436
+ }
1437
+ }
1438
+ /**
1439
+ * Starts the server.
1440
+ */
1441
+ async start(options) {
1442
+ const config = this.#parseRuntimeConfig(options);
1443
+ if (config.transportType === "stdio") {
1444
+ const transport = new StdioServerTransport();
1445
+ let auth;
1446
+ if (this.#authenticate) {
1447
+ try {
1448
+ auth = await this.#authenticate(
1449
+ void 0
1450
+ );
1451
+ } catch (error2) {
1452
+ this.#logger.error(
1453
+ "[FastMCP error] Authentication failed for stdio transport:",
1454
+ error2 instanceof Error ? error2.message : String(error2)
1455
+ );
1456
+ }
1457
+ }
1458
+ const session = new FastMCPSession({
1459
+ auth,
1460
+ instructions: this.#options.instructions,
1461
+ logger: this.#logger,
1462
+ name: this.#options.name,
1463
+ onToolCall: this.#options.onToolCall,
1464
+ ping: this.#options.ping,
1465
+ prompts: this.#prompts,
1466
+ resources: this.#resources,
1467
+ resourcesTemplates: this.#resourcesTemplates,
1468
+ roots: this.#options.roots,
1469
+ tools: this.#tools,
1470
+ transportType: "stdio",
1471
+ utils: this.#options.utils,
1472
+ version: this.#options.version
1473
+ });
1474
+ await session.connect(transport);
1475
+ this.#sessions.push(session);
1476
+ session.once("error", () => {
1477
+ this.#removeSession(session);
1478
+ });
1479
+ if (transport.onclose) {
1480
+ const originalOnClose = transport.onclose;
1481
+ transport.onclose = () => {
1482
+ this.#removeSession(session);
1483
+ if (originalOnClose) {
1484
+ originalOnClose();
1485
+ }
1486
+ };
1487
+ } else {
1488
+ transport.onclose = () => {
1489
+ this.#removeSession(session);
1490
+ };
1491
+ }
1492
+ this.emit("connect", {
1493
+ session
1494
+ });
1495
+ this.#serverState = "running";
1496
+ } else if (config.transportType === "httpStream") {
1497
+ const httpConfig = config.httpStream;
1498
+ const protocol = httpConfig.sslCert || httpConfig.sslKey ? "https" : "http";
1499
+ if (httpConfig.stateless) {
1500
+ this.#logger.info(
1501
+ `[FastMCP info] Starting server in stateless mode on HTTP Stream at ${protocol}://${httpConfig.host}:${httpConfig.port}${httpConfig.endpoint}`
1502
+ );
1503
+ this.#httpStreamServer = await startHTTPServer({
1504
+ ...this.#authenticate ? { authenticate: this.#authenticate } : {},
1505
+ cors: httpConfig.cors,
1506
+ createServer: async (request) => {
1507
+ let auth;
1508
+ if (this.#authenticate) {
1509
+ auth = await this.#authenticate(request);
1510
+ if (auth === void 0 || auth === null) {
1511
+ throw new Error("Authentication required");
1512
+ }
1513
+ }
1514
+ const sessionId = Array.isArray(request.headers["mcp-session-id"]) ? request.headers["mcp-session-id"][0] : request.headers["mcp-session-id"];
1515
+ return this.#createSession(auth, sessionId);
1516
+ },
1517
+ enableJsonResponse: httpConfig.enableJsonResponse,
1518
+ eventStore: httpConfig.eventStore,
1519
+ host: httpConfig.host,
1520
+ ...this.#options.oauth?.enabled && this.#options.oauth.protectedResource?.resource ? {
1521
+ oauth: {
1522
+ protectedResource: {
1523
+ resource: this.#options.oauth.protectedResource.resource
1524
+ }
1525
+ }
1526
+ } : {},
1527
+ // In stateless mode, we don't track sessions
1528
+ onClose: async () => {
1529
+ },
1530
+ onConnect: async () => {
1531
+ this.#logger.debug(
1532
+ `[FastMCP debug] Stateless HTTP Stream request handled`
1533
+ );
1534
+ },
1535
+ onUnhandledRequest: async (req, res) => {
1536
+ await this.#handleUnhandledRequest(
1537
+ req,
1538
+ res,
1539
+ true,
1540
+ httpConfig.host,
1541
+ httpConfig.endpoint
1542
+ );
1543
+ },
1544
+ port: httpConfig.port,
1545
+ sslCa: httpConfig.sslCa,
1546
+ sslCert: httpConfig.sslCert,
1547
+ sslKey: httpConfig.sslKey,
1548
+ stateless: true,
1549
+ streamEndpoint: httpConfig.endpoint
1550
+ });
1551
+ } else {
1552
+ this.#httpStreamServer = await startHTTPServer({
1553
+ ...this.#authenticate ? { authenticate: this.#authenticate } : {},
1554
+ cors: httpConfig.cors,
1555
+ createServer: async (request) => {
1556
+ let auth;
1557
+ if (this.#authenticate) {
1558
+ auth = await this.#authenticate(request);
1559
+ }
1560
+ const sessionId = Array.isArray(request.headers["mcp-session-id"]) ? request.headers["mcp-session-id"][0] : request.headers["mcp-session-id"];
1561
+ return this.#createSession(auth, sessionId);
1562
+ },
1563
+ enableJsonResponse: httpConfig.enableJsonResponse,
1564
+ eventStore: httpConfig.eventStore,
1565
+ host: httpConfig.host,
1566
+ ...this.#options.oauth?.enabled && this.#options.oauth.protectedResource?.resource ? {
1567
+ oauth: {
1568
+ protectedResource: {
1569
+ resource: this.#options.oauth.protectedResource.resource
1570
+ }
1571
+ }
1572
+ } : {},
1573
+ onClose: async (session) => {
1574
+ const sessionIndex = this.#sessions.indexOf(session);
1575
+ if (sessionIndex !== -1) this.#sessions.splice(sessionIndex, 1);
1576
+ this.emit("disconnect", {
1577
+ session
1578
+ });
1579
+ },
1580
+ onConnect: async (session) => {
1581
+ this.#sessions.push(session);
1582
+ this.#logger.info(`[FastMCP info] HTTP Stream session established`);
1583
+ this.emit("connect", {
1584
+ session
1585
+ });
1586
+ },
1587
+ onUnhandledRequest: async (req, res) => {
1588
+ await this.#handleUnhandledRequest(
1589
+ req,
1590
+ res,
1591
+ false,
1592
+ httpConfig.host,
1593
+ httpConfig.endpoint
1594
+ );
1595
+ },
1596
+ port: httpConfig.port,
1597
+ sslCa: httpConfig.sslCa,
1598
+ sslCert: httpConfig.sslCert,
1599
+ sslKey: httpConfig.sslKey,
1600
+ stateless: httpConfig.stateless,
1601
+ streamEndpoint: httpConfig.endpoint
1602
+ });
1603
+ this.#logger.info(
1604
+ `[FastMCP info] server is running on HTTP Stream at ${protocol}://${httpConfig.host}:${httpConfig.port}${httpConfig.endpoint}`
1605
+ );
1606
+ }
1607
+ this.#serverState = "running";
1608
+ } else {
1609
+ throw new Error("Invalid transport type");
1610
+ }
1611
+ }
1612
+ /**
1613
+ * Stops the server.
1614
+ */
1615
+ async stop() {
1616
+ if (this.#httpStreamServer) {
1617
+ await this.#httpStreamServer.close();
1618
+ }
1619
+ this.#serverState = "stopped";
1620
+ }
1621
+ /**
1622
+ * Creates a new FastMCPSession instance with the current configuration.
1623
+ * Used both for regular sessions and stateless requests.
1624
+ */
1625
+ #createSession(auth, sessionId) {
1626
+ if (auth && typeof auth === "object" && "authenticated" in auth && !auth.authenticated) {
1627
+ const errorMessage = "error" in auth && typeof auth.error === "string" ? auth.error : "Authentication failed";
1628
+ throw new Error(errorMessage);
1629
+ }
1630
+ const allowedTools = auth ? this.#tools.filter(
1631
+ (tool) => tool.canAccess ? tool.canAccess(auth) : true
1632
+ ) : this.#tools;
1633
+ return new FastMCPSession({
1634
+ auth,
1635
+ instructions: this.#options.instructions,
1636
+ logger: this.#logger,
1637
+ name: this.#options.name,
1638
+ onToolCall: this.#options.onToolCall,
1639
+ ping: this.#options.ping,
1640
+ prompts: this.#prompts,
1641
+ resources: this.#resources,
1642
+ resourcesTemplates: this.#resourcesTemplates,
1643
+ roots: this.#options.roots,
1644
+ sessionId,
1645
+ tools: allowedTools,
1646
+ transportType: "httpStream",
1647
+ utils: this.#options.utils,
1648
+ version: this.#options.version
1649
+ });
1650
+ }
1651
+ /**
1652
+ * Handles unhandled HTTP requests with health, readiness, OAuth endpoints, and custom routes
1653
+ */
1654
+ #handleUnhandledRequest = async (req, res, isStateless = false, host, streamEndpoint) => {
1655
+ const url = new URL(req.url || "", `http://${host}`);
1656
+ try {
1657
+ const webRequest = this.#nodeRequestToWebRequest(req, url);
1658
+ const honoResponse = await this.#honoApp.fetch(webRequest, {
1659
+ incoming: req,
1660
+ outgoing: res
1661
+ });
1662
+ if (honoResponse.status !== 404) {
1663
+ if (!res.headersSent) {
1664
+ res.statusCode = honoResponse.status;
1665
+ honoResponse.headers.forEach((value, key) => {
1666
+ res.setHeader(key, value);
1667
+ });
1668
+ if (honoResponse.body) {
1669
+ const reader = honoResponse.body.getReader();
1670
+ try {
1671
+ while (true) {
1672
+ const { done, value } = await reader.read();
1673
+ if (done) break;
1674
+ res.write(value);
1675
+ }
1676
+ } finally {
1677
+ reader.releaseLock();
1678
+ }
1679
+ }
1680
+ res.end();
1681
+ }
1682
+ return;
1683
+ }
1684
+ } catch (error2) {
1685
+ this.#logger.debug("[FastMCP debug] Hono route not matched", error2);
1686
+ }
1687
+ const healthConfig = this.#options.health ?? {};
1688
+ const enabled = healthConfig.enabled === void 0 ? true : healthConfig.enabled;
1689
+ if (enabled) {
1690
+ const path2 = healthConfig.path ?? "/health";
1691
+ const url2 = new URL(req.url || "", `http://${host}`);
1692
+ try {
1693
+ if ((req.method === "GET" || req.method === "HEAD") && url2.pathname === path2) {
1694
+ res.writeHead(healthConfig.status ?? 200, {
1695
+ "Content-Type": "text/plain"
1696
+ }).end(
1697
+ req.method === "HEAD" ? void 0 : healthConfig.message ?? "\u2713 Ok"
1698
+ );
1699
+ return;
1700
+ }
1701
+ if ((req.method === "GET" || req.method === "HEAD") && url2.pathname === "/ready") {
1702
+ if (isStateless) {
1703
+ const response = {
1704
+ mode: "stateless",
1705
+ ready: 1,
1706
+ status: "ready",
1707
+ total: 1
1708
+ };
1709
+ res.writeHead(200, {
1710
+ "Content-Type": "application/json"
1711
+ }).end(
1712
+ req.method === "HEAD" ? void 0 : JSON.stringify(response)
1713
+ );
1714
+ } else {
1715
+ const readySessions = this.#sessions.filter(
1716
+ (s) => s.isReady
1717
+ ).length;
1718
+ const totalSessions = this.#sessions.length;
1719
+ const allReady = readySessions === totalSessions && totalSessions > 0;
1720
+ const response = {
1721
+ ready: readySessions,
1722
+ status: allReady ? "ready" : totalSessions === 0 ? "no_sessions" : "initializing",
1723
+ total: totalSessions
1724
+ };
1725
+ res.writeHead(allReady ? 200 : 503, {
1726
+ "Content-Type": "application/json"
1727
+ }).end(
1728
+ req.method === "HEAD" ? void 0 : JSON.stringify(response)
1729
+ );
1730
+ }
1731
+ return;
1732
+ }
1733
+ } catch (error2) {
1734
+ this.#logger.error("[FastMCP error] health endpoint error", error2);
1735
+ }
1736
+ }
1737
+ const oauthConfig = this.#options.oauth;
1738
+ if (oauthConfig?.enabled && req.method === "GET") {
1739
+ const url2 = new URL(req.url || "", `http://${host}`);
1740
+ if (url2.pathname === "/.well-known/oauth-authorization-server" && oauthConfig.authorizationServer) {
1741
+ const metadata = convertObjectToSnakeCase(
1742
+ oauthConfig.authorizationServer
1743
+ );
1744
+ res.writeHead(200, {
1745
+ "Content-Type": "application/json"
1746
+ }).end(JSON.stringify(metadata));
1747
+ return;
1748
+ }
1749
+ if (oauthConfig.protectedResource) {
1750
+ const wellKnownBase = "/.well-known/oauth-protected-resource";
1751
+ let shouldServeMetadata = false;
1752
+ if (streamEndpoint && url2.pathname === `${wellKnownBase}${streamEndpoint}`) {
1753
+ shouldServeMetadata = true;
1754
+ } else if (url2.pathname === wellKnownBase) {
1755
+ shouldServeMetadata = true;
1756
+ }
1757
+ if (shouldServeMetadata) {
1758
+ const metadata = convertObjectToSnakeCase(
1759
+ oauthConfig.protectedResource
1760
+ );
1761
+ res.writeHead(200, {
1762
+ "Content-Type": "application/json"
1763
+ }).end(JSON.stringify(metadata));
1764
+ return;
1765
+ }
1766
+ }
1767
+ }
1768
+ const oauthProxy = oauthConfig?.proxy;
1769
+ if (oauthProxy && oauthConfig?.enabled) {
1770
+ const url2 = new URL(req.url || "", `http://${host}`);
1771
+ try {
1772
+ if (req.method === "POST" && url2.pathname === "/oauth/register") {
1773
+ await new Promise((resolve) => {
1774
+ let body = "";
1775
+ req.on("data", (chunk) => body += chunk);
1776
+ req.on("end", async () => {
1777
+ try {
1778
+ const request = JSON.parse(body);
1779
+ const response = await oauthProxy.registerClient(request);
1780
+ res.writeHead(201, { "Content-Type": "application/json" }).end(JSON.stringify(response));
1781
+ } catch (error2) {
1782
+ const statusCode = error2.statusCode || 400;
1783
+ res.writeHead(statusCode, { "Content-Type": "application/json" }).end(
1784
+ JSON.stringify(
1785
+ error2.toJSON?.() || {
1786
+ error: "invalid_request"
1787
+ }
1788
+ )
1789
+ );
1790
+ }
1791
+ resolve();
1792
+ });
1793
+ });
1794
+ return;
1795
+ }
1796
+ if (req.method === "GET" && url2.pathname === "/oauth/authorize") {
1797
+ try {
1798
+ const params = Object.fromEntries(url2.searchParams.entries());
1799
+ const response = await oauthProxy.authorize(
1800
+ params
1801
+ );
1802
+ const location = response.headers.get("Location");
1803
+ if (location) {
1804
+ res.writeHead(response.status, { Location: location }).end();
1805
+ } else {
1806
+ const html = await response.text();
1807
+ res.writeHead(response.status, { "Content-Type": "text/html" }).end(html);
1808
+ }
1809
+ } catch (error2) {
1810
+ res.writeHead(400, { "Content-Type": "application/json" }).end(
1811
+ JSON.stringify(
1812
+ error2.toJSON?.() || {
1813
+ error: "invalid_request"
1814
+ }
1815
+ )
1816
+ );
1817
+ }
1818
+ return;
1819
+ }
1820
+ if (req.method === "GET" && url2.pathname === "/oauth/callback") {
1821
+ try {
1822
+ const mockRequest = new Request(`http://${host}${req.url}`);
1823
+ const response = await oauthProxy.handleCallback(mockRequest);
1824
+ const location = response.headers.get("Location");
1825
+ if (location) {
1826
+ res.writeHead(response.status, { Location: location }).end();
1827
+ } else {
1828
+ const text = await response.text();
1829
+ res.writeHead(response.status).end(text);
1830
+ }
1831
+ } catch (error2) {
1832
+ res.writeHead(400, { "Content-Type": "application/json" }).end(
1833
+ JSON.stringify(
1834
+ error2.toJSON?.() || {
1835
+ error: "server_error"
1836
+ }
1837
+ )
1838
+ );
1839
+ }
1840
+ return;
1841
+ }
1842
+ if (req.method === "POST" && url2.pathname === "/oauth/consent") {
1843
+ await new Promise((resolve) => {
1844
+ let body = "";
1845
+ req.on("data", (chunk) => body += chunk);
1846
+ req.on("end", async () => {
1847
+ try {
1848
+ const mockRequest = new Request(
1849
+ `http://${host}/oauth/consent`,
1850
+ {
1851
+ body,
1852
+ headers: {
1853
+ "Content-Type": "application/x-www-form-urlencoded"
1854
+ },
1855
+ method: "POST"
1856
+ }
1857
+ );
1858
+ const response = await oauthProxy.handleConsent(mockRequest);
1859
+ const location = response.headers.get("Location");
1860
+ if (location) {
1861
+ res.writeHead(response.status, { Location: location }).end();
1862
+ } else {
1863
+ const text = await response.text();
1864
+ res.writeHead(response.status).end(text);
1865
+ }
1866
+ } catch (error2) {
1867
+ res.writeHead(400, { "Content-Type": "application/json" }).end(
1868
+ JSON.stringify(
1869
+ error2.toJSON?.() || {
1870
+ error: "server_error"
1871
+ }
1872
+ )
1873
+ );
1874
+ }
1875
+ resolve();
1876
+ });
1877
+ });
1878
+ return;
1879
+ }
1880
+ if (req.method === "POST" && url2.pathname === "/oauth/token") {
1881
+ await new Promise((resolve) => {
1882
+ let body = "";
1883
+ req.on("data", (chunk) => body += chunk);
1884
+ req.on("end", async () => {
1885
+ try {
1886
+ const params = new URLSearchParams(body);
1887
+ const grantType = params.get("grant_type");
1888
+ const basicAuth = parseBasicAuthHeader(
1889
+ req.headers.authorization
1890
+ );
1891
+ const clientId = basicAuth?.clientId || params.get("client_id") || "";
1892
+ const clientSecret = basicAuth?.clientSecret ?? params.get("client_secret") ?? void 0;
1893
+ let response;
1894
+ if (grantType === "authorization_code") {
1895
+ response = await oauthProxy.exchangeAuthorizationCode({
1896
+ client_id: clientId,
1897
+ client_secret: clientSecret,
1898
+ code: params.get("code") || "",
1899
+ code_verifier: params.get("code_verifier") || void 0,
1900
+ grant_type: "authorization_code",
1901
+ redirect_uri: params.get("redirect_uri") || ""
1902
+ });
1903
+ } else if (grantType === "refresh_token") {
1904
+ response = await oauthProxy.exchangeRefreshToken({
1905
+ client_id: clientId,
1906
+ client_secret: clientSecret,
1907
+ grant_type: "refresh_token",
1908
+ refresh_token: params.get("refresh_token") || "",
1909
+ scope: params.get("scope") || void 0
1910
+ });
1911
+ } else {
1912
+ throw {
1913
+ statusCode: 400,
1914
+ toJSON: () => ({ error: "unsupported_grant_type" })
1915
+ };
1916
+ }
1917
+ res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify(response));
1918
+ } catch (error2) {
1919
+ const statusCode = error2.statusCode || 400;
1920
+ res.writeHead(statusCode, { "Content-Type": "application/json" }).end(
1921
+ JSON.stringify(
1922
+ error2.toJSON?.() || {
1923
+ error: "invalid_request"
1924
+ }
1925
+ )
1926
+ );
1927
+ }
1928
+ resolve();
1929
+ });
1930
+ });
1931
+ return;
1932
+ }
1933
+ } catch (error2) {
1934
+ this.#logger.error("[FastMCP error] OAuth Proxy endpoint error", error2);
1935
+ res.writeHead(500).end();
1936
+ return;
1937
+ }
1938
+ }
1939
+ };
1940
+ /**
1941
+ * Converts Node.js IncomingMessage to Web Request for Hono
1942
+ */
1943
+ #nodeRequestToWebRequest(req, url) {
1944
+ const method = req.method || "GET";
1945
+ const headers = new Headers();
1946
+ for (const [key, value] of Object.entries(req.headers)) {
1947
+ if (value) {
1948
+ if (Array.isArray(value)) {
1949
+ for (const v of value) {
1950
+ headers.append(key, v);
1951
+ }
1952
+ } else {
1953
+ headers.set(key, value);
1954
+ }
1955
+ }
1956
+ }
1957
+ const hasBody = method !== "GET" && method !== "HEAD";
1958
+ if (hasBody) {
1959
+ return new Request(url.toString(), {
1960
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1961
+ body: req,
1962
+ // Node.js IncomingMessage is readable stream
1963
+ duplex: "half",
1964
+ // Required for streaming bodies
1965
+ headers,
1966
+ method
1967
+ });
1968
+ } else {
1969
+ return new Request(url.toString(), {
1970
+ headers,
1971
+ method
1972
+ });
1973
+ }
1974
+ }
1975
+ #parseRuntimeConfig(overrides) {
1976
+ const args2 = process.argv.slice(2);
1977
+ const getArg = (name2) => {
1978
+ const index = args2.findIndex((arg) => arg === `--${name2}`);
1979
+ return index !== -1 && index + 1 < args2.length ? args2[index + 1] : void 0;
1980
+ };
1981
+ const transportArg = getArg("transport");
1982
+ const portArg = getArg("port");
1983
+ const endpointArg = getArg("endpoint");
1984
+ const statelessArg = getArg("stateless");
1985
+ const hostArg = getArg("host");
1986
+ const envTransport = process.env.FASTMCP_TRANSPORT;
1987
+ const envPort = process.env.FASTMCP_PORT;
1988
+ const envEndpoint = process.env.FASTMCP_ENDPOINT;
1989
+ const envStateless = process.env.FASTMCP_STATELESS;
1990
+ const envHost = process.env.FASTMCP_HOST;
1991
+ const transportType = overrides?.transportType || (transportArg === "http-stream" ? "httpStream" : transportArg) || envTransport || "stdio";
1992
+ if (transportType === "httpStream") {
1993
+ const port = parseInt(
1994
+ overrides?.httpStream?.port?.toString() || portArg || envPort || "8080"
1995
+ );
1996
+ const host = overrides?.httpStream?.host || hostArg || envHost || "localhost";
1997
+ const endpoint = overrides?.httpStream?.endpoint || endpointArg || envEndpoint || "/mcp";
1998
+ const enableJsonResponse = overrides?.httpStream?.enableJsonResponse || false;
1999
+ const stateless = overrides?.httpStream?.stateless || statelessArg === "true" || envStateless === "true" || false;
2000
+ const cors = overrides?.httpStream?.cors;
2001
+ const eventStore = overrides?.httpStream?.eventStore;
2002
+ const sslCa = overrides?.httpStream?.sslCa;
2003
+ const sslCert = overrides?.httpStream?.sslCert;
2004
+ const sslKey = overrides?.httpStream?.sslKey;
2005
+ return {
2006
+ httpStream: {
2007
+ cors,
2008
+ enableJsonResponse,
2009
+ endpoint,
2010
+ eventStore,
2011
+ host,
2012
+ port,
2013
+ sslCa,
2014
+ sslCert,
2015
+ sslKey,
2016
+ stateless
2017
+ },
2018
+ transportType: "httpStream"
2019
+ };
2020
+ }
2021
+ return { transportType: "stdio" };
2022
+ }
2023
+ /**
2024
+ * Notifies all sessions that the prompts list has changed.
2025
+ */
2026
+ #promptsListChanged(prompts) {
2027
+ for (const session of this.#sessions) {
2028
+ session.promptsListChanged(prompts);
2029
+ }
2030
+ }
2031
+ #removeSession(session) {
2032
+ const sessionIndex = this.#sessions.indexOf(session);
2033
+ if (sessionIndex !== -1) {
2034
+ this.#sessions.splice(sessionIndex, 1);
2035
+ this.emit("disconnect", {
2036
+ session
2037
+ });
2038
+ }
2039
+ }
2040
+ /**
2041
+ * Notifies all sessions that the resources list has changed.
2042
+ */
2043
+ #resourcesListChanged(resources) {
2044
+ for (const session of this.#sessions) {
2045
+ session.resourcesListChanged(resources);
2046
+ }
2047
+ }
2048
+ /**
2049
+ * Notifies all sessions that the resource templates list has changed.
2050
+ */
2051
+ #resourceTemplatesListChanged(templates) {
2052
+ for (const session of this.#sessions) {
2053
+ session.resourceTemplatesListChanged(templates);
2054
+ }
2055
+ }
2056
+ /**
2057
+ * Notifies all sessions that the tools list has changed.
2058
+ */
2059
+ #toolsListChanged(tools) {
2060
+ for (const session of this.#sessions) {
2061
+ session.toolsListChanged(tools);
2062
+ }
2063
+ }
2064
+ };
2065
+
2066
+ // node_modules/.pnpm/fastmcp@4.3.2_patch_hash=08f77b633292d9ae4cb022c0fa5bd722dbd5c1aaaad600204f8ed38339bfd37b/node_modules/fastmcp/dist/chunk-OJG4XYXA.js
2067
+ import { z as z3 } from "zod";
2068
+ import { createHmac as createHmac2, pbkdf2, randomBytes } from "crypto";
2069
+ import { promisify } from "util";
2070
+ var pbkdf2Async = promisify(pbkdf2);
2071
+
2072
+ // src/index.ts
87
2073
  import { readFile } from "fs/promises";
88
2074
  import { createRequire as createRequire2 } from "module";
89
2075
  import { randomUUID } from "crypto";
90
2076
  import path from "path";
91
- import { z as z8 } from "zod";
2077
+ import { z as z10 } from "zod";
92
2078
 
93
2079
  // src/agent-hints.ts
94
2080
  var AGENT_HINTS_HEADERS = { "X-Firecrawl-Agent-Hints": "true" };
@@ -130,7 +2116,7 @@ function readErrorAgentHints(error2) {
130
2116
  }
131
2117
 
132
2118
  // src/tool-output.ts
133
- import { z as z2 } from "zod";
2119
+ import { z as z4 } from "zod";
134
2120
  function isRecord(value) {
135
2121
  return typeof value === "object" && value !== null && !Array.isArray(value);
136
2122
  }
@@ -150,15 +2136,15 @@ function structuredJsonText(text) {
150
2136
  return { content: [{ type: "text", text }] };
151
2137
  }
152
2138
  }
153
- var str = (description) => z2.string().nullable().optional().describe(description);
154
- var num = (description) => z2.number().nullable().optional().describe(description);
155
- var bool = (description) => z2.boolean().nullable().optional().describe(description);
156
- var unknown = (description) => z2.unknown().optional().describe(description);
2139
+ var str = (description) => z4.string().nullable().optional().describe(description);
2140
+ var num = (description) => z4.number().nullable().optional().describe(description);
2141
+ var bool = (description) => z4.boolean().nullable().optional().describe(description);
2142
+ var unknown = (description) => z4.unknown().optional().describe(description);
157
2143
  var success = bool("Whether the API call succeeded.");
158
2144
  var error = unknown("Error message or error object when the call did not succeed.");
159
2145
  var warning = str("Non-fatal warning about the result.");
160
2146
  var agentHints = {
161
- agent_hints: z2.array(z2.string()).optional().describe("Optional response guidance from the Firecrawl API.")
2147
+ agent_hints: z4.array(z4.string()).optional().describe("Optional response guidance from the Firecrawl API.")
162
2148
  };
163
2149
  var alexandriaEnvelope = {
164
2150
  requestId: str("Identifier of this Alexandria execution."),
@@ -176,7 +2162,7 @@ var alexandriaEnvelope = {
176
2162
  nextTool: unknown("A follow-up tool call (`{name, arguments}`) that continues or inspects this result."),
177
2163
  feedbackTool: unknown("Pointer to the feedback tool for reporting how this result served the task.")
178
2164
  };
179
- var scrapeOutputSchema = z2.object({
2165
+ var scrapeOutputSchema = z4.object({
180
2166
  ...agentHints,
181
2167
  // URL mode: the scraped document, with the formats that were requested.
182
2168
  markdown: str("Page content as markdown."),
@@ -208,7 +2194,7 @@ var scrapeOutputSchema = z2.object({
208
2194
  data: unknown("Alexandria mode: per-capability results in `data.alexandria`, each with `data`, `records`, or an `error`."),
209
2195
  ...alexandriaEnvelope
210
2196
  }).describe("A scraped document, or the Alexandria execution envelope when `alexandria` was passed.");
211
- var mapOutputSchema = z2.object({
2197
+ var mapOutputSchema = z4.object({
212
2198
  ...agentHints,
213
2199
  links: unknown("URLs discovered under the website."),
214
2200
  success,
@@ -216,7 +2202,7 @@ var mapOutputSchema = z2.object({
216
2202
  warning,
217
2203
  id: str("Identifier of the map run, for optional map feedback.")
218
2204
  }).describe("URLs indexed under the requested website.");
219
- var searchOutputSchema = z2.object({
2205
+ var searchOutputSchema = z4.object({
220
2206
  ...agentHints,
221
2207
  success,
222
2208
  data: unknown("Ranked results grouped by source, such as `web`, `news`, `images`, and `alexandria`."),
@@ -228,14 +2214,14 @@ var searchOutputSchema = z2.object({
228
2214
  nextTool: unknown("A follow-up tool call that continues this search."),
229
2215
  feedbackTool: unknown("Pointer to the feedback tool for this search.")
230
2216
  }).describe("Ranked search results grouped by source.");
231
- var findToolsOutputSchema = z2.object({
2217
+ var findToolsOutputSchema = z4.object({
232
2218
  ...agentHints,
233
2219
  success,
234
2220
  data: unknown("Discovery page in `data.alexandria[0].data`, with `level`, `items`, `total`, and an optional `nextTool`."),
235
2221
  error,
236
2222
  ...alexandriaEnvelope
237
2223
  }).describe("One page of the Alexandria catalogue: categories, providers, tools, or one contract.");
238
- var feedbackOutputSchema = z2.object({
2224
+ var feedbackOutputSchema = z4.object({
239
2225
  ...agentHints,
240
2226
  success,
241
2227
  error,
@@ -253,7 +2239,7 @@ var feedbackOutputSchema = z2.object({
253
2239
  data: unknown("Payload returned with the accepted feedback."),
254
2240
  raw: str("Response body when it was not JSON.")
255
2241
  }).describe("Result of submitting feedback.");
256
- var crawlOutputSchema = z2.object({
2242
+ var crawlOutputSchema = z4.object({
257
2243
  ...agentHints,
258
2244
  success,
259
2245
  error,
@@ -268,7 +2254,7 @@ var crawlOutputSchema = z2.object({
268
2254
  url: str("The URL the crawl started from."),
269
2255
  data: unknown("Scraped documents collected by the crawl.")
270
2256
  }).describe("Crawl job state and the documents collected so far.");
271
- var agentOutputSchema = z2.object({
2257
+ var agentOutputSchema = z4.object({
272
2258
  ...agentHints,
273
2259
  success,
274
2260
  error,
@@ -277,7 +2263,7 @@ var agentOutputSchema = z2.object({
277
2263
  threadId: str("Research thread this job belongs to."),
278
2264
  threadTurn: num("Turn number of this job within its thread.")
279
2265
  }).describe("The queued research job. The result is read with `firecrawl_agent_status`.");
280
- var agentStatusOutputSchema = z2.object({
2266
+ var agentStatusOutputSchema = z4.object({
281
2267
  ...agentHints,
282
2268
  success,
283
2269
  error,
@@ -301,7 +2287,7 @@ var agentStatusOutputSchema = z2.object({
301
2287
  'Set when the job ended waiting on the caller. Answer it by calling firecrawl_agent with this threadId and exchange.approve or exchange.decline carrying its id. kind "terms" lists providers whose data terms need accepting; otherwise calls lists paid calls waiting for approval.'
302
2288
  )
303
2289
  }).describe("Progress or final result of a research agent job.");
304
- var interactOutputSchema = z2.object({
2290
+ var interactOutputSchema = z4.object({
305
2291
  ...agentHints,
306
2292
  success,
307
2293
  error: unknown("Why the interaction or the session could not run."),
@@ -316,14 +2302,14 @@ var interactOutputSchema = z2.object({
316
2302
  sessionUrl: str("URL for viewing the live session."),
317
2303
  liveUrl: str("URL for viewing the live session.")
318
2304
  }).describe("Result of a browser interaction, with the session identifier to continue or stop it.");
319
- var interactStopOutputSchema = z2.object({
2305
+ var interactStopOutputSchema = z4.object({
320
2306
  ...agentHints,
321
2307
  success,
322
2308
  error,
323
2309
  message: str("Confirmation that the session was stopped."),
324
2310
  scrapeId: str("Session that was stopped.")
325
2311
  }).describe("Confirmation that the interact session was stopped.");
326
- var parseOutputSchema = z2.object({
2312
+ var parseOutputSchema = z4.object({
327
2313
  ...agentHints,
328
2314
  success,
329
2315
  error,
@@ -331,7 +2317,7 @@ var parseOutputSchema = z2.object({
331
2317
  mode: str("Which phase of the hosted flow produced this response."),
332
2318
  message: str("Guidance for the next call."),
333
2319
  data: unknown("Parsed document content; can include `data.metadata.scrapeId` for parse feedback."),
334
- upload: z2.object({
2320
+ upload: z4.object({
335
2321
  command: str("Local command that performs the upload. It carries no Firecrawl API key."),
336
2322
  method: str("HTTP method for the upload."),
337
2323
  headers: unknown("Headers the upload request must send."),
@@ -342,48 +2328,48 @@ var parseOutputSchema = z2.object({
342
2328
  maxSizeBytes: num("Largest file the upload URL accepts.")
343
2329
  }).optional().describe("Hosted phase one: how to upload the local file."),
344
2330
  nextToolCall: unknown("Hosted phase one: the second `firecrawl_parse` call to make once the upload succeeds, as `{name, arguments}`."),
345
- notes: z2.array(z2.string()).nullable().optional().describe("Hosted phase one: constraints on completing the upload flow."),
2331
+ notes: z4.array(z4.string()).nullable().optional().describe("Hosted phase one: constraints on completing the upload flow."),
346
2332
  raw: str("Response body when it was not JSON.")
347
2333
  }).describe("Parsed document content, or the upload instructions for the hosted two-call flow.");
348
- var deprecatedToolOutputSchema = z2.object({
2334
+ var deprecatedToolOutputSchema = z4.object({
349
2335
  code: str("Always `DEPRECATED_TOOL`."),
350
2336
  message: str("Why the tool is unavailable and what to call instead."),
351
2337
  replacement: unknown("The replacement tool, with instructions and example arguments."),
352
2338
  docs_url: str("Documentation for the replacement.")
353
2339
  }).describe("A pointer to the replacement tool. This entry point is deprecated and always errors.");
354
- var monitorOutputSchema = z2.object({
2340
+ var monitorOutputSchema = z4.object({
355
2341
  ...agentHints,
356
2342
  success,
357
2343
  error,
358
2344
  data: unknown("The monitor record: configuration, schedule, targets, and current state.")
359
2345
  }).describe("One monitor record.");
360
- var monitorListOutputSchema = z2.object({
2346
+ var monitorListOutputSchema = z4.object({
361
2347
  ...agentHints,
362
2348
  success,
363
2349
  error,
364
2350
  data: unknown("One page of monitor records."),
365
2351
  pagination: unknown("Pagination metadata for the listing.")
366
2352
  }).describe("One page of monitors for the authenticated account.");
367
- var monitorDeleteOutputSchema = z2.object({
2353
+ var monitorDeleteOutputSchema = z4.object({
368
2354
  ...agentHints,
369
2355
  success,
370
2356
  error,
371
2357
  data: unknown("Deletion details, when the API returns any.")
372
2358
  }).describe("Deletion status for the monitor.");
373
- var monitorRunOutputSchema = z2.object({
2359
+ var monitorRunOutputSchema = z4.object({
374
2360
  ...agentHints,
375
2361
  success,
376
2362
  error,
377
2363
  data: unknown("The queued check.")
378
2364
  }).describe("The check queued outside the monitor schedule.");
379
- var monitorChecksOutputSchema = z2.object({
2365
+ var monitorChecksOutputSchema = z4.object({
380
2366
  ...agentHints,
381
2367
  success,
382
2368
  error,
383
2369
  data: unknown("One page of check summaries."),
384
2370
  pagination: unknown("Pagination metadata for the listing.")
385
2371
  }).describe("One page of historical checks for the monitor.");
386
- var monitorCheckOutputSchema = z2.object({
2372
+ var monitorCheckOutputSchema = z4.object({
387
2373
  ...agentHints,
388
2374
  success,
389
2375
  error,
@@ -391,47 +2377,47 @@ var monitorCheckOutputSchema = z2.object({
391
2377
  pagination: unknown("Pagination metadata for the page results."),
392
2378
  next: str("URL of the next page of results, when more exist.")
393
2379
  }).describe("One monitor check with its page-level diff results.");
394
- var paperSchema = z2.looseObject({
2380
+ var paperSchema = z4.looseObject({
395
2381
  paperId: str("Canonical paper identifier."),
396
2382
  primaryId: str("Display identifier, ordered for citation and fetch use."),
397
2383
  ids: unknown("Source identifiers by namespace, such as `arxiv`, `doi`, or `pmid`."),
398
2384
  title: str("Paper title."),
399
2385
  abstract: str("Paper abstract."),
400
2386
  authors: unknown("Authors, as a comma-joined string or as `{name, affiliation}` entries."),
401
- categories: z2.array(z2.string()).nullable().optional().describe("Paper categories, such as `cs.LG`."),
2387
+ categories: z4.array(z4.string()).nullable().optional().describe("Paper categories, such as `cs.LG`."),
402
2388
  createdDate: str("Date the paper was first indexed or published."),
403
2389
  updateDate: str("Date the paper was last updated.")
404
2390
  });
405
- var researchSearchOutputSchema = z2.object({
2391
+ var researchSearchOutputSchema = z4.object({
406
2392
  ...agentHints,
407
- results: z2.array(paperSchema).describe("Ranked papers, in the order the text block lists them.")
2393
+ results: z4.array(paperSchema).describe("Ranked papers, in the order the text block lists them.")
408
2394
  }).describe("Ranked papers with canonical IDs, titles, authors, and abstracts.");
409
- var researchPaperOutputSchema = z2.object({
2395
+ var researchPaperOutputSchema = z4.object({
410
2396
  ...agentHints,
411
2397
  paper: paperSchema.optional().describe("Canonical metadata for the requested paper, absent when it was not found.")
412
2398
  }).describe("Canonical metadata for one paper.");
413
- var researchRelatedOutputSchema = z2.object({
2399
+ var researchRelatedOutputSchema = z4.object({
414
2400
  ...agentHints,
415
- results: z2.array(paperSchema).describe("Ranked citation-graph candidates."),
416
- poolSize: z2.number().describe("Number of candidates evaluated before ranking."),
2401
+ results: z4.array(paperSchema).describe("Ranked citation-graph candidates."),
2402
+ poolSize: z4.number().describe("Number of candidates evaluated before ranking."),
417
2403
  note: str("Note about how the candidates were produced.")
418
2404
  }).describe("Ranked citation-graph candidates and the size of the evaluated pool.");
419
- var researchReadOutputSchema = z2.object({
2405
+ var researchReadOutputSchema = z4.object({
420
2406
  ...agentHints,
421
- passages: z2.array(z2.looseObject({ text: str("Passage text.") })).describe("In-body passages relevant to the question; empty when no full text is indexed.")
2407
+ passages: z4.array(z4.looseObject({ text: str("Passage text.") })).describe("In-body passages relevant to the question; empty when no full text is indexed.")
422
2408
  }).describe("Full-text passages from one paper.");
423
- var developerSearchOutputSchema = z2.object({
2409
+ var developerSearchOutputSchema = z4.object({
424
2410
  ...agentHints,
425
- results: z2.array(
426
- z2.looseObject({
2411
+ results: z4.array(
2412
+ z4.looseObject({
427
2413
  id: str("Stable result id, such as `issue:owner/repo#123` or `doc:<hash>`; its prefix gives the source type."),
428
2414
  url: str("Source URL."),
429
2415
  title: str("Result title."),
430
- passages: z2.array(z2.looseObject({ text: str("Matched passage, in markdown.") })).optional().describe("Matched passages.")
2416
+ passages: z4.array(z4.looseObject({ text: str("Matched passage, in markdown.") })).optional().describe("Matched passages.")
431
2417
  })
432
2418
  ).describe("Ranked results, in the order the text block lists them.")
433
2419
  }).describe("Ranked developer-index results with their matched passages.");
434
- var creditUsageOutputSchema = z2.object({
2420
+ var creditUsageOutputSchema = z4.object({
435
2421
  // Current view.
436
2422
  remainingCredits: num("Credits left in the current billing period; can exceed `planCredits` when extra credits were purchased or granted."),
437
2423
  planCredits: num("Credits included in the plan for the period."),
@@ -444,11 +2430,11 @@ var creditUsageOutputSchema = z2.object({
444
2430
  }).describe("The team credit balance, or historical credit consumption by period.");
445
2431
 
446
2432
  // src/alexandria.ts
447
- import { z as z3 } from "zod";
2433
+ import { z as z5 } from "zod";
448
2434
  var catalogueTypes = ["alexandria", "exchange"];
449
- var searchSourceSchema = z3.union([
450
- z3.enum(["web", "images", "news", ...catalogueTypes]),
451
- z3.object({ type: z3.enum(["web", "images", "news", ...catalogueTypes]) }).strict()
2435
+ var searchSourceSchema = z5.union([
2436
+ z5.enum(["web", "images", "news", ...catalogueTypes]),
2437
+ z5.object({ type: z5.enum(["web", "images", "news", ...catalogueTypes]) }).strict()
452
2438
  ]);
453
2439
  function hasAlexandria(sources) {
454
2440
  return Array.isArray(sources) && sources.some(
@@ -471,19 +2457,19 @@ function normalizeSearchSources(sources) {
471
2457
  function searchQueryIsValid(args2) {
472
2458
  return !!args2.query?.trim();
473
2459
  }
474
- var findToolsSchema = z3.object({
475
- query: z3.string().trim().min(1).max(2e3).optional().describe("Semantic lookup of tools for the data you need. Selectors constrain the search."),
476
- urls: z3.array(
477
- z3.string().url().regex(/^https?:\/\//)
2460
+ var findToolsSchema = z5.object({
2461
+ query: z5.string().trim().min(1).max(2e3).optional().describe("Semantic lookup of tools for the data you need. Selectors constrain the search."),
2462
+ urls: z5.array(
2463
+ z5.string().url().regex(/^https?:\/\//)
478
2464
  ).min(1).max(100).optional(),
479
- providers: z3.array(z3.string().min(1)).min(1).max(50).optional().describe("Provider IDs returned by category browsing. Lists compact tools by default."),
480
- categories: z3.array(z3.string().min(1)).min(1).max(50).optional().describe("Category IDs returned by an empty call. Lists providers by default."),
481
- groups: z3.array(z3.string().min(1)).min(1).max(50).optional().describe("Optional group IDs for explicit group browsing."),
482
- capabilities: z3.array(z3.string().min(1)).min(1).max(50).optional().describe("Exact capability IDs. A selected capability expands its complete contract by default."),
483
- level: z3.enum(["categories", "providers", "groups", "tools"]).optional(),
484
- expand: z3.array(z3.enum(["options", "response", "examples"])).optional().describe('Use ["options", "response"] for inputs and output shape without example payloads; [] keeps results compact. Omit for full contracts on selected capabilities.'),
485
- limit: z3.number().int().min(1).max(100).optional(),
486
- offset: z3.number().int().nonnegative().optional()
2465
+ providers: z5.array(z5.string().min(1)).min(1).max(50).optional().describe("Provider IDs returned by category browsing. Lists compact tools by default."),
2466
+ categories: z5.array(z5.string().min(1)).min(1).max(50).optional().describe("Category IDs returned by an empty call. Lists providers by default."),
2467
+ groups: z5.array(z5.string().min(1)).min(1).max(50).optional().describe("Optional group IDs for explicit group browsing."),
2468
+ capabilities: z5.array(z5.string().min(1)).min(1).max(50).optional().describe("Exact capability IDs. A selected capability expands its complete contract by default."),
2469
+ level: z5.enum(["categories", "providers", "groups", "tools"]).optional(),
2470
+ expand: z5.array(z5.enum(["options", "response", "examples"])).optional().describe('Use ["options", "response"] for inputs and output shape without example payloads; [] keeps results compact. Omit for full contracts on selected capabilities.'),
2471
+ limit: z5.number().int().min(1).max(100).optional(),
2472
+ offset: z5.number().int().nonnegative().optional()
487
2473
  }).strict();
488
2474
  var ALEXANDRIA_CATALOGUE_VERTICALS = "companies, people, jobs, finance and filings, public records and government spending, real estate, places and restaurants, retail and prices, package registries and developer data, news, research, and more";
489
2475
  var ALEXANDRIA_CATALOGUE_SENTENCE = "Alexandria is Firecrawl's catalogue of data providers and workflows across " + ALEXANDRIA_CATALOGUE_VERTICALS + "; providers return typed, sourced records through published contracts.";
@@ -587,7 +2573,7 @@ async function alexandriaOutput(payload, calls, post, recoveryRequestId, extras
587
2573
  }
588
2574
 
589
2575
  // src/developer.ts
590
- import { z as z4 } from "zod";
2576
+ import { z as z6 } from "zod";
591
2577
 
592
2578
  // src/origin.ts
593
2579
  import { createRequire } from "module";
@@ -654,12 +2640,12 @@ Search an index of public repositories, GitHub issues, merged pull requests, rep
654
2640
  Returns ranked results with an ID, source type, URL, title, and the matched passages in markdown.
655
2641
  `,
656
2642
  outputSchema: developerSearchOutputSchema,
657
- parameters: z4.object({
658
- query: z4.string().min(1).describe(
2643
+ parameters: z6.object({
2644
+ query: z6.string().min(1).describe(
659
2645
  "Natural-language developer question or search phrase, including the library, error message, or API involved when relevant."
660
2646
  ),
661
- k: z4.number().int().min(1).max(100).optional().describe("Number of ranked results to return (default 10)."),
662
- skills: z4.enum(["only"]).optional().describe('Set to "only" to search only agent-skill files.')
2647
+ k: z6.number().int().min(1).max(100).optional().describe("Number of ranked results to return (default 10)."),
2648
+ skills: z6.enum(["only"]).optional().describe('Set to "only" to search only agent-skill files.')
663
2649
  }),
664
2650
  execute: async (args2, { session, client: mcpClient }) => {
665
2651
  const { query, k, skills } = args2;
@@ -722,7 +2708,7 @@ function checkKeylessSignupUrl(value) {
722
2708
  }
723
2709
 
724
2710
  // src/monitor.ts
725
- import { z as z5 } from "zod";
2711
+ import { z as z7 } from "zod";
726
2712
 
727
2713
  // src/session-credential.ts
728
2714
  import { createHmac } from "crypto";
@@ -856,8 +2842,8 @@ async function monitorRequest(session, origin, path2, init = {}) {
856
2842
  function monitorResult(data) {
857
2843
  return withAgentHints(structuredText(data), data);
858
2844
  }
859
- var pageStatusSchema = z5.enum(["same", "new", "changed", "removed", "error"]);
860
- var checkStatusSchema = z5.enum([
2845
+ var pageStatusSchema = z7.enum(["same", "new", "changed", "removed", "error"]);
2846
+ var checkStatusSchema = z7.enum([
861
2847
  "queued",
862
2848
  "running",
863
2849
  "completed",
@@ -951,22 +2937,22 @@ Create a recurring scrape, crawl, or search monitor that compares each check wit
951
2937
  In the simple form, a \`goal\` is required. If \`queries\` contains one or more non-empty values and is supplied with \`page\`/\`pages\`, \`queries\` create the search target and page targets are ignored. A monitor schedules future network checks and can send configured email or webhook notifications. Returns the created monitor.
952
2938
  `,
953
2939
  outputSchema: monitorOutputSchema,
954
- parameters: z5.object({
955
- body: z5.record(z5.string(), z5.any()).optional(),
956
- page: z5.string().optional(),
957
- pages: z5.array(z5.string()).optional(),
958
- queries: z5.array(z5.string()).optional(),
959
- searchWindow: z5.enum(["5m", "15m", "1h", "6h", "24h", "7d"]).optional(),
960
- maxResults: z5.number().int().min(1).max(50).optional(),
961
- includeDomains: z5.array(z5.string()).optional(),
962
- excludeDomains: z5.array(z5.string()).optional(),
963
- goal: z5.string().optional(),
964
- name: z5.string().optional(),
965
- scheduleText: z5.string().optional(),
966
- timezone: z5.string().optional(),
967
- email: z5.string().optional(),
968
- includeDiffs: z5.boolean().optional(),
969
- webhookUrl: z5.string().optional()
2940
+ parameters: z7.object({
2941
+ body: z7.record(z7.string(), z7.any()).optional(),
2942
+ page: z7.string().optional(),
2943
+ pages: z7.array(z7.string()).optional(),
2944
+ queries: z7.array(z7.string()).optional(),
2945
+ searchWindow: z7.enum(["5m", "15m", "1h", "6h", "24h", "7d"]).optional(),
2946
+ maxResults: z7.number().int().min(1).max(50).optional(),
2947
+ includeDomains: z7.array(z7.string()).optional(),
2948
+ excludeDomains: z7.array(z7.string()).optional(),
2949
+ goal: z7.string().optional(),
2950
+ name: z7.string().optional(),
2951
+ scheduleText: z7.string().optional(),
2952
+ timezone: z7.string().optional(),
2953
+ email: z7.string().optional(),
2954
+ includeDiffs: z7.boolean().optional(),
2955
+ webhookUrl: z7.string().optional()
970
2956
  }),
971
2957
  execute: async (args2, { session, log, client: mcpClient }) => {
972
2958
  const body = buildMonitorCreateBody(args2);
@@ -998,9 +2984,9 @@ In the simple form, a \`goal\` is required. If \`queries\` contains one or more
998
2984
  List monitors for the authenticated account with optional pagination controls. Returns one page of monitor records and pagination metadata.
999
2985
  `,
1000
2986
  outputSchema: monitorListOutputSchema,
1001
- parameters: z5.object({
1002
- limit: z5.number().int().positive().optional(),
1003
- offset: z5.number().int().nonnegative().optional()
2987
+ parameters: z7.object({
2988
+ limit: z7.number().int().positive().optional(),
2989
+ offset: z7.number().int().nonnegative().optional()
1004
2990
  }),
1005
2991
  execute: async (args2, { session, client: mcpClient }) => {
1006
2992
  const { limit, offset } = args2;
@@ -1030,7 +3016,7 @@ List monitors for the authenticated account with optional pagination controls. R
1030
3016
  Retrieve one monitor by ID, including its configuration and current state. This does not run or modify the monitor.
1031
3017
  `,
1032
3018
  outputSchema: monitorOutputSchema,
1033
- parameters: z5.object({ id: z5.string() }),
3019
+ parameters: z7.object({ id: z7.string() }),
1034
3020
  execute: async (args2, { session, client: mcpClient }) => {
1035
3021
  const { id } = args2;
1036
3022
  const res = await monitorRequest(
@@ -1058,9 +3044,9 @@ Patch an existing monitor by ID. The body can change its name, active/paused sta
1058
3044
  Returns the updated monitor.
1059
3045
  `,
1060
3046
  outputSchema: monitorOutputSchema,
1061
- parameters: z5.object({
1062
- id: z5.string(),
1063
- body: z5.record(z5.string(), z5.any())
3047
+ parameters: z7.object({
3048
+ id: z7.string(),
3049
+ body: z7.record(z7.string(), z7.any())
1064
3050
  }),
1065
3051
  execute: async (args2, { session, client: mcpClient }) => {
1066
3052
  const { id, body } = args2;
@@ -1088,7 +3074,7 @@ Returns the updated monitor.
1088
3074
  Permanently delete a monitor by ID and stop its future schedule. This operation cannot be undone and returns deletion status.
1089
3075
  `,
1090
3076
  outputSchema: monitorDeleteOutputSchema,
1091
- parameters: z5.object({ id: z5.string() }),
3077
+ parameters: z7.object({ id: z7.string() }),
1092
3078
  execute: async (args2, { session, log, client: mcpClient }) => {
1093
3079
  const { id } = args2;
1094
3080
  log.info("Deleting monitor", { id });
@@ -1116,7 +3102,7 @@ Permanently delete a monitor by ID and stop its future schedule. This operation
1116
3102
  Queue an immediate check for a monitor outside its normal schedule. This starts network work for the monitor's configured targets and returns the queued check.
1117
3103
  `,
1118
3104
  outputSchema: monitorRunOutputSchema,
1119
- parameters: z5.object({ id: z5.string() }),
3105
+ parameters: z7.object({ id: z7.string() }),
1120
3106
  execute: async (args2, { session, client: mcpClient }) => {
1121
3107
  const { id } = args2;
1122
3108
  const res = await monitorRequest(
@@ -1143,10 +3129,10 @@ Queue an immediate check for a monitor outside its normal schedule. This starts
1143
3129
  List historical checks for a monitor, optionally filtered by status and bounded by a result limit. Returns one page of check summaries and pagination metadata.
1144
3130
  `,
1145
3131
  outputSchema: monitorChecksOutputSchema,
1146
- parameters: z5.object({
1147
- id: z5.string(),
1148
- limit: z5.number().int().positive().optional(),
1149
- offset: z5.number().int().nonnegative().optional(),
3132
+ parameters: z7.object({
3133
+ id: z7.string(),
3134
+ limit: z7.number().int().positive().optional(),
3135
+ offset: z7.number().int().nonnegative().optional(),
1150
3136
  status: checkStatusSchema.optional()
1151
3137
  }),
1152
3138
  execute: async (args2, { session, client: mcpClient }) => {
@@ -1177,11 +3163,11 @@ Retrieve one monitor check and its page-level results, optionally filtered by pa
1177
3163
  Markdown tracking returns a unified text diff, JSON tracking returns field paths with previous/current values and a current snapshot, and mixed tracking returns both. Returns one page of results plus a \`next\` URL when more pages exist.
1178
3164
  `,
1179
3165
  outputSchema: monitorCheckOutputSchema,
1180
- parameters: z5.object({
1181
- id: z5.string(),
1182
- checkId: z5.string(),
1183
- limit: z5.number().int().positive().optional(),
1184
- skip: z5.number().int().nonnegative().optional(),
3166
+ parameters: z7.object({
3167
+ id: z7.string(),
3168
+ checkId: z7.string(),
3169
+ limit: z7.number().int().positive().optional(),
3170
+ skip: z7.number().int().nonnegative().optional(),
1185
3171
  pageStatus: pageStatusSchema.optional()
1186
3172
  }),
1187
3173
  execute: async (args2, { session, client: mcpClient }) => {
@@ -1198,8 +3184,7 @@ Markdown tracking returns a unified text diff, JSON tracking returns field paths
1198
3184
  }
1199
3185
 
1200
3186
  // src/research.ts
1201
- import { z as z6 } from "zod";
1202
- import { UserError } from "fastmcp";
3187
+ import { z as z8 } from "zod";
1203
3188
  var BASE2 = "/v2/search/research";
1204
3189
  function researchHeaders(origin) {
1205
3190
  return { ...originHeaders(origin), ...AGENT_HINTS_HEADERS };
@@ -1318,21 +3303,21 @@ Several distinct framings of the same question surface different papers than a s
1318
3303
  Returns ranked papers with canonical IDs, titles, authors, and abstracts.
1319
3304
  `,
1320
3305
  outputSchema: researchSearchOutputSchema,
1321
- parameters: z6.object({
1322
- query: z6.string().min(1).describe(
3306
+ parameters: z8.object({
3307
+ query: z8.string().min(1).describe(
1323
3308
  "Natural-language research topic or question, including methods, systems, conditions, populations, interventions, or outcomes when relevant."
1324
3309
  ),
1325
- k: z6.number().int().min(1).max(500).optional().describe("Number of ranked papers to return (default 40)."),
1326
- authors: z6.array(z6.string()).optional().describe(
3310
+ k: z8.number().int().min(1).max(500).optional().describe("Number of ranked papers to return (default 40)."),
3311
+ authors: z8.array(z8.string()).optional().describe(
1327
3312
  "Author substring filter(s); ALL must match (case-insensitive)."
1328
3313
  ),
1329
- categories: z6.array(z6.string()).optional().describe(
3314
+ categories: z8.array(z8.string()).optional().describe(
1330
3315
  "Paper category filter(s) (e.g. `cs.LG`); ALL provided values must match."
1331
3316
  ),
1332
- from: z6.string().optional().describe(
3317
+ from: z8.string().optional().describe(
1333
3318
  "Inclusive lower bound on created/updated date (`YYYY-MM-DD`)."
1334
3319
  ),
1335
- to: z6.string().optional().describe(
3320
+ to: z8.string().optional().describe(
1336
3321
  "Inclusive upper bound on created/updated date (`YYYY-MM-DD`)."
1337
3322
  )
1338
3323
  }),
@@ -1369,8 +3354,8 @@ Returns ranked papers with canonical IDs, titles, authors, and abstracts.
1369
3354
  Retrieve canonical metadata for one paper ID, such as an arXiv, PMC, PMID, or DOI identifier. Returns the title, abstract, authors, categories, source IDs, and dates as markdown.
1370
3355
  `,
1371
3356
  outputSchema: researchPaperOutputSchema,
1372
- parameters: z6.object({
1373
- paperId: z6.string().min(1).describe(
3357
+ parameters: z8.object({
3358
+ paperId: z8.string().min(1).describe(
1374
3359
  "Canonical paperId or primaryId such as `arxiv:1706.03762`, `pmcid:PMC12530322`, `pmid:40953549`, or `doi:10.1016/j.neunet.2025.108095`."
1375
3360
  )
1376
3361
  }),
@@ -1405,12 +3390,12 @@ Find citation-graph candidates from one to ten \`seed_ids\`; the first ID is the
1405
3390
  Returns ranked candidates and the evaluated pool size.
1406
3391
  `,
1407
3392
  outputSchema: researchRelatedOutputSchema,
1408
- parameters: z6.object({
1409
- seed_ids: z6.array(z6.string()).min(1).max(10),
1410
- intent: z6.string().min(1),
1411
- mode: z6.enum(["similar", "citers", "references"]).optional(),
1412
- k: z6.number().int().min(1).max(500).optional(),
1413
- rerank: z6.boolean().optional().describe("Apply an additional rerank over the fused candidates.")
3393
+ parameters: z8.object({
3394
+ seed_ids: z8.array(z8.string()).min(1).max(10),
3395
+ intent: z8.string().min(1),
3396
+ mode: z8.enum(["similar", "citers", "references"]).optional(),
3397
+ k: z8.number().int().min(1).max(500).optional(),
3398
+ rerank: z8.boolean().optional().describe("Apply an additional rerank over the fused candidates.")
1414
3399
  }),
1415
3400
  execute: async (args2, { session, client: mcpClient }) => {
1416
3401
  const { seed_ids, intent, mode, k, rerank } = args2;
@@ -1457,12 +3442,12 @@ Retrieve in-body passages from one paper that are relevant to a specific questio
1457
3442
  Returns matching passages or a notice when full text is unavailable.
1458
3443
  `,
1459
3444
  outputSchema: researchReadOutputSchema,
1460
- parameters: z6.object({
1461
- paperId: z6.string().min(1).describe(
3445
+ parameters: z8.object({
3446
+ paperId: z8.string().min(1).describe(
1462
3447
  "Canonical paperId or primaryId such as `arxiv:1706.03762`, `pmcid:PMC12530322`, `pmid:40953549`, or `doi:10.1016/j.neunet.2025.108095`."
1463
3448
  ),
1464
- question: z6.string().min(1),
1465
- k: z6.number().int().min(1).max(50).optional().describe("Number of passages to return (default 4).")
3449
+ question: z8.string().min(1),
3450
+ k: z8.number().int().min(1).max(50).optional().describe("Number of passages to return (default 4).")
1466
3451
  }),
1467
3452
  execute: async (args2, { session, client: mcpClient }) => {
1468
3453
  const { paperId, question, k } = args2;
@@ -1493,9 +3478,9 @@ Returns matching passages or a notice when full text is unavailable.
1493
3478
  Deprecated compatibility entry point. Use firecrawl_developer_search for GitHub issues, pull requests, and READMEs, plus code documentation, returned as matched passages.
1494
3479
  `,
1495
3480
  outputSchema: deprecatedToolOutputSchema,
1496
- parameters: z6.object({
1497
- query: z6.string().min(1),
1498
- k: z6.number().int().min(1).max(100).optional()
3481
+ parameters: z8.object({
3482
+ query: z8.string().min(1),
3483
+ k: z8.number().int().min(1).max(100).optional()
1499
3484
  }),
1500
3485
  canList: () => false,
1501
3486
  beforeValidate: () => {
@@ -1514,7 +3499,7 @@ Deprecated compatibility entry point. Use firecrawl_developer_search for GitHub
1514
3499
  }
1515
3500
 
1516
3501
  // src/usage.ts
1517
- import { z as z7 } from "zod";
3502
+ import { z as z9 } from "zod";
1518
3503
  function assertSuccessful(status, response, operation) {
1519
3504
  if (status === 200 && response.success) return;
1520
3505
  throw new Error(response.error || `Failed to ${operation}`);
@@ -1536,11 +3521,11 @@ The current view returns \`remainingCredits\`, \`planCredits\`, \`billingPeriodS
1536
3521
  The historical view returns periods sorted by start date with \`startDate\`, \`endDate\`, and \`creditsUsed\`. Set \`byApiKey\` to include separate periods per API key, identified by the optional \`apiKey\` field. The newest period's \`endDate\` can be null.
1537
3522
  `,
1538
3523
  outputSchema: creditUsageOutputSchema,
1539
- parameters: z7.object({
1540
- view: z7.enum(["current", "historical"]).optional().describe(
3524
+ parameters: z9.object({
3525
+ view: z9.enum(["current", "historical"]).optional().describe(
1541
3526
  "Select current balance or historical monthly usage. Defaults to current."
1542
3527
  ),
1543
- byApiKey: z7.boolean().optional().describe(
3528
+ byApiKey: z9.boolean().optional().describe(
1544
3529
  'Break historical usage down by API key. When view is omitted, true selects the historical view; it cannot be combined with view "current".'
1545
3530
  )
1546
3531
  }),
@@ -2163,30 +4148,30 @@ function removeEmptyTopLevel(obj) {
2163
4148
  }
2164
4149
  return out;
2165
4150
  }
2166
- var searchDomainSchema = z8.string().trim().toLowerCase().min(1).max(253).regex(
4151
+ var searchDomainSchema = z10.string().trim().toLowerCase().min(1).max(253).regex(
2167
4152
  /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/,
2168
4153
  "Domain must be a valid hostname without protocol or path"
2169
4154
  );
2170
4155
  var searchToolBaseFields = {
2171
- toolDetail: z8.enum(["compact", "summary", "full"]).optional().describe("Compact by default. Compact returns only provider, capability and description; full includes contracts. Inspect selected compact tools with firecrawl_find_tools providers and capabilities."),
2172
- query: z8.string().min(1).describe("Query for web and semantic tool discovery. Operators include quoted phrases, `-term`, `site:host`, `inurl:term`, `intitle:term`, and `related:host`; the set is non-exhaustive. Catalogue browsing is available through firecrawl_find_tools."),
2173
- domainTools: z8.boolean().optional().describe(
4156
+ toolDetail: z10.enum(["compact", "summary", "full"]).optional().describe("Compact by default. Compact returns only provider, capability and description; full includes contracts. Inspect selected compact tools with firecrawl_find_tools providers and capabilities."),
4157
+ query: z10.string().min(1).describe("Query for web and semantic tool discovery. Operators include quoted phrases, `-term`, `site:host`, `inurl:term`, `intitle:term`, and `related:host`; the set is non-exhaustive. Catalogue browsing is available through firecrawl_find_tools."),
4158
+ domainTools: z10.boolean().optional().describe(
2174
4159
  "Include domain-matched tools for result URLs. Defaults to true when Alexandria is combined with web, news or images; semantic-only search leaves domain matching off."
2175
4160
  ),
2176
- highlights: z8.boolean().optional().describe(
4161
+ highlights: z10.boolean().optional().describe(
2177
4162
  "Return query-relevant page excerpts for web and news results when available (default). Highlights appear in web `description` and news `snippet`; otherwise, original snippets are returned. Set to false to keep the original search snippets."
2178
4163
  ),
2179
- limit: z8.number().int().min(1).max(100).optional(),
2180
- tbs: z8.string().optional(),
2181
- filter: z8.string().optional(),
2182
- location: z8.string().optional(),
2183
- includeDomains: z8.array(searchDomainSchema).optional().describe("Hostnames to restrict results to. Mutually exclusive with excludeDomains."),
2184
- excludeDomains: z8.array(searchDomainSchema).optional().describe("Hostnames to leave out of results. Mutually exclusive with includeDomains."),
2185
- sources: z8.array(searchSourceSchema).optional().describe("Search sources; authenticated sessions default to web + alexandria, keyless sessions to web only. " + ALEXANDRIA_SOURCES_OPT_OUT + ' Use ["alexandria"] alone for provider discovery without web results.'),
2186
- categories: z8.array(z8.enum(["research", "pdf", "developer"])).optional().describe(
4164
+ limit: z10.number().int().min(1).max(100).optional(),
4165
+ tbs: z10.string().optional(),
4166
+ filter: z10.string().optional(),
4167
+ location: z10.string().optional(),
4168
+ includeDomains: z10.array(searchDomainSchema).optional().describe("Hostnames to restrict results to. Mutually exclusive with excludeDomains."),
4169
+ excludeDomains: z10.array(searchDomainSchema).optional().describe("Hostnames to leave out of results. Mutually exclusive with includeDomains."),
4170
+ sources: z10.array(searchSourceSchema).optional().describe("Search sources; authenticated sessions default to web + alexandria, keyless sessions to web only. " + ALEXANDRIA_SOURCES_OPT_OUT + ' Use ["alexandria"] alone for provider discovery without web results.'),
4171
+ categories: z10.array(z10.enum(["research", "pdf", "developer"])).optional().describe(
2187
4172
  'Limit results to specific source types. `research` restricts ordinary web results to research-affiliated websites and returns page snippets, which is separate from the `firecrawl_research_*` tools that search paper abstracts and full text across biomedical (PubMed, bioRxiv, medRxiv) and arXiv literature; `pdf` searches PDF results; `developer` searches an index built for coding agents over public repositories, GitHub issues, merged pull requests, repository READMEs, and code documentation. `developer` returns hits in `data.web` with `category: "developer"`; the other categories also filter `data.web`.'
2188
4173
  ),
2189
- enterprise: z8.array(z8.enum(["default", "anon", "zdr"])).optional()
4174
+ enterprise: z10.array(z10.enum(["default", "anon", "zdr"])).optional()
2190
4175
  };
2191
4176
  function searchDomainsAreExclusive(args2) {
2192
4177
  return !(args2.includeDomains?.length && args2.excludeDomains?.length);
@@ -2194,15 +4179,15 @@ function searchDomainsAreExclusive(args2) {
2194
4179
  var SEARCH_DOMAINS_CONFLICT_MESSAGE = "includeDomains and excludeDomains cannot both be specified";
2195
4180
  var EXCHANGE_KEY_REQUIRED_MESSAGE = "Alexandria requires an API key on a team with Alexandria access";
2196
4181
  var EXCHANGE_MAX_CALLS = 10;
2197
- var exchangeCallSchema = z8.object({
2198
- provider: z8.string().min(1).describe('Provider slug, e.g. "fred".'),
2199
- capability: z8.string().min(1).describe(
4182
+ var exchangeCallSchema = z10.object({
4183
+ provider: z10.string().min(1).describe('Provider slug, e.g. "fred".'),
4184
+ capability: z10.string().min(1).describe(
2200
4185
  'Capability address as returned by search or discover, e.g. "series/observations".'
2201
4186
  ),
2202
- version: z8.string().trim().min(1).max(128).optional().describe("Optional published workflow version. Omit to use the latest version."),
2203
- options: z8.record(z8.string(), z8.any()).optional().describe("Capability options as declared by its contract.")
4187
+ version: z10.string().trim().min(1).max(128).optional().describe("Optional published workflow version. Omit to use the latest version."),
4188
+ options: z10.record(z10.string(), z10.any()).optional().describe("Capability options as declared by its contract.")
2204
4189
  });
2205
- var exchangeCallsSchema = z8.array(exchangeCallSchema).min(1).max(EXCHANGE_MAX_CALLS);
4190
+ var exchangeCallsSchema = z10.array(exchangeCallSchema).min(1).max(EXCHANGE_MAX_CALLS);
2206
4191
  function assertExchangeCredential(session) {
2207
4192
  if (hasCredential(session)) return;
2208
4193
  const payload = {
@@ -2210,7 +4195,7 @@ function assertExchangeCredential(session) {
2210
4195
  message: EXCHANGE_KEY_REQUIRED_MESSAGE,
2211
4196
  docs_url: MCP_CONNECTION_GUIDE_URL
2212
4197
  };
2213
- throw new UserError2(payload.message, payload);
4198
+ throw new UserError(payload.message, payload);
2214
4199
  }
2215
4200
  var TERMS_REQUIRED_CODE = "THIRD_PARTY_DATA_TERMS_REQUIRED";
2216
4201
  function termsRequiredAction(body) {
@@ -2230,7 +4215,7 @@ function termsRequiredError(action, context, hints) {
2230
4215
  const requestId = context.requestId;
2231
4216
  const retryIdentity = requestId ? ` and requestId ${requestId}` : "";
2232
4217
  const message = `Alexandria provider terms required. An organization admin must accept the ${action.terms} provider's terms (version ${action.version}) before this request can run.`;
2233
- return new UserError2(
4218
+ return new UserError(
2234
4219
  `${message}
2235
4220
 
2236
4221
  1. Read the agreement with firecrawl_scrape using alexandria: {provider: "firecrawl", capability: "terms/show", options: {provider: "${action.terms}"}} and present it to the user. Only after explicit authorization to accept that exact version and digest, use firecrawl_scrape with alexandria: {provider: "firecrawl", capability: "terms/accept", options: {provider: "${action.terms}", version: "<reviewed-version>", digest: "<reviewed-digest>", confirmed: true}}. Send terms calls separately from provider execution. If authority or eligibility requires a dashboard action, ask an organization admin to visit ${action.url} or https://www.firecrawl.dev/app/settings?tab=data-sources if that page is unavailable. Never infer acceptance from a data request.
@@ -2295,7 +4280,7 @@ async function relayExchangeError(run, context) {
2295
4280
  const message = typeof data?.error === "string" ? data.error : `Alexandria request failed (HTTP ${response.status})`;
2296
4281
  const disabledProvider = response.status === 403 ? context.providers?.find((provider) => message === `Access to ${provider} is disabled for this organization.`) : void 0;
2297
4282
  if (disabledProvider) {
2298
- throw new UserError2(
4283
+ throw new UserError(
2299
4284
  `${message} Read the provider terms and status using nextTool and present them to the user. Never infer acceptance from a data request. Only after explicit authorization for the reviewed version and digest may you call firecrawl_scrape with alexandria:{provider:"firecrawl",capability:"terms/accept",options:{provider:"${disabledProvider}",version:"<reviewed-version>",digest:"<reviewed-digest>",confirmed:true}}. Send terms calls separately. Acceptance may not restore disabled access; an organization admin may need to review https://www.firecrawl.dev/app/settings?tab=data-sources. Retry the original request only after access is restored.`,
2300
4285
  {
2301
4286
  code: typeof data?.code === "string" ? data.code : "exchange_error",
@@ -2311,7 +4296,7 @@ async function relayExchangeError(run, context) {
2311
4296
  }
2312
4297
  );
2313
4298
  }
2314
- throw new UserError2(message, {
4299
+ throw new UserError(message, {
2315
4300
  code: typeof data?.code === "string" ? data.code : "exchange_error",
2316
4301
  status: response.status,
2317
4302
  message,
@@ -2548,12 +4533,12 @@ async function runWithCredentialRecovery(run, requestId, session) {
2548
4533
  const hints = readErrorAgentHints(error2);
2549
4534
  if (hints) {
2550
4535
  const message = error2 instanceof Error ? error2.message : String(error2);
2551
- throw new UserError2(
4536
+ throw new UserError(
2552
4537
  hints.length ? `${message}
2553
4538
 
2554
4539
  ${agentHintsText(hints)}` : message,
2555
4540
  {
2556
- ...error2 instanceof UserError2 ? error2.extras : {},
4541
+ ...error2 instanceof UserError ? error2.extras : {},
2557
4542
  error: message,
2558
4543
  agent_hints: hints
2559
4544
  }
@@ -2562,7 +4547,7 @@ ${agentHintsText(hints)}` : message,
2562
4547
  throw error2;
2563
4548
  }
2564
4549
  const payload = recoveryPayload("CREDENTIAL_INVALID", requestId);
2565
- throw new UserError2(String(payload.message), payload);
4550
+ throw new UserError(String(payload.message), payload);
2566
4551
  }
2567
4552
  }
2568
4553
  function recoveryPayload(code, requestId = randomUUID(), options = {}) {
@@ -2691,7 +4676,7 @@ function guardHostedTool(tool, { logActions }) {
2691
4676
  signupUrl: code === "KEYLESS_TOOL_NOT_AVAILABLE" ? await hostedKeylessSignupUrl(session) : void 0
2692
4677
  });
2693
4678
  if (logActions) {
2694
- emitActionLog(tool.name, "error", session, new UserError2(String(payload2.message), payload2), requestId, code);
4679
+ emitActionLog(tool.name, "error", session, new UserError(String(payload2.message), payload2), requestId, code);
2695
4680
  }
2696
4681
  return {
2697
4682
  content: [{ type: "text", text: String(payload2.message) }],
@@ -2707,7 +4692,7 @@ function guardHostedTool(tool, { logActions }) {
2707
4692
  tool.name,
2708
4693
  "error",
2709
4694
  session,
2710
- new UserError2(`Tool validation failed: ${recoveryCode}`, payload),
4695
+ new UserError(`Tool validation failed: ${recoveryCode}`, payload),
2711
4696
  randomUUID(),
2712
4697
  recoveryCode
2713
4698
  );
@@ -2728,16 +4713,16 @@ function guardHostedTool(tool, { logActions }) {
2728
4713
  if (invocationSession.credentialError) {
2729
4714
  const code = "CREDENTIAL_INVALID";
2730
4715
  const payload = recoveryPayload(code, requestId);
2731
- if (logActions) emitActionLog(tool.name, "error", invocationSession, new UserError2(String(payload.message), payload), requestId, code);
2732
- throw new UserError2(String(payload.message), payload);
4716
+ if (logActions) emitActionLog(tool.name, "error", invocationSession, new UserError(String(payload.message), payload), requestId, code);
4717
+ throw new UserError(String(payload.message), payload);
2733
4718
  }
2734
4719
  if (isHostedKeylessSession(invocationSession) && !keylessTool) {
2735
4720
  const code = "KEYLESS_TOOL_NOT_AVAILABLE";
2736
4721
  const payload = recoveryPayload(code, requestId, {
2737
4722
  signupUrl: await hostedKeylessSignupUrl(invocationSession)
2738
4723
  });
2739
- if (logActions) emitActionLog(tool.name, "error", invocationSession, new UserError2(String(payload.message), payload), requestId, code);
2740
- throw new UserError2(String(payload.message), payload);
4724
+ if (logActions) emitActionLog(tool.name, "error", invocationSession, new UserError(String(payload.message), payload), requestId, code);
4725
+ throw new UserError(String(payload.message), payload);
2741
4726
  }
2742
4727
  const runTool = async () => {
2743
4728
  try {
@@ -2969,10 +4954,10 @@ function transformScrapeParams(args2) {
2969
4954
  delete out.pdfOptions;
2970
4955
  return out;
2971
4956
  }
2972
- var scrapeParamsSchema = z8.object({
2973
- url: z8.string().url(),
2974
- formats: z8.array(
2975
- z8.enum([
4957
+ var scrapeParamsSchema = z10.object({
4958
+ url: z10.string().url(),
4959
+ formats: z10.array(
4960
+ z10.enum([
2976
4961
  "markdown",
2977
4962
  "html",
2978
4963
  "rawHtml",
@@ -2986,71 +4971,71 @@ var scrapeParamsSchema = z8.object({
2986
4971
  "audio"
2987
4972
  ])
2988
4973
  ).optional(),
2989
- jsonOptions: z8.object({
2990
- prompt: z8.string().optional(),
2991
- schema: z8.record(z8.string(), z8.any()).optional()
4974
+ jsonOptions: z10.object({
4975
+ prompt: z10.string().optional(),
4976
+ schema: z10.record(z10.string(), z10.any()).optional()
2992
4977
  }).optional(),
2993
- queryOptions: z8.object({
2994
- prompt: z8.string().max(1e4),
2995
- mode: z8.enum(["directQuote", "freeform"]).default("freeform")
4978
+ queryOptions: z10.object({
4979
+ prompt: z10.string().max(1e4),
4980
+ mode: z10.enum(["directQuote", "freeform"]).default("freeform")
2996
4981
  }).optional(),
2997
- screenshotOptions: z8.object({
2998
- fullPage: z8.boolean().optional(),
2999
- quality: z8.number().optional(),
3000
- viewport: z8.object({ width: z8.number(), height: z8.number() }).optional()
4982
+ screenshotOptions: z10.object({
4983
+ fullPage: z10.boolean().optional(),
4984
+ quality: z10.number().optional(),
4985
+ viewport: z10.object({ width: z10.number(), height: z10.number() }).optional()
3001
4986
  }).optional(),
3002
- parsers: z8.array(z8.enum(["pdf"])).optional(),
3003
- pdfOptions: z8.object({
3004
- maxPages: z8.number().int().min(1).max(1e4).optional()
4987
+ parsers: z10.array(z10.enum(["pdf"])).optional(),
4988
+ pdfOptions: z10.object({
4989
+ maxPages: z10.number().int().min(1).max(1e4).optional()
3005
4990
  }).optional(),
3006
- onlyMainContent: z8.boolean().optional(),
3007
- redactPII: z8.boolean().optional(),
3008
- includeTags: z8.array(z8.string()).optional(),
3009
- excludeTags: z8.array(z8.string()).optional(),
3010
- waitFor: z8.number().optional(),
4991
+ onlyMainContent: z10.boolean().optional(),
4992
+ redactPII: z10.boolean().optional(),
4993
+ includeTags: z10.array(z10.string()).optional(),
4994
+ excludeTags: z10.array(z10.string()).optional(),
4995
+ waitFor: z10.number().optional(),
3011
4996
  ...SAFE_MODE ? {} : {
3012
- actions: z8.array(
3013
- z8.object({
3014
- type: z8.enum(allowedActionTypes),
3015
- selector: z8.string().optional(),
3016
- milliseconds: z8.number().optional(),
3017
- text: z8.string().optional(),
3018
- key: z8.string().optional(),
3019
- direction: z8.enum(["up", "down"]).optional(),
3020
- script: z8.string().optional(),
3021
- fullPage: z8.boolean().optional()
4997
+ actions: z10.array(
4998
+ z10.object({
4999
+ type: z10.enum(allowedActionTypes),
5000
+ selector: z10.string().optional(),
5001
+ milliseconds: z10.number().optional(),
5002
+ text: z10.string().optional(),
5003
+ key: z10.string().optional(),
5004
+ direction: z10.enum(["up", "down"]).optional(),
5005
+ script: z10.string().optional(),
5006
+ fullPage: z10.boolean().optional()
3022
5007
  })
3023
5008
  ).optional()
3024
5009
  },
3025
- mobile: z8.boolean().optional(),
3026
- skipTlsVerification: z8.boolean().optional(),
3027
- removeBase64Images: z8.boolean().optional(),
3028
- location: z8.object({
3029
- country: z8.string().optional(),
3030
- languages: z8.array(z8.string()).optional()
5010
+ mobile: z10.boolean().optional(),
5011
+ skipTlsVerification: z10.boolean().optional(),
5012
+ removeBase64Images: z10.boolean().optional(),
5013
+ location: z10.object({
5014
+ country: z10.string().optional(),
5015
+ languages: z10.array(z10.string()).optional()
3031
5016
  }).optional(),
3032
- storeInCache: z8.boolean().optional(),
3033
- zeroDataRetention: z8.boolean().optional(),
3034
- maxAge: z8.number().optional(),
3035
- lockdown: z8.boolean().optional(),
3036
- proxy: z8.enum(["basic", "stealth", "enhanced", "auto"]).optional(),
3037
- profile: z8.object({
3038
- name: z8.string(),
3039
- saveChanges: z8.boolean().optional()
5017
+ storeInCache: z10.boolean().optional(),
5018
+ zeroDataRetention: z10.boolean().optional(),
5019
+ maxAge: z10.number().optional(),
5020
+ lockdown: z10.boolean().optional(),
5021
+ proxy: z10.enum(["basic", "stealth", "enhanced", "auto"]).optional(),
5022
+ profile: z10.object({
5023
+ name: z10.string(),
5024
+ saveChanges: z10.boolean().optional()
3040
5025
  }).optional()
3041
5026
  });
3042
5027
  var ALEXANDRIA_IGNORED_SCRAPE_OPTIONS = /* @__PURE__ */ new Set(["toolDetail", "domainTools"]);
3043
5028
  var scrapeToolParamsSchema = scrapeParamsSchema.extend({
3044
- url: z8.string().url().optional(),
3045
- timeout: z8.number().int().positive().optional().describe("Execution timeout in milliseconds."),
3046
- requestId: z8.string().regex(/^[A-Za-z0-9._:-]{1,128}$/).optional().describe(
5029
+ url: z10.string().url().optional(),
5030
+ timeout: z10.number().int().positive().optional().describe("Execution timeout in milliseconds."),
5031
+ requestId: z10.string().regex(/^[A-Za-z0-9._:-]{1,128}$/).optional().describe(
3047
5032
  "Idempotency key bound to one Alexandria execution payload. Generated when omitted and returned with the result."
3048
5033
  ),
3049
- alexandria: z8.union([exchangeCallSchema, exchangeCallsSchema]).optional().describe(
5034
+ alexandria: z10.union([exchangeCallSchema, exchangeCallsSchema]).optional().describe(
3050
5035
  "Catalogued Alexandria capability invocation, mutually exclusive with url. One {provider, capability, options} object or an array of 1-10, with contracts available through firecrawl_search or firecrawl_find_tools. Each call may include version to pin a published workflow; omitting it uses latest. Only requestId and timeout are supported alongside alexandria. " + ALEXANDRIA_CONTRACT_GUIDANCE + " Returns per-capability results in data.alexandria with data, records, or an error with a code; individual capabilities can fail even when the outer response succeeds. Requires an API key on a team with Alexandria enabled. Some providers require accepted terms; blocked requests return the applicable requirements."
3051
5036
  ),
3052
- toolDetail: z8.enum(["compact", "summary", "full"]).optional().describe("URL mode only: domain discovery detail, summary by default; compact returns provider/capability/description, full includes contracts. Ignored with alexandria."),
3053
- domainTools: z8.boolean().optional().describe(
5037
+ toolDetail: z10.enum(["compact", "summary", "full"]).optional().describe("URL mode only: domain discovery detail, summary by default; compact returns provider/capability/description, full includes contracts. Ignored with alexandria."),
5038
+ domainTools: z10.boolean().optional().describe(
3054
5039
  "URL mode only: include domain-matched Alexandria tools for the page in tools on the returned document. Ignored with alexandria."
3055
5040
  )
3056
5041
  }).refine(
@@ -3070,9 +5055,9 @@ var scrapeToolParamsSchema = scrapeParamsSchema.extend({
3070
5055
  (args2) => !args2.requestId || !!args2.alexandria,
3071
5056
  "requestId applies to Alexandria execution only"
3072
5057
  );
3073
- var parseOptionParamsSchema = z8.object({
3074
- formats: z8.array(
3075
- z8.enum([
5058
+ var parseOptionParamsSchema = z10.object({
5059
+ formats: z10.array(
5060
+ z10.enum([
3076
5061
  "markdown",
3077
5062
  "html",
3078
5063
  "rawHtml",
@@ -3082,48 +5067,48 @@ var parseOptionParamsSchema = z8.object({
3082
5067
  "query"
3083
5068
  ])
3084
5069
  ).optional(),
3085
- jsonOptions: z8.object({
3086
- prompt: z8.string().optional(),
3087
- schema: z8.record(z8.string(), z8.any()).optional()
5070
+ jsonOptions: z10.object({
5071
+ prompt: z10.string().optional(),
5072
+ schema: z10.record(z10.string(), z10.any()).optional()
3088
5073
  }).optional(),
3089
- queryOptions: z8.object({
3090
- prompt: z8.string().max(1e4),
3091
- mode: z8.enum(["directQuote", "freeform"]).default("freeform")
5074
+ queryOptions: z10.object({
5075
+ prompt: z10.string().max(1e4),
5076
+ mode: z10.enum(["directQuote", "freeform"]).default("freeform")
3092
5077
  }).optional(),
3093
- parsers: z8.array(z8.enum(["pdf"])).optional(),
3094
- pdfOptions: z8.object({
3095
- maxPages: z8.number().int().min(1).max(1e4).optional()
5078
+ parsers: z10.array(z10.enum(["pdf"])).optional(),
5079
+ pdfOptions: z10.object({
5080
+ maxPages: z10.number().int().min(1).max(1e4).optional()
3096
5081
  }).optional(),
3097
- onlyMainContent: z8.boolean().optional(),
3098
- redactPII: z8.boolean().optional(),
3099
- includeTags: z8.array(z8.string()).optional(),
3100
- excludeTags: z8.array(z8.string()).optional(),
3101
- removeBase64Images: z8.boolean().optional(),
3102
- skipTlsVerification: z8.boolean().optional(),
3103
- storeInCache: z8.boolean().optional(),
3104
- zeroDataRetention: z8.boolean().optional(),
3105
- maxAge: z8.number().optional().describe("Ignored: parse never reuses or stores indexed content."),
3106
- proxy: z8.enum(["basic", "auto"]).optional()
5082
+ onlyMainContent: z10.boolean().optional(),
5083
+ redactPII: z10.boolean().optional(),
5084
+ includeTags: z10.array(z10.string()).optional(),
5085
+ excludeTags: z10.array(z10.string()).optional(),
5086
+ removeBase64Images: z10.boolean().optional(),
5087
+ skipTlsVerification: z10.boolean().optional(),
5088
+ storeInCache: z10.boolean().optional(),
5089
+ zeroDataRetention: z10.boolean().optional(),
5090
+ maxAge: z10.number().optional().describe("Ignored: parse never reuses or stores indexed content."),
5091
+ proxy: z10.enum(["basic", "auto"]).optional()
3107
5092
  });
3108
5093
  var localParseParamsSchema = parseOptionParamsSchema.extend({
3109
- filePath: z8.string().min(1).describe(
5094
+ filePath: z10.string().min(1).describe(
3110
5095
  "Absolute or relative path to a local file to parse. Supported: .html, .htm, .pdf, .docx, .doc, .odt, .rtf, .xlsx, .xls"
3111
5096
  ),
3112
- contentType: z8.string().optional().describe(
5097
+ contentType: z10.string().optional().describe(
3113
5098
  "Optional MIME type override. If omitted, the server infers the file kind from the extension."
3114
5099
  )
3115
5100
  });
3116
5101
  var hostedParseParamsSchema = parseOptionParamsSchema.extend({
3117
- filePath: z8.string().min(1).optional().describe(
5102
+ filePath: z10.string().min(1).optional().describe(
3118
5103
  "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."
3119
5104
  ),
3120
- uploadRef: z8.string().min(1).optional().describe(
5105
+ uploadRef: z10.string().min(1).optional().describe(
3121
5106
  "Phase 2 only: short-lived upload reference returned by phase 1 after the local PUT upload completes."
3122
5107
  ),
3123
- contentType: z8.string().optional().describe(
5108
+ contentType: z10.string().optional().describe(
3124
5109
  "Phase 1 MIME type override. If omitted, the server infers it from the file extension without reading the file."
3125
5110
  ),
3126
- declaredSizeBytes: z8.number().int().positive().optional().describe(
5111
+ declaredSizeBytes: z10.number().int().positive().optional().describe(
3127
5112
  "Optional phase 1 size declaration. Hosted MCP does not stat the file; provide this only if the caller already knows it."
3128
5113
  )
3129
5114
  }).superRefine((value, ctx) => {
@@ -3131,7 +5116,7 @@ var hostedParseParamsSchema = parseOptionParamsSchema.extend({
3131
5116
  const hasUploadRef = typeof value.uploadRef === "string" && value.uploadRef.length > 0;
3132
5117
  if (hasFilePath === hasUploadRef) {
3133
5118
  ctx.addIssue({
3134
- code: z8.ZodIssueCode.custom,
5119
+ code: z10.ZodIssueCode.custom,
3135
5120
  message: "Hosted firecrawl_parse requires exactly one of filePath (phase 1) or uploadRef (phase 2).",
3136
5121
  path: hasFilePath && hasUploadRef ? ["uploadRef"] : ["filePath"]
3137
5122
  });
@@ -3266,7 +5251,7 @@ async function executeHostedParse(args2, session, log, origin) {
3266
5251
  option: "zeroDataRetention",
3267
5252
  message: "Zero Data Retention is not available in anonymous keyless mode. Omit zeroDataRetention to parse with keyless access, or configure an API key for a team where Zero Data Retention is enabled, then retry."
3268
5253
  };
3269
- throw new UserError2(String(payload.message), payload);
5254
+ throw new UserError(String(payload.message), payload);
3270
5255
  }
3271
5256
  const options = extractParseOptions(args2);
3272
5257
  if (hasFilePath && args2.filePath) {
@@ -3427,13 +5412,13 @@ Enumerate URLs indexed under one website through Firecrawl without fetching each
3427
5412
  Returns matching URLs rather than page bodies. Retrieve one page with \`firecrawl_scrape\`; collect content across multiple pages with \`firecrawl_crawl\`. Authenticated responses can include an \`id\` for optional map feedback.
3428
5413
  `,
3429
5414
  outputSchema: mapOutputSchema,
3430
- parameters: z8.object({
3431
- url: z8.string().url(),
3432
- search: z8.string().optional(),
3433
- sitemap: z8.enum(["include", "skip", "only"]).optional(),
3434
- includeSubdomains: z8.boolean().optional(),
3435
- limit: z8.number().optional(),
3436
- ignoreQueryParameters: z8.boolean().optional()
5415
+ parameters: z10.object({
5416
+ url: z10.string().url(),
5417
+ search: z10.string().optional(),
5418
+ sitemap: z10.enum(["include", "skip", "only"]).optional(),
5419
+ includeSubdomains: z10.boolean().optional(),
5420
+ limit: z10.number().optional(),
5421
+ ignoreQueryParameters: z10.boolean().optional()
3437
5422
  }),
3438
5423
  execute: async (args2, { session, log, client: mcpClient }) => {
3439
5424
  const { url, ...options } = args2;
@@ -3472,7 +5457,7 @@ On an authenticated session, tool matches describe available capabilities; \`fir
3472
5457
  For a programming question, add \`categories: ["developer"]\`; its hits return in \`data.web\` with \`category: "developer"\`. \`categories: ["research"]\` restricts web results to research-affiliated websites; the \`firecrawl_research_*\` tools are a separate surface over paper abstracts and full text (PubMed, bioRxiv, medRxiv, arXiv). Query operators, domain filters, \`categories\`, \`toolDetail\` and \`scrapeOptions\` are described on their parameters. Returns source-type result groups and usage metadata. Authenticated responses can include an \`id\` for optional search feedback.
3473
5458
  `,
3474
5459
  outputSchema: searchOutputSchema,
3475
- parameters: z8.object({
5460
+ parameters: z10.object({
3476
5461
  ...searchToolBaseFields,
3477
5462
  scrapeOptions: scrapeParamsSchema.omit({ url: true }).partial().optional().describe("Attach page content for web results in the same call. These fetches ignore maxAge, so use firecrawl_scrape when you need a live fetch. scrapeOptions fetches web pages, never Alexandria provider tools.")
3478
5463
  }).refine(searchDomainsAreExclusive, SEARCH_DOMAINS_CONFLICT_MESSAGE).refine(
@@ -3559,9 +5544,9 @@ async function executeExchangeCalls(session, alexandria, suppliedRequestId, time
3559
5544
  );
3560
5545
  } catch (error2) {
3561
5546
  if (error2?.response?.status === 401) throw error2;
3562
- throw new UserError2(
5547
+ throw new UserError(
3563
5548
  `${error2 instanceof Error ? error2.message : String(error2)} Request ID: ${requestId}. Reuse this ID only with the identical payload.`,
3564
- { ...error2 instanceof UserError2 ? error2.extras : {}, requestId }
5549
+ { ...error2 instanceof UserError ? error2.extras : {}, requestId }
3565
5550
  );
3566
5551
  }
3567
5552
  }
@@ -3583,7 +5568,7 @@ var findToolsTool = {
3583
5568
  try {
3584
5569
  options = findToolsOptions(args2);
3585
5570
  } catch (error2) {
3586
- throw new UserError2(error2 instanceof Error ? error2.message : String(error2));
5571
+ throw new UserError(error2 instanceof Error ? error2.message : String(error2));
3587
5572
  }
3588
5573
  if (options.level === "categories") {
3589
5574
  assertExchangeCredential(session);
@@ -3593,7 +5578,7 @@ var findToolsTool = {
3593
5578
  );
3594
5579
  const rows = response?.data?.cohorts;
3595
5580
  if (!Array.isArray(rows) || rows.some((row) => typeof row?.cohort !== "string" || typeof row?.about !== "string")) {
3596
- throw new UserError2("Category discovery returned an invalid category index.");
5581
+ throw new UserError("Category discovery returned an invalid category index.");
3597
5582
  }
3598
5583
  const offset = options.offset ?? 0;
3599
5584
  const items = rows.slice(offset, offset + options.limit).map((row) => ({
@@ -3695,7 +5680,7 @@ async function keylessPost(path2, body, session, originOverride) {
3695
5680
  retryAfterSeconds: eligibility.retryAfterSeconds,
3696
5681
  signupUrl: eligibility.signupUrl
3697
5682
  });
3698
- throw new UserError2(String(payload.message), payload);
5683
+ throw new UserError(String(payload.message), payload);
3699
5684
  }
3700
5685
  }
3701
5686
  const headers = {
@@ -3722,7 +5707,7 @@ async function keylessPost(path2, body, session, originOverride) {
3722
5707
  });
3723
5708
  const hints = readAgentHints(json);
3724
5709
  if (hints) payload.agent_hints = hints;
3725
- throw new UserError2(String(payload.message), payload);
5710
+ throw new UserError(String(payload.message), payload);
3726
5711
  }
3727
5712
  throw new CoreHttpError(
3728
5713
  json?.error || `Firecrawl request failed (HTTP ${response.status})`,
@@ -3792,17 +5777,17 @@ async function waitForCrawlCompletionWithOrigin(client, jobId, origin, pollInter
3792
5777
  );
3793
5778
  }
3794
5779
  }
3795
- var feedbackIssueSchema = z8.string().trim().min(1).max(80).regex(
5780
+ var feedbackIssueSchema = z10.string().trim().min(1).max(80).regex(
3796
5781
  /^[a-z0-9][a-z0-9_-]*$/,
3797
5782
  "Issue codes must use lowercase letters, numbers, underscores, or hyphens"
3798
5783
  );
3799
- var valuableSourceSchema = z8.object({
3800
- url: z8.string().url(),
3801
- reason: z8.string().max(1e3).optional()
5784
+ var valuableSourceSchema = z10.object({
5785
+ url: z10.string().url(),
5786
+ reason: z10.string().max(1e3).optional()
3802
5787
  });
3803
- var missingContentSchema = z8.object({
3804
- topic: z8.string().min(1, "topic must not be empty").max(200, "topic must be 200 characters or fewer"),
3805
- description: z8.string().max(2e3).optional()
5788
+ var missingContentSchema = z10.object({
5789
+ topic: z10.string().min(1, "topic must not be empty").max(200, "topic must be 200 characters or fewer"),
5790
+ description: z10.string().max(2e3).optional()
3806
5791
  });
3807
5792
  var FEEDBACK_DISABLED_VALUES = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
3808
5793
  function feedbackEnvEnabled(...keys) {
@@ -3841,24 +5826,24 @@ Records schema-validated quality feedback for a prior \`firecrawl_search\` UUID
3841
5826
  Eligibility is limited to successful searches within the feedback age window. The record is idempotent per search ID. Eligible first feedback for a search can refund 1 credit; refunds are subject to the team's daily cap. The response reports whether a refund was applied, along with submission and daily-cap status.
3842
5827
  `,
3843
5828
  outputSchema: feedbackOutputSchema,
3844
- parameters: z8.object({
3845
- searchId: z8.string().uuid("searchId must be the UUID returned by firecrawl_search"),
3846
- rating: z8.enum(["good", "bad", "partial"]),
3847
- valuableSources: z8.array(
3848
- z8.object({
3849
- url: z8.string().url(),
3850
- reason: z8.string().max(1e3).optional()
5829
+ parameters: z10.object({
5830
+ searchId: z10.string().uuid("searchId must be the UUID returned by firecrawl_search"),
5831
+ rating: z10.enum(["good", "bad", "partial"]),
5832
+ valuableSources: z10.array(
5833
+ z10.object({
5834
+ url: z10.string().url(),
5835
+ reason: z10.string().max(1e3).optional()
3851
5836
  })
3852
5837
  ).max(50).optional(),
3853
- missingContent: z8.array(
3854
- z8.object({
3855
- topic: z8.string().min(1, "topic must not be empty").max(200, "topic must be 200 characters or fewer"),
3856
- description: z8.string().max(2e3).optional()
5838
+ missingContent: z10.array(
5839
+ z10.object({
5840
+ topic: z10.string().min(1, "topic must not be empty").max(200, "topic must be 200 characters or fewer"),
5841
+ description: z10.string().max(2e3).optional()
3857
5842
  })
3858
5843
  ).max(20).optional().describe(
3859
5844
  "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`."
3860
5845
  ),
3861
- querySuggestions: z8.string().max(2e3).optional()
5846
+ querySuggestions: z10.string().max(2e3).optional()
3862
5847
  }),
3863
5848
  execute: async (args2, { session, log, client: mcpClient }) => {
3864
5849
  const origin = requestOrigin(mcpClient, session);
@@ -3960,20 +5945,20 @@ For an Alexandria session, set endpoint to \`alexandria\`, omit jobId, and provi
3960
5945
  Returns submission status, feedback ID, and accounting fields.
3961
5946
  `,
3962
5947
  outputSchema: feedbackOutputSchema,
3963
- parameters: z8.object({
3964
- endpoint: z8.enum(["search", "scrape", "parse", "map", "alexandria"]),
3965
- jobId: z8.string().uuid("jobId must be the UUID returned by Firecrawl").optional(),
5948
+ parameters: z10.object({
5949
+ endpoint: z10.enum(["search", "scrape", "parse", "map", "alexandria"]),
5950
+ jobId: z10.string().uuid("jobId must be the UUID returned by Firecrawl").optional(),
3966
5951
  ...alexandriaFeedbackFields,
3967
- rating: z8.enum(["good", "bad", "partial"]),
3968
- issues: z8.array(feedbackIssueSchema).max(20).optional(),
3969
- tags: z8.array(feedbackIssueSchema).max(20).optional(),
3970
- note: z8.string().max(4e3).optional(),
3971
- valuableSources: z8.array(valuableSourceSchema).max(50).optional(),
3972
- missingContent: z8.array(missingContentSchema).max(50).optional(),
3973
- querySuggestions: z8.string().max(2e3).optional(),
3974
- url: z8.string().url().optional(),
3975
- pageNumbers: z8.array(z8.number().int().positive()).max(100).optional(),
3976
- metadata: z8.record(z8.string(), z8.unknown()).optional()
5952
+ rating: z10.enum(["good", "bad", "partial"]),
5953
+ issues: z10.array(feedbackIssueSchema).max(20).optional(),
5954
+ tags: z10.array(feedbackIssueSchema).max(20).optional(),
5955
+ note: z10.string().max(4e3).optional(),
5956
+ valuableSources: z10.array(valuableSourceSchema).max(50).optional(),
5957
+ missingContent: z10.array(missingContentSchema).max(50).optional(),
5958
+ querySuggestions: z10.string().max(2e3).optional(),
5959
+ url: z10.string().url().optional(),
5960
+ pageNumbers: z10.array(z10.number().int().positive()).max(100).optional(),
5961
+ metadata: z10.record(z10.string(), z10.unknown()).optional()
3977
5962
  }).superRefine((value, ctx) => {
3978
5963
  if (value.endpoint === "alexandria") {
3979
5964
  const parsed = alexandriaSessionFeedbackSchema.safeParse(value);
@@ -4085,25 +6070,25 @@ Start a multi-page crawl at a website URL, poll it to a terminal state, and retu
4085
6070
 
4086
6071
  Crawl results can be large; use conservative limits when full-site coverage is unnecessary. Webhooks and interactive scrape actions are unavailable in safe mode. Returns the crawl ID, status, and page data.
4087
6072
  `,
4088
- parameters: z8.object({
4089
- url: z8.string(),
4090
- prompt: z8.string().optional(),
4091
- excludePaths: z8.array(z8.string()).optional(),
4092
- includePaths: z8.array(z8.string()).optional(),
4093
- maxDiscoveryDepth: z8.number().optional(),
4094
- sitemap: z8.enum(["skip", "include", "only"]).optional(),
4095
- limit: z8.number().optional(),
4096
- allowExternalLinks: z8.boolean().optional(),
4097
- allowSubdomains: z8.boolean().optional(),
4098
- crawlEntireDomain: z8.boolean().optional(),
4099
- delay: z8.number().optional(),
4100
- maxConcurrency: z8.number().optional(),
6073
+ parameters: z10.object({
6074
+ url: z10.string(),
6075
+ prompt: z10.string().optional(),
6076
+ excludePaths: z10.array(z10.string()).optional(),
6077
+ includePaths: z10.array(z10.string()).optional(),
6078
+ maxDiscoveryDepth: z10.number().optional(),
6079
+ sitemap: z10.enum(["skip", "include", "only"]).optional(),
6080
+ limit: z10.number().optional(),
6081
+ allowExternalLinks: z10.boolean().optional(),
6082
+ allowSubdomains: z10.boolean().optional(),
6083
+ crawlEntireDomain: z10.boolean().optional(),
6084
+ delay: z10.number().optional(),
6085
+ maxConcurrency: z10.number().optional(),
4101
6086
  ...SAFE_MODE ? {} : {
4102
- webhook: z8.string().optional(),
4103
- webhookHeaders: z8.record(z8.string(), z8.string()).optional()
6087
+ webhook: z10.string().optional(),
6088
+ webhookHeaders: z10.record(z10.string(), z10.string()).optional()
4104
6089
  },
4105
- deduplicateSimilarURLs: z8.boolean().optional(),
4106
- ignoreQueryParameters: z8.boolean().optional(),
6090
+ deduplicateSimilarURLs: z10.boolean().optional(),
6091
+ ignoreQueryParameters: z10.boolean().optional(),
4107
6092
  scrapeOptions: scrapeParamsSchema.omit({ url: true }).partial().optional()
4108
6093
  }),
4109
6094
  outputSchema: crawlOutputSchema,
@@ -4160,7 +6145,7 @@ server.addTool({
4160
6145
  Retrieve the current status, progress, and available results for an existing crawl ID. This only reads Firecrawl job state and does not start or modify the crawl.
4161
6146
  `,
4162
6147
  outputSchema: crawlOutputSchema,
4163
- parameters: z8.object({ id: z8.string() }),
6148
+ parameters: z10.object({ id: z10.string() }),
4164
6149
  execute: async (args2, {
4165
6150
  session,
4166
6151
  client: mcpClient
@@ -4187,13 +6172,13 @@ server.addTool({
4187
6172
  Deprecated compatibility entry point. Use firecrawl_scrape once per known URL with formats: ["json"] and jsonOptions containing the prompt and schema. Use firecrawl_agent for multi-source research when the URLs are not known or the data spans several sites.
4188
6173
  `,
4189
6174
  outputSchema: deprecatedToolOutputSchema,
4190
- parameters: z8.object({
4191
- urls: z8.array(z8.string()),
4192
- prompt: z8.string().optional(),
4193
- schema: z8.record(z8.string(), z8.any()).optional(),
4194
- allowExternalLinks: z8.boolean().optional(),
4195
- enableWebSearch: z8.boolean().optional(),
4196
- includeSubdomains: z8.boolean().optional()
6175
+ parameters: z10.object({
6176
+ urls: z10.array(z10.string()),
6177
+ prompt: z10.string().optional(),
6178
+ schema: z10.record(z10.string(), z10.any()).optional(),
6179
+ allowExternalLinks: z10.boolean().optional(),
6180
+ enableWebSearch: z10.boolean().optional(),
6181
+ includeSubdomains: z10.boolean().optional()
4197
6182
  }),
4198
6183
  canList: () => false,
4199
6184
  beforeValidate: () => {
@@ -4206,27 +6191,27 @@ Deprecated compatibility entry point. Use firecrawl_scrape once per known URL wi
4206
6191
  },
4207
6192
  execute: async () => {
4208
6193
  const payload = deprecatedExtractPayload();
4209
- throw new UserError2(payload.message, payload);
6194
+ throw new UserError(payload.message, payload);
4210
6195
  }
4211
6196
  });
4212
- var agentExchangeSchema = z8.strictObject({
4213
- enabled: z8.boolean().optional().describe("Let the agent call Alexandria providers. On by default."),
4214
- toolkits: z8.array(z8.string()).max(5).optional().describe("Pin up to 5 providers the agent may use, by slug. Omitted means the whole catalog."),
4215
- maxCalls: z8.number().int().min(1).max(30).optional().describe("Most provider calls the agent may make in this turn."),
4216
- requireApproval: z8.boolean().optional().describe(
6197
+ var agentExchangeSchema = z10.strictObject({
6198
+ enabled: z10.boolean().optional().describe("Let the agent call Alexandria providers. On by default."),
6199
+ toolkits: z10.array(z10.string()).max(5).optional().describe("Pin up to 5 providers the agent may use, by slug. Omitted means the whole catalog."),
6200
+ maxCalls: z10.number().int().min(1).max(30).optional().describe("Most provider calls the agent may make in this turn."),
6201
+ requireApproval: z10.boolean().optional().describe(
4217
6202
  'End the turn with a paid-call pendingApproval before any paid provider call. Requires mode "chat" on the same request, even on a follow-up.'
4218
6203
  ),
4219
- approve: z8.strictObject({
4220
- approvalId: z8.string().uuid(),
4221
- callIds: z8.array(z8.string()).optional(),
4222
- always: z8.boolean().optional()
6204
+ approve: z10.strictObject({
6205
+ approvalId: z10.string().uuid(),
6206
+ callIds: z10.array(z10.string()).optional(),
6207
+ always: z10.boolean().optional()
4223
6208
  }).optional().describe(
4224
6209
  "Answer yes to the pendingApproval the previous turn of this thread ended on (its id, also exchange.requiresAction.approvalId). Needs threadId. For a terms offer, send it only after the user explicitly agreed and terms/accept succeeded; callIds and always are ignored on terms offers. For paid calls, callIds picks a subset (default all) and always stops asking for the rest of the thread."
4225
6210
  ),
4226
- decline: z8.strictObject({ approvalId: z8.string().uuid() }).optional().describe(
6211
+ decline: z10.strictObject({ approvalId: z10.string().uuid() }).optional().describe(
4227
6212
  "Answer no to that pendingApproval. Needs threadId. A declined terms offer keeps those providers out of the rest of the thread."
4228
6213
  ),
4229
- onTermsRequired: z8.enum(["skip", "ask"]).optional().describe(
6214
+ onTermsRequired: z10.enum(["skip", "ask"]).optional().describe(
4230
6215
  `What to do when a provider the agent would use needs data terms the team has not accepted. Gated providers are never called. "skip" (default): answer with accepted providers and list the rest in exchange.skippedProviders. "ask": the same, plus a terms pendingApproval and exchange.requiresAction with the terms/show and terms/accept calls. Each provider digest is string | null and always present; when null, terms/show returns it. There is no auto-accept. Omitted on a follow-up keeps the previous turn's value.`
4231
6216
  )
4232
6217
  }).describe(
@@ -4253,21 +6238,21 @@ The job also returns a \`threadId\`. To continue that thread, pass it with a fol
4253
6238
  The agent only calls Alexandria providers whose data terms the team has accepted. The status result's \`exchange.skippedProviders\` lists gated providers that would have helped. With \`exchange.onTermsRequired\` "ask", a terms offer ends the turn: \`pendingApproval\` (kind "terms") and \`exchange.requiresAction\` carry the \`approvalId\` and the exact terms/show and terms/accept calls. Show the user the terms, get their EXPLICIT consent, run terms/accept through \`firecrawl_scrape\`, then call \`firecrawl_agent\` with the same \`threadId\` and \`exchange.approve: {approvalId}\`. If they decline, send \`exchange.decline: {approvalId}\` instead. Never call terms/accept without that consent; a data request is not consent.
4254
6239
  `,
4255
6240
  outputSchema: agentOutputSchema,
4256
- parameters: z8.object({
4257
- prompt: z8.string().min(1).max(1e4),
4258
- urls: z8.array(z8.string().url()).optional(),
4259
- schema: z8.record(z8.string(), z8.any()).optional(),
4260
- effort: z8.enum(["low", "medium", "high"]).optional().describe("Reasoning budget for the agent task."),
4261
- maxCredits: z8.number().int().positive().optional().describe(
6241
+ parameters: z10.object({
6242
+ prompt: z10.string().min(1).max(1e4),
6243
+ urls: z10.array(z10.string().url()).optional(),
6244
+ schema: z10.record(z10.string(), z10.any()).optional(),
6245
+ effort: z10.enum(["low", "medium", "high"]).optional().describe("Reasoning budget for the agent task."),
6246
+ maxCredits: z10.number().int().positive().optional().describe(
4262
6247
  "Spending limit in credits for this run. Defaults to 2500 on the API."
4263
6248
  ),
4264
- strictConstrainToURLs: z8.boolean().optional().describe(
6249
+ strictConstrainToURLs: z10.boolean().optional().describe(
4265
6250
  "If true, agent will only visit URLs provided in the urls array."
4266
6251
  ),
4267
- threadId: z8.string().uuid().optional().describe(
6252
+ threadId: z10.string().uuid().optional().describe(
4268
6253
  "Continue this thread: the threadId from an earlier firecrawl_agent or firecrawl_agent_status result. Omit to start a new thread."
4269
6254
  ),
4270
- mode: z8.enum(["extract", "chat"]).optional().describe(
6255
+ mode: z10.enum(["extract", "chat"]).optional().describe(
4271
6256
  `"extract" (default) returns the complete structured result every turn. "chat" lets a follow-up that asks no new data get a short reply in message instead of a re-run; required for exchange.requireApproval. Omitted on a follow-up keeps the previous turn's mode.`
4272
6257
  ),
4273
6258
  exchange: agentExchangeSchema.optional()
@@ -4337,7 +6322,7 @@ Retrieve progress or final results for a \`firecrawl_agent\` job ID. A \`process
4337
6322
  Returns job status, progress information, and result data when completed.
4338
6323
  `,
4339
6324
  outputSchema: agentStatusOutputSchema,
4340
- parameters: z8.object({ id: z8.string() }),
6325
+ parameters: z10.object({ id: z10.string() }),
4341
6326
  execute: async (args2, { session, log, client: mcpClient }) => {
4342
6327
  const client = getClient(session);
4343
6328
  const { id } = args2;
@@ -4366,13 +6351,13 @@ Open or reuse a live browser session to navigate a page, click controls, fill fi
4366
6351
  This acts on the live site, so actions such as form submission can create persistent external side effects. Returns execution output, stdout/stderr, exit status, and session viewing URLs.
4367
6352
  `,
4368
6353
  outputSchema: interactOutputSchema,
4369
- parameters: z8.object({
4370
- scrapeId: z8.string().trim().min(1).optional(),
4371
- url: z8.string().trim().url().optional(),
4372
- prompt: z8.string().trim().min(1).optional(),
4373
- code: z8.string().trim().min(1).optional(),
4374
- language: z8.enum(["bash", "python", "node"]).optional(),
4375
- timeout: z8.number().min(1).max(300).optional(),
6354
+ parameters: z10.object({
6355
+ scrapeId: z10.string().trim().min(1).optional(),
6356
+ url: z10.string().trim().url().optional(),
6357
+ prompt: z10.string().trim().min(1).optional(),
6358
+ code: z10.string().trim().min(1).optional(),
6359
+ language: z10.enum(["bash", "python", "node"]).optional(),
6360
+ timeout: z10.number().min(1).max(300).optional(),
4376
6361
  scrapeOptions: scrapeParamsSchema.omit({ url: true }).partial().optional()
4377
6362
  }).refine((data) => Boolean(data.scrapeId) !== Boolean(data.url), {
4378
6363
  message: "Provide either 'url' (interact directly) or 'scrapeId' (reuse a previous scrape), not both."
@@ -4451,8 +6436,8 @@ server.addTool({
4451
6436
  Stop the live interact session associated with a \`scrapeId\` and release its resources. Returns a success confirmation.
4452
6437
  `,
4453
6438
  outputSchema: interactStopOutputSchema,
4454
- parameters: z8.object({
4455
- scrapeId: z8.string()
6439
+ parameters: z10.object({
6440
+ scrapeId: z10.string()
4456
6441
  }),
4457
6442
  execute: async (args2, { session, log, client: mcpClient }) => {
4458
6443
  const client = getClient(session);
@@ -4576,7 +6561,7 @@ ${ALEXANDRIA_SEARCH_INSTRUCTIONS}
4576
6561
  Returns result groups in \`data\` and an operation \`id\`.
4577
6562
  `,
4578
6563
  outputSchema: searchOutputSchema,
4579
- parameters: z8.object({ ...searchToolBaseFields }).strict().refine(searchDomainsAreExclusive, SEARCH_DOMAINS_CONFLICT_MESSAGE).refine(
6564
+ parameters: z10.object({ ...searchToolBaseFields }).strict().refine(searchDomainsAreExclusive, SEARCH_DOMAINS_CONFLICT_MESSAGE).refine(
4580
6565
  searchQueryIsValid,
4581
6566
  "A query is required. Use firecrawl_find_tools to browse the catalogue."
4582
6567
  ),