Skip to content

MCP invalid JSON errors still include raw tool input when DONT_LOG_TOOL_DATA is enabled #3087

@Aphroq

Description

@Aphroq

Please read this first

  • Have you read the docs? Agents SDK docs
  • Have you searched for related issues? Others may have faced similar issues.

Describe the bug

MCPUtil.invoke_mcp_tool() suppresses debug logging when DONT_LOG_TOOL_DATA is enabled, but it still includes the raw malformed input_json in the raised ModelBehaviorError.

Current code in src/agents/mcp/util.py:

try:
    json_data: dict[str, Any] = json.loads(input_json) if input_json else {}
except Exception as e:
    if _debug.DONT_LOG_TOOL_DATA:
        logger.debug(f"Invalid JSON input for tool {tool.name}")
    else:
        logger.debug(f"Invalid JSON input for tool {tool.name}: {input_json}")
    raise ModelBehaviorError(
        f"Invalid JSON input for tool {tool.name}: {input_json}"
    ) from e

That means malformed MCP tool arguments can still leak secrets through exception strings even when the runtime is explicitly configured not to log tool data.

This is especially risky because MCP inputs often carry tokens, cookies, internal URLs, file paths, or user data, and exception strings frequently end up in application logs, traces, and alerting systems.

Debug information

  • Agents SDK version: main at f2fb9ffb (also reproducible from the local editable build reporting 0.15.1)
  • Python version: Python 3.12

Repro steps

Minimal reproducer:

import asyncio

from agents.mcp.server import MCPServer
from agents.mcp.util import MCPUtil
from mcp import Tool as MCPTool


class FakeServer(MCPServer):
    @property
    def name(self) -> str:
        return "fake"

    async def connect(self):
        return None

    async def cleanup(self):
        return None

    async def list_tools(self, run_context=None, agent=None):
        return []

    async def list_prompts(self, run_context=None, agent=None):
        return []

    async def get_prompt(self, name: str, arguments=None):
        raise NotImplementedError

    async def call_tool(self, tool_name, arguments, meta=None):
        raise NotImplementedError


async def main() -> None:
    server = FakeServer()
    tool = MCPTool(name="test_tool", inputSchema={})

    try:
        await MCPUtil.invoke_mcp_tool(
            server,
            tool,
            None,
            '{"secret":"SECRET_TOKEN_123"',
        )
    except Exception as exc:
        print(type(exc).__name__)
        print(str(exc))


asyncio.run(main())

Current output:

ModelBehaviorError
Invalid JSON input for tool test_tool: {"secret":"SECRET_TOKEN_123"

Suggested regression coverage:

  1. Enable the no-tool-data logging mode.
  2. Pass malformed JSON containing a sentinel secret.
  3. Assert that both logs and the raised exception string omit the raw payload.

Expected behavior

When DONT_LOG_TOOL_DATA is enabled, malformed MCP input should be redacted consistently across both logging and exceptions.

For example, the raised error should look more like:

Invalid JSON input for tool test_tool

instead of embedding the raw input payload.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions