MCP server separation into Authorization Server (AS) and Resource Server (RS) roles per spec PR #338 (#982)
Co-authored-by: Paul Carleton <paulc@anthropic.com>
This commit is contained in:
parent
d0443a1832
commit
17f9c00c53
1 changed files with 1851 additions and 1603 deletions
|
|
@ -0,0 +1,65 @@
|
|||
"""Example token verifier implementation using OAuth 2.0 Token Introspection (RFC 7662)."""
|
||||
|
||||
import logging
|
||||
|
||||
from mcp.server.auth.provider import AccessToken, TokenVerifier
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IntrospectionTokenVerifier(TokenVerifier):
|
||||
"""Example token verifier that uses OAuth 2.0 Token Introspection (RFC 7662).
|
||||
|
||||
This is a simple example implementation for demonstration purposes.
|
||||
Production implementations should consider:
|
||||
- Connection pooling and reuse
|
||||
- More sophisticated error handling
|
||||
- Rate limiting and retry logic
|
||||
- Comprehensive configuration options
|
||||
"""
|
||||
|
||||
def __init__(self, introspection_endpoint: str):
|
||||
self.introspection_endpoint = introspection_endpoint
|
||||
|
||||
async def verify_token(self, token: str) -> AccessToken | None:
|
||||
"""Verify token via introspection endpoint."""
|
||||
import httpx
|
||||
|
||||
# Validate URL to prevent SSRF attacks
|
||||
if not self.introspection_endpoint.startswith(("https://", "http://localhost", "http://127.0.0.1")):
|
||||
logger.warning(f"Rejecting introspection endpoint with unsafe scheme: {self.introspection_endpoint}")
|
||||
return None
|
||||
|
||||
# Configure secure HTTP client
|
||||
timeout = httpx.Timeout(10.0, connect=5.0)
|
||||
limits = httpx.Limits(max_connections=10, max_keepalive_connections=5)
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
timeout=timeout,
|
||||
limits=limits,
|
||||
verify=True, # Enforce SSL verification
|
||||
) as client:
|
||||
try:
|
||||
response = await client.post(
|
||||
self.introspection_endpoint,
|
||||
data={"token": token},
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
logger.debug(f"Token introspection returned status {response.status_code}")
|
||||
return None
|
||||
|
||||
data = response.json()
|
||||
if not data.get("active", False):
|
||||
return None
|
||||
|
||||
return AccessToken(
|
||||
token=token,
|
||||
client_id=data.get("client_id", "unknown"),
|
||||
scopes=data.get("scope", "").split() if data.get("scope") else [],
|
||||
expires_at=data.get("exp"),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Token introspection failed: {e}")
|
||||
return None
|
||||
Loading…
Reference in a new issue