Describe the ID-JAG confidential-client rule as SDK policy, not a SEP… · modelcontextprotocol/python-sdk@19e4f2a · GitHub
Skip to content

Commit 19e4f2a

Browse files
authored
Describe the ID-JAG confidential-client rule as SDK policy, not a SEP-990 requirement (#3622)
1 parent afa7edc commit 19e4f2a

7 files changed

Lines changed: 19 additions & 13 deletions

File tree

‎docs/client/identity-assertion.md‎

Lines changed: 2 additions & 1 deletion

‎examples/snippets/clients/identity_assertion_client.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
Obtaining the ID-JAG (logging into the IdP and the leg-1 exchange against it) is deployment-specific
99
and out of scope for the SDK; supply it through the `assertion_provider` callback. The callback
1010
receives the authorization server's issuer (the ID-JAG `aud`) and the MCP server's resource
11-
identifier (the ID-JAG `resource` claim). SEP-990 requires a confidential client, so a client secret
11+
identifier (the ID-JAG `resource` claim). The provider requires a confidential client, so a client secret
1212
is mandatory, and `issuer` is the authorization server the credentials are provisioned for - the
1313
provider fetches metadata from that issuer's well-known and never asks the resource server which AS
1414
to use.

‎examples/snippets/servers/identity_assertion_server.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class IdentityAssertionProvider(OAuthAuthorizationServerProvider[AuthorizationCo
5050

5151
def __init__(self) -> None:
5252
self.access_tokens: dict[str, AccessToken] = {}
53-
# SEP-990 clients are pre-registered out of band (DCR refuses the grant) and must be
53+
# ID-JAG clients here are pre-registered out of band (DCR refuses the grant) and must be
5454
# confidential. `get_client` must return them, or the token endpoint 401s before the
5555
# exchange runs. Real deployments load these from their own store.
5656
self.clients: dict[str, OAuthClientInformationFull] = {

‎src/mcp/client/auth/extensions/identity_assertion.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def __init__(
104104
server_url: The MCP server URL.
105105
storage: Token storage implementation.
106106
client_id: The OAuth client ID registered with the MCP authorization server.
107-
client_secret: The client secret. SEP-990 section 5.1 requires a confidential client.
107+
client_secret: The client secret. This provider supports confidential clients only.
108108
issuer: The issuer identifier of the MCP authorization server this client is provisioned
109109
for. Authorization-server metadata is fetched from this issuer's well-known and the
110110
ID-JAG and secret are sent only to its token endpoint.
@@ -115,7 +115,7 @@ def __init__(
115115
(default) or `client_secret_basic`.
116116
"""
117117
if not client_secret:
118-
raise ValueError("client_secret is required: SEP-990 mandates a confidential client")
118+
raise ValueError("client_secret is required: this provider supports confidential clients only")
119119
if not issuer:
120120
raise ValueError("issuer is required: the authorization server is configuration, not discovery")
121121
self._resource = resource_url_from_server_url(server_url)

‎src/mcp/server/auth/handlers/register.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,9 @@ async def handle(self, request: Request) -> Response:
9191
status_code=400,
9292
)
9393

94-
# SEP-990 §5.1 / draft-ietf-oauth-identity-assertion-authz-grant §8.1: the ID-JAG flow is
95-
# for confidential clients provisioned out of band. Refuse to grant it through DCR so a
96-
# self-registered client cannot reach the identity-assertion provider hook.
94+
# SDK policy: the ID-JAG flow is for confidential clients (a SHOULD in
95+
# draft-ietf-oauth-identity-assertion-authz-grant-04 §9.1) provisioned out of band. Refuse to
96+
# grant it through DCR so a self-registered client cannot reach the provider hook.
9797
if JWT_BEARER_GRANT_TYPE in client_metadata.grant_types:
9898
return PydanticJSONResponse(
9999
content=RegistrationErrorResponse(

‎src/mcp/server/auth/handlers/token.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -248,10 +248,10 @@ async def handle(self, request: Request):
248248
)
249249
)
250250

251-
# SEP-990 §5.1: only confidential clients may present an ID-JAG. ClientAuthenticator
252-
# already rejects a secret-based method with no stored secret; this additionally
253-
# rejects the public `none` method so an unauthenticated client never reaches the
254-
# provider hook.
251+
# SDK policy, adopting the SHOULD in draft-ietf-oauth-identity-assertion-authz-grant-04
252+
# §9.1: only confidential clients may present an ID-JAG. ClientAuthenticator already
253+
# rejects a secret-based method with no stored secret; this additionally rejects the
254+
# public `none` method so an unauthenticated client never reaches the provider hook.
255255
if not client_info.client_secret:
256256
# RFC 6749 §5.2: the client authenticated but is not permitted this grant, so
257257
# unauthorized_client (not invalid_client, which is for failed authentication).

‎tests/client/auth/extensions/test_identity_assertion.py‎

Lines changed: 6 additions & 1 deletion

0 commit comments

Comments
 (0)