Skip to content

auth

OAuthToken

Bases: BaseModel

See https://datatracker.ietf.org/doc/html/rfc6749#section-5.1

Source code in src/mcp/shared/auth.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class OAuthToken(BaseModel):
    """See https://datatracker.ietf.org/doc/html/rfc6749#section-5.1"""

    access_token: str
    token_type: Literal["Bearer"] = "Bearer"
    expires_in: int | None = None
    scope: str | None = None
    refresh_token: str | None = None

    @field_validator("token_type", mode="before")
    @classmethod
    def normalize_token_type(cls, v: str | None) -> str | None:
        if isinstance(v, str):
            # Bearer is title-cased in the spec, so we normalize it
            # https://datatracker.ietf.org/doc/html/rfc6750#section-4
            return v.title()
        return v  # pragma: no cover

AuthorizationCodeResult

Bases: BaseModel

Authorization-code-grant redirect parameters returned by a callback handler.

iss carries the RFC 9207 authorization-response issuer when the authorization server includes it in the redirect; the client validates it against the expected issuer.

Source code in src/mcp/shared/auth.py
28
29
30
31
32
33
34
35
36
37
class AuthorizationCodeResult(BaseModel):
    """Authorization-code-grant redirect parameters returned by a callback handler.

    `iss` carries the RFC 9207 authorization-response issuer when the authorization server
    includes it in the redirect; the client validates it against the expected issuer.
    """

    code: str
    state: str | None = None
    iss: str | None = None

OAuthClientMetadata

Bases: BaseModel

RFC 7591 OAuth 2.0 Dynamic Client Registration Metadata. See https://datatracker.ietf.org/doc/html/rfc7591#section-2

Source code in src/mcp/shared/auth.py
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
class OAuthClientMetadata(BaseModel):
    """RFC 7591 OAuth 2.0 Dynamic Client Registration Metadata.
    See https://datatracker.ietf.org/doc/html/rfc7591#section-2
    """

    model_config = ConfigDict(url_preserve_empty_path=True)

    redirect_uris: list[AnyUrl] | None = Field(..., min_length=1)
    # supported auth methods for the token endpoint
    token_endpoint_auth_method: (
        Literal["none", "client_secret_post", "client_secret_basic", "private_key_jwt"] | None
    ) = None
    # supported grant_types of this implementation
    grant_types: list[
        Literal["authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:jwt-bearer"] | str
    ] = [
        "authorization_code",
        "refresh_token",
    ]
    # The MCP spec requires the "code" response type, but OAuth
    # servers may also return additional types they support
    response_types: list[str] = ["code"]
    scope: str | None = None
    # SEP-837: OIDC application_type. Defaults to "native" since MCP clients typically use
    # loopback redirect URIs; set "web" for remote browser-based clients on a non-local host.
    application_type: Literal["web", "native"] = "native"

    # these fields are currently unused, but we support & store them for potential
    # future use
    client_name: str | None = None
    client_uri: AnyHttpUrl | None = None
    logo_uri: AnyHttpUrl | None = None
    contacts: list[str] | None = None
    tos_uri: AnyHttpUrl | None = None
    policy_uri: AnyHttpUrl | None = None
    jwks_uri: AnyHttpUrl | None = None
    jwks: Any | None = None
    software_id: str | None = None
    software_version: str | None = None

    @field_validator(
        "client_uri",
        "logo_uri",
        "tos_uri",
        "policy_uri",
        "jwks_uri",
        mode="before",
    )
    @classmethod
    def _empty_string_optional_url_to_none(cls, v: object) -> object:
        # RFC 7591 §2 marks these URL fields OPTIONAL. Some authorization servers
        # echo omitted metadata back as "" instead of dropping the keys, which
        # AnyHttpUrl would otherwise reject — throwing away an otherwise valid
        # registration response. Treat "" as absent.
        if v == "":
            return None
        return v

    def validate_scope(self, requested_scope: str | None) -> list[str] | None:
        if requested_scope is None:
            return None
        requested_scopes = requested_scope.split(" ")
        allowed_scopes = [] if self.scope is None else self.scope.split(" ")
        for scope in requested_scopes:
            if scope not in allowed_scopes:
                raise InvalidScopeError(f"Client was not registered with scope {scope}")
        return requested_scopes

    def validate_redirect_uri(self, redirect_uri: AnyUrl | None) -> AnyUrl:
        if redirect_uri is not None:
            # Validate redirect_uri against client's registered redirect URIs
            if self.redirect_uris is None or redirect_uri not in self.redirect_uris:
                raise InvalidRedirectUriError(f"Redirect URI '{redirect_uri}' not registered for client")
            return redirect_uri
        elif self.redirect_uris is not None and len(self.redirect_uris) == 1:
            return self.redirect_uris[0]
        else:
            raise InvalidRedirectUriError("redirect_uri must be specified when client has multiple registered URIs")

OAuthClientInformationFull

Bases: OAuthClientMetadata

RFC 7591 OAuth 2.0 Dynamic Client Registration full response (client information plus metadata).

Source code in src/mcp/shared/auth.py
130
131
132
133
134
135
136
137
138
139
140
141
class OAuthClientInformationFull(OAuthClientMetadata):
    """RFC 7591 OAuth 2.0 Dynamic Client Registration full response
    (client information plus metadata).
    """

    client_id: str | None = None
    client_secret: str | None = None
    client_id_issued_at: int | None = None
    client_secret_expires_at: int | None = None
    # SEP-2352: the issuer these credentials were registered with, recorded by the SDK (not an
    # RFC 7591 field) to detect authorization-server migration and avoid cross-AS credential reuse.
    issuer: str | None = None

OAuthMetadata

Bases: BaseModel

RFC 8414 OAuth 2.0 Authorization Server Metadata. See https://datatracker.ietf.org/doc/html/rfc8414#section-2

Source code in src/mcp/shared/auth.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
class OAuthMetadata(BaseModel):
    """RFC 8414 OAuth 2.0 Authorization Server Metadata.
    See https://datatracker.ietf.org/doc/html/rfc8414#section-2
    """

    model_config = ConfigDict(url_preserve_empty_path=True)

    issuer: AnyHttpUrl
    authorization_endpoint: AnyHttpUrl
    token_endpoint: AnyHttpUrl
    registration_endpoint: AnyHttpUrl | None = None
    scopes_supported: list[str] | None = None
    response_types_supported: list[str] = ["code"]
    response_modes_supported: list[str] | None = None
    grant_types_supported: list[str] | None = None
    token_endpoint_auth_methods_supported: list[str] | None = None
    token_endpoint_auth_signing_alg_values_supported: list[str] | None = None
    service_documentation: AnyHttpUrl | None = None
    ui_locales_supported: list[str] | None = None
    op_policy_uri: AnyHttpUrl | None = None
    op_tos_uri: AnyHttpUrl | None = None
    revocation_endpoint: AnyHttpUrl | None = None
    revocation_endpoint_auth_methods_supported: list[str] | None = None
    revocation_endpoint_auth_signing_alg_values_supported: list[str] | None = None
    introspection_endpoint: AnyHttpUrl | None = None
    introspection_endpoint_auth_methods_supported: list[str] | None = None
    introspection_endpoint_auth_signing_alg_values_supported: list[str] | None = None
    code_challenge_methods_supported: list[str] | None = None
    client_id_metadata_document_supported: bool | None = None
    authorization_response_iss_parameter_supported: bool | None = None
    # SEP-990 / draft-ietf-oauth-identity-assertion-authz-grant §7.2: profiles whose grants the
    # authorization server supports, e.g. `urn:ietf:params:oauth:grant-profile:id-jag`.
    authorization_grant_profiles_supported: list[str] | None = None

ProtectedResourceMetadata

Bases: BaseModel

RFC 9728 OAuth 2.0 Protected Resource Metadata. See https://datatracker.ietf.org/doc/html/rfc9728#section-2

Source code in src/mcp/shared/auth.py
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
class ProtectedResourceMetadata(BaseModel):
    """RFC 9728 OAuth 2.0 Protected Resource Metadata.
    See https://datatracker.ietf.org/doc/html/rfc9728#section-2
    """

    model_config = ConfigDict(url_preserve_empty_path=True)

    resource: AnyHttpUrl
    authorization_servers: list[AnyHttpUrl] = Field(..., min_length=1)
    jwks_uri: AnyHttpUrl | None = None
    scopes_supported: list[str] | None = None
    bearer_methods_supported: list[str] | None = Field(default=["header"])  # MCP only supports header method
    resource_signing_alg_values_supported: list[str] | None = None
    resource_name: str | None = None
    resource_documentation: AnyHttpUrl | None = None
    resource_policy_uri: AnyHttpUrl | None = None
    resource_tos_uri: AnyHttpUrl | None = None
    # tls_client_certificate_bound_access_tokens default is False, but omitted here for clarity
    tls_client_certificate_bound_access_tokens: bool | None = None
    authorization_details_types_supported: list[str] | None = None
    dpop_signing_alg_values_supported: list[str] | None = None
    # dpop_bound_access_tokens_required default is False, but omitted here for clarity
    dpop_bound_access_tokens_required: bool | None = None