Is your feature request related to a problem? Please describe.
The context is per-user OAuth (3LO) MCP servers: each end user must individually
authorize the remote server, and until they do, McpToolset tool listing fails with
HTTP 401. This is not an outage — it is the expected steady state for every user who
has not connected yet, and the product requirement is to prompt that user to connect.
Since 2.7.0, SkillToolset._resolve_additional_tools_from_state gathers with
return_exceptions=True and skips a failing toolset with a warning. That isolation is
right for what it targets: one unreachable server must not take down unrelated tools.
The side effect for the 3LO case is that the failing toolset's own tools disappear
silently. Skill adk_additional_tools are resolved by name against the candidate pool,
so the names served by that MCP server never enter the pool. The model therefore has
no tool to call, never observes an authorization error, and has no way to ask the user
to authorize. The user is simply told the capability does not exist.
#4300 was closed with the guidance to wrap the get_tools() call in try/except
ConnectionError. That works when the application calls get_tools itself. It does not
when the toolset is owned by an agent or by SkillToolset, because the framework makes
the call. The only remaining seam is for the application to wrap every MCP toolset in
its own BaseToolset subclass, which is per-toolset opt-in and fails silently the day
someone adds a new toolset and forgets.
Describe the solution you'd like
An extension point that lets the owning application decide what a toolset contributes
when listing fails, instead of the framework deciding it always contributes nothing.
Conceptually an overridable hook on BaseToolset, invoked with the raised exception and
the readonly context, whose return value replaces that toolset's tool list for the
current resolution, with a default that behaves exactly as today (skip and warn). An
application could then return placeholder tools carrying an "authorization required"
instruction, so the authorization prompt reaches the user through the normal
tool-calling path.
Describe alternatives you've considered
Wrapping each MCP toolset in an application-side BaseToolset. This is what we do today;
it works, but it is opt-in per toolset and silent when omitted.
Checking authorization before building the toolset. This duplicates the server's own
auth decision and races with token expiry between the check and the listing.
Special-casing 401 inside McpToolset. Too opinionated for the framework — what should
happen on an unauthorized listing is application- and product-specific.
Additional context
Observed on google-adk 2.7.0. This affects any ADK application fronting MCP servers
that authorize per end user rather than per application.
Not a Contribution: this issue is submitted solely to report a limitation and request a
feature. It is expressly not intended as a Contribution under the Google CLA, and no
code, patch, or other work of authorship is submitted with it.
Is your feature request related to a problem? Please describe.
The context is per-user OAuth (3LO) MCP servers: each end user must individually
authorize the remote server, and until they do, McpToolset tool listing fails with
HTTP 401. This is not an outage — it is the expected steady state for every user who
has not connected yet, and the product requirement is to prompt that user to connect.
Since 2.7.0, SkillToolset._resolve_additional_tools_from_state gathers with
return_exceptions=True and skips a failing toolset with a warning. That isolation is
right for what it targets: one unreachable server must not take down unrelated tools.
The side effect for the 3LO case is that the failing toolset's own tools disappear
silently. Skill adk_additional_tools are resolved by name against the candidate pool,
so the names served by that MCP server never enter the pool. The model therefore has
no tool to call, never observes an authorization error, and has no way to ask the user
to authorize. The user is simply told the capability does not exist.
#4300 was closed with the guidance to wrap the get_tools() call in try/except
ConnectionError. That works when the application calls get_tools itself. It does not
when the toolset is owned by an agent or by SkillToolset, because the framework makes
the call. The only remaining seam is for the application to wrap every MCP toolset in
its own BaseToolset subclass, which is per-toolset opt-in and fails silently the day
someone adds a new toolset and forgets.
Describe the solution you'd like
An extension point that lets the owning application decide what a toolset contributes
when listing fails, instead of the framework deciding it always contributes nothing.
Conceptually an overridable hook on BaseToolset, invoked with the raised exception and
the readonly context, whose return value replaces that toolset's tool list for the
current resolution, with a default that behaves exactly as today (skip and warn). An
application could then return placeholder tools carrying an "authorization required"
instruction, so the authorization prompt reaches the user through the normal
tool-calling path.
Describe alternatives you've considered
Wrapping each MCP toolset in an application-side BaseToolset. This is what we do today;
it works, but it is opt-in per toolset and silent when omitted.
Checking authorization before building the toolset. This duplicates the server's own
auth decision and races with token expiry between the check and the listing.
Special-casing 401 inside McpToolset. Too opinionated for the framework — what should
happen on an unauthorized listing is application- and product-specific.
Additional context
Observed on google-adk 2.7.0. This affects any ADK application fronting MCP servers
that authorize per end user rather than per application.
Not a Contribution: this issue is submitted solely to report a limitation and request a
feature. It is expressly not intended as a Contribution under the Google CLA, and no
code, patch, or other work of authorship is submitted with it.