* 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.
* fix: task.wait() now returns on input_required instead of hanging
Previously, wait() used a terminal-state allowlist (completed, failed,
cancelled), so tasks entering input_required would hang until timeout.
Replaced with inverse logic: return whenever the task exits the 'working'
state. This handles input_required and any future blocking states without
needing to update the allowlist.
Fixes#3779
* fix: include submitted in in_progress_states to avoid premature return
* fix: revert submitted, update state docstring to match MCP spec
* fix: add _wait_terminal() so result() waits for completed/failed/cancelled
wait() correctly returns on input_required for human-in-the-loop use cases,
but result() needs to wait until the task fully resolves. Add a private
_wait_terminal() helper that loops through non-terminal states and use it
in all result() implementations.
* fix: elicitation scalar return, resource auto-serialization, Client.new() state, prompt errors
- Auto-wrap scalar elicitation responses for ScalarElicitationType schemas
so handlers can return T directly for ctx.elicit("msg", str/int/float)
- Auto-serialize dict/int/float/bool/None resource returns to JSON text
instead of crashing with TypeError
- Reset _task_registry and _submitted_task_ids in Client.new() so cloned
clients have independent task tracking state
- Include original error message in prompt render errors (matching tool
error behavior)
Fixes#3856
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix misleading comment and add list/tuple auto-serialization for resources
The comment said "list/tuple of primitives" but the isinstance check
didn't include list or tuple. Now it does, and the comment matches.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix TaskNotificationHandler binding in Client.new() and meta forwarding for JSON resources
Two review-identified bugs:
1. Client.new() shallow-copies _session_kwargs, so the cloned client's
TaskNotificationHandler still dispatches to the original client.
Fix: create a fresh _session_kwargs dict with a new handler bound
to the new client.
2. convert_result() for dict/int/float/bool/None fell through to
ResourceResult(raw_value) which lost component meta (CSP, permissions).
The str/bytes path correctly wrapped in ResourceContent with meta.
Fix: explicitly serialize JSON-native types and wrap with meta,
matching the str/bytes path. Other types still fall through for
error handling.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Preserve custom message handlers in Client.new()
Only replace the message handler with a new TaskNotificationHandler
if the current handler IS a TaskNotificationHandler. If the user
provided a custom message_handler, preserve it in the clone.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix client.new and add regression tests
* Honor declared MIME type for auto-serialized JSON resources
* Fix static analysis: remove unused StdioTransport import, fix ty:ignore comment
* Exclude list[ResourceContent] from JSON auto-serialization path
A bare list[ResourceContent] would match the isinstance(list) check
and get JSON-serialized instead of passing through to ResourceResult
normalization. Check for ResourceContent items first.
🤖 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>
* Scope tasks to authorization context, not session
Tasks were keyed by the transport-layer Mcp-Session-Id, which is
server-assigned and changes on reconnect — so clients lost access to
their running tasks after any connection interruption.
The MCP spec says tasks should be bound to authorization context, not
session. This replaces session_id with task_scope (derived from
AccessToken.client_id, URL-encoded) in all task data Redis keys and
Docket task keys. When no auth is configured, a "_" sentinel is used
and security comes from UUID task ID entropy per the spec.
Session ID is still used for transport-level concerns (notification
queues, subscriber registration) and is now stored in the
TaskContextSnapshot payload so background workers can still deliver
notifications.
Also extracts all the task context infrastructure (TaskContextInfo,
TaskContextSnapshot, snapshot loading, session/server registries) from
server/dependencies.py into a new server/tasks/context.py to keep the
DI module from sprawling further.
Closes#3758🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Rename _redis_key to _snapshot_redis_key
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Document in-process session registry as an optimization
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Tidy imports and docstrings
Hoist imports where safe, keep subscriptions/notifications deferred in
handlers.py since they pull in docket at module level. Sharpen docstrings
on keys.py and context.py so each module owns its lane. Clean up the
re-export block in dependencies.py.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix misleading comment on re-export block
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Tighten task scope: include sub claim, partition keyspaces
Addresses review feedback on #3800:
- Compose task scope from client_id and the JWT sub claim (when present)
so fixed-OAuth deployments isolate per user, not just per client.
- Replace the "_" anonymous sentinel with a tagged keyspace partition.
Docket keys are now auth:{enc_scope}:... or anon:..., and Redis keys
use fastmcp:task:auth:{enc_scope}:... or fastmcp:task:anon:...,
routed through a single task_redis_prefix() helper.
- get_task_scope() returns the raw scope (or None); encoding happens
once at the keys.py boundary, collapsing the previous double-quote
invariant.
- Drop the dormant fallback in notifications.py that routed
input_required relays into the anon keyspace when task_scope was
missing -- log and skip instead.
- Add comprehensive parser/encoder tests in test_task_keys.py covering
round-trips, malformed keys, and adversarial scopes ("anon", "_", and
scopes containing : / | %).
- Add cross-scope rejection tests: distinct client_ids, distinct sub
claims under a shared client_id, and authenticated vs anonymous.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a tool returns `list[dict]`, the client deserializes each dict as a
`Root()` dataclass with no fields instead of preserving the original dict
data.
The root cause is in `_get_from_type_handler`: its `"object"` branch
always fell through to `_create_dataclass` for schemas without
`properties`, creating an empty dataclass named `Root`. The top-level
`json_schema_to_type` already handled this case correctly (returning
`dict[str, Any]`), but that logic was not shared with `_schema_to_type`
which is used when converting nested schemas (e.g., array items).
Extract `_object_schema_to_type` to unify the four object-schema cases
(dict, typed dict, BaseModel with extra, dataclass) so both top-level
and nested paths produce the correct type.
Fixes#3867
Co-authored-by: Ke Wang <ke@pika.art>
- Delete entirely commented-out test_run_server.py (99 lines dead code)
- Fix test_pydantic_model_with_stringified_json_no_strict: replace
try/except-both-branches-pass with clear pytest.raises assertion
- Fix test_path_traversal_blocked: remove dead assertions after
pytest.raises (lines after raise never execute)
Error handling middleware test fixes are in a separate PR (#3858)
which also fixes the underlying RetryMiddleware bug.
🤖 Generated with Claude Code
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Cap consecutive final_response validation retries to 3
Previously, when the LLM repeatedly called final_response with data that
failed validation, the retry loop would continue up to 100 times (the
shared max_iterations limit), wasting tokens on a model that cannot
satisfy the schema.
Add _MAX_VALIDATION_RETRIES (default 3) that caps consecutive validation
failures. The counter resets when the LLM calls other tools (not
final_response), so the cap only applies to consecutive failures.
Fixes#3848
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add tests for consecutive validation retry cap
Tests cover:
- Validation failures within cap followed by success
- Consecutive validation failures exceeding cap (raises RuntimeError)
- Counter reset when LLM calls other tools between validation failures
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Slim down validation retry cap tests
Reduce boilerplate with helper functions.
Simplify counter-reset test from 5 calls to 4.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix static analysis: move imports to module level and format
Move CreateMessageResultWithTools and ToolUseContent imports to the
top of the test file so ty can resolve the names used in return-type
annotations of the helper functions. Also fix ruff import sorting
and formatting issues.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Align validation retry semantics with text-response retries
Change `>=` to `>` so _MAX_VALIDATION_RETRIES means "number of
retries after the initial attempt" (total = N+1), matching the
convention used by _MAX_TEXT_RESPONSE_RETRIES in the text-response
retry path.
Before: _MAX=3 meant 3 total attempts (>= comparison)
After: _MAX=3 means 1 initial + 3 retries = 4 total (> comparison)
🤖 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 GoogleGenaiSamplingHandler thought part leaking and unhelpful errors
- Filter thought parts (part.thought=True) from response content instead
of leaking them as TextContent in _response_to_result_with_tools
- Include finish_reason in error messages when no content is found, so
safety-filtered responses (SAFETY, RECITATION, etc.) are distinguishable
- Add specific error message for thinking-only responses in
_response_to_create_message_result
Fixes#3846
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add tests for thought part filtering and error message improvements
Tests cover:
- Thought parts filtered from tool-path responses
- Thought-only responses produce descriptive errors
- Safety-filtered responses include finish_reason in error
- Normal responses (text + function calls) unaffected
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix ruff format and ty check issues
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix ty check errors in tests
Remove unused ty: ignore comments from lines where isinstance() narrows
the type, and add correct ty: ignore[invalid-argument-type] and
ty: ignore[not-subscriptable] comments on lines in newly added test
functions where ty cannot infer the union type is a list. Also apply
ruff format fix in test_task_return_types.py.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix: use all() not any() for thinking-only detection
Addresses review feedback: any() would misclassify mixed responses
(thought + function_call) as thinking-only, hiding the real error.
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 broken code examples in docs
- Tag error output blocks as ```text instead of ```python (anthropic,
openai integration docs + v2 mirrors)
- Quote unquoted URL in Descope config example (+ v2 mirror)
- Fix GoogleGenAISamplingHandler → GoogleGenaiSamplingHandler casing
in sampling docs
- Fix import path: handlers.GoogleGenaiSamplingHandler →
handlers.google_genai.GoogleGenaiSamplingHandler in v3-features
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix remaining broken doc examples and add skip tags for false positives
- BearerTokenAuth → StaticTokenVerifier in deployment/http.mdx
- providers.oauth → server.auth import in authentication.mdx
- ListToolsNext → updated list_tools API in v3-features.mdx
- OAuthClientProvider → OAuth in v2/storage-backends.mdx
- Add test="skip" for upgrade guides, contrib placeholders, f-string backticks
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Ratchet doc example baselines to zero
All 1444 examples now pass syntax and import checks.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add pytest-examples dev dep, fix client_id in StaticTokenVerifier example, commit missed openapi fixes
- Add pytest-examples to dev dependencies (fixes CI ModuleNotFoundError)
- Include required client_id in StaticTokenVerifier token payload
- Commit previously unstaged HTTPRoute import fixes in openapi.mdx
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Update deprecated import paths across docs
- fastmcp.server.openapi → fastmcp.server.providers.openapi
- fastmcp.server.proxy → fastmcp.server.providers.proxy
- fastmcp.server.apps → fastmcp.apps
- Tag upgrade guide "Before" examples with test="skip"
🤖 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>
* Raise on unhandled content types in sampling handler dispatch chains
The Anthropic and OpenAI sampling handlers have isinstance chains that
dispatch on MCP content types but silently drop unhandled variants like
EmbeddedResource and ResourceLink. This adds explicit else-raise guards
to match the Gemini handler's behavior and the single-content dispatch
paths that already raise.
Raising is the right choice over warn-and-skip: a partial conversion
produces a plausible-but-wrong LLM response (the model confidently
answers based on incomplete input), which is worse than a clear error
that tells the user exactly what isn't supported.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add tests for unsupported content type raises in sampling handlers
Tests the new ValueError raises for unsupported content types
(e.g. EmbeddedResource) in the Anthropic and OpenAI message
conversion loops. Uses model_construct to bypass Pydantic's
union validation since the raise is a defensive guard for
future SDK content types.
🤖 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>
* Retry when LLM returns text instead of calling final_response tool
Instead of raising RuntimeError immediately when the LLM returns a text
response instead of calling the `final_response` tool for structured
output, retry up to 3 times with an explicit nudge message asking the
model to use the tool. This mirrors the existing retry behavior for
validation errors but with a separate, smaller cap.
Fixes#3847
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add tests for text response retry logic
Tests cover:
- Text response followed by successful final_response (retry works)
- Text response exceeding max retries (raises RuntimeError)
- Nudge message appended to history on retry
- No retry when result_type is None (text is valid)
Addresses review feedback from PR review tool (v1 flagged missing tests as high severity).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Slim down text response retry tests
Remove test_nudge_message_in_history (implementation detail).
Reduce boilerplate in remaining 3 tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix ruff format
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the ___ separator for FastMCPApp backend tool routing with a
deterministic hash(app_name, tool_name) prefix, and replaces the shared
singleton prefab renderer resource with per-tool resources synthesized
on demand.
Backend tools are now callable via <hash>_<local_name> instead of
<app_name>___<local_name>. The dispatcher walks the provider tree
recursively via get_tool_by_hash (same pattern as get_app_tool).
Each prefab tool gets its own renderer resource at
ui://prefab/tool/<hash>/renderer.html with per-tool CSP — fixing the
bug where PrefabAppConfig(csp=...) never actually applied.
Closes#3735, closes#3805
* fix: strip title fields from tool schemas for Gemini compatibility
Gemini 2.5 Flash produces MALFORMED_FUNCTION_CALL when a function
declaration's parameters_json_schema contains 'title' fields (which
Pydantic adds by default). Strip them in _convert_tool_to_google_genai
before passing to the API.
Fixes#3860
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix ty errors and lowest-deps test failure
- Add assertions for non-None before subscripting FunctionDeclaration
fields (ty check)
- Test compress_schema directly instead of constructing FunctionDeclaration
which may not support parameters_json_schema in google-genai==1.18.0
🤖 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>
- AggregateProvider._collect_list_results detects duplicate component
names across providers, respecting the server's on_duplicate setting
- Provider errors logged at WARNING instead of DEBUG
- Parent server re-masks ToolErrors from mounted children at the
FastMCPError catch boundary instead of mutating the child server
Fixes#3825
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Enables stricter type checking by promoting rules that default to
ignore: division-by-zero, possibly-missing-attribute,
possibly-missing-import, possibly-unresolved-reference,
unsupported-dynamic-base, unsupported-operator, unused-ignore-comment.
6 of the 7 rules had zero violations. possibly-unresolved-reference
had 9 (5 in src/, 3 in tests/, 1 walrus-operator false positive
suppressed with ty: ignore).
🤖 Generated with Claude Code
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix crash bugs in json_schema_to_type
- Handle boolean schemas (True/False) at the public entry point
- Append trailing underscore to Python keyword property names (PEP 8)
- Return Any for empty enum values instead of crashing Pydantic
- Deduplicate field names after sanitization to prevent collisions
(e.g. "foo-bar" and "foo_bar" both sanitizing to "foo_bar")
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Move local imports to module level in test_json_schema_type
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
Integration test that runs json_schema_to_type against 232K schemas
from 4,120 real-world OpenAPI specs (APIs.guru openapi-directory).
Snapshots crash counts as regression baselines so future changes
can't silently increase the crash rate.
Current baseline (openapi-directory@f7207cf0):
TypeErrors: 2,342 (datetime serialization)
SchemaErrors: 273 (invalid regexes in specs)
Timeouts: 0
Other: 0
Skipped unless openapi-directory is cloned locally.
Run with: pytest -m integration tests/.../test_real_world_schemas.py
🤖 Generated with Claude Code
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Detect async/sync generators after tool execution and materialize
into lists before the result conversion pipeline processes them
- Generator materialization runs inside timeout scope so slow generators
respect the configured timeout
- Handle bytes return types: UTF-8 bytes as text, non-UTF-8 as base64
- Suppress output_schema for bytes return types (can't be structured JSON)
- Catch UnicodeDecodeError alongside PydanticSerializationError in
convert_result for robustness
Fixes#3829
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The store_files tool checked the client-provided `size` field to enforce
max_file_size, but this field is untrusted input. A client could set
size=1 while sending a multi-megabyte payload, bypassing the limit.
Now computes actual size from the base64 data length instead.
🤖 Generated with Claude Code
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
pydocket 0.19.0 fixes the fakeredis 2.35.0 FakeConnection rename
internally, so we no longer need to carry the fakeredis ceiling
ourselves. Removes the direct fakeredis[lua]<2.35.0 dependency from the
tasks extra entirely — it's just a transitive of pydocket now.
🤖 Generated with Claude Code
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* 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>