Skip to content

Commit bf11ebe

Browse files
committed
chore(keycardai-mcp): rename and test verifier
1 parent 5faa3f8 commit bf11ebe

File tree

7 files changed

+518
-42
lines changed

7 files changed

+518
-42
lines changed

justfile

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,8 @@ build:
99

1010
# Run tests for all packages
1111
test: build
12-
just test-oauth
13-
14-
# Run tests for OAuth package
15-
test-oauth:
16-
cd packages/oauth && uv run --extra test pytest tests/ -v
12+
just test-package oauth
13+
just test-package mcp
1714

1815
# Run tests for a specific package
1916
test-package PACKAGE:

packages/mcp/pyproject.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ license = { text = "MIT" }
88
authors = [{ name = "KeyCard AI", email = "support@keycard.ai" }]
99
dependencies = [
1010
"keycardai-oauth",
11-
"mcp>=1.13.1",
11+
"mcp==1.14.0",
1212
"pydantic>=2.11.7",
1313
]
1414
keywords = ["mcp", "model-context-protocol", "authentication", "authorization", "ai", "llm"]
@@ -28,6 +28,13 @@ classifiers = [
2828
"License :: OSI Approved :: MIT License",
2929
]
3030

31+
[project.optional-dependencies]
32+
test = [
33+
"pytest>=8.4.1",
34+
"pytest-asyncio>=1.1.0",
35+
"pytest-cov>=6.2.1",
36+
]
37+
3138
[project.urls]
3239
Homepage = "https://github.com/keycardai/python-sdk"
3340
Repository = "https://github.com/keycardai/python-sdk"
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .provider import AccessContext, KeycardAuthProvider
2-
from .verifier import KeycardTokenVerifier
1+
from .provider import AccessContext, AuthProvider
2+
from .verifier import TokenVerifier
33

4-
__all__ = ["KeycardAuthProvider", "AccessContext", "KeycardTokenVerifier"]
4+
__all__ = ["AuthProvider", "AccessContext", "TokenVerifier"]

packages/mcp/src/keycardai/mcp/server/auth/provider.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from keycardai.oauth import AsyncClient, AuthStrategy, Client, ClientConfig, NoneAuth
1212
from keycardai.oauth.types.models import TokenResponse
1313

14-
from .verifier import KeycardTokenVerifier
14+
from .verifier import TokenVerifier
1515

1616

1717
class AccessContext:
@@ -41,17 +41,17 @@ def access(self, resource: str) -> TokenResponse:
4141
raise KeyError(f"Resource '{resource}' not granted. Available resources: {list(self._access_tokens.keys())}")
4242
return self._access_tokens[resource]
4343

44-
class KeycardAuthProvider:
44+
class AuthProvider:
4545
"""KeyCard authentication provider with token exchange capabilities.
4646
4747
This provider handles both authentication (token verification) and authorization
4848
(token exchange for resource access) in MCP servers.
4949
5050
Example:
5151
```python
52-
from keycardai.mcp.server import KeycardAuthProvider
52+
from keycardai.mcp.server import AuthProvider
5353
54-
provider = KeycardAuthProvider(
54+
provider = AuthProvider(
5555
zone_url="https://abc1234.keycard.cloud",
5656
mcp_server_name="My MCP Server"
5757
)
@@ -68,7 +68,6 @@ def __init__(self,
6868
mcp_server_name: str | None = None,
6969
required_scopes: list[str] | None = None,
7070
mcp_server_url: AnyHttpUrl | str | None = None,
71-
client_name: str | None = None,
7271
auth: AuthStrategy = NoneAuth):
7372
"""Initialize the KeyCard auth provider.
7473
@@ -77,13 +76,13 @@ def __init__(self,
7776
mcp_server_name: Human-readable name for the MCP server
7877
required_scopes: Required scopes for token validation
7978
mcp_server_url: Resource server URL (defaults to server URL)
80-
client_name: OAuth client name for registration (defaults to mcp_server_name)
79+
auth: Authentication strategy for OAuth operations
8180
"""
8281
self.zone_url = zone_url
8382
self.mcp_server_name = mcp_server_name
8483
self.required_scopes = required_scopes
8584
self.mcp_server_url = mcp_server_url
86-
self.client_name = client_name or mcp_server_name or "MCP Server OAuth Client"
85+
self.client_name = mcp_server_name or "MCP Server OAuth Client"
8786

8887
self._client: AsyncClient | None = None
8988
self._init_lock: asyncio.Lock | None = None
@@ -135,11 +134,11 @@ def get_auth_settings(self) -> AuthSettings:
135134
}
136135
)
137136

138-
def get_token_verifier(self) -> KeycardTokenVerifier:
137+
def get_token_verifier(self) -> TokenVerifier:
139138
"""Get a token verifier for the MCP server."""
140139
with Client(self.zone_url) as client:
141140
jwks_uri = client.discover_server_metadata().jwks_uri
142-
return KeycardTokenVerifier(
141+
return TokenVerifier(
143142
required_scopes=self.required_scopes,
144143
issuer=self.zone_url,
145144
jwks_uri=jwks_uri,

packages/mcp/src/keycardai/mcp/server/auth/verifier.py

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from typing import Any
33

44
from mcp.server.auth.provider import AccessToken
5-
from mcp.server.auth.settings import AuthSettings
65

76
from keycardai.oauth.utils.jwt import (
87
get_header,
@@ -13,12 +12,12 @@
1312
from ._cache import JWKSCache, JWKSKey
1413

1514

16-
class KeycardTokenVerifier:
15+
class TokenVerifier:
1716
"""Token verifier for KeyCard zone-issued tokens."""
1817

1918
def __init__(
2019
self,
21-
issuer: str | None = None,
20+
issuer: str,
2221
required_scopes: list[str] | None = None,
2322
jwks_uri: str | None = None,
2423
allowed_algorithms: list[str] = None,
@@ -27,12 +26,14 @@ def __init__(
2726
"""Initialize the KeyCard token verifier.
2827
2928
Args:
30-
issuer: Expected token issuer
29+
issuer: Expected token issuer (required)
3130
required_scopes: Required scopes for token validation
3231
jwks_uri: JWKS endpoint URL for key fetching
3332
allowed_algorithms: JWT algorithms (default RS256)
3433
cache_ttl: JWKS cache TTL in seconds (default 300 = 5 minutes)
3534
"""
35+
if not issuer:
36+
raise ValueError("Issuer is required for token verification")
3637
if allowed_algorithms is None:
3738
allowed_algorithms = ["RS256"]
3839
self.issuer = issuer
@@ -61,7 +62,10 @@ async def _get_verification_key(self, token: str) -> JWKSKey:
6162
verification_key = await get_verification_key(token, self.jwks_uri)
6263

6364
self._jwks_cache.set_key(kid, verification_key, algorithm)
64-
return self._jwks_cache.get_key(kid)
65+
cached_key = self._jwks_cache.get_key(kid)
66+
if cached_key is None:
67+
raise ValueError("Failed to cache verification key")
68+
return cached_key
6569

6670
def clear_cache(self) -> None:
6771
"""Clear the JWKS key cache."""
@@ -105,7 +109,7 @@ async def verify_token(self, token: str) -> AccessToken | None:
105109
if jwt_access_token.exp < time.time():
106110
return None
107111

108-
if self.issuer and jwt_access_token.iss != self.issuer:
112+
if jwt_access_token.iss != self.issuer:
109113
return None
110114

111115
if self.required_scopes:
@@ -132,17 +136,3 @@ async def verify_token(self, token: str) -> AccessToken | None:
132136
except Exception:
133137
return None
134138

135-
136-
def get_token_verifier(auth_settings: AuthSettings) -> KeycardTokenVerifier:
137-
"""Get a token verifier for the MCP server.
138-
139-
Creates a token verifier with JWKS support for cryptographic signature verification
140-
and configurable key caching.
141-
"""
142-
return KeycardTokenVerifier(
143-
required_scopes=auth_settings.required_scopes,
144-
issuer=str(auth_settings.issuer_url).rstrip("/"),
145-
jwks_uri=getattr(auth_settings, "jwks_uri", None),
146-
algorithm=getattr(auth_settings, "algorithm", "RS256"),
147-
cache_ttl=getattr(auth_settings, "cache_ttl", 300),
148-
)

0 commit comments

Comments
 (0)