Skip to content
75 changes: 58 additions & 17 deletions temporalio/contrib/google_adk_agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,35 +118,43 @@ from temporalio.worker import Worker
from temporalio.contrib.google_adk_agents import (
GoogleAdkPlugin,
TemporalMcpToolSetProvider,
TemporalMcpToolSet
TemporalMcpToolSet,
)

# Create toolset provider
provider = TemporalMcpToolSetProvider("my-tools",
lambda _: McpToolset(
connection_params=StdioConnectionParams(
server_params=StdioServerParameters(
command="npx",
args=[
"-y",
"@modelcontextprotocol/server-filesystem",
os.path.dirname(os.path.abspath(__file__)),
],
),
),
))
# Share one MCP toolset factory between the plugin and the agent.
toolset_factory = lambda _: McpToolset(
connection_params=StdioConnectionParams(
server_params=StdioServerParameters(
command="npx",
args=[
"-y",
"@modelcontextprotocol/server-filesystem",
os.path.dirname(os.path.abspath(__file__)),
],
),
),
)

# Use in agent workflow
agent = Agent(
name="test_agent",
model="gemini-2.5-pro",
tools=[TemporalMcpToolSet("my-tools")]
tools=[
TemporalMcpToolSet(
"my-tools",
not_in_workflow_toolset=toolset_factory,
)
],
)

client = await Client.connect(
"localhost:7233",
plugins=[
GoogleAdkPlugin(toolset_providers=[provider]),
GoogleAdkPlugin(
toolset_providers=[
TemporalMcpToolSetProvider("my-tools", toolset_factory),
],
),
],
)

Expand All @@ -157,6 +165,39 @@ worker = Worker(
)
```

### Local ADK Runs

The same agent definitions can also be exercised outside Temporal with
`adk run` or `adk web`.

- `TemporalModel` and `activity_tool(...)` work in local ADK runs without
additional configuration.
- If the agent uses `TemporalMcpToolSet`, provide
`not_in_workflow_toolset=...` so the agent can fall back to the underlying
`McpToolset` when it is not running inside `workflow.in_workflow()`.

Example:

```python
agent = Agent(
name="test_agent",
model=TemporalModel("gemini-2.5-pro"),
tools=[
TemporalMcpToolSet(
Copy link
Copy Markdown
Contributor

@drewhoskins-temporal drewhoskins-temporal Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also add to this snippet: registering the MCP toolset in the plugin and then sharing a function. Don't want to encourage config duplication.

"my-tools",
not_in_workflow_toolset=lambda _: McpToolset(
connection_params=StdioConnectionParams(
server_params=StdioServerParameters(
command="npx",
args=["-y", "@modelcontextprotocol/server-filesystem", "."],
),
),
),
)
],
)
```

## Integration Points

This integration provides comprehensive support for running Google ADK Agents within Temporal workflows while maintaining:
Expand Down
23 changes: 22 additions & 1 deletion temporalio/contrib/google_adk_agents/_mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ class TemporalMcpToolSetProvider:
within Temporal workflows.
"""

def __init__(self, name: str, toolset_factory: Callable[[Any | None], McpToolset]):
def __init__(
self, name: str, toolset_factory: Callable[[Any | None], McpToolset]
) -> None:
"""Initializes the toolset provider.

Args:
Expand Down Expand Up @@ -215,20 +217,28 @@ def __init__(
name: str,
config: ActivityConfig | None = None,
factory_argument: Any | None = None,
not_in_workflow_toolset: Callable[[Any | None], McpToolset] | None = None,
):
"""Initializes the Temporal MCP toolset.

Args:
name: Name of the toolset (used for activity naming).
config: Optional activity configuration.
factory_argument: Optional argument passed to toolset factory.
not_in_workflow_toolset: Optional factory that returns the
underlying ``McpToolset`` to use when this wrapper executes
outside ``workflow.in_workflow()``, such as local ADK runs.
This is not needed during normal workflow execution, but
``get_tools()`` raises ``ValueError`` outside a workflow if it
is omitted.
"""
super().__init__()
self._name = name
self._factory_argument = factory_argument
self._config = config or ActivityConfig(
start_to_close_timeout=timedelta(minutes=1)
)
self._not_in_workflow_toolset = not_in_workflow_toolset

async def get_tools(
self, readonly_context: ReadonlyContext | None = None
Expand All @@ -241,6 +251,17 @@ async def get_tools(
Returns:
List of available tools wrapped as Temporal activities.
"""
# If executed outside a workflow, like when doing local adk runs, use the mcp server directly
if not workflow.in_workflow():
if self._not_in_workflow_toolset is None:
raise ValueError(
"Attempted to use TemporalMcpToolSet outside a workflow, but "
"no not_in_workflow_toolset was provided. Either use "
"McpToolSet directly or pass a factory that returns the "
"underlying McpToolset for non-workflow execution."
)
return await self._not_in_workflow_toolset(None).get_tools(readonly_context)

tool_results: list[_ToolResult] = await workflow.execute_activity(
self._name + "-list-tools",
_GetToolsArguments(self._factory_argument),
Expand Down
9 changes: 9 additions & 0 deletions temporalio/contrib/google_adk_agents/_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from google.adk.models.llm_request import LlmRequest
from google.adk.models.llm_response import LlmResponse

import temporalio.workflow
from temporalio import activity, workflow
from temporalio.workflow import ActivityConfig

Expand Down Expand Up @@ -67,6 +68,14 @@ async def generate_content_async(
Yields:
The responses from the model.
"""
# If executed outside a workflow, like when doing local adk runs, use the model directly
if not temporalio.workflow.in_workflow():
async for response in LLMRegistry.new_llm(
self._model_name
).generate_content_async(llm_request, stream=stream):
yield response
return

responses = await workflow.execute_activity(
invoke_model,
args=[llm_request],
Expand Down
9 changes: 9 additions & 0 deletions temporalio/contrib/google_adk_agents/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import inspect
from typing import Any, Callable

import temporalio.workflow
from temporalio import workflow


Expand All @@ -29,6 +30,14 @@ async def wrapper(*args: Any, **kw: Any):
# Decorator kwargs are defaults.
options = kwargs.copy()

if not temporalio.workflow.in_workflow():
# If executed outside a workflow, like when doing local adk runs, use the function directly
result = activity_def(*args, **kw)
if inspect.isawaitable(result):
return await result
else:
return result

return await workflow.execute_activity(activity_def, *activity_args, **options)

# Copy metadata
Expand Down
Loading
Loading