Skip to content

Prefix Redis keys with docket name for ACL isolation (2.x backport)#2812

Merged
jlowin merged 1 commit intorelease/2.xfrom
prefix-redis-keys-docket-2x
Jan 9, 2026
Merged

Prefix Redis keys with docket name for ACL isolation (2.x backport)#2812
jlowin merged 1 commit intorelease/2.xfrom
prefix-redis-keys-docket-2x

Conversation

@chrisguidry
Copy link
Copy Markdown
Collaborator

Backport of #2811 for the 2.x release branch.

The Redis keys fastmcp uses directly (not via docket) weren't prefixed with the docket name, making ACL-based isolation impossible. Now uses docket.key() to prefix all task metadata keys consistently.

Keys now look like {docket_name}:fastmcp:task:{session_id}:{task_id} instead of fastmcp:task:{session_id}:{task_id}.

Bumps pydocket requirement to >=0.16.4 for the new key() method.

🤖 Generated with Claude Code

The Redis keys fastmcp uses directly (not via docket) weren't prefixed
with the docket name, making ACL-based isolation impossible. Now uses
docket.key() to prefix all task metadata keys consistently.

Bumps pydocket requirement to >=0.16.4 for the new key() method.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 31569f8ea9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +80 to +83
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"
)
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 👍 / 👎.

@marvin-context-protocol
Copy link
Copy Markdown
Contributor

Test Failure Analysis

Summary: The Windows test suite times out in after approximately 5 seconds.

Root Cause: Test timeout at - specifically around line 920 during OAuth proxy testing. The pytest timeout is set to 5 seconds globally (visible in test output: timeout: 5.0s), and this test is exceeding that limit on Windows.

Context: This PR changes how Redis keys are prefixed by using docket.key() to add the docket name prefix to all task metadata keys. While this change is correct for ACL isolation (the PR's stated goal), it appears the OAuth proxy tests may have some platform-specific behavior on Windows that causes them to run slower or hang.

Suggested Solution:

The test failure is in tests/server/auth/test_oauth_proxy.py, but this is likely NOT caused by this PR's changes. The PR only modifies task-related Redis key prefixing in src/fastmcp/server/tasks/ files, while the OAuth proxy timeout is occurring in authentication tests that don't directly use the task system.

Possible causes:

  1. Timing issue on Windows: Windows CI runners may be slower, and the OAuth proxy tests might need a longer timeout
  2. Unrelated flake: This could be a pre-existing flaky test that's surfacing during this PR's CI run
  3. Resource contention: The test may be waiting on a network operation (the mock OAuth provider) that's timing out on Windows

Recommended actions:

  1. Re-run the CI to see if this is a flaky test
  2. If it persists, investigate the specific test at around the OAuth token refresh flow (line 809-924)
  3. Consider marking slow OAuth tests with @pytest.mark.timeout(30) to override the global 5-second timeout on Windows
Detailed Analysis

Log excerpt showing timeout:

tests\server\auth\test_oauth_proxy.py [32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m[32m.[0m+++++++++++++++++++++++++++++++++++ Timeout +++++++++++++++++++++++++++++++++++

The test was running at approximately 35% completion when it timed out (running for ~2 minutes total, timing out after the 9th passing test in the file).

Files changed in this PR that affect Redis:

  • src/fastmcp/server/tasks/handlers.py - Now uses docket.key() to prefix task metadata keys
  • src/fastmcp/server/tasks/protocol.py - Uses prefixed keys when looking up tasks
  • src/fastmcp/server/tasks/subscriptions.py - Minor import path change

Key changes example:

# Before: fastmcp:task:{session_id}:{server_task_id}
# After:  {docket_name}:fastmcp:task:{session_id}:{server_task_id}

task_meta_key = docket.key(f"fastmcp:task:{session_id}:{server_task_id}")
Related Files

Files relevant to the failure:

  • tests/server/auth/test_oauth_proxy.py:754-924 - The test class that's timing out
  • src/fastmcp/server/auth/oauth_proxy.py - OAuth proxy implementation (not modified in this PR)

Files modified in this PR:

  • src/fastmcp/server/tasks/handlers.py - Task submission logic
  • src/fastmcp/server/tasks/protocol.py - Task protocol handlers
  • src/fastmcp/server/tasks/subscriptions.py - Task subscriptions
  • pyproject.toml - Bumped pydocket version to >=0.16.4 (for key() method)

@marvin-context-protocol
Copy link
Copy Markdown
Contributor

Test Failure Analysis

Summary: The Windows test suite times out in test_oauth_proxy.py after approximately 5 seconds.

Root Cause: Test timeout at tests/server/auth/test_oauth_proxy.py - specifically around line 920 during OAuth proxy testing. The pytest timeout is set to 5 seconds globally (visible in test output: timeout: 5.0s), and this test is exceeding that limit on Windows.

Context: This PR changes how Redis keys are prefixed by using docket.key() to add the docket name prefix to all task metadata keys. While this change is correct for ACL isolation (the PR's stated goal), it appears the OAuth proxy tests may have some platform-specific behavior on Windows that causes them to run slower or hang.

Suggested Solution:

The test failure is in tests/server/auth/test_oauth_proxy.py, but this is likely NOT caused by this PR's changes. The PR only modifies task-related Redis key prefixing in src/fastmcp/server/tasks/ files, while the OAuth proxy timeout is occurring in authentication tests that don't directly use the task system.

Possible causes:

  1. Timing issue on Windows: Windows CI runners may be slower, and the OAuth proxy tests might need a longer timeout
  2. Unrelated flake: This could be a pre-existing flaky test that's surfacing during this PR's CI run
  3. Resource contention: The test may be waiting on a network operation (the mock OAuth provider) that's timing out on Windows

Recommended actions:

  1. Re-run the CI to see if this is a flaky test
  2. If it persists, investigate the specific test at line 809-924 in the OAuth proxy test file
  3. Consider marking slow OAuth tests with @pytest.mark.timeout(30) to override the global 5-second timeout on Windows
Detailed Analysis

Log excerpt showing timeout:

tests\server\auth\test_oauth_proxy.py ........+++++++++++++++++++++++++++++++++++ Timeout ++++++++++++++++++++++++++++++++++

The test was running at approximately 35% completion when it timed out (running for ~2 minutes total, timing out after the 9th passing test in the file).

Files changed in this PR that affect Redis:

  • src/fastmcp/server/tasks/handlers.py - Now uses docket.key() to prefix task metadata keys
  • src/fastmcp/server/tasks/protocol.py - Uses prefixed keys when looking up tasks
  • src/fastmcp/server/tasks/subscriptions.py - Minor import path change

Key changes example:

# Before: fastmcp:task:{session_id}:{server_task_id}
# After:  {docket_name}:fastmcp:task:{session_id}:{server_task_id}

task_meta_key = docket.key(f"fastmcp:task:{session_id}:{server_task_id}")
Related Files

Files relevant to the failure:

  • tests/server/auth/test_oauth_proxy.py:754-924 - The test class that's timing out
  • src/fastmcp/server/auth/oauth_proxy.py - OAuth proxy implementation (not modified in this PR)

Files modified in this PR:

  • src/fastmcp/server/tasks/handlers.py - Task submission logic
  • src/fastmcp/server/tasks/protocol.py - Task protocol handlers
  • src/fastmcp/server/tasks/subscriptions.py - Task subscriptions
  • pyproject.toml - Bumped pydocket version to >=0.16.4 (for key() method)

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 8, 2026

Walkthrough

The pull request systematically refactors Redis key generation across three task-related modules. In handlers.py, protocol.py, and subscriptions.py, raw string Redis keys (such as redis_key = f"fastmcp:task:{session_id}:{client_task_id}") are replaced with docket-wrapped equivalents (task_meta_key = docket.key(...)). The changes affect task metadata lookups and created_at key retrievals in handlers for tool, prompt, and resource tasks, as well as in subscription notification methods. The underlying logic, TTL calculations, and error handling remain unchanged.

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description check ❓ Inconclusive The description clearly explains the change purpose, implementation details, and dependency bump, but omits several required checklist items from the template. Complete the Contributors and Review checklists by checking the relevant boxes to confirm testing, self-review, and workflow compliance.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: prefixing Redis keys with docket name for ACL isolation, and identifies this as a 2.x backport.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/fastmcp/server/tasks/subscriptions.py (1)

177-179: Consistent key prefixing applied.

The change mirrors the pattern in _send_status_notification above. Both functions now use docket.key() for the created_at key construction.

💡 Optional: Consider extracting the key pattern to reduce duplication

The key pattern f"fastmcp:task:{session_id}:{task_id}:created_at" appears in both functions. While the current implementation works correctly, you could optionally extract this to a helper function for consistency:

def _build_created_at_key(docket: Docket, session_id: str, task_id: str) -> str:
    return docket.key(f"fastmcp:task:{session_id}:{task_id}:created_at")

This would centralize the key format definition, though given the limited scope, the current approach is acceptable.

src/fastmcp/server/tasks/handlers.py (1)

292-301: Consistent key prefixing for resource tasks.

The changes complete the pattern across all three task handlers. All task metadata keys are now prefixed via docket.key().

💡 Optional: Consider extracting the key building pattern

The key pattern appears identically in all three handlers (lines 75-78, 186-189, 292-295). You could optionally extract this to a helper function:

def _build_task_redis_keys(
    docket: Docket, session_id: str, task_id: str
) -> tuple[str, str]:
    """Build Redis keys for task metadata storage."""
    task_meta_key = docket.key(f"fastmcp:task:{session_id}:{task_id}")
    created_at_key = docket.key(f"fastmcp:task:{session_id}:{task_id}:created_at")
    return task_meta_key, created_at_key

This would centralize the key format, though the current implementation is clear and works correctly.

src/fastmcp/server/tasks/protocol.py (1)

80-86: Consider migration strategy for in-flight tasks during deployment.

This PR changes the Redis key structure from fastmcp:task:... to {docket_name}:fastmcp:task:.... Tasks created before this deployment will not be accessible after the upgrade since their keys won't match the new prefixed format.

For production deployments, consider:

  • Documenting that in-flight tasks will be lost during the upgrade
  • Timing the deployment during low-traffic periods
  • Or implementing a temporary migration layer that checks both old and new key formats during a transition period

The ACL isolation benefit likely justifies this breaking change, but it's worth documenting the upgrade impact.

Also applies to: 181-183, 314-320

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae25527 and 31569f8.

⛔ Files ignored due to path filters (2)
  • pyproject.toml is excluded by none and included by none
  • uv.lock is excluded by !**/*.lock and included by none
📒 Files selected for processing (3)
  • src/fastmcp/server/tasks/handlers.py
  • src/fastmcp/server/tasks/protocol.py
  • src/fastmcp/server/tasks/subscriptions.py
🧰 Additional context used
📓 Path-based instructions (1)
src/fastmcp/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/fastmcp/**/*.py: Python ≥ 3.10 with full type annotations required
Prioritize readable, understandable code - clarity over cleverness. Avoid obfuscated or confusing patterns even if shorter
Follow existing patterns and maintain consistency in code implementation
Be intentional about re-exports - don't blindly re-export everything to parent namespaces. Core types defining a module's purpose should be exported. Specialized features can live in submodules. Only re-export to fastmcp.* for most fundamental types
Never use bare except - be specific with exception types

Files:

  • src/fastmcp/server/tasks/handlers.py
  • src/fastmcp/server/tasks/protocol.py
  • src/fastmcp/server/tasks/subscriptions.py
🧬 Code graph analysis (3)
src/fastmcp/server/tasks/handlers.py (4)
src/fastmcp/server/server.py (1)
  • docket (378-383)
src/fastmcp/resources/resource.py (1)
  • key (149-156)
src/fastmcp/resources/template.py (1)
  • key (221-228)
src/fastmcp/server/context.py (1)
  • session_id (360-409)
src/fastmcp/server/tasks/protocol.py (4)
src/fastmcp/server/server.py (1)
  • docket (378-383)
src/fastmcp/resources/resource.py (1)
  • key (149-156)
src/fastmcp/resources/template.py (1)
  • key (221-228)
src/fastmcp/server/context.py (1)
  • session_id (360-409)
src/fastmcp/server/tasks/subscriptions.py (2)
src/fastmcp/resources/resource.py (1)
  • key (149-156)
src/fastmcp/resources/template.py (1)
  • key (221-228)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: label-issue-or-pr
  • GitHub Check: Run tests: Python 3.13 on ubuntu-latest
  • GitHub Check: Run tests: Python 3.10 on windows-latest
  • GitHub Check: Run tests: Python 3.10 on ubuntu-latest
  • GitHub Check: Run tests with lowest-direct dependencies
🔇 Additional comments (7)
src/fastmcp/server/tasks/subscriptions.py (1)

104-106: LGTM - Consistent key prefixing for ACL isolation.

The change correctly wraps the Redis key with docket.key() to enable docket name prefixing. This aligns with the same pattern applied in handlers.py and protocol.py for task metadata keys.

src/fastmcp/server/tasks/handlers.py (2)

75-84: LGTM - Task metadata keys correctly prefixed.

The changes properly wrap both task_meta_key and created_at_key with docket.key(), ensuring they'll match the lookups in protocol.py. The TTL calculation and Redis storage semantics remain unchanged.


186-195: Consistent key prefixing for prompt tasks.

The changes mirror the pattern in handle_tool_as_task, correctly applying docket.key() wrapping to both metadata keys.

src/fastmcp/server/tasks/protocol.py (4)

80-86: Correct key prefixing for task status lookup.

The changes properly wrap both lookup keys with docket.key(), ensuring they match the prefixed keys stored by handlers.py. Both task_meta_key and created_at_key are needed for returning complete task status per SEP-1686.


181-183: Correct key prefixing for task result lookup.

The change correctly wraps the task metadata key lookup. Note that created_at_key is not needed here since tasks/result only requires the task execution key, not the creation timestamp.


314-320: Consistent key prefixing for task cancellation.

The changes correctly wrap both lookup keys with docket.key(), matching the pattern in tasks_get_handler. Both keys are needed since cancellation returns full task status including createdAt.


1-10: All concerns verified. The pydocket requirement is correctly updated to >=0.16.4 in pyproject.toml, and the key() method is available in pydocket 0.16.4+ as used throughout the task handlers and protocol code for prefixing Redis keys with the docket name for ACL isolation.

@jlowin jlowin added enhancement Improvement to existing functionality. For issues and smaller PR improvements. bug Something isn't working. Reports of errors, unexpected behavior, or broken functionality. and removed enhancement Improvement to existing functionality. For issues and smaller PR improvements. labels Jan 9, 2026
@jlowin jlowin merged commit 3df8826 into release/2.x Jan 9, 2026
15 of 18 checks passed
@jlowin jlowin deleted the prefix-redis-keys-docket-2x branch January 9, 2026 00:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working. Reports of errors, unexpected behavior, or broken functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants