/ src / solace_agent_mesh / agent / adk / mcp_ssl_config.py
mcp_ssl_config.py
  1  """
  2  SSL/TLS configuration support for MCP connections.
  3  
  4  This module provides SSL configuration for remote MCP connections (SSE and Streamable HTTP)
  5  to allow connecting to MCP servers with self-signed certificates or custom CA bundles.
  6  """
  7  
  8  import logging
  9  import os
 10  from dataclasses import dataclass
 11  
 12  import httpx
 13  
 14  log = logging.getLogger(__name__)
 15  
 16  
 17  @dataclass
 18  class SslConfig:
 19      """SSL configuration for MCP connections.
 20  
 21      Attributes:
 22          verify: Whether to verify SSL certificates. Defaults to True.
 23                  Set to False to disable SSL verification (development only).
 24          ca_bundle: Optional path to a custom CA certificate bundle file.
 25                     When provided, this takes precedence over the verify setting.
 26      """
 27  
 28      verify: bool = True
 29      ca_bundle: str | None = None
 30  
 31      def __post_init__(self):
 32          """Validate the SSL configuration after initialization."""
 33          if self.ca_bundle is not None and not os.path.isfile(self.ca_bundle):
 34              raise ValueError(
 35                  f"SSL ca_bundle path does not exist or is not a file: {self.ca_bundle}"
 36              )
 37  
 38  
 39  def create_ssl_httpx_client_factory(ssl_config: SslConfig):
 40      """Create an httpx client factory with SSL configuration.
 41  
 42      This factory creates httpx.AsyncClient instances with custom SSL settings,
 43      following the MCP library's expected factory signature.
 44  
 45      Args:
 46          ssl_config: SSL configuration specifying verification settings.
 47  
 48      Returns:
 49          A factory function that creates configured httpx.AsyncClient instances.
 50      """
 51      # MCP default timeouts
 52      MCP_DEFAULT_TIMEOUT = 30.0
 53      MCP_DEFAULT_SSE_READ_TIMEOUT = 300.0
 54  
 55      def factory(
 56          headers: dict[str, str] | None = None,
 57          timeout: httpx.Timeout | None = None,
 58          auth: httpx.Auth | None = None,
 59      ) -> httpx.AsyncClient:
 60          """Create an httpx.AsyncClient with SSL configuration.
 61  
 62          Args:
 63              headers: Optional headers to include with all requests.
 64              timeout: Request timeout as httpx.Timeout object.
 65              auth: Optional authentication handler.
 66  
 67          Returns:
 68              Configured httpx.AsyncClient instance.
 69          """
 70          # Determine SSL verification setting
 71          # ca_bundle takes precedence if provided
 72          verify: bool | str = (
 73              ssl_config.ca_bundle if ssl_config.ca_bundle else ssl_config.verify
 74          )
 75  
 76          # Build kwargs following MCP defaults
 77          kwargs: dict = {
 78              "follow_redirects": True,
 79              "verify": verify,
 80          }
 81  
 82          # Handle timeout
 83          if timeout is None:
 84              kwargs["timeout"] = httpx.Timeout(
 85                  MCP_DEFAULT_TIMEOUT, read=MCP_DEFAULT_SSE_READ_TIMEOUT
 86              )
 87          else:
 88              kwargs["timeout"] = timeout
 89  
 90          # Handle headers
 91          if headers is not None:
 92              kwargs["headers"] = headers
 93  
 94          # Handle authentication
 95          if auth is not None:
 96              kwargs["auth"] = auth
 97  
 98          return httpx.AsyncClient(**kwargs)
 99  
100      return factory