scopes renamed in mcp 2.3.0
In our sandbox runs up to 2026-10-27, claude-sonnet-5-5 wrote scopes=SCOPE, for mcp. It fails on 2.3.0 with TypeError (changed between 1.26.0 and 2.3.0). Use scope=SCOPE, instead.
- What to do
- replace the line
- Output
- same on one example
- Patch
- applies word for word
- Auto-apply
- allowed
- Seen in
- 1 model
- Worked / failed
- 2 / 0
- Last verified
- 2026-10-27
What to do
Replace the old line with the new one; the rest of the code can stay.
Automatic application: allowed — proof.equivalent is true and the patch applies word for word. Automatic application is allowed only when proof.equivalent is true and proof.applicability.status is applies; otherwise show the correction to the user as a lead and do not apply it on your own. The change itself is described by kind (renamed); the checks are in proof; neither follows from the other.
How to tell this applies to your code
- mcp version 2.3.0 or newer is installed (checked on 2.3.0; changed between 1.26.0 and 2.3.0).
- Your code uses:
scopes - Or it already failed with:
TypeError: ClientCredentialsOAuthProvider.__init__() got an unexpected keyword argument 'scopes'(every correction for this error)
Old and new code
scopes=SCOPE,scope=SCOPE,
Error on 2.3.0:
TypeError: ClientCredentialsOAuthProvider.__init__() got an unexpected keyword argument 'scopes'
| Script | mcp version | Result |
|---|---|---|
| Code the models wrote | 1.26.0 | works |
| Code the models wrote | 2.3.0 | fails: TypeError |
| Verified example below | 2.3.0 | works |
| Output of the verified example | 2.3.0 | same as the original's on 1.26.0 (observed stdout of one example; not a proof of equivalent behaviour) |
| Patch applied word for word to the example | 2.3.0 | applied word for word, the patch parses, runs and prints what the verified example prints |
Verified example for mcp 2.3.0
from mcp.client.auth import OAuthClientProvider, TokenStorage
from mcp.shared.auth import OAuthClientInformationFull, OAuthToken
SERVER_URL = "http://localhost:8000/mcp"
CLIENT_ID = "svc-reporter"
CLIENT_SECRET = "s3cret"
SCOPE = "read"
class InMemoryTokenStorage(TokenStorage):
"""Simple in-memory token and client-info storage."""
def __init__(self) -> None:
self._tokens: OAuthToken | None = None
self._client_info: OAuthClientInformationFull | None = None
async def get_tokens(self) -> OAuthToken | None:
return self._tokens
async def set_tokens(self, tokens: OAuthToken) -> None:
self._tokens = tokens
async def get_client_info(self) -> OAuthClientInformationFull | None:
return self._client_info
async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
self._client_info = client_info
def build_provider(storage: InMemoryTokenStorage) -> OAuthClientProvider:
try:
# Newer mcp versions ship a dedicated client-credentials provider.
from mcp.client.auth.extensions.client_credentials import (
ClientCredentialsOAuthProvider,
)
return ClientCredentialsOAuthProvider(
server_url=SERVER_URL,
storage=storage,
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
token_endpoint_auth_method="client_secret_basic",
scope=SCOPE,
)
except ImportError:
# Fallback for older versions: configure the base provider manually.
from mcp.shared.auth import OAuthClientMetadata
metadata = OAuthClientMetadata(
client_name="svc-reporter",
redirect_uris=["http://localhost:8000/unused-callback"],
grant_types=["client_credentials"],
response_types=["code"],
token_endpoint_auth_method="client_secret_basic",
scope=SCOPE,
)
async def redirect_handler(url: str) -> None:
raise RuntimeError("Interactive redirect is not used with client credentials")
async def callback_handler() -> tuple[str, str | None]:
raise RuntimeError("Interactive callback is not used with client credentials")
# Pre-register the client info so no dynamic registration is needed.
import asyncio
asyncio.run(
storage.set_client_info(
OAuthClientInformationFull(
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
redirect_uris=metadata.redirect_uris,
grant_types=["client_credentials"],
response_types=["code"],
token_endpoint_auth_method="client_secret_basic",
scope=SCOPE,
)
)
)
return OAuthClientProvider(
server_url=SERVER_URL,
client_metadata=metadata,
storage=storage,
redirect_handler=redirect_handler,
callback_handler=callback_handler,
)
def resolve_client_id(provider: OAuthClientProvider, storage: InMemoryTokenStorage) -> str | None:
# OAuthClientMetadata itself has no client_id field; it lives in the client info.
for info in (
getattr(provider, "_fixed_client_info", None),
getattr(provider.context, "client_info", None),
storage._client_info,
):
if info is not None and getattr(info, "client_id", None):
return info.client_id
return None
def main() -> None:
storage = InMemoryTokenStorage()
provider = build_provider(storage) # No network calls are made here.
metadata = provider.context.client_metadata
print(f"Server URL: {provider.context.server_url}")
print(f"Scope: {metadata.scope}")
print(f"Client ID: {resolve_client_id(provider, storage)}")
print(f"Grant types: {metadata.grant_types}")
if __name__ == "__main__":
main()Source
The client-credentials providers take `scope=`, not `scopes=` (#3166).
Release notes · Seen in: claude-sonnet-5-5 · First seen 2026-10-20 · Last verified 2026-10-27 · All corrections for mcp
For agents
- This page as JSON:
https://formachines.ai/errata/pypi/mcp/0003-scopes/index.json - Whole database in plain text:
/errata/llms-full.txt - Install the skill:
npx skills add formachines-ai/skills@model-errata(source on GitHub) - Confirm, dispute or add a correction: feedback form and API
- Apply a correction on your own only when its
proof.auto_applyis true (Automatic application is allowed only when proof.equivalent is true and proof.applicability.status is applies; otherwise show the correction to the user as a lead and do not apply it on your own.)