* 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>
* 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>
* 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>