Skip to content

Commit 8e6b329

Browse files
committed
feat(url-reader): support explicit browser solvers
Coverage: 94.29% (was 94.21%)
1 parent 9bf9d90 commit 8e6b329

23 files changed

Lines changed: 1411 additions & 568 deletions

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ Versions follow [Semantic Versioning](https://semver.org/).
77

88
### Added
99

10-
- **FlareSolverr-assisted URL reading:** Operators can configure `FLARESOLVERR_URL`, `FLARESOLVERR_TIMEOUT_MS` (default 60,000 ms), and `FLARESOLVERR_MAX_CONCURRENT_REQUESTS` (default 2) to attempt browser-session acquisition after an uncached `web_url_read` passes URL validation and the HEAD size preflight, then replay its user-agent and scoped cookies through the existing bounded URL reader. A full concurrency limit and transient acquisition failures fall back once to an uncached direct read; invalid configuration, incompatible client errors, and cross-host solutions fail closed. The integration was verified with FlareSolverr 3.5.0 on 2026-07-30; Byparr has not been verified and is not currently supported.
10+
- **Explicit FlareSolverr or Byparr URL reading:** Operators can select exactly one browser solver with `FLARESOLVERR_URL` or `BYPARR_URL`. FlareSolverr retains its millisecond timeout and concurrency variables; Byparr adds `BYPARR_TIMEOUT_SECONDS` and `BYPARR_MAX_CONCURRENT_REQUESTS`. Simultaneous endpoints fail closed, provider counters remain independent, MCP cancellation now reaches solver acquisition, replay, body streaming, and PDF workers, and transient failures retain the existing one-time uncached direct fallback. Verified with FlareSolverr 3.5.0 and Byparr 2.1.0 on 2026-07-30.
11+
12+
**Migration note:** Browser-solver endpoints are now validated during startup. A `FLARESOLVERR_URL` containing userinfo, a query, a fragment, or a non-HTTP(S) scheme now prevents startup instead of failing only when a URL read first uses it.
1113

1214
- **Bounded PDF text extraction:** `web_url_read` now extracts text-layer content from `application/pdf` responses using the new production `unpdf` dependency in a resource-limited worker. Input and output are capped at the lower of `URL_READ_MAX_CONTENT_LENGTH_BYTES` and 16 MiB, documents above 500 pages are rejected, parsing has a separate 30-second budget, and at most two extractions run concurrently. OCR is not supported. This supersedes the v1.10.0 behavior that rejected PDF responses.
1315

CONFIGURATION.md

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -95,27 +95,43 @@ for trust, evaluation, and conservative-use guidance.
9595
| `FLARESOLVERR_URL` | No || Base URL of a trusted FlareSolverr service, such as `http://flaresolverr:8191`. When set, `web_url_read` attempts to ask its `/v1` API for a browser session after an uncached URL passes URL validation and the HEAD size preflight. |
9696
| `FLARESOLVERR_TIMEOUT_MS` | No | `60000` | Maximum session-acquisition time in milliseconds, from `1` through `300000`. Invalid values use the default. This is separate from `FETCH_TIMEOUT_MS`, which starts when the target is replayed. |
9797
| `FLARESOLVERR_MAX_CONCURRENT_REQUESTS` | No | `2` | Maximum concurrent solver acquisitions per MCP process, from `1` through `16`. When all slots are occupied, the request uses the direct URL-reader path instead of waiting in a queue. |
98+
| `BYPARR_URL` | No || Base URL of a trusted Byparr service, such as `http://byparr:8191`. It is mutually exclusive with `FLARESOLVERR_URL`. |
99+
| `BYPARR_TIMEOUT_SECONDS` | No | `60` | Maximum Byparr session-acquisition time in whole seconds, from `1` through `300`. Invalid values use the default. |
100+
| `BYPARR_MAX_CONCURRENT_REQUESTS` | No | `2` | Maximum concurrent Byparr acquisitions per MCP process, from `1` through `16`. It is independent from the FlareSolverr counter. |
98101
| `CACHE_TTL_MS` | No | `86400000` | URL cache TTL in milliseconds. Invalid or non-positive values fall back to the default (24 hours). |
99102
| `CACHE_MAX_ENTRIES` | No | `500` | Maximum number of cached URLs. When the cache exceeds this size, the least frequently used entry is evicted, with oldest entry used as the tie-breaker. Invalid or non-positive values fall back to the default. |
100103

101-
Verified with FlareSolverr 3.5.0 on 2026-07-30.
102-
Byparr has not been verified and is not currently supported. Independent
103-
compatibility reports are welcome.
104+
FlareSolverr 3.5.0 and Byparr 2.1.0 were verified on 2026-07-30. Configure
105+
either provider or neither provider. If both endpoint variables are non-blank,
106+
startup fails closed. This release does not automatically fall back between
107+
providers.
104108

105-
`FLARESOLVERR_URL` accepts either an absolute HTTP(S) service base URL or an
109+
The verified `linux/amd64` images came from multi-architecture manifests
110+
`ghcr.io/flaresolverr/flaresolverr:v3.5.0@sha256:139dfee1c6f89249c8d665d1333a42e8ec74ec0a86bc6bb1c8461e10d3a66a47`
111+
and
112+
`ghcr.io/thephaseless/byparr:2.1.0@sha256:01a46a2865d9a6db5eb8ead04ec0dd33b8fbe233e8565ae70b50d4cc0af4cfb0`.
113+
Client cancellation stops local work promptly, but a remote browser may
114+
continue until its configured provider timeout after the HTTP client
115+
disconnects. See [browser solver verification](docs/browser-solver-verification.md).
116+
117+
Each provider URL accepts either an absolute HTTP(S) service base URL or an
106118
already-complete `/v1` endpoint; the suffix is normalized idempotently. Query
107-
strings, fragments, and other URL schemes are rejected. The value is validated
108-
when `web_url_read` first uses it rather than at process startup; an invalid
109-
value fails every URL read, including a potential cache hit, until corrected.
119+
strings, fragments, userinfo, and other URL schemes are rejected at startup and
120+
again during request resolution without echoing the configured value.
121+
This is stricter than the previous per-read validation: an existing
122+
`FLARESOLVERR_URL` containing userinfo, a query, a fragment, or an invalid
123+
scheme now prevents startup until corrected.
110124
`FLARESOLVERR_TIMEOUT_MS` is sent to the solver as its browser-work budget;
111-
the client permits up to 5 additional seconds to receive and validate the
112-
solver response.
125+
`BYPARR_TIMEOUT_SECONDS` is sent in seconds. The client permits up to 5
126+
additional seconds to receive and validate either solver response.
113127

114-
With `FLARESOLVERR_URL` configured, `web_url_read` first performs its normal
128+
With one browser-solver endpoint configured, `web_url_read` first performs its normal
115129
target URL security and HEAD size preflight for every uncached read. It then
116-
requests only the browser session cookies and user-agent from the solver.
130+
requests a browser session and uses only its cookies and user-agent. Byparr
131+
2.1.0 also returns rendered content; that field is discarded after a bounded
132+
parse.
117133
When a solver slot is available, every uncached URL that passes URL validation
118-
and the HEAD size preflight is disclosed to the configured FlareSolverr service.
134+
and the HEAD size preflight is disclosed to the configured browser solver.
119135
Cache hits and reads made while the solver concurrency limit is full bypass
120136
solver acquisition. The actual target is fetched by `mcp-searxng`, so
121137
redirect validation, URL-reader proxy selection, streaming size limits, and
@@ -129,7 +145,8 @@ URL-reader path. Invalid solver configuration, other HTTP 4xx responses, a
129145
solver result for a different hostname, and a non-success target status
130146
reported by the solver fail closed. A direct-fetch fallback result is not
131147
cached, so repeated reads re-fetch until solver acquisition succeeds.
132-
Solver-backed cache entries are isolated from direct-fetch entries. When the
148+
Solver-backed cache entries are isolated by provider and from direct-fetch
149+
entries. Cancellation never falls back or writes a cache entry. When the
133150
replay response is `application/pdf`, the URL reader applies its bounded PDF
134151
text-extraction path.
135152

@@ -155,6 +172,25 @@ services:
155172
- TZ=America/Chicago
156173
```
157174
175+
Equivalent Byparr configuration (use this block instead of FlareSolverr):
176+
177+
```yaml
178+
services:
179+
mcp-searxng:
180+
image: isokoliuk/mcp-searxng:latest
181+
stdin_open: true
182+
environment:
183+
- SEARXNG_URL=${SEARXNG_URL:?Set SEARXNG_URL in the environment}
184+
- BYPARR_URL=http://byparr:8191
185+
- BYPARR_TIMEOUT_SECONDS=60
186+
- BYPARR_MAX_CONCURRENT_REQUESTS=2
187+
depends_on:
188+
- byparr
189+
190+
byparr:
191+
image: ghcr.io/thephaseless/byparr:2.1.0
192+
```
193+
158194
The solver is an operator-trusted browser service. Keep it on a private
159195
container network, do not expose port 8191 publicly, and restrict its egress
160196
from private services and cloud metadata endpoints. See
@@ -170,7 +206,7 @@ from private services and cloud metadata endpoints. See
170206

171207
`SEARCH_USER_AGENT` and `URL_READER_USER_AGENT` are per-group overrides. When unset, both fall back to `USER_AGENT`. If neither the group override nor `USER_AGENT` is set, no User-Agent header is added by `mcp-searxng`.
172208

173-
When FlareSolverr returns a solved session, its browser User-Agent replaces
209+
When a browser solver returns a solved session, its browser User-Agent replaces
174210
`URL_READER_USER_AGENT` / `USER_AGENT` on the replay fetch because the returned
175211
cookies are tied to that browser identity. The configured URL-reader User-Agent
176212
still applies to the pre-solve HEAD size check and to direct or fallback reads.
@@ -187,7 +223,7 @@ Interface-specific proxies take priority over global proxies for their respectiv
187223
| `NO_PROXY` | No | — | Comma-separated bypass list (e.g. `localhost,.internal,example.com`) |
188224

189225
The solver API request uses only the global `HTTP_PROXY` / `HTTPS_PROXY` and
190-
`NO_PROXY` settings because `FLARESOLVERR_URL` identifies an operator-trusted
226+
`NO_PROXY` settings because the selected endpoint identifies an operator-trusted
191227
service. The target replay continues to use the URL-reader-specific proxy
192228
settings first.
193229

@@ -309,10 +345,10 @@ operating-system sandbox.
309345
PDF parsing starts only after the response body is complete and has its own
310346
30-second worker budget. On the direct path, the HEAD checks and response body
311347
share the configured `FETCH_TIMEOUT_MS` network budget, after which parsing can
312-
take up to 30 additional seconds. With FlareSolverr enabled, add the initial
313-
HEAD preflight (up to 3 seconds), solver acquisition
314-
(`FLARESOLVERR_TIMEOUT_MS` plus up to 5 response-transfer seconds), and then the
315-
same replay-fetch and parser budgets.
348+
take up to 30 additional seconds. With a browser solver enabled, add the initial
349+
HEAD preflight (up to 3 seconds), solver acquisition (the selected provider
350+
timeout plus up to 5 response-transfer seconds), and then the same replay-fetch
351+
and parser budgets.
316352

317353
Set `MCP_HTTP_ALLOW_PRIVATE_URLS=true` only when internal URL reads are intentional for your deployment. This also allows hostnames that DNS-resolve to private/internal addresses.
318354

README.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,22 @@ For measured MCP-process CPU and memory starting points, see
5959
- **Search Suggestions**: Query autocomplete via SearXNG's `/autocompleter` endpoint.
6060
- **Instance Capability Discovery**: Inspect configured categories, engines, defaults, locales, and plugins from `/config`.
6161
- **URL Content Reading**: Content-type-aware Markdown conversion, including bounded PDF text extraction, with pagination, section filtering, paragraph ranges, and heading extraction.
62-
- **FlareSolverr Support**: For each uncached URL that passes URL validation and the HEAD size preflight, optionally attempt to acquire a browser session from FlareSolverr, then replay the returned user-agent and scoped cookies through the bounded URL reader. Verified with FlareSolverr 3.5.0 on 2026-07-30. Byparr has not been verified and is not currently supported. Independent compatibility reports are welcome.
62+
- **Browser Solver Support**: For each uncached URL that passes URL validation and the HEAD size preflight, optionally acquire a browser session from either FlareSolverr or Byparr, then replay the returned user-agent and scoped cookies through the bounded URL reader. FlareSolverr 3.5.0 and Byparr 2.1.0 were verified on 2026-07-30. Configure exactly one provider; automatic cross-provider fallback is not enabled.
6363
- **Intelligent Caching**: Both search results and URL content are cached in memory with configurable TTL and least-frequently-used (LFU) eviction, reducing redundant requests.
6464
- **SSRF Protection**: `web_url_read` blocks private/internal URLs and redirects by default in all transport modes.
6565
- **HTTP Transport**: Optional Streamable HTTP mode with opt-in hardening — bearer-token auth, CORS allowlist, and rate limiting.
6666
- **HTML Fallback**: Optionally parse results from the HTML page for public instances that reject `format=json`.
6767
- **Lite Tools Mode**: Minimal tool schemas for local models with small context windows.
6868
- **Proxy Support**: Global or per-tool HTTP/HTTPS proxies for search and URL-reader traffic.
6969

70+
The verified `linux/amd64` images came from multi-architecture manifests
71+
`ghcr.io/flaresolverr/flaresolverr:v3.5.0@sha256:139dfee1c6f89249c8d665d1333a42e8ec74ec0a86bc6bb1c8461e10d3a66a47`
72+
and
73+
`ghcr.io/thephaseless/byparr:2.1.0@sha256:01a46a2865d9a6db5eb8ead04ec0dd33b8fbe233e8565ae70b50d4cc0af4cfb0`.
74+
Client cancellation stops local work promptly, but a remote browser may
75+
continue until its configured provider timeout after the HTTP client
76+
disconnects. See [browser solver verification](docs/browser-solver-verification.md).
77+
7078
## Why mcp-searxng?
7179

7280
As of 2026-07-29, the capability comparison below reflects the official
@@ -150,10 +158,10 @@ For SearXNG deployment, configuration, and troubleshooting, see
150158
- Missing or generic content types are read under the existing size cap; non-binary bodies continue through the HTML-to-markdown path for compatibility
151159
- PDF input and extracted text are each capped at the lower of `URL_READ_MAX_CONTENT_LENGTH_BYTES` and 16 MiB. OCR is not supported, and scanned/image-only or password-protected PDFs return a short explanation.
152160
- A response declared as PDF must begin with the `%PDF-` signature; a mismatch usually indicates an interstitial or error page served with the wrong content type.
153-
- PDF parsing has a separate 30-second worker budget after the response body is downloaded. On the direct path, the network fetch and parse take at most the configured fetch budget plus 30 seconds; configured FlareSolverr preflight and acquisition time is additional.
161+
- PDF parsing has a separate 30-second worker budget after the response body is downloaded. On the direct path, the network fetch and parse take at most the configured fetch budget plus 30 seconds; configured browser-solver preflight and acquisition time is additional.
154162
- At most two PDF extractions run concurrently per MCP process. There is no queue; additional concurrent reads return a busy message and may be retried.
155163
- Other binary, media, archive, and octet-stream downloads are intentionally rejected with a short hint instead of returning raw bytes
156-
- When `FLARESOLVERR_URL` is configured, an uncached URL is validated and checked by the HEAD size preflight before `mcp-searxng` attempts FlareSolverr acquisition. Cache hits bypass acquisition; a full solver concurrency limit and transient solver failures use an uncached direct-fetch fallback. When a solver slot is available, every uncached URL that passes URL validation and the HEAD size preflight is disclosed to the configured FlareSolverr service.
164+
- When `FLARESOLVERR_URL` or `BYPARR_URL` is configured, an uncached URL is validated and checked by the HEAD size preflight before `mcp-searxng` attempts browser-session acquisition. Cache hits bypass acquisition; a full provider-specific concurrency limit and transient solver failures use an uncached direct-fetch fallback. When a solver slot is available, every uncached URL that passes URL validation and the HEAD size preflight is disclosed to the configured browser solver.
157165
- Inputs:
158166
- `url` (string): The URL to fetch and process
159167
- `startChar` (number, optional): Starting character position for content extraction (default: 0)
@@ -218,8 +226,8 @@ Image signatures can be verified with Cosign — see [SECURITY.md](SECURITY.md)
218226
```
219227

220228
To pass additional env vars, add `-e VAR_NAME` to `args` and the variable to `env`.
221-
For FlareSolverr integration, pass `FLARESOLVERR_URL` as well and make the
222-
service reachable from this container. See
229+
For browser-solver integration, pass either `FLARESOLVERR_URL` or `BYPARR_URL`
230+
and make that service reachable from this container. See
223231
[URL Reader Controls](CONFIGURATION.md#url-reader-controls) for the complete
224232
behavior and Docker Compose example.
225233

SECURITY.md

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,24 @@ To allow private URL reads and private DNS-resolved targets (e.g. for internal d
5959

6060
### Delegated Browser Service
6161

62-
Setting `FLARESOLVERR_URL` delegates challenge-page navigation to a trusted
63-
FlareSolverr service. Verified with FlareSolverr 3.5.0 on 2026-07-30.
64-
Byparr has not been verified and is not currently supported.
62+
Setting `FLARESOLVERR_URL` or `BYPARR_URL` delegates challenge-page navigation
63+
to a trusted browser service. FlareSolverr 3.5.0 and Byparr 2.1.0 were verified
64+
on 2026-07-30. Configure exactly one provider; simultaneous endpoints fail
65+
closed and automatic cross-provider fallback is not enabled.
66+
67+
The verified `linux/amd64` images came from multi-architecture manifests
68+
`ghcr.io/flaresolverr/flaresolverr:v3.5.0@sha256:139dfee1c6f89249c8d665d1333a42e8ec74ec0a86bc6bb1c8461e10d3a66a47`
69+
and
70+
`ghcr.io/thephaseless/byparr:2.1.0@sha256:01a46a2865d9a6db5eb8ead04ec0dd33b8fbe233e8565ae70b50d4cc0af4cfb0`.
71+
Client cancellation stops local work promptly, but a remote browser may
72+
continue until its configured provider timeout after the HTTP client
73+
disconnects.
6574

6675
Cache hits bypass solver acquisition. For uncached reads, `mcp-searxng`
6776
validates the requested target and performs the HEAD size preflight before
6877
attempting acquisition. When a solver slot is available, every uncached URL that
6978
passes URL validation and the HEAD size preflight is disclosed to the configured
70-
FlareSolverr service. Reads made while the solver concurrency limit is full use
79+
browser solver. Reads made while that provider's concurrency limit is full use
7180
the direct path without contacting it. `mcp-searxng` accepts a solution only for
7281
the same hostname, filters returned cookies by domain, path, secure flag, and
7382
expiry, rejects cookie names or values outside the HTTP cookie character set or
@@ -86,10 +95,14 @@ challenge. Treat that service as part of the trusted deployment boundary:
8695
workload;
8796
- keep the solver image updated and review its own security guidance.
8897

89-
Transient solver failures use the direct URL-reader path once. Invalid solver
98+
Transient solver failures use the direct URL-reader path once. Cancellation
99+
does not trigger fallback and is propagated through acquisition, replay, body
100+
streaming, and PDF extraction. Invalid solver
90101
configuration and hostname-divergent solutions fail closed. The solver API
91-
response is capped at 256 KiB, and concurrent solver acquisitions are bounded
92-
by `FLARESOLVERR_MAX_CONCURRENT_REQUESTS`.
102+
response is capped at 256 KiB for FlareSolverr and 5 MiB for Byparr, whose
103+
current API always returns rendered content alongside cookies. Concurrent
104+
acquisitions are bounded independently by the selected provider's
105+
`*_MAX_CONCURRENT_REQUESTS` variable.
93106

94107
### PDF Text Extraction
95108

0 commit comments

Comments
 (0)