Skip to content

Commit ffd3ea5

Browse files
committed
fix
1 parent 304cc6b commit ffd3ea5

File tree

14 files changed

+404
-257
lines changed

14 files changed

+404
-257
lines changed

examples/clients/simple-chatbot/mcp_simple_chatbot/main.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,8 +322,7 @@ async def process_llm_response(self, llm_response: str) -> str:
322322
total = result["total"]
323323
percentage = (progress / total) * 100
324324
logging.info(
325-
f"Progress: {progress}/{total} "
326-
f"({percentage:.1f}%)"
325+
f"Progress: {progress}/{total} ({percentage:.1f}%)"
327326
)
328327

329328
return f"Tool execution result: {result}"

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ mcp = "mcp.cli:app [cli]"
4343
[tool.uv]
4444
resolution = "lowest-direct"
4545
dev-dependencies = [
46-
"pyright>=1.1.391",
46+
"pyright>=1.1.396",
4747
"pytest>=8.3.4",
4848
"ruff>=0.8.5",
4949
"trio>=0.26.2",

src/mcp/client/auth/oauth.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -385,15 +385,15 @@ async def open_user_agent(self, url: AnyHttpUrl) -> None:
385385
...
386386

387387
async def client_registration(
388-
self, endpoint: AnyHttpUrl
388+
self, issuer: AnyHttpUrl
389389
) -> DynamicClientRegistration | None:
390390
"""
391391
Loads the client registration for the given endpoint.
392392
"""
393393
...
394394

395395
async def store_client_registration(
396-
self, endpoint: AnyHttpUrl, metadata: DynamicClientRegistration
396+
self, issuer: AnyHttpUrl, metadata: DynamicClientRegistration
397397
) -> None:
398398
"""
399399
Stores the client registration to be retreived for the next session

src/mcp/server/auth/errors.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,5 +146,9 @@ class InsufficientScopeError(OAuthError):
146146

147147
error_code = "insufficient_scope"
148148

149+
149150
def stringify_pydantic_error(validation_error: ValidationError) -> str:
150-
return "\n".join(f"{'.'.join(str(loc) for loc in e['loc'])}: {e['msg']}" for e in validation_error.errors())
151+
return "\n".join(
152+
f"{'.'.join(str(loc) for loc in e['loc'])}: {e['msg']}"
153+
for e in validation_error.errors()
154+
)

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

Lines changed: 57 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

@@ -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

5455
def 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+
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+
96+
9297
class 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+
107116
class 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:

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

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,24 @@
1111

1212
from pydantic import BaseModel, ValidationError
1313
from starlette.requests import Request
14-
from starlette.responses import JSONResponse, Response
14+
from starlette.responses import Response
1515

1616
from mcp.server.auth.errors import (
1717
InvalidRequestError,
18-
OAuthError,
19-
ServerError,
2018
stringify_pydantic_error,
2119
)
2220
from mcp.server.auth.json_response import PydanticJSONResponse
2321
from mcp.server.auth.provider import OAuthRegisteredClientsStore
2422
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata
2523

24+
2625
class ErrorResponse(BaseModel):
27-
error: Literal["invalid_redirect_uri", "invalid_client_metadata", "invalid_software_statement", "unapproved_software_statement"]
26+
error: Literal[
27+
"invalid_redirect_uri",
28+
"invalid_client_metadata",
29+
"invalid_software_statement",
30+
"unapproved_software_statement",
31+
]
2832
error_description: str
2933

3034

@@ -38,10 +42,13 @@ async def registration_handler(request: Request) -> Response:
3842
body = await request.json()
3943
client_metadata = OAuthClientMetadata.model_validate(body)
4044
except ValidationError as validation_error:
41-
return PydanticJSONResponse(content=ErrorResponse(
42-
error="invalid_client_metadata",
43-
error_description=stringify_pydantic_error(validation_error)
44-
), status_code=400)
45+
return PydanticJSONResponse(
46+
content=ErrorResponse(
47+
error="invalid_client_metadata",
48+
error_description=stringify_pydantic_error(validation_error),
49+
),
50+
status_code=400,
51+
)
4552
raise InvalidRequestError(f"Invalid client metadata: {str(e)}")
4653

4754
client_id = str(uuid4())

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

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

7-
from tokenize import Token
87
from typing import Callable
98

109
from pydantic import ValidationError
1110
from starlette.requests import Request
1211
from starlette.responses import Response
1312

1413
from mcp.server.auth.errors import (
15-
InvalidRequestError,
1614
stringify_pydantic_error,
1715
)
16+
from mcp.server.auth.json_response import PydanticJSONResponse
1817
from mcp.server.auth.middleware.client_auth import (
1918
ClientAuthenticator,
2019
ClientAuthRequest,
2120
)
2221
from mcp.server.auth.provider import OAuthServerProvider, OAuthTokenRevocationRequest
23-
from mcp.server.auth.json_response import PydanticJSONResponse
2422
from mcp.shared.auth import TokenErrorResponse
2523

2624

@@ -39,10 +37,13 @@ async def revocation_handler(request: Request) -> Response:
3937
form_data = await request.form()
4038
revocation_request = RevocationRequest.model_validate(dict(form_data))
4139
except ValidationError as e:
42-
return PydanticJSONResponse(status_code=400,content=TokenErrorResponse(
43-
error="invalid_request",
44-
error_description=stringify_pydantic_error(e)
45-
))
40+
return PydanticJSONResponse(
41+
status_code=400,
42+
content=TokenErrorResponse(
43+
error="invalid_request",
44+
error_description=stringify_pydantic_error(e),
45+
),
46+
)
4647

4748
# Authenticate client
4849
client_auth_result = await client_authenticator(revocation_request)

0 commit comments

Comments
 (0)