@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.
- package/README.md +58 -38
- package/dist/http.js +1 -1
- package/dist/http.js.map +1 -1
- package/dist/prompts.d.ts +2 -2
- package/dist/prompts.js +38 -6
- package/dist/prompts.js.map +1 -1
- package/dist/resources.d.ts +3 -3
- package/dist/resources.js +5 -5
- package/dist/server.js +5 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/auto.d.ts +101 -0
- package/dist/tools/auto.js +409 -0
- package/dist/tools/auto.js.map +1 -0
- package/dist/tools/browser.d.ts +1 -0
- package/dist/tools/browser.js +27 -22
- package/dist/tools/browser.js.map +1 -1
- package/dist/tools/proxy.js +26 -26
- package/dist/tools/proxy.js.map +1 -1
- package/dist/tools/single.js +29 -29
- package/dist/tools/single.js.map +1 -1
- package/package.json +1 -1
|
@@ -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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"}
|
package/dist/tools/browser.d.ts
CHANGED
|
@@ -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: {
|
package/dist/tools/browser.js
CHANGED
|
@@ -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}
|
|
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}
|
|
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
|
|
87
|
-
// Permissive
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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`)
|
|
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
|
|
165
|
-
|
|
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
|
|
174
|
-
description: "Load a URL in a real browser session. JS runs, DOM renders, cookies come back. 2
|
|
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
|
|
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
|
|
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
|
-
"
|
|
186
|
-
"the flag
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
271
|
+
text: `FourA browser - page failure (status ${innerStatus}): ${parsedObj.error}`,
|
|
267
272
|
},
|
|
268
273
|
],
|
|
269
274
|
structuredContent: {
|