Skip to content

Enable background task support for custom component subclasses#2657

Merged
jlowin merged 1 commit intomainfrom
feature/docket-component-methods
Dec 20, 2025
Merged

Enable background task support for custom component subclasses#2657
jlowin merged 1 commit intomainfrom
feature/docket-component-methods

Conversation

@jlowin
Copy link
Copy Markdown
Member

@jlowin jlowin commented Dec 20, 2025

Custom Tool, Resource, ResourceTemplate, and Prompt subclasses can now support background task execution. Previously, only decorator-based components (@mcp.tool(task=True)) could use docket.

Each component now encapsulates its docket integration via two methods:

  • register_with_docket(docket) - Registers the component with docket, checking task_config internally
  • add_to_docket(docket, ...) - Handles component-specific calling conventions for scheduling execution

Custom subclasses enable task support by setting task_config:

from fastmcp.tools import Tool, ToolResult
from fastmcp.server.tasks import TaskConfig

class MyCustomTool(Tool):
    task_config = TaskConfig(mode="optional")

    async def run(self, arguments: dict) -> ToolResult:
        ...

The task_config field has been elevated to FastMCPComponent (default: mode="forbidden"), so all components inherit it.

Refactors docket/background task support to be encapsulated within each
component rather than requiring external coordination:

- Move task_config to FastMCPComponent base class (default: forbidden)
- Add register_with_docket(docket) method that components use to register
  themselves, checking task_config internally
- Add add_to_docket() method that handles component-specific calling
  conventions (splatted kwargs vs positional dict)
- Simplify server registration to just call component.register_with_docket()
- Update task handlers to use component.add_to_docket()

This enables custom Tool/Resource/Prompt subclasses to support background
tasks by setting task_config and optionally overriding the docket methods.
@marvin-context-protocol marvin-context-protocol Bot added enhancement Improvement to existing functionality. For issues and smaller PR improvements. server Related to FastMCP server implementation or server-side functionality. labels Dec 20, 2025
@jlowin jlowin force-pushed the feature/docket-component-methods branch from 4230ddd to 1a1a25a Compare December 20, 2025 02:39
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 20, 2025

Walkthrough

This PR introduces a uniform docket integration pattern across Tool, Resource, Prompt, and their Template variants by adding register_with_docket() and add_to_docket() methods to each component class. The task_config field is removed from function-based variants, with task configuration now handled during factory construction. Server-side registration logic is simplified to use these new methods uniformly rather than type-specific branches. A documentation section is added showing how custom component subclasses can enable background task support via task_config assignment.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description check ❓ Inconclusive The description is well-structured and covers the key changes, technical details, and includes a code example. However, it does not follow the provided template structure with all required checklist items checked. Complete the Contributors Checklist and Review Checklist by checking all applicable items, and confirm issue closure if applicable.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: enabling background task support for custom component subclasses through the docket integration.
Docstring Coverage ✅ Passed Docstring coverage is 95.12% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/docket-component-methods

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 (1)
src/fastmcp/resources/template.py (1)

235-239: Minor formatting: extra space in method call.

Line 239 has inconsistent spacing ( params) - there's an extra space after the opening parenthesis.

🔎 Proposed fix
     async def add_to_docket(  # type: ignore[override]
         self, docket: Docket, params: dict[str, Any], **kwargs: Any
     ) -> Execution:
         """Schedule this template for background execution via docket."""
-        return await docket.add(self.key, **kwargs)( params)
+        return await docket.add(self.key, **kwargs)(params)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3a6b1ba and 4230ddd.

⛔ Files ignored due to path filters (1)
  • tests/server/middleware/test_logging.py is excluded by none and included by none
📒 Files selected for processing (8)
  • docs/servers/tasks.mdx (1 hunks)
  • src/fastmcp/prompts/prompt.py (3 hunks)
  • src/fastmcp/resources/resource.py (3 hunks)
  • src/fastmcp/resources/template.py (3 hunks)
  • src/fastmcp/server/server.py (4 hunks)
  • src/fastmcp/server/tasks/handlers.py (3 hunks)
  • src/fastmcp/tools/tool.py (3 hunks)
  • src/fastmcp/utilities/components.py (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
docs/**/*.mdx

📄 CodeRabbit inference engine (docs/.cursor/rules/mintlify.mdc)

docs/**/*.mdx: Use clear, direct language appropriate for technical audiences
Write in second person ('you') for instructions and procedures in MDX documentation
Use active voice over passive voice in MDX technical documentation
Employ present tense for current states and future tense for outcomes in MDX documentation
Maintain consistent terminology throughout all MDX documentation
Keep sentences concise while providing necessary context in MDX documentation
Use parallel structure in lists, headings, and procedures in MDX documentation
Lead with the most important information using inverted pyramid structure in MDX documentation
Use progressive disclosure in MDX documentation: present basic concepts before advanced ones
Break complex procedures into numbered steps in MDX documentation
Include prerequisites and context before instructions in MDX documentation
Provide expected outcomes for each major step in MDX documentation
End sections with next steps or related information in MDX documentation
Use descriptive, keyword-rich headings for navigation and SEO in MDX documentation
Focus on user goals and outcomes rather than system features in MDX documentation
Anticipate common questions and address them proactively in MDX documentation
Include troubleshooting for likely failure points in MDX documentation
Provide multiple pathways (beginner vs advanced) but offer an opinionated path to avoid overwhelming users in MDX documentation
Always include complete, runnable code examples that users can copy and execute in MDX documentation
Show proper error handling and edge case management in MDX code examples
Use realistic data instead of placeholder values in MDX code examples
Include expected outputs and results for verification in MDX code examples
Test all code examples thoroughly before publishing in MDX documentation
Specify language and include filename when relevant in MDX code examples
Add explanatory comments for complex logic in MDX code examples
Document all API...

Files:

  • docs/servers/tasks.mdx
src/fastmcp/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/fastmcp/**/*.py: Write Python code with ≥3.10 type annotations required throughout
Never use bare except - be specific with exception types

Files:

  • src/fastmcp/server/server.py
  • src/fastmcp/utilities/components.py
  • src/fastmcp/server/tasks/handlers.py
  • src/fastmcp/tools/tool.py
  • src/fastmcp/resources/resource.py
  • src/fastmcp/resources/template.py
  • src/fastmcp/prompts/prompt.py
🧠 Learnings (1)
📚 Learning: 2025-12-17T03:06:14.522Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T03:06:14.522Z
Learning: Applies to src/fastmcp/**/*.py : Write Python code with ≥3.10 type annotations required throughout

Applied to files:

  • src/fastmcp/utilities/components.py
🧬 Code graph analysis (7)
src/fastmcp/server/server.py (4)
src/fastmcp/resources/resource.py (4)
  • Resource (137-294)
  • register_with_docket (284-288)
  • register_with_docket (382-390)
  • key (280-282)
src/fastmcp/resources/template.py (4)
  • ResourceTemplate (96-239)
  • register_with_docket (229-233)
  • register_with_docket (297-305)
  • key (225-227)
src/fastmcp/prompts/prompt.py (2)
  • register_with_docket (236-240)
  • register_with_docket (474-482)
src/fastmcp/tools/tool.py (2)
  • register_with_docket (243-247)
  • register_with_docket (430-438)
src/fastmcp/utilities/components.py (2)
src/fastmcp/server/tasks/config.py (1)
  • TaskConfig (19-89)
src/fastmcp/resources/resource.py (3)
  • register_with_docket (284-288)
  • register_with_docket (382-390)
  • add_to_docket (290-294)
src/fastmcp/server/tasks/handlers.py (7)
src/fastmcp/prompts/prompt.py (2)
  • add_to_docket (242-246)
  • add_to_docket (484-491)
src/fastmcp/resources/resource.py (2)
  • add_to_docket (290-294)
  • key (280-282)
src/fastmcp/resources/template.py (4)
  • add_to_docket (235-239)
  • add_to_docket (307-314)
  • key (225-227)
  • ResourceTemplate (96-239)
src/fastmcp/tools/tool.py (2)
  • add_to_docket (249-253)
  • add_to_docket (440-447)
src/fastmcp/utilities/components.py (2)
  • add_to_docket (133-153)
  • key (69-77)
src/fastmcp/server/context.py (1)
  • fastmcp (169-174)
src/fastmcp/server/low_level.py (2)
  • fastmcp (43-48)
  • fastmcp (144-149)
src/fastmcp/tools/tool.py (5)
src/fastmcp/server/server.py (2)
  • docket (389-394)
  • run (550-569)
src/fastmcp/prompts/prompt.py (4)
  • register_with_docket (236-240)
  • register_with_docket (474-482)
  • add_to_docket (242-246)
  • add_to_docket (484-491)
src/fastmcp/resources/resource.py (4)
  • register_with_docket (284-288)
  • register_with_docket (382-390)
  • key (280-282)
  • add_to_docket (290-294)
src/fastmcp/resources/template.py (5)
  • register_with_docket (229-233)
  • register_with_docket (297-305)
  • key (225-227)
  • add_to_docket (235-239)
  • add_to_docket (307-314)
src/fastmcp/utilities/components.py (3)
  • register_with_docket (125-131)
  • key (69-77)
  • add_to_docket (133-153)
src/fastmcp/resources/resource.py (5)
src/fastmcp/server/server.py (1)
  • docket (389-394)
src/fastmcp/resources/template.py (5)
  • register_with_docket (229-233)
  • register_with_docket (297-305)
  • read (173-177)
  • read (266-295)
  • key (225-227)
src/fastmcp/tools/tool.py (2)
  • register_with_docket (243-247)
  • register_with_docket (430-438)
src/fastmcp/utilities/components.py (2)
  • register_with_docket (125-131)
  • key (69-77)
src/fastmcp/server/proxy.py (1)
  • read (408-433)
src/fastmcp/resources/template.py (3)
src/fastmcp/prompts/prompt.py (2)
  • register_with_docket (236-240)
  • register_with_docket (474-482)
src/fastmcp/resources/resource.py (3)
  • register_with_docket (284-288)
  • register_with_docket (382-390)
  • key (280-282)
src/fastmcp/tools/tool.py (2)
  • register_with_docket (243-247)
  • register_with_docket (430-438)
src/fastmcp/prompts/prompt.py (6)
src/fastmcp/server/server.py (1)
  • docket (389-394)
src/fastmcp/resources/resource.py (4)
  • register_with_docket (284-288)
  • register_with_docket (382-390)
  • key (280-282)
  • add_to_docket (290-294)
src/fastmcp/resources/template.py (5)
  • register_with_docket (229-233)
  • register_with_docket (297-305)
  • key (225-227)
  • add_to_docket (235-239)
  • add_to_docket (307-314)
src/fastmcp/tools/tool.py (4)
  • register_with_docket (243-247)
  • register_with_docket (430-438)
  • add_to_docket (249-253)
  • add_to_docket (440-447)
src/fastmcp/utilities/components.py (3)
  • register_with_docket (125-131)
  • key (69-77)
  • add_to_docket (133-153)
src/fastmcp/server/proxy.py (1)
  • render (575-587)
🪛 Ruff (0.14.8)
src/fastmcp/utilities/components.py

134-134: Unused method argument: docket

(ARG002)


134-134: Unused method argument: args

(ARG002)


134-134: Unused method argument: kwargs

(ARG002)


147-150: Avoid specifying long messages outside the exception class

(TRY003)

⏰ 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). (4)
  • GitHub Check: Run tests: Python 3.10 on ubuntu-latest
  • GitHub Check: Run tests: Python 3.13 on ubuntu-latest
  • GitHub Check: Run tests: Python 3.10 on windows-latest
  • GitHub Check: Run tests with lowest-direct dependencies
🔇 Additional comments (20)
docs/servers/tasks.mdx (1)

224-241: LGTM! Clear documentation for custom component subclasses.

The new section effectively documents how to enable background task support for custom subclasses by setting task_config. The code example is clear and demonstrates the pattern correctly.

One minor consideration: the example shows task_config as a class attribute. This works because Pydantic will use it as a default value, but users should be aware this creates a shared default across all instances. Since TaskConfig is immutable, this is safe behavior.

src/fastmcp/utilities/components.py (2)

63-66: LGTM! Task config field with appropriate default.

The task_config field with mode="forbidden" as the default ensures backward compatibility - existing components won't suddenly support background tasks unless explicitly configured.

The use of default_factory=lambda: TaskConfig(mode="forbidden") is correct for Pydantic models to avoid shared mutable state issues.


125-153: LGTM! Base class docket integration methods are well-designed.

The two-method pattern provides clear extension points:

  • register_with_docket: No-op base that subclasses override to register their callable
  • add_to_docket: Guard against forbidden mode + NotImplementedError for unimplemented subclasses

The static analysis hints (ARG002, TRY003) are false positives here - this is intentionally a base class with unused parameters that subclasses will use.

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

103-104: LGTM! Clean refactoring to component-encapsulated docket scheduling.

The change from direct docket.add() calls to tool.add_to_docket() properly encapsulates the docket scheduling logic within each component, allowing FunctionTool to splat arguments while Tool passes them as a dict.


206-207: LGTM! Consistent pattern for prompt docket scheduling.

Same clean encapsulation pattern as tools - the prompt handles its own calling conventions internally.


307-314: LGTM! Proper handling of Resource vs ResourceTemplate.

The conditional correctly distinguishes between:

  • ResourceTemplate: requires params dict extracted from URI matching
  • Resource: no params needed, just key

The inline import of ResourceTemplate and match_uri_template avoids circular import issues.

src/fastmcp/tools/tool.py (2)

243-253: LGTM! Tool docket integration with correct calling convention.

The implementation correctly:

  • Guards registration when mode is "forbidden"
  • Registers self.run which expects arguments: dict[str, Any]
  • Passes arguments as a single positional arg in add_to_docket: docket.add(key)(arguments)

This matches the Tool.run(self, arguments: dict) signature.


430-447: LGTM! FunctionTool correctly splats arguments for the underlying function.

Key distinction from base Tool:

  • Registers self.fn (the user's function with Depends parameters for docket DI)
  • Splats arguments: docket.add(key)(**arguments) since fn expects keyword arguments

The docstring clearly explains why FunctionTool differs from Tool. The # type: ignore[override] is appropriate since the method signature intentionally differs.

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

419-431: LGTM! Unified docket registration is cleaner and more maintainable.

The refactoring from type-specific registration branches to a uniform component.register_with_docket(docket) call:

  1. Simplifies the code significantly
  2. Each component encapsulates its own registration logic and task_config check
  3. Makes it easy to add new component types without modifying this code

The comment accurately describes the behavior: components check task_config internally and no-op if forbidden.


434-444: LGTM! Provider component registration follows the same unified pattern.

Consistent with local component registration - providers' task-enabled components use the same register_with_docket() API.


629-633: LGTM! Resource task routing correctly delegates to handler.

The routing logic properly extracts task metadata and delegates to handle_resource_as_task, which will use the component's add_to_docket() method internally.


1455-1460: LGTM! Tool task routing with clear comments.

The comment correctly notes that ProxyTool has mode="forbidden" and won't enter this branch. The delegation to handle_tool_as_task maintains clean separation of concerns.

src/fastmcp/prompts/prompt.py (3)

9-15: LGTM!

The TYPE_CHECKING guard pattern for Docket and Execution imports is correctly implemented, avoiding runtime import overhead while maintaining type hints. This is consistent with the pattern used in other component files (resource.py, template.py).


236-246: LGTM!

The register_with_docket and add_to_docket implementations for Prompt are consistent with the established pattern in Tool and Resource classes. The base Prompt correctly registers self.render (the high-level method) rather than the underlying function, and the add_to_docket signature properly accepts arguments: dict[str, Any] | None matching the prompt's render interface.


473-491: LGTM!

The FunctionPrompt implementation correctly:

  1. Registers self.fn (the wrapped user function) instead of self.render to preserve Depends parameters for docket resolution
  2. Splats arguments via **(arguments or {}) since the underlying function expects keyword arguments
  3. Uses # type: ignore[arg-type] appropriately for the callable type mismatch

This pattern is consistent with FunctionTool (tool.py lines 439-446) and FunctionResource implementations.

src/fastmcp/resources/template.py (2)

8-15: LGTM!

TYPE_CHECKING guard correctly implemented for Docket and Execution imports, consistent with the pattern across all component files.


297-314: LGTM!

The FunctionResourceTemplate implementation correctly:

  1. Registers self.fn to preserve Depends parameters for docket resolution
  2. Splats params via **params since the underlying function expects keyword arguments
  3. Provides clear docstrings explaining the difference from base ResourceTemplate

This is consistent with the established pattern in FunctionTool, FunctionPrompt, and FunctionResource.

src/fastmcp/resources/resource.py (3)

9-15: LGTM!

TYPE_CHECKING guard correctly implemented for Docket and Execution imports, maintaining consistency across all component files.


284-294: LGTM!

The Resource docket integration correctly:

  1. Registers self.read (the public read method) with the resource's URI as the key
  2. Calls docket.add(self.key, **kwargs)() with no arguments since Resource.read() takes no parameters

This is consistent with the pattern established in other component base classes.


381-390: LGTM!

The FunctionResource.register_with_docket correctly registers self.fn (the wrapped user function) to preserve Depends parameters for docket resolution, consistent with other function-based component implementations.

Note: FunctionResource appropriately inherits add_to_docket from Resource since both read() and the wrapped fn take no arguments—no override is needed.

@jlowin jlowin requested a review from chrisguidry December 20, 2025 12:46
@jlowin jlowin merged commit 9f486e5 into main Dec 20, 2025
12 checks passed
@jlowin jlowin deleted the feature/docket-component-methods branch December 20, 2025 12:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Improvement to existing functionality. For issues and smaller PR improvements. server Related to FastMCP server implementation or server-side functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant