Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit f64ccc2

Browse files
committedApr 22, 2025·
Fix type validation issues with AuthConfig
1 parent 983351e commit f64ccc2

File tree

2 files changed

+117
-9
lines changed

2 files changed

+117
-9
lines changed
 

‎fastapi_mcp/types.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,13 @@ class OAuthMetadata(BaseType):
4141
]
4242

4343
authorization_endpoint: Annotated[
44-
StrHttpUrl,
44+
Optional[StrHttpUrl],
4545
Doc(
4646
"""
4747
URL of the authorization server's authorization endpoint.
4848
"""
4949
),
50-
]
50+
] = None
5151

5252
token_endpoint: Annotated[
5353
StrHttpUrl,
@@ -353,18 +353,15 @@ async def authenticate_request(request: Request, token: str = Depends(oauth2_sch
353353

354354
@model_validator(mode="after")
355355
def validate_required_fields(self):
356-
if self.custom_oauth_metadata is None and self.issuer is None:
357-
raise ValueError("'issuer' is required when 'custom_oauth_metadata' is not provided")
356+
if self.custom_oauth_metadata is None and self.issuer is None and not self.dependencies:
357+
raise ValueError("at least one of 'issuer', 'custom_oauth_metadata' or 'dependencies' is required")
358358

359359
if self.setup_proxies:
360360
if self.client_id is None:
361361
raise ValueError("'client_id' is required when 'setup_proxies' is True")
362362

363-
if self.setup_fake_dynamic_registration and not self.client_secret:
364-
raise ValueError("'client_secret' is required when 'setup_fake_dynamic_registration' is True")
365-
366-
if self.setup_fake_dynamic_registration and not self.setup_proxies:
367-
raise ValueError("'setup_fake_dynamic_registration' can only be True when 'setup_proxies' is True")
363+
if self.setup_fake_dynamic_registration and not self.client_secret:
364+
raise ValueError("'client_secret' is required when 'setup_fake_dynamic_registration' is True")
368365

369366
return self
370367

‎tests/test_types_validation.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import pytest
2+
from pydantic import ValidationError
3+
from fastapi import Depends
4+
5+
from fastapi_mcp.types import (
6+
OAuthMetadata,
7+
AuthConfig,
8+
)
9+
10+
11+
class TestOAuthMetadata:
12+
def test_non_empty_lists_validation(self):
13+
for field in [
14+
"scopes_supported",
15+
"response_types_supported",
16+
"grant_types_supported",
17+
"token_endpoint_auth_methods_supported",
18+
"code_challenge_methods_supported",
19+
]:
20+
with pytest.raises(ValidationError, match=f"{field} cannot be empty"):
21+
OAuthMetadata(
22+
issuer="https://example.com",
23+
authorization_endpoint="https://example.com/auth",
24+
token_endpoint="https://example.com/token",
25+
**{field: []},
26+
)
27+
28+
def test_authorization_endpoint_required_for_authorization_code(self):
29+
with pytest.raises(ValidationError) as exc_info:
30+
OAuthMetadata(
31+
issuer="https://example.com",
32+
token_endpoint="https://example.com/token",
33+
grant_types_supported=["authorization_code", "client_credentials"],
34+
)
35+
assert "authorization_endpoint is required when authorization_code grant type is supported" in str(
36+
exc_info.value
37+
)
38+
39+
OAuthMetadata(
40+
issuer="https://example.com",
41+
token_endpoint="https://example.com/token",
42+
authorization_endpoint="https://example.com/auth",
43+
grant_types_supported=["client_credentials"],
44+
)
45+
46+
def test_model_dump_excludes_none(self):
47+
metadata = OAuthMetadata(
48+
issuer="https://example.com",
49+
authorization_endpoint="https://example.com/auth",
50+
token_endpoint="https://example.com/token",
51+
)
52+
53+
dumped = metadata.model_dump()
54+
55+
assert "registration_endpoint" not in dumped
56+
57+
58+
class TestAuthConfig:
59+
def test_required_fields_validation(self):
60+
with pytest.raises(
61+
ValidationError, match="at least one of 'issuer', 'custom_oauth_metadata' or 'dependencies' is required"
62+
):
63+
AuthConfig()
64+
65+
AuthConfig(issuer="https://example.com")
66+
67+
AuthConfig(
68+
custom_oauth_metadata={
69+
"issuer": "https://example.com",
70+
"authorization_endpoint": "https://example.com/auth",
71+
"token_endpoint": "https://example.com/token",
72+
},
73+
)
74+
75+
def dummy_dependency():
76+
pass
77+
78+
AuthConfig(dependencies=[Depends(dummy_dependency)])
79+
80+
def test_client_id_required_for_setup_proxies(self):
81+
with pytest.raises(ValidationError, match="'client_id' is required when 'setup_proxies' is True"):
82+
AuthConfig(
83+
issuer="https://example.com",
84+
setup_proxies=True,
85+
)
86+
87+
AuthConfig(
88+
issuer="https://example.com",
89+
setup_proxies=True,
90+
client_id="test-client-id",
91+
client_secret="test-client-secret",
92+
)
93+
94+
def test_client_secret_required_for_fake_registration(self):
95+
with pytest.raises(
96+
ValidationError, match="'client_secret' is required when 'setup_fake_dynamic_registration' is True"
97+
):
98+
AuthConfig(
99+
issuer="https://example.com",
100+
setup_proxies=True,
101+
client_id="test-client-id",
102+
setup_fake_dynamic_registration=True,
103+
)
104+
105+
AuthConfig(
106+
issuer="https://example.com",
107+
setup_proxies=True,
108+
client_id="test-client-id",
109+
client_secret="test-client-secret",
110+
setup_fake_dynamic_registration=True,
111+
)

0 commit comments

Comments
 (0)
Please sign in to comment.