@fouradata/mcp 0.2.11 → 0.3.1

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.
@@ -0,0 +1,409 @@
1
+ import { request } from "undici";
2
+ import { z } from "zod";
3
+ import { getApiKey } from "../auth.js";
4
+ import { assertPublicTarget, SsrfBlockedError } from "../safe-target.js";
5
+ import { storePayload, THRESHOLD_BYTES } from "../resources.js";
6
+ function extractContentTypeFromHeaderInfo(headers) {
7
+ if (!Array.isArray(headers))
8
+ return null;
9
+ for (const h of headers) {
10
+ if (h && typeof h === "object") {
11
+ const entries = Object.entries(h);
12
+ for (const [k, v] of entries) {
13
+ if (k.toLowerCase() === "content-type") {
14
+ const value = Array.isArray(v) ? v[0] : v;
15
+ if (typeof value === "string")
16
+ return value.split(";")[0]?.trim() ?? null;
17
+ }
18
+ }
19
+ }
20
+ }
21
+ return null;
22
+ }
23
+ // Derive a stable error code from upstream HTTP status + envelope.
24
+ // LLM agents read `code` for retry / classify logic without parsing prose.
25
+ // Same code set as the other three tools - auto reuses them verbatim.
26
+ function deriveCode(status, envelope) {
27
+ if (status === 400)
28
+ return "bad_request";
29
+ if (status === 401)
30
+ return "auth_failed";
31
+ if (status === 403)
32
+ return "forbidden";
33
+ if (status === 404)
34
+ return "not_found";
35
+ if (status === 429)
36
+ return "rate_limited";
37
+ if (status === 503) {
38
+ if (envelope.current)
39
+ return "at_capacity";
40
+ const err = typeof envelope.error === "string" ? envelope.error : "";
41
+ if (err.toLowerCase().includes("disabled"))
42
+ return "service_disabled";
43
+ return "service_unavailable";
44
+ }
45
+ if (status >= 500)
46
+ return "upstream_error";
47
+ if (status >= 400)
48
+ return "upstream_client_error";
49
+ return "upstream_unknown";
50
+ }
51
+ // /api/auto is registered on the gateway WITHOUT a trailing slash (the route is
52
+ // an exact `POST /api/auto`), unlike /single/ /proxy/ /browser/ which keep the
53
+ // slash. Hitting `/auto/` would miss the dedicated handler and fall through to
54
+ // the transparent table - so this URL must stay slash-less.
55
+ const AUTO_API_URL = (process.env.FOURA_API_BASE ?? "https://api.foura.ai/api") + "/auto";
56
+ // The client's success criteria, passed verbatim to auto's internal sub-calls.
57
+ // Same DwValidate shape as foura_single / foura_proxy - auto enforces it on
58
+ // every rung itself, so a rung that only superficially "succeeds" (e.g. a
59
+ // challenge interstitial) is rejected and the ladder keeps climbing.
60
+ const AutoValidateSchema = z
61
+ .object({
62
+ status: z
63
+ .object({
64
+ accept: z.array(z.number().int()).optional().describe("HTTP status codes to treat as success"),
65
+ fail: z.array(z.number().int()).optional().describe("HTTP status codes to treat as failure"),
66
+ })
67
+ .optional(),
68
+ headers: z
69
+ .object({
70
+ accept: z
71
+ .record(z.string(), z.string())
72
+ .optional()
73
+ .describe("Map of header-name-substring → header-value-substring (case-insensitive). PASSES if at least one entry matches a response header."),
74
+ fail: z
75
+ .record(z.string(), z.string())
76
+ .optional()
77
+ .describe("Map of header-name-substring → header-value-substring (case-insensitive). FAILS if any entry matches a response header (use to reject challenge / block headers)."),
78
+ })
79
+ .optional(),
80
+ data: z
81
+ .object({
82
+ accept: z.array(z.string()).optional().describe("Substrings the final body MUST contain for the fetch to count as solved (CASE-SENSITIVE). Strongly recommended on protected targets so auto can tell a real page from a challenge page."),
83
+ fail: z.array(z.string()).optional().describe("Substrings the final body must NOT contain"),
84
+ })
85
+ .optional(),
86
+ })
87
+ .optional();
88
+ // Response headers come back as an array of per-hop objects (same shape the
89
+ // other tools surface). `result` carries the status line; every other key is a
90
+ // response header name → value (string, or array of strings for multi-value
91
+ // headers like Set-Cookie / Link).
92
+ const HeaderInfoSchema = z
93
+ .object({
94
+ result: z
95
+ .object({
96
+ version: z.string().optional(),
97
+ code: z.number().int().optional(),
98
+ reason: z.string().optional(),
99
+ })
100
+ .optional(),
101
+ })
102
+ .catchall(z.union([z.string(), z.array(z.string())]));
103
+ // The trace auto always returns: which rung delivered, whether a defense was
104
+ // solved, how many sub-call attempts it took, and the summed credits.
105
+ const AutoMetaSchema = z
106
+ .object({
107
+ rung: z.string().optional().describe("Which rung delivered the content (e.g. probe / proxy / browser / cache). `cache` means a warm session was replayed cheaply."),
108
+ solved: z.boolean().optional().describe("True when a bot-defense was actively solved on the way to the content (vs. the target being open)."),
109
+ attempts: z.number().optional().describe("Total internal sub-call attempts across the whole ladder."),
110
+ credits: z.number().optional().describe("Total credits spent (sum of every internal sub-call). A cold solve is expensive; a subsequent warm replay amortizes to the cheap-fetch cost."),
111
+ })
112
+ .catchall(z.unknown());
113
+ // The {proxy, cookies, userAgent} session triple. proxy is the OPAQUE base36
114
+ // proxy id (never a raw IP). Returned by default so a power client can DIY-replay
115
+ // the same session through foura_single / foura_proxy later.
116
+ const AutoSessionSchema = z
117
+ .object({
118
+ proxy: z.string().optional().describe("Opaque base36 exit id of the session (e.g. `4DZ3VE`) - pass to foura_single.proxy / foura_proxy.proxy to replay through the same exit. Never a raw IP."),
119
+ cookies: z.unknown().optional().describe("Cookies accumulated by the winning session - replay them on a follow-up request."),
120
+ userAgent: z.string().optional().describe("User-Agent string the winning session used - send the same one when replaying."),
121
+ })
122
+ .catchall(z.unknown());
123
+ const autoOutputShape = {
124
+ // Success path - single-shaped body so any client that renders foura_single
125
+ // also renders auto. Note: NO `total_time` (auto does not surface it).
126
+ status: z
127
+ .number()
128
+ .int()
129
+ .optional()
130
+ .describe("HTTP status code from the target on the rung that delivered the content. `0` indicates the whole ladder failed before any HTTP response - check `error`."),
131
+ headers: z
132
+ .union([z.array(HeaderInfoSchema), z.string(), z.record(z.string(), z.unknown())])
133
+ .optional()
134
+ .describe("Response headers from the delivering rung, as an array of objects. Each entry has `result.{version, code, reason}` plus arbitrary header-name keys whose values are strings (or arrays of strings for multi-value headers like Set-Cookie / Link). Last array entry is the final response."),
135
+ data: z
136
+ .unknown()
137
+ .optional()
138
+ .describe("Decoded response body of the delivered page. String by default; object when the body parsed as JSON. Omitted when offloaded."),
139
+ meta: AutoMetaSchema.optional().describe("Trace of what the ladder did: rung, solved, attempts, credits. Always present."),
140
+ session: AutoSessionSchema.optional().describe("The {proxy, cookies, userAgent} triple of the winning session, for DIY replay through foura_single / foura_proxy. Present by default (send returnSession:false to omit)."),
141
+ // Offload path - MCP layer adds these when body >= 50KB AND offload_large=true
142
+ offloaded_resource_uri: z.string().optional().describe("foura-mcp://payload/<uuid>"),
143
+ size_bytes: z.number().int().optional().describe("Total offloaded body size in bytes"),
144
+ // Error path - auto failure surfaces the failure status + message + attempts.
145
+ error: z.string().optional().describe("Human-readable error message when the ladder could not deliver the content within the budget."),
146
+ attempts: z.number().optional().describe("Total sub-call attempts when the ladder failed (also present inside `meta`)."),
147
+ service: z.enum(["single", "proxy", "browser", "api", "auto"]).optional(),
148
+ retryAfter: z.number().optional().describe("Seconds to wait before retrying (429/503 from the gateway)"),
149
+ current: z
150
+ .object({ concurrency: z.number().optional(), rpm: z.number().optional() })
151
+ .optional()
152
+ .describe("Caller's current usage at error time"),
153
+ limits: z
154
+ .object({ maxConcurrency: z.number().optional(), maxRpm: z.number().optional() })
155
+ .optional()
156
+ .describe("Per-service limits at error time"),
157
+ code: z
158
+ .string()
159
+ .optional()
160
+ .describe("Stable error code for retry classification. One of: ssrf_blocked, upstream_non_json, output_validation_failed, bad_request (400), auth_failed (401), forbidden (403), not_found (404), rate_limited (429), at_capacity (503), service_disabled (503), service_unavailable (503), upstream_error (>=500), upstream_client_error (other 4xx), upstream_unknown (defensive)."),
161
+ };
162
+ const autoInputShape = {
163
+ url: z
164
+ .string()
165
+ .url()
166
+ .describe("Target URL. Public hosts only - private/reserved ranges (RFC 1918 10/8, 172.16/12, 192.168/16, loopback 127/8, link-local, IPv6 ULA fc00::/7, IPv6 loopback ::1, plus *.local mDNS) are refused with code `ssrf_blocked`. Example: https://example.com/page. Use {ts} anywhere in the URL to insert the current Unix timestamp for cache-bust."),
167
+ method: z
168
+ .string()
169
+ .min(1)
170
+ .optional()
171
+ .describe("HTTP method for the target request (default GET)."),
172
+ headers: z
173
+ .array(z.tuple([z.string(), z.string()]))
174
+ .optional()
175
+ .describe("Custom HTTP headers to send to the TARGET, as [name, value] tuples. Example: [[\"Accept\", \"application/json\"], [\"Authorization\", \"Bearer ...\"]]"),
176
+ data: z
177
+ .union([z.string(), z.record(z.string(), z.unknown())])
178
+ .optional()
179
+ .describe("Request body for non-GET methods. Strings sent as-is; objects auto-serialized to JSON."),
180
+ validate: AutoValidateSchema,
181
+ returnSession: z
182
+ .boolean()
183
+ .optional()
184
+ .describe("Return the {proxy, cookies, userAgent} session triple of the winning session so you can replay it through foura_single / foura_proxy later. Default true. Send false for a leaner response when you only need the content."),
185
+ forceProxy: z
186
+ .boolean()
187
+ .optional()
188
+ .describe("Always reach the target through a rotating proxy, never from FourA's own egress. Default true (the target never sees FourA's origin IP). Send false to allow the cheaper direct path - but note some trust-gated defenses actually resolve more easily from the direct egress, so forcing a proxy can make those targets harder (more attempts / credits)."),
189
+ timeout_ms: z
190
+ .number()
191
+ .int()
192
+ .min(5_000)
193
+ .max(180_000)
194
+ .optional()
195
+ .describe("Total time budget in ms for the WHOLE operation - auto fires several internal attempts and they must all fit inside this. Default 120000, max 180000. Auto portions the budget across its attempts; it does not hand the whole budget to one attempt."),
196
+ ignoreProxies: z
197
+ .array(z.string())
198
+ .optional()
199
+ .describe("Exits to AVOID - base36 proxy ids (like \"4DZ3VE\") or proxy URLs. Auto skips a warm session on one of these and tells its internal proxy search to avoid them too. Use to rotate away from an exit that just got blocked."),
200
+ followRedirects: z
201
+ .number()
202
+ .int()
203
+ .min(0)
204
+ .max(20)
205
+ .optional()
206
+ .describe("Follow up to N redirects on the cheap (direct / proxy) rungs so a 301/302 lands on the real content instead of being returned as-is. Default 5; 0 = don't follow. The browser rung follows redirects natively."),
207
+ // Bug 3 parity - opt-in offload. Default false → response inline regardless of
208
+ // size so clients that can't read resource_link blocks still get usable output.
209
+ offload_large: z
210
+ .boolean()
211
+ .optional()
212
+ .describe("If true, response bodies >= 50KB are written to disk and returned as a resource_link instead of inlined. Saves token context but requires a client that supports `resources/read`. Default false."),
213
+ };
214
+ // Convert any handler-level crash OR output-validation failure into the
215
+ // documented {service, code, error} envelope (same guard as the other tools).
216
+ async function guardHandler(service,
217
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
218
+ outputSchema,
219
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
220
+ fn) {
221
+ try {
222
+ const result = await fn();
223
+ if (result?.structuredContent) {
224
+ const parsed = outputSchema.safeParse(result.structuredContent);
225
+ if (!parsed.success) {
226
+ const issues = parsed.error.issues.slice(0, 5).map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
227
+ return {
228
+ isError: true,
229
+ content: [{ type: "text", text: `FourA ${service} - upstream response failed schema: ${issues}` }],
230
+ structuredContent: {
231
+ service,
232
+ code: "output_validation_failed",
233
+ error: `Upstream response did not match the expected schema: ${issues}`,
234
+ },
235
+ };
236
+ }
237
+ }
238
+ return result;
239
+ }
240
+ catch (e) {
241
+ const msg = e instanceof Error ? e.message : String(e);
242
+ return {
243
+ isError: true,
244
+ content: [{ type: "text", text: `FourA ${service} - internal error: ${msg}` }],
245
+ structuredContent: {
246
+ service,
247
+ code: "output_validation_failed",
248
+ error: `Tool handler crashed before producing a response: ${msg}`,
249
+ },
250
+ };
251
+ }
252
+ }
253
+ export function registerAutoTool(server) {
254
+ server.registerTool("foura_auto", {
255
+ title: "FourA - auto (smart fetch, picks the method for you)",
256
+ description: "Give a URL, get the content back. The default first choice for any page when you just want " +
257
+ "the data and don't want to decide how to fetch it. Internally it walks a cost-aware ladder - a " +
258
+ "fast direct request first, then a rotating proxy, then a full browser session - escalating only " +
259
+ "as far as the target forces it, solving common bot challenges (Cloudflare, and similar) on the " +
260
+ "way, and cheaply replaying a warm session on repeat calls to the same host. It learns the right " +
261
+ "settings per host on its own, so there are no maxTries / pool / retry knobs to tune. " +
262
+ "Pass `validate` (a substring the real page must contain) on protected targets so it can tell a " +
263
+ "real page from a challenge page. The response includes a `meta` trace (which rung delivered, " +
264
+ "credits spent) and, by default, the winning `session` ({proxy, cookies, userAgent}) so you can " +
265
+ "replay it through foura_single / foura_proxy afterwards. " +
266
+ "Use one of the lower-level tools instead only when you need explicit control: foura_single for a " +
267
+ "specific raw HTTP request, foura_proxy to drive the rotation/exit yourself, foura_browser for a " +
268
+ "scripted browser session.",
269
+ inputSchema: autoInputShape,
270
+ outputSchema: autoOutputShape,
271
+ annotations: {
272
+ readOnlyHint: true,
273
+ destructiveHint: false,
274
+ openWorldHint: true,
275
+ },
276
+ }, async (input) => guardHandler("auto", z.object(autoOutputShape), async () => {
277
+ try {
278
+ await assertPublicTarget(input.url);
279
+ }
280
+ catch (e) {
281
+ if (e instanceof SsrfBlockedError) {
282
+ return {
283
+ isError: true,
284
+ content: [{ type: "text", text: e.message }],
285
+ structuredContent: { service: "auto", code: "ssrf_blocked", error: e.message },
286
+ };
287
+ }
288
+ throw e;
289
+ }
290
+ // Strip MCP-layer-only fields before forwarding upstream.
291
+ const { offload_large, ...upstreamBody } = input;
292
+ // auto can run up to its 180s budget; give undici a generous ceiling so it
293
+ // never cuts the orchestrator short (the server-side budget is the real cap).
294
+ const res = await request(AUTO_API_URL, {
295
+ method: "POST",
296
+ headers: {
297
+ "X-API-Key": getApiKey(),
298
+ "Content-Type": "application/json",
299
+ "User-Agent": "foura-mcp/0.3.1 (auto)",
300
+ },
301
+ body: JSON.stringify(upstreamBody),
302
+ headersTimeout: 200_000,
303
+ bodyTimeout: 200_000,
304
+ });
305
+ const text = await res.body.text();
306
+ let parsed;
307
+ try {
308
+ parsed = JSON.parse(text);
309
+ }
310
+ catch {
311
+ return {
312
+ isError: true,
313
+ content: [
314
+ {
315
+ type: "text",
316
+ text: `FourA auto - non-JSON response (${res.statusCode}): ${text.slice(0, 200)}`,
317
+ },
318
+ ],
319
+ structuredContent: {
320
+ service: "auto",
321
+ code: "upstream_non_json",
322
+ status: res.statusCode,
323
+ error: `Upstream returned non-JSON (${res.statusCode}): ${text.slice(0, 200)}`,
324
+ },
325
+ };
326
+ }
327
+ // Gateway-level error (rate limit / auth / capacity) - auto itself ALWAYS
328
+ // replies transport-200, so a non-2xx transport status is the gateway
329
+ // rejecting before auto ran. Surface it like the other tools do.
330
+ if (res.statusCode < 200 || res.statusCode >= 300) {
331
+ const e = parsed;
332
+ const errMsg = typeof e.error === "string" ? e.error : "Unknown";
333
+ const retryStr = typeof e.retryAfter === "number" ? ` · retry ${e.retryAfter}s` : "";
334
+ return {
335
+ isError: true,
336
+ content: [{ type: "text", text: `FourA auto error ${res.statusCode}: ${errMsg}${retryStr}` }],
337
+ structuredContent: {
338
+ ...e,
339
+ service: "auto",
340
+ code: deriveCode(res.statusCode, e),
341
+ status: typeof e.status === "number" ? e.status : res.statusCode,
342
+ },
343
+ };
344
+ }
345
+ const parsedObj = parsed;
346
+ // auto returns transport-200; the real verdict is in the body. The
347
+ // PRIMARY error signal is a non-empty `error` (the ladder failed). A
348
+ // non-2xx body.status WITHOUT an error is a legitimate success (the
349
+ // client's validate accepted that status), so it is NOT an error.
350
+ if (typeof parsedObj.error === "string" && parsedObj.error.length > 0) {
351
+ const innerStatus = typeof parsedObj.status === "number" ? parsedObj.status : 0;
352
+ return {
353
+ isError: true,
354
+ content: [
355
+ {
356
+ type: "text",
357
+ text: `FourA auto - could not deliver content (status ${innerStatus}): ${parsedObj.error}`,
358
+ },
359
+ ],
360
+ structuredContent: {
361
+ ...parsedObj,
362
+ service: "auto",
363
+ code: innerStatus > 0 ? deriveCode(innerStatus, parsedObj) : "upstream_error",
364
+ status: innerStatus,
365
+ },
366
+ };
367
+ }
368
+ const data = parsedObj.data;
369
+ let bodyStr = null;
370
+ if (typeof data === "string")
371
+ bodyStr = data;
372
+ else if (data && typeof data === "object")
373
+ bodyStr = JSON.stringify(data);
374
+ const statusLabel = parsedObj.status ?? "?";
375
+ const meta = (parsedObj.meta ?? {});
376
+ const rungLabel = meta.rung ? ` · ${meta.rung}` : "";
377
+ const creditLabel = typeof meta.credits === "number" ? ` · ${meta.credits}cr` : "";
378
+ const shouldOffload = offload_large === true
379
+ && bodyStr
380
+ && Buffer.byteLength(bodyStr, "utf8") >= THRESHOLD_BYTES;
381
+ if (shouldOffload && bodyStr) {
382
+ const ct = extractContentTypeFromHeaderInfo(parsedObj.headers) ?? "text/plain";
383
+ const stored = await storePayload(bodyStr, ct, "response-body");
384
+ const sizeKb = (stored.size / 1024).toFixed(1);
385
+ return {
386
+ content: [
387
+ { type: "text", text: `${statusLabel} · offloaded ${sizeKb} KB${rungLabel}${creditLabel}` },
388
+ { type: "resource_link", uri: stored.uri, name: stored.name, mimeType: stored.mimeType },
389
+ ],
390
+ structuredContent: {
391
+ status: parsedObj.status,
392
+ headers: parsedObj.headers,
393
+ meta: parsedObj.meta,
394
+ session: parsedObj.session,
395
+ offloaded_resource_uri: stored.uri,
396
+ size_bytes: stored.size,
397
+ },
398
+ };
399
+ }
400
+ const sizeKb = bodyStr ? (Buffer.byteLength(bodyStr, "utf8") / 1024).toFixed(1) : "0";
401
+ return {
402
+ content: [{ type: "text", text: `${statusLabel} OK · ${sizeKb} KB${rungLabel}${creditLabel}` }],
403
+ structuredContent: parsedObj,
404
+ };
405
+ }));
406
+ }
407
+ // Export internals for unit tests + schema-parity checks.
408
+ export const __test = { deriveCode, HeaderInfoSchema, AutoValidateSchema, autoInputShape, autoOutputShape, guardHandler, AUTO_API_URL };
409
+ //# sourceMappingURL=auto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto.js","sourceRoot":"","sources":["../../src/tools/auto.ts"],"names":[],"mappings":"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"}
@@ -34,6 +34,7 @@ export declare const __test: {
34
34
  timeout_ms: z.ZodOptional<z.ZodNumber>;
35
35
  checkStatus: z.ZodOptional<z.ZodNumber>;
36
36
  checkText: z.ZodOptional<z.ZodString>;
37
+ unblocker: z.ZodOptional<z.ZodBoolean>;
37
38
  offload_large: z.ZodOptional<z.ZodBoolean>;
38
39
  };
39
40
  browserOutputShape: {
@@ -53,7 +53,7 @@ fn) {
53
53
  const issues = parsed.error.issues.slice(0, 5).map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
54
54
  return {
55
55
  isError: true,
56
- content: [{ type: "text", text: `FourA ${service} upstream response failed schema: ${issues}` }],
56
+ content: [{ type: "text", text: `FourA ${service} - upstream response failed schema: ${issues}` }],
57
57
  structuredContent: {
58
58
  service,
59
59
  code: "output_validation_failed",
@@ -68,7 +68,7 @@ fn) {
68
68
  const msg = e instanceof Error ? e.message : String(e);
69
69
  return {
70
70
  isError: true,
71
- content: [{ type: "text", text: `FourA ${service} internal error: ${msg}` }],
71
+ content: [{ type: "text", text: `FourA ${service} - internal error: ${msg}` }],
72
72
  structuredContent: {
73
73
  service,
74
74
  code: "output_validation_failed",
@@ -83,8 +83,8 @@ const BrowserCookieInputSchema = z.object({
83
83
  value: z.string(),
84
84
  domain: z.string().optional(),
85
85
  });
86
- // Full CDP cookie object captured from Chrome via Network.getAllCookies after navigation.
87
- // Permissive Protocol.Network.Cookie has more fields than we model, and CDP versions vary.
86
+ // Full CDP cookie object - captured from Chrome via Network.getAllCookies after navigation.
87
+ // Permissive - Protocol.Network.Cookie has more fields than we model, and CDP versions vary.
88
88
  const CdpCookieSchema = z
89
89
  .object({
90
90
  name: z.string(),
@@ -100,10 +100,10 @@ const CdpCookieSchema = z
100
100
  })
101
101
  .catchall(z.unknown());
102
102
  const browserOutputShape = {
103
- // Success dwstack browser DwResponse (packages/browser/src/schema.ts:19-26)
104
- status: z.number().int().optional().describe("HTTP status code from the target page. `0` indicates the navigation failed before any HTTP response (DNS / connection refused / timeout) check the `error` field for the underlying reason."),
103
+ // Success - dwstack browser DwResponse (packages/browser/src/schema.ts:19-26)
104
+ status: z.number().int().optional().describe("HTTP status code from the target page. `0` indicates the navigation failed before any HTTP response (DNS / connection refused / timeout) - check the `error` field for the underlying reason."),
105
105
  // CDP headers (Protocol.Network.Headers) are a Record but values can be
106
- // string OR string[] in some CDP versions. Bug 11 fix permissive.
106
+ // string OR string[] in some CDP versions. Bug 11 fix - permissive.
107
107
  headers: z
108
108
  .record(z.string(), z.unknown())
109
109
  .optional()
@@ -115,7 +115,7 @@ const browserOutputShape = {
115
115
  .describe("Fully-rendered page content. String HTML when content-type is HTML; object when the page returned JSON and it was auto-parsed. Field is named `body`, not `data`. Omitted when offloaded."),
116
116
  cookies: z.array(CdpCookieSchema).optional().describe("Full cookie objects collected after navigation (name, value, domain, path, expires, httpOnly, secure, session, sameSite, …)"),
117
117
  userAgent: z.string().optional().describe("The User-Agent the browser session presented"),
118
- // Offload path MCP layer adds these when body >= 50KB AND offload_large=true
118
+ // Offload path - MCP layer adds these when body >= 50KB AND offload_large=true
119
119
  offloaded_resource_uri: z.string().optional().describe("foura-mcp://payload/<uuid>"),
120
120
  size_bytes: z.number().int().optional().describe("Total offloaded body size in bytes"),
121
121
  // Error path
@@ -131,7 +131,7 @@ const browserOutputShape = {
131
131
  code: z.string().optional().describe("Stable error code for retry classification. One of: ssrf_blocked, upstream_non_json, output_validation_failed, bad_request (400), auth_failed (401), forbidden (403), not_found (404), rate_limited (429), at_capacity (503), service_disabled (503), service_unavailable (503), upstream_error (>=500), upstream_client_error (other 4xx), upstream_unknown (defensive)."),
132
132
  };
133
133
  const browserInputShape = {
134
- url: z.string().url().describe("Target URL to load in a full browser session. Public hosts only private/reserved ranges (RFC 1918 + loopback + link-local + IPv6 ULA/loopback + *.local mDNS) are refused with code `ssrf_blocked`. Example: https://shop.example.com/product/123 or any single-page-app URL."),
134
+ url: z.string().url().describe("Target URL to load in a full browser session. Public hosts only - private/reserved ranges (RFC 1918 + loopback + link-local + IPv6 ULA/loopback + *.local mDNS) are refused with code `ssrf_blocked`. Example: https://shop.example.com/product/123 or any single-page-app URL."),
135
135
  headers: z
136
136
  .record(z.string(), z.string())
137
137
  .optional()
@@ -145,7 +145,7 @@ const browserInputShape = {
145
145
  .string()
146
146
  .optional()
147
147
  .describe("Optional proxy. Three forms: (1) URL `http://user:pass@host:port` or `socks5://host:port`; " +
148
- "(2) base36 ID from foura_proxy (e.g. `4DZ3VE`) same pool exit IP; (3) omit → fixed container egress."),
148
+ "(2) base36 ID from foura_proxy (e.g. `4DZ3VE`) - same pool exit IP; (3) omit → fixed container egress."),
149
149
  timeout_ms: z
150
150
  .number()
151
151
  .int()
@@ -161,8 +161,12 @@ const browserInputShape = {
161
161
  checkText: z
162
162
  .string()
163
163
  .optional()
164
- .describe("One-shot post-render validator substring search on the rendered HTML AFTER navigation completes. NOT a waiter: does not poll, does not block until the substring appears. If the substring is missing, the tool returns an error envelope. Use to catch silent failures like a 200 response that captured a challenge page or empty SPA shell. Example: \"add to cart\" for product pages."),
165
- // Bug 3 fix — opt-in offload, default false (inline).
164
+ .describe("One-shot post-render validator - substring search on the rendered HTML AFTER navigation completes. NOT a waiter: does not poll, does not block until the substring appears. If the substring is missing, the tool returns an error envelope. Use to catch silent failures like a 200 response that captured a challenge page or empty SPA shell. Example: \"add to cart\" for product pages."),
165
+ unblocker: z
166
+ .boolean()
167
+ .optional()
168
+ .describe("Actively SOLVE an anti-bot / captcha challenge (Cloudflare Turnstile and similar) encountered during navigation, instead of just rendering it. Default true. Set false to render and return the page exactly as it loads - including a challenge page - without attempting to solve; cheaper when you already know the target is open or you want the raw challenge page."),
169
+ // Bug 3 fix - opt-in offload, default false (inline).
166
170
  offload_large: z
167
171
  .boolean()
168
172
  .optional()
@@ -170,20 +174,21 @@ const browserInputShape = {
170
174
  };
171
175
  export function registerBrowserTool(server) {
172
176
  server.registerTool("foura_browser", {
173
- title: "FourA full browser navigation",
174
- description: "Load a URL in a real browser session. JS runs, DOM renders, cookies come back. 210s. " +
177
+ title: "FourA - full browser navigation",
178
+ description: "Load a URL in a real browser session. JS runs, DOM renders, cookies come back. 2-10s. " +
175
179
  "Use for SPAs, lazy-loaded content, or JS anti-bot challenges (Cloudflare Turnstile etc.). " +
176
180
  "Prefer foura_single for static HTML; foura_proxy for static pages where your IP is blocked. " +
177
181
  "To rotate the browser's exit IP: call foura_proxy first, pass its returned `proxy` ID into " +
178
- "this tool's `proxy` field the browser exits through that pool IP. Default (no `proxy`) " +
182
+ "this tool's `proxy` field - the browser exits through that pool IP. Default (no `proxy`) " +
179
183
  "is one fixed container egress. If the target is behind a tier-1 WAF challenge (Vercel / " +
180
184
  "Cloudflare / Akamai), calling this tool directly will usually still capture the challenge " +
181
- "page rather than the post-challenge content the snapshot fires before the challenge's " +
185
+ "page rather than the post-challenge content - the snapshot fires before the challenge's " +
182
186
  "deferred reload completes. Correct chain: call foura_proxy first with maxTries:25-30 against " +
183
187
  "the same URL → take the returned proxy base36 ID → pass it into this tool's `proxy` field. " +
184
188
  "The browser then exits through the IP that already cleared the challenge for this target. " +
185
- "No `unblocker` flag browser navigation already sends real browser headers by default, so " +
186
- "the flag would be redundant.",
189
+ "By default the browser actively solves an anti-bot / captcha challenge it meets along the way " +
190
+ "(the `unblocker` flag, default true); set `unblocker:false` to skip solving and return the " +
191
+ "page exactly as it loads, challenge page included.",
187
192
  inputSchema: browserInputShape,
188
193
  outputSchema: browserOutputShape,
189
194
  annotations: {
@@ -211,7 +216,7 @@ export function registerBrowserTool(server) {
211
216
  headers: {
212
217
  "X-API-Key": getApiKey(),
213
218
  "Content-Type": "application/json",
214
- "User-Agent": "foura-mcp/0.2.11 (browser)",
219
+ "User-Agent": "foura-mcp/0.3.1 (browser)",
215
220
  },
216
221
  body: JSON.stringify(upstreamBody),
217
222
  });
@@ -226,7 +231,7 @@ export function registerBrowserTool(server) {
226
231
  content: [
227
232
  {
228
233
  type: "text",
229
- text: `FourA browser non-JSON response (${res.statusCode}): ${text.slice(0, 200)}`,
234
+ text: `FourA browser - non-JSON response (${res.statusCode}): ${text.slice(0, 200)}`,
230
235
  },
231
236
  ],
232
237
  structuredContent: {
@@ -253,7 +258,7 @@ export function registerBrowserTool(server) {
253
258
  };
254
259
  }
255
260
  const parsedObj = parsed;
256
- // Bug 8 fix browser request.ts also rejects with DwResponseError on
261
+ // Bug 8 fix - browser request.ts also rejects with DwResponseError on
257
262
  // page failure (timeout, navigation error, checkStatus mismatch).
258
263
  // Service forwards as 200 + {error, ...} via dwRequestToExpressRes.
259
264
  if (typeof parsedObj.error === "string" && parsedObj.error.length > 0) {
@@ -263,7 +268,7 @@ export function registerBrowserTool(server) {
263
268
  content: [
264
269
  {
265
270
  type: "text",
266
- text: `FourA browser page failure (status ${innerStatus}): ${parsedObj.error}`,
271
+ text: `FourA browser - page failure (status ${innerStatus}): ${parsedObj.error}`,
267
272
  },
268
273
  ],
269
274
  structuredContent: {