Consistent decorator-based MCP handler registration#2732
Conversation
Add read_resource() and get_prompt() decorators to LowLevelServer that handle task metadata extraction, contextvar management, and MCP format conversion. This enables consistent registration patterns across all MCP handlers. The SDK's call_tool decorator already supports CreateTaskResult, but read_resource and get_prompt do not. These custom decorators bridge that gap until the SDK adds native support.
WalkthroughThis change refactors resource reading and prompt handling in the server architecture by introducing new decorators on Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/fastmcp/server/context.py (2)
306-311: Consider using TypeError for invalid return type.The RuntimeError is appropriate here, but static analysis suggests TypeError might be more semantically correct since this is about receiving an unexpected type (CreateTaskResult when a regular result was expected). However, RuntimeError is also defensible since this represents an unexpected execution state.
🔎 Alternative using TypeError
result = await self.fastmcp.render_prompt(name, arguments) if isinstance(result, mcp.types.CreateTaskResult): - raise RuntimeError( + raise TypeError( "Unexpected CreateTaskResult: Context calls should not have task metadata" )
322-327: Consider using TypeError for invalid return type.Same as the get_prompt method above - TypeError might be more semantically accurate for an unexpected return type, though RuntimeError is also reasonable for this execution state error.
🔎 Alternative using TypeError
result = await self.fastmcp.read_resource(str(uri)) if isinstance(result, mcp.types.CreateTaskResult): - raise RuntimeError( + raise TypeError( "Unexpected CreateTaskResult: Context calls should not have task metadata" )
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/fastmcp/server/context.pysrc/fastmcp/server/low_level.pysrc/fastmcp/server/server.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/context.pysrc/fastmcp/server/low_level.pysrc/fastmcp/server/server.py
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.646Z
Learning: Applies to src/fastmcp/**/*.py : Follow existing patterns and maintain consistency in code implementation
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: .cursor/rules/core-mcp-objects.mdc:0-0
Timestamp: 2025-11-26T21:51:44.174Z
Learning: Review and update related Manager classes (ToolManager, ResourceManager, PromptManager) when modifying MCP object definitions
📚 Learning: 2025-11-26T21:51:44.174Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: .cursor/rules/core-mcp-objects.mdc:0-0
Timestamp: 2025-11-26T21:51:44.174Z
Learning: Review and update related Manager classes (ToolManager, ResourceManager, PromptManager) when modifying MCP object definitions
Applied to files:
src/fastmcp/server/context.pysrc/fastmcp/server/server.py
📚 Learning: 2025-12-25T15:53:07.646Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.646Z
Learning: Applies to src/fastmcp/**/*.py : Python ≥ 3.10 with full type annotations required
Applied to files:
src/fastmcp/server/low_level.pysrc/fastmcp/server/server.py
📚 Learning: 2025-12-25T15:53:07.646Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.646Z
Learning: Applies to src/fastmcp/**/*.py : 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
Applied to files:
src/fastmcp/server/server.py
🧬 Code graph analysis (3)
src/fastmcp/server/context.py (4)
src/fastmcp/server/low_level.py (3)
fastmcp(50-55)fastmcp(151-156)read_resource(209-278)src/fastmcp/server/server.py (3)
render_prompt(1291-1354)name(419-420)read_resource(1195-1289)src/fastmcp/prompts/prompt.py (1)
to_mcp_prompt_result(109-115)src/fastmcp/client/client.py (3)
read_resource(834-839)read_resource(842-849)read_resource(851-889)
src/fastmcp/server/low_level.py (3)
src/fastmcp/prompts/prompt.py (3)
Prompt(118-306)PromptResult(73-115)to_mcp_prompt_result(109-115)src/fastmcp/resources/resource.py (3)
Resource(137-315)ResourceContent(39-134)to_mcp_resource_contents(110-134)src/fastmcp/utilities/components.py (1)
make_key(80-91)
src/fastmcp/server/server.py (1)
src/fastmcp/server/low_level.py (2)
read_resource(209-278)get_prompt(280-347)
🪛 Ruff (0.14.10)
src/fastmcp/server/context.py
308-310: Prefer TypeError exception for invalid type
(TRY004)
308-310: Avoid specifying long messages outside the exception class
(TRY003)
324-326: Prefer TypeError exception for invalid type
(TRY004)
324-326: Avoid specifying long messages outside the exception class
(TRY003)
src/fastmcp/server/server.py
1554-1554: Avoid specifying long messages outside the exception class
(TRY003)
1573-1573: 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 windows-latest
- GitHub Check: Run tests: Python 3.13 on ubuntu-latest
- GitHub Check: Run tests: Python 3.10 on ubuntu-latest
- GitHub Check: Run tests with lowest-direct dependencies
🔇 Additional comments (5)
src/fastmcp/server/low_level.py (2)
209-278: LGTM! Well-structured decorator with proper resource management.The decorator correctly:
- Extracts task metadata from request context with appropriate error handling
- Manages contextvars with proper token-based cleanup in finally blocks
- Converts results to MCP format while preserving CreateTaskResult for task flows
- Uses type annotations consistently per coding guidelines
The implementation provides a clean bridge until the MCP SDK adds native CreateTaskResult support for resources.
280-347: LGTM! Consistent implementation with read_resource decorator.The decorator follows the same robust pattern as
read_resource():
- Task metadata extraction with graceful error handling
- Proper contextvar lifecycle management
- Correct result conversion while preserving CreateTaskResult
- Complete type annotations
The parallel structure between these two decorators makes the codebase more maintainable.
src/fastmcp/server/server.py (3)
607-624: LGTM! Clear documentation of the unified decorator approach.The updated docstring clearly explains:
- All handlers now use decorator-based registration
- The distinction between SDK decorators (call_tool) and temporary LowLevelServer decorators (read_resource, get_prompt)
- The temporary nature of the custom decorators pending SDK support
The implementation correctly applies the decorators to the handler methods.
1541-1557: LGTM! Simplified delegation to decorator-based handler.The method correctly:
- Delegates to the public
read_resource()API which flows through the decorator- Converts DisabledError to NotFoundError for consistent client-facing errors
- Re-raises NotFoundError directly (appropriate since it's already the correct error type)
- Returns CreateTaskResult when appropriate for task flows
The static analysis hint about message length can be safely ignored - the error message is appropriately concise.
1558-1575: LGTM! Consistent with read_resource handler.The method follows the same clean delegation pattern:
- Routes through the public
render_prompt()API and decorator- Appropriate error type conversion (DisabledError → NotFoundError)
- Direct re-raise of NotFoundError
- Supports CreateTaskResult for task-enabled prompts
The parallel structure with
_read_resource_mcpmakes the codebase easier to understand.
Handler registration was inconsistent: tools used the SDK's
call_tool()decorator while resources and prompts used manualrequest_handlers[]registration because the SDK's decorators don't supportCreateTaskResult.This PR adds
read_resource()andget_prompt()decorator methods toLowLevelServerthat handle task metadata extraction, contextvar management, and MCP format conversion. This enables consistent registration:The decorators handle MCP SDK concerns (task metadata, contextvars, result conversion) while the handler methods remain focused on FastMCP-specific logic (error translation). These custom decorators can be removed once the MCP SDK adds native
CreateTaskResultsupport for resources and prompts.