44Corresponds to TypeScript file: src/server/auth/handlers/authorize.ts
55"""
66
7+ import logging
78from 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
1011from pydantic import AnyHttpUrl , AnyUrl , BaseModel , Field , RootModel , ValidationError
1112from starlette .datastructures import FormData , QueryParams
1213from starlette .requests import Request
1314from starlette .responses import RedirectResponse , Response
1415
1516from 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
2321from 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
2729logger = 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+
8386ErrorCode = 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
9397class 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+
108116class 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 ))
0 commit comments