Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/dbtools-mcp-server/dbtools-mcp-server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.

Expand Down
70 changes: 70 additions & 0 deletions src/dbtools-mcp-server/test_null_inputs.py
Original file line number Diff line number Diff line change
@@ -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})
4 changes: 2 additions & 2 deletions src/mysql-mcp-server/oracle/mysql_mcp_server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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})
4 changes: 4 additions & 0 deletions src/oci-cloud-guard-mcp-server/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Fixed

- Allow explicit null for the optional comment in update_problem_status, matching omission.

### Security

- Updated locked AnyIO to 4.15.1; updated locked multidict to 6.9.1; updated PyJWT to 2.15.1 and urllib3 to 2.8.0; 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).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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})
4 changes: 4 additions & 0 deletions src/oci-database-mcp-server/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 locked AnyIO to 4.15.1; updated locked multidict to 6.9.1; updated PyJWT to 2.15.1 and urllib3 to 2.8.0; 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).
Expand Down
Loading
Loading