* fix: forward custom_route endpoints from mounted servers
When a child server with custom HTTP routes (registered via
@server.custom_route()) is mounted onto a parent, the routes were
silently dropped because _get_additional_http_routes() only returned
self._additional_http_routes without recursing into mounted providers.
This caused 404s for endpoints like /readyz health checks that worked
in v2 but broke in v3 (regression).
The fix updates _get_additional_http_routes() to traverse providers,
unwrap _WrappedProvider layers (from namespace transforms), find
FastMCPProvider instances, and recursively collect their server's
custom routes.
Fixes#3457
* fix: narrow type annotation from BaseRoute to Route
All items in _additional_http_routes are Route objects (created via
Route(...) in custom_route()). Using list[Route] instead of
list[BaseRoute] fixes the ty type checker failure where .path is
accessed on BaseRoute which doesn't have that attribute.
Removes unused BaseRoute imports from both server.py and transport.py.
* fix: revert route type to list[BaseRoute] to fix ty errors
The previous commit narrowed _additional_http_routes from list[BaseRoute]
to list[Route], which broke:
- component_manager appending Mount objects (Mount is BaseRoute, not Route)
- tests assigning list[BaseRoute] variables (generics are invariant)
Revert to list[BaseRoute] and use isinstance(r, Route) guards in tests
for type-safe .path access.
* fix: remove unused import and fix import grouping
- Remove unused `Route` import from server.py
- Fix import grouping in test_advanced.py (ruff check)
* Address review: move imports to module root, type Provider, add collision note
* fix: sort imports in transport.py
---------
Co-authored-by: voidborne-d <voidborne-d@users.noreply.github.com>
Co-authored-by: d 🔹 <258577966+voidborne-d@users.noreply.github.com>
Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
* fix: shield lifespan teardown from cancellation
Co-authored-by: Claude <noreply@anthropic.com>
* fix: stabilize flaky task and timeout tests under parallel execution
Co-authored-by: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix: cache component lists in ProxyProvider to avoid redundant backend connections
Every call_tool through a proxy was triggering _list_tools() to resolve
the tool by name, opening a full MCP session just for the lookup, then
opening a second session for the actual execution. This caches component
lists on the ProxyProvider with a configurable TTL (default 300s),
cutting backend handshakes in half for repeated calls.
* docs: document component caching and session reuse for proxy providers
* fix: add sleep in cache TTL test for Windows clock resolution
* docs: clarify cache scope and dynamic backend guidance
* fix: normalize Google scope shorthands and surface valid_scopes
Google accepts shorthand scopes like "email" in authorization requests but
returns full URIs like "https://www.googleapis.com/auth/userinfo.email" in
token responses. The verifier now normalizes shorthands at initialization so
the subset check works regardless of which form was used. GoogleProvider also
now exposes valid_scopes for controlling which scopes clients can request
beyond the required minimum.
Co-authored-by: Claude <noreply@anthropic.com>
* remove unused GOOGLE_SCOPE_ALIASES_REVERSE
---------
Co-authored-by: Claude <noreply@anthropic.com>
When OIDCProxy has verify_id_token=True and the IdP issues the same JWT
for both access_token and id_token, the value-equality check
`verification_token != upstream_token_set.access_token` evaluated to
False, skipping the scope patch entirely. This left AccessToken.scopes
empty, causing RequireAuthMiddleware to return 403 insufficient_scope.
Replace the value-equality check with an intent-based virtual method
`_uses_alternate_verification()` that OIDCProxy overrides to return
`self._verify_id_token`. The base OAuthProxy returns False (preserving
existing behavior for non-OIDC providers).
Fixes#3461
Co-authored-by: voidborne-d <voidborne-d@users.noreply.github.com>
* Add FastMCPApp — a Provider for composable MCP applications
* Wire Prefab callable resolver via to_json(tool_resolver=) parameter
* Remove inspect.signature compat check, use try/except until prefab 0.10.0
* Address review: fix add_tool registry gaps, normalize auth errors, bump prefab to 0.10.0
* Register global key after _add_component succeeds
* Simplify: extract decorator dispatch, use get_fastmcp_meta, expose get_global_tool
* Remove prek from Marvin workflows
These workflows run Claude to respond to /marvin mentions — linting
the repo is unnecessary and fails without renderer deps installed.
* Return ResolvedTool from callable resolver, add contacts example
The callable resolver now returns ResolvedTool (from prefab_ui) instead of a
plain string, carrying metadata like unwrap_result that the renderer needs to
correctly handle structuredContent envelopes. The unwrap_result flag is derived
from the tool's x-fastmcp-wrap-result output schema marker.
* Bump prefab-ui requirement to >=0.11.0
* Remove stale ty ignore comments now that prefab-ui 0.11 is published
* Block HS* JWT verification with public keys/JWKS
🤖 Generated with GPT-5.2-Codex
* Fix ruff format violations
🤖 Generated with Claude Code
* Handle bytes public_key in HS* algorithm PEM check
* Fix async partial callables rejected by iscoroutinefunction (#3423)
Add `is_coroutine_function()` utility that unwraps `functools.partial`
before checking, and guard `isroutine` checks so partials aren't
misrouted through `__call__`.
* Also check asyncio.iscoroutinefunction in is_coroutine_function
* Fix get_* returning None when latest version is disabled (#3421)
When a visibility transform disabled the highest version of a component,
get_tool/get_resource/get_resource_template/get_prompt returned None
instead of falling back to the next-highest enabled version. The list_*
path already worked correctly because deduplication runs after visibility
filtering. The get_* path now falls back to listing all versions and
picking the highest enabled one when the top version is disabled.
* Apply auth checks in version fallback paths
The fallback code in get_tool, get_resource, get_resource_template, and
get_prompt bypassed auth filtering when falling back to older versions
after the highest version was disabled. This could expose auth-protected
older versions to unauthorized users.
* Block BulkToolCaller self-invocation
🤖 Generated with GPT-5.2-Codex
* Fix ruff format violation in test_bulk_tool_caller.py
🤖 Generated with Claude Code
* Bind Cognito verifier audience to client ID
🤖 Generated with GPT-5.2-Codex
* Fix ty error: narrow return type of AWSCognitoProvider.get_token_verifier
🤖 Generated with Claude Code
* Cap client auto-pagination pages
🤖 Generated with GPT-5.2-Codex
* Raise on pagination limit instead of returning partial data
Add max_pages kwarg (default 250) to list_tools/list_resources/
list_resource_templates/list_prompts so users can control the bound.
Message.content now accepts ImageContent and AudioContent in addition to
TextContent and EmbeddedResource, matching MCP's ContentBlock type. This
fixes ProxyPrompt.render() silently JSON-serializing image/audio content
instead of preserving it.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add --config-path flag to claude-desktop install command
* feat: add --config-path flag to claude-desktop install command
* docs: add --config-path option to install-mcp documentation
* fix: show specific error message when provided --config-path does not exist