firecrawl-mcp 3.29.0 → 3.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -38
- package/dist/index.js +95 -553
- package/dist/server.d.ts +30 -8
- package/dist/server.js +51 -223
- package/package.json +1 -1
- package/dist/agent-hints.js +0 -46
package/dist/index.js
CHANGED
|
@@ -3,17 +3,6 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import dotenv from "dotenv";
|
|
5
5
|
|
|
6
|
-
// src/headers.ts
|
|
7
|
-
function normalizeHeader(value) {
|
|
8
|
-
if (value == null) return void 0;
|
|
9
|
-
const v = Array.isArray(value) ? value[0] : value;
|
|
10
|
-
const trimmed = typeof v === "string" ? v.trim() : "";
|
|
11
|
-
return trimmed || void 0;
|
|
12
|
-
}
|
|
13
|
-
function withoutTrailingSlash(value) {
|
|
14
|
-
return value.replace(/\/+$/, "");
|
|
15
|
-
}
|
|
16
|
-
|
|
17
6
|
// src/alexandria-feedback.ts
|
|
18
7
|
import { z } from "zod";
|
|
19
8
|
var detail = z.string().trim().min(1).max(2e3);
|
|
@@ -96,7 +85,7 @@ var alexandriaSessionFeedbackSchema = z.strictObject({
|
|
|
96
85
|
// src/server.ts
|
|
97
86
|
import FirecrawlApp from "firecrawl";
|
|
98
87
|
|
|
99
|
-
// node_modules/.pnpm/fastmcp@4.3.2_patch_hash=
|
|
88
|
+
// node_modules/.pnpm/fastmcp@4.3.2_patch_hash=37f9a424ae5b0b6202b2b16109a1ea88b69cdcda5407f13eda6654a56489ad70/node_modules/fastmcp/dist/chunk-LWU5CQGW.js
|
|
100
89
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
101
90
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
102
91
|
import {
|
|
@@ -900,6 +889,7 @@ ${error2 instanceof Error ? error2.stack : JSON.stringify(error2)}`
|
|
|
900
889
|
// Top-level Tool.title (MCP 2025-06-18). Clients such as Codex index this
|
|
901
890
|
// for tool search and do not read annotations.title.
|
|
902
891
|
...tool.annotations?.title && { title: tool.annotations.title },
|
|
892
|
+
...tool.icons && { icons: tool.icons },
|
|
903
893
|
description: tool.description,
|
|
904
894
|
inputSchema: tool.parameters ? strictJsonSchema(await toJsonSchema(tool.parameters)) : {
|
|
905
895
|
additionalProperties: false,
|
|
@@ -2075,7 +2065,7 @@ var FastMCP = class extends FastMCPEventEmitter {
|
|
|
2075
2065
|
}
|
|
2076
2066
|
};
|
|
2077
2067
|
|
|
2078
|
-
// node_modules/.pnpm/fastmcp@4.3.2_patch_hash=
|
|
2068
|
+
// node_modules/.pnpm/fastmcp@4.3.2_patch_hash=37f9a424ae5b0b6202b2b16109a1ea88b69cdcda5407f13eda6654a56489ad70/node_modules/fastmcp/dist/chunk-OJG4XYXA.js
|
|
2079
2069
|
import { z as z3 } from "zod";
|
|
2080
2070
|
import { createHmac as createHmac2, pbkdf2, randomBytes } from "crypto";
|
|
2081
2071
|
import { promisify } from "util";
|
|
@@ -2088,45 +2078,6 @@ import { randomUUID as randomUUID2 } from "crypto";
|
|
|
2088
2078
|
import path from "path";
|
|
2089
2079
|
import { z as z12 } from "zod";
|
|
2090
2080
|
|
|
2091
|
-
// src/agent-hints.ts
|
|
2092
|
-
var AGENT_HINTS_HEADERS = { "X-Firecrawl-Agent-Hints": "true" };
|
|
2093
|
-
function readAgentHints(value) {
|
|
2094
|
-
if (!value || typeof value !== "object") return void 0;
|
|
2095
|
-
const hints = value.agent_hints;
|
|
2096
|
-
return Array.isArray(hints) && hints.every((hint) => typeof hint === "string") ? hints : void 0;
|
|
2097
|
-
}
|
|
2098
|
-
function preserveAgentHints(data, envelope) {
|
|
2099
|
-
const hints = readAgentHints(envelope);
|
|
2100
|
-
if (!hints) return data;
|
|
2101
|
-
return data && typeof data === "object" && !Array.isArray(data) ? { ...data, agent_hints: hints } : { data, agent_hints: hints };
|
|
2102
|
-
}
|
|
2103
|
-
function agentHintsText(hints) {
|
|
2104
|
-
return `Firecrawl API agent_hints (response guidance, separate from page content):
|
|
2105
|
-
${JSON.stringify(hints, null, 2)}`;
|
|
2106
|
-
}
|
|
2107
|
-
function withAgentHints(result, response, separateText = false) {
|
|
2108
|
-
const hints = readAgentHints(response);
|
|
2109
|
-
if (!hints) return result;
|
|
2110
|
-
return {
|
|
2111
|
-
...result,
|
|
2112
|
-
content: separateText && hints.length ? [...result.content, { type: "text", text: agentHintsText(hints) }] : result.content,
|
|
2113
|
-
structuredContent: { ...result.structuredContent, agent_hints: hints }
|
|
2114
|
-
};
|
|
2115
|
-
}
|
|
2116
|
-
function readErrorAgentHints(error2) {
|
|
2117
|
-
const hints = readAgentHints(error2);
|
|
2118
|
-
if (hints) return hints;
|
|
2119
|
-
if (!error2 || typeof error2 !== "object") return void 0;
|
|
2120
|
-
const extras = error2.extras;
|
|
2121
|
-
const extrasHints = readAgentHints(extras);
|
|
2122
|
-
if (extrasHints) return extrasHints;
|
|
2123
|
-
const details = error2.details;
|
|
2124
|
-
const detailsHints = readAgentHints(details);
|
|
2125
|
-
if (detailsHints) return detailsHints;
|
|
2126
|
-
const response = error2.response;
|
|
2127
|
-
return readAgentHints(response?.data);
|
|
2128
|
-
}
|
|
2129
|
-
|
|
2130
2081
|
// src/tool-output.ts
|
|
2131
2082
|
import { z as z4 } from "zod";
|
|
2132
2083
|
function isRecord(value) {
|
|
@@ -2155,9 +2106,6 @@ var unknown = (description) => z4.unknown().optional().describe(description);
|
|
|
2155
2106
|
var success = bool("Whether the API call succeeded.");
|
|
2156
2107
|
var error = unknown("Error message or error object when the call did not succeed.");
|
|
2157
2108
|
var warning = str("Non-fatal warning about the result.");
|
|
2158
|
-
var agentHints = {
|
|
2159
|
-
agent_hints: z4.array(z4.string()).optional().describe("Optional response guidance from the Firecrawl API.")
|
|
2160
|
-
};
|
|
2161
2109
|
var alexandriaEnvelope = {
|
|
2162
2110
|
requestId: str("Identifier of this Alexandria execution."),
|
|
2163
2111
|
scrape_id: str("Identifier of the underlying scrape."),
|
|
@@ -2175,7 +2123,6 @@ var alexandriaEnvelope = {
|
|
|
2175
2123
|
feedbackTool: unknown("Pointer to the feedback tool for reporting how this result served the task.")
|
|
2176
2124
|
};
|
|
2177
2125
|
var scrapeOutputSchema = z4.object({
|
|
2178
|
-
...agentHints,
|
|
2179
2126
|
// URL mode: the scraped document, with the formats that were requested.
|
|
2180
2127
|
markdown: str("Page content as markdown."),
|
|
2181
2128
|
html: str("Processed HTML of the page."),
|
|
@@ -2207,7 +2154,6 @@ var scrapeOutputSchema = z4.object({
|
|
|
2207
2154
|
...alexandriaEnvelope
|
|
2208
2155
|
}).describe("A scraped document, or the Alexandria execution envelope when `alexandria` was passed.");
|
|
2209
2156
|
var mapOutputSchema = z4.object({
|
|
2210
|
-
...agentHints,
|
|
2211
2157
|
links: unknown("URLs discovered under the website."),
|
|
2212
2158
|
success,
|
|
2213
2159
|
error,
|
|
@@ -2215,7 +2161,6 @@ var mapOutputSchema = z4.object({
|
|
|
2215
2161
|
id: str("Identifier of the map run, for optional map feedback.")
|
|
2216
2162
|
}).describe("URLs indexed under the requested website.");
|
|
2217
2163
|
var searchOutputSchema = z4.object({
|
|
2218
|
-
...agentHints,
|
|
2219
2164
|
success,
|
|
2220
2165
|
data: unknown("Ranked results grouped by source, such as `web`, `news`, `images`, and `alexandria`."),
|
|
2221
2166
|
error,
|
|
@@ -2227,14 +2172,12 @@ var searchOutputSchema = z4.object({
|
|
|
2227
2172
|
feedbackTool: unknown("Pointer to the feedback tool for this search.")
|
|
2228
2173
|
}).describe("Ranked search results grouped by source.");
|
|
2229
2174
|
var findToolsOutputSchema = z4.object({
|
|
2230
|
-
...agentHints,
|
|
2231
2175
|
success,
|
|
2232
2176
|
data: unknown("Discovery page in `data.alexandria[0].data`, with `level`, `items`, `total`, and an optional `nextTool`."),
|
|
2233
2177
|
error,
|
|
2234
2178
|
...alexandriaEnvelope
|
|
2235
2179
|
}).describe("One page of the Alexandria catalogue: categories, providers, tools, or one contract.");
|
|
2236
2180
|
var feedbackOutputSchema = z4.object({
|
|
2237
|
-
...agentHints,
|
|
2238
2181
|
success,
|
|
2239
2182
|
error,
|
|
2240
2183
|
status: num("HTTP status when the submission was rejected."),
|
|
@@ -2253,7 +2196,6 @@ var feedbackOutputSchema = z4.object({
|
|
|
2253
2196
|
raw: str("Response body when it was not JSON.")
|
|
2254
2197
|
}).describe("Result of submitting feedback.");
|
|
2255
2198
|
var crawlOutputSchema = z4.object({
|
|
2256
|
-
...agentHints,
|
|
2257
2199
|
success,
|
|
2258
2200
|
error,
|
|
2259
2201
|
warning,
|
|
@@ -2268,7 +2210,6 @@ var crawlOutputSchema = z4.object({
|
|
|
2268
2210
|
data: unknown("Scraped documents collected by the crawl.")
|
|
2269
2211
|
}).describe("Crawl job state and the documents collected so far.");
|
|
2270
2212
|
var agentOutputSchema = z4.object({
|
|
2271
|
-
...agentHints,
|
|
2272
2213
|
success,
|
|
2273
2214
|
error,
|
|
2274
2215
|
id: str("Agent job identifier, for `firecrawl_agent_status`."),
|
|
@@ -2277,7 +2218,6 @@ var agentOutputSchema = z4.object({
|
|
|
2277
2218
|
threadTurn: num("Turn number of this job within its thread.")
|
|
2278
2219
|
}).describe("The queued research job. The result is read with `firecrawl_agent_status`.");
|
|
2279
2220
|
var agentStatusOutputSchema = z4.object({
|
|
2280
|
-
...agentHints,
|
|
2281
2221
|
success,
|
|
2282
2222
|
error,
|
|
2283
2223
|
id: str("Agent job identifier."),
|
|
@@ -2305,7 +2245,6 @@ var agentStatusOutputSchema = z4.object({
|
|
|
2305
2245
|
)
|
|
2306
2246
|
}).describe("Progress or final result of a research agent job.");
|
|
2307
2247
|
var interactOutputSchema = z4.object({
|
|
2308
|
-
...agentHints,
|
|
2309
2248
|
success,
|
|
2310
2249
|
error: unknown("Why the interaction or the session could not run."),
|
|
2311
2250
|
url: str("The URL the session was opened from, when opening failed."),
|
|
@@ -2320,14 +2259,12 @@ var interactOutputSchema = z4.object({
|
|
|
2320
2259
|
liveUrl: str("URL for viewing the live session.")
|
|
2321
2260
|
}).describe("Result of a browser interaction, with the session identifier to continue or stop it.");
|
|
2322
2261
|
var interactStopOutputSchema = z4.object({
|
|
2323
|
-
...agentHints,
|
|
2324
2262
|
success,
|
|
2325
2263
|
error,
|
|
2326
2264
|
message: str("Confirmation that the session was stopped."),
|
|
2327
2265
|
scrapeId: str("Session that was stopped.")
|
|
2328
2266
|
}).describe("Confirmation that the interact session was stopped.");
|
|
2329
2267
|
var parseOutputSchema = z4.object({
|
|
2330
|
-
...agentHints,
|
|
2331
2268
|
success,
|
|
2332
2269
|
error,
|
|
2333
2270
|
warning,
|
|
@@ -2355,39 +2292,33 @@ var deprecatedToolOutputSchema = z4.object({
|
|
|
2355
2292
|
docs_url: str("Documentation for the replacement.")
|
|
2356
2293
|
}).describe("A pointer to the replacement tool. This entry point is deprecated and always errors.");
|
|
2357
2294
|
var monitorOutputSchema = z4.object({
|
|
2358
|
-
...agentHints,
|
|
2359
2295
|
success,
|
|
2360
2296
|
error,
|
|
2361
2297
|
data: unknown("The monitor record: configuration, schedule, targets, and current state.")
|
|
2362
2298
|
}).describe("One monitor record.");
|
|
2363
2299
|
var monitorListOutputSchema = z4.object({
|
|
2364
|
-
...agentHints,
|
|
2365
2300
|
success,
|
|
2366
2301
|
error,
|
|
2367
2302
|
data: unknown("One page of monitor records."),
|
|
2368
2303
|
pagination: unknown("Pagination metadata for the listing.")
|
|
2369
2304
|
}).describe("One page of monitors for the authenticated account.");
|
|
2370
2305
|
var monitorDeleteOutputSchema = z4.object({
|
|
2371
|
-
...agentHints,
|
|
2372
2306
|
success,
|
|
2373
2307
|
error,
|
|
2374
2308
|
data: unknown("Deletion details, when the API returns any.")
|
|
2375
2309
|
}).describe("Deletion status for the monitor.");
|
|
2376
2310
|
var monitorRunOutputSchema = z4.object({
|
|
2377
|
-
...agentHints,
|
|
2378
2311
|
success,
|
|
2379
2312
|
error,
|
|
2380
2313
|
data: unknown("The queued check.")
|
|
2381
2314
|
}).describe("The check queued outside the monitor schedule.");
|
|
2382
2315
|
var monitorChecksOutputSchema = z4.object({
|
|
2383
|
-
...agentHints,
|
|
2384
2316
|
success,
|
|
2385
2317
|
error,
|
|
2386
2318
|
data: unknown("One page of check summaries."),
|
|
2387
2319
|
pagination: unknown("Pagination metadata for the listing.")
|
|
2388
2320
|
}).describe("One page of historical checks for the monitor.");
|
|
2389
2321
|
var monitorCheckOutputSchema = z4.object({
|
|
2390
|
-
...agentHints,
|
|
2391
2322
|
success,
|
|
2392
2323
|
error,
|
|
2393
2324
|
data: unknown("The check and its page-level results: each page reports `same`, `new`, `changed`, `removed`, or `error`, with diffs and any goal judgment."),
|
|
@@ -2406,25 +2337,20 @@ var paperSchema = z4.looseObject({
|
|
|
2406
2337
|
updateDate: str("Date the paper was last updated.")
|
|
2407
2338
|
});
|
|
2408
2339
|
var researchSearchOutputSchema = z4.object({
|
|
2409
|
-
...agentHints,
|
|
2410
2340
|
results: z4.array(paperSchema).describe("Ranked papers, in the order the text block lists them.")
|
|
2411
2341
|
}).describe("Ranked papers with canonical IDs, titles, authors, and abstracts.");
|
|
2412
2342
|
var researchPaperOutputSchema = z4.object({
|
|
2413
|
-
...agentHints,
|
|
2414
2343
|
paper: paperSchema.optional().describe("Canonical metadata for the requested paper, absent when it was not found.")
|
|
2415
2344
|
}).describe("Canonical metadata for one paper.");
|
|
2416
2345
|
var researchRelatedOutputSchema = z4.object({
|
|
2417
|
-
...agentHints,
|
|
2418
2346
|
results: z4.array(paperSchema).describe("Ranked citation-graph candidates."),
|
|
2419
2347
|
poolSize: z4.number().describe("Number of candidates evaluated before ranking."),
|
|
2420
2348
|
note: str("Note about how the candidates were produced.")
|
|
2421
2349
|
}).describe("Ranked citation-graph candidates and the size of the evaluated pool.");
|
|
2422
2350
|
var researchReadOutputSchema = z4.object({
|
|
2423
|
-
...agentHints,
|
|
2424
2351
|
passages: z4.array(z4.looseObject({ text: str("Passage text.") })).describe("In-body passages relevant to the question; empty when no full text is indexed.")
|
|
2425
2352
|
}).describe("Full-text passages from one paper.");
|
|
2426
2353
|
var developerSearchOutputSchema = z4.object({
|
|
2427
|
-
...agentHints,
|
|
2428
2354
|
results: z4.array(
|
|
2429
2355
|
z4.looseObject({
|
|
2430
2356
|
id: str("Stable result id, such as `issue:owner/repo#123` or `doc:<hash>`; its prefix gives the source type."),
|
|
@@ -2435,7 +2361,6 @@ var developerSearchOutputSchema = z4.object({
|
|
|
2435
2361
|
).describe("Ranked results, in the order the text block lists them.")
|
|
2436
2362
|
}).describe("Ranked developer-index results with their matched passages.");
|
|
2437
2363
|
var govSearchOutputSchema = z4.object({
|
|
2438
|
-
...agentHints,
|
|
2439
2364
|
results: z4.array(
|
|
2440
2365
|
z4.looseObject({
|
|
2441
2366
|
url: str("Source URL."),
|
|
@@ -2684,7 +2609,7 @@ Returns ranked results with an ID, source type, URL, title, and the matched pass
|
|
|
2684
2609
|
const client = getClient(session);
|
|
2685
2610
|
const res = await client.http.get(`${BASE}?${params.toString()}`, originHeaders(requestOrigin(mcpClient, session)));
|
|
2686
2611
|
const results = res.data?.results ?? [];
|
|
2687
|
-
return
|
|
2612
|
+
return withStructured(fmtDeveloper(results), { results });
|
|
2688
2613
|
}
|
|
2689
2614
|
});
|
|
2690
2615
|
}
|
|
@@ -2733,11 +2658,7 @@ Returns ranked results with a position, title, URL, and matched snippet. Results
|
|
|
2733
2658
|
originHeaders(requestOrigin(mcpClient, session))
|
|
2734
2659
|
);
|
|
2735
2660
|
const results = res.data?.data?.web ?? [];
|
|
2736
|
-
return
|
|
2737
|
-
withStructured(fmtGov(results), { results }),
|
|
2738
|
-
res.data,
|
|
2739
|
-
true
|
|
2740
|
-
);
|
|
2661
|
+
return withStructured(fmtGov(results), { results });
|
|
2741
2662
|
}
|
|
2742
2663
|
});
|
|
2743
2664
|
}
|
|
@@ -2748,12 +2669,10 @@ import { z as z8 } from "zod";
|
|
|
2748
2669
|
// src/core-http-error.ts
|
|
2749
2670
|
var CoreHttpError = class extends Error {
|
|
2750
2671
|
status;
|
|
2751
|
-
|
|
2752
|
-
constructor(message, status, agentHints2) {
|
|
2672
|
+
constructor(message, status) {
|
|
2753
2673
|
super(message);
|
|
2754
2674
|
this.name = "CoreHttpError";
|
|
2755
2675
|
this.status = status;
|
|
2756
|
-
if (agentHints2 !== void 0) this.agent_hints = agentHints2;
|
|
2757
2676
|
}
|
|
2758
2677
|
};
|
|
2759
2678
|
|
|
@@ -2776,7 +2695,6 @@ async function monitorRequest(api, session, origin, path2, init = {}) {
|
|
|
2776
2695
|
}
|
|
2777
2696
|
const headers = {
|
|
2778
2697
|
...originHeaders(origin),
|
|
2779
|
-
...AGENT_HINTS_HEADERS,
|
|
2780
2698
|
...extraHeaders
|
|
2781
2699
|
};
|
|
2782
2700
|
if (apiKey2) headers.Authorization = `Bearer ${apiKey2}`;
|
|
@@ -2789,13 +2707,10 @@ async function monitorRequest(api, session, origin, path2, init = {}) {
|
|
|
2789
2707
|
const payload = await response.json().catch(() => ({}));
|
|
2790
2708
|
if (!response.ok || payload?.success === false) {
|
|
2791
2709
|
const message = payload?.error || `HTTP ${response.status}: ${response.statusText || "Request failed"}`;
|
|
2792
|
-
throw new CoreHttpError(message, response.status
|
|
2710
|
+
throw new CoreHttpError(message, response.status);
|
|
2793
2711
|
}
|
|
2794
2712
|
return payload;
|
|
2795
2713
|
}
|
|
2796
|
-
function monitorResult(data) {
|
|
2797
|
-
return withAgentHints(structuredText(data), data);
|
|
2798
|
-
}
|
|
2799
2714
|
var pageStatusSchema = z8.enum(["same", "new", "changed", "removed", "error"]);
|
|
2800
2715
|
var checkStatusSchema = z8.enum([
|
|
2801
2716
|
"queued",
|
|
@@ -2921,7 +2836,7 @@ In the simple form, a \`goal\` is required. If \`queries\` contains one or more
|
|
|
2921
2836
|
body
|
|
2922
2837
|
}
|
|
2923
2838
|
);
|
|
2924
|
-
return
|
|
2839
|
+
return structuredText(res);
|
|
2925
2840
|
}
|
|
2926
2841
|
});
|
|
2927
2842
|
server.addTool({
|
|
@@ -2954,7 +2869,7 @@ List monitors for the authenticated account with optional pagination controls. R
|
|
|
2954
2869
|
query: { limit, offset }
|
|
2955
2870
|
}
|
|
2956
2871
|
);
|
|
2957
|
-
return
|
|
2872
|
+
return structuredText(res);
|
|
2958
2873
|
}
|
|
2959
2874
|
});
|
|
2960
2875
|
server.addTool({
|
|
@@ -2981,7 +2896,7 @@ Retrieve one monitor by ID, including its configuration and current state. This
|
|
|
2981
2896
|
requestOrigin(mcpClient, session),
|
|
2982
2897
|
`/monitor/${encodeURIComponent(id)}`
|
|
2983
2898
|
);
|
|
2984
|
-
return
|
|
2899
|
+
return structuredText(res);
|
|
2985
2900
|
}
|
|
2986
2901
|
});
|
|
2987
2902
|
server.addTool({
|
|
@@ -3014,7 +2929,7 @@ Returns the updated monitor.
|
|
|
3014
2929
|
`/monitor/${encodeURIComponent(id)}`,
|
|
3015
2930
|
{ method: "PATCH", body }
|
|
3016
2931
|
);
|
|
3017
|
-
return
|
|
2932
|
+
return structuredText(res);
|
|
3018
2933
|
}
|
|
3019
2934
|
});
|
|
3020
2935
|
server.addTool({
|
|
@@ -3043,7 +2958,7 @@ Permanently delete a monitor by ID and stop its future schedule. This operation
|
|
|
3043
2958
|
`/monitor/${encodeURIComponent(id)}`,
|
|
3044
2959
|
{ method: "DELETE" }
|
|
3045
2960
|
);
|
|
3046
|
-
return
|
|
2961
|
+
return structuredText(res);
|
|
3047
2962
|
}
|
|
3048
2963
|
});
|
|
3049
2964
|
server.addTool({
|
|
@@ -3071,7 +2986,7 @@ Queue an immediate check for a monitor outside its normal schedule. This starts
|
|
|
3071
2986
|
`/monitor/${encodeURIComponent(id)}/run`,
|
|
3072
2987
|
{ method: "POST" }
|
|
3073
2988
|
);
|
|
3074
|
-
return
|
|
2989
|
+
return structuredText(res);
|
|
3075
2990
|
}
|
|
3076
2991
|
});
|
|
3077
2992
|
server.addTool({
|
|
@@ -3104,7 +3019,7 @@ List historical checks for a monitor, optionally filtered by status and bounded
|
|
|
3104
3019
|
`/monitor/${encodeURIComponent(id)}/checks`,
|
|
3105
3020
|
{ query: { limit, offset, status } }
|
|
3106
3021
|
);
|
|
3107
|
-
return
|
|
3022
|
+
return structuredText(res);
|
|
3108
3023
|
}
|
|
3109
3024
|
});
|
|
3110
3025
|
server.addTool({
|
|
@@ -3140,7 +3055,7 @@ Markdown tracking returns a unified text diff, JSON tracking returns field paths
|
|
|
3140
3055
|
`/monitor/${encodeURIComponent(id)}/checks/${encodeURIComponent(checkId)}`,
|
|
3141
3056
|
{ query: { limit, skip, status: pageStatus } }
|
|
3142
3057
|
);
|
|
3143
|
-
return
|
|
3058
|
+
return structuredText(res);
|
|
3144
3059
|
}
|
|
3145
3060
|
});
|
|
3146
3061
|
}
|
|
@@ -3148,9 +3063,6 @@ Markdown tracking returns a unified text diff, JSON tracking returns field paths
|
|
|
3148
3063
|
// src/research.ts
|
|
3149
3064
|
import { z as z9 } from "zod";
|
|
3150
3065
|
var BASE3 = "/v2/search/research";
|
|
3151
|
-
function researchHeaders(origin) {
|
|
3152
|
-
return { ...originHeaders(origin), ...AGENT_HINTS_HEADERS };
|
|
3153
|
-
}
|
|
3154
3066
|
function appendParam(params, key, value) {
|
|
3155
3067
|
if (value == null) return;
|
|
3156
3068
|
if (Array.isArray(value)) {
|
|
@@ -3295,10 +3207,10 @@ Returns ranked papers with canonical IDs, titles, authors, and abstracts.
|
|
|
3295
3207
|
const client = getClient(session);
|
|
3296
3208
|
const res = await client.http.get(
|
|
3297
3209
|
withQuery(`${BASE3}/papers`, params),
|
|
3298
|
-
|
|
3210
|
+
originHeaders(requestOrigin(mcpClient, session))
|
|
3299
3211
|
);
|
|
3300
3212
|
const results = res.data?.results ?? [];
|
|
3301
|
-
return
|
|
3213
|
+
return withStructured(fmtHits(results), { results });
|
|
3302
3214
|
}
|
|
3303
3215
|
});
|
|
3304
3216
|
server.addTool({
|
|
@@ -3326,13 +3238,13 @@ Retrieve canonical metadata for one paper ID, such as an arXiv, PMC, PMID, or DO
|
|
|
3326
3238
|
const client = getClient(session);
|
|
3327
3239
|
const res = await client.http.get(
|
|
3328
3240
|
`${BASE3}/papers/${encodeURIComponent(paperId)}`,
|
|
3329
|
-
|
|
3241
|
+
originHeaders(requestOrigin(mcpClient, session))
|
|
3330
3242
|
);
|
|
3331
3243
|
const paper = res.data?.paper;
|
|
3332
|
-
return
|
|
3244
|
+
return withStructured(
|
|
3333
3245
|
fmtPaperMetadata(paper),
|
|
3334
3246
|
paper ? { paper } : {}
|
|
3335
|
-
)
|
|
3247
|
+
);
|
|
3336
3248
|
}
|
|
3337
3249
|
});
|
|
3338
3250
|
server.addTool({
|
|
@@ -3374,17 +3286,17 @@ Returns ranked candidates and the evaluated pool size.
|
|
|
3374
3286
|
`${BASE3}/papers/${encodeURIComponent(primary)}/similar`,
|
|
3375
3287
|
params
|
|
3376
3288
|
),
|
|
3377
|
-
|
|
3289
|
+
originHeaders(requestOrigin(mcpClient, session))
|
|
3378
3290
|
);
|
|
3379
3291
|
const results = res.data?.results ?? [];
|
|
3380
3292
|
const poolSize = res.data?.poolSize ?? 0;
|
|
3381
3293
|
const note = res.data?.note ? `
|
|
3382
3294
|
note: ${res.data.note}` : "";
|
|
3383
|
-
return
|
|
3295
|
+
return withStructured(
|
|
3384
3296
|
`${fmtHits(results)}
|
|
3385
3297
|
(poolSize=${poolSize})${note}`,
|
|
3386
3298
|
{ results, poolSize, ...res.data?.note ? { note: res.data.note } : {} }
|
|
3387
|
-
)
|
|
3299
|
+
);
|
|
3388
3300
|
}
|
|
3389
3301
|
});
|
|
3390
3302
|
server.addTool({
|
|
@@ -3419,13 +3331,13 @@ Returns matching passages or a notice when full text is unavailable.
|
|
|
3419
3331
|
const client = getClient(session);
|
|
3420
3332
|
const res = await client.http.get(
|
|
3421
3333
|
withQuery(`${BASE3}/papers/${encodeURIComponent(paperId)}`, params),
|
|
3422
|
-
|
|
3334
|
+
originHeaders(requestOrigin(mcpClient, session))
|
|
3423
3335
|
);
|
|
3424
3336
|
const passages = res.data?.passages ?? [];
|
|
3425
|
-
return
|
|
3337
|
+
return withStructured(
|
|
3426
3338
|
passages.length ? passages.map((p) => p.text).join("\n---\n") : "(no full-text passages available for this paper)",
|
|
3427
3339
|
{ passages }
|
|
3428
|
-
)
|
|
3340
|
+
);
|
|
3429
3341
|
}
|
|
3430
3342
|
});
|
|
3431
3343
|
server.addTool({
|
|
@@ -3530,6 +3442,17 @@ The historical view returns periods sorted by start date with \`startDate\`, \`e
|
|
|
3530
3442
|
});
|
|
3531
3443
|
}
|
|
3532
3444
|
|
|
3445
|
+
// src/headers.ts
|
|
3446
|
+
function normalizeHeader(value) {
|
|
3447
|
+
if (value == null) return void 0;
|
|
3448
|
+
const v = Array.isArray(value) ? value[0] : value;
|
|
3449
|
+
const trimmed = typeof v === "string" ? v.trim() : "";
|
|
3450
|
+
return trimmed || void 0;
|
|
3451
|
+
}
|
|
3452
|
+
function withoutTrailingSlash(value) {
|
|
3453
|
+
return value.replace(/\/+$/, "");
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3533
3456
|
// src/recovery.ts
|
|
3534
3457
|
import { randomUUID } from "crypto";
|
|
3535
3458
|
|
|
@@ -3640,20 +3563,6 @@ async function runWithCredentialRecovery(run, requestId, session) {
|
|
|
3640
3563
|
return await run();
|
|
3641
3564
|
} catch (error2) {
|
|
3642
3565
|
if (session?.authType !== "api-key" || !isCoreCredentialRejection(error2, session)) {
|
|
3643
|
-
const hints = readErrorAgentHints(error2);
|
|
3644
|
-
if (hints) {
|
|
3645
|
-
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
3646
|
-
throw new UserError(
|
|
3647
|
-
hints.length ? `${message}
|
|
3648
|
-
|
|
3649
|
-
${agentHintsText(hints)}` : message,
|
|
3650
|
-
{
|
|
3651
|
-
...error2 instanceof UserError ? error2.extras : {},
|
|
3652
|
-
error: message,
|
|
3653
|
-
agent_hints: hints
|
|
3654
|
-
}
|
|
3655
|
-
);
|
|
3656
|
-
}
|
|
3657
3566
|
throw error2;
|
|
3658
3567
|
}
|
|
3659
3568
|
const payload = recoveryPayload("CREDENTIAL_INVALID", requestId);
|
|
@@ -3712,8 +3621,6 @@ var searchDomainSchema = z11.string().trim().toLowerCase().min(1).max(253).regex
|
|
|
3712
3621
|
var searchToolBaseFields = {
|
|
3713
3622
|
toolDetail: z11.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."),
|
|
3714
3623
|
query: z11.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."),
|
|
3715
|
-
objective: z11.string().trim().min(1).max(5e3).optional().describe("Optional broader goal for this search, if known. Avoid sensitive information."),
|
|
3716
|
-
clientModel: z11.string().trim().min(1).max(128).optional().describe("Optional model identifier, if known."),
|
|
3717
3624
|
domainTools: z11.boolean().optional().describe(
|
|
3718
3625
|
"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."
|
|
3719
3626
|
),
|
|
@@ -3779,7 +3686,7 @@ function termsRequiredAction(body) {
|
|
|
3779
3686
|
url: action.url
|
|
3780
3687
|
};
|
|
3781
3688
|
}
|
|
3782
|
-
function termsRequiredError(action, context
|
|
3689
|
+
function termsRequiredError(action, context) {
|
|
3783
3690
|
const requestId = context.requestId;
|
|
3784
3691
|
const retryIdentity = requestId ? ` and requestId ${requestId}` : "";
|
|
3785
3692
|
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.`;
|
|
@@ -3794,7 +3701,6 @@ Do not retry until acceptance is confirmed.`,
|
|
|
3794
3701
|
code: TERMS_REQUIRED_CODE,
|
|
3795
3702
|
status: 403,
|
|
3796
3703
|
message,
|
|
3797
|
-
...hints ? { agent_hints: hints } : {},
|
|
3798
3704
|
...requestId ? { requestId } : {},
|
|
3799
3705
|
requiresAction: action,
|
|
3800
3706
|
nextTool: {
|
|
@@ -3825,8 +3731,7 @@ Do not retry until acceptance is confirmed.`,
|
|
|
3825
3731
|
function throwIfTermsRequired(error2, context) {
|
|
3826
3732
|
const source = error2;
|
|
3827
3733
|
const action = termsRequiredAction(source?.response?.data ?? source?.details);
|
|
3828
|
-
if (action)
|
|
3829
|
-
throw termsRequiredError(action, context, readErrorAgentHints(error2));
|
|
3734
|
+
if (action) throw termsRequiredError(action, context);
|
|
3830
3735
|
}
|
|
3831
3736
|
async function relayTermsRequired(run, context) {
|
|
3832
3737
|
try {
|
|
@@ -3844,7 +3749,6 @@ async function relayExchangeError(run, context) {
|
|
|
3844
3749
|
const response = error2?.response;
|
|
3845
3750
|
if (!response || response.status === 401) throw error2;
|
|
3846
3751
|
const data = response.data;
|
|
3847
|
-
const hints = readErrorAgentHints(error2);
|
|
3848
3752
|
const message = typeof data?.error === "string" ? data.error : `Alexandria request failed (HTTP ${response.status})`;
|
|
3849
3753
|
const disabledProvider = response.status === 403 ? context.providers?.find((provider) => message === `Access to ${provider} is disabled for this organization.`) : void 0;
|
|
3850
3754
|
if (disabledProvider) {
|
|
@@ -3854,7 +3758,6 @@ async function relayExchangeError(run, context) {
|
|
|
3854
3758
|
code: typeof data?.code === "string" ? data.code : "exchange_error",
|
|
3855
3759
|
status: 403,
|
|
3856
3760
|
message,
|
|
3857
|
-
...hints ? { agent_hints: hints } : {},
|
|
3858
3761
|
nextTool: {
|
|
3859
3762
|
name: "firecrawl_scrape",
|
|
3860
3763
|
arguments: {
|
|
@@ -3868,7 +3771,6 @@ async function relayExchangeError(run, context) {
|
|
|
3868
3771
|
code: typeof data?.code === "string" ? data.code : "exchange_error",
|
|
3869
3772
|
status: response.status,
|
|
3870
3773
|
message,
|
|
3871
|
-
...hints ? { agent_hints: hints } : {},
|
|
3872
3774
|
...typeof data?.chargeId === "string" ? { chargeId: data.chargeId } : {}
|
|
3873
3775
|
});
|
|
3874
3776
|
}
|
|
@@ -3915,10 +3817,6 @@ function copySession(session) {
|
|
|
3915
3817
|
Object.getOwnPropertyDescriptors(session ?? {})
|
|
3916
3818
|
);
|
|
3917
3819
|
}
|
|
3918
|
-
function resultAgentHints(result) {
|
|
3919
|
-
return result && typeof result === "object" ? readAgentHints(result.structuredContent) : void 0;
|
|
3920
|
-
}
|
|
3921
|
-
var warnedMissingAgentHintsInterceptor = false;
|
|
3922
3820
|
var ConsoleLogger = class {
|
|
3923
3821
|
constructor(shouldLog) {
|
|
3924
3822
|
this.shouldLog = shouldLog;
|
|
@@ -4070,20 +3968,10 @@ function createFirecrawlMcpServer(options2 = {}) {
|
|
|
4070
3968
|
requestId,
|
|
4071
3969
|
session
|
|
4072
3970
|
);
|
|
4073
|
-
hooks.onToolResult?.({
|
|
4074
|
-
...call,
|
|
4075
|
-
status: "success",
|
|
4076
|
-
result,
|
|
4077
|
-
agentHints: resultAgentHints(result)
|
|
4078
|
-
});
|
|
3971
|
+
hooks.onToolResult?.({ ...call, status: "success", result });
|
|
4079
3972
|
return result;
|
|
4080
3973
|
} catch (error2) {
|
|
4081
|
-
hooks.onToolResult?.({
|
|
4082
|
-
...call,
|
|
4083
|
-
status: "error",
|
|
4084
|
-
error: error2,
|
|
4085
|
-
agentHints: readErrorAgentHints(error2)
|
|
4086
|
-
});
|
|
3974
|
+
hooks.onToolResult?.({ ...call, status: "error", error: error2 });
|
|
4087
3975
|
throw error2;
|
|
4088
3976
|
}
|
|
4089
3977
|
}
|
|
@@ -4114,44 +4002,7 @@ function createFirecrawlMcpServer(options2 = {}) {
|
|
|
4114
4002
|
if (apiKey2) {
|
|
4115
4003
|
clientConfig.apiKey = apiKey2;
|
|
4116
4004
|
}
|
|
4117
|
-
|
|
4118
|
-
const axiosInstance = client.http?.instance;
|
|
4119
|
-
if (axiosInstance?.interceptors?.request?.use) {
|
|
4120
|
-
axiosInstance.interceptors.request.use((request) => {
|
|
4121
|
-
if (typeof request.headers?.set === "function") {
|
|
4122
|
-
request.headers.set("X-Firecrawl-Agent-Hints", "true");
|
|
4123
|
-
} else {
|
|
4124
|
-
request.headers = { ...request.headers ?? {}, ...AGENT_HINTS_HEADERS };
|
|
4125
|
-
}
|
|
4126
|
-
return request;
|
|
4127
|
-
});
|
|
4128
|
-
} else if (!warnedMissingAgentHintsInterceptor) {
|
|
4129
|
-
warnedMissingAgentHintsInterceptor = true;
|
|
4130
|
-
console.warn(
|
|
4131
|
-
"[firecrawl-mcp] SDK request interceptor unavailable; API agent hints may be absent."
|
|
4132
|
-
);
|
|
4133
|
-
}
|
|
4134
|
-
return client;
|
|
4135
|
-
}
|
|
4136
|
-
async function sdkResultWithAgentHints(client, execute) {
|
|
4137
|
-
const responseInterceptors = client.http?.instance?.interceptors?.response;
|
|
4138
|
-
if (!responseInterceptors?.use) return execute();
|
|
4139
|
-
let hints;
|
|
4140
|
-
const interceptor = responseInterceptors.use((response) => {
|
|
4141
|
-
hints = readAgentHints(response?.data) ?? hints;
|
|
4142
|
-
return response;
|
|
4143
|
-
});
|
|
4144
|
-
try {
|
|
4145
|
-
const result = await execute();
|
|
4146
|
-
return preserveAgentHints(result, { agent_hints: hints });
|
|
4147
|
-
} catch (error2) {
|
|
4148
|
-
if (hints && error2 && typeof error2 === "object" && !readErrorAgentHints(error2)) {
|
|
4149
|
-
error2.agent_hints = hints;
|
|
4150
|
-
}
|
|
4151
|
-
throw error2;
|
|
4152
|
-
} finally {
|
|
4153
|
-
responseInterceptors.eject?.(interceptor);
|
|
4154
|
-
}
|
|
4005
|
+
return new FirecrawlApp(clientConfig);
|
|
4155
4006
|
}
|
|
4156
4007
|
const SAFE_MODE = config.safeMode;
|
|
4157
4008
|
function getClient(session) {
|
|
@@ -4291,6 +4142,7 @@ function createFirecrawlMcpServer(options2 = {}) {
|
|
|
4291
4142
|
"rawHtml",
|
|
4292
4143
|
"screenshot",
|
|
4293
4144
|
"links",
|
|
4145
|
+
"images",
|
|
4294
4146
|
"summary",
|
|
4295
4147
|
"changeTracking",
|
|
4296
4148
|
"branding",
|
|
@@ -4503,7 +4355,6 @@ function createFirecrawlMcpServer(options2 = {}) {
|
|
|
4503
4355
|
const response = await fetch(`${resolveApiBaseUrl()}${pathName}`, {
|
|
4504
4356
|
method: "POST",
|
|
4505
4357
|
headers: {
|
|
4506
|
-
...AGENT_HINTS_HEADERS,
|
|
4507
4358
|
"Content-Type": "application/json",
|
|
4508
4359
|
...extraHeaders,
|
|
4509
4360
|
Authorization: `Bearer ${apiKey2}`,
|
|
@@ -4521,8 +4372,7 @@ function createFirecrawlMcpServer(options2 = {}) {
|
|
|
4521
4372
|
if (!response.ok) {
|
|
4522
4373
|
throw new CoreHttpError(
|
|
4523
4374
|
parsed?.error || parsed?.message || `Firecrawl request failed (HTTP ${response.status})`,
|
|
4524
|
-
response.status
|
|
4525
|
-
readAgentHints(parsed)
|
|
4375
|
+
response.status
|
|
4526
4376
|
);
|
|
4527
4377
|
}
|
|
4528
4378
|
return parsed;
|
|
@@ -4604,7 +4454,6 @@ function createFirecrawlMcpServer(options2 = {}) {
|
|
|
4604
4454
|
session,
|
|
4605
4455
|
origin
|
|
4606
4456
|
);
|
|
4607
|
-
const uploadHints = readAgentHints(uploadJson);
|
|
4608
4457
|
const upload = parseApiData(uploadJson);
|
|
4609
4458
|
if (!upload?.uploadUrl || !upload?.uploadRef) {
|
|
4610
4459
|
throw new Error(
|
|
@@ -4615,7 +4464,6 @@ function createFirecrawlMcpServer(options2 = {}) {
|
|
|
4615
4464
|
const uploadForCommand = { ...upload, headers: uploadHeaders };
|
|
4616
4465
|
return asText({
|
|
4617
4466
|
success: true,
|
|
4618
|
-
...uploadHints ? { agent_hints: uploadHints } : {},
|
|
4619
4467
|
mode: "hosted-upload-ref-awaiting-upload",
|
|
4620
4468
|
message: "Hosted MCP cannot read local files. Run the local upload command, then call firecrawl_parse again with uploadRef. No Firecrawl API key is included in this command.",
|
|
4621
4469
|
upload: {
|
|
@@ -4714,17 +4562,14 @@ Alexandria mode, on an authenticated session with Alexandria access: \`alexandri
|
|
|
4714
4562
|
},
|
|
4715
4563
|
session
|
|
4716
4564
|
);
|
|
4717
|
-
return structuredText(
|
|
4565
|
+
return structuredText(json?.data ?? json);
|
|
4718
4566
|
}
|
|
4719
4567
|
const client = getClient(session);
|
|
4720
4568
|
const res = await relayTermsRequired(
|
|
4721
|
-
() =>
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
origin
|
|
4726
|
-
})
|
|
4727
|
-
),
|
|
4569
|
+
() => client.scrape(String(url), {
|
|
4570
|
+
...cleaned,
|
|
4571
|
+
origin
|
|
4572
|
+
}),
|
|
4728
4573
|
{ tool: "firecrawl_scrape" }
|
|
4729
4574
|
);
|
|
4730
4575
|
return structuredText(res);
|
|
@@ -4764,13 +4609,10 @@ Returns matching URLs rather than page bodies. Retrieve one page with \`firecraw
|
|
|
4764
4609
|
const client = getClient(session);
|
|
4765
4610
|
const cleaned = removeEmptyTopLevel(options3);
|
|
4766
4611
|
log.info("Mapping URL", { url: String(url) });
|
|
4767
|
-
const res = await
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
origin: requestOrigin(mcpClient, session)
|
|
4772
|
-
})
|
|
4773
|
-
);
|
|
4612
|
+
const res = await client.map(String(url), {
|
|
4613
|
+
...cleaned,
|
|
4614
|
+
origin: requestOrigin(mcpClient, session)
|
|
4615
|
+
});
|
|
4774
4616
|
return structuredText(res);
|
|
4775
4617
|
}
|
|
4776
4618
|
});
|
|
@@ -4934,9 +4776,9 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
|
|
|
4934
4776
|
}
|
|
4935
4777
|
};
|
|
4936
4778
|
server.addTool(findToolsTool);
|
|
4937
|
-
const
|
|
4779
|
+
const DEFAULT_CLOUD_API_URL = "https://api.firecrawl.dev";
|
|
4938
4780
|
function resolveApiBaseUrl() {
|
|
4939
|
-
return (config.apiUrl ||
|
|
4781
|
+
return (config.apiUrl || DEFAULT_CLOUD_API_URL).replace(
|
|
4940
4782
|
/\/$/,
|
|
4941
4783
|
""
|
|
4942
4784
|
);
|
|
@@ -4950,7 +4792,6 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
|
|
|
4950
4792
|
const origin = originOverride ?? (typeof body.origin === "string" && body.origin.length > 0 ? body.origin : requestOrigin(void 0, session));
|
|
4951
4793
|
await hooks.beforeKeylessRequest?.(session, origin);
|
|
4952
4794
|
const headers = {
|
|
4953
|
-
...AGENT_HINTS_HEADERS,
|
|
4954
4795
|
...originHeaders(origin),
|
|
4955
4796
|
"Content-Type": "application/json",
|
|
4956
4797
|
...resolveOutbound(session).headers
|
|
@@ -4968,14 +4809,11 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
|
|
|
4968
4809
|
retryAfterSeconds: Number.isFinite(json?.retry_after_seconds) && json.retry_after_seconds > 0 ? json.retry_after_seconds : void 0,
|
|
4969
4810
|
signupUrl: keylessSignupUrlFrom(json?.signup_url)
|
|
4970
4811
|
});
|
|
4971
|
-
const hints = readAgentHints(json);
|
|
4972
|
-
if (hints) payload.agent_hints = hints;
|
|
4973
4812
|
throw new UserError2(String(payload.message), payload);
|
|
4974
4813
|
}
|
|
4975
4814
|
throw new CoreHttpError(
|
|
4976
4815
|
json?.error || `Firecrawl request failed (HTTP ${response.status})`,
|
|
4977
|
-
response.status
|
|
4978
|
-
readAgentHints(json)
|
|
4816
|
+
response.status
|
|
4979
4817
|
);
|
|
4980
4818
|
}
|
|
4981
4819
|
return json;
|
|
@@ -4996,8 +4834,7 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
|
|
|
4996
4834
|
creditsUsed: body.creditsUsed,
|
|
4997
4835
|
expiresAt: body.expiresAt,
|
|
4998
4836
|
next: body.next ?? null,
|
|
4999
|
-
data: initialDocs
|
|
5000
|
-
...readAgentHints(body) ? { agent_hints: readAgentHints(body) } : {}
|
|
4837
|
+
data: initialDocs
|
|
5001
4838
|
};
|
|
5002
4839
|
}
|
|
5003
4840
|
const docs = initialDocs.slice();
|
|
@@ -5021,8 +4858,7 @@ For a programming question, add \`categories: ["developer"]\`; its hits return i
|
|
|
5021
4858
|
creditsUsed: body.creditsUsed,
|
|
5022
4859
|
expiresAt: body.expiresAt,
|
|
5023
4860
|
next: null,
|
|
5024
|
-
data: docs
|
|
5025
|
-
...readAgentHints(body) ? { agent_hints: readAgentHints(body) } : {}
|
|
4861
|
+
data: docs
|
|
5026
4862
|
};
|
|
5027
4863
|
}
|
|
5028
4864
|
async function waitForCrawlCompletionWithOrigin(client, jobId, origin, pollInterval = 2, timeout) {
|
|
@@ -5118,7 +4954,6 @@ Eligibility is limited to successful searches within the feedback age window. Th
|
|
|
5118
4954
|
}
|
|
5119
4955
|
if (querySuggestions) body.querySuggestions = querySuggestions;
|
|
5120
4956
|
const headers = {
|
|
5121
|
-
...AGENT_HINTS_HEADERS,
|
|
5122
4957
|
...originHeaders(origin),
|
|
5123
4958
|
"Content-Type": "application/json"
|
|
5124
4959
|
};
|
|
@@ -5159,8 +4994,7 @@ Eligibility is limited to successful searches within the feedback age window. Th
|
|
|
5159
4994
|
status: response.status,
|
|
5160
4995
|
feedbackErrorCode: parsed?.feedbackErrorCode,
|
|
5161
4996
|
error: parsed?.error ?? `HTTP ${response.status}`,
|
|
5162
|
-
retryable: response.status >= 500
|
|
5163
|
-
...readAgentHints(parsed) ? { agent_hints: readAgentHints(parsed) } : {}
|
|
4997
|
+
retryable: response.status >= 500
|
|
5164
4998
|
});
|
|
5165
4999
|
}
|
|
5166
5000
|
return structuredText(parsed);
|
|
@@ -5244,7 +5078,6 @@ Returns submission status, feedback ID, and accounting fields.
|
|
|
5244
5078
|
} = args2;
|
|
5245
5079
|
const apiBase = resolveApiBaseUrl();
|
|
5246
5080
|
const headers = {
|
|
5247
|
-
...AGENT_HINTS_HEADERS,
|
|
5248
5081
|
...originHeaders(origin),
|
|
5249
5082
|
"Content-Type": "application/json"
|
|
5250
5083
|
};
|
|
@@ -5300,8 +5133,7 @@ Returns submission status, feedback ID, and accounting fields.
|
|
|
5300
5133
|
status: response.status,
|
|
5301
5134
|
feedbackErrorCode: parsed?.feedbackErrorCode,
|
|
5302
5135
|
error: parsed?.error ?? `HTTP ${response.status}`,
|
|
5303
|
-
retryable: response.status >= 500
|
|
5304
|
-
...readAgentHints(parsed) ? { agent_hints: readAgentHints(parsed) } : {}
|
|
5136
|
+
retryable: response.status >= 500
|
|
5305
5137
|
});
|
|
5306
5138
|
}
|
|
5307
5139
|
return structuredText(parsed);
|
|
@@ -5789,7 +5621,6 @@ Set \`redactPII\` to request redaction of personally identifiable information in
|
|
|
5789
5621
|
form.append("file", blob, filename);
|
|
5790
5622
|
form.append("options", JSON.stringify(optionsPayload));
|
|
5791
5623
|
const headers = {
|
|
5792
|
-
...AGENT_HINTS_HEADERS,
|
|
5793
5624
|
...originHeaders(origin)
|
|
5794
5625
|
};
|
|
5795
5626
|
const outbound = resolveOutbound(session);
|
|
@@ -5811,15 +5642,9 @@ Set \`redactPII\` to request redaction of personally identifiable information in
|
|
|
5811
5642
|
});
|
|
5812
5643
|
const responseText = await response.text();
|
|
5813
5644
|
if (!response.ok) {
|
|
5814
|
-
let parsed;
|
|
5815
|
-
try {
|
|
5816
|
-
parsed = JSON.parse(responseText);
|
|
5817
|
-
} catch {
|
|
5818
|
-
}
|
|
5819
5645
|
throw new CoreHttpError(
|
|
5820
5646
|
`Parse request failed with status ${response.status}: ${responseText}`,
|
|
5821
|
-
response.status
|
|
5822
|
-
readAgentHints(parsed)
|
|
5647
|
+
response.status
|
|
5823
5648
|
);
|
|
5824
5649
|
}
|
|
5825
5650
|
try {
|
|
@@ -5847,6 +5672,9 @@ Set \`redactPII\` to request redaction of personally identifiable information in
|
|
|
5847
5672
|
builtInTool: (name2) => builtInTools.get(name2)
|
|
5848
5673
|
}
|
|
5849
5674
|
);
|
|
5675
|
+
hooks.registerResources?.({
|
|
5676
|
+
addResource: (resource) => server.addResource(resource)
|
|
5677
|
+
});
|
|
5850
5678
|
return {
|
|
5851
5679
|
start: (args2) => server.start(args2),
|
|
5852
5680
|
stop: () => server.stop()
|
|
@@ -5923,20 +5751,6 @@ function introspectionTtlMs(value, now, activeTtlMs = INTROSPECTION_ACTIVE_TTL_M
|
|
|
5923
5751
|
return Math.min(activeTtlMs, value.exp * 1e3 - now);
|
|
5924
5752
|
}
|
|
5925
5753
|
|
|
5926
|
-
// src/service/keyless-client-ip.ts
|
|
5927
|
-
import net from "net";
|
|
5928
|
-
function extractSingleTrustedClientIp(rawForwardedFor) {
|
|
5929
|
-
if (Array.isArray(rawForwardedFor) && rawForwardedFor.length !== 1) {
|
|
5930
|
-
return void 0;
|
|
5931
|
-
}
|
|
5932
|
-
const raw = Array.isArray(rawForwardedFor) ? rawForwardedFor[0] : rawForwardedFor;
|
|
5933
|
-
if (typeof raw !== "string") return void 0;
|
|
5934
|
-
const parts = raw.split(",").map((part) => part.trim()).filter(Boolean);
|
|
5935
|
-
if (parts.length !== 1) return void 0;
|
|
5936
|
-
const candidate = parts[0].replace(/^\[(.*)\]$/, "$1").toLowerCase();
|
|
5937
|
-
return net.isIP(candidate) ? candidate : void 0;
|
|
5938
|
-
}
|
|
5939
|
-
|
|
5940
5754
|
// src/service/session-credential.ts
|
|
5941
5755
|
import { createHmac } from "crypto";
|
|
5942
5756
|
var managedOAuthApiKey = /* @__PURE__ */ Symbol("firecrawlManagedOAuthApiKey");
|
|
@@ -6113,9 +5927,6 @@ function makePrimaryProfile() {
|
|
|
6113
5927
|
}
|
|
6114
5928
|
|
|
6115
5929
|
// src/service/auth.ts
|
|
6116
|
-
function extractClientIp(request) {
|
|
6117
|
-
return extractSingleTrustedClientIp(request?.headers?.["x-forwarded-for"]);
|
|
6118
|
-
}
|
|
6119
5930
|
function extractBearerToken(headers) {
|
|
6120
5931
|
const headerAuth = normalizeHeader(headers["authorization"]);
|
|
6121
5932
|
if (!headerAuth?.toLowerCase().startsWith("bearer ")) return void 0;
|
|
@@ -6128,9 +5939,6 @@ function isFirecrawlOAuthAccessToken(token) {
|
|
|
6128
5939
|
function isFirecrawlApiKey(token) {
|
|
6129
5940
|
return token.startsWith("fc-");
|
|
6130
5941
|
}
|
|
6131
|
-
function isLegacyKeyPathRequest(request) {
|
|
6132
|
-
return normalizeHeader(request?.headers?.["x-firecrawl-key-transport"]) === "path";
|
|
6133
|
-
}
|
|
6134
5942
|
function requestShouldReceiveOAuthChallenge(request, profile) {
|
|
6135
5943
|
if (!profile.acceptApiKeys) return true;
|
|
6136
5944
|
if (!request?.headers) return true;
|
|
@@ -6415,19 +6223,11 @@ async function authenticateRequest(request, profile, options2) {
|
|
|
6415
6223
|
return {
|
|
6416
6224
|
authType: "api-key",
|
|
6417
6225
|
credentialError: "CREDENTIAL_INVALID",
|
|
6418
|
-
firecrawlApiKey: void 0
|
|
6419
|
-
keylessClientIp: extractClientIp(request)
|
|
6226
|
+
firecrawlApiKey: void 0
|
|
6420
6227
|
};
|
|
6421
6228
|
}
|
|
6422
6229
|
throw new InvalidFirecrawlCredentialError();
|
|
6423
6230
|
}
|
|
6424
|
-
if (profile.allowKeyless) {
|
|
6425
|
-
return {
|
|
6426
|
-
authType: "keyless",
|
|
6427
|
-
firecrawlApiKey: void 0,
|
|
6428
|
-
keylessClientIp: extractClientIp(request)
|
|
6429
|
-
};
|
|
6430
|
-
}
|
|
6431
6231
|
if (!profile.acceptApiKeys) {
|
|
6432
6232
|
throw new Error(
|
|
6433
6233
|
`OAuth access token required for the Firecrawl MCP resource ${profile.endpoint}`
|
|
@@ -6440,7 +6240,6 @@ async function authenticateRequest(request, profile, options2) {
|
|
|
6440
6240
|
const session2 = {
|
|
6441
6241
|
authType: resolved?.source === "oauth" ? "oauth" : "api-key",
|
|
6442
6242
|
firecrawlApiKey: headerCred,
|
|
6443
|
-
...isLegacyKeyPathRequest(request) ? { keyTransport: "path" } : {},
|
|
6444
6243
|
...resolved?.metadata
|
|
6445
6244
|
};
|
|
6446
6245
|
return managedCred ? setManagedOAuthApiKey(session2, managedCred) : session2;
|
|
@@ -6489,18 +6288,6 @@ function emitSearchCompanionAuthTelemetry(hosted2, profile, request, outcome, se
|
|
|
6489
6288
|
})
|
|
6490
6289
|
);
|
|
6491
6290
|
}
|
|
6492
|
-
function emitLegacyKeyPathTelemetry(profile, request, outcome, session) {
|
|
6493
|
-
if (profile.id !== "full" || !isLegacyKeyPathRequest(request)) return;
|
|
6494
|
-
console.log(
|
|
6495
|
-
"[MCP_LEGACY_KEY_PATH]",
|
|
6496
|
-
JSON.stringify({
|
|
6497
|
-
auth_type: session?.authType ?? "none",
|
|
6498
|
-
key_transport: "path",
|
|
6499
|
-
outcome,
|
|
6500
|
-
resource: profile.resourceUrl
|
|
6501
|
-
})
|
|
6502
|
-
);
|
|
6503
|
-
}
|
|
6504
6291
|
function createServiceAuthenticate(profile, options2) {
|
|
6505
6292
|
return function authenticateWithOAuthChallenge(request) {
|
|
6506
6293
|
return authenticateRequest(request, profile, options2).then((session) => {
|
|
@@ -6512,12 +6299,6 @@ function createServiceAuthenticate(profile, options2) {
|
|
|
6512
6299
|
session.credentialError ? "rejected" : "accepted",
|
|
6513
6300
|
session
|
|
6514
6301
|
);
|
|
6515
|
-
emitLegacyKeyPathTelemetry(
|
|
6516
|
-
profile,
|
|
6517
|
-
request,
|
|
6518
|
-
session.credentialError ? "rejected" : "accepted",
|
|
6519
|
-
session
|
|
6520
|
-
);
|
|
6521
6302
|
return session;
|
|
6522
6303
|
}).catch((error2) => {
|
|
6523
6304
|
emitSearchCompanionAuthTelemetry(
|
|
@@ -6526,7 +6307,6 @@ function createServiceAuthenticate(profile, options2) {
|
|
|
6526
6307
|
request,
|
|
6527
6308
|
"rejected"
|
|
6528
6309
|
);
|
|
6529
|
-
emitLegacyKeyPathTelemetry(profile, request, "rejected");
|
|
6530
6310
|
if (error2 instanceof InvalidFirecrawlCredentialError) {
|
|
6531
6311
|
throw createInvalidCredentialResponse(error2);
|
|
6532
6312
|
}
|
|
@@ -6553,76 +6333,6 @@ function createServiceAuthenticate(profile, options2) {
|
|
|
6553
6333
|
};
|
|
6554
6334
|
}
|
|
6555
6335
|
|
|
6556
|
-
// src/service/session.ts
|
|
6557
|
-
function isHostedKeylessSession(session) {
|
|
6558
|
-
return session?.authType === "keyless" && !session.firecrawlApiKey;
|
|
6559
|
-
}
|
|
6560
|
-
|
|
6561
|
-
// src/service/keyless.ts
|
|
6562
|
-
var DEFAULT_CLOUD_API_URL = "https://api.firecrawl.dev";
|
|
6563
|
-
function apiBaseUrl(apiUrl2) {
|
|
6564
|
-
return (apiUrl2 || DEFAULT_CLOUD_API_URL).replace(/\/$/, "");
|
|
6565
|
-
}
|
|
6566
|
-
async function keylessEligible(apiBaseUrl2, clientIp, origin, { signupLink = false } = {}) {
|
|
6567
|
-
const secret = process.env.KEYLESS_PROXY_SECRET;
|
|
6568
|
-
if (!secret) return { eligible: false, unavailable: true };
|
|
6569
|
-
try {
|
|
6570
|
-
const response = await fetch(
|
|
6571
|
-
`${apiBaseUrl2}/v2/keyless/eligibility${signupLink ? "?signup_link=1" : ""}`,
|
|
6572
|
-
{
|
|
6573
|
-
headers: {
|
|
6574
|
-
...originHeaders(origin),
|
|
6575
|
-
"x-firecrawl-keyless-ip": clientIp,
|
|
6576
|
-
"x-firecrawl-keyless-secret": secret
|
|
6577
|
-
}
|
|
6578
|
-
}
|
|
6579
|
-
);
|
|
6580
|
-
if (!response.ok) return { eligible: false, unavailable: true };
|
|
6581
|
-
const json = await response.json().catch(() => null);
|
|
6582
|
-
if (typeof json?.eligible !== "boolean") {
|
|
6583
|
-
return { eligible: false, unavailable: true };
|
|
6584
|
-
}
|
|
6585
|
-
return {
|
|
6586
|
-
eligible: json?.eligible === true,
|
|
6587
|
-
...typeof json?.reason === "string" ? { reason: json.reason } : {},
|
|
6588
|
-
...Number.isFinite(json?.retryAfterSeconds) && json.retryAfterSeconds > 0 ? { retryAfterSeconds: json.retryAfterSeconds } : {},
|
|
6589
|
-
...keylessSignupUrlFrom(json?.signupUrl) ? { signupUrl: json.signupUrl } : {}
|
|
6590
|
-
};
|
|
6591
|
-
} catch {
|
|
6592
|
-
return { eligible: false, unavailable: true };
|
|
6593
|
-
}
|
|
6594
|
-
}
|
|
6595
|
-
async function hostedKeylessSignupUrl(apiBaseUrl2, session) {
|
|
6596
|
-
if (!session?.keylessClientIp) return void 0;
|
|
6597
|
-
const eligibility = await keylessEligible(
|
|
6598
|
-
apiBaseUrl2,
|
|
6599
|
-
session.keylessClientIp,
|
|
6600
|
-
requestOrigin(void 0, session),
|
|
6601
|
-
{ signupLink: true }
|
|
6602
|
-
);
|
|
6603
|
-
return eligibility.signupUrl;
|
|
6604
|
-
}
|
|
6605
|
-
async function requireKeylessEligibility(apiBase, session, origin) {
|
|
6606
|
-
if (!isHostedKeylessSession(session)) return;
|
|
6607
|
-
const eligibility = session?.keylessClientIp ? await keylessEligible(apiBase, session.keylessClientIp, origin) : { eligible: false };
|
|
6608
|
-
if (!eligibility.eligible) {
|
|
6609
|
-
const code = eligibility.unavailable ? "KEYLESS_ELIGIBILITY_UNAVAILABLE" : keylessQuotaReason(eligibility.reason) ? "KEYLESS_QUOTA_EXHAUSTED" : "KEYLESS_ACCESS_NOT_AVAILABLE";
|
|
6610
|
-
const payload = recoveryPayload(code, session?.requestId, {
|
|
6611
|
-
retryAfterSeconds: eligibility.retryAfterSeconds,
|
|
6612
|
-
signupUrl: eligibility.signupUrl
|
|
6613
|
-
});
|
|
6614
|
-
throw new UserError2(String(payload.message), payload);
|
|
6615
|
-
}
|
|
6616
|
-
}
|
|
6617
|
-
function keylessForwardingHeaders(session) {
|
|
6618
|
-
const secret = process.env.KEYLESS_PROXY_SECRET;
|
|
6619
|
-
if (!session?.keylessClientIp || !secret) return void 0;
|
|
6620
|
-
return {
|
|
6621
|
-
"x-firecrawl-keyless-ip": session.keylessClientIp,
|
|
6622
|
-
"x-firecrawl-keyless-secret": secret
|
|
6623
|
-
};
|
|
6624
|
-
}
|
|
6625
|
-
|
|
6626
6336
|
// src/service/search-surface.ts
|
|
6627
6337
|
import { z as z13 } from "zod";
|
|
6628
6338
|
function searchSurfaceScrapeDescription(safeMode) {
|
|
@@ -6657,6 +6367,13 @@ ${ALEXANDRIA_SEARCH_INSTRUCTIONS}
|
|
|
6657
6367
|
Returns result groups in \`data\` and an operation \`id\`.
|
|
6658
6368
|
`,
|
|
6659
6369
|
outputSchema: searchOutputSchema,
|
|
6370
|
+
beforeValidate: (args2) => {
|
|
6371
|
+
if (args2 && typeof args2 === "object") {
|
|
6372
|
+
delete args2.objective;
|
|
6373
|
+
delete args2.clientModel;
|
|
6374
|
+
}
|
|
6375
|
+
return void 0;
|
|
6376
|
+
},
|
|
6660
6377
|
parameters: z13.object({ ...searchToolBaseFields }).strict().refine(searchDomainsAreExclusive, SEARCH_DOMAINS_CONFLICT_MESSAGE).refine(
|
|
6661
6378
|
searchQueryIsValid,
|
|
6662
6379
|
"A query is required. Use firecrawl_find_tools to browse the catalogue."
|
|
@@ -6664,8 +6381,6 @@ Returns result groups in \`data\` and an operation \`id\`.
|
|
|
6664
6381
|
execute: async (args2, { session, log, client: mcpClient }) => {
|
|
6665
6382
|
const {
|
|
6666
6383
|
query,
|
|
6667
|
-
objective,
|
|
6668
|
-
clientModel,
|
|
6669
6384
|
includeDomains,
|
|
6670
6385
|
excludeDomains,
|
|
6671
6386
|
limit,
|
|
@@ -6683,8 +6398,6 @@ Returns result groups in \`data\` and an operation \`id\`.
|
|
|
6683
6398
|
const searchBody = {
|
|
6684
6399
|
query: searchQuery,
|
|
6685
6400
|
...removeEmptyTopLevel({
|
|
6686
|
-
objective,
|
|
6687
|
-
clientModel,
|
|
6688
6401
|
limit,
|
|
6689
6402
|
includeDomains,
|
|
6690
6403
|
excludeDomains,
|
|
@@ -6750,66 +6463,7 @@ function searchSurfaceTools({
|
|
|
6750
6463
|
}
|
|
6751
6464
|
|
|
6752
6465
|
// src/service/tool-guard.ts
|
|
6753
|
-
|
|
6754
|
-
function emitActionLog(profile, apiUrl2, toolName, status, session, error2, requestId = randomUUID4(), code) {
|
|
6755
|
-
const payload = {
|
|
6756
|
-
team_id: session?.teamId,
|
|
6757
|
-
user_id: session?.userId,
|
|
6758
|
-
api_key_id: session?.apiKeyId,
|
|
6759
|
-
oauth_client_id: session?.oauthClientId,
|
|
6760
|
-
auth_type: session?.authType ?? "none",
|
|
6761
|
-
tool_name: toolName,
|
|
6762
|
-
status,
|
|
6763
|
-
request_id: requestId,
|
|
6764
|
-
resource: profile.resourceUrl,
|
|
6765
|
-
...error2 ? { error_class: error2 instanceof Error ? error2.name : typeof error2 } : {},
|
|
6766
|
-
...code ? { code } : {}
|
|
6767
|
-
};
|
|
6768
|
-
console.error("[MCP_ACTION]", JSON.stringify(payload));
|
|
6769
|
-
const secret = normalizeHeader(process.env.FIRECRAWL_MCP_ACTION_LOG_SECRET);
|
|
6770
|
-
const configuredApiUrl = normalizeHeader(apiUrl2);
|
|
6771
|
-
const endpoint = normalizeHeader(process.env.FIRECRAWL_MCP_ACTION_LOG_URL) ?? (configuredApiUrl ? `${withoutTrailingSlash(configuredApiUrl)}/v2/mcp/action-logs` : void 0);
|
|
6772
|
-
if (!secret || !endpoint || !payload.team_id || status === "started") return;
|
|
6773
|
-
const actionLogPayload = { ...payload };
|
|
6774
|
-
delete actionLogPayload.code;
|
|
6775
|
-
void fetch(endpoint, {
|
|
6776
|
-
method: "POST",
|
|
6777
|
-
headers: {
|
|
6778
|
-
Authorization: `Bearer ${secret}`,
|
|
6779
|
-
"Content-Type": "application/json"
|
|
6780
|
-
},
|
|
6781
|
-
body: JSON.stringify(actionLogPayload),
|
|
6782
|
-
signal: AbortSignal.timeout(1500)
|
|
6783
|
-
}).catch(() => void 0);
|
|
6784
|
-
}
|
|
6785
|
-
var AGENT_HINT_LOG_MAX_CHARS = 1e3;
|
|
6786
|
-
function emitAgentHintsLog(profile, toolName, status, hints, session, requestId) {
|
|
6787
|
-
if (!hints) return;
|
|
6788
|
-
console.error(
|
|
6789
|
-
"[MCP_AGENT_HINTS]",
|
|
6790
|
-
JSON.stringify({
|
|
6791
|
-
tool_name: toolName,
|
|
6792
|
-
status,
|
|
6793
|
-
request_id: requestId,
|
|
6794
|
-
auth_type: session.authType ?? "none",
|
|
6795
|
-
// The companion search server shares this process and wrapper.
|
|
6796
|
-
profile: session.profile ?? profile.id,
|
|
6797
|
-
hint_count: hints.length,
|
|
6798
|
-
hints: hints.map((hint) => hint.slice(0, AGENT_HINT_LOG_MAX_CHARS))
|
|
6799
|
-
})
|
|
6800
|
-
);
|
|
6801
|
-
}
|
|
6802
|
-
function createToolGuard({
|
|
6803
|
-
profile,
|
|
6804
|
-
apiUrl: apiUrl2,
|
|
6805
|
-
apiBaseUrl: apiBaseUrl2,
|
|
6806
|
-
logActions
|
|
6807
|
-
}) {
|
|
6808
|
-
const logRejection = (toolName, session, error2, requestId, code) => {
|
|
6809
|
-
if (logActions) {
|
|
6810
|
-
emitActionLog(profile, apiUrl2, toolName, "error", session, error2, requestId, code);
|
|
6811
|
-
}
|
|
6812
|
-
};
|
|
6466
|
+
function createToolGuard() {
|
|
6813
6467
|
return (tool) => {
|
|
6814
6468
|
const keylessTool = keylessToolNames.has(tool.name);
|
|
6815
6469
|
const execute = tool.execute;
|
|
@@ -6818,58 +6472,33 @@ function createToolGuard({
|
|
|
6818
6472
|
return {
|
|
6819
6473
|
...tool,
|
|
6820
6474
|
canList: (session) => (
|
|
6821
|
-
// A credentialError session lists the keyless tool surface (
|
|
6822
|
-
//
|
|
6823
|
-
//
|
|
6824
|
-
//
|
|
6825
|
-
//
|
|
6826
|
-
//
|
|
6827
|
-
//
|
|
6828
|
-
(session?.credentialError
|
|
6475
|
+
// A credentialError session lists the keyless tool surface (not the
|
|
6476
|
+
// full authenticated schema) so the client proceeds past tools/list and
|
|
6477
|
+
// calling any listed tool returns the CREDENTIAL_INVALID recovery
|
|
6478
|
+
// payload (below). An empty list would leave MCP clients that stop after
|
|
6479
|
+
// tools/list unable to ever surface the recovery guidance; the full
|
|
6480
|
+
// schema would over-disclose to a request carrying an unrecognized or
|
|
6481
|
+
// invalid credential.
|
|
6482
|
+
(session?.credentialError ? keylessTool : true) && (canList?.(session) ?? true)
|
|
6829
6483
|
),
|
|
6830
6484
|
beforeValidate: async (args2, session) => {
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
const requestId = randomUUID4();
|
|
6834
|
-
const payload2 = recoveryPayload(code, requestId, {
|
|
6835
|
-
signupUrl: code === "KEYLESS_TOOL_NOT_AVAILABLE" ? await hostedKeylessSignupUrl(apiBaseUrl2, session) : void 0
|
|
6836
|
-
});
|
|
6837
|
-
logRejection(tool.name, session, new UserError2(String(payload2.message), payload2), requestId, code);
|
|
6485
|
+
if (session?.credentialError) {
|
|
6486
|
+
const payload = recoveryPayload("CREDENTIAL_INVALID");
|
|
6838
6487
|
return {
|
|
6839
|
-
content: [{ type: "text", text: String(
|
|
6488
|
+
content: [{ type: "text", text: String(payload.message) }],
|
|
6840
6489
|
isError: true,
|
|
6841
|
-
structuredContent:
|
|
6490
|
+
structuredContent: payload
|
|
6842
6491
|
};
|
|
6843
6492
|
}
|
|
6844
|
-
|
|
6845
|
-
const payload = earlyResult?.structuredContent;
|
|
6846
|
-
const recoveryCode = payload && typeof payload === "object" && "code" in payload && typeof payload.code === "string" ? payload.code : void 0;
|
|
6847
|
-
if (earlyResult?.isError && recoveryCode) {
|
|
6848
|
-
logRejection(
|
|
6849
|
-
tool.name,
|
|
6850
|
-
session,
|
|
6851
|
-
new UserError2(`Tool validation failed: ${recoveryCode}`, payload),
|
|
6852
|
-
randomUUID4(),
|
|
6853
|
-
recoveryCode
|
|
6854
|
-
);
|
|
6855
|
-
}
|
|
6856
|
-
return earlyResult;
|
|
6493
|
+
return beforeValidate?.(args2, session);
|
|
6857
6494
|
},
|
|
6858
6495
|
execute: async (args2, context) => {
|
|
6859
6496
|
const session = context.session;
|
|
6860
|
-
const requestId = session.requestId;
|
|
6861
6497
|
if (session.credentialError) {
|
|
6862
|
-
const
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
}
|
|
6867
|
-
if (isHostedKeylessSession(session) && !keylessTool) {
|
|
6868
|
-
const code = "KEYLESS_TOOL_NOT_AVAILABLE";
|
|
6869
|
-
const payload = recoveryPayload(code, requestId, {
|
|
6870
|
-
signupUrl: await hostedKeylessSignupUrl(apiBaseUrl2, session)
|
|
6871
|
-
});
|
|
6872
|
-
logRejection(tool.name, session, new UserError2(String(payload.message), payload), requestId, code);
|
|
6498
|
+
const payload = recoveryPayload(
|
|
6499
|
+
"CREDENTIAL_INVALID",
|
|
6500
|
+
session.requestId
|
|
6501
|
+
);
|
|
6873
6502
|
throw new UserError2(String(payload.message), payload);
|
|
6874
6503
|
}
|
|
6875
6504
|
return execute(args2, context);
|
|
@@ -6877,76 +6506,6 @@ function createToolGuard({
|
|
|
6877
6506
|
};
|
|
6878
6507
|
};
|
|
6879
6508
|
}
|
|
6880
|
-
function createToolCallLogger({
|
|
6881
|
-
profile,
|
|
6882
|
-
apiUrl: apiUrl2,
|
|
6883
|
-
logActions
|
|
6884
|
-
}) {
|
|
6885
|
-
return (event) => {
|
|
6886
|
-
const session = event.session;
|
|
6887
|
-
if (event.status === "started") {
|
|
6888
|
-
if (logActions) {
|
|
6889
|
-
emitActionLog(profile, apiUrl2, event.tool, "started", session, void 0, event.requestId);
|
|
6890
|
-
}
|
|
6891
|
-
return;
|
|
6892
|
-
}
|
|
6893
|
-
emitAgentHintsLog(
|
|
6894
|
-
profile,
|
|
6895
|
-
event.tool,
|
|
6896
|
-
event.status === "success" && !event.result?.isError ? "success" : "error",
|
|
6897
|
-
event.agentHints,
|
|
6898
|
-
session,
|
|
6899
|
-
event.requestId
|
|
6900
|
-
);
|
|
6901
|
-
if (logActions) {
|
|
6902
|
-
emitActionLog(
|
|
6903
|
-
profile,
|
|
6904
|
-
apiUrl2,
|
|
6905
|
-
event.tool,
|
|
6906
|
-
event.status,
|
|
6907
|
-
session,
|
|
6908
|
-
event.status === "error" ? event.error : void 0,
|
|
6909
|
-
event.requestId
|
|
6910
|
-
);
|
|
6911
|
-
}
|
|
6912
|
-
};
|
|
6913
|
-
}
|
|
6914
|
-
|
|
6915
|
-
// src/service/http.ts
|
|
6916
|
-
function registerServiceRoutes(app, profile, { hosted: hosted2 }) {
|
|
6917
|
-
const openAiAppsChallengeToken = normalizeHeader(
|
|
6918
|
-
process.env.OPENAI_APPS_CHALLENGE_TOKEN
|
|
6919
|
-
);
|
|
6920
|
-
if (openAiAppsChallengeToken) {
|
|
6921
|
-
app.get(
|
|
6922
|
-
"/.well-known/openai-apps-challenge",
|
|
6923
|
-
(context) => context.text(openAiAppsChallengeToken)
|
|
6924
|
-
);
|
|
6925
|
-
}
|
|
6926
|
-
app.get("/ready", (context) => {
|
|
6927
|
-
if (!hosted2) {
|
|
6928
|
-
return context.json({ ok: true }, 200);
|
|
6929
|
-
}
|
|
6930
|
-
const searchPrimary = profile.id === "search";
|
|
6931
|
-
const required = [
|
|
6932
|
-
"FIRECRAWL_API_URL",
|
|
6933
|
-
"FIRECRAWL_OAUTH_INTROSPECT_SECRET",
|
|
6934
|
-
"MCP_DELEGATED_CREDENTIAL_SECRET"
|
|
6935
|
-
];
|
|
6936
|
-
if (profile.allowKeyless) {
|
|
6937
|
-
required.push("KEYLESS_PROXY_SECRET");
|
|
6938
|
-
}
|
|
6939
|
-
const missing = required.filter((name2) => !normalizeHeader(process.env[name2]));
|
|
6940
|
-
const configuredEndpoint = getPrimaryEndpoint();
|
|
6941
|
-
const resourceMatchesEndpoint = searchPrimary ? withoutTrailingSlash(profile.resourceUrl) === DEFAULT_MCP_SEARCH_RESOURCE_URL : withoutTrailingSlash(profile.resourceUrl).endsWith(configuredEndpoint);
|
|
6942
|
-
if (!resourceMatchesEndpoint) {
|
|
6943
|
-
missing.push(
|
|
6944
|
-
searchPrimary ? "FIRECRAWL_MCP_SEARCH_RESOURCE_URL (endpoint mismatch)" : "FIRECRAWL_MCP_RESOURCE_URL (endpoint mismatch)"
|
|
6945
|
-
);
|
|
6946
|
-
}
|
|
6947
|
-
return missing.length ? context.json({ ok: false, missing }, 503) : context.json({ ok: true }, 200);
|
|
6948
|
-
});
|
|
6949
|
-
}
|
|
6950
6509
|
|
|
6951
6510
|
// src/service/index.ts
|
|
6952
6511
|
function createServiceHooks(profile, settings) {
|
|
@@ -6963,27 +6522,13 @@ function createServiceHooks(profile, settings) {
|
|
|
6963
6522
|
hooks.outboundRequest = (session) => {
|
|
6964
6523
|
const serviceSession = session;
|
|
6965
6524
|
const credential = hasManagedOAuthCredential(serviceSession) ? credentialForOutboundRequest(serviceSession) : void 0;
|
|
6966
|
-
|
|
6967
|
-
return credential || headers ? { credential, headers } : void 0;
|
|
6525
|
+
return credential ? { credential } : void 0;
|
|
6968
6526
|
};
|
|
6969
6527
|
}
|
|
6970
6528
|
if (!settings.hosted) return hooks;
|
|
6971
|
-
const apiBase = apiBaseUrl(settings.apiUrl);
|
|
6972
|
-
const logActions = !searchSurface;
|
|
6973
6529
|
return {
|
|
6974
6530
|
...hooks,
|
|
6975
|
-
wrapTool: createToolGuard(
|
|
6976
|
-
profile,
|
|
6977
|
-
apiUrl: settings.apiUrl,
|
|
6978
|
-
apiBaseUrl: apiBase,
|
|
6979
|
-
logActions
|
|
6980
|
-
}),
|
|
6981
|
-
onToolResult: createToolCallLogger({
|
|
6982
|
-
profile,
|
|
6983
|
-
apiUrl: settings.apiUrl,
|
|
6984
|
-
logActions
|
|
6985
|
-
}),
|
|
6986
|
-
beforeKeylessRequest: (session, origin) => requireKeylessEligibility(apiBase, session, origin),
|
|
6531
|
+
wrapTool: createToolGuard(),
|
|
6987
6532
|
...profile.advertiseOAuth ? {
|
|
6988
6533
|
oauth: {
|
|
6989
6534
|
protectedResource: {
|
|
@@ -7047,7 +6592,9 @@ var { start } = createFirecrawlMcpServer({
|
|
|
7047
6592
|
...options,
|
|
7048
6593
|
unstable_hooks: {
|
|
7049
6594
|
...primaryHooks,
|
|
7050
|
-
configureHttp: (app) =>
|
|
6595
|
+
configureHttp: (app) => {
|
|
6596
|
+
app.get("/ready", (context) => context.json({ ok: true }, 200));
|
|
6597
|
+
}
|
|
7051
6598
|
}
|
|
7052
6599
|
});
|
|
7053
6600
|
var PORT = Number(process.env.PORT || 3e3);
|
|
@@ -7061,11 +6608,6 @@ var args = transport === "httpStream" ? {
|
|
|
7061
6608
|
stateless: true
|
|
7062
6609
|
}
|
|
7063
6610
|
} : { transportType: "stdio" };
|
|
7064
|
-
if (hosted && primaryProfile.allowKeyless && !normalizeHeader(process.env.KEYLESS_PROXY_SECRET)) {
|
|
7065
|
-
console.warn(
|
|
7066
|
-
"[firecrawl-mcp] KEYLESS_PROXY_SECRET is missing; keyless requests will be unavailable and /ready will fail."
|
|
7067
|
-
);
|
|
7068
|
-
}
|
|
7069
6611
|
if (transport === "stdio" && !process.env.FIRECRAWL_API_KEY && !process.env.FIRECRAWL_API_URL) {
|
|
7070
6612
|
console.error(
|
|
7071
6613
|
"No FIRECRAWL_API_KEY or FIRECRAWL_API_URL set \u2014 running in keyless mode. firecrawl_scrape and firecrawl_search are free (rate-limited per IP) against the Firecrawl cloud; other tools require an API key (get one free at https://firecrawl.dev)."
|