From 7acc0157dffed1521dad610a5807ddd207fa9015 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 --- 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 5baa7cbc9c5..ba07d116e26 100644 --- a/litellm/experimental_mcp_client/client.py +++ b/litellm/experimental_mcp_client/client.py @@ -563,7 +563,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( @@ -672,7 +672,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 704dd7f92f1..499d98e5cfe 100644 --- a/tests/test_litellm/experimental_mcp_client/test_mcp_client.py +++ b/tests/test_litellm/experimental_mcp_client/test_mcp_client.py @@ -582,5 +582,51 @@ class TestMCPClientResolvedAuth: await http_client.aclose() +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__])