`_run_async_impl` returns `CallToolResult.model_dump(...)` straight to the
caller, so the SDK's field names are ADK's response contract. Nothing pinned
them. Every existing assertion in the file compares the result against
`model_dump` of the same object, which carries whatever the SDK calls its
fields and so agrees with itself under any rename.
Add three tests that name the keys: `isError` is present and true, the content
entries keep `type` and `text`, and no second snake_case spelling appears
beside the camelCase one.
The third one matters as much as the first. Emitting both would let a caller
switch on either, and the pair would outlive whatever migration introduced it.
Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 970128478
`test_agent_to_mcp` reaches the agent server through
`mcp.shared.memory.create_connected_server_and_client_session`. That helper is a
convenience wrapper the SDK does not promise, and this file is the only place in
ADK that uses it.
Assemble the session here instead, from the four pieces underneath it that the
SDK does promise: `create_client_server_memory_streams`, the low-level server's
`run`, its `create_initialization_options`, and `ClientSession`. Same wiring, so
no test changes behaviour.
`raise_exceptions` now defaults to True. The wrapper defaulted to False, which
turns a raising handler into an error result and hides the traceback. No test
here relied on that.
Reaching the low-level server behind the high-level one is the one step with no
public route. `_lowlevel_server` does it, and tries both names the SDK has given
that attribute, so the helper does not pin a private name to one spelling.
Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 970124629
`CheckableMcpHttpClientFactory` exists to add `@runtime_checkable` to the SDK's
`McpHttpClientFactory`. Pydantic compiles a Protocol-annotated field into an
`is-instance` validator, and that fails at class construction time on a
protocol without it, so `SseConnectionParams` and
`StreamableHTTPConnectionParams` cannot declare `httpx_client_factory` any
other way.
The base class it inherits is not public. It lives in
`mcp.shared._httpx_utils`, is absent from that module's `__all__`, and reaches
ADK only because `mcp.client.streamable_http` happens to re-export it. A
release that stops re-exporting it makes this module fail to import, and with
it every MCP tool.
Declare the protocol here instead. Structural typing means a factory written
against either declaration satisfies both, so nothing else changes. The
signature still has to match the SDK's: `_DebugHttpxClientFactory` wraps the
given factory and calls it by keyword, and `sse_client` receives that wrapper,
typed there with the SDK's own protocol.
Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 969961072
`_run` builds its `ClientSession` from `transports[:2]`, and the comment above
the slice explains why: the streamable HTTP client yields a session-id callback
after the read and write streams. Nothing tested it. Every `MockClient` in the
file yields exactly two values, so removing the slice broke no test.
Add a case that yields three values and asserts the session is built from the
first two.
Two timeout assertions nearby compared against a literal `timedelta`. Those
tests are about which timeout reaches the session, not how it is spelled, so
they now compare against `_read_timeout`, the helper that owns the conversion.
`TestReadTimeout` remains the one place that pins the concrete type.
Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 969943788
`_is_session_disconnected` reads `session._read_stream._closed` and
`session._write_stream._closed`. Four attribute reads, all four private to the
MCP SDK, none of them promised.
The probe is not the only liveness signal. `create_session` pairs it with
`SessionContext._is_task_alive`, which ADK owns and which catches strictly
more: a crashed transport can leave both streams open while the task behind
them is already dead. So a missing attribute has a sensible answer -- treat
the session as connected and let the task check decide -- and no reason to
take down the call with an `AttributeError`.
Read the four defensively and say in the docstring where liveness actually
comes from. No behaviour change while the SDK keeps the streams: a closed
stream still reports disconnected, and either stream counts.
Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 968726270
`_detect_error_in_response` reads `isError` off the dumped `CallToolResult`.
MCP SDK 1.x names that field `isError`, so the lookup works. 2.x renames it to
`is_error`, so `model_dump` emits the snake_case key and the lookup returns
`None` for every result. Tool errors would stop being reported to telemetry,
with nothing in the logs to say so.
`_get_declaration` has the same problem one step earlier: it reads
`inputSchema` and `outputSchema`, which 2.x renames and removes. That one at
least fails loudly with an `AttributeError`.
Read both spellings. `_read_field` returns the first attribute a model
defines, so it picks up the camelCase name on 1.x and the snake_case name on
2.x, and raises a named error if a future release renames the field again.
`model_dump(by_alias=True)` would have been a smaller change, but it is not
equivalent: every MCP result model aliases `meta` to `_meta`, so dumping by
alias would rename that key in the payload ADK returns to the caller. The
casing of the returned dict is left alone here and belongs with the SDK
upgrade itself.
Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 968586089
`SessionContext._start` builds `ClientSession`'s `read_timeout_seconds` twice,
once for stdio and once for SSE and streamable HTTP, each with its own inline
`timedelta(...) if ... is not None else None`.
Move that to `_read_timeout`. ADK carries every timeout as float seconds and
now converts once, at the boundary where the SDK is called.
This is a plain de-duplication today. It also isolates a difference between
MCP SDK versions: 1.x types `read_timeout_seconds` as a `timedelta` and 2.x
types it as a float, so the conversion is the only line that has to change.
The explicit `is None` check is deliberate. A zero timeout is a real value, not
a missing one, and a truthiness check would silently turn it into "no timeout".
Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 968580764
Allow callers to explicitly specify the Google Cloud location when listing data agents, following standard three-level precedence (explicit argument, toolset config location, falling back to global).
PiperOrigin-RevId: 968067492
`to_mcp_server` keeps one ADK session per MCP connection, so successive tool
calls on that connection form a single conversation. It keyed that map on
`ctx.session`.
That key is correct on MCP SDK 1.x, where the server builds one session object
per connection. It is wrong on 2.x: the server builds a fresh `ServerSession`
for every inbound request and holds the connection on the session's private
`_connection`. The key would change on every call, so every tool call would
start a new conversation. Nothing raises. The agent just forgets.
Route the key through `_connection_key`, which reads `_connection` when the SDK
provides it and falls back to the session when it does not. That gives one key
per connection on both versions, and leaves 1.x behaviour unchanged.
The fallback degrades to one session per request on purpose. It must not fall
back to an object shared by all connections, because separate clients would
then share one conversation.
The private attribute is a stopgap. SDK 2.x already defines a public
`mcp.server.context.Context.connection`, but the server does not hand that
class to tool functions yet, so a tool's `Context` has no public route to its
connection.
Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 967462559
Merge https://github.com/google/adk-python/pull/6660
Change service-account OpenAPI helpers to return an OAuth2 client-credentials scheme so CredentialManager can perform token exchange.
Also, bypass the credential service caching for all SERVICE_ACCOUNT credentials. This ensures we don't cache exchanged tokens that cannot be refreshed, but means token exchange will run on each tool execution if the manager/exchanger is not reused.
Fixes#6656
PiperOrigin-RevId: 966305100
Implements the update_data_agent tool mimicking the MCP toolbox implementation,
allowing the updating of Gemini Data Agents.
PiperOrigin-RevId: 966070636
Implements the delete_data_agent tool mimicking the MCP toolbox implementation,
allowing the deletion of Gemini Data Agents.
PiperOrigin-RevId: 966052260
Implements the create_data_agent tool mimicking the MCP toolbox implementation,
allowing the creation of Gemini Data Agents.
PiperOrigin-RevId: 964346405
PreloadMemoryTool appended recalled memory to system_instruction, but explicit caches fingerprint and store that system prefix, so memory picked for one query could destabilize cache identity or stick to a reusable prefix on later turns. This inserts recalled memory into the trailing user-context batch for the current request instead, which the cache manager already excludes, through one typed internal LlmRequest seam that dynamic user instructions also reuse.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 963713313
ComputerUseToolset passed the url the model supplied straight to the
browser driver, without checking it. Now navigate runs the same checks
load_web_page does. A url that fails returns an error to the model
instead of reaching the driver.
If the agent is meant to drive the browser against an internal host,
pass allow_private_network_access=True
Co-authored-by: Jason Zhang <jasoncz@google.com>
PiperOrigin-RevId: 963643277
Some MCP servers declare a string-typed field whose enum lists integer
values. Gemini requires enum members to match the declared string type, so
the tool declaration was rejected and the integration failed. Normalize enum
values to their string form when the effective (non-null) type is string,
leaving numeric enums on numeric types untouched.
Close#3401
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 963579568
Live tool execution (`FunctionTool._call_live` and `__call_tool_live` in
flows/llm_flows/functions.py) called the wrapped function directly instead of
going through `BaseTool.run_async`. Every guardrail and preprocessing step the
async path applies was silently skipped in live sessions.
Remove `_call_live` and route live tool execution through `__call_tool_async` /
`tool.run_async`. Behavior changes that follow from the unification:
- A tool gated behind `require_confirmation` is no longer executed unattended in
a live session. The check was previously skipped and the tool body ran; the
call is now refused and a confirmation request is recorded. See the limitation
below -- this is the request half only.
- Parameter preprocessing (Pydantic model coercion) now applies in live mode.
- `BaseTool` subclasses overriding `run_async` now execute polymorphically in
live mode instead of being invoked as plain functions.
- A streaming tool that raises now returns an error FunctionResponse instead of
leaving the live session waiting for a response that never arrives.
- `_get_mandatory_args` no longer counts `_ignore_params` (`tool_context`,
`input_stream`) as mandatory, so schema generation and validation report only
the parameters actually required from the caller.
Known limitation: human-in-the-loop confirmation is still not end-to-end in live
mode. The request is raised but cannot be answered, because the live flow never
emits an `adk_request_confirmation` function call, the confirmation request
processor only runs once before the live connection opens, and the live
execution path does not accept a `ToolConfirmation`. A confirmation-gated tool
therefore cannot be approved and resumed inside a live session. TODOs in the
code mark the sites that need to change; closing the loop is follow-up work.
Co-authored-by: Liang Wu <wuliang@google.com>
PiperOrigin-RevId: 963548842
`McpTool` registered under the verbatim name the remote server advertised, with
no check against the names the framework itself puts on the wire. A server that
advertised `adk_request_credential`, `adk_request_confirmation`,
`adk_request_input` or `transfer_to_agent` therefore had its own tool dispatched
in place of the framework's, so it could harvest the credentials meant for an
auth callback or route the conversation to an agent of its choosing.
`McpToolset.get_tools` now drops any tool carrying one of those four names and
logs that it did, and `McpTool.__init__` refuses the name outright. The listing
skips rather than raises because a single reserved name would otherwise fail the
whole `list_tools` call and take the server's honest tools down with it; the
constructor check is the backstop for anything that builds an `McpTool`
directly. Only exact matches are refused, so `transfer_to_agent_v2` still
registers.
Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 963544587
Loading an agent config that declared a stdio MCP server launched the
config-supplied `command` as a local process, before the model was ever
contacted. `McpToolset.from_config()` now rejects `stdio_server_params` and
`stdio_connection_params` unless the operator opts in by setting
`ADK_ALLOW_CONFIG_STDIO_MCP_SERVERS=1`. Remote transports
(`sse_connection_params`, `streamable_http_connection_params`) and toolsets
constructed in Python code are unaffected.
Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 962401277
`get_tools()` sends a `tools/list` request every time it runs. `BaseToolset`
memoizes the result for the rest of an invocation, so repeated LLM steps within
one turn share a listing, but that cache is keyed on the invocation ID and lives
on the toolset instance: every new turn pays the round trip again, an agent that
hands each sub-agent its own `McpToolset` pays it once per sub-agent, and
anything wrapped in `AgentTool` gets a fresh invocation ID per call and so never
hits the cache. Listing happens during agent setup, so the cost lands on every
chat request.
Add an opt-in `tool_list_cache_ttl_seconds` that reuses the response for that
long. Entries are keyed by the session pool key, so they never outlive the
identity they were fetched with: a `header_provider` that distinguishes tenants
gets an entry per tenant, and one that mints a fresh value per request gets no
reuse, which is also the case where the session pool already thrashes. Only the
round trip is skipped. Tools are rebuilt and `tool_filter` re-evaluated on every
call, so a context-dependent filter keeps deciding per call.
The cache is bounded rather than left to the TTL. A read only evicts the key it
was asked for, so the per-request-header case above would otherwise accumulate
an entry per call for the life of the toolset. Each write sweeps whatever has
expired and then caps the cache at 64 entries, evicting least-recently-used, so
the footprint holds even when every key is still inside its TTL.
Reuse stays off by default. ADK does not subscribe to
`notifications/tools/list_changed`, so a server that adds or removes a tool goes
unnoticed until the entry expires, and the TTL is how a caller says how stale a
tool list may get.
Co-authored-by: Kathy Wu <wukathy@google.com>
PiperOrigin-RevId: 962353730
Merge https://github.com/google/adk-python/pull/6001
## Summary
Fixes#5995.
`create_empty_state()` only walked `sub_agents`, so graph-based `Workflow` roots crashed when the dev server tried to add the current session to an eval set. Workflow children live in `workflow.graph.nodes`, not `sub_agents`.
This updates the state traversal to:
- keep the existing `sub_agents` walk for normal agents
- also walk `graph.nodes` for Workflow-style roots and nested graph nodes
- track visited objects so shared graph/agent nodes are not processed repeatedly
## Testing
```
python -m pytest tests\unittests\cli\utils\test_state.py -q
python -m py_compile src\google\adk\cli\utils\state.py tests\unittests\cli\utils\test_state.py
python -m pyink --check src\google\adk\cli\utils\state.py tests\unittests\cli\utils\test_state.py
git diff --check
```
Co-authored-by: Yi Liu <yiliuly@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/6001 from he-yufeng:fix/workflow-empty-state 1bc442a5508cc527cbf364d36dfbf90d1c762dec
PiperOrigin-RevId: 960722840
Gemini 1.x is fully deprecated, so sorting Gemini model ids into "1.x"
and "or 2.0+" buckets no longer buys anything. Non-Gemini ids are unaffected: they still raise error.
PiperOrigin-RevId: 960655458
An agent used as a tool runs in a sub-Runner that never received the
parent App's context_cache_config, so context caching was silently off
for every wrapped agent. Propagate it to the sub-runner.
PiperOrigin-RevId: 960628394
An agent used as a tool runs in a sub-Runner that never received the
parent App's context_cache_config, so context caching was silently off
for every wrapped agent. Propagate it to the sub-runner.
Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 960515999