Skip to content

Latest commit

 

History

History
265 lines (215 loc) · 12.5 KB

File metadata and controls

265 lines (215 loc) · 12.5 KB

Measured MCP Deployment Profiles

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.

Measurement snapshot

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.

Starting profiles

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.

Apply a profile

NPX and STDIO

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 and STDIO

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:latest

For 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.

Standalone HTTP with Compose

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 -d

The 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 -d

MCP_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 \
  config

docker 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.

Map workload to current controls

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.

Observe and adjust

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.

SearXNG is a separate capacity plan

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.