On October 27, 2026, GitHub added three advisories for the MCP Python SDK (the mcp package on PyPI) to its Advisory Database, and one of them is not fixed by running pip install --upgrade. If your team runs an MCP server or an agent client on this SDK, you have a version check to do and a config change to make. The version check takes a minute. The config change needs a decision from whoever owns your authorization server.
All three are rated Moderate. All three are fixed in mcp 1.30.0 (1.x line) and 2.2.0 (2.x line), which went out on September 7 according to the release notes and the PyPI upload times. The repository advisories carry earlier dates than the database listing (September 28, October 2 and October 5), so the fixes shipped before the advisories were public. PyPI currently shows 2.3.0 as the latest release. We could not find any statement in the advisories about exploitation in the wild, and we are not claiming any.
What each advisory covers
CVE-2026-116684 (CVSS 6.8) is about the server side. The SDK’s bearer authentication could not check the audience of a token. If your authorization server issues tokens for several services, a valid token meant for a different service was accepted by your MCP server. It affects mcp 1.10.0 through 1.29.1 and 2.0.0 through 2.1.1, and it matters only if you use auth=AuthSettings(...) with a token_verifier that does not check the aud claim itself.
CVE-2026-116685 (CVSS 5.9) is about the client side. The Streamable HTTP and SSE client transports followed redirects to any host. If the endpoint you configured redirected to another origin, custom headers such as X-API-Key went along, and on 307 and 308 responses the request body was sent again too. The advisory says a lone Authorization bearer header was not exposed, because the underlying HTTP library already drops it on cross-origin redirects. The SDK’s OAuth providers had the same behavior on discovery, registration and token requests. Affected: 1.8.0 through 1.29.1 and 2.0.0 through 2.1.1.
CVE-2026-116686 (CVSS 6.5) is also client side. When a server declared an outputSchema for a tool, the client validated each call_tool result with the jsonschema default resolver, which fetches any $ref it cannot resolve locally. A server you connect to could therefore make your client request an http://, https:// or file:// URL on every call. The fetched content was used only as a schema and never sent back to the server. The fetch also ran synchronously on the event loop with no timeout, so a target that accepted the connection and went silent could freeze every session in the process. Affected: 1.10.0 through 1.29.1 and 2.0.0 through 2.1.1.
The release notes list these changes as behavior changes (same-origin redirects, a new validate_token_resource option, $ref resolution limited to the schema document). They have no security heading, so a team that skims release notes for the word “security” would not have flagged the upgrade.
The audience check is off until you turn it on
This is the one that needs your attention. The patched releases add AuthSettings.validate_token_resource, and it defaults to off. The advisory says upgrading alone does not fix the issue, and that the default will flip to True in version 3.0. Turning it on takes two changes made together: set validate_token_resource=True with resource_server_url set to the audience your authorization server puts in tokens for your server, and return that audience from verify_token() as AccessToken.resource. The advisory is explicit that doing only the first change gives you a 401 on every request.
Here is how we would wire it. This example is ours, written against the 1.x API shape the advisory describes, and it uses PyJWT with a JWKS endpoint. Check the import paths against the version you pin.
import jwt # PyJWT
from jwt import PyJWKClient
from mcp.server.auth.provider import AccessToken
from mcp.server.auth.settings import AuthSettings
from mcp.server.fastmcp import FastMCP
ISSUER = "https://auth.example.com"
AUDIENCE = "https://mcp.example.com" # what your IdP puts in aud for THIS server
jwks = PyJWKClient(f"{ISSUER}/.well-known/jwks.json")
class JwtVerifier:
async def verify_token(self, token: str) -> AccessToken | None:
try:
key = jwks.get_signing_key_from_jwt(token).key
claims = jwt.decode(
token,
key,
algorithms=["RS256"],
audience=AUDIENCE, # rejects tokens minted for other services
issuer=ISSUER,
options={"require": ["exp", "aud", "iss"]},
)
except jwt.PyJWTError:
return None
return AccessToken(
token=token,
client_id=claims.get("azp", claims["sub"]),
scopes=claims.get("scope", "").split(),
expires_at=claims["exp"],
resource=AUDIENCE, # the SDK compares this to resource_server_url
)
mcp = FastMCP(
"orders",
token_verifier=JwtVerifier(),
auth=AuthSettings(
issuer_url=ISSUER,
resource_server_url=AUDIENCE,
validate_token_resource=True,
),
)
Passing audience= to jwt.decode is the workaround the advisory itself gives for versions you cannot upgrade: reject any token whose aud does not include your server inside verify_token(). It closes the hole on every affected version. If your verifier already did this before the advisory, set validate_token_resource=False explicitly to silence the deprecation warning, and write down why.
The deployment shape to worry about is one authorization server in front of many internal services, each with its own scopes. A token minted for the billing service should not open the MCP server that can write to your CRM, and before this fix the SDK would not stop it unless your verifier did.
Redirects: what leaked, and what may now break
If your agent talks to an MCP endpoint behind a load balancer, a vanity domain or a migration redirect, the redirect advisory applies to you. Before the fix, a redirect to another origin carried your X-API-Key with it. After the fix, a cross-origin redirect fails with an error: httpx.HTTPStatusError in 1.30.0, MCPError in 2.2.0, where the session stays usable. Any client that quietly depended on a cross-host redirect will stop connecting after the upgrade. The advisory’s answer is to set the endpoint URL to the redirect target, and that is the right fix. It is also a change you want to find in staging, so grep your client configs for URLs that answer with a 301, 302, 307 or 308.
# list MCP endpoints that redirect (our script; feed it your own URL list)
while read -r url; do
code=$(curl -s -o /dev/null -w '%{http_code} %{redirect_url}' "$url")
echo "$url -> $code"
done < mcp-endpoints.txt | grep -E ' 30[1278] '
If you cannot upgrade yet, the advisory’s stopgap is to pass your own HTTP client with follow_redirects left at False. With the 1.x client transport that looks like this (ours, and it assumes the httpx_client_factory hook on streamablehttp_client):
import httpx
from mcp.client.streamable_http import streamablehttp_client
def no_redirects(headers=None, timeout=None, auth=None) -> httpx.AsyncClient:
return httpx.AsyncClient(
headers=headers,
timeout=timeout or httpx.Timeout(30.0),
auth=auth,
follow_redirects=False,
)
async def connect(url: str, api_key: str):
return streamablehttp_client(
url,
headers={"X-API-Key": api_key},
httpx_client_factory=no_redirects,
)
The advisory adds one more instruction that people skip. If a client may already have followed a redirect to a host you do not control, rotate the credentials it carried. If your API keys travel in custom headers, that means every key held by an agent whose endpoint ever redirected off-origin.
Tool results that make your client fetch URLs
The third advisory only affects you if you use the SDK as a client against servers you do not fully control, which describes most agent platforms that let users add their own MCP servers. The patched client resolves $ref only inside the schema the server sent, plus the standard JSON Schema metaschemas, so validation touches no network and no files. A tool whose output schema points outside its own document now fails at call time with RuntimeError: Invalid schema for tool .... In-document references such as #/$defs/Order keep working.
The advisory’s partial stopgap is to skip any tool whose outputSchema contains an $id, or a $ref or $dynamicRef that does not start with #. That filter is short enough to run on every list_tools response:
def schema_is_local(node) -> bool:
"""True if every $ref/$dynamicRef is in-document and no $id is present."""
if isinstance(node, dict):
if "$id" in node:
return False
for key in ("$ref", "$dynamicRef"):
ref = node.get(key)
if isinstance(ref, str) and not ref.startswith("#"):
return False
return all(schema_is_local(v) for v in node.values())
if isinstance(node, list):
return all(schema_is_local(v) for v in node)
return True
async def safe_tools(session):
result = await session.list_tools()
return [t for t in result.tools if t.outputSchema is None or schema_is_local(t.outputSchema)]
A server with a hostile schema can still be a bad neighbor in other ways, so this does not make an untrusted server safe. It closes the one path the advisory describes.
Tradeoffs and what we could not verify
The 1.30.0 release notes call it a maintenance release and say 2.x is the current line. If you are still on 1.x and plan to move to 2.x, do that as its own change, since we have not tested your code against 2.x. We have not run any of the examples above against a live authorization server, so treat them as a starting point to adapt and test. And we only read the advisories and release notes, not the diffs, so the behavior described here is the maintainers’ account of the fix.
What to do this week
Run pip show mcp (or uv pip list | grep mcp) in every service image and every agent runtime, and pin mcp>=1.30.0,<2 or mcp>=2.2.0 in your constraints file. For each MCP server that uses AuthSettings, ask your identity owner what aud your tokens carry for that server, then ship the two-part change above in staging and confirm that a token minted for a different service now gets a 401. For each client, run the redirect check against your endpoint list, update any URL that redirects, and rotate any key that was sent in a custom header to an endpoint that has ever redirected off-origin. Finally, add a line to your dependency review that treats release notes mentioning new auth options, redirect policy or schema resolution as security-relevant even when the word is missing.