"""Token generation utilities for MCP proxy authentication.
This module provides secure token generation and validation utilities
for API authentication in the MCP proxy server.
"""
import hashlib
import hmac
import secrets
import time
import uuid
from dataclasses import dataclass
from typing import Any
@dataclass
class TokenInfo:
"""Information about a generated token."""
token: str
token_type: str
created_at: float
expires_at: float | None = None
class TokenGenerator:
"""Secure token generator for API authentication."""
@staticmethod
def generate_api_key(length: int = 32) -> str:
"""Generate a secure API key using cryptographically strong random bytes.
Args:
length: The length of the token in bytes (default: 32)
Returns:
A hexadecimal string token
"""
return secrets.token_hex(length)
@staticmethod
def generate_uuid_token() -> str:
"""Generate a UUID-based token.
Returns:
A UUID4 string
"""
return str(uuid.uuid4())
@staticmethod
def generate_base64_token(length: int = 32) -> str:
"""Generate a base64-encoded token.
Args:
length: The length of the token in bytes (default: 32)
Returns:
A base64 URL-safe string token
"""
return secrets.token_urlsafe(length)
@staticmethod
def generate_prefixed_token(prefix: str = "mcp", length: int = 32) -> str:
"""Generate a token with a prefix for easy identification.
Args:
prefix: The prefix to add to the token (default: "mcp")
length: The length of the random part in bytes (default: 32)
Returns:
A prefixed token string
"""
token_part = secrets.token_hex(length)
return f"{prefix}_{token_part}"
@staticmethod
def generate_timestamped_token(secret_key: str | None = None) -> TokenInfo:
"""Generate a token with timestamp for expiration tracking.
Args:
secret_key: Optional secret key for HMAC signing
Returns:
TokenInfo object containing token and metadata
"""
timestamp = int(time.time())
random_part = secrets.token_hex(16)
if secret_key:
# Create HMAC signature for additional security
message = f"{timestamp}:{random_part}"
signature = hmac.new(
secret_key.encode(),
message.encode(),
hashlib.sha256
).hexdigest()[:16] # Take first 16 chars
token = f"mcp_{timestamp}_{random_part}_{signature}"
else:
token = f"mcp_{timestamp}_{random_part}"
return TokenInfo(
token=token,
token_type="timestamped",
created_at=float(timestamp),
)
@staticmethod
def validate_token_format(token: str) -> bool:
"""Validate basic token format.
Args:
token: The token to validate
Returns:
True if token format is valid, False otherwise
"""
if not token or len(token) < 8:
return False
# Check for common weak patterns
weak_patterns = [
"123456", "password", "admin", "test", "demo",
"secret", "key", "token", "api"
]
token_lower = token.lower()
for pattern in weak_patterns:
if pattern in token_lower:
return False
return True
@staticmethod
def extract_timestamp_from_token(token: str) -> float | None:
"""Extract timestamp from a timestamped token.
Args:
token: The timestamped token
Returns:
The timestamp as float, or None if not a valid timestamped token
"""
try:
if not token.startswith("mcp_"):
return None
parts = token.split("_")
if len(parts) < 3:
return None
timestamp_str = parts[1]
return float(timestamp_str)
except (ValueError, IndexError):
return None
@classmethod
def generate_secure_token(cls, token_type: str = "api_key", **kwargs: Any) -> TokenInfo:
"""Generate a secure token of the specified type.
Args:
token_type: Type of token to generate ("api_key", "uuid", "base64", "prefixed", "timestamped")
**kwargs: Additional arguments for specific token types
Returns:
TokenInfo object containing the generated token and metadata
"""
current_time = time.time()
if token_type == "api_key":
length = kwargs.get("length", 32)
token = cls.generate_api_key(length)
elif token_type == "uuid":
token = cls.generate_uuid_token()
elif token_type == "base64":
length = kwargs.get("length", 32)
token = cls.generate_base64_token(length)
elif token_type == "prefixed":
prefix = kwargs.get("prefix", "mcp")
length = kwargs.get("length", 32)
token = cls.generate_prefixed_token(prefix, length)
elif token_type == "timestamped":
secret_key = kwargs.get("secret_key")
return cls.generate_timestamped_token(secret_key)
else:
raise ValueError(f"Unknown token type: {token_type}")
return TokenInfo(
token=token,
token_type=token_type,
created_at=current_time,
)
def generate_production_token() -> str:
"""Generate a production-ready API token.
This is a convenience function that generates a secure token
suitable for production use.
Returns:
A secure API token string
"""
return TokenGenerator.generate_prefixed_token("mcp_api", 32)
def main() -> None:
"""Command-line interface for token generation."""
import argparse
parser = argparse.ArgumentParser(description="Generate secure API tokens for MCP proxy")
parser.add_argument(
"--type",
choices=["api_key", "uuid", "base64", "prefixed", "timestamped"],
default="api_key",
help="Type of token to generate (default: api_key)"
)
parser.add_argument(
"--length",
type=int,
default=32,
help="Length of the token in bytes (default: 32)"
)
parser.add_argument(
"--prefix",
default="mcp",
help="Prefix for prefixed tokens (default: mcp)"
)
parser.add_argument(
"--count",
type=int,
default=1,
help="Number of tokens to generate (default: 1)"
)
parser.add_argument(
"--production",
action="store_true",
help="Generate production-ready token (overrides other options)"
)
args = parser.parse_args()
if args.production:
print("Production Token:")
print(generate_production_token())
print("\nStore this token securely and do not share it!")
return
print(f"Generating {args.count} token(s) of type '{args.type}':")
print()
for i in range(args.count):
if args.count > 1:
print(f"Token {i + 1}:")
token_info = TokenGenerator.generate_secure_token(
token_type=args.type,
length=args.length,
prefix=args.prefix,
)
print(f" Token: {token_info.token}")
print(f" Type: {token_info.token_type}")
print(f" Length: {len(token_info.token)} characters")
if args.count > 1:
print()
print("Store these tokens securely and do not share them!")
if __name__ == "__main__":
main()