Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 18 additions & 11 deletions CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,30 +311,37 @@ Never set `NODE_TLS_REJECT_UNAUTHORIZED=0`. It disables all TLS certificate vali

## HTTP Transport

By default the server communicates over STDIO. Set `MCP_HTTP_PORT` to enable HTTP mode instead.
By default the server communicates over STDIO. Set `MCP_HTTP_PORT` to enable HTTP mode instead. The SDK v2 server accepts modern MCP requests and retains legacy compatibility; modern HTTP clients should send the negotiated `MCP-Protocol-Version` header.

Both transports support modern `2026-07-28` plus legacy `2025-11-25`, `2025-06-18`, `2025-03-26`, `2024-11-05`, and `2024-10-07`. Modern HTTP requests are sessionless POSTs; legacy HTTP requests retain the stateful default or the configured legacy stateless mode.

Modern HTTP requests always use an isolated per-request server and are bounded by `MCP_HTTP_STATELESS_MAX_IN_FLIGHT`, `MCP_HTTP_STATELESS_MAX_IN_FLIGHT_PER_IP`, and `MCP_HTTP_STATELESS_REQUEST_TIMEOUT_MS`, even when `MCP_HTTP_STATELESS` is false. Setting `MCP_HTTP_STATELESS=true` extends that per-request serving model and the shared capacity controls to retained legacy POST requests.

The published server SDK `2.0.0` has a temporary compatibility guard for a 2026-07-28 request that omits that header: it returns the standard HTTP 400 HeaderMismatch response. The guard will be removed only after upgrading to a stable SDK containing upstream PR 2594 and proving that the SDK itself returns the same response.

| Variable | Required | Default | Description |
|---|---|---|---|
| `MCP_HTTP_PORT` | No | — | Port number to enable HTTP transport (e.g. `3000`) |
| `MCP_HTTP_HOST` | No | `127.0.0.1` | Interface address to bind to. Defaults to localhost-only for security. Set `0.0.0.0` for all interfaces (required for Docker and remote deployments), or a specific IP. Works in pair with `MCP_HTTP_PORT` only. **Breaking change from v1.2.1:** previous default was `0.0.0.0`. |
| `MCP_HTTP_TRUST_PROXY` | No | `false` | Express `trust proxy` setting for deployments behind a trusted reverse proxy. Use `true`, a trusted hop count such as `1`, or a proxy subnet/preset such as `loopback` or `10.0.0.0/8`. Unset, `false`, or `0` disables it (the secure default). |
| `MCP_HTTP_STATELESS` | No | `false` | Set to the exact value `true` to create an isolated MCP server and transport for every `POST /mcp`. `false`, blank, or unset disables it; any other nonblank value warns and uses `false`. Intended for deployments that cannot preserve process-local sessions. |
| `MCP_HTTP_STATELESS_MAX_IN_FLIGHT` | No | `16` (range `1`-`256`) | Global maximum number of admitted stateless POST requests in flight. Invalid values use the default. |
| `MCP_HTTP_STATELESS_MAX_IN_FLIGHT_PER_IP` | No | `8` (range `1`-global cap) | Per-client-IP in-flight maximum. Values above the normalized global cap are reduced to that cap. |
| `MCP_HTTP_STATELESS_REQUEST_TIMEOUT_MS` | No | `900000` (range `1000`-`2147483647`) | Maximum lifetime of an admitted stateless POST, including server construction, MCP handling, and an active response stream. |
| `MCP_HTTP_STATELESS` | No | `false` | Set to the exact value `true` to create an isolated MCP server and transport for every retained legacy `POST /mcp`; modern POSTs are always isolated. `false`, blank, or unset retains legacy stateful mode; any other nonblank value warns and uses `false`. Intended for deployments that cannot preserve process-local legacy sessions. |
| `MCP_HTTP_STATELESS_MAX_IN_FLIGHT` | No | `16` (range `1`-`256`) | Global maximum number of admitted modern or legacy-stateless POST requests in flight. Invalid values use the default. |
| `MCP_HTTP_STATELESS_MAX_IN_FLIGHT_PER_IP` | No | `8` (range `1`-global cap) | Per-client-IP in-flight maximum for modern or legacy-stateless POSTs. Values above the normalized global cap are reduced to that cap. |
| `MCP_HTTP_STATELESS_REQUEST_TIMEOUT_MS` | No | `900000` (range `1000`-`2147483647`) | Maximum lifetime of an admitted modern or legacy-stateless POST, including server construction, MCP handling, and an active response stream. |

**HTTP endpoints (when HTTP mode is active):**
- Stateful default: `POST/GET/DELETE /mcp` — session-based MCP protocol
- Modern: `POST /mcp` — sessionless MCP protocol
- Legacy stateful default: `POST/GET/DELETE /mcp` — session-based MCP protocol
- With `MCP_HTTP_STATELESS=true`: `POST /mcp` only; GET and DELETE return HTTP 405 with `Allow: POST`
- `GET /health` — health check

HTTP sessions are stored in memory per process. A stale or unknown `mcp-session-id` on a non-initialize `POST /mcp` receives HTTP 404 with JSON-RPC error code `-32001` and message `"Session not found"`. Clients should recover by running `initialize` again; initialize requests are accepted even when they still carry a stale session header.

In stateless mode, every POST creates a fresh MCP server and transport, ignores incoming `mcp-session-id` headers, and never emits a response session ID. A POST can return negotiated JSON or an SSE stream within that same POST. Cross-request sessions, resumable streams, standalone GET notification streams, and DELETE-based session termination are unavailable. This mode does not require an SDK 2.0 upgrade; it uses the stateless transport contract provided by the currently supported SDK.
In stateless mode, every POST creates a fresh MCP server and transport, ignores incoming `mcp-session-id` headers, and never emits a response session ID. A POST can return negotiated JSON or an SSE stream within that same POST. Cross-request sessions, resumable streams, standalone GET notification streams, and DELETE-based session termination are unavailable. Modern requests use the SDK v2 request handler; retained legacy requests use the Node transport.

## Rate Limiting (HTTP mode)

Rate limiting is always active in HTTP mode to prevent resource exhaustion. Before the MCP handler runs, each request is counted by resolved client IP against exactly one limit. In stateful mode, POST requests with a currently live session use the session limit, other POST requests use the initialization limit, and GET/DELETE requests always use the session limit. In stateless mode, only a single parsed request object recognized by the SDK as `initialize` uses the initialization limit; all other POST bodies, including notifications and batches, use the session limit, and GET/DELETE still use the session limit. Malformed or oversized JSON is rejected by parsing before rate limiting or MCP server construction.
Rate limiting is always active in HTTP mode to prevent resource exhaustion. Before the MCP handler runs, each request is counted by resolved client IP against exactly one limit. In stateful mode, retained legacy POST requests with a currently live session use the session limit; modern sessionless POST requests and all other POST requests use the initialization limit, even if they present a live legacy `mcp-session-id`. GET/DELETE requests always use the session limit. In stateless mode, only a single parsed request object recognized by the SDK as `initialize` uses the initialization limit; all other POST bodies, including notifications and batches, use the session limit, and GET/DELETE still use the session limit. Malformed or oversized JSON is rejected by parsing before rate limiting or MCP server construction.

Each `MCP_RATE_*` value must be a positive decimal safe integer after JavaScript whitespace trimming. A leading `+` and leading zeros are accepted; fractions, suffixes, exponents, hexadecimal forms, non-positive values, and integers above `Number.MAX_SAFE_INTEGER` are rejected. An invalid value uses the documented default and emits one startup warning per variable without copying the raw value into diagnostics. Blank or unset variables use the default silently.

Expand All @@ -343,8 +350,8 @@ Before this correction, spellings such as `20requests`, `12.5`, or `1e3` could b
| Variable | Required | Default | Description |
|---|---|---|---|
| `MCP_RATE_WINDOW_MS` | No | `60000` | Sliding window duration in milliseconds for all rate limits |
| `MCP_RATE_INIT_MAX` | No | `20` | Max POST `/mcp` requests per window when `mcp-session-id` is missing or does not identify a currently live session. Guards initialization, invalid, unknown-session, and stale-session flooding. |
| `MCP_RATE_SESSION_MAX` | No | `300` | Max POST `/mcp` requests for currently live sessions and all GET/DELETE `/mcp` requests per window, including GET/DELETE requests with missing or invalid session IDs. Intentionally generous for AI agents. |
| `MCP_RATE_INIT_MAX` | No | `20` | Max POST `/mcp` requests for modern sessionless traffic and requests without a currently live retained legacy session in stateful mode, plus SDK-recognized initialize requests in stateless mode. Guards initialization, invalid, unknown-session, stale-session, and legacy-session-header borrowing. |
| `MCP_RATE_SESSION_MAX` | No | `300` | Max POST `/mcp` requests for currently live retained legacy sessions and all GET/DELETE `/mcp` requests per window, including GET/DELETE requests with missing or invalid session IDs. Intentionally generous for AI agents. |

Requests exceeding a limit receive HTTP 429 with a JSON-RPC error body (`code: -32029`). `/health` has a fixed limit of 60 requests per minute. Standard `RateLimit-*` headers are included on all responses.

Expand Down Expand Up @@ -415,7 +422,7 @@ Opt-in security layer for when you expose the HTTP transport on a network. Defau

### Origin validation and upgrade notice

Every present `Origin` on `/mcp` is validated in all modes; an absent `Origin` remains valid for non-browser clients. In non-hardened mode, an unset `MCP_HTTP_ALLOWED_ORIGINS` defaults to the exact HTTP/HTTPS loopback origins `http://127.0.0.1`, `https://127.0.0.1`, `http://localhost`, `https://localhost`, `http://[::1]`, and `https://[::1]`, both portless and with the configured `MCP_HTTP_PORT`. A non-empty `MCP_HTTP_ALLOWED_ORIGINS` replaces those defaults. Entries are trimmed but otherwise literal; matching is exact, case-sensitive literal matching, including scheme and port. Malformed, scheme-less, path-bearing, trailing slash, or differently-cased values silently do not match and must be corrected. Hardened mode still requires an explicit allowlist and adds authentication plus Host enforcement. An invalid present `Origin` on `/mcp` receives a fixed, non-reflecting 403 before parser, authentication, rate limiting, or transport construction. `/health` is outside the MCP 403 boundary but uses the narrowed global CORS allowlist. Before upgrading, existing non-hardened browser deployments using non-loopback Origins must set `MCP_HTTP_ALLOWED_ORIGINS` or receive a fixed 403. This change addresses the Origin security requirement; legacy transport compatibility remains and is not a claim of full MCP 2026-07-28 transport compliance.
Every present `Origin` on `/mcp` is validated in all modes; an absent `Origin` remains valid for non-browser clients. In non-hardened mode, an unset `MCP_HTTP_ALLOWED_ORIGINS` defaults to the exact HTTP/HTTPS loopback origins `http://127.0.0.1`, `https://127.0.0.1`, `http://localhost`, `https://localhost`, `http://[::1]`, and `https://[::1]`, both portless and with the configured `MCP_HTTP_PORT`. A non-empty `MCP_HTTP_ALLOWED_ORIGINS` replaces those defaults. Entries are trimmed but otherwise literal; matching is exact, case-sensitive literal matching, including scheme and port. Malformed, scheme-less, path-bearing, trailing slash, or differently-cased values silently do not match and must be corrected. Hardened mode still requires an explicit allowlist and adds authentication plus Host enforcement. An invalid present `Origin` on `/mcp` receives a fixed, non-reflecting 403 before parser, authentication, rate limiting, or transport construction. `/health` is outside the MCP 403 boundary but uses the narrowed global CORS allowlist. Before upgrading, existing non-hardened browser deployments using non-loopback Origins must set `MCP_HTTP_ALLOWED_ORIGINS` or receive a fixed 403. The transport support matrix above defines the modern sessionless and retained legacy boundaries.

## URL Reader Security

Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ For measured MCP-process CPU and memory starting points, see
- **Browser Solver Support**: For each uncached URL that passes static URL validation and the HEAD size preflight, optionally acquire a browser session from FlareSolverr, Byparr, or both, then replay the returned user-agent and scoped cookies through the bounded URL reader. In dual-provider mode FlareSolverr is always primary and Byparr is attempted only after a busy or transient-unavailable primary. FlareSolverr 3.5.0 and Byparr 2.1.0 were verified on 2026-07-30.
- **Intelligent Caching**: Both search results and URL content are cached in memory with configurable TTL and least-frequently-used (LFU) eviction, reducing redundant requests.
- **SSRF Protection**: `web_url_read` blocks private/internal URLs and redirects by default in all transport modes.
- **HTTP Transport**: Optional Streamable HTTP mode with opt-in hardening, rate limiting, and bounded stateless compatibility for serverless or horizontally scaled deployments.
- **HTTP Transport**: Optional MCP SDK v2 Streamable HTTP mode with opt-in hardening, rate limiting, and bounded stateless compatibility for serverless or horizontally scaled deployments. Modern 2026-07-28 requests and retained legacy clients share the same tool and resource surface.
- **HTML Fallback**: Optionally parse results from the HTML page for public instances that reject `format=json`.
- **Lite Tools Mode**: Minimal tool schemas for local models with small context windows.
- **Proxy Support**: Global or per-tool HTTP/HTTPS proxies for search and URL-reader traffic.
Expand Down Expand Up @@ -340,13 +340,15 @@ The `--add-host` mapping lets the container reach a SearXNG instance on the host
}
```

**Endpoints:** `POST/GET/DELETE /mcp` (stateful MCP protocol), `GET /health` (health check)
**Protocol support:** HTTP and STDIO serve modern MCP `2026-07-28` and the retained legacy revisions `2025-11-25`, `2025-06-18`, `2025-03-26`, `2024-11-05`, and `2024-10-07`. Modern HTTP is sessionless `POST /mcp`; legacy HTTP remains stateful by default (`POST/GET/DELETE /mcp`) or uses the existing POST-only stateless mode.

Stateful sessions remain the default. Set `MCP_HTTP_STATELESS=true` when a deployment cannot preserve in-memory sessions between requests. Every stateless POST creates a fresh MCP server and transport, ignores any incoming session ID, and returns negotiated JSON or an SSE stream within that same POST. Stateless mode is POST-only: `GET /mcp` and `DELETE /mcp` return HTTP 405 with `Allow: POST`, and no cross-request subscriptions, resumability, or server-to-client notifications are preserved.
**Endpoints:** modern `POST /mcp`; legacy `POST/GET/DELETE /mcp` in the stateful default, or legacy `POST /mcp` only with stateless mode; `GET /health`.

For legacy HTTP clients, stateful sessions remain the default. Set `MCP_HTTP_STATELESS=true` when a deployment cannot preserve in-memory legacy sessions between requests. Modern HTTP remains sessionless regardless of that setting. Every stateless POST creates a fresh MCP server and transport, ignores any incoming session ID, and returns negotiated JSON or an SSE stream within that same POST. Stateless mode is POST-only: `GET /mcp` and `DELETE /mcp` return HTTP 405 with `Allow: POST`, and no cross-request subscriptions, resumability, or server-to-client notifications are preserved.

Stateless requests are bounded by global and per-client-IP in-flight limits plus a request lifetime. See [CONFIGURATION.md](CONFIGURATION.md#http-transport) for defaults, overload and timeout responses, proxy-aware fairness, and the complete compatibility contract.

**Origin validation and upgrade notice:** Every present `Origin` on `/mcp` is validated in all modes; an absent `Origin` remains valid for non-browser clients. In non-hardened mode, an unset `MCP_HTTP_ALLOWED_ORIGINS` defaults to the exact HTTP/HTTPS loopback origins `http://127.0.0.1`, `https://127.0.0.1`, `http://localhost`, `https://localhost`, `http://[::1]`, and `https://[::1]`, both portless and with the configured `MCP_HTTP_PORT`. A non-empty `MCP_HTTP_ALLOWED_ORIGINS` replaces those defaults. Entries are trimmed but otherwise literal; matching is exact, case-sensitive literal matching, including scheme and port. Malformed, scheme-less, path-bearing, trailing slash, or differently-cased values silently do not match and must be corrected. Hardened mode still requires an explicit allowlist and adds authentication plus Host enforcement. An invalid present `Origin` on `/mcp` receives a fixed, non-reflecting 403 before parser, authentication, rate limiting, or transport construction. `/health` is outside the MCP 403 boundary but uses the narrowed global CORS allowlist. Before upgrading, existing non-hardened browser deployments using non-loopback Origins must set `MCP_HTTP_ALLOWED_ORIGINS` or receive a fixed 403. This change addresses the Origin security requirement; legacy transport compatibility remains and is not a claim of full MCP 2026-07-28 transport compliance.
**Origin validation and upgrade notice:** Every present `Origin` on `/mcp` is validated in all modes; an absent `Origin` remains valid for non-browser clients. In non-hardened mode, an unset `MCP_HTTP_ALLOWED_ORIGINS` defaults to the exact HTTP/HTTPS loopback origins `http://127.0.0.1`, `https://127.0.0.1`, `http://localhost`, `https://localhost`, `http://[::1]`, and `https://[::1]`, both portless and with the configured `MCP_HTTP_PORT`. A non-empty `MCP_HTTP_ALLOWED_ORIGINS` replaces those defaults. Entries are trimmed but otherwise literal; matching is exact, case-sensitive literal matching, including scheme and port. Malformed, scheme-less, path-bearing, trailing slash, or differently-cased values silently do not match and must be corrected. Hardened mode still requires an explicit allowlist and adds authentication plus Host enforcement. An invalid present `Origin` on `/mcp` receives a fixed, non-reflecting 403 before parser, authentication, rate limiting, or transport construction. `/health` is outside the MCP 403 boundary but uses the narrowed global CORS allowlist. Before upgrading, existing non-hardened browser deployments using non-loopback Origins must set `MCP_HTTP_ALLOWED_ORIGINS` or receive a fixed 403.

**Test it:**

Expand Down
Loading