{"id":"CVE-2026-115563","kind":"vulnerability","aliases":["CVE-2026-115563","GHSA-qx49-fqc8-xw99"],"related":[],"title":"OAuth client could send credentials to an authorization server chosen by the MCP server","description":"### Summary\nIn affected versions, the SDK's OAuth client support (`mcp.client.auth`) let the MCP server a client connected to decide where the client's OAuth credentials were sent:\n\n- the authorization server metadata `issuer` was not validated on every discovery path, and\n- stored or pre-provisioned client credentials were not bound to the authorization server they belong to.\n\nA malicious or compromised MCP server could therefore direct them to a token endpoint of its choosing:\n\n- by naming its own authorization server in its protected resource metadata, or\n- by publishing none and serving metadata that presents the user's real authorization server as the `issuer`.\n\nIt would then receive the `client_secret`, authorization code and PKCE `code_verifier` meant for that real server (or, with `PrivateKeyJWTOAuthProvider`, a signed client assertion).\n\n### Am I affected?\nYes, if both of these hold:\n\n- your application uses the SDK as an MCP client over HTTP with `OAuthClientProvider`, `ClientCredentialsOAuthProvider`, `PrivateKeyJWTOAuthProvider` or the deprecated 1.x `RFC7523OAuthClientProvider` as the transport's `auth` handler;\n- it may connect to an MCP server you do not fully trust while holding credentials for a legitimate authorization server (a pre-provisioned secret or signing key, or a stored client registration).\n\nAffected versions:\n\n- 1.9.1 through 1.29.1: no `issuer` check and no credential binding on any path.\n- 2.0.0 through 2.1.1: both missing whenever the server publishes no protected resource metadata (the legacy fallback) or answers `403 insufficient_scope`.\n- Both lines, on every path: `ClientCredentialsOAuthProvider` and `PrivateKeyJWTOAuthProvider` had no way to name the authorization server their credentials belong to.\n\nNot affected:\n\n- MCP servers built with the SDK\n- stdio clients\n- clients that attach their own tokens or headers\n\nThe rating is scored for the unattended providers. With the interactive `OAuthClientProvider` a person has to start the sign-in; scored that way it is 6.5 (`CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N`).\n\n### Fix\nUpgrade to `mcp` 2.2.0 (2.x) or 1.30.0 (1.x) or later.\n\nIf you use `ClientCredentialsOAuthProvider` or `PrivateKeyJWTOAuthProvider`, upgrading changes nothing until you also pass `issuer=` (for example `issuer=\"https://auth.example.com\"`):\n\n- Without it they still follow whichever authorization server the MCP server advertises.\n- Omitting it now emits a deprecation warning, and it becomes required in 3.0. On 1.30.0 it is a `DeprecationWarning`, which Python hides by default.\n- The deprecated 1.x `RFC7523OAuthClientProvider` has no `issuer` option. Move to one of these two providers.\n\nIn the fixed versions the client also:\n\n- works out which issuer it expects before fetching any authorization server metadata, on every path;\n- refuses metadata whose `issuer` differs, with `OAuthFlowError`;\n- binds stored registrations to that issuer, and discards one bound to a different issuer so the client registers again.\n\nAfter upgrading:\n\n- **Stored registrations:** a registration stored without an issuer stays unbound. That includes everything 1.x stored before 1.30.0. Clear stored OAuth client information once so the client registers again. If you placed a pre-registered client in storage yourself, set `issuer` on that record instead.\n- **Past exposure:** if such a client may have connected to an untrusted MCP server, rotate its client secret and revoke its tokens at the authorization server.\n\nOn earlier versions there is no workaround other than connecting OAuth-enabled clients only to MCP servers you trust.","summary":"The MCP Python SDK's OAuth client did not check the authorization server issuer or bind stored client credentials to it, so a malicious or compromised MCP server could redirect the client's client_secret, authorization code, PKCE verifier or client assertion to a token endpoint it controls. Affected applications may lose the credentials they hold for a legitimate authorization server.","severity":"high","cvss":7.5,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"mcp","fixed":["2.2.0"],"affected":">= 2.0.0a1, < 2.2.0","ecosystem":"pypi"},{"name":"mcp","fixed":["1.30.0"],"affected":">= 1.9.1, < 1.30.0","ecosystem":"pypi"}],"ecosystems":["pypi"],"tags":["cwe-345","cwe-522","mcp","oauth","package","python"],"not_affected_if":["mcp-server-only","own-tokens-or-headers","stdio-transport","trusted-mcp-servers-only"],"how_to_check":"- Run `pip show mcp` (or `uv pip show mcp`) and compare the version against the affected ranges: `1.9.1` through `1.29.1`, and `2.0.0` through `2.1.1`.\n- Look for uses of `OAuthClientProvider`, `ClientCredentialsOAuthProvider`, `PrivateKeyJWTOAuthProvider` or `RFC7523OAuthClientProvider` as the `auth` handler of an HTTP MCP client transport.\n- Check whether such a client connects to MCP servers you do not fully trust while holding a pre-provisioned client secret, signing key, or stored client registration.","fix":"- `mcp` 2.x: upgrade to `2.2.0` or later.\n- `mcp` 1.x: upgrade to `1.30.0` or later.\n- For `ClientCredentialsOAuthProvider` and `PrivateKeyJWTOAuthProvider`, also pass `issuer=` (for example `issuer=\"https://auth.example.com\"`); upgrading alone does not bind credentials to the issuer.\n- `RFC7523OAuthClientProvider` has no `issuer` option; move to one of the two providers above.","status":"open","published_at":"2026-09-28T20:06:03+00:00","first_seen_at":"2026-11-09T22:53:24+00:00","triaged_at":"2026-11-09T22:59:26+00:00","updated_at":"2026-11-09T22:59:26+00:00","category":"package","confidence":0.85,"triage":{"at":"2026-11-09T22:59:26Z","model":"claude-haiku-5-5","output":{"fix":"- `mcp` 2.x: upgrade to `2.2.0` or later.\n- `mcp` 1.x: upgrade to `1.30.0` or later.\n- For `ClientCredentialsOAuthProvider` and `PrivateKeyJWTOAuthProvider`, also pass `issuer=` (for example `issuer=\"https://auth.example.com\"`); upgrading alone does not bind credentials to the issuer.\n- `RFC7523OAuthClientProvider` has no `issuer` option; move to one of the two providers above.","reach":"internet","title":"MCP Python SDK: OAuth client leaks credentials to an attacker-chosen auth server","impact":"high","summary":"The MCP Python SDK's OAuth client did not check the authorization server issuer or bind stored client credentials to it, so a malicious or compromised MCP server could redirect the client's client_secret, authorization code, PKCE verifier or client assertion to a token endpoint it controls. Affected applications may lose the credentials they hold for a legitimate authorization server.","audience":"developers","category":"package","nicknames":[],"who_cares":["python","mcp","oauth"],"confidence":0.85,"fix_status":"available","needs_auth":"unknown","worst_case":"Attacker-controlled server receives client secret, auth code and PKCE verifier","analyst_note":"","how_to_check":"- Run `pip show mcp` (or `uv pip show mcp`) and compare the version against the affected ranges: `1.9.1` through `1.29.1`, and `2.0.0` through `2.1.1`.\n- Look for uses of `OAuthClientProvider`, `ClientCredentialsOAuthProvider`, `PrivateKeyJWTOAuthProvider` or `RFC7523OAuthClientProvider` as the `auth` handler of an HTTP MCP client transport.\n- Check whether such a client connects to MCP servers you do not fully trust while holding a pre-provisioned client secret, signing key, or stored client registration.","plain_action":"","product_name":"","needs_analyst":false,"not_affected_if":["stdio-transport","mcp-server-only","own-tokens-or-headers","trusted-mcp-servers-only"],"who_is_affected":"Applications that use the MCP Python SDK as an HTTP client with an OAuth provider and hold credentials for a real authorization server, and that may connect to an MCP server they do not fully trust. MCP servers built with the SDK, stdio clients, and clients that attach their own tokens or headers are not affected.","user_interaction":"unknown","product_ecosystem":"","needs_special_setup":"yes","severity_if_unknown":"unknown"},"cost_usd":0.001052,"input_tokens":5903,"output_tokens":923,"severity_before":"high"},"analyst_status":null,"analyst_note":null,"search":"'-115563':17A,25A '-2026':16A,24A '-345':29B '-522':31B '/av':398C '1':253C,416C,476C,553C '1.29.1':299C '1.30.0':415C,464C,557C '1.9.1':297C '2':412C '2.0.0':310C '2.1.1':312C '2.2.0':411C '3.0':462C '3.1':397C '403':328C '6.5':395C 'advertis':449C 'affect':84B,99C,228C,295C,352C 'also':431C,496C 'answer':327C 'applic':85B,236C 'assert':77B,225C 'attach':363C 'auth':260C 'auth.example.com':437C 'author':8A,47B,71B,95B,126C,150C,176C,196C,208C,280C,345C,444C,506C,610C 'becom':459C 'belong':153C,349C 'bind':51B,306C,519C 'bound':147C,528C 'built':355C 'chang':427C 'check':45B,302C 'choos':171C 'chosen':10A 'clear':558C 'client':2A,42B,53B,67B,69B,76B,105C,113C,119C,143C,206C,224C,243C,293C,360C,361C,495C,535C,561C,566C,576C,591C,602C,625C 'clientcredentialsoauthprovid':248C,336C,423C 'code':72B,209C,212C 'compromis':61B,158C 'connect':114C,264C,594C,621C 'control':83B 'could':3A,64B,161C 'credenti':5A,54B,89B,122C,144C,276C,305C,348C 'cve':15A,23A 'cvss':396C 'cwe':28B,30B 'decid':116C 'default':473C 'deprec':252C,455C,475C 'deprecationwarn':468C 'differ':516C,531C 'direct':163C 'discard':526C 'discoveri':135C 'earlier':613C 'emit':453C 'enabl':624C 'endpoint':81B,168C 'everi':134C,334C,510C 'everyth':552C 'exampl':435C 'expect':502C 'exposur':587C 'fallback':325C 'fetch':504C 'fix':407C,492C 'follow':442C 'fqc8':21A 'fulli':272C 'ghsa':19A 'ghsa-qx49-fqc8-xw99':18A 'h/i':404C 'handler':261C 'header':368C 'hide':471C 'hold':91B,234C,275C 'http':245C 'includ':551C 'inform':562C 'instead':585C 'insuffici':329C 'interact':379C 'issuer':49B,129C,200C,301C,433C,436C,481C,500C,515C,524C,532C,547C,581C 'key':289C 'l/pr':400C 'later':419C 'legaci':324C 'legitim':94B,279C 'let':108C 'line':332C 'lose':87B 'malici':59B,156C 'may':86B,263C,592C 'mcp':13A,26A,27A,32B,37B,62B,110C,159C,242C,267C,353C,410C,447C,598C,628C 'mcp.client.auth':107C 'meant':214C 'metadata':128C,182C,189C,322C,508C,513C 'miss':314C 'move':483C 'n':406C 'n/a':405C 'n/ac':399C 'n/ui':401C 'name':173C,343C 'none':186C 'noth':428C 'oauth':1A,33B,41B,104C,121C,560C,623C 'oauth-en':622C 'oauthclientprovid':247C,380C 'oauthflowerror':518C 'omit':450C 'one':485C,527C 'option':482C 'packag':34B 'pass':432C 'past':586C 'path':136C,309C,335C,511C 'person':382C 'pkce':73B,211C 'place':571C 'pre':141C,284C,574C 'pre-provis':140C,283C 'pre-regist':573C 'present':191C 'privatekeyjwtoauthprovid':221C,249C,338C,425C 'protect':180C,320C 'provid':376C,489C 'provis':142C,285C 'publish':185C,318C 'python':35B,38B,470C 'qx49':20A 'r/s':402C 'rate':370C 'real':195C,217C 'receiv':204C 'record':584C 'redirect':65B 'refus':512C 'regist':536C,567C,575C 'registr':294C,521C,541C,543C 'requir':460C 'resourc':181C,321C 'revok':605C 'rfc7523oauthclientprovider':255C,478C 'rotat':600C 'scope':330C 'score':372C,390C 'sdk':39B,102C,239C,358C 'secret':70B,207C,286C,603C 'send':4A 'sent':124C 'serv':188C 'server':9A,14A,48B,63B,96B,111C,127C,151C,160C,177C,197C,218C,268C,281C,317C,346C,354C,445C,448C,507C,599C,611C,629C 'set':580C 'sign':223C,288C,388C 'sign-in':387C 'start':385C 'stay':548C 'stdio':359C 'still':441C 'storag':578C 'store':52B,138C,292C,520C,540C,544C,555C,559C 'summari':97C 'support':106C 'therefor':162C 'token':80B,167C,366C,607C 'transport':258C 'trust':273C,631C 'two':488C 'u/c':403C 'unattend':375C 'unbound':549C 'untrust':597C 'upgrad':408C,426C,539C 'use':237C,422C 'user':193C 'valid':132C 'verifi':74B,213C 'version':100C,296C,493C,614C 'warn':456C 'way':341C,392C 'whenev':315C 'whichev':443C 'whose':514C 'without':438C,545C 'work':497C 'workaround':618C 'would':202C 'x':254C,413C,417C,477C,554C 'xw99':22A 'yes':229C","nicknames":[],"mitigations":[],"who_affected":"Applications that use the MCP Python SDK as an HTTP client with an OAuth provider and hold credentials for a real authorization server, and that may connect to an MCP server they do not fully trust. MCP servers built with the SDK, stdio clients, and clients that attach their own tokens or headers are not affected.","tier":"B","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"developers","plain_action":null,"pkeys":["mcp"],"ptokens":["mcp"],"labels":["cwe345","cwe522","high","mcp","oauth","package","pypi","python"],"campaign_type":null,"search_a":"'-115563':17A,25A '-2026':16A,24A 'author':8A 'chosen':10A 'client':2A 'could':3A 'credenti':5A 'cve':15A,23A 'fqc8':21A 'ghsa':19A 'ghsa-qx49-fqc8-xw99':18A 'mcp':13A,26A,27A 'oauth':1A 'qx49':20A 'send':4A 'server':9A,14A 'xw99':22A","search_b":"'-345':29B '-522':31B 'affect':84B 'applic':85B 'assert':77B 'author':47B,71B,95B 'bind':51B 'check':45B 'client':42B,53B,67B,69B,76B 'code':72B 'compromis':61B 'control':83B 'could':64B 'credenti':54B,89B 'cwe':28B,30B 'endpoint':81B 'hold':91B 'issuer':49B 'legitim':94B 'lose':87B 'malici':59B 'may':86B 'mcp':32B,37B,62B 'oauth':33B,41B 'packag':34B 'pkce':73B 'python':35B,38B 'redirect':65B 'sdk':39B 'secret':70B 'server':48B,63B,96B 'store':52B 'token':80B 'verifi':74B","search_c":"'/av':398C '1':253C,416C,476C,553C '1.29.1':299C '1.30.0':415C,464C,557C '1.9.1':297C '2':412C '2.0.0':310C '2.1.1':312C '2.2.0':411C '3.0':462C '3.1':397C '403':328C '6.5':395C 'advertis':449C 'affect':99C,228C,295C,352C 'also':431C,496C 'answer':327C 'applic':236C 'assert':225C 'attach':363C 'auth':260C 'auth.example.com':437C 'author':126C,150C,176C,196C,208C,280C,345C,444C,506C,610C 'becom':459C 'belong':153C,349C 'bind':306C,519C 'bound':147C,528C 'built':355C 'chang':427C 'check':302C 'choos':171C 'clear':558C 'client':105C,113C,119C,143C,206C,224C,243C,293C,360C,361C,495C,535C,561C,566C,576C,591C,602C,625C 'clientcredentialsoauthprovid':248C,336C,423C 'code':209C,212C 'compromis':158C 'connect':114C,264C,594C,621C 'could':161C 'credenti':122C,144C,276C,305C,348C 'cvss':396C 'decid':116C 'default':473C 'deprec':252C,455C,475C 'deprecationwarn':468C 'differ':516C,531C 'direct':163C 'discard':526C 'discoveri':135C 'earlier':613C 'emit':453C 'enabl':624C 'endpoint':168C 'everi':134C,334C,510C 'everyth':552C 'exampl':435C 'expect':502C 'exposur':587C 'fallback':325C 'fetch':504C 'fix':407C,492C 'follow':442C 'fulli':272C 'h/i':404C 'handler':261C 'header':368C 'hide':471C 'hold':234C,275C 'http':245C 'includ':551C 'inform':562C 'instead':585C 'insuffici':329C 'interact':379C 'issuer':129C,200C,301C,433C,436C,481C,500C,515C,524C,532C,547C,581C 'key':289C 'l/pr':400C 'later':419C 'legaci':324C 'legitim':279C 'let':108C 'line':332C 'malici':156C 'may':263C,592C 'mcp':110C,159C,242C,267C,353C,410C,447C,598C,628C 'mcp.client.auth':107C 'meant':214C 'metadata':128C,182C,189C,322C,508C,513C 'miss':314C 'move':483C 'n':406C 'n/a':405C 'n/ac':399C 'n/ui':401C 'name':173C,343C 'none':186C 'noth':428C 'oauth':104C,121C,560C,623C 'oauth-en':622C 'oauthclientprovid':247C,380C 'oauthflowerror':518C 'omit':450C 'one':485C,527C 'option':482C 'pass':432C 'past':586C 'path':136C,309C,335C,511C 'person':382C 'pkce':211C 'place':571C 'pre':141C,284C,574C 'pre-provis':140C,283C 'pre-regist':573C 'present':191C 'privatekeyjwtoauthprovid':221C,249C,338C,425C 'protect':180C,320C 'provid':376C,489C 'provis':142C,285C 'publish':185C,318C 'python':470C 'r/s':402C 'rate':370C 'real':195C,217C 'receiv':204C 'record':584C 'refus':512C 'regist':536C,567C,575C 'registr':294C,521C,541C,543C 'requir':460C 'resourc':181C,321C 'revok':605C 'rfc7523oauthclientprovider':255C,478C 'rotat':600C 'scope':330C 'score':372C,390C 'sdk':102C,239C,358C 'secret':207C,286C,603C 'sent':124C 'serv':188C 'server':111C,127C,151C,160C,177C,197C,218C,268C,281C,317C,346C,354C,445C,448C,507C,599C,611C,629C 'set':580C 'sign':223C,288C,388C 'sign-in':387C 'start':385C 'stay':548C 'stdio':359C 'still':441C 'storag':578C 'store':138C,292C,520C,540C,544C,555C,559C 'summari':97C 'support':106C 'therefor':162C 'token':167C,366C,607C 'transport':258C 'trust':273C,631C 'two':488C 'u/c':403C 'unattend':375C 'unbound':549C 'untrust':597C 'upgrad':408C,426C,539C 'use':237C,422C 'user':193C 'valid':132C 'verifi':213C 'version':100C,296C,493C,614C 'warn':456C 'way':341C,392C 'whenev':315C 'whichev':443C 'whose':514C 'without':438C,545C 'work':497C 'workaround':618C 'would':202C 'x':254C,413C,417C,477C,554C 'yes':229C","cause_entry":null,"cause_path":null,"disclosures_checked_at":null,"first_public_at":"2026-09-28T20:06:03+00:00","kev":null,"cvss_vector":null,"cvss_by":null,"poc_count":0,"sources":[{"source":"ghsa-repo","external_id":"GHSA-qx49-fqc8-xw99","url":"https://github.com/modelcontextprotocol/python-sdk/security/advisories/GHSA-qx49-fqc8-xw99","severity":"high","published_at":"2026-09-28T20:06:03+00:00","modified_at":"2026-10-23T15:40:26+00:00","first_seen_at":"2026-11-09T22:53:24+00:00","fetched_at":"2026-11-09T22:53:24+00:00","note":null}],"documents":[{"id":110612,"url":"https://github.com/modelcontextprotocol/python-sdk/security/advisories/GHSA-qx49-fqc8-xw99","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa-repo","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: github.com"}],"disclosures":[],"exploit_sources":[],"notice":"Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai"}