Skip to content

Tool Context

ToolContext dataclass

Bases: RunContextWrapper[TContext]

The context of a tool call.

Source code in src/agents/tool_context.py
@dataclass(eq=False)
class ToolContext(RunContextWrapper[TContext]):
    """The context of a tool call."""

    tool_name: str = field(default_factory=_assert_must_pass_tool_name)
    """The name of the tool being invoked."""

    tool_call_id: str = field(default_factory=_assert_must_pass_tool_call_id)
    """The ID of the tool call."""

    tool_arguments: str = field(default_factory=_assert_must_pass_tool_arguments)
    """The raw arguments string of the tool call."""

    tool_call: ResponseFunctionToolCall | None = None
    """The tool call object associated with this invocation."""

    tool_namespace: str | None = None
    """The Responses API namespace for this tool call, when present."""

    agent: AgentBase[Any] | None = None
    """The active agent for this tool call, when available."""

    run_config: RunConfig | None = None
    """The active run config for this tool call, when available."""

    def __init__(
        self,
        context: TContext,
        usage: Usage | object = _MISSING,
        tool_name: str | object = _MISSING,
        tool_call_id: str | object = _MISSING,
        tool_arguments: str | object = _MISSING,
        tool_call: ResponseFunctionToolCall | None = None,
        *,
        tool_namespace: str | None = None,
        agent: AgentBase[Any] | None = None,
        run_config: RunConfig | dict[str, Any] | None = None,
        turn_input: list[TResponseInputItem] | None = None,
        _approvals: dict[str | HostedMCPApprovalKey, _ApprovalRecord] | None = None,
        tool_input: Any | None = None,
    ) -> None:
        """Preserve the v0.7 positional constructor while accepting new context fields."""
        resolved_usage = Usage() if usage is _MISSING else cast(Usage, usage)
        super().__init__(
            context=context,
            usage=resolved_usage,
            turn_input=list(turn_input or []),
            _approvals={} if _approvals is None else _approvals,
            tool_input=tool_input,
        )
        self.tool_name = (
            _assert_must_pass_tool_name() if tool_name is _MISSING else cast(str, tool_name)
        )
        self.tool_arguments = (
            _assert_must_pass_tool_arguments()
            if tool_arguments is _MISSING
            else cast(str, tool_arguments)
        )
        self.tool_call_id = (
            _assert_must_pass_tool_call_id()
            if tool_call_id is _MISSING
            else cast(str, tool_call_id)
        )
        self.tool_call = tool_call
        self.tool_namespace = (
            tool_namespace
            if isinstance(tool_namespace, str)
            else get_tool_call_namespace(tool_call)
        )
        self.agent = agent
        if run_config is not None:
            from .run_config import _coerce_run_config

            self.run_config = _coerce_run_config(run_config)
        else:
            self.run_config = None
        # Internal adapter hook used to attach SDK-only custom data to the emitted output item.
        self._custom_data: dict[str, Any] | None = None

    @property
    def qualified_tool_name(self) -> str:
        """Return the tool name qualified by namespace when available."""
        return tool_trace_name(self.tool_name, self.tool_namespace) or self.tool_name

    def _find_nested_approval_target(
        self,
        approval_item: ToolApprovalItem,
    ) -> tuple[RunContextWrapper[Any], ToolApprovalItem] | None:
        """Find a pending nested agent-tool context that owns an approval item."""
        if self.tool_call is None:
            return None
        pending_result = peek_agent_tool_run_result(
            self.tool_call,
            scope_id=get_agent_tool_state_scope(self),
        )
        interruptions = getattr(pending_result, "interruptions", None)
        to_state = getattr(pending_result, "to_state", None)
        if not isinstance(interruptions, list) or not callable(to_state):
            return None
        nested_context = getattr(to_state(), "_context", None)
        if not isinstance(nested_context, RunContextWrapper) or nested_context is self:
            return None

        target_identity = tool_invocation_identity(
            approval_item.raw_item,
            tool_lookup_key=approval_item.tool_lookup_key,
            tool_name=approval_item.tool_name,
        )
        target_identity_and_scope = tool_invocation_identity_and_scope(
            approval_item.raw_item,
            tool_lookup_key=approval_item.tool_lookup_key,
            tool_name=approval_item.tool_name,
        )
        current_context_owns_approval = False
        if target_identity_and_scope is not None:
            invocation_type, call_id, approval_scope, fingerprint = target_identity_and_scope
            current_record = self._tool_invocations.get(call_id)
            current_context_owns_approval = current_record is not None and (
                not current_record.completed
                and current_record.invocation_type == invocation_type
                and current_record.approval_scope == approval_scope
                and current_record.fingerprint == fingerprint
            )

        exact_match: ToolApprovalItem | None = None
        canonical_matches: list[ToolApprovalItem] = []
        for candidate in interruptions:
            if candidate is approval_item:
                exact_match = candidate
                continue
            candidate_identity = tool_invocation_identity(
                candidate.raw_item,
                tool_lookup_key=candidate.tool_lookup_key,
                tool_name=candidate.tool_name,
            )
            if target_identity is not None and candidate_identity == target_identity:
                canonical_matches.append(candidate)
        if current_context_owns_approval and (exact_match is not None or canonical_matches):
            raise UserError(
                "Cannot apply approval because the same tool invocation identity belongs to both "
                "the current run and a nested agent-tool run."
            )
        if exact_match is not None:
            return (nested_context, exact_match)
        if len(canonical_matches) == 1:
            return (nested_context, canonical_matches[0])
        if len(canonical_matches) > 1:
            raise UserError(
                "Cannot apply approval because multiple nested agent-tool calls contain the same "
                "tool invocation identity. Use distinct call IDs."
            )
        return None

    def approve_tool(self, approval_item: ToolApprovalItem, always_approve: bool = False) -> None:
        """Approve this context's call or route a surfaced nested approval to its owner."""
        nested_target = self._find_nested_approval_target(approval_item)
        if nested_target is None:
            super().approve_tool(approval_item, always_approve=always_approve)
            return
        nested_context, nested_item = nested_target
        RunContextWrapper.approve_tool(
            nested_context,
            nested_item,
            always_approve=always_approve,
        )

    def reject_tool(
        self,
        approval_item: ToolApprovalItem,
        always_reject: bool = False,
        rejection_message: str | None = None,
    ) -> None:
        """Reject this context's call or route a surfaced nested rejection to its owner."""
        nested_target = self._find_nested_approval_target(approval_item)
        if nested_target is None:
            super().reject_tool(
                approval_item,
                always_reject=always_reject,
                rejection_message=rejection_message,
            )
            return
        nested_context, nested_item = nested_target
        RunContextWrapper.reject_tool(
            nested_context,
            nested_item,
            always_reject=always_reject,
            rejection_message=rejection_message,
        )

    @classmethod
    def from_agent_context(
        cls,
        context: RunContextWrapper[TContext],
        tool_call_id: str,
        tool_call: ResponseFunctionToolCall | None = None,
        agent: AgentBase[Any] | None = None,
        *,
        tool_name: str | None = None,
        tool_arguments: str | None = None,
        tool_namespace: str | None = None,
        run_config: RunConfig | dict[str, Any] | None = None,
    ) -> ToolContext:
        """
        Create a ToolContext from a RunContextWrapper.
        """
        # Grab the names of the RunContextWrapper's init=True fields
        base_values: dict[str, Any] = {
            f.name: getattr(context, f.name)
            for f in fields(RunContextWrapper)
            if f.init and f.name != "_approvals"
        }
        resolved_tool_name = (
            tool_name
            if tool_name is not None
            else (tool_call.name if tool_call is not None else _assert_must_pass_tool_name())
        )
        resolved_tool_args = (
            tool_arguments
            if tool_arguments is not None
            else (
                tool_call.arguments if tool_call is not None else _assert_must_pass_tool_arguments()
            )
        )
        tool_agent = agent
        if tool_agent is None and isinstance(context, ToolContext):
            tool_agent = context.agent
        tool_run_config = run_config
        if tool_run_config is None and isinstance(context, ToolContext):
            tool_run_config = context.run_config

        tool_context = cls(
            tool_name=resolved_tool_name,
            tool_call_id=tool_call_id,
            tool_arguments=resolved_tool_args,
            tool_call=tool_call,
            tool_namespace=(
                tool_namespace
                if isinstance(tool_namespace, str)
                else (
                    getattr(tool_call, "namespace", None)
                    if tool_call is not None
                    and isinstance(getattr(tool_call, "namespace", None), str)
                    else None
                )
            ),
            agent=tool_agent,
            run_config=tool_run_config,
            **base_values,
        )
        context._share_tool_state_with(tool_context)
        set_agent_tool_state_scope(tool_context, get_agent_tool_state_scope(context))
        return tool_context

run_config class-attribute instance-attribute

run_config: RunConfig | None = None

The active run config for this tool call, when available.

tool_name class-attribute instance-attribute

tool_name: str = (
    _assert_must_pass_tool_name()
    if tool_name is _MISSING
    else cast(str, tool_name)
)

The name of the tool being invoked.

tool_arguments class-attribute instance-attribute

tool_arguments: str = (
    _assert_must_pass_tool_arguments()
    if tool_arguments is _MISSING
    else cast(str, tool_arguments)
)

The raw arguments string of the tool call.

tool_call_id class-attribute instance-attribute

tool_call_id: str = (
    _assert_must_pass_tool_call_id()
    if tool_call_id is _MISSING
    else cast(str, tool_call_id)
)

The ID of the tool call.

tool_call class-attribute instance-attribute

tool_call: ResponseFunctionToolCall | None = tool_call

The tool call object associated with this invocation.

tool_namespace class-attribute instance-attribute

tool_namespace: str | None = (
    tool_namespace
    if isinstance(tool_namespace, str)
    else get_tool_call_namespace(tool_call)
)

The Responses API namespace for this tool call, when present.

agent class-attribute instance-attribute

agent: AgentBase[Any] | None = agent

The active agent for this tool call, when available.

qualified_tool_name property

qualified_tool_name: str

Return the tool name qualified by namespace when available.

context instance-attribute

context: TContext

The context object (or None), passed by you to Runner.run()

usage class-attribute instance-attribute

usage: Usage = field(default_factory=Usage)

The usage of the agent run so far. For streamed responses, the usage will be stale until the last chunk of the stream is processed.

tool_input class-attribute instance-attribute

tool_input: Any | None = None

Structured input for the current agent tool run, when available.

__init__

__init__(
    context: TContext,
    usage: Usage | object = _MISSING,
    tool_name: str | object = _MISSING,
    tool_call_id: str | object = _MISSING,
    tool_arguments: str | object = _MISSING,
    tool_call: ResponseFunctionToolCall | None = None,
    *,
    tool_namespace: str | None = None,
    agent: AgentBase[Any] | None = None,
    run_config: RunConfig | dict[str, Any] | None = None,
    turn_input: list[TResponseInputItem] | None = None,
    _approvals: dict[
        str | HostedMCPApprovalKey, _ApprovalRecord
    ]
    | None = None,
    tool_input: Any | None = None,
) -> None

Preserve the v0.7 positional constructor while accepting new context fields.

Source code in src/agents/tool_context.py
def __init__(
    self,
    context: TContext,
    usage: Usage | object = _MISSING,
    tool_name: str | object = _MISSING,
    tool_call_id: str | object = _MISSING,
    tool_arguments: str | object = _MISSING,
    tool_call: ResponseFunctionToolCall | None = None,
    *,
    tool_namespace: str | None = None,
    agent: AgentBase[Any] | None = None,
    run_config: RunConfig | dict[str, Any] | None = None,
    turn_input: list[TResponseInputItem] | None = None,
    _approvals: dict[str | HostedMCPApprovalKey, _ApprovalRecord] | None = None,
    tool_input: Any | None = None,
) -> None:
    """Preserve the v0.7 positional constructor while accepting new context fields."""
    resolved_usage = Usage() if usage is _MISSING else cast(Usage, usage)
    super().__init__(
        context=context,
        usage=resolved_usage,
        turn_input=list(turn_input or []),
        _approvals={} if _approvals is None else _approvals,
        tool_input=tool_input,
    )
    self.tool_name = (
        _assert_must_pass_tool_name() if tool_name is _MISSING else cast(str, tool_name)
    )
    self.tool_arguments = (
        _assert_must_pass_tool_arguments()
        if tool_arguments is _MISSING
        else cast(str, tool_arguments)
    )
    self.tool_call_id = (
        _assert_must_pass_tool_call_id()
        if tool_call_id is _MISSING
        else cast(str, tool_call_id)
    )
    self.tool_call = tool_call
    self.tool_namespace = (
        tool_namespace
        if isinstance(tool_namespace, str)
        else get_tool_call_namespace(tool_call)
    )
    self.agent = agent
    if run_config is not None:
        from .run_config import _coerce_run_config

        self.run_config = _coerce_run_config(run_config)
    else:
        self.run_config = None
    # Internal adapter hook used to attach SDK-only custom data to the emitted output item.
    self._custom_data: dict[str, Any] | None = None

approve_tool

approve_tool(
    approval_item: ToolApprovalItem,
    always_approve: bool = False,
) -> None

Approve this context's call or route a surfaced nested approval to its owner.

Source code in src/agents/tool_context.py
def approve_tool(self, approval_item: ToolApprovalItem, always_approve: bool = False) -> None:
    """Approve this context's call or route a surfaced nested approval to its owner."""
    nested_target = self._find_nested_approval_target(approval_item)
    if nested_target is None:
        super().approve_tool(approval_item, always_approve=always_approve)
        return
    nested_context, nested_item = nested_target
    RunContextWrapper.approve_tool(
        nested_context,
        nested_item,
        always_approve=always_approve,
    )

reject_tool

reject_tool(
    approval_item: ToolApprovalItem,
    always_reject: bool = False,
    rejection_message: str | None = None,
) -> None

Reject this context's call or route a surfaced nested rejection to its owner.

Source code in src/agents/tool_context.py
def reject_tool(
    self,
    approval_item: ToolApprovalItem,
    always_reject: bool = False,
    rejection_message: str | None = None,
) -> None:
    """Reject this context's call or route a surfaced nested rejection to its owner."""
    nested_target = self._find_nested_approval_target(approval_item)
    if nested_target is None:
        super().reject_tool(
            approval_item,
            always_reject=always_reject,
            rejection_message=rejection_message,
        )
        return
    nested_context, nested_item = nested_target
    RunContextWrapper.reject_tool(
        nested_context,
        nested_item,
        always_reject=always_reject,
        rejection_message=rejection_message,
    )

from_agent_context classmethod

from_agent_context(
    context: RunContextWrapper[TContext],
    tool_call_id: str,
    tool_call: ResponseFunctionToolCall | None = None,
    agent: AgentBase[Any] | None = None,
    *,
    tool_name: str | None = None,
    tool_arguments: str | None = None,
    tool_namespace: str | None = None,
    run_config: RunConfig | dict[str, Any] | None = None,
) -> ToolContext

Create a ToolContext from a RunContextWrapper.

Source code in src/agents/tool_context.py
@classmethod
def from_agent_context(
    cls,
    context: RunContextWrapper[TContext],
    tool_call_id: str,
    tool_call: ResponseFunctionToolCall | None = None,
    agent: AgentBase[Any] | None = None,
    *,
    tool_name: str | None = None,
    tool_arguments: str | None = None,
    tool_namespace: str | None = None,
    run_config: RunConfig | dict[str, Any] | None = None,
) -> ToolContext:
    """
    Create a ToolContext from a RunContextWrapper.
    """
    # Grab the names of the RunContextWrapper's init=True fields
    base_values: dict[str, Any] = {
        f.name: getattr(context, f.name)
        for f in fields(RunContextWrapper)
        if f.init and f.name != "_approvals"
    }
    resolved_tool_name = (
        tool_name
        if tool_name is not None
        else (tool_call.name if tool_call is not None else _assert_must_pass_tool_name())
    )
    resolved_tool_args = (
        tool_arguments
        if tool_arguments is not None
        else (
            tool_call.arguments if tool_call is not None else _assert_must_pass_tool_arguments()
        )
    )
    tool_agent = agent
    if tool_agent is None and isinstance(context, ToolContext):
        tool_agent = context.agent
    tool_run_config = run_config
    if tool_run_config is None and isinstance(context, ToolContext):
        tool_run_config = context.run_config

    tool_context = cls(
        tool_name=resolved_tool_name,
        tool_call_id=tool_call_id,
        tool_arguments=resolved_tool_args,
        tool_call=tool_call,
        tool_namespace=(
            tool_namespace
            if isinstance(tool_namespace, str)
            else (
                getattr(tool_call, "namespace", None)
                if tool_call is not None
                and isinstance(getattr(tool_call, "namespace", None), str)
                else None
            )
        ),
        agent=tool_agent,
        run_config=tool_run_config,
        **base_values,
    )
    context._share_tool_state_with(tool_context)
    set_agent_tool_state_scope(tool_context, get_agent_tool_state_scope(context))
    return tool_context

is_tool_approved

is_tool_approved(
    tool_name: str, call_id: str
) -> bool | None

Return True/False/None for the given tool call.

Source code in src/agents/run_context.py
def is_tool_approved(self, tool_name: str, call_id: str) -> bool | None:
    """Return True/False/None for the given tool call."""
    hosted_query_record = self._approvals.get(("hosted_mcp_query", tool_name, call_id))
    hosted_query_status = self._get_per_call_approval_status_for_record(
        hosted_query_record,
        call_id,
    )
    if hosted_query_status is not None:
        return hosted_query_status
    return self._get_approval_status_for_key(tool_name, call_id)

get_rejection_message

get_rejection_message(
    tool_name: str,
    call_id: str,
    *,
    tool_namespace: str | None = None,
    existing_pending: ToolApprovalItem | None = None,
    tool_lookup_key: FunctionToolLookupKey | None = None,
) -> str | None

Return a stored rejection message for a tool call if one exists.

Source code in src/agents/run_context.py
def get_rejection_message(
    self,
    tool_name: str,
    call_id: str,
    *,
    tool_namespace: str | None = None,
    existing_pending: ToolApprovalItem | None = None,
    tool_lookup_key: FunctionToolLookupKey | None = None,
) -> str | None:
    """Return a stored rejection message for a tool call if one exists."""
    if existing_pending is not None:
        hosted_request = get_hosted_mcp_approval_request_identity(existing_pending)
        if hosted_request is not None:
            _, rejection_message = self._resolve_hosted_mcp_approval_decision(existing_pending)
            return rejection_message

    hosted_query_record = self._approvals.get(("hosted_mcp_query", tool_name, call_id))
    hosted_query_status = self._get_per_call_approval_status_for_record(
        hosted_query_record,
        call_id,
    )
    if hosted_query_status is not None:
        assert hosted_query_record is not None
        return self._get_rejection_message_for_key(hosted_query_record, call_id)

    candidates: list[str] = []
    explicit_namespace = (
        tool_namespace if isinstance(tool_namespace, str) and tool_namespace else None
    )
    pending_namespace = (
        self._resolve_tool_namespace(existing_pending) if existing_pending is not None else None
    )
    pending_key = (
        self._resolve_approval_key(existing_pending) if existing_pending is not None else None
    )
    pending_tool_name = (
        self._resolve_tool_name(existing_pending) if existing_pending is not None else None
    )
    pending_keys = (
        list(self._resolve_approval_keys(existing_pending))
        if existing_pending is not None
        else []
    )

    if existing_pending is not None and pending_key is not None:
        candidates.append(pending_key)
    explicit_keys = (
        list(
            get_function_tool_approval_keys(
                tool_name=tool_name,
                tool_namespace=explicit_namespace,
                tool_lookup_key=tool_lookup_key,
                include_legacy_deferred_key=True,
            )
        )
        if explicit_namespace is not None or tool_lookup_key is not None
        else []
    )
    for explicit_key in explicit_keys:
        if explicit_key not in candidates:
            candidates.append(explicit_key)
    if not explicit_keys and pending_namespace and pending_key is not None:
        if pending_key not in candidates:
            candidates.append(pending_key)
    if (
        explicit_namespace is None
        and tool_lookup_key is None
        and existing_pending is None
        and tool_name not in candidates
    ):
        candidates.append(tool_name)
    if existing_pending is not None:
        for pending_candidate in pending_keys:
            if pending_candidate not in candidates:
                candidates.append(pending_candidate)
        if (
            pending_namespace is None
            and pending_tool_name is not None
            and pending_tool_name not in candidates
        ):
            candidates.append(pending_tool_name)

    for candidate in candidates:
        approval_entry = self._approvals.get(candidate)
        if not approval_entry:
            continue
        message = self._get_rejection_message_for_key(approval_entry, call_id)
        if message is not None:
            return message
    return None

get_approval_status

get_approval_status(
    tool_name: str,
    call_id: str,
    *,
    tool_namespace: str | None = None,
    existing_pending: ToolApprovalItem | None = None,
    tool_lookup_key: FunctionToolLookupKey | None = None,
    current_invocation: ToolApprovalItem | None = None,
) -> bool | None

Return approval status, retrying with pending item's tool name if necessary.

Source code in src/agents/run_context.py
def get_approval_status(
    self,
    tool_name: str,
    call_id: str,
    *,
    tool_namespace: str | None = None,
    existing_pending: ToolApprovalItem | None = None,
    tool_lookup_key: FunctionToolLookupKey | None = None,
    current_invocation: ToolApprovalItem | None = None,
) -> bool | None:
    """Return approval status, retrying with pending item's tool name if necessary."""
    if not isinstance(call_id, str) or not call_id:
        raise ModelBehaviorError("Approval-gated tool calls require a non-empty call ID.")
    if existing_pending is not None:
        self._restore_pending_approval_binding(existing_pending)
        pending_identity = tool_invocation_identity(
            existing_pending.raw_item,
            tool_lookup_key=existing_pending.tool_lookup_key,
            tool_name=existing_pending.tool_name,
        )
        if pending_identity is None:
            pending_call_id = self._resolve_call_id(existing_pending)
            if pending_call_id is not None and (
                current_invocation is None or pending_call_id not in self._tool_invocations
            ):
                self._restored_unbound_approval_call_ids.add(pending_call_id)
            if current_invocation is None:
                return None
        hosted_request = get_hosted_mcp_approval_request_identity(existing_pending)
        if hosted_request is not None:
            hosted_status, _ = self._resolve_hosted_mcp_approval_decision(existing_pending)
            if hosted_status is None:
                return None
            effective_invocation = (
                current_invocation if current_invocation is not None else existing_pending
            )
            binding_status = self._approved_tool_invocation_status(
                effective_invocation.raw_item,
                tool_lookup_key=effective_invocation.tool_lookup_key,
                tool_name=effective_invocation.tool_name,
            )
            return hosted_status if binding_status is not None else None

    candidates: list[str] = []
    explicit_namespace = (
        tool_namespace if isinstance(tool_namespace, str) and tool_namespace else None
    )
    pending_namespace = (
        self._resolve_tool_namespace(existing_pending) if existing_pending is not None else None
    )
    pending_key = (
        self._resolve_approval_key(existing_pending) if existing_pending is not None else None
    )
    pending_tool_name = (
        self._resolve_tool_name(existing_pending) if existing_pending is not None else None
    )
    pending_keys = (
        list(self._resolve_approval_keys(existing_pending))
        if existing_pending is not None
        else []
    )

    if existing_pending is not None and pending_key is not None:
        candidates.append(pending_key)
    explicit_keys = (
        list(
            get_function_tool_approval_keys(
                tool_name=tool_name,
                tool_namespace=explicit_namespace,
                tool_lookup_key=tool_lookup_key,
                include_legacy_deferred_key=True,
            )
        )
        if explicit_namespace is not None or tool_lookup_key is not None
        else []
    )
    for explicit_key in explicit_keys:
        if explicit_key not in candidates:
            candidates.append(explicit_key)
    if not explicit_keys and pending_namespace and pending_key is not None:
        if pending_key not in candidates:
            candidates.append(pending_key)
    if (
        explicit_namespace is None
        and tool_lookup_key is None
        and existing_pending is None
        and tool_name not in candidates
    ):
        candidates.append(tool_name)
    if existing_pending is not None:
        for pending_candidate in pending_keys:
            if pending_candidate not in candidates:
                candidates.append(pending_candidate)
        if (
            pending_namespace is None
            and pending_tool_name is not None
            and pending_tool_name not in candidates
        ):
            candidates.append(pending_tool_name)

    status: bool | None = None
    matched_record: _ApprovalRecord | None = None
    for candidate in candidates:
        status = self._get_approval_status_for_key(candidate, call_id)
        if status is not None:
            matched_record = self._approvals.get(candidate)
            break
    selected_invocation = (
        current_invocation if current_invocation is not None else existing_pending
    )
    if status is None or matched_record is None or selected_invocation is None:
        return status
    is_sticky = isinstance(matched_record.approved, bool) or isinstance(
        matched_record.rejected, bool
    )
    if is_sticky:
        if (
            matched_record.sticky_scope is None
            and self._allow_legacy_approval_binding_reconstruction
        ):
            scope_identity = tool_invocation_approval_scope(
                selected_invocation.raw_item,
                tool_lookup_key=selected_invocation.tool_lookup_key,
                tool_name=selected_invocation.tool_name,
            )
            if scope_identity is not None:
                matched_record.sticky_scope = scope_identity[1]
        binding_status = self._approved_tool_invocation_status(
            selected_invocation.raw_item,
            tool_lookup_key=selected_invocation.tool_lookup_key,
            tool_name=selected_invocation.tool_name,
        )
        return status if binding_status is not None else None
    if current_invocation is not None:
        current_identity = tool_invocation_identity(
            current_invocation.raw_item,
            tool_lookup_key=current_invocation.tool_lookup_key,
            tool_name=current_invocation.tool_name,
        )
        if current_identity is None:
            self._approved_tool_invocation_status(
                current_invocation.raw_item,
                tool_lookup_key=current_invocation.tool_lookup_key,
                tool_name=current_invocation.tool_name,
            )
            return None
    binding_status = self._approved_tool_invocation_status(
        selected_invocation.raw_item,
        tool_lookup_key=selected_invocation.tool_lookup_key,
        tool_name=selected_invocation.tool_name,
    )
    if binding_status is None:
        current_identity = tool_invocation_identity(
            selected_invocation.raw_item,
            tool_lookup_key=selected_invocation.tool_lookup_key,
            tool_name=selected_invocation.tool_name,
        )
        if current_identity is not None:
            return None
        if existing_pending is not None:
            pending_identity = tool_invocation_identity(
                existing_pending.raw_item,
                tool_lookup_key=existing_pending.tool_lookup_key,
                tool_name=existing_pending.tool_name,
            )
            if pending_identity is None and is_mcp_approval_invocation(
                existing_pending.raw_item
            ):
                return None
        return status
    return status