Skip to content

Add OpenAI Agents MCP v2 support - #6

Merged
brianstrauch merged 29 commits into
mainfrom
sync/python-openai-agents-mcp-v2
Sep 16, 2026
Merged

brianstrauch merged 29 commits into
mainfrom
sync/python-openai-agents-mcp-v2

Conversation

@brianstrauch

@brianstrauch brianstrauch commented Sep 3, 2026 •

Copy link
Copy Markdown
Member

Summary

  • add MCP Python SDK v2 support to the independently packaged temporalio-openai-agents integration
  • register named OpenAI MCPServer factories on the worker and expose them through temporal_mcp_server() in workflow code
  • share the durable Activity implementation from Add native MCP integration #5 while retaining the existing Activity names and deprecated MCP v1 APIs for source and workflow-history compatibility
  • forward OpenAI tool filters, approvals, metadata, prompts, resources, and resource templates through MCP Python SDK v2
  • reuse modern sessionless connections safely and clean them up during plugin shutdown
  • classify malformed repeated pagination cursors as non-retryable MCP protocol failures
  • update multi-invocation test models for OpenAI Agents 0.20's unique tool-call ID requirement while preserving the testing helper's public defaults
  • reserve history-import for commits reachable from an upstream repository's default branch and remove the commit-count heuristic

The adapter is based on work proposed in temporalio/sdk-python#1793, but that PR was never merged into sdk-python's default branch. Per this repository's policy, the adapter is ordinary local work, not a history import; it does not receive a history-import label or an IMPORTS.md row.

Dependency

Depends on #5. The branch now resolves the published temporalio-mcp==0.1.0 package from PyPI and records it in python/openai_agents/uv.lock; it does not use a local path or workspace source.

Testing

Validated against published temporalio-mcp==0.1.0:

  • OpenAI Agents suite: 227 passed, 2 skipped
  • Ruff import and format checks
  • Pyright, mypy, and basedpyright
  • pydocstyle
  • repository conventions
  • tooling tests: 91 passed
  • package build

Merge

This is an ordinary local-work PR, not a history import. Squash merge it per CONTRIBUTING.md.

brianstrauch and others added 9 commits August 27, 2026 11:01
A failed operation evicted the pooled connection by closing it outright,
which unwound the owning task's `async with backend` while concurrent
operations were still using the same connection. Since `CancelledError` is
a `BaseException`, activity cancellation and worker shutdown hit this path
too, so cancelling one MCP activity aborted every other in-flight activity
sharing the connection. The failing operation also never released its slot.

Failures now retire the record: it is dropped from the cache so no later
operation reuses it, and the last operation to release it closes it.

Also drop a dead uniqueness check in `_MCPActivities` (`set()` over a dict
compares key counts, so it never fired) and correct the deprecation
directives to 1.32, the unreleased version.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Migrated-From: temporalio/sdk-python@4fb9cc8
`temporalio.contrib.mcp` is an implementation detail shared by the contrib
integrations, not a public API, but it was advertised as one: the CHANGELOG
called it stable, `workflow.py`/`MCPClient` were the only non-underscore
names in an otherwise private package, and pydoctor rendered the package
into the published docs. Rename to `_workflow._MCPClient`, mark the package
PRIVATE for pydoctor, and describe the release in terms of the OpenAI Agents
surface users actually call.

`meta` was threaded through all seven operations, but only `call_tool` has a
caller that supplies it (the OpenAI Agents base `MCPServer` resolves it per
tool call). The other six accepted it and dropped it on the floor, so a
caller passing metadata would silently get none. Drop the parameter there;
`_CallToolRequest` now owns the field.

Replace the `-k 'legacy_mcp_apis_are_deprecated or mcp_server'` CI selection
with `@pytest.mark.mcp_v1`. Substring matching silently under-selects on a
rename while still passing, and `mcp_server` also matches `mcp_servers` and
any future `test_hosted_mcp_server_*`. The marker selects the same 13 tests
today and stays correct as tests are renamed or added.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Migrated-From: temporalio/sdk-python@8b74c9e
An idle-eviction task armed before an operation acquired a pooled record
could close the connection while that operation was still in flight: once
a peer failure unmapped the record, `_unmap` skipped the idle check and
reported the record closable. Apply the idle check regardless of whether
the record is still mapped.

Restore the legacy `inspect.signature` handling for MCP server factories,
now shared by both backends. A factory declaring a positional parameter
receives the `factory_argument` (`None` when a workflow supplied none), and
a parameterless factory is called bare. Signatures that could satisfy
neither form raise when the plugin is built, and passing a
`factory_argument` to a parameterless factory raises a non-retryable error
instead of a bare `TypeError` that retries forever.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Migrated-From: temporalio/sdk-python@0168e19
Fix the proto Docker build, which pinned googleapis-common-protos into
the dev group while the conflicting requirement now lives in dev-common,
leaving the following uv sync unsatisfiable.

Bound the MCP pool close during worker shutdown. The run context swallows
the shutdown cancellation, so an MCP server that never finishes closing
its transport would hang the worker with no cancellation left to break
out with.

Stop treating activity cancellation as a transport failure in the
connection pool. The MCP client cancels the in-flight request on the
wire, so the shared connection stays healthy and should not be retired
out from under every other workflow using it.

Reject a callable tool_filter on a worker-side MCPServer. It needs the
run context and agent, which exist only in the workflow, so the Agents
SDK raises for it and the list-tools Activity would retry forever.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Migrated-From: temporalio/sdk-python@7100059
@brianstrauch brianstrauch added the history-import History import/re-sync from an SDK repo: merge with a merge commit, never squash label Sep 3, 2026
@brianstrauch brianstrauch removed the history-import History import/re-sync from an SDK repo: merge with a merge commit, never squash label Sep 3, 2026
@brianstrauch
brianstrauch changed the base branch from main to import/python-mcp September 4, 2026 21:20
…andbox, use the public MCP client import

exclude-newer = "2 weeks" limits candidates to distributions uploaded before the cutoff, so a freshly published temporalio-mcp would have no eligible release for two weeks and uv lock would keep failing after the release; exempt it like temporalio.

MCPPlugin passes mcp_types through the sandbox and the adapter imports it directly, so OpenAIAgentsPlugin does the same. TemporalMCPClient is a public export of temporalio.contrib.mcp; import it from there instead of the private module.
…mported-files rule

The adapter was developed in sdk-python PR #1793, closed unmerged on 2026-09-03, and migrated here with Migrated-From trailers. Its commits touch ten imported files and can never arrive from sdk-python main, so AGENTS.md, CONTRIBUTING.md, scripts/migrate/README.md and IMPORTS.md now say so, and future re-syncs merge on top of them. The cooldown docs cover the second exclude-newer-package exemption.
@DABH DABH added the history-import History import/re-sync from an SDK repo: merge with a merge commit, never squash label Sep 10, 2026

@DABH DABH left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed at beed6dd (Brian's ae8d088 plus two adaptation commits I pushed today; details below). Overall the adapter reads well: the legacy v1 surface and activity names are preserved (verified symbol by symbol against main), the re-sync from the closed sdk-python#1793 did not regress anything on main (_invoke_model_activity.py and the retry-after tests are identical), and the five new tests run offline. Because the lock could never resolve, no lint, type-check or test has run in CI on this branch yet; the first uv lock after the temporalio-mcp release will be the first signal.

What I pushed (shape-independent, per the plan David approved):

  • 88367ad: exclude-newer-package = { temporalio = false, temporalio-mcp = false } (uv limits candidates to distributions uploaded before the two-week cutoff, so a freshly published temporalio-mcp would otherwise have zero eligible releases for 14 days and uv lock would keep failing after the release); mcp_types added to the sandbox passthrough for parity with MCPPlugin; TemporalMCPClient imported from the public temporalio.contrib.mcp.
  • beed6dd: AGENTS.md / CONTRIBUTING.md / migrate README / IMPORTS.md record this adapter as the one exception to "never edit imported files" (ten imported files, closed upstream PR, non-descendant SRC_REF), and the cooldown docs mention the second exemption. Label history-import added.

One decision for you (D1), left untouched on purpose: temporalio-mcp>=0.1.0,<0.2 and mcp-types>=2,<3 are hard dependencies, which forces mcp>=2; but mcp>=1.24,<3, the ImportError fallbacks, the README's "can still run with MCP Python SDK v1", the mcp_v1 marker (no lane runs it here) and test_mcp_servers_requires_v2's < 2 branch all assume MCP v1 stays installable. As written, nobody pinned to mcp 1.x (anything next to google-adk, which pins mcp<2) can install the package. Either (a) keep the hard dependency and make the text coherent (mcp>=2,<3, reword the v1 claims, drop the dead branch and marker), or (b) make temporalio-mcp an optional [mcp] extra, which matches the lazy imports and AGENTS.md's "lazy imports go in an extra" rule and keeps mcp 1.x users installable during the deprecation period. I lean (b) but it is your call; inline comments mark the exact sites.

Sequencing: #5 merges with a merge commit (Thursday), then python/mcp/v0.1.0 (held until the repo is public), then uv lock here, make sync && make lint && make test && make build, merge main (expect a one-line conflict with the litellm fix next to openai-agents[litellm]), mark ready. The 1.0.0rc1 dry run consumes that version, so this PR's own pre-release will need a 1.0.0rc2 release PR.

Smaller items inline: deprecation version text, the shared activity namespace, the ResponseBuilders.tool_call default change, private cross-package imports, and adapter test coverage.

Comment thread python/openai_agents/pyproject.toml Outdated
Comment thread python/openai_agents/pyproject.toml Outdated
Comment thread python/openai_agents/README.md Outdated
Comment thread python/openai_agents/src/temporalio/contrib/openai_agents/_mcp.py Outdated
Comment thread python/openai_agents/src/temporalio/contrib/openai_agents/testing.py Outdated
Comment thread python/openai_agents/tests/contrib/openai_agents/test_openai.py Outdated
Comment thread python/openai_agents/tests/contrib/openai_agents/test_mcp_v2.py
Base automatically changed from import/python-mcp to main September 10, 2026 18:37
@brianstrauch brianstrauch removed the history-import History import/re-sync from an SDK repo: merge with a merge commit, never squash label Sep 10, 2026
…ents-mcp-v2

# Conflicts:
#	CONTRIBUTING.md
#	python/mcp/README.md
#	python/mcp/plugin.toml
#	python/mcp/src/temporalio/contrib/mcp/_activities.py
#	python/mcp/src/temporalio/contrib/mcp/_backend.py
#	python/mcp/src/temporalio/contrib/mcp/_client.py
#	python/mcp/src/temporalio/contrib/mcp/_pool.py
#	python/mcp/src/temporalio/contrib/mcp/_workflow.py
#	python/mcp/tests/test_activities.py
#	python/mcp/tests/test_pool.py
#	python/mcp/tests/test_workflow.py
#	python/mcp/uv.lock
#	python/openai_agents/pyproject.toml
@brianstrauch brianstrauch added history-import History import/re-sync from an SDK repo: merge with a merge commit, never squash and removed history-import History import/re-sync from an SDK repo: merge with a merge commit, never squash labels Sep 10, 2026
@brianstrauch
brianstrauch marked this pull request as ready for review September 15, 2026 21:31
@brianstrauch
brianstrauch requested a review from a team as a code owner September 15, 2026 21:31
@DABH
DABH requested a balanced review from Copilot September 15, 2026 21:34

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Pagination fails when a later valid response omits its TTL hint.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds durable MCP Python SDK v2 support to the OpenAI Agents integration while preserving legacy MCP APIs and workflow-history compatibility.

Changes:

  • Adds worker-side MCP factories, workflow proxies, connection reuse, and protocol handling.
  • Updates dependencies, documentation, and MCP-related tests.
  • Clarifies history-import policy and removes the commit-count heuristic.
File summaries
File Description
AGENTS.md Documents migration and dependency rules.
CONTRIBUTING.md Clarifies history-import eligibility.
.github/workflows/ci.yml Removes commit-count metadata.
scripts/ci/check_conventions.py Removes commit-count enforcement.
scripts/tests/test_check_conventions.py Updates convention tests.
scripts/migrate/README.md Documents default-branch import requirements.
python/README.md Documents first-party dependency exemptions.
python/openai_agents/plugin.toml Records the local adapter divergence.
python/openai_agents/pyproject.toml Adds MCP v2 dependencies and extra.
python/openai_agents/uv.lock Locks updated MCP and Agents packages.
python/openai_agents/README.md Documents MCP v2 configuration and lifecycle.
python/openai_agents/src/temporalio/contrib/openai_agents/_mcp.py Deprecates legacy MCP providers.
python/openai_agents/src/temporalio/contrib/openai_agents/_mcp_backend.py Adds the worker-side MCP adapter.
python/openai_agents/src/temporalio/contrib/openai_agents/_openai_runner.py Accepts the durable MCP v2 proxy.
python/openai_agents/src/temporalio/contrib/openai_agents/_temporal_mcp_server.py Adds the workflow-side MCP server proxy.
python/openai_agents/src/temporalio/contrib/openai_agents/_temporal_openai_agents.py Registers and manages MCP v2 activities.
python/openai_agents/src/temporalio/contrib/openai_agents/testing.py Extends testing helpers for MCP v2.
python/openai_agents/src/temporalio/contrib/openai_agents/workflow.py Exposes temporal_mcp_server().
python/openai_agents/tests/contrib/openai_agents/test_mcp_v2.py Adds MCP v2 integration coverage.
python/openai_agents/tests/contrib/openai_agents/test_openai.py Updates IDs and deprecation coverage.
python/openai_agents/tests/contrib/openai_agents/test_openai_sandbox.py Uses unique tool-call IDs.
Review details
  • Files reviewed: 20/21 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@brianstrauch
brianstrauch requested a review from DABH September 15, 2026 21:47

@DABH DABH left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Did a full review pass on head c69c44e, including running the whole local toolchain (make sync/lint/test — 227 passed 2 skipped, matching the PR body — plus the scripts suite, check_conventions, and wheel checks, all green) and verifying the code against the installed locked dependency surfaces (agents 0.20 signatures, mcp-types field types, temporalio-mcp private API contract). All nine earlier review threads' fixes are verifiably present on this head, and Copilot's ttlMs concern is refuted with a regression test (details in that thread) — those can all be resolved.

One thing I'd fix pre-merge, because re-sync correctness depends on it:

  • The divergence policy is now self-contradictory. scripts/migrate/README.md says the MCP v2 adapter files to preserve are "listed in AGENTS.md, 'Transition rules'" — but that section lists no files, and still says adaptation-only changes "are the only local commits" / "never cherry-pick or patch imported files", both of which this PR (correctly) violates for seven imported files plus two new ones. The next upstream re-sync has no authoritative list of what to preserve. Suggest enumerating the diverged/new files in the Transition rules section and carving the documented-divergence exception into those two bullets.

Smaller items, fine as follow-ups:

  • _TemporalMCPServer.cached_tools returns the client cache unfiltered while list_tools applies the static tool filter. No agents-0.20 code path reads the property, so it's latent — but worth filtering or documenting.
  • The repeated-cursor detection string-matches agents' error text (verified to match both 0.20 wordings, and the pin is <0.21, so safe today) — but the test fabricates the message itself, so an upstream rewording would silently degrade the error to retryable without failing any test. Worth a line in the version-bump checklist or one test asserting through the real agents error path.
  • get_prompt argument stringification (str(True) → "True", dicts → repr) diverges from native agents passthrough — forced by the dict[str, str] client signature; a docstring note would do.
  • A dynamic tool_filter callable runs workflow-side and must be deterministic; neither README nor docstring says so — one sentence in the temporal_mcp_server() docstring would cover it.
  • The "legacy v1 APIs still work without the extra" claim has no CI lane exercising an mcp 1.x environment, so a v1 regression would ship blind.

None of these block my overall take: the adapter matches the dependency surfaces exactly, workflow-side code is replay-safe (no nondeterminism, sandbox passthrough correct, legacy activity names byte-identical to main), and the legacy/v2 activity namespaces can't collide.

@brianstrauch

Copy link
Copy Markdown
Member Author

Addressed the four non-CI follow-ups in ac5459b:

  • cached_tools now applies static filters and declines to expose a context-free cache for dynamic filters.
  • repeated-cursor coverage now exercises the real OpenAI Agents pagination error path.
  • get_prompt documents argument stringification.
  • callable tool_filter determinism is documented in the API docstring and README.

The “Divergence policy is now self-contradictory” concern is resolved by #23. That cutover PR removes upstream and updates the migration/transition guidance so ai-integrations becomes the authoritative source; consequently there is no post-cutover re-sync divergence policy or exception list to maintain.

@brianstrauch
brianstrauch requested a review from DABH September 15, 2026 23:44
@brianstrauch
brianstrauch merged commit a6f6cff into main Sep 16, 2026
23 checks passed
@brianstrauch
brianstrauch deleted the sync/python-openai-agents-mcp-v2 branch September 16, 2026 00:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants