Skip to content

Commit 3968f86

Browse files
praboud-antdsp-ant
authored andcommitted
Clean up registration endpoint
1 parent 86404fd commit 3968f86

File tree

5 files changed

+27
-59
lines changed

5 files changed

+27
-59
lines changed

src/mcp/server/auth/handlers/authorize.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,6 @@
2121

2222

2323
class AuthorizationRequest(BaseModel):
24-
"""
25-
Model for the authorization request parameters.
26-
27-
Corresponds to request schema in authorizationHandler in src/server/auth/handlers/authorize.ts
28-
"""
29-
3024
client_id: str = Field(..., description="The client ID")
3125
redirect_uri: AnyHttpUrl | None = Field(
3226
..., description="URL to redirect to after authorization"
@@ -42,7 +36,8 @@ class AuthorizationRequest(BaseModel):
4236
state: Optional[str] = Field(None, description="Optional state parameter")
4337
scope: Optional[str] = Field(
4438
None,
45-
description="Optional scope; if specified, should be a space-separated list of scope strings",
39+
description="Optional scope; if specified, should be " \
40+
"a space-separated list of scope strings",
4641
)
4742

4843
class Config:

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

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,13 @@
2424

2525

2626
class AuthorizationCodeRequest(ClientAuthRequest):
27-
"""
28-
Model for the authorization code grant request parameters.
29-
30-
Corresponds to AuthorizationCodeExchangeSchema in src/server/auth/handlers/token.ts
31-
"""
32-
3327
grant_type: Literal["authorization_code"]
3428
code: str = Field(..., description="The authorization code")
3529
code_verifier: str = Field(..., description="PKCE code verifier")
30+
# TODO: this should take redirect_uri
3631

3732

3833
class RefreshTokenRequest(ClientAuthRequest):
39-
"""
40-
Model for the refresh token grant request parameters.
41-
42-
Corresponds to RefreshTokenExchangeSchema in src/server/auth/handlers/token.ts
43-
"""
44-
4534
grant_type: Literal["refresh_token"]
4635
refresh_token: str = Field(..., description="The refresh token")
4736
scope: Optional[str] = Field(None, description="Optional scope parameter")
@@ -54,48 +43,25 @@ class TokenRequest(RootModel):
5443
]
5544

5645

57-
# TokenRequest = RootModel(Annotated[Union[AuthorizationCodeRequest, RefreshTokenRequest], Field(discriminator="grant_type")])
58-
5946

6047
def create_token_handler(
6148
provider: OAuthServerProvider, client_authenticator: ClientAuthenticator
6249
) -> Callable:
63-
"""
64-
Create a handler for the OAuth 2.0 Token endpoint.
65-
66-
Corresponds to tokenHandler in src/server/auth/handlers/token.ts
67-
68-
Args:
69-
provider: The OAuth server provider
70-
71-
Returns:
72-
A Starlette endpoint handler function
73-
"""
74-
7550
async def token_handler(request: Request):
76-
"""
77-
Handler for the OAuth 2.0 Token endpoint.
78-
79-
Args:
80-
request: The Starlette request
81-
82-
Returns:
83-
JSON response with tokens or error
84-
"""
85-
# Parse request body as form data or JSON
86-
content_type = request.headers.get("Content-Type", "")
87-
8851
try:
8952
token_request = TokenRequest.model_validate_json(await request.body()).root
9053
except ValidationError as e:
9154
raise InvalidRequestError(f"Invalid request body: {e}")
9255
client_info = await client_authenticator(token_request)
9356

57+
if token_request.grant_type not in client_info.grant_types:
58+
raise InvalidRequestError(f"Unsupported grant type (supported grant types are {client_info.grant_types})")
59+
9460
tokens: OAuthTokens
9561

9662
match token_request:
9763
case AuthorizationCodeRequest():
98-
# TODO: verify that the redirect URIs match; does the client actually provide this?
64+
# TODO: verify that the redirect URIs match
9965
# see https://datatracker.ietf.org/doc/html/rfc6749#section-10.6
10066
# TODO: enforce TTL on the authorization code
10167

src/mcp/server/auth/middleware/client_auth.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ class ClientAuthRequest(BaseModel):
3131

3232
class ClientAuthenticator:
3333
"""
34-
Dependency that authenticates a client using client_id and client_secret.
35-
36-
This is a callable that can be used to validate client credentials in a request.
37-
38-
Corresponds to authenticateClient in src/server/auth/middleware/clientAuth.ts
34+
ClientAuthenticator is a callable which validates requests from a client application,
35+
used to verify /token and /revoke calls.
36+
If, during registration, the client requested to be issued a secret, the authenticator
37+
asserts that /token and /register calls must be authenticated with that same token.
38+
NOTE: clients can opt for no authentication during registration, in which case this logic
39+
is skipped.
3940
"""
40-
4141
def __init__(self, clients_store: OAuthRegisteredClientsStore):
4242
"""
4343
Initialize the dependency.

src/mcp/shared/auth.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
Corresponds to TypeScript file: src/shared/auth.ts
55
"""
66

7-
from typing import Any, List, Optional
7+
from typing import Any, List, Literal, Optional
88

99
from pydantic import AnyHttpUrl, BaseModel, Field
1010

@@ -38,18 +38,24 @@ class OAuthTokens(BaseModel):
3838
class OAuthClientMetadata(BaseModel):
3939
"""
4040
RFC 7591 OAuth 2.0 Dynamic Client Registration metadata.
41-
42-
Corresponds to OAuthClientMetadataSchema in src/shared/auth.ts
41+
See https://datatracker.ietf.org/doc/html/rfc7591#section-2
42+
for the full specification.
4343
"""
4444

4545
redirect_uris: List[AnyHttpUrl] = Field(..., min_length=1)
46-
token_endpoint_auth_method: Optional[str] = None
47-
grant_types: Optional[List[str]] = None
48-
response_types: Optional[List[str]] = None
46+
# token_endpoint_auth_method: this implementation only supports none & client_secret_basic;
47+
# ie: we do not support client_secret_post
48+
token_endpoint_auth_method: Literal["none", "client_secret_basic"] = "client_secret_basic"
49+
# grant_types: this implementation only supports authorization_code & refresh_token
50+
grant_types: List[Literal["authorization_code", "refresh_token"]] = ["authorization_code"]
51+
# this implementation only supports code; ie: it does not support implicit grants
52+
response_types: List[Literal["code"]] = ["code"]
53+
scope: Optional[str] = None
54+
55+
# these fields are currently unused, but we support & store them for potential future use
4956
client_name: Optional[str] = None
5057
client_uri: Optional[AnyHttpUrl] = None
5158
logo_uri: Optional[AnyHttpUrl] = None
52-
scope: Optional[str] = None
5359
contacts: Optional[List[str]] = None
5460
tos_uri: Optional[AnyHttpUrl] = None
5561
policy_uri: Optional[AnyHttpUrl] = None

tests/server/fastmcp/auth/test_auth_integration.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,7 @@ async def test_authorization_flow(
335335
client_metadata = {
336336
"redirect_uris": ["https://client.example.com/callback"],
337337
"client_name": "Test Client",
338+
"grant_types": ["authorization_code", "refresh_token"]
338339
}
339340

340341
response = await test_client.post(

0 commit comments

Comments
 (0)