A FastMCP client now transparently completes tasked tools/call: the tasks
ClientExtension advertises the capability and claims the CreateTaskResult, and
the resolver drives the tasks/get poll loop to completion, answering in-task
input through the client's elicitation handler and returning the tool's real
result. call_tool is transparent, call_tool_mcp exposes the raw result, and
call_tool_task yields a Task handle. The client half moves to fastmcp-tasks;
the [tasks] client extension auto-wires into Client (ProxyClient opts out).
Co-Authored-By: Claude <noreply@anthropic.com>
TasksExtension serves io.modelcontextprotocol/tasks on the extension API:
a decide-and-task tools/call interceptor (era-gated to modern connections),
tasks/get with inlined results and inputRequests, tasks/update delivering
poll-based in-task elicitation, tasks/cancel, durable creation, and
auth-scoped task isolation. Wire models validate against the vendored
ext-tasks schema. Worker-side Context hooks are refcounted so sibling
servers cannot strand each other's workers.
Co-Authored-By: Claude <noreply@anthropic.com>
Engine modules (keys, context snapshot, docket lifespan, worker CLI,
client handles) move intact; SEP-1686 wire modules park in _legacy_wire
for adaptation to SEP-2663. Core keeps task=True declaration on tools
only and raises at serve time until the tasks extension is registered.
Co-Authored-By: Claude <noreply@anthropic.com>
Resource/prompt error detail and proxy instructions/connection-error
surfacing now work on the modern protocol era, so the tests pinned to
mode="legacy" with a TODO(defect)/TODO(mode="legacy" pin) marker run
on the default auto mode again.
Remove 4 unjustified pins (proxy header passthrough, connect timeout,
two response_title validation tests that fail before any request is
dispatched). Keep 81 pins that genuinely exercise older-protocol-only
behavior (ctx.elicit back-channel, sampling, roots, ping, session IDs,
initialize handshake, client.set_logging_level).
Flags a real defect: _on_read_resource/_on_get_prompt only catch
(DisabledError, NotFoundError), unlike _on_call_tool which catches
FastMCPError broadly. A ResourceError/PromptError escapes as a raw
exception and the modern protocol's generic exception ladder masks it
as "Internal server error", losing the detailed message tool errors
still get. Left pinned with a TODO in test_client.py and
test_error_handling.py rather than hidden.
Client negotiates the newest mutual protocol era by default (probe
server/discover, fall back to the initialize handshake). ProxyClient and the
inspect utility explicitly pin the handshake era so proxy forwarding and
server_info reads are unchanged. SSE and multi-server config transports are
legacy-only. extensions= and result_claims= (SEP-2133) are thin passthroughs
to the SDK session.
* 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>
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>
* Propagate x-fastmcp-wrap-result flag in tool result _meta
🤖 Generated with Claude Code
Co-authored-by: Claude <noreply@anthropic.com>
* Skip listTools round-trip when _meta has x-fastmcp-wrap-result
🤖 Generated with Claude Code
Co-authored-by: Claude <noreply@anthropic.com>
* Use namespaced meta key: {"fastmcp": {"wrap_result": true}}
🤖 Generated with Claude Code
Co-authored-by: Claude <noreply@anthropic.com>
* Clean up _parse_call_tool_result: hoist cast import, document local CallToolResult import, extract fastmcp_meta
🤖 Generated with Claude Code
Co-authored-by: Claude <noreply@anthropic.com>
* Merge _meta in tasks result handler instead of overwriting
🤖 Generated with Claude Code
* Preserve type validation in meta-based unwrap path
🤖 Generated with Claude Code
* Fix type validation for wrapped task results, guard non-dict meta
🤖 Generated with Claude Code
---------
Co-authored-by: Claude <noreply@anthropic.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>
* Update repository references from jlowin/fastmcp to prefecthq/fastmcp
* Retrigger CI after repo transfer
* chore: Update SDK documentation
* Only run deep triage on bug issues for jlowin
---------
Co-authored-by: marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com>