These profiles are starting points for the mcp-searxng process only. They do not size SearXNG, Redis, a reverse proxy, or any upstream search engine. Measure your own workload before enforcing a production limit.
The baseline below was captured on 2026-07-29 from source commit
ecd0b7c99941d8e204d633676873058b2a07fffe:
- locally built mcp-searxng 1.12.1 production image;
- Node 24.18.0 on
linux/amd64; - Docker Engine 29.6.2 under Docker Desktop;
- 24 virtual CPUs and 15.18 GiB assigned to the Docker VM;
- one or two deterministic mock SearXNG replicas with 75 ms response latency;
- 20-result search responses and approximately 48 KiB HTML pages;
- Docker CPU and memory sampled with
docker stats --no-stream.
This repository retains the point-in-time description and results below, but
not the benchmark harness or raw docker stats output. The snapshot therefore
cannot be independently rerun from repository artifacts alone. Treat it as
historical starting evidence and measure the current image with your own
representative workload before enforcing limits.
The snapshot did not exercise browser-solver acquisition, dual-provider failover, or PDF text extraction. PDF reads can use two PDF extractions concurrently per MCP process. Each parse accepts at most 16 MiB of input, rejects documents above 500 pages, has a separate 30-second budget, and runs with a 192 MiB V8 old-generation ceiling plus a 4 MiB stack ceiling. The 192 MiB value is not a reservation, a complete worker-memory bound, or a container-memory recommendation.
Each client opened its own Streamable HTTP session. Every cycle called
searxng_web_search, web_url_read, searxng_search_suggestions, and
searxng_instance_info. Queries rotated across four cache keys, search pages
alternated between pageno 1 and 2, URL reads used a 12,000-character window,
and the first capability call refreshed /config.
| Profile | Concurrent sessions | Tool calls | Measured duration | Observed memory | Average CPU | Peak CPU |
|---|---|---|---|---|---|---|
| Small | 1 | 160 | 6.03 s | 52.39-110.60 MiB | 8.72% | 11.39% |
| Balanced | 4 | 480 | 6.06 s | 53.43-139.30 MiB | 20.39% | 31.84% |
| Research-heavy | 8 | 960 | 6.06 s | 53.63-254.40 MiB | 40.80% | 65.32% |
Docker CPU percentages use one logical CPU as approximately 100%. The balanced
run configured two replicas in default failover mode. The research-heavy run
queried two replicas with SEARXNG_FANOUT=true. The small, balanced, and
research-heavy cache caps were 100, 500, and 2,000 entries respectively.
This is a short, deterministic MCP overhead sample, not a soak test or an SLA. Real pages, response sizes, TLS, proxies, logging, cache cardinality, Node versions, and client behavior can move both CPU and memory. The ranges below add headroom to this sample; each is a starting range, not a universal requirement.
| Profile | Intended MCP workload | CPU starting range | Memory starting range |
|---|---|---|---|
| Small | 1-2 mostly sequential clients, one SearXNG URL, modest caches | 0.25-0.50 CPU | 192-256 MiB |
| Balanced | About 4 concurrent clients, replicas in failover mode, default-sized caches | 0.50-1.00 CPU | 256-384 MiB |
| Research-heavy | About 8 concurrent sessions, fan-out or large caches, frequent 12 KiB page reads | 1.00-2.00 CPU | 512-768 MiB |
Start at the lower end only when the measured workload resembles the baseline. Use the upper end when pages are larger, cache keys are less reusable, TLS or proxy work is significant, or concurrency arrives in bursts. Treat an out-of-memory kill or sustained CPU throttling as evidence that the enforced limit is too low, not as an application retry condition.
The optional 256 MiB balanced container overlay is a measured non-PDF starting point. It may be insufficient when representative traffic can reach the two-worker PDF concurrency limit. Measure PDF and browser-solver-enabled traffic before enforcing a memory ceiling.
NPX does not impose CPU or memory limits. Set the profile's mcp-searxng environment variables in the MCP client, observe the Node process with the operating system's process monitor, and use an operating-system or service manager limit only after measuring that client workload.
One STDIO process normally serves one client connection. Multiple clients that launch separate NPX processes need the profile allowance per process.
Docker's --cpus and --memory flags place ceilings on the MCP container. For
the balanced starting point:
docker run -i --rm \
--name mcp-searxng-profile \
--cpus 0.50 \
--memory 256m \
-e SEARXNG_URL=https://searxng.example.com \
isokoliuk/mcp-searxng:latestFor STDIO, keep -i; do not publish an HTTP port. Replace the two resource
values with the selected profile and watch for throttling or OOM termination.
The base docker-compose.yml remains STDIO-only. Add
docker-compose.http.yml to enable a standalone hardened Streamable HTTP
service, and optionally add docker-compose.resources.yml for balanced
resource defaults.
Before starting it, set SEARXNG_URL, MCP_HTTP_AUTH_TOKEN, and
MCP_HTTP_ALLOWED_ORIGINS in the operator environment. Compose fails during
interpolation, before creating a container, if either hardening value is
missing.
docker compose \
-f docker-compose.yml \
-f docker-compose.http.yml \
-f docker-compose.resources.yml \
up -dThe published endpoint defaults to 127.0.0.1:3000. Change the host-side
address only with MCP_SEARXNG_HTTP_BIND_ADDRESS, and change the host-side port
only with MCP_SEARXNG_HTTP_PUBLISHED_PORT. MCP_HTTP_PORT controls the
container-side listener and defaults to 3000; the overlay sets
MCP_HTTP_HOST=0.0.0.0 inside the container so Docker port forwarding can reach
it.
If MCP_SEARXNG_HTTP_PUBLISHED_PORT differs from MCP_HTTP_PORT, add the
client-visible host and published port in MCP_HTTP_ALLOWED_HOSTS. Hardened
mode derives its loopback defaults from the container-side MCP_HTTP_PORT,
while clients send the host-side port in the Host header.
For example, choose another point in the measured resource ranges without changing the loopback-only network default:
MCP_SEARXNG_CPUS=1.50 \
MCP_SEARXNG_MEMORY_LIMIT=768m \
MCP_SEARXNG_MEMORY_RESERVATION=512m \
docker compose \
-f docker-compose.yml \
-f docker-compose.http.yml \
-f docker-compose.resources.yml \
up -dMCP_SEARXNG_CPUS, MCP_SEARXNG_MEMORY_LIMIT, and
MCP_SEARXNG_MEMORY_RESERVATION are Compose interpolation variables, not
mcp-searxng application settings. Compose cpus is a CPU ceiling,
mem_limit is the memory ceiling, and mem_reservation is the requested
reservation. See Docker's current
Compose service reference
and container resource guidance.
When MCP_HTTP_ALLOWED_HOSTS is unset, hardened mode accepts the existing
loopback hostname defaults and their configured-port forms. Setting it replaces
those defaults, so list the exact Host forwarded by a reverse proxy.
MCP_HTTP_TRUST_PROXY is also optional and disabled by default. Enable it only
for a known proxy topology; otherwise clients can spoof X-Forwarded-For and
therefore the IP identity used for rate limiting and logs.
The overlay uses Compose pass-through syntax for those two optional variables. When either is absent from the operator environment, Compose passes no value for an optional variable. The server also treats a blank allowed-hosts value as unset and a blank trust-proxy value as disabled, preserving the safe defaults.
To inspect the merged model before launch, supply only a disposable placeholder token:
MCP_HTTP_AUTH_TOKEN=compose-test-token \
MCP_HTTP_ALLOWED_ORIGINS=https://client.example.invalid \
SEARXNG_URL=https://searxng.example.com \
docker compose \
-f docker-compose.yml \
-f docker-compose.http.yml \
-f docker-compose.resources.yml \
configdocker compose config prints expanded environment values, including
MCP_HTTP_AUTH_TOKEN. Never run or capture that command with a real production
token in CI logs or shared output.
Follow Hardened HTTP Mode for exact Host, Origin, TLS, and reverse-proxy guidance. Resource limits do not replace authentication, Host/Origin validation, TLS, or rate limiting.
| Concern | Current control | Capacity effect |
|---|---|---|
| Results per call | SEARXNG_MAX_RESULTS |
A lower 1-20 ceiling reduces response processing and agent context. |
| URL timeout | FETCH_TIMEOUT_MS |
Bounds how long a page read can occupy an in-flight request. |
| Solver timeout | FLARESOLVERR_TIMEOUT_MS or BYPARR_TIMEOUT_SECONDS |
Bounds browser-session acquisition separately from the target replay fetch; use only the selected provider's variable. |
| Solver concurrency | FLARESOLVERR_MAX_CONCURRENT_REQUESTS or BYPARR_MAX_CONCURRENT_REQUESTS |
Bounds acquisitions per provider per MCP process; excess requests use the direct path instead of queuing. |
| Search cache | SEARCH_CACHE_TTL_MS, SEARCH_CACHE_MAX_ENTRIES |
Larger or longer-lived caches trade memory for fewer upstream searches. |
| URL output | URL_READ_MAX_CHARS |
Sets the default returned window when the caller omits maxLength. |
| URL body cap | URL_READ_MAX_CONTENT_LENGTH_BYTES |
Bounds decompressed bytes read before conversion; the default is 5 MiB. |
| PDF extraction | Fixed limits | At most two PDF extractions run concurrently; each uses a 16 MiB input/output ceiling, 500-page limit, 30-second budget, 192 MiB V8 old-generation ceiling, and 4 MiB stack ceiling. |
| URL cache | CACHE_TTL_MS, CACHE_MAX_ENTRIES |
Larger or longer-lived caches trade memory for fewer page fetches. |
| Replica mode | SEARXNG_FANOUT |
Fan-out increases simultaneous upstream work; default failover is cheaper. |
| HTTP window | MCP_RATE_WINDOW_MS |
Defines the rate-limit accounting window. |
| New sessions | MCP_RATE_INIT_MAX |
Bounds initialization and invalid-session POST traffic per client IP. |
| Live sessions | MCP_RATE_SESSION_MAX |
Bounds established-session HTTP traffic per client IP. |
Do not raise cache caps and concurrency together without observing memory.
Do not use a short FETCH_TIMEOUT_MS to compensate for insufficient CPU.
Pagination and larger result counts increase total work even when each call
stays within its individual bound.
For the named docker run example, sample the container during representative
traffic:
docker stats --no-stream mcp-searxng-profile
docker inspect mcp-searxng-profile \
--format '{{.State.OOMKilled}} {{.RestartCount}}'For the three-file Compose HTTP profile, address the service through Compose instead of assuming Docker's generated container name:
docker compose \
-f docker-compose.yml \
-f docker-compose.http.yml \
-f docker-compose.resources.yml \
stats --no-stream mcp-searxng
container_id="$(docker compose \
-f docker-compose.yml \
-f docker-compose.http.yml \
-f docker-compose.resources.yml \
ps -q mcp-searxng)"
docker inspect "$container_id" --format '{{.State.OOMKilled}} {{.RestartCount}}'Record the Node version, image digest, architecture, active environment, concurrent sessions, call mix, page sizes, cache hit rate, sample duration, CPU average/peak, memory low/peak, errors, and restarts. Repeat after material changes to the workload or runtime.
Increase memory when normal peaks approach the ceiling or the container is OOM-killed. Increase CPU when latency rises with sustained throttling. Reduce cache caps or page/result limits when retained content is the cause. Scale out to separate MCP instances when clients need different security policies, SearXNG endpoints, cache lifecycles, failure domains, or when one process cannot meet the measured concurrency target with acceptable headroom.
These measurements cover only the MCP adapter process. They exclude SearXNG engine fan-out, Redis, result rendering, bot detection, and upstream network behavior. Use the self-hosted operator guide for the integration boundary and the SearXNG installation documentation for the search service itself. This project does not bundle SearXNG, and this guide does not size SearXNG.