Bump ty floor to >=0.0.29 and suppress five new false positives from
stricter tuple-union indexing, overload return-type inference, and
AsyncBaseTransport attribute resolution.
* fix: resolve OpenAPI 3.x server variables in _create_default_client
When an OpenAPI spec defines server variables (e.g. `https://{region}.api.example.com/v1`),
the default values are now substituted before constructing the httpx client base URL.
Previously, the URL was used as-is, causing all requests to fail for specs that use
server variable templating.
Fixes#1681
* fix: use str.replace instead of format_map for server variable substitution
format_map applies Python string formatting rules, so variable names
like {api.version} would be treated as attribute access and raise errors.
Literal token replacement handles all valid OpenAPI variable names safely.
* Unify background task context forwarding and fix concurrent dependency bugs
We've been getting a steady trickle of edge-case reports around background tasks
and contextual dependencies over the last few months (#3654, #3656, #3569). Each
one pointed at a different symptom, but they all traced back to the same area:
the way context is negotiated between the "frontend" server and Docket workers
was grown piecemeal, with each new piece of context (access tokens, HTTP headers,
origin request IDs) getting its own Redis key, its own restore function, and its
own ContextVar. This made it hard to reason about what state was available where,
and the shared-instance Dependency pattern made concurrent tasks stomp on each
other's cleanup state.
This takes a step back and reworks the whole thing as a single unified system:
- Dependency subclasses (_CurrentContext, Progress, _CurrentAccessToken, etc.)
are now stateless factories — __aenter__ returns a fresh per-invocation
object, so concurrent tasks never share mutable state. Fixes#3654, #3656.
- The three individual context-snapshot Redis keys (access_token, http_headers,
origin_request_id) are collapsed into a single TaskContextSnapshot stored as
one JSON key per task. The three _restore_task_* functions and two ContextVars
they populated are gone.
- Sync functions like get_http_request() and get_access_token() now find the
snapshot transparently in background tasks via a 3-tier sync fallback:
ContextVar (set by _CurrentContext for functions with deps) → in-memory dict
(same-process workers) → sync Redis GET (out-of-process workers). No function
wrapping needed.
- The _wrap_for_task_http_headers hack is deleted. FunctionTool registers its
raw function with Docket so Docket sees and resolves ALL dependencies,
including Docket-native ones like Retry and Timeout.
- ProxyTool.from_mcp_tool() now propagates execution.taskSupport metadata from
remote tools. Fixes#3569.
- Removed redundant _current_docket/_current_worker ContextVar management from
Context.__aenter__/__aexit__ (they're only set in the lifespan now).
Closes#3654Closes#3656Closes#3569🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Address code review feedback
- _OptionalCurrentContext: guard __aexit__ against cleaning up contexts it
didn't create (check is_background_task before delegating)
- Narrow except clauses in snapshot loading (OSError, JSONDecodeError, etc.
instead of bare Exception)
- Fix docstrings on register_with_docket for resources/prompts/templates
- Simplify Progress: read ExecutionProgress directly from current_execution
instead of creating and manually entering a DocketProgress wrapper
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Use pop-on-access transfer buffer instead of bounded LRU cache for snapshots
The in-memory snapshot dict is a transfer mechanism, not a cache. Entries go
in at submission and come out at the worker's first access. Using pop instead
of get means the dict only holds entries during the brief submission-to-execution
window, bounded by task concurrency (~10) rather than a 10,000-entry LRU limit.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Drop in-memory transfer buffer, use sync Redis for all backends
Instead of maintaining an in-memory dict to bridge the async/sync gap, use
a sync Redis client directly. For memory:// backends (fakeredis), shares the
same FakeServer instance via docket._redis.get_memory_server() so data written
by the async Docket client is visible to sync reads. For real Redis, creates a
standard sync connection. No in-process state to manage at all.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Move snapshot operations to TaskContextSnapshot methods
capture(), from_json(), to_json(), save() are now classmethod/instance methods
on the dataclass instead of free functions. Deduplicates JSON parsing that was
copy-pasted between the async and sync load paths.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Trim implementation details from register_with_docket docstrings
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Clarify docket lookup comment in submit_to_docket
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Restore docket/worker ContextVar bridge in Context.__aenter__
Servers that own the Docket (the parent) re-set _current_docket/_current_worker
from their instance attributes when entering a Context. Mounted children skip
this (their _docket is None), so they inherit the parent's value. This is needed
for ASGI deployments where ContextVars set during the lifespan don't propagate
to request handlers.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Key snapshot cache by task_id to prevent cross-task context leakage
Docket workers may reuse the same asyncio context for sequential tasks.
The ContextVar cache now stores (task_id, snapshot) tuples so stale entries
from previous tasks are automatically ignored.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Object-typed query parameters with explode=true (the default) were
passed as raw Python dicts to httpx, which called str() on them —
producing Python repr syntax (single quotes, capitalized booleans)
instead of proper query parameter serialization.
Per the OpenAPI specification, style=form with explode=true on objects
expands each property as a separate query parameter (e.g.
?myAttribute=true). This change handles dict values in both the
explode=true and explode=false branches of _serialize_query_params,
using the existing _query_scalar_to_str helper for correct boolean
formatting.
Fixes#2857
Hosts (Goose, MCP Jam) don't forward _meta on callServerTool, which
broke app tool routing entirely. Encode the app identity in the tool
name on the wire instead: the resolver writes "AppName___tool_name",
and the server parses it to route via get_app_tool.
* fix: recover StdioTransport after subprocess exits
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: improve Windows reliability for stdio crash recovery
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: filesystem provider import machinery
- Temporary sys.path entries (both package and non-package mode) now removed
immediately after exec_module via try/finally, eliminating permanent process-wide pollution
- Non-package files use bare stem as sys.modules key only if unclaimed; falls back
to private hash-based key to prevent stdlib shadowing (e.g. json.py clobbering json)
- Reload of private-key modules uses spec.loader.exec_module directly instead of
importlib.reload, which cannot find files by their private synthetic name
- _find_package_root gains stop_at parameter; discover_and_import passes provider_root
to prevent package root discovery from escaping above the provider boundary
Closes#3625 (issues 2, 3, 6)
🤖 Generated with Claude Code
* fix: use contextlib.suppress for SIM105 linting
🤖 Generated with Claude Code
* test: add import machinery regression tests
🤖 Generated with Claude Code
* fix: resolve provider_root before path comparison; improve tests
- Resolve provider_root in import_module_from_file so the stop_at boundary
in _find_package_root works correctly when provider_root is a relative path
(e.g. FileSystemProvider(Path("./mcp"))) — previously the resolved file_path
and unresolved stop_at.parent would never compare equal
- Fix test_stdlib_not_shadowed: use unconditional finally to restore sys.modules["json"]
- Strengthen test_same_stem_files: assert mod_a is not mod_b and that sys.modules["helpers"]
was not clobbered by the second import
- Replace direct _find_package_root unit test with an integration test through
import_module_from_file(provider_root=...) that also verifies the module name
and that tmp_path is not added to sys.path
🤖 Generated with Claude Code
Adds Provider.get_app_tool(app_name, tool_name) — a dedicated method for
finding app-visible tools by their original name, bypassing transforms.
AggregateProvider queries children, WrappedProvider delegates to inner,
FastMCPProvider delegates to nested server. The default implementation
checks _get_tool and matches meta.fastmcp.app.
This replaces the process-level _APP_TOOLS registry. Tool routing now
works through the provider tree, which exists in every process — no
shared state needed for horizontal scaling.
* Transparently refresh upstream token in OAuthProxy.load_access_token()
When upstream token validation fails during load_access_token, attempt
to refresh using the stored refresh token before returning None. This
prevents premature 401s that force clients into expensive full re-auth
flows when the upstream token expires.
Co-authored-by: Claude <noreply@anthropic.com>
* Gate transparent refresh on token expiry, add advisory lock
Only attempt upstream refresh when the token is actually expired, not
on any validation failure (scope mismatch, revocation, etc.). Add
per-token advisory lock to prevent concurrent async tasks from racing
to refresh the same upstream token.
* Re-check expiry inside lock, reload from storage after refresh failure
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Replace UUID global keys with (app_name, tool_name) registry
Collapses three module-level registries (_APP_TOOL_REGISTRY,
_FN_TO_GLOBAL_KEY, _NAME_TO_GLOBAL_KEY) into one: _APP_TOOLS keyed by
(app_name, tool_name). Removes UUID generation, global key stamping in
metadata, and the complex resolver that mapped callables and strings
through multiple fallback paths.
The server now reads _meta.fastmcp.app from the MCP request (set by the
Prefab renderer) and routes directly to the named app's tool. Two apps
with the same tool name are disambiguated by app name, not by UUID.
The resolver is simplified to pass-through: CallTool("save") serializes
as "save", and the server resolves it at call time using the app context.
* Read app name from _meta.prefab.app to match Prefab renderer
* Inject _meta.fastmcp.app into @app.ui() structured content
The @app.ui() decorator stores the FastMCPApp name in the tool's metadata.
When the tool result is serialized, _prefab_to_json injects it as
_meta.fastmcp.app in the structured content. The Prefab renderer reads
this on init and echoes it back as _meta.fastmcp.app on every
callServerTool call, completing the routing loop.
* feat: Add encoding parameter to FileResource
- Add optional encoding field (str | None, default None) to FileResource.
- Pass encoding through to read_text() for cross-platform text file reading.
- Preserve backward compatibility by defaulting to system encoding.
* test: Add tests for FileResource encoding parameter
- Test UTF-8 reading with explicit encoding for non-ASCII content.
- Test backward compatibility when no encoding is specified.
- Test that encoding is ignored for binary file reads.
- Test Latin-1 reading with matching encoding.
* docs: Document FileResource encoding parameter
- Add encoding="utf-8" to FileResource example in resource classes guide.
- Update FileResource description to mention encoding support.
* feat: Change FileResource encoding default from None to utf-8
- Default to utf-8 instead of system encoding to prevent cross-platform footgun.
- Update field description to reflect new default.
- Update test to verify default encoding is utf-8 with non-ASCII content.
- Remove redundant encoding="utf-8" from docs example since it is now the default.
* Comprehensive MCP Apps docs, string CallTool resolution, bump prefab-ui >=0.13.0
Rewrites the apps documentation as a learning journey: overview → Prefab apps
→ FastMCPApp → patterns → dev tools → custom HTML. Adds a new FastMCPApp page
covering composable apps with @app.tool()/@app.ui(), CallTool, forms, actions,
and composition. Teaches Rx() and set_initial_state() as the primary state API.
Adds string-based CallTool resolution so CallTool("save_contact") resolves to
the tool's global key, matching callable ref behavior. Requires prefab-ui 0.13.0
which passes strings through the tool resolver.
* Detect ambiguous string CallTool resolution across apps
* Simplify string name registry to plain dict (last-write-wins)
* feat: add TokenCache utility and caching to GitHubTokenVerifier
Extract the caching machinery from IntrospectionTokenVerifier into a
shared TokenCache class in fastmcp.utilities.token_cache, then wire
it into both IntrospectionTokenVerifier and GitHubTokenVerifier.
* Remove dead constant, validate negative cache params
* Fix overwrite eviction bug, skip cache on scope lookup failure
* feat: support ImageContent and AudioContent in sampling handlers
Co-authored-by: Claude <noreply@anthropic.com>
* Validate image MIME types, fix silent drop in assistant list messages
* Reject image/audio in assistant messages with tool_calls
* Reject ImageContent in assistant messages for Anthropic
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix: suppress output schema for ToolResult subclass annotations
* use issubclass_safe/is_class_member_of_type for ToolResult subclass checks
* use parsed_fn.return_type for ToolResult check in transform fallback