diff --git a/fastmcp_slim/fastmcp/client/mixins/tools.py b/fastmcp_slim/fastmcp/client/mixins/tools.py index efa053bd7..d70d70d69 100644 --- a/fastmcp_slim/fastmcp/client/mixins/tools.py +++ b/fastmcp_slim/fastmcp/client/mixins/tools.py @@ -347,7 +347,9 @@ class ClientToolsMixin: # Per SEP-1686 final spec: client sends only ttl, server generates taskId # Inject trace context into meta for propagation to server propagated_meta = inject_trace_context(meta) - request_meta = cast(mcp_types.RequestParams.Meta | None, propagated_meta) + # SDK v2: request `_meta` is `RequestParamsMeta` (a TypedDict), not the + # old `RequestParams.Meta` nested model. + request_meta = cast(mcp_types.RequestParamsMeta | None, propagated_meta) # Build request with task metadata request = mcp_types.CallToolRequest( diff --git a/fastmcp_slim/fastmcp/server/tasks/handlers.py b/fastmcp_slim/fastmcp/server/tasks/handlers.py index 9aacf2027..373abe4d9 100644 --- a/fastmcp_slim/fastmcp/server/tasks/handlers.py +++ b/fastmcp_slim/fastmcp/server/tasks/handlers.py @@ -77,8 +77,11 @@ async def submit_to_docket( # Server MUST generate task IDs, clients no longer provide them server_task_id = str(uuid.uuid4()) - # Record creation timestamp per SEP-1686 final spec (line 430) + # Record creation timestamp per SEP-1686 final spec (line 430). SDK v2 + # types `Task.created_at` / `TaskStatusNotificationParams.created_at` as ISO + # strings, so carry a serialized copy for wire-crossing models. created_at = datetime.now(timezone.utc) + created_at_iso = created_at.isoformat() ctx = get_context() @@ -149,8 +152,8 @@ async def submit_to_docket( "taskId": server_task_id, "status": "working", "statusMessage": "Task submitted", - "createdAt": created_at, - "lastUpdatedAt": created_at, + "createdAt": created_at_iso, + "lastUpdatedAt": created_at_iso, "ttl": ttl_ms, "pollInterval": poll_interval_ms, }, @@ -161,10 +164,11 @@ async def submit_to_docket( }, } ) - server_notification = mcp_types.ServerNotification(notification) + # SDK v2: `ServerNotification` is a union type, not a wrapper class; + # `send_notification` takes the bare notification model directly. with suppress(Exception): # Don't let notification failures break task creation - await ctx.session.send_notification(server_notification) + await ctx.session.send_notification(notification) # type: ignore[arg-type] # ty:ignore[invalid-argument-type] # Queue function to Docket by key (result storage via execution_ttl) # Use component.add_to_docket() which handles calling conventions @@ -228,8 +232,8 @@ async def submit_to_docket( task=mcp_types.Task( task_id=server_task_id, status="working", - created_at=created_at, - last_updated_at=created_at, + created_at=created_at_iso, + last_updated_at=created_at_iso, ttl=ttl_ms, poll_interval=poll_interval_ms, ) diff --git a/fastmcp_slim/fastmcp/server/tasks/notifications.py b/fastmcp_slim/fastmcp/server/tasks/notifications.py index 6aabdcd6a..78a9034d4 100644 --- a/fastmcp_slim/fastmcp/server/tasks/notifications.py +++ b/fastmcp_slim/fastmcp/server/tasks/notifications.py @@ -193,9 +193,8 @@ async def _send_mcp_notification( "_meta": notification_dict.get("_meta"), } ) - server_notification = mcp_types.ServerNotification(notification) - - await session.send_notification(server_notification) + # SDK v2: `ServerNotification` is a union type; send the bare model. + await session.send_notification(notification) # type: ignore[arg-type] # ty:ignore[invalid-argument-type] # If this is an input_required notification with elicitation metadata, # relay the elicitation to the client via standard elicitation/create