From 20d4dcc4ed01e44db25c3057eea65786fb38eb6b Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Fri, 26 Jun 2026 17:42:05 -0700 Subject: [PATCH] fix(mcp): stop logging tool-call input in MCP client (#31393) The MCP client logged the full tool arguments (and prompt arguments) at INFO on every call, so caller input such as user queries, model names, and instructions landed in the proxy application logs and any downstream log aggregator Log only the tool or prompt name and drop the arguments from these INFO lines (cherry picked from commit 7acc0157dffed1521dad610a5807ddd207fa9015) --- litellm/experimental_mcp_client/client.py | 4 +- .../test_mcp_client.py | 46 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/litellm/experimental_mcp_client/client.py b/litellm/experimental_mcp_client/client.py index c6d427e7f09..63be47b64f8 100644 --- a/litellm/experimental_mcp_client/client.py +++ b/litellm/experimental_mcp_client/client.py @@ -555,7 +555,7 @@ class MCPClient: Call an MCP Tool. """ verbose_logger.info( - f"MCP client calling tool '{call_tool_request_params.name}' with arguments: {call_tool_request_params.arguments}" + f"MCP client calling tool '{call_tool_request_params.name}'" ) async def on_progress( @@ -664,7 +664,7 @@ class MCPClient: ) -> GetPromptResult: """Fetch a prompt definition from the MCP server.""" verbose_logger.info( - f"MCP client fetching prompt '{get_prompt_request_params.name}' with arguments: {get_prompt_request_params.arguments}" + f"MCP client fetching prompt '{get_prompt_request_params.name}'" ) async def _get_prompt_operation(session: ClientSession): diff --git a/tests/test_litellm/experimental_mcp_client/test_mcp_client.py b/tests/test_litellm/experimental_mcp_client/test_mcp_client.py index c9e500b4a5b..5cf062ab8ac 100644 --- a/tests/test_litellm/experimental_mcp_client/test_mcp_client.py +++ b/tests/test_litellm/experimental_mcp_client/test_mcp_client.py @@ -543,5 +543,51 @@ class TestExecuteSessionOperationSurfacesTransportError: assert result == "done" +def _all_logged_messages(mock_logger): + return " ".join( + str(call.args[0]) + for level in ("info", "debug", "warning", "error", "exception") + for call in getattr(mock_logger, level).call_args_list + if call.args + ) + + +@pytest.mark.asyncio +async def test_call_tool_does_not_log_arguments(): + from mcp.types import CallToolRequestParams + + secret = "ssn-123-45-6789" + client = MCPClient(server_url="http://test-server") + client.run_with_session = AsyncMock(return_value=MagicMock()) + params = CallToolRequestParams( + name="search_tool", arguments={"input": secret, "model": "gpt-5-mini"} + ) + + with patch.object(mcp_client_module, "verbose_logger") as mock_logger: + await client.call_tool(params) + + logged = _all_logged_messages(mock_logger) + assert "search_tool" in logged + assert secret not in logged + assert "gpt-5-mini" not in logged + + +@pytest.mark.asyncio +async def test_get_prompt_does_not_log_arguments(): + from mcp.types import GetPromptRequestParams + + secret = "ssn-987-65-4321" + client = MCPClient(server_url="http://test-server") + client.run_with_session = AsyncMock(return_value=MagicMock()) + params = GetPromptRequestParams(name="my_prompt", arguments={"input": secret}) + + with patch.object(mcp_client_module, "verbose_logger") as mock_logger: + await client.get_prompt(params) + + logged = _all_logged_messages(mock_logger) + assert "my_prompt" in logged + assert secret not in logged + + if __name__ == "__main__": pytest.main([__file__])