Skip to content

Fix type errors for ty 0.0.1-alpha.31 upgrade#2561

Merged
jlowin merged 4 commits intomainfrom
ty-upgrade-fixes
Dec 6, 2025
Merged

Fix type errors for ty 0.0.1-alpha.31 upgrade#2561
jlowin merged 4 commits intomainfrom
ty-upgrade-fixes

Conversation

@jlowin
Copy link
Copy Markdown
Member

@jlowin jlowin commented Dec 6, 2025

Upgrades ty compatibility from 0.0.1-alpha.29 to 0.0.1-alpha.31, which introduced stricter type checking.

Source code fixes:

  • Added guards for Path(None) in CLI
  • Added isinstance checks for URL elicitation (new in MCP SDK)
  • Used cast() for validated async functions in docket registration

ty bug workarounds (with TODO(ty): comments for future cleanup):

  • Match statement narrowing (messages.py)
  • isinstance union narrowing (roots.py)
  • Unpack[TypedDict] inference (transports.py)
  • in operator on str | bytes (test files)
  • Starlette Middleware typing (third-party stub issue)

Test fixes:

  • Added asserts for optional attribute access
  • Type ignores for inherently dynamic code (httpx internals)

Add type ignores and fixes for ty's stricter checking:
- Path(None) guards in cli.py
- isinstance checks for ElicitRequestFormParams (URL elicitation support)
- TODO(ty) comments for match/isinstance narrowing bugs
- Method override type ignores for generic covariance
- Starlette Middleware typing workarounds
- Dynamic type construction ignores in json_schema_type.py
- Add asserts for optional attribute access in tests
- Add type ignores for dynamic httpx transport internals
- Add TODO(ty) comments for `in` operator on str|bytes
- Add TODO(ty) comments for Starlette Middleware typing
- Use cast for prompt.fn async validation in server.py
@marvin-context-protocol marvin-context-protocol Bot added the enhancement Improvement to existing functionality. For issues and smaller PR improvements. label Dec 6, 2025
Fixes additional test file type errors discovered after upgrade.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 6, 2025

Walkthrough

This pull request adds runtime guards and handling for distinct request parameter types, while introducing widespread type-checking suppression annotations throughout the codebase. Key functional changes include enforcing None-checks in the CLI's prepare command, handling both form-based and URL-based ElicitRequest types in the client elicitation flow, and extending the proxy server's elicitation handling. The majority of changes consist of adding type: ignore comments and explicit type annotations across client, server, and utility modules to address type-checker warnings without modifying control flow or runtime behavior.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 74.42% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change—upgrading ty compatibility and fixing resulting type errors.
Description check ✅ Passed The description is comprehensive, covering source code fixes, ty bug workarounds with TODO comments, and test fixes. However, the Contributors Checklist items are not checked.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ty-upgrade-fixes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/fastmcp/prompts/prompt.py (1)

208-209: Consider adding a TODO comment for consistency.

The type: ignore[assignment] is appropriate here since the type checker doesn't properly narrow staticmethod.__func__ after the isinstance check. However, for consistency with other type suppressions in this PR (see lines 120-121 in http.py, line 378 in transports.py), consider adding a TODO comment:

 if isinstance(fn, staticmethod):
-    fn = fn.__func__  # type: ignore[assignment]
+    # TODO(ty): remove when ty supports staticmethod unwrapping
+    fn = fn.__func__  # type: ignore[assignment]
src/fastmcp/server/proxy.py (1)

639-643: Consider adding parameter type annotations for context manager protocol.

While the type: ignore[override] annotation suppresses ty's warning, the __aexit__ method parameters should ideally be typed according to the context manager protocol:

async def __aexit__(
    self,
    exc_type: type[BaseException] | None,
    exc_value: BaseException | None,
    traceback: types.TracebackType | None
) -> None:

This would eliminate the need for the type ignore and improve type safety.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ed976b and 85cbe92.

⛔ Files ignored due to path filters (8)
  • tests/client/test_client.py is excluded by none and included by none
  • tests/client/transports/test_transports.py is excluded by none and included by none
  • tests/resources/test_resource_template.py is excluded by none and included by none
  • tests/server/auth/providers/test_azure.py is excluded by none and included by none
  • tests/server/http/test_http_middleware.py is excluded by none and included by none
  • tests/server/test_server_docket.py is excluded by none and included by none
  • tests/server/test_server_lifespan.py is excluded by none and included by none
  • uv.lock is excluded by !**/*.lock and included by none
📒 Files selected for processing (20)
  • src/fastmcp/cli/cli.py (2 hunks)
  • src/fastmcp/client/elicitation.py (3 hunks)
  • src/fastmcp/client/messages.py (1 hunks)
  • src/fastmcp/client/roots.py (1 hunks)
  • src/fastmcp/client/tasks.py (1 hunks)
  • src/fastmcp/client/transports.py (1 hunks)
  • src/fastmcp/experimental/sampling/handlers/openai.py (1 hunks)
  • src/fastmcp/prompts/prompt.py (1 hunks)
  • src/fastmcp/server/auth/auth.py (1 hunks)
  • src/fastmcp/server/auth/oauth_proxy.py (3 hunks)
  • src/fastmcp/server/auth/providers/in_memory.py (2 hunks)
  • src/fastmcp/server/auth/providers/oci.py (2 hunks)
  • src/fastmcp/server/context.py (2 hunks)
  • src/fastmcp/server/elicitation.py (1 hunks)
  • src/fastmcp/server/http.py (1 hunks)
  • src/fastmcp/server/proxy.py (6 hunks)
  • src/fastmcp/server/server.py (1 hunks)
  • src/fastmcp/utilities/components.py (3 hunks)
  • src/fastmcp/utilities/json_schema_type.py (3 hunks)
  • src/fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.py: Python source code must use Python ≥3.10 with full type annotations
Never use bare except - be specific with exception types
Prioritize readable, understandable code - clarity over cleverness; avoid obfuscated or confusing patterns even if shorter
Follow existing patterns and maintain consistency in code organization and style

Files:

  • src/fastmcp/server/http.py
  • src/fastmcp/server/elicitation.py
  • src/fastmcp/server/server.py
  • src/fastmcp/server/auth/providers/oci.py
  • src/fastmcp/server/auth/providers/in_memory.py
  • src/fastmcp/client/tasks.py
  • src/fastmcp/utilities/json_schema_type.py
  • src/fastmcp/client/roots.py
  • src/fastmcp/server/auth/auth.py
  • src/fastmcp/client/transports.py
  • src/fastmcp/experimental/sampling/handlers/openai.py
  • src/fastmcp/server/context.py
  • src/fastmcp/utilities/components.py
  • src/fastmcp/server/proxy.py
  • src/fastmcp/cli/cli.py
  • src/fastmcp/prompts/prompt.py
  • src/fastmcp/client/messages.py
  • src/fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py
  • src/fastmcp/client/elicitation.py
  • src/fastmcp/server/auth/oauth_proxy.py
🧠 Learnings (2)
📚 Learning: 2025-12-01T15:48:05.095Z
Learnt from: jlowin
Repo: jlowin/fastmcp PR: 0
File: :0-0
Timestamp: 2025-12-01T15:48:05.095Z
Learning: PR #2505 in fastmcp adds NEW functionality to get_access_token(): it now first checks request.scope["user"] for the token (which never existed before), then falls back to _sdk_get_access_token() (the only thing the original code did). This is not a reversal of order but entirely new functionality to fix stale token issues.

Applied to files:

  • src/fastmcp/server/auth/providers/in_memory.py
📚 Learning: 2025-12-04T00:17:41.238Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T00:17:41.238Z
Learning: Applies to tests/**/*.py : Use `# type: ignore[attr-defined]` in tests for MCP results instead of type assertions

Applied to files:

  • src/fastmcp/utilities/components.py
🧬 Code graph analysis (7)
src/fastmcp/server/http.py (1)
src/fastmcp/server/middleware/middleware.py (1)
  • Middleware (80-208)
src/fastmcp/server/auth/providers/in_memory.py (2)
src/fastmcp/server/auth/providers/jwt.py (3)
  • load_access_token (367-483)
  • verify_token (485-498)
  • verify_token (535-564)
src/fastmcp/server/auth/auth.py (5)
  • AccessToken (43-46)
  • verify_token (118-129)
  • verify_token (241-243)
  • verify_token (287-289)
  • verify_token (382-395)
src/fastmcp/utilities/components.py (1)
src/fastmcp/server/middleware/middleware.py (1)
  • copy (63-64)
src/fastmcp/server/proxy.py (2)
src/fastmcp/resources/template.py (1)
  • from_mcp_template (207-217)
src/fastmcp/server/context.py (5)
  • session (395-405)
  • elicit (573-579)
  • elicit (585-589)
  • elicit (595-599)
  • elicit (604-693)
src/fastmcp/client/messages.py (1)
src/fastmcp/server/dependencies.py (1)
  • message (400-401)
src/fastmcp/client/elicitation.py (1)
src/fastmcp/utilities/json_schema_type.py (1)
  • json_schema_to_type (110-190)
src/fastmcp/server/auth/oauth_proxy.py (2)
src/fastmcp/server/auth/providers/in_memory.py (1)
  • load_access_token (287-296)
src/fastmcp/server/auth/providers/jwt.py (1)
  • load_access_token (367-483)
🪛 Ruff (0.14.7)
src/fastmcp/utilities/json_schema_type.py

268-268: Unused noqa directive (non-enabled: UP007)

Remove unused noqa directive

(RUF100)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Run tests with lowest-direct dependencies
  • GitHub Check: Run tests: Python 3.10 on windows-latest
  • GitHub Check: Run tests: Python 3.10 on ubuntu-latest
🔇 Additional comments (25)
src/fastmcp/server/http.py (1)

120-121: Well-documented typing workaround.

The type: ignore[arg-type] with accompanying TODO comment is appropriate for handling Starlette's Middleware typing issues. This aligns with the PR's stated objective of working around third-party stub issues.

src/fastmcp/experimental/sampling/handlers/openai.py (1)

162-168: Type suppression is safe due to runtime validation.

The type: ignore[assignment] is appropriate here. The runtime check on line 166 (if model_option in get_args(ChatModel)) ensures that model_option is a valid ChatModel value before assignment, making this type-safe despite the checker's inability to narrow the type.

src/fastmcp/server/auth/auth.py (1)

192-199: Consistent typing workaround for Starlette middleware.

The type: ignore[arg-type] annotations with TODO comment are consistent with the same workaround applied in src/fastmcp/server/http.py (lines 120-121). This appropriately handles the Starlette Middleware typing issue mentioned in the PR objectives.

src/fastmcp/server/auth/providers/in_memory.py (2)

287-287: Type suppression for override is appropriate.

The type: ignore[override] is necessary here because the method returns fastmcp.server.auth.auth.AccessToken (which extends the MCP SDK's AccessToken with additional claims field) rather than the base SDK type. This is a safe covariant override, but the type checker may not properly recognize it. Runtime behavior is correct.


298-311: Type suppression for override is appropriate.

The type: ignore[override] is consistent with the same issue in load_access_token at line 287. The method correctly implements the TokenVerifier protocol by delegating to load_access_token.

src/fastmcp/server/auth/providers/oci.py (1)

174-190: Type suppression appropriate for Pydantic settings pattern.

The explicit dict[str, object] annotation and type: ignore[arg-type] are appropriate for this Pydantic settings initialization pattern. The dictionary contains mixed types that will be validated at runtime by Pydantic's validation logic (lines 192-210 show the validation checks). This is a standard pattern when passing dynamic kwargs to Pydantic models.

src/fastmcp/client/transports.py (1)

378-379: Well-documented workaround for ty limitation.

The type: ignore[arg-type] with TODO comment appropriately handles the Unpack[TypedDict] inference issue explicitly mentioned in the PR objectives. The session_kwargs parameter is properly typed at the function signature (line 363), and runtime behavior is unaffected.

src/fastmcp/client/roots.py (1)

36-38: Appropriate workaround for isinstance narrowing limitation.

The type: ignore[arg-type] with TODO comment correctly handles the type narrowing issue mentioned in the PR objectives ("isinstance union narrowing adjustments in roots.py"). The isinstance(handler, list) check on line 36 logically ensures handler is a RootsList, but ty doesn't properly narrow the union type. Runtime behavior is correct.

src/fastmcp/server/server.py (1)

409-412: Casting prompt.fn to async callable is appropriate here

Given FunctionPrompt.task guarantees an async function at creation, the cast(Callable[..., Awaitable[Any]], prompt.fn) cleanly satisfies Docket’s typing without changing behavior. Looks good.

src/fastmcp/server/context.py (1)

391-392: Targeted type ignores are reasonable for dynamic MCP internals

The session._fastmcp_id assignment and passing self.request_context into the sampling handler are both existing behaviors; the added type: ignore comments simply acknowledge the dynamic attributes/signature and keep the code type-checkable without altering runtime semantics.

Also applies to: 544-544

src/fastmcp/cli/cli.py (2)

100-107: Version root-path guard is a safe improvement

Using Path(fastmcp.__file__ or ".") avoids the Path(None) edge case when __file__ is missing/falsey, without affecting typical installs where it is set. Change looks good.


822-830: Asserts in project prepare nicely document non-None assumptions

Given earlier error handling already exits when config_path or output_dir are None, the added assert statements are purely for typing clarity and won’t change behavior; they’re acceptable here.

src/fastmcp/server/elicitation.py (1)

40-48: Enum inlining override with localized type ignores looks correct

Overriding generate_inner to special-case enum schemas and calling enum_schema directly achieves the desired “no refs for enums” behavior; the added type: ignore annotations just reconcile this with Pydantic’s types and don’t affect runtime.

src/fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py (1)

181-196: validate_source type ignore matches intended flexible Source handling

Returning v with # type: ignore[return-value] is consistent with the function’s docstring: it passes through existing Source instances while constructing FileSystemSource from dicts. This keeps runtime behavior flexible while satisfying the narrower SourceType alias for static checking.

src/fastmcp/utilities/components.py (1)

99-121: Copy/model_copy overrides correctly manage private attributes

The customized model_copy and copy implementations work around Pydantic’s private-attr update limitations while keeping the public API intact: _key and _mirrored are set explicitly on the cloned instance, and the type: ignore[override] annotations are confined to typing concerns. This is a sensible and clear approach.

Also applies to: 140-143, 176-181

src/fastmcp/utilities/json_schema_type.py (1)

251-252: Remove unused # noqa: UP007 while keeping necessary type ignores

The added type: ignore[...] annotations are fine to satisfy stricter typing without changing behavior. However, the # noqa: UP007 on the combined = Union[tuple(item_types)] line is unused and flagged by Ruff (RUF100); you can drop just that directive:

-        combined = Union[tuple(item_types)]  # type: ignore[arg-type] # noqa: UP007
+        combined = Union[tuple(item_types)]  # type: ignore[arg-type]

This keeps the type suppression you need while silencing the linter.

Also applies to: 268-269, 285-285

⛔ Skipped due to learnings
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T00:17:41.238Z
Learning: Applies to tests/**/*.py : Use `# type: ignore[attr-defined]` in tests for MCP results instead of type assertions
src/fastmcp/server/auth/oauth_proxy.py (2)

1009-1010: Type annotation workaround for ty upgrade.

The type: ignore[arg-type] annotation accommodates stricter type checking in ty 0.0.1-alpha.31 for the AuthorizeError error parameter. The error code "invalid_client" is a valid OAuth 2.0 error code per RFC 6749 Section 5.2, and runtime behavior is unaffected.


1517-1517: Return type covariance workaround consistent with other providers.

The type: ignore[override] annotation handles ty's stricter checking on return type covariance. This method returns AccessToken | None where the parent class expects a different signature. The same pattern appears in other auth providers (in_memory.py line 286, jwt.py line 366), confirming this is the standard workaround for the ty upgrade.

src/fastmcp/client/tasks.py (1)

204-204: Type narrowing workaround for fire-and-forget async callback.

The type: ignore[arg-type] annotation addresses ty's inability to narrow the type of result to Awaitable[None] after the inspect.isawaitable(result) runtime check. The fire-and-forget pattern is correct for status notification callbacks, and the runtime guard ensures type safety.

src/fastmcp/client/messages.py (1)

38-49: Temporary workarounds for match statement type narrowing limitations.

The type: ignore annotations throughout the match statement address ty's current inability to narrow union types within match cases. The TODO(ty) comments appropriately mark these as temporary until ty adds support for match statement narrowing. The logic correctly handles all message types, and runtime behavior is unaffected.

src/fastmcp/server/proxy.py (3)

367-369: Override annotation for proxy-specific factory method signature.

The type: ignore[override] annotation handles the signature difference where from_mcp_template adds a client parameter not present in the parent class. This is necessary for the proxy pattern where remote templates require a client connection.


571-572: Workaround for isinstance exclusion narrowing limitation.

The type: ignore[arg-type] annotation addresses ty's inability to narrow types after an isinstance check that excludes specific types (line 566 checks for ResourceLink | EmbeddedResource and raises). The runtime guard ensures type safety, and the TODO comment appropriately marks this as temporary.


588-595: Correctly handles URL-based elicitation introduced in MCP SDK update.

The new logic properly distinguishes between form-based elicitation (with requestedSchema) and URL-based elicitation (without schema). Using an empty object schema {"type": "object", "properties": {}} for URL-based elicitation aligns with the pattern in src/fastmcp/server/context.py lines 640-641 where response_type=None generates the same empty schema. This ensures the proxy correctly forwards both elicitation types to connected clients.

src/fastmcp/client/elicitation.py (2)

29-30: Well-documented type update for URL elicitation support.

The addition of | None to the response type parameter, along with the clarifying comment, properly supports URL-based elicitation where no structured response schema is expected. This aligns with the MCP SDK's distinction between form-based and URL-based elicitation introduced in the upgrade.


47-54: Correctly implements URL elicitation support with proper schema handling.

The logic properly distinguishes between:

  1. Form-based elicitation with schema: Derives response type from params.requestedSchema via json_schema_to_type
  2. Form-based elicitation without schema: Empty object schema {"type": "object", "properties": {}} maps to None response type
  3. URL-based elicitation: No schema available, uses None response type

This implementation aligns with the proxy handler in src/fastmcp/server/proxy.py lines 588-595 and correctly handles the new elicitation variants introduced in the MCP SDK upgrade.

@jlowin jlowin merged commit 07750ef into main Dec 6, 2025
11 checks passed
@jlowin jlowin deleted the ty-upgrade-fixes branch December 6, 2025 02:29
@marvin-context-protocol
Copy link
Copy Markdown
Contributor

marvin-context-protocol Bot commented Dec 6, 2025

Test Failure Analysis

Summary: Two tests are crashing pytest-xdist workers on Windows Python 3.10, likely due to event loop conflicts in TestClient or RSA key generation.

Root Cause: The tests are failing on Windows Python 3.10 with pytest-xdist workers. This is a known platform-specific issue related to how pytest-xdist handles asyncio event loops on Windows. The issue is NOT caused by the ty upgrade changes in this PR.

Suggested Solutions:

  1. Short-term: Re-run the CI workflow - these are flaky tests specific to Windows + Python 3.10 + pytest-xdist
  2. Long-term: Fix the asyncio.run() call in test_successful_authorization_not_enhanced (line 130) which can cause worker crashes when pytest-xdist workers already have an event loop

In tests/server/auth/test_enhanced_error_responses.py line 116-130, the test uses:

def test_successful_authorization_not_enhanced(self, oauth_proxy):
    # ...
    asyncio.run(oauth_proxy.register_client(client_info))

This pattern is problematic under pytest-xdist on Windows. Consider converting to an async test or using a different approach for client registration that does not require asyncio.run().

Detailed Analysis

Failed Tests:

  • tests/server/auth/test_enhanced_error_responses.py::TestContentNegotiation::test_html_preferred_when_both_accepted
  • tests/server/auth/test_oauth_mounting.py::TestOAuthMounting::test_oauth_authorization_server_metadata_path_aware_discovery

Error Pattern:

worker 'gw0' crashed while running 'tests/server/auth/test_enhanced_error_responses.py::...'
worker 'gw1' crashed while running 'tests/server/auth/test_oauth_mounting.py::...'

Platform-Specific Issue:

  • Only fails on: Windows + Python 3.10 + pytest-xdist
  • Passes on: Linux, macOS, and other Python versions
  • 2970 tests passed - only these 2 failed

Technical Background:
pytest-xdist workers on Windows Python 3.10 have known issues with asyncio when running async code. The worker crash occurs when:

  1. pytest-xdist workers on Windows may have an active event loop
  2. Tests or fixtures attempt to create/manipulate event loops (via asyncio.run(), Starlette TestClient with lifespan, or RSA key generation)
  3. Event loop conflicts cause the worker process to crash

Why This PR Did Not Cause It:
The tests that failed were NOT modified in this PR. The PR only added type annotations and type: ignore comments. This is a pre-existing flaky test issue that happened to surface during this CI run.

Recent Test History:
Looking at recent workflow runs on main, there have been several test failures, suggesting ongoing instability with these specific test scenarios on Windows.

Related Files
  • tests/server/auth/test_enhanced_error_responses.py:130 - Contains asyncio.run() in sync test
  • tests/server/auth/test_enhanced_error_responses.py:291-306 - oauth_proxy fixture with RSAKeyPair.generate()
  • tests/server/auth/test_oauth_mounting.py - All async tests with complex OAuth setup

Recommendation: Re-run the workflow. These failures are environmental and not related to the ty upgrade changes.

Note: This is not related to the ty upgrade changes - the failing tests were not modified in this PR. This is a pre-existing test flakiness issue specific to Windows Python 3.10 + pytest-xdist.

Sources:


Updated with more accurate analysis after investigating the actual failing test code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Improvement to existing functionality. For issues and smaller PR improvements.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant