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
@@ -48,7 +50,6 @@ class AuthorizationRequest(BaseModel):
4850 description = "Optional scope; if specified, should be "
4951 "a space-separated list of scope strings" ,
5052 )
51-
5253
5354
5455def validate_scope (
@@ -80,30 +81,38 @@ def validate_redirect_uri(
8081 raise InvalidRequestError (
8182 "redirect_uri must be specified when client has multiple registered URIs"
8283 )
84+
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+
96+
9297class ErrorResponse (BaseModel ):
9398 error : ErrorCode
9499 error_description : str
95100 error_uri : Optional [AnyUrl ] = None
96101 # must be set if provided in the request
97102 state : Optional [str ]
98103
99- 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 ]:
100108 if params is None :
101109 return None
102110 value = params .get (key )
103111 if isinstance (value , str ):
104112 return value
105113 return None
106114
115+
107116class AnyHttpUrlModel (RootModel ):
108117 root : AnyHttpUrl
109118
@@ -118,18 +127,24 @@ async def authorization_handler(request: Request) -> Response:
118127 client = None
119128 params = None
120129
121- 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+ ):
122133 nonlocal client , redirect_uri , state
123134 if client is None and attempt_load_client :
124135 # make last-ditch attempt to load the client
125136 client_id = best_effort_extract_string ("client_id" , params )
126- 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+ )
127140 if redirect_uri is None and client :
128141 # make last-ditch effort to load the redirect uri
129142 if params is not None and "redirect_uri" not in params :
130143 raw_redirect_uri = None
131144 else :
132- 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
133148 try :
134149 redirect_uri = validate_redirect_uri (raw_redirect_uri , client )
135150 except (ValidationError , InvalidRequestError ):
@@ -146,7 +161,9 @@ async def error_response(error: ErrorCode, error_description: str, attempt_load_
146161
147162 if redirect_uri and client :
148163 return RedirectResponse (
149- 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+ ),
150167 status_code = 302 ,
151168 headers = {"Cache-Control" : "no-store" },
152169 )
@@ -156,7 +173,7 @@ async def error_response(error: ErrorCode, error_description: str, attempt_load_
156173 content = error_resp ,
157174 headers = {"Cache-Control" : "no-store" },
158175 )
159-
176+
160177 try :
161178 # Parse request parameters
162179 if request .method == "GET" :
@@ -165,20 +182,22 @@ async def error_response(error: ErrorCode, error_description: str, attempt_load_
165182 else :
166183 # Parse form data for POST requests
167184 params = await request .form ()
168-
185+
169186 # Save state if it exists, even before validation
170187 state = best_effort_extract_string ("state" , params )
171-
188+
172189 try :
173190 auth_request = AuthorizationRequest .model_validate (params )
174191 state = auth_request .state # Update with validated state
175192 except ValidationError as validation_error :
176193 error : ErrorCode = "invalid_request"
177194 for e in validation_error .errors ():
178- if e [' loc' ] == (' response_type' ,) and e [' type' ] == ' literal_error' :
195+ if e [" loc" ] == (" response_type" ,) and e [" type" ] == " literal_error" :
179196 error = "unsupported_response_type"
180197 break
181- return await error_response (error , stringify_pydantic_error (validation_error ))
198+ return await error_response (
199+ error , stringify_pydantic_error (validation_error )
200+ )
182201
183202 # Get client information
184203 client = await provider .clients_store .get_client (auth_request .client_id )
@@ -190,7 +209,6 @@ async def error_response(error: ErrorCode, error_description: str, attempt_load_
190209 attempt_load_client = False ,
191210 )
192211
193-
194212 # Validate redirect_uri against client's registered URIs
195213 try :
196214 redirect_uri = validate_redirect_uri (auth_request .redirect_uri , client )
@@ -200,7 +218,7 @@ async def error_response(error: ErrorCode, error_description: str, attempt_load_
200218 error = "invalid_request" ,
201219 error_description = validation_error .message ,
202220 )
203-
221+
204222 # Validate scope - for scope errors, we can redirect
205223 try :
206224 scopes = validate_scope (auth_request .scope , client )
@@ -210,28 +228,30 @@ async def error_response(error: ErrorCode, error_description: str, attempt_load_
210228 error = "invalid_scope" ,
211229 error_description = validation_error .message ,
212230 )
213-
231+
214232 # Setup authorization parameters
215233 auth_params = AuthorizationParams (
216234 state = state ,
217235 scopes = scopes ,
218236 code_challenge = auth_request .code_challenge ,
219237 redirect_uri = redirect_uri ,
220238 )
221-
239+
222240 # Let the provider pick the next URI to redirect to
223241 response = RedirectResponse (
224242 url = "" , status_code = 302 , headers = {"Cache-Control" : "no-store" }
225243 )
226- response .headers ["location" ] = await provider .authorize (
227- client , auth_params
228- )
244+ response .headers ["location" ] = await provider .authorize (client , auth_params )
229245 return response
230-
246+
231247 except Exception as validation_error :
232248 # Catch-all for unexpected errors
233- logger .exception ("Unexpected error in authorization_handler" , exc_info = validation_error )
234- 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+ )
235255
236256 return authorization_handler
237257
@@ -240,7 +260,7 @@ def create_error_redirect(
240260 redirect_uri : AnyUrl , error : Union [Exception , ErrorResponse ]
241261) -> str :
242262 parsed_uri = urlparse (str (redirect_uri ))
243-
263+
244264 if isinstance (error , ErrorResponse ):
245265 # Convert ErrorResponse to dict
246266 error_dict = error .model_dump (exclude_none = True )
@@ -251,7 +271,7 @@ def create_error_redirect(
251271 query_params [key ] = str (value )
252272 else :
253273 query_params [key ] = value
254-
274+
255275 elif isinstance (error , OAuthError ):
256276 query_params = {"error" : error .error_code , "error_description" : str (error )}
257277 else :
0 commit comments