Skip to content

Commit b805aad

Browse files
committed
refactor: use sync function for fetch server config
1 parent 97b1b55 commit b805aad

16 files changed

+357
-813
lines changed

mcpauth/__init__.py

Lines changed: 23 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,23 @@
33

44
from .middleware.create_bearer_auth import BaseBearerAuthConfig, BearerAuthConfig
55
from .types import VerifyAccessTokenFunction
6-
from .utils.fetch_server_config import ServerMetadataPaths
76
from .config import MCPAuthConfig
87
from .exceptions import MCPAuthAuthServerException, AuthServerExceptionCode
9-
from .utils.validate_server_config import validate_server_config
10-
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
11-
from starlette.requests import Request
12-
from starlette.responses import Response, JSONResponse
8+
from .utils import validate_server_config
9+
from starlette.middleware.base import BaseHTTPMiddleware
10+
from starlette.responses import JSONResponse
1311

1412

1513
class MCPAuth:
14+
"""
15+
The main class for the mcp-auth library, which provides methods for creating middleware
16+
functions for handling OAuth 2.0-related tasks and bearer token auth.
17+
18+
See Also: https://mcp-auth.dev for more information about the library and its usage.
19+
20+
:param config: An instance of `MCPAuthConfig` containing the server configuration.
21+
"""
22+
1623
def __init__(self, config: MCPAuthConfig):
1724
result = validate_server_config(config.server)
1825

@@ -32,41 +39,24 @@ def __init__(self, config: MCPAuthConfig):
3239

3340
self.config = config
3441

35-
def delegated_middleware(self) -> type[BaseHTTPMiddleware]:
42+
def metadata_response(self) -> JSONResponse:
3643
"""
37-
Returns a middleware that handles OAuth 2.0 Authorization Metadata endpoint
38-
(`/.well-known/oauth-authorization-server`) with CORS support (delegated mode).
39-
40-
:return: A middleware class that can be used in a Starlette or FastAPI application.
44+
Returns a response containing the server metadata in JSON format with CORS support.
4145
"""
4246
server_config = self.config.server
4347

44-
class DelegatedMiddleware(BaseHTTPMiddleware):
45-
async def dispatch(
46-
self, request: Request, call_next: RequestResponseEndpoint
47-
) -> Response:
48-
path = request.url.path
49-
if path == ServerMetadataPaths.OAUTH:
50-
response = JSONResponse(
51-
{
52-
k: v
53-
for k, v in server_config.metadata.model_dump().items()
54-
if v is not None
55-
},
56-
status_code=200,
57-
)
58-
response.headers["Access-Control-Allow-Origin"] = "*"
59-
response.headers["Access-Control-Allow-Methods"] = "GET, OPTIONS"
60-
return response
61-
else:
62-
return await call_next(request)
63-
64-
return DelegatedMiddleware
48+
response = JSONResponse(
49+
server_config.metadata.model_dump(exclude_none=True),
50+
status_code=200,
51+
)
52+
response.headers["Access-Control-Allow-Origin"] = "*"
53+
response.headers["Access-Control-Allow-Methods"] = "GET, OPTIONS"
54+
return response
6555

6656
def bearer_auth_middleware(
6757
self,
6858
mode_or_verify: Union[Literal["jwt"], VerifyAccessTokenFunction],
69-
config: BaseBearerAuthConfig,
59+
config: BaseBearerAuthConfig = BaseBearerAuthConfig(),
7060
jwt_options: dict[str, Any] = {},
7161
) -> type[BaseHTTPMiddleware]:
7262
"""
@@ -83,7 +73,7 @@ def bearer_auth_middleware(
8373

8474
metadata = self.config.server.metadata
8575
if isinstance(mode_or_verify, str) and mode_or_verify == "jwt":
86-
from .utils.create_verify_jwt import create_verify_jwt
76+
from .utils import create_verify_jwt
8777

8878
if not metadata.jwks_uri:
8979
raise MCPAuthAuthServerException(

mcpauth/exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def to_json(self, show_cause: bool = False) -> Record:
3636
"error_description": self.message,
3737
"cause": (
3838
(
39-
{k: v for k, v in self.cause.model_dump().items() if v is not None}
39+
self.cause.model_dump(exclude_none=True)
4040
if isinstance(self.cause, BaseModel)
4141
else str(self.cause)
4242
)

mcpauth/models/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from .auth_server import (
2+
AuthServerConfig as AuthServerConfig,
3+
AuthServerType as AuthServerType,
4+
)
5+
from .oauth import (
6+
AuthorizationServerMetadata as AuthorizationServerMetadata,
7+
)

mcpauth/utils/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from ._create_verify_jwt import create_verify_jwt as create_verify_jwt
2+
from ._fetch_server_config import (
3+
fetch_server_config as fetch_server_config,
4+
fetch_server_config_by_well_known_url as fetch_server_config_by_well_known_url,
5+
ServerMetadataPaths as ServerMetadataPaths,
6+
)
7+
from ._validate_server_config import (
8+
validate_server_config as validate_server_config,
9+
AuthServerConfigErrorCode as AuthServerConfigErrorCode,
10+
AuthServerConfigError as AuthServerConfigError,
11+
AuthServerConfigWarningCode as AuthServerConfigWarningCode,
12+
AuthServerConfigWarning as AuthServerConfigWarning,
13+
AuthServerConfigValidationResult as AuthServerConfigValidationResult,
14+
)

mcpauth/utils/fetch_server_config.py renamed to mcpauth/utils/_fetch_server_config.py

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from enum import Enum
22
from typing import Callable, Optional
33
from urllib.parse import urlparse, urlunparse
4-
import aiohttp
4+
import requests
55
import pydantic
66
from pathlib import Path
77

@@ -46,7 +46,7 @@ def get_oidc_well_known_url(issuer: str) -> str:
4646
return urlunparse(parsed._replace(path=new_path))
4747

4848

49-
async def fetch_server_config_by_well_known_url(
49+
def fetch_server_config_by_well_known_url(
5050
well_known_url: str,
5151
type: AuthServerType,
5252
transpile_data: Optional[Callable[[Record], Record]] = None,
@@ -69,14 +69,13 @@ async def fetch_server_config_by_well_known_url(
6969
"""
7070

7171
try:
72-
async with aiohttp.ClientSession() as session:
73-
async with session.get(well_known_url) as response:
74-
response.raise_for_status()
75-
json = await response.json()
76-
transpiled_data = transpile_data(json) if transpile_data else json
77-
return AuthServerConfig(
78-
metadata=AuthorizationServerMetadata(**transpiled_data), type=type
79-
)
72+
response = requests.get(well_known_url, timeout=10)
73+
response.raise_for_status()
74+
json = response.json()
75+
transpiled_data = transpile_data(json) if transpile_data else json
76+
return AuthServerConfig(
77+
metadata=AuthorizationServerMetadata(**transpiled_data), type=type
78+
)
8079
except pydantic.ValidationError as e:
8180
raise MCPAuthAuthServerException(
8281
AuthServerExceptionCode.INVALID_SERVER_METADATA,
@@ -90,7 +89,7 @@ async def fetch_server_config_by_well_known_url(
9089
) from e
9190

9291

93-
async def fetch_server_config(
92+
def fetch_server_config(
9493
issuer: str,
9594
type: AuthServerType,
9695
transpile_data: Optional[Callable[[Record], Record]] = None,
@@ -141,6 +140,4 @@ async def fetch_server_config(
141140
if type == AuthServerType.OAUTH
142141
else get_oidc_well_known_url(issuer)
143142
)
144-
return await fetch_server_config_by_well_known_url(
145-
well_known_url, type, transpile_data
146-
)
143+
return fetch_server_config_by_well_known_url(well_known_url, type, transpile_data)

pyproject.toml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ keywords = [
1414
"openid-connect",
1515
]
1616
dependencies = [
17-
"aiohttp>=3.11.18",
1817
"pydantic>=2.11.3",
1918
"pyjwt[crypto]>=2.9.0",
19+
"requests>=2.32.3",
2020
"starlette>=0.46.2",
2121
]
2222

@@ -27,9 +27,14 @@ documentation = "https://mcp-auth.dev/docs"
2727

2828
[dependency-groups]
2929
dev = [
30-
"aresponses>=3.0.0",
3130
"black>=24.8.0",
3231
"pytest>=8.3.5",
3332
"pytest-asyncio>=0.26.0",
3433
"pytest-cov>=6.1.1",
34+
"responses>=0.25.7",
35+
"uvicorn>=0.34.2",
3536
]
37+
38+
[tool.coverage.run]
39+
branch = true
40+
source = ["mcpauth"]

samples/server/fast_api.py

Lines changed: 0 additions & 5 deletions
This file was deleted.

samples/server/starlette.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from mcpauth import MCPAuth
2+
from mcpauth.config import MCPAuthConfig
3+
from mcpauth.models import AuthServerType
4+
from mcpauth.utils import fetch_server_config, ServerMetadataPaths
5+
from starlette.applications import Starlette
6+
from starlette.middleware import Middleware
7+
from starlette.responses import JSONResponse
8+
from starlette.requests import Request
9+
10+
mcpAuth = MCPAuth(
11+
MCPAuthConfig(
12+
server=fetch_server_config("https://auth.logto.io/oidc", AuthServerType.OIDC)
13+
)
14+
)
15+
16+
protected_app = Starlette(
17+
middleware=[Middleware(mcpAuth.bearer_auth_middleware("jwt"))]
18+
)
19+
20+
21+
@protected_app.route("/") # type: ignore
22+
async def secret_endpoint(_: Request):
23+
return JSONResponse({"secret": True})
24+
25+
26+
app = Starlette(
27+
debug=True,
28+
)
29+
app.mount(ServerMetadataPaths.OAUTH.value, mcpAuth.metadata_response())
30+
app.mount("/mcp", protected_app)

0 commit comments

Comments
 (0)