diff --git a/src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/__init__.py b/src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/__init__.py index a6fa816..ee6bc89 100644 --- a/src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/__init__.py +++ b/src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/__init__.py @@ -5,4 +5,4 @@ """ __project__ = "oracle.oci-cloud-guard-mcp-server" -__version__ = "1.1.4" +__version__ = "1.1.5" diff --git a/src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/server.py b/src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/server.py index dc2065a..5f9004b 100644 --- a/src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/server.py +++ b/src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/server.py @@ -25,6 +25,21 @@ mcp = FastMCP(name=__project__) +def _get_oci_client_kwargs(signer=None): + kwargs = { + "circuit_breaker_strategy": oci.circuit_breaker.CircuitBreakerStrategy( + failure_threshold=int(os.getenv("OCI_CIRCUIT_BREAKER_FAILURE_THRESHOLD", "10")), + recovery_timeout=int(os.getenv("OCI_CIRCUIT_BREAKER_RECOVERY_TIMEOUT", "30")), + ), + "circuit_breaker_callback": lambda exc: logger.warning( + "Circuit breaker triggered: %s", exc + ), + } + if signer is not None: + kwargs["signer"] = signer + return kwargs + + def get_cloud_guard_client(): config = oci.config.from_file( file_location=os.getenv("OCI_CONFIG_FILE", oci.config.DEFAULT_LOCATION), @@ -40,7 +55,7 @@ def get_cloud_guard_client(): with open(token_file, "r") as f: token = f.read() signer = oci.auth.signers.SecurityTokenSigner(token, private_key) - return CloudGuardClient(config, signer=signer) + return CloudGuardClient(config, **_get_oci_client_kwargs(signer)) @mcp.tool( diff --git a/src/oci-cloud-guard-mcp-server/pyproject.toml b/src/oci-cloud-guard-mcp-server/pyproject.toml index b6d1279..e46538b 100644 --- a/src/oci-cloud-guard-mcp-server/pyproject.toml +++ b/src/oci-cloud-guard-mcp-server/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oracle.oci-cloud-guard-mcp-server" -version = "1.1.4" +version = "1.1.5" description = "OCI Cloud Guard Service MCP server" readme = "README.md" requires-python = ">=3.13" diff --git a/src/oci-cloud-guard-mcp-server/uv.lock b/src/oci-cloud-guard-mcp-server/uv.lock index f494b08..803c82b 100644 --- a/src/oci-cloud-guard-mcp-server/uv.lock +++ b/src/oci-cloud-guard-mcp-server/uv.lock @@ -783,7 +783,7 @@ wheels = [ [[package]] name = "oracle-oci-cloud-guard-mcp-server" -version = "1.1.4" +version = "1.1.5" source = { editable = "." } dependencies = [ { name = "fastmcp" }, diff --git a/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/__init__.py b/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/__init__.py index 2f83381..9a42b18 100644 --- a/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/__init__.py +++ b/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/__init__.py @@ -5,4 +5,4 @@ """ __project__ = "oracle.oci-cloud-mcp-server" -__version__ = "1.1.3" +__version__ = "1.1.4" diff --git a/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/server.py b/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/server.py index d8e0757..2b21085 100644 --- a/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/server.py +++ b/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/server.py @@ -35,6 +35,21 @@ _user_agent_name = __project__.split("oracle.", 1)[1].split("-server", 1)[0] _ADDITIONAL_UA = f"{_user_agent_name}/{__version__}" + +def _get_oci_client_kwargs(signer=None): + kwargs = { + "circuit_breaker_strategy": oci.circuit_breaker.CircuitBreakerStrategy( + failure_threshold=int(os.getenv("OCI_CIRCUIT_BREAKER_FAILURE_THRESHOLD", "10")), + recovery_timeout=int(os.getenv("OCI_CIRCUIT_BREAKER_RECOVERY_TIMEOUT", "30")), + ), + "circuit_breaker_callback": lambda exc: logger.warning( + "Circuit breaker triggered: %s", exc + ), + } + if signer is not None: + kwargs["signer"] = signer + return kwargs + # Backwards-compat pagination allowlist placeholder. # Heuristic detection handles most cases; keep an empty set to avoid NameError # in any residual fallback checks. @@ -103,7 +118,23 @@ def _import_client(client_fqn: str) -> Any: if not inspect.isclass(cls): raise ValueError(f"{client_fqn} is not a class") config, signer = _get_config_and_signer() - instance = cls(config, signer=signer) + client_kwargs = _get_oci_client_kwargs(signer) + try: + init_signature = inspect.signature(cls.__init__) + supports_kwargs = any( + param.kind == inspect.Parameter.VAR_KEYWORD + for param in init_signature.parameters.values() + ) + except (TypeError, ValueError): + supports_kwargs = True + + if supports_kwargs: + instance = cls(config, **client_kwargs) + else: + filtered_kwargs = { + key: value for key, value in client_kwargs.items() if key in init_signature.parameters + } + instance = cls(config, **filtered_kwargs) return instance diff --git a/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_server_extras.py b/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_server_extras.py index 27cc006..9a76318 100644 --- a/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_server_extras.py +++ b/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_server_extras.py @@ -7,6 +7,7 @@ from types import SimpleNamespace from unittest.mock import mock_open, patch +import oci import pytest from fastmcp import Client from fastmcp.exceptions import ToolError @@ -294,6 +295,27 @@ def __init__(self, config, signer): inst = _import_client("x.y.FakeClient") assert isinstance(inst, FakeClient) + def test_import_client_passes_circuit_breaker_to_kwargs_capable_client(self): + class FakeClient: + def __init__(self, config, **kwargs): + self.config = config + self.kwargs = kwargs + + fake_module = SimpleNamespace(FakeClient=FakeClient) + + with ( + patch("oracle.oci_cloud_mcp_server.server.import_module") as m_import, + patch("oracle.oci_cloud_mcp_server.server._get_config_and_signer") as m_cfg, + ): + signer = object() + m_import.return_value = fake_module + m_cfg.return_value = ({"k": "v"}, signer) + inst = _import_client("x.y.FakeClient") + + assert isinstance(inst.kwargs["circuit_breaker_strategy"], oci.circuit_breaker.CircuitBreakerStrategy) + assert callable(inst.kwargs["circuit_breaker_callback"]) + assert inst.kwargs["signer"] is signer + class TestInvokeErrors: @pytest.mark.asyncio diff --git a/src/oci-cloud-mcp-server/pyproject.toml b/src/oci-cloud-mcp-server/pyproject.toml index 856a449..99ff9f5 100644 --- a/src/oci-cloud-mcp-server/pyproject.toml +++ b/src/oci-cloud-mcp-server/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oracle.oci-cloud-mcp-server" -version = "1.1.3" +version = "1.1.4" description = "OCI Python SDK MCP server" readme = "README.md" requires-python = ">=3.13" diff --git a/src/oci-cloud-mcp-server/uv.lock b/src/oci-cloud-mcp-server/uv.lock index 3507cf0..06e7a63 100644 --- a/src/oci-cloud-mcp-server/uv.lock +++ b/src/oci-cloud-mcp-server/uv.lock @@ -727,7 +727,7 @@ wheels = [ [[package]] name = "oracle-oci-cloud-mcp-server" -version = "1.1.3" +version = "1.1.4" source = { editable = "." } dependencies = [ { name = "fastmcp" }, diff --git a/src/oci-compute-instance-agent-mcp-server/oracle/oci_compute_instance_agent_mcp_server/__init__.py b/src/oci-compute-instance-agent-mcp-server/oracle/oci_compute_instance_agent_mcp_server/__init__.py index 0e724d7..4b27381 100644 --- a/src/oci-compute-instance-agent-mcp-server/oracle/oci_compute_instance_agent_mcp_server/__init__.py +++ b/src/oci-compute-instance-agent-mcp-server/oracle/oci_compute_instance_agent_mcp_server/__init__.py @@ -5,4 +5,4 @@ """ __project__ = "oracle.oci-compute-instance-agent-mcp-server" -__version__ = "2.1.4" +__version__ = "2.1.5" diff --git a/src/oci-compute-instance-agent-mcp-server/oracle/oci_compute_instance_agent_mcp_server/server.py b/src/oci-compute-instance-agent-mcp-server/oracle/oci_compute_instance_agent_mcp_server/server.py index a127dc1..53522e3 100644 --- a/src/oci-compute-instance-agent-mcp-server/oracle/oci_compute_instance_agent_mcp_server/server.py +++ b/src/oci-compute-instance-agent-mcp-server/oracle/oci_compute_instance_agent_mcp_server/server.py @@ -33,6 +33,21 @@ mcp = FastMCP(name=__project__) +def _get_oci_client_kwargs(signer=None): + kwargs = { + "circuit_breaker_strategy": oci.circuit_breaker.CircuitBreakerStrategy( + failure_threshold=int(os.getenv("OCI_CIRCUIT_BREAKER_FAILURE_THRESHOLD", "10")), + recovery_timeout=int(os.getenv("OCI_CIRCUIT_BREAKER_RECOVERY_TIMEOUT", "30")), + ), + "circuit_breaker_callback": lambda exc: logger.warning( + "Circuit breaker triggered: %s", exc + ), + } + if signer is not None: + kwargs["signer"] = signer + return kwargs + + def get_compute_instance_agent_client(): logger.info("entering get_compute_instance_agent_client") config = oci.config.from_file( @@ -49,7 +64,9 @@ def get_compute_instance_agent_client(): with open(token_file, "r") as f: token = f.read() signer = oci.auth.signers.SecurityTokenSigner(token, private_key) - return oci.compute_instance_agent.ComputeInstanceAgentClient(config, signer=signer) + return oci.compute_instance_agent.ComputeInstanceAgentClient( + config, **_get_oci_client_kwargs(signer) + ) @mcp.tool( diff --git a/src/oci-compute-instance-agent-mcp-server/pyproject.toml b/src/oci-compute-instance-agent-mcp-server/pyproject.toml index 17db519..803b203 100644 --- a/src/oci-compute-instance-agent-mcp-server/pyproject.toml +++ b/src/oci-compute-instance-agent-mcp-server/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oracle.oci-compute-instance-agent-mcp-server" -version = "2.1.4" +version = "2.1.5" description = "OCI Compute Instance Agent MCP Server" readme = "README.md" requires-python = ">=3.13" diff --git a/src/oci-compute-instance-agent-mcp-server/uv.lock b/src/oci-compute-instance-agent-mcp-server/uv.lock index 0885a14..c4cf81c 100644 --- a/src/oci-compute-instance-agent-mcp-server/uv.lock +++ b/src/oci-compute-instance-agent-mcp-server/uv.lock @@ -783,7 +783,7 @@ wheels = [ [[package]] name = "oracle-oci-compute-instance-agent-mcp-server" -version = "2.1.4" +version = "2.1.5" source = { editable = "." } dependencies = [ { name = "fastmcp" }, diff --git a/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/__init__.py b/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/__init__.py index 36488fb..603923b 100644 --- a/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/__init__.py +++ b/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/__init__.py @@ -5,4 +5,4 @@ """ __project__ = "oracle.oci-compute-mcp-server" -__version__ = "1.2.4" +__version__ = "1.2.5" diff --git a/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/server.py b/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/server.py index 7007f88..824a251 100644 --- a/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/server.py +++ b/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/server.py @@ -35,6 +35,21 @@ mcp = FastMCP(name=__project__) +def _get_oci_client_kwargs(signer=None): + kwargs = { + "circuit_breaker_strategy": oci.circuit_breaker.CircuitBreakerStrategy( + failure_threshold=int(os.getenv("OCI_CIRCUIT_BREAKER_FAILURE_THRESHOLD", "10")), + recovery_timeout=int(os.getenv("OCI_CIRCUIT_BREAKER_RECOVERY_TIMEOUT", "30")), + ), + "circuit_breaker_callback": lambda exc: logger.warning( + "Circuit breaker triggered: %s", exc + ), + } + if signer is not None: + kwargs["signer"] = signer + return kwargs + + def get_compute_client(): logger.info("entering get_compute_client") config = oci.config.from_file( @@ -50,7 +65,7 @@ def get_compute_client(): with open(token_file, "r") as f: token = f.read() signer = oci.auth.signers.SecurityTokenSigner(token, private_key) - return oci.core.ComputeClient(config, signer=signer) + return oci.core.ComputeClient(config, **_get_oci_client_kwargs(signer)) @mcp.tool(description="List Instances in a given compartment") diff --git a/src/oci-compute-mcp-server/pyproject.toml b/src/oci-compute-mcp-server/pyproject.toml index e5ede40..b2a4223 100644 --- a/src/oci-compute-mcp-server/pyproject.toml +++ b/src/oci-compute-mcp-server/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oracle.oci-compute-mcp-server" -version = "1.2.4" +version = "1.2.5" description = "OCI Compute Service MCP server" readme = "README.md" requires-python = ">=3.13" diff --git a/src/oci-compute-mcp-server/uv.lock b/src/oci-compute-mcp-server/uv.lock index e2d1e90..5b463ae 100644 --- a/src/oci-compute-mcp-server/uv.lock +++ b/src/oci-compute-mcp-server/uv.lock @@ -783,7 +783,7 @@ wheels = [ [[package]] name = "oracle-oci-compute-mcp-server" -version = "1.2.4" +version = "1.2.5" source = { editable = "." } dependencies = [ { name = "fastmcp" }, diff --git a/src/oci-database-mcp-server/oracle/oci_database_mcp_server/__init__.py b/src/oci-database-mcp-server/oracle/oci_database_mcp_server/__init__.py index dfe5111..2ed48ca 100644 --- a/src/oci-database-mcp-server/oracle/oci_database_mcp_server/__init__.py +++ b/src/oci-database-mcp-server/oracle/oci_database_mcp_server/__init__.py @@ -5,4 +5,4 @@ """ __project__ = "oracle.oci-database-mcp-server" -__version__ = "1.0.5" +__version__ = "1.0.6" diff --git a/src/oci-database-mcp-server/oracle/oci_database_mcp_server/server.py b/src/oci-database-mcp-server/oracle/oci_database_mcp_server/server.py index c7b747e..6f8778d 100644 --- a/src/oci-database-mcp-server/oracle/oci_database_mcp_server/server.py +++ b/src/oci-database-mcp-server/oracle/oci_database_mcp_server/server.py @@ -283,6 +283,21 @@ mcp = FastMCP(name=__project__) +def _get_oci_client_kwargs(signer=None): + kwargs = { + "circuit_breaker_strategy": oci.circuit_breaker.CircuitBreakerStrategy( + failure_threshold=int(os.getenv("OCI_CIRCUIT_BREAKER_FAILURE_THRESHOLD", "10")), + recovery_timeout=int(os.getenv("OCI_CIRCUIT_BREAKER_RECOVERY_TIMEOUT", "30")), + ), + "circuit_breaker_callback": lambda exc: logger.warning( + "Circuit breaker triggered: %s", exc + ), + } + if signer is not None: + kwargs["signer"] = signer + return kwargs + + def get_database_client(region: str = None): config = oci.config.from_file( file_location=os.getenv("OCI_CONFIG_FILE", oci.config.DEFAULT_LOCATION), @@ -296,10 +311,10 @@ def get_database_client(region: str = None): token = f.read() signer = oci.auth.signers.SecurityTokenSigner(token, private_key) if region is None: - return oci.database.DatabaseClient(config, signer=signer) + return oci.database.DatabaseClient(config, **_get_oci_client_kwargs(signer)) regional_config = config.copy() regional_config["region"] = region - return oci.database.DatabaseClient(regional_config, signer=signer) + return oci.database.DatabaseClient(regional_config, **_get_oci_client_kwargs(signer)) def call_create_pdb(client, details, opc_retry_token=None, opc_request_id=None): diff --git a/src/oci-database-mcp-server/oracle/oci_database_mcp_server/tests/test_database.py b/src/oci-database-mcp-server/oracle/oci_database_mcp_server/tests/test_database.py index ee35cc1..c2471f9 100644 --- a/src/oci-database-mcp-server/oracle/oci_database_mcp_server/tests/test_database.py +++ b/src/oci-database-mcp-server/oracle/oci_database_mcp_server/tests/test_database.py @@ -1,11 +1,50 @@ -from unittest.mock import MagicMock, create_autospec, patch +from unittest.mock import MagicMock, create_autospec, mock_open, patch import oci import pytest from fastmcp import Client +import oracle.oci_database_mcp_server.server as server from oracle.oci_database_mcp_server.server import mcp +class TestGetDatabaseClient: + @patch("oracle.oci_database_mcp_server.server.oci.database.DatabaseClient") + @patch("oracle.oci_database_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_database_mcp_server.server.oci.signer.load_private_key_from_file") + @patch( + "oracle.oci_database_mcp_server.server.open", + new_callable=mock_open, + read_data="SECURITY_TOKEN", + ) + @patch("oracle.oci_database_mcp_server.server.oci.config.from_file") + @patch("oracle.oci_database_mcp_server.server.os.getenv") + def test_get_database_client_passes_circuit_breaker_and_region( + self, + mock_getenv, + mock_from_file, + mock_open_file, + mock_load_private_key, + mock_security_token_signer, + mock_client, + ): + mock_getenv.side_effect = lambda k, default=None: default + config = {"key_file": "/key.pem", "security_token_file": "/token", "region": "us-ashburn-1"} + mock_from_file.return_value = config + private_key_obj = object() + mock_load_private_key.return_value = private_key_obj + + result = server.get_database_client(region="us-phoenix-1") + + mock_open_file.assert_called_once_with("/token", "r") + mock_security_token_signer.assert_called_once_with("SECURITY_TOKEN", private_key_obj) + args, kwargs = mock_client.call_args + assert args[0]["region"] == "us-phoenix-1" + assert kwargs["signer"] is mock_security_token_signer.return_value + assert isinstance(kwargs["circuit_breaker_strategy"], oci.circuit_breaker.CircuitBreakerStrategy) + assert callable(kwargs["circuit_breaker_callback"]) + assert result is mock_client.return_value + + @pytest.mark.asyncio @patch("oracle.oci_database_mcp_server.server.get_database_client") async def test_list_application_vips(mock_get_client): diff --git a/src/oci-database-mcp-server/pyproject.toml b/src/oci-database-mcp-server/pyproject.toml index 62b38d9..6b3bf3f 100644 --- a/src/oci-database-mcp-server/pyproject.toml +++ b/src/oci-database-mcp-server/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oracle.oci-database-mcp-server" -version = "1.0.5" +version = "1.0.6" description = "OCI Database Service MCP server" readme = "README.md" requires-python = ">=3.13" diff --git a/src/oci-database-mcp-server/uv.lock b/src/oci-database-mcp-server/uv.lock index c3ab72f..b289aab 100644 --- a/src/oci-database-mcp-server/uv.lock +++ b/src/oci-database-mcp-server/uv.lock @@ -783,7 +783,7 @@ wheels = [ [[package]] name = "oracle-oci-database-mcp-server" -version = "1.0.5" +version = "1.0.6" source = { editable = "." } dependencies = [ { name = "fastmcp" }, diff --git a/src/oci-faaas-mcp-server/oracle/oci_faaas_mcp_server/__init__.py b/src/oci-faaas-mcp-server/oracle/oci_faaas_mcp_server/__init__.py index 304beee..34f1161 100644 --- a/src/oci-faaas-mcp-server/oracle/oci_faaas_mcp_server/__init__.py +++ b/src/oci-faaas-mcp-server/oracle/oci_faaas_mcp_server/__init__.py @@ -5,4 +5,4 @@ """ __project__ = "oracle.oci-faaas-mcp-server" -__version__ = "1.0.3" +__version__ = "1.0.4" diff --git a/src/oci-faaas-mcp-server/oracle/oci_faaas_mcp_server/server.py b/src/oci-faaas-mcp-server/oracle/oci_faaas_mcp_server/server.py index d1ffbaf..6231ce8 100644 --- a/src/oci-faaas-mcp-server/oracle/oci_faaas_mcp_server/server.py +++ b/src/oci-faaas-mcp-server/oracle/oci_faaas_mcp_server/server.py @@ -27,6 +27,21 @@ mcp = FastMCP(name=__project__) +def _get_oci_client_kwargs(signer=None): + kwargs = { + "circuit_breaker_strategy": oci.circuit_breaker.CircuitBreakerStrategy( + failure_threshold=int(os.getenv("OCI_CIRCUIT_BREAKER_FAILURE_THRESHOLD", "10")), + recovery_timeout=int(os.getenv("OCI_CIRCUIT_BREAKER_RECOVERY_TIMEOUT", "30")), + ), + "circuit_breaker_callback": lambda exc: logger.warning( + "Circuit breaker triggered: %s", exc + ), + } + if signer is not None: + kwargs["signer"] = signer + return kwargs + + def get_faaas_client(): """Initialize and return an OCI Fusion Applications client using security token auth.""" logger.info("entering get_faaas_client") @@ -46,7 +61,7 @@ def get_faaas_client(): token = f.read() signer = oci.auth.signers.SecurityTokenSigner(token, private_key) - return oci.fusion_apps.FusionApplicationsClient(config, signer=signer) + return oci.fusion_apps.FusionApplicationsClient(config, **_get_oci_client_kwargs(signer)) @mcp.tool(description="Returns a list of Fusion Environment Families in the specified compartment.") diff --git a/src/oci-faaas-mcp-server/pyproject.toml b/src/oci-faaas-mcp-server/pyproject.toml index b1b06ad..7baf09e 100644 --- a/src/oci-faaas-mcp-server/pyproject.toml +++ b/src/oci-faaas-mcp-server/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oracle.oci-faaas-mcp-server" -version = "1.0.3" +version = "1.0.4" description = "OCI Fusion Applications (FAaaS) MCP server" readme = "README.md" requires-python = ">=3.13" diff --git a/src/oci-faaas-mcp-server/uv.lock b/src/oci-faaas-mcp-server/uv.lock index 2540ff2..9f09616 100644 --- a/src/oci-faaas-mcp-server/uv.lock +++ b/src/oci-faaas-mcp-server/uv.lock @@ -784,7 +784,7 @@ wheels = [ [[package]] name = "oracle-oci-faaas-mcp-server" -version = "1.0.3" +version = "1.0.4" source = { editable = "." } dependencies = [ { name = "fastmcp" }, diff --git a/src/oci-identity-mcp-server/oracle/oci_identity_mcp_server/__init__.py b/src/oci-identity-mcp-server/oracle/oci_identity_mcp_server/__init__.py index bbd321d..94009c2 100644 --- a/src/oci-identity-mcp-server/oracle/oci_identity_mcp_server/__init__.py +++ b/src/oci-identity-mcp-server/oracle/oci_identity_mcp_server/__init__.py @@ -5,4 +5,4 @@ """ __project__ = "oracle.oci-identity-mcp-server" -__version__ = "2.1.5" +__version__ = "2.1.6" diff --git a/src/oci-identity-mcp-server/oracle/oci_identity_mcp_server/server.py b/src/oci-identity-mcp-server/oracle/oci_identity_mcp_server/server.py index 9a229ff..2533a0e 100644 --- a/src/oci-identity-mcp-server/oracle/oci_identity_mcp_server/server.py +++ b/src/oci-identity-mcp-server/oracle/oci_identity_mcp_server/server.py @@ -35,6 +35,21 @@ mcp = FastMCP(name=__project__) +def _get_oci_client_kwargs(signer=None): + kwargs = { + "circuit_breaker_strategy": oci.circuit_breaker.CircuitBreakerStrategy( + failure_threshold=int(os.getenv("OCI_CIRCUIT_BREAKER_FAILURE_THRESHOLD", "10")), + recovery_timeout=int(os.getenv("OCI_CIRCUIT_BREAKER_RECOVERY_TIMEOUT", "30")), + ), + "circuit_breaker_callback": lambda exc: logger.warning( + "Circuit breaker triggered: %s", exc + ), + } + if signer is not None: + kwargs["signer"] = signer + return kwargs + + def get_identity_client(): config = oci.config.from_file( file_location=os.getenv("OCI_CONFIG_FILE", oci.config.DEFAULT_LOCATION), @@ -47,7 +62,7 @@ def get_identity_client(): with open(token_file, "r") as f: token = f.read() signer = oci.auth.signers.SecurityTokenSigner(token, private_key) - return oci.identity.IdentityClient(config, signer=signer) + return oci.identity.IdentityClient(config, **_get_oci_client_kwargs(signer)) @mcp.tool(description="List compartments in a given compartment or tenancy.") diff --git a/src/oci-identity-mcp-server/pyproject.toml b/src/oci-identity-mcp-server/pyproject.toml index cf8e177..0cdb246 100644 --- a/src/oci-identity-mcp-server/pyproject.toml +++ b/src/oci-identity-mcp-server/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oracle.oci-identity-mcp-server" -version = "2.1.5" +version = "2.1.6" description = "OCI Identity Service MCP server" readme = "README.md" requires-python = ">=3.13" diff --git a/src/oci-identity-mcp-server/uv.lock b/src/oci-identity-mcp-server/uv.lock index 279db68..c54d19b 100644 --- a/src/oci-identity-mcp-server/uv.lock +++ b/src/oci-identity-mcp-server/uv.lock @@ -783,7 +783,7 @@ wheels = [ [[package]] name = "oracle-oci-identity-mcp-server" -version = "2.1.5" +version = "2.1.6" source = { editable = "." } dependencies = [ { name = "fastmcp" }, diff --git a/src/oci-limits-mcp-server/oracle/oci_limits_mcp_server/__init__.py b/src/oci-limits-mcp-server/oracle/oci_limits_mcp_server/__init__.py index fc04a8c..c234f1f 100644 --- a/src/oci-limits-mcp-server/oracle/oci_limits_mcp_server/__init__.py +++ b/src/oci-limits-mcp-server/oracle/oci_limits_mcp_server/__init__.py @@ -5,4 +5,4 @@ """ __project__ = "oracle.oci-limits-mcp-server" -__version__ = "1.0.1" +__version__ = "1.0.2" diff --git a/src/oci-limits-mcp-server/oracle/oci_limits_mcp_server/server.py b/src/oci-limits-mcp-server/oracle/oci_limits_mcp_server/server.py index 825b967..9f4c765 100644 --- a/src/oci-limits-mcp-server/oracle/oci_limits_mcp_server/server.py +++ b/src/oci-limits-mcp-server/oracle/oci_limits_mcp_server/server.py @@ -30,6 +30,21 @@ mcp = FastMCP(name=__project__) +def _get_oci_client_kwargs(signer=None): + kwargs = { + "circuit_breaker_strategy": oci.circuit_breaker.CircuitBreakerStrategy( + failure_threshold=int(os.getenv("OCI_CIRCUIT_BREAKER_FAILURE_THRESHOLD", "10")), + recovery_timeout=int(os.getenv("OCI_CIRCUIT_BREAKER_RECOVERY_TIMEOUT", "30")), + ), + "circuit_breaker_callback": lambda exc: logger.warning( + "Circuit breaker triggered: %s", exc + ), + } + if signer is not None: + kwargs["signer"] = signer + return kwargs + + def get_limits_client(): """ Build an OCI LimitsClient using Security Token auth (consistent with other servers in this repo). @@ -49,7 +64,7 @@ def get_limits_client(): signer = oci.auth.signers.SecurityTokenSigner(token, private_key) # Limits client - return oci.limits.LimitsClient(config, signer=signer) + return oci.limits.LimitsClient(config, **_get_oci_client_kwargs(signer)) def get_identity_client(): diff --git a/src/oci-limits-mcp-server/oracle/oci_limits_mcp_server/tests/test_limit_tool.py b/src/oci-limits-mcp-server/oracle/oci_limits_mcp_server/tests/test_limit_tool.py index 5eec98e..6c4e18c 100644 --- a/src/oci-limits-mcp-server/oracle/oci_limits_mcp_server/tests/test_limit_tool.py +++ b/src/oci-limits-mcp-server/oracle/oci_limits_mcp_server/tests/test_limit_tool.py @@ -4,11 +4,12 @@ https://oss.oracle.com/licenses/upl. """ -from unittest.mock import MagicMock, create_autospec, patch +from unittest.mock import MagicMock, create_autospec, mock_open, patch import oci import pytest from fastmcp import Client +import oracle.oci_limits_mcp_server.server as server from oracle.oci_limits_mcp_server.server import mcp @@ -101,6 +102,46 @@ async def test_list_limit_value(self, mock_get_client): assert len(result) == 1 assert result[0]["name"] == "limit_value1" + +class TestGetClient: + @patch("oracle.oci_limits_mcp_server.server.oci.limits.LimitsClient") + @patch("oracle.oci_limits_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_limits_mcp_server.server.oci.signer.load_private_key_from_file") + @patch( + "oracle.oci_limits_mcp_server.server.open", + new_callable=mock_open, + read_data="SECURITY_TOKEN", + ) + @patch("oracle.oci_limits_mcp_server.server.oci.config.from_file") + @patch("oracle.oci_limits_mcp_server.server.os.getenv") + def test_get_limits_client_passes_circuit_breaker( + self, + mock_getenv, + mock_from_file, + mock_open_file, + mock_load_private_key, + mock_security_token_signer, + mock_client, + ): + mock_getenv.side_effect = lambda k, default=None: ( + "MYPROFILE" if k == "OCI_CONFIG_PROFILE" else default + ) + config = {"key_file": "/key.pem", "security_token_file": "/token"} + mock_from_file.return_value = config + private_key_obj = object() + mock_load_private_key.return_value = private_key_obj + + result = server.get_limits_client() + + mock_open_file.assert_called_once_with("/token", "r") + mock_security_token_signer.assert_called_once_with("SECURITY_TOKEN", private_key_obj) + args, kwargs = mock_client.call_args + assert args[0] is config + assert kwargs["signer"] is mock_security_token_signer.return_value + assert isinstance(kwargs["circuit_breaker_strategy"], oci.circuit_breaker.CircuitBreakerStrategy) + assert callable(kwargs["circuit_breaker_callback"]) + assert result is mock_client.return_value + @pytest.mark.asyncio @patch("oracle.oci_limits_mcp_server.server.get_limits_client") async def test_get_resource_availability_ad_scope(self, mock_get_client): diff --git a/src/oci-limits-mcp-server/pyproject.toml b/src/oci-limits-mcp-server/pyproject.toml index 67110c6..b588335 100644 --- a/src/oci-limits-mcp-server/pyproject.toml +++ b/src/oci-limits-mcp-server/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oracle.oci-limits-mcp-server" -version = "1.0.1" +version = "1.0.2" description = "OCI Limits MCP server" readme = "README.md" requires-python = ">=3.13" diff --git a/src/oci-limits-mcp-server/uv.lock b/src/oci-limits-mcp-server/uv.lock index 1fcb7e3..8c24e82 100644 --- a/src/oci-limits-mcp-server/uv.lock +++ b/src/oci-limits-mcp-server/uv.lock @@ -686,7 +686,7 @@ wheels = [ [[package]] name = "oracle-oci-limits-mcp-server" -version = "1.0.1" +version = "1.0.2" source = { editable = "." } dependencies = [ { name = "fastmcp" }, diff --git a/src/oci-load-balancer-mcp-server/oracle/oci_load_balancer_mcp_server/__init__.py b/src/oci-load-balancer-mcp-server/oracle/oci_load_balancer_mcp_server/__init__.py index 3ae4e17..c6136f0 100644 --- a/src/oci-load-balancer-mcp-server/oracle/oci_load_balancer_mcp_server/__init__.py +++ b/src/oci-load-balancer-mcp-server/oracle/oci_load_balancer_mcp_server/__init__.py @@ -5,4 +5,4 @@ """ __project__ = "oracle.oci-load-balancer-mcp-server" -__version__ = "0.0.0" +__version__ = "0.0.2" diff --git a/src/oci-load-balancer-mcp-server/oracle/oci_load_balancer_mcp_server/server.py b/src/oci-load-balancer-mcp-server/oracle/oci_load_balancer_mcp_server/server.py index d4b88d0..936ad0f 100644 --- a/src/oci-load-balancer-mcp-server/oracle/oci_load_balancer_mcp_server/server.py +++ b/src/oci-load-balancer-mcp-server/oracle/oci_load_balancer_mcp_server/server.py @@ -63,6 +63,21 @@ ) +def _get_oci_client_kwargs(signer=None): + kwargs = { + "circuit_breaker_strategy": oci.circuit_breaker.CircuitBreakerStrategy( + failure_threshold=int(os.getenv("OCI_CIRCUIT_BREAKER_FAILURE_THRESHOLD", "10")), + recovery_timeout=int(os.getenv("OCI_CIRCUIT_BREAKER_RECOVERY_TIMEOUT", "30")), + ), + "circuit_breaker_callback": lambda exc: logger.warning( + "Circuit breaker triggered: %s", exc + ), + } + if signer is not None: + kwargs["signer"] = signer + return kwargs + + def get_load_balancer_client(): logger.info("entering get_load_balancer_client") config = oci.config.from_file( @@ -82,7 +97,7 @@ def get_load_balancer_client(): with open(token_file, "r") as f: token = f.read() signer = oci.auth.signers.SecurityTokenSigner(token, private_key) if token else None - return oci.load_balancer.LoadBalancerClient(config, signer=signer) + return oci.load_balancer.LoadBalancerClient(config, **_get_oci_client_kwargs(signer)) @mcp.tool( diff --git a/src/oci-load-balancer-mcp-server/oracle/oci_load_balancer_mcp_server/tests/test_load_balancer_tools.py b/src/oci-load-balancer-mcp-server/oracle/oci_load_balancer_mcp_server/tests/test_load_balancer_tools.py index 29f643a..fd571f2 100644 --- a/src/oci-load-balancer-mcp-server/oracle/oci_load_balancer_mcp_server/tests/test_load_balancer_tools.py +++ b/src/oci-load-balancer-mcp-server/oracle/oci_load_balancer_mcp_server/tests/test_load_balancer_tools.py @@ -7,8 +7,9 @@ """ import types -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, mock_open, patch +import oci import pytest # Import the server module where the tools are defined @@ -29,8 +30,12 @@ def __init__( @pytest.fixture(autouse=True) -def mock_client(monkeypatch): +def mock_client(monkeypatch, request): """Patch ``get_load_balancer_client`` to return a MagicMock client and stub OCI model classes.""" + if request.node.cls is TestGetClient: + yield None + return + # Stub OCI SDK model constructors to accept any kwargs (avoid strict validations during tests) import oci @@ -75,6 +80,44 @@ def __init__(self, **kwargs): yield mock +class TestGetClient: + @patch("oracle.oci_load_balancer_mcp_server.server.oci.load_balancer.LoadBalancerClient") + @patch("oracle.oci_load_balancer_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_load_balancer_mcp_server.server.oci.signer.load_private_key_from_file") + @patch( + "oracle.oci_load_balancer_mcp_server.server.open", + new_callable=mock_open, + read_data="SECURITY_TOKEN", + ) + @patch("oracle.oci_load_balancer_mcp_server.server.oci.config.from_file") + @patch("oracle.oci_load_balancer_mcp_server.server.os.getenv") + def test_get_load_balancer_client_passes_circuit_breaker( + self, + mock_getenv, + mock_from_file, + mock_open_file, + mock_load_private_key, + mock_security_token_signer, + mock_client, + ): + mock_getenv.side_effect = lambda k, default=None: default + config = {"key_file": "/key.pem", "security_token_file": "/token"} + mock_from_file.return_value = config + private_key_obj = object() + mock_load_private_key.return_value = private_key_obj + + result = server.get_load_balancer_client() + + mock_open_file.assert_called_once_with("/token", "r") + mock_security_token_signer.assert_called_once_with("SECURITY_TOKEN", private_key_obj) + args, kwargs = mock_client.call_args + assert args[0] is config + assert kwargs["signer"] is mock_security_token_signer.return_value + assert isinstance(kwargs["circuit_breaker_strategy"], oci.circuit_breaker.CircuitBreakerStrategy) + assert callable(kwargs["circuit_breaker_callback"]) + assert result is mock_client.return_value + + # ---------------------------------------------------------------------- # Load Balancer tools # ---------------------------------------------------------------------- diff --git a/src/oci-load-balancer-mcp-server/pyproject.toml b/src/oci-load-balancer-mcp-server/pyproject.toml index 77bd606..cf5f70c 100644 --- a/src/oci-load-balancer-mcp-server/pyproject.toml +++ b/src/oci-load-balancer-mcp-server/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "oracle.oci-load-balancer-mcp-server" -version = "0.0.1" +version = "0.0.2" description = "An OCI Model Context Protocol server for load-balancer" readme = "README.md" requires-python = ">=3.13" diff --git a/src/oci-load-balancer-mcp-server/uv.lock b/src/oci-load-balancer-mcp-server/uv.lock index 32dd402..c3ddc28 100644 --- a/src/oci-load-balancer-mcp-server/uv.lock +++ b/src/oci-load-balancer-mcp-server/uv.lock @@ -678,7 +678,7 @@ wheels = [ [[package]] name = "oracle-oci-load-balancer-mcp-server" -version = "0.0.1" +version = "0.0.2" source = { editable = "." } dependencies = [ { name = "fastmcp" }, diff --git a/src/oci-logging-mcp-server/oracle/oci_logging_mcp_server/__init__.py b/src/oci-logging-mcp-server/oracle/oci_logging_mcp_server/__init__.py index 682dda4..757a8b5 100644 --- a/src/oci-logging-mcp-server/oracle/oci_logging_mcp_server/__init__.py +++ b/src/oci-logging-mcp-server/oracle/oci_logging_mcp_server/__init__.py @@ -5,4 +5,4 @@ """ __project__ = "oracle.oci-logging-mcp-server" -__version__ = "1.2.4" +__version__ = "1.2.5" diff --git a/src/oci-logging-mcp-server/oracle/oci_logging_mcp_server/server.py b/src/oci-logging-mcp-server/oracle/oci_logging_mcp_server/server.py index 06d414e..65e37af 100644 --- a/src/oci-logging-mcp-server/oracle/oci_logging_mcp_server/server.py +++ b/src/oci-logging-mcp-server/oracle/oci_logging_mcp_server/server.py @@ -37,6 +37,21 @@ mcp = FastMCP(name=__project__) +def _get_oci_client_kwargs(signer=None): + kwargs = { + "circuit_breaker_strategy": oci.circuit_breaker.CircuitBreakerStrategy( + failure_threshold=int(os.getenv("OCI_CIRCUIT_BREAKER_FAILURE_THRESHOLD", "10")), + recovery_timeout=int(os.getenv("OCI_CIRCUIT_BREAKER_RECOVERY_TIMEOUT", "30")), + ), + "circuit_breaker_callback": lambda exc: logger.warning( + "Circuit breaker triggered: %s", exc + ), + } + if signer is not None: + kwargs["signer"] = signer + return kwargs + + def get_logging_client(): logger.info("entering get_logging_client") config = oci.config.from_file( @@ -50,7 +65,7 @@ def get_logging_client(): with open(token_file, "r") as f: token = f.read() signer = oci.auth.signers.SecurityTokenSigner(token, private_key) - return oci.logging.LoggingManagementClient(config, signer=signer) + return oci.logging.LoggingManagementClient(config, **_get_oci_client_kwargs(signer)) def get_logging_search_client(): @@ -66,7 +81,7 @@ def get_logging_search_client(): with open(token_file, "r") as f: token = f.read() signer = oci.auth.signers.SecurityTokenSigner(token, private_key) - return oci.loggingsearch.LogSearchClient(config, signer=signer) + return oci.loggingsearch.LogSearchClient(config, **_get_oci_client_kwargs(signer)) @mcp.tool( diff --git a/src/oci-logging-mcp-server/oracle/oci_logging_mcp_server/tests/test_logging_tools.py b/src/oci-logging-mcp-server/oracle/oci_logging_mcp_server/tests/test_logging_tools.py index 0fd73f4..673b59f 100644 --- a/src/oci-logging-mcp-server/oracle/oci_logging_mcp_server/tests/test_logging_tools.py +++ b/src/oci-logging-mcp-server/oracle/oci_logging_mcp_server/tests/test_logging_tools.py @@ -14,6 +14,80 @@ from oracle.oci_logging_mcp_server.server import mcp +class TestGetClient: + @patch("oracle.oci_logging_mcp_server.server.oci.logging.LoggingManagementClient") + @patch("oracle.oci_logging_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_logging_mcp_server.server.oci.signer.load_private_key_from_file") + @patch( + "oracle.oci_logging_mcp_server.server.open", + new_callable=mock_open, + read_data="SECURITY_TOKEN", + ) + @patch("oracle.oci_logging_mcp_server.server.oci.config.from_file") + @patch("oracle.oci_logging_mcp_server.server.os.getenv") + def test_get_logging_client_passes_circuit_breaker( + self, + mock_getenv, + mock_from_file, + mock_open_file, + mock_load_private_key, + mock_security_token_signer, + mock_client, + ): + mock_getenv.side_effect = lambda k, default=None: default + config = {"key_file": "/key.pem", "security_token_file": "/token"} + mock_from_file.return_value = config + private_key_obj = object() + mock_load_private_key.return_value = private_key_obj + + result = server.get_logging_client() + + mock_open_file.assert_called_once_with("/token", "r") + mock_security_token_signer.assert_called_once_with("SECURITY_TOKEN", private_key_obj) + args, kwargs = mock_client.call_args + assert args[0] is config + assert kwargs["signer"] is mock_security_token_signer.return_value + assert isinstance(kwargs["circuit_breaker_strategy"], oci.circuit_breaker.CircuitBreakerStrategy) + assert callable(kwargs["circuit_breaker_callback"]) + assert result is mock_client.return_value + + @patch("oracle.oci_logging_mcp_server.server.oci.loggingsearch.LogSearchClient") + @patch("oracle.oci_logging_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_logging_mcp_server.server.oci.signer.load_private_key_from_file") + @patch( + "oracle.oci_logging_mcp_server.server.open", + new_callable=mock_open, + read_data="SECURITY_TOKEN", + ) + @patch("oracle.oci_logging_mcp_server.server.oci.config.from_file") + @patch("oracle.oci_logging_mcp_server.server.os.getenv") + def test_get_logging_search_client_passes_circuit_breaker( + self, + mock_getenv, + mock_from_file, + mock_open_file, + mock_load_private_key, + mock_security_token_signer, + mock_client, + ): + mock_getenv.side_effect = lambda k, default=None: default + config = {"key_file": "/key.pem", "security_token_file": "/token"} + mock_from_file.return_value = config + private_key_obj = object() + mock_load_private_key.return_value = private_key_obj + + result = server.get_logging_search_client() + + mock_open_file.assert_called_once_with("/token", "r") + mock_security_token_signer.assert_called_once_with("SECURITY_TOKEN", private_key_obj) + args, kwargs = mock_client.call_args + assert args[0] is config + assert kwargs["signer"] is mock_security_token_signer.return_value + assert isinstance(kwargs["circuit_breaker_strategy"], oci.circuit_breaker.CircuitBreakerStrategy) + assert callable(kwargs["circuit_breaker_callback"]) + assert result is mock_client.return_value + + class TestLoggingTools: @pytest.mark.asyncio @patch("oracle.oci_logging_mcp_server.server.get_logging_client") diff --git a/src/oci-logging-mcp-server/pyproject.toml b/src/oci-logging-mcp-server/pyproject.toml index 46131b6..c867f08 100644 --- a/src/oci-logging-mcp-server/pyproject.toml +++ b/src/oci-logging-mcp-server/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oracle.oci-logging-mcp-server" -version = "1.2.4" +version = "1.2.5" description = "OCI Logging Service MCP server" readme = "README.md" authors = [ diff --git a/src/oci-logging-mcp-server/uv.lock b/src/oci-logging-mcp-server/uv.lock index fe439bd..38e3a2d 100644 --- a/src/oci-logging-mcp-server/uv.lock +++ b/src/oci-logging-mcp-server/uv.lock @@ -783,7 +783,7 @@ wheels = [ [[package]] name = "oracle-oci-logging-mcp-server" -version = "1.2.4" +version = "1.2.5" source = { editable = "." } dependencies = [ { name = "fastmcp" }, diff --git a/src/oci-migration-mcp-server/oracle/oci_migration_mcp_server/__init__.py b/src/oci-migration-mcp-server/oracle/oci_migration_mcp_server/__init__.py index 47da140..8477573 100644 --- a/src/oci-migration-mcp-server/oracle/oci_migration_mcp_server/__init__.py +++ b/src/oci-migration-mcp-server/oracle/oci_migration_mcp_server/__init__.py @@ -5,4 +5,4 @@ """ __project__ = "oracle.oci-migration-mcp-server" -__version__ = "2.1.4" +__version__ = "2.1.5" diff --git a/src/oci-migration-mcp-server/oracle/oci_migration_mcp_server/server.py b/src/oci-migration-mcp-server/oracle/oci_migration_mcp_server/server.py index b9c7f49..5a2236b 100644 --- a/src/oci-migration-mcp-server/oracle/oci_migration_mcp_server/server.py +++ b/src/oci-migration-mcp-server/oracle/oci_migration_mcp_server/server.py @@ -25,6 +25,21 @@ mcp = FastMCP(name=__project__) +def _get_oci_client_kwargs(signer=None): + kwargs = { + "circuit_breaker_strategy": oci.circuit_breaker.CircuitBreakerStrategy( + failure_threshold=int(os.getenv("OCI_CIRCUIT_BREAKER_FAILURE_THRESHOLD", "10")), + recovery_timeout=int(os.getenv("OCI_CIRCUIT_BREAKER_RECOVERY_TIMEOUT", "30")), + ), + "circuit_breaker_callback": lambda exc: logger.warning( + "Circuit breaker triggered: %s", exc + ), + } + if signer is not None: + kwargs["signer"] = signer + return kwargs + + def get_migration_client(): logger.info("entering get_migration_client") config = oci.config.from_file( @@ -39,7 +54,7 @@ def get_migration_client(): with open(token_file, "r") as f: token = f.read() signer = oci.auth.signers.SecurityTokenSigner(token, private_key) - return oci.cloud_migrations.MigrationClient(config, signer=signer) + return oci.cloud_migrations.MigrationClient(config, **_get_oci_client_kwargs(signer)) @mcp.tool(description="Get details for a specific Migration Project by OCID") diff --git a/src/oci-migration-mcp-server/pyproject.toml b/src/oci-migration-mcp-server/pyproject.toml index 8da33d8..0edcc4c 100644 --- a/src/oci-migration-mcp-server/pyproject.toml +++ b/src/oci-migration-mcp-server/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oracle.oci-migration-mcp-server" -version = "2.1.4" +version = "2.1.5" description = "Add your description here" readme = "README.md" requires-python = ">=3.13" diff --git a/src/oci-migration-mcp-server/uv.lock b/src/oci-migration-mcp-server/uv.lock index df2f0d2..ce8c429 100644 --- a/src/oci-migration-mcp-server/uv.lock +++ b/src/oci-migration-mcp-server/uv.lock @@ -783,7 +783,7 @@ wheels = [ [[package]] name = "oracle-oci-migration-mcp-server" -version = "2.1.4" +version = "2.1.5" source = { editable = "." } dependencies = [ { name = "fastmcp" }, diff --git a/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/__init__.py b/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/__init__.py index 98bd179..fd69014 100644 --- a/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/__init__.py +++ b/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/__init__.py @@ -5,4 +5,4 @@ """ __project__ = "oracle.oci-monitoring-mcp-server" -__version__ = "1.1.4" +__version__ = "1.1.5" diff --git a/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/server.py b/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/server.py index 84d5642..a99cde4 100644 --- a/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/server.py +++ b/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/server.py @@ -41,6 +41,21 @@ ) +def _get_oci_client_kwargs(signer=None): + kwargs = { + "circuit_breaker_strategy": oci.circuit_breaker.CircuitBreakerStrategy( + failure_threshold=int(os.getenv("OCI_CIRCUIT_BREAKER_FAILURE_THRESHOLD", "10")), + recovery_timeout=int(os.getenv("OCI_CIRCUIT_BREAKER_RECOVERY_TIMEOUT", "30")), + ), + "circuit_breaker_callback": lambda exc: logger.warning( + "Circuit breaker triggered: %s", exc + ), + } + if signer is not None: + kwargs["signer"] = signer + return kwargs + + def get_monitoring_client(): logger.info("entering get_monitoring_client") config = oci.config.from_file( @@ -56,7 +71,7 @@ def get_monitoring_client(): with open(token_file, "r") as f: token = f.read() signer = oci.auth.signers.SecurityTokenSigner(token, private_key) - return oci.monitoring.MonitoringClient(config, signer=signer) + return oci.monitoring.MonitoringClient(config, **_get_oci_client_kwargs(signer)) @mcp.tool(name="list_alarms", description="Lists all alarms in a given compartment") diff --git a/src/oci-monitoring-mcp-server/pyproject.toml b/src/oci-monitoring-mcp-server/pyproject.toml index 9ab0a81..8b997b5 100644 --- a/src/oci-monitoring-mcp-server/pyproject.toml +++ b/src/oci-monitoring-mcp-server/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oracle.oci-monitoring-mcp-server" -version = "1.1.4" +version = "1.1.5" description = "OCI Monitoring Service MCP server" readme = "README.md" requires-python = ">=3.13" diff --git a/src/oci-monitoring-mcp-server/uv.lock b/src/oci-monitoring-mcp-server/uv.lock index 9e28092..599bb1c 100644 --- a/src/oci-monitoring-mcp-server/uv.lock +++ b/src/oci-monitoring-mcp-server/uv.lock @@ -783,7 +783,7 @@ wheels = [ [[package]] name = "oracle-oci-monitoring-mcp-server" -version = "1.1.4" +version = "1.1.5" source = { editable = "." } dependencies = [ { name = "fastmcp" }, diff --git a/src/oci-network-load-balancer-mcp-server/oracle/oci_network_load_balancer_mcp_server/__init__.py b/src/oci-network-load-balancer-mcp-server/oracle/oci_network_load_balancer_mcp_server/__init__.py index a4dcfed..0540354 100644 --- a/src/oci-network-load-balancer-mcp-server/oracle/oci_network_load_balancer_mcp_server/__init__.py +++ b/src/oci-network-load-balancer-mcp-server/oracle/oci_network_load_balancer_mcp_server/__init__.py @@ -5,4 +5,4 @@ """ __project__ = "oracle.oci-network-load-balancer-mcp-server" -__version__ = "2.1.4" +__version__ = "2.1.5" diff --git a/src/oci-network-load-balancer-mcp-server/oracle/oci_network_load_balancer_mcp_server/server.py b/src/oci-network-load-balancer-mcp-server/oracle/oci_network_load_balancer_mcp_server/server.py index cb140e6..b07cd7f 100644 --- a/src/oci-network-load-balancer-mcp-server/oracle/oci_network_load_balancer_mcp_server/server.py +++ b/src/oci-network-load-balancer-mcp-server/oracle/oci_network_load_balancer_mcp_server/server.py @@ -29,6 +29,21 @@ mcp = FastMCP(name=__project__) +def _get_oci_client_kwargs(signer=None): + kwargs = { + "circuit_breaker_strategy": oci.circuit_breaker.CircuitBreakerStrategy( + failure_threshold=int(os.getenv("OCI_CIRCUIT_BREAKER_FAILURE_THRESHOLD", "10")), + recovery_timeout=int(os.getenv("OCI_CIRCUIT_BREAKER_RECOVERY_TIMEOUT", "30")), + ), + "circuit_breaker_callback": lambda exc: logger.warning( + "Circuit breaker triggered: %s", exc + ), + } + if signer is not None: + kwargs["signer"] = signer + return kwargs + + def get_nlb_client(): logger.info("entering get_nlb_client") config = oci.config.from_file( @@ -43,7 +58,9 @@ def get_nlb_client(): with open(token_file, "r") as f: token = f.read() signer = oci.auth.signers.SecurityTokenSigner(token, private_key) - return oci.network_load_balancer.NetworkLoadBalancerClient(config, signer=signer) + return oci.network_load_balancer.NetworkLoadBalancerClient( + config, **_get_oci_client_kwargs(signer) + ) @mcp.tool( diff --git a/src/oci-network-load-balancer-mcp-server/pyproject.toml b/src/oci-network-load-balancer-mcp-server/pyproject.toml index 0e23ebd..e81d0f5 100644 --- a/src/oci-network-load-balancer-mcp-server/pyproject.toml +++ b/src/oci-network-load-balancer-mcp-server/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oracle.oci-network-load-balancer-mcp-server" -version = "2.1.4" +version = "2.1.5" description = "OCI Network Load Balancer MCP server" readme = "README.md" requires-python = ">=3.13" diff --git a/src/oci-network-load-balancer-mcp-server/uv.lock b/src/oci-network-load-balancer-mcp-server/uv.lock index b2768a4..e955345 100644 --- a/src/oci-network-load-balancer-mcp-server/uv.lock +++ b/src/oci-network-load-balancer-mcp-server/uv.lock @@ -783,7 +783,7 @@ wheels = [ [[package]] name = "oracle-oci-network-load-balancer-mcp-server" -version = "2.1.4" +version = "2.1.5" source = { editable = "." } dependencies = [ { name = "fastmcp" }, diff --git a/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/__init__.py b/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/__init__.py index d77b4d8..42f533f 100644 --- a/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/__init__.py +++ b/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/__init__.py @@ -5,4 +5,4 @@ """ __project__ = "oracle.oci-networking-mcp-server" -__version__ = "1.2.4" +__version__ = "1.2.5" diff --git a/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/server.py b/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/server.py index 548460b..645a8d3 100644 --- a/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/server.py +++ b/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/server.py @@ -33,6 +33,21 @@ mcp = FastMCP(name=__project__) +def _get_oci_client_kwargs(signer=None): + kwargs = { + "circuit_breaker_strategy": oci.circuit_breaker.CircuitBreakerStrategy( + failure_threshold=int(os.getenv("OCI_CIRCUIT_BREAKER_FAILURE_THRESHOLD", "10")), + recovery_timeout=int(os.getenv("OCI_CIRCUIT_BREAKER_RECOVERY_TIMEOUT", "30")), + ), + "circuit_breaker_callback": lambda exc: logger.warning( + "Circuit breaker triggered: %s", exc + ), + } + if signer is not None: + kwargs["signer"] = signer + return kwargs + + def get_networking_client(): config = oci.config.from_file( file_location=os.getenv("OCI_CONFIG_FILE", oci.config.DEFAULT_LOCATION), @@ -46,7 +61,7 @@ def get_networking_client(): with open(token_file, "r") as f: token = f.read() signer = oci.auth.signers.SecurityTokenSigner(token, private_key) - return oci.core.VirtualNetworkClient(config, signer=signer) + return oci.core.VirtualNetworkClient(config, **_get_oci_client_kwargs(signer)) @mcp.tool(description="Lists the VCNs in the specified compartment.") diff --git a/src/oci-networking-mcp-server/pyproject.toml b/src/oci-networking-mcp-server/pyproject.toml index 10527e9..b4c2a4c 100644 --- a/src/oci-networking-mcp-server/pyproject.toml +++ b/src/oci-networking-mcp-server/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oracle.oci-networking-mcp-server" -version = "1.2.4" +version = "1.2.5" description = "OCI Networking Service MCP server" readme = "README.md" requires-python = ">=3.13" @@ -49,5 +49,3 @@ omit = [ [tool.coverage.report] precision = 2 fail_under = 90 - - diff --git a/src/oci-networking-mcp-server/uv.lock b/src/oci-networking-mcp-server/uv.lock index bf6d0be..6588451 100644 --- a/src/oci-networking-mcp-server/uv.lock +++ b/src/oci-networking-mcp-server/uv.lock @@ -783,7 +783,7 @@ wheels = [ [[package]] name = "oracle-oci-networking-mcp-server" -version = "1.2.4" +version = "1.2.5" source = { editable = "." } dependencies = [ { name = "fastmcp" }, diff --git a/src/oci-object-storage-mcp-server/oracle/oci_object_storage_mcp_server/__init__.py b/src/oci-object-storage-mcp-server/oracle/oci_object_storage_mcp_server/__init__.py index ea31b04..a04c31a 100644 --- a/src/oci-object-storage-mcp-server/oracle/oci_object_storage_mcp_server/__init__.py +++ b/src/oci-object-storage-mcp-server/oracle/oci_object_storage_mcp_server/__init__.py @@ -5,4 +5,4 @@ """ __project__ = "oracle.oci-object-storage-mcp-server" -__version__ = "1.1.4" +__version__ = "1.1.5" diff --git a/src/oci-object-storage-mcp-server/oracle/oci_object_storage_mcp_server/server.py b/src/oci-object-storage-mcp-server/oracle/oci_object_storage_mcp_server/server.py index c9b7d1b..fe6367c 100644 --- a/src/oci-object-storage-mcp-server/oracle/oci_object_storage_mcp_server/server.py +++ b/src/oci-object-storage-mcp-server/oracle/oci_object_storage_mcp_server/server.py @@ -29,6 +29,21 @@ mcp = FastMCP(name=__project__) +def _get_oci_client_kwargs(signer=None): + kwargs = { + "circuit_breaker_strategy": oci.circuit_breaker.CircuitBreakerStrategy( + failure_threshold=int(os.getenv("OCI_CIRCUIT_BREAKER_FAILURE_THRESHOLD", "10")), + recovery_timeout=int(os.getenv("OCI_CIRCUIT_BREAKER_RECOVERY_TIMEOUT", "30")), + ), + "circuit_breaker_callback": lambda exc: logger.warning( + "Circuit breaker triggered: %s", exc + ), + } + if signer is not None: + kwargs["signer"] = signer + return kwargs + + def get_object_storage_client(): config = oci.config.from_file( file_location=os.getenv("OCI_CONFIG_FILE", oci.config.DEFAULT_LOCATION), @@ -43,7 +58,7 @@ def get_object_storage_client(): with open(token_file, "r") as f: token = f.read() signer = oci.auth.signers.SecurityTokenSigner(token, private_key) - return oci.object_storage.ObjectStorageClient(config, signer=signer) + return oci.object_storage.ObjectStorageClient(config, **_get_oci_client_kwargs(signer)) # Object storage namespace diff --git a/src/oci-object-storage-mcp-server/pyproject.toml b/src/oci-object-storage-mcp-server/pyproject.toml index 9285baa..45c9e6c 100644 --- a/src/oci-object-storage-mcp-server/pyproject.toml +++ b/src/oci-object-storage-mcp-server/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oracle.oci-object-storage-mcp-server" -version = "1.1.4" +version = "1.1.5" description = "OCI Object Storage Service MCP server" readme = "README.md" authors = [ diff --git a/src/oci-object-storage-mcp-server/uv.lock b/src/oci-object-storage-mcp-server/uv.lock index d71f09f..2d91821 100644 --- a/src/oci-object-storage-mcp-server/uv.lock +++ b/src/oci-object-storage-mcp-server/uv.lock @@ -783,7 +783,7 @@ wheels = [ [[package]] name = "oracle-oci-object-storage-mcp-server" -version = "1.1.4" +version = "1.1.5" source = { editable = "." } dependencies = [ { name = "fastmcp" }, diff --git a/src/oci-recovery-mcp-server/oracle/oci_recovery_mcp_server/__init__.py b/src/oci-recovery-mcp-server/oracle/oci_recovery_mcp_server/__init__.py index d07bb5f..1c755a0 100644 --- a/src/oci-recovery-mcp-server/oracle/oci_recovery_mcp_server/__init__.py +++ b/src/oci-recovery-mcp-server/oracle/oci_recovery_mcp_server/__init__.py @@ -5,4 +5,4 @@ """ __project__ = "oracle.oci-recovery-mcp-server" -__version__ = "1.0.0" +__version__ = "1.0.1" diff --git a/src/oci-recovery-mcp-server/oracle/oci_recovery_mcp_server/server.py b/src/oci-recovery-mcp-server/oracle/oci_recovery_mcp_server/server.py index 1119a52..c57347f 100644 --- a/src/oci-recovery-mcp-server/oracle/oci_recovery_mcp_server/server.py +++ b/src/oci-recovery-mcp-server/oracle/oci_recovery_mcp_server/server.py @@ -613,6 +613,21 @@ def _build_signer_for_session(config: dict): return oci.auth.signers.SecurityTokenSigner(token, private_key) +def _get_oci_client_kwargs(signer=None): + kwargs = { + "circuit_breaker_strategy": oci.circuit_breaker.CircuitBreakerStrategy( + failure_threshold=int(os.getenv("OCI_CIRCUIT_BREAKER_FAILURE_THRESHOLD", "10")), + recovery_timeout=int(os.getenv("OCI_CIRCUIT_BREAKER_RECOVERY_TIMEOUT", "30")), + ), + "circuit_breaker_callback": lambda exc: logger.warning( + "Circuit breaker triggered: %s", exc + ), + } + if signer is not None: + kwargs["signer"] = signer + return kwargs + + # Create the FastMCP app that exposes the functions decorated with @mcp.tool mcp = FastMCP(name=__project__) @@ -630,10 +645,12 @@ def get_recovery_client( method = _effective_auth_method() if method == "apikey": - client = oci.recovery.DatabaseRecoveryClient(regional_config) + client = oci.recovery.DatabaseRecoveryClient(regional_config, **_get_oci_client_kwargs()) else: signer = _build_signer_for_session(regional_config) - client = oci.recovery.DatabaseRecoveryClient(regional_config, signer=signer) + client = oci.recovery.DatabaseRecoveryClient( + regional_config, **_get_oci_client_kwargs(signer) + ) rid = request_id or uuid.uuid4().hex return _wrap_oci_client(client, request_id=rid, client_name="recovery") @@ -643,10 +660,10 @@ def get_identity_client(*, request_id: Optional[str] = None): config = _load_oci_config_for_server() method = _effective_auth_method() if method == "apikey": - client = oci.identity.IdentityClient(config) + client = oci.identity.IdentityClient(config, **_get_oci_client_kwargs()) else: signer = _build_signer_for_session(config) - client = oci.identity.IdentityClient(config, signer=signer) + client = oci.identity.IdentityClient(config, **_get_oci_client_kwargs(signer)) rid = request_id or uuid.uuid4().hex return _wrap_oci_client(client, request_id=rid, client_name="identity") @@ -657,10 +674,10 @@ def get_database_client(region: str = None, *, request_id: Optional[str] = None) regional_config = config if region is None else {**config, "region": region} method = _effective_auth_method() if method == "apikey": - client = oci.database.DatabaseClient(regional_config) + client = oci.database.DatabaseClient(regional_config, **_get_oci_client_kwargs()) else: signer = _build_signer_for_session(regional_config) - client = oci.database.DatabaseClient(regional_config, signer=signer) + client = oci.database.DatabaseClient(regional_config, **_get_oci_client_kwargs(signer)) rid = request_id or uuid.uuid4().hex return _wrap_oci_client(client, request_id=rid, client_name="database") @@ -672,10 +689,12 @@ def get_monitoring_client(region: str | None = None, *, request_id: Optional[str regional_config = config if region is None else {**config, "region": region} method = _effective_auth_method() if method == "apikey": - client = oci.monitoring.MonitoringClient(regional_config) + client = oci.monitoring.MonitoringClient(regional_config, **_get_oci_client_kwargs()) else: signer = _build_signer_for_session(regional_config) - client = oci.monitoring.MonitoringClient(regional_config, signer=signer) + client = oci.monitoring.MonitoringClient( + regional_config, **_get_oci_client_kwargs(signer) + ) rid = request_id or uuid.uuid4().hex return _wrap_oci_client(client, request_id=rid, client_name="monitoring") @@ -3078,4 +3097,3 @@ def main(): if __name__ == "__main__": main() - diff --git a/src/oci-recovery-mcp-server/oracle/oci_recovery_mcp_server/tests/test_recovery_tools.py b/src/oci-recovery-mcp-server/oracle/oci_recovery_mcp_server/tests/test_recovery_tools.py index ebabd0a..c9c649b 100644 --- a/src/oci-recovery-mcp-server/oracle/oci_recovery_mcp_server/tests/test_recovery_tools.py +++ b/src/oci-recovery-mcp-server/oracle/oci_recovery_mcp_server/tests/test_recovery_tools.py @@ -10,9 +10,60 @@ import oci import pytest from fastmcp import Client +import oracle.oci_recovery_mcp_server.server as server from oracle.oci_recovery_mcp_server.server import mcp +class TestGetClientFactories: + @patch("oracle.oci_recovery_mcp_server.server._wrap_oci_client", side_effect=lambda client, **_: client) + @patch("oracle.oci_recovery_mcp_server.server.oci.recovery.DatabaseRecoveryClient") + @patch("oracle.oci_recovery_mcp_server.server._effective_auth_method", return_value="apikey") + @patch("oracle.oci_recovery_mcp_server.server._load_oci_config_for_server") + def test_get_recovery_client_apikey_passes_circuit_breaker( + self, + mock_load_config, + _mock_auth_method, + mock_client, + _mock_wrap, + ): + mock_load_config.return_value = {"region": "us-ashburn-1"} + + result = server.get_recovery_client(region="us-phoenix-1", request_id="rid") + + args, kwargs = mock_client.call_args + assert args[0]["region"] == "us-phoenix-1" + assert "signer" not in kwargs + assert isinstance(kwargs["circuit_breaker_strategy"], oci.circuit_breaker.CircuitBreakerStrategy) + assert callable(kwargs["circuit_breaker_callback"]) + assert result is mock_client.return_value + + @patch("oracle.oci_recovery_mcp_server.server._wrap_oci_client", side_effect=lambda client, **_: client) + @patch("oracle.oci_recovery_mcp_server.server._build_signer_for_session") + @patch("oracle.oci_recovery_mcp_server.server.oci.monitoring.MonitoringClient") + @patch("oracle.oci_recovery_mcp_server.server._effective_auth_method", return_value="session") + @patch("oracle.oci_recovery_mcp_server.server._load_oci_config_for_server") + def test_get_monitoring_client_session_passes_circuit_breaker( + self, + mock_load_config, + _mock_auth_method, + mock_client, + mock_build_signer, + _mock_wrap, + ): + mock_load_config.return_value = {"region": "us-ashburn-1"} + signer = object() + mock_build_signer.return_value = signer + + result = server.get_monitoring_client(region="us-phoenix-1", request_id="rid") + + args, kwargs = mock_client.call_args + assert args[0]["region"] == "us-phoenix-1" + assert kwargs["signer"] is signer + assert isinstance(kwargs["circuit_breaker_strategy"], oci.circuit_breaker.CircuitBreakerStrategy) + assert callable(kwargs["circuit_breaker_callback"]) + assert result is mock_client.return_value + + class TestRecoveryTools: @pytest.mark.asyncio @patch("oracle.oci_recovery_mcp_server.server.get_recovery_client") diff --git a/src/oci-recovery-mcp-server/pyproject.toml b/src/oci-recovery-mcp-server/pyproject.toml index a4285b5..3293435 100644 --- a/src/oci-recovery-mcp-server/pyproject.toml +++ b/src/oci-recovery-mcp-server/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oracle.oci-recovery-mcp-server" -version = "1.0.0" +version = "1.0.1" description = "OCI Recovery Service MCP server" readme = "README.md" requires-python = ">=3.13" diff --git a/src/oci-recovery-mcp-server/uv.lock b/src/oci-recovery-mcp-server/uv.lock index a480a1f..056af63 100644 --- a/src/oci-recovery-mcp-server/uv.lock +++ b/src/oci-recovery-mcp-server/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.13" [[package]] @@ -803,7 +803,7 @@ wheels = [ [[package]] name = "oracle-oci-recovery-mcp-server" -version = "1.0.0" +version = "1.0.1" source = { editable = "." } dependencies = [ { name = "fastmcp" }, diff --git a/src/oci-registry-mcp-server/oracle/oci_registry_mcp_server/__init__.py b/src/oci-registry-mcp-server/oracle/oci_registry_mcp_server/__init__.py index 4591e09..214f07e 100644 --- a/src/oci-registry-mcp-server/oracle/oci_registry_mcp_server/__init__.py +++ b/src/oci-registry-mcp-server/oracle/oci_registry_mcp_server/__init__.py @@ -5,4 +5,4 @@ """ __project__ = "oracle.oci-registry-mcp-server" -__version__ = "2.1.4" +__version__ = "2.1.5" diff --git a/src/oci-registry-mcp-server/oracle/oci_registry_mcp_server/server.py b/src/oci-registry-mcp-server/oracle/oci_registry_mcp_server/server.py index 689a304..c3eee77 100644 --- a/src/oci-registry-mcp-server/oracle/oci_registry_mcp_server/server.py +++ b/src/oci-registry-mcp-server/oracle/oci_registry_mcp_server/server.py @@ -25,6 +25,21 @@ mcp = FastMCP(name=__project__) +def _get_oci_client_kwargs(signer=None): + kwargs = { + "circuit_breaker_strategy": oci.circuit_breaker.CircuitBreakerStrategy( + failure_threshold=int(os.getenv("OCI_CIRCUIT_BREAKER_FAILURE_THRESHOLD", "10")), + recovery_timeout=int(os.getenv("OCI_CIRCUIT_BREAKER_RECOVERY_TIMEOUT", "30")), + ), + "circuit_breaker_callback": lambda exc: logger.warning( + "Circuit breaker triggered: %s", exc + ), + } + if signer is not None: + kwargs["signer"] = signer + return kwargs + + def get_ocir_client(): config = oci.config.from_file( file_location=os.getenv("OCI_CONFIG_FILE", oci.config.DEFAULT_LOCATION), @@ -40,7 +55,7 @@ def get_ocir_client(): with open(token_file, "r") as f: token = f.read() signer = oci.auth.signers.SecurityTokenSigner(token, private_key) - return oci.artifacts.ArtifactsClient(config, signer=signer) + return oci.artifacts.ArtifactsClient(config, **_get_oci_client_kwargs(signer)) @mcp.tool(description="List container repositories in the given compartment") diff --git a/src/oci-registry-mcp-server/pyproject.toml b/src/oci-registry-mcp-server/pyproject.toml index 1395112..234c773 100644 --- a/src/oci-registry-mcp-server/pyproject.toml +++ b/src/oci-registry-mcp-server/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oracle.oci-registry-mcp-server" -version = "2.1.4" +version = "2.1.5" description = "OCI Registry Service MCP server" readme = "README.md" requires-python = ">=3.13" diff --git a/src/oci-registry-mcp-server/uv.lock b/src/oci-registry-mcp-server/uv.lock index 2e1623b..7d94a49 100644 --- a/src/oci-registry-mcp-server/uv.lock +++ b/src/oci-registry-mcp-server/uv.lock @@ -783,7 +783,7 @@ wheels = [ [[package]] name = "oracle-oci-registry-mcp-server" -version = "2.1.4" +version = "2.1.5" source = { editable = "." } dependencies = [ { name = "fastmcp" }, diff --git a/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/__init__.py b/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/__init__.py index e3e5213..5120813 100644 --- a/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/__init__.py +++ b/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/__init__.py @@ -5,4 +5,4 @@ """ __project__ = "oracle.oci-resource-search-mcp-server" -__version__ = "2.1.4" +__version__ = "2.1.5" diff --git a/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/server.py b/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/server.py index a7d5175..a92728a 100644 --- a/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/server.py +++ b/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/server.py @@ -24,6 +24,21 @@ mcp = FastMCP(name=__project__) +def _get_oci_client_kwargs(signer=None): + kwargs = { + "circuit_breaker_strategy": oci.circuit_breaker.CircuitBreakerStrategy( + failure_threshold=int(os.getenv("OCI_CIRCUIT_BREAKER_FAILURE_THRESHOLD", "10")), + recovery_timeout=int(os.getenv("OCI_CIRCUIT_BREAKER_RECOVERY_TIMEOUT", "30")), + ), + "circuit_breaker_callback": lambda exc: logger.warning( + "Circuit breaker triggered: %s", exc + ), + } + if signer is not None: + kwargs["signer"] = signer + return kwargs + + def get_search_client(): logger.info("entering get_search_client") config = oci.config.from_file( @@ -40,7 +55,7 @@ def get_search_client(): with open(token_file, "r") as f: token = f.read() signer = oci.auth.signers.SecurityTokenSigner(token, private_key) - return oci.resource_search.ResourceSearchClient(config, signer=signer) + return oci.resource_search.ResourceSearchClient(config, **_get_oci_client_kwargs(signer)) @mcp.tool(description="Returns all resources") diff --git a/src/oci-resource-search-mcp-server/pyproject.toml b/src/oci-resource-search-mcp-server/pyproject.toml index fa3ba9d..3fb5a54 100644 --- a/src/oci-resource-search-mcp-server/pyproject.toml +++ b/src/oci-resource-search-mcp-server/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oracle.oci-resource-search-mcp-server" -version = "2.1.4" +version = "2.1.5" description = "OCI Resource Search Service MCP server" readme = "README.md" requires-python = ">=3.13" diff --git a/src/oci-resource-search-mcp-server/uv.lock b/src/oci-resource-search-mcp-server/uv.lock index b30b57f..107c02c 100644 --- a/src/oci-resource-search-mcp-server/uv.lock +++ b/src/oci-resource-search-mcp-server/uv.lock @@ -783,7 +783,7 @@ wheels = [ [[package]] name = "oracle-oci-resource-search-mcp-server" -version = "2.1.4" +version = "2.1.5" source = { editable = "." } dependencies = [ { name = "fastmcp" }, diff --git a/src/oci-usage-mcp-server/oracle/oci_usage_mcp_server/__init__.py b/src/oci-usage-mcp-server/oracle/oci_usage_mcp_server/__init__.py index 22a2fe4..2ea193e 100644 --- a/src/oci-usage-mcp-server/oracle/oci_usage_mcp_server/__init__.py +++ b/src/oci-usage-mcp-server/oracle/oci_usage_mcp_server/__init__.py @@ -5,4 +5,4 @@ """ __project__ = "oracle.oci-usage-mcp-server" -__version__ = "1.1.4" +__version__ = "1.1.5" diff --git a/src/oci-usage-mcp-server/oracle/oci_usage_mcp_server/server.py b/src/oci-usage-mcp-server/oracle/oci_usage_mcp_server/server.py index 1087ea8..511548f 100644 --- a/src/oci-usage-mcp-server/oracle/oci_usage_mcp_server/server.py +++ b/src/oci-usage-mcp-server/oracle/oci_usage_mcp_server/server.py @@ -19,6 +19,21 @@ mcp = FastMCP(name=__project__) +def _get_oci_client_kwargs(signer=None): + kwargs = { + "circuit_breaker_strategy": oci.circuit_breaker.CircuitBreakerStrategy( + failure_threshold=int(os.getenv("OCI_CIRCUIT_BREAKER_FAILURE_THRESHOLD", "10")), + recovery_timeout=int(os.getenv("OCI_CIRCUIT_BREAKER_RECOVERY_TIMEOUT", "30")), + ), + "circuit_breaker_callback": lambda exc: logger.warning( + "Circuit breaker triggered: %s", exc + ), + } + if signer is not None: + kwargs["signer"] = signer + return kwargs + + def get_usage_client(): logger.info("entering get_monitoring_client") config = oci.config.from_file( @@ -34,7 +49,7 @@ def get_usage_client(): with open(token_file, "r") as f: token = f.read() signer = oci.auth.signers.SecurityTokenSigner(token, private_key) - return oci.usage_api.UsageapiClient(config, signer=signer) + return oci.usage_api.UsageapiClient(config, **_get_oci_client_kwargs(signer)) @mcp.tool diff --git a/src/oci-usage-mcp-server/pyproject.toml b/src/oci-usage-mcp-server/pyproject.toml index 94c0876..85b74e3 100644 --- a/src/oci-usage-mcp-server/pyproject.toml +++ b/src/oci-usage-mcp-server/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "oracle.oci-usage-mcp-server" -version = "1.1.4" +version = "1.1.5" description = "OCI Usage MCP server" readme = "README.md" license = "UPL-1.0" diff --git a/src/oci-usage-mcp-server/uv.lock b/src/oci-usage-mcp-server/uv.lock index 84d4560..85d5adc 100644 --- a/src/oci-usage-mcp-server/uv.lock +++ b/src/oci-usage-mcp-server/uv.lock @@ -783,7 +783,7 @@ wheels = [ [[package]] name = "oracle-oci-usage-mcp-server" -version = "1.1.4" +version = "1.1.5" source = { editable = "." } dependencies = [ { name = "fastmcp" },