CVE
CVE-2026-111767, CVE-2026-111770, CVE-2026-111768, CVE-2026-111771, CVE-2026-111769, CVE-2026-25536
CWE
CWE-639, CWE-862, CWE-362, CWE-770, CWE-863, CWE-200
Affected Surface
- npm package `@modelcontextprotocol/sdk` versions `< 1.32.0` (`1.24.0` to `1.31.0` for `CVE-2026-111767`, `1.3.0` to `1.31.0` for `CVE-2026-111768`, `1.6.0` to `1.31.0` for `CVE-2026-111771`, and all `1.x <= 1.31.0` for `CVE-2026-111769`)
- npm package `@modelcontextprotocol/server` versions `2.0.0` through `2.2.0`, plus `@modelcontextprotocol/client` (`2.0.0` to `2.2.0`), `@modelcontextprotocol/express` (`2.0.0` to `2.0.1`), `@modelcontextprotocol/server-legacy` (`2.0.0` to `2.3.0`), `@modelcontextprotocol/node` (`2.0.0`), and `@modelcontextprotocol/hono` (`2.0.0`)
- HTTP-exposed Model Context Protocol servers built with Express, Fastify, Hono, `node:http`, or Cloudflare Workers that share a single `McpServer`, `Server`, `InMemoryTaskStore`, or stateless `StreamableHTTPServerTransport` across clients
TL;DR
On 20 October 2026, the GitHub Advisory Database and NVD published five CVE-assigned advisories (CVE-2026-111767 through CVE-2026-111771) alongside a sixth repository advisory (GHSA-f897-62hf-6gxq) for the official Model Context Protocol TypeScript SDK (@modelcontextprotocol/sdk and the v2 @modelcontextprotocol/server package family). While the fixes shipped quietly in @modelcontextprotocol/sdk@1.32.0 and @modelcontextprotocol/server@2.3.0 on 2 October, the public advisories reveal three operational traps that a version bump alone will not solve:
- Cross-client message delivery taught by the official READMEs (
CVE-2026-111770,GHSA-jm63-cq47-j5jp, CVSS 7.7 High): In@modelcontextprotocol/server2.0.0through2.2.0, sharing oneMcpServer,Server, or statelessStreamableHTTPServerTransportacross HTTP requests matched replies by JSON-RPCidalone. Because MCP clients start request IDs at0, concurrent clients received each other’s tool outputs and notifications. Until2.3.0, the official README examples for@modelcontextprotocol/hono,express,fastify, andnodeinstructed developers to write that exact shared-instance pattern. Upgrading to2.3.0makes the second request fail withALREADY_CONNECTEDor a500error unless you refactor your handler to constructMcpServerand the transport per request. - Un-scoped experimental tasks across HTTP sessions (
CVE-2026-111767,GHSA-22jm-h49p-29qw, CVSS 8.6 High): In@modelcontextprotocol/sdk1.24.0through1.31.0,InMemoryTaskStoreaccepted_sessionIdand ignored it. Any HTTP client sharing the store could list, read results from, and cancel tasks created by other sessions. - Two fixes that do nothing until you change code (
CVE-2026-111768/GHSA-46jr-4wx8-v453, CVSS 7.5 High, andCVE-2026-111771/GHSA-rvq5-wwqv-78pq, CVSS 6.8 Medium): A single HTTP request inside the 4 MiB body limit can crash the Node.js process withJavaScript heap out of memoryduring tool argument validation (CVE-2026-111768), andrequireBearerAuth()accepts OAuth bearer tokens issued for other services (CVE-2026-111771). Both advisories state that “Upgrading alone changes nothing”: you must explicitly setmaxToolInputElementsonMcpServerand wireexpectedResourceplusAuthInfo.resourceinrequireBearerAuth().
Affected packages and projects
| Ecosystem | Package | CVE / GHSA | CVSS v3.1 | Affected versions | Fixed version | Code change required after upgrade? |
|---|---|---|---|---|---|---|
| npm | @modelcontextprotocol/sdk | CVE-2026-111767 (GHSA-22jm-h49p-29qw) | 8.6 High | >= 1.24.0, < 1.32.0 | 1.32.0 (1.32.1 current) | Custom TaskStore implementations must scope by sessionId |
| npm | @modelcontextprotocol/server | CVE-2026-111770 (GHSA-jm63-cq47-j5jp) | 7.7 High | >= 2.0.0, < 2.3.0 | 2.3.0 (2.3.1 current) | Yes: construct McpServer and transport per request or session |
| npm | @modelcontextprotocol/sdk, @modelcontextprotocol/server | CVE-2026-111768 (GHSA-46jr-4wx8-v453) | 7.5 High | sdk >= 1.3.0, < 1.32.0; server >= 2.0.0, < 2.3.0 | sdk 1.32.0, server 2.3.0 | Yes: pass { maxToolInputElements } and lower maxRequestBodySize |
| npm | @modelcontextprotocol/sdk, server, node, hono | GHSA-f897-62hf-6gxq | 7.5 High | sdk 1.10.0..1.30.0; server 2.0.0; node 2.0.0; hono 2.0.0 | sdk 1.30.1, server 2.1.0, node 2.1.0, hono 2.0.1 | Default 4 MiB body cap and 100-message batch cap enabled automatically |
| npm | @modelcontextprotocol/sdk, server, express, server-legacy | CVE-2026-111771 (GHSA-rvq5-wwqv-78pq) | 6.8 Medium | sdk 1.6.0..1.31.0; server 2.0.0..2.2.0; express 2.0.0..2.0.1; server-legacy 2.0.0..2.3.0 | sdk 1.32.0, server 2.3.0, express 2.0.2, server-legacy 2.3.1 | Yes: set expectedResource AND return AuthInfo.resource |
| npm | @modelcontextprotocol/sdk, @modelcontextprotocol/client | CVE-2026-111769 (GHSA-6prh-2h8m-c8cw) | 6.5 Medium | sdk <= 1.31.0; client 2.0.0..2.2.0 | sdk 1.32.0, client 2.3.0 | Same-origin redirect policy enabled by default; rotate leaked secrets |
Technical analysis
1. CVE-2026-111770: the shared-server regression and the quickstart pattern that caused it
In February 2026, @modelcontextprotocol/sdk 1.26.0 patched CVE-2026-25536 (GHSA-345p-7cg4-v4c7), a bug where sharing a single McpServer or transport instance across HTTP connections routed responses using an internal requestId -> stream map keyed only by the JSON-RPC id. Because MCP client SDKs initialize their request counter at 0, two clients talking to the same HTTP endpoint generated colliding request IDs (0, 1, 2), causing Client A’s tool responses and server notifications to be written to Client B’s HTTP stream.
When the 2.x rewrite (@modelcontextprotocol/server 2.0.0) shipped on 27 July 2026, that guard was omitted. Worse, the official READMEs for @modelcontextprotocol/hono, @modelcontextprotocol/express, @modelcontextprotocol/fastify, and @modelcontextprotocol/node taught developers to instantiate McpServer (and in Hono, WebStandardStreamableHTTPServerTransport) once at module scope and reuse it on every request.
Commit 84804c22e4 (PR #2918) fixes both the runtime enforcement and the documentation across all four middleware packages. In packages/server/src/server/server.ts and streamableHttp.ts, the SDK now throws immediately if a Server is connected twice or a stateless transport handles a second request:
--- a/packages/server/src/server/server.ts
+++ b/packages/server/src/server/server.ts
@@ -351,6 +352,17 @@ export class Server extends Protocol<ServerContext> {
+ /** Attaches to the given transport; rejects while this instance is connected to another one. */
+ override async connect(transport: Transport): Promise<void> {
+ if (this.transport !== undefined) {
+ throw new SdkError(
+ SdkErrorCode.AlreadyConnected,
+ 'Use a separate Server instance per connection: this instance is already connected to a transport. To connect it to a new transport, call close() first.'
+ );
+ }
+ await super.connect(transport);
+ }
--- a/packages/server/src/server/streamableHttp.ts
+++ b/packages/server/src/server/streamableHttp.ts
@@ -415,6 +424,12 @@ export class WebStandardStreamableHTTPServerTransport implements Transport {
+ // A stateless transport (no sessionIdGenerator) serves exactly one request.
+ if (!this.sessionIdGenerator && this._hasHandledRequest) {
+ throw new Error('Stateless transport cannot be reused across requests. Create a new transport per request.');
+ }
+ this._hasHandledRequest = true;
And the adapter READMEs (such as packages/middleware/hono/README.md) show the required code change:
--- a/packages/middleware/hono/README.md
+++ b/packages/middleware/hono/README.md
@@ -30,12 +30,14 @@
-const server = new McpServer({ name: 'my-server', version: '1.0.0' });
-const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined });
-await server.connect(transport);
-
const app = createMcpHonoApp();
-app.all('/mcp', c => transport.handleRequest(c.req.raw, { parsedBody: c.get('parsedBody') }));
+app.all('/mcp', async c => {
+ // Stateless example: create a server and a transport per request.
+ const server = new McpServer({ name: 'my-server', version: '1.0.0' });
+ const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined });
+ await server.connect(transport);
+ return transport.handleRequest(c.req.raw, { parsedBody: c.get('parsedBody') });
+});
2. CVE-2026-111767: InMemoryTaskStore ignored _sessionId
In @modelcontextprotocol/sdk 1.24.0 through 1.31.0, every method on InMemoryTaskStore (createTask, getTask, getTaskResult, updateTaskStatus, listTasks) received an optional _sessionId?: string parameter from the protocol layer and discarded it. When an HTTP MCP server passed a single InMemoryTaskStore to McpServer or Server, any client could call tasks/list to enumerate every active task on the server, tasks/result to read another client’s task output or queued messages, or tasks/cancel to abort another user’s job.
Commit 727075b5bb (PR #2925) stores sessionId on each StoredTask and gates every lookup and queue delivery on session equality:
--- a/src/experimental/tasks/stores/in-memory.ts
+++ b/src/experimental/tasks/stores/in-memory.ts
@@ -39,7 +45,18 @@ export class InMemoryTaskStore implements TaskStore {
- async createTask(taskParams: CreateTaskOptions, requestId: RequestId, request: Request, _sessionId?: string): Promise<Task> {
+ private lookup(taskId: string, sessionId?: string): StoredTask | undefined {
+ const stored = this.tasks.get(taskId);
+ if (stored && sessionId !== undefined && stored.sessionId !== undefined && stored.sessionId !== sessionId) {
+ return undefined;
+ }
+ return stored;
+ }
+
+ async createTask(taskParams: CreateTaskOptions, requestId: RequestId, request: Request, sessionId?: string): Promise<Task> {
...
- async getTask(taskId: string, _sessionId?: string): Promise<Task | null> {
- const stored = this.tasks.get(taskId);
+ async getTask(taskId: string, sessionId?: string): Promise<Task | null> {
+ const stored = this.lookup(taskId, sessionId);
return stored ? { ...stored.task } : null;
}
3. CVE-2026-111768 and CVE-2026-111771: opt-in fixes for heap OOM and bearer audience checks
Two advisories in the batch require explicit configuration after upgrading.
For tool argument element amplification (CVE-2026-111768), even after GHSA-f897-62hf-6gxq capped HTTP request bodies at 4 MiB and JSON-RPC batches at 100 messages in 1.30.1 and 2.1.0, a single 4 MiB JSON body containing hundreds of thousands of tiny array elements ([0,0,0,...]) or empty object keys ({"a":0,"b":0,...}) causes Zod schema validation to allocate enough per-element error and parse state to crash Node.js with JavaScript heap out of memory. Commit 6d8dbc6cb5 adds toolInputElementCount() before tool.inputSchema runs, but leaves maxToolInputElements undefined by default:
if (
this._maxToolInputElements !== undefined &&
toolInputElementCount(args, this._maxToolInputElements) > this._maxToolInputElements
) {
throw new ProtocolError(
ProtocolErrorCode.InvalidParams,
`Invalid arguments for tool ${toolName}: arguments contain more than the maximum of ${this._maxToolInputElements} elements`
);
}
For bearer token audience confusion (CVE-2026-111771), when an MCP server shares an OAuth authorization server with other internal APIs, requireBearerAuth() did not check that the token’s audience (aud or RFC 8707 resource indicator) matched the MCP server. In 1.32.0 and 2.3.0, the SDK adds expectedResource to requireBearerAuth(), also off by default. Enabling expectedResource without updating your verifyAccessToken() callback to populate AuthInfo.resource causes every request to fail with 401.
The fifth CVE covers cross-origin redirect credential leaks (CVE-2026-111769). In @modelcontextprotocol/sdk <= 1.31.0 and @modelcontextprotocol/client 2.0.0..2.2.0, StreamableHTTPClientTransport, SSEClientTransport, and OAuth auth() followed 301, 302, 307, and 308 redirects across origins, forwarding custom headers (X-API-Key, mcp-session-id) and, on 307 and 308, the POST body containing refresh_token and client_secret. Versions 1.32.0 and 2.3.0 restrict redirects to the same origin by default.
IOCs and runtime signatures
No in-the-wild exploitation campaign is tied to these five CVEs, so there are no external C2 domains or malware hashes. When reviewing logs and codebases, look for these runtime and configuration signatures:
- Process crashes with
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memoryon HTTP requests under 4 MiB (CVE-2026-111768) SdkError: Use a separate Server instance per connection: this instance is already connected to a transport(ALREADY_CONNECTED) orStateless transport cannot be reused across requestsafter upgrading to@modelcontextprotocol/server >= 2.3.0(CVE-2026-111770)- Outbound
301,302,307, or308redirect responses in MCP client logs pointing to unexpected third-party origins, or code settingredirectPolicy: 'follow'(CVE-2026-111769)
Detection and remediation
1. Find vulnerable package versions and shared-instance code patterns
Audit both your lockfiles and your server initialization code, because npm audit will not flag the shared-instance pattern or missing maxToolInputElements / expectedResource options:
# Check installed MCP TypeScript packages
npm ls @modelcontextprotocol/sdk @modelcontextprotocol/server @modelcontextprotocol/client @modelcontextprotocol/express @modelcontextprotocol/node @modelcontextprotocol/hono @modelcontextprotocol/server-legacy
# Check for experimental TaskStore usage (CVE-2026-111767)
rg -n -i 'taskstore|InMemoryTaskStore' --glob '!node_modules/**' .
# Find module-scoped McpServer or StreamableHTTPServerTransport instances (CVE-2026-111770)
rg -n 'new (McpServer|Server|WebStandardStreamableHTTPServerTransport|NodeStreamableHTTPServerTransport|StreamableHTTPServerTransport)\s*\(' \
--glob '!node_modules/**' .
# Check whether maxToolInputElements and expectedResource are configured (CVE-2026-111768, CVE-2026-111771)
rg -n 'maxToolInputElements|maxRequestBodySize|expectedResource|requireBearerAuth' \
--glob '!node_modules/**' .
2. Upgrade packages and update server construction and options
- Upgrade all MCP packages:
# v1.x SDK users npm install @modelcontextprotocol/sdk@^1.32.1 # v2.x modular package users npm install @modelcontextprotocol/server@^2.3.1 @modelcontextprotocol/client@^2.3.1 \ @modelcontextprotocol/express@^2.0.2 @modelcontextprotocol/node@^2.1.1 @modelcontextprotocol/hono@^2.0.2 - Move
McpServerand stateless transport creation inside the request handler (CVE-2026-111770) and setmaxToolInputElements(CVE-2026-111768):import { McpServer, WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/server'; app.all('/mcp', async (c) => { const server = new McpServer( { name: 'my-server', version: '1.0.0' }, { maxToolInputElements: 10_000 } ); registerTools(server); const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined, maxRequestBodySize: 256 * 1024, }); await server.connect(transport); return transport.handleRequest(c.req.raw); }); - Wire both halves of
expectedResourceinrequireBearerAuth()(CVE-2026-111771):requireBearerAuth({ expectedResource: new URL('https://mcp.example.com/mcp'), verifier: { async verifyAccessToken(token) { const claims = await verifyJwt(token); return { token, clientId: claims.client_id, scopes: claims.scope.split(' '), resource: new URL(claims.aud), }; }, }, }); - Rotate credentials if an MCP client connected to untrusted servers (
CVE-2026-111769): If an application usingStreamableHTTPClientTransport,SSEClientTransport, orauth()connected to an external MCP server that could have redirected across origins before1.32.0/2.3.0, rotate any custom header API keys, OAuthclient_secretvalues, and refresh tokens that client held.
Timeline
- 2026-02-04:
@modelcontextprotocol/sdk1.26.0fixes the original shared-instance cross-client leak (CVE-2026-25536/GHSA-345p-7cg4-v4c7) on the1.xline. - 2026-07-27:
@modelcontextprotocol/server2.0.0is published to npm without the shared-instance guard, and the v2 middleware READMEs document module-scoped server reuse. - 2026-09-23:
@modelcontextprotocol/sdk1.30.1and@modelcontextprotocol/server2.1.0add the 4 MiB body limit and 100-message batch cap (GHSA-f897-62hf-6gxq). - 2026-10-02:
@modelcontextprotocol/sdk1.32.0and@modelcontextprotocol/server2.3.0ship on npm with commits84804c22e4,727075b5bb, and6d8dbc6cb5. - 2026-10-20 (18:26 to 20:07 UTC): Five advisories (
CVE-2026-111767throughCVE-2026-111771) are published to the global GitHub Advisory Database and NVD. - 2026-10-21: Independent analysis documents the 18-day gap between the silent 2 October release and the 20 October CVE assignments, as well as the opt-in default posture of
maxToolInputElementsandexpectedResource.
From research to remediation
Check whether this pattern exists in your codebase
Turn this research into a remediation workflow. Scan dependencies and package manifests for similar supply-chain risk, then prioritize fixes with reachability context.
References
- MCP SDK advisory GHSA-22jm-h49p-29qw (CVE-2026-111767: experimental tasks not tied to session)
- MCP SDK advisory GHSA-jm63-cq47-j5jp (CVE-2026-111770: shared transport or server cross-client leak)
- MCP SDK advisory GHSA-46jr-4wx8-v453 (CVE-2026-111768: tool input validation heap OOM)
- MCP SDK advisory GHSA-rvq5-wwqv-78pq (CVE-2026-111771: bearer token audience confusion)
- MCP SDK advisory GHSA-6prh-2h8m-c8cw (CVE-2026-111769: cross-origin redirect credential leak)
- MCP SDK advisory GHSA-f897-62hf-6gxq (unbounded HTTP body and JSON-RPC batch size)
- modelcontextprotocol/typescript-sdk commit 84804c22e4 (refuse second connect and stateless transport reuse)
- modelcontextprotocol/typescript-sdk commit 727075b5bb (scope InMemoryTaskStore to session)
- modelcontextprotocol/typescript-sdk commit 6d8dbc6cb5 (add maxToolInputElements option)
- al-ice.ai: Five MCP SDK CVEs and a cross-client leak the 1.x line already fixed