{
  "question": "We're about to expose our MCP server over Streamable HTTP. What stops one anonymous client from knocking it offline with a broken or oversized request, or from grabbing the server so nobody else can connect, and what limits should the HTTP layer enforce before a request reaches the MCP SDK?",
  "slug": "how-to-keep-one-client-from-crashing-or-locking-up-an-mcp-servers-http-mode",
  "url": "https://www.rightshold.com/answers/how-to-keep-one-client-from-crashing-or-locking-up-an-mcp-servers-http-mode",
  "short_answer": "Put the limits in the HTTP layer, in front of the SDK: a byte cap on the request body that answers 413, a try/catch around JSON parsing that answers 400, one transport per client session (or a stateless server) with caps on how many sessions exist, idle timeouts, request and header timeouts, and a rate limit. Three advisories for @tsparticles/mcp-server, added to GitHub's advisory database on November 9, 2026, show what happens without them. One malformed body such as {\"jsonrpc\": made JSON.parse throw outside any error handler and ended the Node.js process (CVE-2026-76892, fixed in 4.3.1). The handler buffered every chunk of a POST with no size check, so a chunked 300 MB stream got a container with a 256 MB memory cap OOM-killed (CVE-2026-63655, fixed in 4.3.1); the project's Docker image ran HTTP mode on 0.0.0.0:3000 out of the box. And one shared transport served every client, so the first anonymous initialize claimed it and every later client got \"Server already initialized\" until a restart (CVE-2026-76893, fixed in 4.3.2). None of the three needed a credential. The MCP 2026-07-28 specification retires the initialize handshake and the Mcp-Session-Id header, so a server on that version has no protocol session for anyone to grab, but body caps, parse errors and rate limits are still yours to handle.",
  "checklist": [
    "Cap the request body by counting bytes as chunks arrive, not by trusting Content-Length; a chunked request has none. Stop buffering at the cap and answer 413.",
    "Wrap JSON parsing of every request in a local try/catch and answer 400 with a short error. One bad request should never reach the process-level crash handler.",
    "Give each client session its own server and transport, kept in a bounded map, or run stateless so no session exists. Never share one stateful transport across all HTTP clients.",
    "Cap total sessions and sessions per client, close sessions that go idle, and set request, header and keep-alive timeouts on the HTTP server so a slow sender can't hold a connection open.",
    "Rate limit per client and add a process-wide ceiling, because a per-IP limit alone can be spread across many source addresses. Derive the client address only from your own proxy's forwarding header.",
    "Run under a supervisor that restarts the process, with a memory limit, so a crash you didn't foresee costs seconds, not an outage that lasts until someone notices.",
    "Require a credential before any of this is reachable from another machine; bind to 127.0.0.1 when only local clients need it.",
    "Add tests that send a truncated JSON body, a body one byte over the cap, and two initialize requests from different clients, and assert 400, 413 and two working sessions."
  ],
  "sections": [
    {
      "heading": "What the tsParticles MCP server advisories showed",
      "points": [
        "The HTTP mode of @tsparticles/mcp-server read POST /mcp bodies by pushing every data chunk into an array, joining them and calling JSON.parse on the result, then handing that to the SDK transport. GHSA-4p8p-pjpq-2w7h (CVE-2026-76892, CWE-248, CVSS 4.0 8.7) says the parse sat outside a request-local error boundary: the truncated body {\"jsonrpc\": ended the process in 3 out of 3 runs, and the service stayed down until an outside supervisor restarted it. It affects 4.3.0 and earlier and is fixed in 4.3.1, which answers 400 for invalid JSON.",
        "GHSA-447x-qqwf-m7xq (CVE-2026-63655, CWE-400, CVSS 3.1 7.5) covers the same handler from the other side: no cumulative size check, no Content-Length check and no 413 anywhere in the server. The report streamed a 300 MB chunked body at a container limited to 256 MB; after about 209 MB Docker's OOM killer ended the process. It notes the project's Dockerfile started the server with --port 3000, which bound HTTP mode to all interfaces, so anyone running the image was exposed without opting in. It is fixed in 4.3.1.",
        "GHSA-h7c4-q426-7jh9 (CVE-2026-76893, CWE-400, CVSS 4.0 6.9) is a different kind of denial: the server created one stateful StreamableHTTPServerTransport and reused it for every request. The first client's initialize got a session id; a second, independent initialize got HTTP 400 \"Invalid Request: Server already initialized\". An anonymous client that connects first keeps every legitimate client out until a restart. The advisory's remedy is a transport per session or a bounded map from session id to transport, and it is fixed in 4.3.2."
      ]
    },
    {
      "heading": "What the fixed server does now",
      "points": [
        "The project's current HTTP server code (main branch, read November 13, 2026) is a compact example of the limits above. It counts body bytes as they arrive, stops buffering past 1 MB and answers 413 once the body has been read, and answers 400 \"Invalid JSON\" when parsing throws. Each initialize gets a new server and transport pair kept in a map, with at most 500 concurrent sessions and 10 per client, and sessions idle for 30 minutes are closed by a sweep every 5 minutes.",
        "It also sets a 30-second request timeout and a 15-second header timeout, rate limits each client to 120 requests a minute with a process-wide ceiling of 2,000, and its own comments say the per-IP limit is easy to spread across many addresses, which is why the global ceiling exists. It reads X-Forwarded-For only when the connecting peer is a configured trusted proxy, and it refuses to start on a non-loopback host without an auth token.",
        "Those numbers suit a small tool server; pick yours from your real request sizes and client counts. The pattern is what carries over: every limit is enforced before the SDK sees the request, and every rejection is a bounded response, not a crash."
      ]
    },
    {
      "heading": "What the 2026-07-28 specification changes",
      "points": [
        "The 2026-07-28 MCP specification retires the initialize/initialized exchange and the Mcp-Session-Id header (SEP-2575, SEP-2567). Each request carries its protocol version, client identity and capabilities itself, so any request can land on any server instance, and there is no shared protocol session for a first caller to claim. A server that also serves clients on older protocol versions still creates sessions for them, so keep the per-session limits until you drop those versions.",
        "Streamable HTTP requests on the new version must carry Mcp-Method and Mcp-Name headers (SEP-2243). The project announcement says gateways, rate limiters and WAFs can route and meter on those headers instead of parsing JSON bodies, which lets you reject or throttle a request before your server reads its body. Your server still has to cap and parse that body safely when it does arrive."
      ]
    }
  ],
  "checks": [],
  "ask_rightshold": {
    "start": "https://www.rightshold.com/start?ref=seo",
    "mcp_endpoint": "https://www.rightshold.com/mcp",
    "ask": "I just upgraded our @tsparticles/mcp-server deployment to 4.3.2 and put it behind our proxy. Check what it serves now: has its tool list changed from what was recorded, and does any tool definition carry hidden text?",
    "mcp_call": {
      "tool": "precall_check",
      "arguments": {
        "url": "<your server's https MCP URL>"
      }
    },
    "also": [
      "Free, no key. It probes the tool definitions your server serves now and compares them with any recorded fingerprint (unchanged, changed with a plain diff, or unreachable, never reported as unchanged), scans them for invisible text and reports standing security flags and tool-name collisions. Checking a server doesn't register it.",
      "It doesn't send malformed or oversized bodies, open competing sessions, load-test your server or check its version, body cap, timeouts or rate limits. Those are the tests in the checklist, and they belong in your own CI against a server you own."
    ],
    "scope": "Rightshold isn't affiliated with tsParticles or the Model Context Protocol project, and it doesn't load-test, fuzz or scan MCP servers. Advisory details are as published in GitHub's advisory database (GHSA-4p8p-pjpq-2w7h, GHSA-447x-qqwf-m7xq, GHSA-h7c4-q426-7jh9), the limits as written in the tsparticles/tsparticles repository's mcp-server HTTP source on its main branch, and the session and header changes as described in the MCP project's 2026-07-28 release announcement, all read November 13, 2026; advisories and code change, so confirm fixed versions on the linked pages."
  },
  "sources": [
    {
      "title": "GitHub Advisory Database: GHSA-4p8p-pjpq-2w7h, malformed JSON requests can terminate the @tsparticles/mcp-server HTTP process (CVE-2026-76892)",
      "url": "https://github.com/advisories/GHSA-4p8p-pjpq-2w7h"
    },
    {
      "title": "GitHub Advisory Database: GHSA-447x-qqwf-m7xq, unbounded HTTP request body buffering in @tsparticles/mcp-server (CVE-2026-63655)",
      "url": "https://github.com/advisories/GHSA-447x-qqwf-m7xq"
    },
    {
      "title": "GitHub Advisory Database: GHSA-h7c4-q426-7jh9, a single anonymous MCP session can monopolize HTTP mode (CVE-2026-76893)",
      "url": "https://github.com/advisories/GHSA-h7c4-q426-7jh9"
    },
    {
      "title": "tsparticles/tsparticles: mcp-server HTTP server source (main)",
      "url": "https://github.com/tsparticles/tsparticles/blob/main/integrations/mcp-server/src/http/server.ts"
    },
    {
      "title": "tsparticles/tsparticles: mcp-server HTTP limits (constants.ts, main)",
      "url": "https://github.com/tsparticles/tsparticles/blob/main/integrations/mcp-server/src/http/constants.ts"
    },
    {
      "title": "Model Context Protocol blog: The 2026-07-28 Specification",
      "url": "https://blog.modelcontextprotocol.io/posts/2026-07-28/"
    }
  ],
  "related": [
    {
      "title": "Is it safe to run an MCP server in HTTP mode without authentication?",
      "url": "https://www.rightshold.com/answers/is-it-safe-to-run-an-mcp-server-in-http-mode-without-authentication"
    },
    {
      "title": "Protecting a local MCP server from DNS rebinding",
      "url": "https://www.rightshold.com/answers/how-to-protect-a-local-mcp-server-from-dns-rebinding"
    },
    {
      "title": "Can an MCP session ID or state handle be used to authenticate a user?",
      "url": "https://www.rightshold.com/answers/can-an-mcp-session-id-or-state-handle-be-used-to-authenticate-a-user"
    },
    {
      "title": "How to cap MCP tool output size to prevent context flooding",
      "url": "https://www.rightshold.com/answers/how-to-cap-mcp-tool-output-size-to-prevent-context-flooding"
    },
    {
      "title": "Can a spoofed X-Forwarded-For header get past an MCP server's IP allowlist?",
      "url": "https://www.rightshold.com/answers/can-a-spoofed-x-forwarded-for-header-get-past-an-mcp-servers-ip-allowlist"
    }
  ],
  "computed_from": "Curated guidance; license verdicts computed from Rightshold's license profiles at render time. Not legal advice.",
  "updated": "2026-11-13"
}