Skip to content

Commit 4aad4ce

Browse files
Linting
1 parent 8762d53 commit 4aad4ce

File tree

14 files changed

+389
-249
lines changed

14 files changed

+389
-249
lines changed

src/mcp/server/auth/errors.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,4 +155,7 @@ class InsufficientScopeError(OAuthError):
155155

156156

157157
def stringify_pydantic_error(validation_error: ValidationError) -> str:
158-
return "\n".join(f"{'.'.join(str(loc) for loc in e['loc'])}: {e['msg']}" for e in validation_error.errors())
158+
return "\n".join(
159+
f"{'.'.join(str(loc) for loc in e['loc'])}: {e['msg']}"
160+
for e in validation_error.errors()
161+
)

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

Lines changed: 56 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,27 @@
44
Corresponds to TypeScript file: src/server/auth/handlers/authorize.ts
55
"""
66

7+
import logging
78
from typing import Callable, Literal, Optional, Union
8-
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
9+
from urllib.parse import urlencode, urlparse, urlunparse
910

1011
from pydantic import AnyHttpUrl, AnyUrl, BaseModel, Field, RootModel, ValidationError
1112
from starlette.datastructures import FormData, QueryParams
1213
from starlette.requests import Request
1314
from starlette.responses import RedirectResponse, Response
1415

1516
from mcp.server.auth.errors import (
16-
InvalidClientError,
1717
InvalidRequestError,
1818
OAuthError,
1919
stringify_pydantic_error,
2020
)
21-
from mcp.server.auth.provider import AuthorizationParams, OAuthServerProvider, construct_redirect_uri
22-
from mcp.shared.auth import OAuthClientInformationFull
2321
from mcp.server.auth.json_response import PydanticJSONResponse
24-
25-
import logging
22+
from mcp.server.auth.provider import (
23+
AuthorizationParams,
24+
OAuthServerProvider,
25+
construct_redirect_uri,
26+
)
27+
from mcp.shared.auth import OAuthClientInformationFull
2628

2729
logger = logging.getLogger(__name__)
2830

@@ -80,15 +82,17 @@ def validate_redirect_uri(
8082
"redirect_uri must be specified when client has multiple registered URIs"
8183
)
8284

85+
8386
ErrorCode = Literal[
84-
"invalid_request",
85-
"unauthorized_client",
86-
"access_denied",
87-
"unsupported_response_type",
88-
"invalid_scope",
89-
"server_error",
90-
"temporarily_unavailable"
91-
]
87+
"invalid_request",
88+
"unauthorized_client",
89+
"access_denied",
90+
"unsupported_response_type",
91+
"invalid_scope",
92+
"server_error",
93+
"temporarily_unavailable",
94+
]
95+
9296

9397
class ErrorResponse(BaseModel):
9498
error: ErrorCode
@@ -97,14 +101,18 @@ class ErrorResponse(BaseModel):
97101
# must be set if provided in the request
98102
state: Optional[str]
99103

100-
def best_effort_extract_string(key: str, params: None | FormData | QueryParams) -> Optional[str]:
104+
105+
def best_effort_extract_string(
106+
key: str, params: None | FormData | QueryParams
107+
) -> Optional[str]:
101108
if params is None:
102109
return None
103110
value = params.get(key)
104111
if isinstance(value, str):
105112
return value
106113
return None
107114

115+
108116
class AnyHttpUrlModel(RootModel):
109117
root: AnyHttpUrl
110118

@@ -119,18 +127,24 @@ async def authorization_handler(request: Request) -> Response:
119127
client = None
120128
params = None
121129

122-
async def error_response(error: ErrorCode, error_description: str, attempt_load_client: bool = True):
130+
async def error_response(
131+
error: ErrorCode, error_description: str, attempt_load_client: bool = True
132+
):
123133
nonlocal client, redirect_uri, state
124134
if client is None and attempt_load_client:
125135
# make last-ditch attempt to load the client
126136
client_id = best_effort_extract_string("client_id", params)
127-
client = client_id and await provider.clients_store.get_client(client_id)
137+
client = client_id and await provider.clients_store.get_client(
138+
client_id
139+
)
128140
if redirect_uri is None and client:
129141
# make last-ditch effort to load the redirect uri
130142
if params is not None and "redirect_uri" not in params:
131143
raw_redirect_uri = None
132144
else:
133-
raw_redirect_uri = AnyHttpUrlModel.model_validate(best_effort_extract_string("redirect_uri", params)).root
145+
raw_redirect_uri = AnyHttpUrlModel.model_validate(
146+
best_effort_extract_string("redirect_uri", params)
147+
).root
134148
try:
135149
redirect_uri = validate_redirect_uri(raw_redirect_uri, client)
136150
except (ValidationError, InvalidRequestError):
@@ -147,7 +161,9 @@ async def error_response(error: ErrorCode, error_description: str, attempt_load_
147161

148162
if redirect_uri and client:
149163
return RedirectResponse(
150-
url=construct_redirect_uri(str(redirect_uri), **error_resp.model_dump(exclude_none=True)),
164+
url=construct_redirect_uri(
165+
str(redirect_uri), **error_resp.model_dump(exclude_none=True)
166+
),
151167
status_code=302,
152168
headers={"Cache-Control": "no-store"},
153169
)
@@ -157,7 +173,7 @@ async def error_response(error: ErrorCode, error_description: str, attempt_load_
157173
content=error_resp,
158174
headers={"Cache-Control": "no-store"},
159175
)
160-
176+
161177
try:
162178
# Parse request parameters
163179
if request.method == "GET":
@@ -166,20 +182,22 @@ async def error_response(error: ErrorCode, error_description: str, attempt_load_
166182
else:
167183
# Parse form data for POST requests
168184
params = await request.form()
169-
185+
170186
# Save state if it exists, even before validation
171187
state = best_effort_extract_string("state", params)
172-
188+
173189
try:
174190
auth_request = AuthorizationRequest.model_validate(params)
175191
state = auth_request.state # Update with validated state
176192
except ValidationError as validation_error:
177193
error: ErrorCode = "invalid_request"
178194
for e in validation_error.errors():
179-
if e['loc'] == ('response_type',) and e['type'] == 'literal_error':
195+
if e["loc"] == ("response_type",) and e["type"] == "literal_error":
180196
error = "unsupported_response_type"
181197
break
182-
return await error_response(error, stringify_pydantic_error(validation_error))
198+
return await error_response(
199+
error, stringify_pydantic_error(validation_error)
200+
)
183201

184202
# Get client information
185203
client = await provider.clients_store.get_client(auth_request.client_id)
@@ -191,7 +209,6 @@ async def error_response(error: ErrorCode, error_description: str, attempt_load_
191209
attempt_load_client=False,
192210
)
193211

194-
195212
# Validate redirect_uri against client's registered URIs
196213
try:
197214
redirect_uri = validate_redirect_uri(auth_request.redirect_uri, client)
@@ -201,7 +218,7 @@ async def error_response(error: ErrorCode, error_description: str, attempt_load_
201218
error="invalid_request",
202219
error_description=validation_error.message,
203220
)
204-
221+
205222
# Validate scope - for scope errors, we can redirect
206223
try:
207224
scopes = validate_scope(auth_request.scope, client)
@@ -211,28 +228,30 @@ async def error_response(error: ErrorCode, error_description: str, attempt_load_
211228
error="invalid_scope",
212229
error_description=validation_error.message,
213230
)
214-
231+
215232
# Setup authorization parameters
216233
auth_params = AuthorizationParams(
217234
state=state,
218235
scopes=scopes,
219236
code_challenge=auth_request.code_challenge,
220237
redirect_uri=redirect_uri,
221238
)
222-
239+
223240
# Let the provider pick the next URI to redirect to
224241
response = RedirectResponse(
225242
url="", status_code=302, headers={"Cache-Control": "no-store"}
226243
)
227-
response.headers["location"] = await provider.authorize(
228-
client, auth_params
229-
)
244+
response.headers["location"] = await provider.authorize(client, auth_params)
230245
return response
231-
246+
232247
except Exception as validation_error:
233248
# Catch-all for unexpected errors
234-
logger.exception("Unexpected error in authorization_handler", exc_info=validation_error)
235-
return await error_response(error="server_error", error_description="An unexpected error occurred")
249+
logger.exception(
250+
"Unexpected error in authorization_handler", exc_info=validation_error
251+
)
252+
return await error_response(
253+
error="server_error", error_description="An unexpected error occurred"
254+
)
236255

237256
return authorization_handler
238257

@@ -241,7 +260,7 @@ def create_error_redirect(
241260
redirect_uri: AnyUrl, error: Union[Exception, ErrorResponse]
242261
) -> str:
243262
parsed_uri = urlparse(str(redirect_uri))
244-
263+
245264
if isinstance(error, ErrorResponse):
246265
# Convert ErrorResponse to dict
247266
error_dict = error.model_dump(exclude_none=True)
@@ -252,7 +271,7 @@ def create_error_redirect(
252271
query_params[key] = str(value)
253272
else:
254273
query_params[key] = value
255-
274+
256275
elif isinstance(error, OAuthError):
257276
query_params = {"error": error.error_code, "error_description": str(error)}
258277
else:
@@ -265,4 +284,4 @@ def create_error_redirect(
265284
if parsed_uri.query:
266285
new_query = f"{parsed_uri.query}&{new_query}"
267286

268-
return urlunparse(parsed_uri._replace(query=new_query))
287+
return urlunparse(parsed_uri._replace(query=new_query))

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

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

7-
from typing import Any, Callable, Dict
7+
from typing import Callable
88

99
from starlette.requests import Request
1010
from starlette.responses import JSONResponse, Response
@@ -35,12 +35,13 @@ async def metadata_handler(request: Request) -> Response:
3535
Returns:
3636
JSON response with the authorization server metadata
3737
"""
38-
# Remove any None values from metadata
39-
clean_metadata = {k: v for k, v in metadata.items() if v is not None}
38+
# Convert metadata to dict and remove any None values
39+
metadata_dict = metadata.model_dump()
40+
clean_metadata = {k: v for k, v in metadata_dict.items() if v is not None}
4041

4142
return JSONResponse(
4243
content=clean_metadata,
4344
headers={"Cache-Control": "public, max-age=3600"}, # Cache for 1 hour
4445
)
4546

46-
return metadata_handler
47+
return metadata_handler

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

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
from starlette.responses import JSONResponse, Response
1515

1616
from mcp.server.auth.errors import (
17-
InvalidRequestError,
1817
OAuthError,
1918
ServerError,
2019
stringify_pydantic_error,
@@ -23,8 +22,14 @@
2322
from mcp.server.auth.provider import OAuthRegisteredClientsStore
2423
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata
2524

25+
2626
class ErrorResponse(BaseModel):
27-
error: Literal["invalid_redirect_uri", "invalid_client_metadata", "invalid_software_statement", "unapproved_software_statement"]
27+
error: Literal[
28+
"invalid_redirect_uri",
29+
"invalid_client_metadata",
30+
"invalid_software_statement",
31+
"unapproved_software_statement",
32+
]
2833
error_description: str
2934

3035

@@ -60,10 +65,13 @@ async def registration_handler(request: Request) -> Response:
6065
body = await request.json()
6166
client_metadata = OAuthClientMetadata.model_validate(body)
6267
except ValidationError as validation_error:
63-
return PydanticJSONResponse(content=ErrorResponse(
64-
error="invalid_client_metadata",
65-
error_description=stringify_pydantic_error(validation_error)
66-
), status_code=400)
68+
return PydanticJSONResponse(
69+
content=ErrorResponse(
70+
error="invalid_client_metadata",
71+
error_description=stringify_pydantic_error(validation_error),
72+
),
73+
status_code=400,
74+
)
6775

6876
client_id = str(uuid4())
6977
client_secret = None
@@ -103,4 +111,4 @@ async def registration_handler(request: Request) -> Response:
103111
status_code = 500 if isinstance(e, ServerError) else 400
104112
return JSONResponse(status_code=status_code, content=e.to_response_object())
105113

106-
return registration_handler
114+
return registration_handler

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

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,14 @@
1111
from starlette.responses import Response
1212

1313
from mcp.server.auth.errors import (
14-
InvalidRequestError,
1514
stringify_pydantic_error,
1615
)
16+
from mcp.server.auth.json_response import PydanticJSONResponse
1717
from mcp.server.auth.middleware.client_auth import (
1818
ClientAuthenticator,
1919
ClientAuthRequest,
2020
)
2121
from mcp.server.auth.provider import OAuthServerProvider
22-
from mcp.server.auth.json_response import PydanticJSONResponse
2322
from mcp.shared.auth import OAuthTokenRevocationRequest, TokenErrorResponse
2423

2524

@@ -51,17 +50,25 @@ async def revocation_handler(request: Request) -> Response:
5150
form_data = await request.form()
5251
revocation_request = RevocationRequest.model_validate(dict(form_data))
5352
except ValidationError as e:
54-
return PydanticJSONResponse(status_code=400, content=TokenErrorResponse(
55-
error="invalid_request",
56-
error_description=stringify_pydantic_error(e)
57-
))
53+
return PydanticJSONResponse(
54+
status_code=400,
55+
content=TokenErrorResponse(
56+
error="invalid_request",
57+
error_description=stringify_pydantic_error(e),
58+
),
59+
)
5860

5961
# Authenticate client
6062
client_auth_result = await client_authenticator(revocation_request)
6163

6264
# Revoke token
6365
if provider.revoke_token:
64-
await provider.revoke_token(client_auth_result, revocation_request)
66+
# Convert RevocationRequest to OAuthTokenRevocationRequest
67+
oauth_revocation_request = OAuthTokenRevocationRequest(
68+
token=revocation_request.token,
69+
token_type_hint=revocation_request.token_type_hint,
70+
)
71+
await provider.revoke_token(client_auth_result, oauth_revocation_request)
6572

6673
# Return successful empty response
6774
return Response(
@@ -72,4 +79,4 @@ async def revocation_handler(request: Request) -> Response:
7279
},
7380
)
7481

75-
return revocation_handler
82+
return revocation_handler

0 commit comments

Comments
 (0)