Skip to content

McpTool's require_confirmation gate doesn't set skip_summarization #6977

Description

@OTPkat

Title

McpTool's require_confirmation gate doesn't set skip_summarization, causing an infinite confirmation loop

Labels / component

tools / mcp_tool / tool confirmation (experimental)

Body

Environment

  • google-adk 2.5.0 and 2.8.0 (both verified in source, see below)
  • Python 3.12, McpToolset(..., require_confirmation=True) over StreamableHTTPConnectionParams
  • Reproduced with adk web-launched runs and on Vertex AI Agent Engine (any runner that drives BaseLlmFlow)

Summary

When a tool served by McpToolset(require_confirmation=True) is called, McpTool.run_async's confirmation branch calls tool_context.request_confirmation(...) and returns the "This tool call requires confirmation, please approve or reject." error dict — but unlike FunctionTool.run_async, it never sets tool_context.actions.skip_summarization = True.

Because the flag is missing, the function-response event carrying the pause error is not treated as a final response, so the LLM flow re-invokes the model with that error in context. A model that reacts to the "error" by retrying the tool call produces a new confirmation request, which pauses again, and so on — an unbounded loop of tool call → pause → model re-invocation → retried tool call within a single invocation, with no user input in between. We observed ~15 consecutive createItem → adk_request_confirmation cycles in one turn before killing the session; every pause event in the trace had empty actions.

FunctionTool with require_confirmation=True does not loop: its gate sets the flag, the turn ends at the pause, and the client gets exactly one adk_request_confirmation to answer.

Source comparison

google/adk/tools/function_tool.py (2.8.0, line ~343) — flag is set:

if require_confirmation:
  if not tool_context.tool_confirmation:
    tool_context.request_confirmation(
        hint=(...),
    )
    tool_context.actions.skip_summarization = True
    return {
        'error': 'This tool call requires confirmation, please approve or reject.'
    }

google/adk/tools/mcp_tool/mcp_tool.py (2.8.0, line ~401) — flag is never set:

if require_confirmation:
  if not tool_context.tool_confirmation:
    tool_context.request_confirmation(
        hint=(...),
    )
    return {
        "error": "This tool call requires confirmation, please approve or reject."
    }

The McpTool branch is otherwise identical to the FunctionTool one; the missing line looks like an oversight when the confirmation gate was brought to MCP tools (#3008 is the feature request that added it).

Steps to reproduce

  1. Any MCP server exposing one write-ish tool (a trivial FastMCP create_thing(name) is enough).
  2. Wire it into an agent via McpToolset(connection_params=..., require_confirmation=True).
  3. Prompt the agent to call the tool ("create a thing called banana").
  4. Inspect the event stream: the confirmation pause is followed by another model turn and a repeated tool call instead of ending the invocation. With a model that persistently retries on tool errors (we hit this with gemini-2.5-flash class models), the loop does not terminate on its own.

Expected behavior

The invocation ends at the confirmation pause — exactly one adk_request_confirmation long-running call is emitted, and nothing else happens until the client answers it with a FunctionResponse ({"confirmed": true|false}), the same contract FunctionTool honors.

Workaround we ship

A before_tool_callback that performs the gate itself for MCP tools, so McpTool.run_async's branch is never reached:

def pause_for_confirmation(tool, args, tool_context):
    if tool.name not in GATED_TOOL_NAMES or tool_context.tool_confirmation:
        return None
    tool_context.request_confirmation(
        hint=f"Please approve or reject the tool call {tool.name}() by responding"
        " with a FunctionResponse with an expected ToolConfirmation payload.",
    )
    tool_context.actions.skip_summarization = True
    return {"error": "This tool call requires confirmation, please approve or reject."}

This works because the flow copies the callback's tool_context.actions onto the response event, making the pause final. Happy to open a PR adding the one-line fix to McpTool if that's welcome.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

mcp[Component] This issues is related to MCP support

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions