From 5aed21e22be1216da726923654387cd8fd5ad4e9 Mon Sep 17 00:00:00 2001 From: Chris Boffa Date: Tue, 10 Feb 2026 12:21:08 -0500 Subject: [PATCH] feat(ruff): replace tox with ruff linter --- .github/workflows/lint.yml | 8 +- Makefile | 4 +- requirements-dev.txt | 2 +- ruff.toml | 37 ++ scripts/oci-api-denylist-generator.py | 26 +- .../oracle/oci_api_mcp_server/denylist.py | 7 +- .../oracle/oci_api_mcp_server/server.py | 5 +- .../tests/test_oci_api_tools.py | 34 +- .../oracle/oci_api_mcp_server/utils.py | 8 +- .../oci_cloud_guard_mcp_server/models.py | 79 ++--- .../oci_cloud_guard_mcp_server/server.py | 24 +- .../tests/test_cloud_guard_models.py | 3 +- .../tests/test_cloud_guard_tools.py | 37 +- .../oracle/oci_cloud_mcp_server/server.py | 81 ++--- .../tests/test_bootstrap_and_introspection.py | 32 +- .../tests/test_edge_cases.py | 108 ++---- .../tests/test_model_coercion.py | 13 +- .../tests/test_models_and_serialization.py | 13 +- .../tests/test_pagination_and_invocation.py | 12 +- .../tests/test_server_extras.py | 320 ++++++------------ .../oci_cloud_mcp_server/tests/test_tools.py | 112 +++--- .../oracle/oci_cloud_mcp_server/utils.py | 8 +- .../models.py | 122 +++---- .../server.py | 30 +- .../test_compute_instance_agent_models.py | 8 +- .../test_compute_instance_agent_tools.py | 91 ++--- .../oracle/oci_compute_mcp_server/consts.py | 4 +- .../oracle/oci_compute_mcp_server/models.py | 212 +++--------- .../oracle/oci_compute_mcp_server/server.py | 31 +- .../tests/test_compute_models.py | 8 +- .../tests/test_compute_tools.py | 25 +- .../oracle/oci_faaas_mcp_server/models.py | 75 ++-- .../oracle/oci_faaas_mcp_server/server.py | 58 +--- .../tests/test_faaas_models.py | 4 +- .../tests/test_faaas_tools.py | 50 +-- .../oracle/oci_identity_mcp_server/models.py | 54 +-- .../oracle/oci_identity_mcp_server/server.py | 34 +- .../tests/test_identity_tools.py | 77 ++--- .../oracle/oci_logging_mcp_server/models.py | 84 ++--- .../oracle/oci_logging_mcp_server/server.py | 36 +- .../tests/test_logging_models.py | 14 +- .../tests/test_logging_tools.py | 44 +-- .../oracle/oci_migration_mcp_server/models.py | 32 +- .../oracle/oci_migration_mcp_server/server.py | 12 +- .../tests/test_migration_models.py | 16 +- .../tests/test_migration_tools.py | 45 +-- .../oci_monitoring_mcp_server/alarm_models.py | 57 +--- .../metric_models.py | 59 +--- .../oci_monitoring_mcp_server/server.py | 4 +- .../tests/test_monitoring_models.py | 4 +- .../tests/test_monitoring_tools.py | 45 +-- .../models.py | 86 ++--- .../server.py | 42 +-- .../test_network_load_balancer_models.py | 8 +- .../tests/test_network_load_balancer_tools.py | 79 ++--- .../oci_networking_mcp_server/models.py | 183 +++------- .../oci_networking_mcp_server/server.py | 12 +- .../tests/test_networking_models.py | 8 +- .../tests/test_networking_tools.py | 47 +-- .../oci_object_storage_mcp_server/models.py | 63 +--- .../oci_object_storage_mcp_server/server.py | 8 +- .../tests/test_object_storage_tools.py | 50 +-- .../oracle/oci_registry_mcp_server/models.py | 60 +--- .../oracle/oci_registry_mcp_server/server.py | 26 +- .../tests/test_registry_models.py | 4 +- .../tests/test_registry_tools.py | 26 +- .../oci_resource_search_mcp_server/models.py | 4 +- .../oci_resource_search_mcp_server/server.py | 23 +- .../tests/test_resource_search_models.py | 8 +- .../tests/test_resource_search_tools.py | 117 +++---- .../oracle/oci_usage_mcp_server/server.py | 8 +- .../tests/test_usage_tools.py | 17 +- tests/e2e/features/environment.py | 14 +- tests/e2e/features/mocks/proxy_shim.py | 8 +- .../features/mocks/services/compute_routes.py | 8 +- .../features/mocks/services/faaas_routes.py | 10 +- .../mocks/services/identity_routes.py | 6 +- tests/e2e/features/steps/general-prompts.py | 8 +- .../steps/oci-api-mcp-server-steps.py | 14 +- .../steps/oci-cloud-guard-mcp-server-steps.py | 22 +- .../steps/oci-compute-mcp-server-steps.py | 30 +- .../steps/oci-faaas-mcp-server-steps.py | 14 +- .../steps/oci-identity-mcp-server-steps.py | 14 +- .../steps/oci-logging-mcp-server-steps.py | 16 +- .../steps/oci-migration-mcp-server-steps.py | 18 +- tox.ini | 44 --- 86 files changed, 1015 insertions(+), 2398 deletions(-) create mode 100644 ruff.toml delete mode 100644 tox.ini diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b7f2961f..2229a47f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,7 +1,7 @@ name: Lint on: push: - branches: + branches: - "main" pull_request: branches: @@ -19,8 +19,8 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install -r requirements-dev.txt - - name: Test with tox + - name: Lint with Ruff run: | - tox run -e lint + ruff check - name: Check uv.lock - run: make lock-check \ No newline at end of file + run: make lock-check diff --git a/Makefile b/Makefile index b019cf16..1e17175c 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,7 @@ lock-check: done lint: - uv run tox -e lint + uv tool run ruff check test: @set -e -o pipefail; \ @@ -88,7 +88,7 @@ publish: done format: - uv tool run --from 'tox==4.30.2' tox -e format + uv tool run ruff format e2e-tests: build install behave tests/e2e/features && cd .. diff --git a/requirements-dev.txt b/requirements-dev.txt index 602a9eba..b48ec989 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,5 +2,5 @@ pytest pytest-asyncio pytest-cov -tox uv +ruff diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 00000000..2683ac11 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,37 @@ +# Exclude a variety of commonly ignored directories plus some external +exclude = [ + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "build", + "dist", + "venv", + "src/dbtools-mcp-server", + "src/mysql-mcp-server", + "src/oci-pricing-mcp-server", + "src/oracle-db-doc-mcp-server", + "src/oci-database-mcp-server" +] + +# Same as Black. +line-length = 110 + +[lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or +# McCabe complexity (`C901`) by default. +select = ["E701"] +ignore = [] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +[format] diff --git a/scripts/oci-api-denylist-generator.py b/scripts/oci-api-denylist-generator.py index efc37436..dde2ba8b 100644 --- a/scripts/oci-api-denylist-generator.py +++ b/scripts/oci-api-denylist-generator.py @@ -11,9 +11,7 @@ def get_oci_version(): - result = subprocess.run( - ["oci", "--version", "--raw-output"], capture_output=True, text=True - ) + result = subprocess.run(["oci", "--version", "--raw-output"], capture_output=True, text=True) return result.stdout.strip() @@ -32,11 +30,9 @@ def get_services(): def get_sub_commands(command: str): indentation_level = len(command.split()) - print(f"{' '*indentation_level}Getting subcommands for: {command}") + print(f"{' ' * indentation_level}Getting subcommands for: {command}") try: - result = subprocess.run( - f"oci {command} --help", shell=True, capture_output=True, text=True - ) + result = subprocess.run(f"oci {command} --help", shell=True, capture_output=True, text=True) output = result.stdout.splitlines() sub_commands = [] in_commands_section = False @@ -50,7 +46,7 @@ def get_sub_commands(command: str): # match = re.match(r"^\s{2}(\w+)", line) match = line.split()[0] if match: - print(f"{' '*indentation_level}Appending sub command {match}") + print(f"{' ' * indentation_level}Appending sub command {match}") sub_commands.append(match) if not sub_commands: return [command] @@ -95,17 +91,11 @@ def create_denylist(version): commands_file = f"commands_{version}.txt" if os.path.exists(denylist_filename): - backup_filename = ( - f"{denylist_filename}_backup_{datetime.now().strftime('%d%b%y_%H%M')}" - ) + backup_filename = f"{denylist_filename}_backup_{datetime.now().strftime('%d%b%y_%H%M')}" os.rename(denylist_filename, backup_filename) with open(commands_file, "r") as f: - commands = [ - line.strip() - for line in f - if not line.strip().startswith("#") and len(line.strip()) > 0 - ] + commands = [line.strip() for line in f if not line.strip().startswith("#") and len(line.strip()) > 0] actions = [ "delete", @@ -118,9 +108,7 @@ def create_denylist(version): ] denied_commands = [ - cmd.strip() - for cmd in commands - if any(cmd.split()[-1].startswith(action) for action in actions) + cmd.strip() for cmd in commands if any(cmd.split()[-1].startswith(action) for action in actions) ] with open(denylist_filename, "w") as f: diff --git a/src/oci-api-mcp-server/oracle/oci_api_mcp_server/denylist.py b/src/oci-api-mcp-server/oracle/oci_api_mcp_server/denylist.py index 97280e4b..f5c9b878 100644 --- a/src/oci-api-mcp-server/oracle/oci_api_mcp_server/denylist.py +++ b/src/oci-api-mcp-server/oracle/oci_api_mcp_server/denylist.py @@ -39,12 +39,7 @@ def remove_params_from_command(self, command: str) -> str: i = 0 while i < len(command_parts): if command_parts[i].startswith("--"): - i += ( - 1 - if i + 1 >= len(command_parts) - or command_parts[i + 1].startswith("--") - else 2 - ) + i += 1 if i + 1 >= len(command_parts) or command_parts[i + 1].startswith("--") else 2 else: filtered_parts.append(command_parts[i]) i += 1 diff --git a/src/oci-api-mcp-server/oracle/oci_api_mcp_server/server.py b/src/oci-api-mcp-server/oracle/oci_api_mcp_server/server.py index 9b9499c9..6adc4f8b 100644 --- a/src/oci-api-mcp-server/oracle/oci_api_mcp_server/server.py +++ b/src/oci-api-mcp-server/oracle/oci_api_mcp_server/server.py @@ -169,10 +169,7 @@ def run_oci_command( # Run OCI CLI command using subprocess try: result = subprocess.run( - ["oci", "--profile"] - + [profile] - + ["--auth", "security_token"] - + command.split(), + ["oci", "--profile"] + [profile] + ["--auth", "security_token"] + command.split(), env=env_copy, capture_output=True, text=True, diff --git a/src/oci-api-mcp-server/oracle/oci_api_mcp_server/tests/test_oci_api_tools.py b/src/oci-api-mcp-server/oracle/oci_api_mcp_server/tests/test_oci_api_tools.py index 07bb803b..cb546ee6 100644 --- a/src/oci-api-mcp-server/oracle/oci_api_mcp_server/tests/test_oci_api_tools.py +++ b/src/oci-api-mcp-server/oracle/oci_api_mcp_server/tests/test_oci_api_tools.py @@ -30,16 +30,11 @@ async def test_get_oci_command_help_success(self, mock_run): async with Client(mcp) as client: result = ( - await client.call_tool( - "get_oci_command_help", {"command": "compute instance list"} - ) + await client.call_tool("get_oci_command_help", {"command": "compute instance list"}) ).structured_content["result"] assert result == "Help output" - assert ( - mock_run.call_args.kwargs["env"]["OCI_SDK_APPEND_USER_AGENT"] - == USER_AGENT - ) + assert mock_run.call_args.kwargs["env"]["OCI_SDK_APPEND_USER_AGENT"] == USER_AGENT mock_run.assert_called_once_with( ["oci", "compute", "instance", "list", "--help"], env=ANY, @@ -64,9 +59,7 @@ async def test_get_oci_command_help_failure(self, mock_run): async with Client(mcp) as client: result = ( - await client.call_tool( - "get_oci_command_help", {"command": "compute instance list"} - ) + await client.call_tool("get_oci_command_help", {"command": "compute instance list"}) ).structured_content["result"] assert "Error: Some error" in result @@ -83,9 +76,7 @@ async def test_run_oci_command_success(self, mock_run): mock_run.return_value = mock_result async with Client(mcp) as client: - result = ( - await client.call_tool("run_oci_command", {"command": command}) - ).data + result = (await client.call_tool("run_oci_command", {"command": command})).data assert result == { "command": command, @@ -106,9 +97,7 @@ async def test_run_oci_command_string_success(self, mock_run): mock_run.return_value = mock_result async with Client(mcp) as client: - result = ( - await client.call_tool("run_oci_command", {"command": command}) - ).data + result = (await client.call_tool("run_oci_command", {"command": command})).data assert result == { "command": command, @@ -135,9 +124,7 @@ async def test_run_oci_command_failure(self, mock_run): ) async with Client(mcp) as client: - result = ( - await client.call_tool("run_oci_command", {"command": command}) - ).data + result = (await client.call_tool("run_oci_command", {"command": command})).data assert result == { "command": command, @@ -158,10 +145,7 @@ async def test_get_oci_commands_success(self, mock_run): result = (await client.read_resource("resource://oci-api-commands"))[0].text assert result == "OCI commands output" - assert ( - mock_run.call_args.kwargs["env"]["OCI_SDK_APPEND_USER_AGENT"] - == USER_AGENT - ) + assert mock_run.call_args.kwargs["env"]["OCI_SDK_APPEND_USER_AGENT"] == USER_AGENT mock_run.assert_called_once_with( ["oci", "--help"], env=ANY, @@ -200,9 +184,7 @@ async def test_run_oci_command_denied(self, mock_json_loads, mock_run): async with Client(mcp) as client: result = ( - await client.call_tool( - "run_oci_command", {"command": "compute instance terminate"} - ) + await client.call_tool("run_oci_command", {"command": "compute instance terminate"}) ).data assert "error" in result diff --git a/src/oci-api-mcp-server/oracle/oci_api_mcp_server/utils.py b/src/oci-api-mcp-server/oracle/oci_api_mcp_server/utils.py index 933dce0b..420a17cf 100644 --- a/src/oci-api-mcp-server/oracle/oci_api_mcp_server/utils.py +++ b/src/oci-api-mcp-server/oracle/oci_api_mcp_server/utils.py @@ -10,15 +10,11 @@ def initAuditLogger(logger): # Create a rotating file handler - handler = RotatingFileHandler( - "/tmp/audit.log", maxBytes=5 * 1024 * 1024, backupCount=1 - ) + handler = RotatingFileHandler("/tmp/audit.log", maxBytes=5 * 1024 * 1024, backupCount=1) handler.setLevel(logging.INFO) # Create a logging format - formatter = logging.Formatter( - "%(asctime)s - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") handler.setFormatter(formatter) # Add the handler to the logger diff --git a/src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/models.py b/src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/models.py index 33d21732..935c19c5 100644 --- a/src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/models.py +++ b/src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/models.py @@ -47,13 +47,10 @@ class ResourceLock(BaseModel): message: Optional[str] = Field( None, description=( - "A message added by the creator of the lock, typically indicating why " - "the resource is locked." + "A message added by the creator of the lock, typically indicating why the resource is locked." ), ) - time_created: Optional[datetime] = Field( - None, description="When the lock was created (RFC3339)." - ) + time_created: Optional[datetime] = Field(None, description="When the lock was created (RFC3339).") class Problem(BaseModel): @@ -61,35 +58,23 @@ class Problem(BaseModel): Pydantic model mirroring oci.cloud_guard.models.Problem. """ - id: Optional[str] = Field( - None, description="Unique identifier that can't be changed after creation." - ) - compartment_id: Optional[str] = Field( - None, description="Compartment OCID where the resource is created." - ) + id: Optional[str] = Field(None, description="Unique identifier that can't be changed after creation.") + compartment_id: Optional[str] = Field(None, description="Compartment OCID where the resource is created.") detector_rule_id: Optional[str] = Field( None, description="Unique identifier of the detector rule that triggered the problem.", ) region: Optional[str] = Field(None, description="DEPRECATED.") - regions: Optional[List[str]] = Field( - None, description="Regions where the problem is found." - ) - risk_level: Optional[ - Literal["CRITICAL", "HIGH", "MEDIUM", "LOW", "MINOR", "UNKNOWN_ENUM_VALUE"] - ] = Field(None, description="The risk level for the problem.") - risk_score: Optional[float] = Field( - None, description="The risk score for the problem." + regions: Optional[List[str]] = Field(None, description="Regions where the problem is found.") + risk_level: Optional[Literal["CRITICAL", "HIGH", "MEDIUM", "LOW", "MINOR", "UNKNOWN_ENUM_VALUE"]] = Field( + None, description="The risk level for the problem." ) + risk_score: Optional[float] = Field(None, description="The risk score for the problem.") peak_risk_score_date: Optional[str] = Field( None, - description=( - "The date and time for the peak risk score observed for the problem (RFC3339)." - ), - ) - peak_risk_score: Optional[float] = Field( - None, description="Peak risk score for the problem." + description=("The date and time for the peak risk score observed for the problem (RFC3339)."), ) + peak_risk_score: Optional[float] = Field(None, description="Peak risk score for the problem.") auto_resolve_date: Optional[str] = Field( None, description="The date and time when the problem will be auto resolved (RFC3339).", @@ -101,28 +86,20 @@ class Problem(BaseModel): resource_id: Optional[str] = Field( None, description="Unique identifier of the resource affected by the problem." ) - resource_name: Optional[str] = Field( - None, description="Display name of the affected resource." - ) - resource_type: Optional[str] = Field( - None, description="Type of the affected resource." - ) - labels: Optional[List[str]] = Field( - None, description="User-defined labels on the problem." - ) + resource_name: Optional[str] = Field(None, description="Display name of the affected resource.") + resource_type: Optional[str] = Field(None, description="Type of the affected resource.") + labels: Optional[List[str]] = Field(None, description="User-defined labels on the problem.") time_last_detected: Optional[datetime] = Field( None, description="The date and time the problem was last detected (RFC3339)." ) time_first_detected: Optional[datetime] = Field( None, description="The date and time the problem was first detected (RFC3339)." ) - lifecycle_state: Optional[Literal["ACTIVE", "INACTIVE", "UNKNOWN_ENUM_VALUE"]] = ( - Field(None, description="The current lifecycle state of the problem.") + lifecycle_state: Optional[Literal["ACTIVE", "INACTIVE", "UNKNOWN_ENUM_VALUE"]] = Field( + None, description="The current lifecycle state of the problem." ) - lifecycle_detail: Optional[ - Literal["OPEN", "RESOLVED", "DISMISSED", "DELETED", "UNKNOWN_ENUM_VALUE"] - ] = Field( - None, description="Additional details on the substate of the lifecycle state." + lifecycle_detail: Optional[Literal["OPEN", "RESOLVED", "DISMISSED", "DELETED", "UNKNOWN_ENUM_VALUE"]] = ( + Field(None, description="Additional details on the substate of the lifecycle state.") ) detector_id: Optional[ Literal[ @@ -145,22 +122,14 @@ class Problem(BaseModel): None, description="Additional details of the problem as key/value pairs." ) description: Optional[str] = Field(None, description="Description of the problem.") - recommendation: Optional[str] = Field( - None, description="Recommendation for the problem." - ) + recommendation: Optional[str] = Field(None, description="Recommendation for the problem.") comment: Optional[str] = Field(None, description="User comments on the problem.") impacted_resource_id: Optional[str] = Field( None, description="Unique identifier of the resource impacted by the problem." ) - impacted_resource_name: Optional[str] = Field( - None, description="Display name of the impacted resource." - ) - impacted_resource_type: Optional[str] = Field( - None, description="Type of the impacted resource." - ) - locks: Optional[List[ResourceLock]] = Field( - None, description="Locks associated with this resource." - ) + impacted_resource_name: Optional[str] = Field(None, description="Display name of the impacted resource.") + impacted_resource_type: Optional[str] = Field(None, description="Type of the impacted resource.") + locks: Optional[List[ResourceLock]] = Field(None, description="Locks associated with this resource.") def map_resource_lock(rl) -> ResourceLock | None: @@ -227,9 +196,9 @@ class UpdateProblemStatusDetails(BaseModel): Pydantic model mirroring oci.cloud_guard.models.UpdateProblemStatusDetails. """ - status: Optional[ - Literal["OPEN", "RESOLVED", "DISMISSED", "DELETED", "UNKNOWN_ENUM_VALUE"] - ] = Field(None, description="Action taken by user.") + status: Optional[Literal["OPEN", "RESOLVED", "DISMISSED", "DELETED", "UNKNOWN_ENUM_VALUE"]] = Field( + None, description="Action taken by user." + ) comment: Optional[str] = Field(None, description="User comments.") 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 86bf9bcc..dc2065a3 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 @@ -50,9 +50,7 @@ def get_cloud_guard_client(): def list_problems( compartment_id: str = Field(..., description="The OCID of the compartment"), risk_level: Optional[str] = Field(None, description="Risk level of the problem"), - lifecycle_state: Optional[ - Literal["ACTIVE", "INACTIVE", "UNKNOWN_ENUM_VALUE"] - ] = Field( + lifecycle_state: Optional[Literal["ACTIVE", "INACTIVE", "UNKNOWN_ENUM_VALUE"]] = Field( "ACTIVE", description="The field lifecycle state. " "Only one state can be provided. Default value for state is active.", @@ -61,14 +59,10 @@ def list_problems( None, description="Comma separated list of detector rule IDs to be passed in to match against Problems.", ), - time_range_days: Optional[int] = Field( - 30, description="Number of days to look back for problems" - ), + time_range_days: Optional[int] = Field(30, description="Number of days to look back for problems"), limit: Optional[int] = Field(10, description="The number of problems to return"), ) -> list[Problem]: - time_filter = ( - datetime.now(timezone.utc) - timedelta(days=time_range_days) - ).isoformat() + time_filter = (datetime.now(timezone.utc) - timedelta(days=time_range_days)).isoformat() kwargs = { "compartment_id": compartment_id, @@ -97,9 +91,7 @@ def list_problems( name="get_problem_details", description="Get the details for a Problem identified by problemId.", ) -def get_problem_details( - problem_id: str = Field(..., description="The OCID of the problem") -) -> Problem: +def get_problem_details(problem_id: str = Field(..., description="The OCID of the problem")) -> Problem: response = get_cloud_guard_client().get_problem(problem_id=problem_id) problem = response.data return map_problem(problem) @@ -112,17 +104,13 @@ def get_problem_details( ) def update_problem_status( problem_id: str = Field(..., description="The OCID of the problem"), - status: Literal[ - "OPEN", "RESOLVED", "DISMISSED", "DELETED", "UNKNOWN_ENUM_VALUE" - ] = Field( + status: Literal["OPEN", "RESOLVED", "DISMISSED", "DELETED", "UNKNOWN_ENUM_VALUE"] = Field( "OPEN", description="Action taken by user. Allowed values are: OPEN, RESOLVED, DISMISSED, CLOSED", ), comment: str = Field(None, description="A comment from the user"), ) -> Problem: - updated_problem_status = oci.cloud_guard.models.UpdateProblemStatusDetails( - status=status, comment=comment - ) + updated_problem_status = oci.cloud_guard.models.UpdateProblemStatusDetails(status=status, comment=comment) response = get_cloud_guard_client().update_problem_status( problem_id=problem_id, update_problem_status_details=updated_problem_status, diff --git a/src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/tests/test_cloud_guard_models.py b/src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/tests/test_cloud_guard_models.py index e0eda31a..fcfc13a1 100644 --- a/src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/tests/test_cloud_guard_models.py +++ b/src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/tests/test_cloud_guard_models.py @@ -168,8 +168,7 @@ def test_map_problem_maps_all_known_fields_and_locks(self): assert mapped.peak_risk_score == problem_data.peak_risk_score assert mapped.auto_resolve_date == problem_data.auto_resolve_date assert ( - mapped.peak_risk_score_lookup_period_in_days - == problem_data.peak_risk_score_lookup_period_in_days + mapped.peak_risk_score_lookup_period_in_days == problem_data.peak_risk_score_lookup_period_in_days ) assert mapped.resource_id == problem_data.resource_id assert mapped.resource_name == problem_data.resource_name diff --git a/src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/tests/test_cloud_guard_tools.py b/src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/tests/test_cloud_guard_tools.py index e3fa177f..50084b91 100644 --- a/src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/tests/test_cloud_guard_tools.py +++ b/src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/tests/test_cloud_guard_tools.py @@ -37,9 +37,7 @@ async def test_list_all_problems(self, mock_get_client): async with Client(server.mcp) as client: result = ( - await client.call_tool( - "list_problems", {"compartment_id": "test_compartment"} - ) + await client.call_tool("list_problems", {"compartment_id": "test_compartment"}) ).structured_content["result"] assert len(result) == 1 @@ -97,9 +95,7 @@ async def test_update_problem_status(self, mock_get_client): lifecycle_detail=status, comment=comment, ) - mock_client.update_problem_status.return_value = ( - mock_update_problem_status_response - ) + mock_client.update_problem_status.return_value = mock_update_problem_status_response async with Client(server.mcp) as client: result = ( @@ -166,12 +162,8 @@ def test_main_with_only_port(self, mock_getenv, mock_mcp_run): class TestGetClient: @patch("oracle.oci_cloud_guard_mcp_server.server.CloudGuardClient") - @patch( - "oracle.oci_cloud_guard_mcp_server.server.oci.auth.signers.SecurityTokenSigner" - ) - @patch( - "oracle.oci_cloud_guard_mcp_server.server.oci.signer.load_private_key_from_file" - ) + @patch("oracle.oci_cloud_guard_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_cloud_guard_mcp_server.server.oci.signer.load_private_key_from_file") @patch( "oracle.oci_cloud_guard_mcp_server.server.open", new_callable=mock_open, @@ -209,25 +201,21 @@ def test_get_cloud_guard_client_with_profile_env( profile_name="MYPROFILE", ) mock_open_file.assert_called_once_with("/abs/path/to/token", "r") - mock_security_token_signer.assert_called_once_with( - "SECURITY_TOKEN", private_key_obj - ) + mock_security_token_signer.assert_called_once_with("SECURITY_TOKEN", private_key_obj) # Ensure user agent was set on the same config dict passed into client args, _ = mock_client.call_args passed_config = args[0] assert passed_config is config - expected_user_agent = f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + expected_user_agent = ( + f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + ) assert passed_config.get("additional_user_agent") == expected_user_agent # And we returned the client instance assert result == mock_client.return_value @patch("oracle.oci_cloud_guard_mcp_server.server.CloudGuardClient") - @patch( - "oracle.oci_cloud_guard_mcp_server.server.oci.auth.signers.SecurityTokenSigner" - ) - @patch( - "oracle.oci_cloud_guard_mcp_server.server.oci.signer.load_private_key_from_file" - ) + @patch("oracle.oci_cloud_guard_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_cloud_guard_mcp_server.server.oci.signer.load_private_key_from_file") @patch( "oracle.oci_cloud_guard_mcp_server.server.open", new_callable=mock_open, @@ -269,9 +257,6 @@ def test_get_cloud_guard_client_uses_default_profile_when_env_missing( cc_args, _ = mock_client.call_args assert cc_args[0] is config assert "additional_user_agent" in config - assert ( - isinstance(config["additional_user_agent"], str) - and "/" in config["additional_user_agent"] - ) + assert isinstance(config["additional_user_agent"], str) and "/" in config["additional_user_agent"] # Returned object is client instance assert srv_client is mock_client.return_value 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 a308c96d..d8e07575 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 @@ -49,9 +49,7 @@ def _get_config_and_signer() -> Tuple[Dict[str, Any], Any]: - If a security_token_file exists, use SecurityTokenSigner (session auth). - Otherwise, fall back to API key Signer from config. """ - config = oci.config.from_file( - profile_name=os.getenv("OCI_CONFIG_PROFILE", oci.config.DEFAULT_PROFILE) - ) + config = oci.config.from_file(profile_name=os.getenv("OCI_CONFIG_PROFILE", oci.config.DEFAULT_PROFILE)) config["additional_user_agent"] = _ADDITIONAL_UA # try security token @@ -98,9 +96,7 @@ def _import_client(client_fqn: str) -> Any: Example: 'oci.core.ComputeClient' """ if "." not in client_fqn: - raise ValueError( - "client_fqn must be a fully-qualified class name like 'oci.core.ComputeClient'" - ) + raise ValueError("client_fqn must be a fully-qualified class name like 'oci.core.ComputeClient'") module_name, class_name = client_fqn.rsplit(".", 1) module = import_module(module_name) cls = getattr(module, class_name) @@ -160,9 +156,7 @@ def _coerce_mapping_values( new_list: List[Any] = [] for item in val: if isinstance(item, dict): - new_list.append( - _construct_model_from_mapping(item, models_module, []) - ) + new_list.append(_construct_model_from_mapping(item, models_module, [])) else: new_list.append(item) out[key] = new_list @@ -190,9 +184,7 @@ def _construct_model_from_mapping( if parent_prefix_hint is None: parent_prefix_hint = cand # recursively coerce nested mappings/lists before attempting construction - clean = _coerce_mapping_values( - clean, models_module, parent_prefix=parent_prefix_hint - ) + clean = _coerce_mapping_values(clean, models_module, parent_prefix=parent_prefix_hint) # try explicit FQN first if isinstance(fqn, str): try: @@ -215,9 +207,7 @@ def _construct_model_from_mapping( try: swagger_types = getattr(cls, "swagger_types", None) if isinstance(swagger_types, dict): - filtered_clean = { - k: v for k, v in clean.items() if k in swagger_types - } + filtered_clean = {k: v for k, v in clean.items() if k in swagger_types} except Exception: filtered_clean = clean try: @@ -237,9 +227,7 @@ def _construct_model_from_mapping( try: swagger_types = getattr(cls, "swagger_types", None) if isinstance(swagger_types, dict): - filtered_clean = { - k: v for k, v in clean.items() if k in swagger_types - } + filtered_clean = {k: v for k, v in clean.items() if k in swagger_types} except Exception: filtered_clean = clean try: @@ -253,9 +241,7 @@ def _construct_model_from_mapping( return mapping -def _coerce_params_to_oci_models( - client_fqn: str, operation: str, params: Dict[str, Any] -) -> Dict[str, Any]: +def _coerce_params_to_oci_models(client_fqn: str, operation: str, params: Dict[str, Any]) -> Dict[str, Any]: """ Convert plain dict/list params to OCI model instances when appropriate. Heuristics: @@ -284,9 +270,7 @@ def _coerce_params_to_oci_models( candidates.append(f"Update{base}Details") # rename "_details" to the SDK's expected # "create__details"/"update__details" - if operation.startswith("create_") or operation.startswith( - "update_" - ): + if operation.startswith("create_") or operation.startswith("update_"): _, _, op_rest = operation.partition("_") if key == f"{op_rest}_details": # only rename to SDK-expected key when destination not already @@ -295,21 +279,14 @@ def _coerce_params_to_oci_models( if alt_key not in params: dest_key = alt_key break - out[dest_key] = _construct_model_from_mapping( - val, models_module, candidates - ) + out[dest_key] = _construct_model_from_mapping(val, models_module, candidates) elif isinstance(val, list): new_list: List[Any] = [] for item in val: if isinstance(item, dict): # only construct list items if explicit model hint is present - if any( - hint in item - for hint in ("__model_fqn", "__model", "__class_fqn", "__class") - ): - new_list.append( - _construct_model_from_mapping(item, models_module, []) - ) + if any(hint in item for hint in ("__model_fqn", "__model", "__class_fqn", "__class")): + new_list.append(_construct_model_from_mapping(item, models_module, [])) else: new_list.append(item) else: @@ -480,9 +457,7 @@ def _call_with_pagination_if_applicable( call_params[dst] = call_params.pop(src) try: - logger.debug( - f"_call_with_pagination_if_applicable call_params keys: {list(call_params.keys())}" - ) + logger.debug(f"_call_with_pagination_if_applicable call_params keys: {list(call_params.keys())}") logger.debug(f"op: {operation_name}") response = method(**call_params) except TypeError as e: @@ -523,12 +498,8 @@ def _call_with_pagination_if_applicable( @mcp.tool(description="Invoke an OCI Python SDK API via client and operation name.") def invoke_oci_api( - client_fqn: Annotated[ - str, "Fully-qualified client class, e.g. 'oci.core.ComputeClient'" - ], - operation: Annotated[ - str, "Client method/operation name, e.g. 'list_instances' or 'get_instance'" - ], + client_fqn: Annotated[str, "Fully-qualified client class, e.g. 'oci.core.ComputeClient'"], + operation: Annotated[str, "Client method/operation name, e.g. 'list_instances' or 'get_instance'"], params: Annotated[ Dict[str, Any], "Keyword arguments for the operation (JSON object). Use snake_case keys as in SDK.", @@ -543,15 +514,11 @@ def invoke_oci_api( try: client = _import_client(client_fqn) if not hasattr(client, operation): - raise AttributeError( - f"Operation '{operation}' not found on client '{client_fqn}'" - ) + raise AttributeError(f"Operation '{operation}' not found on client '{client_fqn}'") method = getattr(client, operation) if not callable(method): - raise AttributeError( - f"Attribute '{operation}' on client '{client_fqn}' is not callable" - ) + raise AttributeError(f"Attribute '{operation}' on client '{client_fqn}' is not callable") params = params or {} # pre-normalize parameter key to the SDK-expected kw for create_/update_ ops, @@ -564,9 +531,7 @@ def invoke_oci_api( if src in normalized_params and dst not in normalized_params: normalized_params[dst] = normalized_params.pop(src) - coerced_params = _coerce_params_to_oci_models( - client_fqn, operation, normalized_params - ) + coerced_params = _coerce_params_to_oci_models(client_fqn, operation, normalized_params) # final kwarg aliasing at the top-level prior to invocation to ensure correct SDK kw final_params = dict(coerced_params) @@ -574,9 +539,7 @@ def invoke_oci_api( logger.debug(f"invoke_oci_api final_params keys: {list(final_params.keys())}") logger.debug(f"op: {operation}") try: - data, opc_request_id = _call_with_pagination_if_applicable( - method, final_params, operation - ) + data, opc_request_id = _call_with_pagination_if_applicable(method, final_params, operation) except TypeError as e: msg = str(e) if "unexpected keyword argument" in msg and ( @@ -589,9 +552,7 @@ def invoke_oci_api( if src in final_params and dst not in final_params: alt_params = dict(final_params) alt_params[dst] = alt_params.pop(src) - data, opc_request_id = _call_with_pagination_if_applicable( - method, alt_params, operation - ) + data, opc_request_id = _call_with_pagination_if_applicable(method, alt_params, operation) else: raise else: @@ -621,9 +582,7 @@ def invoke_oci_api( @mcp.tool(description="List public callable operations for a given OCI client class.") def list_client_operations( - client_fqn: Annotated[ - str, "Fully-qualified client class, e.g. 'oci.core.ComputeClient'" - ], + client_fqn: Annotated[str, "Fully-qualified client class, e.g. 'oci.core.ComputeClient'"], ) -> dict: try: module_name, class_name = client_fqn.rsplit(".", 1) diff --git a/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_bootstrap_and_introspection.py b/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_bootstrap_and_introspection.py index 586b5659..296527b7 100644 --- a/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_bootstrap_and_introspection.py +++ b/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_bootstrap_and_introspection.py @@ -79,9 +79,7 @@ def test_falls_back_to_api_key_signer_when_no_token(self, monkeypatch): lambda **kwargs: dict(cfg), ) # no token file - monkeypatch.setattr( - "oracle.oci_cloud_mcp_server.server.os.path.exists", lambda p: False - ) + monkeypatch.setattr("oracle.oci_cloud_mcp_server.server.os.path.exists", lambda p: False) # key loader ok monkeypatch.setattr( "oracle.oci_cloud_mcp_server.server.oci.signer.load_private_key_from_file", @@ -102,9 +100,7 @@ def __init__( self.fingerprint = fingerprint self.private_key_file_location = private_key_file_location - monkeypatch.setattr( - "oracle.oci_cloud_mcp_server.server.oci.signer.Signer", FakeSigner - ) + monkeypatch.setattr("oracle.oci_cloud_mcp_server.server.oci.signer.Signer", FakeSigner) config, signer = _get_config_and_signer() assert isinstance(signer, FakeSigner) @@ -173,13 +169,9 @@ def fake_run(self, *args, **kwargs): import runpy import sys as _sys - monkeypatch.delitem( - _sys.modules, "oracle.oci_cloud_mcp_server.server", raising=False - ) + monkeypatch.delitem(_sys.modules, "oracle.oci_cloud_mcp_server.server", raising=False) - runpy.run_module( - "oracle.oci_cloud_mcp_server.server", run_name="__main__", alter_sys=True - ) + runpy.run_module("oracle.oci_cloud_mcp_server.server", run_name="__main__", alter_sys=True) assert called["kwargs"] == { "transport": "http", "host": "127.0.0.1", @@ -194,9 +186,7 @@ def test_client_fqn_without_dot_raises(self): def test_attribute_not_a_class_raises(self, monkeypatch): fake_mod = SimpleNamespace(NotClass=42) - monkeypatch.setattr( - "oracle.oci_cloud_mcp_server.server.import_module", lambda name: fake_mod - ) + monkeypatch.setattr("oracle.oci_cloud_mcp_server.server.import_module", lambda name: fake_mod) with pytest.raises(ValueError): _import_client("x.y.NotClass") @@ -273,9 +263,7 @@ def test_token_signer_build_failure_falls_back_to_api_key(self, monkeypatch): "oracle.oci_cloud_mcp_server.server.oci.signer.load_private_key_from_file", lambda p: "PK", ) - monkeypatch.setattr( - "builtins.open", lambda *a, **k: StringIO("token"), raising=False - ) + monkeypatch.setattr("builtins.open", lambda *a, **k: StringIO("token"), raising=False) # SecurityTokenSigner init fails to trigger warning path, then API key used class BoomSTS: @@ -322,9 +310,7 @@ def test_api_key_signer_raises_is_propagated(self, monkeypatch): lambda **kwargs: dict(cfg), ) # token file not present - monkeypatch.setattr( - "oracle.oci_cloud_mcp_server.server.os.path.exists", lambda p: False - ) + monkeypatch.setattr("oracle.oci_cloud_mcp_server.server.os.path.exists", lambda p: False) # key loader ok monkeypatch.setattr( "oracle.oci_cloud_mcp_server.server.oci.signer.load_private_key_from_file", @@ -335,8 +321,6 @@ def test_api_key_signer_raises_is_propagated(self, monkeypatch): def boom_signer(**kwargs): # noqa: ARG001 raise Exception("signer ctor boom") - monkeypatch.setattr( - "oracle.oci_cloud_mcp_server.server.oci.signer.Signer", boom_signer - ) + monkeypatch.setattr("oracle.oci_cloud_mcp_server.server.oci.signer.Signer", boom_signer) with pytest.raises(Exception): _get_config_and_signer() diff --git a/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_edge_cases.py b/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_edge_cases.py index 1b2a45cf..dcf63231 100644 --- a/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_edge_cases.py +++ b/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_edge_cases.py @@ -34,9 +34,7 @@ def raising_from_dict(cls, data): # noqa: ARG001 monkeypatch.setattr(_oci.util, "from_dict", raising_from_dict, raising=False) - inst = _construct_model_from_mapping( - {"a": 1, "b": 2}, fake_models, ["MyDetails"] - ) + inst = _construct_model_from_mapping({"a": 1, "b": 2}, fake_models, ["MyDetails"]) assert isinstance(inst, MyDetails) assert inst._data == {"a": 1} @@ -64,9 +62,7 @@ def __init__(self, **kwargs): class TestInvokePreNormalize: @pytest.mark.asyncio - async def test_invoke_pre_normalizes_create_details_before_coerce( - self, monkeypatch - ): + async def test_invoke_pre_normalizes_create_details_before_coerce(self, monkeypatch): class FakeResponse: def __init__(self, data): self.data = data @@ -125,9 +121,7 @@ def boom(name): async with Client(mcp) as client: with pytest.raises(ToolError): - await client.call_tool( - "list_client_operations", {"client_fqn": "x.y.Klass"} - ) + await client.call_tool("list_client_operations", {"client_fqn": "x.y.Klass"}) class TestInvokePlainListReturn: @@ -180,13 +174,9 @@ def __init__(self, **kwargs): from oracle.oci_cloud_mcp_server.server import oci as _oci # make from_dict call the constructor successfully - monkeypatch.setattr( - _oci.util, "from_dict", lambda cls, data: cls(**data), raising=False - ) + monkeypatch.setattr(_oci.util, "from_dict", lambda cls, data: cls(**data), raising=False) - inst = _construct_model_from_mapping( - {"__class_fqn": "mymod9.Kiwi", "a": 7}, None, [] - ) + inst = _construct_model_from_mapping({"__class_fqn": "mymod9.Kiwi", "a": 7}, None, []) assert isinstance(inst, Kiwi) assert inst.kw == {"a": 7} @@ -225,9 +215,7 @@ def foo(self, a): # noqa: ARG002 return 1 fake_module = SimpleNamespace(Klass=Klass) - monkeypatch.setattr( - "oracle.oci_cloud_mcp_server.server.import_module", lambda name: fake_module - ) + monkeypatch.setattr("oracle.oci_cloud_mcp_server.server.import_module", lambda name: fake_module) # force inspect.getdoc to raise to hit exception path for summary extraction import inspect as _inspect @@ -239,16 +227,10 @@ def boom_getdoc(obj): raise Exception("doc boom") return orig_getdoc(obj) - monkeypatch.setattr( - "oracle.oci_cloud_mcp_server.server.inspect.getdoc", boom_getdoc - ) + monkeypatch.setattr("oracle.oci_cloud_mcp_server.server.inspect.getdoc", boom_getdoc) async with Client(mcp) as client: - res = ( - await client.call_tool( - "list_client_operations", {"client_fqn": "x.y.Klass"} - ) - ).data + res = (await client.call_tool("list_client_operations", {"client_fqn": "x.y.Klass"})).data ops = res["operations"] entry = next(o for o in ops if o["name"] == "foo") @@ -256,9 +238,7 @@ def boom_getdoc(obj): class TestConstructExplicitClassKey: - def test_construct_model_with___class_key_uses_from_dict_then_ctor( - self, monkeypatch - ): + def test_construct_model_with___class_key_uses_from_dict_then_ctor(self, monkeypatch): class Foo: def __init__(self, **kwargs): self._data = dict(kwargs) @@ -268,13 +248,9 @@ def __init__(self, **kwargs): from oracle.oci_cloud_mcp_server.server import oci as _oci # first let from_dict succeed - monkeypatch.setattr( - _oci.util, "from_dict", lambda cls, data: cls(**data), raising=False - ) + monkeypatch.setattr(_oci.util, "from_dict", lambda cls, data: cls(**data), raising=False) - inst1 = _construct_model_from_mapping( - {"__class": "Foo", "a": 1}, fake_models, [] - ) + inst1 = _construct_model_from_mapping({"__class": "Foo", "a": 1}, fake_models, []) assert isinstance(inst1, Foo) assert inst1._data == {"a": 1} @@ -283,9 +259,7 @@ def raising_from_dict(cls, data): # noqa: ARG001 raise Exception("nope") monkeypatch.setattr(_oci.util, "from_dict", raising_from_dict, raising=False) - inst2 = _construct_model_from_mapping( - {"__class": "Foo", "a": 2}, fake_models, [] - ) + inst2 = _construct_model_from_mapping({"__class": "Foo", "a": 2}, fake_models, []) assert isinstance(inst2, Foo) assert inst2._data == {"a": 2} @@ -389,9 +363,7 @@ def raising_from_dict(cls, data): # noqa: ARG001 monkeypatch.setattr(_oci.util, "from_dict", raising_from_dict, raising=False) - inst = _construct_model_from_mapping( - {"__model_fqn": "mymod5.Apple", "a": 10}, None, [] - ) + inst = _construct_model_from_mapping({"__model_fqn": "mymod5.Apple", "a": 10}, None, []) assert isinstance(inst, Apple) assert inst.kw == {"a": 10} @@ -425,15 +397,9 @@ def _private(self): return 1 fake_module = SimpleNamespace(Klass=Klass) - monkeypatch.setattr( - "oracle.oci_cloud_mcp_server.server.import_module", lambda name: fake_module - ) + monkeypatch.setattr("oracle.oci_cloud_mcp_server.server.import_module", lambda name: fake_module) async with Client(mcp) as client: - res = ( - await client.call_tool( - "list_client_operations", {"client_fqn": "x.y.Klass"} - ) - ).data + res = (await client.call_tool("list_client_operations", {"client_fqn": "x.y.Klass"})).data assert res == {"operations": []} @@ -448,9 +414,7 @@ class MM: class TestParentPrefixAlt: - def test_parent_prefix_prefers_non_details_variant_when_available( - self, monkeypatch - ): + def test_parent_prefix_prefers_non_details_variant_when_available(self, monkeypatch): # ensure when only 'InstanceShapeConfig' exists (no '...Details'), # nested coercion picks it using the parent prefix hint path. class InstanceDetails: @@ -500,13 +464,9 @@ def fake_run(self, *args, **kwargs): monkeypatch.delenv("ORACLE_MCP_PORT", raising=False) # remove pre-imported module to avoid runpy RuntimeWarning - monkeypatch.delitem( - sys.modules, "oracle.oci_cloud_mcp_server.server", raising=False - ) + monkeypatch.delitem(sys.modules, "oracle.oci_cloud_mcp_server.server", raising=False) - runpy.run_module( - "oracle.oci_cloud_mcp_server.server", run_name="__main__", alter_sys=True - ) + runpy.run_module("oracle.oci_cloud_mcp_server.server", run_name="__main__", alter_sys=True) assert called["args"] == () assert called["kwargs"] == {} @@ -537,9 +497,7 @@ def raising_from_dict(cls, data): # noqa: ARG001 class TestCandidateCtorRaisesContinueAndFallback: - def test_candidate_ctor_and_from_dict_fail_continue_and_return_mapping( - self, monkeypatch - ): + def test_candidate_ctor_and_from_dict_fail_continue_and_return_mapping(self, monkeypatch): class Candidate: def __init__(self, **kwargs): raise Exception("ctor fail") @@ -569,9 +527,7 @@ def __init__(self, **kwargs): "oracle.oci_cloud_mcp_server.server._import_models_module_from_client_fqn", lambda fqn: fake_models, ) - out = _coerce_params_to_oci_models( - "x.y.Client", "op", {"source_details": {"a": 1}} - ) + out = _coerce_params_to_oci_models("x.y.Client", "op", {"source_details": {"a": 1}}) assert isinstance(out["source_details"], SourceDetails) assert out["source_details"].kw == {"a": 1} @@ -607,9 +563,7 @@ def test_final_alias_block_renames_non_dict_value(self, monkeypatch): "oracle.oci_cloud_mcp_server.server._import_models_module_from_client_fqn", lambda fqn: None, ) - out = _coerce_params_to_oci_models( - "x.y.Client", "create_vcn", {"vcn_details": [1, 2]} - ) + out = _coerce_params_to_oci_models("x.y.Client", "create_vcn", {"vcn_details": [1, 2]}) assert "create_vcn_details" in out and "vcn_details" not in out assert out["create_vcn_details"] == [1, 2] @@ -796,13 +750,9 @@ def __init__(self, **kwargs): from oracle.oci_cloud_mcp_server.server import oci as _oci # from_dict should receive filtered data (without 'b') - monkeypatch.setattr( - _oci.util, "from_dict", lambda cls, data: cls(**data), raising=False - ) + monkeypatch.setattr(_oci.util, "from_dict", lambda cls, data: cls(**data), raising=False) - inst = _construct_model_from_mapping( - {"__model": "MyModel2", "a": 1, "b": 2}, fake_models, [] - ) + inst = _construct_model_from_mapping({"__model": "MyModel2", "a": 1, "b": 2}, fake_models, []) assert isinstance(inst, MyModel2) assert inst.kw == {"a": 1} @@ -879,22 +829,16 @@ def foo(self): return 1 fake_module = SimpleNamespace(Klass=Klass) - monkeypatch.setattr( - "oracle.oci_cloud_mcp_server.server.import_module", lambda name: fake_module - ) + monkeypatch.setattr("oracle.oci_cloud_mcp_server.server.import_module", lambda name: fake_module) def boom(*args, **kwargs): raise Exception("scan boom") - monkeypatch.setattr( - "oracle.oci_cloud_mcp_server.server.inspect.getmembers", boom - ) + monkeypatch.setattr("oracle.oci_cloud_mcp_server.server.inspect.getmembers", boom) async with Client(mcp) as client: with pytest.raises(ToolError): - await client.call_tool( - "list_client_operations", {"client_fqn": "x.y.Klass"} - ) + await client.call_tool("list_client_operations", {"client_fqn": "x.y.Klass"}) class TestSupportsPaginationHeuristics: diff --git a/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_model_coercion.py b/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_model_coercion.py index cb427f4f..19084015 100644 --- a/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_model_coercion.py +++ b/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_model_coercion.py @@ -50,12 +50,13 @@ def import_side_effect(name): return fake_models_module raise ImportError(name) - with patch( - "oracle.oci_cloud_mcp_server.server.import_module", - side_effect=import_side_effect, - ), patch( - "oracle.oci_cloud_mcp_server.server._get_config_and_signer" - ) as mock_cfg: + with ( + patch( + "oracle.oci_cloud_mcp_server.server.import_module", + side_effect=import_side_effect, + ), + patch("oracle.oci_cloud_mcp_server.server._get_config_and_signer") as mock_cfg, + ): mock_cfg.return_value = ({}, object()) # note: We intentionally pass "vcn_details" (missing the "create_") diff --git a/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_models_and_serialization.py b/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_models_and_serialization.py index f87f297e..58ebc62c 100644 --- a/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_models_and_serialization.py +++ b/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_models_and_serialization.py @@ -11,10 +11,7 @@ class TestSnakeToCamel: def test_basic_conversion(self): assert _snake_to_camel("create_vcn_details") == "CreateVcnDetails" - assert ( - _snake_to_camel("update_instance_configuration") - == "UpdateInstanceConfiguration" - ) + assert _snake_to_camel("update_instance_configuration") == "UpdateInstanceConfiguration" def test_ignores_empty_segments(self): # leading/trailing/multiple underscores produce empty parts which should be skipped @@ -30,9 +27,7 @@ def fake_import(name): return SimpleNamespace() raise ImportError("nope") - monkeypatch.setattr( - "oracle.oci_cloud_mcp_server.server.import_module", fake_import - ) + monkeypatch.setattr("oracle.oci_cloud_mcp_server.server.import_module", fake_import) mod = _import_models_module_from_client_fqn("x.y.ClientClass") assert mod is not None @@ -60,9 +55,7 @@ def test_handles_nested_primitives_and_tuples(self): assert out["c"] == ["x", "y"] assert out["a"][1] == {"b": 2} - def test_when_to_dict_raises_uses_fallback_and_str_for_non_jsonable( - self, monkeypatch - ): + def test_when_to_dict_raises_uses_fallback_and_str_for_non_jsonable(self, monkeypatch): # force oci.util.to_dict to raise to hit exception path from oracle.oci_cloud_mcp_server.server import oci as _oci diff --git a/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_pagination_and_invocation.py b/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_pagination_and_invocation.py index 95376dcc..f83eafdf 100644 --- a/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_pagination_and_invocation.py +++ b/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_pagination_and_invocation.py @@ -17,9 +17,7 @@ def test_allowlist_true_when_signature_introspection_fails(self, monkeypatch): def boom_sig(obj): raise Exception("no sig") - monkeypatch.setattr( - "oracle.oci_cloud_mcp_server.server.inspect.signature", boom_sig - ) + monkeypatch.setattr("oracle.oci_cloud_mcp_server.server.inspect.signature", boom_sig) def fn(**kwargs): # noqa: ARG001 """ @@ -109,9 +107,7 @@ def __init__(self, config, signer=None, **kwargs): # module with GoodClient fake_mod = SimpleNamespace(GoodClient=GoodClient) - monkeypatch.setattr( - "oracle.oci_cloud_mcp_server.server.import_module", lambda name: fake_mod - ) + monkeypatch.setattr("oracle.oci_cloud_mcp_server.server.import_module", lambda name: fake_mod) # basic config/signer monkeypatch.setattr( "oracle.oci_cloud_mcp_server.server._get_config_and_signer", @@ -178,9 +174,7 @@ def create_vcn(create_vcn_details): # noqa: ARG001 class TestSupportsPaginationAdditional: def test_known_allowlist_variant_get_rr_set_true(self): - def fn( - zone_name_or_id, domain, rtype, page=None, limit=None, **kwargs - ): # noqa: ARG001 + def fn(zone_name_or_id, domain, rtype, page=None, limit=None, **kwargs): # noqa: ARG001 return None assert _supports_pagination(fn, "get_rr_set") is True 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 11f42d73..27cc006b 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 @@ -34,20 +34,14 @@ def test_uses_security_token_signer_when_token_exists(self): "security_token_file": "/path/to/token", } - with patch( - "oracle.oci_cloud_mcp_server.server.oci.config.from_file" - ) as m_from, patch( - "oracle.oci_cloud_mcp_server.server.oci.signer.load_private_key_from_file" - ) as m_loadkey, patch( - "oracle.oci_cloud_mcp_server.server.oci.auth.signers.SecurityTokenSigner" - ) as m_sts, patch( - "oracle.oci_cloud_mcp_server.server.oci.signer.Signer" - ) as m_signer, patch( - "oracle.oci_cloud_mcp_server.server.os.path.exists", return_value=True - ), patch( - "builtins.open", mock_open(read_data="TOKEN123") + with ( + patch("oracle.oci_cloud_mcp_server.server.oci.config.from_file") as m_from, + patch("oracle.oci_cloud_mcp_server.server.oci.signer.load_private_key_from_file") as m_loadkey, + patch("oracle.oci_cloud_mcp_server.server.oci.auth.signers.SecurityTokenSigner") as m_sts, + patch("oracle.oci_cloud_mcp_server.server.oci.signer.Signer") as m_signer, + patch("oracle.oci_cloud_mcp_server.server.os.path.exists", return_value=True), + patch("builtins.open", mock_open(read_data="TOKEN123")), ): - m_from.return_value = dict(cfg) # function mutates to add UA m_loadkey.return_value = object() sentinel_signer = object() @@ -69,18 +63,13 @@ def test_falls_back_to_api_key_signer_when_no_token(self): "security_token_file": "/path/to/token", } - with patch( - "oracle.oci_cloud_mcp_server.server.oci.config.from_file" - ) as m_from, patch( - "oracle.oci_cloud_mcp_server.server.oci.signer.load_private_key_from_file" - ) as m_loadkey, patch( - "oracle.oci_cloud_mcp_server.server.oci.auth.signers.SecurityTokenSigner" - ) as m_sts, patch( - "oracle.oci_cloud_mcp_server.server.oci.signer.Signer" - ) as m_signer, patch( - "oracle.oci_cloud_mcp_server.server.os.path.exists", return_value=False + with ( + patch("oracle.oci_cloud_mcp_server.server.oci.config.from_file") as m_from, + patch("oracle.oci_cloud_mcp_server.server.oci.signer.load_private_key_from_file") as m_loadkey, + patch("oracle.oci_cloud_mcp_server.server.oci.auth.signers.SecurityTokenSigner") as m_sts, + patch("oracle.oci_cloud_mcp_server.server.oci.signer.Signer") as m_signer, + patch("oracle.oci_cloud_mcp_server.server.os.path.exists", return_value=False), ): - m_from.return_value = dict(cfg) m_loadkey.return_value = object() sentinel_signer = object() @@ -124,9 +113,7 @@ def test_align_params_to_signature_remaps_create_details(self): def create_vcn(create_vcn_details): # noqa: ARG001 return None - aligned = _align_params_to_signature( - create_vcn, "create_vcn", {"vcn_details": {"x": 1}} - ) + aligned = _align_params_to_signature(create_vcn, "create_vcn", {"vcn_details": {"x": 1}}) assert "create_vcn_details" in aligned assert "vcn_details" not in aligned @@ -151,11 +138,10 @@ def get_weird(self, id): # noqa: ARG002 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: + 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, + ): m_import.return_value = fake_module m_cfg.return_value = ({}, object()) @@ -190,9 +176,7 @@ async def test_list_client_operations_raises_on_not_class(self): m_import.return_value = SimpleNamespace(NotAClass=42) async with Client(mcp) as client: with pytest.raises(ToolError): - await client.call_tool( - "list_client_operations", {"client_fqn": "x.y.NotAClass"} - ) + await client.call_tool("list_client_operations", {"client_fqn": "x.y.NotAClass"}) class TestModelCoercionAdvanced: @@ -211,9 +195,7 @@ def __init__(self, **kwargs): InstanceShapeConfigDetails=InstanceShapeConfigDetails, ) - with patch( - "oracle.oci_cloud_mcp_server.server._import_models_module_from_client_fqn" - ) as m_models: + with patch("oracle.oci_cloud_mcp_server.server._import_models_module_from_client_fqn") as m_models: m_models.return_value = fake_models coerced = _coerce_params_to_oci_models( @@ -303,11 +285,10 @@ def __init__(self, config, signer): 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: + 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, + ): m_import.return_value = fake_module m_cfg.return_value = ({"k": "v"}, object()) inst = _import_client("x.y.FakeClient") @@ -323,11 +304,10 @@ def __init__(self, config, signer): 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: + 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, + ): m_import.return_value = fake_module m_cfg.return_value = ({}, object()) @@ -358,11 +338,10 @@ def __init__(self, config, signer): 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: + 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, + ): m_import.return_value = fake_module m_cfg.return_value = ({}, object()) @@ -394,11 +373,12 @@ def test_private_key_failure_raises(self): "security_token_file": "/path/to/token", } - with patch( - "oracle.oci_cloud_mcp_server.server.oci.config.from_file" - ) as m_from, patch( - "oracle.oci_cloud_mcp_server.server.oci.signer.load_private_key_from_file", - side_effect=Exception("bad key"), + with ( + patch("oracle.oci_cloud_mcp_server.server.oci.config.from_file") as m_from, + patch( + "oracle.oci_cloud_mcp_server.server.oci.signer.load_private_key_from_file", + side_effect=Exception("bad key"), + ), ): m_from.return_value = dict(cfg) with pytest.raises(Exception): @@ -414,19 +394,16 @@ def test_token_present_but_sts_raises_falls_back_to_api_key(self): "key_file": "/path/to/key.pem", "security_token_file": "/path/to/token", } - with patch( - "oracle.oci_cloud_mcp_server.server.oci.config.from_file" - ) as m_from, patch( - "oracle.oci_cloud_mcp_server.server.oci.signer.load_private_key_from_file" - ) as m_loadkey, patch( - "oracle.oci_cloud_mcp_server.server.oci.auth.signers.SecurityTokenSigner", - side_effect=Exception("boom"), - ), patch( - "oracle.oci_cloud_mcp_server.server.oci.signer.Signer" - ) as m_signer, patch( - "oracle.oci_cloud_mcp_server.server.os.path.exists", return_value=True - ), patch( - "builtins.open", mock_open(read_data="TOKEN123") + with ( + patch("oracle.oci_cloud_mcp_server.server.oci.config.from_file") as m_from, + patch("oracle.oci_cloud_mcp_server.server.oci.signer.load_private_key_from_file") as m_loadkey, + patch( + "oracle.oci_cloud_mcp_server.server.oci.auth.signers.SecurityTokenSigner", + side_effect=Exception("boom"), + ), + patch("oracle.oci_cloud_mcp_server.server.oci.signer.Signer") as m_signer, + patch("oracle.oci_cloud_mcp_server.server.os.path.exists", return_value=True), + patch("builtins.open", mock_open(read_data="TOKEN123")), ): m_from.return_value = dict(cfg) m_loadkey.return_value = object() @@ -445,9 +422,7 @@ def test_create_alias_rename_in_coerce_params(self): "oracle.oci_cloud_mcp_server.server._import_models_module_from_client_fqn", return_value=None, ): - out = _coerce_params_to_oci_models( - "x.y.Fake", "create_vcn", {"vcn_details": {"x": 1}} - ) + out = _coerce_params_to_oci_models("x.y.Fake", "create_vcn", {"vcn_details": {"x": 1}}) assert "create_vcn_details" in out assert "vcn_details" not in out @@ -455,9 +430,7 @@ def test_align_params_no_dst_in_signature_no_change(self): def create_vcn(vcn_details): # noqa: ARG001 return None - aligned = _align_params_to_signature( - create_vcn, "create_vcn", {"vcn_details": 1} - ) + aligned = _align_params_to_signature(create_vcn, "create_vcn", {"vcn_details": 1}) assert "vcn_details" in aligned assert "create_vcn_details" not in aligned @@ -475,9 +448,7 @@ def create_vcn(create_vcn_details): # noqa: ARG001 data, opc = __import__( "oracle.oci_cloud_mcp_server.server", fromlist=["_call_with_pagination_if_applicable"], - )._call_with_pagination_if_applicable( - create_vcn, {"vcn_details": {}}, "create_vcn" - ) + )._call_with_pagination_if_applicable(create_vcn, {"vcn_details": {}}, "create_vcn") assert data == {"ok": True} assert opc is None @@ -498,9 +469,7 @@ def __init__(self, **kwargs): from oracle.oci_cloud_mcp_server.server import _construct_model_from_mapping - inst = _construct_model_from_mapping( - {"__model_fqn": "mymod.Banana", "a": 1}, None, [] - ) + inst = _construct_model_from_mapping({"__model_fqn": "mymod.Banana", "a": 1}, None, []) assert isinstance(inst, Banana) assert inst.kwargs["a"] == 1 @@ -512,9 +481,7 @@ def __init__(self, **kwargs): self.kw = dict(kwargs) fake_models = SimpleNamespace(MyModel=MyModel) - with patch( - "oracle.oci_cloud_mcp_server.server._import_models_module_from_client_fqn" - ) as m_models: + with patch("oracle.oci_cloud_mcp_server.server._import_models_module_from_client_fqn") as m_models: m_models.return_value = fake_models out = _coerce_params_to_oci_models( "x.y.Fake", @@ -531,13 +498,9 @@ def __init__(self, **kwargs): self.kw = dict(kwargs) fake_models = SimpleNamespace(SourceDetails=SourceDetails) - with patch( - "oracle.oci_cloud_mcp_server.server._import_models_module_from_client_fqn" - ) as m_models: + with patch("oracle.oci_cloud_mcp_server.server._import_models_module_from_client_fqn") as m_models: m_models.return_value = fake_models - out = _coerce_params_to_oci_models( - "x.y.Fake", "op", {"source_details": {"foo": "bar"}} - ) + out = _coerce_params_to_oci_models("x.y.Fake", "op", {"source_details": {"foo": "bar"}}) assert isinstance(out["source_details"], SourceDetails) assert out["source_details"].kw["foo"] == "bar" @@ -577,9 +540,7 @@ def fake_import(name): assert name == "x.y.models" return fake_models - monkeypatch.setattr( - "oracle.oci_cloud_mcp_server.server.import_module", fake_import - ) + monkeypatch.setattr("oracle.oci_cloud_mcp_server.server.import_module", fake_import) from oracle.oci_cloud_mcp_server.server import ( _import_models_module_from_client_fqn, ) @@ -605,11 +566,10 @@ def get_plain(self, id): # noqa: ARG002 return {"ok": True} 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: + 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, + ): m_import.return_value = fake_module m_cfg.return_value = ({}, object()) async with Client(mcp) as client: @@ -630,10 +590,13 @@ def get_plain(self, id): # noqa: ARG002 class TestInvokeImportFailure: @pytest.mark.asyncio async def test_invoke_oci_api_import_error_surfaces_as_error_payload(self): - with patch( - "oracle.oci_cloud_mcp_server.server.import_module", - side_effect=ImportError("boom"), - ), patch("oracle.oci_cloud_mcp_server.server._get_config_and_signer") as m_cfg: + with ( + patch( + "oracle.oci_cloud_mcp_server.server.import_module", + side_effect=ImportError("boom"), + ), + patch("oracle.oci_cloud_mcp_server.server._get_config_and_signer") as m_cfg, + ): m_cfg.return_value = ({}, object()) async with Client(mcp) as client: res = ( @@ -659,18 +622,13 @@ def test_token_file_exists_but_open_fails_falls_back(self): "key_file": "/path/to/key.pem", "security_token_file": "/path/to/token", } - with patch( - "oracle.oci_cloud_mcp_server.server.oci.config.from_file" - ) as m_from, patch( - "oracle.oci_cloud_mcp_server.server.oci.signer.load_private_key_from_file" - ) as m_loadkey, patch( - "oracle.oci_cloud_mcp_server.server.os.path.exists", return_value=True - ), patch( - "oracle.oci_cloud_mcp_server.server.oci.auth.signers.SecurityTokenSigner" - ) as m_sts, patch( - "oracle.oci_cloud_mcp_server.server.oci.signer.Signer" - ) as m_signer, patch( - "builtins.open", side_effect=Exception("io") + with ( + patch("oracle.oci_cloud_mcp_server.server.oci.config.from_file") as m_from, + patch("oracle.oci_cloud_mcp_server.server.oci.signer.load_private_key_from_file") as m_loadkey, + patch("oracle.oci_cloud_mcp_server.server.os.path.exists", return_value=True), + patch("oracle.oci_cloud_mcp_server.server.oci.auth.signers.SecurityTokenSigner") as m_sts, + patch("oracle.oci_cloud_mcp_server.server.oci.signer.Signer") as m_signer, + patch("builtins.open", side_effect=Exception("io")), ): m_from.return_value = dict(cfg) m_loadkey.return_value = object() @@ -691,12 +649,8 @@ def fn(x): # noqa: ARG001 def _raise_sig(*args, **kwargs): raise Exception("boom") - monkeypatch.setattr( - "oracle.oci_cloud_mcp_server.server.inspect.signature", _raise_sig - ) - aligned = _align_params_to_signature( - fn, "create_something", {"something_details": 1} - ) + monkeypatch.setattr("oracle.oci_cloud_mcp_server.server.inspect.signature", _raise_sig) + aligned = _align_params_to_signature(fn, "create_something", {"something_details": 1}) # should return params unchanged when inspect.signature fails assert aligned == {"something_details": 1} @@ -729,15 +683,9 @@ def _hidden(self): fake_module = SimpleNamespace(Klass=Klass) # first run with normal behavior - monkeypatch.setattr( - "oracle.oci_cloud_mcp_server.server.import_module", lambda name: fake_module - ) + monkeypatch.setattr("oracle.oci_cloud_mcp_server.server.import_module", lambda name: fake_module) async with Client(mcp) as client: - res = ( - await client.call_tool( - "list_client_operations", {"client_fqn": "x.y.Klass"} - ) - ).data + res = (await client.call_tool("list_client_operations", {"client_fqn": "x.y.Klass"})).data assert isinstance(res, dict) assert "operations" in res names = [op["name"] for op in res["operations"]] @@ -748,15 +696,9 @@ def _hidden(self): def sig_raises(_): raise Exception("sig boom") - monkeypatch.setattr( - "oracle.oci_cloud_mcp_server.server.inspect.signature", sig_raises - ) + monkeypatch.setattr("oracle.oci_cloud_mcp_server.server.inspect.signature", sig_raises) async with Client(mcp) as client: - res2 = ( - await client.call_tool( - "list_client_operations", {"client_fqn": "x.y.Klass"} - ) - ).data + res2 = (await client.call_tool("list_client_operations", {"client_fqn": "x.y.Klass"})).data # should still succeed with empty/summary fallback assert isinstance(res2, dict) assert "operations" in res2 @@ -790,9 +732,7 @@ def get(self, *args, **kwargs): def fn_ok(): return Resp() - data, opc = _call_with_pagination_if_applicable( - lambda: fn_ok(), {}, "get_thing" - ) + data, opc = _call_with_pagination_if_applicable(lambda: fn_ok(), {}, "get_thing") assert data == {"val": 1} assert opc is None @@ -808,11 +748,10 @@ def get_thing(self, id): # noqa: ARG002 raise TypeError("some other error") 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: + 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, + ): m_import.return_value = fake_module m_cfg.return_value = ({}, object()) async with Client(mcp) as client: @@ -837,9 +776,7 @@ def test_update_alias_rename_in_coerce_params(self): "oracle.oci_cloud_mcp_server.server._import_models_module_from_client_fqn", return_value=None, ): - out = _coerce_params_to_oci_models( - "x.y.Fake", "update_vcn", {"vcn_details": {"x": 1}} - ) + out = _coerce_params_to_oci_models("x.y.Fake", "update_vcn", {"vcn_details": {"x": 1}}) assert "update_vcn_details" in out assert "vcn_details" not in out @@ -852,9 +789,7 @@ def test_invalid_fqn_raises(self): def test_not_class_raises_direct(self, monkeypatch): fake_module = SimpleNamespace(NotAClass=42) - monkeypatch.setattr( - "oracle.oci_cloud_mcp_server.server.import_module", lambda name: fake_module - ) + monkeypatch.setattr("oracle.oci_cloud_mcp_server.server.import_module", lambda name: fake_module) with pytest.raises(Exception): list_client_operations("x.y.NotAClass") @@ -868,15 +803,14 @@ def test_api_key_signer_failure_raises(self): "key_file": "/path/to/key.pem", "security_token_file": "/path/to/token", } - with patch( - "oracle.oci_cloud_mcp_server.server.oci.config.from_file" - ) as m_from, patch( - "oracle.oci_cloud_mcp_server.server.oci.signer.load_private_key_from_file" - ) as m_loadkey, patch( - "oracle.oci_cloud_mcp_server.server.os.path.exists", return_value=False - ), patch( - "oracle.oci_cloud_mcp_server.server.oci.signer.Signer", - side_effect=Exception("signer-fail"), + with ( + patch("oracle.oci_cloud_mcp_server.server.oci.config.from_file") as m_from, + patch("oracle.oci_cloud_mcp_server.server.oci.signer.load_private_key_from_file") as m_loadkey, + patch("oracle.oci_cloud_mcp_server.server.os.path.exists", return_value=False), + patch( + "oracle.oci_cloud_mcp_server.server.oci.signer.Signer", + side_effect=Exception("signer-fail"), + ), ): m_from.return_value = dict(cfg) m_loadkey.return_value = object() @@ -893,15 +827,9 @@ def foo(self, a, b=1): # noqa: ARG002 return 1 fake_module = SimpleNamespace(Klass=Klass) - monkeypatch.setattr( - "oracle.oci_cloud_mcp_server.server.import_module", lambda name: fake_module - ) + monkeypatch.setattr("oracle.oci_cloud_mcp_server.server.import_module", lambda name: fake_module) async with Client(mcp) as client: - res = ( - await client.call_tool( - "list_client_operations", {"client_fqn": "x.y.Klass"} - ) - ).data + res = (await client.call_tool("list_client_operations", {"client_fqn": "x.y.Klass"})).data ops = res["operations"] assert isinstance(ops, list) and ops entry = next(o for o in ops if o["name"] == "foo") @@ -921,15 +849,11 @@ def __init__(self, **kwargs): # inject a from_dict into oci.util that calls the constructor from oracle.oci_cloud_mcp_server.server import oci as _oci - monkeypatch.setattr( - _oci.util, "from_dict", lambda cls, data: cls(**data), raising=False - ) + monkeypatch.setattr(_oci.util, "from_dict", lambda cls, data: cls(**data), raising=False) from oracle.oci_cloud_mcp_server.server import _construct_model_from_mapping - inst = _construct_model_from_mapping( - {"__model": "MyModel", "a": 1}, fake_models, [] - ) + inst = _construct_model_from_mapping({"__model": "MyModel", "a": 1}, fake_models, []) assert isinstance(inst, MyModel) assert inst._data == {"a": 1} @@ -943,9 +867,7 @@ def __init__(self, **kwargs): from oracle.oci_cloud_mcp_server.server import oci as _oci - monkeypatch.setattr( - _oci.util, "from_dict", lambda cls, data: cls(**data), raising=False - ) + monkeypatch.setattr(_oci.util, "from_dict", lambda cls, data: cls(**data), raising=False) from oracle.oci_cloud_mcp_server.server import _construct_model_from_mapping @@ -969,15 +891,11 @@ def __init__(self, **kwargs): from oracle.oci_cloud_mcp_server.server import oci as _oci - monkeypatch.setattr( - _oci.util, "from_dict", lambda cls, data: cls(**data), raising=False - ) + monkeypatch.setattr(_oci.util, "from_dict", lambda cls, data: cls(**data), raising=False) from oracle.oci_cloud_mcp_server.server import _construct_model_from_mapping - inst = _construct_model_from_mapping( - {"__model_fqn": "mymod2.Pear", "a": 3}, None, [] - ) + inst = _construct_model_from_mapping({"__model_fqn": "mymod2.Pear", "a": 3}, None, []) assert isinstance(inst, Pear) assert inst.kw == {"a": 3} @@ -1072,9 +990,7 @@ def __init__(self, **kwargs): "oracle.oci_cloud_mcp_server.server._import_models_module_from_client_fqn", lambda fqn: fake_models, ) - out = _coerce_params_to_oci_models( - "x.y.Fake", "op", {"source_configuration": {"a": 1}} - ) + out = _coerce_params_to_oci_models("x.y.Fake", "op", {"source_configuration": {"a": 1}}) assert isinstance(out["source_configuration"], SourceConfiguration) assert out["source_configuration"].kw["a"] == 1 @@ -1095,9 +1011,7 @@ def __init__(self, **kwargs): from oracle.oci_cloud_mcp_server.server import _construct_model_from_mapping - inst = _construct_model_from_mapping( - {"__class_fqn": "mymod3.Grape", "v": 7}, None, [] - ) + inst = _construct_model_from_mapping({"__class_fqn": "mymod3.Grape", "v": 7}, None, []) assert isinstance(inst, Grape) assert inst.kw == {"v": 7} @@ -1110,15 +1024,9 @@ def foo(self): # no docstring return 1 fake_module = SimpleNamespace(Klass=Klass) - monkeypatch.setattr( - "oracle.oci_cloud_mcp_server.server.import_module", lambda name: fake_module - ) + monkeypatch.setattr("oracle.oci_cloud_mcp_server.server.import_module", lambda name: fake_module) async with Client(mcp) as client: - res = ( - await client.call_tool( - "list_client_operations", {"client_fqn": "x.y.Klass"} - ) - ).data + res = (await client.call_tool("list_client_operations", {"client_fqn": "x.y.Klass"})).data ops = res["operations"] entry = next(o for o in ops if o["name"] == "foo") assert entry["summary"] == "" @@ -1129,9 +1037,7 @@ def test_update_remap_when_signature_requires(self): def update_vcn(update_vcn_details): # noqa: ARG001 return None - aligned = _align_params_to_signature( - update_vcn, "update_vcn", {"vcn_details": {"x": 1}} - ) + aligned = _align_params_to_signature(update_vcn, "update_vcn", {"vcn_details": {"x": 1}}) assert "update_vcn_details" in aligned assert "vcn_details" not in aligned @@ -1153,9 +1059,7 @@ class TestListClientOperationsInvalidFqnTool: async def test_invalid_fqn_tool_error(self): async with Client(mcp) as client: with pytest.raises(ToolError): - await client.call_tool( - "list_client_operations", {"client_fqn": "InvalidFqn"} - ) + await client.call_tool("list_client_operations", {"client_fqn": "InvalidFqn"}) class TestSerializeTuple: @@ -1245,9 +1149,7 @@ class Z: class TestInvokeUnexpectedKwOther: @pytest.mark.asyncio - async def test_invoke_oci_api_unexpected_kw_non_matching_raises_error( - self, monkeypatch - ): + async def test_invoke_oci_api_unexpected_kw_non_matching_raises_error(self, monkeypatch): # if TypeError occurs with unexpected kw that does not match expected alias, error should surface class FakeClient: def __init__(self, config, signer): # noqa: ARG002 @@ -1302,9 +1204,7 @@ def raising_from_dict(cls, data): # noqa: ARG001 monkeypatch.setattr(_oci.util, "from_dict", raising_from_dict, raising=False) - inst = _construct_model_from_mapping( - {"__model": "MyModel", "a": 1, "b": 2}, fake_models, [] - ) + inst = _construct_model_from_mapping({"__model": "MyModel", "a": 1, "b": 2}, fake_models, []) assert isinstance(inst, MyModel) # 'b' should be filtered out because it's not in swagger_types assert inst.kw == {"a": 1} diff --git a/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_tools.py b/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_tools.py index 79f0210e..84e1d8c5 100644 --- a/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_tools.py +++ b/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/tests/test_tools.py @@ -45,17 +45,10 @@ def _hidden(self): {"client_fqn": "x.y.FakeClient"}, ) ).data - ops = ( - result.get("operations", result) - if isinstance(result, dict) - else result or [] - ) + ops = result.get("operations", result) if isinstance(result, dict) else result or [] # only public callable functions should be listed - names = [ - op["name"] if isinstance(op, dict) else getattr(op, "name", None) - for op in ops - ] + names = [op["name"] if isinstance(op, dict) else getattr(op, "name", None) for op in ops] names = [n for n in names if n] assert "get_thing" in names assert "_hidden" not in names @@ -78,11 +71,10 @@ def get_thing(self, id): fake_module = SimpleNamespace(FakeClient=FakeClient) - with patch( - "oracle.oci_cloud_mcp_server.server.import_module" - ) as mock_import, patch( - "oracle.oci_cloud_mcp_server.server._get_config_and_signer" - ) as mock_cfg: + with ( + patch("oracle.oci_cloud_mcp_server.server.import_module") as mock_import, + patch("oracle.oci_cloud_mcp_server.server._get_config_and_signer") as mock_cfg, + ): mock_import.return_value = fake_module mock_cfg.return_value = ({}, object()) @@ -122,18 +114,16 @@ def list_things(self, compartment_id): fake_module = SimpleNamespace(FakeClient=FakeClient) - with patch( - "oracle.oci_cloud_mcp_server.server.import_module" - ) as mock_import, patch( - "oracle.oci_cloud_mcp_server.server._get_config_and_signer" - ) as mock_cfg, patch( - "oracle.oci_cloud_mcp_server.server.oci.pagination.list_call_get_all_results" - ) as mock_pager: + with ( + patch("oracle.oci_cloud_mcp_server.server.import_module") as mock_import, + patch("oracle.oci_cloud_mcp_server.server._get_config_and_signer") as mock_cfg, + patch( + "oracle.oci_cloud_mcp_server.server.oci.pagination.list_call_get_all_results" + ) as mock_pager, + ): mock_import.return_value = fake_module mock_cfg.return_value = ({}, object()) - mock_pager.return_value = FakeResponse( - [{"name": "a"}, {"name": "b"}, {"name": "c"}] - ) + mock_pager.return_value = FakeResponse([{"name": "a"}, {"name": "b"}, {"name": "c"}]) async with Client(mcp) as client: result = ( @@ -170,13 +160,13 @@ def get_zone_records(self, zone_name, page=None, limit=None): fake_module = SimpleNamespace(FakeClient=FakeClient) - with patch( - "oracle.oci_cloud_mcp_server.server.import_module" - ) as mock_import, patch( - "oracle.oci_cloud_mcp_server.server._get_config_and_signer" - ) as mock_cfg, patch( - "oracle.oci_cloud_mcp_server.server.oci.pagination.list_call_get_all_results" - ) as mock_pager: + with ( + patch("oracle.oci_cloud_mcp_server.server.import_module") as mock_import, + patch("oracle.oci_cloud_mcp_server.server._get_config_and_signer") as mock_cfg, + patch( + "oracle.oci_cloud_mcp_server.server.oci.pagination.list_call_get_all_results" + ) as mock_pager, + ): mock_import.return_value = fake_module mock_cfg.return_value = ({}, object()) mock_pager.return_value = FakeResponse( @@ -190,9 +180,7 @@ def get_zone_records(self, zone_name, page=None, limit=None): { "client_fqn": "x.y.FakeClient", "operation": "get_zone_records", - "params": { - "zone_name": "do-not-delete-me-testing-zone.example" - }, + "params": {"zone_name": "do-not-delete-me-testing-zone.example"}, }, ) ).data @@ -220,13 +208,13 @@ def summarize_metrics(self, compartment_id): fake_module = SimpleNamespace(FakeClient=FakeClient) - with patch( - "oracle.oci_cloud_mcp_server.server.import_module" - ) as mock_import, patch( - "oracle.oci_cloud_mcp_server.server._get_config_and_signer" - ) as mock_cfg, patch( - "oracle.oci_cloud_mcp_server.server.oci.pagination.list_call_get_all_results" - ) as mock_pager: + with ( + patch("oracle.oci_cloud_mcp_server.server.import_module") as mock_import, + patch("oracle.oci_cloud_mcp_server.server._get_config_and_signer") as mock_cfg, + patch( + "oracle.oci_cloud_mcp_server.server.oci.pagination.list_call_get_all_results" + ) as mock_pager, + ): mock_import.return_value = fake_module mock_cfg.return_value = ({}, object()) mock_pager.return_value = FakeResponse([{"sum": 10}, {"sum": 20}]) @@ -266,13 +254,13 @@ def get_rr_set(self, zone_name_or_id, domain, rtype, page=None, limit=None): fake_module = SimpleNamespace(FakeClient=FakeClient) - with patch( - "oracle.oci_cloud_mcp_server.server.import_module" - ) as mock_import, patch( - "oracle.oci_cloud_mcp_server.server._get_config_and_signer" - ) as mock_cfg, patch( - "oracle.oci_cloud_mcp_server.server.oci.pagination.list_call_get_all_results" - ) as mock_pager: + with ( + patch("oracle.oci_cloud_mcp_server.server.import_module") as mock_import, + patch("oracle.oci_cloud_mcp_server.server._get_config_and_signer") as mock_cfg, + patch( + "oracle.oci_cloud_mcp_server.server.oci.pagination.list_call_get_all_results" + ) as mock_pager, + ): mock_import.return_value = fake_module mock_cfg.return_value = ({}, object()) mock_pager.return_value = FakeResponse([{"rr": 1}, {"rr": 2}, {"rr": 3}]) @@ -316,13 +304,13 @@ def get_config(self, id): fake_module = SimpleNamespace(FakeClient=FakeClient) - with patch( - "oracle.oci_cloud_mcp_server.server.import_module" - ) as mock_import, patch( - "oracle.oci_cloud_mcp_server.server._get_config_and_signer" - ) as mock_cfg, patch( - "oracle.oci_cloud_mcp_server.server.oci.pagination.list_call_get_all_results" - ) as mock_pager: + with ( + patch("oracle.oci_cloud_mcp_server.server.import_module") as mock_import, + patch("oracle.oci_cloud_mcp_server.server._get_config_and_signer") as mock_cfg, + patch( + "oracle.oci_cloud_mcp_server.server.oci.pagination.list_call_get_all_results" + ) as mock_pager, + ): mock_import.return_value = fake_module mock_cfg.return_value = ({}, object()) @@ -367,13 +355,13 @@ def get_widget(self, widget_id, **kwargs): # noqa: ARG002 fake_module = SimpleNamespace(FakeClient=FakeClient) - with patch( - "oracle.oci_cloud_mcp_server.server.import_module" - ) as mock_import, patch( - "oracle.oci_cloud_mcp_server.server._get_config_and_signer" - ) as mock_cfg, patch( - "oracle.oci_cloud_mcp_server.server.oci.pagination.list_call_get_all_results" - ) as mock_pager: + with ( + patch("oracle.oci_cloud_mcp_server.server.import_module") as mock_import, + patch("oracle.oci_cloud_mcp_server.server._get_config_and_signer") as mock_cfg, + patch( + "oracle.oci_cloud_mcp_server.server.oci.pagination.list_call_get_all_results" + ) as mock_pager, + ): mock_import.return_value = fake_module mock_cfg.return_value = ({}, object()) diff --git a/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/utils.py b/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/utils.py index 364409d2..e226677b 100644 --- a/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/utils.py +++ b/src/oci-cloud-mcp-server/oracle/oci_cloud_mcp_server/utils.py @@ -10,15 +10,11 @@ def initAuditLogger(logger): # Create a rotating file handler - handler = RotatingFileHandler( - "/tmp/audit.log", maxBytes=5 * 1024 * 1024, backupCount=1 - ) + handler = RotatingFileHandler("/tmp/audit.log", maxBytes=5 * 1024 * 1024, backupCount=1) handler.setLevel(logging.INFO) # Create a logging format - formatter = logging.Formatter( - "%(asctime)s - %(name)s - %(levelname)s - %(message)s" - ) + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") handler.setFormatter(formatter) # Add the handler to the logger diff --git a/src/oci-compute-instance-agent-mcp-server/oracle/oci_compute_instance_agent_mcp_server/models.py b/src/oci-compute-instance-agent-mcp-server/oracle/oci_compute_instance_agent_mcp_server/models.py index 2317ea9a..e54e2dec 100644 --- a/src/oci-compute-instance-agent-mcp-server/oracle/oci_compute_instance_agent_mcp_server/models.py +++ b/src/oci-compute-instance-agent-mcp-server/oracle/oci_compute_instance_agent_mcp_server/models.py @@ -36,9 +36,7 @@ def _oci_to_dict(obj): class InstanceAgentCommandExecutionOutputViaTextDetails(BaseModel): """The execution output from a command when returned in plain text.""" - output_type: Literal["TEXT"] = Field( - "TEXT", description="The output destination type for the command." - ) + output_type: Literal["TEXT"] = Field("TEXT", description="The output destination type for the command.") exit_code: int = Field( ..., description="The exit code for the command. Exit code `0` indicates success.", @@ -49,9 +47,7 @@ class InstanceAgentCommandExecutionOutputViaTextDetails(BaseModel): "can populate for additional troubleshooting.", ) text: Optional[str] = Field(None, description="The command output.") - text_sha256: Optional[str] = Field( - None, description="SHA-256 checksum value of the text content." - ) + text_sha256: Optional[str] = Field(None, description="SHA-256 checksum value of the text content.") class InstanceAgentCommandExecutionOutputViaObjectStorageUriDetails(BaseModel): @@ -91,15 +87,9 @@ class InstanceAgentCommandExecutionOutputViaObjectStorageTupleDetails(BaseModel) description="An optional status message that Oracle Cloud Agent " "can populate for additional troubleshooting.", ) - bucket_name: str = Field( - ..., description="The Object Storage bucket for the command output." - ) - namespace_name: str = Field( - ..., description="The Object Storage namespace for the command output." - ) - object_name: str = Field( - ..., description="The Object Storage object name for the command output." - ) + bucket_name: str = Field(..., description="The Object Storage bucket for the command output.") + namespace_name: str = Field(..., description="The Object Storage namespace for the command output.") + object_name: str = Field(..., description="The Object Storage object name for the command output.") OutputContent = Union[ @@ -118,9 +108,7 @@ class InstanceAgentCommandExecution(BaseModel): instance_agent_command_id: str = Field(..., description="The OCID of the command.") instance_id: str = Field(..., description="The OCID of the instance.") - delivery_state: Literal[ - "VISIBLE", "PENDING", "ACKED", "ACKED_CANCELED", "EXPIRED" - ] = Field( + delivery_state: Literal["VISIBLE", "PENDING", "ACKED", "ACKED_CANCELED", "EXPIRED"] = Field( ..., description=( "Specifies the command delivery state. " @@ -133,19 +121,19 @@ class InstanceAgentCommandExecution(BaseModel): "its delivery has expired." ), ) - lifecycle_state: Literal[ - "ACCEPTED", "IN_PROGRESS", "SUCCEEDED", "FAILED", "TIMED_OUT", "CANCELED" - ] = Field( - ..., - description=( - "Command execution life cycle state. " - "* `ACCEPTED` - The command execution has been accepted to run. " - "* `IN_PROGRESS` - The command execution is in progress. " - "* `SUCCEEDED` - The command execution is successful. " - "* `FAILED` - The command execution has failed. " - "* `TIMED_OUT` - The command execution has timedout. " - "* `CANCELED` - The command execution has canceled." - ), + lifecycle_state: Literal["ACCEPTED", "IN_PROGRESS", "SUCCEEDED", "FAILED", "TIMED_OUT", "CANCELED"] = ( + Field( + ..., + description=( + "Command execution life cycle state. " + "* `ACCEPTED` - The command execution has been accepted to run. " + "* `IN_PROGRESS` - The command execution is in progress. " + "* `SUCCEEDED` - The command execution is successful. " + "* `FAILED` - The command execution has failed. " + "* `TIMED_OUT` - The command execution has timedout. " + "* `CANCELED` - The command execution has canceled." + ), + ) ) time_created: datetime = Field(..., description="The command creation date.") time_updated: datetime = Field(..., description="The command last updated at date.") @@ -154,9 +142,7 @@ class InstanceAgentCommandExecution(BaseModel): description="The large non-consecutive number that Run Command Service " "assigns to each created command.", ) - display_name: Optional[str] = Field( - None, description="The user friendly display name of the command." - ) + display_name: Optional[str] = Field(None, description="The user friendly display name of the command.") content: OutputContent = Field(..., description="The command output details.") @@ -197,9 +183,7 @@ def map_tuple_output( exit_code=data.get("exit_code", getattr(content, "exit_code", None)), message=data.get("message", getattr(content, "message", None)), bucket_name=data.get("bucket_name", getattr(content, "bucket_name", None)), - namespace_name=data.get( - "namespace_name", getattr(content, "namespace_name", None) - ), + namespace_name=data.get("namespace_name", getattr(content, "namespace_name", None)), object_name=data.get("object_name", getattr(content, "object_name", None)), ) @@ -228,9 +212,7 @@ def map_instance_agent_command_execution( including all nested types. """ return InstanceAgentCommandExecution( - instance_agent_command_id=getattr( - command_execution, "instance_agent_command_id", None - ), + instance_agent_command_id=getattr(command_execution, "instance_agent_command_id", None), instance_id=getattr(command_execution, "instance_id", None), delivery_state=getattr(command_execution, "delivery_state", None), lifecycle_state=getattr(command_execution, "lifecycle_state", None), @@ -253,15 +235,9 @@ class InstanceAgentCommandSummary(BaseModel): """ instance_agent_command_id: str = Field(..., description="The OCID of the command.") - display_name: Optional[str] = Field( - None, description="A user-friendly name. Does not have to be unique." - ) - compartment_id: str = Field( - ..., description="The OCID of the compartment containing the command." - ) - time_created: datetime = Field( - ..., description="The date and time the command was created (RFC3339)." - ) + display_name: Optional[str] = Field(None, description="A user-friendly name. Does not have to be unique.") + compartment_id: str = Field(..., description="The OCID of the compartment containing the command.") + time_created: datetime = Field(..., description="The date and time the command was created (RFC3339).") time_updated: datetime = Field( ..., description="The date and time the command was last updated (RFC3339)." ) @@ -280,9 +256,7 @@ def map_instance_agent_command_summary( oracle.oci_compute_instance_agent_mcp_server.models.InstanceAgentCommandSummary. """ return InstanceAgentCommandSummary( - instance_agent_command_id=getattr( - command_summary, "instance_agent_command_id", None - ), + instance_agent_command_id=getattr(command_summary, "instance_agent_command_id", None), display_name=getattr(command_summary, "display_name", None), compartment_id=getattr(command_summary, "compartment_id", None), time_created=getattr(command_summary, "time_created", None), @@ -305,9 +279,7 @@ class InstanceAgentCommandExecutionSummary(BaseModel): instance_agent_command_id: str = Field(..., description="The OCID of the command.") instance_id: str = Field(..., description="The OCID of the instance.") - delivery_state: Literal[ - "VISIBLE", "PENDING", "ACKED", "ACKED_CANCELED", "EXPIRED" - ] = Field( + delivery_state: Literal["VISIBLE", "PENDING", "ACKED", "ACKED_CANCELED", "EXPIRED"] = Field( ..., description="The command delivery state. " "* `VISIBLE` - The command is visible to the instance. " @@ -316,21 +288,19 @@ class InstanceAgentCommandExecutionSummary(BaseModel): "* `ACKED_CANCELED` - The canceled command has been received and acknowledged by the instance. " "* `EXPIRED` - The instance has not requested for commands and the command's delivery has expired.", ) - lifecycle_state: Literal[ - "ACCEPTED", "IN_PROGRESS", "SUCCEEDED", "FAILED", "TIMED_OUT", "CANCELED" - ] = Field( - ..., - description="The command execution lifecycle state. " - "* `ACCEPTED` - The command has been accepted to run. " - "* `IN_PROGRESS` - The command is in progress. " - "* `SUCCEEDED` - The command was successfully executed. " - "* `FAILED` - The command failed to execute. " - "* `TIMED_OUT` - The command execution timed out. " - "* `CANCELED` - The command execution was canceled.", - ) - time_created: datetime = Field( - ..., description="The date and time the command was created (RFC3339)." - ) + lifecycle_state: Literal["ACCEPTED", "IN_PROGRESS", "SUCCEEDED", "FAILED", "TIMED_OUT", "CANCELED"] = ( + Field( + ..., + description="The command execution lifecycle state. " + "* `ACCEPTED` - The command has been accepted to run. " + "* `IN_PROGRESS` - The command is in progress. " + "* `SUCCEEDED` - The command was successfully executed. " + "* `FAILED` - The command failed to execute. " + "* `TIMED_OUT` - The command execution timed out. " + "* `CANCELED` - The command execution was canceled.", + ) + ) + time_created: datetime = Field(..., description="The date and time the command was created (RFC3339).") time_updated: datetime = Field( ..., description="The date and time the command was last updated (RFC3339)." ) @@ -339,12 +309,8 @@ class InstanceAgentCommandExecutionSummary(BaseModel): description="A large, non-consecutive number that Oracle Cloud Agent " "assigns to each created command.", ) - display_name: Optional[str] = Field( - None, description="A user-friendly name. Does not have to be unique." - ) - content: Optional[OutputContent] = Field( - None, description="The execution output from a command." - ) + display_name: Optional[str] = Field(None, description="A user-friendly name. Does not have to be unique.") + content: Optional[OutputContent] = Field(None, description="The execution output from a command.") def map_instance_agent_command_execution_summary( @@ -356,9 +322,7 @@ def map_instance_agent_command_execution_summary( including all nested types. """ return InstanceAgentCommandExecutionSummary( - instance_agent_command_id=getattr( - command_execution_summary, "instance_agent_command_id", None - ), + instance_agent_command_id=getattr(command_execution_summary, "instance_agent_command_id", None), instance_id=getattr(command_execution_summary, "instance_id", None), delivery_state=getattr(command_execution_summary, "delivery_state", None), lifecycle_state=getattr(command_execution_summary, "lifecycle_state", None), 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 4c744e44..a127dc1f 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 @@ -56,12 +56,8 @@ def get_compute_instance_agent_client(): description="Runs a script on a compute instance", ) def run_instance_agent_command( - compartment_id: str = Field( - ..., description="The OCID of the compartment to create the command in" - ), - instance_id: str = Field( - ..., description="The OCID of the instance to run the command on" - ), + compartment_id: str = Field(..., description="The OCID of the compartment to create the command in"), + instance_id: str = Field(..., description="The OCID of the instance to run the command on"), display_name: str = Field( ..., description="The display name of the command" @@ -70,9 +66,7 @@ def run_instance_agent_command( "where those time values come from the current date time", ), script: str = Field(..., description="The plain text command to run"), - execution_time_out_in_seconds: Optional[int] = Field( - 30, description="The command's timeout in seconds" - ), + execution_time_out_in_seconds: Optional[int] = Field(30, description="The command's timeout in seconds"), ) -> InstanceAgentCommandExecution: try: client = get_compute_instance_agent_client() @@ -102,10 +96,8 @@ def run_instance_agent_command( # Poll until the command finishes command_id = data.id - execution_response: oci.response.Response = ( - client.get_instance_agent_command_execution( - instance_agent_command_id=command_id, instance_id=instance_id - ) + execution_response: oci.response.Response = client.get_instance_agent_command_execution( + instance_agent_command_id=command_id, instance_id=instance_id ) final_response: oci.response.Response = oci.wait_until( @@ -127,12 +119,8 @@ def run_instance_agent_command( @mcp.tool(description="Lists an instance's agent command executions") def list_instance_agent_command_executions( - compartment_id: str = Field( - ..., description="The OCID of the compartment to list commands from" - ), - instance_id: str = Field( - ..., description="The OCID of the instance to list commands from" - ), + compartment_id: str = Field(..., description="The OCID of the compartment to list commands from"), + instance_id: str = Field(..., description="The OCID of the instance to list commands from"), limit: Optional[int] = Field( None, description="The maximum amount of commands to return. If None, there is no limit.", @@ -160,9 +148,7 @@ def list_instance_agent_command_executions( has_next_page = response.has_next_page next_page = response.next_page if hasattr(response, "next_page") else None - data: list[ - oci.compute_instance_agent.models.InstanceAgentCommandExecutionSummary - ] = response.data + data: list[oci.compute_instance_agent.models.InstanceAgentCommandExecutionSummary] = response.data for d in data: commands.append(map_instance_agent_command_execution_summary(d)) diff --git a/src/oci-compute-instance-agent-mcp-server/oracle/oci_compute_instance_agent_mcp_server/tests/test_compute_instance_agent_models.py b/src/oci-compute-instance-agent-mcp-server/oracle/oci_compute_instance_agent_mcp_server/tests/test_compute_instance_agent_models.py index e55f7bce..63454248 100644 --- a/src/oci-compute-instance-agent-mcp-server/oracle/oci_compute_instance_agent_mcp_server/tests/test_compute_instance_agent_models.py +++ b/src/oci-compute-instance-agent-mcp-server/oracle/oci_compute_instance_agent_mcp_server/tests/test_compute_instance_agent_models.py @@ -24,9 +24,7 @@ def test_map_text_uri_tuple_outputs(self): uri_input = {"exit_code": 1, "message": "u", "output_uri": "https://x"} uri = mdl.map_uri_output(uri_input) - assert uri.output_type == "OBJECT_STORAGE_URI" and uri.output_uri.startswith( - "https://" - ) + assert uri.output_type == "OBJECT_STORAGE_URI" and uri.output_uri.startswith("https://") tup_input = { "exit_code": 2, @@ -84,9 +82,7 @@ def boom(_): assert res.content.text == cmd.content.text assert res.content.exit_code == cmd.content.exit_code - def test_map_instance_agent_command_execution_summary_maps_fields( - self, monkeypatch - ): + def test_map_instance_agent_command_execution_summary_maps_fields(self, monkeypatch): # Force models._oci_to_dict to bypass oci.util.to_dict and use __dict__ fallback def boom(_): raise RuntimeError("boom") diff --git a/src/oci-compute-instance-agent-mcp-server/oracle/oci_compute_instance_agent_mcp_server/tests/test_compute_instance_agent_tools.py b/src/oci-compute-instance-agent-mcp-server/oracle/oci_compute_instance_agent_mcp_server/tests/test_compute_instance_agent_tools.py index 6cefff8c..f34b64dc 100644 --- a/src/oci-compute-instance-agent-mcp-server/oracle/oci_compute_instance_agent_mcp_server/tests/test_compute_instance_agent_tools.py +++ b/src/oci-compute-instance-agent-mcp-server/oracle/oci_compute_instance_agent_mcp_server/tests/test_compute_instance_agent_tools.py @@ -26,9 +26,7 @@ class TestComputeInstanceAgent: @pytest.mark.asyncio @patch("oci.wait_until") - @patch( - "oracle.oci_compute_instance_agent_mcp_server.server.get_compute_instance_agent_client" - ) + @patch("oracle.oci_compute_instance_agent_mcp_server.server.get_compute_instance_agent_client") async def test_run_instance_agent_command(self, mock_get_client, mock_wait_until): compartment_id = "test_compartment" instance_id = "test_instance" @@ -74,9 +72,7 @@ async def test_run_instance_agent_command(self, mock_get_client, mock_wait_until time_updated="2023-01-01T00:00:00Z", sequence_number=1, ) - mock_client.get_instance_agent_command_execution.return_value = ( - mock_execution_response - ) + mock_client.get_instance_agent_command_execution.return_value = mock_execution_response mock_wait_until.return_value = mock_execution_response @@ -102,14 +98,10 @@ async def test_run_instance_agent_command(self, mock_get_client, mock_wait_until mock_wait_until.assert_called() args, kwargs = mock_wait_until.call_args assert kwargs["property"] == "lifecycle_state" - assert ( - kwargs["state"] == InstanceAgentCommandExecution.LIFECYCLE_STATE_SUCCEEDED - ) + assert kwargs["state"] == InstanceAgentCommandExecution.LIFECYCLE_STATE_SUCCEEDED @pytest.mark.asyncio - @patch( - "oracle.oci_compute_instance_agent_mcp_server.server.get_compute_instance_agent_client" - ) + @patch("oracle.oci_compute_instance_agent_mcp_server.server.get_compute_instance_agent_client") async def test_list_instance_agent_commands(self, mock_get_client): compartment_id = "test_compartment" instance_id = "test_instance" @@ -132,9 +124,7 @@ async def test_list_instance_agent_commands(self, mock_get_client): ] mock_list_response.has_next_page = False mock_list_response.next_page = None - mock_client.list_instance_agent_command_executions.return_value = ( - mock_list_response - ) + mock_client.list_instance_agent_command_executions.return_value = mock_list_response async with Client(mcp) as client: result = ( @@ -148,18 +138,11 @@ async def test_list_instance_agent_commands(self, mock_get_client): ).structured_content["result"] assert len(result) == 1 - assert ( - result[0]["instance_agent_command_id"] - == mock_command_1.instance_agent_command_id - ) + assert result[0]["instance_agent_command_id"] == mock_command_1.instance_agent_command_id @pytest.mark.asyncio - @patch( - "oracle.oci_compute_instance_agent_mcp_server.server.get_compute_instance_agent_client" - ) - async def test_list_instance_agent_commands_pagination_and_limit_and_output_types( - self, mock_get_client - ): + @patch("oracle.oci_compute_instance_agent_mcp_server.server.get_compute_instance_agent_client") + async def test_list_instance_agent_commands_pagination_and_limit_and_output_types(self, mock_get_client): # This test exercises: # - pagination (has_next_page + next_page) # - limit enforcement @@ -255,9 +238,7 @@ async def test_list_instance_agent_commands_pagination_and_limit_and_output_type "limit": limit, } result = ( - await client.call_tool( - "list_instance_agent_command_executions", payload - ) + await client.call_tool("list_instance_agent_command_executions", payload) ).structured_content["result"] # Verify pagination and mapping of content subtypes @@ -269,30 +250,19 @@ async def test_list_instance_agent_commands_pagination_and_limit_and_output_type # Second is OBJECT_STORAGE_URI assert result[1]["instance_agent_command_id"] == "cmd-uri-2" assert result[1]["content"]["output_type"] == "OBJECT_STORAGE_URI" - assert ( - result[1]["content"]["output_uri"] - == "https://objectstorage.example.com/n/bkt/o/out" - ) + assert result[1]["content"]["output_uri"] == "https://objectstorage.example.com/n/bkt/o/out" # Ensure pagination called with correct page tokens - first_kwargs = ( - mock_client.list_instance_agent_command_executions.call_args_list[0].kwargs - ) - second_kwargs = ( - mock_client.list_instance_agent_command_executions.call_args_list[1].kwargs - ) + first_kwargs = mock_client.list_instance_agent_command_executions.call_args_list[0].kwargs + second_kwargs = mock_client.list_instance_agent_command_executions.call_args_list[1].kwargs assert first_kwargs["page"] is None assert first_kwargs["limit"] == limit assert second_kwargs["page"] == "token-1" assert second_kwargs["limit"] == limit @pytest.mark.asyncio - @patch( - "oracle.oci_compute_instance_agent_mcp_server.server.get_compute_instance_agent_client" - ) - async def test_run_instance_agent_command_exception_propagates( - self, mock_get_client - ): + @patch("oracle.oci_compute_instance_agent_mcp_server.server.get_compute_instance_agent_client") + async def test_run_instance_agent_command_exception_propagates(self, mock_get_client): mock_client = MagicMock() mock_get_client.return_value = mock_client mock_client.create_instance_agent_command.side_effect = RuntimeError("boom") @@ -360,9 +330,7 @@ def test_main_with_only_port(self, mock_getenv, mock_mcp_run): mock_mcp_run.assert_called_once_with() -@patch( - "oracle.oci_compute_instance_agent_mcp_server.server.get_compute_instance_agent_client" -) +@patch("oracle.oci_compute_instance_agent_mcp_server.server.get_compute_instance_agent_client") @pytest.mark.asyncio async def test_list_instance_agent_command_executions_exception_propagates( mock_get_client, @@ -387,12 +355,8 @@ class TestGetClient: @patch( "oracle.oci_compute_instance_agent_mcp_server.server.oci.compute_instance_agent.ComputeInstanceAgentClient" # noqa ) - @patch( - "oracle.oci_compute_instance_agent_mcp_server.server.oci.auth.signers.SecurityTokenSigner" - ) - @patch( - "oracle.oci_compute_instance_agent_mcp_server.server.oci.signer.load_private_key_from_file" - ) + @patch("oracle.oci_compute_instance_agent_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_compute_instance_agent_mcp_server.server.oci.signer.load_private_key_from_file") @patch( "oracle.oci_compute_instance_agent_mcp_server.server.open", new_callable=mock_open, @@ -430,14 +394,14 @@ def test_get_compute_instance_agent_client_with_profile_env( profile_name="MYPROFILE", ) mock_open_file.assert_called_once_with("/abs/path/to/token", "r") - mock_security_token_signer.assert_called_once_with( - "SECURITY_TOKEN", private_key_obj - ) + mock_security_token_signer.assert_called_once_with("SECURITY_TOKEN", private_key_obj) # Ensure user agent was set on the same config dict passed into client args, _ = mock_client.call_args passed_config = args[0] assert passed_config is config - expected_user_agent = f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + expected_user_agent = ( + f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + ) assert passed_config.get("additional_user_agent") == expected_user_agent # And we returned the client instance assert result == mock_client.return_value @@ -445,12 +409,8 @@ def test_get_compute_instance_agent_client_with_profile_env( @patch( "oracle.oci_compute_instance_agent_mcp_server.server.oci.compute_instance_agent.ComputeInstanceAgentClient" # noqa ) - @patch( - "oracle.oci_compute_instance_agent_mcp_server.server.oci.auth.signers.SecurityTokenSigner" - ) - @patch( - "oracle.oci_compute_instance_agent_mcp_server.server.oci.signer.load_private_key_from_file" - ) + @patch("oracle.oci_compute_instance_agent_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_compute_instance_agent_mcp_server.server.oci.signer.load_private_key_from_file") @patch( "oracle.oci_compute_instance_agent_mcp_server.server.open", new_callable=mock_open, @@ -492,9 +452,6 @@ def test_get_compute_instance_agent_client_uses_default_profile_when_env_missing cc_args, _ = mock_client.call_args assert cc_args[0] is config assert "additional_user_agent" in config - assert ( - isinstance(config["additional_user_agent"], str) - and "/" in config["additional_user_agent"] - ) + assert isinstance(config["additional_user_agent"], str) and "/" in config["additional_user_agent"] # Returned object is client instance assert srv_client is mock_client.return_value diff --git a/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/consts.py b/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/consts.py index 94723b28..023df169 100644 --- a/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/consts.py +++ b/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/consts.py @@ -4,9 +4,7 @@ https://oss.oracle.com/licenses/upl. """ -ORACLE_LINUX_9_IMAGE = ( - "ocid1.image.oc1.iad.aaaaaaaa4l64brs5udx52nedrhlex4cpaorcd2jwvpoududksmw4lgmameqq" -) +ORACLE_LINUX_9_IMAGE = "ocid1.image.oc1.iad.aaaaaaaa4l64brs5udx52nedrhlex4cpaorcd2jwvpoududksmw4lgmameqq" E5_FLEX = "VM.Standard.E5.Flex" DEFAULT_OCPU_COUNT = 1 DEFAULT_MEMORY_IN_GBS = 12 diff --git a/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/models.py b/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/models.py index 3f6cbf7d..b8f0814e 100644 --- a/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/models.py +++ b/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/models.py @@ -40,26 +40,16 @@ class PlacementConstraintDetails(BaseModel): None, description="Placement strategy/policy identifier (e.g., anti-affinity, cluster).", # noqa ) - details: Optional[Dict[str, Any]] = Field( - None, description="Additional placement constraint details." - ) + details: Optional[Dict[str, Any]] = Field(None, description="Additional placement constraint details.") class LaunchOptions(BaseModel): """Launch options for VM instances.""" - boot_volume_type: Optional[str] = Field( - None, description="Boot volume attachment type." - ) - firmware: Optional[str] = Field( - None, description="Firmware type to use when launching instances." - ) - network_type: Optional[str] = Field( - None, description="Network attachment type for VNIC." - ) - remote_data_volume_type: Optional[str] = Field( - None, description="Data volume attachment type." - ) + boot_volume_type: Optional[str] = Field(None, description="Boot volume attachment type.") + firmware: Optional[str] = Field(None, description="Firmware type to use when launching instances.") + network_type: Optional[str] = Field(None, description="Network attachment type for VNIC.") + remote_data_volume_type: Optional[str] = Field(None, description="Data volume attachment type.") is_pv_encryption_in_transit_enabled: Optional[bool] = Field( None, description="Whether paravirtualized volume encryption in transit is enabled.", @@ -87,9 +77,7 @@ class InstanceAvailabilityConfig(BaseModel): None, description="Action when host fails (e.g., RESTORE_INSTANCE, STOP_INSTANCE).", ) - is_pmu_enabled: Optional[bool] = Field( - None, description="Whether PMU is enabled (platform-dependent)." - ) + is_pmu_enabled: Optional[bool] = Field(None, description="Whether PMU is enabled (platform-dependent).") class PreemptibleInstanceConfigDetails(BaseModel): @@ -112,38 +100,24 @@ class InstanceShapeConfig(BaseModel): ) nvmes: Optional[int] = Field(None, description="Number of local NVMe drives.") local_disks: Optional[int] = Field(None, description="Number of local disks.") - local_disks_total_size_in_gbs: Optional[float] = Field( - None, description="Total local disk size in GB." - ) + local_disks_total_size_in_gbs: Optional[float] = Field(None, description="Total local disk size in GB.") class InstanceSourceDetails(BaseModel): """Source details used for instance creation.""" - source_type: Optional[str] = Field( - None, description="Type of source (e.g., image, bootVolume)." - ) + source_type: Optional[str] = Field(None, description="Type of source (e.g., image, bootVolume).") image_id: Optional[str] = Field(None, description="Image OCID.") - boot_volume_size_in_gbs: Optional[int] = Field( - None, description="Boot volume size in GB." - ) + boot_volume_size_in_gbs: Optional[int] = Field(None, description="Boot volume size in GB.") class InstanceAgentConfig(BaseModel): """Oracle Cloud Agent configuration.""" - is_monitoring_disabled: Optional[bool] = Field( - None, description="Disable monitoring plugins." - ) - is_management_disabled: Optional[bool] = Field( - None, description="Disable management plugins." - ) - are_all_plugins_disabled: Optional[bool] = Field( - None, description="Disable all plugins." - ) - plugins_config: Optional[List[Dict[str, Any]]] = Field( - None, description="Per-plugin configuration list." - ) + is_monitoring_disabled: Optional[bool] = Field(None, description="Disable monitoring plugins.") + is_management_disabled: Optional[bool] = Field(None, description="Disable management plugins.") + are_all_plugins_disabled: Optional[bool] = Field(None, description="Disable all plugins.") + plugins_config: Optional[List[Dict[str, Any]]] = Field(None, description="Per-plugin configuration list.") class PlatformConfig(BaseModel): @@ -153,21 +127,15 @@ class PlatformConfig(BaseModel): None, description="Platform config discriminator (e.g., AMD_VM, INTEL_VM, AARCH64_VM).", # noqa ) - details: Optional[Dict[str, Any]] = Field( - None, description="Additional platform-specific details." - ) + details: Optional[Dict[str, Any]] = Field(None, description="Additional platform-specific details.") class LicensingConfig(BaseModel): """Licensing configuration associated with the instance.""" license_type: Optional[str] = Field(None, description="License type or SKU.") - is_vendor_oracle: Optional[bool] = Field( - None, description="Whether Oracle is the license vendor." - ) - is_bring_your_own_license: Optional[bool] = Field( - None, description="Whether BYOL is used." - ) + is_vendor_oracle: Optional[bool] = Field(None, description="Whether Oracle is the license vendor.") + is_bring_your_own_license: Optional[bool] = Field(None, description="Whether BYOL is used.") # Based on oci.core.Instance @@ -208,12 +176,10 @@ class Instance(BaseModel): None, description="Security attributes for Zero Trust Packet Routing (ZPR), labeled by namespace.", # noqa ) - security_attributes_state: Optional[ - Literal["STABLE", "UPDATING", "UNKNOWN_ENUM_VALUE"] - ] = Field(None, description="The lifecycle state of the securityAttributes.") - display_name: Optional[str] = Field( - None, description="A user-friendly name. Does not have to be unique." + security_attributes_state: Optional[Literal["STABLE", "UPDATING", "UNKNOWN_ENUM_VALUE"]] = Field( + None, description="The lifecycle state of the securityAttributes." ) + display_name: Optional[str] = Field(None, description="A user-friendly name. Does not have to be unique.") extended_metadata: Optional[Dict[str, Any]] = Field( None, description="Additional metadata key/value pairs; may contain nested JSON objects.", # noqa @@ -225,9 +191,7 @@ class Instance(BaseModel): None, description="Free-form tags for this resource as simple key/value pairs." ) id: Optional[str] = Field(None, description="The OCID of the instance.") - image_id: Optional[str] = Field( - None, description="Deprecated. Use sourceDetails instead." - ) + image_id: Optional[str] = Field(None, description="Deprecated. Use sourceDetails instead.") ipxe_script: Optional[str] = Field( None, description="Custom iPXE script to run when the instance boots. Not used for paravirtualized boot volumes.", # noqa @@ -236,9 +200,7 @@ class Instance(BaseModel): Literal["NATIVE", "EMULATED", "PARAVIRTUALIZED", "CUSTOM", "UNKNOWN_ENUM_VALUE"] ] = Field(None, description="Configuration mode for launching VM instances.") launch_options: Optional[LaunchOptions] = Field(None, description="Launch options.") - instance_options: Optional[InstanceOptions] = Field( - None, description="Instance options." - ) + instance_options: Optional[InstanceOptions] = Field(None, description="Instance options.") availability_config: Optional[InstanceAvailabilityConfig] = Field( None, description="Availability configuration." ) @@ -259,9 +221,7 @@ class Instance(BaseModel): "UNKNOWN_ENUM_VALUE", ] ] = Field(None, description="The current lifecycle state of the instance.") - metadata: Optional[Dict[str, str]] = Field( - None, description="Custom metadata that you provide." - ) + metadata: Optional[Dict[str, str]] = Field(None, description="Custom metadata that you provide.") region: Optional[str] = Field( None, description="The region that contains the availability domain the instance is running in.", # noqa @@ -270,9 +230,7 @@ class Instance(BaseModel): None, description="The shape of the instance. Determines CPU and memory allocated.", ) - shape_config: Optional[InstanceShapeConfig] = Field( - None, description="Instance shape configuration." - ) + shape_config: Optional[InstanceShapeConfig] = Field(None, description="Instance shape configuration.") is_cross_numa_node: Optional[bool] = Field( None, description="Whether the instance’s OCPUs and memory are distributed across multiple NUMA nodes.", # noqa @@ -287,16 +245,12 @@ class Instance(BaseModel): time_created: Optional[datetime] = Field( None, description="The date and time the instance was created (RFC3339)." ) - agent_config: Optional[InstanceAgentConfig] = Field( - None, description="Instance agent configuration." - ) + agent_config: Optional[InstanceAgentConfig] = Field(None, description="Instance agent configuration.") time_maintenance_reboot_due: Optional[datetime] = Field( None, description="The date and time the instance is expected to be stopped/started (RFC3339).", # noqa ) - platform_config: Optional[PlatformConfig] = Field( - None, description="Platform configuration." - ) + platform_config: Optional[PlatformConfig] = Field(None, description="Platform configuration.") instance_configuration_id: Optional[str] = Field( None, description="The OCID of the Instance Configuration used to source launch details for this instance.", # noqa @@ -325,12 +279,8 @@ def map_launch_options(lo) -> LaunchOptions | None: firmware=getattr(lo, "firmware", None), network_type=getattr(lo, "network_type", None), remote_data_volume_type=getattr(lo, "remote_data_volume_type", None), - is_pv_encryption_in_transit_enabled=getattr( - lo, "is_pv_encryption_in_transit_enabled", None - ), - is_consistent_volume_naming_enabled=getattr( - lo, "is_consistent_volume_naming_enabled", None - ), + is_pv_encryption_in_transit_enabled=getattr(lo, "is_pv_encryption_in_transit_enabled", None), + is_consistent_volume_naming_enabled=getattr(lo, "is_consistent_volume_naming_enabled", None), ) @@ -338,9 +288,7 @@ def map_instance_options(io) -> InstanceOptions | None: if not io: return None return InstanceOptions( - are_legacy_imds_endpoints_disabled=getattr( - io, "are_legacy_imds_endpoints_disabled", None - ) + are_legacy_imds_endpoints_disabled=getattr(io, "are_legacy_imds_endpoints_disabled", None) ) @@ -371,9 +319,7 @@ def map_shape_config(sc) -> InstanceShapeConfig | None: vcpus=getattr(sc, "vcpus", None), baseline_ocpu_utilization=getattr(sc, "baseline_ocpu_utilization", None), local_disks=getattr(sc, "local_disks", None), - local_disks_total_size_in_gbs=getattr( - sc, "local_disks_total_size_in_gbs", None - ), + local_disks_total_size_in_gbs=getattr(sc, "local_disks_total_size_in_gbs", None), ) @@ -424,8 +370,7 @@ def map_licensing_configs(items) -> list[LicensingConfig] | None: data = _oci_to_dict(it) or {} result.append( LicensingConfig( - license_type=getattr(it, "license_type", None) - or data.get("license_type"), + license_type=getattr(it, "license_type", None) or data.get("license_type"), ) ) return result @@ -445,15 +390,11 @@ def map_instance( placement_constraint_details=map_placement_constraint_details( getattr(instance_data, "placement_constraint_details", None) ), - cluster_placement_group_id=getattr( - instance_data, "cluster_placement_group_id", None - ), + cluster_placement_group_id=getattr(instance_data, "cluster_placement_group_id", None), dedicated_vm_host_id=getattr(instance_data, "dedicated_vm_host_id", None), defined_tags=getattr(instance_data, "defined_tags", None), security_attributes=getattr(instance_data, "security_attributes", None), - security_attributes_state=getattr( - instance_data, "security_attributes_state", None - ), + security_attributes_state=getattr(instance_data, "security_attributes_state", None), display_name=getattr(instance_data, "display_name", None), extended_metadata=getattr(instance_data, "extended_metadata", None), fault_domain=getattr(instance_data, "fault_domain", None), @@ -462,15 +403,9 @@ def map_instance( image_id=getattr(instance_data, "image_id", None), ipxe_script=getattr(instance_data, "ipxe_script", None), launch_mode=getattr(instance_data, "launch_mode", None), - launch_options=map_launch_options( - getattr(instance_data, "launch_options", None) - ), - instance_options=map_instance_options( - getattr(instance_data, "instance_options", None) - ), - availability_config=map_availability_config( - getattr(instance_data, "availability_config", None) - ), + launch_options=map_launch_options(getattr(instance_data, "launch_options", None)), + instance_options=map_instance_options(getattr(instance_data, "instance_options", None)), + availability_config=map_availability_config(getattr(instance_data, "availability_config", None)), preemptible_instance_config=map_preemptible_config( getattr(instance_data, "preemptible_instance_config", None) ), @@ -480,24 +415,14 @@ def map_instance( shape=getattr(instance_data, "shape", None), shape_config=map_shape_config(getattr(instance_data, "shape_config", None)), is_cross_numa_node=getattr(instance_data, "is_cross_numa_node", None), - source_details=map_source_details( - getattr(instance_data, "source_details", None) - ), + source_details=map_source_details(getattr(instance_data, "source_details", None)), system_tags=getattr(instance_data, "system_tags", None), time_created=getattr(instance_data, "time_created", None), agent_config=map_agent_config(getattr(instance_data, "agent_config", None)), - time_maintenance_reboot_due=getattr( - instance_data, "time_maintenance_reboot_due", None - ), - platform_config=map_platform_config( - getattr(instance_data, "platform_config", None) - ), - instance_configuration_id=getattr( - instance_data, "instance_configuration_id", None - ), - licensing_configs=map_licensing_configs( - getattr(instance_data, "licensing_configs", None) - ), + time_maintenance_reboot_due=getattr(instance_data, "time_maintenance_reboot_due", None), + platform_config=map_platform_config(getattr(instance_data, "platform_config", None)), + instance_configuration_id=getattr(instance_data, "instance_configuration_id", None), + licensing_configs=map_licensing_configs(getattr(instance_data, "licensing_configs", None)), ) @@ -509,12 +434,8 @@ def map_instance( class InstanceAgentFeatures(BaseModel): """Oracle Cloud Agent features supported on the image.""" - is_monitoring_supported: Optional[bool] = Field( - None, description="This attribute is not used." - ) - is_management_supported: Optional[bool] = Field( - None, description="This attribute is not used." - ) + is_monitoring_supported: Optional[bool] = Field(None, description="This attribute is not used.") + is_management_supported: Optional[bool] = Field(None, description="This attribute is not used.") class Image(BaseModel): @@ -620,9 +541,7 @@ def map_image(image_data: oci.core.models.Image) -> Image: lifecycle_state=getattr(image_data, "lifecycle_state", None), operating_system=getattr(image_data, "operating_system", None), operating_system_version=getattr(image_data, "operating_system_version", None), - agent_features=map_instance_agent_features( - getattr(image_data, "agent_features", None) - ), + agent_features=map_instance_agent_features(getattr(image_data, "agent_features", None)), listing_type=getattr(image_data, "listing_type", None), size_in_mbs=getattr(image_data, "size_in_mbs", None), billable_size_in_gbs=getattr(image_data, "billable_size_in_gbs", None), @@ -642,16 +561,10 @@ class Request(BaseModel): method: Optional[str] = Field(None, description="The HTTP method.") url: Optional[str] = Field(None, description="URL that will serve the request.") - query_params: Optional[Dict[str, Any]] = Field( - None, description="Query parameters in the URL." - ) - header_params: Optional[Dict[str, Any]] = Field( - None, description="Request header parameters." - ) + query_params: Optional[Dict[str, Any]] = Field(None, description="Query parameters in the URL.") + header_params: Optional[Dict[str, Any]] = Field(None, description="Request header parameters.") body: Optional[Any] = Field(None, description="Request body.") - response_type: Optional[str] = Field( - None, description="Expected response data type." - ) + response_type: Optional[str] = Field(None, description="Expected response data type.") enforce_content_headers: Optional[bool] = Field( None, description=( @@ -667,24 +580,12 @@ class Response(BaseModel): """ status: Optional[int] = Field(None, description="The HTTP status code.") - headers: Optional[Dict[str, Any]] = Field( - None, description="The HTTP headers (case-insensitive keys)." - ) - data: Optional[Any] = Field( - None, description="The response data. Type depends on the request." - ) - request: Optional[Request] = Field( - None, description="The corresponding request for this response." - ) - next_page: Optional[str] = Field( - None, description="The value of the opc-next-page response header." - ) - request_id: Optional[str] = Field( - None, description="The ID of the request that generated this response." - ) - has_next_page: Optional[bool] = Field( - None, description="Whether there is a next page of results." - ) + headers: Optional[Dict[str, Any]] = Field(None, description="The HTTP headers (case-insensitive keys).") + data: Optional[Any] = Field(None, description="The response data. Type depends on the request.") + request: Optional[Request] = Field(None, description="The corresponding request for this response.") + next_page: Optional[str] = Field(None, description="The value of the opc-next-page response header.") + request_id: Optional[str] = Field(None, description="The ID of the request that generated this response.") + has_next_page: Optional[bool] = Field(None, description="Whether there is a next page of results.") def map_request(req) -> Request | None: @@ -817,9 +718,7 @@ class VnicAttachment(BaseModel): "Cards (VNICs)." ), ) - subnet_id: Optional[str] = Field( - None, description="The OCID of the subnet to create the VNIC in." - ) + subnet_id: Optional[str] = Field(None, description="The OCID of the subnet to create the VNIC in.") vlan_id: Optional[str] = Field( None, description=( @@ -848,10 +747,7 @@ class VnicAttachment(BaseModel): ) vnic_id: Optional[str] = Field( None, - description=( - "The OCID of the VNIC. Available after the attachment process is " - "complete." - ), + description=("The OCID of the VNIC. Available after the attachment process is complete."), ) 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 462657d7..7007f88b 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 @@ -112,9 +112,7 @@ def list_instances( @mcp.tool(description="Get Instance with a given instance OCID") -def get_instance( - instance_id: str = Field(..., description="The OCID of the instance") -) -> Instance: +def get_instance(instance_id: str = Field(..., description="The OCID of the instance")) -> Instance: try: client = get_compute_client() @@ -216,9 +214,7 @@ def launch_instance( @mcp.tool(description="Delete instance with given instance OCID") -def terminate_instance( - instance_id: str = Field(..., description="The OCID of the instance") -) -> Response: +def terminate_instance(instance_id: str = Field(..., description="The OCID of the instance")) -> Response: try: client = get_compute_client() @@ -231,9 +227,7 @@ def terminate_instance( raise e -@mcp.tool( - description="Update instance. This may restart the instance, so warn the user" -) +@mcp.tool(description="Update instance. This may restart the instance, so warn the user") def update_instance( instance_id: str = Field(..., description="The OCID of the instance"), ocpus: Optional[int] = Field( @@ -266,15 +260,10 @@ def update_instance( raise e -@mcp.tool( - description="List images in a given compartment, " - "optionally filtered by operating system" -) +@mcp.tool(description="List images in a given compartment, optionally filtered by operating system") def list_images( compartment_id: str = Field(..., description="The OCID of the compartment"), - operating_system: Optional[str] = Field( - None, description="The operating system to filter with" - ), + operating_system: Optional[str] = Field(None, description="The operating system to filter with"), limit: Optional[int] = Field( None, description="The maximum amount of resources to return. If None, there is no limit.", @@ -359,9 +348,7 @@ def instance_action( raise e -@mcp.tool( - description="List vnic attachments in a given compartment and/or on a given instance. " -) +@mcp.tool(description="List vnic attachments in a given compartment and/or on a given instance. ") def list_vnic_attachments( compartment_id: str = Field( ..., @@ -414,14 +401,12 @@ def list_vnic_attachments( @mcp.tool(description="Get Vnic Attachment with a given OCID") def get_vnic_attachment( - vnic_attachment_id: str = Field(..., description="The OCID of the vnic attachment") + vnic_attachment_id: str = Field(..., description="The OCID of the vnic attachment"), ) -> VnicAttachment: try: client = get_compute_client() - response: oci.response.Response = client.get_vnic_attachment( - vnic_attachment_id=vnic_attachment_id - ) + response: oci.response.Response = client.get_vnic_attachment(vnic_attachment_id=vnic_attachment_id) data: oci.core.models.VnicAttachment = response.data logger.info("Found Vnic Attachment") return map_vnic_attachment(data) diff --git a/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/tests/test_compute_models.py b/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/tests/test_compute_models.py index 3df8f280..76a76b9f 100644 --- a/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/tests/test_compute_models.py +++ b/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/tests/test_compute_models.py @@ -384,9 +384,7 @@ async def test_map_response_derives_next_page_and_request_id_from_headers(): async def test_map_response_data_instance_object_and_list(): # Single instance object path inst = oci.core.models.Instance(id="ocid1.instance..single", display_name="one") - oci_resp_single = oci.response.Response( - status=200, headers={}, data=inst, request=None - ) + oci_resp_single = oci.response.Response(status=200, headers={}, data=inst, request=None) mapped_single = map_response(oci_resp_single) assert isinstance(mapped_single, Response) assert isinstance(mapped_single.data, Instance) @@ -395,9 +393,7 @@ async def test_map_response_data_instance_object_and_list(): # List of instances path inst2 = oci.core.models.Instance(id="ocid1.instance..two", display_name="two") - oci_resp_list = oci.response.Response( - status=200, headers={}, data=[inst, inst2], request=None - ) + oci_resp_list = oci.response.Response(status=200, headers={}, data=[inst, inst2], request=None) mapped_list = map_response(oci_resp_list) assert isinstance(mapped_list.data, list) assert all(isinstance(x, Instance) for x in mapped_list.data) diff --git a/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/tests/test_compute_tools.py b/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/tests/test_compute_tools.py index 0c4bf019..e0ca0122 100644 --- a/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/tests/test_compute_tools.py +++ b/src/oci-compute-mcp-server/oracle/oci_compute_mcp_server/tests/test_compute_tools.py @@ -61,9 +61,7 @@ async def test_list_instances_exception(self, mock_get_client): async with Client(mcp) as client: with pytest.raises(fastmcp.exceptions.ToolError) as e: - await client.call_tool( - "list_instances", {"compartment_id": "test_compartment"} - ) + await client.call_tool("list_instances", {"compartment_id": "test_compartment"}) # Verify the ToolError message contains the expected details assert "Error calling tool 'list_instances'" in str(e.value) @@ -84,9 +82,7 @@ async def test_get_instance(self, mock_get_client): mock_client.get_instance.return_value = mock_get_response async with Client(mcp) as client: - call_tool_result = await client.call_tool( - "get_instance", {"instance_id": "instance1"} - ) + call_tool_result = await client.call_tool("get_instance", {"instance_id": "instance1"}) result = call_tool_result.structured_content assert result["id"] == "instance1" @@ -566,9 +562,7 @@ async def test_get_vnic_attachment_exception(self, mock_get_client): async with Client(mcp) as client: with pytest.raises(fastmcp.exceptions.ToolError) as e: - await client.call_tool( - "get_vnic_attachment", {"vnic_attachment_id": "vnicattachment1"} - ) + await client.call_tool("get_vnic_attachment", {"vnic_attachment_id": "vnicattachment1"}) # Verify the ToolError message contains the expected details assert "Error calling tool 'get_vnic_attachment'" in str(e.value) @@ -708,14 +702,14 @@ def test_get_compute_client_with_profile_env( profile_name="MYPROFILE", ) mock_open_file.assert_called_once_with("/abs/path/to/token", "r") - mock_security_token_signer.assert_called_once_with( - "SECURITY_TOKEN", private_key_obj - ) + mock_security_token_signer.assert_called_once_with("SECURITY_TOKEN", private_key_obj) # Ensure user agent was set on the same config dict passed into client args, _ = mock_client.call_args passed_config = args[0] assert passed_config is config - expected_user_agent = f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + expected_user_agent = ( + f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + ) assert passed_config.get("additional_user_agent") == expected_user_agent # And we returned the client instance assert result == mock_client.return_value @@ -764,9 +758,6 @@ def test_get_compute_client_uses_default_profile_when_env_missing( cc_args, _ = mock_client.call_args assert cc_args[0] is config assert "additional_user_agent" in config - assert ( - isinstance(config["additional_user_agent"], str) - and "/" in config["additional_user_agent"] - ) + assert isinstance(config["additional_user_agent"], str) and "/" in config["additional_user_agent"] # Returned object is client instance assert srv_client is mock_client.return_value diff --git a/src/oci-faaas-mcp-server/oracle/oci_faaas_mcp_server/models.py b/src/oci-faaas-mcp-server/oracle/oci_faaas_mcp_server/models.py index 10e95562..d6a26650 100644 --- a/src/oci-faaas-mcp-server/oracle/oci_faaas_mcp_server/models.py +++ b/src/oci-faaas-mcp-server/oracle/oci_faaas_mcp_server/models.py @@ -36,19 +36,11 @@ class FusionEnvironmentFamily(BaseModel): None, description="Lifecycle state (e.g., CREATING, UPDATING, ACTIVE, DELETING, DELETED, FAILED)", ) - compartment_id: Optional[str] = Field( - None, description="Compartment OCID containing this family" - ) - time_created: Optional[datetime] = Field( - None, description="Creation time (RFC3339)" - ) - time_updated: Optional[datetime] = Field( - None, description="Last update time (RFC3339)" - ) + compartment_id: Optional[str] = Field(None, description="Compartment OCID containing this family") + time_created: Optional[datetime] = Field(None, description="Creation time (RFC3339)") + time_updated: Optional[datetime] = Field(None, description="Last update time (RFC3339)") freeform_tags: Optional[Dict[str, str]] = Field(None, description="Freeform tags") - defined_tags: Optional[Dict[str, Dict[str, Any]]] = Field( - None, description="Defined tags" - ) + defined_tags: Optional[Dict[str, Dict[str, Any]]] = Field(None, description="Defined tags") class FusionEnvironment(BaseModel): @@ -56,9 +48,7 @@ class FusionEnvironment(BaseModel): id: Optional[str] = Field(None, description="OCID of the Fusion Environment") display_name: Optional[str] = Field(None, description="Display name") - compartment_id: Optional[str] = Field( - None, description="Compartment OCID containing the environment" - ) + compartment_id: Optional[str] = Field(None, description="Compartment OCID containing the environment") fusion_environment_family_id: Optional[str] = Field( None, description="OCID of the parent Fusion Environment Family" ) @@ -71,70 +61,44 @@ class FusionEnvironment(BaseModel): domain_id: Optional[str] = Field(None, description="IDCS domain OCID") lifecycle_state: Optional[str] = Field(None, description="Lifecycle state") - lifecycle_details: Optional[str] = Field( - None, description="Additional lifecycle details" - ) + lifecycle_details: Optional[str] = Field(None, description="Additional lifecycle details") is_suspended: Optional[bool] = Field(None, description="Suspended flag") system_name: Optional[str] = Field(None, description="System name/code") environment_role: Optional[str] = Field(None, description="Environment role") - maintenance_policy: Optional[Dict[str, Any]] = Field( - None, description="Maintenance policy details" - ) + maintenance_policy: Optional[Dict[str, Any]] = Field(None, description="Maintenance policy details") time_upcoming_maintenance: Optional[datetime] = Field( None, description="Upcoming maintenance window (RFC3339)" ) - applied_patch_bundles: Optional[List[str]] = Field( - None, description="Applied patch bundles" - ) + applied_patch_bundles: Optional[List[str]] = Field(None, description="Applied patch bundles") - subscription_ids: Optional[List[str]] = Field( - None, description="Associated subscription OCIDs" - ) - additional_language_packs: Optional[List[str]] = Field( - None, description="Enabled language packs" - ) + subscription_ids: Optional[List[str]] = Field(None, description="Associated subscription OCIDs") + additional_language_packs: Optional[List[str]] = Field(None, description="Enabled language packs") kms_key_id: Optional[str] = Field(None, description="KMS key OCID") kms_key_info: Optional[Dict[str, Any]] = Field(None, description="KMS key info") dns_prefix: Optional[str] = Field(None, description="DNS prefix") lockbox_id: Optional[str] = Field(None, description="Lockbox OCID") - is_break_glass_enabled: Optional[bool] = Field( - None, description="Break glass access enabled" - ) + is_break_glass_enabled: Optional[bool] = Field(None, description="Break glass access enabled") refresh: Optional[Any] = Field(None, description="Refresh details") rules: Optional[List[Any]] = Field(None, description="Rules") - time_created: Optional[datetime] = Field( - None, description="Creation time (RFC3339)" - ) - time_updated: Optional[datetime] = Field( - None, description="Last update time (RFC3339)" - ) + time_created: Optional[datetime] = Field(None, description="Creation time (RFC3339)") + time_updated: Optional[datetime] = Field(None, description="Last update time (RFC3339)") freeform_tags: Optional[Dict[str, Any]] = Field(None, description="Freeform tags") - defined_tags: Optional[Dict[str, Dict[str, Any]]] = Field( - None, description="Defined tags" - ) + defined_tags: Optional[Dict[str, Dict[str, Any]]] = Field(None, description="Defined tags") class FusionEnvironmentStatus(BaseModel): """Pydantic model representing the status of a Fusion Environment.""" - fusion_environment_id: Optional[str] = Field( - None, description="OCID of the Fusion Environment" - ) + fusion_environment_id: Optional[str] = Field(None, description="OCID of the Fusion Environment") status: Optional[str] = Field(None, description="Status value") - time_updated: Optional[datetime] = Field( - None, description="Last status update time (RFC3339)" - ) - time_created: Optional[datetime] = Field( - None, description="Creation time if present (RFC3339)" - ) - details: Optional[Dict[str, Any]] = Field( - None, description="Additional status details" - ) + time_updated: Optional[datetime] = Field(None, description="Last status update time (RFC3339)") + time_created: Optional[datetime] = Field(None, description="Creation time if present (RFC3339)") + details: Optional[Dict[str, Any]] = Field(None, description="Additional status details") def _get(data: Any, key: str) -> Any: @@ -203,8 +167,7 @@ def map_fusion_environment_status(data: Any) -> FusionEnvironmentStatus: details = { k: v for k, v in coerced.items() - if k - not in {"fusion_environment_id", "id", "status", "time_updated", "time_created"} + if k not in {"fusion_environment_id", "id", "status", "time_updated", "time_created"} } # noqa: E501 return FusionEnvironmentStatus( fusion_environment_id=fe_id, 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 bf4c36a8..d1ffbaf0 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 @@ -49,23 +49,16 @@ def get_faaas_client(): return oci.fusion_apps.FusionApplicationsClient(config, signer=signer) -@mcp.tool( - description="Returns a list of Fusion Environment Families in the specified compartment." -) +@mcp.tool(description="Returns a list of Fusion Environment Families in the specified compartment.") def list_fusion_environment_families( - compartment_id: str = Field( - ..., description="The ID of the compartment in which to list resources." - ), - display_name: Optional[str] = Field( - None, description="Filter to match entire display name." - ), + compartment_id: str = Field(..., description="The ID of the compartment in which to list resources."), + display_name: Optional[str] = Field(None, description="Filter to match entire display name."), lifecycle_state: Optional[ Literal["CREATING", "UPDATING", "ACTIVE", "DELETING", "DELETED", "FAILED"] ] = Field( None, description=( - "Filter by lifecycle state. Allowed: CREATING, UPDATING, ACTIVE, " - "DELETING, DELETED, FAILED" + "Filter by lifecycle state. Allowed: CREATING, UPDATING, ACTIVE, DELETING, DELETED, FAILED" ), ), ) -> list[FusionEnvironmentFamily]: @@ -84,18 +77,12 @@ def list_fusion_environment_families( if lifecycle_state is not None: kwargs["lifecycle_state"] = lifecycle_state - response: oci.response.Response = client.list_fusion_environment_families( - **kwargs - ) + response: oci.response.Response = client.list_fusion_environment_families(**kwargs) # Normalize response data to an iterable without using helpers data_obj = response.data or [] items = getattr(data_obj, "items", None) - iterable = ( - items - if items is not None - else (data_obj if isinstance(data_obj, list) else [data_obj]) - ) + iterable = items if items is not None else (data_obj if isinstance(data_obj, list) else [data_obj]) for d in iterable: families.append(map_fusion_environment_family(d)) @@ -115,20 +102,15 @@ def list_fusion_environment_families( @mcp.tool( description=( - "Returns a list of Fusion Environments in the specified compartment " - "(optionally filtered by family)." + "Returns a list of Fusion Environments in the specified compartment (optionally filtered by family)." ) ) def list_fusion_environments( - compartment_id: str = Field( - ..., description="The ID of the compartment in which to list resources." - ), + compartment_id: str = Field(..., description="The ID of the compartment in which to list resources."), fusion_environment_family_id: Optional[str] = Field( None, description="Optional Fusion Environment Family OCID" ), - display_name: Optional[str] = Field( - None, description="Filter to match entire display name." - ), + display_name: Optional[str] = Field(None, description="Filter to match entire display name."), lifecycle_state: Optional[ Literal[ "CREATING", @@ -169,11 +151,7 @@ def list_fusion_environments( # Normalize response data to an iterable without using helpers data_obj = response.data or [] items = getattr(data_obj, "items", None) - iterable = ( - items - if items is not None - else (data_obj if isinstance(data_obj, list) else [data_obj]) - ) + iterable = items if items is not None else (data_obj if isinstance(data_obj, list) else [data_obj]) for d in iterable: environments.append(map_fusion_environment(d)) @@ -193,27 +171,19 @@ def list_fusion_environments( @mcp.tool(description="Gets a Fusion Environment by OCID.") def get_fusion_environment( - fusion_environment_id: str = Field( - ..., description="Unique FusionEnvironment identifier (OCID)" - ), + fusion_environment_id: str = Field(..., description="Unique FusionEnvironment identifier (OCID)"), ) -> FusionEnvironment: client = get_faaas_client() - response: oci.response.Response = client.get_fusion_environment( - fusion_environment_id - ) + response: oci.response.Response = client.get_fusion_environment(fusion_environment_id) return map_fusion_environment(response.data) @mcp.tool(description="Gets the status of a Fusion Environment by OCID.") def get_fusion_environment_status( - fusion_environment_id: str = Field( - ..., description="Unique FusionEnvironment identifier (OCID)" - ), + fusion_environment_id: str = Field(..., description="Unique FusionEnvironment identifier (OCID)"), ) -> FusionEnvironmentStatus: client = get_faaas_client() - response: oci.response.Response = client.get_fusion_environment_status( - fusion_environment_id - ) + response: oci.response.Response = client.get_fusion_environment_status(fusion_environment_id) return map_fusion_environment_status(response.data) diff --git a/src/oci-faaas-mcp-server/oracle/oci_faaas_mcp_server/tests/test_faaas_models.py b/src/oci-faaas-mcp-server/oracle/oci_faaas_mcp_server/tests/test_faaas_models.py index 37ffa10c..7372388c 100644 --- a/src/oci-faaas-mcp-server/oracle/oci_faaas_mcp_server/tests/test_faaas_models.py +++ b/src/oci-faaas-mcp-server/oracle/oci_faaas_mcp_server/tests/test_faaas_models.py @@ -78,9 +78,7 @@ def test_map_fusion_environment_family_from_dict_and_obj(self): assert fam_o.id == obj.id and fam_o.display_name == obj.display_name assert fam_o.lifecycle_state == obj.lifecycle_state - def test_map_fusion_environment_includes_maintenance_policy_to_dict( - self, monkeypatch - ): + def test_map_fusion_environment_includes_maintenance_policy_to_dict(self, monkeypatch): # Ensure maintenance_policy goes through _oci_to_dict via oci.util.to_dict def to_dict_override(x): # Simulate SDK model conversion diff --git a/src/oci-faaas-mcp-server/oracle/oci_faaas_mcp_server/tests/test_faaas_tools.py b/src/oci-faaas-mcp-server/oracle/oci_faaas_mcp_server/tests/test_faaas_tools.py index 24067f24..b9658542 100644 --- a/src/oci-faaas-mcp-server/oracle/oci_faaas_mcp_server/tests/test_faaas_tools.py +++ b/src/oci-faaas-mcp-server/oracle/oci_faaas_mcp_server/tests/test_faaas_tools.py @@ -29,9 +29,7 @@ oci.auth = types.SimpleNamespace( signers=types.SimpleNamespace(SecurityTokenSigner=lambda token, key: object()) ) - oci.fusion_apps = types.SimpleNamespace( - FusionApplicationsClient=lambda config, signer=None: object() - ) + oci.fusion_apps = types.SimpleNamespace(FusionApplicationsClient=lambda config, signer=None: object()) class TestFaaasTools: @@ -145,9 +143,7 @@ async def test_get_fusion_environment_status(self, mock_get_client): @pytest.mark.asyncio @patch("oracle.oci_faaas_mcp_server.server.get_faaas_client") - async def test_list_fusion_environment_families_pagination_header_fallback( - self, mock_get_client - ): + async def test_list_fusion_environment_families_pagination_header_fallback(self, mock_get_client): mock_client = MagicMock() mock_get_client.return_value = mock_client @@ -179,9 +175,7 @@ async def test_list_fusion_environment_families_pagination_header_fallback( @pytest.mark.asyncio @patch("oracle.oci_faaas_mcp_server.server.get_faaas_client") - async def test_list_fusion_environments_pagination_and_filters( - self, mock_get_client - ): + async def test_list_fusion_environments_pagination_and_filters(self, mock_get_client): mock_client = MagicMock() mock_get_client.return_value = mock_client @@ -214,9 +208,7 @@ async def test_list_fusion_environments_pagination_and_filters( # First call includes filters (no 'page'), second call includes page first_kwargs = mock_client.list_fusion_environments.call_args_list[0].kwargs - second_kwargs = mock_client.list_fusion_environments.call_args_list[ - 1 - ].kwargs + second_kwargs = mock_client.list_fusion_environments.call_args_list[1].kwargs assert first_kwargs["compartment_id"] == "comp" assert first_kwargs["fusion_environment_family_id"] == "fam" assert first_kwargs["display_name"] == "E" @@ -228,23 +220,18 @@ async def test_list_fusion_environments_pagination_and_filters( @pytest.mark.asyncio async def test_main_runs_mcp_run(self): with patch("oracle.oci_faaas_mcp_server.server.mcp.run") as mock_run: - main() mock_run.assert_called_once() @pytest.mark.asyncio @patch("oracle.oci_faaas_mcp_server.server.get_faaas_client") - async def test_list_fusion_environment_families_handles_items_attr_and_filters( - self, mock_get_client - ): + async def test_list_fusion_environment_families_handles_items_attr_and_filters(self, mock_get_client): mock_client = MagicMock() mock_get_client.return_value = mock_client # Response has a 'data' object with an 'items' attribute resp = create_autospec(oci.response.Response) - resp.data = types.SimpleNamespace( - items=[{"id": "famX", "display_name": "Family X"}] - ) + resp.data = types.SimpleNamespace(items=[{"id": "famX", "display_name": "Family X"}]) resp.next_page = None resp.headers = {} @@ -270,9 +257,7 @@ async def test_list_fusion_environment_families_handles_items_attr_and_filters( @pytest.mark.asyncio @patch("oracle.oci_faaas_mcp_server.server.get_faaas_client") - async def test_list_fusion_environment_families_headers_exception_fallback( - self, mock_get_client - ): + async def test_list_fusion_environment_families_headers_exception_fallback(self, mock_get_client): mock_client = MagicMock() mock_get_client.return_value = mock_client @@ -320,9 +305,7 @@ async def test_list_fusion_environments_data_single_object(self, mock_get_client class TestGetClient: - @patch( - "oracle.oci_faaas_mcp_server.server.oci.fusion_apps.FusionApplicationsClient" - ) + @patch("oracle.oci_faaas_mcp_server.server.oci.fusion_apps.FusionApplicationsClient") @patch("oracle.oci_faaas_mcp_server.server.oci.auth.signers.SecurityTokenSigner") @patch("oracle.oci_faaas_mcp_server.server.oci.signer.load_private_key_from_file") @patch( @@ -362,21 +345,19 @@ def test_get_faaas_client_with_profile_env( profile_name="MYPROFILE", ) mock_open_file.assert_called_once_with("/abs/path/to/token", "r") - mock_security_token_signer.assert_called_once_with( - "SECURITY_TOKEN", private_key_obj - ) + mock_security_token_signer.assert_called_once_with("SECURITY_TOKEN", private_key_obj) # Ensure user agent was set on the same config dict passed into client args, _ = mock_client.call_args passed_config = args[0] assert passed_config is config - expected_user_agent = f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + expected_user_agent = ( + f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + ) assert passed_config.get("additional_user_agent") == expected_user_agent # And we returned the client instance assert result == mock_client.return_value - @patch( - "oracle.oci_faaas_mcp_server.server.oci.fusion_apps.FusionApplicationsClient" - ) + @patch("oracle.oci_faaas_mcp_server.server.oci.fusion_apps.FusionApplicationsClient") @patch("oracle.oci_faaas_mcp_server.server.oci.auth.signers.SecurityTokenSigner") @patch("oracle.oci_faaas_mcp_server.server.oci.signer.load_private_key_from_file") @patch( @@ -420,9 +401,6 @@ def test_get_faaas_client_uses_default_profile_when_env_missing( cc_args, _ = mock_client.call_args assert cc_args[0] is config assert "additional_user_agent" in config - assert ( - isinstance(config["additional_user_agent"], str) - and "/" in config["additional_user_agent"] - ) + assert isinstance(config["additional_user_agent"], str) and "/" in config["additional_user_agent"] # Returned object is client instance assert srv_client is mock_client.return_value diff --git a/src/oci-identity-mcp-server/oracle/oci_identity_mcp_server/models.py b/src/oci-identity-mcp-server/oracle/oci_identity_mcp_server/models.py index 6456319e..c99d89ab 100644 --- a/src/oci-identity-mcp-server/oracle/oci_identity_mcp_server/models.py +++ b/src/oci-identity-mcp-server/oracle/oci_identity_mcp_server/models.py @@ -59,9 +59,9 @@ class Compartment(BaseModel): description="Date and time the compartment was created, in the format " "defined by RFC3339. Example: `2016-08-25T21:10:29.600Z`", ) - lifecycle_state: Optional[ - Literal["CREATING", "ACTIVE", "INACTIVE", "DELETING", "DELETED", "FAILED"] - ] = Field(None, description="The compartment's current state.") + lifecycle_state: Optional[Literal["CREATING", "ACTIVE", "INACTIVE", "DELETING", "DELETED", "FAILED"]] = ( + Field(None, description="The compartment's current state.") + ) inactive_status: Optional[int] = Field( None, description="The detailed status of INACTIVE lifecycleState." ) @@ -111,12 +111,8 @@ class Tenancy(BaseModel): id: Optional[str] = Field(None, description="The OCID of the tenancy.") name: Optional[str] = Field(None, description="The name of the tenancy.") - description: Optional[str] = Field( - None, description="The description of the tenancy." - ) - home_region_key: Optional[str] = Field( - None, description="The region key for the tenancy's home region." - ) + description: Optional[str] = Field(None, description="The description of the tenancy.") + home_region_key: Optional[str] = Field(None, description="The region key for the tenancy's home region.") freeform_tags: Optional[Dict[str, str]] = Field( None, description='Free-form tags for this resource. Each tag is a simple key-value pair with "' @@ -159,9 +155,7 @@ class AvailabilityDomain(BaseModel): """ id: Optional[str] = Field(None, description="The OCID of the Availability Domain.") - name: Optional[str] = Field( - None, description="The name of the Availability Domain." - ) + name: Optional[str] = Field(None, description="The name of the Availability Domain.") compartment_id: Optional[str] = Field( None, description="The OCID of the tenancy containing the Availability Domain." ) @@ -194,9 +188,7 @@ class AuthToken(BaseModel): id: Optional[str] = Field(None, description="The OCID of the auth token.") token: Optional[str] = Field(None, description="The raw auth token string.") - user_id: Optional[str] = Field( - None, description="The OCID of the user the password belongs to." - ) + user_id: Optional[str] = Field(None, description="The OCID of the user the password belongs to.") description: Optional[str] = Field( None, description="The description you assign to the auth token. " @@ -212,9 +204,9 @@ class AuthToken(BaseModel): description="Date and time when this auth token will expire, in the format " "defined by RFC3339. Example: `2016-08-25T21:10:29.600Z`", ) - lifecycle_state: Optional[ - Literal["CREATING", "ACTIVE", "INACTIVE", "DELETING", "DELETED"] - ] = Field(None, description="The auth token's current state.") + lifecycle_state: Optional[Literal["CREATING", "ACTIVE", "INACTIVE", "DELETING", "DELETED"]] = Field( + None, description="The auth token's current state." + ) inactive_status: Optional[int] = Field( None, description="The detailed status of INACTIVE lifecycleState." ) @@ -264,9 +256,7 @@ class User(BaseModel): """ id: Optional[str] = Field(None, description="The OCID of the user.") - compartment_id: Optional[str] = Field( - None, description="The OCID of the tenancy containing the user." - ) + compartment_id: Optional[str] = Field(None, description="The OCID of the tenancy containing the user.") name: Optional[str] = Field( None, description="The name you assign to the user during creation. " @@ -279,9 +269,7 @@ class User(BaseModel): "Does not have to be unique, and it's changeable.", ) email: Optional[str] = Field(None, description="The email address of the user.") - email_verified: Optional[bool] = Field( - None, description="Whether the email address has been validated." - ) + email_verified: Optional[bool] = Field(None, description="Whether the email address has been validated.") db_user_name: Optional[str] = Field( None, description="DB username of the DB credential. Has to be unique across the tenancy.", @@ -297,9 +285,9 @@ class User(BaseModel): description="Date and time the user was created, in the format defined by RFC3339. " "Example: `2016-08-25T21:10:29.600Z`", ) - lifecycle_state: Optional[ - Literal["CREATING", "ACTIVE", "INACTIVE", "DELETING", "DELETED"] - ] = Field(None, description="The user's current state.") + lifecycle_state: Optional[Literal["CREATING", "ACTIVE", "INACTIVE", "DELETING", "DELETED"]] = Field( + None, description="The user's current state." + ) inactive_status: Optional[int] = Field( None, description="Returned only if the user's `lifecycleState` is INACTIVE. " @@ -360,15 +348,9 @@ class RegionSubscription(BaseModel): Pydantic model mirroring the fields of oci.identity.models.RegionSubscription. """ - region_key: Optional[str] = Field( - None, description="The region key. Example: `PHX`" - ) - region_name: Optional[str] = Field( - None, description="The region name. Example: `us-phoenix-1`" - ) - status: Optional[str] = Field( - None, description="The status of the region subscription." - ) + region_key: Optional[str] = Field(None, description="The region key. Example: `PHX`") + region_name: Optional[str] = Field(None, description="The region name. Example: `us-phoenix-1`") + status: Optional[str] = Field(None, description="The status of the region subscription.") is_home_region: Optional[bool] = Field( None, description="Indicates if the region is the home region for the tenancy." ) 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 d93062cc..9a229ff2 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 @@ -110,9 +110,7 @@ def list_compartments( if include_root: config = oci.config.from_file( file_location=os.getenv("OCI_CONFIG_FILE", oci.config.DEFAULT_LOCATION), - profile_name=os.getenv( - "OCI_CONFIG_PROFILE", oci.config.DEFAULT_PROFILE - ), + profile_name=os.getenv("OCI_CONFIG_PROFILE", oci.config.DEFAULT_PROFILE), ) tenancy_id = os.getenv("TENANCY_ID_OVERRIDE", config["tenancy"]) tenancy_response: oci.response.Response = client.get_compartment( @@ -129,9 +127,7 @@ def list_compartments( @mcp.tool(description="Get tenancy with a given OCID") -def get_tenancy( - tenancy_id: str = Field(..., description="The OCID of the tenancy") -) -> Tenancy: +def get_tenancy(tenancy_id: str = Field(..., description="The OCID of the tenancy")) -> Tenancy: try: client = get_identity_client() @@ -194,16 +190,12 @@ def get_current_tenancy() -> Tenancy: @mcp.tool def create_auth_token( user_id: str = Field(..., description="The OCID of the user"), - description: Optional[str] = Field( - "", description="The description of the auth token" - ), + description: Optional[str] = Field("", description="The description of the auth token"), ) -> AuthToken: try: client = get_identity_client() - create_auth_token_details = oci.identity.models.CreateAuthTokenDetails( - description=description - ) + create_auth_token_details = oci.identity.models.CreateAuthTokenDetails(description=description) response: oci.response.Response = client.create_auth_token( user_id=user_id, @@ -242,9 +234,7 @@ def get_current_user() -> User: try: payload_b64 = token.split(".", 2)[1] padding = "=" * (-len(payload_b64) % 4) - payload_json = base64.urlsafe_b64decode( - payload_b64 + padding - ).decode("utf-8") + payload_json = base64.urlsafe_b64decode(payload_b64 + padding).decode("utf-8") payload = json.loads(payload_json) # 'sub' typically contains the user OCID for session tokens; # fallback to opc-user-id if present @@ -253,9 +243,7 @@ def get_current_user() -> User: user_id = None if not user_id: - raise KeyError( - "Unable to determine current user OCID from config or security token" - ) + raise KeyError("Unable to determine current user OCID from config or security token") response: oci.response.Response = client.get_user(user_id) data: oci.identity.models.User = response.data @@ -267,9 +255,7 @@ def get_current_user() -> User: raise e -@mcp.tool( - description="Get a specific compartment by its name within a parent compartment." -) +@mcp.tool(description="Get a specific compartment by its name within a parent compartment.") def get_compartment_by_name( name: str = Field(description="The name of the compartment to find."), parent_compartment_id: str = Field( @@ -302,9 +288,7 @@ def get_compartment_by_name( has_next_page = response.has_next_page next_page = response.next_page if hasattr(response, "next_page") else None - logger.warning( - f"Compartment '{name}' not found in parent '{parent_compartment_id}'" - ) + logger.warning(f"Compartment '{name}' not found in parent '{parent_compartment_id}'") return None except Exception as e: @@ -314,7 +298,7 @@ def get_compartment_by_name( @mcp.tool(description="List the regions a tenancy is subscribed to.") def list_subscribed_regions( - tenancy_id: str = Field(..., description="The OCID of the tenancy.") + tenancy_id: str = Field(..., description="The OCID of the tenancy."), ) -> list[RegionSubscription]: regions: list[RegionSubscription] = [] diff --git a/src/oci-identity-mcp-server/oracle/oci_identity_mcp_server/tests/test_identity_tools.py b/src/oci-identity-mcp-server/oracle/oci_identity_mcp_server/tests/test_identity_tools.py index f9d0d14d..429a4442 100644 --- a/src/oci-identity-mcp-server/oracle/oci_identity_mcp_server/tests/test_identity_tools.py +++ b/src/oci-identity-mcp-server/oracle/oci_identity_mcp_server/tests/test_identity_tools.py @@ -106,9 +106,7 @@ async def test_list_compartments_without_root(self, mock_get_client): @pytest.mark.asyncio @patch("oracle.oci_identity_mcp_server.server.oci.config.from_file") @patch("oracle.oci_identity_mcp_server.server.get_identity_client") - async def test_list_compartments_pagination_without_limit( - self, mock_get_client, mock_config_from_file - ): + async def test_list_compartments_pagination_without_limit(self, mock_get_client, mock_config_from_file): mock_config_from_file.return_value = {"tenancy": "test_tenancy"} mock_client = MagicMock() mock_get_client.return_value = mock_client @@ -185,9 +183,7 @@ async def test_list_compartments_pagination_without_limit( @pytest.mark.asyncio @patch("oracle.oci_identity_mcp_server.server.oci.config.from_file") @patch("oracle.oci_identity_mcp_server.server.get_identity_client") - async def test_list_compartments_limit_stops_pagination( - self, mock_get_client, mock_config_from_file - ): + async def test_list_compartments_limit_stops_pagination(self, mock_get_client, mock_config_from_file): mock_config_from_file.return_value = {"tenancy": "test_tenancy"} mock_client = MagicMock() mock_get_client.return_value = mock_client @@ -383,17 +379,13 @@ async def test_create_auth_token(self, mock_get_client): @pytest.mark.asyncio @patch("oracle.oci_identity_mcp_server.server.get_identity_client") @patch("oracle.oci_identity_mcp_server.server.oci.config.from_file") - async def test_get_current_user_from_config_user( - self, mock_config_from_file, mock_get_client - ): + async def test_get_current_user_from_config_user(self, mock_config_from_file, mock_get_client): mock_config_from_file.return_value = {"user": "test_user"} mock_client = MagicMock() mock_get_client.return_value = mock_client mock_get_response = create_autospec(oci.response.Response) - mock_get_response.data = oci.identity.models.User( - id="user1", name="User 1", description="Test user" - ) + mock_get_response.data = oci.identity.models.User(id="user1", name="User 1", description="Test user") mock_client.get_user.return_value = mock_get_response async with Client(mcp) as client: @@ -406,16 +398,8 @@ async def test_get_current_user_from_config_user( assert result["id"] == "user1" def _make_jwt(self, payload_dict: dict) -> str: - header_b64 = ( - base64.urlsafe_b64encode(json.dumps({"alg": "none"}).encode()) - .decode() - .rstrip("=") - ) - payload_b64 = ( - base64.urlsafe_b64encode(json.dumps(payload_dict).encode()) - .decode() - .rstrip("=") - ) + header_b64 = base64.urlsafe_b64encode(json.dumps({"alg": "none"}).encode()).decode().rstrip("=") + payload_b64 = base64.urlsafe_b64encode(json.dumps(payload_dict).encode()).decode().rstrip("=") return f"{header_b64}.{payload_b64}.signature" @pytest.mark.asyncio @@ -442,9 +426,7 @@ async def test_get_current_user_fallback_from_token_sub( with patch("builtins.open", m): async with Client(mcp) as client: - result = ( - await client.call_tool("get_current_user", {}) - ).structured_content + result = (await client.call_tool("get_current_user", {})).structured_content assert result["id"] == "user-sub" # Ensure client.get_user called with derived OCID mock_client.get_user.assert_called_once_with("ocid1.user.oc1..sub") @@ -472,9 +454,7 @@ async def test_get_current_user_fallback_from_token_opc_user_id( with patch("builtins.open", m): async with Client(mcp) as client: - result = ( - await client.call_tool("get_current_user", {}) - ).structured_content + result = (await client.call_tool("get_current_user", {})).structured_content assert result["id"] == "user-opc" mock_client.get_user.assert_called_once_with("ocid1.user.oc1..opc") @@ -504,9 +484,7 @@ async def test_get_compartment_by_name(self, mock_get_client): mock_get_client.return_value = mock_client mock_response_p1 = create_autospec(oci.response.Response) - mock_response_p1.data = [ - oci.identity.models.Compartment(name="WrongName", id="wrong_id") - ] + mock_response_p1.data = [oci.identity.models.Compartment(name="WrongName", id="wrong_id")] mock_response_p1.has_next_page = True mock_response_p1.next_page = "page_2_token" @@ -677,13 +655,9 @@ async def test_get_current_tenancy_with_env_override(mock_from_file, mock_get_cl ) mock_client.get_tenancy.return_value = mock_get_response - with patch.dict( - os.environ, {"TENANCY_ID_OVERRIDE": "ocid1.tenancy.oc1..override"}, clear=False - ): + with patch.dict(os.environ, {"TENANCY_ID_OVERRIDE": "ocid1.tenancy.oc1..override"}, clear=False): async with Client(mcp) as client: - result = ( - await client.call_tool("get_current_tenancy", {}) - ).structured_content + result = (await client.call_tool("get_current_tenancy", {})).structured_content assert result["id"] == "tenancy1" mock_client.get_tenancy.assert_called_once_with("ocid1.tenancy.oc1..override") @@ -710,18 +684,14 @@ async def test_list_availability_domains_exception_propagates(mock_get_client): async with Client(mcp) as client: with pytest.raises(ToolError): - await client.call_tool( - "list_availability_domains", {"compartment_id": "ocid1.tenancy"} - ) + await client.call_tool("list_availability_domains", {"compartment_id": "ocid1.tenancy"}) @pytest.mark.asyncio @patch("oracle.oci_identity_mcp_server.server.get_identity_client") @patch("oracle.oci_identity_mcp_server.server.os.path.exists") @patch("oracle.oci_identity_mcp_server.server.oci.config.from_file") -async def test_get_current_user_invalid_token_decode_raises( - mock_from_file, mock_exists, mock_get_client -): +async def test_get_current_user_invalid_token_decode_raises(mock_from_file, mock_exists, mock_get_client): mock_from_file.return_value = {"security_token_file": "/tmp/token.txt"} mock_exists.return_value = True invalid_token = "a.b.c" # invalid base64 payload to trigger decode error @@ -736,9 +706,7 @@ async def test_get_current_user_invalid_token_decode_raises( class TestGetClient: @patch("oracle.oci_identity_mcp_server.server.oci.identity.IdentityClient") @patch("oracle.oci_identity_mcp_server.server.oci.auth.signers.SecurityTokenSigner") - @patch( - "oracle.oci_identity_mcp_server.server.oci.signer.load_private_key_from_file" - ) + @patch("oracle.oci_identity_mcp_server.server.oci.signer.load_private_key_from_file") @patch( "oracle.oci_identity_mcp_server.server.open", new_callable=mock_open, @@ -776,23 +744,21 @@ def test_get_identity_client_with_profile_env( profile_name="MYPROFILE", ) mock_open_file.assert_called_once_with("/abs/path/to/token", "r") - mock_security_token_signer.assert_called_once_with( - "SECURITY_TOKEN", private_key_obj - ) + mock_security_token_signer.assert_called_once_with("SECURITY_TOKEN", private_key_obj) # Ensure user agent was set on the same config dict passed into client args, _ = mock_client.call_args passed_config = args[0] assert passed_config is config - expected_user_agent = f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + expected_user_agent = ( + f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + ) assert passed_config.get("additional_user_agent") == expected_user_agent # And we returned the client instance assert result == mock_client.return_value @patch("oracle.oci_identity_mcp_server.server.oci.identity.IdentityClient") @patch("oracle.oci_identity_mcp_server.server.oci.auth.signers.SecurityTokenSigner") - @patch( - "oracle.oci_identity_mcp_server.server.oci.signer.load_private_key_from_file" - ) + @patch("oracle.oci_identity_mcp_server.server.oci.signer.load_private_key_from_file") @patch( "oracle.oci_identity_mcp_server.server.open", new_callable=mock_open, @@ -834,9 +800,6 @@ def test_get_identity_client_uses_default_profile_when_env_missing( cc_args, _ = mock_client.call_args assert cc_args[0] is config assert "additional_user_agent" in config - assert ( - isinstance(config["additional_user_agent"], str) - and "/" in config["additional_user_agent"] - ) + assert isinstance(config["additional_user_agent"], str) and "/" in config["additional_user_agent"] # Returned object is client instance assert srv_client is mock_client.return_value diff --git a/src/oci-logging-mcp-server/oracle/oci_logging_mcp_server/models.py b/src/oci-logging-mcp-server/oracle/oci_logging_mcp_server/models.py index 8429fad1..f279d6e2 100644 --- a/src/oci-logging-mcp-server/oracle/oci_logging_mcp_server/models.py +++ b/src/oci-logging-mcp-server/oracle/oci_logging_mcp_server/models.py @@ -48,14 +48,10 @@ class LogGroupSummary(BaseModel): "and it's changeable. Avoid entering confidential information." ), ) - description: Optional[str] = Field( - None, description="Description for this resource." - ) + description: Optional[str] = Field(None, description="Description for this resource.") defined_tags: Optional[Dict[str, Dict[str, Any]]] = Field( None, - description=( - "Defined tags for this resource. Each key is predefined and scoped to a namespace." - ), + description=("Defined tags for this resource. Each key is predefined and scoped to a namespace."), ) freeform_tags: Optional[Dict[str, str]] = Field( None, @@ -64,9 +60,7 @@ class LogGroupSummary(BaseModel): "type, or namespace." ), ) - time_created: Optional[datetime] = Field( - None, description="Time the resource was created (RFC3339)." - ) + time_created: Optional[datetime] = Field(None, description="Time the resource was created (RFC3339).") time_last_modified: Optional[datetime] = Field( None, description="Time the resource was last modified (RFC3339)." ) @@ -131,9 +125,7 @@ class LogGroup(BaseModel): "and it's changeable. Avoid entering confidential information." ), ) - description: Optional[str] = Field( - None, description="Description for this resource." - ) + description: Optional[str] = Field(None, description="Description for this resource.") lifecycle_state: Optional[ Literal[ "CREATING", @@ -147,9 +139,7 @@ class LogGroup(BaseModel): ] = Field(None, description="The log group object state.") defined_tags: Optional[Dict[str, Dict[str, Any]]] = Field( None, - description=( - "Defined tags for this resource. Each key is predefined and scoped to a namespace." - ), + description=("Defined tags for this resource. Each key is predefined and scoped to a namespace."), ) freeform_tags: Optional[Dict[str, str]] = Field( None, @@ -158,9 +148,7 @@ class LogGroup(BaseModel): "type, or namespace." ), ) - time_created: Optional[datetime] = Field( - None, description="Time the resource was created (RFC3339)." - ) + time_created: Optional[datetime] = Field(None, description="Time the resource was created (RFC3339).") time_last_modified: Optional[datetime] = Field( None, description="Time the resource was last modified (RFC3339)." ) @@ -221,13 +209,9 @@ class OciService(Source): """ service: Optional[str] = Field(None, description="Service generating the log.") - resource: Optional[str] = Field( - None, description="Unique identifier of the resource emitting the log." - ) + resource: Optional[str] = Field(None, description="Unique identifier of the resource emitting the log.") category: Optional[str] = Field(None, description="Log object category.") - parameters: Optional[Dict[str, str]] = Field( - None, description="Log category parameters." - ) + parameters: Optional[Dict[str, str]] = Field(None, description="Log category parameters.") class Configuration(BaseModel): @@ -239,12 +223,8 @@ class Configuration(BaseModel): compartment_id: Optional[str] = Field( None, description="The OCID of the compartment that the resource belongs to." ) - source: Optional[Source] = Field( - None, description="The source the log object comes from." - ) - archiving: Optional[Archiving] = Field( - None, description="Log archiving configuration." - ) + source: Optional[Source] = Field(None, description="The source the log object comes from.") + archiving: Optional[Archiving] = Field(None, description="Log archiving configuration.") class LogSummary(BaseModel): @@ -262,9 +242,7 @@ class LogSummary(BaseModel): "and it's changeable. Avoid entering confidential information." ), ) - is_enabled: Optional[bool] = Field( - None, description="Whether or not this resource is currently enabled." - ) + is_enabled: Optional[bool] = Field(None, description="Whether or not this resource is currently enabled.") lifecycle_state: Optional[ Literal[ "CREATING", @@ -280,9 +258,7 @@ class LogSummary(BaseModel): None, description="The logType that the log object is for, whether custom or service.", ) - configuration: Optional[Configuration] = Field( - None, description="Log object configuration." - ) + configuration: Optional[Configuration] = Field(None, description="Log object configuration.") defined_tags: Optional[Dict[str, Dict[str, Any]]] = Field( None, description="Defined tags for this resource. Each key is predefined and scoped to a namespace.", @@ -291,9 +267,7 @@ class LogSummary(BaseModel): None, description="Free-form tags for this resource as simple key/value pairs without predefined names.", ) - time_created: Optional[datetime] = Field( - None, description="Time the resource was created (RFC3339)." - ) + time_created: Optional[datetime] = Field(None, description="Time the resource was created (RFC3339).") time_last_modified: Optional[datetime] = Field( None, description="Time the resource was last modified (RFC3339)." ) @@ -391,9 +365,7 @@ class Log(BaseModel): None, description="The logType that the log object is for, whether custom or service.", ) - is_enabled: Optional[bool] = Field( - None, description="Whether or not this resource is currently enabled." - ) + is_enabled: Optional[bool] = Field(None, description="Whether or not this resource is currently enabled.") defined_tags: Optional[Dict[str, Dict[str, Any]]] = Field( None, description="Defined tags for this resource. Each key is predefined and scoped to a namespace.", @@ -402,9 +374,7 @@ class Log(BaseModel): None, description="Free-form tags for this resource as simple key/value pairs without predefined names.", ) - configuration: Optional[Configuration] = Field( - None, description="Log object configuration." - ) + configuration: Optional[Configuration] = Field(None, description="Log object configuration.") lifecycle_state: Optional[ Literal[ "CREATING", @@ -416,9 +386,7 @@ class Log(BaseModel): "UNKNOWN_ENUM_VALUE", ] ] = Field(None, description="The pipeline state.") - time_created: Optional[datetime] = Field( - None, description="Time the resource was created (RFC3339)." - ) + time_created: Optional[datetime] = Field(None, description="Time the resource was created (RFC3339).") time_last_modified: Optional[datetime] = Field( None, description="Time the resource was last modified (RFC3339)." ) @@ -487,26 +455,16 @@ class SearchResult(BaseModel): class SearchResultSummary(BaseModel): """Summary of results.""" - result_count: Optional[int] = Field( - None, description="Total number of search results." - ) - field_count: Optional[int] = Field( - None, description="Total number of field schema information." - ) + result_count: Optional[int] = Field(None, description="Total number of search results.") + field_count: Optional[int] = Field(None, description="Total number of field schema information.") class SearchResponse(BaseModel): """Search response object.""" - results: Optional[List[SearchResult]] = Field( - None, description="List of search results" - ) - fields: Optional[List[FieldInfo]] = Field( - None, description="List of log field schema information." - ) - summary: Optional[SearchResultSummary] = Field( - None, description="Summary of results." - ) + results: Optional[List[SearchResult]] = Field(None, description="List of search results") + fields: Optional[List[FieldInfo]] = Field(None, description="List of log field schema information.") + summary: Optional[SearchResultSummary] = Field(None, description="Summary of results.") def map_field_info(fi) -> FieldInfo | None: 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 b8222956..06d414ec 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 @@ -118,16 +118,12 @@ def list_log_groups( "Only use this tool if the user specifically mentions Log Groups" ) def get_log_group( - log_group_id: str = Field( - ..., description="The OCID of the log group that the log belongs to." - ), + log_group_id: str = Field(..., description="The OCID of the log group that the log belongs to."), ) -> LogGroup: try: client = get_logging_client() - response: oci.response.Response = client.get_log_group( - log_group_id=log_group_id - ) + response: oci.response.Response = client.get_log_group(log_group_id=log_group_id) data: oci.logging.models.Log = response.data logger.info("Found Log Group") return map_log_group(data) @@ -142,9 +138,7 @@ def get_log_group( "Only use this tool if the user explicitly supplies a Log Group OCID" ) def list_logs( - log_group_id: str = Field( - ..., description="The OCID of the log group to list logs from." - ), + log_group_id: str = Field(..., description="The OCID of the log group to list logs from."), limit: Optional[int] = Field( None, description="The maximum amount of resources to return. If None, there is no limit.", @@ -189,16 +183,12 @@ def list_logs( ) def get_log( log_id: str = Field(..., description="The OCID of the log"), - log_group_id: str = Field( - ..., description="The OCID of the log group that the log belongs to." - ), + log_group_id: str = Field(..., description="The OCID of the log group that the log belongs to."), ) -> Log: try: client = get_logging_client() - response: oci.response.Response = client.get_log( - log_group_id=log_group_id, log_id=log_id - ) + response: oci.response.Response = client.get_log(log_group_id=log_group_id, log_id=log_id) data: oci.logging.models.Log = response.data logger.info("Found Log") return map_log(data) @@ -225,9 +215,7 @@ def search_log_query_syntax_guide() -> str: ) def get_paginated_event_types( page: int = Field(1, description="The page number to retrieve", ge=1), - page_size: int = Field( - 50, description="Number of event types per page", ge=1, le=200 - ), + page_size: int = Field(50, description="Number of event types per page", ge=1, le=200), ) -> str: content = get_script_content(SEARCH_LOG_EVENT_TYPES_SCRIPT) lines = content.split("\n") @@ -238,17 +226,13 @@ def get_paginated_event_types( table_start = i + 2 # Skip header and separator break - intro = "\n".join( - lines[: table_start - 2] - ) # Content before table header and separator + intro = "\n".join(lines[: table_start - 2]) # Content before table header and separator if table_start == 0: return "Table not found in the guide." # Extract table rows rows = [ - line - for line in lines[table_start:] - if line.strip().startswith("|") and not line.startswith("|---") + line for line in lines[table_start:] if line.strip().startswith("|") and not line.startswith("|---") ] total_rows = len(rows) @@ -302,9 +286,7 @@ def search_logs( ge=1, le=50, ), - page: Optional[str] = Field( - None, description="The next page token for the search_logs API call. " - ), + page: Optional[str] = Field(None, description="The next page token for the search_logs API call. "), ) -> SearchResponse: try: client = get_logging_search_client() diff --git a/src/oci-logging-mcp-server/oracle/oci_logging_mcp_server/tests/test_logging_models.py b/src/oci-logging-mcp-server/oracle/oci_logging_mcp_server/tests/test_logging_models.py index 91ac7efa..ae6a4cee 100644 --- a/src/oci-logging-mcp-server/oracle/oci_logging_mcp_server/tests/test_logging_models.py +++ b/src/oci-logging-mcp-server/oracle/oci_logging_mcp_server/tests/test_logging_models.py @@ -168,10 +168,7 @@ def test_map_log_summary_nested(self): assert isinstance(mapped, LogSummary) assert mapped.id == log.id assert mapped.configuration is not None - assert ( - mapped.configuration.archiving.is_enabled - == log.configuration.archiving.is_enabled - ) + assert mapped.configuration.archiving.is_enabled == log.configuration.archiving.is_enabled def test_map_log_nested(self): log = SimpleNamespace( @@ -197,10 +194,7 @@ def test_map_log_nested(self): mapped = map_log(log) assert isinstance(mapped, Log) assert mapped.log_type == log.log_type - assert ( - mapped.configuration.source.source_type - == log.configuration.source.source_type - ) + assert mapped.configuration.source.source_type == log.configuration.source.source_type def test_map_search_response_full(self): # FieldInfo mapping @@ -245,9 +239,7 @@ def fake_to_dict(obj): # If oci.util is already present, patch its to_dict. Otherwise, inject a minimal module. if "oci.util" in sys.modules: - monkeypatch.setattr( - sys.modules["oci.util"], "to_dict", fake_to_dict, raising=False - ) + monkeypatch.setattr(sys.modules["oci.util"], "to_dict", fake_to_dict, raising=False) else: util_mod = types.ModuleType("oci.util") util_mod.to_dict = fake_to_dict 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 5ae063e9..0fd73f46 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 @@ -34,9 +34,7 @@ async def test_list_log_groups(self, mock_get_client): mock_client.list_log_groups.return_value = mock_summarize_response async with Client(mcp) as client: - call_tool_result = await client.call_tool( - "list_log_groups", {"compartment_id": "compartment1"} - ) + call_tool_result = await client.call_tool("list_log_groups", {"compartment_id": "compartment1"}) result = call_tool_result.structured_content["result"] assert len(result) == 1 @@ -136,9 +134,7 @@ async def test_get_log_group(self, mock_get_client): mock_client.get_log_group.return_value = mock_get_response async with Client(mcp) as client: - call_tool_result = await client.call_tool( - "get_log_group", {"log_group_id": "logGroup1"} - ) + call_tool_result = await client.call_tool("get_log_group", {"log_group_id": "logGroup1"}) result = call_tool_result.structured_content assert result["id"] == "logGroup1" @@ -165,9 +161,7 @@ async def test_list_logs(self, mock_get_client): mock_client.list_logs.return_value = mock_summarize_response async with Client(mcp) as client: - call_tool_result = await client.call_tool( - "list_logs", {"log_group_id": "logGroup1"} - ) + call_tool_result = await client.call_tool("list_logs", {"log_group_id": "logGroup1"}) result = call_tool_result.structured_content["result"] assert result[0]["id"] == "logid1" @@ -327,9 +321,7 @@ async def test_search_logs(self, mock_get_client): mock_get_response = create_autospec(oci.response.Response) mock_get_response.data = oci.loggingsearch.models.SearchResponse( - results=[ - oci.loggingsearch.models.SearchResult(data={"event": "testEvent"}) - ], + results=[oci.loggingsearch.models.SearchResult(data={"event": "testEvent"})], fields=[], summary=[], ) @@ -351,9 +343,7 @@ async def test_search_logs(self, mock_get_client): @pytest.mark.asyncio @patch("oracle.oci_logging_mcp_server.server.map_search_response") @patch("oracle.oci_logging_mcp_server.server.get_logging_search_client") - async def test_search_logs_oversize_raises_toolerror( - self, mock_get_client, mock_map - ): + async def test_search_logs_oversize_raises_toolerror(self, mock_get_client, mock_map): class DummySearchResponse: def model_dump_json(self): # Force the ValueError path by exceeding size threshold @@ -386,9 +376,7 @@ async def test_search_log_query_syntax_guide_resource(self, mock_get_script): mock_get_script.return_value = "GUIDE CONTENT" async with Client(mcp) as client: - content = ( - await client.read_resource("resource://search-log-query-syntax-guide") - )[0].text + content = (await client.read_resource("resource://search-log-query-syntax-guide"))[0].text assert content == "GUIDE CONTENT" mock_get_script.assert_called_once() @@ -410,9 +398,7 @@ async def test_get_paginated_event_types_basic_pagination(self, mock_get_script) async with Client(mcp) as client: raw = ( - await client.call_tool( - "get_paginated_event_types", {"page": 1, "page_size": 2} - ) + await client.call_tool("get_paginated_event_types", {"page": 1, "page_size": 2}) ).structured_content text = raw["result"] if isinstance(raw, dict) and "result" in raw else raw @@ -436,9 +422,7 @@ async def test_get_paginated_event_types_no_more_data(self, mock_get_script): async with Client(mcp) as client: raw = ( - await client.call_tool( - "get_paginated_event_types", {"page": 3, "page_size": 2} - ) + await client.call_tool("get_paginated_event_types", {"page": 3, "page_size": 2}) ).structured_content text = raw["result"] if isinstance(raw, dict) and "result" in raw else raw @@ -449,9 +433,7 @@ async def test_get_paginated_event_types_no_more_data(self, mock_get_script): async def test_get_paginated_event_types_table_not_found(self, mock_get_script): mock_get_script.return_value = "This is a guide without a table." async with Client(mcp) as client: - raw = ( - await client.call_tool("get_paginated_event_types", {}) - ).structured_content + raw = (await client.call_tool("get_paginated_event_types", {})).structured_content text = raw["result"] if isinstance(raw, dict) and "result" in raw else raw assert "Table not found in the guide." in text @@ -546,9 +528,7 @@ def test_get_logging_client_with_profile_env( profile_name="MYPROFILE", ) mock_open_file.assert_called_once_with("/abs/path/to/token", "r") - mock_security_token_signer.assert_called_once_with( - "SECURITY_TOKEN", private_key_obj - ) + mock_security_token_signer.assert_called_once_with("SECURITY_TOKEN", private_key_obj) # Ensure user agent was set on the same config dict passed into client args, _ = mock_client.call_args passed_config = args[0] @@ -642,9 +622,7 @@ def test_get_logging_search_client_with_profile_env( profile_name="MYPROFILE", ) mock_open_file.assert_called_once_with("/abs/path/to/token", "r") - mock_security_token_signer.assert_called_once_with( - "SECURITY_TOKEN", private_key_obj - ) + mock_security_token_signer.assert_called_once_with("SECURITY_TOKEN", private_key_obj) # Ensure user agent was set on the same config dict passed into client args, _ = mock_client.call_args passed_config = args[0] diff --git a/src/oci-migration-mcp-server/oracle/oci_migration_mcp_server/models.py b/src/oci-migration-mcp-server/oracle/oci_migration_mcp_server/models.py index 39eba853..396958d9 100644 --- a/src/oci-migration-mcp-server/oracle/oci_migration_mcp_server/models.py +++ b/src/oci-migration-mcp-server/oracle/oci_migration_mcp_server/models.py @@ -37,12 +37,8 @@ class Migration(BaseModel): This model has no nested custom types; all fields are primitives or dicts. """ - id: Optional[str] = Field( - None, description="Unique identifier that is immutable on creation" - ) - display_name: Optional[str] = Field( - None, description="Migration Identifier that can be renamed" - ) + id: Optional[str] = Field(None, description="Unique identifier that is immutable on creation") + display_name: Optional[str] = Field(None, description="Migration Identifier that can be renamed") compartment_id: Optional[str] = Field(None, description="Compartment Identifier") lifecycle_state: Optional[str] = Field( None, @@ -72,9 +68,7 @@ class Migration(BaseModel): None, description="The time when the migration project was updated. An RFC3339 formatted datetime string", ) - replication_schedule_id: Optional[str] = Field( - None, description="Replication schedule identifier" - ) + replication_schedule_id: Optional[str] = Field(None, description="Replication schedule identifier") is_completed: Optional[bool] = Field( None, description="Indicates whether migration is marked as completed." ) @@ -106,9 +100,7 @@ def map_migration(migration_data: oci.cloud_migrations.models.Migration) -> Migr lifecycle_details=getattr(migration_data, "lifecycle_details", None), time_created=getattr(migration_data, "time_created", None), time_updated=getattr(migration_data, "time_updated", None), - replication_schedule_id=getattr( - migration_data, "replication_schedule_id", None - ), + replication_schedule_id=getattr(migration_data, "replication_schedule_id", None), is_completed=getattr(migration_data, "is_completed", None), freeform_tags=getattr(migration_data, "freeform_tags", None), defined_tags=getattr(migration_data, "defined_tags", None), @@ -127,12 +119,8 @@ class MigrationSummary(BaseModel): This model has no nested custom types; all fields are primitives or dicts. """ - id: Optional[str] = Field( - None, description="Unique identifier that is immutable on creation." - ) - display_name: Optional[str] = Field( - None, description="Migration identifier that can be renamed" - ) + id: Optional[str] = Field(None, description="Unique identifier that is immutable on creation.") + display_name: Optional[str] = Field(None, description="Migration identifier that can be renamed") compartment_id: Optional[str] = Field(None, description="Compartment identifier") time_created: Optional[datetime] = Field( None, @@ -142,9 +130,7 @@ class MigrationSummary(BaseModel): None, description="The time when the migration project was updated. An RFC3339 formatted datetime string.", ) - lifecycle_state: Optional[str] = Field( - None, description="The current state of migration." - ) + lifecycle_state: Optional[str] = Field(None, description="The current state of migration.") lifecycle_details: Optional[str] = Field( None, description="A message describing the current state in more detail. " @@ -153,9 +139,7 @@ class MigrationSummary(BaseModel): is_completed: Optional[bool] = Field( None, description="Indicates whether migration is marked as complete." ) - replication_schedule_id: Optional[str] = Field( - None, description="Replication schedule identifier" - ) + replication_schedule_id: Optional[str] = Field(None, description="Replication schedule identifier") freeform_tags: Optional[Dict[str, str]] = Field( None, description="Simple key-value pair that is applied without any predefined name, type or scope. " 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 2bcf48f5..b9c7f497 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 @@ -43,9 +43,7 @@ def get_migration_client(): @mcp.tool(description="Get details for a specific Migration Project by OCID") -def get_migration( - migration_id: str = Field(..., description="OCID of the migration project") -) -> Migration: +def get_migration(migration_id: str = Field(..., description="OCID of the migration project")) -> Migration: try: client = get_migration_client() @@ -59,9 +57,7 @@ def get_migration( raise e -@mcp.tool( - description="List Migration Projects for a compartment, optionally filtered by lifecycle state" -) +@mcp.tool(description="List Migration Projects for a compartment, optionally filtered by lifecycle state") def list_migrations( compartment_id: str = Field(..., description="The OCID of the compartment"), limit: Optional[int] = Field( @@ -104,9 +100,7 @@ def list_migrations( has_next_page = response.has_next_page next_page = response.next_page if hasattr(response, "next_page") else None - data: list[oci.cloud_migrations.models.MigrationSummary] = ( - response.data.items - ) + data: list[oci.cloud_migrations.models.MigrationSummary] = response.data.items for d in data: migrations.append(map_migration_summary(d)) diff --git a/src/oci-migration-mcp-server/oracle/oci_migration_mcp_server/tests/test_migration_models.py b/src/oci-migration-mcp-server/oracle/oci_migration_mcp_server/tests/test_migration_models.py index b172bbbd..1754c413 100644 --- a/src/oci-migration-mcp-server/oracle/oci_migration_mcp_server/tests/test_migration_models.py +++ b/src/oci-migration-mcp-server/oracle/oci_migration_mcp_server/tests/test_migration_models.py @@ -74,12 +74,8 @@ def test_map_migration_full(self): assert mapped.compartment_id == src.compartment_id assert mapped.lifecycle_state == src.lifecycle_state assert mapped.lifecycle_details == src.lifecycle_details - expected_created = datetime.fromisoformat( - src.time_created.replace("Z", "+00:00") - ) - expected_updated = datetime.fromisoformat( - src.time_updated.replace("Z", "+00:00") - ) + expected_created = datetime.fromisoformat(src.time_created.replace("Z", "+00:00")) + expected_updated = datetime.fromisoformat(src.time_updated.replace("Z", "+00:00")) assert mapped.time_created == expected_created assert mapped.time_updated == expected_updated assert mapped.replication_schedule_id == src.replication_schedule_id @@ -125,12 +121,8 @@ def test_map_migration_summary_full(self): assert mapped.id == src.id assert mapped.display_name == src.display_name assert mapped.compartment_id == src.compartment_id - expected_created = datetime.fromisoformat( - src.time_created.replace("Z", "+00:00") - ) - expected_updated = datetime.fromisoformat( - src.time_updated.replace("Z", "+00:00") - ) + expected_created = datetime.fromisoformat(src.time_created.replace("Z", "+00:00")) + expected_updated = datetime.fromisoformat(src.time_updated.replace("Z", "+00:00")) assert mapped.time_created == expected_created assert mapped.time_updated == expected_updated assert mapped.lifecycle_state == src.lifecycle_state diff --git a/src/oci-migration-mcp-server/oracle/oci_migration_mcp_server/tests/test_migration_tools.py b/src/oci-migration-mcp-server/oracle/oci_migration_mcp_server/tests/test_migration_tools.py index 852dfe2a..6d69f144 100644 --- a/src/oci-migration-mcp-server/oracle/oci_migration_mcp_server/tests/test_migration_tools.py +++ b/src/oci-migration-mcp-server/oracle/oci_migration_mcp_server/tests/test_migration_tools.py @@ -29,9 +29,7 @@ async def test_get_migration(self, mock_get_client): mock_client.get_migration.return_value = mock_get_response async with Client(mcp) as client: - call_tool_result = await client.call_tool( - "get_migration", {"migration_id": "migration1"} - ) + call_tool_result = await client.call_tool("get_migration", {"migration_id": "migration1"}) result = call_tool_result.structured_content assert result["id"] == "migration1" @@ -246,21 +244,13 @@ async def test_list_migrations_exception_propagates(mock_get_client): async with Client(mcp) as client: with pytest.raises(ToolError): - await client.call_tool( - "list_migrations", {"compartment_id": "ocid1.tenancy"} - ) + await client.call_tool("list_migrations", {"compartment_id": "ocid1.tenancy"}) class TestGetClient: - @patch( - "oracle.oci_migration_mcp_server.server.oci.cloud_migrations.MigrationClient" - ) - @patch( - "oracle.oci_migration_mcp_server.server.oci.auth.signers.SecurityTokenSigner" - ) - @patch( - "oracle.oci_migration_mcp_server.server.oci.signer.load_private_key_from_file" - ) + @patch("oracle.oci_migration_mcp_server.server.oci.cloud_migrations.MigrationClient") + @patch("oracle.oci_migration_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_migration_mcp_server.server.oci.signer.load_private_key_from_file") @patch( "oracle.oci_migration_mcp_server.server.open", new_callable=mock_open, @@ -298,27 +288,21 @@ def test_get_migration_client_with_profile_env( profile_name="MYPROFILE", ) mock_open_file.assert_called_once_with("/abs/path/to/token", "r") - mock_security_token_signer.assert_called_once_with( - "SECURITY_TOKEN", private_key_obj - ) + mock_security_token_signer.assert_called_once_with("SECURITY_TOKEN", private_key_obj) # Ensure user agent was set on the same config dict passed into client args, _ = mock_client.call_args passed_config = args[0] assert passed_config is config - expected_user_agent = f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + expected_user_agent = ( + f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + ) assert passed_config.get("additional_user_agent") == expected_user_agent # And we returned the client instance assert result == mock_client.return_value - @patch( - "oracle.oci_migration_mcp_server.server.oci.cloud_migrations.MigrationClient" - ) - @patch( - "oracle.oci_migration_mcp_server.server.oci.auth.signers.SecurityTokenSigner" - ) - @patch( - "oracle.oci_migration_mcp_server.server.oci.signer.load_private_key_from_file" - ) + @patch("oracle.oci_migration_mcp_server.server.oci.cloud_migrations.MigrationClient") + @patch("oracle.oci_migration_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_migration_mcp_server.server.oci.signer.load_private_key_from_file") @patch( "oracle.oci_migration_mcp_server.server.open", new_callable=mock_open, @@ -360,9 +344,6 @@ def test_get_migration_client_uses_default_profile_when_env_missing( cc_args, _ = mock_client.call_args assert cc_args[0] is config assert "additional_user_agent" in config - assert ( - isinstance(config["additional_user_agent"], str) - and "/" in config["additional_user_agent"] - ) + assert isinstance(config["additional_user_agent"], str) and "/" in config["additional_user_agent"] # Returned object is client instance assert srv_client is mock_client.return_value diff --git a/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/alarm_models.py b/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/alarm_models.py index 8054d127..aea3034f 100644 --- a/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/alarm_models.py +++ b/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/alarm_models.py @@ -18,9 +18,7 @@ class Suppression(BaseModel): Pydantic model mirroring oci.monitoring.models.Suppression. """ - description: Optional[str] = Field( - None, description="Human-readable description of the suppression." - ) + description: Optional[str] = Field(None, description="Human-readable description of the suppression.") time_suppress_from: Optional[datetime] = Field( None, description="The start time for the suppression (RFC3339)." ) @@ -34,10 +32,8 @@ def map_suppression(s: oci.monitoring.models.Suppression | None) -> Suppression return None return Suppression( description=getattr(s, "description", None), - time_suppress_from=getattr(s, "time_suppress_from", None) - or getattr(s, "timeSuppressFrom", None), - time_suppress_until=getattr(s, "time_suppress_until", None) - or getattr(s, "timeSuppressUntil", None), + time_suppress_from=getattr(s, "time_suppress_from", None) or getattr(s, "timeSuppressFrom", None), + time_suppress_until=getattr(s, "time_suppress_until", None) or getattr(s, "timeSuppressUntil", None), ) @@ -51,12 +47,8 @@ class AlarmOverride(BaseModel): None, description="Identifier of the alarm's base/override values. Default is 'BASE'.", ) - query: Optional[str] = Field( - None, description="MQL expression override for this rule." - ) - severity: Optional[SeverityType] = Field( - None, description="Severity override for this rule." - ) + query: Optional[str] = Field(None, description="MQL expression override for this rule.") + severity: Optional[SeverityType] = Field(None, description="Severity override for this rule.") body: Optional[str] = Field(None, description="Message body override (alarm body).") pending_duration: Optional[str] = Field( None, @@ -74,8 +66,7 @@ def map_alarm_override( query=getattr(o, "query", None), severity=getattr(o, "severity", None), body=getattr(o, "body", None), - pending_duration=getattr(o, "pending_duration", None) - or getattr(o, "pendingDuration", None), + pending_duration=getattr(o, "pending_duration", None) or getattr(o, "pendingDuration", None), ) @@ -107,9 +98,7 @@ class AlarmSummary(BaseModel): None, description="The OCID of the compartment containing the metric evaluated by the alarm.", ) - namespace: Optional[str] = Field( - None, description="The source service/application emitting the metric." - ) + namespace: Optional[str] = Field(None, description="The source service/application emitting the metric.") query: Optional[str] = Field( None, description="The Monitoring Query Language (MQL) expression to evaluate for the alarm.", @@ -125,9 +114,7 @@ class AlarmSummary(BaseModel): suppression: Optional[Suppression] = Field( None, description="Configuration details for suppressing an alarm." ) - is_enabled: Optional[bool] = Field( - None, description="Whether the alarm is enabled." - ) + is_enabled: Optional[bool] = Field(None, description="Whether the alarm is enabled.") is_notifications_per_metric_dimension_enabled: Optional[bool] = Field( None, description="Whether the alarm sends a separate message for each metric stream.", @@ -138,9 +125,7 @@ class AlarmSummary(BaseModel): defined_tags: Optional[Dict[str, Dict[str, Any]]] = Field( None, description="Defined tags for this resource, scoped to namespaces." ) - lifecycle_state: Optional[str] = Field( - None, description="The current lifecycle state of the alarm." - ) + lifecycle_state: Optional[str] = Field(None, description="The current lifecycle state of the alarm.") overrides: Optional[List[AlarmOverride]] = Field( None, description="Overrides controlling alarm evaluations (query, severity, body, pending duration).", @@ -180,10 +165,8 @@ def map_alarm_summary( """ return AlarmSummary( id=getattr(alarm, "id", None), - display_name=getattr(alarm, "display_name", None) - or getattr(alarm, "displayName", None), - compartment_id=getattr(alarm, "compartment_id", None) - or getattr(alarm, "compartmentId", None), + display_name=getattr(alarm, "display_name", None) or getattr(alarm, "displayName", None), + compartment_id=getattr(alarm, "compartment_id", None) or getattr(alarm, "compartmentId", None), metric_compartment_id=getattr(alarm, "metric_compartment_id", None) or getattr(alarm, "metricCompartmentId", None), namespace=getattr(alarm, "namespace", None), @@ -191,18 +174,14 @@ def map_alarm_summary( severity=getattr(alarm, "severity", None), destinations=getattr(alarm, "destinations", None), suppression=map_suppression(getattr(alarm, "suppression", None)), - is_enabled=getattr(alarm, "is_enabled", None) - or getattr(alarm, "isEnabled", None), + is_enabled=getattr(alarm, "is_enabled", None) or getattr(alarm, "isEnabled", None), is_notifications_per_metric_dimension_enabled=getattr( alarm, "is_notifications_per_metric_dimension_enabled", None ) or getattr(alarm, "isNotificationsPerMetricDimensionEnabled", None), - freeform_tags=getattr(alarm, "freeform_tags", None) - or getattr(alarm, "freeformTags", None), - defined_tags=getattr(alarm, "defined_tags", None) - or getattr(alarm, "definedTags", None), - lifecycle_state=getattr(alarm, "lifecycle_state", None) - or getattr(alarm, "lifecycleState", None), + freeform_tags=getattr(alarm, "freeform_tags", None) or getattr(alarm, "freeformTags", None), + defined_tags=getattr(alarm, "defined_tags", None) or getattr(alarm, "definedTags", None), + lifecycle_state=getattr(alarm, "lifecycle_state", None) or getattr(alarm, "lifecycleState", None), overrides=map_alarm_overrides(getattr(alarm, "overrides", None)), rule_name=getattr(alarm, "rule_name", None) or getattr(alarm, "ruleName", None), notification_version=getattr(alarm, "notification_version", None) @@ -211,8 +190,6 @@ def map_alarm_summary( or getattr(alarm, "notificationTitle", None), evaluation_slack_duration=getattr(alarm, "evaluation_slack_duration", None) or getattr(alarm, "evaluationSlackDuration", None), - alarm_summary=getattr(alarm, "alarm_summary", None) - or getattr(alarm, "alarmSummary", None), - resource_group=getattr(alarm, "resource_group", None) - or getattr(alarm, "resourceGroup", None), + alarm_summary=getattr(alarm, "alarm_summary", None) or getattr(alarm, "alarmSummary", None), + resource_group=getattr(alarm, "resource_group", None) or getattr(alarm, "resourceGroup", None), ) diff --git a/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/metric_models.py b/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/metric_models.py index 4970c3bc..a0b134bc 100644 --- a/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/metric_models.py +++ b/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/metric_models.py @@ -105,9 +105,7 @@ class Metric(BaseModel): None, description="The OCID of the compartment containing the resource emitting the metric.", ) - name: Optional[str] = Field( - None, description="The metric name (for example, CpuUtilization)." - ) + name: Optional[str] = Field(None, description="The metric name (for example, CpuUtilization).") dimensions: Optional[Dict[str, str]] = Field( None, description="Dimensions (key/value pairs) that qualify the metric (for example, resourceId).", @@ -147,9 +145,7 @@ class AggregatedDatapoint(BaseModel): None, description="The date and time associated with the aggregated value (RFC3339).", ) - value: Optional[float] = Field( - None, description="The aggregated metric value for the time window." - ) + value: Optional[float] = Field(None, description="The aggregated metric value for the time window.") class MetricData(BaseModel): @@ -160,22 +156,16 @@ class MetricData(BaseModel): namespace: Optional[str] = Field( None, description="The source service or application emitting the metric." ) - resource_group: Optional[str] = Field( - None, description="Resource group specified for the metric." - ) + resource_group: Optional[str] = Field(None, description="Resource group specified for the metric.") compartment_id: Optional[str] = Field( None, description="The OCID of the compartment containing the resource." ) - name: Optional[str] = Field( - None, description="The metric name (for example, CpuUtilization)." - ) + name: Optional[str] = Field(None, description="The metric name (for example, CpuUtilization).") dimensions: Optional[Dict[str, str]] = Field( None, description="Dimensions that qualify the metric (for example, resourceId).", ) - metadata: Optional[Dict[str, str]] = Field( - None, description="Metric metadata such as unit." - ) + metadata: Optional[Dict[str, str]] = Field(None, description="Metric metadata such as unit.") resolution: Optional[str] = Field( None, description="The publication resolution of the metric (for example, '1m').", @@ -220,9 +210,7 @@ def map_metric_data(metric_data: oci.monitoring.models.MetricData) -> MetricData dimensions=getattr(metric_data, "dimensions", None), metadata=getattr(metric_data, "metadata", None), resolution=getattr(metric_data, "resolution", None), - aggregated_datapoints=map_aggregated_datapoints( - getattr(metric_data, "aggregated_datapoints", None) - ), + aggregated_datapoints=map_aggregated_datapoints(getattr(metric_data, "aggregated_datapoints", None)), ) @@ -235,9 +223,7 @@ class Datapoint(BaseModel): timestamp: Optional[datetime] = Field( None, description="The time the metric value was recorded (RFC3339)." ) - value: Optional[float] = Field( - None, description="Metric value at the given timestamp." - ) + value: Optional[float] = Field(None, description="Metric value at the given timestamp.") count: Optional[int] = Field( None, description="Optional number of samples represented by this value (if provided).", @@ -274,25 +260,17 @@ class MetricDataDetails(BaseModel): namespace: Optional[str] = Field( None, description="The source service or application emitting the metric." ) - resource_group: Optional[str] = Field( - None, description="Resource group specified for the metric." - ) + resource_group: Optional[str] = Field(None, description="Resource group specified for the metric.") compartment_id: Optional[str] = Field( None, description="The OCID of the compartment containing the resource." ) - name: Optional[str] = Field( - None, description="The metric name (for example, CpuUtilization)." - ) + name: Optional[str] = Field(None, description="The metric name (for example, CpuUtilization).") dimensions: Optional[Dict[str, str]] = Field( None, description="Dimensions that qualify the metric (for example, resourceId).", ) - metadata: Optional[Dict[str, str]] = Field( - None, description="Metric metadata such as unit." - ) - datapoints: Optional[List[Datapoint]] = Field( - None, description="Raw datapoints to post for this metric." - ) + metadata: Optional[Dict[str, str]] = Field(None, description="Metric metadata such as unit.") + datapoints: Optional[List[Datapoint]] = Field(None, description="Raw datapoints to post for this metric.") def map_metric_data_details( @@ -325,12 +303,8 @@ class ListMetricsDetails(BaseModel): namespace: Optional[str] = Field( None, description="The source service or application emitting the metric." ) - resource_group: Optional[str] = Field( - None, description="Resource group specified for the metric." - ) - name: Optional[str] = Field( - None, description="Optional metric name to filter by (e.g., CpuUtilization)." - ) + resource_group: Optional[str] = Field(None, description="Resource group specified for the metric.") + name: Optional[str] = Field(None, description="Optional metric name to filter by (e.g., CpuUtilization).") dimension_filters: Optional[Dict[str, str]] = Field( None, description="Filter to only include metrics that match these dimension key/value pairs.", @@ -355,8 +329,7 @@ def map_list_metrics_details( resource_group=getattr(lmd, "resource_group", None), name=getattr(lmd, "name", None), # OCI SDK may expose snake_case or camelCase depending on version - dimension_filters=getattr(lmd, "dimension_filters", None) - or getattr(lmd, "dimensionFilters", None), + dimension_filters=getattr(lmd, "dimension_filters", None) or getattr(lmd, "dimensionFilters", None), group_by=getattr(lmd, "group_by", None) or getattr(lmd, "groupBy", None), ) @@ -370,9 +343,7 @@ class SummarizeMetricsDataDetails(BaseModel): namespace: Optional[str] = Field( None, description="The source service or application emitting the metric." ) - resource_group: Optional[str] = Field( - None, description="Resource group specified for the metric." - ) + resource_group: Optional[str] = Field(None, description="Resource group specified for the metric.") query: Optional[str] = Field( None, description=( 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 d22403c0..84d56425 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 @@ -68,9 +68,7 @@ def list_alarms( ], ) -> list[AlarmSummary] | str: monitoring_client = get_monitoring_client() - response: Response | None = monitoring_client.list_alarms( - compartment_id=compartment_id - ) + response: Response | None = monitoring_client.list_alarms(compartment_id=compartment_id) if response is None: logger.error("Received None response from list_metrics") return "There was no response returned from the Monitoring API" diff --git a/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/tests/test_monitoring_models.py b/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/tests/test_monitoring_models.py index 74ad5042..15d89a41 100644 --- a/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/tests/test_monitoring_models.py +++ b/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/tests/test_monitoring_models.py @@ -180,9 +180,7 @@ def test_map_metric_data(self) -> None: dimensions={"k": "v"}, metadata={"unit": "%"}, resolution="1m", - aggregated_datapoints=[ - SimpleNamespace(timestamp=datetime(2024, 1, 1), value=2.0) - ], + aggregated_datapoints=[SimpleNamespace(timestamp=datetime(2024, 1, 1), value=2.0)], ) mapped = map_metric_data(md) assert isinstance(mapped, MetricData) diff --git a/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/tests/test_monitoring_tools.py b/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/tests/test_monitoring_tools.py index 904384f6..1dd94745 100644 --- a/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/tests/test_monitoring_tools.py +++ b/src/oci-monitoring-mcp-server/oracle/oci_monitoring_mcp_server/tests/test_monitoring_tools.py @@ -43,9 +43,7 @@ async def test_get_metrics_data(self, mock_get_client): mock_get_client.return_value = Mock() mock_list_response = Mock() mock_list_response.data = [metric] - mock_get_client.return_value.summarize_metrics_data.return_value = ( - mock_list_response - ) + mock_get_client.return_value.summarize_metrics_data.return_value = mock_list_response async with Client(mcp) as client: call_tool_result = await client.call_tool( @@ -146,9 +144,7 @@ async def test_list_alarms(self, mock_get_client): mock_get_client.return_value.list_alarms.return_value = mock_list_response async with Client(mcp) as client: - call_tool_result = await client.call_tool( - "list_alarms", {"compartment_id": "compartment1"} - ) + call_tool_result = await client.call_tool("list_alarms", {"compartment_id": "compartment1"}) result = call_tool_result.structured_content["result"] for alarm in result: @@ -190,9 +186,7 @@ async def test_list_alarms_with_overrides(self, mock_get_client): mock_get_client.return_value.list_alarms.return_value = mock_list_response async with Client(mcp) as client: - call_tool_result = await client.call_tool( - "list_alarms", {"compartment_id": "compartment1"} - ) + call_tool_result = await client.call_tool("list_alarms", {"compartment_id": "compartment1"}) result = call_tool_result.structured_content["result"] for alarm in result: @@ -208,9 +202,7 @@ async def test_list_alarms_no_response(self, mock_get_client): mock_get_client.return_value.list_alarms.return_value = mock_list_response async with Client(mcp) as client: - call_tool_result = await client.call_tool( - "list_alarms", {"compartment_id": "compartment1"} - ) + call_tool_result = await client.call_tool("list_alarms", {"compartment_id": "compartment1"}) result = call_tool_result.structured_content["result"] assert result == "There was no response returned from the Monitoring API" @@ -301,12 +293,8 @@ def test_read_file(self): class TestGetClient: @patch("oracle.oci_monitoring_mcp_server.server.oci.monitoring.MonitoringClient") - @patch( - "oracle.oci_monitoring_mcp_server.server.oci.auth.signers.SecurityTokenSigner" - ) - @patch( - "oracle.oci_monitoring_mcp_server.server.oci.signer.load_private_key_from_file" - ) + @patch("oracle.oci_monitoring_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_monitoring_mcp_server.server.oci.signer.load_private_key_from_file") @patch( "oracle.oci_monitoring_mcp_server.server.open", new_callable=mock_open, @@ -344,25 +332,21 @@ def test_get_monitoring_client_with_profile_env( profile_name="MYPROFILE", ) mock_open_file.assert_called_once_with("/abs/path/to/token", "r") - mock_security_token_signer.assert_called_once_with( - "SECURITY_TOKEN", private_key_obj - ) + mock_security_token_signer.assert_called_once_with("SECURITY_TOKEN", private_key_obj) # Ensure user agent was set on the same config dict passed into client args, _ = mock_client.call_args passed_config = args[0] assert passed_config is config - expected_user_agent = f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + expected_user_agent = ( + f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + ) assert passed_config.get("additional_user_agent") == expected_user_agent # And we returned the client instance assert result == mock_client.return_value @patch("oracle.oci_monitoring_mcp_server.server.oci.monitoring.MonitoringClient") - @patch( - "oracle.oci_monitoring_mcp_server.server.oci.auth.signers.SecurityTokenSigner" - ) - @patch( - "oracle.oci_monitoring_mcp_server.server.oci.signer.load_private_key_from_file" - ) + @patch("oracle.oci_monitoring_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_monitoring_mcp_server.server.oci.signer.load_private_key_from_file") @patch( "oracle.oci_monitoring_mcp_server.server.open", new_callable=mock_open, @@ -404,9 +388,6 @@ def test_get_monitoring_client_uses_default_profile_when_env_missing( cc_args, _ = mock_client.call_args assert cc_args[0] is config assert "additional_user_agent" in config - assert ( - isinstance(config["additional_user_agent"], str) - and "/" in config["additional_user_agent"] - ) + assert isinstance(config["additional_user_agent"], str) and "/" in config["additional_user_agent"] # Returned object is client instance assert srv_client is mock_client.return_value diff --git a/src/oci-network-load-balancer-mcp-server/oracle/oci_network_load_balancer_mcp_server/models.py b/src/oci-network-load-balancer-mcp-server/oracle/oci_network_load_balancer_mcp_server/models.py index 4d233729..61bae75e 100644 --- a/src/oci-network-load-balancer-mcp-server/oracle/oci_network_load_balancer_mcp_server/models.py +++ b/src/oci-network-load-balancer-mcp-server/oracle/oci_network_load_balancer_mcp_server/models.py @@ -35,40 +35,28 @@ def _oci_to_dict(obj): class ReservedIP(BaseModel): """Reserved IP details.""" - id: Optional[str] = Field( - None, description="The OCID of the reserved public IP address." - ) + id: Optional[str] = Field(None, description="The OCID of the reserved public IP address.") class IpAddress(BaseModel): """IP address details.""" ip_address: Optional[str] = Field(None, description="The IP address.") - is_public: Optional[bool] = Field( - None, description="Whether the IP address is public." - ) - ip_version: Optional[Literal["IPV4", "IPV6"]] = Field( - None, description="IP version." - ) - reserved_ip: Optional[ReservedIP] = Field( - None, description="Reserved IP information." - ) + is_public: Optional[bool] = Field(None, description="Whether the IP address is public.") + ip_version: Optional[Literal["IPV4", "IPV6"]] = Field(None, description="IP version.") + reserved_ip: Optional[ReservedIP] = Field(None, description="Reserved IP information.") class Backend(BaseModel): """Backend server details.""" name: Optional[str] = Field(None, description="The name of the backend.") - ip_address: Optional[str] = Field( - None, description="The IP address of the backend server." - ) + ip_address: Optional[str] = Field(None, description="The IP address of the backend server.") target_id: Optional[str] = Field( None, description="The IP OCID/Instance OCID associated with the backend server.", ) - port: Optional[int] = Field( - None, description="The communication port for the backend server." - ) + port: Optional[int] = Field(None, description="The communication port for the backend server.") weight: Optional[int] = Field( None, description="The network load balancing policy weight assigned to the server.", @@ -89,9 +77,7 @@ class Backend(BaseModel): class DnsHealthCheckerDetails(BaseModel): """DNS health checker details.""" - transport_protocol: Optional[Literal["UDP", "TCP"]] = Field( - None, description="DNS transport protocol." - ) + transport_protocol: Optional[Literal["UDP", "TCP"]] = Field(None, description="DNS transport protocol.") domain_name: Optional[str] = Field( None, description="The absolute fully-qualified domain name to perform periodic DNS queries.", @@ -102,9 +88,7 @@ class DnsHealthCheckerDetails(BaseModel): query_type: Optional[Literal["A", "TXT", "AAAA"]] = Field( None, description="The type of the DNS health check query." ) - rcodes: Optional[List[str]] = Field( - None, description="Acceptable RCODE values for DNS query response." - ) + rcodes: Optional[List[str]] = Field(None, description="Acceptable RCODE values for DNS query response.") class HealthChecker(BaseModel): @@ -128,9 +112,7 @@ class HealthChecker(BaseModel): interval_in_millis: Optional[int] = Field( None, description="The interval between health checks, in milliseconds." ) - url_path: Optional[str] = Field( - None, description="The path against which to run the health check." - ) + url_path: Optional[str] = Field(None, description="The path against which to run the health check.") response_body_regex: Optional[str] = Field( None, description="A regular expression for parsing the response body from the backend server.", @@ -146,9 +128,7 @@ class HealthChecker(BaseModel): None, description="Base64 encoded pattern to be validated as UDP or TCP health check probe response.", ) - dns: Optional[DnsHealthCheckerDetails] = Field( - None, description="DNS health checker details." - ) + dns: Optional[DnsHealthCheckerDetails] = Field(None, description="DNS health checker details.") class Listener(BaseModel): @@ -158,9 +138,7 @@ class Listener(BaseModel): default_backend_set_name: Optional[str] = Field( None, description="The name of the associated backend set." ) - port: Optional[int] = Field( - None, description="The communication port for the listener." - ) + port: Optional[int] = Field(None, description="The communication port for the listener.") protocol: Optional[Literal["ANY", "TCP", "UDP", "TCP_AND_UDP", "L3IP"]] = Field( None, description="The protocol on which the listener accepts connection requests.", @@ -171,12 +149,8 @@ class Listener(BaseModel): is_ppv2_enabled: Optional[bool] = Field( None, description="Property to enable/disable PPv2 feature for this listener." ) - tcp_idle_timeout: Optional[int] = Field( - None, description="The duration for TCP idle timeout in seconds." - ) - udp_idle_timeout: Optional[int] = Field( - None, description="The duration for UDP idle timeout in seconds." - ) + tcp_idle_timeout: Optional[int] = Field(None, description="The duration for TCP idle timeout in seconds.") + udp_idle_timeout: Optional[int] = Field(None, description="The duration for UDP idle timeout in seconds.") l3_ip_idle_timeout: Optional[int] = Field( None, description="The duration for L3IP idle timeout in seconds." ) @@ -185,9 +159,7 @@ class Listener(BaseModel): class BackendSet(BaseModel): """Backend set configuration.""" - name: Optional[str] = Field( - None, description="A user-friendly name for the backend set." - ) + name: Optional[str] = Field(None, description="A user-friendly name for the backend set.") policy: Optional[Literal["TWO_TUPLE", "THREE_TUPLE", "FIVE_TUPLE"]] = Field( None, description="The network load balancer policy for the backend set." ) @@ -225,17 +197,15 @@ class BackendSet(BaseModel): class NetworkLoadBalancer(BaseModel): """Network load balancer.""" - id: Optional[str] = Field( - None, description="The OCID of the network load balancer." - ) + id: Optional[str] = Field(None, description="The OCID of the network load balancer.") compartment_id: Optional[str] = Field( None, description="The OCID of the compartment containing the network load balancer.", ) display_name: Optional[str] = Field(None, description="A user-friendly name.") - lifecycle_state: Optional[ - Literal["CREATING", "UPDATING", "ACTIVE", "DELETING", "DELETED", "FAILED"] - ] = Field(None, description="The current state of the network load balancer.") + lifecycle_state: Optional[Literal["CREATING", "UPDATING", "ACTIVE", "DELETING", "DELETED", "FAILED"]] = ( + Field(None, description="The current state of the network load balancer.") + ) lifecycle_details: Optional[str] = Field( None, description="A message describing the current state in more detail." ) @@ -248,9 +218,7 @@ class NetworkLoadBalancer(BaseModel): time_updated: Optional[datetime] = Field( None, description="The time the network load balancer was updated." ) - ip_addresses: Optional[List[IpAddress]] = Field( - None, description="An array of IP addresses." - ) + ip_addresses: Optional[List[IpAddress]] = Field(None, description="An array of IP addresses.") is_private: Optional[bool] = Field( None, description="Whether the network load balancer has a " @@ -279,9 +247,7 @@ class NetworkLoadBalancer(BaseModel): backend_sets: Optional[Dict[str, BackendSet]] = Field( None, description="Backend sets associated with the network load balancer." ) - freeform_tags: Optional[Dict[str, str]] = Field( - None, description="Free-form tags for this resource." - ) + freeform_tags: Optional[Dict[str, str]] = Field(None, description="Free-form tags for this resource.") security_attributes: Optional[Dict[str, Dict[str, Any]]] = Field( None, description="ZPR tags for this resource." ) @@ -379,9 +345,7 @@ def map_backend_set(obj) -> BackendSet | None: if not obj: return None backends = ( - [map_backend(b) for b in getattr(obj, "backends", [])] - if getattr(obj, "backends", None) - else None + [map_backend(b) for b in getattr(obj, "backends", [])] if getattr(obj, "backends", None) else None ) return BackendSet( name=getattr(obj, "name", None), @@ -389,9 +353,7 @@ def map_backend_set(obj) -> BackendSet | None: is_preserve_source=getattr(obj, "is_preserve_source", None), is_fail_open=getattr(obj, "is_fail_open", None), is_instant_failover_enabled=getattr(obj, "is_instant_failover_enabled", None), - is_instant_failover_tcp_reset_enabled=getattr( - obj, "is_instant_failover_tcp_reset_enabled", None - ), + is_instant_failover_tcp_reset_enabled=getattr(obj, "is_instant_failover_tcp_reset_enabled", None), are_operationally_active_backends_preferred=getattr( obj, "are_operationally_active_backends_preferred", None ), @@ -431,9 +393,7 @@ def map_network_load_balancer( time_updated=getattr(obj, "time_updated", None), ip_addresses=ip_addresses, is_private=getattr(obj, "is_private", None), - is_preserve_source_destination=getattr( - obj, "is_preserve_source_destination", None - ), + is_preserve_source_destination=getattr(obj, "is_preserve_source_destination", None), is_symmetric_hash_enabled=getattr(obj, "is_symmetric_hash_enabled", None), subnet_id=getattr(obj, "subnet_id", None), network_security_group_ids=getattr(obj, "network_security_group_ids", None), 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 14626868..cb140e66 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 @@ -94,9 +94,7 @@ def list_network_load_balancers( has_next_page = response.has_next_page next_page = response.next_page if hasattr(response, "next_page") else None - data: list[oci.network_load_balancer.models.NetworkLoadBalancer] = ( - response.data.items - ) + data: list[oci.network_load_balancer.models.NetworkLoadBalancer] = response.data.items for d in data: nlbs.append(map_network_load_balancer(d)) @@ -108,20 +106,14 @@ def list_network_load_balancers( raise e -@mcp.tool( - name="get_network_load_balancer", description="Get network load balancer details" -) +@mcp.tool(name="get_network_load_balancer", description="Get network load balancer details") def get_network_load_balancer( - network_load_balancer_id: str = Field( - ..., description="The OCID of the network load balancer" - ) + network_load_balancer_id: str = Field(..., description="The OCID of the network load balancer"), ): try: client = get_nlb_client() - response: oci.response.Response = client.get_network_load_balancer( - network_load_balancer_id - ) + response: oci.response.Response = client.get_network_load_balancer(network_load_balancer_id) data: oci.network_load_balancer.models.NetworkLoadBalancer = response.data logger.info("Found Network Load Balancer") return map_network_load_balancer(data) @@ -179,8 +171,7 @@ def list_listeners( @mcp.tool( name="get_network_load_balancer_listener", - description="Gets the listener with the given listener name" - "from the given network load balancer", + description="Gets the listener with the given listener namefrom the given network load balancer", ) def get_listener( network_load_balancer_id: str = Field( @@ -192,9 +183,7 @@ def get_listener( try: client = get_nlb_client() - response: oci.response.Response = client.get_listener( - network_load_balancer_id, listener_name - ) + response: oci.response.Response = client.get_listener(network_load_balancer_id, listener_name) data: oci.network_load_balancer.models.Listener = response.data logger.info("Found Listener") return map_listener(data) @@ -239,9 +228,7 @@ def list_backend_sets( has_next_page = response.has_next_page next_page = response.next_page if hasattr(response, "next_page") else None - data: list[oci.network_load_balancer.models.BackendSet] = ( - response.data.items - ) + data: list[oci.network_load_balancer.models.BackendSet] = response.data.items for d in data: backend_sets.append(map_backend_set(d)) @@ -255,8 +242,7 @@ def list_backend_sets( @mcp.tool( name="get_network_load_balancer_backend_set", - description="Gets the backend set with the given backend set name" - "from the given network load balancer", + description="Gets the backend set with the given backend set namefrom the given network load balancer", ) def get_backend_set( network_load_balancer_id: str = Field( @@ -268,9 +254,7 @@ def get_backend_set( try: client = get_nlb_client() - response: oci.response.Response = client.get_backend_set( - network_load_balancer_id, backend_set_name - ) + response: oci.response.Response = client.get_backend_set(network_load_balancer_id, backend_set_name) data: oci.network_load_balancer.models.BackendSet = response.data logger.info("Found Backend Set") return map_backend_set(data) @@ -289,9 +273,7 @@ def list_backends( ..., description="The OCID of the network load balancer to list the backends from", ), - backend_set_name: str = Field( - ..., description="The name of the backend set to list the backends from" - ), + backend_set_name: str = Field(..., description="The name of the backend set to list the backends from"), limit: Optional[int] = Field( None, description="The maximum amount of backends to return. If None, there is no limit.", @@ -340,9 +322,7 @@ def get_backend( network_load_balancer_id: str = Field( ..., description="The OCID of the network load balancer to get the backend from" ), - backend_set_name: str = Field( - ..., description="The name of the backend set to get the backend from" - ), + backend_set_name: str = Field(..., description="The name of the backend set to get the backend from"), backend_name: str = Field(..., description="The name of the backend"), ): try: diff --git a/src/oci-network-load-balancer-mcp-server/oracle/oci_network_load_balancer_mcp_server/tests/test_network_load_balancer_models.py b/src/oci-network-load-balancer-mcp-server/oracle/oci_network_load_balancer_mcp_server/tests/test_network_load_balancer_models.py index 99159350..441098ec 100644 --- a/src/oci-network-load-balancer-mcp-server/oracle/oci_network_load_balancer_mcp_server/tests/test_network_load_balancer_models.py +++ b/src/oci-network-load-balancer-mcp-server/oracle/oci_network_load_balancer_mcp_server/tests/test_network_load_balancer_models.py @@ -136,9 +136,7 @@ def test_map_backend_set(self): oci.network_load_balancer.models.Backend(name="b1", port=80), oci.network_load_balancer.models.Backend(name="b2", port=81), ], - health_checker=oci.network_load_balancer.models.HealthChecker( - protocol="TCP" - ), + health_checker=oci.network_load_balancer.models.HealthChecker(protocol="TCP"), ) dst = map_backend_set(src) assert isinstance(dst, BackendSet) @@ -172,9 +170,7 @@ def test_map_network_load_balancer(self): "ln1": oci.network_load_balancer.models.Listener(name="ln1", port=80), }, backend_sets={ - "bs1": oci.network_load_balancer.models.BackendSet( - name="bs1", policy="THREE_TUPLE" - ) + "bs1": oci.network_load_balancer.models.BackendSet(name="bs1", policy="THREE_TUPLE") }, freeform_tags={"a": "b"}, defined_tags={"ns": {"k": "v"}}, diff --git a/src/oci-network-load-balancer-mcp-server/oracle/oci_network_load_balancer_mcp_server/tests/test_network_load_balancer_tools.py b/src/oci-network-load-balancer-mcp-server/oracle/oci_network_load_balancer_mcp_server/tests/test_network_load_balancer_tools.py index 2c23bfcb..12d1c87b 100644 --- a/src/oci-network-load-balancer-mcp-server/oracle/oci_network_load_balancer_mcp_server/tests/test_network_load_balancer_tools.py +++ b/src/oci-network-load-balancer-mcp-server/oracle/oci_network_load_balancer_mcp_server/tests/test_network_load_balancer_tools.py @@ -22,24 +22,18 @@ async def test_list_nlbs(self, mock_get_client): mock_get_client.return_value = mock_client mock_list_response = create_autospec(oci.response.Response) - mock_list_response.data = ( - oci.network_load_balancer.models.NetworkLoadBalancerCollection( - items=[ - oci.network_load_balancer.models.NetworkLoadBalancerSummary( - id="nlb1", - display_name="NLB 1", - lifecycle_state="ACTIVE", - ip_addresses=[ - oci.network_load_balancer.models.IpAddress( - ip_address="192.168.1.1", is_public=True - ), - oci.network_load_balancer.models.IpAddress( - ip_address="10.0.0.0", is_public=False - ), - ], - ) - ] - ) + mock_list_response.data = oci.network_load_balancer.models.NetworkLoadBalancerCollection( + items=[ + oci.network_load_balancer.models.NetworkLoadBalancerSummary( + id="nlb1", + display_name="NLB 1", + lifecycle_state="ACTIVE", + ip_addresses=[ + oci.network_load_balancer.models.IpAddress(ip_address="192.168.1.1", is_public=True), + oci.network_load_balancer.models.IpAddress(ip_address="10.0.0.0", is_public=False), + ], + ) + ] ) mock_list_response.has_next_page = False mock_list_response.next_page = None @@ -83,9 +77,7 @@ async def test_list_nlbs_pagination(self, mock_get_client): async with Client(mcp) as client: result = ( - await client.call_tool( - "list_network_load_balancers", {"compartment_id": "c1"} - ) + await client.call_tool("list_network_load_balancers", {"compartment_id": "c1"}) ).structured_content["result"] assert [n["id"] for n in result] == ["n1", "n2", "n3"] @@ -99,9 +91,7 @@ async def test_list_nlbs_error(self, mock_get_client): async with Client(mcp) as client: with pytest.raises(ToolError): - await client.call_tool( - "list_network_load_balancers", {"compartment_id": "c1"} - ) + await client.call_tool("list_network_load_balancers", {"compartment_id": "c1"}) @pytest.mark.asyncio @patch("oracle.oci_network_load_balancer_mcp_server.server.get_nlb_client") @@ -372,16 +362,12 @@ async def test_get_network_load_balancer(self, mock_get_client): mock_get_client.return_value = mock_client mock_resp = create_autospec(oci.response.Response) - mock_resp.data = oci.network_load_balancer.models.NetworkLoadBalancer( - id="nlb1", display_name="nlb" - ) + mock_resp.data = oci.network_load_balancer.models.NetworkLoadBalancer(id="nlb1", display_name="nlb") mock_client.get_network_load_balancer.return_value = mock_resp async with Client(mcp) as client: res = ( - await client.call_tool( - "get_network_load_balancer", {"network_load_balancer_id": "nlb1"} - ) + await client.call_tool("get_network_load_balancer", {"network_load_balancer_id": "nlb1"}) ).structured_content assert res["id"] == "nlb1" @@ -394,9 +380,7 @@ async def test_get_network_load_balancer_error(self, mock_get_client): async with Client(mcp) as client: with pytest.raises(ToolError): - await client.call_tool( - "get_network_load_balancer", {"network_load_balancer_id": "x"} - ) + await client.call_tool("get_network_load_balancer", {"network_load_balancer_id": "x"}) @pytest.mark.asyncio @patch("oracle.oci_network_load_balancer_mcp_server.server.get_nlb_client") @@ -563,12 +547,8 @@ class TestGetClient: "oracle.oci_network_load_balancer_mcp_server.server.oci" ".network_load_balancer.NetworkLoadBalancerClient" ) - @patch( - "oracle.oci_network_load_balancer_mcp_server.server.oci.auth.signers.SecurityTokenSigner" - ) - @patch( - "oracle.oci_network_load_balancer_mcp_server.server.oci.signer.load_private_key_from_file" - ) + @patch("oracle.oci_network_load_balancer_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_network_load_balancer_mcp_server.server.oci.signer.load_private_key_from_file") @patch( "oracle.oci_network_load_balancer_mcp_server.server.open", new_callable=mock_open, @@ -606,14 +586,14 @@ def test_get_nlb_client_with_profile_env( profile_name="MYPROFILE", ) mock_open_file.assert_called_once_with("/abs/path/to/token", "r") - mock_security_token_signer.assert_called_once_with( - "SECURITY_TOKEN", private_key_obj - ) + mock_security_token_signer.assert_called_once_with("SECURITY_TOKEN", private_key_obj) # Ensure user agent was set on the same config dict passed into client args, _ = mock_client.call_args passed_config = args[0] assert passed_config is config - expected_user_agent = f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + expected_user_agent = ( + f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + ) assert passed_config.get("additional_user_agent") == expected_user_agent # And we returned the client instance assert result == mock_client.return_value @@ -622,12 +602,8 @@ def test_get_nlb_client_with_profile_env( "oracle.oci_network_load_balancer_mcp_server.server.oci" ".network_load_balancer.NetworkLoadBalancerClient" ) - @patch( - "oracle.oci_network_load_balancer_mcp_server.server.oci.auth.signers.SecurityTokenSigner" - ) - @patch( - "oracle.oci_network_load_balancer_mcp_server.server.oci.signer.load_private_key_from_file" - ) + @patch("oracle.oci_network_load_balancer_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_network_load_balancer_mcp_server.server.oci.signer.load_private_key_from_file") @patch( "oracle.oci_network_load_balancer_mcp_server.server.open", new_callable=mock_open, @@ -669,9 +645,6 @@ def test_get_nlb_client_uses_default_profile_when_env_missing( cc_args, _ = mock_client.call_args assert cc_args[0] is config assert "additional_user_agent" in config - assert ( - isinstance(config["additional_user_agent"], str) - and "/" in config["additional_user_agent"] - ) + assert isinstance(config["additional_user_agent"], str) and "/" in config["additional_user_agent"] # Returned object is client instance assert srv_client is mock_client.return_value diff --git a/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/models.py b/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/models.py index 47d62767..5fe75c55 100644 --- a/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/models.py +++ b/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/models.py @@ -55,9 +55,7 @@ class Vcn(BaseModel): cidr_blocks: Optional[List[str]] = Field( None, description="The list of IPv4 CIDR blocks the VCN will use." ) - compartment_id: Optional[str] = Field( - None, description="The OCID of the compartment containing the VCN." - ) + compartment_id: Optional[str] = Field(None, description="The OCID of the compartment containing the VCN.") default_dhcp_options_id: Optional[str] = Field( None, description="The OCID for the VCN's default set of DHCP options." ) @@ -71,9 +69,7 @@ class Vcn(BaseModel): None, description="Defined tags for this resource, each key scoped to a namespace.", ) - display_name: Optional[str] = Field( - None, description="A user-friendly name. Does not have to be unique." - ) + display_name: Optional[str] = Field(None, description="A user-friendly name. Does not have to be unique.") dns_label: Optional[str] = Field( None, description="DNS label for the VCN, used to form FQDNs with subnet DNS label and VNIC hostname.", # noqa @@ -107,9 +103,7 @@ class Vcn(BaseModel): None, description="The VCN's domain name, which consists of the VCN's DNS label and the oraclevcn.com domain.", # noqa ) - is_zpr_only: Optional[bool] = Field( - None, description="Indicates whether ZPR Only Mode is enforced." - ) + is_zpr_only: Optional[bool] = Field(None, description="Indicates whether ZPR Only Mode is enforced.") def map_vcn(vcn: oci.core.models.Vcn) -> Vcn | None: @@ -157,9 +151,7 @@ class Subnet(BaseModel): None, description="The subnet's availability domain. Null if this is a regional subnet.", # noqa ) - cidr_block: Optional[str] = Field( - None, description="The IPv4 CIDR block of the subnet." - ) + cidr_block: Optional[str] = Field(None, description="The IPv4 CIDR block of the subnet.") compartment_id: Optional[str] = Field( None, description="The OCID of the compartment containing the subnet." ) @@ -170,9 +162,7 @@ class Subnet(BaseModel): dhcp_options_id: Optional[str] = Field( None, description="The OCID of the set of DHCP options that the subnet uses." ) - display_name: Optional[str] = Field( - None, description="A user-friendly name. Does not have to be unique." - ) + display_name: Optional[str] = Field(None, description="A user-friendly name. Does not have to be unique.") dns_label: Optional[str] = Field( None, description="DNS label for the subnet, used with the VNIC's hostname and the VCN's DNS label to form the FQDN.", # noqa @@ -225,15 +215,9 @@ class Subnet(BaseModel): time_created: Optional[datetime] = Field( None, description="The date and time the subnet was created (RFC3339)." ) - vcn_id: Optional[str] = Field( - None, description="The OCID of the VCN the subnet is in." - ) - virtual_router_ip: Optional[str] = Field( - None, description="The IPv4 address of the virtual router." - ) - virtual_router_mac: Optional[str] = Field( - None, description="The MAC address of the virtual router." - ) + vcn_id: Optional[str] = Field(None, description="The OCID of the VCN the subnet is in.") + virtual_router_ip: Optional[str] = Field(None, description="The IPv4 address of the virtual router.") + virtual_router_mac: Optional[str] = Field(None, description="The MAC address of the virtual router.") def map_subnet(subnet: oci.core.models.Subnet) -> Subnet | None: @@ -298,18 +282,12 @@ class TcpOptions(BaseModel): Supports both singular and list-based port range fields. """ - destination_port_range: Optional[PortRange] = Field( - None, description="Single destination port range." - ) - source_port_range: Optional[PortRange] = Field( - None, description="Single source port range." - ) + destination_port_range: Optional[PortRange] = Field(None, description="Single destination port range.") + source_port_range: Optional[PortRange] = Field(None, description="Single source port range.") destination_port_ranges: Optional[List[PortRange]] = Field( None, description="List of destination port ranges." ) - source_port_ranges: Optional[List[PortRange]] = Field( - None, description="List of source port ranges." - ) + source_port_ranges: Optional[List[PortRange]] = Field(None, description="List of source port ranges.") class UdpOptions(BaseModel): @@ -318,18 +296,12 @@ class UdpOptions(BaseModel): Supports both singular and list-based port range fields. """ - destination_port_range: Optional[PortRange] = Field( - None, description="Single destination port range." - ) - source_port_range: Optional[PortRange] = Field( - None, description="Single source port range." - ) + destination_port_range: Optional[PortRange] = Field(None, description="Single destination port range.") + source_port_range: Optional[PortRange] = Field(None, description="Single source port range.") destination_port_ranges: Optional[List[PortRange]] = Field( None, description="List of destination port ranges." ) - source_port_ranges: Optional[List[PortRange]] = Field( - None, description="List of source port ranges." - ) + source_port_ranges: Optional[List[PortRange]] = Field(None, description="List of source port ranges.") class EgressSecurityRule(BaseModel): @@ -337,12 +309,8 @@ class EgressSecurityRule(BaseModel): Pydantic model mirroring oci.core.models.EgressSecurityRule. """ - description: Optional[str] = Field( - None, description="An optional description of the rule." - ) - destination: Optional[str] = Field( - None, description="The destination CIDR block or service CIDR." - ) + description: Optional[str] = Field(None, description="An optional description of the rule.") + destination: Optional[str] = Field(None, description="The destination CIDR block or service CIDR.") destination_type: Optional[ Literal[ "CIDR_BLOCK", @@ -377,9 +345,7 @@ class IngressSecurityRule(BaseModel): Pydantic model mirroring oci.core.models.IngressSecurityRule. """ - description: Optional[str] = Field( - None, description="An optional description of the rule." - ) + description: Optional[str] = Field(None, description="An optional description of the rule.") icmp_options: Optional[IcmpOptions] = Field( None, description="ICMP options. Required if protocol is ICMP and using ICMP type/code.", # noqa @@ -391,9 +357,7 @@ class IngressSecurityRule(BaseModel): None, description="The transport protocol. Specify either all or the protocol number.", # noqa ) - source: Optional[str] = Field( - None, description="The source CIDR block or service CIDR." - ) + source: Optional[str] = Field(None, description="The source CIDR block or service CIDR.") source_type: Optional[ Literal[ "CIDR_BLOCK", @@ -424,9 +388,7 @@ class SecurityList(BaseModel): None, description="Defined tags for this resource. Each key is predefined and scoped to a namespace.", # noqa ) - display_name: Optional[str] = Field( - None, description="A user-friendly name. Does not have to be unique." - ) + display_name: Optional[str] = Field(None, description="A user-friendly name. Does not have to be unique.") egress_security_rules: Optional[List[EgressSecurityRule]] = Field( None, description="Rules for allowing egress IP packets." ) @@ -449,9 +411,7 @@ class SecurityList(BaseModel): time_created: Optional[datetime] = Field( None, description="The date and time the security list was created (RFC3339)." ) - vcn_id: Optional[str] = Field( - None, description="The OCID of the VCN the security list belongs to." - ) + vcn_id: Optional[str] = Field(None, description="The OCID of the VCN the security list belongs to.") def map_port_range(pr) -> PortRange | None: @@ -487,16 +447,10 @@ def map_tcp_options(tcp) -> TcpOptions | None: if not tcp: return None data = _oci_to_dict(tcp) or {} - dest_range = getattr(tcp, "destination_port_range", None) or data.get( - "destination_port_range" - ) + dest_range = getattr(tcp, "destination_port_range", None) or data.get("destination_port_range") src_range = getattr(tcp, "source_port_range", None) or data.get("source_port_range") - dest_ranges = getattr(tcp, "destination_port_ranges", None) or data.get( - "destination_port_ranges" - ) - src_ranges = getattr(tcp, "source_port_ranges", None) or data.get( - "source_port_ranges" - ) + dest_ranges = getattr(tcp, "destination_port_ranges", None) or data.get("destination_port_ranges") + src_ranges = getattr(tcp, "source_port_ranges", None) or data.get("source_port_ranges") return TcpOptions( destination_port_range=map_port_range(dest_range), source_port_range=map_port_range(src_range), @@ -509,16 +463,10 @@ def map_udp_options(udp) -> UdpOptions | None: if not udp: return None data = _oci_to_dict(udp) or {} - dest_range = getattr(udp, "destination_port_range", None) or data.get( - "destination_port_range" - ) + dest_range = getattr(udp, "destination_port_range", None) or data.get("destination_port_range") src_range = getattr(udp, "source_port_range", None) or data.get("source_port_range") - dest_ranges = getattr(udp, "destination_port_ranges", None) or data.get( - "destination_port_ranges" - ) - src_ranges = getattr(udp, "source_port_ranges", None) or data.get( - "source_port_ranges" - ) + dest_ranges = getattr(udp, "destination_port_ranges", None) or data.get("destination_port_ranges") + src_ranges = getattr(udp, "source_port_ranges", None) or data.get("source_port_ranges") return UdpOptions( destination_port_range=map_port_range(dest_range), source_port_range=map_port_range(src_range), @@ -534,19 +482,12 @@ def map_egress_security_rule(rule) -> EgressSecurityRule | None: return EgressSecurityRule( description=getattr(rule, "description", None) or data.get("description"), destination=getattr(rule, "destination", None) or data.get("destination"), - destination_type=getattr(rule, "destination_type", None) - or data.get("destination_type"), - icmp_options=map_icmp_options( - getattr(rule, "icmp_options", None) or data.get("icmp_options") - ), + destination_type=getattr(rule, "destination_type", None) or data.get("destination_type"), + icmp_options=map_icmp_options(getattr(rule, "icmp_options", None) or data.get("icmp_options")), is_stateless=getattr(rule, "is_stateless", None) or data.get("is_stateless"), protocol=getattr(rule, "protocol", None) or data.get("protocol"), - tcp_options=map_tcp_options( - getattr(rule, "tcp_options", None) or data.get("tcp_options") - ), - udp_options=map_udp_options( - getattr(rule, "udp_options", None) or data.get("udp_options") - ), + tcp_options=map_tcp_options(getattr(rule, "tcp_options", None) or data.get("tcp_options")), + udp_options=map_udp_options(getattr(rule, "udp_options", None) or data.get("udp_options")), ) @@ -556,19 +497,13 @@ def map_ingress_security_rule(rule) -> IngressSecurityRule | None: data = _oci_to_dict(rule) or {} return IngressSecurityRule( description=getattr(rule, "description", None) or data.get("description"), - icmp_options=map_icmp_options( - getattr(rule, "icmp_options", None) or data.get("icmp_options") - ), + icmp_options=map_icmp_options(getattr(rule, "icmp_options", None) or data.get("icmp_options")), is_stateless=getattr(rule, "is_stateless", None) or data.get("is_stateless"), protocol=getattr(rule, "protocol", None) or data.get("protocol"), source=getattr(rule, "source", None) or data.get("source"), source_type=getattr(rule, "source_type", None) or data.get("source_type"), - tcp_options=map_tcp_options( - getattr(rule, "tcp_options", None) or data.get("tcp_options") - ), - udp_options=map_udp_options( - getattr(rule, "udp_options", None) or data.get("udp_options") - ), + tcp_options=map_tcp_options(getattr(rule, "tcp_options", None) or data.get("tcp_options")), + udp_options=map_udp_options(getattr(rule, "udp_options", None) or data.get("udp_options")), ) @@ -591,9 +526,7 @@ def map_security_list( defined_tags=getattr(sl, "defined_tags", None), display_name=getattr(sl, "display_name", None), egress_security_rules=( - [map_egress_security_rule(r) for r in (egress_rules or [])] - if egress_rules is not None - else None + [map_egress_security_rule(r) for r in (egress_rules or [])] if egress_rules is not None else None ), freeform_tags=getattr(sl, "freeform_tags", None), id=getattr(sl, "id", None), @@ -626,15 +559,11 @@ class NetworkSecurityGroup(BaseModel): None, description="Defined tags for this resource. Each key is predefined and scoped to a namespace.", # noqa ) - display_name: Optional[str] = Field( - None, description="A user-friendly name. Does not have to be unique." - ) + display_name: Optional[str] = Field(None, description="A user-friendly name. Does not have to be unique.") freeform_tags: Optional[Dict[str, str]] = Field( None, description="Free-form tags for this resource as simple key/value pairs." ) - id: Optional[str] = Field( - None, description="The OCID of the network security group." - ) + id: Optional[str] = Field(None, description="The OCID of the network security group.") lifecycle_state: Optional[ Literal[ "PROVISIONING", @@ -687,16 +616,10 @@ class Request(BaseModel): method: Optional[str] = Field(None, description="The HTTP method.") url: Optional[str] = Field(None, description="URL that will serve the request.") - query_params: Optional[Dict[str, Any]] = Field( - None, description="Query parameters in the URL." - ) - header_params: Optional[Dict[str, Any]] = Field( - None, description="Request header parameters." - ) + query_params: Optional[Dict[str, Any]] = Field(None, description="Query parameters in the URL.") + header_params: Optional[Dict[str, Any]] = Field(None, description="Request header parameters.") body: Optional[Any] = Field(None, description="Request body.") - response_type: Optional[str] = Field( - None, description="Expected response data type." - ) + response_type: Optional[str] = Field(None, description="Expected response data type.") enforce_content_headers: Optional[bool] = Field( None, description=( @@ -712,24 +635,12 @@ class Response(BaseModel): """ status: Optional[int] = Field(None, description="The HTTP status code.") - headers: Optional[Dict[str, Any]] = Field( - None, description="The HTTP headers (case-insensitive keys)." - ) - data: Optional[Any] = Field( - None, description="The response data. Type depends on the request." - ) - request: Optional[Request] = Field( - None, description="The corresponding request for this response." - ) - next_page: Optional[str] = Field( - None, description="The value of the opc-next-page response header." - ) - request_id: Optional[str] = Field( - None, description="The ID of the request that generated this response." - ) - has_next_page: Optional[bool] = Field( - None, description="Whether there is a next page of results." - ) + headers: Optional[Dict[str, Any]] = Field(None, description="The HTTP headers (case-insensitive keys).") + data: Optional[Any] = Field(None, description="The response data. Type depends on the request.") + request: Optional[Request] = Field(None, description="The corresponding request for this response.") + next_page: Optional[str] = Field(None, description="The value of the opc-next-page response header.") + request_id: Optional[str] = Field(None, description="The ID of the request that generated this response.") + has_next_page: Optional[bool] = Field(None, description="Whether there is a next page of results.") def map_request(req) -> Request | None: @@ -936,9 +847,7 @@ class Vnic(BaseModel): "Example: `true`" ), ) - subnet_id: Optional[str] = Field( - None, description="The OCID of the subnet the VNIC is in." - ) + subnet_id: Optional[str] = Field(None, description="The OCID of the subnet the VNIC is in.") time_created: Optional[datetime] = Field( None, description=( 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 ffd2e032..e569abde 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 @@ -140,9 +140,7 @@ def list_subnets(compartment_id: str, vcn_id: str = None) -> list[Subnet]: next_page: str = None while has_next_page: - response = client.list_subnets( - compartment_id=compartment_id, vcn_id=vcn_id, page=next_page - ) + response = client.list_subnets(compartment_id=compartment_id, vcn_id=vcn_id, page=next_page) has_next_page = response.has_next_page next_page = response.next_page if hasattr(response, "next_page") else None @@ -175,9 +173,7 @@ def get_subnet(subnet_id: str) -> Subnet: @mcp.tool -def create_subnet( - vcn_id: str, compartment_id: str, cidr_block: str, display_name: str -) -> Subnet: +def create_subnet(vcn_id: str, compartment_id: str, cidr_block: str, display_name: str) -> Subnet: try: client = get_networking_client() @@ -304,9 +300,7 @@ def get_network_security_group( try: client = get_networking_client() - response: oci.response.Response = client.get_network_security_group( - network_security_group_id - ) + response: oci.response.Response = client.get_network_security_group(network_security_group_id) data: oci.core.models.Subnet = response.data logger.info("Found Network Security Group") return map_network_security_group(data) diff --git a/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/tests/test_networking_models.py b/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/tests/test_networking_models.py index 177c2d18..b31fac89 100644 --- a/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/tests/test_networking_models.py +++ b/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/tests/test_networking_models.py @@ -165,9 +165,7 @@ def test_map_security_list(self): source="0.0.0.0/0", protocol="6", tcp_options=oci.core.models.TcpOptions( - destination_port_range=oci.core.models.PortRange( - min=443, max=443 - ) + destination_port_range=oci.core.models.PortRange(min=443, max=443) ), ) ], @@ -177,9 +175,7 @@ def test_map_security_list(self): assert isinstance(sl, SecurityList) assert sl.id == "sl1" assert sl.egress_security_rules and sl.ingress_security_rules - assert ( - sl.ingress_security_rules[0].tcp_options.destination_port_range.min == 443 - ) + assert sl.ingress_security_rules[0].tcp_options.destination_port_range.min == 443 def test_map_network_security_group(self): nsg_src = oci.core.models.NetworkSecurityGroup( diff --git a/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/tests/test_networking_tools.py b/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/tests/test_networking_tools.py index aa7ff47c..5920edcf 100644 --- a/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/tests/test_networking_tools.py +++ b/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/tests/test_networking_tools.py @@ -64,9 +64,9 @@ async def test_list_vcns_pagination(self, mock_get_client): mock_client.list_vcns.side_effect = [first, second] async with Client(mcp) as client: - result = ( - await client.call_tool("list_vcns", {"compartment_id": "c1"}) - ).structured_content["result"] + result = (await client.call_tool("list_vcns", {"compartment_id": "c1"})).structured_content[ + "result" + ] assert [v["id"] for v in result] == ["v1", "v2", "v3"] @@ -231,9 +231,7 @@ async def test_list_subnets_pagination(self, mock_get_client): async with Client(mcp) as client: result = ( - await client.call_tool( - "list_subnets", {"compartment_id": "c1", "vcn_id": "v1"} - ) + await client.call_tool("list_subnets", {"compartment_id": "c1", "vcn_id": "v1"}) ).structured_content["result"] assert [s["id"] for s in result] == ["s1", "s2", "s3"] @@ -437,9 +435,7 @@ async def test_list_network_security_groups(self, mock_get_client): async def test_list_network_security_groups_error(self, mock_get_client): mock_client = MagicMock() mock_get_client.return_value = mock_client - mock_client.list_network_security_groups.side_effect = Exception( - "nsg list fail" - ) + mock_client.list_network_security_groups.side_effect = Exception("nsg list fail") async with Client(mcp) as client: with pytest.raises(ToolError): @@ -499,9 +495,7 @@ async def test_get_vnic(self, mock_get_client): async with Client(mcp) as client: # Expect ToolError due to schema validation issue in installed package with pytest.raises(ToolError): - call_tool_result = await client.call_tool( - "get_vnic", {"vnic_id": "vnic1"} - ) + call_tool_result = await client.call_tool("get_vnic", {"vnic_id": "vnic1"}) result = call_tool_result.structured_content assert result["id"] == "vnic1" @@ -557,12 +551,8 @@ def test_main_with_only_port(self, mock_getenv, mock_mcp_run): class TestGetClient: @patch("oracle.oci_networking_mcp_server.server.oci.core.VirtualNetworkClient") - @patch( - "oracle.oci_networking_mcp_server.server.oci.auth.signers.SecurityTokenSigner" - ) - @patch( - "oracle.oci_networking_mcp_server.server.oci.signer.load_private_key_from_file" - ) + @patch("oracle.oci_networking_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_networking_mcp_server.server.oci.signer.load_private_key_from_file") @patch( "oracle.oci_networking_mcp_server.server.open", new_callable=mock_open, @@ -600,25 +590,21 @@ def test_get_networking_client_with_profile_env( profile_name="MYPROFILE", ) mock_open_file.assert_called_once_with("/abs/path/to/token", "r") - mock_security_token_signer.assert_called_once_with( - "SECURITY_TOKEN", private_key_obj - ) + mock_security_token_signer.assert_called_once_with("SECURITY_TOKEN", private_key_obj) # Ensure user agent was set on the same config dict passed into client args, _ = mock_client.call_args passed_config = args[0] assert passed_config is config - expected_user_agent = f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + expected_user_agent = ( + f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + ) assert passed_config.get("additional_user_agent") == expected_user_agent # And we returned the client instance assert result == mock_client.return_value @patch("oracle.oci_networking_mcp_server.server.oci.core.VirtualNetworkClient") - @patch( - "oracle.oci_networking_mcp_server.server.oci.auth.signers.SecurityTokenSigner" - ) - @patch( - "oracle.oci_networking_mcp_server.server.oci.signer.load_private_key_from_file" - ) + @patch("oracle.oci_networking_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_networking_mcp_server.server.oci.signer.load_private_key_from_file") @patch( "oracle.oci_networking_mcp_server.server.open", new_callable=mock_open, @@ -660,9 +646,6 @@ def test_get_networking_client_uses_default_profile_when_env_missing( cc_args, _ = mock_client.call_args assert cc_args[0] is config assert "additional_user_agent" in config - assert ( - isinstance(config["additional_user_agent"], str) - and "/" in config["additional_user_agent"] - ) + assert isinstance(config["additional_user_agent"], str) and "/" in config["additional_user_agent"] # Returned object is client instance assert srv_client is mock_client.return_value diff --git a/src/oci-object-storage-mcp-server/oracle/oci_object_storage_mcp_server/models.py b/src/oci-object-storage-mcp-server/oracle/oci_object_storage_mcp_server/models.py index 162b531f..472d5788 100644 --- a/src/oci-object-storage-mcp-server/oracle/oci_object_storage_mcp_server/models.py +++ b/src/oci-object-storage-mcp-server/oracle/oci_object_storage_mcp_server/models.py @@ -14,7 +14,7 @@ class Bucket(BaseModel): namespace: Optional[str] = Field( None, - description=("The Object Storage namespace in which the bucket " "resides."), + description=("The Object Storage namespace in which the bucket resides."), ) name: Optional[str] = Field( None, @@ -22,11 +22,11 @@ class Bucket(BaseModel): ) compartment_id: Optional[str] = Field( None, - description=("The compartment ID in which the bucket is " "authorized."), + description=("The compartment ID in which the bucket is authorized."), ) metadata: Optional[Dict[str, str]] = Field( None, - description=("Arbitrary string keys and values for user-defined " "metadata."), + description=("Arbitrary string keys and values for user-defined metadata."), ) created_by: Optional[str] = Field( None, @@ -50,10 +50,7 @@ class Bucket(BaseModel): ) object_events_enabled: Optional[bool] = Field( None, - description=( - "Whether or not events are emitted for object state " - "changes in this bucket." - ), + description=("Whether or not events are emitted for object state changes in this bucket."), ) freeform_tags: Optional[Dict[str, str]] = Field( None, @@ -65,17 +62,11 @@ class Bucket(BaseModel): ) kms_key_id: Optional[str] = Field( None, - description=( - "The OCID of a master encryption key used to call " - "the Key Management service." - ), + description=("The OCID of a master encryption key used to call the Key Management service."), ) object_lifecycle_policy_etag: Optional[str] = Field( None, - description=( - "The entity tag (ETag) for the live object lifecycle " - "policy on the bucket." - ), + description=("The entity tag (ETag) for the live object lifecycle policy on the bucket."), ) approximate_count: Optional[int] = Field( None, @@ -83,9 +74,7 @@ class Bucket(BaseModel): ) approximate_size: Optional[int] = Field( None, - description=( - "The approximate total size in bytes of all objects " "in the bucket." - ), + description=("The approximate total size in bytes of all objects in the bucket."), ) replication_enabled: Optional[bool] = Field( None, @@ -123,20 +112,17 @@ class NamespaceMetadata(BaseModel): namespace: str = Field( ..., - description=("The Object Storage namespace to which the metadata " "belongs."), + description=("The Object Storage namespace to which the metadata belongs."), ) default_s3_compartment_id: Optional[str] = Field( None, description=( - "If set, specifies the default compartment assignment " - "for the Amazon S3 Compatibility API." + "If set, specifies the default compartment assignment for the Amazon S3 Compatibility API." ), ) default_swift_compartment_id: Optional[str] = Field( None, - description=( - "If set, specifies the default compartment assignment " "for the Swift API." - ), + description=("If set, specifies the default compartment assignment for the Swift API."), ) class Config: @@ -155,14 +141,11 @@ class CreateBucketDetails(BaseModel): ) compartment_id: str = Field( ..., - description=("The ID of the compartment in which to create the " "bucket."), + description=("The ID of the compartment in which to create the bucket."), ) metadata: Optional[Dict[str, str]] = Field( None, - description=( - "Arbitrary string, up to 4KB, of keys and values for " - "user-defined metadata." - ), + description=("Arbitrary string, up to 4KB, of keys and values for user-defined metadata."), ) public_access_type: Optional[str] = Field( None, @@ -174,10 +157,7 @@ class CreateBucketDetails(BaseModel): ) object_events_enabled: Optional[bool] = Field( None, - description=( - "Whether or not events are emitted for object state " - "changes in this bucket." - ), + description=("Whether or not events are emitted for object state changes in this bucket."), ) freeform_tags: Optional[Dict[str, str]] = Field( None, @@ -189,10 +169,7 @@ class CreateBucketDetails(BaseModel): ) kms_key_id: Optional[str] = Field( None, - description=( - "The OCID of a master encryption key used to call " - "the Key Management service." - ), + description=("The OCID of a master encryption key used to call the Key Management service."), ) versioning: Optional[str] = Field( None, @@ -210,7 +187,7 @@ class Config: class BucketSummary(BaseModel): namespace: Optional[str] = Field( None, - description=("The Object Storage namespace in which the bucket " "lives."), + description=("The Object Storage namespace in which the bucket lives."), ) name: Optional[str] = Field( None, @@ -218,7 +195,7 @@ class BucketSummary(BaseModel): ) compartment_id: Optional[str] = Field( None, - description=("The compartment ID in which the bucket is " "authorized."), + description=("The compartment ID in which the bucket is authorized."), ) created_by: Optional[str] = Field( None, @@ -372,9 +349,7 @@ class ObjectVersionCollection(BaseModel): Pydantic model mirroring the fields of oci.object_storage.models.ObjectVersionCollection. """ - items: List[ObjectVersionSummary] = Field( - ..., description="An array of object version summaries." - ) + items: List[ObjectVersionSummary] = Field(..., description="An array of object version summaries.") prefixes: Optional[List[str]] = Field( None, description=( @@ -441,9 +416,7 @@ def map_bucket(bucket: oci.object_storage.models.Bucket) -> Bucket: freeform_tags=getattr(bucket, "freeform_tags", None), defined_tags=getattr(bucket, "defined_tags", None), kms_key_id=getattr(bucket, "kms_key_id", None), - object_lifecycle_policy_etag=getattr( - bucket, "object_lifecycle_policy_etag", None - ), + object_lifecycle_policy_etag=getattr(bucket, "object_lifecycle_policy_etag", None), approximate_count=getattr(bucket, "approximate_count", None), approximate_size=getattr(bucket, "approximate_size", None), replication_enabled=getattr(bucket, "replication_enabled", None), 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 5a6026c0..c9b7d1b9 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 @@ -148,9 +148,7 @@ def list_object_versions( fields="timeModified", ).data - versioned_objects = [ - map_object_version_summary(obj) for obj in list_object_versions.items - ] + versioned_objects = [map_object_version_summary(obj) for obj in list_object_versions.items] prefixes = list_object_versions.prefixes if list_object_versions.prefixes else [] return ObjectVersionCollection(items=versioned_objects, prefixes=prefixes) @@ -199,9 +197,7 @@ def upload_object( logger.info("Checking file at path: %s", file_path) try: with open(file_path, "rb") as file: - object_storage_client.put_object( - namespace_name, bucket_name, object_name, file - ) + object_storage_client.put_object(namespace_name, bucket_name, object_name, file) return {"message": "Object uploaded successfully"} except Exception as e: return {"error": str(e)} diff --git a/src/oci-object-storage-mcp-server/oracle/oci_object_storage_mcp_server/tests/test_object_storage_tools.py b/src/oci-object-storage-mcp-server/oracle/oci_object_storage_mcp_server/tests/test_object_storage_tools.py index 591d0542..6c0d86ad 100644 --- a/src/oci-object-storage-mcp-server/oracle/oci_object_storage_mcp_server/tests/test_object_storage_tools.py +++ b/src/oci-object-storage-mcp-server/oracle/oci_object_storage_mcp_server/tests/test_object_storage_tools.py @@ -33,9 +33,7 @@ async def test_get_namespace(self, mock_get_client): mock_client.get_namespace.return_value = mock_namespace_response async with Client(mcp) as client: - response = await client.call_tool( - "get_namespace", {"compartment_id": "test_compartment"} - ) + response = await client.call_tool("get_namespace", {"compartment_id": "test_compartment"}) result = response.content[0].text assert result == "test_namespace" @@ -62,9 +60,7 @@ async def test_list_buckets(self, mock_get_client): async with Client(mcp) as client: result = ( - await client.call_tool( - "list_buckets", {"compartment_id": "test_compartment"} - ) + await client.call_tool("list_buckets", {"compartment_id": "test_compartment"}) ).structured_content["result"] assert len(result) == 1 @@ -265,10 +261,7 @@ async def test_upload_object_error(self, mock_get_client, tmp_path): ).structured_content assert "error" in result - assert ( - "No such file" in result["error"] - or "No such file or directory" in result["error"] - ) + assert "No such file" in result["error"] or "No such file or directory" in result["error"] class TestServer: @@ -322,15 +315,9 @@ def test_main_with_only_port(self, mock_getenv, mock_mcp_run): class TestGetClient: - @patch( - "oracle.oci_object_storage_mcp_server.server.oci.object_storage.ObjectStorageClient" - ) - @patch( - "oracle.oci_object_storage_mcp_server.server.oci.auth.signers.SecurityTokenSigner" - ) - @patch( - "oracle.oci_object_storage_mcp_server.server.oci.signer.load_private_key_from_file" - ) + @patch("oracle.oci_object_storage_mcp_server.server.oci.object_storage.ObjectStorageClient") + @patch("oracle.oci_object_storage_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_object_storage_mcp_server.server.oci.signer.load_private_key_from_file") @patch( "oracle.oci_object_storage_mcp_server.server.open", new_callable=mock_open, @@ -368,27 +355,21 @@ def test_get_object_storage_client_with_profile_env( profile_name="MYPROFILE", ) mock_open_file.assert_called_once_with("/abs/path/to/token", "r") - mock_security_token_signer.assert_called_once_with( - "SECURITY_TOKEN", private_key_obj - ) + mock_security_token_signer.assert_called_once_with("SECURITY_TOKEN", private_key_obj) # Ensure user agent was set on the same config dict passed into client args, _ = mock_client.call_args passed_config = args[0] assert passed_config is config - expected_user_agent = f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + expected_user_agent = ( + f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + ) assert passed_config.get("additional_user_agent") == expected_user_agent # And we returned the client instance assert result == mock_client.return_value - @patch( - "oracle.oci_object_storage_mcp_server.server.oci.object_storage.ObjectStorageClient" - ) - @patch( - "oracle.oci_object_storage_mcp_server.server.oci.auth.signers.SecurityTokenSigner" - ) - @patch( - "oracle.oci_object_storage_mcp_server.server.oci.signer.load_private_key_from_file" - ) + @patch("oracle.oci_object_storage_mcp_server.server.oci.object_storage.ObjectStorageClient") + @patch("oracle.oci_object_storage_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_object_storage_mcp_server.server.oci.signer.load_private_key_from_file") @patch( "oracle.oci_object_storage_mcp_server.server.open", new_callable=mock_open, @@ -430,9 +411,6 @@ def test_get_object_storage_client_uses_default_profile_when_env_missing( cc_args, _ = mock_client.call_args assert cc_args[0] is config assert "additional_user_agent" in config - assert ( - isinstance(config["additional_user_agent"], str) - and "/" in config["additional_user_agent"] - ) + assert isinstance(config["additional_user_agent"], str) and "/" in config["additional_user_agent"] # Returned object is client instance assert srv_client is mock_client.return_value diff --git a/src/oci-registry-mcp-server/oracle/oci_registry_mcp_server/models.py b/src/oci-registry-mcp-server/oracle/oci_registry_mcp_server/models.py index 434cf246..a143468f 100644 --- a/src/oci-registry-mcp-server/oracle/oci_registry_mcp_server/models.py +++ b/src/oci-registry-mcp-server/oracle/oci_registry_mcp_server/models.py @@ -38,11 +38,9 @@ class ContainerRepositoryReadme(BaseModel): content: Optional[str] = Field( None, description="Readme content. Avoid entering confidential information." ) - format: Optional[Literal["TEXT_MARKDOWN", "TEXT_PLAIN", "UNKNOWN_ENUM_VALUE"]] = ( - Field( - None, - description="Readme format. Supported formats are text/plain and text/markdown.", - ) + format: Optional[Literal["TEXT_MARKDOWN", "TEXT_PLAIN", "UNKNOWN_ENUM_VALUE"]] = Field( + None, + description="Readme format. Supported formats are text/plain and text/markdown.", ) @@ -69,9 +67,7 @@ class ContainerRepository(BaseModel): created_by: Optional[str] = Field( None, description="The id of the user or principal that created the resource." ) - display_name: Optional[str] = Field( - None, description="The container repository name." - ) + display_name: Optional[str] = Field(None, description="The container repository name.") id: Optional[str] = Field(None, description="The OCID of the container repository.") image_count: Optional[int] = Field(None, description="Total number of images.") is_immutable: Optional[bool] = Field( @@ -87,12 +83,10 @@ class ContainerRepository(BaseModel): layers_size_in_bytes: Optional[int] = Field( None, description="Total storage in bytes consumed by layers." ) - lifecycle_state: Optional[ - Literal["AVAILABLE", "DELETING", "DELETED", "UNKNOWN_ENUM_VALUE"] - ] = Field(None, description="The current state of the container repository.") - readme: Optional[ContainerRepositoryReadme] = Field( - None, description="The repository readme." + lifecycle_state: Optional[Literal["AVAILABLE", "DELETING", "DELETED", "UNKNOWN_ENUM_VALUE"]] = Field( + None, description="The current state of the container repository." ) + readme: Optional[ContainerRepositoryReadme] = Field(None, description="The repository readme.") time_created: Optional[datetime] = Field( None, description="An RFC 3339 timestamp indicating when the repository was created.", @@ -107,9 +101,7 @@ class ContainerRepository(BaseModel): namespace: Optional[str] = Field( None, description="The tenancy namespace used in the container repository path." ) - freeform_tags: Optional[Dict[str, str]] = Field( - None, description="Free-form tags for this resource." - ) + freeform_tags: Optional[Dict[str, str]] = Field(None, description="Free-form tags for this resource.") defined_tags: Optional[Dict[str, Dict[str, Any]]] = Field( None, description="Defined tags for this resource." ) @@ -160,16 +152,10 @@ class Request(BaseModel): method: Optional[str] = Field(None, description="The HTTP method.") url: Optional[str] = Field(None, description="URL that will serve the request.") - query_params: Optional[Dict[str, Any]] = Field( - None, description="Query parameters in the URL." - ) - header_params: Optional[Dict[str, Any]] = Field( - None, description="Request header parameters." - ) + query_params: Optional[Dict[str, Any]] = Field(None, description="Query parameters in the URL.") + header_params: Optional[Dict[str, Any]] = Field(None, description="Request header parameters.") body: Optional[Any] = Field(None, description="Request body.") - response_type: Optional[str] = Field( - None, description="Expected response data type." - ) + response_type: Optional[str] = Field(None, description="Expected response data type.") enforce_content_headers: Optional[bool] = Field( None, description=( @@ -185,24 +171,12 @@ class Response(BaseModel): """ status: Optional[int] = Field(None, description="The HTTP status code.") - headers: Optional[Dict[str, Any]] = Field( - None, description="The HTTP headers (case-insensitive keys)." - ) - data: Optional[Any] = Field( - None, description="The response data. Type depends on the request." - ) - request: Optional[Request] = Field( - None, description="The corresponding request for this response." - ) - next_page: Optional[str] = Field( - None, description="The value of the opc-next-page response header." - ) - request_id: Optional[str] = Field( - None, description="The ID of the request that generated this response." - ) - has_next_page: Optional[bool] = Field( - None, description="Whether there is a next page of results." - ) + headers: Optional[Dict[str, Any]] = Field(None, description="The HTTP headers (case-insensitive keys).") + data: Optional[Any] = Field(None, description="The response data. Type depends on the request.") + request: Optional[Request] = Field(None, description="The corresponding request for this response.") + next_page: Optional[str] = Field(None, description="The value of the opc-next-page response header.") + request_id: Optional[str] = Field(None, description="The ID of the request that generated this response.") + has_next_page: Optional[bool] = Field(None, description="Whether there is a next page of results.") def map_request(req) -> Request | None: 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 ee4e011f..689a3049 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 @@ -86,7 +86,7 @@ def list_container_repositories( @mcp.tool def get_container_repository( - repository_id: str = Field(..., description="The OCID of the container repository") + repository_id: str = Field(..., description="The OCID of the container repository"), ) -> ContainerRepository: try: client = get_ocir_client() @@ -116,24 +116,18 @@ def create_container_repository( min_length=1, max_length=255, ), - is_public: bool = Field( - False, description="Whether or not the repository is public" - ), + is_public: bool = Field(False, description="Whether or not the repository is public"), ) -> ContainerRepository: try: client = get_ocir_client() - create_repository_details = ( - oci.artifacts.models.CreateContainerRepositoryDetails( - compartment_id=compartment_id, - display_name=repository_name, - is_public=is_public, - ) + create_repository_details = oci.artifacts.models.CreateContainerRepositoryDetails( + compartment_id=compartment_id, + display_name=repository_name, + is_public=is_public, ) - response: oci.response.Response = client.create_container_repository( - create_repository_details - ) + response: oci.response.Response = client.create_container_repository(create_repository_details) data: oci.artifacts.models.ContainerRepository = response.data logger.info("Created Container Repository") return map_container_repository(data) @@ -145,14 +139,12 @@ def create_container_repository( @mcp.tool def delete_container_repository( - repository_id: str = Field(..., description="The OCID of the container repository") + repository_id: str = Field(..., description="The OCID of the container repository"), ) -> Response: try: client = get_ocir_client() - response: oci.response.Response = client.delete_container_repository( - repository_id - ) + response: oci.response.Response = client.delete_container_repository(repository_id) logger.info("Deleted Container Repository") return map_response(response) diff --git a/src/oci-registry-mcp-server/oracle/oci_registry_mcp_server/tests/test_registry_models.py b/src/oci-registry-mcp-server/oracle/oci_registry_mcp_server/tests/test_registry_models.py index a8ba9cf4..c88bd9b5 100644 --- a/src/oci-registry-mcp-server/oracle/oci_registry_mcp_server/tests/test_registry_models.py +++ b/src/oci-registry-mcp-server/oracle/oci_registry_mcp_server/tests/test_registry_models.py @@ -201,9 +201,7 @@ def test_oci_to_dict_passthrough(self) -> None: data = {"a": 1} assert _oci_to_dict(data) == {"a": 1} - def test_oci_to_dict_fallback_filters_private( - self, monkeypatch: pytest.MonkeyPatch - ) -> None: + def test_oci_to_dict_fallback_filters_private(self, monkeypatch: pytest.MonkeyPatch) -> None: class Dummy: def __init__(self) -> None: self.a = 1 diff --git a/src/oci-registry-mcp-server/oracle/oci_registry_mcp_server/tests/test_registry_tools.py b/src/oci-registry-mcp-server/oracle/oci_registry_mcp_server/tests/test_registry_tools.py index 9591d877..85327a85 100644 --- a/src/oci-registry-mcp-server/oracle/oci_registry_mcp_server/tests/test_registry_tools.py +++ b/src/oci-registry-mcp-server/oracle/oci_registry_mcp_server/tests/test_registry_tools.py @@ -133,10 +133,7 @@ async def test_list_container_repositories_raises(self, mock_get_client): ) assert call_tool_result.is_error is True # The error text is present in the first content block - assert any( - getattr(block, "text", "").find("boom") != -1 - for block in call_tool_result.content - ) + assert any(getattr(block, "text", "").find("boom") != -1 for block in call_tool_result.content) @pytest.mark.asyncio @patch("oracle.oci_registry_mcp_server.server.get_ocir_client") @@ -247,9 +244,7 @@ def test_main_with_only_port(self, mock_getenv, mock_mcp_run): class TestGetClient: @patch("oracle.oci_registry_mcp_server.server.oci.artifacts.ArtifactsClient") @patch("oracle.oci_registry_mcp_server.server.oci.auth.signers.SecurityTokenSigner") - @patch( - "oracle.oci_registry_mcp_server.server.oci.signer.load_private_key_from_file" - ) + @patch("oracle.oci_registry_mcp_server.server.oci.signer.load_private_key_from_file") @patch( "oracle.oci_registry_mcp_server.server.open", new_callable=mock_open, @@ -287,23 +282,21 @@ def test_get_ocir_client_with_profile_env( profile_name="MYPROFILE", ) mock_open_file.assert_called_once_with("/abs/path/to/token", "r") - mock_security_token_signer.assert_called_once_with( - "SECURITY_TOKEN", private_key_obj - ) + mock_security_token_signer.assert_called_once_with("SECURITY_TOKEN", private_key_obj) # Ensure user agent was set on the same config dict passed into client args, _ = mock_client.call_args passed_config = args[0] assert passed_config is config - expected_user_agent = f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + expected_user_agent = ( + f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + ) assert passed_config.get("additional_user_agent") == expected_user_agent # And we returned the client instance assert result == mock_client.return_value @patch("oracle.oci_registry_mcp_server.server.oci.artifacts.ArtifactsClient") @patch("oracle.oci_registry_mcp_server.server.oci.auth.signers.SecurityTokenSigner") - @patch( - "oracle.oci_registry_mcp_server.server.oci.signer.load_private_key_from_file" - ) + @patch("oracle.oci_registry_mcp_server.server.oci.signer.load_private_key_from_file") @patch( "oracle.oci_registry_mcp_server.server.open", new_callable=mock_open, @@ -345,9 +338,6 @@ def test_get_ocir_client_uses_default_profile_when_env_missing( cc_args, _ = mock_client.call_args assert cc_args[0] is config assert "additional_user_agent" in config - assert ( - isinstance(config["additional_user_agent"], str) - and "/" in config["additional_user_agent"] - ) + assert isinstance(config["additional_user_agent"], str) and "/" in config["additional_user_agent"] # Returned object is client instance assert srv_client is mock_client.return_value diff --git a/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/models.py b/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/models.py index e8548197..966d4b80 100644 --- a/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/models.py +++ b/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/models.py @@ -70,9 +70,7 @@ class ResourceSummary(BaseModel): compartment_id: Optional[str] = Field( None, description="The OCID of the compartment that contains this resource." ) - time_created: Optional[datetime] = Field( - None, description="The time that this resource was created." - ) + time_created: Optional[datetime] = Field(None, description="The time that this resource was created.") display_name: Optional[str] = Field( None, description="The display name (or name) of this resource, if one exists." ) 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 f35e4afa..fc5f8fe6 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 @@ -50,9 +50,7 @@ def list_all_resources( description="The tenancy ID, which can be used to specify a different tenancy " "(for cross-tenancy authorization) when searching for resources in a different tenancy", ), - compartment_id: str = Field( - ..., description="The OCID of the compartment to list from" - ), + compartment_id: str = Field(..., description="The OCID of the compartment to list from"), limit: Optional[int] = Field( None, description="The maximum amount of resources to return. If None, there is no limit.", @@ -102,12 +100,8 @@ def search_resources( description="The tenancy ID, which can be used to specify a different tenancy " "(for cross-tenancy authorization) when searching for resources in a different tenancy", ), - compartment_id: str = Field( - ..., description="The OCID of the compartment to list from" - ), - display_name: str = Field( - ..., description="The display name (full or substring) of the resource" - ), + compartment_id: str = Field(..., description="The OCID of the compartment to list from"), + display_name: str = Field(..., description="The display name (full or substring) of the resource"), limit: Optional[int] = Field( None, description="The maximum amount of resources to return. If None, there is no limit.", @@ -155,9 +149,7 @@ def search_resources( raise e -@mcp.tool( - description="Searches for the presence of the search string in all resource fields" -) +@mcp.tool(description="Searches for the presence of the search string in all resource fields") def search_resources_free_form( tenant_id: str = Field( ..., @@ -214,9 +206,7 @@ def search_resources_by_type( description="The tenancy ID, which can be used to specify a different tenancy " "(for cross-tenancy authorization) when searching for resources in a different tenancy", ), - compartment_id: str = Field( - ..., description="The OCID of the compartment to list from" - ), + compartment_id: str = Field(..., description="The OCID of the compartment to list from"), resource_type: str = Field( ..., description="The source type to search by" @@ -244,8 +234,7 @@ def search_resources_by_type( "search_details": StructuredSearchDetails( type="Structured", query=( - f"query {resource_type.lower()} " - f"resources where compartmentId = '{compartment_id}'" + f"query {resource_type.lower()} resources where compartmentId = '{compartment_id}'" ), ), "page": next_page, diff --git a/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/tests/test_resource_search_models.py b/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/tests/test_resource_search_models.py index 1c21aff5..1c88a11f 100644 --- a/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/tests/test_resource_search_models.py +++ b/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/tests/test_resource_search_models.py @@ -40,9 +40,7 @@ def test_map_resource_summary_full(self): freeform_tags={"Owner": "Dev"}, defined_tags={"Operations": {"CostCenter": "42"}}, system_tags={"orcl-cloud": {"free-tier-retain": True}}, - search_context=SimpleNamespace( - highlights={"displayName": ["

My Instance

"]} - ), + search_context=SimpleNamespace(highlights={"displayName": ["

My Instance

"]}), identity_context={"keyA": "valueA"}, additional_details={"attachedVnic": []}, ) @@ -60,9 +58,7 @@ def test_map_resource_summary_full(self): assert mapped.defined_tags == {"Operations": {"CostCenter": "42"}} assert mapped.system_tags == {"orcl-cloud": {"free-tier-retain": True}} assert mapped.search_context is not None - assert mapped.search_context.highlights == { - "displayName": ["

My Instance

"] - } + assert mapped.search_context.highlights == {"displayName": ["

My Instance

"]} assert mapped.identity_context == {"keyA": "valueA"} assert mapped.additional_details == {"attachedVnic": []} diff --git a/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/tests/test_resource_search_tools.py b/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/tests/test_resource_search_tools.py index cc75b69e..868c8e11 100644 --- a/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/tests/test_resource_search_tools.py +++ b/src/oci-resource-search-mcp-server/oracle/oci_resource_search_mcp_server/tests/test_resource_search_tools.py @@ -22,17 +22,15 @@ async def test_list_all_resources(self, mock_get_client): mock_get_client.return_value = mock_client mock_search_response = create_autospec(oci.response.Response) - mock_search_response.data = ( - oci.resource_search.models.ResourceSummaryCollection( - items=[ - oci.resource_search.models.ResourceSummary( - identifier="resource1", - display_name="Resource 1", - resource_type="instance", - lifecycle_state="RUNNING", - ) - ] - ) + mock_search_response.data = oci.resource_search.models.ResourceSummaryCollection( + items=[ + oci.resource_search.models.ResourceSummary( + identifier="resource1", + display_name="Resource 1", + resource_type="instance", + lifecycle_state="RUNNING", + ) + ] ) mock_search_response.has_next_page = False mock_search_response.next_page = None @@ -59,17 +57,15 @@ async def test_search_resources(self, mock_get_client): mock_get_client.return_value = mock_client mock_search_response = create_autospec(oci.response.Response) - mock_search_response.data = ( - oci.resource_search.models.ResourceSummaryCollection( - items=[ - oci.resource_search.models.ResourceSummary( - identifier="resource1", - display_name="Resource 1", - resource_type="instance", - lifecycle_state="RUNNING", - ) - ] - ) + mock_search_response.data = oci.resource_search.models.ResourceSummaryCollection( + items=[ + oci.resource_search.models.ResourceSummary( + identifier="resource1", + display_name="Resource 1", + resource_type="instance", + lifecycle_state="RUNNING", + ) + ] ) mock_search_response.has_next_page = False mock_search_response.next_page = None @@ -97,17 +93,15 @@ async def test_search_resources_free_form(self, mock_get_client): mock_get_client.return_value = mock_client mock_search_response = create_autospec(oci.response.Response) - mock_search_response.data = ( - oci.resource_search.models.ResourceSummaryCollection( - items=[ - oci.resource_search.models.ResourceSummary( - identifier="resource1", - display_name="Resource 1", - resource_type="instance", - lifecycle_state="RUNNING", - ) - ] - ) + mock_search_response.data = oci.resource_search.models.ResourceSummaryCollection( + items=[ + oci.resource_search.models.ResourceSummary( + identifier="resource1", + display_name="Resource 1", + resource_type="instance", + lifecycle_state="RUNNING", + ) + ] ) mock_search_response.has_next_page = False mock_search_response.next_page = None @@ -134,17 +128,15 @@ async def test_search_resources_by_type(self, mock_get_client): mock_get_client.return_value = mock_client mock_search_response = create_autospec(oci.response.Response) - mock_search_response.data = ( - oci.resource_search.models.ResourceSummaryCollection( - items=[ - oci.resource_search.models.ResourceSummary( - identifier="db1", - display_name="DB 1", - resource_type="dbsystem", - lifecycle_state="AVAILABLE", - ) - ] - ) + mock_search_response.data = oci.resource_search.models.ResourceSummaryCollection( + items=[ + oci.resource_search.models.ResourceSummary( + identifier="db1", + display_name="DB 1", + resource_type="dbsystem", + lifecycle_state="AVAILABLE", + ) + ] ) mock_search_response.has_next_page = False mock_search_response.next_page = None @@ -464,15 +456,9 @@ def test_main_with_only_port(self, mock_getenv, mock_mcp_run): class TestGetClient: - @patch( - "oracle.oci_resource_search_mcp_server.server.oci.resource_search.ResourceSearchClient" - ) - @patch( - "oracle.oci_resource_search_mcp_server.server.oci.auth.signers.SecurityTokenSigner" - ) - @patch( - "oracle.oci_resource_search_mcp_server.server.oci.signer.load_private_key_from_file" - ) + @patch("oracle.oci_resource_search_mcp_server.server.oci.resource_search.ResourceSearchClient") + @patch("oracle.oci_resource_search_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_resource_search_mcp_server.server.oci.signer.load_private_key_from_file") @patch( "oracle.oci_resource_search_mcp_server.server.open", new_callable=mock_open, @@ -510,27 +496,21 @@ def test_get_search_client_with_profile_env( profile_name="MYPROFILE", ) mock_open_file.assert_called_once_with("/abs/path/to/token", "r") - mock_security_token_signer.assert_called_once_with( - "SECURITY_TOKEN", private_key_obj - ) + mock_security_token_signer.assert_called_once_with("SECURITY_TOKEN", private_key_obj) # Ensure user agent was set on the same config dict passed into client args, _ = mock_client.call_args passed_config = args[0] assert passed_config is config - expected_user_agent = f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + expected_user_agent = ( + f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + ) assert passed_config.get("additional_user_agent") == expected_user_agent # And we returned the client instance assert result == mock_client.return_value - @patch( - "oracle.oci_resource_search_mcp_server.server.oci.resource_search.ResourceSearchClient" - ) - @patch( - "oracle.oci_resource_search_mcp_server.server.oci.auth.signers.SecurityTokenSigner" - ) - @patch( - "oracle.oci_resource_search_mcp_server.server.oci.signer.load_private_key_from_file" - ) + @patch("oracle.oci_resource_search_mcp_server.server.oci.resource_search.ResourceSearchClient") + @patch("oracle.oci_resource_search_mcp_server.server.oci.auth.signers.SecurityTokenSigner") + @patch("oracle.oci_resource_search_mcp_server.server.oci.signer.load_private_key_from_file") @patch( "oracle.oci_resource_search_mcp_server.server.open", new_callable=mock_open, @@ -572,9 +552,6 @@ def test_get_search_client_uses_default_profile_when_env_missing( cc_args, _ = mock_client.call_args assert cc_args[0] is config assert "additional_user_agent" in config - assert ( - isinstance(config["additional_user_agent"], str) - and "/" in config["additional_user_agent"] - ) + assert isinstance(config["additional_user_agent"], str) and "/" in config["additional_user_agent"] # Returned object is client instance assert srv_client is mock_client.return_value 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 de832340..1087ea83 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 @@ -85,13 +85,9 @@ def get_summarized_usage( compartment_depth=compartment_depth, ) - response = usage_client.request_summarized_usages( - request_summarized_usages_details=summarized_details - ) + response = usage_client.request_summarized_usages(request_summarized_usages_details=summarized_details) # Convert UsageSummary objects to dictionaries for proper serialization - summarized_usages = [ - oci.util.to_dict(usage_summary) for usage_summary in response.data.items - ] + summarized_usages = [oci.util.to_dict(usage_summary) for usage_summary in response.data.items] return summarized_usages diff --git a/src/oci-usage-mcp-server/oracle/oci_usage_mcp_server/tests/test_usage_tools.py b/src/oci-usage-mcp-server/oracle/oci_usage_mcp_server/tests/test_usage_tools.py index 50270cee..fe0d2a2f 100644 --- a/src/oci-usage-mcp-server/oracle/oci_usage_mcp_server/tests/test_usage_tools.py +++ b/src/oci-usage-mcp-server/oracle/oci_usage_mcp_server/tests/test_usage_tools.py @@ -33,9 +33,7 @@ async def test_get_summarized_usage(self, mock_get_client): ] ) - mock_client.request_summarized_usages.return_value = ( - mock_request_summarized_response - ) + mock_client.request_summarized_usages.return_value = mock_request_summarized_response print("mock response", mock_request_summarized_response) async with Client(mcp) as client: result = ( @@ -143,14 +141,14 @@ def test_get_search_client_with_profile_env( profile_name="MYPROFILE", ) mock_open_file.assert_called_once_with("/abs/path/to/token", "r") - mock_security_token_signer.assert_called_once_with( - "SECURITY_TOKEN", private_key_obj - ) + mock_security_token_signer.assert_called_once_with("SECURITY_TOKEN", private_key_obj) # Ensure user agent was set on the same config dict passed into client args, _ = mock_client.call_args passed_config = args[0] assert passed_config is config - expected_user_agent = f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + expected_user_agent = ( + f"{server.__project__.split('oracle.', 1)[1].split('-server', 1)[0]}/{server.__version__}" # noqa + ) assert passed_config.get("additional_user_agent") == expected_user_agent # And we returned the client instance assert result == mock_client.return_value @@ -199,9 +197,6 @@ def test_get_search_client_uses_default_profile_when_env_missing( cc_args, _ = mock_client.call_args assert cc_args[0] is config assert "additional_user_agent" in config - assert ( - isinstance(config["additional_user_agent"], str) - and "/" in config["additional_user_agent"] - ) + assert isinstance(config["additional_user_agent"], str) and "/" in config["additional_user_agent"] # Returned object is client instance assert srv_client is mock_client.return_value diff --git a/tests/e2e/features/environment.py b/tests/e2e/features/environment.py index 35b8f878..d7647c07 100644 --- a/tests/e2e/features/environment.py +++ b/tests/e2e/features/environment.py @@ -29,9 +29,7 @@ except FileNotFoundError: raise EnvironmentError( - print( - f"{config["MCP_HOST_FILE"]} could not be found. Provide one to configure the MCP servers" - ) + print(f"{config['MCP_HOST_FILE']} could not be found. Provide one to configure the MCP servers") ) _system_prompt = f"""You are an Oracle Cloud Infrastructure expert generative chat assistant. You are working out of this tenancy (also know as the root compartment): ocid1.tenancy.oc1..mock. For any compartment IDs, just pass in the tenancy ID. Limit your answers to OCI. OCID is synonymous with ID. If the user makes a request that relies on a tool that requires a compartment id, and the user doesn't specify one, don't ask the user for the compartment id and use the active (current) compartment instead. If I ask you for a list of things, prefer either a tabular or text-based approach over dumping them in a code block. When formatting your response, don't use bullets or lists within tables. When a user makes a request, you must first attempt to fulfill it by using the available MCP tools. These tools are connected to our live data sources and provide the most accurate and real-time information. Only after exhausting the capabilities of the MCP tools should you resort to other methods, such as using a general web search, if the MCP tools cannot provide the necessary information. If there is an error in calling the run_oci_command tool, then try to use the get_oci_command_help tool to get more information on the command and retry with the updated information. Don't send back emojis in the responses.""" # noqa ES501 @@ -48,7 +46,7 @@ def set_mcp_servers(context): print("Configured servers: ", ", ".join(sorted(context.mcp_servers))) except FileNotFoundError: raise EnvironmentError( - f"{config["MCP_HOST_FILE"]} could not be found. Provide one to configure the MCP servers" + f"{config['MCP_HOST_FILE']} could not be found. Provide one to configure the MCP servers" ) @@ -68,9 +66,7 @@ def wait_for_health_check(context, url, service_name, max_retries=30): if not ready: cleanup_all_processes(context) - raise RuntimeError( - f"{service_name} failed to become healthy within {max_retries} seconds." - ) + raise RuntimeError(f"{service_name} failed to become healthy within {max_retries} seconds.") def before_all(context): @@ -137,9 +133,7 @@ def before_all(context): # Start Proxy Shim (Port 5000) print("Starting Proxy Shim on http://127.0.0.1:5000...") - context.shim_proc = subprocess.Popen( - [sys.executable, proxy_shim_path], env=shim_env - ) + context.shim_proc = subprocess.Popen([sys.executable, proxy_shim_path], env=shim_env) wait_for_health_check(context, "http://127.0.0.1:5000", "Proxy Shim") diff --git a/tests/e2e/features/mocks/proxy_shim.py b/tests/e2e/features/mocks/proxy_shim.py index 36de3143..7a864b0b 100644 --- a/tests/e2e/features/mocks/proxy_shim.py +++ b/tests/e2e/features/mocks/proxy_shim.py @@ -111,12 +111,8 @@ def handle_client(client_sock): target_sock.connect(("127.0.0.1", 5001)) # Create threads for bidirectional traffic - t1 = threading.Thread( - target=pipe, args=(ssock, target_sock), daemon=True - ) - t2 = threading.Thread( - target=pipe, args=(target_sock, ssock), daemon=True - ) + t1 = threading.Thread(target=pipe, args=(ssock, target_sock), daemon=True) + t2 = threading.Thread(target=pipe, args=(target_sock, ssock), daemon=True) t1.start() t2.start() diff --git a/tests/e2e/features/mocks/services/compute_routes.py b/tests/e2e/features/mocks/services/compute_routes.py index 4745c5a2..a2327a34 100644 --- a/tests/e2e/features/mocks/services/compute_routes.py +++ b/tests/e2e/features/mocks/services/compute_routes.py @@ -40,9 +40,7 @@ def launch_instance(): @compute_bp.route("/instances/", methods=["GET"]) def get_instance(instance_id): inst = next((i for i in INSTANCES if i["id"] == instance_id), None) - return ( - oci_res(inst) if inst else (jsonify({"code": "NotAuthorizedOrNotFound"}), 404) - ) + return oci_res(inst) if inst else (jsonify({"code": "NotAuthorizedOrNotFound"}), 404) @compute_bp.route("/instances/", methods=["DELETE"]) @@ -82,6 +80,4 @@ def list_vnic_attachments(): @compute_bp.route("/vnicAttachments/", methods=["GET"]) def get_vnic_attachment(vnic_attachment_id): vnic = next((i for i in VNIC_ATTACHMENTS if i["id"] == vnic_attachment_id), None) - return ( - oci_res(vnic) if vnic else (jsonify({"code": "NotAuthorizedOrNotFound"}), 404) - ) + return oci_res(vnic) if vnic else (jsonify({"code": "NotAuthorizedOrNotFound"}), 404) diff --git a/tests/e2e/features/mocks/services/faaas_routes.py b/tests/e2e/features/mocks/services/faaas_routes.py index de0a920c..81a91ba3 100644 --- a/tests/e2e/features/mocks/services/faaas_routes.py +++ b/tests/e2e/features/mocks/services/faaas_routes.py @@ -59,9 +59,7 @@ def list_fusion_environments(): @faaas_bp.route("/fusionEnvironments/", methods=["GET"]) def get_fusion_environment(fusion_environment_id): - env = next( - (i for i in FUSION_ENVIRONMENTS if i.get("id") == fusion_environment_id), None - ) + env = next((i for i in FUSION_ENVIRONMENTS if i.get("id") == fusion_environment_id), None) if not env: return jsonify({"code": "NotAuthorizedOrNotFound"}), 404 return oci_res(env) @@ -70,11 +68,7 @@ def get_fusion_environment(fusion_environment_id): @faaas_bp.route("/fusionEnvironments//status", methods=["GET"]) def get_fusion_environment_status(fusion_environment_id): status = next( - ( - i - for i in FUSION_ENVIRONMENT_STATUSES - if i.get("fusionEnvironmentId") == fusion_environment_id - ), + (i for i in FUSION_ENVIRONMENT_STATUSES if i.get("fusionEnvironmentId") == fusion_environment_id), None, ) if not status: diff --git a/tests/e2e/features/mocks/services/identity_routes.py b/tests/e2e/features/mocks/services/identity_routes.py index 0ea4691a..6cf42e16 100644 --- a/tests/e2e/features/mocks/services/identity_routes.py +++ b/tests/e2e/features/mocks/services/identity_routes.py @@ -21,11 +21,7 @@ def list_ads(): def get_compartment(compartment_id): compartments = COMPARTMENTS + [TENANCY] compartment = next((i for i in compartments if i["id"] == compartment_id), None) - return ( - oci_res(compartment) - if compartment - else (jsonify({"code": "NotAuthorizedOrNotFound"}), 404) - ) + return oci_res(compartment) if compartment else (jsonify({"code": "NotAuthorizedOrNotFound"}), 404) @identity_bp.route("/compartments", methods=["GET"]) diff --git a/tests/e2e/features/steps/general-prompts.py b/tests/e2e/features/steps/general-prompts.py index 735472da..c308dd24 100644 --- a/tests/e2e/features/steps/general-prompts.py +++ b/tests/e2e/features/steps/general-prompts.py @@ -20,9 +20,7 @@ def step_impl_ollama_model(context): response = requests.get("http://localhost:8000/health") response.raise_for_status() except requests.exceptions.RequestException as e: - raise ConnectionError( - f"Could not connect to Ollama or model not found: {e}. Is Ollama running?" - ) + raise ConnectionError(f"Could not connect to Ollama or model not found: {e}. Is Ollama running?") @when('I send a request with the prompt "{prompt}"') @@ -62,6 +60,4 @@ def step_impl_tools_available(context): assert "content" in result["message"], "Response does not contain a content key." for tool_server in context.mcp_servers: - assert ( - tool_server in result["message"]["content"] - ), f"{tool_server} is missing from tools." + assert tool_server in result["message"]["content"], f"{tool_server} is missing from tools." diff --git a/tests/e2e/features/steps/oci-api-mcp-server-steps.py b/tests/e2e/features/steps/oci-api-mcp-server-steps.py index 672aac7b..9e055ff3 100644 --- a/tests/e2e/features/steps/oci-api-mcp-server-steps.py +++ b/tests/e2e/features/steps/oci-api-mcp-server-steps.py @@ -10,13 +10,10 @@ @then("the response should contain terraform configuration") def step_impl_terraform_configuration(context): response_json = context.response.json() - assert ( - "content" in response_json["message"] - ), "Response does not contain a content key." + assert "content" in response_json["message"], "Response does not contain a content key." content = response_json["message"]["content"].lower() assert any( - keyword in content - for keyword in ["terraform", "resource", "provider", ".tf", "infrastructure"] + keyword in content for keyword in ["terraform", "resource", "provider", ".tf", "infrastructure"] ), "Terraform configuration could not be generated." @@ -24,10 +21,9 @@ def step_impl_terraform_configuration(context): def step_impl_gpu_instances(context): response_json = context.response.json() content = response_json["message"]["content"].lower() - assert any( - keyword in content - for keyword in ["gpu", "bm.gpu", "vm.gpu", "a10", "v100", "graphics"] - ), "GPU instances were not mentioned in the response." + assert any(keyword in content for keyword in ["gpu", "bm.gpu", "vm.gpu", "a10", "v100", "graphics"]), ( + "GPU instances were not mentioned in the response." + ) @then("the response should mention OCI Gen AI services") diff --git a/tests/e2e/features/steps/oci-cloud-guard-mcp-server-steps.py b/tests/e2e/features/steps/oci-cloud-guard-mcp-server-steps.py index 9774cb61..5d9d14d3 100644 --- a/tests/e2e/features/steps/oci-cloud-guard-mcp-server-steps.py +++ b/tests/e2e/features/steps/oci-cloud-guard-mcp-server-steps.py @@ -10,14 +10,12 @@ @then("the response should contain a list of cloud guard tools available") def step_impl_cloud_guard_tools_available(context): response_json = context.response.json() - assert ( - "content" in response_json["message"] - ), "Response does not contain a content key." + assert "content" in response_json["message"], "Response does not contain a content key." content = response_json["message"]["content"].lower() # Tools from Cloud Guard server - assert any( - tool in content for tool in ["list_problems", "get_problem_details"] - ), "Cloud Guard tools could not be queried." + assert any(tool in content for tool in ["list_problems", "get_problem_details"]), ( + "Cloud Guard tools could not be queried." + ) @then("the response should contain a list of cloud guard problems") @@ -25,15 +23,15 @@ def step_impl_list_problems(context): result = context.response.json() assert "content" in result["message"], "Response does not contain a content key." # Look for a known OCID prefix for Cloud Guard Problem - assert ( - "ocid1.cloudguardproblem" in result["message"]["content"].lower() - ), "List of Cloud Guard problems not found." + assert "ocid1.cloudguardproblem" in result["message"]["content"].lower(), ( + "List of Cloud Guard problems not found." + ) @then("the response should contain the details of a cloud guard problem") def step_impl_get_problem_details(context): result = context.response.json() assert "content" in result["message"], "Response does not contain a content key." - assert ( - "ocid1.cloudguardproblem" in result["message"]["content"].lower() - ), "Cloud Guard problem details not found." + assert "ocid1.cloudguardproblem" in result["message"]["content"].lower(), ( + "Cloud Guard problem details not found." + ) diff --git a/tests/e2e/features/steps/oci-compute-mcp-server-steps.py b/tests/e2e/features/steps/oci-compute-mcp-server-steps.py index f7c4dee1..81339a4c 100644 --- a/tests/e2e/features/steps/oci-compute-mcp-server-steps.py +++ b/tests/e2e/features/steps/oci-compute-mcp-server-steps.py @@ -10,9 +10,7 @@ @then("the response should contain a list of compute tools available") def step_impl_compute_tools_available(context): response_json = context.response.json() - assert ( - "content" in response_json["message"] - ), "Response does not contain a content key." + assert "content" in response_json["message"], "Response does not contain a content key." content = response_json["message"]["content"].lower() assert any( tool in content @@ -30,18 +28,14 @@ def step_impl_compute_tools_available(context): def step_impl_list_instances(context): result = context.response.json() assert "content" in result["message"], "Response does not contain a content key." - assert ( - "ocid1.instance" in result["message"]["content"] - ), "List of instances not found." + assert "ocid1.instance" in result["message"]["content"], "List of instances not found." @then("the response should contain the details of an instance") def step_impl_get_instance(context): result = context.response.json() assert "content" in result["message"], "Response does not contain a content key." - assert ( - "ocid1.instance" in result["message"]["content"] - ), "Instance details not found." + assert "ocid1.instance" in result["message"]["content"], "Instance details not found." @then("the response should contain a list of images") @@ -62,30 +56,23 @@ def step_impl_get_image(context): def step_impl_list_vnic_attachments(context): result = context.response.json() assert "content" in result["message"], "Response does not contain a content key." - assert ( - "ocid1.vnicattachment" in result["message"]["content"] - ), "List of vnic attachments not found." + assert "ocid1.vnicattachment" in result["message"]["content"], "List of vnic attachments not found." @then("the response should contain the details of a vnic attachment") def step_impl_get_vnic_attachment(context): result = context.response.json() assert "content" in result["message"], "Response does not contain a content key." - assert ( - "ocid1.vnicattachment" in result["message"]["content"] - ), "Vnic attachment details not found." + assert "ocid1.vnicattachment" in result["message"]["content"], "Vnic attachment details not found." @then("the response should contain security analysis") def step_impl_security_analysis(context): response_json = context.response.json() - assert ( - "content" in response_json["message"] - ), "Response does not contain a content key." + assert "content" in response_json["message"], "Response does not contain a content key." content = response_json["message"]["content"].lower() assert any( - keyword in content - for keyword in ["security", "configuration", "analysis", "review", "assessment"] + keyword in content for keyword in ["security", "configuration", "analysis", "review", "assessment"] ), "Security analysis could not be performed." @@ -113,6 +100,5 @@ def step_impl_regional_considerations(context): response_json = context.response.json() content = response_json["message"]["content"].lower() assert any( - keyword in content - for keyword in ["ashburn", "us-ashburn-1", "region", "regional", "location"] + keyword in content for keyword in ["ashburn", "us-ashburn-1", "region", "regional", "location"] ), "Regional considerations were not mentioned in the response." diff --git a/tests/e2e/features/steps/oci-faaas-mcp-server-steps.py b/tests/e2e/features/steps/oci-faaas-mcp-server-steps.py index cc7d14c2..a53e5ffe 100644 --- a/tests/e2e/features/steps/oci-faaas-mcp-server-steps.py +++ b/tests/e2e/features/steps/oci-faaas-mcp-server-steps.py @@ -10,9 +10,7 @@ @then("the response should contain a list of faaas tools available") def step_impl_faaas_tools_available(context): response_json = context.response.json() - assert ( - "content" in response_json["message"] - ), "Response does not contain a content key." + assert "content" in response_json["message"], "Response does not contain a content key." content = response_json["message"]["content"].lower() assert any( tool in content @@ -31,9 +29,7 @@ def step_impl_list_families(context): assert "content" in result["message"], "Response does not contain a content key." content = result["message"]["content"].lower() # Look for family OCID prefix to be present - assert ( - "ocid1.fusionenvironmentfamily" in content - ), "Fusion environment families not found." + assert "ocid1.fusionenvironmentfamily" in content, "Fusion environment families not found." @then("the response should contain a list of fusion environments") @@ -58,6 +54,6 @@ def step_impl_get_environment_status(context): assert "content" in result["message"], "Response does not contain a content key." content = result["message"]["content"].lower() # Expect to mention status and/or environment OCID - assert any( - kw in content for kw in ["status", "available", "ocid1.fusionenvironment"] - ), "Fusion environment status not found." + assert any(kw in content for kw in ["status", "available", "ocid1.fusionenvironment"]), ( + "Fusion environment status not found." + ) diff --git a/tests/e2e/features/steps/oci-identity-mcp-server-steps.py b/tests/e2e/features/steps/oci-identity-mcp-server-steps.py index ed312261..9a51adae 100644 --- a/tests/e2e/features/steps/oci-identity-mcp-server-steps.py +++ b/tests/e2e/features/steps/oci-identity-mcp-server-steps.py @@ -10,9 +10,7 @@ @then("the response should contain a list of identity tools available") def step_impl_identity_tools_available(context): response_json = context.response.json() - assert ( - "content" in response_json["message"] - ), "Response does not contain a content key." + assert "content" in response_json["message"], "Response does not contain a content key." content = response_json["message"]["content"].lower() # Tools from Identity server we expect the model to list assert any( @@ -31,9 +29,7 @@ def step_impl_identity_tools_available(context): def step_impl_list_compartments(context): result = context.response.json() assert "content" in result["message"], "Response does not contain a content key." - assert ( - "ocid1.compartment" in result["message"]["content"] - ), "Compartments not found." + assert "ocid1.compartment" in result["message"]["content"], "Compartments not found." @then("the response should contain the details of a tenancy") @@ -66,6 +62,6 @@ def step_impl_list_regions(context): result = context.response.json() assert "content" in result["message"], "Response does not contain a content key." content = result["message"]["content"].lower() - assert any( - kw in content for kw in ["region", "us-mock-1", "home region", "phx", "iad"] - ), "Subscribed regions not found." + assert any(kw in content for kw in ["region", "us-mock-1", "home region", "phx", "iad"]), ( + "Subscribed regions not found." + ) diff --git a/tests/e2e/features/steps/oci-logging-mcp-server-steps.py b/tests/e2e/features/steps/oci-logging-mcp-server-steps.py index 94e7e242..183fbbdc 100644 --- a/tests/e2e/features/steps/oci-logging-mcp-server-steps.py +++ b/tests/e2e/features/steps/oci-logging-mcp-server-steps.py @@ -10,9 +10,7 @@ @then("the response should contain a list of logging tools available") def step_impl_logging_tools_available(context): response_json = context.response.json() - assert ( - "content" in response_json["message"] - ), "Response does not contain a content key." + assert "content" in response_json["message"], "Response does not contain a content key." content = response_json["message"]["content"].lower() assert any( tool in content @@ -29,27 +27,21 @@ def step_impl_logging_tools_available(context): def step_impl_list_log_groups(context): result = context.response.json() assert "content" in result["message"], "Response does not contain a content key." - assert ( - "ocid1.loggroup" in result["message"]["content"].lower() - ), "List of log groups not found." + assert "ocid1.loggroup" in result["message"]["content"].lower(), "List of log groups not found." @then("the response should contain the details of a log group") def step_impl_get_log_group(context): result = context.response.json() assert "content" in result["message"], "Response does not contain a content key." - assert ( - "ocid1.loggroup" in result["message"]["content"].lower() - ), "Log group details not found." + assert "ocid1.loggroup" in result["message"]["content"].lower(), "Log group details not found." @then("the response should contain a list of logs") def step_impl_list_logs(context): result = context.response.json() assert "content" in result["message"], "Response does not contain a content key." - assert ( - "ocid1.log" in result["message"]["content"].lower() - ), "List of logs not found." + assert "ocid1.log" in result["message"]["content"].lower(), "List of logs not found." @then("the response should contain the details of a log") diff --git a/tests/e2e/features/steps/oci-migration-mcp-server-steps.py b/tests/e2e/features/steps/oci-migration-mcp-server-steps.py index 3b6f97c6..3765198c 100644 --- a/tests/e2e/features/steps/oci-migration-mcp-server-steps.py +++ b/tests/e2e/features/steps/oci-migration-mcp-server-steps.py @@ -10,28 +10,22 @@ @then("the response should contain a list of migration tools available") def step_impl_migration_tools_available(context): response_json = context.response.json() - assert ( - "content" in response_json["message"] - ), "Response does not contain a content key." + assert "content" in response_json["message"], "Response does not contain a content key." content = response_json["message"]["content"].lower() - assert any( - tool in content for tool in ["list_migrations", "get_migration"] - ), "Migration tools could not be queried." + assert any(tool in content for tool in ["list_migrations", "get_migration"]), ( + "Migration tools could not be queried." + ) @then("the response should contain a list of migrations") def step_impl_list_migrations(context): result = context.response.json() assert "content" in result["message"], "Response does not contain a content key." - assert ( - "ocid1.migration" in result["message"]["content"].lower() - ), "List of migrations not found." + assert "ocid1.migration" in result["message"]["content"].lower(), "List of migrations not found." @then("the response should contain the details of a migration") def step_impl_get_migration(context): result = context.response.json() assert "content" in result["message"], "Response does not contain a content key." - assert ( - "ocid1.migration" in result["message"]["content"].lower() - ), "Migration details not found." + assert "ocid1.migration" in result["message"]["content"].lower(), "Migration details not found." diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 27467166..00000000 --- a/tox.ini +++ /dev/null @@ -1,44 +0,0 @@ -[tox] -isolated_build = True - -[isort] -profile = black -extend_skip = - src/dbtools-mcp-server/, - src/mysql-mcp-server/, - src/oci-pricing-mcp-server, - src/oracle-db-doc-mcp-server - -[flake8] -; to match Black -max-line-length = 110 -extend-ignore = E701 -extend-exclude = - venv, - .venv, - venv, - src/dbtools-mcp-server, - src/mysql-mcp-server, - src/oci-pricing-mcp-server, - src/oracle-db-doc-mcp-server - src/oci-database-mcp-server - -[testenv] -deps = - black==25.1.0 - flake8==7.3.0 - isort==6.0.1 -setenv = - PYTHON = python3 - -[testenv:lint] -commands = - isort -c {posargs:{toxinidir}} - black --force-exclude 'src/dbtools-mcp-server|mysql-mcp-server|oci-pricing-mcp-server' --check {posargs:{toxinidir}} - flake8 {posargs:{toxinidir}} - -[testenv:format] -commands = - isort {posargs:{toxinidir}} - black --force-exclude 'src/dbtools-mcp-server|mysql-mcp-server|oci-pricing-mcp-server' {posargs:{toxinidir}} - flake8 {posargs:{toxinidir}}