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/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=08f77b633292d9ae4cb022c0fa5bd722dbd5c1aaaad600204f8ed38339bfd37b/node_modules/fastmcp/dist/chunk-LWU5CQGW.js
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=08f77b633292d9ae4cb022c0fa5bd722dbd5c1aaaad600204f8ed38339bfd37b/node_modules/fastmcp/dist/chunk-OJG4XYXA.js
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 withAgentHints(withStructured(fmtDeveloper(results), { results }), res.data, true);
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 withAgentHints(
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
- agent_hints;
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, readAgentHints(payload));
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 monitorResult(res);
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 monitorResult(res);
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 monitorResult(res);
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 monitorResult(res);
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 monitorResult(res);
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 monitorResult(res);
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 monitorResult(res);
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 monitorResult(res);
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
- researchHeaders(requestOrigin(mcpClient, session))
3210
+ originHeaders(requestOrigin(mcpClient, session))
3299
3211
  );
3300
3212
  const results = res.data?.results ?? [];
3301
- return withAgentHints(withStructured(fmtHits(results), { results }), res.data, true);
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
- researchHeaders(requestOrigin(mcpClient, session))
3241
+ originHeaders(requestOrigin(mcpClient, session))
3330
3242
  );
3331
3243
  const paper = res.data?.paper;
3332
- return withAgentHints(withStructured(
3244
+ return withStructured(
3333
3245
  fmtPaperMetadata(paper),
3334
3246
  paper ? { paper } : {}
3335
- ), res.data, true);
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
- researchHeaders(requestOrigin(mcpClient, session))
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 withAgentHints(withStructured(
3295
+ return withStructured(
3384
3296
  `${fmtHits(results)}
3385
3297
  (poolSize=${poolSize})${note}`,
3386
3298
  { results, poolSize, ...res.data?.note ? { note: res.data.note } : {} }
3387
- ), res.data, true);
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
- researchHeaders(requestOrigin(mcpClient, session))
3334
+ originHeaders(requestOrigin(mcpClient, session))
3423
3335
  );
3424
3336
  const passages = res.data?.passages ?? [];
3425
- return withAgentHints(withStructured(
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
- ), res.data, true);
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, hints) {
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
- const client = new FirecrawlApp(clientConfig);
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(preserveAgentHints(json?.data ?? json, json));
4565
+ return structuredText(json?.data ?? json);
4718
4566
  }
4719
4567
  const client = getClient(session);
4720
4568
  const res = await relayTermsRequired(
4721
- () => sdkResultWithAgentHints(
4722
- client,
4723
- () => client.scrape(String(url), {
4724
- ...cleaned,
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 sdkResultWithAgentHints(
4768
- client,
4769
- () => client.map(String(url), {
4770
- ...cleaned,
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 DEFAULT_CLOUD_API_URL2 = "https://api.firecrawl.dev";
4779
+ const DEFAULT_CLOUD_API_URL = "https://api.firecrawl.dev";
4938
4780
  function resolveApiBaseUrl() {
4939
- return (config.apiUrl || DEFAULT_CLOUD_API_URL2).replace(
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
- import { randomUUID as randomUUID4 } from "crypto";
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 (same as a
6822
- // real keyless session, not the full authenticated schema) so the
6823
- // client proceeds past tools/list and calling any listed tool returns
6824
- // the CREDENTIAL_INVALID recovery payload (below). An empty list would
6825
- // leave MCP clients that stop after tools/list unable to ever surface
6826
- // the recovery guidance; the full non-keyless schema would over-disclose
6827
- // to a request carrying an unrecognized or invalid credential.
6828
- (session?.credentialError || isHostedKeylessSession(session) ? keylessTool : true) && (canList?.(session) ?? true)
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
- const code = session?.credentialError ? "CREDENTIAL_INVALID" : isHostedKeylessSession(session) && !keylessTool ? "KEYLESS_TOOL_NOT_AVAILABLE" : void 0;
6832
- if (code) {
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(payload2.message) }],
6488
+ content: [{ type: "text", text: String(payload.message) }],
6840
6489
  isError: true,
6841
- structuredContent: payload2
6490
+ structuredContent: payload
6842
6491
  };
6843
6492
  }
6844
- const earlyResult = await beforeValidate?.(args2, session);
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 code = "CREDENTIAL_INVALID";
6863
- const payload = recoveryPayload(code, requestId);
6864
- logRejection(tool.name, session, new UserError2(String(payload.message), payload), requestId, code);
6865
- throw new UserError2(String(payload.message), payload);
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
- const headers = keylessForwardingHeaders(serviceSession);
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) => registerServiceRoutes(app, primaryProfile, { hosted })
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)."