Skip to content

Commit 1d4b540

Browse files
committed
Clean up unused error classes
1 parent 672affd commit 1d4b540

File tree

8 files changed

+61
-142
lines changed

8 files changed

+61
-142
lines changed

src/mcp/server/auth/errors.py

Lines changed: 17 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@
44
Corresponds to TypeScript file: src/server/auth/errors.ts
55
"""
66

7-
from typing import Dict
7+
from typing import Literal
88

9-
from pydantic import ValidationError
9+
from pydantic import BaseModel, ValidationError
10+
11+
ErrorCode = Literal["invalid_request", "invalid_client"]
12+
13+
class ErrorResponse(BaseModel):
14+
error: ErrorCode
15+
error_description: str
1016

1117

1218
class OAuthError(Exception):
@@ -16,25 +22,17 @@ class OAuthError(Exception):
1622
Corresponds to OAuthError in src/server/auth/errors.ts
1723
"""
1824

19-
error_code: str = "server_error"
20-
21-
def __init__(self, message: str):
22-
super().__init__(message)
23-
self.message = message
24-
25-
def to_response_object(self) -> Dict[str, str]:
26-
"""Convert error to JSON response object."""
27-
return {"error": self.error_code, "error_description": self.message}
25+
error_code: ErrorCode
2826

27+
def __init__(self, error_description: str):
28+
super().__init__(error_description)
29+
self.error_description = error_description
2930

30-
class ServerError(OAuthError):
31-
"""
32-
Server error.
33-
34-
Corresponds to ServerError in src/server/auth/errors.ts
35-
"""
36-
37-
error_code = "server_error"
31+
def error_response(self) -> ErrorResponse:
32+
return ErrorResponse(
33+
error=self.error_code,
34+
error_description=self.error_description,
35+
)
3836

3937

4038
class InvalidRequestError(OAuthError):
@@ -57,96 +55,6 @@ class InvalidClientError(OAuthError):
5755
error_code = "invalid_client"
5856

5957

60-
class InvalidGrantError(OAuthError):
61-
"""
62-
Invalid grant error.
63-
64-
Corresponds to InvalidGrantError in src/server/auth/errors.ts
65-
"""
66-
67-
error_code = "invalid_grant"
68-
69-
70-
class UnauthorizedClientError(OAuthError):
71-
"""
72-
Unauthorized client error.
73-
74-
Corresponds to UnauthorizedClientError in src/server/auth/errors.ts
75-
"""
76-
77-
error_code = "unauthorized_client"
78-
79-
80-
class UnsupportedGrantTypeError(OAuthError):
81-
"""
82-
Unsupported grant type error.
83-
84-
Corresponds to UnsupportedGrantTypeError in src/server/auth/errors.ts
85-
"""
86-
87-
error_code = "unsupported_grant_type"
88-
89-
90-
class UnsupportedResponseTypeError(OAuthError):
91-
"""
92-
Unsupported response type error.
93-
94-
Corresponds to UnsupportedResponseTypeError in src/server/auth/errors.ts
95-
"""
96-
97-
error_code = "unsupported_response_type"
98-
99-
100-
class InvalidScopeError(OAuthError):
101-
"""
102-
Invalid scope error.
103-
104-
Corresponds to InvalidScopeError in src/server/auth/errors.ts
105-
"""
106-
107-
error_code = "invalid_scope"
108-
109-
110-
class AccessDeniedError(OAuthError):
111-
"""
112-
Access denied error.
113-
114-
Corresponds to AccessDeniedError in src/server/auth/errors.ts
115-
"""
116-
117-
error_code = "access_denied"
118-
119-
120-
class TemporarilyUnavailableError(OAuthError):
121-
"""
122-
Temporarily unavailable error.
123-
124-
Corresponds to TemporarilyUnavailableError in src/server/auth/errors.ts
125-
"""
126-
127-
error_code = "temporarily_unavailable"
128-
129-
130-
class InvalidTokenError(OAuthError):
131-
"""
132-
Invalid token error.
133-
134-
Corresponds to InvalidTokenError in src/server/auth/errors.ts
135-
"""
136-
137-
error_code = "invalid_token"
138-
139-
140-
class InsufficientScopeError(OAuthError):
141-
"""
142-
Insufficient scope error.
143-
144-
Corresponds to InsufficientScopeError in src/server/auth/errors.ts
145-
"""
146-
147-
error_code = "insufficient_scope"
148-
149-
15058
def stringify_pydantic_error(validation_error: ValidationError) -> str:
15159
return "\n".join(
15260
f"{'.'.join(str(loc) for loc in e['loc'])}: {e['msg']}"

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ async def error_response(
216216
# For redirect_uri validation errors, return direct error (no redirect)
217217
return await error_response(
218218
error="invalid_request",
219-
error_description=validation_error.message,
219+
error_description=validation_error.error_description,
220220
)
221221

222222
# Validate scope - for scope errors, we can redirect
@@ -226,7 +226,7 @@ async def error_response(
226226
# For scope errors, redirect with error parameters
227227
return await error_response(
228228
error="invalid_scope",
229-
error_description=validation_error.message,
229+
error_description=validation_error.error_description,
230230
)
231231

232232
# Setup authorization parameters

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,9 @@ async def registration_handler(request: Request) -> Response:
8383
software_version=client_metadata.software_version,
8484
)
8585
# Register client
86-
client = await clients_store.register_client(client_info)
86+
await clients_store.register_client(client_info)
8787

8888
# Return client information
89-
return PydanticJSONResponse(content=client, status_code=201)
89+
return PydanticJSONResponse(content=client_info, status_code=201)
9090

9191
return registration_handler

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from starlette.responses import Response
1212

1313
from mcp.server.auth.errors import (
14+
InvalidClientError,
1415
stringify_pydantic_error,
1516
)
1617
from mcp.server.auth.json_response import PydanticJSONResponse
@@ -46,7 +47,10 @@ async def revocation_handler(request: Request) -> Response:
4647
)
4748

4849
# Authenticate client
49-
client_auth_result = await client_authenticator(revocation_request)
50+
try:
51+
client_auth_result = await client_authenticator(revocation_request)
52+
except InvalidClientError as e:
53+
return PydanticJSONResponse(status_code=401, content=e.error_response())
5054

5155
# Revoke token
5256
if provider.revoke_token:

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
from starlette.requests import Request
1414

1515
from mcp.server.auth.errors import (
16+
ErrorResponse,
17+
InvalidClientError,
1618
stringify_pydantic_error,
1719
)
1820
from mcp.server.auth.json_response import PydanticJSONResponse
@@ -53,7 +55,7 @@ class TokenRequest(RootModel):
5355
def create_token_handler(
5456
provider: OAuthServerProvider, client_authenticator: ClientAuthenticator
5557
) -> Callable:
56-
def response(obj: TokenSuccessResponse | TokenErrorResponse):
58+
def response(obj: TokenSuccessResponse | TokenErrorResponse | ErrorResponse):
5759
status_code = 200
5860
if isinstance(obj, TokenErrorResponse):
5961
status_code = 400
@@ -78,7 +80,11 @@ async def token_handler(request: Request):
7880
error_description=stringify_pydantic_error(validation_error),
7981
)
8082
)
81-
client_info = await client_authenticator(token_request)
83+
84+
try:
85+
client_info = await client_authenticator(token_request)
86+
except InvalidClientError as e:
87+
return response(e.error_response())
8288

8389
if token_request.grant_type not in client_info.grant_types:
8490
return response(
@@ -124,8 +130,9 @@ async def token_handler(request: Request):
124130
TokenErrorResponse(
125131
error="invalid_request",
126132
error_description=(
127-
"redirect_uri didn't match the one used when creating auth code"
128-
),
133+
"redirect_uri did not match the one "
134+
"used when creating auth code"
135+
),
129136
)
130137
)
131138

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

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
from starlette.requests import HTTPConnection
1717
from starlette.types import Scope
1818

19-
from mcp.server.auth.errors import InsufficientScopeError, InvalidTokenError, OAuthError
2019
from mcp.server.auth.provider import OAuthServerProvider
2120
from mcp.server.auth.types import AuthInfo
2221

@@ -51,21 +50,17 @@ async def authenticate(self, conn: HTTPConnection):
5150

5251
token = auth_header[7:] # Remove "Bearer " prefix
5352

54-
try:
55-
# Validate the token with the provider
56-
auth_info = await self.provider.load_access_token(token)
53+
# Validate the token with the provider
54+
auth_info = await self.provider.load_access_token(token)
5755

58-
if not auth_info:
59-
raise InvalidTokenError("Invalid access token")
56+
if not auth_info:
57+
return None
6058

61-
if auth_info.expires_at and auth_info.expires_at < int(time.time()):
62-
raise InvalidTokenError("Token has expired")
59+
if auth_info.expires_at and auth_info.expires_at < int(time.time()):
60+
return None
6361

64-
return AuthCredentials(auth_info.scopes), AuthenticatedUser(auth_info)
62+
return AuthCredentials(auth_info.scopes), AuthenticatedUser(auth_info)
6563

66-
except (InvalidTokenError, InsufficientScopeError, OAuthError):
67-
# Return None to indicate authentication failure
68-
return None
6964

7065

7166
class RequireAuthMiddleware:

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@
55
"""
66

77
import time
8-
from typing import Any, Callable, Dict, Optional
8+
from typing import Optional
99

1010
from pydantic import BaseModel
11-
from starlette.exceptions import HTTPException
12-
from starlette.requests import Request
1311

1412
from mcp.server.auth.errors import (
1513
InvalidClientError,

tests/server/fastmcp/auth/test_auth_integration.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from starlette.applications import Starlette
1919
from starlette.routing import Mount
2020

21-
from mcp.server.auth.errors import InvalidTokenError
2221
from mcp.server.auth.provider import (
2322
AuthorizationCode,
2423
AuthorizationParams,
@@ -97,8 +96,7 @@ async def load_authorization_code(
9796
async def exchange_authorization_code(
9897
self, client: OAuthClientInformationFull, authorization_code: AuthorizationCode
9998
) -> TokenSuccessResponse:
100-
if authorization_code.code not in self.auth_codes:
101-
raise InvalidTokenError("Invalid authorization code")
99+
assert authorization_code.code in self.auth_codes
102100

103101
# Generate an access token and refresh token
104102
access_token = f"access_{secrets.token_hex(32)}"
@@ -152,19 +150,16 @@ async def exchange_refresh_token(
152150
scopes: List[str],
153151
) -> TokenSuccessResponse:
154152
# Check if refresh token exists
155-
if refresh_token.token not in self.refresh_tokens:
156-
raise InvalidTokenError("Invalid refresh token")
153+
assert refresh_token.token in self.refresh_tokens
157154

158155
old_access_token = self.refresh_tokens[refresh_token.token]
159156

160157
# Check if the access token exists
161-
if old_access_token not in self.tokens:
162-
raise InvalidTokenError("Invalid refresh token")
158+
assert old_access_token in self.tokens
163159

164160
# Check if the token was issued to this client
165161
token_info = self.tokens[old_access_token]
166-
if token_info.client_id != client.client_id:
167-
raise InvalidTokenError("Refresh token was not issued to this client")
162+
assert token_info.client_id == client.client_id
168163

169164
# Generate a new access token and refresh token
170165
new_access_token = f"access_{secrets.token_hex(32)}"
@@ -1017,6 +1012,18 @@ def test_tool(x: int) -> str:
10171012
# TODO: we should return 401/403 depending on whether authn or authz fails
10181013
assert response.status_code == 403, response.content
10191014

1015+
response = await test_client.post(
1016+
"/messages/",
1017+
headers={"Authorization": "invalid"},
1018+
)
1019+
assert response.status_code == 403
1020+
1021+
response = await test_client.post(
1022+
"/messages/",
1023+
headers={"Authorization": "Bearer invalid"},
1024+
)
1025+
assert response.status_code == 403
1026+
10201027
# now, become authenticated and try to go through the flow again
10211028
client_metadata = {
10221029
"redirect_uris": ["https://client.example.com/callback"],

0 commit comments

Comments
 (0)