Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ dependencies = [
"mcp>=1.24.0,<2.0",
"openapi-pydantic>=0.5.1",
"platformdirs>=4.0.0",
"pydocket>=0.16.3",
"pydocket>=0.16.4",
"rich>=13.9.4",
"cyclopts>=4.0.0",
"authlib>=1.6.5",
Expand Down
24 changes: 15 additions & 9 deletions src/fastmcp/server/tasks/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,15 @@ async def handle_tool_as_task(
tool = await server.get_tool(tool_name)

# Store task key mapping and creation timestamp in Redis for protocol handlers
redis_key = f"fastmcp:task:{session_id}:{server_task_id}"
created_at_key = f"fastmcp:task:{session_id}:{server_task_id}:created_at"
task_meta_key = docket.key(f"fastmcp:task:{session_id}:{server_task_id}")
created_at_key = docket.key(
f"fastmcp:task:{session_id}:{server_task_id}:created_at"
)
ttl_seconds = int(
docket.execution_ttl.total_seconds() + TASK_MAPPING_TTL_BUFFER_SECONDS
)
async with docket.redis() as redis:
await redis.set(redis_key, task_key, ex=ttl_seconds)
await redis.set(task_meta_key, task_key, ex=ttl_seconds)
await redis.set(created_at_key, created_at, ex=ttl_seconds)

# Send notifications/tasks/created per SEP-1686 (mandatory)
Expand Down Expand Up @@ -181,13 +183,15 @@ async def handle_prompt_as_task(
prompt = await server.get_prompt(prompt_name)

# Store task key mapping and creation timestamp in Redis for protocol handlers
redis_key = f"fastmcp:task:{session_id}:{server_task_id}"
created_at_key = f"fastmcp:task:{session_id}:{server_task_id}:created_at"
task_meta_key = docket.key(f"fastmcp:task:{session_id}:{server_task_id}")
created_at_key = docket.key(
f"fastmcp:task:{session_id}:{server_task_id}:created_at"
)
ttl_seconds = int(
docket.execution_ttl.total_seconds() + TASK_MAPPING_TTL_BUFFER_SECONDS
)
async with docket.redis() as redis:
await redis.set(redis_key, task_key, ex=ttl_seconds)
await redis.set(task_meta_key, task_key, ex=ttl_seconds)
await redis.set(created_at_key, created_at, ex=ttl_seconds)

# Send notifications/tasks/created per SEP-1686 (mandatory)
Expand Down Expand Up @@ -285,13 +289,15 @@ async def handle_resource_as_task(
task_key = build_task_key(session_id, server_task_id, "resource", str(uri))

# Store task key mapping and creation timestamp in Redis for protocol handlers
redis_key = f"fastmcp:task:{session_id}:{server_task_id}"
created_at_key = f"fastmcp:task:{session_id}:{server_task_id}:created_at"
task_meta_key = docket.key(f"fastmcp:task:{session_id}:{server_task_id}")
created_at_key = docket.key(
f"fastmcp:task:{session_id}:{server_task_id}:created_at"
)
ttl_seconds = int(
docket.execution_ttl.total_seconds() + TASK_MAPPING_TTL_BUFFER_SECONDS
)
async with docket.redis() as redis:
await redis.set(redis_key, task_key, ex=ttl_seconds)
await redis.set(task_meta_key, task_key, ex=ttl_seconds)
await redis.set(created_at_key, created_at, ex=ttl_seconds)

# Send notifications/tasks/created per SEP-1686 (mandatory)
Expand Down
20 changes: 12 additions & 8 deletions src/fastmcp/server/tasks/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,12 @@ async def tasks_get_handler(server: FastMCP, params: dict[str, Any]) -> GetTaskR
)

# Look up full task key and creation timestamp from Redis
redis_key = f"fastmcp:task:{session_id}:{client_task_id}"
created_at_key = f"fastmcp:task:{session_id}:{client_task_id}:created_at"
task_meta_key = docket.key(f"fastmcp:task:{session_id}:{client_task_id}")
created_at_key = docket.key(
f"fastmcp:task:{session_id}:{client_task_id}:created_at"
)
Comment on lines +80 to +83
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add fallback for legacy task metadata keys

Using docket.key(...) for task metadata lookups means tasks/get, tasks/result, and tasks/cancel will no longer find tasks created by pre-5935d0c instances because those were stored under the unprefixed fastmcp:task:... keys. During a rolling upgrade or immediately after deploying this change, any in-flight tasks created by older servers will surface as “task not found,” even though the execution still exists in Docket. Consider a migration or a fallback lookup to the legacy key to keep upgrades non-disruptive.

Useful? React with 👍 / 👎.

async with docket.redis() as redis:
task_key_bytes = await redis.get(redis_key)
task_key_bytes = await redis.get(task_meta_key)
created_at_bytes = await redis.get(created_at_key)

task_key = None if task_key_bytes is None else task_key_bytes.decode("utf-8")
Expand Down Expand Up @@ -176,9 +178,9 @@ async def tasks_result_handler(server: FastMCP, params: dict[str, Any]) -> Any:
)

# Look up full task key from Redis
redis_key = f"fastmcp:task:{session_id}:{client_task_id}"
task_meta_key = docket.key(f"fastmcp:task:{session_id}:{client_task_id}")
async with docket.redis() as redis:
task_key_bytes = await redis.get(redis_key)
task_key_bytes = await redis.get(task_meta_key)

task_key = None if task_key_bytes is None else task_key_bytes.decode("utf-8")

Expand Down Expand Up @@ -309,10 +311,12 @@ async def tasks_cancel_handler(
)

# Look up full task key and creation timestamp from Redis
redis_key = f"fastmcp:task:{session_id}:{client_task_id}"
created_at_key = f"fastmcp:task:{session_id}:{client_task_id}:created_at"
task_meta_key = docket.key(f"fastmcp:task:{session_id}:{client_task_id}")
created_at_key = docket.key(
f"fastmcp:task:{session_id}:{client_task_id}:created_at"
)
async with docket.redis() as redis:
task_key_bytes = await redis.get(redis_key)
task_key_bytes = await redis.get(task_meta_key)
created_at_bytes = await redis.get(created_at_key)

task_key = None if task_key_bytes is None else task_key_bytes.decode("utf-8")
Expand Down
6 changes: 2 additions & 4 deletions src/fastmcp/server/tasks/subscriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,7 @@ async def _send_status_notification(
key_parts = parse_task_key(task_key)
session_id = key_parts["session_id"]

# Retrieve createdAt timestamp from Redis
created_at_key = f"fastmcp:task:{session_id}:{task_id}:created_at"
created_at_key = docket.key(f"fastmcp:task:{session_id}:{task_id}:created_at")
async with docket.redis() as redis:
created_at_bytes = await redis.get(created_at_key)

Expand Down Expand Up @@ -175,8 +174,7 @@ async def _send_progress_notification(
key_parts = parse_task_key(task_key)
session_id = key_parts["session_id"]

# Retrieve createdAt timestamp from Redis
created_at_key = f"fastmcp:task:{session_id}:{task_id}:created_at"
created_at_key = docket.key(f"fastmcp:task:{session_id}:{task_id}:created_at")
async with docket.redis() as redis:
created_at_bytes = await redis.get(created_at_key)

Expand Down
8 changes: 4 additions & 4 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading