Skip to content

fix(openapi): recreate and rebind the provider-owned client on later lifespans - #5845

Closed
BlueX888 wants to merge 6 commits into
PrefectHQ:mainfrom
BlueX888:fix/openapi-owned-client-relifespan
Closed

BlueX888 wants to merge 6 commits into
PrefectHQ:mainfrom
BlueX888:fix/openapi-owned-client-relifespan

Conversation

@BlueX888

Copy link
Copy Markdown
Contributor

Closes #5844

Description

FastMCP.from_openapi(spec) without an explicit client= could only be connected once. A second lifespan (a second async with Client(mcp), re-entering the same Client, or a fixture that reconnects) failed with RuntimeError: Client failed to connect: Cannot reopen a client instance, once it has been closed. The provider auto-creates an httpx2.AsyncClient, enters it per lifespan, and httpx2 clients are single-use after aclose().

What changed

  • Store the resolved default-client base URL so a replacement can be built with the same configuration (DEFAULT_TIMEOUT).
  • Add _create_owned_client() and _rebind_client(); rebinding updates self._client and every pre-created OpenAPITool, OpenAPIResource, and OpenAPIResourceTemplate, which capture the client object directly.
  • lifespan() recreates and rebinds only when the provider owns the client and self._client.is_closed. The first lifespan reuses the original client; a caller-supplied client is never closed, recreated, or replaced.
  • No public API changes.

Contribution type

  • Bug fix (simple, well-scoped fix for a clearly broken behavior)
  • Documentation improvement
  • Enhancement

Bug fix verification

Test path: tests/server/providers/openapi/test_owned_client_lifespan.py (five tests).

Red at adf90b66 (test file from the fix applied, implementation reverted): 3 failed, 2 passed, deterministic RuntimeError: Cannot reopen a client instance, once it has been closed.; no collection errors. The two passing tests are intentional guards: a supplied client is not closed, and concurrent first-lifespan entries share the original client.

Green at the fix commit: 1209 passed, 26 warnings across tests/server/providers/ tests/server/test_providers.py.

Validation

  • uv run --isolated pytest tests/server/providers/ tests/server/test_providers.py -q -> 1209 passed
  • uv run --isolated ruff check and ruff format --check on the changed files -> clean
  • uv run --isolated ty check -> All checks passed

AI assistance

Tool(s) used: Claude Code
How you used it: investigated from a verified reproduction, implemented the client recreation/rebinding and the regression tests.

Checklist

  • This PR addresses an existing issue (or fixes a self-evident bug)
  • I have read CONTRIBUTING.md
  • I have added tests that cover my changes
  • I have run uv run prek run --all-files and all checks pass (focused checks passed)
  • I have self-reviewed my changes
  • If I used an LLM, it followed the repo's contributing conventions (not generic output)

@github-actions

This comment has been minimized.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-19T12:34:31.021167Z 5cb5805 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@marvin-context-protocol marvin-context-protocol Bot added bug Something isn't working. Reports of errors, unexpected behavior, or broken functionality. openapi Related to OpenAPI integration, parsing, or code generation features. labels Oct 19, 2026
The provider's auto-created httpx2 client is closed at the end of each
server lifespan and httpx2 clients cannot be reopened. Track the default
client's base URL, recreate the client when it is closed, and rebind the
pre-created tools, resources, and templates to the fresh instance.
Caller-supplied clients are still never closed or replaced.

@fastmcp-triage-reviewer fastmcp-triage-reviewer Bot 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.

Automated review by the FastMCP triage reviewer bot.

The lifecycle fix passed targeted verification. One non-blocking scope issue remains: the unrelated OAuth test now ignores every warning originating outside the repository, which can hide dependency warnings caused by the operation being tested.

Remove the tests/cli/test_client_commands.py warning-filter change from this PR; handle any OAuth warning issue separately with its own diagnosis.

Checks

Baseline lifecycle tests: 3 expected failures, 2 passes. Head: 114 focused tests passed. An independent probe verified tool/resource/template results and outgoing requests across two lifespans.

@zzstoatzz

Copy link
Copy Markdown
Collaborator

Closing for now: #5844 stays open. This also edits an unrelated test, and the fix will land after 4.2.

@zzstoatzz zzstoatzz closed this Nov 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working. Reports of errors, unexpected behavior, or broken functionality. bypass-issue-check openapi Related to OpenAPI integration, parsing, or code generation features.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] from_openapi() without an explicit client can only be connected once

2 participants