* Fix OCI Provider issue in 3.x version. Add OCI auth provider example and test
* Fix OCI Provider issue in 3.x version. Add OCI auth provider example and test. Fixed a couple of minor issues in README.
* Rerun CI
* Restore task snapshot via a worker-level dependency
`get_access_token()` returned `None` inside background tasks whenever
`FASTMCP_DOCKET_URL` pointed at a `redis+cluster://` URL. The write side
was fine — it went through `docket.redis()`, which is cluster-aware —
but fastmcp kept a parallel sync Redis client just to read the snapshot
back, and `Redis.from_url()` rejects the cluster scheme.
Docket 0.19.1 ships worker-level dependencies that resolve per task in
the same asyncio.Task as user code, so ContextVars propagate cleanly.
That lets us load the snapshot once via `restore_task_snapshot` and
drop the sync Redis path entirely. Sync helpers like
`get_access_token()` and `get_http_request()` now just read a
ContextVar; Docket is the sole Redis consumer.
Closes#3897
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Revert TaskKey stub to a plain return
NotImplementedError would fire at module import if anything evaluated
the default; a no-op stub keeps the module usable without the
fastmcp[tasks] extra, which is what we want.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: OpenAPI request director content-type dispatch and cookie params
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Stringify multipart form values and cookie params for httpx
httpx rejects non-string scalars in files= and cookies=.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add tests for non-string multipart values and cookie stringification
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Consolidate to single httpx.Request construction point
Eliminate early returns by using variables for files/data kwargs.
All httpx body kwargs accept None, so we set exactly one and
pass all to a single Request() call.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Use _query_scalar_to_str for multipart booleans, add tuple passthrough test
Reuse existing boolean serialization (true/false not True/False) for
multipart form fields. Add test for file-like tuple passthrough.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Normalize media type for dispatch, use OpenAPI serialization for cookies
- Strip content-type parameters (e.g. "; charset=utf-8") and lowercase
before matching, so variants like "Multipart/Form-Data" match correctly
- Use _query_scalar_to_str for cookie values (true/false not True/False)
- Add boolean cookie test
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Preserve media-type parameters in Content-Type header
Use raw_content_type (with charset etc.) for the outgoing header,
normalized form only for dispatch matching.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Pass bytes/file-like values directly in multipart, add charset preservation test
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: allow hyphens in resource template parameter names
Normalize hyphens to underscores at the regex group level in build_regex()
and at the param extraction level in from_function(). No API changes —
build_regex still returns Pattern | None, match_uri_template still returns
the same dict shape.
Closes#3921🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Guard against query params clobbering path params
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add tests for wildcard hyphens, expand, and query clobber guard
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* ruff format fix
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add collision detection for hyphen/underscore param name normalization
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* OTEL: Fix attribute compliance and improve telemetry helpers
Attribute compliance:
- Remove rpc.system/service/method (MCP is not traditional RPC)
- Add gen_ai.tool.name on tools/call spans
- Add gen_ai.prompt.name on prompts/get spans
- Fix session_id check (truthy -> is not None)
Telemetry helper improvements:
- Add is_recording() guards to skip work on non-recording spans
- Add error.type attribute with __qualname__ on error spans
- Use isinstance check for ToolError to set "tool_error" error type
- Include exception message in span status description
- Add tool_name/prompt_name params to server_span and client_span
Client call_tool enrichment:
- Reflect tool-level errors (result.isError) on client span status
so callers see ERROR even though the MCP protocol call succeeded
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove resource URI from span names to avoid high-cardinality
Per MCP semantic conventions, resource URIs SHOULD NOT be included in
span names by default since they can be unbounded (especially with
templates like users://{id}/profile). The URI remains available via
the mcp.resource.uri attribute.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add missing gen_ai/mcp attributes to proxy and delegate spans
- Proxy tool spans: add gen_ai.tool.name
- Proxy prompt spans: add gen_ai.prompt.name
- All delegate spans: add mcp.method.name
- Docs: remove rpc.* references, update span names and attributes table
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Hoist ToolError imports to module level, add rpc.* migration note
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: handle non-TextContent error responses in ProxyTool
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Avoid serializing binary content into ToolError messages
Use type name instead of str(content) to prevent dumping
large base64 payloads into error messages.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* ruff format fix
🤖 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: server safety guards for self-mount, duplicate middleware, mount arg order
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Remove duplicate middleware and mount arg order checks
These are runtime type checking, not bugs — a type checker catches them.
Keep only the self-mount guard which is a semantic check.
🤖 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: 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>