Skip to content

Commit 24bd45b

Browse files
committed
fix(auth): pad EC P-256 JWK coordinates to fixed 32 bytes
Also sync grant_types_supported test assertions with 6cf977b.
1 parent de25956 commit 24bd45b

3 files changed

Lines changed: 19 additions & 6 deletions

File tree

src/mcp/client/auth/dpop.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,19 @@
2626
_RSA_PUBLIC_EXPONENT = 65537
2727

2828

29-
def _int_to_base64url(num: int) -> str:
30-
"""Encode integer to base64url without padding."""
31-
size = (num.bit_length() + _BITS_PER_BYTE - 1) // _BITS_PER_BYTE
29+
def _int_to_base64url(num: int, *, fixed_length: int | None = None) -> str:
30+
"""Encode integer to base64url without padding.
31+
32+
Args:
33+
num: Non-negative integer to encode.
34+
fixed_length: If set, pad the big-endian representation to exactly
35+
this many bytes. Required for EC coordinates where RFC 7518 §6.2.1
36+
mandates a fixed octet length (e.g. 32 for P-256).
37+
"""
38+
if fixed_length is not None:
39+
size = fixed_length
40+
else:
41+
size = (num.bit_length() + _BITS_PER_BYTE - 1) // _BITS_PER_BYTE
3242
data = num.to_bytes(size, "big")
3343
return base64.urlsafe_b64encode(data).decode().rstrip("=")
3444

@@ -107,11 +117,13 @@ def _key_to_jwk(key: EllipticCurvePrivateKey | RSAPrivateKey) -> dict[str, Any]:
107117
if isinstance(key, EllipticCurvePrivateKey):
108118
pub = key.public_key()
109119
nums = pub.public_numbers()
120+
# P-256 coordinates must be exactly 32 bytes per RFC 7518 §6.2.1.2
121+
ec_coord_length = 32
110122
return {
111123
"kty": "EC",
112124
"crv": "P-256",
113-
"x": _int_to_base64url(nums.x),
114-
"y": _int_to_base64url(nums.y),
125+
"x": _int_to_base64url(nums.x, fixed_length=ec_coord_length),
126+
"y": _int_to_base64url(nums.y, fixed_length=ec_coord_length),
115127
}
116128
# key is RSAPrivateKey (union type)
117129
pub = key.public_key()

tests/client/test_auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1346,7 +1346,7 @@ def test_build_metadata(
13461346
"token_endpoint": Is(token_endpoint),
13471347
"registration_endpoint": Is(registration_endpoint),
13481348
"scopes_supported": ["read", "write", "admin"],
1349-
"grant_types_supported": ["authorization_code", "refresh_token"],
1349+
"grant_types_supported": ["authorization_code", "refresh_token", "client_credentials"],
13501350
"token_endpoint_auth_methods_supported": ["client_secret_post", "client_secret_basic"],
13511351
"service_documentation": Is(service_documentation_url),
13521352
"revocation_endpoint": Is(revocation_endpoint),

tests/server/mcpserver/auth/test_auth_integration.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ async def test_metadata_endpoint(self, test_client: httpx.AsyncClient):
322322
assert metadata["grant_types_supported"] == [
323323
"authorization_code",
324324
"refresh_token",
325+
"client_credentials",
325326
]
326327
assert metadata["service_documentation"] == "https://docs.example.com/"
327328

0 commit comments

Comments
 (0)