Skip to content

Consistent decorator-based MCP handler registration#2732

Merged
jlowin merged 1 commit intomainfrom
refactor-mcp-handlers
Dec 25, 2025
Merged

Consistent decorator-based MCP handler registration#2732
jlowin merged 1 commit intomainfrom
refactor-mcp-handlers

Conversation

@jlowin
Copy link
Copy Markdown
Member

@jlowin jlowin commented Dec 25, 2025

Handler registration was inconsistent: tools used the SDK's call_tool() decorator while resources and prompts used manual request_handlers[] registration because the SDK's decorators don't support CreateTaskResult.

This PR adds read_resource() and get_prompt() decorator methods to LowLevelServer that handle task metadata extraction, contextvar management, and MCP format conversion. This enables consistent registration:

self._mcp_server.call_tool(validate_input=...)(self._call_tool_mcp)
self._mcp_server.read_resource()(self._read_resource_mcp)
self._mcp_server.get_prompt()(self._get_prompt_mcp)

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 CreateTaskResult support for resources and prompts.

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.
@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 25, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 25, 2025

Walkthrough

This change refactors resource reading and prompt handling in the server architecture by introducing new decorators on LowLevelServer (read_resource and get_prompt) that manage task metadata and context variables while supporting CreateTaskResult returns. The Server class now delegates request handling through these decorators instead of manual request handlers. The Context class was updated to call through FastMCP methods (render_prompt and read_resource) rather than direct MCP calls, with appropriate handling for CreateTaskResult returns.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: introducing consistent decorator-based MCP handler registration across tools, resources, and prompts.
Description check ✅ Passed The description provides clear context, explains the problem solved, shows the solution with code examples, and mentions the temporary nature of the decorators. However, the Contributors Checklist items (issue reference, testing, etc.) are not marked.
✨ 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 refactor-mcp-handlers

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.

@jlowin jlowin added the v3 Targeted for FastMCP 3 label Dec 25, 2025
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 (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

📥 Commits

Reviewing files that changed from the base of the PR and between 9f72096 and 1b098b6.

📒 Files selected for processing (3)
  • src/fastmcp/server/context.py
  • src/fastmcp/server/low_level.py
  • src/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.py
  • src/fastmcp/server/low_level.py
  • src/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.py
  • src/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.py
  • src/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_mcp makes the codebase easier to understand.

@jlowin jlowin merged commit 9f5a177 into main Dec 25, 2025
14 checks passed
@jlowin jlowin deleted the refactor-mcp-handlers branch December 25, 2025 22:48
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. v3 Targeted for FastMCP 3

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant