firecrawl-mcp 3.27.0 → 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.
- package/README.md +3 -0
- package/dist/index.js +2337 -349
- package/package.json +8 -1
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ var alexandriaFeedbackFields = {
|
|
|
10
10
|
requestedFunctionality: detail
|
|
11
11
|
}).optional(),
|
|
12
12
|
rationale: detail.optional(),
|
|
13
|
+
objective: detail.optional(),
|
|
13
14
|
providerFeedback: z.array(
|
|
14
15
|
z.strictObject({
|
|
15
16
|
name,
|
|
@@ -55,6 +56,7 @@ var ALEXANDRIA_FEEDBACK_HINT = {
|
|
|
55
56
|
url: "<website the user needed data from>",
|
|
56
57
|
requestedFunctionality: "<what they needed from it>"
|
|
57
58
|
},
|
|
59
|
+
objective: "<underlying goal of the task>",
|
|
58
60
|
rationale: "<why this rating, from observed results>",
|
|
59
61
|
providerFeedback: "<optional [{name, issue, why}]: providers that were missing, thin, or unavailable>",
|
|
60
62
|
capabilityFeedback: "<optional [{name, provider, issue, why, requestedFunctionality?}]: capabilities that were missing, wrong, or failed>"
|
|
@@ -74,18 +76,2005 @@ var alexandriaSessionFeedbackSchema = z.strictObject({
|
|
|
74
76
|
rating: z.enum(["good", "bad", "partial"]),
|
|
75
77
|
...alexandriaFeedbackFields,
|
|
76
78
|
requestedWebsite: alexandriaFeedbackFields.requestedWebsite.unwrap(),
|
|
77
|
-
rationale: detail
|
|
79
|
+
rationale: detail,
|
|
80
|
+
objective: detail
|
|
78
81
|
});
|
|
79
82
|
|
|
80
83
|
// src/index.ts
|
|
81
84
|
import FirecrawlApp from "firecrawl";
|
|
82
85
|
import dotenv from "dotenv";
|
|
83
|
-
|
|
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
|
|
84
2073
|
import { readFile } from "fs/promises";
|
|
85
2074
|
import { createRequire as createRequire2 } from "module";
|
|
86
2075
|
import { randomUUID } from "crypto";
|
|
87
2076
|
import path from "path";
|
|
88
|
-
import { z as
|
|
2077
|
+
import { z as z10 } from "zod";
|
|
89
2078
|
|
|
90
2079
|
// src/agent-hints.ts
|
|
91
2080
|
var AGENT_HINTS_HEADERS = { "X-Firecrawl-Agent-Hints": "true" };
|
|
@@ -127,7 +2116,7 @@ function readErrorAgentHints(error2) {
|
|
|
127
2116
|
}
|
|
128
2117
|
|
|
129
2118
|
// src/tool-output.ts
|
|
130
|
-
import { z as
|
|
2119
|
+
import { z as z4 } from "zod";
|
|
131
2120
|
function isRecord(value) {
|
|
132
2121
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
133
2122
|
}
|
|
@@ -147,15 +2136,15 @@ function structuredJsonText(text) {
|
|
|
147
2136
|
return { content: [{ type: "text", text }] };
|
|
148
2137
|
}
|
|
149
2138
|
}
|
|
150
|
-
var str = (description) =>
|
|
151
|
-
var num = (description) =>
|
|
152
|
-
var bool = (description) =>
|
|
153
|
-
var unknown = (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);
|
|
154
2143
|
var success = bool("Whether the API call succeeded.");
|
|
155
2144
|
var error = unknown("Error message or error object when the call did not succeed.");
|
|
156
2145
|
var warning = str("Non-fatal warning about the result.");
|
|
157
2146
|
var agentHints = {
|
|
158
|
-
agent_hints:
|
|
2147
|
+
agent_hints: z4.array(z4.string()).optional().describe("Optional response guidance from the Firecrawl API.")
|
|
159
2148
|
};
|
|
160
2149
|
var alexandriaEnvelope = {
|
|
161
2150
|
requestId: str("Identifier of this Alexandria execution."),
|
|
@@ -173,7 +2162,7 @@ var alexandriaEnvelope = {
|
|
|
173
2162
|
nextTool: unknown("A follow-up tool call (`{name, arguments}`) that continues or inspects this result."),
|
|
174
2163
|
feedbackTool: unknown("Pointer to the feedback tool for reporting how this result served the task.")
|
|
175
2164
|
};
|
|
176
|
-
var scrapeOutputSchema =
|
|
2165
|
+
var scrapeOutputSchema = z4.object({
|
|
177
2166
|
...agentHints,
|
|
178
2167
|
// URL mode: the scraped document, with the formats that were requested.
|
|
179
2168
|
markdown: str("Page content as markdown."),
|
|
@@ -205,7 +2194,7 @@ var scrapeOutputSchema = z2.object({
|
|
|
205
2194
|
data: unknown("Alexandria mode: per-capability results in `data.alexandria`, each with `data`, `records`, or an `error`."),
|
|
206
2195
|
...alexandriaEnvelope
|
|
207
2196
|
}).describe("A scraped document, or the Alexandria execution envelope when `alexandria` was passed.");
|
|
208
|
-
var mapOutputSchema =
|
|
2197
|
+
var mapOutputSchema = z4.object({
|
|
209
2198
|
...agentHints,
|
|
210
2199
|
links: unknown("URLs discovered under the website."),
|
|
211
2200
|
success,
|
|
@@ -213,7 +2202,7 @@ var mapOutputSchema = z2.object({
|
|
|
213
2202
|
warning,
|
|
214
2203
|
id: str("Identifier of the map run, for optional map feedback.")
|
|
215
2204
|
}).describe("URLs indexed under the requested website.");
|
|
216
|
-
var searchOutputSchema =
|
|
2205
|
+
var searchOutputSchema = z4.object({
|
|
217
2206
|
...agentHints,
|
|
218
2207
|
success,
|
|
219
2208
|
data: unknown("Ranked results grouped by source, such as `web`, `news`, `images`, and `alexandria`."),
|
|
@@ -225,14 +2214,14 @@ var searchOutputSchema = z2.object({
|
|
|
225
2214
|
nextTool: unknown("A follow-up tool call that continues this search."),
|
|
226
2215
|
feedbackTool: unknown("Pointer to the feedback tool for this search.")
|
|
227
2216
|
}).describe("Ranked search results grouped by source.");
|
|
228
|
-
var findToolsOutputSchema =
|
|
2217
|
+
var findToolsOutputSchema = z4.object({
|
|
229
2218
|
...agentHints,
|
|
230
2219
|
success,
|
|
231
2220
|
data: unknown("Discovery page in `data.alexandria[0].data`, with `level`, `items`, `total`, and an optional `nextTool`."),
|
|
232
2221
|
error,
|
|
233
2222
|
...alexandriaEnvelope
|
|
234
2223
|
}).describe("One page of the Alexandria catalogue: categories, providers, tools, or one contract.");
|
|
235
|
-
var feedbackOutputSchema =
|
|
2224
|
+
var feedbackOutputSchema = z4.object({
|
|
236
2225
|
...agentHints,
|
|
237
2226
|
success,
|
|
238
2227
|
error,
|
|
@@ -250,7 +2239,7 @@ var feedbackOutputSchema = z2.object({
|
|
|
250
2239
|
data: unknown("Payload returned with the accepted feedback."),
|
|
251
2240
|
raw: str("Response body when it was not JSON.")
|
|
252
2241
|
}).describe("Result of submitting feedback.");
|
|
253
|
-
var crawlOutputSchema =
|
|
2242
|
+
var crawlOutputSchema = z4.object({
|
|
254
2243
|
...agentHints,
|
|
255
2244
|
success,
|
|
256
2245
|
error,
|
|
@@ -265,7 +2254,7 @@ var crawlOutputSchema = z2.object({
|
|
|
265
2254
|
url: str("The URL the crawl started from."),
|
|
266
2255
|
data: unknown("Scraped documents collected by the crawl.")
|
|
267
2256
|
}).describe("Crawl job state and the documents collected so far.");
|
|
268
|
-
var agentOutputSchema =
|
|
2257
|
+
var agentOutputSchema = z4.object({
|
|
269
2258
|
...agentHints,
|
|
270
2259
|
success,
|
|
271
2260
|
error,
|
|
@@ -274,7 +2263,7 @@ var agentOutputSchema = z2.object({
|
|
|
274
2263
|
threadId: str("Research thread this job belongs to."),
|
|
275
2264
|
threadTurn: num("Turn number of this job within its thread.")
|
|
276
2265
|
}).describe("The queued research job. The result is read with `firecrawl_agent_status`.");
|
|
277
|
-
var agentStatusOutputSchema =
|
|
2266
|
+
var agentStatusOutputSchema = z4.object({
|
|
278
2267
|
...agentHints,
|
|
279
2268
|
success,
|
|
280
2269
|
error,
|
|
@@ -298,7 +2287,7 @@ var agentStatusOutputSchema = z2.object({
|
|
|
298
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.'
|
|
299
2288
|
)
|
|
300
2289
|
}).describe("Progress or final result of a research agent job.");
|
|
301
|
-
var interactOutputSchema =
|
|
2290
|
+
var interactOutputSchema = z4.object({
|
|
302
2291
|
...agentHints,
|
|
303
2292
|
success,
|
|
304
2293
|
error: unknown("Why the interaction or the session could not run."),
|
|
@@ -313,14 +2302,14 @@ var interactOutputSchema = z2.object({
|
|
|
313
2302
|
sessionUrl: str("URL for viewing the live session."),
|
|
314
2303
|
liveUrl: str("URL for viewing the live session.")
|
|
315
2304
|
}).describe("Result of a browser interaction, with the session identifier to continue or stop it.");
|
|
316
|
-
var interactStopOutputSchema =
|
|
2305
|
+
var interactStopOutputSchema = z4.object({
|
|
317
2306
|
...agentHints,
|
|
318
2307
|
success,
|
|
319
2308
|
error,
|
|
320
2309
|
message: str("Confirmation that the session was stopped."),
|
|
321
2310
|
scrapeId: str("Session that was stopped.")
|
|
322
2311
|
}).describe("Confirmation that the interact session was stopped.");
|
|
323
|
-
var parseOutputSchema =
|
|
2312
|
+
var parseOutputSchema = z4.object({
|
|
324
2313
|
...agentHints,
|
|
325
2314
|
success,
|
|
326
2315
|
error,
|
|
@@ -328,7 +2317,7 @@ var parseOutputSchema = z2.object({
|
|
|
328
2317
|
mode: str("Which phase of the hosted flow produced this response."),
|
|
329
2318
|
message: str("Guidance for the next call."),
|
|
330
2319
|
data: unknown("Parsed document content; can include `data.metadata.scrapeId` for parse feedback."),
|
|
331
|
-
upload:
|
|
2320
|
+
upload: z4.object({
|
|
332
2321
|
command: str("Local command that performs the upload. It carries no Firecrawl API key."),
|
|
333
2322
|
method: str("HTTP method for the upload."),
|
|
334
2323
|
headers: unknown("Headers the upload request must send."),
|
|
@@ -339,48 +2328,48 @@ var parseOutputSchema = z2.object({
|
|
|
339
2328
|
maxSizeBytes: num("Largest file the upload URL accepts.")
|
|
340
2329
|
}).optional().describe("Hosted phase one: how to upload the local file."),
|
|
341
2330
|
nextToolCall: unknown("Hosted phase one: the second `firecrawl_parse` call to make once the upload succeeds, as `{name, arguments}`."),
|
|
342
|
-
notes:
|
|
2331
|
+
notes: z4.array(z4.string()).nullable().optional().describe("Hosted phase one: constraints on completing the upload flow."),
|
|
343
2332
|
raw: str("Response body when it was not JSON.")
|
|
344
2333
|
}).describe("Parsed document content, or the upload instructions for the hosted two-call flow.");
|
|
345
|
-
var deprecatedToolOutputSchema =
|
|
2334
|
+
var deprecatedToolOutputSchema = z4.object({
|
|
346
2335
|
code: str("Always `DEPRECATED_TOOL`."),
|
|
347
2336
|
message: str("Why the tool is unavailable and what to call instead."),
|
|
348
2337
|
replacement: unknown("The replacement tool, with instructions and example arguments."),
|
|
349
2338
|
docs_url: str("Documentation for the replacement.")
|
|
350
2339
|
}).describe("A pointer to the replacement tool. This entry point is deprecated and always errors.");
|
|
351
|
-
var monitorOutputSchema =
|
|
2340
|
+
var monitorOutputSchema = z4.object({
|
|
352
2341
|
...agentHints,
|
|
353
2342
|
success,
|
|
354
2343
|
error,
|
|
355
2344
|
data: unknown("The monitor record: configuration, schedule, targets, and current state.")
|
|
356
2345
|
}).describe("One monitor record.");
|
|
357
|
-
var monitorListOutputSchema =
|
|
2346
|
+
var monitorListOutputSchema = z4.object({
|
|
358
2347
|
...agentHints,
|
|
359
2348
|
success,
|
|
360
2349
|
error,
|
|
361
2350
|
data: unknown("One page of monitor records."),
|
|
362
2351
|
pagination: unknown("Pagination metadata for the listing.")
|
|
363
2352
|
}).describe("One page of monitors for the authenticated account.");
|
|
364
|
-
var monitorDeleteOutputSchema =
|
|
2353
|
+
var monitorDeleteOutputSchema = z4.object({
|
|
365
2354
|
...agentHints,
|
|
366
2355
|
success,
|
|
367
2356
|
error,
|
|
368
2357
|
data: unknown("Deletion details, when the API returns any.")
|
|
369
2358
|
}).describe("Deletion status for the monitor.");
|
|
370
|
-
var monitorRunOutputSchema =
|
|
2359
|
+
var monitorRunOutputSchema = z4.object({
|
|
371
2360
|
...agentHints,
|
|
372
2361
|
success,
|
|
373
2362
|
error,
|
|
374
2363
|
data: unknown("The queued check.")
|
|
375
2364
|
}).describe("The check queued outside the monitor schedule.");
|
|
376
|
-
var monitorChecksOutputSchema =
|
|
2365
|
+
var monitorChecksOutputSchema = z4.object({
|
|
377
2366
|
...agentHints,
|
|
378
2367
|
success,
|
|
379
2368
|
error,
|
|
380
2369
|
data: unknown("One page of check summaries."),
|
|
381
2370
|
pagination: unknown("Pagination metadata for the listing.")
|
|
382
2371
|
}).describe("One page of historical checks for the monitor.");
|
|
383
|
-
var monitorCheckOutputSchema =
|
|
2372
|
+
var monitorCheckOutputSchema = z4.object({
|
|
384
2373
|
...agentHints,
|
|
385
2374
|
success,
|
|
386
2375
|
error,
|
|
@@ -388,47 +2377,47 @@ var monitorCheckOutputSchema = z2.object({
|
|
|
388
2377
|
pagination: unknown("Pagination metadata for the page results."),
|
|
389
2378
|
next: str("URL of the next page of results, when more exist.")
|
|
390
2379
|
}).describe("One monitor check with its page-level diff results.");
|
|
391
|
-
var paperSchema =
|
|
2380
|
+
var paperSchema = z4.looseObject({
|
|
392
2381
|
paperId: str("Canonical paper identifier."),
|
|
393
2382
|
primaryId: str("Display identifier, ordered for citation and fetch use."),
|
|
394
2383
|
ids: unknown("Source identifiers by namespace, such as `arxiv`, `doi`, or `pmid`."),
|
|
395
2384
|
title: str("Paper title."),
|
|
396
2385
|
abstract: str("Paper abstract."),
|
|
397
2386
|
authors: unknown("Authors, as a comma-joined string or as `{name, affiliation}` entries."),
|
|
398
|
-
categories:
|
|
2387
|
+
categories: z4.array(z4.string()).nullable().optional().describe("Paper categories, such as `cs.LG`."),
|
|
399
2388
|
createdDate: str("Date the paper was first indexed or published."),
|
|
400
2389
|
updateDate: str("Date the paper was last updated.")
|
|
401
2390
|
});
|
|
402
|
-
var researchSearchOutputSchema =
|
|
2391
|
+
var researchSearchOutputSchema = z4.object({
|
|
403
2392
|
...agentHints,
|
|
404
|
-
results:
|
|
2393
|
+
results: z4.array(paperSchema).describe("Ranked papers, in the order the text block lists them.")
|
|
405
2394
|
}).describe("Ranked papers with canonical IDs, titles, authors, and abstracts.");
|
|
406
|
-
var researchPaperOutputSchema =
|
|
2395
|
+
var researchPaperOutputSchema = z4.object({
|
|
407
2396
|
...agentHints,
|
|
408
2397
|
paper: paperSchema.optional().describe("Canonical metadata for the requested paper, absent when it was not found.")
|
|
409
2398
|
}).describe("Canonical metadata for one paper.");
|
|
410
|
-
var researchRelatedOutputSchema =
|
|
2399
|
+
var researchRelatedOutputSchema = z4.object({
|
|
411
2400
|
...agentHints,
|
|
412
|
-
results:
|
|
413
|
-
poolSize:
|
|
2401
|
+
results: z4.array(paperSchema).describe("Ranked citation-graph candidates."),
|
|
2402
|
+
poolSize: z4.number().describe("Number of candidates evaluated before ranking."),
|
|
414
2403
|
note: str("Note about how the candidates were produced.")
|
|
415
2404
|
}).describe("Ranked citation-graph candidates and the size of the evaluated pool.");
|
|
416
|
-
var researchReadOutputSchema =
|
|
2405
|
+
var researchReadOutputSchema = z4.object({
|
|
417
2406
|
...agentHints,
|
|
418
|
-
passages:
|
|
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.")
|
|
419
2408
|
}).describe("Full-text passages from one paper.");
|
|
420
|
-
var developerSearchOutputSchema =
|
|
2409
|
+
var developerSearchOutputSchema = z4.object({
|
|
421
2410
|
...agentHints,
|
|
422
|
-
results:
|
|
423
|
-
|
|
2411
|
+
results: z4.array(
|
|
2412
|
+
z4.looseObject({
|
|
424
2413
|
id: str("Stable result id, such as `issue:owner/repo#123` or `doc:<hash>`; its prefix gives the source type."),
|
|
425
2414
|
url: str("Source URL."),
|
|
426
2415
|
title: str("Result title."),
|
|
427
|
-
passages:
|
|
2416
|
+
passages: z4.array(z4.looseObject({ text: str("Matched passage, in markdown.") })).optional().describe("Matched passages.")
|
|
428
2417
|
})
|
|
429
2418
|
).describe("Ranked results, in the order the text block lists them.")
|
|
430
2419
|
}).describe("Ranked developer-index results with their matched passages.");
|
|
431
|
-
var creditUsageOutputSchema =
|
|
2420
|
+
var creditUsageOutputSchema = z4.object({
|
|
432
2421
|
// Current view.
|
|
433
2422
|
remainingCredits: num("Credits left in the current billing period; can exceed `planCredits` when extra credits were purchased or granted."),
|
|
434
2423
|
planCredits: num("Credits included in the plan for the period."),
|
|
@@ -441,11 +2430,11 @@ var creditUsageOutputSchema = z2.object({
|
|
|
441
2430
|
}).describe("The team credit balance, or historical credit consumption by period.");
|
|
442
2431
|
|
|
443
2432
|
// src/alexandria.ts
|
|
444
|
-
import { z as
|
|
2433
|
+
import { z as z5 } from "zod";
|
|
445
2434
|
var catalogueTypes = ["alexandria", "exchange"];
|
|
446
|
-
var searchSourceSchema =
|
|
447
|
-
|
|
448
|
-
|
|
2435
|
+
var searchSourceSchema = z5.union([
|
|
2436
|
+
z5.enum(["web", "images", "news", ...catalogueTypes]),
|
|
2437
|
+
z5.object({ type: z5.enum(["web", "images", "news", ...catalogueTypes]) }).strict()
|
|
449
2438
|
]);
|
|
450
2439
|
function hasAlexandria(sources) {
|
|
451
2440
|
return Array.isArray(sources) && sources.some(
|
|
@@ -468,19 +2457,19 @@ function normalizeSearchSources(sources) {
|
|
|
468
2457
|
function searchQueryIsValid(args2) {
|
|
469
2458
|
return !!args2.query?.trim();
|
|
470
2459
|
}
|
|
471
|
-
var findToolsSchema =
|
|
472
|
-
query:
|
|
473
|
-
urls:
|
|
474
|
-
|
|
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?:\/\//)
|
|
475
2464
|
).min(1).max(100).optional(),
|
|
476
|
-
providers:
|
|
477
|
-
categories:
|
|
478
|
-
groups:
|
|
479
|
-
capabilities:
|
|
480
|
-
level:
|
|
481
|
-
expand:
|
|
482
|
-
limit:
|
|
483
|
-
offset:
|
|
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()
|
|
484
2473
|
}).strict();
|
|
485
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";
|
|
486
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.";
|
|
@@ -584,7 +2573,7 @@ async function alexandriaOutput(payload, calls, post, recoveryRequestId, extras
|
|
|
584
2573
|
}
|
|
585
2574
|
|
|
586
2575
|
// src/developer.ts
|
|
587
|
-
import { z as
|
|
2576
|
+
import { z as z6 } from "zod";
|
|
588
2577
|
|
|
589
2578
|
// src/origin.ts
|
|
590
2579
|
import { createRequire } from "module";
|
|
@@ -651,12 +2640,12 @@ Search an index of public repositories, GitHub issues, merged pull requests, rep
|
|
|
651
2640
|
Returns ranked results with an ID, source type, URL, title, and the matched passages in markdown.
|
|
652
2641
|
`,
|
|
653
2642
|
outputSchema: developerSearchOutputSchema,
|
|
654
|
-
parameters:
|
|
655
|
-
query:
|
|
2643
|
+
parameters: z6.object({
|
|
2644
|
+
query: z6.string().min(1).describe(
|
|
656
2645
|
"Natural-language developer question or search phrase, including the library, error message, or API involved when relevant."
|
|
657
2646
|
),
|
|
658
|
-
k:
|
|
659
|
-
skills:
|
|
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.')
|
|
660
2649
|
}),
|
|
661
2650
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
662
2651
|
const { query, k, skills } = args2;
|
|
@@ -719,7 +2708,7 @@ function checkKeylessSignupUrl(value) {
|
|
|
719
2708
|
}
|
|
720
2709
|
|
|
721
2710
|
// src/monitor.ts
|
|
722
|
-
import { z as
|
|
2711
|
+
import { z as z7 } from "zod";
|
|
723
2712
|
|
|
724
2713
|
// src/session-credential.ts
|
|
725
2714
|
import { createHmac } from "crypto";
|
|
@@ -853,8 +2842,8 @@ async function monitorRequest(session, origin, path2, init = {}) {
|
|
|
853
2842
|
function monitorResult(data) {
|
|
854
2843
|
return withAgentHints(structuredText(data), data);
|
|
855
2844
|
}
|
|
856
|
-
var pageStatusSchema =
|
|
857
|
-
var checkStatusSchema =
|
|
2845
|
+
var pageStatusSchema = z7.enum(["same", "new", "changed", "removed", "error"]);
|
|
2846
|
+
var checkStatusSchema = z7.enum([
|
|
858
2847
|
"queued",
|
|
859
2848
|
"running",
|
|
860
2849
|
"completed",
|
|
@@ -948,22 +2937,22 @@ Create a recurring scrape, crawl, or search monitor that compares each check wit
|
|
|
948
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.
|
|
949
2938
|
`,
|
|
950
2939
|
outputSchema: monitorOutputSchema,
|
|
951
|
-
parameters:
|
|
952
|
-
body:
|
|
953
|
-
page:
|
|
954
|
-
pages:
|
|
955
|
-
queries:
|
|
956
|
-
searchWindow:
|
|
957
|
-
maxResults:
|
|
958
|
-
includeDomains:
|
|
959
|
-
excludeDomains:
|
|
960
|
-
goal:
|
|
961
|
-
name:
|
|
962
|
-
scheduleText:
|
|
963
|
-
timezone:
|
|
964
|
-
email:
|
|
965
|
-
includeDiffs:
|
|
966
|
-
webhookUrl:
|
|
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()
|
|
967
2956
|
}),
|
|
968
2957
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
969
2958
|
const body = buildMonitorCreateBody(args2);
|
|
@@ -995,9 +2984,9 @@ In the simple form, a \`goal\` is required. If \`queries\` contains one or more
|
|
|
995
2984
|
List monitors for the authenticated account with optional pagination controls. Returns one page of monitor records and pagination metadata.
|
|
996
2985
|
`,
|
|
997
2986
|
outputSchema: monitorListOutputSchema,
|
|
998
|
-
parameters:
|
|
999
|
-
limit:
|
|
1000
|
-
offset:
|
|
2987
|
+
parameters: z7.object({
|
|
2988
|
+
limit: z7.number().int().positive().optional(),
|
|
2989
|
+
offset: z7.number().int().nonnegative().optional()
|
|
1001
2990
|
}),
|
|
1002
2991
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
1003
2992
|
const { limit, offset } = args2;
|
|
@@ -1027,7 +3016,7 @@ List monitors for the authenticated account with optional pagination controls. R
|
|
|
1027
3016
|
Retrieve one monitor by ID, including its configuration and current state. This does not run or modify the monitor.
|
|
1028
3017
|
`,
|
|
1029
3018
|
outputSchema: monitorOutputSchema,
|
|
1030
|
-
parameters:
|
|
3019
|
+
parameters: z7.object({ id: z7.string() }),
|
|
1031
3020
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
1032
3021
|
const { id } = args2;
|
|
1033
3022
|
const res = await monitorRequest(
|
|
@@ -1055,9 +3044,9 @@ Patch an existing monitor by ID. The body can change its name, active/paused sta
|
|
|
1055
3044
|
Returns the updated monitor.
|
|
1056
3045
|
`,
|
|
1057
3046
|
outputSchema: monitorOutputSchema,
|
|
1058
|
-
parameters:
|
|
1059
|
-
id:
|
|
1060
|
-
body:
|
|
3047
|
+
parameters: z7.object({
|
|
3048
|
+
id: z7.string(),
|
|
3049
|
+
body: z7.record(z7.string(), z7.any())
|
|
1061
3050
|
}),
|
|
1062
3051
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
1063
3052
|
const { id, body } = args2;
|
|
@@ -1085,7 +3074,7 @@ Returns the updated monitor.
|
|
|
1085
3074
|
Permanently delete a monitor by ID and stop its future schedule. This operation cannot be undone and returns deletion status.
|
|
1086
3075
|
`,
|
|
1087
3076
|
outputSchema: monitorDeleteOutputSchema,
|
|
1088
|
-
parameters:
|
|
3077
|
+
parameters: z7.object({ id: z7.string() }),
|
|
1089
3078
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
1090
3079
|
const { id } = args2;
|
|
1091
3080
|
log.info("Deleting monitor", { id });
|
|
@@ -1113,7 +3102,7 @@ Permanently delete a monitor by ID and stop its future schedule. This operation
|
|
|
1113
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.
|
|
1114
3103
|
`,
|
|
1115
3104
|
outputSchema: monitorRunOutputSchema,
|
|
1116
|
-
parameters:
|
|
3105
|
+
parameters: z7.object({ id: z7.string() }),
|
|
1117
3106
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
1118
3107
|
const { id } = args2;
|
|
1119
3108
|
const res = await monitorRequest(
|
|
@@ -1140,10 +3129,10 @@ Queue an immediate check for a monitor outside its normal schedule. This starts
|
|
|
1140
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.
|
|
1141
3130
|
`,
|
|
1142
3131
|
outputSchema: monitorChecksOutputSchema,
|
|
1143
|
-
parameters:
|
|
1144
|
-
id:
|
|
1145
|
-
limit:
|
|
1146
|
-
offset:
|
|
3132
|
+
parameters: z7.object({
|
|
3133
|
+
id: z7.string(),
|
|
3134
|
+
limit: z7.number().int().positive().optional(),
|
|
3135
|
+
offset: z7.number().int().nonnegative().optional(),
|
|
1147
3136
|
status: checkStatusSchema.optional()
|
|
1148
3137
|
}),
|
|
1149
3138
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
@@ -1174,11 +3163,11 @@ Retrieve one monitor check and its page-level results, optionally filtered by pa
|
|
|
1174
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.
|
|
1175
3164
|
`,
|
|
1176
3165
|
outputSchema: monitorCheckOutputSchema,
|
|
1177
|
-
parameters:
|
|
1178
|
-
id:
|
|
1179
|
-
checkId:
|
|
1180
|
-
limit:
|
|
1181
|
-
skip:
|
|
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(),
|
|
1182
3171
|
pageStatus: pageStatusSchema.optional()
|
|
1183
3172
|
}),
|
|
1184
3173
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
@@ -1195,8 +3184,7 @@ Markdown tracking returns a unified text diff, JSON tracking returns field paths
|
|
|
1195
3184
|
}
|
|
1196
3185
|
|
|
1197
3186
|
// src/research.ts
|
|
1198
|
-
import { z as
|
|
1199
|
-
import { UserError } from "fastmcp";
|
|
3187
|
+
import { z as z8 } from "zod";
|
|
1200
3188
|
var BASE2 = "/v2/search/research";
|
|
1201
3189
|
function researchHeaders(origin) {
|
|
1202
3190
|
return { ...originHeaders(origin), ...AGENT_HINTS_HEADERS };
|
|
@@ -1315,21 +3303,21 @@ Several distinct framings of the same question surface different papers than a s
|
|
|
1315
3303
|
Returns ranked papers with canonical IDs, titles, authors, and abstracts.
|
|
1316
3304
|
`,
|
|
1317
3305
|
outputSchema: researchSearchOutputSchema,
|
|
1318
|
-
parameters:
|
|
1319
|
-
query:
|
|
3306
|
+
parameters: z8.object({
|
|
3307
|
+
query: z8.string().min(1).describe(
|
|
1320
3308
|
"Natural-language research topic or question, including methods, systems, conditions, populations, interventions, or outcomes when relevant."
|
|
1321
3309
|
),
|
|
1322
|
-
k:
|
|
1323
|
-
authors:
|
|
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(
|
|
1324
3312
|
"Author substring filter(s); ALL must match (case-insensitive)."
|
|
1325
3313
|
),
|
|
1326
|
-
categories:
|
|
3314
|
+
categories: z8.array(z8.string()).optional().describe(
|
|
1327
3315
|
"Paper category filter(s) (e.g. `cs.LG`); ALL provided values must match."
|
|
1328
3316
|
),
|
|
1329
|
-
from:
|
|
3317
|
+
from: z8.string().optional().describe(
|
|
1330
3318
|
"Inclusive lower bound on created/updated date (`YYYY-MM-DD`)."
|
|
1331
3319
|
),
|
|
1332
|
-
to:
|
|
3320
|
+
to: z8.string().optional().describe(
|
|
1333
3321
|
"Inclusive upper bound on created/updated date (`YYYY-MM-DD`)."
|
|
1334
3322
|
)
|
|
1335
3323
|
}),
|
|
@@ -1366,8 +3354,8 @@ Returns ranked papers with canonical IDs, titles, authors, and abstracts.
|
|
|
1366
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.
|
|
1367
3355
|
`,
|
|
1368
3356
|
outputSchema: researchPaperOutputSchema,
|
|
1369
|
-
parameters:
|
|
1370
|
-
paperId:
|
|
3357
|
+
parameters: z8.object({
|
|
3358
|
+
paperId: z8.string().min(1).describe(
|
|
1371
3359
|
"Canonical paperId or primaryId such as `arxiv:1706.03762`, `pmcid:PMC12530322`, `pmid:40953549`, or `doi:10.1016/j.neunet.2025.108095`."
|
|
1372
3360
|
)
|
|
1373
3361
|
}),
|
|
@@ -1402,12 +3390,12 @@ Find citation-graph candidates from one to ten \`seed_ids\`; the first ID is the
|
|
|
1402
3390
|
Returns ranked candidates and the evaluated pool size.
|
|
1403
3391
|
`,
|
|
1404
3392
|
outputSchema: researchRelatedOutputSchema,
|
|
1405
|
-
parameters:
|
|
1406
|
-
seed_ids:
|
|
1407
|
-
intent:
|
|
1408
|
-
mode:
|
|
1409
|
-
k:
|
|
1410
|
-
rerank:
|
|
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.")
|
|
1411
3399
|
}),
|
|
1412
3400
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
1413
3401
|
const { seed_ids, intent, mode, k, rerank } = args2;
|
|
@@ -1454,12 +3442,12 @@ Retrieve in-body passages from one paper that are relevant to a specific questio
|
|
|
1454
3442
|
Returns matching passages or a notice when full text is unavailable.
|
|
1455
3443
|
`,
|
|
1456
3444
|
outputSchema: researchReadOutputSchema,
|
|
1457
|
-
parameters:
|
|
1458
|
-
paperId:
|
|
3445
|
+
parameters: z8.object({
|
|
3446
|
+
paperId: z8.string().min(1).describe(
|
|
1459
3447
|
"Canonical paperId or primaryId such as `arxiv:1706.03762`, `pmcid:PMC12530322`, `pmid:40953549`, or `doi:10.1016/j.neunet.2025.108095`."
|
|
1460
3448
|
),
|
|
1461
|
-
question:
|
|
1462
|
-
k:
|
|
3449
|
+
question: z8.string().min(1),
|
|
3450
|
+
k: z8.number().int().min(1).max(50).optional().describe("Number of passages to return (default 4).")
|
|
1463
3451
|
}),
|
|
1464
3452
|
execute: async (args2, { session, client: mcpClient }) => {
|
|
1465
3453
|
const { paperId, question, k } = args2;
|
|
@@ -1490,9 +3478,9 @@ Returns matching passages or a notice when full text is unavailable.
|
|
|
1490
3478
|
Deprecated compatibility entry point. Use firecrawl_developer_search for GitHub issues, pull requests, and READMEs, plus code documentation, returned as matched passages.
|
|
1491
3479
|
`,
|
|
1492
3480
|
outputSchema: deprecatedToolOutputSchema,
|
|
1493
|
-
parameters:
|
|
1494
|
-
query:
|
|
1495
|
-
k:
|
|
3481
|
+
parameters: z8.object({
|
|
3482
|
+
query: z8.string().min(1),
|
|
3483
|
+
k: z8.number().int().min(1).max(100).optional()
|
|
1496
3484
|
}),
|
|
1497
3485
|
canList: () => false,
|
|
1498
3486
|
beforeValidate: () => {
|
|
@@ -1511,7 +3499,7 @@ Deprecated compatibility entry point. Use firecrawl_developer_search for GitHub
|
|
|
1511
3499
|
}
|
|
1512
3500
|
|
|
1513
3501
|
// src/usage.ts
|
|
1514
|
-
import { z as
|
|
3502
|
+
import { z as z9 } from "zod";
|
|
1515
3503
|
function assertSuccessful(status, response, operation) {
|
|
1516
3504
|
if (status === 200 && response.success) return;
|
|
1517
3505
|
throw new Error(response.error || `Failed to ${operation}`);
|
|
@@ -1533,11 +3521,11 @@ The current view returns \`remainingCredits\`, \`planCredits\`, \`billingPeriodS
|
|
|
1533
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.
|
|
1534
3522
|
`,
|
|
1535
3523
|
outputSchema: creditUsageOutputSchema,
|
|
1536
|
-
parameters:
|
|
1537
|
-
view:
|
|
3524
|
+
parameters: z9.object({
|
|
3525
|
+
view: z9.enum(["current", "historical"]).optional().describe(
|
|
1538
3526
|
"Select current balance or historical monthly usage. Defaults to current."
|
|
1539
3527
|
),
|
|
1540
|
-
byApiKey:
|
|
3528
|
+
byApiKey: z9.boolean().optional().describe(
|
|
1541
3529
|
'Break historical usage down by API key. When view is omitted, true selects the historical view; it cannot be combined with view "current".'
|
|
1542
3530
|
)
|
|
1543
3531
|
}),
|
|
@@ -2160,30 +4148,30 @@ function removeEmptyTopLevel(obj) {
|
|
|
2160
4148
|
}
|
|
2161
4149
|
return out;
|
|
2162
4150
|
}
|
|
2163
|
-
var searchDomainSchema =
|
|
4151
|
+
var searchDomainSchema = z10.string().trim().toLowerCase().min(1).max(253).regex(
|
|
2164
4152
|
/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/,
|
|
2165
4153
|
"Domain must be a valid hostname without protocol or path"
|
|
2166
4154
|
);
|
|
2167
4155
|
var searchToolBaseFields = {
|
|
2168
|
-
toolDetail:
|
|
2169
|
-
query:
|
|
2170
|
-
domainTools:
|
|
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(
|
|
2171
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."
|
|
2172
4160
|
),
|
|
2173
|
-
highlights:
|
|
4161
|
+
highlights: z10.boolean().optional().describe(
|
|
2174
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."
|
|
2175
4163
|
),
|
|
2176
|
-
limit:
|
|
2177
|
-
tbs:
|
|
2178
|
-
filter:
|
|
2179
|
-
location:
|
|
2180
|
-
includeDomains:
|
|
2181
|
-
excludeDomains:
|
|
2182
|
-
sources:
|
|
2183
|
-
categories:
|
|
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(
|
|
2184
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`.'
|
|
2185
4173
|
),
|
|
2186
|
-
enterprise:
|
|
4174
|
+
enterprise: z10.array(z10.enum(["default", "anon", "zdr"])).optional()
|
|
2187
4175
|
};
|
|
2188
4176
|
function searchDomainsAreExclusive(args2) {
|
|
2189
4177
|
return !(args2.includeDomains?.length && args2.excludeDomains?.length);
|
|
@@ -2191,15 +4179,15 @@ function searchDomainsAreExclusive(args2) {
|
|
|
2191
4179
|
var SEARCH_DOMAINS_CONFLICT_MESSAGE = "includeDomains and excludeDomains cannot both be specified";
|
|
2192
4180
|
var EXCHANGE_KEY_REQUIRED_MESSAGE = "Alexandria requires an API key on a team with Alexandria access";
|
|
2193
4181
|
var EXCHANGE_MAX_CALLS = 10;
|
|
2194
|
-
var exchangeCallSchema =
|
|
2195
|
-
provider:
|
|
2196
|
-
capability:
|
|
4182
|
+
var exchangeCallSchema = z10.object({
|
|
4183
|
+
provider: z10.string().min(1).describe('Provider slug, e.g. "fred".'),
|
|
4184
|
+
capability: z10.string().min(1).describe(
|
|
2197
4185
|
'Capability address as returned by search or discover, e.g. "series/observations".'
|
|
2198
4186
|
),
|
|
2199
|
-
version:
|
|
2200
|
-
options:
|
|
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.")
|
|
2201
4189
|
});
|
|
2202
|
-
var exchangeCallsSchema =
|
|
4190
|
+
var exchangeCallsSchema = z10.array(exchangeCallSchema).min(1).max(EXCHANGE_MAX_CALLS);
|
|
2203
4191
|
function assertExchangeCredential(session) {
|
|
2204
4192
|
if (hasCredential(session)) return;
|
|
2205
4193
|
const payload = {
|
|
@@ -2207,7 +4195,7 @@ function assertExchangeCredential(session) {
|
|
|
2207
4195
|
message: EXCHANGE_KEY_REQUIRED_MESSAGE,
|
|
2208
4196
|
docs_url: MCP_CONNECTION_GUIDE_URL
|
|
2209
4197
|
};
|
|
2210
|
-
throw new
|
|
4198
|
+
throw new UserError(payload.message, payload);
|
|
2211
4199
|
}
|
|
2212
4200
|
var TERMS_REQUIRED_CODE = "THIRD_PARTY_DATA_TERMS_REQUIRED";
|
|
2213
4201
|
function termsRequiredAction(body) {
|
|
@@ -2227,7 +4215,7 @@ function termsRequiredError(action, context, hints) {
|
|
|
2227
4215
|
const requestId = context.requestId;
|
|
2228
4216
|
const retryIdentity = requestId ? ` and requestId ${requestId}` : "";
|
|
2229
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.`;
|
|
2230
|
-
return new
|
|
4218
|
+
return new UserError(
|
|
2231
4219
|
`${message}
|
|
2232
4220
|
|
|
2233
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.
|
|
@@ -2292,7 +4280,7 @@ async function relayExchangeError(run, context) {
|
|
|
2292
4280
|
const message = typeof data?.error === "string" ? data.error : `Alexandria request failed (HTTP ${response.status})`;
|
|
2293
4281
|
const disabledProvider = response.status === 403 ? context.providers?.find((provider) => message === `Access to ${provider} is disabled for this organization.`) : void 0;
|
|
2294
4282
|
if (disabledProvider) {
|
|
2295
|
-
throw new
|
|
4283
|
+
throw new UserError(
|
|
2296
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.`,
|
|
2297
4285
|
{
|
|
2298
4286
|
code: typeof data?.code === "string" ? data.code : "exchange_error",
|
|
@@ -2308,7 +4296,7 @@ async function relayExchangeError(run, context) {
|
|
|
2308
4296
|
}
|
|
2309
4297
|
);
|
|
2310
4298
|
}
|
|
2311
|
-
throw new
|
|
4299
|
+
throw new UserError(message, {
|
|
2312
4300
|
code: typeof data?.code === "string" ? data.code : "exchange_error",
|
|
2313
4301
|
status: response.status,
|
|
2314
4302
|
message,
|
|
@@ -2545,12 +4533,12 @@ async function runWithCredentialRecovery(run, requestId, session) {
|
|
|
2545
4533
|
const hints = readErrorAgentHints(error2);
|
|
2546
4534
|
if (hints) {
|
|
2547
4535
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
2548
|
-
throw new
|
|
4536
|
+
throw new UserError(
|
|
2549
4537
|
hints.length ? `${message}
|
|
2550
4538
|
|
|
2551
4539
|
${agentHintsText(hints)}` : message,
|
|
2552
4540
|
{
|
|
2553
|
-
...error2 instanceof
|
|
4541
|
+
...error2 instanceof UserError ? error2.extras : {},
|
|
2554
4542
|
error: message,
|
|
2555
4543
|
agent_hints: hints
|
|
2556
4544
|
}
|
|
@@ -2559,7 +4547,7 @@ ${agentHintsText(hints)}` : message,
|
|
|
2559
4547
|
throw error2;
|
|
2560
4548
|
}
|
|
2561
4549
|
const payload = recoveryPayload("CREDENTIAL_INVALID", requestId);
|
|
2562
|
-
throw new
|
|
4550
|
+
throw new UserError(String(payload.message), payload);
|
|
2563
4551
|
}
|
|
2564
4552
|
}
|
|
2565
4553
|
function recoveryPayload(code, requestId = randomUUID(), options = {}) {
|
|
@@ -2688,7 +4676,7 @@ function guardHostedTool(tool, { logActions }) {
|
|
|
2688
4676
|
signupUrl: code === "KEYLESS_TOOL_NOT_AVAILABLE" ? await hostedKeylessSignupUrl(session) : void 0
|
|
2689
4677
|
});
|
|
2690
4678
|
if (logActions) {
|
|
2691
|
-
emitActionLog(tool.name, "error", session, new
|
|
4679
|
+
emitActionLog(tool.name, "error", session, new UserError(String(payload2.message), payload2), requestId, code);
|
|
2692
4680
|
}
|
|
2693
4681
|
return {
|
|
2694
4682
|
content: [{ type: "text", text: String(payload2.message) }],
|
|
@@ -2704,7 +4692,7 @@ function guardHostedTool(tool, { logActions }) {
|
|
|
2704
4692
|
tool.name,
|
|
2705
4693
|
"error",
|
|
2706
4694
|
session,
|
|
2707
|
-
new
|
|
4695
|
+
new UserError(`Tool validation failed: ${recoveryCode}`, payload),
|
|
2708
4696
|
randomUUID(),
|
|
2709
4697
|
recoveryCode
|
|
2710
4698
|
);
|
|
@@ -2725,16 +4713,16 @@ function guardHostedTool(tool, { logActions }) {
|
|
|
2725
4713
|
if (invocationSession.credentialError) {
|
|
2726
4714
|
const code = "CREDENTIAL_INVALID";
|
|
2727
4715
|
const payload = recoveryPayload(code, requestId);
|
|
2728
|
-
if (logActions) emitActionLog(tool.name, "error", invocationSession, new
|
|
2729
|
-
throw new
|
|
4716
|
+
if (logActions) emitActionLog(tool.name, "error", invocationSession, new UserError(String(payload.message), payload), requestId, code);
|
|
4717
|
+
throw new UserError(String(payload.message), payload);
|
|
2730
4718
|
}
|
|
2731
4719
|
if (isHostedKeylessSession(invocationSession) && !keylessTool) {
|
|
2732
4720
|
const code = "KEYLESS_TOOL_NOT_AVAILABLE";
|
|
2733
4721
|
const payload = recoveryPayload(code, requestId, {
|
|
2734
4722
|
signupUrl: await hostedKeylessSignupUrl(invocationSession)
|
|
2735
4723
|
});
|
|
2736
|
-
if (logActions) emitActionLog(tool.name, "error", invocationSession, new
|
|
2737
|
-
throw new
|
|
4724
|
+
if (logActions) emitActionLog(tool.name, "error", invocationSession, new UserError(String(payload.message), payload), requestId, code);
|
|
4725
|
+
throw new UserError(String(payload.message), payload);
|
|
2738
4726
|
}
|
|
2739
4727
|
const runTool = async () => {
|
|
2740
4728
|
try {
|
|
@@ -2966,10 +4954,10 @@ function transformScrapeParams(args2) {
|
|
|
2966
4954
|
delete out.pdfOptions;
|
|
2967
4955
|
return out;
|
|
2968
4956
|
}
|
|
2969
|
-
var scrapeParamsSchema =
|
|
2970
|
-
url:
|
|
2971
|
-
formats:
|
|
2972
|
-
|
|
4957
|
+
var scrapeParamsSchema = z10.object({
|
|
4958
|
+
url: z10.string().url(),
|
|
4959
|
+
formats: z10.array(
|
|
4960
|
+
z10.enum([
|
|
2973
4961
|
"markdown",
|
|
2974
4962
|
"html",
|
|
2975
4963
|
"rawHtml",
|
|
@@ -2983,71 +4971,71 @@ var scrapeParamsSchema = z8.object({
|
|
|
2983
4971
|
"audio"
|
|
2984
4972
|
])
|
|
2985
4973
|
).optional(),
|
|
2986
|
-
jsonOptions:
|
|
2987
|
-
prompt:
|
|
2988
|
-
schema:
|
|
4974
|
+
jsonOptions: z10.object({
|
|
4975
|
+
prompt: z10.string().optional(),
|
|
4976
|
+
schema: z10.record(z10.string(), z10.any()).optional()
|
|
2989
4977
|
}).optional(),
|
|
2990
|
-
queryOptions:
|
|
2991
|
-
prompt:
|
|
2992
|
-
mode:
|
|
4978
|
+
queryOptions: z10.object({
|
|
4979
|
+
prompt: z10.string().max(1e4),
|
|
4980
|
+
mode: z10.enum(["directQuote", "freeform"]).default("freeform")
|
|
2993
4981
|
}).optional(),
|
|
2994
|
-
screenshotOptions:
|
|
2995
|
-
fullPage:
|
|
2996
|
-
quality:
|
|
2997
|
-
viewport:
|
|
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()
|
|
2998
4986
|
}).optional(),
|
|
2999
|
-
parsers:
|
|
3000
|
-
pdfOptions:
|
|
3001
|
-
maxPages:
|
|
4987
|
+
parsers: z10.array(z10.enum(["pdf"])).optional(),
|
|
4988
|
+
pdfOptions: z10.object({
|
|
4989
|
+
maxPages: z10.number().int().min(1).max(1e4).optional()
|
|
3002
4990
|
}).optional(),
|
|
3003
|
-
onlyMainContent:
|
|
3004
|
-
redactPII:
|
|
3005
|
-
includeTags:
|
|
3006
|
-
excludeTags:
|
|
3007
|
-
waitFor:
|
|
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(),
|
|
3008
4996
|
...SAFE_MODE ? {} : {
|
|
3009
|
-
actions:
|
|
3010
|
-
|
|
3011
|
-
type:
|
|
3012
|
-
selector:
|
|
3013
|
-
milliseconds:
|
|
3014
|
-
text:
|
|
3015
|
-
key:
|
|
3016
|
-
direction:
|
|
3017
|
-
script:
|
|
3018
|
-
fullPage:
|
|
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()
|
|
3019
5007
|
})
|
|
3020
5008
|
).optional()
|
|
3021
5009
|
},
|
|
3022
|
-
mobile:
|
|
3023
|
-
skipTlsVerification:
|
|
3024
|
-
removeBase64Images:
|
|
3025
|
-
location:
|
|
3026
|
-
country:
|
|
3027
|
-
languages:
|
|
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()
|
|
3028
5016
|
}).optional(),
|
|
3029
|
-
storeInCache:
|
|
3030
|
-
zeroDataRetention:
|
|
3031
|
-
maxAge:
|
|
3032
|
-
lockdown:
|
|
3033
|
-
proxy:
|
|
3034
|
-
profile:
|
|
3035
|
-
name:
|
|
3036
|
-
saveChanges:
|
|
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()
|
|
3037
5025
|
}).optional()
|
|
3038
5026
|
});
|
|
3039
5027
|
var ALEXANDRIA_IGNORED_SCRAPE_OPTIONS = /* @__PURE__ */ new Set(["toolDetail", "domainTools"]);
|
|
3040
5028
|
var scrapeToolParamsSchema = scrapeParamsSchema.extend({
|
|
3041
|
-
url:
|
|
3042
|
-
timeout:
|
|
3043
|
-
requestId:
|
|
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(
|
|
3044
5032
|
"Idempotency key bound to one Alexandria execution payload. Generated when omitted and returned with the result."
|
|
3045
5033
|
),
|
|
3046
|
-
alexandria:
|
|
5034
|
+
alexandria: z10.union([exchangeCallSchema, exchangeCallsSchema]).optional().describe(
|
|
3047
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."
|
|
3048
5036
|
),
|
|
3049
|
-
toolDetail:
|
|
3050
|
-
domainTools:
|
|
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(
|
|
3051
5039
|
"URL mode only: include domain-matched Alexandria tools for the page in tools on the returned document. Ignored with alexandria."
|
|
3052
5040
|
)
|
|
3053
5041
|
}).refine(
|
|
@@ -3067,9 +5055,9 @@ var scrapeToolParamsSchema = scrapeParamsSchema.extend({
|
|
|
3067
5055
|
(args2) => !args2.requestId || !!args2.alexandria,
|
|
3068
5056
|
"requestId applies to Alexandria execution only"
|
|
3069
5057
|
);
|
|
3070
|
-
var parseOptionParamsSchema =
|
|
3071
|
-
formats:
|
|
3072
|
-
|
|
5058
|
+
var parseOptionParamsSchema = z10.object({
|
|
5059
|
+
formats: z10.array(
|
|
5060
|
+
z10.enum([
|
|
3073
5061
|
"markdown",
|
|
3074
5062
|
"html",
|
|
3075
5063
|
"rawHtml",
|
|
@@ -3079,48 +5067,48 @@ var parseOptionParamsSchema = z8.object({
|
|
|
3079
5067
|
"query"
|
|
3080
5068
|
])
|
|
3081
5069
|
).optional(),
|
|
3082
|
-
jsonOptions:
|
|
3083
|
-
prompt:
|
|
3084
|
-
schema:
|
|
5070
|
+
jsonOptions: z10.object({
|
|
5071
|
+
prompt: z10.string().optional(),
|
|
5072
|
+
schema: z10.record(z10.string(), z10.any()).optional()
|
|
3085
5073
|
}).optional(),
|
|
3086
|
-
queryOptions:
|
|
3087
|
-
prompt:
|
|
3088
|
-
mode:
|
|
5074
|
+
queryOptions: z10.object({
|
|
5075
|
+
prompt: z10.string().max(1e4),
|
|
5076
|
+
mode: z10.enum(["directQuote", "freeform"]).default("freeform")
|
|
3089
5077
|
}).optional(),
|
|
3090
|
-
parsers:
|
|
3091
|
-
pdfOptions:
|
|
3092
|
-
maxPages:
|
|
5078
|
+
parsers: z10.array(z10.enum(["pdf"])).optional(),
|
|
5079
|
+
pdfOptions: z10.object({
|
|
5080
|
+
maxPages: z10.number().int().min(1).max(1e4).optional()
|
|
3093
5081
|
}).optional(),
|
|
3094
|
-
onlyMainContent:
|
|
3095
|
-
redactPII:
|
|
3096
|
-
includeTags:
|
|
3097
|
-
excludeTags:
|
|
3098
|
-
removeBase64Images:
|
|
3099
|
-
skipTlsVerification:
|
|
3100
|
-
storeInCache:
|
|
3101
|
-
zeroDataRetention:
|
|
3102
|
-
maxAge:
|
|
3103
|
-
proxy:
|
|
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()
|
|
3104
5092
|
});
|
|
3105
5093
|
var localParseParamsSchema = parseOptionParamsSchema.extend({
|
|
3106
|
-
filePath:
|
|
5094
|
+
filePath: z10.string().min(1).describe(
|
|
3107
5095
|
"Absolute or relative path to a local file to parse. Supported: .html, .htm, .pdf, .docx, .doc, .odt, .rtf, .xlsx, .xls"
|
|
3108
5096
|
),
|
|
3109
|
-
contentType:
|
|
5097
|
+
contentType: z10.string().optional().describe(
|
|
3110
5098
|
"Optional MIME type override. If omitted, the server infers the file kind from the extension."
|
|
3111
5099
|
)
|
|
3112
5100
|
});
|
|
3113
5101
|
var hostedParseParamsSchema = parseOptionParamsSchema.extend({
|
|
3114
|
-
filePath:
|
|
5102
|
+
filePath: z10.string().min(1).optional().describe(
|
|
3115
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."
|
|
3116
5104
|
),
|
|
3117
|
-
uploadRef:
|
|
5105
|
+
uploadRef: z10.string().min(1).optional().describe(
|
|
3118
5106
|
"Phase 2 only: short-lived upload reference returned by phase 1 after the local PUT upload completes."
|
|
3119
5107
|
),
|
|
3120
|
-
contentType:
|
|
5108
|
+
contentType: z10.string().optional().describe(
|
|
3121
5109
|
"Phase 1 MIME type override. If omitted, the server infers it from the file extension without reading the file."
|
|
3122
5110
|
),
|
|
3123
|
-
declaredSizeBytes:
|
|
5111
|
+
declaredSizeBytes: z10.number().int().positive().optional().describe(
|
|
3124
5112
|
"Optional phase 1 size declaration. Hosted MCP does not stat the file; provide this only if the caller already knows it."
|
|
3125
5113
|
)
|
|
3126
5114
|
}).superRefine((value, ctx) => {
|
|
@@ -3128,7 +5116,7 @@ var hostedParseParamsSchema = parseOptionParamsSchema.extend({
|
|
|
3128
5116
|
const hasUploadRef = typeof value.uploadRef === "string" && value.uploadRef.length > 0;
|
|
3129
5117
|
if (hasFilePath === hasUploadRef) {
|
|
3130
5118
|
ctx.addIssue({
|
|
3131
|
-
code:
|
|
5119
|
+
code: z10.ZodIssueCode.custom,
|
|
3132
5120
|
message: "Hosted firecrawl_parse requires exactly one of filePath (phase 1) or uploadRef (phase 2).",
|
|
3133
5121
|
path: hasFilePath && hasUploadRef ? ["uploadRef"] : ["filePath"]
|
|
3134
5122
|
});
|
|
@@ -3263,7 +5251,7 @@ async function executeHostedParse(args2, session, log, origin) {
|
|
|
3263
5251
|
option: "zeroDataRetention",
|
|
3264
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."
|
|
3265
5253
|
};
|
|
3266
|
-
throw new
|
|
5254
|
+
throw new UserError(String(payload.message), payload);
|
|
3267
5255
|
}
|
|
3268
5256
|
const options = extractParseOptions(args2);
|
|
3269
5257
|
if (hasFilePath && args2.filePath) {
|
|
@@ -3424,13 +5412,13 @@ Enumerate URLs indexed under one website through Firecrawl without fetching each
|
|
|
3424
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.
|
|
3425
5413
|
`,
|
|
3426
5414
|
outputSchema: mapOutputSchema,
|
|
3427
|
-
parameters:
|
|
3428
|
-
url:
|
|
3429
|
-
search:
|
|
3430
|
-
sitemap:
|
|
3431
|
-
includeSubdomains:
|
|
3432
|
-
limit:
|
|
3433
|
-
ignoreQueryParameters:
|
|
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()
|
|
3434
5422
|
}),
|
|
3435
5423
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
3436
5424
|
const { url, ...options } = args2;
|
|
@@ -3469,7 +5457,7 @@ On an authenticated session, tool matches describe available capabilities; \`fir
|
|
|
3469
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.
|
|
3470
5458
|
`,
|
|
3471
5459
|
outputSchema: searchOutputSchema,
|
|
3472
|
-
parameters:
|
|
5460
|
+
parameters: z10.object({
|
|
3473
5461
|
...searchToolBaseFields,
|
|
3474
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.")
|
|
3475
5463
|
}).refine(searchDomainsAreExclusive, SEARCH_DOMAINS_CONFLICT_MESSAGE).refine(
|
|
@@ -3556,9 +5544,9 @@ async function executeExchangeCalls(session, alexandria, suppliedRequestId, time
|
|
|
3556
5544
|
);
|
|
3557
5545
|
} catch (error2) {
|
|
3558
5546
|
if (error2?.response?.status === 401) throw error2;
|
|
3559
|
-
throw new
|
|
5547
|
+
throw new UserError(
|
|
3560
5548
|
`${error2 instanceof Error ? error2.message : String(error2)} Request ID: ${requestId}. Reuse this ID only with the identical payload.`,
|
|
3561
|
-
{ ...error2 instanceof
|
|
5549
|
+
{ ...error2 instanceof UserError ? error2.extras : {}, requestId }
|
|
3562
5550
|
);
|
|
3563
5551
|
}
|
|
3564
5552
|
}
|
|
@@ -3580,7 +5568,7 @@ var findToolsTool = {
|
|
|
3580
5568
|
try {
|
|
3581
5569
|
options = findToolsOptions(args2);
|
|
3582
5570
|
} catch (error2) {
|
|
3583
|
-
throw new
|
|
5571
|
+
throw new UserError(error2 instanceof Error ? error2.message : String(error2));
|
|
3584
5572
|
}
|
|
3585
5573
|
if (options.level === "categories") {
|
|
3586
5574
|
assertExchangeCredential(session);
|
|
@@ -3590,7 +5578,7 @@ var findToolsTool = {
|
|
|
3590
5578
|
);
|
|
3591
5579
|
const rows = response?.data?.cohorts;
|
|
3592
5580
|
if (!Array.isArray(rows) || rows.some((row) => typeof row?.cohort !== "string" || typeof row?.about !== "string")) {
|
|
3593
|
-
throw new
|
|
5581
|
+
throw new UserError("Category discovery returned an invalid category index.");
|
|
3594
5582
|
}
|
|
3595
5583
|
const offset = options.offset ?? 0;
|
|
3596
5584
|
const items = rows.slice(offset, offset + options.limit).map((row) => ({
|
|
@@ -3692,7 +5680,7 @@ async function keylessPost(path2, body, session, originOverride) {
|
|
|
3692
5680
|
retryAfterSeconds: eligibility.retryAfterSeconds,
|
|
3693
5681
|
signupUrl: eligibility.signupUrl
|
|
3694
5682
|
});
|
|
3695
|
-
throw new
|
|
5683
|
+
throw new UserError(String(payload.message), payload);
|
|
3696
5684
|
}
|
|
3697
5685
|
}
|
|
3698
5686
|
const headers = {
|
|
@@ -3719,7 +5707,7 @@ async function keylessPost(path2, body, session, originOverride) {
|
|
|
3719
5707
|
});
|
|
3720
5708
|
const hints = readAgentHints(json);
|
|
3721
5709
|
if (hints) payload.agent_hints = hints;
|
|
3722
|
-
throw new
|
|
5710
|
+
throw new UserError(String(payload.message), payload);
|
|
3723
5711
|
}
|
|
3724
5712
|
throw new CoreHttpError(
|
|
3725
5713
|
json?.error || `Firecrawl request failed (HTTP ${response.status})`,
|
|
@@ -3789,17 +5777,17 @@ async function waitForCrawlCompletionWithOrigin(client, jobId, origin, pollInter
|
|
|
3789
5777
|
);
|
|
3790
5778
|
}
|
|
3791
5779
|
}
|
|
3792
|
-
var feedbackIssueSchema =
|
|
5780
|
+
var feedbackIssueSchema = z10.string().trim().min(1).max(80).regex(
|
|
3793
5781
|
/^[a-z0-9][a-z0-9_-]*$/,
|
|
3794
5782
|
"Issue codes must use lowercase letters, numbers, underscores, or hyphens"
|
|
3795
5783
|
);
|
|
3796
|
-
var valuableSourceSchema =
|
|
3797
|
-
url:
|
|
3798
|
-
reason:
|
|
5784
|
+
var valuableSourceSchema = z10.object({
|
|
5785
|
+
url: z10.string().url(),
|
|
5786
|
+
reason: z10.string().max(1e3).optional()
|
|
3799
5787
|
});
|
|
3800
|
-
var missingContentSchema =
|
|
3801
|
-
topic:
|
|
3802
|
-
description:
|
|
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()
|
|
3803
5791
|
});
|
|
3804
5792
|
var FEEDBACK_DISABLED_VALUES = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
3805
5793
|
function feedbackEnvEnabled(...keys) {
|
|
@@ -3838,24 +5826,24 @@ Records schema-validated quality feedback for a prior \`firecrawl_search\` UUID
|
|
|
3838
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.
|
|
3839
5827
|
`,
|
|
3840
5828
|
outputSchema: feedbackOutputSchema,
|
|
3841
|
-
parameters:
|
|
3842
|
-
searchId:
|
|
3843
|
-
rating:
|
|
3844
|
-
valuableSources:
|
|
3845
|
-
|
|
3846
|
-
url:
|
|
3847
|
-
reason:
|
|
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()
|
|
3848
5836
|
})
|
|
3849
5837
|
).max(50).optional(),
|
|
3850
|
-
missingContent:
|
|
3851
|
-
|
|
3852
|
-
topic:
|
|
3853
|
-
description:
|
|
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()
|
|
3854
5842
|
})
|
|
3855
5843
|
).max(20).optional().describe(
|
|
3856
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`."
|
|
3857
5845
|
),
|
|
3858
|
-
querySuggestions:
|
|
5846
|
+
querySuggestions: z10.string().max(2e3).optional()
|
|
3859
5847
|
}),
|
|
3860
5848
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
3861
5849
|
const origin = requestOrigin(mcpClient, session);
|
|
@@ -3952,25 +5940,25 @@ if (alexandriaFeedbackAvailable()) {
|
|
|
3952
5940
|
description: `
|
|
3953
5941
|
Submit concise quality feedback for a completed search, scrape, parse, or map job. Provide the endpoint, job ID, rating, and relevant issue codes or small contextual fields; omit large page contents and raw outputs.
|
|
3954
5942
|
|
|
3955
|
-
For an Alexandria session, set endpoint to \`alexandria\`, omit jobId, and provide requestedWebsite (url and requestedFunctionality), rationale, and rating. Optional providerFeedback and capabilityFeedback describe gaps or errors. Capability issues: new_capability_request (requires requestedFunctionality), missing_capability, insufficient_functionality, incorrect_result, execution_error, other. Alexandria feedback has no job-age deadline and no credit refund.
|
|
5943
|
+
For an Alexandria session, set endpoint to \`alexandria\`, omit jobId, and provide requestedWebsite (url and requestedFunctionality), objective, rationale, and rating. objective is the underlying goal behind the session: what you or your user were ultimately trying to accomplish (for example, "shortlist federal IT contracts to bid on this quarter"), not only what was needed from this website. Optional providerFeedback and capabilityFeedback describe gaps or errors. Capability issues: new_capability_request (requires requestedFunctionality), missing_capability, insufficient_functionality, incorrect_result, execution_error, other. Alexandria feedback has no job-age deadline and no credit refund.
|
|
3956
5944
|
|
|
3957
5945
|
Returns submission status, feedback ID, and accounting fields.
|
|
3958
5946
|
`,
|
|
3959
5947
|
outputSchema: feedbackOutputSchema,
|
|
3960
|
-
parameters:
|
|
3961
|
-
endpoint:
|
|
3962
|
-
jobId:
|
|
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(),
|
|
3963
5951
|
...alexandriaFeedbackFields,
|
|
3964
|
-
rating:
|
|
3965
|
-
issues:
|
|
3966
|
-
tags:
|
|
3967
|
-
note:
|
|
3968
|
-
valuableSources:
|
|
3969
|
-
missingContent:
|
|
3970
|
-
querySuggestions:
|
|
3971
|
-
url:
|
|
3972
|
-
pageNumbers:
|
|
3973
|
-
metadata:
|
|
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()
|
|
3974
5962
|
}).superRefine((value, ctx) => {
|
|
3975
5963
|
if (value.endpoint === "alexandria") {
|
|
3976
5964
|
const parsed = alexandriaSessionFeedbackSchema.safeParse(value);
|
|
@@ -4082,25 +6070,25 @@ Start a multi-page crawl at a website URL, poll it to a terminal state, and retu
|
|
|
4082
6070
|
|
|
4083
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.
|
|
4084
6072
|
`,
|
|
4085
|
-
parameters:
|
|
4086
|
-
url:
|
|
4087
|
-
prompt:
|
|
4088
|
-
excludePaths:
|
|
4089
|
-
includePaths:
|
|
4090
|
-
maxDiscoveryDepth:
|
|
4091
|
-
sitemap:
|
|
4092
|
-
limit:
|
|
4093
|
-
allowExternalLinks:
|
|
4094
|
-
allowSubdomains:
|
|
4095
|
-
crawlEntireDomain:
|
|
4096
|
-
delay:
|
|
4097
|
-
maxConcurrency:
|
|
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(),
|
|
4098
6086
|
...SAFE_MODE ? {} : {
|
|
4099
|
-
webhook:
|
|
4100
|
-
webhookHeaders:
|
|
6087
|
+
webhook: z10.string().optional(),
|
|
6088
|
+
webhookHeaders: z10.record(z10.string(), z10.string()).optional()
|
|
4101
6089
|
},
|
|
4102
|
-
deduplicateSimilarURLs:
|
|
4103
|
-
ignoreQueryParameters:
|
|
6090
|
+
deduplicateSimilarURLs: z10.boolean().optional(),
|
|
6091
|
+
ignoreQueryParameters: z10.boolean().optional(),
|
|
4104
6092
|
scrapeOptions: scrapeParamsSchema.omit({ url: true }).partial().optional()
|
|
4105
6093
|
}),
|
|
4106
6094
|
outputSchema: crawlOutputSchema,
|
|
@@ -4157,7 +6145,7 @@ server.addTool({
|
|
|
4157
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.
|
|
4158
6146
|
`,
|
|
4159
6147
|
outputSchema: crawlOutputSchema,
|
|
4160
|
-
parameters:
|
|
6148
|
+
parameters: z10.object({ id: z10.string() }),
|
|
4161
6149
|
execute: async (args2, {
|
|
4162
6150
|
session,
|
|
4163
6151
|
client: mcpClient
|
|
@@ -4184,13 +6172,13 @@ server.addTool({
|
|
|
4184
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.
|
|
4185
6173
|
`,
|
|
4186
6174
|
outputSchema: deprecatedToolOutputSchema,
|
|
4187
|
-
parameters:
|
|
4188
|
-
urls:
|
|
4189
|
-
prompt:
|
|
4190
|
-
schema:
|
|
4191
|
-
allowExternalLinks:
|
|
4192
|
-
enableWebSearch:
|
|
4193
|
-
includeSubdomains:
|
|
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()
|
|
4194
6182
|
}),
|
|
4195
6183
|
canList: () => false,
|
|
4196
6184
|
beforeValidate: () => {
|
|
@@ -4203,27 +6191,27 @@ Deprecated compatibility entry point. Use firecrawl_scrape once per known URL wi
|
|
|
4203
6191
|
},
|
|
4204
6192
|
execute: async () => {
|
|
4205
6193
|
const payload = deprecatedExtractPayload();
|
|
4206
|
-
throw new
|
|
6194
|
+
throw new UserError(payload.message, payload);
|
|
4207
6195
|
}
|
|
4208
6196
|
});
|
|
4209
|
-
var agentExchangeSchema =
|
|
4210
|
-
enabled:
|
|
4211
|
-
toolkits:
|
|
4212
|
-
maxCalls:
|
|
4213
|
-
requireApproval:
|
|
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(
|
|
4214
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.'
|
|
4215
6203
|
),
|
|
4216
|
-
approve:
|
|
4217
|
-
approvalId:
|
|
4218
|
-
callIds:
|
|
4219
|
-
always:
|
|
6204
|
+
approve: z10.strictObject({
|
|
6205
|
+
approvalId: z10.string().uuid(),
|
|
6206
|
+
callIds: z10.array(z10.string()).optional(),
|
|
6207
|
+
always: z10.boolean().optional()
|
|
4220
6208
|
}).optional().describe(
|
|
4221
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."
|
|
4222
6210
|
),
|
|
4223
|
-
decline:
|
|
6211
|
+
decline: z10.strictObject({ approvalId: z10.string().uuid() }).optional().describe(
|
|
4224
6212
|
"Answer no to that pendingApproval. Needs threadId. A declined terms offer keeps those providers out of the rest of the thread."
|
|
4225
6213
|
),
|
|
4226
|
-
onTermsRequired:
|
|
6214
|
+
onTermsRequired: z10.enum(["skip", "ask"]).optional().describe(
|
|
4227
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.`
|
|
4228
6216
|
)
|
|
4229
6217
|
}).describe(
|
|
@@ -4250,21 +6238,21 @@ The job also returns a \`threadId\`. To continue that thread, pass it with a fol
|
|
|
4250
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.
|
|
4251
6239
|
`,
|
|
4252
6240
|
outputSchema: agentOutputSchema,
|
|
4253
|
-
parameters:
|
|
4254
|
-
prompt:
|
|
4255
|
-
urls:
|
|
4256
|
-
schema:
|
|
4257
|
-
effort:
|
|
4258
|
-
maxCredits:
|
|
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(
|
|
4259
6247
|
"Spending limit in credits for this run. Defaults to 2500 on the API."
|
|
4260
6248
|
),
|
|
4261
|
-
strictConstrainToURLs:
|
|
6249
|
+
strictConstrainToURLs: z10.boolean().optional().describe(
|
|
4262
6250
|
"If true, agent will only visit URLs provided in the urls array."
|
|
4263
6251
|
),
|
|
4264
|
-
threadId:
|
|
6252
|
+
threadId: z10.string().uuid().optional().describe(
|
|
4265
6253
|
"Continue this thread: the threadId from an earlier firecrawl_agent or firecrawl_agent_status result. Omit to start a new thread."
|
|
4266
6254
|
),
|
|
4267
|
-
mode:
|
|
6255
|
+
mode: z10.enum(["extract", "chat"]).optional().describe(
|
|
4268
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.`
|
|
4269
6257
|
),
|
|
4270
6258
|
exchange: agentExchangeSchema.optional()
|
|
@@ -4334,7 +6322,7 @@ Retrieve progress or final results for a \`firecrawl_agent\` job ID. A \`process
|
|
|
4334
6322
|
Returns job status, progress information, and result data when completed.
|
|
4335
6323
|
`,
|
|
4336
6324
|
outputSchema: agentStatusOutputSchema,
|
|
4337
|
-
parameters:
|
|
6325
|
+
parameters: z10.object({ id: z10.string() }),
|
|
4338
6326
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
4339
6327
|
const client = getClient(session);
|
|
4340
6328
|
const { id } = args2;
|
|
@@ -4363,13 +6351,13 @@ Open or reuse a live browser session to navigate a page, click controls, fill fi
|
|
|
4363
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.
|
|
4364
6352
|
`,
|
|
4365
6353
|
outputSchema: interactOutputSchema,
|
|
4366
|
-
parameters:
|
|
4367
|
-
scrapeId:
|
|
4368
|
-
url:
|
|
4369
|
-
prompt:
|
|
4370
|
-
code:
|
|
4371
|
-
language:
|
|
4372
|
-
timeout:
|
|
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(),
|
|
4373
6361
|
scrapeOptions: scrapeParamsSchema.omit({ url: true }).partial().optional()
|
|
4374
6362
|
}).refine((data) => Boolean(data.scrapeId) !== Boolean(data.url), {
|
|
4375
6363
|
message: "Provide either 'url' (interact directly) or 'scrapeId' (reuse a previous scrape), not both."
|
|
@@ -4448,8 +6436,8 @@ server.addTool({
|
|
|
4448
6436
|
Stop the live interact session associated with a \`scrapeId\` and release its resources. Returns a success confirmation.
|
|
4449
6437
|
`,
|
|
4450
6438
|
outputSchema: interactStopOutputSchema,
|
|
4451
|
-
parameters:
|
|
4452
|
-
scrapeId:
|
|
6439
|
+
parameters: z10.object({
|
|
6440
|
+
scrapeId: z10.string()
|
|
4453
6441
|
}),
|
|
4454
6442
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
4455
6443
|
const client = getClient(session);
|
|
@@ -4573,7 +6561,7 @@ ${ALEXANDRIA_SEARCH_INSTRUCTIONS}
|
|
|
4573
6561
|
Returns result groups in \`data\` and an operation \`id\`.
|
|
4574
6562
|
`,
|
|
4575
6563
|
outputSchema: searchOutputSchema,
|
|
4576
|
-
parameters:
|
|
6564
|
+
parameters: z10.object({ ...searchToolBaseFields }).strict().refine(searchDomainsAreExclusive, SEARCH_DOMAINS_CONFLICT_MESSAGE).refine(
|
|
4577
6565
|
searchQueryIsValid,
|
|
4578
6566
|
"A query is required. Use firecrawl_find_tools to browse the catalogue."
|
|
4579
6567
|
),
|