diff --git a/docs/python-sdk/fastmcp-server-context.mdx b/docs/python-sdk/fastmcp-server-context.mdx
index 19d67e5d6..2873100b7 100644
--- a/docs/python-sdk/fastmcp-server-context.mdx
+++ b/docs/python-sdk/fastmcp-server-context.mdx
@@ -189,12 +189,16 @@ report_progress(self, progress: float, total: float | None = None, message: str
Report progress for the current operation.
+Works in both foreground (MCP progress notifications) and background
+(Docket task execution) contexts.
+
**Args:**
- `progress`: Current progress value e.g. 24
- `total`: Optional total value e.g. 100
+- `message`: Optional status message describing current progress
-#### `list_resources`
+#### `list_resources`
```python
list_resources(self) -> list[SDKResource]
@@ -206,7 +210,7 @@ List all available resources from the server.
- List of Resource objects available on the server
-#### `list_prompts`
+#### `list_prompts`
```python
list_prompts(self) -> list[SDKPrompt]
@@ -218,7 +222,7 @@ List all available prompts from the server.
- List of Prompt objects available on the server
-#### `get_prompt`
+#### `get_prompt`
```python
get_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> GetPromptResult
@@ -234,7 +238,7 @@ Get a prompt by name with optional arguments.
- The prompt result
-#### `read_resource`
+#### `read_resource`
```python
read_resource(self, uri: str | AnyUrl) -> ResourceResult
@@ -249,7 +253,7 @@ Read a resource by URI.
- ResourceResult with contents
-#### `log`
+#### `log`
```python
log(self, message: str, level: LoggingLevel | None = None, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
@@ -267,7 +271,7 @@ Messages sent to Clients are also logged to the `fastmcp.server.context.to_clien
- `extra`: Optional mapping for additional arguments
-#### `transport`
+#### `transport`
```python
transport(self) -> TransportType | None
@@ -279,7 +283,7 @@ Returns the transport type used to run this server: "stdio", "sse",
or "streamable-http". Returns None if called outside of a server context.
-#### `client_supports_extension`
+#### `client_supports_extension`
```python
client_supports_extension(self, extension_id: str) -> bool
@@ -304,7 +308,7 @@ Example::
return "text-only client"
-#### `client_id`
+#### `client_id`
```python
client_id(self) -> str | None
@@ -313,7 +317,7 @@ client_id(self) -> str | None
Get the client ID if available.
-#### `request_id`
+#### `request_id`
```python
request_id(self) -> str
@@ -324,7 +328,7 @@ Get the unique ID for this request.
Raises RuntimeError if MCP request context is not available.
-#### `session_id`
+#### `session_id`
```python
session_id(self) -> str
@@ -341,7 +345,7 @@ the same client session.
- for other transports.
-#### `session`
+#### `session`
```python
session(self) -> ServerSession
@@ -355,7 +359,7 @@ In background task mode: Returns the session stored at Context creation.
Raises RuntimeError if no session is available.
-#### `debug`
+#### `debug`
```python
debug(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
@@ -366,7 +370,7 @@ Send a `DEBUG`-level message to the connected MCP Client.
Messages sent to Clients are also logged to the `fastmcp.server.context.to_client` logger with a level of `DEBUG`.
-#### `info`
+#### `info`
```python
info(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
@@ -377,7 +381,7 @@ Send a `INFO`-level message to the connected MCP Client.
Messages sent to Clients are also logged to the `fastmcp.server.context.to_client` logger with a level of `DEBUG`.
-#### `warning`
+#### `warning`
```python
warning(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
@@ -388,7 +392,7 @@ Send a `WARNING`-level message to the connected MCP Client.
Messages sent to Clients are also logged to the `fastmcp.server.context.to_client` logger with a level of `DEBUG`.
-#### `error`
+#### `error`
```python
error(self, message: str, logger_name: str | None = None, extra: Mapping[str, Any] | None = None) -> None
@@ -399,7 +403,7 @@ Send a `ERROR`-level message to the connected MCP Client.
Messages sent to Clients are also logged to the `fastmcp.server.context.to_client` logger with a level of `DEBUG`.
-#### `list_roots`
+#### `list_roots`
```python
list_roots(self) -> list[Root]
@@ -408,7 +412,7 @@ list_roots(self) -> list[Root]
List the roots available to the server, as indicated by the client.
-#### `send_notification`
+#### `send_notification`
```python
send_notification(self, notification: mcp.types.ServerNotificationType) -> None
@@ -420,7 +424,7 @@ Send a notification to the client immediately.
- `notification`: An MCP notification instance (e.g., ToolListChangedNotification())
-#### `close_sse_stream`
+#### `close_sse_stream`
```python
close_sse_stream(self) -> None
@@ -438,7 +442,7 @@ Instead of holding a connection open for minutes, you can periodically close
and let the client reconnect.
-#### `sample_step`
+#### `sample_step`
```python
sample_step(self, messages: str | Sequence[str | SamplingMessage]) -> SampleStep
@@ -481,7 +485,7 @@ regardless of this setting.
- - .text: The text content (if any)
-#### `sample`
+#### `sample`
```python
sample(self, messages: str | Sequence[str | SamplingMessage]) -> SamplingResult[ResultT]
@@ -490,7 +494,7 @@ sample(self, messages: str | Sequence[str | SamplingMessage]) -> SamplingResult[
Overload: With result_type, returns SamplingResult[ResultT].
-#### `sample`
+#### `sample`
```python
sample(self, messages: str | Sequence[str | SamplingMessage]) -> SamplingResult[str]
@@ -499,7 +503,7 @@ sample(self, messages: str | Sequence[str | SamplingMessage]) -> SamplingResult[
Overload: Without result_type, returns SamplingResult[str].
-#### `sample`
+#### `sample`
```python
sample(self, messages: str | Sequence[str | SamplingMessage]) -> SamplingResult[ResultT] | SamplingResult[str]
@@ -547,43 +551,43 @@ regardless of this setting.
- - .history: All messages exchanged during sampling
-#### `elicit`
+#### `elicit`
```python
elicit(self, message: str, response_type: None) -> AcceptedElicitation[dict[str, Any]] | DeclinedElicitation | CancelledElicitation
```
-#### `elicit`
+#### `elicit`
```python
elicit(self, message: str, response_type: type[T]) -> AcceptedElicitation[T] | DeclinedElicitation | CancelledElicitation
```
-#### `elicit`
+#### `elicit`
```python
elicit(self, message: str, response_type: list[str]) -> AcceptedElicitation[str] | DeclinedElicitation | CancelledElicitation
```
-#### `elicit`
+#### `elicit`
```python
elicit(self, message: str, response_type: dict[str, dict[str, str]]) -> AcceptedElicitation[str] | DeclinedElicitation | CancelledElicitation
```
-#### `elicit`
+#### `elicit`
```python
elicit(self, message: str, response_type: list[list[str]]) -> AcceptedElicitation[list[str]] | DeclinedElicitation | CancelledElicitation
```
-#### `elicit`
+#### `elicit`
```python
elicit(self, message: str, response_type: list[dict[str, dict[str, str]]]) -> AcceptedElicitation[list[str]] | DeclinedElicitation | CancelledElicitation
```
-#### `elicit`
+#### `elicit`
```python
elicit(self, message: str, response_type: type[T] | list[str] | dict[str, dict[str, str]] | list[list[str]] | list[dict[str, dict[str, str]]] | None = None) -> AcceptedElicitation[T] | AcceptedElicitation[dict[str, Any]] | AcceptedElicitation[str] | AcceptedElicitation[list[str]] | DeclinedElicitation | CancelledElicitation
@@ -612,7 +616,7 @@ type or dataclass or BaseModel. If it is a primitive type, an
object schema with a single "value" field will be generated.
-#### `set_state`
+#### `set_state`
```python
set_state(self, key: str, value: Any) -> None
@@ -625,7 +629,7 @@ The key is automatically prefixed with the session identifier.
State expires after 1 day to prevent unbounded memory growth.
-#### `get_state`
+#### `get_state`
```python
get_state(self, key: str) -> Any
@@ -636,7 +640,7 @@ Get a value from the session-scoped state store.
Returns None if the key is not found.
-#### `delete_state`
+#### `delete_state`
```python
delete_state(self, key: str) -> None
@@ -645,7 +649,7 @@ delete_state(self, key: str) -> None
Delete a value from the session-scoped state store.
-#### `enable_components`
+#### `enable_components`
```python
enable_components(self) -> None
@@ -669,7 +673,7 @@ ResourceListChangedNotification, and PromptListChangedNotification.
- `match_all`: If True, matches all components regardless of other criteria.
-#### `disable_components`
+#### `disable_components`
```python
disable_components(self) -> None
@@ -693,7 +697,7 @@ ResourceListChangedNotification, and PromptListChangedNotification.
- `match_all`: If True, matches all components regardless of other criteria.
-#### `reset_visibility`
+#### `reset_visibility`
```python
reset_visibility(self) -> None
diff --git a/docs/python-sdk/fastmcp-server-tasks-elicitation.mdx b/docs/python-sdk/fastmcp-server-tasks-elicitation.mdx
index 1b06baa8e..f30b084ca 100644
--- a/docs/python-sdk/fastmcp-server-tasks-elicitation.mdx
+++ b/docs/python-sdk/fastmcp-server-tasks-elicitation.mdx
@@ -13,7 +13,7 @@ in Docket workers. Unlike regular MCP requests, background tasks don't have
an active request context, so elicitation requires special handling:
1. Set task status to "input_required" via Redis
-2. Send notifications/tasks/updated with elicitation metadata
+2. Send notifications/tasks/status with elicitation metadata
3. Wait for client to send input via tasks/sendInput
4. Resume task execution with the provided input
@@ -26,7 +26,7 @@ internal APIs for background task coordination.
### `elicit_for_task`
```python
-elicit_for_task(task_id: str, session: ServerSession, message: str, schema: dict[str, Any], fastmcp: FastMCP) -> mcp.types.ElicitResult
+elicit_for_task(task_id: str, session: ServerSession | None, message: str, schema: dict[str, Any], fastmcp: FastMCP) -> mcp.types.ElicitResult
```
@@ -50,7 +50,7 @@ in a Docket worker context where there's no active MCP request.
- `McpError`: If the elicitation request fails
-### `handle_task_input`
+### `handle_task_input`
```python
handle_task_input(task_id: str, session_id: str, action: str, content: dict[str, Any] | None, fastmcp: FastMCP) -> bool
diff --git a/docs/python-sdk/fastmcp-server-tasks-handlers.mdx b/docs/python-sdk/fastmcp-server-tasks-handlers.mdx
index bee4b0b93..94e094174 100644
--- a/docs/python-sdk/fastmcp-server-tasks-handlers.mdx
+++ b/docs/python-sdk/fastmcp-server-tasks-handlers.mdx
@@ -13,7 +13,7 @@ Handles queuing tool/prompt/resource executions to Docket as background tasks.
## Functions
-### `submit_to_docket`
+### `submit_to_docket`
```python
submit_to_docket(task_type: Literal['tool', 'resource', 'template', 'prompt'], key: str, component: Tool | Resource | ResourceTemplate | Prompt, arguments: dict[str, Any] | None = None, task_meta: TaskMeta | None = None) -> mcp.types.CreateTaskResult
diff --git a/docs/python-sdk/fastmcp-server-tasks-notifications.mdx b/docs/python-sdk/fastmcp-server-tasks-notifications.mdx
new file mode 100644
index 000000000..a69f97f7b
--- /dev/null
+++ b/docs/python-sdk/fastmcp-server-tasks-notifications.mdx
@@ -0,0 +1,111 @@
+---
+title: notifications
+sidebarTitle: notifications
+---
+
+# `fastmcp.server.tasks.notifications`
+
+
+Distributed notification queue for background task events (SEP-1686).
+
+Enables distributed Docket workers to send MCP notifications to clients
+without holding session references. Workers push to a Redis queue,
+the MCP server process subscribes and forwards to the client's session.
+
+Pattern: Fire-and-forward with retry
+- One queue per session_id
+- LPUSH/BRPOP for reliable ordered delivery
+- Retry up to 3 times on delivery failure, then discard
+- TTL-based expiration for stale messages
+
+Note: Docket's execution.subscribe() handles task state/progress events via
+Redis Pub/Sub. This module handles elicitation-specific notifications that
+require reliable delivery (input_required prompts, cancel signals).
+
+
+## Functions
+
+### `push_notification`
+
+```python
+push_notification(session_id: str, notification: dict[str, Any], docket: Docket) -> None
+```
+
+
+Push notification to session's queue (called from Docket worker).
+
+Used for elicitation-specific notifications (input_required, cancel)
+that need reliable delivery across distributed processes.
+
+**Args:**
+- `session_id`: Target session's identifier
+- `notification`: MCP notification dict (method, params, _meta)
+- `docket`: Docket instance for Redis access
+
+
+### `notification_subscriber_loop`
+
+```python
+notification_subscriber_loop(session_id: str, session: ServerSession, docket: Docket) -> None
+```
+
+
+Subscribe to notification queue and forward to session.
+
+Runs in the MCP server process. Bridges distributed workers to clients.
+
+This loop:
+1. Maintains a heartbeat (active subscriber marker for debugging)
+2. Blocks on BRPOP waiting for notifications
+3. Forwards notifications to the client's session
+4. Retries failed deliveries, then discards (no dead-letter queue)
+
+**Args:**
+- `session_id`: Session identifier to subscribe to
+- `session`: MCP ServerSession for sending notifications
+- `docket`: Docket instance for Redis access
+
+
+### `ensure_subscriber_running`
+
+```python
+ensure_subscriber_running(session_id: str, session: ServerSession, docket: Docket) -> None
+```
+
+
+Start notification subscriber if not already running (idempotent).
+
+Subscriber is created on first task submission and cleaned up on disconnect.
+Safe to call multiple times for the same session.
+
+**Args:**
+- `session_id`: Session identifier
+- `session`: MCP ServerSession
+- `docket`: Docket instance
+
+
+### `stop_subscriber`
+
+```python
+stop_subscriber(session_id: str) -> None
+```
+
+
+Stop notification subscriber for a session.
+
+Called when session disconnects. Pending messages remain in queue
+for delivery if client reconnects (with TTL expiration).
+
+**Args:**
+- `session_id`: Session identifier
+
+
+### `get_subscriber_count`
+
+```python
+get_subscriber_count() -> int
+```
+
+
+Get number of active subscribers (for monitoring).
+