{
 "action": "Replace the old line with the new one; the rest of the code can stay.",
 "alternatives": [],
 "applicability": {
  "exit_code": 0,
  "line": 43,
  "method": "literal_patch",
  "occurrences": 1,
  "output": "same",
  "patched_sha": "38a1a66bb2d3",
  "reason": "applied word for word, the patch parses, drops the old names, runs on 2.3.0 and prints what the verified example prints",
  "run_on": "2.3.0",
  "status": "applies"
 },
 "by_notes": true,
 "changelog_ref": {
  "quote": "The client-credentials providers take `scope=`, not `scopes=` (#3166).",
  "url": "https://github.com/modelcontextprotocol/python-sdk/releases/tag/v1.28.0"
 },
 "confirmations": {
  "failed": 0,
  "worked": 2
 },
 "detect": [
  "scopes"
 ],
 "ecosystem": "pypi",
 "equivalence": "same",
 "error_kind": "unexpected_kwarg",
 "error_signature": {
  "message": "ClientCredentialsOAuthProvider.__init__() got an unexpected keyword argument 'scopes'",
  "norm": "unexpected_kwarg:scopes",
  "type": "TypeError"
 },
 "first_seen": "2026-10-20T16:11:13+00:00",
 "format": "replace",
 "format_version": 2,
 "id": "pypi/mcp/2.3.0/0003",
 "kind": "renamed",
 "license": "https://creativecommons.org/licenses/by/4.0/",
 "package": "mcp",
 "proof": {
  "applicability": {
   "exit_code": 0,
   "line": 43,
   "method": "literal_patch",
   "occurrences": 1,
   "output": "same",
   "patched_sha": "38a1a66bb2d3",
   "reason": "applied word for word, the patch parses, drops the old names, runs on 2.3.0 and prints what the verified example prints",
   "run_on": "2.3.0",
   "status": "applies"
  },
  "auto_apply": true,
  "auto_apply_reason": "proof.equivalent is true and the patch applies word for word",
  "auto_apply_rule": "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.",
  "equivalence": "same",
  "equivalent": true,
  "fails_on": "2.3.0",
  "fixed_on": "2.3.0",
  "level": "differential",
  "method": {
   "fix_on": "2.3.0",
   "name": "stdout_match",
   "normalization": "lines stripped, blank lines dropped, runs of whitespace collapsed; memory addresses, timestamps and temporary paths masked",
   "original_on": "1.26.0",
   "scope": "the observable stdout of one example script; return values, files, other inputs and exception types are not compared, so a match is not a proof of equivalent behaviour"
  },
  "script_right": "from mcp.client.auth import OAuthClientProvider, TokenStorage\nfrom mcp.shared.auth import OAuthClientInformationFull, OAuthToken\n\nSERVER_URL = \"http://localhost:8000/mcp\"\nCLIENT_ID = \"svc-reporter\"\nCLIENT_SECRET = \"s3cret\"\nSCOPE = \"read\"\n\n\nclass InMemoryTokenStorage(TokenStorage):\n    \"\"\"Simple in-memory token and client-info storage.\"\"\"\n\n    def __init__(self) -> None:\n        self._tokens: OAuthToken | None = None\n        self._client_info: OAuthClientInformationFull | None = None\n\n    async def get_tokens(self) -> OAuthToken | None:\n        return self._tokens\n\n    async def set_tokens(self, tokens: OAuthToken) -> None:\n        self._tokens = tokens\n\n    async def get_client_info(self) -> OAuthClientInformationFull | None:\n        return self._client_info\n\n    async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:\n        self._client_info = client_info\n\n\ndef build_provider(storage: InMemoryTokenStorage) -> OAuthClientProvider:\n    try:\n        # Newer mcp versions ship a dedicated client-credentials provider.\n        from mcp.client.auth.extensions.client_credentials import (\n            ClientCredentialsOAuthProvider,\n        )\n\n        return ClientCredentialsOAuthProvider(\n            server_url=SERVER_URL,\n            storage=storage,\n            client_id=CLIENT_ID,\n            client_secret=CLIENT_SECRET,\n            token_endpoint_auth_method=\"client_secret_basic\",\n            scope=SCOPE,\n        )\n    except ImportError:\n        # Fallback for older versions: configure the base provider manually.\n        from mcp.shared.auth import OAuthClientMetadata\n\n        metadata = OAuthClientMetadata(\n            client_name=\"svc-reporter\",\n            redirect_uris=[\"http://localhost:8000/unused-callback\"],\n            grant_types=[\"client_credentials\"],\n            response_types=[\"code\"],\n            token_endpoint_auth_method=\"client_secret_basic\",\n            scope=SCOPE,\n        )\n\n        async def redirect_handler(url: str) -> None:\n            raise RuntimeError(\"Interactive redirect is not used with client credentials\")\n\n        async def callback_handler() -> tuple[str, str | None]:\n            raise RuntimeError(\"Interactive callback is not used with client credentials\")\n\n        # Pre-register the client info so no dynamic registration is needed.\n        import asyncio\n\n        asyncio.run(\n            storage.set_client_info(\n                OAuthClientInformationFull(\n                    client_id=CLIENT_ID,\n                    client_secret=CLIENT_SECRET,\n                    redirect_uris=metadata.redirect_uris,\n                    grant_types=[\"client_credentials\"],\n                    response_types=[\"code\"],\n                    token_endpoint_auth_method=\"client_secret_basic\",\n                    scope=SCOPE,\n                )\n            )\n        )\n        return OAuthClientProvider(\n            server_url=SERVER_URL,\n            client_metadata=metadata,\n            storage=storage,\n            redirect_handler=redirect_handler,\n            callback_handler=callback_handler,\n        )\n\n\ndef resolve_client_id(provider: OAuthClientProvider, storage: InMemoryTokenStorage) -> str | None:\n    # OAuthClientMetadata itself has no client_id field; it lives in the client info.\n    for info in (\n        getattr(provider, \"_fixed_client_info\", None),\n        getattr(provider.context, \"client_info\", None),\n        storage._client_info,\n    ):\n        if info is not None and getattr(info, \"client_id\", None):\n            return info.client_id\n    return None\n\n\ndef main() -> None:\n    storage = InMemoryTokenStorage()\n    provider = build_provider(storage)  # No network calls are made here.\n\n    metadata = provider.context.client_metadata\n    print(f\"Server URL: {provider.context.server_url}\")\n    print(f\"Scope:      {metadata.scope}\")\n    print(f\"Client ID:  {resolve_client_id(provider, storage)}\")\n    print(f\"Grant types: {metadata.grant_types}\")\n\n\nif __name__ == \"__main__\":\n    main()",
  "script_wrong": "from mcp.client.auth import OAuthClientProvider, TokenStorage\nfrom mcp.shared.auth import OAuthClientInformationFull, OAuthToken\n\nSERVER_URL = \"http://localhost:8000/mcp\"\nCLIENT_ID = \"svc-reporter\"\nCLIENT_SECRET = \"s3cret\"\nSCOPE = \"read\"\n\n\nclass InMemoryTokenStorage(TokenStorage):\n    \"\"\"Simple in-memory token and client-info storage.\"\"\"\n\n    def __init__(self) -> None:\n        self._tokens: OAuthToken | None = None\n        self._client_info: OAuthClientInformationFull | None = None\n\n    async def get_tokens(self) -> OAuthToken | None:\n        return self._tokens\n\n    async def set_tokens(self, tokens: OAuthToken) -> None:\n        self._tokens = tokens\n\n    async def get_client_info(self) -> OAuthClientInformationFull | None:\n        return self._client_info\n\n    async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:\n        self._client_info = client_info\n\n\ndef build_provider(storage: InMemoryTokenStorage) -> OAuthClientProvider:\n    try:\n        # Newer mcp versions ship a dedicated client-credentials provider.\n        from mcp.client.auth.extensions.client_credentials import (\n            ClientCredentialsOAuthProvider,\n        )\n\n        return ClientCredentialsOAuthProvider(\n            server_url=SERVER_URL,\n            storage=storage,\n            client_id=CLIENT_ID,\n            client_secret=CLIENT_SECRET,\n            token_endpoint_auth_method=\"client_secret_basic\",\n            scopes=SCOPE,\n        )\n    except ImportError:\n        # Fallback for older versions: configure the base provider manually.\n        from mcp.shared.auth import OAuthClientMetadata\n\n        metadata = OAuthClientMetadata(\n            client_name=\"svc-reporter\",\n            redirect_uris=[\"http://localhost:8000/unused-callback\"],\n            grant_types=[\"client_credentials\"],\n            response_types=[\"code\"],\n            token_endpoint_auth_method=\"client_secret_basic\",\n            scope=SCOPE,\n        )\n\n        async def redirect_handler(url: str) -> None:\n            raise RuntimeError(\"Interactive redirect is not used with client credentials\")\n\n        async def callback_handler() -> tuple[str, str | None]:\n            raise RuntimeError(\"Interactive callback is not used with client credentials\")\n\n        # Pre-register the client info so no dynamic registration is needed.\n        import asyncio\n\n        asyncio.run(\n            storage.set_client_info(\n                OAuthClientInformationFull(\n                    client_id=CLIENT_ID,\n                    client_secret=CLIENT_SECRET,\n                    redirect_uris=metadata.redirect_uris,\n                    grant_types=[\"client_credentials\"],\n                    response_types=[\"code\"],\n                    token_endpoint_auth_method=\"client_secret_basic\",\n                    scope=SCOPE,\n                )\n            )\n        )\n        return OAuthClientProvider(\n            server_url=SERVER_URL,\n            client_metadata=metadata,\n            storage=storage,\n            redirect_handler=redirect_handler,\n            callback_handler=callback_handler,\n        )\n\n\ndef resolve_client_id(provider: OAuthClientProvider, storage: InMemoryTokenStorage) -> str | None:\n    # OAuthClientMetadata itself has no client_id field; it lives in the client info.\n    for info in (\n        getattr(provider, \"_fixed_client_info\", None),\n        getattr(provider.context, \"client_info\", None),\n        storage._client_info,\n    ):\n        if info is not None and getattr(info, \"client_id\", None):\n            return info.client_id\n    return None\n\n\ndef main() -> None:\n    storage = InMemoryTokenStorage()\n    provider = build_provider(storage)  # No network calls are made here.\n\n    metadata = provider.context.client_metadata\n    print(f\"Server URL: {provider.context.server_url}\")\n    print(f\"Scope:      {metadata.scope}\")\n    print(f\"Client ID:  {resolve_client_id(provider, storage)}\")\n    print(f\"Grant types: {metadata.grant_types}\")\n\n\nif __name__ == \"__main__\":\n    main()",
  "works_on": "1.26.0"
 },
 "right": "scope=SCOPE,",
 "seen_in_models": [
  "claude-sonnet-5-5"
 ],
 "seq": 3,
 "slug": "scopes",
 "source": "pipeline",
 "symbol": "scopes",
 "updated": "2026-10-27T17:20:00+00:00",
 "url": "https://formachines.ai/errata/pypi/mcp/0003-scopes/",
 "variant_confirmations": 2,
 "variant_models": [
  "claude-sonnet-5-5"
 ],
 "verified": "sandbox",
 "versions": {
  "changed_in": ">1.26.0,<=2.3.0",
  "fails_on": "2.3.0",
  "works_on": "1.26.0"
 },
 "wrong": "scopes=SCOPE,"
}