@fouradata/mcp 0.3.0 → 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 +39 -39
- package/dist/http.js +1 -1
- package/dist/prompts.d.ts +2 -2
- package/dist/prompts.js +9 -9
- package/dist/resources.d.ts +3 -3
- package/dist/resources.js +5 -5
- package/dist/server.js +1 -1
- package/dist/tools/auto.js +24 -24
- package/dist/tools/browser.js +21 -21
- package/dist/tools/proxy.js +25 -25
- package/dist/tools/single.js +29 -29
- package/dist/tools/single.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# @fouradata/mcp
|
|
2
2
|
|
|
3
|
-
[FourA Web Scraping API](https://foura.ai/) as
|
|
3
|
+
[FourA Web Scraping API](https://foura.ai/) as four [Model Context Protocol](https://modelcontextprotocol.io) tools plus six built-in workflow prompts. Plug it into Claude Desktop, Claude Code, Cursor, Windsurf, or any other MCP client and fetch arbitrary public web pages, bypass anti-bot challenges, and render JavaScript-heavy sites - without writing a line of integration code.
|
|
4
4
|
|
|
5
5
|
Four tools, six prompts, one API key.
|
|
6
6
|
|
|
7
|
-
## Quick Start
|
|
7
|
+
## Quick Start - local stdio (recommended for Claude Desktop)
|
|
8
8
|
|
|
9
9
|
Grab a key at [foura.ai/dashboard#api-keys](https://foura.ai/dashboard#api-keys) (one click, shown once on creation, format `pk_live_...`). Then drop this into your MCP client's config:
|
|
10
10
|
|
|
@@ -24,7 +24,7 @@ Grab a key at [foura.ai/dashboard#api-keys](https://foura.ai/dashboard#api-keys)
|
|
|
24
24
|
|
|
25
25
|
> **Claude Desktop gotcha:** fully quit Claude Desktop (`Cmd+Q` on macOS) **before** editing the config file. If the app is still running, it will overwrite your edits with its in-memory config on exit.
|
|
26
26
|
|
|
27
|
-
The npx command downloads the package on first launch (~10s) and runs it as a subprocess of your MCP client. No global install needed. Same JSON works in every major client
|
|
27
|
+
The npx command downloads the package on first launch (~10s) and runs it as a subprocess of your MCP client. No global install needed. Same JSON works in every major client - just point it at the right file:
|
|
28
28
|
|
|
29
29
|
| Client | Where the config lives |
|
|
30
30
|
|---|---|
|
|
@@ -37,7 +37,7 @@ The npx command downloads the package on first launch (~10s) and runs it as a su
|
|
|
37
37
|
|
|
38
38
|
Restart the client and `foura_auto`, `foura_single`, `foura_proxy`, `foura_browser` show up in your tool list, plus six prompts under `/prompts`.
|
|
39
39
|
|
|
40
|
-
## Quick Start
|
|
40
|
+
## Quick Start - hosted (Streamable HTTP)
|
|
41
41
|
|
|
42
42
|
For clients that support the Streamable HTTP transport (Cursor, Windsurf, VS Code, Claude Code with `--transport http`), point them at the hosted endpoint instead of running a local subprocess:
|
|
43
43
|
|
|
@@ -54,7 +54,7 @@ For clients that support the Streamable HTTP transport (Cursor, Windsurf, VS Cod
|
|
|
54
54
|
}
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
Current Claude Desktop builds reject the bare `url` form
|
|
57
|
+
Current Claude Desktop builds reject the bare `url` form - use the stdio config above for Claude Desktop, or bridge through `mcp-remote`:
|
|
58
58
|
|
|
59
59
|
```json
|
|
60
60
|
{
|
|
@@ -69,15 +69,15 @@ Current Claude Desktop builds reject the bare `url` form — use the stdio confi
|
|
|
69
69
|
|
|
70
70
|
## The Tools
|
|
71
71
|
|
|
72
|
-
`foura_auto` is the **default**
|
|
72
|
+
`foura_auto` is the **default** - give it a URL and it returns the content, picking the fetch method for you. The other three are the lower-level primitives it orchestrates; reach for them when you want explicit control.
|
|
73
73
|
|
|
74
|
-
All four are marked `readOnlyHint: true` and `openWorldHint: true` per the [MCP spec](https://modelcontextprotocol.io/specification/2025-06-18/server/tools)
|
|
74
|
+
All four are marked `readOnlyHint: true` and `openWorldHint: true` per the [MCP spec](https://modelcontextprotocol.io/specification/2025-06-18/server/tools) - clients that auto-approve trusted read-only tools (Claude Desktop, Cursor in 2026) call them without a per-request confirmation modal.
|
|
75
75
|
|
|
76
76
|
Every response carries both human-readable text (`content`) and a typed `structuredContent` JSON object validated against the tool's `outputSchema`. Clients pass `structuredContent` to your LLM natively, skipping the re-tokenization tax on stringified JSON.
|
|
77
77
|
|
|
78
|
-
### `foura_auto`
|
|
78
|
+
### `foura_auto` - smart fetch (the default)
|
|
79
79
|
|
|
80
|
-
Give a URL, get the content back. Use this first when you just want the page and don't want to choose a method. Internally it walks a cost-aware ladder
|
|
80
|
+
Give a URL, get the content back. Use this first when you just want the page and don't want to choose a method. Internally it walks a cost-aware ladder - a fast direct request, then a rotating proxy, then a full browser session - escalating only as far as the target forces it, solving common bot challenges on the way, and cheaply replaying a warm session on repeat calls to the same host. It learns the right settings per host, so there are no `maxTries` / pool / retry knobs to tune.
|
|
81
81
|
|
|
82
82
|
```jsonc
|
|
83
83
|
{
|
|
@@ -88,13 +88,13 @@ Give a URL, get the content back. Use this first when you just want the page and
|
|
|
88
88
|
}
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
The client surface is intentionally minimal: `url` (required), plus optional `method`, `headers`, `data`, `validate`, `returnSession` (default `true`), `forceProxy` (default `true`), `timeout_ms` (5000
|
|
91
|
+
The client surface is intentionally minimal: `url` (required), plus optional `method`, `headers`, `data`, `validate`, `returnSession` (default `true`), `forceProxy` (default `true`), `timeout_ms` (5000-180000, default 120000), `ignoreProxies`.
|
|
92
92
|
|
|
93
|
-
`structuredContent` shape: `{status, headers, data, meta, session}`. `meta` is always present
|
|
93
|
+
`structuredContent` shape: `{status, headers, data, meta, session}`. `meta` is always present - `{rung, solved, attempts, credits}` - the trace of which rung delivered and what it cost. `session` (`{proxy, cookies, userAgent}`) is returned by default so you can replay the same session through `foura_single` / `foura_proxy` afterwards (pass `session.proxy` into their `proxy` field). Send `returnSession: false` to omit it. There is no `total_time` field on auto.
|
|
94
94
|
|
|
95
|
-
### `foura_single`
|
|
95
|
+
### `foura_single` - fast HTTP
|
|
96
96
|
|
|
97
|
-
One HTTP request, response back. Typically 200ms
|
|
97
|
+
One HTTP request, response back. Typically 200ms-2s. Use it for static pages, JSON APIs, server-rendered HTML - the bread and butter of scraping. Set `unblocker: true` if the target is picky about wire-level signals.
|
|
98
98
|
|
|
99
99
|
```jsonc
|
|
100
100
|
{
|
|
@@ -104,11 +104,11 @@ One HTTP request, response back. Typically 200ms–2s. Use it for static pages,
|
|
|
104
104
|
}
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
-
Supports custom headers, a body, per-stage timeouts, redirect controls, JSON auto-parse, a binary-buffer mode, and built-in response validation (`validate.status.accept`, `validate.data.fail`, and so on). If `foura_single` comes back blocked
|
|
107
|
+
Supports custom headers, a body, per-stage timeouts, redirect controls, JSON auto-parse, a binary-buffer mode, and built-in response validation (`validate.status.accept`, `validate.data.fail`, and so on). If `foura_single` comes back blocked - status 403/429, captcha page, OR response headers `x-vercel-mitigated: challenge` / `cf-mitigated: challenge`, OR body title matches `Vercel Security Checkpoint` / `Just a moment` / `Attention Required` - escalate to `foura_proxy` with `maxTries: 25-30` for these tier-1 WAFs. If the page also needs JavaScript to render, chain `foura_proxy`'s returned `proxy` ID into `foura_browser.proxy`.
|
|
108
108
|
|
|
109
109
|
`structuredContent` shape: `{status, headers, data, total_time, ...}`.
|
|
110
110
|
|
|
111
|
-
### `foura_proxy`
|
|
111
|
+
### `foura_proxy` - rotating proxies with retry
|
|
112
112
|
|
|
113
113
|
Same target shape as `foura_single`, but routed through a pool of proxies with automatic retry on failure. Per-host scoring picks the proxies most likely to succeed against this particular target, so you're not burning attempts on known-bad routes.
|
|
114
114
|
|
|
@@ -123,9 +123,9 @@ Same target shape as `foura_single`, but routed through a pool of proxies with a
|
|
|
123
123
|
}
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
-
Typical latency 1
|
|
126
|
+
Typical latency 1-5s. `structuredContent` adds `proxy` (the encoded ID of the proxy that succeeded - pass it to `ignoreProxies` next time if it later goes bad) and `total` (outer timing including selection + retries). For tier-1 WAF challenges (Vercel Security Checkpoint, Cloudflare 'Just a moment', Akamai Bot Manager) use `maxTries: 25-30` - the default 5 is sized for lightly-blocked sites. If still blocked after 30 attempts the gate is likely country / ASN allowlist (not solvable by rotation) - pivot strategy. If the target needs JavaScript render, chain the returned `proxy` ID into `foura_browser.proxy` - the browser then exits through the IP that already cleared the challenge for this target.
|
|
127
127
|
|
|
128
|
-
### `foura_browser`
|
|
128
|
+
### `foura_browser` - full browser session
|
|
129
129
|
|
|
130
130
|
A real browser session. JavaScript runs, the DOM finishes rendering, cookies come back with the response. Use it when the page is a single-page app, when content lazy-loads after first paint, or when there's an anti-bot challenge that needs a real browser to clear.
|
|
131
131
|
|
|
@@ -137,13 +137,13 @@ A real browser session. JavaScript runs, the DOM finishes rendering, cookies com
|
|
|
137
137
|
}
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
-
Slowest of the
|
|
140
|
+
Slowest of the lower-level tools (2-10s) but the only tool that handles JavaScript end-to-end. `checkText` is a one-shot post-render validator (substring search on the rendered HTML AFTER navigation completes - not a waiter, does not poll): if the substring is missing, the call fails with an error envelope. Useful when a page returns 200 but the actual content is missing. `unblocker` defaults to `true` - the session actively solves an anti-bot / captcha challenge (Cloudflare Turnstile and similar) it meets along the way; set `unblocker: false` to render and return the page exactly as it loads, challenge page included.
|
|
141
141
|
|
|
142
142
|
`structuredContent` shape is intentionally different from single/proxy: `{status, headers (object, not array), body (not data), cookies (full browser cookie shape), userAgent}`.
|
|
143
143
|
|
|
144
144
|
## Built-in Prompts
|
|
145
145
|
|
|
146
|
-
|
|
146
|
+
Six workflow templates surfaced under `/prompts` in your MCP client. They orchestrate one or more tools without you spelling out the steps.
|
|
147
147
|
|
|
148
148
|
| Prompt | Arguments | What it does |
|
|
149
149
|
|---|---|---|
|
|
@@ -154,7 +154,7 @@ Five workflow templates surfaced under `/prompts` in your MCP client. They orche
|
|
|
154
154
|
| `check_endpoint_health` | `url, expected_text?` | Single with strict validation → reachable/status/timing report |
|
|
155
155
|
| `bulk_fetch_urls` | `urls` (comma-separated) | Parallel single → auto-fallback to proxy per URL → metadata only |
|
|
156
156
|
|
|
157
|
-
Each prompt arrives as a templated user message your LLM executes with the right tools. They cost zero tokens at idle
|
|
157
|
+
Each prompt arrives as a templated user message your LLM executes with the right tools. They cost zero tokens at idle - only invoked prompts enter the context window.
|
|
158
158
|
|
|
159
159
|
Full recipe text + manual fallback prompts: [foura.ai/docs/mcp/recipes](https://foura.ai/docs/mcp/recipes). For the full error code list, see [foura.ai/docs/mcp/errors](https://foura.ai/docs/mcp/errors).
|
|
160
160
|
|
|
@@ -162,9 +162,9 @@ Full recipe text + manual fallback prompts: [foura.ai/docs/mcp/recipes](https://
|
|
|
162
162
|
|
|
163
163
|
Your `Bearer` token (or the `FOURA_API_KEY` env var in stdio mode) forwards to the FourA API as `X-API-Key`. One key, all four tools.
|
|
164
164
|
|
|
165
|
-
Keys are managed in the [dashboard](https://foura.ai/dashboard#api-keys)
|
|
165
|
+
Keys are managed in the [dashboard](https://foura.ai/dashboard#api-keys) - shown once on creation, rotate or deactivate any time. See [foura.ai/docs/getting-started/authentication](https://foura.ai/docs/getting-started/authentication) for the full key-management walkthrough.
|
|
166
166
|
|
|
167
|
-
## Error envelope
|
|
167
|
+
## Error envelope - typed contract for agent retries
|
|
168
168
|
|
|
169
169
|
Every error (`isError: true`) carries a `structuredContent` envelope with at minimum these three fields:
|
|
170
170
|
|
|
@@ -180,27 +180,27 @@ Where the upstream returned a status, you also get `status` (HTTP code) and on r
|
|
|
180
180
|
|
|
181
181
|
| `code` | When | Retry safe? |
|
|
182
182
|
|---|---|---|
|
|
183
|
-
| `ssrf_blocked` | Target IP in a private / reserved range (RFC 5735+6598+IPv6 reserved) | No
|
|
184
|
-
| `upstream_non_json` | Upstream returned malformed body | Maybe
|
|
185
|
-
| `bad_request` (400) | Input shape rejected by FourA | No
|
|
186
|
-
| `auth_failed` (401) | Key missing, invalid, or deactivated | No
|
|
183
|
+
| `ssrf_blocked` | Target IP in a private / reserved range (RFC 5735+6598+IPv6 reserved) | No - change the URL |
|
|
184
|
+
| `upstream_non_json` | Upstream returned malformed body | Maybe - investigate |
|
|
185
|
+
| `bad_request` (400) | Input shape rejected by FourA | No - fix arguments |
|
|
186
|
+
| `auth_failed` (401) | Key missing, invalid, or deactivated | No - fix the key |
|
|
187
187
|
| `forbidden` (403) | Authenticated but not allowed | No |
|
|
188
188
|
| `not_found` (404) | Target / endpoint doesn't exist | No |
|
|
189
|
-
| `rate_limited` (429) | RPM cap hit | Yes
|
|
190
|
-
| `at_capacity` (503) | Concurrency cap hit | Yes
|
|
191
|
-
| `service_disabled` (503) | Maintenance window | Yes
|
|
192
|
-
| `service_unavailable` (503) | Generic 503 | Yes
|
|
193
|
-
| `upstream_error` (≥500) | Upstream 5xx | Yes
|
|
189
|
+
| `rate_limited` (429) | RPM cap hit | Yes - wait `retryAfter` |
|
|
190
|
+
| `at_capacity` (503) | Concurrency cap hit | Yes - wait `retryAfter` |
|
|
191
|
+
| `service_disabled` (503) | Maintenance window | Yes - wait `retryAfter` |
|
|
192
|
+
| `service_unavailable` (503) | Generic 503 | Yes - short backoff |
|
|
193
|
+
| `upstream_error` (≥500) | Upstream 5xx | Yes - exponential backoff |
|
|
194
194
|
| `upstream_client_error` (4xx) | Other 4xx | Usually no |
|
|
195
195
|
|
|
196
196
|
LLM agents can read `code` directly for retry logic without parsing prose. Spec reference: [foura.ai/docs/api/errors](https://foura.ai/docs/api/errors).
|
|
197
197
|
|
|
198
|
-
## Combining the tools
|
|
198
|
+
## Combining the tools - sticky exit IPs
|
|
199
199
|
|
|
200
|
-
The
|
|
200
|
+
The lower-level tools compose. `foura_proxy` returns the base36 ID of the exit it used. Pass that ID back into `foura_single.proxy` or `foura_browser.proxy` and the next call exits through the **same IP** - same session, same fingerprint, same geo.
|
|
201
201
|
|
|
202
202
|
```jsonc
|
|
203
|
-
// 1. Find a working exit for the target
|
|
203
|
+
// 1. Find a working exit for the target - use maxTries:25-30 for tier-1 WAFs
|
|
204
204
|
const r = await foura_proxy({
|
|
205
205
|
maxTries: 30,
|
|
206
206
|
request: { method: "GET", url: "https://probe.example.com", unblocker: true }
|
|
@@ -210,17 +210,17 @@ const r = await foura_proxy({
|
|
|
210
210
|
// 2. Reuse it for follow-up HTTP (cookies, multi-step flows)
|
|
211
211
|
await foura_single({ method: "GET", url: "https://target/api", proxy: r.proxy });
|
|
212
212
|
|
|
213
|
-
// 3. Or render JS through the same egress
|
|
213
|
+
// 3. Or render JS through the same egress - exits through the IP that already
|
|
214
214
|
// cleared the challenge for this target, so the snapshot captures the real
|
|
215
215
|
// post-challenge content instead of a challenge page.
|
|
216
216
|
await foura_browser({ url: "https://target/spa", proxy: r.proxy });
|
|
217
217
|
```
|
|
218
218
|
|
|
219
|
-
This chain is the canonical pattern for **tier-1 WAF + JavaScript-rendered targets** (Vercel Security Checkpoint, Cloudflare 'Just a moment', Akamai Bot Manager protecting SPAs). Calling `foura_browser` directly against a WAF target usually captures the challenge page
|
|
219
|
+
This chain is the canonical pattern for **tier-1 WAF + JavaScript-rendered targets** (Vercel Security Checkpoint, Cloudflare 'Just a moment', Akamai Bot Manager protecting SPAs). Calling `foura_browser` directly against a WAF target usually captures the challenge page - the snapshot fires before the challenge's deferred reload completes. Solve via `foura_proxy` first, then chain.
|
|
220
220
|
|
|
221
221
|
To rotate AWAY from a known-bad proxy on the next `foura_proxy` call, pass it as `ignoreProxies: ["4DZ3VE"]`. The `proxy` field on `foura_single` and `foura_browser` also accepts raw URLs (`http://host:port`, `socks5://...`) if you have your own list.
|
|
222
222
|
|
|
223
|
-
## Large responses
|
|
223
|
+
## Large responses - `offload_large` (default: inline)
|
|
224
224
|
|
|
225
225
|
By default (since v0.2.0), full response bodies are returned inline in `structuredContent` regardless of size. This works in every MCP client.
|
|
226
226
|
|
|
@@ -236,7 +236,7 @@ If your client supports MCP `resources/read` (and you want to save tokens on big
|
|
|
236
236
|
|
|
237
237
|
| Client | `offload_large: true` |
|
|
238
238
|
|---|---|
|
|
239
|
-
| Claude Desktop | not yet
|
|
239
|
+
| Claude Desktop | not yet - leave default `false` |
|
|
240
240
|
| Claude Code, Cursor, Windsurf | supported |
|
|
241
241
|
| VS Code MCP extension | supported |
|
|
242
242
|
|
|
@@ -251,7 +251,7 @@ Tenant-isolated: only the API key that stored a payload can read it back.
|
|
|
251
251
|
|
|
252
252
|
## Self-Hosting
|
|
253
253
|
|
|
254
|
-
The MCP server runs in one container, statelessly
|
|
254
|
+
The MCP server runs in one container, statelessly - each request brings its own key, so there's no session state, no sticky load balancing, nothing to coordinate. Scale horizontally behind any load balancer.
|
|
255
255
|
|
|
256
256
|
Configurable environment:
|
|
257
257
|
|
package/dist/http.js
CHANGED
|
@@ -4,7 +4,7 @@ import { SUPPORTED_PROTOCOL_VERSIONS } from "@modelcontextprotocol/sdk/types.js"
|
|
|
4
4
|
import { createServer } from "./server.js";
|
|
5
5
|
import { withApiKey } from "./auth.js";
|
|
6
6
|
const PORT = Number(process.env.PORT ?? 3076);
|
|
7
|
-
const SERVER_VERSION = "0.3.
|
|
7
|
+
const SERVER_VERSION = "0.3.1";
|
|
8
8
|
// Spec MUSTs covered in this file:
|
|
9
9
|
// audit 1.1 — Origin + Host validation (CVE-2025-66414 DNS rebinding)
|
|
10
10
|
// audit 1.2 — WWW-Authenticate on 401
|
package/dist/prompts.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
/**
|
|
3
|
-
* MCP Prompts
|
|
3
|
+
* MCP Prompts - pre-written workflow templates the user can invoke from the
|
|
4
4
|
* MCP client UI (Claude Desktop / Cursor / etc) instead of figuring out the
|
|
5
5
|
* tool orchestration themselves.
|
|
6
6
|
*
|
|
7
|
-
* Prompts are LAZY context
|
|
7
|
+
* Prompts are LAZY context - they only enter the LLM's window when invoked,
|
|
8
8
|
* unlike tool descriptions which are loaded on every turn. So we can be more
|
|
9
9
|
* verbose here.
|
|
10
10
|
*/
|
package/dist/prompts.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
/**
|
|
3
|
-
* MCP Prompts
|
|
3
|
+
* MCP Prompts - pre-written workflow templates the user can invoke from the
|
|
4
4
|
* MCP client UI (Claude Desktop / Cursor / etc) instead of figuring out the
|
|
5
5
|
* tool orchestration themselves.
|
|
6
6
|
*
|
|
7
|
-
* Prompts are LAZY context
|
|
7
|
+
* Prompts are LAZY context - they only enter the LLM's window when invoked,
|
|
8
8
|
* unlike tool descriptions which are loaded on every turn. So we can be more
|
|
9
9
|
* verbose here.
|
|
10
10
|
*/
|
|
@@ -21,7 +21,7 @@ export function registerPrompts(server) {
|
|
|
21
21
|
role: "user",
|
|
22
22
|
content: {
|
|
23
23
|
type: "text",
|
|
24
|
-
text: `Fetch the product page at ${url} using the foura_browser tool
|
|
24
|
+
text: `Fetch the product page at ${url} using the foura_browser tool - most product pages are single-page apps and need JavaScript to render.\n\n` +
|
|
25
25
|
`From the response body extract:\n` +
|
|
26
26
|
`- product title\n` +
|
|
27
27
|
`- price (with currency)\n` +
|
|
@@ -48,7 +48,7 @@ export function registerPrompts(server) {
|
|
|
48
48
|
content: {
|
|
49
49
|
type: "text",
|
|
50
50
|
text: `Fetch ${url} using the foura_single tool with unblocker:true. Most news and blog sites are server-rendered, so HTTP is fastest (200ms-2s).\n\n` +
|
|
51
|
-
`If foura_single returns a 403, captcha page, or empty content, retry the same URL with foura_proxy (maxTries:3)
|
|
51
|
+
`If foura_single returns a 403, captcha page, or empty content, retry the same URL with foura_proxy (maxTries:3) - it routes through a rotating proxy pool.\n\n` +
|
|
52
52
|
`From the response, extract:\n` +
|
|
53
53
|
`- headline (the main H1, not the page title bar)\n` +
|
|
54
54
|
`- author byline (may be inside .author / [rel=author] / itemprop)\n` +
|
|
@@ -137,23 +137,23 @@ export function registerPrompts(server) {
|
|
|
137
137
|
type: "text",
|
|
138
138
|
text: `Parse the following comma-separated URLs and fetch each one concurrently using foura_single (unblocker:true).\n\n` +
|
|
139
139
|
`URLs: ${urls}\n\n` +
|
|
140
|
-
`For any URL that returns 403, captcha page, or empty body
|
|
140
|
+
`For any URL that returns 403, captcha page, or empty body - retry that single URL with foura_proxy (maxTries:3).\n\n` +
|
|
141
141
|
`Return a JSON array, one entry per URL in input order:\n` +
|
|
142
142
|
`[{"url": "...", "status": 200, "success": true, "body_size_bytes": 0, "via": "single|proxy", "error": null}, ...]\n\n` +
|
|
143
|
-
`Do NOT inline full response bodies in the output
|
|
143
|
+
`Do NOT inline full response bodies in the output - only metadata. If the caller needs body content, they should call foura_single individually.`,
|
|
144
144
|
},
|
|
145
145
|
},
|
|
146
146
|
],
|
|
147
147
|
}));
|
|
148
148
|
server.registerPrompt("smart_fetch", {
|
|
149
149
|
title: "Fetch a URL the smart way (auto)",
|
|
150
|
-
description: "Fetch any URL with foura_auto
|
|
150
|
+
description: "Fetch any URL with foura_auto - one call that picks the method (direct / proxy / browser), gets past common bot protection, and returns the content. Use when you just want the page and don't want to choose a tool.",
|
|
151
151
|
argsSchema: {
|
|
152
152
|
url: z.string().describe("URL to fetch"),
|
|
153
153
|
must_contain: z
|
|
154
154
|
.string()
|
|
155
155
|
.optional()
|
|
156
|
-
.describe("Optional substring the real page must contain
|
|
156
|
+
.describe("Optional substring the real page must contain - lets auto tell a real page from a challenge page on protected targets."),
|
|
157
157
|
extract: z
|
|
158
158
|
.string()
|
|
159
159
|
.optional()
|
|
@@ -165,7 +165,7 @@ export function registerPrompts(server) {
|
|
|
165
165
|
role: "user",
|
|
166
166
|
content: {
|
|
167
167
|
type: "text",
|
|
168
|
-
text: `Fetch ${url} using the foura_auto tool. It picks the fetch method for you (direct request, rotating proxy, or full browser) and gets past common bot protection automatically
|
|
168
|
+
text: `Fetch ${url} using the foura_auto tool. It picks the fetch method for you (direct request, rotating proxy, or full browser) and gets past common bot protection automatically - you do not need to choose between foura_single / foura_proxy / foura_browser.\n\n` +
|
|
169
169
|
(must_contain
|
|
170
170
|
? `Pass validate.data.accept:["${must_contain}"] so auto keeps escalating until the real page (containing "${must_contain}") comes back, not a challenge page.\n\n`
|
|
171
171
|
: `If the first response looks like a challenge / block page rather than real content, re-call with validate.data.accept:["<a string the real page must contain>"] so auto knows what success looks like.\n\n`) +
|
package/dist/resources.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
/**
|
|
3
|
-
* Resources
|
|
3
|
+
* Resources - offload large response bodies (>= THRESHOLD bytes) onto host
|
|
4
4
|
* disk and return a MCP resource_link instead of inlining megabytes into the
|
|
5
5
|
* LLM context.
|
|
6
6
|
*
|
|
@@ -9,10 +9,10 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
9
9
|
* which response field is the "large payload" (single/proxy use `data`,
|
|
10
10
|
* browser uses `body`).
|
|
11
11
|
*
|
|
12
|
-
* Audit 1.5
|
|
12
|
+
* Audit 1.5 - tenant isolation. Payloads are stored under
|
|
13
13
|
* `<PAYLOADS_DIR>/<keyhash>/<uuid>.{bin,meta.json}`, where `keyhash` is the
|
|
14
14
|
* first 16 hex chars of sha256(apiKey). The resource handler validates the
|
|
15
|
-
* caller's keyhash matches the storage path before serving
|
|
15
|
+
* caller's keyhash matches the storage path before serving - any other
|
|
16
16
|
* tenant gets a `resource not found` (no leaking whether the UUID exists).
|
|
17
17
|
*/
|
|
18
18
|
export declare const THRESHOLD_BYTES = 50000;
|
package/dist/resources.js
CHANGED
|
@@ -5,7 +5,7 @@ import { tmpdir } from "node:os";
|
|
|
5
5
|
import { ResourceTemplate, } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
6
|
import { getApiKey } from "./auth.js";
|
|
7
7
|
/**
|
|
8
|
-
* Resources
|
|
8
|
+
* Resources - offload large response bodies (>= THRESHOLD bytes) onto host
|
|
9
9
|
* disk and return a MCP resource_link instead of inlining megabytes into the
|
|
10
10
|
* LLM context.
|
|
11
11
|
*
|
|
@@ -14,16 +14,16 @@ import { getApiKey } from "./auth.js";
|
|
|
14
14
|
* which response field is the "large payload" (single/proxy use `data`,
|
|
15
15
|
* browser uses `body`).
|
|
16
16
|
*
|
|
17
|
-
* Audit 1.5
|
|
17
|
+
* Audit 1.5 - tenant isolation. Payloads are stored under
|
|
18
18
|
* `<PAYLOADS_DIR>/<keyhash>/<uuid>.{bin,meta.json}`, where `keyhash` is the
|
|
19
19
|
* first 16 hex chars of sha256(apiKey). The resource handler validates the
|
|
20
|
-
* caller's keyhash matches the storage path before serving
|
|
20
|
+
* caller's keyhash matches the storage path before serving - any other
|
|
21
21
|
* tenant gets a `resource not found` (no leaking whether the UUID exists).
|
|
22
22
|
*/
|
|
23
23
|
export const THRESHOLD_BYTES = 50_000;
|
|
24
24
|
export const PAYLOADS_DIR = process.env.FOURA_MCP_PAYLOADS_DIR ?? path.join(tmpdir(), "foura-mcp-payloads");
|
|
25
25
|
const URI_PREFIX = "foura-mcp://payload/";
|
|
26
|
-
// sha256(apiKey).hex().slice(0, 16)
|
|
26
|
+
// sha256(apiKey).hex().slice(0, 16) - gives 64 bits of namespacing entropy,
|
|
27
27
|
// short enough for a filesystem path component and unguessable for an
|
|
28
28
|
// attacker who doesn't have the corresponding API key.
|
|
29
29
|
export function hashApiKey(apiKey) {
|
|
@@ -100,7 +100,7 @@ export function registerResourceHandler(server) {
|
|
|
100
100
|
throw new Error(`Payload not found: ${uuidStr}`);
|
|
101
101
|
}
|
|
102
102
|
const meta = JSON.parse(metaRaw);
|
|
103
|
-
// Defense in depth
|
|
103
|
+
// Defense in depth - even if filesystem permissions ever got bypassed,
|
|
104
104
|
// the meta sidecar carries the keyhash; reject on mismatch.
|
|
105
105
|
if (meta.keyhash !== keyhash) {
|
|
106
106
|
throw new Error(`Payload not found: ${uuidStr}`);
|
package/dist/server.js
CHANGED
package/dist/tools/auto.js
CHANGED
|
@@ -22,7 +22,7 @@ function extractContentTypeFromHeaderInfo(headers) {
|
|
|
22
22
|
}
|
|
23
23
|
// Derive a stable error code from upstream HTTP status + envelope.
|
|
24
24
|
// LLM agents read `code` for retry / classify logic without parsing prose.
|
|
25
|
-
// Same code set as the other three tools
|
|
25
|
+
// Same code set as the other three tools - auto reuses them verbatim.
|
|
26
26
|
function deriveCode(status, envelope) {
|
|
27
27
|
if (status === 400)
|
|
28
28
|
return "bad_request";
|
|
@@ -51,10 +51,10 @@ function deriveCode(status, envelope) {
|
|
|
51
51
|
// /api/auto is registered on the gateway WITHOUT a trailing slash (the route is
|
|
52
52
|
// an exact `POST /api/auto`), unlike /single/ /proxy/ /browser/ which keep the
|
|
53
53
|
// slash. Hitting `/auto/` would miss the dedicated handler and fall through to
|
|
54
|
-
// the transparent table
|
|
54
|
+
// the transparent table - so this URL must stay slash-less.
|
|
55
55
|
const AUTO_API_URL = (process.env.FOURA_API_BASE ?? "https://api.foura.ai/api") + "/auto";
|
|
56
56
|
// The client's success criteria, passed verbatim to auto's internal sub-calls.
|
|
57
|
-
// Same DwValidate shape as foura_single / foura_proxy
|
|
57
|
+
// Same DwValidate shape as foura_single / foura_proxy - auto enforces it on
|
|
58
58
|
// every rung itself, so a rung that only superficially "succeeds" (e.g. a
|
|
59
59
|
// challenge interstitial) is rejected and the ladder keeps climbing.
|
|
60
60
|
const AutoValidateSchema = z
|
|
@@ -115,19 +115,19 @@ const AutoMetaSchema = z
|
|
|
115
115
|
// the same session through foura_single / foura_proxy later.
|
|
116
116
|
const AutoSessionSchema = z
|
|
117
117
|
.object({
|
|
118
|
-
proxy: z.string().optional().describe("Opaque base36 exit id of the session (e.g. `4DZ3VE`)
|
|
119
|
-
cookies: z.unknown().optional().describe("Cookies accumulated by the winning session
|
|
120
|
-
userAgent: z.string().optional().describe("User-Agent string the winning session used
|
|
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
121
|
})
|
|
122
122
|
.catchall(z.unknown());
|
|
123
123
|
const autoOutputShape = {
|
|
124
|
-
// Success path
|
|
124
|
+
// Success path - single-shaped body so any client that renders foura_single
|
|
125
125
|
// also renders auto. Note: NO `total_time` (auto does not surface it).
|
|
126
126
|
status: z
|
|
127
127
|
.number()
|
|
128
128
|
.int()
|
|
129
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
|
|
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
131
|
headers: z
|
|
132
132
|
.union([z.array(HeaderInfoSchema), z.string(), z.record(z.string(), z.unknown())])
|
|
133
133
|
.optional()
|
|
@@ -138,10 +138,10 @@ const autoOutputShape = {
|
|
|
138
138
|
.describe("Decoded response body of the delivered page. String by default; object when the body parsed as JSON. Omitted when offloaded."),
|
|
139
139
|
meta: AutoMetaSchema.optional().describe("Trace of what the ladder did: rung, solved, attempts, credits. Always present."),
|
|
140
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
|
|
141
|
+
// Offload path - MCP layer adds these when body >= 50KB AND offload_large=true
|
|
142
142
|
offloaded_resource_uri: z.string().optional().describe("foura-mcp://payload/<uuid>"),
|
|
143
143
|
size_bytes: z.number().int().optional().describe("Total offloaded body size in bytes"),
|
|
144
|
-
// Error path
|
|
144
|
+
// Error path - auto failure surfaces the failure status + message + attempts.
|
|
145
145
|
error: z.string().optional().describe("Human-readable error message when the ladder could not deliver the content within the budget."),
|
|
146
146
|
attempts: z.number().optional().describe("Total sub-call attempts when the ladder failed (also present inside `meta`)."),
|
|
147
147
|
service: z.enum(["single", "proxy", "browser", "api", "auto"]).optional(),
|
|
@@ -163,7 +163,7 @@ const autoInputShape = {
|
|
|
163
163
|
url: z
|
|
164
164
|
.string()
|
|
165
165
|
.url()
|
|
166
|
-
.describe("Target URL. Public hosts only
|
|
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
167
|
method: z
|
|
168
168
|
.string()
|
|
169
169
|
.min(1)
|
|
@@ -185,18 +185,18 @@ const autoInputShape = {
|
|
|
185
185
|
forceProxy: z
|
|
186
186
|
.boolean()
|
|
187
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
|
|
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
189
|
timeout_ms: z
|
|
190
190
|
.number()
|
|
191
191
|
.int()
|
|
192
192
|
.min(5_000)
|
|
193
193
|
.max(180_000)
|
|
194
194
|
.optional()
|
|
195
|
-
.describe("Total time budget in ms for the WHOLE operation
|
|
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
196
|
ignoreProxies: z
|
|
197
197
|
.array(z.string())
|
|
198
198
|
.optional()
|
|
199
|
-
.describe("Exits to AVOID
|
|
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
200
|
followRedirects: z
|
|
201
201
|
.number()
|
|
202
202
|
.int()
|
|
@@ -204,7 +204,7 @@ const autoInputShape = {
|
|
|
204
204
|
.max(20)
|
|
205
205
|
.optional()
|
|
206
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
|
|
207
|
+
// Bug 3 parity - opt-in offload. Default false → response inline regardless of
|
|
208
208
|
// size so clients that can't read resource_link blocks still get usable output.
|
|
209
209
|
offload_large: z
|
|
210
210
|
.boolean()
|
|
@@ -226,7 +226,7 @@ fn) {
|
|
|
226
226
|
const issues = parsed.error.issues.slice(0, 5).map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
227
227
|
return {
|
|
228
228
|
isError: true,
|
|
229
|
-
content: [{ type: "text", text: `FourA ${service}
|
|
229
|
+
content: [{ type: "text", text: `FourA ${service} - upstream response failed schema: ${issues}` }],
|
|
230
230
|
structuredContent: {
|
|
231
231
|
service,
|
|
232
232
|
code: "output_validation_failed",
|
|
@@ -241,7 +241,7 @@ fn) {
|
|
|
241
241
|
const msg = e instanceof Error ? e.message : String(e);
|
|
242
242
|
return {
|
|
243
243
|
isError: true,
|
|
244
|
-
content: [{ type: "text", text: `FourA ${service}
|
|
244
|
+
content: [{ type: "text", text: `FourA ${service} - internal error: ${msg}` }],
|
|
245
245
|
structuredContent: {
|
|
246
246
|
service,
|
|
247
247
|
code: "output_validation_failed",
|
|
@@ -252,10 +252,10 @@ fn) {
|
|
|
252
252
|
}
|
|
253
253
|
export function registerAutoTool(server) {
|
|
254
254
|
server.registerTool("foura_auto", {
|
|
255
|
-
title: "FourA
|
|
255
|
+
title: "FourA - auto (smart fetch, picks the method for you)",
|
|
256
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
|
|
258
|
-
"fast direct request first, then a rotating proxy, then a full browser session
|
|
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
259
|
"as far as the target forces it, solving common bot challenges (Cloudflare, and similar) on the " +
|
|
260
260
|
"way, and cheaply replaying a warm session on repeat calls to the same host. It learns the right " +
|
|
261
261
|
"settings per host on its own, so there are no maxTries / pool / retry knobs to tune. " +
|
|
@@ -296,7 +296,7 @@ export function registerAutoTool(server) {
|
|
|
296
296
|
headers: {
|
|
297
297
|
"X-API-Key": getApiKey(),
|
|
298
298
|
"Content-Type": "application/json",
|
|
299
|
-
"User-Agent": "foura-mcp/0.3.
|
|
299
|
+
"User-Agent": "foura-mcp/0.3.1 (auto)",
|
|
300
300
|
},
|
|
301
301
|
body: JSON.stringify(upstreamBody),
|
|
302
302
|
headersTimeout: 200_000,
|
|
@@ -313,7 +313,7 @@ export function registerAutoTool(server) {
|
|
|
313
313
|
content: [
|
|
314
314
|
{
|
|
315
315
|
type: "text",
|
|
316
|
-
text: `FourA auto
|
|
316
|
+
text: `FourA auto - non-JSON response (${res.statusCode}): ${text.slice(0, 200)}`,
|
|
317
317
|
},
|
|
318
318
|
],
|
|
319
319
|
structuredContent: {
|
|
@@ -324,7 +324,7 @@ export function registerAutoTool(server) {
|
|
|
324
324
|
},
|
|
325
325
|
};
|
|
326
326
|
}
|
|
327
|
-
// Gateway-level error (rate limit / auth / capacity)
|
|
327
|
+
// Gateway-level error (rate limit / auth / capacity) - auto itself ALWAYS
|
|
328
328
|
// replies transport-200, so a non-2xx transport status is the gateway
|
|
329
329
|
// rejecting before auto ran. Surface it like the other tools do.
|
|
330
330
|
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
@@ -354,7 +354,7 @@ export function registerAutoTool(server) {
|
|
|
354
354
|
content: [
|
|
355
355
|
{
|
|
356
356
|
type: "text",
|
|
357
|
-
text: `FourA auto
|
|
357
|
+
text: `FourA auto - could not deliver content (status ${innerStatus}): ${parsedObj.error}`,
|
|
358
358
|
},
|
|
359
359
|
],
|
|
360
360
|
structuredContent: {
|
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,12 +161,12 @@ const browserInputShape = {
|
|
|
161
161
|
checkText: z
|
|
162
162
|
.string()
|
|
163
163
|
.optional()
|
|
164
|
-
.describe("One-shot post-render validator
|
|
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
165
|
unblocker: z
|
|
166
166
|
.boolean()
|
|
167
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
|
|
169
|
-
// Bug 3 fix
|
|
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).
|
|
170
170
|
offload_large: z
|
|
171
171
|
.boolean()
|
|
172
172
|
.optional()
|
|
@@ -174,15 +174,15 @@ const browserInputShape = {
|
|
|
174
174
|
};
|
|
175
175
|
export function registerBrowserTool(server) {
|
|
176
176
|
server.registerTool("foura_browser", {
|
|
177
|
-
title: "FourA
|
|
178
|
-
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. " +
|
|
179
179
|
"Use for SPAs, lazy-loaded content, or JS anti-bot challenges (Cloudflare Turnstile etc.). " +
|
|
180
180
|
"Prefer foura_single for static HTML; foura_proxy for static pages where your IP is blocked. " +
|
|
181
181
|
"To rotate the browser's exit IP: call foura_proxy first, pass its returned `proxy` ID into " +
|
|
182
|
-
"this tool's `proxy` field
|
|
182
|
+
"this tool's `proxy` field - the browser exits through that pool IP. Default (no `proxy`) " +
|
|
183
183
|
"is one fixed container egress. If the target is behind a tier-1 WAF challenge (Vercel / " +
|
|
184
184
|
"Cloudflare / Akamai), calling this tool directly will usually still capture the challenge " +
|
|
185
|
-
"page rather than the post-challenge content
|
|
185
|
+
"page rather than the post-challenge content - the snapshot fires before the challenge's " +
|
|
186
186
|
"deferred reload completes. Correct chain: call foura_proxy first with maxTries:25-30 against " +
|
|
187
187
|
"the same URL → take the returned proxy base36 ID → pass it into this tool's `proxy` field. " +
|
|
188
188
|
"The browser then exits through the IP that already cleared the challenge for this target. " +
|
|
@@ -216,7 +216,7 @@ export function registerBrowserTool(server) {
|
|
|
216
216
|
headers: {
|
|
217
217
|
"X-API-Key": getApiKey(),
|
|
218
218
|
"Content-Type": "application/json",
|
|
219
|
-
"User-Agent": "foura-mcp/0.3.
|
|
219
|
+
"User-Agent": "foura-mcp/0.3.1 (browser)",
|
|
220
220
|
},
|
|
221
221
|
body: JSON.stringify(upstreamBody),
|
|
222
222
|
});
|
|
@@ -231,7 +231,7 @@ export function registerBrowserTool(server) {
|
|
|
231
231
|
content: [
|
|
232
232
|
{
|
|
233
233
|
type: "text",
|
|
234
|
-
text: `FourA browser
|
|
234
|
+
text: `FourA browser - non-JSON response (${res.statusCode}): ${text.slice(0, 200)}`,
|
|
235
235
|
},
|
|
236
236
|
],
|
|
237
237
|
structuredContent: {
|
|
@@ -258,7 +258,7 @@ export function registerBrowserTool(server) {
|
|
|
258
258
|
};
|
|
259
259
|
}
|
|
260
260
|
const parsedObj = parsed;
|
|
261
|
-
// Bug 8 fix
|
|
261
|
+
// Bug 8 fix - browser request.ts also rejects with DwResponseError on
|
|
262
262
|
// page failure (timeout, navigation error, checkStatus mismatch).
|
|
263
263
|
// Service forwards as 200 + {error, ...} via dwRequestToExpressRes.
|
|
264
264
|
if (typeof parsedObj.error === "string" && parsedObj.error.length > 0) {
|
|
@@ -268,7 +268,7 @@ export function registerBrowserTool(server) {
|
|
|
268
268
|
content: [
|
|
269
269
|
{
|
|
270
270
|
type: "text",
|
|
271
|
-
text: `FourA browser
|
|
271
|
+
text: `FourA browser - page failure (status ${innerStatus}): ${parsedObj.error}`,
|
|
272
272
|
},
|
|
273
273
|
],
|
|
274
274
|
structuredContent: {
|
package/dist/tools/proxy.js
CHANGED
|
@@ -58,7 +58,7 @@ fn) {
|
|
|
58
58
|
const issues = parsed.error.issues.slice(0, 5).map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
59
59
|
return {
|
|
60
60
|
isError: true,
|
|
61
|
-
content: [{ type: "text", text: `FourA ${service}
|
|
61
|
+
content: [{ type: "text", text: `FourA ${service} - upstream response failed schema: ${issues}` }],
|
|
62
62
|
structuredContent: {
|
|
63
63
|
service,
|
|
64
64
|
code: "output_validation_failed",
|
|
@@ -73,7 +73,7 @@ fn) {
|
|
|
73
73
|
const msg = e instanceof Error ? e.message : String(e);
|
|
74
74
|
return {
|
|
75
75
|
isError: true,
|
|
76
|
-
content: [{ type: "text", text: `FourA ${service}
|
|
76
|
+
content: [{ type: "text", text: `FourA ${service} - internal error: ${msg}` }],
|
|
77
77
|
structuredContent: {
|
|
78
78
|
service,
|
|
79
79
|
code: "output_validation_failed",
|
|
@@ -111,7 +111,7 @@ const ProxyValidateSchema = z
|
|
|
111
111
|
.optional(),
|
|
112
112
|
})
|
|
113
113
|
.optional();
|
|
114
|
-
// Inner DwRequest
|
|
114
|
+
// Inner DwRequest - matches dwstack types/src/single.ts DwRequestSchema
|
|
115
115
|
// (which is what /api/proxy/ inner.request validates against). Method is
|
|
116
116
|
// permissive z.string() not enum (Bug 9). All optionals match upstream.
|
|
117
117
|
const ProxyInnerRequestSchema = z
|
|
@@ -123,7 +123,7 @@ const ProxyInnerRequestSchema = z
|
|
|
123
123
|
url: z
|
|
124
124
|
.string()
|
|
125
125
|
.url()
|
|
126
|
-
.describe("Target URL the proxy should fetch. Public hosts only
|
|
126
|
+
.describe("Target URL the proxy should fetch. 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/pricing for blocked sites. {ts} placeholder is replaced with current Unix timestamp."),
|
|
127
127
|
headers: z
|
|
128
128
|
.array(z.tuple([z.string(), z.string()]))
|
|
129
129
|
.optional()
|
|
@@ -131,7 +131,7 @@ const ProxyInnerRequestSchema = z
|
|
|
131
131
|
unblocker: z
|
|
132
132
|
.boolean()
|
|
133
133
|
.optional()
|
|
134
|
-
.describe("Inject realistic browser headers (User-Agent, Sec-Ch-Ua, Accept-Encoding, …) and make the request look like it's coming from a real browser at the wire level. Default false
|
|
134
|
+
.describe("Inject realistic browser headers (User-Agent, Sec-Ch-Ua, Accept-Encoding, …) and make the request look like it's coming from a real browser at the wire level. Default false - STRONGLY recommended on proxy paths since most sites that need a proxy also have wire-level anti-bot (Cloudflare, Akamai, PerimeterX, Datadome). Cheap to leave on for production scrapes."),
|
|
135
135
|
data: z
|
|
136
136
|
.union([z.string(), z.record(z.string(), z.unknown())])
|
|
137
137
|
.optional()
|
|
@@ -147,8 +147,8 @@ const ProxyInnerRequestSchema = z
|
|
|
147
147
|
validate: ProxyValidateSchema,
|
|
148
148
|
})
|
|
149
149
|
.describe("The inner HTTP request to send through each proxy attempt. Validation rules here determine when a proxy is treated as failed and retried.");
|
|
150
|
-
// HeaderInfo[]
|
|
151
|
-
// come as string OR string[] from node-libcurl
|
|
150
|
+
// HeaderInfo[] - same as single. Multi-value headers (Set-Cookie, Link)
|
|
151
|
+
// come as string OR string[] from node-libcurl - Bug 1 fix.
|
|
152
152
|
const ProxyHeaderInfoSchema = z
|
|
153
153
|
.object({
|
|
154
154
|
result: z
|
|
@@ -161,17 +161,17 @@ const ProxyHeaderInfoSchema = z
|
|
|
161
161
|
})
|
|
162
162
|
.catchall(z.union([z.string(), z.array(z.string())]));
|
|
163
163
|
const proxyOutputShape = {
|
|
164
|
-
// Success
|
|
164
|
+
// Success - PrResponse = DwResponse + {proxy, total}. The backend type
|
|
165
165
|
// also has an optional `proxyId` (number), but the api gateway encodes it
|
|
166
166
|
// into the `proxy` string and strips it from the response. We don't
|
|
167
|
-
// surface it in outputSchema
|
|
168
|
-
status: z.number().int().optional().describe("HTTP status code from the target (from the succeeding proxy attempt). `0` indicates every attempt failed before any HTTP response (DNS / connection refused / timeout)
|
|
167
|
+
// surface it in outputSchema - zod silently drops any stray copy.
|
|
168
|
+
status: z.number().int().optional().describe("HTTP status code from the target (from the succeeding proxy attempt). `0` indicates every attempt failed before any HTTP response (DNS / connection refused / timeout) - check the `error` field for the underlying reason."),
|
|
169
169
|
headers: z
|
|
170
170
|
.union([z.array(ProxyHeaderInfoSchema), z.string(), z.record(z.string(), z.unknown())])
|
|
171
171
|
.optional()
|
|
172
172
|
.describe("Response headers per redirect hop, 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)."),
|
|
173
173
|
data: z.unknown().optional().describe("Decoded response body. Omitted when offloaded."),
|
|
174
|
-
// total_time can be string | number | null per dwstack types
|
|
174
|
+
// total_time can be string | number | null per dwstack types - Bug 6 fix.
|
|
175
175
|
total_time: z
|
|
176
176
|
.union([z.number(), z.string(), z.null()])
|
|
177
177
|
.optional()
|
|
@@ -186,10 +186,10 @@ const proxyOutputShape = {
|
|
|
186
186
|
.number()
|
|
187
187
|
.optional()
|
|
188
188
|
.describe("Outer total time in seconds (proxy selection + retries + the successful inner attempt). Float."),
|
|
189
|
-
// Offload path
|
|
189
|
+
// Offload path - MCP layer adds these when body >= 50KB AND offload_large=true
|
|
190
190
|
offloaded_resource_uri: z.string().optional().describe("foura-mcp://payload/<uuid>"),
|
|
191
191
|
size_bytes: z.number().int().optional().describe("Total offloaded body size in bytes"),
|
|
192
|
-
// Error path
|
|
192
|
+
// Error path - includes PrResponseError shape: {error, request, total} (no status, no headers, no data)
|
|
193
193
|
error: z.string().optional().describe("Human-readable error message"),
|
|
194
194
|
service: z.enum(["single", "proxy", "browser", "api"]).optional(),
|
|
195
195
|
retryAfter: z.number().optional(),
|
|
@@ -204,7 +204,7 @@ const proxyOutputShape = {
|
|
|
204
204
|
};
|
|
205
205
|
const proxyInputShape = {
|
|
206
206
|
request: ProxyInnerRequestSchema,
|
|
207
|
-
// PrRequestSchema.timeout_ms is `.positive()` in upstream
|
|
207
|
+
// PrRequestSchema.timeout_ms is `.positive()` in upstream - 0 is invalid here
|
|
208
208
|
// (different from single's .min(0)).
|
|
209
209
|
maxTries: z
|
|
210
210
|
.number()
|
|
@@ -212,7 +212,7 @@ const proxyInputShape = {
|
|
|
212
212
|
.min(1)
|
|
213
213
|
.max(90)
|
|
214
214
|
.optional()
|
|
215
|
-
.describe("Maximum proxy rotation attempts before giving up (default 5, max 90). Default 5 is sized for lightly-blocked sites. Raise to 25-30 for tier-1 WAF challenges (Vercel Security Checkpoint, Cloudflare 'Just a moment', Akamai Bot Manager)
|
|
215
|
+
.describe("Maximum proxy rotation attempts before giving up (default 5, max 90). Default 5 is sized for lightly-blocked sites. Raise to 25-30 for tier-1 WAF challenges (Vercel Security Checkpoint, Cloudflare 'Just a moment', Akamai Bot Manager) - most rotations on these targets need this range. If still blocked after 30 attempts, the gate is likely country / ASN allowlist (not solvable by rotation) - pivot strategy instead of climbing to 60."),
|
|
216
216
|
timeout_ms: z
|
|
217
217
|
.number()
|
|
218
218
|
.int()
|
|
@@ -224,7 +224,7 @@ const proxyInputShape = {
|
|
|
224
224
|
.array(z.string())
|
|
225
225
|
.optional()
|
|
226
226
|
.describe("Encoded proxy IDs (base36 strings like \"4DZ3VE\") OR proxy URLs to exclude from rotation. Both forms are accepted."),
|
|
227
|
-
// Bug 3 fix
|
|
227
|
+
// Bug 3 fix - opt-in offload, default false (inline).
|
|
228
228
|
offload_large: z
|
|
229
229
|
.boolean()
|
|
230
230
|
.optional()
|
|
@@ -232,18 +232,18 @@ const proxyInputShape = {
|
|
|
232
232
|
};
|
|
233
233
|
export function registerProxyTool(server) {
|
|
234
234
|
server.registerTool("foura_proxy", {
|
|
235
|
-
title: "FourA
|
|
235
|
+
title: "FourA - HTTP request via rotating proxies",
|
|
236
236
|
description: "Route an HTTP request through FourA's proxy pool with automatic retry across multiple proxies. " +
|
|
237
237
|
"Per-host proxy rating picks proxies most likely to succeed for the target. Use when foura_single " +
|
|
238
|
-
"returns 403, captcha, or geo-blocked content. Typical latency 1
|
|
239
|
-
"encoded proxy ID that succeeded ('proxy' field)
|
|
238
|
+
"returns 403, captcha, or geo-blocked content. Typical latency 1-5s. The response includes the " +
|
|
239
|
+
"encoded proxy ID that succeeded ('proxy' field) - reuse it in foura_single.proxy or " +
|
|
240
240
|
"foura_browser.proxy to pin follow-up requests to the same exit IP, or pass it in ignoreProxies " +
|
|
241
241
|
"to skip this exit on the next rotation. Escalate to foura_browser if all proxies fail or the " +
|
|
242
242
|
"page needs JavaScript rendering. When the trigger is a tier-1 WAF challenge (Vercel Security " +
|
|
243
|
-
"Checkpoint, Cloudflare 'Just a moment', Akamai Bot Manager), set maxTries to 25-30
|
|
243
|
+
"Checkpoint, Cloudflare 'Just a moment', Akamai Bot Manager), set maxTries to 25-30 - the default " +
|
|
244
244
|
"5 will usually be too low for these targets. Distinguish from country / ASN allowlist denials " +
|
|
245
245
|
"(country-licensed bookmakers, government sites): there rotation never helps regardless of " +
|
|
246
|
-
"maxTries
|
|
246
|
+
"maxTries - after ~30 failed attempts on the same block, stop climbing and pivot strategy instead.",
|
|
247
247
|
inputSchema: proxyInputShape,
|
|
248
248
|
outputSchema: proxyOutputShape,
|
|
249
249
|
annotations: {
|
|
@@ -271,7 +271,7 @@ export function registerProxyTool(server) {
|
|
|
271
271
|
headers: {
|
|
272
272
|
"X-API-Key": getApiKey(),
|
|
273
273
|
"Content-Type": "application/json",
|
|
274
|
-
"User-Agent": "foura-mcp/0.3.
|
|
274
|
+
"User-Agent": "foura-mcp/0.3.1 (proxy)",
|
|
275
275
|
},
|
|
276
276
|
body: JSON.stringify(upstreamBody),
|
|
277
277
|
});
|
|
@@ -286,7 +286,7 @@ export function registerProxyTool(server) {
|
|
|
286
286
|
content: [
|
|
287
287
|
{
|
|
288
288
|
type: "text",
|
|
289
|
-
text: `FourA proxy
|
|
289
|
+
text: `FourA proxy - non-JSON response (${res.statusCode}): ${text.slice(0, 200)}`,
|
|
290
290
|
},
|
|
291
291
|
],
|
|
292
292
|
structuredContent: {
|
|
@@ -313,7 +313,7 @@ export function registerProxyTool(server) {
|
|
|
313
313
|
};
|
|
314
314
|
}
|
|
315
315
|
const parsedObj = parsed;
|
|
316
|
-
// Bug 8 fix
|
|
316
|
+
// Bug 8 fix - all-proxies-fail returns HTTP 200 + PrResponseError shape
|
|
317
317
|
// {error, request, total}. dwstack proxy/src/api/request.ts:43
|
|
318
318
|
// forwards without overriding the response status. Without this check,
|
|
319
319
|
// foura-mcp would silently return "success" with a missing-data body.
|
|
@@ -324,7 +324,7 @@ export function registerProxyTool(server) {
|
|
|
324
324
|
content: [
|
|
325
325
|
{
|
|
326
326
|
type: "text",
|
|
327
|
-
text: `FourA proxy
|
|
327
|
+
text: `FourA proxy - all attempts failed: ${parsedObj.error}`,
|
|
328
328
|
},
|
|
329
329
|
],
|
|
330
330
|
structuredContent: {
|
package/dist/tools/single.js
CHANGED
|
@@ -79,10 +79,10 @@ const SingleValidateSchema = z
|
|
|
79
79
|
.optional(),
|
|
80
80
|
})
|
|
81
81
|
.optional();
|
|
82
|
-
// HeaderInfo[]
|
|
82
|
+
// HeaderInfo[] - one entry per response in the redirect chain. `result` holds
|
|
83
83
|
// the HTTP status line; all other keys are response header name → value pairs.
|
|
84
84
|
// Multi-value headers (Set-Cookie, Link, WWW-Authenticate, etc.) come as
|
|
85
|
-
// `string | string[]` from node-libcurl HeaderInfo
|
|
85
|
+
// `string | string[]` from node-libcurl HeaderInfo - Bug 1 fix.
|
|
86
86
|
const HeaderInfoSchema = z
|
|
87
87
|
.object({
|
|
88
88
|
result: z
|
|
@@ -95,9 +95,9 @@ const HeaderInfoSchema = z
|
|
|
95
95
|
})
|
|
96
96
|
.catchall(z.union([z.string(), z.array(z.string())]));
|
|
97
97
|
const singleOutputShape = {
|
|
98
|
-
// Success path
|
|
99
|
-
status: z.number().int().optional().describe("HTTP status code from the target. `0` indicates the request failed before any HTTP response (DNS failure, connection refused, timeout)
|
|
100
|
-
// `Buffer | HeaderInfo[]`
|
|
98
|
+
// Success path - matches dwstack DwResponse (packages/types/src/single.ts:47-57)
|
|
99
|
+
status: z.number().int().optional().describe("HTTP status code from the target. `0` indicates the request failed before any HTTP response (DNS failure, connection refused, timeout) - check the `error` field for the underlying reason."),
|
|
100
|
+
// `Buffer | HeaderInfo[]` - Buffer when raw mode is requested upstream
|
|
101
101
|
// (we don't expose that mode but accept it permissively). Array entries
|
|
102
102
|
// hold the libcurl HeaderInfo shape with multi-value header support.
|
|
103
103
|
headers: z
|
|
@@ -107,16 +107,16 @@ const singleOutputShape = {
|
|
|
107
107
|
data: z
|
|
108
108
|
.unknown()
|
|
109
109
|
.optional()
|
|
110
|
-
.describe("Decoded response body. String by default; object when tryJsonData=true and the body parsed as JSON; serialized Buffer JSON shape (`{type:\"Buffer\", data:[byte, ...]}`, bytes 0-255) when returnBuffer=true
|
|
111
|
-
// total_time can be string | number | null per dwstack types/src/single.ts:52
|
|
110
|
+
.describe("Decoded response body. String by default; object when tryJsonData=true and the body parsed as JSON; serialized Buffer JSON shape (`{type:\"Buffer\", data:[byte, ...]}`, bytes 0-255) when returnBuffer=true - reconstruct with `Buffer.from(data.data)` in Node, `new Uint8Array(data.data)` elsewhere. Omitted when offloaded."),
|
|
111
|
+
// total_time can be string | number | null per dwstack types/src/single.ts:52 - Bug 6 fix.
|
|
112
112
|
total_time: z
|
|
113
113
|
.union([z.number(), z.string(), z.null()])
|
|
114
114
|
.optional()
|
|
115
115
|
.describe("Wall-clock request duration in seconds. Number when present; string in some variants; null when the request never started."),
|
|
116
|
-
// Offload path
|
|
116
|
+
// Offload path - MCP layer adds these when body >= 50KB AND offload_large=true
|
|
117
117
|
offloaded_resource_uri: z.string().optional().describe("foura-mcp://payload/<uuid>"),
|
|
118
118
|
size_bytes: z.number().int().optional().describe("Total offloaded body size in bytes"),
|
|
119
|
-
// Error path
|
|
119
|
+
// Error path - common envelope across all FourA services (see foura.ai/docs/api/errors)
|
|
120
120
|
error: z.string().optional().describe("Human-readable error message"),
|
|
121
121
|
service: z.enum(["single", "proxy", "browser", "api"]).optional(),
|
|
122
122
|
retryAfter: z.number().optional().describe("Seconds to wait before retrying (429/503)"),
|
|
@@ -131,14 +131,14 @@ const singleOutputShape = {
|
|
|
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 singleInputShape = {
|
|
134
|
-
// method is z.string() in upstream (packages/types/src/single.ts:29)
|
|
134
|
+
// method is z.string() in upstream (packages/types/src/single.ts:29) - Bug 9.
|
|
135
135
|
// We don't restrict to a closed enum; libcurl accepts arbitrary verbs
|
|
136
136
|
// (PROPFIND, MKCOL, REPORT, etc.) used by WebDAV / OData / GraphQL clients.
|
|
137
137
|
method: z
|
|
138
138
|
.string()
|
|
139
139
|
.min(1)
|
|
140
140
|
.describe("HTTP method (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, or any WebDAV verb like PROPFIND/MKCOL)"),
|
|
141
|
-
url: z.string().url().describe("Target URL. Public hosts only
|
|
141
|
+
url: z.string().url().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 or https://api.example.com/v1/users. Use {ts} anywhere in the URL to insert current Unix timestamp for cache-bust."),
|
|
142
142
|
headers: z
|
|
143
143
|
.array(z.tuple([z.string(), z.string()]))
|
|
144
144
|
.optional()
|
|
@@ -146,7 +146,7 @@ const singleInputShape = {
|
|
|
146
146
|
unblocker: z
|
|
147
147
|
.boolean()
|
|
148
148
|
.optional()
|
|
149
|
-
.describe("Inject realistic browser headers (User-Agent, Sec-Ch-Ua, Accept-Encoding, …) and make the request look like it's coming from a real browser at the wire level. Default false
|
|
149
|
+
.describe("Inject realistic browser headers (User-Agent, Sec-Ch-Ua, Accept-Encoding, …) and make the request look like it's coming from a real browser at the wire level. Default false - set true for any target with anti-bot or WAF (Cloudflare, Akamai, PerimeterX, Datadome). Cheap to leave on for production scrapes."),
|
|
150
150
|
data: z
|
|
151
151
|
.union([z.string(), z.record(z.string(), z.unknown())])
|
|
152
152
|
.optional()
|
|
@@ -155,9 +155,9 @@ const singleInputShape = {
|
|
|
155
155
|
.string()
|
|
156
156
|
.optional()
|
|
157
157
|
.describe("Optional proxy. Two forms: (1) URL `http://host:port` or `socks5://host:port`; " +
|
|
158
|
-
"(2) base36 ID from foura_proxy (e.g. `4DZ3VE`)
|
|
158
|
+
"(2) base36 ID from foura_proxy (e.g. `4DZ3VE`) - same exit IP. For rotation, use foura_proxy."),
|
|
159
159
|
// Note: `proxyId` (number) is intentionally NOT exposed. The backend zod
|
|
160
|
-
// schema declares it, but no code path reads it
|
|
160
|
+
// schema declares it, but no code path reads it - gateway only resolves
|
|
161
161
|
// base36 IDs from the `proxy` string field, and libcurl never reads
|
|
162
162
|
// request.proxyId. Exposing the dead field would silently no-op.
|
|
163
163
|
timeout_ms: z.number().int().min(0).max(120_000).optional().describe("Overall request timeout in ms (max 120000, default 15000)"),
|
|
@@ -172,10 +172,10 @@ const singleInputShape = {
|
|
|
172
172
|
.max(20)
|
|
173
173
|
.optional()
|
|
174
174
|
.describe("Max number of redirects to follow (0-20). Omit to disable redirect following."),
|
|
175
|
-
tryJsonData: z.boolean().optional().describe("If true, attempt JSON.parse on the response body. On success, `data` is the parsed value (typically object or array). On parse failure, `data` silently stays as the original string
|
|
175
|
+
tryJsonData: z.boolean().optional().describe("If true, attempt JSON.parse on the response body. On success, `data` is the parsed value (typically object or array). On parse failure, `data` silently stays as the original string - no error, no warning. Set false (or omit) when you need to detect parse failures explicitly."),
|
|
176
176
|
returnBuffer: z.boolean().optional().describe("Return raw bytes as a serialized Buffer JSON shape (`{type:\"Buffer\", data:[byte, ...]}`, bytes 0-255) instead of decoded string. Use for binary responses (images, protobuf). Reconstruct: `Buffer.from(data.data)` in Node, `new Uint8Array(data.data)` elsewhere."),
|
|
177
177
|
validate: SingleValidateSchema,
|
|
178
|
-
// Bug 3 fix
|
|
178
|
+
// Bug 3 fix - opt-in offload. Default false → response inline regardless
|
|
179
179
|
// of size so Claude Desktop (which can't read resource_link content blocks)
|
|
180
180
|
// gets usable output on real product pages.
|
|
181
181
|
offload_large: z
|
|
@@ -183,10 +183,10 @@ const singleInputShape = {
|
|
|
183
183
|
.optional()
|
|
184
184
|
.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."),
|
|
185
185
|
};
|
|
186
|
-
// Bug 2 fix
|
|
186
|
+
// Bug 2 fix - convert any handler-level crash OR output-validation failure
|
|
187
187
|
// into the documented {service, code, error} envelope. Two guards:
|
|
188
|
-
// 1. Outer try/catch around handler
|
|
189
|
-
// 2. Output validation against outputSchema BEFORE returning
|
|
188
|
+
// 1. Outer try/catch around handler - catches throws (network, JSON, etc).
|
|
189
|
+
// 2. Output validation against outputSchema BEFORE returning - catches
|
|
190
190
|
// shape mismatches that would otherwise bubble up as MCP -32602 errors
|
|
191
191
|
// without a structuredContent envelope.
|
|
192
192
|
// Return type is `any` so the strict CallToolResult union from the SDK
|
|
@@ -204,7 +204,7 @@ fn) {
|
|
|
204
204
|
const issues = parsed.error.issues.slice(0, 5).map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
205
205
|
return {
|
|
206
206
|
isError: true,
|
|
207
|
-
content: [{ type: "text", text: `FourA ${service}
|
|
207
|
+
content: [{ type: "text", text: `FourA ${service} - upstream response failed schema: ${issues}` }],
|
|
208
208
|
structuredContent: {
|
|
209
209
|
service,
|
|
210
210
|
code: "output_validation_failed",
|
|
@@ -219,7 +219,7 @@ fn) {
|
|
|
219
219
|
const msg = e instanceof Error ? e.message : String(e);
|
|
220
220
|
return {
|
|
221
221
|
isError: true,
|
|
222
|
-
content: [{ type: "text", text: `FourA ${service}
|
|
222
|
+
content: [{ type: "text", text: `FourA ${service} - internal error: ${msg}` }],
|
|
223
223
|
structuredContent: {
|
|
224
224
|
service,
|
|
225
225
|
code: "output_validation_failed",
|
|
@@ -230,10 +230,10 @@ fn) {
|
|
|
230
230
|
}
|
|
231
231
|
export function registerSingleTool(server) {
|
|
232
232
|
server.registerTool("foura_single", {
|
|
233
|
-
title: "FourA
|
|
234
|
-
description: "Send one HTTP request and return the response. Fastest of the
|
|
233
|
+
title: "FourA - single HTTP request",
|
|
234
|
+
description: "Send one HTTP request and return the response. Fastest of the four tools (typically 200ms-2s). " +
|
|
235
235
|
"Use for static pages, JSON APIs, server-rendered HTML. Set unblocker:true if the target has " +
|
|
236
|
-
"wire-level anti-bot protection. Switch to foura_proxy if you get blocked
|
|
236
|
+
"wire-level anti-bot protection. Switch to foura_proxy if you get blocked - status 403, status 429, " +
|
|
237
237
|
"captcha page, OR response carries `x-vercel-mitigated: challenge` / `cf-mitigated: challenge` " +
|
|
238
238
|
"headers, OR body title matches `Vercel Security Checkpoint` / `Just a moment` / `Attention Required` / " +
|
|
239
239
|
"`We're verifying your browser`. For these tier-1 WAF challenges call foura_proxy with maxTries:25-30 " +
|
|
@@ -267,7 +267,7 @@ export function registerSingleTool(server) {
|
|
|
267
267
|
headers: {
|
|
268
268
|
"X-API-Key": getApiKey(),
|
|
269
269
|
"Content-Type": "application/json",
|
|
270
|
-
"User-Agent": "foura-mcp/0.3.
|
|
270
|
+
"User-Agent": "foura-mcp/0.3.1 (single)",
|
|
271
271
|
},
|
|
272
272
|
body: JSON.stringify(upstreamBody),
|
|
273
273
|
});
|
|
@@ -282,7 +282,7 @@ export function registerSingleTool(server) {
|
|
|
282
282
|
content: [
|
|
283
283
|
{
|
|
284
284
|
type: "text",
|
|
285
|
-
text: `FourA single
|
|
285
|
+
text: `FourA single - non-JSON response (${res.statusCode}): ${text.slice(0, 200)}`,
|
|
286
286
|
},
|
|
287
287
|
],
|
|
288
288
|
structuredContent: {
|
|
@@ -309,7 +309,7 @@ export function registerSingleTool(server) {
|
|
|
309
309
|
};
|
|
310
310
|
}
|
|
311
311
|
const parsedObj = parsed;
|
|
312
|
-
// Bug 8 fix
|
|
312
|
+
// Bug 8 fix - dwstack returns HTTP 200 + {error, status, headers, data}
|
|
313
313
|
// when libcurl itself failed (DNS, refused, timeout) or when validate.*
|
|
314
314
|
// rejected the response. The api gateway forwards the inner status
|
|
315
315
|
// verbatim. Surface this as a structured error instead of a fake success.
|
|
@@ -320,7 +320,7 @@ export function registerSingleTool(server) {
|
|
|
320
320
|
content: [
|
|
321
321
|
{
|
|
322
322
|
type: "text",
|
|
323
|
-
text: `FourA single
|
|
323
|
+
text: `FourA single - upstream failure (status ${innerStatus}): ${parsedObj.error}`,
|
|
324
324
|
},
|
|
325
325
|
],
|
|
326
326
|
structuredContent: {
|
|
@@ -340,7 +340,7 @@ export function registerSingleTool(server) {
|
|
|
340
340
|
const statusLabel = parsedObj.status ?? "?";
|
|
341
341
|
const timeLabel = parsedObj.total_time !== undefined && parsedObj.total_time !== null
|
|
342
342
|
? `${parsedObj.total_time}s`
|
|
343
|
-
: "
|
|
343
|
+
: "-";
|
|
344
344
|
const shouldOffload = offload_large === true
|
|
345
345
|
&& bodyStr
|
|
346
346
|
&& Buffer.byteLength(bodyStr, "utf8") >= THRESHOLD_BYTES;
|
package/dist/tools/single.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"single.js","sourceRoot":"","sources":["../../src/tools/single.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"single.js","sourceRoot":"","sources":["../../src/tools/single.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fouradata/mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "MCP server for the FourA web scraping API — three tools for HTTP requests, rotating proxies, and full browser sessions, usable from any MCP client.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/server.js",
|