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
Original file line number Diff line number Diff line change
Expand Up @@ -807,10 +807,16 @@ def get_mcp_tool(
if isinstance(approval_mode, str):
mcp["require_approval"] = "always" if approval_mode == "always_require" else "never"
else:
# Both keys can be supplied together ("and/or" per the docstring), so build
# one combined mapping instead of letting the second assignment overwrite
# the first, which silently dropped the always-approval tool list.
require_approval: dict[str, Any] = {}
if always_require := approval_mode.get("always_require_approval"):
mcp["require_approval"] = {"always": {"tool_names": always_require}}
require_approval["always"] = {"tool_names": always_require}
if never_require := approval_mode.get("never_require_approval"):
mcp["require_approval"] = {"never": {"tool_names": never_require}}
require_approval["never"] = {"tool_names": never_require}
if require_approval:
mcp["require_approval"] = require_approval

return mcp

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,23 @@ async def test_bad_request_error_non_content_filter() -> None:
assert "failed to complete the prompt" in str(exc_info.value)


def test_get_mcp_tool_merges_both_approval_lists() -> None:
"""Both approval lists must survive: overwriting the first with the second would
silently auto-approve tools the caller asked to gate."""
tool_config = FoundryChatClient.get_mcp_tool(
name="MCP",
url="https://mcp.example",
approval_mode={
"always_require_approval": ["dangerous_tool"],
"never_require_approval": ["safe_tool"],
},
)
assert tool_config["require_approval"] == {
"always": {"tool_names": ["dangerous_tool"]},
"never": {"tool_names": ["safe_tool"]},
}


def test_get_mcp_tool_with_project_connection_id() -> None:
tool_config = FoundryChatClient.get_mcp_tool(
name="Docs MCP",
Expand Down
10 changes: 8 additions & 2 deletions python/packages/openai/agent_framework_openai/_chat_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1619,10 +1619,16 @@ def get_mcp_tool(
if isinstance(approval_mode, str):
mcp["require_approval"] = "always" if approval_mode == "always_require" else "never"
else:
# Both keys can be supplied together ("and/or" per the docstring), so build
# one combined mapping instead of letting the second assignment overwrite
# the first, which silently dropped the always-approval tool list.
require_approval: dict[str, Any] = {}
if always_require := approval_mode.get("always_require_approval"):
mcp["require_approval"] = {"always": {"tool_names": always_require}}
require_approval["always"] = {"tool_names": always_require}
if never_require := approval_mode.get("never_require_approval"):
mcp["require_approval"] = {"never": {"tool_names": never_require}}
require_approval["never"] = {"tool_names": never_require}
if require_approval:
mcp["require_approval"] = require_approval

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please apply this same combined-mapping fix and regression coverage to FoundryChatClient.get_mcp_tool in python/packages/foundry/agent_framework_foundry/_chat_client.py. That public sibling currently has the identical two-assignment overwrite, so supplying both lists still drops always_require_approval for Foundry users and silently weakens the requested approval policy.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. FoundryChatClient.get_mcp_tool now builds the same combined require_approval mapping, with a matching regression test asserting both lists survive together. It fails on the previous head of this branch and passes now, the full foundry chat client suite passes (104 tests), and ruff is clean on both files. Amended into the branch with the title updated to name both helpers.

return mcp

Expand Down
17 changes: 17 additions & 0 deletions python/packages/openai/tests/openai/test_openai_chat_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6476,6 +6476,23 @@ def test_prepare_tools_for_openai_with_mcp() -> None:
assert "require_approval" in mcp


def test_get_mcp_tool_merges_both_approval_lists() -> None:
"""Both approval lists must survive: overwriting the first with the second would
silently auto-approve tools the caller asked to gate."""
tool_config = OpenAIChatClient.get_mcp_tool(
name="MCP",
url="https://mcp.example",
approval_mode={
"always_require_approval": ["dangerous_tool"],
"never_require_approval": ["safe_tool"],
},
)
assert tool_config["require_approval"] == {
"always": {"tool_names": ["dangerous_tool"]},
"never": {"tool_names": ["safe_tool"]},
}


@pytest.mark.parametrize("allowed_tools", [None, [], ["read_only"]], ids=["omitted", "empty", "nonempty"])
async def test_get_mcp_tool_preserves_allowed_tools_in_request(allowed_tools: list[str] | None) -> None:
tool_config = OpenAIChatClient.get_mcp_tool(
Expand Down
Loading