SevyDevy

Guide · AI and GenAI

MCP Python SDK's Sept-Oct 2026 Advisories Explained: What Upgrading Misses

26 min readUpdated By Saket Bhatnagar

Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.

In short

  • The MCP Python SDK vulnerability news is six GitHub advisories published between 28 September and 5 October 2026 for the mcp package. Upgrade to mcp 1.30.0 or 2.2.0 or later.
  • Four fixes work as soon as you upgrade. Idle sessions expire, requests are capped at 4 MiB, the client stops following redirects to other sites, and it stops fetching outside links found in tool schemas.
  • Two fixes need a line of code each. A client that logs in with a secret must name its own login server with issuer=. A server must check that each token was issued for it, with validate_token_resource=True.
  • Without that second check, your server accepts a genuine token that was issued for a different service, such as billing.
  • Beginners can stop after the advisory table. Engineers get toy tests of each fix on old and fixed versions, and the trap that 1.30.0 hides its warnings by default.

What goes wrong when you only upgrade the MCP Python SDK?

You close four of the six bugs, but two credential bugs stay open. The fixes for them are opt-in, so the code that passed review last month still behaves the old way after pip install.

The MCP Python SDK vulnerability advisories cover the official mcp package that many Python MCP servers and clients are built on. MCP, the Model Context Protocol, is the open standard that lets an AI app call tools and read data from separate servers. This guide is about hardening that SDK.

Picture a common setup. A company runs an internal notes MCP server over HTTP, protected by tokens from its own login server. The same login server also issues tokens for a billing service. The setup is made up, but each bug in this guide is real.

The company also runs an agent service, a program that calls tools on several MCP servers for its users. Some of those servers belong to partners. The agent signs in to its own company's login server with a client ID and secret, a flow called client credentials.

After the team upgrades to mcp 2.2.0, the notes server still accepts a billing token. The agent can still hand its secret to whatever login server a partner's MCP server names. Both need one or two lines of code that the upgrade did not write for them.

Which words do you need before reading the advisories?

You need seven terms. Each one is used in at least one advisory.

  • Streamable HTTP: the MCP transport that carries messages over ordinary HTTP POST and GET requests. The other transports are stdio, which pipes messages between two local processes, and the older SSE transport.
  • Session: in stateful mode, the server gives each client a session ID after the first message, called initialize, and keeps state for it in memory.
  • Authorization server: the login server that issues OAuth access tokens. Its issuer is the URL that names it, and it publishes metadata, a JSON file listing its endpoints.
  • Protected resource metadata: a JSON file the MCP server publishes to say which authorization server its tokens come from.
  • Audience: the service a token was issued for. A token for billing is not meant for the notes server.
  • Origin: the scheme, host and port of a URL together. http://127.0.0.1:8000 and http://127.0.0.1:8001 are different origins.
  • JSON Schema $ref: a pointer inside a schema to another schema. It can point inside the same document, like #/$defs/item, or to an outside URL.

Two labels appear on every advisory. A GHSA ID is GitHub's advisory number. CVSS is a 0 to 10 severity score, and versions 3.1 and 4.0 of the scoring system can give the same bug different numbers.

If MCP itself is new to you, the SevyDevy guide to MCP for developers covers the basics. Attacks on the protocol, such as tool poisoning, are in the MCP security guide.

What are the MCP Python SDK vulnerability advisories of Sept-Oct 2026?

They are six separate bugs, three in the client side of the SDK and three in the server side. Three are rated High at 7.5 on the repository's advisory pages, and three are Moderate.

Most of them let a server you do not fully trust, or anyone who can reach your endpoint, turn the SDK's old defaults against you. The redirect bug is different: it needs one of your trusted endpoints to redirect elsewhere. The table lists them in the order this guide explains them.

The six advisories, as listed on the python-sdk security page on 23 October 2026
Advisory (plain name and ID)What went wrongSideSeverity (repo page)Fixed inUpgrade enough?
Secret sent to the wrong login server (GHSA-qx49-fqc8-xw99)The OAuth client could send credentials to an authorization server chosen by the MCP serverClientHigh, 7.51.30.0, 2.2.0No for client-credentials providers: pass issuer=
Token for another service accepted (GHSA-w4fh-qvv9-3v23)Bearer authentication accepted tokens issued for a different serviceServerModerate, 6.81.30.0, 2.2.0No: turn on validate_token_resource or check aud yourself
Sessions never cleaned up (GHSA-84m7-p3x7-pcfv, CVE-2026-59951)Streamable HTTP sessions were never reclaimed and kept their initialize dataServerHigh, 7.51.30.0, 2.2.0Yes, defaults are on
No limit on request size (GHSA-fmmv-w9g8-j3gc)SSE and OAuth endpoints read request bodies with no size limitServerHigh, 7.51.29.1, 2.1.0Yes, defaults are on
Redirects followed to other hosts (GHSA-5h93-6whr-6q8j)HTTP clients followed redirects to other origins with custom headers and bodiesClientModerate, 5.91.30.0, 2.2.0Yes, but endpoints that redirect to another host stop working
Schema links fetched from outside (GHSA-rwrf-2pqf-9j8j)The client fetched $ref URLs named in a server's tool output schemaClientModerate, 6.51.30.0, 2.2.0Yes, defaults are on

One score needs a note. The repository page rates the session bug, GHSA-84m7, High at 7.5 with CVSS 3.1. The GitHub Advisory Database entry for the same bug shows Moderate at 6.9 with CVSS 4.0, as of 23 October 2026.

The diagram places each bug on the running example. Client-side bugs sit on the agent's arrows, and server-side bugs sit on the notes server.

Where the six advisories sit in one company's MCP setup

Notice that the agent's secret should only ever travel along one arrow, to your own login server. The redirect leak starts at an endpoint you trust, not at the partner.

Diagram connections:

  • Agent service to Partner MCP server (Calls partner tools)
  • Partner MCP server to Agent service (Names a login server (qx49))
  • Agent service to Company login server (Secret should go only here)
  • Agent service to Internal notes MCP server (Normal tool calls)
  • Anyone who reaches notes to Internal notes MCP server (Floods sessions, bodies)
  • Company login server to Billing service (Issues billing tokens)
  • Billing service to Internal notes MCP server (Billing token accepted (w4fh))
  • Partner MCP server to Another origin (Schema links fetched (rwrf))
  • Agent service to Configured endpoint URL (Sends custom headers)
  • Configured endpoint URL to Another origin (Redirect leaks them (5h93))
  • Agent service: SDK client; holds the client secret
  • Partner MCP server: Not fully trusted; sends metadata and schemas
  • Company login server: Issues tokens for notes and billing
  • Internal notes MCP server: SDK server: sessions, bodies, bearer auth
  • Billing service: Shares the same login server
  • Another origin: Redirect target or schema link
  • Configured endpoint URL: Trusted, but now redirects elsewhere
  • Anyone who reaches notes: Needs no login for some bugs

Beginners can stop here. You know the six bugs, which four the upgrade fixes, and which two still need a line of code. The rest is for engineers: how each bug worked, our toy tests, and the exact settings to change.

How could an MCP server steer your OAuth credentials elsewhere?

Before the fix, the MCP server told the client which authorization server to use, and the client did not check that answer properly. A malicious or hacked server could name an address it controls and collect the client's secret.

OAuth discovery works in two hops. The MCP server answers 401 and points to its protected resource metadata. That file names an authorization server, and the client then fetches that server's metadata to find the token endpoint.

RFC 8414 says the issuer inside that metadata must match the server the client meant to ask. The advisory says older SDK versions skipped this check on some or all paths, depending on the version. They also did not tie saved client registrations to the authorization server that issued them.

Depending on the provider, the attacker could receive the client secret, an authorization code, the PKCE code verifier, or a signed client assertion. PKCE is the one-time proof that stops a stolen authorization code from being used alone.

What 2.2.0 checks, and what it cannot know

The fixed client works out the expected issuer before it fetches metadata. It refuses metadata with a different issuer by raising OAuthFlowError, and it registers again if a saved registration belongs to another issuer.

For the client-credentials providers there is a catch. Without issuer=, the MCP server still decides which login server is expected. It can name one in its resource metadata, or publish no resource metadata and serve matching login metadata at its own address. Only you know which login server your secret belongs to, so you must say so with issuer=.

The issuer check in mcp 2.2.0 before any secret is sent

The check only protects you if the expected issuer is right. Without issuer=, a client-credentials provider learns it from the MCP server it is talking to, so a lying server passes its own test.

Diagram connections:

  • MCP server answers 401 to Resource metadata (Fetch metadata)
  • Resource metadata to Login server metadata (Fetch named server)
  • Login server metadata to Issuer as expected? (Compare issuer)
  • Issuer as expected? to Stop: OAuthFlowError (No)
  • Issuer as expected? to Token request with secret (Yes)
  • MCP server answers 401: MCP server → client
  • Resource metadata: MCP server → client: names an auth server
  • Login server metadata: Login server → client: claims an issuer
  • Issuer as expected?: issuer= if set, else what the MCP server points to
  • Stop: OAuthFlowError: No token request is made
  • Token request with secret: Client → token endpoint

We tested this with our own toy MCP server on 127.0.0.1 and a fake secret. The toy server answered 401 and pointed the client at its own token endpoint.

  • mcp 2.1.1: the toy server's token endpoint received the client's credentials in every scenario.
  • mcp 2.2.0, no resource metadata, metadata claiming a different issuer: the client stopped with OAuthFlowError. That is the case the fix targets, but an attacker can avoid it by publishing matching metadata, as the next bullet shows.
  • mcp 2.2.0, 2.3.0 and 1.30.0, no resource metadata, matching metadata at the toy server's own address, no issuer=: the toy server received the credentials.
  • mcp 2.2.0, resource metadata naming the toy server, no issuer=: the toy server still received the credentials.
  • mcp 2.2.0, 2.3.0 and 1.30.0 with issuer= set to the real login server: the client stopped with an issuer mismatch and sent nothing in every scenario.

What just happened? The fixed client did check the issuer, but it learned the expected answer from the server it was checking. A lying server passed its own test. Only issuer= gave the client an answer the server could not change.

Here is the client-credentials provider with the new argument. It runs on 1.30.0, 2.2.0 and 2.3.0 without any warning.

python
import os

from mcp.client.auth import TokenStorage
from mcp.client.auth.extensions.client_credentials import ClientCredentialsOAuthProvider


class MemoryTokenStorage(TokenStorage):
    def __init__(self):
        self.tokens = None
        self.client_info = None

    async def get_tokens(self):
        return self.tokens

    async def set_tokens(self, tokens):
        self.tokens = tokens

    async def get_client_info(self):
        return self.client_info

    async def set_client_info(self, client_info):
        self.client_info = client_info


provider = ClientCredentialsOAuthProvider(
    server_url="https://tools.partner.example/mcp",
    storage=MemoryTokenStorage(),
    client_id="agent-service",
    client_secret=os.environ.get("MCP_CLIENT_SECRET", "set-me"),
    issuer="https://login.yourcompany.example",  # the only server allowed to get this secret
)

The same issuer= argument exists on PrivateKeyJWTOAuthProvider. The deprecated 1.x RFC7523OAuthClientProvider has no such option, so the advisory says to move to one of the two providers above.

Why did servers accept tokens meant for another service?

The SDK's bearer authentication checked that a token was valid and unexpired, but it never compared the token's audience with the server's own address. A token issued by your login server for billing passed the notes server's check too.

Think of a cinema ticket. The ticket is genuine and dated today, but it is for screen 2, and you are at the door of screen 5. A careless usher checks only that the ticket is real.

In a JWT, a common signed token format, the audience sits in the aud claim. RFC 8707 adds a resource request parameter, called a resource indicator, so a client can ask for a token for one service.

Where the picture breaks: a ticket shows its screen number in print. A token's audience sits inside it, and your TokenVerifier must read it out and report it to the SDK.

Pause and predict: you upgrade to 2.2.0 and change nothing else. The notes server then gets three tokens: one for notes, one for billing and one with no audience. Which does it refuse? Hold your guess until the toy results below.

The fix adds validate_token_resource to AuthSettings. It is off unless you turn it on. When resource_server_url is set and you leave the option out, 2.2.0 warns that tokens are not being checked.

The advisory lists two changes that must ship together. Set validate_token_resource=True along with resource_server_url. Then make verify_token() return the token's audience in AccessToken.resource. With only the first change, every request gets a 401.

python
import jwt
from mcp.server.auth.provider import AccessToken
from mcp.server.auth.settings import AuthSettings
from mcp.server.mcpserver import MCPServer

MCP_URL = "https://mcp.example.com/mcp"
SIGNING_KEY = "demo-only-key-use-your-auth-servers-keys"


class AudienceCheckingVerifier:
    async def verify_token(self, token: str) -> AccessToken | None:
        try:
            claims = jwt.decode(token, SIGNING_KEY, algorithms=["HS256"], audience=MCP_URL)
        except jwt.InvalidTokenError:
            return None  # bad signature, expired, or issued for another audience
        return AccessToken(
            token=token,
            client_id=claims["client_id"],
            scopes=claims.get("scope", "").split(),
            expires_at=claims.get("exp"),
            resource=MCP_URL,  # safe only because jwt.decode checked aud above
        )


server = MCPServer(
    "notes",
    token_verifier=AudienceCheckingVerifier(),
    auth=AuthSettings(
        issuer_url="https://auth.example.com",
        resource_server_url=MCP_URL,
        validate_token_resource=True,
    ),
)

This sample is for the 2.x line. On 1.30.0, import FastMCP from mcp.server.fastmcp and use it where the sample says MCPServer. We ran it on 2.2.0, 2.3.0 and, with that swap, 1.30.0, using tokens we signed ourselves. A token whose aud named the notes server got HTTP 200. A token for billing got 401. A token listing both audiences got 200.

The sample checks the audience twice: once in jwt.decode and once in the SDK middleware. Real servers verify the login server's public keys instead of a shared demo key. If your verifier already checks aud, set validate_token_resource=False to silence the warning.

What our toy server returned on each version

Here is what our toy notes server did with those three made-up tokens.

  • 1.29.1 and 2.1.1: all three tokens got HTTP 200. These versions have no validate_token_resource setting.
  • 1.30.0 and 2.2.0 with the option unset: all three got 200, and the SDK raised its warning.
  • 1.30.0 and 2.2.0 with validate_token_resource=True: our token got 200, and the billing and no-audience tokens got 401.

On 1.29.1 and 2.1.1, AuthSettings quietly ignores validate_token_resource=True, with no error and no warning. Confirm the version with pip show mcp before you rely on it. On 1.30.0 you may also never see the new warning; the defaults section shows why.

How did old MCP sessions pile up in server memory?

In stateful mode, the default, older servers never threw a session away. A session that went quiet stayed in memory, a DELETE left it registered, and the number of sessions had no limit.

The advisory says each session also kept its parsed initialize data. Anyone who could reach the endpoint could keep opening sessions until the server ran out of memory, and only a restart freed it. Login does not help much here, because one valid token can open any number of sessions.

Versions 1.30.0 and 2.2.0 change three things, and all three are on by default. A session with nothing in flight for 1800 seconds, 30 minutes, is closed. At most 10,000 sessions exist at once. A session is discarded as soon as it ends.

The cap is one counter shared by every client, not a limit per user or token. Once it is full, new sessions get 503. At the defaults, that can come too late. In our toy test on 2.2.0, each session opened with a 3.5 MB initialize kept about 7 MiB. So 10,000 such sessions would need far more memory than most hosts have. Lower max_sessions to fit your memory, keep rate limits or a gateway in front of public endpoints, or use stateless mode where you can.

A stateful session's life in mcp 2.2.0

Every path now ends in the same place: the session is removed and its ID answers 404. Before the fix, the DELETE and refused-request paths left the session in memory.

Diagram connections:

  • Client sends initialize to Under max_sessions? (Admission check)
  • Under max_sessions? to HTTP 503 (No)
  • Under max_sessions? to Session is live (Yes)
  • Session is live to Session ends (Delete or go idle)
  • Session ends to Removed; ID answers 404 (Discard at once)
  • Client sends initialize: Client → MCP server
  • Under max_sessions?: Default 10000
  • HTTP 503: New session refused; old ones keep working
  • Session is live: Open streams keep it alive
  • Session ends: DELETE, 1800 s idle, or refused first request
  • Removed; ID answers 404: Client must initialize again

We opened four sessions on a toy server, deleted one, and sent one request that was not initialize. On 2.1.1 the server still tracked 4 sessions after the DELETE and 5 after the refused request. On 2.2.0 it tracked 3, then 3.

With max_sessions=3 and session_idle_timeout=2 on 2.2.0, the fourth initialize got HTTP 503. After 3 seconds of silence, a request on an old session got 404, and the server tracked 0 sessions.

You set these values in different places on each line. In 1.x they are arguments to FastMCP(...). In 2.x they go to run(), streamable_http_app() or StreamableHTTPSessionManager, not to the MCPServer constructor. This sample runs on 2.2.0 and 2.3.0. On 1.30.0 the same values go in FastMCP("notes", session_idle_timeout=600, max_sessions=500, max_request_body_size=1_048_576).

python
from mcp.server.mcpserver import MCPServer

server = MCPServer("notes")
app = server.streamable_http_app(
    session_idle_timeout=600,       # seconds; default 1800, None turns expiry off
    max_sessions=500,               # default 10000, None removes the cap
    max_request_body_size=1_048_576,  # bytes; default 4 MiB
)

The fix limits how much memory sessions can hold, but it does not remove the cost. The advisory says a live session still holds its initialize data, up to about twice the 4 MiB body limit. On small hosts, the advisory suggests lowering all three values.

If you do not need per-session state, stateless_http=True avoids sessions completely. It needs 1.12.0 or later, and the advisory lists it as the workaround for older versions.

Why did the 4 MiB request body limit take two releases?

The first fix covered only one door. Versions 1.29.0 and 2.0.0 added a 4 MiB limit to the Streamable HTTP endpoint. The SSE message endpoint and the OAuth routes still read bodies of any size into memory.

Versions 1.29.1 and 2.1.0 put every HTTP endpoint the SDK provides behind the same 4 MiB default limit, which is 4,194,304 bytes. The OAuth routes are /token, /register, /revoke and /authorize. A request over the limit gets HTTP 413 before any parsing.

The advisory says one large request could grow memory to two or three times the body's size. No login is needed against the OAuth endpoints, or against servers without authentication, which is the default.

We sent one 50 MiB registration request to the SDK's own OAuth routes on a toy server. On 2.0.1 the request succeeded with 201, and the process's peak memory rose from about 70 MiB to about 270 to 300 MiB across runs. On 2.1.1 and 2.2.0 it got 413, and memory did not move.

Watch the version numbers. Version 2.0.1 came out on 26 August 2026, two days after 2.1.0, and it does not carry this fix. The advisory says there is no fixed 2.0.x release.

To accept single messages larger than 4 MiB, pass max_request_body_size. That setting changes the transport endpoints only; the OAuth routes keep their fixed 4 MiB limit. Code that drives StreamableHTTPServerTransport directly, without the session manager, does not get the limit and must cap bodies itself.

What changed for redirects and output schema references?

Both client fixes stop the SDK from reaching beyond the server you configured. Redirects to another origin are no longer followed, and schema references are no longer fetched.

Cross-origin redirects now stop with an error

Older Streamable HTTP and SSE clients followed a redirect to any host. Custom headers such as an X-API-Key went along, and on 307 and 308 redirects the request body was sent again.

The advisory notes that a plain Authorization bearer header was not exposed, because the HTTP library already drops it across origins. An attacker needs a trusted endpoint to redirect elsewhere, for example through a lapsed domain or a bad proxy rule.

Our toy endpoint answered 307 and pointed to another port on 127.0.0.1. On 2.1.1 the second server received the POST with the made-up X-API-Key and the 163-byte body. On 2.2.0 it received nothing, and the client raised MCPError naming the redirect target. On 1.30.0 the advisory says the error is httpx.HTTPStatusError instead.

Version 2.2.0 still follows a redirect that stays on the same origin, or an http to https upgrade on the same host. If an endpoint you use moved to another host, change the configured URL to the new address.

Output schema references stay inside the schema

An MCP tool can declare an outputSchema, a JSON Schema for its structured result. The client checks each result against it, and older versions let jsonschema fetch any $ref it could not resolve locally.

A server could put an http, https or file URL in that schema. The client then fetched the URL or read the local file on every call, from inside your network. The fetch had no timeout and blocked the event loop, so a slow target could freeze every session.

In our toy test, the tool's schema pointed at a small web server we ran locally. On 2.1.1 the client fetched it. On 2.2.0 the call failed with RuntimeError, Invalid schema for tool, and the web server saw no request. A reference to #/$defs worked on both versions.

Which new MCP SDK protections are off or warn-only by default?

Two are off and only warn: the issuer binding for client-credentials providers and the token audience check. Everything else is on as soon as you upgrade. The table shows each setting and its default on 2.2.0.

Defaults after upgrading to mcp 2.2.0, read from the source and checked in our toy tests (same on 2.3.0)
ProtectionSettingDefaultWhat you do
Idle session expirysession_idle_timeoutOn, 1800 secondsNothing, or lower it on small hosts
Session capmax_sessionsOn, 10,000 sessionsNothing, or lower it on small hosts
Request body limitmax_request_body_sizeOn, 4 MiBRaise it only for larger single messages; OAuth routes stay at 4 MiB
Same-origin redirects onlyNo settingOnUpdate endpoint URLs that redirect to another host
$ref stays in the schemaNo settingOnNothing
Issuer check in OAuthClientProviderNo settingOnClear stored client information once
Issuer binding for client credentialsissuer=Off, warnsPass your login server's issuer URL
Token audience checkvalidate_token_resourceOff, warnsSet True and fill AccessToken.resource, or False if your verifier checks aud

The warnings behave differently on the two lines. On 2.x they use MCPDeprecationWarning, which is a UserWarning, so Python prints it by default. On 1.30.0 they use the built-in DeprecationWarning.

Python hides a DeprecationWarning unless the code that triggered it sits in the main script. In our test, the same two calls placed in an imported module printed nothing on 1.30.0. Running with python -W default::DeprecationWarning, or with PYTHONWARNINGS=default::DeprecationWarning set in the environment, showed both warnings.

How do you check your version and apply the fixes?

Check the installed version, upgrade within your major line, then make the two code changes and clean up. The steps below take a working service through it.

  1. Find your version

    Run pip show mcp in the service's environment. Look for Version: 1.30.0 or later on the 1.x line, or 2.2.0 or later on 2.x. Version 2.0.1 is not fixed.

  2. Upgrade and pin

    Pin at least 1.30.0 on the 1.x line or 2.2.0 on the 2.x line in your requirements. As of 23 October 2026, the newest release is 2.3.0, from 2 October.

  3. Surface the warnings

    Start the service once with python -W default::DeprecationWarning, or set PYTHONWARNINGS=default::DeprecationWarning if uvicorn or another launcher starts it. On 1.30.0 this shows warnings that are otherwise hidden. On 2.x, look for MCPDeprecationWarning lines in normal output.

  4. Bind client credentials

    Pass issuer= to every ClientCredentialsOAuthProvider and PrivateKeyJWTOAuthProvider. Use your login server's exact issuer URL, as it appears in its metadata.

  5. Check token audience

    On servers with resource_server_url, set validate_token_resource=True and return the audience in AccessToken.resource. Or set it to False only if your verifier already checks aud.

  6. Clean up credentials

    Clear stored OAuth client information once so clients register again. Rotate secrets and revoke tokens for any client that may have connected to an untrusted MCP server.

  7. Test the new limits

    Send a body just over 4 MiB to a test server and expect HTTP 413. Leave a test session idle past your timeout and expect 404 on its next request.

How do you explain MCP Python SDK hardening in a system design interview?

Use these advisories as a checklist for any service that talks to parties it does not fully trust. Walk the interviewer through it in this order.

  1. Draw the trust boundaries

    Mark which servers you control and which you do not. A partner MCP server can send you metadata, schemas and redirects, so treat all three as input.

  2. Bound every resource

    Cap body size, session count and idle time, and say what a shared cap costs honest clients during a flood. Say what the server returns when a limit is hit: 413 for bodies, 503 for the session cap, 404 for expired sessions.

  3. Bind credentials to issuers

    A secret belongs to one login server. The client must know that server in advance and refuse metadata that names any other.

  4. Check the audience

    A valid token is not enough. The resource server must confirm the token was issued for it, using the aud claim or RFC 8707 resource indicators.

  5. Stop outbound surprises

    Do not follow redirects across origins and do not fetch URLs named in untrusted data. Both turn your client into a request-sending tool for someone else.

  6. Roll out safely

    Pin the fixed version, turn on the opt-in checks, watch the logs for 401, 404 and 503 changes, and rotate any credential that may have leaked.

For depth, mention the trade-off in session expiry. A short idle timeout frees memory sooner, but clients that pause for long without a stream open must reconnect more often.

Questions people ask

Is upgrading to mcp 2.2.0 enough to fix all six advisories?
No. Upgrading fixes four of them on its own. For the secret sent to the wrong login server (GHSA-qx49), pass issuer= to the client-credentials providers. For the token accepted from another service (GHSA-w4fh), turn on validate_token_resource or check the aud claim in your verifier. The advisories state both points directly, and our toy tests on 2.2.0 confirmed them.
Am I affected if my MCP server only uses the stdio transport?
Mostly no. The advisories say stdio-only servers are not affected by the session and body size bugs, and stdio clients are not affected by the OAuth and redirect bugs. The output schema bug is different: it affects any SDK client that calls tools on servers you do not fully control, over any transport.
Why does the session bug, GHSA-84m7, show High in one place and Moderate elsewhere?
The two pages use different scoring systems. The repository advisory scores it 7.5, High, with CVSS 3.1. The GitHub Advisory Database entry for CVE-2026-59951 shows 6.9, Moderate, with CVSS 4.0. Both describe the same bug and the same fixed versions, 1.30.0 and 2.2.0, as of 23 October 2026.
Will my clients break after the upgrade?
Some can, in visible ways. A client that idles for 30 minutes with no open stream gets 404 and must initialize again. An endpoint that redirects to another host now fails with an error. A tool whose output schema points outside itself fails with RuntimeError. A client facing a mismatched issuer stops with OAuthFlowError.
Should I install 2.3.0 rather than 2.2.0?
Either fixes the six advisories, and the newest release in your line is the usual choice. As of 23 October 2026, 2.3.0 is the latest, released on 2 October. We compared its source with 2.2.0 and found the session, audience, issuer and redirect defaults unchanged.
What should teams still on the 1.x line do?
Upgrade to 1.30.0, which carries the same fixes as 2.2.0. Session settings go on FastMCP(...) instead of the app call. Its new warnings use DeprecationWarning, which Python hides when the call is in an imported module, so run once with PYTHONWARNINGS=default::DeprecationWarning set to see them.
What will change in version 3.0 of the SDK?
The advisories say 3.0 will require issuer= on the client-credentials providers. They also say validate_token_resource will default to True whenever resource_server_url is set. As of 23 October 2026, 3.0 is not released, so setting both options now means nothing changes for you on that upgrade.

Sources and further reading

Practise this with Lala

Practise