From a432f07d2f2ca7725c16dc38429d3e3fae21861f Mon Sep 17 00:00:00 2001 From: Dustin Sale Date: Wed, 7 Oct 2026 10:54:19 -0500 Subject: [PATCH 1/2] Fix nullable optional inputs across MCP servers Accept explicit null for 154 optional tool parameters in Database, Networking, Cloud Guard, MySQL, and DBTools. Add schema and dispatch regression tests and update existing server changelogs. Refs oracle/mcp#470 Signed-off-by: Dustin Sale --- src/dbtools-mcp-server/dbtools-mcp-server.py | 4 +- src/dbtools-mcp-server/test_null_inputs.py | 70 +++++ .../oracle/mysql_mcp_server/server.py | 4 +- .../tests/test_null_inputs.py | 50 +++ src/oci-cloud-guard-mcp-server/CHANGELOG.md | 4 + .../oci_cloud_guard_mcp_server/server.py | 2 +- .../tests/test_null_inputs.py | 49 +++ src/oci-database-mcp-server/CHANGELOG.md | 4 + .../oracle/oci_database_mcp_server/server.py | 286 +++++++++--------- .../tests/test_null_inputs.py | 55 ++++ src/oci-networking-mcp-server/CHANGELOG.md | 4 + .../oci_networking_mcp_server/server.py | 8 +- .../tests/test_null_inputs.py | 53 ++++ 13 files changed, 441 insertions(+), 152 deletions(-) create mode 100644 src/dbtools-mcp-server/test_null_inputs.py create mode 100644 src/mysql-mcp-server/oracle/mysql_mcp_server/tests/test_null_inputs.py create mode 100644 src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/tests/test_null_inputs.py create mode 100644 src/oci-database-mcp-server/oracle/oci_database_mcp_server/tests/test_null_inputs.py create mode 100644 src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/tests/test_null_inputs.py diff --git a/src/dbtools-mcp-server/dbtools-mcp-server.py b/src/dbtools-mcp-server/dbtools-mcp-server.py index 5bc1cd83..97926fb7 100755 --- a/src/dbtools-mcp-server/dbtools-mcp-server.py +++ b/src/dbtools-mcp-server/dbtools-mcp-server.py @@ -805,7 +805,7 @@ def bootstrap_reports(dbtools_connection_display_name: str) -> str: }) @mcp.tool() -def create_report(dbtools_connection_display_name: str, name: str, sql_query: str, description: str = None, bind_parameters: list = None) -> str: +def create_report(dbtools_connection_display_name: str, name: str, sql_query: str, description: str | None = None, bind_parameters: list | None = None) -> str: """ Create a new report definition in the report_definitions table. @@ -914,7 +914,7 @@ def create_report(dbtools_connection_display_name: str, name: str, sql_query: st }) @mcp.tool() -def execute_report(dbtools_connection_display_name: str, report_name: str, bind_values: dict = None) -> str: +def execute_report(dbtools_connection_display_name: str, report_name: str, bind_values: dict | None = None) -> str: """ Execute a report by its name with optional bind parameter values. diff --git a/src/dbtools-mcp-server/test_null_inputs.py b/src/dbtools-mcp-server/test_null_inputs.py new file mode 100644 index 00000000..9e2ae31f --- /dev/null +++ b/src/dbtools-mcp-server/test_null_inputs.py @@ -0,0 +1,70 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +import importlib.util +import sys +import unittest +from contextlib import ExitStack +from pathlib import Path +from unittest.mock import patch + +from fastmcp import Client +from fastmcp.exceptions import ToolError +from jsonschema import Draft202012Validator + + +class TestNullInputs(unittest.IsolatedAsyncioTestCase): + @classmethod + def setUpClass(cls): + # The script creates OCI clients at import; keep those external boundaries offline. + config = dict.fromkeys(("tenancy", "user", "fingerprint", "key_file", "pass_phrase"), "sample") + with ExitStack() as stack: + stack.enter_context(patch("oci.config.from_file", return_value=config)) + for target in ( + "oci.identity.IdentityClient", "oci.resource_search.ResourceSearchClient", + "oci.database.DatabaseClient", "oci.database_tools.DatabaseToolsClient", + "oci.vault.VaultsClient", "oci.secrets.SecretsClient", + "oci.object_storage.ObjectStorageClient", "oci.signer.Signer", + ): + stack.enter_context(patch(target)) + spec = importlib.util.spec_from_file_location( + "dbtools_null_inputs", Path(__file__).with_name("dbtools-mcp-server.py") + ) + cls.server = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = cls.server + spec.loader.exec_module(cls.server) + + @classmethod + def tearDownClass(cls): + sys.modules.pop("dbtools_null_inputs", None) + + async def test_null_defaults_are_accepted_by_advertised_schemas(self): + async with Client(self.server.mcp) as client: + for tool in await client.list_tools(): + validator = Draft202012Validator(tool.inputSchema) + for name, schema in tool.inputSchema["properties"].items(): + if "default" in schema and schema["default"] is None: + self.assertTrue(validator.evolve(schema=schema).is_valid(None), f"{tool.name}.{name}") + + async def test_optional_report_inputs(self): + cases = [ + ("create_report", {"dbtools_connection_display_name": "sample", "name": "sample", + "sql_query": "SELECT 1"}, "description", "Sample report"), + ("create_report", {"dbtools_connection_display_name": "sample", "name": "sample", + "sql_query": "SELECT 1"}, "bind_parameters", ["id"]), + ("execute_report", {"dbtools_connection_display_name": "sample", "report_name": "sample"}, + "bind_values", {"id": 1}), + ] + with patch.object(self.server, "_resolve_connection_or_error", return_value=(None, '{"error": "Offline test"}')): + async with Client(self.server.mcp) as client: + for name, required, parameter, valid in cases: + with self.subTest(tool=name, parameter=parameter): + omitted = await client.call_tool(name, required) + for value in (None, valid): + result = await client.call_tool(name, {**required, parameter: value}) + self.assertEqual(result.content, omitted.content) + with self.assertRaisesRegex(ToolError, "validation error"): + await client.call_tool(name, {**required, parameter: 7}) diff --git a/src/mysql-mcp-server/oracle/mysql_mcp_server/server.py b/src/mysql-mcp-server/oracle/mysql_mcp_server/server.py index 6520f099..3a987381 100755 --- a/src/mysql-mcp-server/oracle/mysql_mcp_server/server.py +++ b/src/mysql-mcp-server/oracle/mysql_mcp_server/server.py @@ -246,7 +246,7 @@ def list_all_connections() -> str: @mcp.tool() def execute_sql_tool_by_connection_id( - connection_id: str, sql_script: str, params: list = None + connection_id: str, sql_script: str, params: Optional[list] = None ) -> str: """ Execute a SQL script on the specified database connection. @@ -1021,7 +1021,7 @@ def _get_compartment_by_name( @mcp.tool() def object_storage_list_buckets( - compartment_name: str = None, compartment_id: str = None + compartment_name: Optional[str] = None, compartment_id: Optional[str] = None ) -> str: """ [MCP Tool] List all accessible Object Storage buckets in a compartment. diff --git a/src/mysql-mcp-server/oracle/mysql_mcp_server/tests/test_null_inputs.py b/src/mysql-mcp-server/oracle/mysql_mcp_server/tests/test_null_inputs.py new file mode 100644 index 00000000..fe524fe5 --- /dev/null +++ b/src/mysql-mcp-server/oracle/mysql_mcp_server/tests/test_null_inputs.py @@ -0,0 +1,50 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +import importlib +import unittest +from unittest.mock import patch + +from fastmcp import Client +from fastmcp.exceptions import ToolError +from jsonschema import Draft202012Validator + + +class TestNullInputs(unittest.IsolatedAsyncioTestCase): + @classmethod + def setUpClass(cls): + from oracle.mysql_mcp_server import utils + + with patch.object(utils, "load_mysql_config", side_effect=RuntimeError("Offline test")), \ + patch.object(utils, "OciInfo", side_effect=RuntimeError("Offline test")): + cls.server = importlib.import_module("oracle.mysql_mcp_server.server") + + async def test_null_defaults_are_accepted_by_advertised_schemas(self): + async with Client(self.server.mcp) as client: + for tool in await client.list_tools(): + validator = Draft202012Validator(tool.inputSchema) + for name, schema in tool.inputSchema["properties"].items(): + if "default" in schema and schema["default"] is None: + self.assertTrue(validator.evolve(schema=schema).is_valid(None), f"{tool.name}.{name}") + + async def test_optional_inputs(self): + cases = [ + ("execute_sql_tool_by_connection_id", {"connection_id": "sample", "sql_script": "SELECT 1"}, + "params", [1]), + ("object_storage_list_buckets", {}, "compartment_name", "sample"), + ("object_storage_list_buckets", {}, "compartment_id", "sample"), + ] + with patch.object(self.server, "_get_db_connection", side_effect=RuntimeError("Offline test")), \ + patch.object(self.server, "oci_error_msg", '{"error": "Offline test"}'): + async with Client(self.server.mcp) as client: + for name, required, parameter, valid in cases: + with self.subTest(tool=name, parameter=parameter): + omitted = await client.call_tool(name, required) + for value in (None, valid): + result = await client.call_tool(name, {**required, parameter: value}) + self.assertEqual(result.content, omitted.content) + with self.assertRaisesRegex(ToolError, "validation error"): + await client.call_tool(name, {**required, parameter: 7}) diff --git a/src/oci-cloud-guard-mcp-server/CHANGELOG.md b/src/oci-cloud-guard-mcp-server/CHANGELOG.md index 484fc68c..f5850c5c 100644 --- a/src/oci-cloud-guard-mcp-server/CHANGELOG.md +++ b/src/oci-cloud-guard-mcp-server/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Fixed + +- Allow explicit null for the optional comment in update_problem_status, matching omission. + ### Security - Updated `cryptography` to 50.0.1 to prevent PKCS#7 EnvelopedData decryption from exposing a Bleichenbacher oracle through distinguishable errors and timing (CVE-2026-69247). 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 6c5647aa..8d664c1e 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 @@ -156,7 +156,7 @@ def update_problem_status( "OPEN", description="Action taken by user. Allowed values are: OPEN, RESOLVED, DISMISSED, CLOSED", ), - comment: str = Field(None, description="A comment from the user"), + comment: Optional[str] = Field(None, description="A comment from the user"), ) -> Problem: updated_problem_status = oci.cloud_guard.models.UpdateProblemStatusDetails(status=status, comment=comment) response = get_cloud_guard_client().update_problem_status( diff --git a/src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/tests/test_null_inputs.py b/src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/tests/test_null_inputs.py new file mode 100644 index 00000000..35815c64 --- /dev/null +++ b/src/oci-cloud-guard-mcp-server/oracle/oci_cloud_guard_mcp_server/tests/test_null_inputs.py @@ -0,0 +1,49 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from unittest.mock import MagicMock + +import oci +import pytest +from fastmcp import Client +from fastmcp.exceptions import ToolError +from jsonschema import Draft202012Validator + +from oracle.oci_cloud_guard_mcp_server import server + + +@pytest.mark.asyncio +async def test_null_defaults_are_accepted_by_advertised_schemas(): + async with Client(server.mcp) as client: + for tool in await client.list_tools(): + validator = Draft202012Validator(tool.inputSchema) + for name, schema in tool.inputSchema["properties"].items(): + if "default" in schema and schema["default"] is None: + assert validator.evolve(schema=schema).is_valid(None), f"{tool.name}.{name}" + + +@pytest.mark.asyncio +@pytest.mark.parametrize("comment_args", [{}, {"comment": None}, {"comment": "Investigated"}]) +async def test_update_problem_accepts_optional_comment(monkeypatch, comment_args): + sdk = MagicMock() + sdk.update_problem_status.return_value.data = oci.cloud_guard.models.Problem( + id="sample", lifecycle_detail="OPEN", comment=comment_args.get("comment") + ) + monkeypatch.setattr(server, "get_cloud_guard_client", lambda: sdk) + + async with Client(server.mcp) as client: + result = await client.call_tool("update_problem_status", {"problem_id": "sample", **comment_args}) + assert result.structured_content["id"] == "sample" + assert result.structured_content["comment"] == comment_args.get("comment") + details = sdk.update_problem_status.call_args.kwargs["update_problem_status_details"] + assert details.comment == comment_args.get("comment") + + +@pytest.mark.asyncio +async def test_update_problem_rejects_invalid_comment(): + async with Client(server.mcp) as client: + with pytest.raises(ToolError, match="valid string"): + await client.call_tool("update_problem_status", {"problem_id": "sample", "comment": 7}) diff --git a/src/oci-database-mcp-server/CHANGELOG.md b/src/oci-database-mcp-server/CHANGELOG.md index 511cfab4..8a6f9eeb 100644 --- a/src/oci-database-mcp-server/CHANGELOG.md +++ b/src/oci-database-mcp-server/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to OCI Database MCP Server are documented in this file. ## Unreleased +### Fixed + +- Allow explicit null for optional region arguments so calls use the configured default region, matching omission. + ### Security - Updated `cryptography` to 50.0.1 to prevent PKCS#7 EnvelopedData decryption from exposing a Bleichenbacher oracle through distinguishable errors and timing (CVE-2026-69247). diff --git a/src/oci-database-mcp-server/oracle/oci_database_mcp_server/server.py b/src/oci-database-mcp-server/oracle/oci_database_mcp_server/server.py index 593ea124..0e1271a5 100644 --- a/src/oci-database-mcp-server/oracle/oci_database_mcp_server/server.py +++ b/src/oci-database-mcp-server/oracle/oci_database_mcp_server/server.py @@ -365,7 +365,7 @@ def get_public_ip_for_database( "The database `OCID` ", ], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> Optional[str]: @@ -458,7 +458,7 @@ def delete_pluggable_database( Optional[str], "Unique identifier for the request." ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> Any: @@ -484,7 +484,7 @@ def get_pluggable_database( "The database `OCID` ", ], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> PluggableDatabase: @@ -515,7 +515,7 @@ def update_pluggable_database( "For optimistic concurrency control. In the PUT or DELETE call for a resource, set the `if-match` parameter to the value of the etag from a previous GET or POST response for that resource. The resource will be updated or deleted only if the etag you provide matches the resource's current etag value.", ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> PluggableDatabase: @@ -792,7 +792,7 @@ def list_application_vips( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[ApplicationVipSummary]: @@ -837,7 +837,7 @@ def list_autonomous_container_database_dataguard_associations( Optional[Any], ("The pagination token to continue listing from.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[AutonomousContainerDatabaseDataguardAssociation]: @@ -892,7 +892,7 @@ def list_autonomous_container_database_versions( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[AutonomousContainerDatabaseVersionSummary]: @@ -1005,7 +1005,7 @@ def list_autonomous_container_databases( Optional[Any], ("The cloud Autonomous VM Cluster `OCID`.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[AutonomousContainerDatabaseSummary]: @@ -1109,7 +1109,7 @@ def list_autonomous_database_backups( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[AutonomousDatabaseBackupSummary]: @@ -1175,7 +1175,7 @@ def list_autonomous_database_character_sets( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[AutonomousDatabaseCharacterSets]: @@ -1265,7 +1265,7 @@ def list_autonomous_database_clones( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[AutonomousDatabaseSummary]: @@ -1314,7 +1314,7 @@ def list_autonomous_database_dataguard_associations( Optional[Any], ("The pagination token to continue listing from.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[AutonomousDatabaseDataguardAssociation]: @@ -1356,7 +1356,7 @@ def list_autonomous_database_peers( Optional[Any], ("The pagination token to continue listing from.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[AutonomousDatabasePeerCollection]: @@ -1398,7 +1398,7 @@ def list_autonomous_database_refreshable_clones( Optional[Any], ("The pagination token to continue listing from.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[RefreshableCloneCollection]: @@ -1478,7 +1478,7 @@ def list_autonomous_database_software_images( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[AutonomousDatabaseSoftwareImageCollection]: @@ -1648,7 +1648,7 @@ def list_autonomous_databases( ("The database `OCID` of the resourcepool Leader Autonomous" "Database."), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[AutonomousDatabaseSummary]: @@ -1734,7 +1734,7 @@ def list_autonomous_db_preview_versions( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[AutonomousDbPreviewVersionSummary]: @@ -1789,7 +1789,7 @@ def list_autonomous_db_versions( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[AutonomousDbVersionSummary]: @@ -1845,7 +1845,7 @@ def list_autonomous_virtual_machines( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[AutonomousVirtualMachineSummary]: @@ -1928,7 +1928,7 @@ def list_autonomous_vm_clusters( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[AutonomousVmClusterSummary]: @@ -1981,7 +1981,7 @@ def list_backup_destination( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[BackupDestinationSummary]: @@ -2029,7 +2029,7 @@ def list_backups( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[BackupSummary]: @@ -2119,7 +2119,7 @@ def list_cloud_autonomous_vm_clusters( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[CloudAutonomousVmClusterSummary]: @@ -2214,7 +2214,7 @@ def list_cloud_exadata_infrastructures( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[CloudExadataInfrastructureSummary]: @@ -2273,7 +2273,7 @@ def list_cloud_vm_cluster_updates( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[UpdateSummary]: @@ -2356,7 +2356,7 @@ def list_cloud_vm_clusters( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[CloudVmClusterSummary]: @@ -2393,7 +2393,7 @@ def list_cloud_vm_clusters( def list_console_connections( db_node_id: Annotated[Optional[Any], ("The database node `OCID`.")], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[ConsoleConnectionSummary]: @@ -2454,7 +2454,7 @@ def list_console_histories( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[ConsoleHistoryCollection]: @@ -2505,7 +2505,7 @@ def list_container_database_patches( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[AutonomousPatchSummary]: @@ -2539,7 +2539,7 @@ def list_data_guard_associations( Optional[Any], ("The pagination token to continue listing from.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[DataGuardAssociationSummary]: @@ -2636,7 +2636,7 @@ def list_database_software_images( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[DatabaseSoftwareImageSummary]: @@ -2727,7 +2727,7 @@ def list_databases( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[DatabaseSummary]: @@ -2772,7 +2772,7 @@ def list_db_home_patch_history_entries( Optional[Any], ("The pagination token to continue listing from.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[PatchHistoryEntrySummary]: @@ -2803,7 +2803,7 @@ def list_db_home_patches( Optional[Any], ("The pagination token to continue listing from.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[PatchSummary]: @@ -2891,7 +2891,7 @@ def list_db_homes( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[DbHomeSummary]: @@ -2977,7 +2977,7 @@ def list_db_nodes( Optional[Any], ("The `OCID` of the Exacc Db server.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[DbNodeSummary]: @@ -3059,7 +3059,7 @@ def list_db_servers( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[DbServerSummary]: @@ -3107,7 +3107,7 @@ def list_db_system_compute_performances( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[DbSystemComputePerformanceSummary]: @@ -3137,7 +3137,7 @@ def list_db_system_patches( Optional[Any], ("The pagination token to continue listing from.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[PatchSummary]: @@ -3175,7 +3175,7 @@ def list_db_system_shapes( Optional[Any], ("The pagination token to continue listing from.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[DbSystemShapeSummary]: @@ -3221,7 +3221,7 @@ def list_db_system_storage_performances( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[DbSystemStoragePerformanceSummary]: @@ -3310,7 +3310,7 @@ def list_db_systems( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[DbSystemSummary]: @@ -3392,7 +3392,7 @@ def list_db_versions( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[DbVersionSummary]: @@ -3483,7 +3483,7 @@ def list_exadata_infrastructures( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[ExadataInfrastructureSummary]: @@ -3549,7 +3549,7 @@ def list_exadb_vm_cluster_updates( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[ExadbVmClusterUpdateSummary]: @@ -3635,7 +3635,7 @@ def list_exadb_vm_clusters( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[ExadbVmClusterSummary]: @@ -3723,7 +3723,7 @@ def list_exascale_db_storage_vaults( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[ExascaleDbStorageVaultSummary]: @@ -3811,7 +3811,7 @@ def list_execution_actions( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[ExecutionActionSummary]: @@ -3898,7 +3898,7 @@ def list_execution_windows( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[ExecutionWindowSummary]: @@ -3979,7 +3979,7 @@ def list_external_container_databases( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[ExternalContainerDatabaseSummary]: @@ -4064,7 +4064,7 @@ def list_external_database_connectors( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[ExternalDatabaseConnectorSummary]: @@ -4146,7 +4146,7 @@ def list_external_non_container_databases( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[ExternalNonContainerDatabaseSummary]: @@ -4230,7 +4230,7 @@ def list_external_pluggable_databases( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[ExternalPluggableDatabaseSummary]: @@ -4308,7 +4308,7 @@ def list_flex_components( Optional[Any], ("The pagination token to continue listing from.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[FlexComponentCollection]: @@ -4393,7 +4393,7 @@ def list_gi_version_minor_versions( Optional[Any], ("The pagination token to continue listing from.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[GiMinorVersionSummary]: @@ -4454,7 +4454,7 @@ def list_gi_versions( ("The target availability domain. Only passed if the limit is" "AD-specific."), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[GiVersionSummary]: @@ -4492,7 +4492,7 @@ def list_key_stores( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[KeyStoreSummary]: @@ -4589,7 +4589,7 @@ def list_maintenance_run_history( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[MaintenanceRunHistorySummary]: @@ -4698,7 +4698,7 @@ def list_maintenance_runs( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[MaintenanceRunSummary]: @@ -4780,7 +4780,7 @@ def list_oneoff_patches( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[OneoffPatchSummary]: @@ -4860,7 +4860,7 @@ def list_pluggable_databases( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[PluggableDatabaseSummary]: @@ -4957,7 +4957,7 @@ def list_scheduled_actions( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[ScheduledActionCollection]: @@ -5059,7 +5059,7 @@ def list_scheduling_plans( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[SchedulingPlanCollection]: @@ -5142,7 +5142,7 @@ def list_scheduling_policies( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[SchedulingPolicySummary]: @@ -5220,7 +5220,7 @@ def list_scheduling_windows( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[SchedulingWindowSummary]: @@ -5278,7 +5278,7 @@ def list_system_versions( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[SystemVersionCollection]: @@ -5359,7 +5359,7 @@ def list_vm_cluster_networks( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[VmClusterNetworkSummary]: @@ -5404,7 +5404,7 @@ def list_vm_cluster_patches( Optional[Any], ("The pagination token to continue listing from.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[PatchSummary]: @@ -5457,7 +5457,7 @@ def list_vm_cluster_updates( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[VmClusterUpdateSummary]: @@ -5538,7 +5538,7 @@ def list_vm_clusters( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[VmClusterSummary]: @@ -5604,7 +5604,7 @@ def resource_pool_shapes( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[ResourcePoolShapeCollection]: @@ -5641,7 +5641,7 @@ def get_application_vip( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> ApplicationVip: @@ -5666,7 +5666,7 @@ def get_autonomous_container_database( Optional[Any], ("The Autonomous Container Database `OCID`.") ], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> AutonomousContainerDatabase: @@ -5701,7 +5701,7 @@ def get_autonomous_container_database_dataguard_association( ), ], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> AutonomousContainerDatabaseDataguardAssociation: @@ -5736,7 +5736,7 @@ def get_autonomous_container_database_resource_usage( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> AutonomousContainerDatabaseResourceUsage: @@ -5764,7 +5764,7 @@ def get_autonomous_database( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> AutonomousDatabase: @@ -5792,7 +5792,7 @@ def get_autonomous_database_backup( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> AutonomousDatabaseBackup: @@ -5830,7 +5830,7 @@ def get_autonomous_database_dataguard_association( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> AutonomousDatabaseDataguardAssociation: @@ -5860,7 +5860,7 @@ def get_autonomous_database_regional_wallet( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> AutonomousDatabaseWallet: @@ -5891,7 +5891,7 @@ def get_autonomous_database_software_image( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> AutonomousDatabaseSoftwareImage: @@ -5921,7 +5921,7 @@ def get_autonomous_database_wallet( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> AutonomousDatabaseWallet: @@ -5953,7 +5953,7 @@ def get_autonomous_exadata_infrastructure( Optional[Any], ("The Autonomous Exadata Infrastructure `OCID`.") ], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> AutonomousExadataInfrastructure: @@ -5976,7 +5976,7 @@ def get_autonomous_exadata_infrastructure( def get_autonomous_patch( autonomous_patch_id: Annotated[Optional[Any], ("The autonomous patch `OCID`.")], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> AutonomousPatch: @@ -6000,7 +6000,7 @@ def get_autonomous_virtual_machine( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> AutonomousVirtualMachine: @@ -6034,7 +6034,7 @@ def get_autonomous_vm_cluster( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> AutonomousVmCluster: @@ -6064,7 +6064,7 @@ def get_autonomous_vm_cluster_resource_usage( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> AutonomousVmClusterResourceUsage: @@ -6087,7 +6087,7 @@ def get_autonomous_vm_cluster_resource_usage( def get_backup( backup_id: Annotated[Optional[Any], ("The backup `OCID`.")], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> Backup: @@ -6116,7 +6116,7 @@ def get_backup_destination( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> BackupDestination: @@ -6148,7 +6148,7 @@ def get_cloud_autonomous_vm_cluster( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> CloudAutonomousVmCluster: @@ -6181,7 +6181,7 @@ def get_cloud_autonomous_vm_cluster_resource_usage( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> CloudAutonomousVmClusterResourceUsage: @@ -6217,7 +6217,7 @@ def get_cloud_exadata_infrastructure( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> CloudExadataInfrastructure: @@ -6253,7 +6253,7 @@ def get_cloud_exadata_infrastructure_unallocated_resources( Optional[Any], ("The list of `OCIDs` of the Db servers.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> CloudExadataInfrastructureUnallocatedResources: @@ -6289,7 +6289,7 @@ def get_cloud_vm_cluster( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> CloudVmCluster: @@ -6318,7 +6318,7 @@ def get_cloud_vm_cluster_iorm_config( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> ExadataIormConfig: @@ -6350,7 +6350,7 @@ def get_cloud_vm_cluster_update( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> Update: @@ -6383,7 +6383,7 @@ def get_cloud_vm_cluster_update_history_entry( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> UpdateHistoryEntry: @@ -6412,7 +6412,7 @@ def get_console_connection( Optional[Any], ("The OCID of the console connection.") ], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> ConsoleConnection: @@ -6438,7 +6438,7 @@ def get_console_history( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> ConsoleHistory: @@ -6468,7 +6468,7 @@ def get_console_history_content( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> list[Any]: @@ -6497,7 +6497,7 @@ def get_data_guard_association( Optional[Any], ("The Data Guard association's `OCID`.") ], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> DataGuardAssociation: @@ -6517,7 +6517,7 @@ def get_data_guard_association( def get_database( database_id: Annotated[Optional[Any], ("The database `OCID`.")], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> Database: @@ -6536,7 +6536,7 @@ def get_database( def get_database_software_image( database_software_image_id: Annotated[Optional[Any], ("The DB system `OCID`.")], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> DatabaseSoftwareImage: @@ -6561,7 +6561,7 @@ def get_database_upgrade_history_entry( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> DatabaseUpgradeHistoryEntry: @@ -6585,7 +6585,7 @@ def get_database_upgrade_history_entry( def get_db_home( db_home_id: Annotated[Optional[Any], ("The Database Home `OCID`.")], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> DbHome: @@ -6605,7 +6605,7 @@ def get_db_home_patch( db_home_id: Annotated[Optional[Any], ("The Database Home `OCID`.")], patch_id: Annotated[Optional[Any], ("The `OCID` of the patch.")], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> Patch: @@ -6630,7 +6630,7 @@ def get_db_home_patch_history_entry( Optional[Any], ("The `OCID` of the patch history entry.") ], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> PatchHistoryEntry: @@ -6652,7 +6652,7 @@ def get_db_home_patch_history_entry( def get_db_node( db_node_id: Annotated[Optional[Any], ("The database node `OCID`.")], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> DbNode: @@ -6677,7 +6677,7 @@ def get_db_server( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> DbServer: @@ -6699,7 +6699,7 @@ def get_db_server( def get_db_system( db_system_id: Annotated[Optional[Any], ("The DB system `OCID`.")], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> DbSystem: @@ -6719,7 +6719,7 @@ def get_db_system_patch( db_system_id: Annotated[Optional[Any], ("The DB system `OCID`.")], patch_id: Annotated[Optional[Any], ("The `OCID` of the patch.")], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> Patch: @@ -6746,7 +6746,7 @@ def get_db_system_patch_history_entry( Optional[Any], ("The `OCID` of the patch history entry.") ], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> PatchHistoryEntry: @@ -6779,7 +6779,7 @@ def get_db_system_upgrade_history_entry( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> DbSystemUpgradeHistoryEntry: @@ -6820,7 +6820,7 @@ def get_exadata_infrastructure( ), ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> ExadataInfrastructure: @@ -6853,7 +6853,7 @@ def get_exadata_infrastructure_ocpus( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> OCPUs: @@ -6891,7 +6891,7 @@ def get_exadata_infrastructure_un_allocated_resources( Optional[Any], ("The list of `OCIDs` of the Db servers.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> ExadataInfrastructureUnAllocatedResources: @@ -6925,7 +6925,7 @@ def get_exadata_iorm_config( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> ExadataIormConfig: @@ -6957,7 +6957,7 @@ def get_exadb_vm_cluster( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> ExadbVmCluster: @@ -6989,7 +6989,7 @@ def get_exadb_vm_cluster_update( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> ExadbVmClusterUpdate: @@ -7024,7 +7024,7 @@ def get_exadb_vm_cluster_update_history_entry( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> ExadbVmClusterUpdateHistoryEntry: @@ -7058,7 +7058,7 @@ def get_exascale_db_storage_vault( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> ExascaleDbStorageVault: @@ -7082,7 +7082,7 @@ def get_execution_action( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> ExecutionAction: @@ -7106,7 +7106,7 @@ def get_execution_window( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> ExecutionWindow: @@ -7127,7 +7127,7 @@ def get_execution_window( def get_external_backup_job( backup_id: Annotated[Optional[Any], ("The backup `OCID`.")], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> ExternalBackupJob: @@ -7153,7 +7153,7 @@ def get_external_container_database( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> ExternalContainerDatabase: @@ -7187,7 +7187,7 @@ def get_external_database_connector( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> ExternalDatabaseConnector: @@ -7217,7 +7217,7 @@ def get_external_non_container_database( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> ExternalNonContainerDatabase: @@ -7247,7 +7247,7 @@ def get_external_pluggable_database( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> ExternalPluggableDatabase: @@ -7290,7 +7290,7 @@ def get_infrastructure_target_versions( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> InfrastructureTargetVersion: @@ -7320,7 +7320,7 @@ def get_key_store( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> KeyStore: @@ -7341,7 +7341,7 @@ def get_key_store( def get_maintenance_run( maintenance_run_id: Annotated[Optional[Any], ("The maintenance run OCID.")], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> MaintenanceRun: @@ -7362,7 +7362,7 @@ def get_maintenance_run_history( Optional[Any], ("The maintenance run history OCID.") ], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> MaintenanceRunHistory: @@ -7384,7 +7384,7 @@ def get_oneoff_patch( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> OneoffPatch: @@ -7416,7 +7416,7 @@ def get_pdb_conversion_history_entry( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> PdbConversionHistoryEntry: @@ -7443,7 +7443,7 @@ def get_scheduled_action( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> ScheduledAction: @@ -7467,7 +7467,7 @@ def get_scheduling_plan( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> SchedulingPlan: @@ -7491,7 +7491,7 @@ def get_scheduling_policy( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> SchedulingPolicy: @@ -7516,7 +7516,7 @@ def get_scheduling_window( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> SchedulingWindow: @@ -7546,7 +7546,7 @@ def get_vm_cluster( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> VmCluster: @@ -7578,7 +7578,7 @@ def get_vm_cluster_network( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> VmClusterNetwork: @@ -7601,7 +7601,7 @@ def get_vm_cluster_patch( vm_cluster_id: Annotated[Optional[Any], ("The VM cluster `OCID`.")], patch_id: Annotated[Optional[Any], ("The `OCID` of the patch.")], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> Patch: @@ -7628,7 +7628,7 @@ def get_vm_cluster_patch_history_entry( Optional[Any], ("The `OCID` of the patch history entry.") ], region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> PatchHistoryEntry: @@ -7659,7 +7659,7 @@ def get_vm_cluster_update( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> VmClusterUpdate: @@ -7692,7 +7692,7 @@ def get_vm_cluster_update_history_entry( Optional[Any], ("Unique identifier for the request.") ] = None, region: Annotated[ - str, + Optional[str], "Region to execute the request (Use proper region identifiers like us-ashburn-1, eu-zurich-1), if unspecified then default region will be picked", ] = None, ) -> VmClusterUpdateHistoryEntry: diff --git a/src/oci-database-mcp-server/oracle/oci_database_mcp_server/tests/test_null_inputs.py b/src/oci-database-mcp-server/oracle/oci_database_mcp_server/tests/test_null_inputs.py new file mode 100644 index 00000000..50763a12 --- /dev/null +++ b/src/oci-database-mcp-server/oracle/oci_database_mcp_server/tests/test_null_inputs.py @@ -0,0 +1,55 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from types import SimpleNamespace +from unittest.mock import MagicMock + +import pytest +from fastmcp import Client +from fastmcp.exceptions import ToolError +from jsonschema import Draft202012Validator + +from oracle.oci_database_mcp_server import server + + +@pytest.mark.asyncio +async def test_null_defaults_are_accepted_by_advertised_schemas(): + async with Client(server.mcp) as client: + for tool in await client.list_tools(): + validator = Draft202012Validator(tool.inputSchema) + for name, schema in tool.inputSchema["properties"].items(): + if "default" in schema and schema["default"] is None: + assert validator.evolve(schema=schema).is_valid(None), f"{tool.name}.{name}" + + +@pytest.mark.asyncio +@pytest.mark.parametrize("region_args", [{}, {"region": None}, {"region": "us-ashburn-1"}]) +async def test_public_ip_accepts_default_and_explicit_regions(monkeypatch, region_args): + database = MagicMock() + database.get_database.return_value.data = SimpleNamespace( + db_system_id="sample", vm_cluster_id=None, compartment_id="sample" + ) + database.list_db_nodes.return_value.data = [] + monkeypatch.setattr( + server, "build_auth_context", + lambda: SimpleNamespace(config={"region": "us-phoenix-1"}, signer=None), + ) + factory = MagicMock(return_value=database) + monkeypatch.setattr(server.oci.database, "DatabaseClient", factory) + + async with Client(server.mcp) as client: + result = await client.call_tool( + "get_public_ip_for_database", {"database_id": "sample", **region_args} + ) + assert not result.is_error + assert factory.call_args.args[0]["region"] == (region_args.get("region") or "us-phoenix-1") + + +@pytest.mark.asyncio +async def test_public_ip_rejects_invalid_region(): + async with Client(server.mcp) as client: + with pytest.raises(ToolError, match="valid string"): + await client.call_tool("get_public_ip_for_database", {"database_id": "sample", "region": 7}) diff --git a/src/oci-networking-mcp-server/CHANGELOG.md b/src/oci-networking-mcp-server/CHANGELOG.md index a363fce2..590780b5 100644 --- a/src/oci-networking-mcp-server/CHANGELOG.md +++ b/src/oci-networking-mcp-server/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Fixed + +- Allow explicit null for optional VCN and VLAN filters in list_subnets, list_security_lists, and list_network_security_groups. + ### Security - Updated `cryptography` to 50.0.1 to prevent PKCS#7 EnvelopedData decryption from exposing a Bleichenbacher oracle through distinguishable errors and timing (CVE-2026-69247). 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 0d14dbda..40dae708 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 @@ -179,7 +179,7 @@ def create_vcn(compartment_id: str, cidr_block: str, display_name: str) -> Vcn: @mcp.tool( description="Lists the subnets in the specified compartment. Optionally filter by VCN." ) -def list_subnets(compartment_id: str, vcn_id: str = None) -> list[Subnet]: +def list_subnets(compartment_id: str, vcn_id: str | None = None) -> list[Subnet]: subnets: list[Subnet] = [] try: @@ -254,7 +254,7 @@ def create_subnet( ) def list_security_lists( compartment_id: Annotated[str, "Compartment ocid"], - vcn_id: Annotated[str, "VCN ocid"] = None, + vcn_id: Annotated[str | None, "VCN ocid"] = None, ) -> list[SecurityList]: security_lists: list[SecurityList] = [] @@ -306,8 +306,8 @@ def get_security_list(security_list_id: Annotated[str, "security list id"]): ) def list_network_security_groups( compartment_id: Annotated[str, "compartment ocid"], - vlan_id: Annotated[str, "vlan ocid"] = None, - vcn_id: Annotated[str, "vcn ocid"] = None, + vlan_id: Annotated[str | None, "vlan ocid"] = None, + vcn_id: Annotated[str | None, "vcn ocid"] = None, ) -> list[NetworkSecurityGroup]: nsgs: list[NetworkSecurityGroup] = [] diff --git a/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/tests/test_null_inputs.py b/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/tests/test_null_inputs.py new file mode 100644 index 00000000..c01a390b --- /dev/null +++ b/src/oci-networking-mcp-server/oracle/oci_networking_mcp_server/tests/test_null_inputs.py @@ -0,0 +1,53 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +from unittest.mock import MagicMock + +import pytest +from fastmcp import Client +from fastmcp.exceptions import ToolError +from jsonschema import Draft202012Validator + +from oracle.oci_networking_mcp_server import server + + +@pytest.mark.asyncio +async def test_null_defaults_are_accepted_by_advertised_schemas(): + async with Client(server.mcp) as client: + for tool in await client.list_tools(): + validator = Draft202012Validator(tool.inputSchema) + for name, schema in tool.inputSchema["properties"].items(): + if "default" in schema and schema["default"] is None: + assert validator.evolve(schema=schema).is_valid(None), f"{tool.name}.{name}" + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("tool_name", "filters"), + [("list_subnets", ("vcn_id",)), ("list_security_lists", ("vcn_id",)), + ("list_network_security_groups", ("vcn_id", "vlan_id"))], +) +@pytest.mark.parametrize("value", [None, "sample", 7]) +async def test_optional_network_filters(monkeypatch, tool_name, filters, value): + sdk = MagicMock() + response = getattr(sdk, tool_name).return_value + response.data = [] + response.has_next_page = False + response.next_page = None + monkeypatch.setattr(server, "get_networking_client", lambda: sdk) + + async with Client(server.mcp) as client: + omitted = await client.call_tool(tool_name, {"compartment_id": "sample"}) + assert omitted.structured_content["result"] == [] + arguments = {"compartment_id": "sample", **dict.fromkeys(filters, value)} + if isinstance(value, int): + with pytest.raises(ToolError, match="valid string"): + await client.call_tool(tool_name, arguments) + else: + result = await client.call_tool(tool_name, arguments) + assert result.structured_content["result"] == [] + for name in filters: + assert getattr(sdk, tool_name).call_args.kwargs[name] == value From 5495c2739dd471d1a6e638a313df59db87fb0dfe Mon Sep 17 00:00:00 2001 From: Dustin Sale Date: Wed, 7 Oct 2026 11:08:36 -0500 Subject: [PATCH 2/2] Fix nullable optional inputs in Oracle Data Studio Accept explicit null for 148 optional tool parameters across ADP, Essbase, and Data Transforms. Add focused schema and dispatch regression tests, preserving the coverage gate and coverage tests already on main. Refs oracle/mcp#470 Signed-off-by: Dustin Sale --- .../CHANGELOG.md | 4 + .../tests/test_null_inputs.py | 43 +++++ .../data_studio_mcp_server/tools/adp_tools.py | 82 +++++----- .../data_studio_mcp_server/tools/dt_tools.py | 66 ++++---- .../tools/essbase_tools.py | 148 +++++++++--------- 5 files changed, 195 insertions(+), 148 deletions(-) create mode 100644 src/oracle-data-studio-mcp-server/oracle/data_studio_mcp_server/tests/test_null_inputs.py diff --git a/src/oracle-data-studio-mcp-server/CHANGELOG.md b/src/oracle-data-studio-mcp-server/CHANGELOG.md index 05df75a1..79d0ec9f 100644 --- a/src/oracle-data-studio-mcp-server/CHANGELOG.md +++ b/src/oracle-data-studio-mcp-server/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Fixed + +- Allow explicit null for optional tool inputs across Essbase, ADP, and Data Transforms, matching omission. + ### Changed - Updated locked AnyIO to 4.14.2. diff --git a/src/oracle-data-studio-mcp-server/oracle/data_studio_mcp_server/tests/test_null_inputs.py b/src/oracle-data-studio-mcp-server/oracle/data_studio_mcp_server/tests/test_null_inputs.py new file mode 100644 index 00000000..3af132b9 --- /dev/null +++ b/src/oracle-data-studio-mcp-server/oracle/data_studio_mcp_server/tests/test_null_inputs.py @@ -0,0 +1,43 @@ +""" +Copyright (c) 2026, Oracle and/or its affiliates. +Licensed under the Universal Permissive License v1.0 as shown at +https://oss.oracle.com/licenses/upl. +""" + +import pytest +from jsonschema import Draft202012Validator +from mcp.server.fastmcp import FastMCP +from mcp.server.fastmcp.exceptions import ToolError + +from oracle.data_studio_mcp_server.tools import adp_tools, dt_tools, essbase_tools + + +def test_null_defaults_are_accepted_by_advertised_schemas(): + mcp = FastMCP("test") + for module in (adp_tools, dt_tools, essbase_tools): + module.register_tools(mcp) + for tool in mcp._tool_manager.list_tools(): + validator = Draft202012Validator(tool.parameters) + assert "ctx" not in tool.parameters["properties"] + for name, schema in tool.parameters["properties"].items(): + if "default" in schema and schema["default"] is None: + assert validator.evolve(schema=schema).is_valid(None), f"{tool.name}.{name}" + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("module", "connection", "tool_name", "required", "parameter", "valid", "invalid"), + [(adp_tools, "get_adp", "adp_build_analytic_view", {"fact_table": "FACT"}, "av_name", "AV", 7), + (essbase_tools, "get_essbase", "essbase_outline_metadata", + {"app_name": "Sample", "db_name": "Basic"}, "dimension_name", "Measures", 7), + (dt_tools, "get_dt", "dt_manage_connection", {"action": "list"}, "port", 1521, "invalid")], +) +async def test_optional_tool_inputs(monkeypatch, module, connection, tool_name, required, parameter, valid, invalid): + mcp = FastMCP("test") + module.register_tools(mcp) + monkeypatch.setattr(module, connection, lambda ctx: None) + omitted = await mcp.call_tool(tool_name, required) + for value in (None, valid): + assert await mcp.call_tool(tool_name, {**required, parameter: value}) == omitted + with pytest.raises(ToolError, match="validation error"): + await mcp.call_tool(tool_name, {**required, parameter: invalid}) diff --git a/src/oracle-data-studio-mcp-server/oracle/data_studio_mcp_server/tools/adp_tools.py b/src/oracle-data-studio-mcp-server/oracle/data_studio_mcp_server/tools/adp_tools.py index fb660a73..855d2de6 100644 --- a/src/oracle-data-studio-mcp-server/oracle/data_studio_mcp_server/tools/adp_tools.py +++ b/src/oracle-data-studio-mcp-server/oracle/data_studio_mcp_server/tools/adp_tools.py @@ -30,7 +30,7 @@ def register_tools(mcp: FastMCP): # ── adp_build_analytic_view ───────────────────────────────────── @mcp.tool() def adp_build_analytic_view(fact_table: str, - av_name: str = None, + av_name: str | None = None, ctx: Context = None) -> str: """Create an Analytic View from a fact table, compile it, and return metadata with a data preview. @@ -107,7 +107,7 @@ def adp_build_analytic_view(fact_table: str, @mcp.tool() def adp_query_analytic_view(av_name: str, show_sql: bool = False, - owner: str = None, + owner: str | None = None, max_rows: int = 1000, ctx: Context = None) -> str: """Query data from an Analytic View with auto-discovered dimensions and measures. @@ -157,7 +157,7 @@ def adp_query_analytic_view(av_name: str, # ── 19. adp_analyze_analytic_view ───────────────────────────────── @mcp.tool() - def adp_analyze_analytic_view(av_name: str, owner: str = None, + def adp_analyze_analytic_view(av_name: str, owner: str | None = None, ctx: Context = None) -> str: """Get a full health report for an Analytic View: metadata, measures, dimensions, quality issues, errors. @@ -299,11 +299,11 @@ def adp_search(search_term: str, include_ddl: bool = False, # ── 23. adp_load_from_cloud ─────────────────────────────────────── @mcp.tool() - def adp_load_from_cloud(storage_link: str = None, - object_name: str = None, - target_table: str = None, + def adp_load_from_cloud(storage_link: str | None = None, + object_name: str | None = None, + target_table: str | None = None, consumer_group: str = 'LOW', - request_id: str = None, + request_id: str | None = None, ctx: Context = None) -> str: """Load a cloud storage object into the database as a table. @@ -385,7 +385,7 @@ def adp_load_from_cloud(storage_link: str = None, # ── 24. adp_browse_catalog ────────────────────────────────────────── @mcp.tool() - def adp_browse_catalog(action: str, catalog_name: str = None, + def adp_browse_catalog(action: str, catalog_name: str | None = None, ctx: Context = None) -> str: """Browse data catalogs (read-only): list catalogs, get entities, preview data, list database links, list databases, or check a database link. @@ -439,8 +439,8 @@ def adp_browse_catalog(action: str, catalog_name: str = None, # ── 24b. adp_manage_catalog ────────────────────────────────────── @mcp.tool() - def adp_manage_catalog(action: str, catalog_name: str = None, - confirm: str = None, + def adp_manage_catalog(action: str, catalog_name: str | None = None, + confirm: str | None = None, ctx: Context = None) -> str: """Manage data catalogs (admin-only): enable, disable, or unmount catalogs. @@ -479,12 +479,12 @@ def adp_manage_catalog(action: str, catalog_name: str = None, # ── 25. adp_manage_sharing ──────────────────────────────────────── @mcp.tool() - def adp_manage_sharing(action: str, share_name: str = None, - tables: str = None, - recipient_name: str = None, - email: str = None, - new_name: str = None, - confirm: str = None, + def adp_manage_sharing(action: str, share_name: str | None = None, + tables: str | None = None, + recipient_name: str | None = None, + email: str | None = None, + new_name: str | None = None, + confirm: str | None = None, ctx: Context = None) -> str: """Manage data sharing: list shares, create share, publish, delete, manage recipients, providers, and more. @@ -640,9 +640,9 @@ def adp_manage_sharing(action: str, share_name: str = None, # ── 26. adp_manage_analytic_views ──────────────────────────────── @mcp.tool() def adp_manage_analytic_views(action: str, - av_name: str = None, - owner: str = None, - confirm: str = None, + av_name: str | None = None, + owner: str | None = None, + confirm: str | None = None, ctx: Context = None) -> str: """Manage Analytic Views: list all AVs or drop one. @@ -683,17 +683,17 @@ def adp_manage_analytic_views(action: str, # ── 27. adp_manage_credentials ─────────────────────────────────── @mcp.tool() def adp_manage_credentials(action: str, - credential_name: str = None, - username: str = None, - password: str = None, - user_ocid: str = None, - tenancy_ocid: str = None, - private_key: str = None, - fingerprint: str = None, - storage_link_name: str = None, - uri: str = None, - description: str = None, - confirm: str = None, + credential_name: str | None = None, + username: str | None = None, + password: str | None = None, + user_ocid: str | None = None, + tenancy_ocid: str | None = None, + private_key: str | None = None, + fingerprint: str | None = None, + storage_link_name: str | None = None, + uri: str | None = None, + description: str | None = None, + confirm: str | None = None, ctx: Context = None) -> str: """Manage cloud credentials and storage links for data loading. @@ -793,8 +793,8 @@ def adp_manage_credentials(action: str, @mcp.tool() def adp_ai_chat(question: str, mode: str = 'chat', - tables: str = None, - profile_name: str = None, + tables: str | None = None, + profile_name: str | None = None, max_rows: int = 1000, ctx: Context = None) -> str: """Chat with the database using Oracle Select AI in different modes. @@ -872,10 +872,10 @@ def adp_ai_chat(question: str, # ── 29. adp_manage_insights ────────────────────────────────────── @mcp.tool() def adp_manage_insights(action: str, - request_name: str = None, - insight_name: str = None, - viz_id: int = None, - confirm: str = None, + request_name: str | None = None, + insight_name: str | None = None, + viz_id: int | None = None, + confirm: str | None = None, ctx: Context = None) -> str: """Manage AI-generated insights: list requests, view results, check status. @@ -934,10 +934,10 @@ def adp_manage_insights(action: str, # ── 30. adp_manage_db_links ──────────────────────────────────────── @mcp.tool() def adp_manage_db_links(action: str, - db_link_name: str = None, - tables: str = None, + db_link_name: str | None = None, + tables: str | None = None, consumer_group: str = 'LOW', - confirm: str = None, + confirm: str | None = None, ctx: Context = None) -> str: """Manage database links and copy/link tables from remote databases. @@ -1014,8 +1014,8 @@ def adp_manage_db_links(action: str, @mcp.tool() def adp_get_annotations(object_name: str, object_type: str = 'TABLE', - annotation_owner: str = None, - column_name: str = None, + annotation_owner: str | None = None, + column_name: str | None = None, ctx: Context = None) -> str: """Fetch Oracle 23ai annotations on a table/view and its columns — useful for NL→SQL. diff --git a/src/oracle-data-studio-mcp-server/oracle/data_studio_mcp_server/tools/dt_tools.py b/src/oracle-data-studio-mcp-server/oracle/data_studio_mcp_server/tools/dt_tools.py index ed6ac4e6..ba7bf234 100644 --- a/src/oracle-data-studio-mcp-server/oracle/data_studio_mcp_server/tools/dt_tools.py +++ b/src/oracle-data-studio-mcp-server/oracle/data_studio_mcp_server/tools/dt_tools.py @@ -134,7 +134,7 @@ def dt_describe_project(project_name: str, # ── 28b. dt_manage_project ──────────────────────────────────────── @mcp.tool() def dt_manage_project(action: str, project_name: str, - confirm: str = None, + confirm: str | None = None, ctx: Context = None) -> str: """Manage Data Transforms projects (admin-only). @@ -230,7 +230,7 @@ def dt_create_pipeline(project_name: str, target_connection: str, target_schema: str, target_table: str, - dataflow_name: str = None, + dataflow_name: str | None = None, integration_type: str = 'CONTROL_APPEND', ctx: Context = None) -> str: """Create a simple source-to-target dataflow in a Data Transforms project. @@ -371,7 +371,7 @@ def dt_check_health(ctx: Context) -> str: # ── 32. dt_browse_data ──────────────────────────────────────────── @mcp.tool() - def dt_browse_data(connection_name: str, schema: str = None, + def dt_browse_data(connection_name: str, schema: str | None = None, ctx: Context = None) -> str: """Browse schemas and tables available through a Data Transforms connection. @@ -452,15 +452,15 @@ def dt_manage_dataflow(project_name: str, dataflow_name: str, # ── 34. dt_manage_schedule ──────────────────────────────────────── @mcp.tool() def dt_manage_schedule(action: str, - schedule_name: str = None, - project_name: str = None, - resource_name: str = None, + schedule_name: str | None = None, + project_name: str | None = None, + resource_name: str | None = None, resource_type: str = 'dataflow', - frequency: str = None, - time: str = None, - days: str = None, + frequency: str | None = None, + time: str | None = None, + days: str | None = None, status: str = 'INACTIVE', - confirm: str = None, + confirm: str | None = None, ctx: Context = None) -> str: """Manage Data Transforms schedules: list, create, or delete. @@ -576,8 +576,8 @@ def dt_manage_schedule(action: str, # ── 35. dt_manage_variables ─────────────────────────────────────── @mcp.tool() - def dt_manage_variables(action: str, variable_name: str = None, - value: str = None, + def dt_manage_variables(action: str, variable_name: str | None = None, + value: str | None = None, ctx: Context = None) -> str: """Manage Data Transforms variables: list, create, or update. @@ -653,16 +653,16 @@ def dt_run_pipeline(project_name: str, # ── 37. dt_manage_connection ───────────────────────────────────── @mcp.tool() def dt_manage_connection(action: str, - connection_name: str = None, - connection_type: str = None, - host: str = None, - port: int = None, - service_name: str = None, - user: str = None, - password: str = None, - wallet_path: str = None, - jdbc_url: str = None, - confirm: str = None, + connection_name: str | None = None, + connection_type: str | None = None, + host: str | None = None, + port: int | None = None, + service_name: str | None = None, + user: str | None = None, + password: str | None = None, + wallet_path: str | None = None, + jdbc_url: str | None = None, + confirm: str | None = None, ctx: Context = None) -> str: """Manage Data Transforms connections: list, create, delete, or test. @@ -746,12 +746,12 @@ def dt_manage_connection(action: str, @mcp.tool() def dt_manage_dataload(action: str, project_name: str, - dataload_name: str = None, - source_connection: str = None, - source_schema: str = None, - target_connection: str = None, - target_schema: str = None, - tables: str = None, + dataload_name: str | None = None, + source_connection: str | None = None, + source_schema: str | None = None, + target_connection: str | None = None, + target_schema: str | None = None, + tables: str | None = None, load_type: str = 'TRUNCATE', ctx: Context = None) -> str: """Manage dataloads in a Data Transforms project. @@ -838,10 +838,10 @@ def dt_manage_dataload(action: str, # ── 39. dt_manage_data_entities ────────────────────────────────── @mcp.tool() def dt_manage_data_entities(action: str, - connection_name: str = None, - schema_name: str = None, - entity_name: str = None, - entity_id: str = None, + connection_name: str | None = None, + schema_name: str | None = None, + entity_name: str | None = None, + entity_id: str | None = None, ctx: Context = None) -> str: """Manage data entities in Data Transforms: list, get details, or import from a connection. @@ -899,7 +899,7 @@ def dt_manage_data_entities(action: str, @mcp.tool() def dt_manage_workflow(action: str, project_name: str, - workflow_name: str = None, + workflow_name: str | None = None, ctx: Context = None) -> str: """Manage workflows in a Data Transforms project: list, get details, or check existence. diff --git a/src/oracle-data-studio-mcp-server/oracle/data_studio_mcp_server/tools/essbase_tools.py b/src/oracle-data-studio-mcp-server/oracle/data_studio_mcp_server/tools/essbase_tools.py index 7993c11c..f9728ca4 100644 --- a/src/oracle-data-studio-mcp-server/oracle/data_studio_mcp_server/tools/essbase_tools.py +++ b/src/oracle-data-studio-mcp-server/oracle/data_studio_mcp_server/tools/essbase_tools.py @@ -174,7 +174,7 @@ def essbase_query(app_name: str, db_name: str, mdx: str, # ── 4. essbase_browse_outline ───────────────────────────────────── @mcp.tool() def essbase_browse_outline(app_name: str, db_name: str, - parent: str = None, depth: int = 2, + parent: str | None = None, depth: int = 2, ctx: Context = None) -> str: """Browse the cube outline hierarchy. @@ -258,8 +258,8 @@ def essbase_search_members(app_name: str, db_name: str, # ── 6. essbase_run_calculation ──────────────────────────────────── @mcp.tool() def essbase_run_calculation(app_name: str, db_name: str, - script_name: str = None, - calc_script: str = None, + script_name: str | None = None, + calc_script: str | None = None, ctx: Context = None) -> str: """Run a calculation on an Essbase database — fire-and-forget with automatic wait. @@ -312,8 +312,8 @@ def essbase_run_calculation(app_name: str, db_name: str, # ── 7. essbase_load_data ────────────────────────────────────────── @mcp.tool() def essbase_load_data(app_name: str, db_name: str, - rule_file: str = None, - data_file: str = None, + rule_file: str | None = None, + data_file: str | None = None, ctx: Context = None) -> str: """Load data into an Essbase database. @@ -390,11 +390,11 @@ def essbase_deploy_workbook(app_name: str, file_path: str, # ── 9. essbase_manage_variables ─────────────────────────────────── @mcp.tool() def essbase_manage_variables(action: str, scope: str, - app_name: str = None, - db_name: str = None, - variable_name: str = None, - value: str = None, - confirm: str = None, + app_name: str | None = None, + db_name: str | None = None, + variable_name: str | None = None, + value: str | None = None, + confirm: str | None = None, ctx: Context = None) -> str: """Manage Essbase substitution variables at any scope. @@ -515,8 +515,8 @@ def essbase_get_script(app_name: str, db_name: str, # ── 11. essbase_manage_security ─────────────────────────────────── @mcp.tool() - def essbase_manage_security(username: str = None, - group_name: str = None, + def essbase_manage_security(username: str | None = None, + group_name: str | None = None, ctx: Context = None) -> str: """Get the complete security profile for a user or group. @@ -620,8 +620,8 @@ def essbase_server_health(ctx: Context) -> str: # ── 13. essbase_export_data ─────────────────────────────────────── @mcp.tool() def essbase_export_data(app_name: str, db_name: str, - mdx: str = None, - report_name: str = None, + mdx: str | None = None, + report_name: str | None = None, max_rows: int = 1000, ctx: Context = None) -> str: """Export data from an Essbase database. @@ -659,10 +659,10 @@ def essbase_export_data(app_name: str, db_name: str, # ── 14. essbase_manage_application ───────────────────────────────── @mcp.tool() def essbase_manage_application(action: str, app_name: str, - db_name: str = None, + db_name: str | None = None, db_type: str = 'BSO', - new_name: str = None, - confirm: str = None, + new_name: str | None = None, + confirm: str | None = None, ctx: Context = None) -> str: """Create, delete, copy, rename, start, or stop an Essbase application. @@ -736,9 +736,9 @@ def essbase_manage_application(action: str, app_name: str, def essbase_manage_script(action: str, app_name: str, db_name: str, script_name: str, - content: str = None, - new_name: str = None, - confirm: str = None, + content: str | None = None, + new_name: str | None = None, + confirm: str | None = None, ctx: Context = None) -> str: """Manage calc scripts: list, create, update, delete, or validate. @@ -827,10 +827,10 @@ def essbase_manage_script(action: str, app_name: str, # ── 16. essbase_manage_files ─────────────────────────────────────── @mcp.tool() - def essbase_manage_files(action: str, path: str = None, - target_path: str = None, - content: str = None, - confirm: str = None, + def essbase_manage_files(action: str, path: str | None = None, + target_path: str | None = None, + content: str | None = None, + confirm: str | None = None, ctx: Context = None) -> str: """Manage files in the Essbase file catalog: list, upload, download, copy, move, delete, create_folder. @@ -918,14 +918,14 @@ def essbase_manage_files(action: str, path: str = None, # ── 17. essbase_manage_connections ────────────────────────────────── @mcp.tool() def essbase_manage_connections(action: str, - connection_name: str = None, - host: str = None, - port: int = None, - service_name: str = None, - user: str = None, - password: str = None, + connection_name: str | None = None, + host: str | None = None, + port: int | None = None, + service_name: str | None = None, + user: str | None = None, + password: str | None = None, db_type: str = 'oracle', - confirm: str = None, + confirm: str | None = None, ctx: Context = None) -> str: """Manage Essbase global connections: list, get, create, update, test, or delete. @@ -1031,7 +1031,7 @@ def essbase_manage_connections(action: str, @mcp.tool() def essbase_manage_locks(app_name: str, db_name: str, action: str = 'list', - object_name: str = None, + object_name: str | None = None, ctx: Context = None) -> str: """View and manage locks on an Essbase database. @@ -1094,12 +1094,12 @@ def essbase_manage_locks(app_name: str, db_name: str, @mcp.tool() def essbase_manage_filters(action: str, app_name: str, db_name: str, - filter_name: str = None, - filter_rows: str = None, - user_or_group: str = None, + filter_name: str | None = None, + filter_rows: str | None = None, + user_or_group: str | None = None, access: str = 'read', - new_name: str = None, - confirm: str = None, + new_name: str | None = None, + confirm: str | None = None, ctx: Context = None) -> str: """Manage security filters for row-level access control on an Essbase database. @@ -1262,9 +1262,9 @@ def essbase_manage_filters(action: str, app_name: str, # ── 20. essbase_manage_jobs ──────────────────────────────────────── @mcp.tool() def essbase_manage_jobs(action: str = 'list', - job_id: int = None, + job_id: int | None = None, limit: int = 50, - confirm: str = None, + confirm: str | None = None, ctx: Context = None) -> str: """Monitor and manage Essbase jobs: list recent jobs, check status, or rerun a failed job. @@ -1323,15 +1323,15 @@ def essbase_manage_jobs(action: str = 'list', def essbase_edit_outline(app_name: str, db_name: str, action: str, member_name: str, - parent_name: str = None, - new_name: str = None, - formula: str = None, - consolidation: str = None, - data_storage: str = None, + parent_name: str | None = None, + new_name: str | None = None, + formula: str | None = None, + consolidation: str | None = None, + data_storage: str | None = None, alias_table: str = 'Default', - alias_value: str = None, - uda_value: str = None, - confirm: str = None, + alias_value: str | None = None, + uda_value: str | None = None, + confirm: str | None = None, ctx: Context = None) -> str: """Edit the cube outline: add, remove, move, or rename members and set properties. @@ -1451,11 +1451,11 @@ def essbase_edit_outline(app_name: str, db_name: str, # ── essbase_manage_datasources ──────────────────────────────────── @mcp.tool() def essbase_manage_datasources(action: str, - datasource_name: str = None, - connection: str = None, - query: str = None, - columns: str = None, - confirm: str = None, + datasource_name: str | None = None, + connection: str | None = None, + query: str | None = None, + columns: str | None = None, + confirm: str | None = None, ctx: Context = None) -> str: """Manage global datasources for data loads and drill-through. @@ -1534,12 +1534,12 @@ def essbase_manage_datasources(action: str, def essbase_manage_drill_through(action: str, app_name: str, db_name: str, - report_name: str = None, - connection: str = None, - sql_query: str = None, - columns: str = None, - drillable_regions: str = None, - confirm: str = None, + report_name: str | None = None, + connection: str | None = None, + sql_query: str | None = None, + columns: str | None = None, + drillable_regions: str | None = None, + confirm: str | None = None, ctx: Context = None) -> str: """Manage drill-through reports that link cube cells to detail data. @@ -1638,10 +1638,10 @@ def essbase_manage_drill_through(action: str, @mcp.tool() def essbase_manage_database(action: str, app_name: str, db_name: str, - new_name: str = None, - target_app: str = None, - target_db: str = None, - confirm: str = None, + new_name: str | None = None, + target_app: str | None = None, + target_db: str | None = None, + confirm: str | None = None, ctx: Context = None) -> str: """Manage databases within an Essbase application: delete, copy, or rename. @@ -1708,11 +1708,11 @@ def essbase_manage_database(action: str, app_name: str, # ── 25. essbase_manage_users ─────────────────────────────────────── @mcp.tool() def essbase_manage_users(action: str, - user_id: str = None, - password: str = None, - role: str = None, - app_name: str = None, - confirm: str = None, + user_id: str | None = None, + password: str | None = None, + role: str | None = None, + app_name: str | None = None, + confirm: str | None = None, ctx: Context = None) -> str: """Manage Essbase users: list, get details, create, update, delete, provision, deprovision roles, or list roles. @@ -1828,9 +1828,9 @@ def essbase_manage_users(action: str, # ── 26. essbase_manage_groups ────────────────────────────────────── @mcp.tool() def essbase_manage_groups(action: str, - group_id: str = None, - user_ids: str = None, - confirm: str = None, + group_id: str | None = None, + user_ids: str | None = None, + confirm: str | None = None, ctx: Context = None) -> str: """Manage Essbase groups: list, get details, create, delete, add/remove users, get users, add/remove subgroups. @@ -1955,8 +1955,8 @@ def essbase_manage_groups(action: str, # ── 27. essbase_manage_sessions ──────────────────────────────────── @mcp.tool() def essbase_manage_sessions(action: str = 'list', - session_id: str = None, - confirm: str = None, + session_id: str | None = None, + confirm: str | None = None, ctx: Context = None) -> str: """Manage Essbase sessions: list active sessions, get current session, kill a session, or kill all sessions. @@ -2010,7 +2010,7 @@ def essbase_manage_sessions(action: str = 'list', @mcp.tool() def essbase_manage_db_settings(app_name: str, db_name: str, category: str = 'all', - settings_json: str = None, + settings_json: str | None = None, ctx: Context = None) -> str: """Get or update detailed database settings and tuning information for performance analysis. @@ -2160,7 +2160,7 @@ def essbase_get_logs(app_name: str, @mcp.tool() def essbase_outline_metadata(app_name: str, db_name: str, category: str = 'all', - dimension_name: str = None, + dimension_name: str | None = None, ctx: Context = None) -> str: """Get detailed outline metadata: dimensions, generations, levels, smart lists, and outline settings.