Skip to content

Commit 7d52c78

Browse files
authored
AI-374: Warn against passing secrets through MCP factory_argument (#1688)
* AI-374: Warn against passing secrets through MCP factory_argument `factory_argument` is serialized as an activity argument and therefore recorded in workflow history, which is not obvious from the API surface. Document that in the docstrings and READMEs for the MCP support in the openai_agents and google_adk_agents contrib plugins, and point users at resolving credentials worker-side inside the factory instead. * AI-374: Correct and tighten the factory_argument secrets warning Fix the claim that factory_argument is sent to every MCP activity, which holds for stateless servers but not stateful ones, and document that a zero-parameter stateless factory silently discards the value while it is still written to history. Drop the maturity-badge glyph, qualify the web UI claim for users running a payload codec, and state the factory-side contract on the three provider docstrings. * fix AI slop
1 parent 44511c5 commit 7d52c78

5 files changed

Lines changed: 40 additions & 5 deletions

File tree

‎temporalio/contrib/google_adk_agents/README.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ worker = Worker(
166166
)
167167
```
168168

169+
`TemporalMcpToolSet` also accepts an optional `factory_argument`. It is sent to the toolset activities and passed to the registered `toolset_factory` when the `McpToolset` is created.
170+
171+
**Do not pass secrets, credentials, or API keys through `factory_argument`.** It is an activity argument, so it is recorded in workflow history and, without a payload codec, visible in the web UI. Resolve credentials worker-side inside the toolset factory instead.
172+
169173
### Local ADK Runs
170174

171175
The same agent definitions can also be exercised outside Temporal with

‎temporalio/contrib/google_adk_agents/_mcp.py‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,10 +221,17 @@ def __init__(
221221
):
222222
"""Initializes the Temporal MCP toolset.
223223
224+
.. warning::
225+
Do not pass secrets, credentials, or API keys through ``factory_argument``. It
226+
is an activity argument, so it is recorded in workflow history and, without a
227+
payload codec, visible in the web UI. Resolve credentials worker-side inside the
228+
toolset factory instead.
229+
224230
Args:
225231
name: Name of the toolset (used for activity naming).
226232
config: Optional activity configuration.
227-
factory_argument: Optional argument passed to toolset factory.
233+
factory_argument: Optional argument passed to ``toolset_factory``.
234+
Must not contain secrets.
228235
not_in_workflow_toolset: Optional factory that returns the
229236
underlying ``McpToolset`` to use when this wrapper executes
230237
outside ``workflow.in_workflow()``, such as local ADK runs.

‎temporalio/contrib/openai_agents/README.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,14 @@ For implementation details and examples, see the [samples repository](https://gi
447447
When using stateful servers, the dedicated worker maintaining the connection may fail due to network issues or server problems. When this happens, Temporal raises an `ApplicationError` and cannot automatically recover because it cannot restore the lost server state.
448448
To recover from such failures, you need to implement your own application-level retry logic.
449449

450+
### Factory Arguments
451+
452+
Both `stateless_mcp_server()` and `stateful_mcp_server()` accept an optional `factory_argument`, which is passed to the registered server factory when the MCP server is created.
453+
454+
A stateless factory that declares no parameters — like the `lambda: MCPServerStdio(...)` example above — ignores the value, but it is still recorded in history.
455+
456+
**Do not pass secrets, credentials, or API keys through `factory_argument`.** It is an activity argument, so it is recorded in workflow history and, without a payload codec, visible in the web UI. Resolve credentials worker-side inside the server factory instead.
457+
450458
### Hosted MCP Tool
451459

452460
For network-accessible MCP servers, you can also use `HostedMCPTool` from the OpenAI Agents SDK, which uses an MCP client hosted by OpenAI.

‎temporalio/contrib/openai_agents/_mcp.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ def __init__(
152152
Args:
153153
name: The name of the MCP server.
154154
server_factory: A function which will produce MCPServer instances. It should return a new server each time
155-
so that state is not shared between workflow runs.
155+
so that state is not shared between workflow runs. It may accept a single positional parameter, which
156+
receives a ``factory_argument`` from the workflow.
156157
"""
157158
self._server_factory = server_factory
158159

@@ -437,7 +438,8 @@ def __init__(
437438
Args:
438439
name: The name of the MCP server.
439440
server_factory: A function which will produce MCPServer instances. It should return a new server each time
440-
so that state is not shared between workflow runs
441+
so that state is not shared between workflow runs. It receives an optional ``factory_argument`` from the
442+
workflow.
441443
"""
442444
self._server_factory = server_factory
443445
self._name = name + "-stateful"

‎temporalio/contrib/openai_agents/workflow.py‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,12 +291,19 @@ def stateless_mcp_server(
291291
and you don't need to maintain state between operations. It should be preferred to stateful when possible due to its
292292
superior durability guarantees.
293293
294+
.. warning::
295+
Do not pass secrets, credentials, or API keys through ``factory_argument``. It is an
296+
activity argument, so it is recorded in workflow history and, without a payload codec,
297+
visible in the web UI. Resolve credentials worker-side inside the server factory
298+
instead.
299+
294300
Args:
295301
name: A string name for the server. Should match that provided in the plugin.
296302
config: Optional activity configuration for MCP operation activities.
297303
Defaults to 1-minute start-to-close timeout.
298304
cache_tools_list: If true, the list of tools will be cached for the duration of the server
299-
factory_argument: Optional argument to be provided to the factory when producing an MCPServer
305+
factory_argument: Optional argument to be provided to the factory when producing an MCPServer.
306+
Must not contain secrets.
300307
"""
301308
from temporalio.contrib.openai_agents._mcp import (
302309
_StatelessMCPServerReference,
@@ -326,13 +333,20 @@ def stateful_mcp_server(
326333
The caller will have to handle cases where the dedicated worker fails, as Temporal is
327334
unable to seamlessly recreate any lost state in that case.
328335
336+
.. warning::
337+
Do not pass secrets, credentials, or API keys through ``factory_argument``. It is an
338+
activity argument, so it is recorded in workflow history and, without a payload codec,
339+
visible in the web UI. Resolve credentials worker-side inside the server factory
340+
instead.
341+
329342
Args:
330343
name: A string name for the server. Should match that provided in the plugin.
331344
config: Optional activity configuration for MCP operation activities.
332345
Defaults to 1-minute start-to-close and 30-second schedule-to-start timeouts.
333346
server_session_config: Optional activity configuration for the connection activity.
334347
Defaults to 1-hour start-to-close timeout.
335-
factory_argument: Optional argument to be provided to the factory when producing an MCPServer
348+
factory_argument: Optional argument to be provided to the factory when producing an MCPServer.
349+
Must not contain secrets.
336350
"""
337351
from temporalio.contrib.openai_agents._mcp import (
338352
_StatefulMCPServerReference,

0 commit comments

Comments
 (0)