From 23dab1ab6e9593e9642afd1c07d7fb2d7819fe44 Mon Sep 17 00:00:00 2001 From: Vasil Chomakov Date: Wed, 22 Oct 2025 18:20:54 +0300 Subject: [PATCH 1/3] feat: implement EntraID auth for Azure with Service Principal, Managed Identity and Default Credential support --- pyproject.toml | 1 + src/common/config.py | 130 ++++++++++++++++++++++++++ src/common/connection.py | 29 +++++- src/common/entraid_auth.py | 160 ++++++++++++++++++++++++++++++++ src/main.py | 96 ++++++++++++++++++- uv.lock | 182 +++++++++++++++++++++++++------------ 6 files changed, 539 insertions(+), 59 deletions(-) create mode 100644 src/common/entraid_auth.py diff --git a/pyproject.toml b/pyproject.toml index fb51fb1..3fb90b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ dependencies = [ "dotenv>=0.9.9", "numpy>=2.2.4", "click>=8.0.0", + "redis-entraid>=1.0.0", ] [project.scripts] diff --git a/src/common/config.py b/src/common/config.py index ec37ad0..790bd2a 100644 --- a/src/common/config.py +++ b/src/common/config.py @@ -5,6 +5,13 @@ load_dotenv() +# Default values for Entra ID authentication +DEFAULT_TOKEN_EXPIRATION_REFRESH_RATIO = 0.9 +DEFAULT_LOWER_REFRESH_BOUND_MILLIS = 30000 # 30 seconds +DEFAULT_TOKEN_REQUEST_EXECUTION_TIMEOUT_MS = 10000 # 10 seconds +DEFAULT_RETRY_MAX_ATTEMPTS = 3 +DEFAULT_RETRY_DELAY_MS = 100 + REDIS_CFG = { "host": os.getenv("REDIS_HOST", "127.0.0.1"), "port": int(os.getenv("REDIS_PORT", 6379)), @@ -20,6 +27,55 @@ "db": int(os.getenv("REDIS_DB", 0)), } +# Entra ID Authentication Configuration +ENTRAID_CFG = { + # Authentication flow selection + "auth_flow": os.getenv( + "REDIS_ENTRAID_AUTH_FLOW", None + ), # service_principal, managed_identity, default_credential + # Service Principal Authentication + "client_id": os.getenv("REDIS_ENTRAID_CLIENT_ID", None), + "client_secret": os.getenv("REDIS_ENTRAID_CLIENT_SECRET", None), + "tenant_id": os.getenv("REDIS_ENTRAID_TENANT_ID", None), + # Managed Identity Authentication + "identity_type": os.getenv( + "REDIS_ENTRAID_IDENTITY_TYPE", "system_assigned" + ), # system_assigned, user_assigned + "user_assigned_identity_client_id": os.getenv( + "REDIS_ENTRAID_USER_ASSIGNED_CLIENT_ID", None + ), + # Default Azure Credential Authentication + "scopes": os.getenv("REDIS_ENTRAID_SCOPES", "https://redis.azure.com/.default"), + # Token lifecycle configuration + "token_expiration_refresh_ratio": float( + os.getenv( + "REDIS_ENTRAID_TOKEN_EXPIRATION_REFRESH_RATIO", + DEFAULT_TOKEN_EXPIRATION_REFRESH_RATIO, + ) + ), + "lower_refresh_bound_millis": int( + os.getenv( + "REDIS_ENTRAID_LOWER_REFRESH_BOUND_MILLIS", + DEFAULT_LOWER_REFRESH_BOUND_MILLIS, + ) + ), + "token_request_execution_timeout_ms": int( + os.getenv( + "REDIS_ENTRAID_TOKEN_REQUEST_EXECUTION_TIMEOUT_MS", + DEFAULT_TOKEN_REQUEST_EXECUTION_TIMEOUT_MS, + ) + ), + # Retry configuration + "retry_max_attempts": int( + os.getenv("REDIS_ENTRAID_RETRY_MAX_ATTEMPTS", DEFAULT_RETRY_MAX_ATTEMPTS) + ), + "retry_delay_ms": int( + os.getenv("REDIS_ENTRAID_RETRY_DELAY_MS", DEFAULT_RETRY_DELAY_MS) + ), + # Resource configuration + "resource": os.getenv("REDIS_ENTRAID_RESOURCE", "https://redis.azure.com/"), +} + def parse_redis_uri(uri: str) -> dict: """Parse a Redis URI and return connection parameters.""" @@ -99,3 +155,77 @@ def set_redis_config_from_cli(config: dict): else: # Convert other values to strings REDIS_CFG[key] = str(value) if value is not None else None + + +def set_entraid_config_from_cli(config: dict): + """Update Entra ID configuration from CLI parameters.""" + for key, value in config.items(): + if value is not None: + if key in ["token_expiration_refresh_ratio"]: + # Keep float values as floats + ENTRAID_CFG[key] = float(value) + elif key in [ + "lower_refresh_bound_millis", + "token_request_execution_timeout_ms", + "retry_max_attempts", + "retry_delay_ms", + ]: + # Keep integer values as integers + ENTRAID_CFG[key] = int(value) + else: + # Convert other values to strings + ENTRAID_CFG[key] = str(value) + + +def is_entraid_auth_enabled() -> bool: + """Check if Entra ID authentication is enabled.""" + return ENTRAID_CFG["auth_flow"] is not None + + +def get_entraid_auth_flow() -> str: + """Get the configured Entra ID authentication flow.""" + return ENTRAID_CFG["auth_flow"] + + +def validate_entraid_config() -> tuple[bool, str]: + """Validate Entra ID configuration based on the selected auth flow. + + Returns: + tuple: (is_valid, error_message) + """ + auth_flow = ENTRAID_CFG["auth_flow"] + + if not auth_flow: + return True, "" # No Entra ID auth configured, which is valid + + if auth_flow == "service_principal": + required_fields = ["client_id", "client_secret", "tenant_id"] + missing_fields = [field for field in required_fields if not ENTRAID_CFG[field]] + if missing_fields: + return ( + False, + f"Service principal authentication requires: {', '.join(missing_fields)}", + ) + + elif auth_flow == "managed_identity": + identity_type = ENTRAID_CFG["identity_type"] + if ( + identity_type == "user_assigned" + and not ENTRAID_CFG["user_assigned_identity_client_id"] + ): + return ( + False, + "User-assigned managed identity requires user_assigned_identity_client_id", + ) + + elif auth_flow == "default_credential": + # Default credential doesn't require specific configuration + pass + + else: + return ( + False, + f"Invalid auth_flow: {auth_flow}. Must be one of: service_principal, managed_identity, default_credential", + ) + + return True, "" diff --git a/src/common/connection.py b/src/common/connection.py index 7c5554e..176a096 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -5,7 +5,11 @@ from redis import Redis from redis.cluster import RedisCluster -from src.common.config import REDIS_CFG +from src.common.config import REDIS_CFG, is_entraid_auth_enabled +from src.common.entraid_auth import ( + create_credential_provider, + EntraIDAuthenticationError, +) from src.version import __version__ _logger = logging.getLogger(__name__) @@ -18,6 +22,17 @@ class RedisConnectionManager: def get_connection(cls, decode_responses=True) -> Redis: if cls._instance is None: try: + # Create Entra ID credential provider if configured + credential_provider = None + if is_entraid_auth_enabled(): + try: + credential_provider = create_credential_provider() + except EntraIDAuthenticationError as e: + _logger.error( + "Failed to create Entra ID credential provider: %s", e + ) + raise + if REDIS_CFG["cluster_mode"]: redis_class: Type[Union[Redis, RedisCluster]] = ( redis.cluster.RedisCluster @@ -37,6 +52,12 @@ def get_connection(cls, decode_responses=True) -> Redis: "lib_name": f"redis-py(mcp-server_v{__version__})", "max_connections_per_node": 10, } + + # Add credential provider if available + if credential_provider: + connection_params["credential_provider"] = credential_provider + # Note: Azure Redis Enterprise with EntraID uses plain text connections + # SSL setting is controlled by REDIS_SSL environment variable else: redis_class: Type[Union[Redis, RedisCluster]] = redis.Redis connection_params = { @@ -56,6 +77,12 @@ def get_connection(cls, decode_responses=True) -> Redis: "max_connections": 10, } + # Add credential provider if available + if credential_provider: + connection_params["credential_provider"] = credential_provider + # Note: Azure Redis Enterprise with EntraID uses plain text connections + # SSL setting is controlled by REDIS_SSL environment variable + cls._instance = redis_class(**connection_params) except redis.exceptions.ConnectionError: diff --git a/src/common/entraid_auth.py b/src/common/entraid_auth.py new file mode 100644 index 0000000..68d46c5 --- /dev/null +++ b/src/common/entraid_auth.py @@ -0,0 +1,160 @@ +""" +Entra ID authentication provider factory for Redis MCP Server. + +This module provides factory methods to create credential providers for different +Azure authentication flows based on configuration. +""" + +import logging + +from src.common.config import ( + ENTRAID_CFG, + is_entraid_auth_enabled, + validate_entraid_config, +) + +_logger = logging.getLogger(__name__) + +# Reduce Azure SDK logging verbosity +logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(logging.WARNING) +logging.getLogger("azure.identity").setLevel(logging.WARNING) +logging.getLogger("redis.auth.token_manager").setLevel(logging.WARNING) + +# Import redis-entraid components only when needed +try: + from redis_entraid.cred_provider import ( + create_from_default_azure_credential, + create_from_managed_identity, + create_from_service_principal, + ManagedIdentityType, + TokenManagerConfig, + RetryPolicy, + ) + + ENTRAID_AVAILABLE = True +except ImportError: + _logger.warning( + "redis-entraid package not available. Entra ID authentication will be disabled." + ) + ENTRAID_AVAILABLE = False + + +class EntraIDAuthenticationError(Exception): + """Exception raised for Entra ID authentication configuration errors.""" + + pass + + +def create_credential_provider(): + """ + Create an Entra ID credential provider based on the current configuration. + + Returns: + Credential provider instance or None if Entra ID auth is not configured. + + Raises: + EntraIDAuthenticationError: If configuration is invalid or required packages are missing. + """ + if not is_entraid_auth_enabled(): + return None + + if not ENTRAID_AVAILABLE: + raise EntraIDAuthenticationError( + "redis-entraid package is required for Entra ID authentication. " + "Install it with: pip install redis-entraid" + ) + + # Validate configuration + is_valid, error_message = validate_entraid_config() + if not is_valid: + raise EntraIDAuthenticationError( + f"Invalid Entra ID configuration: {error_message}" + ) + + auth_flow = ENTRAID_CFG["auth_flow"] + + try: + # Create token manager configuration + token_manager_config = _create_token_manager_config() + + if auth_flow == "service_principal": + return _create_service_principal_provider(token_manager_config) + elif auth_flow == "managed_identity": + return _create_managed_identity_provider(token_manager_config) + elif auth_flow == "default_credential": + return _create_default_credential_provider(token_manager_config) + else: + raise EntraIDAuthenticationError( + f"Unsupported authentication flow: {auth_flow}" + ) + + except Exception as e: + _logger.error("Failed to create Entra ID credential provider: %s", e) + raise EntraIDAuthenticationError(f"Failed to create credential provider: {e}") + + +def _create_token_manager_config(): + """Create TokenManagerConfig from current configuration.""" + retry_policy = RetryPolicy( + max_attempts=ENTRAID_CFG["retry_max_attempts"], + delay_in_ms=ENTRAID_CFG["retry_delay_ms"], + ) + + return TokenManagerConfig( + expiration_refresh_ratio=ENTRAID_CFG["token_expiration_refresh_ratio"], + lower_refresh_bound_millis=ENTRAID_CFG["lower_refresh_bound_millis"], + token_request_execution_timeout_in_ms=ENTRAID_CFG[ + "token_request_execution_timeout_ms" + ], + retry_policy=retry_policy, + ) + + +def _create_service_principal_provider(token_manager_config): + """Create service principal credential provider.""" + + return create_from_service_principal( + client_id=ENTRAID_CFG["client_id"], + client_credential=ENTRAID_CFG["client_secret"], + tenant_id=ENTRAID_CFG["tenant_id"], + token_manager_config=token_manager_config, + ) + + +def _create_managed_identity_provider(token_manager_config): + """Create managed identity credential provider.""" + identity_type_str = ENTRAID_CFG["identity_type"] + + if identity_type_str == "system_assigned": + identity_type = ManagedIdentityType.SYSTEM_ASSIGNED + + return create_from_managed_identity( + identity_type=identity_type, + resource=ENTRAID_CFG["resource"], + token_manager_config=token_manager_config, + ) + + elif identity_type_str == "user_assigned": + identity_type = ManagedIdentityType.USER_ASSIGNED + + return create_from_managed_identity( + identity_type=identity_type, + resource=ENTRAID_CFG["resource"], + client_id=ENTRAID_CFG["user_assigned_identity_client_id"], + token_manager_config=token_manager_config, + ) + + else: + raise EntraIDAuthenticationError(f"Invalid identity type: {identity_type_str}") + + +def _create_default_credential_provider(token_manager_config): + """Create default Azure credential provider.""" + + # Parse scopes from configuration + scopes_str = ENTRAID_CFG["scopes"] + scopes = tuple(scope.strip() for scope in scopes_str.split(",")) + + return create_from_default_azure_credential( + scopes=scopes, token_manager_config=token_manager_config + ) diff --git a/src/main.py b/src/main.py index e77a669..82d5a78 100644 --- a/src/main.py +++ b/src/main.py @@ -3,7 +3,11 @@ import click -from src.common.config import parse_redis_uri, set_redis_config_from_cli +from src.common.config import ( + parse_redis_uri, + set_redis_config_from_cli, + set_entraid_config_from_cli, +) from src.common.server import mcp from src.common.logging_utils import configure_logging @@ -38,6 +42,52 @@ def run(self): ) @click.option("--ssl-ca-certs", help="Path to CA certificates file") @click.option("--cluster-mode", is_flag=True, help="Enable Redis cluster mode") +# Entra ID Authentication Options +@click.option( + "--entraid-auth-flow", + type=click.Choice(["service_principal", "managed_identity", "default_credential"]), + help="Entra ID authentication flow", +) +@click.option( + "--entraid-client-id", + help="Entra ID client ID (for service principal or user-assigned managed identity)", +) +@click.option( + "--entraid-client-secret", help="Entra ID client secret (for service principal)" +) +@click.option("--entraid-tenant-id", help="Entra ID tenant ID (for service principal)") +@click.option( + "--entraid-identity-type", + type=click.Choice(["system_assigned", "user_assigned"]), + default="system_assigned", + help="Managed identity type", +) +@click.option( + "--entraid-scopes", + default="https://redis.azure.com/.default", + help="Entra ID scopes (comma-separated)", +) +@click.option( + "--entraid-resource", default="https://redis.azure.com/", help="Entra ID resource" +) +@click.option( + "--entraid-token-refresh-ratio", + type=float, + default=0.9, + help="Token expiration refresh ratio", +) +@click.option( + "--entraid-retry-max-attempts", + type=int, + default=3, + help="Maximum retry attempts for token requests", +) +@click.option( + "--entraid-retry-delay-ms", + type=int, + default=100, + help="Retry delay in milliseconds", +) def cli( url, host, @@ -52,6 +102,16 @@ def cli( ssl_cert_reqs, ssl_ca_certs, cluster_mode, + entraid_auth_flow, + entraid_client_id, + entraid_client_secret, + entraid_tenant_id, + entraid_identity_type, + entraid_scopes, + entraid_resource, + entraid_token_refresh_ratio, + entraid_retry_max_attempts, + entraid_retry_delay_ms, ): """Redis MCP Server - Model Context Protocol server for Redis.""" @@ -92,6 +152,40 @@ def cli( set_redis_config_from_cli(config) + # Handle Entra ID authentication configuration + entraid_config = {} + if entraid_auth_flow: + entraid_config["auth_flow"] = entraid_auth_flow + if entraid_client_id: + entraid_config["client_id"] = entraid_client_id + if entraid_client_secret: + entraid_config["client_secret"] = entraid_client_secret + if entraid_tenant_id: + entraid_config["tenant_id"] = entraid_tenant_id + if entraid_identity_type: + entraid_config["identity_type"] = entraid_identity_type + if entraid_scopes: + entraid_config["scopes"] = entraid_scopes + if entraid_resource: + entraid_config["resource"] = entraid_resource + if entraid_token_refresh_ratio is not None: + entraid_config["token_expiration_refresh_ratio"] = entraid_token_refresh_ratio + if entraid_retry_max_attempts is not None: + entraid_config["retry_max_attempts"] = entraid_retry_max_attempts + if entraid_retry_delay_ms is not None: + entraid_config["retry_delay_ms"] = entraid_retry_delay_ms + + # For user-assigned managed identity, use client_id as user_assigned_identity_client_id + if ( + entraid_auth_flow == "managed_identity" + and entraid_identity_type == "user_assigned" + and entraid_client_id + ): + entraid_config["user_assigned_identity_client_id"] = entraid_client_id + + if entraid_config: + set_entraid_config_from_cli(entraid_config) + # Start the server server = RedisMCPServer() server.run() diff --git a/uv.lock b/uv.lock index 67cf8ce..3597d3d 100644 --- a/uv.lock +++ b/uv.lock @@ -60,6 +60,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f8/aa/5082412d1ee302e9e7d80b6949bc4d2a8fa1149aaab610c5fc24709605d6/authlib-1.6.5-py2.py3-none-any.whl", hash = "sha256:3e0e0507807f842b02175507bdee8957a1d5707fd4afb17c32fb43fee90b6e3a", size = 243608, upload-time = "2025-10-02T13:36:07.637Z" }, ] +[[package]] +name = "azure-core" +version = "1.36.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/c4/d4ff3bc3ddf155156460bff340bbe9533f99fac54ddea165f35a8619f162/azure_core-1.36.0.tar.gz", hash = "sha256:22e5605e6d0bf1d229726af56d9e92bc37b6e726b141a18be0b4d424131741b7", size = 351139, upload-time = "2025-10-15T00:33:49.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/3c/b90d5afc2e47c4a45f4bba00f9c3193b0417fad5ad3bb07869f9d12832aa/azure_core-1.36.0-py3-none-any.whl", hash = "sha256:fee9923a3a753e94a259563429f3644aaf05c486d45b1215d098115102d91d3b", size = 213302, upload-time = "2025-10-15T00:33:51.058Z" }, +] + +[[package]] +name = "azure-identity" +version = "1.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "cryptography" }, + { name = "msal" }, + { name = "msal-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ee/89/7d170fab0b85d9650cdb7abda087e849644beb52bd28f6804620dd0cecd9/azure_identity-1.20.0.tar.gz", hash = "sha256:40597210d56c83e15031b0fe2ea3b26420189e1e7f3e20bdbb292315da1ba014", size = 264447, upload-time = "2025-02-12T00:40:41.225Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/aa/819513c1dbef990af690bb5eefb5e337f8698d75dfdb7302528f50ce1994/azure_identity-1.20.0-py3-none-any.whl", hash = "sha256:5f23fc4889a66330e840bd78830287e14f3761820fe3c5f77ac875edcb9ec998", size = 188243, upload-time = "2025-02-12T00:40:44.99Z" }, +] + [[package]] name = "backports-asyncio-runner" version = "1.2.0" @@ -449,67 +478,49 @@ toml = [ [[package]] name = "cryptography" -version = "46.0.2" +version = "45.0.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4a/9b/e301418629f7bfdf72db9e80ad6ed9d1b83c487c471803eaa6464c511a01/cryptography-46.0.2.tar.gz", hash = "sha256:21b6fc8c71a3f9a604f028a329e5560009cc4a3a828bfea5fcba8eb7647d88fe", size = 749293, upload-time = "2025-10-01T00:29:11.856Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/98/7a8df8c19a335c8028414738490fc3955c0cecbfdd37fcc1b9c3d04bd561/cryptography-46.0.2-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:f3e32ab7dd1b1ef67b9232c4cf5e2ee4cd517d4316ea910acaaa9c5712a1c663", size = 7261255, upload-time = "2025-10-01T00:27:22.947Z" }, - { url = "https://files.pythonhosted.org/packages/c6/38/b2adb2aa1baa6706adc3eb746691edd6f90a656a9a65c3509e274d15a2b8/cryptography-46.0.2-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1fd1a69086926b623ef8126b4c33d5399ce9e2f3fac07c9c734c2a4ec38b6d02", size = 4297596, upload-time = "2025-10-01T00:27:25.258Z" }, - { url = "https://files.pythonhosted.org/packages/e4/27/0f190ada240003119488ae66c897b5e97149292988f556aef4a6a2a57595/cryptography-46.0.2-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb7fb9cd44c2582aa5990cf61a4183e6f54eea3172e54963787ba47287edd135", size = 4450899, upload-time = "2025-10-01T00:27:27.458Z" }, - { url = "https://files.pythonhosted.org/packages/85/d5/e4744105ab02fdf6bb58ba9a816e23b7a633255987310b4187d6745533db/cryptography-46.0.2-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9066cfd7f146f291869a9898b01df1c9b0e314bfa182cef432043f13fc462c92", size = 4300382, upload-time = "2025-10-01T00:27:29.091Z" }, - { url = "https://files.pythonhosted.org/packages/33/fb/bf9571065c18c04818cb07de90c43fc042c7977c68e5de6876049559c72f/cryptography-46.0.2-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:97e83bf4f2f2c084d8dd792d13841d0a9b241643151686010866bbd076b19659", size = 4017347, upload-time = "2025-10-01T00:27:30.767Z" }, - { url = "https://files.pythonhosted.org/packages/35/72/fc51856b9b16155ca071080e1a3ad0c3a8e86616daf7eb018d9565b99baa/cryptography-46.0.2-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:4a766d2a5d8127364fd936572c6e6757682fc5dfcbdba1632d4554943199f2fa", size = 4983500, upload-time = "2025-10-01T00:27:32.741Z" }, - { url = "https://files.pythonhosted.org/packages/c1/53/0f51e926799025e31746d454ab2e36f8c3f0d41592bc65cb9840368d3275/cryptography-46.0.2-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:fab8f805e9675e61ed8538f192aad70500fa6afb33a8803932999b1049363a08", size = 4482591, upload-time = "2025-10-01T00:27:34.869Z" }, - { url = "https://files.pythonhosted.org/packages/86/96/4302af40b23ab8aa360862251fb8fc450b2a06ff24bc5e261c2007f27014/cryptography-46.0.2-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:1e3b6428a3d56043bff0bb85b41c535734204e599c1c0977e1d0f261b02f3ad5", size = 4300019, upload-time = "2025-10-01T00:27:37.029Z" }, - { url = "https://files.pythonhosted.org/packages/9b/59/0be12c7fcc4c5e34fe2b665a75bc20958473047a30d095a7657c218fa9e8/cryptography-46.0.2-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:1a88634851d9b8de8bb53726f4300ab191d3b2f42595e2581a54b26aba71b7cc", size = 4950006, upload-time = "2025-10-01T00:27:40.272Z" }, - { url = "https://files.pythonhosted.org/packages/55/1d/42fda47b0111834b49e31590ae14fd020594d5e4dadd639bce89ad790fba/cryptography-46.0.2-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:be939b99d4e091eec9a2bcf41aaf8f351f312cd19ff74b5c83480f08a8a43e0b", size = 4482088, upload-time = "2025-10-01T00:27:42.668Z" }, - { url = "https://files.pythonhosted.org/packages/17/50/60f583f69aa1602c2bdc7022dae86a0d2b837276182f8c1ec825feb9b874/cryptography-46.0.2-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f13b040649bc18e7eb37936009b24fd31ca095a5c647be8bb6aaf1761142bd1", size = 4425599, upload-time = "2025-10-01T00:27:44.616Z" }, - { url = "https://files.pythonhosted.org/packages/d1/57/d8d4134cd27e6e94cf44adb3f3489f935bde85f3a5508e1b5b43095b917d/cryptography-46.0.2-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9bdc25e4e01b261a8fda4e98618f1c9515febcecebc9566ddf4a70c63967043b", size = 4697458, upload-time = "2025-10-01T00:27:46.209Z" }, - { url = "https://files.pythonhosted.org/packages/d1/2b/531e37408573e1da33adfb4c58875013ee8ac7d548d1548967d94a0ae5c4/cryptography-46.0.2-cp311-abi3-win32.whl", hash = "sha256:8b9bf67b11ef9e28f4d78ff88b04ed0929fcd0e4f70bb0f704cfc32a5c6311ee", size = 3056077, upload-time = "2025-10-01T00:27:48.424Z" }, - { url = "https://files.pythonhosted.org/packages/a8/cd/2f83cafd47ed2dc5a3a9c783ff5d764e9e70d3a160e0df9a9dcd639414ce/cryptography-46.0.2-cp311-abi3-win_amd64.whl", hash = "sha256:758cfc7f4c38c5c5274b55a57ef1910107436f4ae842478c4989abbd24bd5acb", size = 3512585, upload-time = "2025-10-01T00:27:50.521Z" }, - { url = "https://files.pythonhosted.org/packages/00/36/676f94e10bfaa5c5b86c469ff46d3e0663c5dc89542f7afbadac241a3ee4/cryptography-46.0.2-cp311-abi3-win_arm64.whl", hash = "sha256:218abd64a2e72f8472c2102febb596793347a3e65fafbb4ad50519969da44470", size = 2927474, upload-time = "2025-10-01T00:27:52.91Z" }, - { url = "https://files.pythonhosted.org/packages/6f/cc/47fc6223a341f26d103cb6da2216805e08a37d3b52bee7f3b2aee8066f95/cryptography-46.0.2-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:bda55e8dbe8533937956c996beaa20266a8eca3570402e52ae52ed60de1faca8", size = 7198626, upload-time = "2025-10-01T00:27:54.8Z" }, - { url = "https://files.pythonhosted.org/packages/93/22/d66a8591207c28bbe4ac7afa25c4656dc19dc0db29a219f9809205639ede/cryptography-46.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7155c0b004e936d381b15425273aee1cebc94f879c0ce82b0d7fecbf755d53a", size = 4287584, upload-time = "2025-10-01T00:27:57.018Z" }, - { url = "https://files.pythonhosted.org/packages/8c/3e/fac3ab6302b928e0398c269eddab5978e6c1c50b2b77bb5365ffa8633b37/cryptography-46.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a61c154cc5488272a6c4b86e8d5beff4639cdb173d75325ce464d723cda0052b", size = 4433796, upload-time = "2025-10-01T00:27:58.631Z" }, - { url = "https://files.pythonhosted.org/packages/7d/d8/24392e5d3c58e2d83f98fe5a2322ae343360ec5b5b93fe18bc52e47298f5/cryptography-46.0.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:9ec3f2e2173f36a9679d3b06d3d01121ab9b57c979de1e6a244b98d51fea1b20", size = 4292126, upload-time = "2025-10-01T00:28:00.643Z" }, - { url = "https://files.pythonhosted.org/packages/ed/38/3d9f9359b84c16c49a5a336ee8be8d322072a09fac17e737f3bb11f1ce64/cryptography-46.0.2-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2fafb6aa24e702bbf74de4cb23bfa2c3beb7ab7683a299062b69724c92e0fa73", size = 3993056, upload-time = "2025-10-01T00:28:02.8Z" }, - { url = "https://files.pythonhosted.org/packages/d6/a3/4c44fce0d49a4703cc94bfbe705adebf7ab36efe978053742957bc7ec324/cryptography-46.0.2-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:0c7ffe8c9b1fcbb07a26d7c9fa5e857c2fe80d72d7b9e0353dcf1d2180ae60ee", size = 4967604, upload-time = "2025-10-01T00:28:04.783Z" }, - { url = "https://files.pythonhosted.org/packages/eb/c2/49d73218747c8cac16bb8318a5513fde3129e06a018af3bc4dc722aa4a98/cryptography-46.0.2-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:5840f05518caa86b09d23f8b9405a7b6d5400085aa14a72a98fdf5cf1568c0d2", size = 4465367, upload-time = "2025-10-01T00:28:06.864Z" }, - { url = "https://files.pythonhosted.org/packages/1b/64/9afa7d2ee742f55ca6285a54386ed2778556a4ed8871571cb1c1bfd8db9e/cryptography-46.0.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:27c53b4f6a682a1b645fbf1cd5058c72cf2f5aeba7d74314c36838c7cbc06e0f", size = 4291678, upload-time = "2025-10-01T00:28:08.982Z" }, - { url = "https://files.pythonhosted.org/packages/50/48/1696d5ea9623a7b72ace87608f6899ca3c331709ac7ebf80740abb8ac673/cryptography-46.0.2-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:512c0250065e0a6b286b2db4bbcc2e67d810acd53eb81733e71314340366279e", size = 4931366, upload-time = "2025-10-01T00:28:10.74Z" }, - { url = "https://files.pythonhosted.org/packages/eb/3c/9dfc778401a334db3b24435ee0733dd005aefb74afe036e2d154547cb917/cryptography-46.0.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:07c0eb6657c0e9cca5891f4e35081dbf985c8131825e21d99b4f440a8f496f36", size = 4464738, upload-time = "2025-10-01T00:28:12.491Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b1/abcde62072b8f3fd414e191a6238ce55a0050e9738090dc6cded24c12036/cryptography-46.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:48b983089378f50cba258f7f7aa28198c3f6e13e607eaf10472c26320332ca9a", size = 4419305, upload-time = "2025-10-01T00:28:14.145Z" }, - { url = "https://files.pythonhosted.org/packages/c7/1f/3d2228492f9391395ca34c677e8f2571fb5370fe13dc48c1014f8c509864/cryptography-46.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e6f6775eaaa08c0eec73e301f7592f4367ccde5e4e4df8e58320f2ebf161ea2c", size = 4681201, upload-time = "2025-10-01T00:28:15.951Z" }, - { url = "https://files.pythonhosted.org/packages/de/77/b687745804a93a55054f391528fcfc76c3d6bfd082ce9fb62c12f0d29fc1/cryptography-46.0.2-cp314-cp314t-win32.whl", hash = "sha256:e8633996579961f9b5a3008683344c2558d38420029d3c0bc7ff77c17949a4e1", size = 3022492, upload-time = "2025-10-01T00:28:17.643Z" }, - { url = "https://files.pythonhosted.org/packages/60/a5/8d498ef2996e583de0bef1dcc5e70186376f00883ae27bf2133f490adf21/cryptography-46.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:48c01988ecbb32979bb98731f5c2b2f79042a6c58cc9a319c8c2f9987c7f68f9", size = 3496215, upload-time = "2025-10-01T00:28:19.272Z" }, - { url = "https://files.pythonhosted.org/packages/56/db/ee67aaef459a2706bc302b15889a1a8126ebe66877bab1487ae6ad00f33d/cryptography-46.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:8e2ad4d1a5899b7caa3a450e33ee2734be7cc0689010964703a7c4bcc8dd4fd0", size = 2919255, upload-time = "2025-10-01T00:28:21.115Z" }, - { url = "https://files.pythonhosted.org/packages/d5/bb/fa95abcf147a1b0bb94d95f53fbb09da77b24c776c5d87d36f3d94521d2c/cryptography-46.0.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a08e7401a94c002e79dc3bc5231b6558cd4b2280ee525c4673f650a37e2c7685", size = 7248090, upload-time = "2025-10-01T00:28:22.846Z" }, - { url = "https://files.pythonhosted.org/packages/b7/66/f42071ce0e3ffbfa80a88feadb209c779fda92a23fbc1e14f74ebf72ef6b/cryptography-46.0.2-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d30bc11d35743bf4ddf76674a0a369ec8a21f87aaa09b0661b04c5f6c46e8d7b", size = 4293123, upload-time = "2025-10-01T00:28:25.072Z" }, - { url = "https://files.pythonhosted.org/packages/a8/5d/1fdbd2e5c1ba822828d250e5a966622ef00185e476d1cd2726b6dd135e53/cryptography-46.0.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bca3f0ce67e5a2a2cf524e86f44697c4323a86e0fd7ba857de1c30d52c11ede1", size = 4439524, upload-time = "2025-10-01T00:28:26.808Z" }, - { url = "https://files.pythonhosted.org/packages/c8/c1/5e4989a7d102d4306053770d60f978c7b6b1ea2ff8c06e0265e305b23516/cryptography-46.0.2-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ff798ad7a957a5021dcbab78dfff681f0cf15744d0e6af62bd6746984d9c9e9c", size = 4297264, upload-time = "2025-10-01T00:28:29.327Z" }, - { url = "https://files.pythonhosted.org/packages/28/78/b56f847d220cb1d6d6aef5a390e116ad603ce13a0945a3386a33abc80385/cryptography-46.0.2-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cb5e8daac840e8879407acbe689a174f5ebaf344a062f8918e526824eb5d97af", size = 4011872, upload-time = "2025-10-01T00:28:31.479Z" }, - { url = "https://files.pythonhosted.org/packages/e1/80/2971f214b066b888944f7b57761bf709ee3f2cf805619a18b18cab9b263c/cryptography-46.0.2-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:3f37aa12b2d91e157827d90ce78f6180f0c02319468a0aea86ab5a9566da644b", size = 4978458, upload-time = "2025-10-01T00:28:33.267Z" }, - { url = "https://files.pythonhosted.org/packages/a5/84/0cb0a2beaa4f1cbe63ebec4e97cd7e0e9f835d0ba5ee143ed2523a1e0016/cryptography-46.0.2-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5e38f203160a48b93010b07493c15f2babb4e0f2319bbd001885adb3f3696d21", size = 4472195, upload-time = "2025-10-01T00:28:36.039Z" }, - { url = "https://files.pythonhosted.org/packages/30/8b/2b542ddbf78835c7cd67b6fa79e95560023481213a060b92352a61a10efe/cryptography-46.0.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d19f5f48883752b5ab34cff9e2f7e4a7f216296f33714e77d1beb03d108632b6", size = 4296791, upload-time = "2025-10-01T00:28:37.732Z" }, - { url = "https://files.pythonhosted.org/packages/78/12/9065b40201b4f4876e93b9b94d91feb18de9150d60bd842a16a21565007f/cryptography-46.0.2-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:04911b149eae142ccd8c9a68892a70c21613864afb47aba92d8c7ed9cc001023", size = 4939629, upload-time = "2025-10-01T00:28:39.654Z" }, - { url = "https://files.pythonhosted.org/packages/f6/9e/6507dc048c1b1530d372c483dfd34e7709fc542765015425f0442b08547f/cryptography-46.0.2-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:8b16c1ede6a937c291d41176934268e4ccac2c6521c69d3f5961c5a1e11e039e", size = 4471988, upload-time = "2025-10-01T00:28:41.822Z" }, - { url = "https://files.pythonhosted.org/packages/b1/86/d025584a5f7d5c5ec8d3633dbcdce83a0cd579f1141ceada7817a4c26934/cryptography-46.0.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:747b6f4a4a23d5a215aadd1d0b12233b4119c4313df83ab4137631d43672cc90", size = 4422989, upload-time = "2025-10-01T00:28:43.608Z" }, - { url = "https://files.pythonhosted.org/packages/4b/39/536370418b38a15a61bbe413006b79dfc3d2b4b0eafceb5581983f973c15/cryptography-46.0.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6b275e398ab3a7905e168c036aad54b5969d63d3d9099a0a66cc147a3cc983be", size = 4685578, upload-time = "2025-10-01T00:28:45.361Z" }, - { url = "https://files.pythonhosted.org/packages/15/52/ea7e2b1910f547baed566c866fbb86de2402e501a89ecb4871ea7f169a81/cryptography-46.0.2-cp38-abi3-win32.whl", hash = "sha256:0b507c8e033307e37af61cb9f7159b416173bdf5b41d11c4df2e499a1d8e007c", size = 3036711, upload-time = "2025-10-01T00:28:47.096Z" }, - { url = "https://files.pythonhosted.org/packages/71/9e/171f40f9c70a873e73c2efcdbe91e1d4b1777a03398fa1c4af3c56a2477a/cryptography-46.0.2-cp38-abi3-win_amd64.whl", hash = "sha256:f9b2dc7668418fb6f221e4bf701f716e05e8eadb4f1988a2487b11aedf8abe62", size = 3500007, upload-time = "2025-10-01T00:28:48.967Z" }, - { url = "https://files.pythonhosted.org/packages/3e/7c/15ad426257615f9be8caf7f97990cf3dcbb5b8dd7ed7e0db581a1c4759dd/cryptography-46.0.2-cp38-abi3-win_arm64.whl", hash = "sha256:91447f2b17e83c9e0c89f133119d83f94ce6e0fb55dd47da0a959316e6e9cfa1", size = 2918153, upload-time = "2025-10-01T00:28:51.003Z" }, - { url = "https://files.pythonhosted.org/packages/25/b2/067a7db693488f19777ecf73f925bcb6a3efa2eae42355bafaafa37a6588/cryptography-46.0.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f25a41f5b34b371a06dad3f01799706631331adc7d6c05253f5bca22068c7a34", size = 3701860, upload-time = "2025-10-01T00:28:53.003Z" }, - { url = "https://files.pythonhosted.org/packages/87/12/47c2aab2c285f97c71a791169529dbb89f48fc12e5f62bb6525c3927a1a2/cryptography-46.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e12b61e0b86611e3f4c1756686d9086c1d36e6fd15326f5658112ad1f1cc8807", size = 3429917, upload-time = "2025-10-01T00:28:55.03Z" }, - { url = "https://files.pythonhosted.org/packages/b7/8c/1aabe338149a7d0f52c3e30f2880b20027ca2a485316756ed6f000462db3/cryptography-46.0.2-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1d3b3edd145953832e09607986f2bd86f85d1dc9c48ced41808b18009d9f30e5", size = 3714495, upload-time = "2025-10-01T00:28:57.222Z" }, - { url = "https://files.pythonhosted.org/packages/e3/0a/0d10eb970fe3e57da9e9ddcfd9464c76f42baf7b3d0db4a782d6746f788f/cryptography-46.0.2-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:fe245cf4a73c20592f0f48da39748b3513db114465be78f0a36da847221bd1b4", size = 4243379, upload-time = "2025-10-01T00:28:58.989Z" }, - { url = "https://files.pythonhosted.org/packages/7d/60/e274b4d41a9eb82538b39950a74ef06e9e4d723cb998044635d9deb1b435/cryptography-46.0.2-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2b9cad9cf71d0c45566624ff76654e9bae5f8a25970c250a26ccfc73f8553e2d", size = 4409533, upload-time = "2025-10-01T00:29:00.785Z" }, - { url = "https://files.pythonhosted.org/packages/19/9a/fb8548f762b4749aebd13b57b8f865de80258083fe814957f9b0619cfc56/cryptography-46.0.2-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9bd26f2f75a925fdf5e0a446c0de2714f17819bf560b44b7480e4dd632ad6c46", size = 4243120, upload-time = "2025-10-01T00:29:02.515Z" }, - { url = "https://files.pythonhosted.org/packages/71/60/883f24147fd4a0c5cab74ac7e36a1ff3094a54ba5c3a6253d2ff4b19255b/cryptography-46.0.2-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:7282d8f092b5be7172d6472f29b0631f39f18512a3642aefe52c3c0e0ccfad5a", size = 4408940, upload-time = "2025-10-01T00:29:04.42Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b5/c5e179772ec38adb1c072b3aa13937d2860509ba32b2462bf1dda153833b/cryptography-46.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c4b93af7920cdf80f71650769464ccf1fb49a4b56ae0024173c24c48eb6b1612", size = 3438518, upload-time = "2025-10-01T00:29:06.139Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/a7/35/c495bffc2056f2dadb32434f1feedd79abde2a7f8363e1974afa9c33c7e2/cryptography-45.0.7.tar.gz", hash = "sha256:4b1654dfc64ea479c242508eb8c724044f1e964a47d1d1cacc5132292d851971", size = 744980, upload-time = "2025-09-01T11:15:03.146Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/91/925c0ac74362172ae4516000fe877912e33b5983df735ff290c653de4913/cryptography-45.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:3be4f21c6245930688bd9e162829480de027f8bf962ede33d4f8ba7d67a00cee", size = 7041105, upload-time = "2025-09-01T11:13:59.684Z" }, + { url = "https://files.pythonhosted.org/packages/fc/63/43641c5acce3a6105cf8bd5baeceeb1846bb63067d26dae3e5db59f1513a/cryptography-45.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:67285f8a611b0ebc0857ced2081e30302909f571a46bfa7a3cc0ad303fe015c6", size = 4205799, upload-time = "2025-09-01T11:14:02.517Z" }, + { url = "https://files.pythonhosted.org/packages/bc/29/c238dd9107f10bfde09a4d1c52fd38828b1aa353ced11f358b5dd2507d24/cryptography-45.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:577470e39e60a6cd7780793202e63536026d9b8641de011ed9d8174da9ca5339", size = 4430504, upload-time = "2025-09-01T11:14:04.522Z" }, + { url = "https://files.pythonhosted.org/packages/62/62/24203e7cbcc9bd7c94739428cd30680b18ae6b18377ae66075c8e4771b1b/cryptography-45.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4bd3e5c4b9682bc112d634f2c6ccc6736ed3635fc3319ac2bb11d768cc5a00d8", size = 4209542, upload-time = "2025-09-01T11:14:06.309Z" }, + { url = "https://files.pythonhosted.org/packages/cd/e3/e7de4771a08620eef2389b86cd87a2c50326827dea5528feb70595439ce4/cryptography-45.0.7-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:465ccac9d70115cd4de7186e60cfe989de73f7bb23e8a7aa45af18f7412e75bf", size = 3889244, upload-time = "2025-09-01T11:14:08.152Z" }, + { url = "https://files.pythonhosted.org/packages/96/b8/bca71059e79a0bb2f8e4ec61d9c205fbe97876318566cde3b5092529faa9/cryptography-45.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:16ede8a4f7929b4b7ff3642eba2bf79aa1d71f24ab6ee443935c0d269b6bc513", size = 4461975, upload-time = "2025-09-01T11:14:09.755Z" }, + { url = "https://files.pythonhosted.org/packages/58/67/3f5b26937fe1218c40e95ef4ff8d23c8dc05aa950d54200cc7ea5fb58d28/cryptography-45.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8978132287a9d3ad6b54fcd1e08548033cc09dc6aacacb6c004c73c3eb5d3ac3", size = 4209082, upload-time = "2025-09-01T11:14:11.229Z" }, + { url = "https://files.pythonhosted.org/packages/0e/e4/b3e68a4ac363406a56cf7b741eeb80d05284d8c60ee1a55cdc7587e2a553/cryptography-45.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b6a0e535baec27b528cb07a119f321ac024592388c5681a5ced167ae98e9fff3", size = 4460397, upload-time = "2025-09-01T11:14:12.924Z" }, + { url = "https://files.pythonhosted.org/packages/22/49/2c93f3cd4e3efc8cb22b02678c1fad691cff9dd71bb889e030d100acbfe0/cryptography-45.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a24ee598d10befaec178efdff6054bc4d7e883f615bfbcd08126a0f4931c83a6", size = 4337244, upload-time = "2025-09-01T11:14:14.431Z" }, + { url = "https://files.pythonhosted.org/packages/04/19/030f400de0bccccc09aa262706d90f2ec23d56bc4eb4f4e8268d0ddf3fb8/cryptography-45.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:fa26fa54c0a9384c27fcdc905a2fb7d60ac6e47d14bc2692145f2b3b1e2cfdbd", size = 4568862, upload-time = "2025-09-01T11:14:16.185Z" }, + { url = "https://files.pythonhosted.org/packages/29/56/3034a3a353efa65116fa20eb3c990a8c9f0d3db4085429040a7eef9ada5f/cryptography-45.0.7-cp311-abi3-win32.whl", hash = "sha256:bef32a5e327bd8e5af915d3416ffefdbe65ed975b646b3805be81b23580b57b8", size = 2936578, upload-time = "2025-09-01T11:14:17.638Z" }, + { url = "https://files.pythonhosted.org/packages/b3/61/0ab90f421c6194705a99d0fa9f6ee2045d916e4455fdbb095a9c2c9a520f/cryptography-45.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:3808e6b2e5f0b46d981c24d79648e5c25c35e59902ea4391a0dcb3e667bf7443", size = 3405400, upload-time = "2025-09-01T11:14:18.958Z" }, + { url = "https://files.pythonhosted.org/packages/63/e8/c436233ddf19c5f15b25ace33979a9dd2e7aa1a59209a0ee8554179f1cc0/cryptography-45.0.7-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bfb4c801f65dd61cedfc61a83732327fafbac55a47282e6f26f073ca7a41c3b2", size = 7021824, upload-time = "2025-09-01T11:14:20.954Z" }, + { url = "https://files.pythonhosted.org/packages/bc/4c/8f57f2500d0ccd2675c5d0cc462095adf3faa8c52294ba085c036befb901/cryptography-45.0.7-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:81823935e2f8d476707e85a78a405953a03ef7b7b4f55f93f7c2d9680e5e0691", size = 4202233, upload-time = "2025-09-01T11:14:22.454Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ac/59b7790b4ccaed739fc44775ce4645c9b8ce54cbec53edf16c74fd80cb2b/cryptography-45.0.7-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3994c809c17fc570c2af12c9b840d7cea85a9fd3e5c0e0491f4fa3c029216d59", size = 4423075, upload-time = "2025-09-01T11:14:24.287Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/d4f07ea21434bf891faa088a6ac15d6d98093a66e75e30ad08e88aa2b9ba/cryptography-45.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dad43797959a74103cb59c5dac71409f9c27d34c8a05921341fb64ea8ccb1dd4", size = 4204517, upload-time = "2025-09-01T11:14:25.679Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ac/924a723299848b4c741c1059752c7cfe09473b6fd77d2920398fc26bfb53/cryptography-45.0.7-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ce7a453385e4c4693985b4a4a3533e041558851eae061a58a5405363b098fcd3", size = 3882893, upload-time = "2025-09-01T11:14:27.1Z" }, + { url = "https://files.pythonhosted.org/packages/83/dc/4dab2ff0a871cc2d81d3ae6d780991c0192b259c35e4d83fe1de18b20c70/cryptography-45.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b04f85ac3a90c227b6e5890acb0edbaf3140938dbecf07bff618bf3638578cf1", size = 4450132, upload-time = "2025-09-01T11:14:28.58Z" }, + { url = "https://files.pythonhosted.org/packages/12/dd/b2882b65db8fc944585d7fb00d67cf84a9cef4e77d9ba8f69082e911d0de/cryptography-45.0.7-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:48c41a44ef8b8c2e80ca4527ee81daa4c527df3ecbc9423c41a420a9559d0e27", size = 4204086, upload-time = "2025-09-01T11:14:30.572Z" }, + { url = "https://files.pythonhosted.org/packages/5d/fa/1d5745d878048699b8eb87c984d4ccc5da4f5008dfd3ad7a94040caca23a/cryptography-45.0.7-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f3df7b3d0f91b88b2106031fd995802a2e9ae13e02c36c1fc075b43f420f3a17", size = 4449383, upload-time = "2025-09-01T11:14:32.046Z" }, + { url = "https://files.pythonhosted.org/packages/36/8b/fc61f87931bc030598e1876c45b936867bb72777eac693e905ab89832670/cryptography-45.0.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dd342f085542f6eb894ca00ef70236ea46070c8a13824c6bde0dfdcd36065b9b", size = 4332186, upload-time = "2025-09-01T11:14:33.95Z" }, + { url = "https://files.pythonhosted.org/packages/0b/11/09700ddad7443ccb11d674efdbe9a832b4455dc1f16566d9bd3834922ce5/cryptography-45.0.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1993a1bb7e4eccfb922b6cd414f072e08ff5816702a0bdb8941c247a6b1b287c", size = 4561639, upload-time = "2025-09-01T11:14:35.343Z" }, + { url = "https://files.pythonhosted.org/packages/71/ed/8f4c1337e9d3b94d8e50ae0b08ad0304a5709d483bfcadfcc77a23dbcb52/cryptography-45.0.7-cp37-abi3-win32.whl", hash = "sha256:18fcf70f243fe07252dcb1b268a687f2358025ce32f9f88028ca5c364b123ef5", size = 2926552, upload-time = "2025-09-01T11:14:36.929Z" }, + { url = "https://files.pythonhosted.org/packages/bc/ff/026513ecad58dacd45d1d24ebe52b852165a26e287177de1d545325c0c25/cryptography-45.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:7285a89df4900ed3bfaad5679b1e668cb4b38a8de1ccbfc84b05f34512da0a90", size = 3392742, upload-time = "2025-09-01T11:14:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/13/3e/e42f1528ca1ea82256b835191eab1be014e0f9f934b60d98b0be8a38ed70/cryptography-45.0.7-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:de58755d723e86175756f463f2f0bddd45cc36fbd62601228a3f8761c9f58252", size = 3572442, upload-time = "2025-09-01T11:14:39.836Z" }, + { url = "https://files.pythonhosted.org/packages/59/aa/e947693ab08674a2663ed2534cd8d345cf17bf6a1facf99273e8ec8986dc/cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a20e442e917889d1a6b3c570c9e3fa2fdc398c20868abcea268ea33c024c4083", size = 4142233, upload-time = "2025-09-01T11:14:41.305Z" }, + { url = "https://files.pythonhosted.org/packages/24/06/09b6f6a2fc43474a32b8fe259038eef1500ee3d3c141599b57ac6c57612c/cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:258e0dff86d1d891169b5af222d362468a9570e2532923088658aa866eb11130", size = 4376202, upload-time = "2025-09-01T11:14:43.047Z" }, + { url = "https://files.pythonhosted.org/packages/00/f2/c166af87e95ce6ae6d38471a7e039d3a0549c2d55d74e059680162052824/cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d97cf502abe2ab9eff8bd5e4aca274da8d06dd3ef08b759a8d6143f4ad65d4b4", size = 4141900, upload-time = "2025-09-01T11:14:45.089Z" }, + { url = "https://files.pythonhosted.org/packages/16/b9/e96e0b6cb86eae27ea51fa8a3151535a18e66fe7c451fa90f7f89c85f541/cryptography-45.0.7-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:c987dad82e8c65ebc985f5dae5e74a3beda9d0a2a4daf8a1115f3772b59e5141", size = 4375562, upload-time = "2025-09-01T11:14:47.166Z" }, + { url = "https://files.pythonhosted.org/packages/36/d0/36e8ee39274e9d77baf7d0dafda680cba6e52f3936b846f0d56d64fec915/cryptography-45.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c13b1e3afd29a5b3b2656257f14669ca8fa8d7956d509926f0b130b600b50ab7", size = 3322781, upload-time = "2025-09-01T11:14:48.747Z" }, + { url = "https://files.pythonhosted.org/packages/99/4e/49199a4c82946938a3e05d2e8ad9482484ba48bbc1e809e3d506c686d051/cryptography-45.0.7-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a862753b36620af6fc54209264f92c716367f2f0ff4624952276a6bbd18cbde", size = 3584634, upload-time = "2025-09-01T11:14:50.593Z" }, + { url = "https://files.pythonhosted.org/packages/16/ce/5f6ff59ea9c7779dba51b84871c19962529bdcc12e1a6ea172664916c550/cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:06ce84dc14df0bf6ea84666f958e6080cdb6fe1231be2a51f3fc1267d9f3fb34", size = 4149533, upload-time = "2025-09-01T11:14:52.091Z" }, + { url = "https://files.pythonhosted.org/packages/ce/13/b3cfbd257ac96da4b88b46372e662009b7a16833bfc5da33bb97dd5631ae/cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d0c5c6bac22b177bf8da7435d9d27a6834ee130309749d162b26c3105c0795a9", size = 4385557, upload-time = "2025-09-01T11:14:53.551Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c5/8c59d6b7c7b439ba4fc8d0cab868027fd095f215031bc123c3a070962912/cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:2f641b64acc00811da98df63df7d59fd4706c0df449da71cb7ac39a0732b40ae", size = 4149023, upload-time = "2025-09-01T11:14:55.022Z" }, + { url = "https://files.pythonhosted.org/packages/55/32/05385c86d6ca9ab0b4d5bb442d2e3d85e727939a11f3e163fc776ce5eb40/cryptography-45.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:f5414a788ecc6ee6bc58560e85ca624258a55ca434884445440a810796ea0e0b", size = 4385722, upload-time = "2025-09-01T11:14:57.319Z" }, + { url = "https://files.pythonhosted.org/packages/23/87/7ce86f3fa14bc11a5a48c30d8103c26e09b6465f8d8e9d74cf7a0714f043/cryptography-45.0.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1f3d56f73595376f4244646dd5c5870c14c196949807be39e79e7bd9bac3da63", size = 3332908, upload-time = "2025-09-01T11:14:58.78Z" }, ] [[package]] @@ -921,6 +932,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, ] +[[package]] +name = "msal" +version = "1.31.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3f/f3/cdf2681e83a73c3355883c2884b6ff2f2d2aadfc399c28e9ac4edc3994fd/msal-1.31.1.tar.gz", hash = "sha256:11b5e6a3f802ffd3a72107203e20c4eac6ef53401961b880af2835b723d80578", size = 145362, upload-time = "2024-11-18T09:51:10.143Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/7c/489cd931a752d05753d730e848039f08f65f86237cf1b8724d0a1cbd700b/msal-1.31.1-py3-none-any.whl", hash = "sha256:29d9882de247e96db01386496d59f29035e5e841bcac892e6d7bf4390bf6bd17", size = 113216, upload-time = "2024-11-18T09:51:08.402Z" }, +] + +[[package]] +name = "msal-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "msal" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/99/5d239b6156eddf761a636bded1118414d161bd6b7b37a9335549ed159396/msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4", size = 23315, upload-time = "2025-03-14T23:51:03.902Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583, upload-time = "2025-03-14T23:51:03.016Z" }, +] + [[package]] name = "mypy" version = "1.18.2" @@ -1381,6 +1418,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] +[[package]] +name = "pyjwt" +version = "2.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/68/ce067f09fca4abeca8771fe667d89cc347d1e99da3e093112ac329c6020e/pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c", size = 78825, upload-time = "2024-08-01T15:01:08.445Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/84/0fdf9b18ba31d69877bd39c9cd6052b47f3761e9910c15de788e519f079f/PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850", size = 22344, upload-time = "2024-08-01T15:01:06.481Z" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + [[package]] name = "pytest" version = "8.4.2" @@ -1587,6 +1638,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e8/02/89e2ed7e85db6c93dfa9e8f691c5087df4e3551ab39081a4d7c6d1f90e05/redis-6.4.0-py3-none-any.whl", hash = "sha256:f0544fa9604264e9464cdf4814e7d4830f74b165d52f2a330a760a88dd248b7f", size = 279847, upload-time = "2025-08-07T08:10:09.84Z" }, ] +[[package]] +name = "redis-entraid" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-identity" }, + { name = "msal" }, + { name = "pyjwt" }, + { name = "redis" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/7b/f56aebb76ce71368e34e27c096537f2ee0249268f11eefee03dc6268c552/redis_entraid-1.0.0.tar.gz", hash = "sha256:585188b49597a70ad149ef012f20e478baf0d722c1a148318146db635be5a71e", size = 9550, upload-time = "2025-05-27T11:46:32.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/3c/187d524f735e531cf7c2e6adc660ac48860b4784a54663bf5dbc972b2f38/redis_entraid-1.0.0-py3-none-any.whl", hash = "sha256:4c9ec857e26e9ed2b3810ddb28b2f33a28035712ccba0dc8b55c70a3bf8a9908", size = 7864, upload-time = "2025-05-27T11:46:31.732Z" }, +] + [[package]] name = "redis-mcp-server" version = "0.3.4" @@ -1598,6 +1664,7 @@ dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "redis" }, + { name = "redis-entraid" }, ] [package.dev-dependencies] @@ -1629,6 +1696,7 @@ requires-dist = [ { name = "mcp", extras = ["cli"], specifier = ">=1.9.4" }, { name = "numpy", specifier = ">=2.2.4" }, { name = "redis", specifier = ">=6.0.0" }, + { name = "redis-entraid", specifier = ">=1.0.0" }, ] [package.metadata.requires-dev] From c9e11f0f7b1aff06a91e0add571d3dd90b9e7c8f Mon Sep 17 00:00:00 2001 From: Vasil Chomakov Date: Wed, 22 Oct 2025 18:36:34 +0300 Subject: [PATCH 2/3] style: fix line length formatting in entraid_auth.py --- src/common/entraid_auth.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/common/entraid_auth.py b/src/common/entraid_auth.py index 68d46c5..5260cfa 100644 --- a/src/common/entraid_auth.py +++ b/src/common/entraid_auth.py @@ -16,7 +16,9 @@ _logger = logging.getLogger(__name__) # Reduce Azure SDK logging verbosity -logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(logging.WARNING) +logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel( + logging.WARNING +) logging.getLogger("azure.identity").setLevel(logging.WARNING) logging.getLogger("redis.auth.token_manager").setLevel(logging.WARNING) From 1567ef8b9c9f6b01a187567fdf56863d14ecd877 Mon Sep 17 00:00:00 2001 From: Vasil Chomakov Date: Thu, 23 Oct 2025 13:57:56 +0300 Subject: [PATCH 3/3] docs: add readme instructions for EntraID support --- README.md | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/README.md b/README.md index fd6b6b6..edcc621 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ The Redis MCP Server is a **natural language interface** designed for agentic ap - [Redis ACL](#redis-acl) - [Configuration via command line arguments](#configuration-via-command-line-arguments) - [Configuration via Environment Variables](#configuration-via-environment-variables) + - [EntraID Authentication for Azure Managed Redis](#entraid-authentication-for-azure-managed-redis) - [Logging](#logging) - [Integrations](#integrations) - [OpenAI Agents SDK](#openai-agents-sdk) @@ -57,6 +58,7 @@ The Redis MCP Server is a **natural language interface** designed for agentic ap - **Full Redis Support**: Handles **hashes, lists, sets, sorted sets, streams**, and more. - **Search & Filtering**: Supports efficient data retrieval and searching in Redis. - **Scalable & Lightweight**: Designed for **high-performance** data operations. +- **EntraID Authentication**: Native support for Azure Active Directory authentication with Azure Managed Redis. - The Redis MCP Server supports the `stdio` [transport](https://modelcontextprotocol.io/docs/concepts/transports#standard-input%2Foutput-stdio). Support to the `stremable-http` transport will be added in the future. ## Tools @@ -316,6 +318,85 @@ If desired, you can use environment variables. Defaults are provided for all var | `REDIS_CA_CERTS` | Path to the trusted CA certificates file | None | | `REDIS_CLUSTER_MODE` | Enable Redis Cluster mode | `False` | +### EntraID Authentication for Azure Managed Redis + +The Redis MCP Server supports **EntraID (Azure Active Directory) authentication** for Azure Managed Redis, enabling OAuth-based authentication with automatic token management. + +#### Authentication Providers + +**Service Principal Authentication** - Application-based authentication using client credentials: +```bash +export REDIS_ENTRAID_AUTH_FLOW=service_principal +export REDIS_ENTRAID_CLIENT_ID=your-client-id +export REDIS_ENTRAID_CLIENT_SECRET=your-client-secret +export REDIS_ENTRAID_TENANT_ID=your-tenant-id +``` + +**Managed Identity Authentication** - For Azure-hosted applications: +```bash +# System-assigned managed identity +export REDIS_ENTRAID_AUTH_FLOW=managed_identity +export REDIS_ENTRAID_IDENTITY_TYPE=system_assigned + +# User-assigned managed identity +export REDIS_ENTRAID_AUTH_FLOW=managed_identity +export REDIS_ENTRAID_IDENTITY_TYPE=user_assigned +export REDIS_ENTRAID_USER_ASSIGNED_CLIENT_ID=your-identity-client-id +``` + +**Default Azure Credential** - Automatic credential discovery (recommended for development): +```bash +export REDIS_ENTRAID_AUTH_FLOW=default_credential +export REDIS_ENTRAID_SCOPES=https://redis.azure.com/.default +``` + +#### EntraID Configuration Variables + +| Name | Description | Default Value | +|-----------------------------------------|-----------------------------------------------------------|--------------------------------------| +| `REDIS_ENTRAID_AUTH_FLOW` | Authentication flow type | None (EntraID disabled) | +| `REDIS_ENTRAID_CLIENT_ID` | Service Principal client ID | None | +| `REDIS_ENTRAID_CLIENT_SECRET` | Service Principal client secret | None | +| `REDIS_ENTRAID_TENANT_ID` | Azure tenant ID | None | +| `REDIS_ENTRAID_IDENTITY_TYPE` | Managed identity type | `"system_assigned"` | +| `REDIS_ENTRAID_USER_ASSIGNED_CLIENT_ID` | User-assigned managed identity client ID | None | +| `REDIS_ENTRAID_SCOPES` | OAuth scopes for Default Azure Credential | `"https://redis.azure.com/.default"` | +| `REDIS_ENTRAID_RESOURCE` | Azure Redis resource identifier | `"https://redis.azure.com/"` | + +#### Key Features + +- **Automatic token renewal** - Background token refresh with no manual intervention +- **Graceful fallback** - Falls back to standard Redis authentication when EntraID not configured +- **Multiple auth flows** - Supports Service Principal, Managed Identity, and Default Azure Credential +- **Enterprise ready** - Designed for Azure Managed Redis with centralized identity management + +#### Example Configuration + +For **local development** with Azure CLI: +```bash +# Login with Azure CLI +az login + +# Configure MCP server +export REDIS_ENTRAID_AUTH_FLOW=default_credential +export REDIS_URL=redis://your-azure-redis.redis.cache.windows.net:6379 +``` + +For **production** with Service Principal: +```bash +export REDIS_ENTRAID_AUTH_FLOW=service_principal +export REDIS_ENTRAID_CLIENT_ID=your-app-client-id +export REDIS_ENTRAID_CLIENT_SECRET=your-app-secret +export REDIS_ENTRAID_TENANT_ID=your-tenant-id +export REDIS_URL=redis://your-azure-redis.redis.cache.windows.net:6379 +``` + +For **Azure-hosted applications** with Managed Identity: +```bash +export REDIS_ENTRAID_AUTH_FLOW=managed_identity +export REDIS_ENTRAID_IDENTITY_TYPE=system_assigned +export REDIS_URL=redis://your-azure-redis.redis.cache.windows.net:6379 +``` There are several ways to set environment variables: @@ -438,6 +519,7 @@ You can also configure the Redis MCP Server in Augment manually by importing the The simplest way to configure MCP clients is using `uvx`. Add the following JSON to your `claude_desktop_config.json`, remember to provide the full path to `uvx`. +**Basic Redis connection:** ```json { "mcpServers": { @@ -454,6 +536,27 @@ The simplest way to configure MCP clients is using `uvx`. Add the following JSON } ``` +**Azure Managed Redis with EntraID authentication:** +```json +{ + "mcpServers": { + "redis-mcp-server": { + "type": "stdio", + "command": "/Users/mortensi/.local/bin/uvx", + "args": [ + "--from", "redis-mcp-server@latest", + "redis-mcp-server", + "--url", "redis://your-azure-redis.redis.cache.windows.net:6379" + ], + "env": { + "REDIS_ENTRAID_AUTH_FLOW": "default_credential", + "REDIS_ENTRAID_SCOPES": "https://redis.azure.com/.default" + } + } + } +} +``` + If you'd like to test the [Redis MCP Server](https://smithery.ai/server/@redis/mcp-redis) via Smithery, you can configure Claude Desktop automatically: ```bash